diff options
author | elijah <elijah@riseup.net> | 2013-05-17 23:08:53 -0700 |
---|---|---|
committer | elijah <elijah@riseup.net> | 2013-05-17 23:08:53 -0700 |
commit | 49c9e2f095fdc9cb815490c8e5afd5453be5fbf5 (patch) | |
tree | 9174074c041cbb4667c7b5cf33634f67738a38c8 | |
parent | 51dc03481a9be5148f16e8022a1b00b658739ff3 (diff) |
rolled custom daemon code to better match the way daemons are supposed to work under debian.
-rw-r--r-- | README.md | 23 | ||||
-rwxr-xr-x | bin/nickserver | 37 | ||||
-rw-r--r-- | config/default.yml | 3 | ||||
-rw-r--r-- | lib/nickserver.rb | 2 | ||||
-rw-r--r-- | lib/nickserver/config.rb | 9 | ||||
-rw-r--r-- | lib/nickserver/daemon.rb | 260 | ||||
-rw-r--r-- | lib/nickserver/version.rb | 2 | ||||
-rw-r--r-- | nickserver.gemspec | 1 |
8 files changed, 282 insertions, 55 deletions
@@ -27,8 +27,8 @@ For more details, see https://leap.se/nicknym Requirements ================================== -Ruby 1.9 -CouchDB +* Ruby 1.9 +* CouchDB Installation ================================== @@ -59,23 +59,14 @@ The default HKP host is set to https://hkps.pool.sks-keyservers.net. The CA for Usage ================================== - Usage: nickserver <command> <options> -- <application options> + Usage: nickserver [OPTION] COMMAND - * where <command> is one of: + where COMMAND is one of: start start an instance of the application stop stop all instances of the application restart stop all instances and restart them afterwards - reload send a SIGHUP to all instances of the application - run start the application and stay on top - zap set the application to a stopped state status show status (PID) of application instances + version print version and exit - * and where <options> may contain several of the following: - - -t, --ontop Stay on top (does not daemonize) - -f, --force Force operation - -n, --no_wait Do not wait for processes to stop - - Common options: - -h, --help Show this message - --version Show version + where OPTION is one of: + --verbose log more
\ No newline at end of file diff --git a/bin/nickserver b/bin/nickserver index b0cabac..de4a818 100755 --- a/bin/nickserver +++ b/bin/nickserver @@ -1,4 +1,5 @@ #!/usr/bin/ruby + # # Nickserver key discovery daemon # @@ -19,40 +20,8 @@ rescue LoadError end end -# -# Handle craceful Ctrl-C -# -Signal.trap("SIGINT") do - puts "\nQuit" - exit -end - -# -# Handle '--version' ourselves -# -if ARGV.grep(/--version/).any? - puts "nickserver #{Nickserver::VERSION}, ruby #{RUBY_VERSION}" - exit(0) -end - -# -# Start the daemon -# -require 'daemons' - -options = {:app_name => 'nickserver', :multiple => false, :backtrace => true} - -# pick where the pid file should go (must be readable/writable by current user) -if File.writable?('/var/run/nickserver') - options.merge!(:dir_mode => :normal, :dir => '/var/run/nickserver') -elsif ENV["USER"] == "root" - options.merge!(:dir_mode => :system) -else - options.merge!(:dir_mode => :normal, :dir => '/tmp') -end - -Daemons.run_proc('nickserver', options) do +Nickserver::Daemon.run do EventMachine.run do Nickserver::Server.start end -end +end
\ No newline at end of file diff --git a/config/default.yml b/config/default.yml index 83bfa48..4328cbd 100644 --- a/config/default.yml +++ b/config/default.yml @@ -5,3 +5,6 @@ couch_database: 'users' #couch_password: 'blahblah' hkp_url: 'https://hkps.pool.sks-keyservers.net:/pks/lookup' port: 6425 +pid_file: '/var/run/nickserver' +user: 'nobody' +log_file: '/var/log/nickserver.log' diff --git a/lib/nickserver.rb b/lib/nickserver.rb index f97a95f..9e6464e 100644 --- a/lib/nickserver.rb +++ b/lib/nickserver.rb @@ -9,4 +9,4 @@ require "nickserver/hkp/fetch_key_info" require "nickserver/hkp/fetch_key" require "nickserver/server" - +require "nickserver/daemon" diff --git a/lib/nickserver/config.rb b/lib/nickserver/config.rb index 3f92186..b283d8b 100644 --- a/lib/nickserver/config.rb +++ b/lib/nickserver/config.rb @@ -15,7 +15,12 @@ module Nickserver attr_accessor :couch_user attr_accessor :couch_password attr_accessor :port + attr_accessor :pid_file + attr_accessor :user + attr_accessor :log_file + attr_accessor :loaded + attr_accessor :verbose end def self.load @@ -39,9 +44,9 @@ module Nickserver exit(1) end end - puts "Loaded #{file_path}" + puts "Loaded #{file_path}" if Config.verbose rescue Errno::ENOENT => exc - puts "Skipping #{file_path}" + puts "Skipping #{file_path}" if Config.verbose rescue Exception => exc STDERR.puts exc.inspect exit(1) diff --git a/lib/nickserver/daemon.rb b/lib/nickserver/daemon.rb new file mode 100644 index 0000000..9eb2ff5 --- /dev/null +++ b/lib/nickserver/daemon.rb @@ -0,0 +1,260 @@ +require 'etc' +require 'fileutils' + +# +# A simple daemon, in a Debian style. Adapted from gem Dante. +# + +module Nickserver + class Daemon + + def self.run(&block) + self.new.run(&block) + end + + def run(&block) + parse_options + Config.load + send("command_#{@command}", &block) + end + + private + + MAX_WAIT = 2 + + # + # PERMISSIONS + # + + # + # see http://timetobleed.com/5-things-you-dont-know-about-user-ids-that-will-destroy-you/ + # (hint: it is easy to get it wrong) + # + def drop_permissions_to(username) + if username != 'root' + if Process::Sys.getuid == 0 + Process::Sys.setuid(Etc.getpwnam(username).uid) + if root? + bail "failed to drop permissions" + end + else + bail "cannot change process uid to #{username}" + end + end + end + + def root? + begin + Process::Sys.setuid(0) + rescue Errno::EPERM + false + else + true + end + end + + # + # PROCESS STUFF + # + + def daemonize + return bail("Process is already started") if daemon_running? + pid = fork do + exit if fork + Process.setsid + exit if fork + create_pid_file(Config.pid_file, Config.user) + catch_interrupt + redirect_output + drop_permissions_to(Config.user) + File.umask 0000 + yield + end + + if until_true { daemon_running? } + puts "Daemon has started successfully" + exit(0) + else # Failed to start + puts "Daemon couldn't be started" + exit(1) + end + end + + def create_pid_file(file, user) + File.open file, 'w' do |f| + f.write("#{Process.pid}\n") + end + FileUtils.chown(user, nil, file) + rescue Errno::EACCES + bail "insufficient permission to create to pid file `#{file}`" + rescue Errno::ENOENT + bail "bad path for pid file `#{file}`" + rescue Errno::EROFS + bail "can't create pid file `#{file}` on read-only filesystem" + end + + def daemon_running? + return false unless File.exist?(Config.pid_file) + Process.kill 0, File.read(Config.pid_file).to_i + true + rescue Errno::ESRCH + false + end + + def pid_from_file(file) + pid = IO.read(file).chomp + if pid != "" + pid.to_i + else + nil + end + end + + def kill_pid + file = Config.pid_file + if File.exists?(file) + pid = pid_from_file(file) + if pid + Process.kill('TERM', pid) + puts "Stopped #{pid}" + else + bail "Error reading pid file #{file}" + end + begin + FileUtils.rm Config.pid_file + rescue Errno::EACCES + bail 'insufficient permission to remove pid file' + end + else + bail "could not find pid file #{file}" + end + rescue => e + puts "Failed to stop: #{e}" + end + + # + # Gracefully handle Ctrl-C + # + def catch_interrupt + Signal.trap("SIGINT") do + command_stop + $stdout.puts "\nQuit" + $stdout.flush + exit + end + end + + + # + # OUTPUT + # + + def usage(msg) + puts msg + puts + puts "Usage: nickserver [OPTION] COMMAND" + puts "COMMAND is one of: start, stop, restart, status, version" + puts "OPTION is one of: --verbose" + puts + exit 1 + end + + def bail(msg) + puts "Nickserver ERROR: #{msg}." + puts "Bailing out." + exit(1) + end + + # + # Redirect output based on log settings (reopens stdout/stderr to specified logfile) + # If log_path is nil, redirect to /dev/null to quiet output + # + def redirect_output + if log_path = Config.log_file + FileUtils.mkdir_p File.dirname(log_path), :mode => 0755 + FileUtils.touch log_path + File.chmod(0644, log_path) + $stdout.reopen(log_path, 'a') + $stderr.reopen $stdout + $stdout.sync = true + else + # redirect to /dev/null + $stdin.reopen '/dev/null' + $stdout.reopen '/dev/null', 'a' + $stderr.reopen $stdout + end + rescue Errno::EACCES + bail "no permission to create log file #{log_path}" + end + + # + # UTILITY + # + + # + # Runs until the block condition is met or the timeout_seconds is exceeded + # until_true(10) { ...return_condition... } + # + def until_true(timeout_seconds=MAX_WAIT, &block) + elapsed_seconds = 0 + interval = 0.5 + while elapsed_seconds < timeout_seconds && block.call != true + elapsed_seconds += interval + sleep(interval) + end + elapsed_seconds < timeout_seconds + end + + def parse_options + loop do + case ARGV[0] + when 'start' then ARGV.shift; @command = :start + when 'stop' then ARGV.shift; @command = :stop + when 'restart' then ARGV.shift; @command = :restart + when 'status' then ARGV.shift; @command = :status + when 'version' then ARGV.shift; @command = :version + when '--verbose' then ARGV.shift; Config.versbose = true + when /^-/ then usage("Unknown option: #{ARGV[0].inspect}") + else break + end + end + usage("Missing command") unless @command + end + + # + # COMMANDS + # + + def command_version + puts "nickserver #{Nickserver::VERSION}, ruby #{RUBY_VERSION}" + exit(0) + end + + def command_start(&block) + daemonize(&block) + end + + def command_stop + if daemon_running? + kill_pid + until_true { !daemon_running? } + else + puts "No processes are running" + end + end + + def command_restart(&block) + command_stop + command_start(&block) + end + + def command_status + if daemon_running? + puts "Process id #{pid_from_file(Config.pid_file)}" + else + puts 'Not running' + end + end + + end +end diff --git a/lib/nickserver/version.rb b/lib/nickserver/version.rb index 799ca6c..6e86d39 100644 --- a/lib/nickserver/version.rb +++ b/lib/nickserver/version.rb @@ -1,3 +1,3 @@ module Nickserver - VERSION = "0.0.1" + VERSION = "0.2.0" end diff --git a/nickserver.gemspec b/nickserver.gemspec index 2c87585..5d0d807 100644 --- a/nickserver.gemspec +++ b/nickserver.gemspec @@ -24,5 +24,4 @@ Gem::Specification.new do |gem| gem.add_dependency 'eventmachine' gem.add_dependency 'em-http-request' gem.add_dependency 'eventmachine_httpserver' - gem.add_dependency 'daemons' end |