summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorelijah <elijah@riseup.net>2013-05-17 23:08:53 -0700
committerelijah <elijah@riseup.net>2013-05-17 23:08:53 -0700
commit49c9e2f095fdc9cb815490c8e5afd5453be5fbf5 (patch)
tree9174074c041cbb4667c7b5cf33634f67738a38c8
parent51dc03481a9be5148f16e8022a1b00b658739ff3 (diff)
rolled custom daemon code to better match the way daemons are supposed to work under debian.
-rw-r--r--README.md23
-rwxr-xr-xbin/nickserver37
-rw-r--r--config/default.yml3
-rw-r--r--lib/nickserver.rb2
-rw-r--r--lib/nickserver/config.rb9
-rw-r--r--lib/nickserver/daemon.rb260
-rw-r--r--lib/nickserver/version.rb2
-rw-r--r--nickserver.gemspec1
8 files changed, 282 insertions, 55 deletions
diff --git a/README.md b/README.md
index bc3a408..1546d57 100644
--- a/README.md
+++ b/README.md
@@ -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