Initial working prototype
authorSteven McDonald <steven@vader.steven-mcdonald.id.au>
Sat, 13 Oct 2012 02:57:01 +0000 (13:57 +1100)
committerSteven McDonald <steven@vader.steven-mcdonald.id.au>
Sat, 13 Oct 2012 02:57:01 +0000 (13:57 +1100)
bin/aids [new file with mode: 0755]
lib/aids.rb [new file with mode: 0644]

diff --git a/bin/aids b/bin/aids
new file mode 100755 (executable)
index 0000000..7b3fa50
--- /dev/null
+++ b/bin/aids
@@ -0,0 +1,48 @@
+#!/usr/bin/ruby1.8
+
+require 'lib/aids'
+
+def main(argv)
+       svc = AIDS::Service.new(argv[1]) if argv[1]
+       case argv[0]
+               when 'enable'
+                       usage unless svc
+                       svc.enable!
+               when 'disable'
+                       usage unless svc
+                       svc.disable!
+               when 'list'
+                       if svc
+                               puts svc.status
+                       else
+                               AIDS.get_all_services.sort_by{|s|s.name}.each do |s|
+                                       puts "#{s.name}: #{s.status}"
+                               end
+                       end
+               else
+                       usage
+       end
+end
+
+def usage
+       $stderr.puts <<-EOF
+AIDS - Assistant for Initialisation of Debian Services
+
+Usage: #{$0} command [service]
+
+Command may be one of:
+
+  - enable
+      Enables the service.
+
+  - disable
+      Disables the service.
+
+  - list
+      Lists the current status of the service, or of all services if
+      none is provided.
+       EOF
+       exit 1
+end
+
+main(ARGV) if $0 == __FILE__
diff --git a/lib/aids.rb b/lib/aids.rb
new file mode 100644 (file)
index 0000000..c2a7e66
--- /dev/null
@@ -0,0 +1,115 @@
+module AIDS
+       RUNLEVEL_SINGLE          = 'S'
+       RUNLEVEL_MULTI_TO_SINGLE = '1'
+       RUNLEVEL_MULTI           = '2'
+       RUNLEVEL_HALT            = '0'
+       RUNLEVEL_REBOOT          = '6'
+
+       class Service
+               attr_reader :name
+
+               def initialize(name)
+                       if name.nil? or name.empty? or not name.is_a?(String)
+                               raise Exception.new("Service name must be a non-empty string, got #{name.inspect}.")
+                       end
+                       name = $1 if name =~ %r{^/etc/init\.d/(.+)$}
+                       if name =~ /[^[:alnum:]\-.]/
+                               raise Exception.new("Invalid init script name: #{name}.")
+                       end
+                       unless File.exist?("/etc/init.d/#{name}")
+                               raise Exception.new("Unknown service: #{name}.")
+                       end
+                       @name = name
+               end
+
+               def enable!
+                       start_on_runlevel!(RUNLEVEL_MULTI)
+               end
+
+               def disable!
+                       stop_on_runlevel!(RUNLEVEL_MULTI)
+               end
+
+               def enabled?
+                       started_on_runlevel?(RUNLEVEL_MULTI)
+               end
+
+               def status
+                       if started_on_runlevel?(RUNLEVEL_MULTI)
+                               :enabled
+                       elsif stopped_on_runlevel?(RUNLEVEL_MULTI)
+                               :disabled
+                       else
+                               :unknown
+                       end
+               end
+
+               private
+
+               def started_on_runlevel?(runlevel)
+                       AIDS.validate_runlevel(runlevel)
+                       not Dir.glob("/etc/rc#{runlevel}.d/S[0-9][0-9]#{@name}").empty?
+               end
+
+               def stopped_on_runlevel?(runlevel)
+                       AIDS.validate_runlevel(runlevel)
+                       not Dir.glob("/etc/rc#{runlevel}.d/K[0-9][0-9]#{@name}").empty?
+               end
+
+               def start_on_runlevel!(runlevel)
+                       AIDS.validate_runlevel(runlevel)
+                       return true if started_on_runlevel?(runlevel)
+                       updatercd(:enable, runlevel)
+               end
+
+               def stop_on_runlevel!(runlevel)
+                       AIDS.validate_runlevel(runlevel)
+                       return true if stopped_on_runlevel?(runlevel)
+                       updatercd(:disable, runlevel)
+               end
+
+               def set_default_runlevels!
+                       updatercd(:remove)
+                       updatercd(:defaults)
+               end
+
+               def updatercd(action, runlevel=nil)
+                       unless [:enable, :disable, :remove, :defaults].include?(action)
+                               raise Exception.new("Invalid action for updatercd: #{action}.")
+                       end
+                       AIDS.validate_runlevel(runlevel) if runlevel
+                       # update-rc.d will baulk at being told to do anything with
+                       # these runlevels.
+                       if ['0', '1', '6'].include?(runlevel.to_s)
+                               raise Exception.new("Unable to comply: update-rc.d is balls.")
+                       end
+                       pid = Process.fork do
+                               $stdout.close
+                               $stderr.close
+                               Kernel.exec(
+                                       '/usr/sbin/update-rc.d',
+                                       @name,
+                                       action.to_s,
+                                       runlevel.to_s
+                               )
+                       end
+                       Process.wait(pid)
+                       if (rc = $?.exitstatus) != 0
+                               raise Exception.new("update-rc.d returned #{rc}")
+                       end
+               end
+       end
+
+       def self.get_all_services
+               Dir.glob("/etc/init.d/*").map do |f|
+                       next unless File.executable?(f)
+                       Service.new(f)
+               end.compact
+       end
+
+       def self.validate_runlevel(runlevel)
+               unless ['0', '1', '2', '3', '4', '5', '6', 'S'].include?(runlevel.to_s)
+                       raise Exception.new("Invalid runlevel: #{runlevel}.")
+               end
+       end
+end