Class: Trinidad::InitServices::Configuration

Inherits:
Object
  • Object
show all
Defined in:
lib/trinidad_init_services/configuration.rb

Constant Summary collapse

SERVICE_DESC =
'JRuby on Rails/Rack server'
MEMORY_DEFAULT =
720

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(stdin = STDIN, stdout = STDOUT) ⇒ Configuration

Returns a new instance of Configuration.



19
20
21
# File 'lib/trinidad_init_services/configuration.rb', line 19

def initialize(stdin = STDIN, stdout = STDOUT)
  @stdin, @stdout = stdin, stdout
end

Class Method Details

.macosx?Boolean

Returns:

  • (Boolean)


15
16
17
# File 'lib/trinidad_init_services/configuration.rb', line 15

def self.macosx?
  RbConfig::CONFIG['host_os'] =~ /darwin/i
end

.windows?Boolean

Returns:

  • (Boolean)


11
12
13
# File 'lib/trinidad_init_services/configuration.rb', line 11

def self.windows?
  RbConfig::CONFIG['host_os'] =~ /mswin|mingw/i
end

Instance Method Details

#add_java_opt(java_opt, opt_suffix = nil) ⇒ Object



177
178
179
180
181
182
183
184
185
# File 'lib/trinidad_init_services/configuration.rb', line 177

def add_java_opt(java_opt, opt_suffix = nil)
  if @java_opts.is_a?(String)
    return false if @java_opts.index(java_opt)
    @java_opts << ( windows? ? ';' : ' ' ) unless @java_opts.strip.empty?
  else
    return false if @java_opts.find { |opt| opt.index(java_opt) }
  end
  @java_opts << "#{java_opt}#{opt_suffix}"
end

#ask(question, default = nil) ⇒ Object



619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
# File 'lib/trinidad_init_services/configuration.rb', line 619

def ask(question, default = nil)
  return default if ! @stdin.tty? || ! ask?

  question = "#{question}?" if ! question.index('?') || ! question.index(':')
  question += " [#{default}]" if default &&
    ( ! default.is_a?(String) || ! default.empty? )

  result = nil
  while result.nil?
    @stdout.print("#{question}  ")
    @stdout.flush

    result = @stdin.gets

    if result
      result.chomp!
      result = default if result.size == 0
    end
  end
  result
end

#ask=(flag) ⇒ Object



645
# File 'lib/trinidad_init_services/configuration.rb', line 645

def ask=(flag); @ask = !!flag end

#ask?Boolean

Returns:

  • (Boolean)


641
642
643
# File 'lib/trinidad_init_services/configuration.rb', line 641

def ask?
  @ask = true unless defined? @ask; return @ask
end

#ask_path(question, default = nil) ⇒ Object



611
612
613
614
615
616
617
# File 'lib/trinidad_init_services/configuration.rb', line 611

def ask_path(question, default = nil)
  unless path = ask(question, default) # nil, false
    return path if path.nil?
    block_given? ? yield : raise("#{question.inspect} not provided!")
  end
  path.empty? ? path : File.expand_path(path)
end

#configure(defaults = {}) ⇒ Object



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/trinidad_init_services/configuration.rb', line 25

def configure(defaults = {})
  if ( @app_path = defaults["app_path"] ).nil?
    unless @base_path = defaults["base_path"]
      @app_path = ask_path('Application (base - in case of multiple apps) path', false) do
        raise "application/base path not provided (try . if current directory is the app)"
      end
    end
  end

  options_ask = 'Trinidad options?'

  @service_id = defaults['service_id'] || defaults['trinidad_service_id']
  @service_name = defaults['service_name'] || defaults['trinidad_name']
  @service_desc = defaults['service_desc'] || defaults['trinidad_service_desc']

  if windows?
    options_ask << ' (separated by `;`)'

    @service_id ||= ask('Service ID? {alphanumeric and underscores only}', default_service_id)
    name_default = @service_id.gsub('_', ' ')
    @service_name ||= ask('Service (display) name? {alphanumeric and spaces only}', name_default)
    @service_desc ||= ask('Service description? {alphanumeric and spaces only}', SERVICE_DESC)
  else
    @service_id ||= default_service_id
    @service_name ||= @service_id
    @service_desc ||= SERVICE_DESC
  end

  @trinidad_opts = defaults["trinidad_options"] || defaults["trinidad_opts"]

  if @trinidad_opts.is_a?(String) # leave 'em as are
    if @app_path && ! @trinidad_opts.index('-d') && ! @trinidad_opts.index('--dir')
      @trinidad_opts = "--dir #{@app_path} #{@trinidad_opts}"
    end
  elsif @trinidad_opts
    if @app_path && ! @trinidad_opts.find { |opt| opt.index('-d') || opt.index('--dir') }
      @trinidad_opts.unshift("--dir #{@app_path}")
    end
  else
    @trinidad_opts = [ ask(options_ask, '-e production') ]
  end

  if @trinidad_opts.is_a?(Array)
    @trinidad_opts.map! { |opt| Shellwords.shellsplit(opt) }
    @trinidad_opts.flatten! # split: 'opt' -> [ 'opt' ]
  end

  @jruby_home = defaults['jruby_home'] || ask_path('JRuby home', default_jruby_home)
  @ruby_compat_version = defaults["ruby_compat_version"] || default_ruby_compat_version
  @jruby_opts = configure_jruby_opts(@jruby_home, @ruby_compat_version)
  initialize_paths(@jruby_home)

  @java_home = defaults['java_home'] || ask_path('Java home', default_java_home)

  @java_opts = defaults['java_opts'] || []
  @java_opts = @java_opts.strip if @java_opts.is_a?(String)

  # can be disabled with *configure_memory: false*
  configure_memory_requirements(defaults, @java_home)

  message = windows? ?
    configure_windows_service(defaults, @java_home) :
      configure_unix_daemon(defaults, @java_home)
  say message if message.is_a?(String)
end

#configure_jruby_opts(jruby_home = @jruby_home, ruby_compat_version = @ruby_compat_version) ⇒ Object



187
188
189
190
191
192
193
194
195
# File 'lib/trinidad_init_services/configuration.rb', line 187

def configure_jruby_opts(jruby_home = @jruby_home, ruby_compat_version = @ruby_compat_version)
  opts = []
  opts << "-Djruby.home=#{jruby_home}"
  opts << "-Djruby.lib=#{File.join(jruby_home, 'lib')}"
  opts << "-Djruby.script=jruby"
  opts << "-Djruby.daemon.module.name=Trinidad"
  opts << "-Djruby.compat.version=#{ruby_compat_version}"
  opts
end

#configure_memory_requirements(defaults, java_home) ⇒ Object



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/trinidad_init_services/configuration.rb', line 93

def configure_memory_requirements(defaults, java_home)
  return if defaults.key?('configure_memory') && ! defaults['configure_memory']

  if defaults['configure_memory'] || ask('Configure JVM memory (JAVA_OPTS)? y/n', 'n') == 'y'

    total_memory = defaults['total_memory'] ||
      ask('Total (max) memory dedicated to Trinidad? (in MB)', MEMORY_DEFAULT)
    total_memory = total_memory.to_i
    if total_memory <= 0
      warn "changing total_memory to '#{MEMORY_DEFAULT}' default (provided value <= 0)"
      total_memory = MEMORY_DEFAULT
    end
    if total_memory <= 160
      warn "provided total_memory '#{total_memory}' seems low (server migh not start)"
    end

    if current_java_home?(java_home) && current_java_vendor_sun_or_oracle?
      # 720 total memory: (Max) 144M PermGen, 72M CodeCache, 504M Heap

      add_java_opt('-XX:+UseCodeCacheFlushing')
      cache_size = total_memory >= 800 ? 80 : ( total_memory / 10 )
      cache_size = 100 if total_memory >= 2000
      cache_size = 120 if total_memory >= 3000
      cache_size = 140 if total_memory >= 4000
      add_java_opt('-XX:ReservedCodeCacheSize=', "#{cache_size}m")

      heap_size = total_memory - cache_size

      if ! defaults.key?('hot_deployment') || ! defaults['hot_deployment']
        hot_deploy = ask('Support hot (re-)deployment? y/n', 'n') == 'y'
      else
        hot_deploy = true
      end

      if hot_deploy && current_java_version_6?
        # on Java 7 G1 sweeps PermGen on full GC
        add_java_opt('-XX:+UseConcMarkSweepGC') if hot_deploy
        add_java_opt('-XX:+CMSClassUnloadingEnabled') if hot_deploy
      end

      if current_java_version_at_least_8?
        # probably a good idea to limit meta-space size :
        meta_size = heap_size / 5 # 20% (unlimited by default)
        meta_size = min(heap_size / 4, meta_size + 100) if hot_deploy

        unless defaults['total_memory'] # do not ask if configured
          meta = ask('Confirm meta-space size limit: (-XX:MaxMetaspaceSize in MB)', meta_size)
          meta_size = parse_memory_setting(meta, meta_size)
        end

        add_java_opt('-XX:MaxMetaspaceSize=', "#{meta_size}m") if meta_size
        heap_size -= meta_size.to_i
      else
        perm_size = heap_size / 5 # 20%
        perm_size = min(heap_size / 4, perm_size + 100) if hot_deploy

        unless defaults['total_memory'] # do not ask if configured
          perm = ask('Confirm perm-gen size limit: (-XX:MaxPermSize in MB)', perm_size)
          perm_size = parse_memory_setting(perm, perm_size)
        end

        add_java_opt('-XX:MaxPermSize=', "#{perm_size}m") if perm_size
        heap_size -= perm_size.to_i
      end

      heap_size = ( heap_size / 10 ) * 10
      add_java_opt('-Xmx', "#{heap_size}m")
      min_heap_size = min(heap_size / 2, 500)
      add_java_opt('-Xms', "#{min_heap_size}m")

    else # only try to limit heap (vendors such as IBM support it) :

      heap_size = total_memory - ( total_memory / 15 ) # just a guess

      heap_size = ( heap_size / 10 ) * 10
      add_java_opt('-Xmx', "#{heap_size}m")

    end

    add_java_opt('-XX:+UseCompressedOops') if current_java_version_6? && os_arch =~ /64/

  end
end

#configure_unix_daemon(defaults, java_home = default_java_home) ⇒ Object



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/trinidad_init_services/configuration.rb', line 205

def configure_unix_daemon(defaults, java_home = default_java_home)
  unless @jsvc = defaults["jsvc_path"] || detect_jsvc_path
    @jsvc = ask_path("path to jsvc binary (leave blank and we'll try to compile)", '')
    if @jsvc.empty? # unpack and compile :
      jsvc_unpack_dir = defaults["jsvc_unpack_dir"] || ask_path("dir where jsvc dist should be unpacked", '/usr/local/src')
      @jsvc = compile_jsvc(jsvc_unpack_dir, java_home)
      say "jsvc binary available at: #{@jsvc} " +
           "(consider adding it to $PATH if you plan to re-run trinidad_init_service)"
    end
  end

  @pid_file = defaults['pid_file'] || ask_path('pid file', '/var/run/trinidad/trinidad.pid')
  @out_file = defaults['out_file'] || defaults['log_file'] ||
    ask_path('out file (where system out/err gets redirected)', '/var/log/trinidad/trinidad.out')

  @pid_file = File.join(@pid_file, 'trinidad.pid') if File.exist?(@pid_file) && File.directory?(@pid_file)
  make_path_dir(@pid_file, "could not create dir for '#{@pid_file}', make sure dir exists before running daemon")
  @out_file = File.join(@out_file, 'trinidad.out') if File.exist?(@out_file) && File.directory?(@out_file)
  make_path_dir(@out_file, "could not create dir for '#{@out_file}', make sure dir exists before running daemon")

  @run_user = defaults['run_user'] || ask('run daemon as user (enter a non-root username or leave blank)', '')
  if ! @run_user.empty? && `id -u #{@run_user}` == ''
    raise ArgumentError, "user '#{@run_user}' does not exist (leave blank if you're planning to `useradd' later)"
  end

  @output_path = defaults['output_path'] || ask_path('init.d output path', '/etc/init.d')

  require('erb'); daemon = ERB.new(
    File.read(
      File.expand_path('../../init.d/trinidad.erb', File.dirname(__FILE__))
    ), nil, '-' # safe_level=nil, trim_mode=nil
  ).result(binding)

  service_file = File.join(@output_path, @service_id ||= 'trinidad')
  begin
    File.open(service_file, 'w') { |file| file.write(daemon) }
  rescue Errno::EACCES => e
    begin
      service_file = File.basename(service_file) # leave in current WD
      service_file = File.expand_path(service_file)
      File.open(service_file, 'w') { |file| file.write(daemon) }
      warn "#{e.message} left init.d script at #{service_file}"
    rescue
      raise e
    end
  end
  FileUtils.chmod @run_user.empty? ? 0744 : 0755, service_file

  if chkconfig?
    command = "chkconfig #{@service_id} on"
  else
    command = "update-rc.d -f #{@service_id} remove"
  end
  if service_file.start_with?('/etc')
    unless exec_system(command, :allow_failure)
      warn "\nNOTE: run `#{command}` as a super-used to enable service"
    end
  else
    warn "\nNOTE: run `cp #{service_file} /etc/init.d` and `#{command}` as a super-used to enable service"
  end
end

#configure_windows_service(defaults, java_home = default_java_home) ⇒ Object



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'lib/trinidad_init_services/configuration.rb', line 267

def configure_windows_service(defaults, java_home = default_java_home)
  srv_path = detect_prunsrv_path

  classpath = escape_windows_options(@classpath)
  trinidad_options = escape_windows_options(@trinidad_opts, :split)

  jvm_options = escape_windows_options(@jruby_opts)
  unless @java_opts.empty?
    jvm_options << ';' << escape_windows_options(@java_opts)
  end

  log_path = defaults['log_path'] || "%SystemRoot%\\System32\\LogFiles\\#{@service_id}"
  @out_file = defaults['out_file'] || defaults['log_file'] ||
    ask_path('out file (where system out/err gets redirected), leave blank for prunsrv default', '')
  @pid_file = defaults['pid_file'] || "#{@service_id}.pid"

  #stop_timeout = defaults['stop_timeout'] || 5

  # //TS  Run the service as a console application
  #       This is the default operation (if no option is provided).
  # //RS  Run the service 	Called only from ServiceManager
  # //ES  Start (execute) the service
  # //SS  Stop the service
  # //US  Update service parameters
  # //IS  Install service
  # //DS  Delete service 	Stops the service first if it is currently running
  # //PP[//seconds]  Pause 	Default is 60 seconds

  if service_listed_windows?(@service_id)
    say "service '#{@service_id}' already installed, will update instead of install"
    command = %Q{//US//#{@service_id} --DisplayName="#{@service_name}"}
  else
    command = %Q{//IS//#{@service_id} --DisplayName="#{@service_name}"}
  end

  command << " --Description=\"#{@service_desc}\""
  command << " --Install=#{srv_path} --Jvm=auto"
  command << " --JavaHome=\"#{escape_windows_path(java_home)}\""
  command << " --StartMode=jvm --StopMode=jvm"
  command << " --StartClass=com.msp.procrun.JRubyService --StartMethod=start"
  command << " --StartParams=\"#{escape_windows_path(@trinidad_daemon_path)};#{trinidad_options}\""
  command << " --StopClass=com.msp.procrun.JRubyService --StopMethod=stop"
  command << " --Classpath=\"#{classpath}\""
  command << " ++JvmOptions=\"#{jvm_options}\""
  command << " --LogPath=\"#{escape_windows_path(log_path)}\""
  command << " --PidFile=#{@pid_file}" # always assumed log_path relative

  if @out_file && ! @out_file.empty?
    out_file = escape_windows_path(@out_file)
    command << " --StdOutput=\"#{out_file}\" --StdError=\"#{out_file}\""
  else
    command << " --StdOutput=auto --StdError=auto"
  end

  exec_system "#{srv_path} #{command}"

  warn "\nNOTE: service needs to be started manually, to start during boot run:\n" <<
    "#{srv_path} //US//#{@service_id} --Startup=auto"

  "\nHINT: you may use prunsrv to manage your service, try running:\n#{srv_path} help"
end

#initialize_paths(jruby_home = default_jruby_home) ⇒ Object



197
198
199
200
201
202
203
# File 'lib/trinidad_init_services/configuration.rb', line 197

def initialize_paths(jruby_home = default_jruby_home)
  @trinidad_daemon_path = File.expand_path('../../trinidad/daemon.rb', __FILE__)
  @jars_path = File.expand_path('../../../trinidad-libs', __FILE__)

  @classpath = ['jruby-jsvc.jar', 'commons-daemon.jar'].map { |jar| File.join(@jars_path, jar) }
  @classpath << File.join(jruby_home, 'lib', 'jruby.jar')
end

#say(msg) ⇒ Object



648
649
650
# File 'lib/trinidad_init_services/configuration.rb', line 648

def say(msg)
  puts msg if say?
end

#say=(flag) ⇒ Object



656
# File 'lib/trinidad_init_services/configuration.rb', line 656

def say=(flag); @say = !!flag end

#say?Boolean

Returns:

  • (Boolean)


652
653
654
# File 'lib/trinidad_init_services/configuration.rb', line 652

def say?
  @say = true unless defined? @say; return @say
end

#service_listed_unix?(service) ⇒ Boolean

Returns:

  • (Boolean)


372
373
374
375
376
377
378
# File 'lib/trinidad_init_services/configuration.rb', line 372

def service_listed_unix?(service)
  if chkconfig?
    ! `chkconfig --list | grep #{service}`.chomp.empty?
  else
    ! `service --status-all | grep #{service}`.chomp.empty?
  end
end

#service_listed_windows?(service) ⇒ Boolean

Returns:

  • (Boolean)


340
341
342
343
344
345
346
# File 'lib/trinidad_init_services/configuration.rb', line 340

def service_listed_windows?(service)
  # *sc* will allow SERVICE_NAME only (DISPLAY_NAME won't work)
  out = `sc queryex type= service state= all | find "#{service}"`
  return false if out.chomp.empty?
  # "SERVICE_NAME: Trinidad\nDISPLAY_NAME: Trinidad\n"
  !! ( out =~ /SERVICE_NAME: #{service}$/i )
end

#uninstall(service = nil) ⇒ Object



329
330
331
332
333
# File 'lib/trinidad_init_services/configuration.rb', line 329

def uninstall(service = nil)
  initialize_paths
  service ||= default_service_id
  windows? ? uninstall_windows_service(service) : uninstall_unix_daemon(service)
end

#uninstall_unix_daemon(service) ⇒ Object



348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
# File 'lib/trinidad_init_services/configuration.rb', line 348

def uninstall_unix_daemon(service)
  unless File.exist?(service)
    service = File.expand_path(service, '/etc/init.d')
  end
  name = File.basename(service) # e.g. /etc/init.d/trinidad

  unless service_listed_unix?(service)
    warn "service '#{service}' seems to be NOT installed/configured"
  end

  if chkconfig?
    exec_system command = "chkconfig #{name} stop", :allow_failure
    exec_system command = "chkconfig #{name} off"
  else # assuming Debian based
    exec_system command = "service #{name} stop", :allow_failure
    exec_system command = "update-rc.d -f #{name} remove"
  end
rescue => e
  say "uninstall failed, maybe try running `#{command}` as super-user"
  raise e
else
  FileUtils.rm(service) if File.exist?(service)
end

#uninstall_windows_service(service) ⇒ Object



335
336
337
338
# File 'lib/trinidad_init_services/configuration.rb', line 335

def uninstall_windows_service(service)
  srv_path = detect_prunsrv_path
  exec_system "#{srv_path} //DS//#{service}" # does stop first if needed
end