diff options
| -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  | 
