diff --git a/spec/service_spec.cr b/spec/service_spec.cr index bdbea2e..3cf6dc2 100644 --- a/spec/service_spec.cr +++ b/spec/service_spec.cr @@ -18,16 +18,16 @@ describe Service do {% for sysinit in %w(OpenRC Systemd) %} describe {{sysinit}} do + Service.init = Service::{{sysinit.id}} test_prefix = Prefix.new(__DIR__ + "/service_test", create: true) test_app = test_prefix.new_app(TEST_APP_PACKAGE_NAME) FileUtils.cp_r __DIR__ + "/samples/" + TEST_APP_PACKAGE_NAME, test_app.path - Service.init = Service::{{sysinit.id}} service_config = Service::{{sysinit.id}}::Config.new it "creates a service" do - Service::{{sysinit.id}}.new(TEST_APP_PACKAGE_NAME).create(test_app, user, group) + test_app.service_create user, group end it "parses the service" do diff --git a/src/manager/application/add.cr b/src/manager/application/add.cr index 3e31d60..85474a6 100644 --- a/src/manager/application/add.cr +++ b/src/manager/application/add.cr @@ -193,11 +193,11 @@ struct Manager::Application::Add # Create system user and group for the application if Process.root? - @app.service?.try do |service| + if @app.service? # Create system services - service.create @app, @user, @group - service.enable @app - Log.info service.type + " system service added", service.name + @app.service_create @user, @group + @app.service_enable + Log.info @app.service.type + " system service added", @app.service.name end libcrown = Libcrown.new diff --git a/src/prefix/app.cr b/src/prefix/app.cr index 7ac03f5..c01f403 100644 --- a/src/prefix/app.cr +++ b/src/prefix/app.cr @@ -21,8 +21,14 @@ struct Prefix::App Pkg.new @prefix, File.basename(File.dirname(File.real_path(app_path))), nil, @pkg_file end - getter service : Service::OpenRC | Service::Systemd do - Service.init.new @name + getter? service : Service::OpenRC | Service::Systemd | Nil + + def service : Service::OpenRC | Service::Systemd + if _service = @service + _service + else + raise "service not available" + end end getter service_dir : String do @@ -33,10 +39,47 @@ struct Prefix::App service_dir + service.type end - def service? : Service::OpenRC | Service::Systemd | Nil - if Service.init? - @service ||= service + def service_create(user : String, group : String) + _service = service + (exec = pkg_file.exec) || raise "exec key not present in #{pkg_file.path}" + + Dir.mkdir_p service_dir + + Log.info "creating system service", @name + + # Set service options + {description: pkg_file.description, + directory: path, + command: path + exec["start"], + user: user, + group: group, + restart_delay: "9", + umask: "007", + log_output: log_file_output, + log_error: log_file_error, + }.each do |key, value| + _service.config.set key.to_s, value + end + + # add a reload directive if available + if exec_reload = exec["reload"]? + _service.config.set("reload", exec_reload) end + + # Add a PATH environment variable if not empty + if !(path_vars = env_vars).empty? + _service.config.env_set("PATH", path_vars) + end + if pkg_env = pkg_file.env + pkg_env.each { |var, value| _service.config.env_set var, value } + end + + # Convert back hashes to service files + File.write service_file, _service.config.build + end + + def service_enable + service.link service_file end def database? : Database::MySQL | Nil @@ -77,6 +120,9 @@ struct Prefix::App @logs_dir = @path + "log/" @log_file_output = @logs_dir + "output.log" @log_file_error = @logs_dir + "error.log" + Service.init?.try do |_service| + @service = _service.new @name + end end def set_config(key : String, value) diff --git a/src/service/init_system.cr b/src/service/init_system.cr new file mode 100644 index 0000000..e9d8055 --- /dev/null +++ b/src/service/init_system.cr @@ -0,0 +1,58 @@ +module Service::InitSystem + getter name : String, + file : String, + boot_file : String + + def type : String + self.class.type + end + + def boot? : Bool + File.exists? @boot_file + end + + def exists? : Bool + File.symlink?(@file) || File.exists?(@file) + end + + def writable? : Bool + File.writable? @file + end + + def boot(value : Bool) : Bool + case value + when boot? # nothing to do + when true then File.symlink @file, @boot_file + when false then File.delete boot_file + end + value + end + + def real_file : String + File.real_path file + end + + private def delete_internal + stop if run? + boot false if boot? + File.delete @file if exists? + end + + def check_availability + if exists? + raise "system service already exist: " + name + elsif !File.writable? File.dirname(@file) + Log.warn "service creation unavailable, root permissions required", name + else + Log.info "service available for creation", name + end + end + + def check_delete + if !writable? + raise "root execution needed for system service deletion: " + name + elsif !exists? + raise "service doesn't exist: " + name + end + end +end diff --git a/src/service/openrc.cr b/src/service/openrc.cr index 8e4d484..cc252bb 100644 --- a/src/service/openrc.cr +++ b/src/service/openrc.cr @@ -1,9 +1,17 @@ -require "./system" +require "./init_system" struct Service::OpenRC - include System + include InitSystem class_getter type : String = "openrc" + class_getter version : String do + output, error = Exec.new "/sbin/openrc", {"-V"}, &.wait + output.to_s =~ /([0-9]+\.[0-9]+\.[0-9]+)/ + $1.not_nil! + rescue + raise "can't retrieve the OpenRC version: #{output}#{error}" + end + getter config : Config do Config.new @file end @@ -27,8 +35,8 @@ struct Service::OpenRC delete_internal end - def enable(app : Prefix::App) - File.symlink app.service_file, @file + def link(service_file : String) + File.symlink service_file, @file File.chmod @file, 0o750 end @@ -37,15 +45,6 @@ struct Service::OpenRC Service.exec? "/sbin/rc-service", {@name, {{action}}} end {% end %} - - def self.version : String - output, error = Exec.new "/sbin/openrc", {"-V"}, &.wait - if output.to_s =~ /([0-9]+\.[0-9]+\.[0-9]+)/ - $1 - else - raise "can't retrieve the OpenRC version: #{output}#{error}" - end - end end require "./openrc/*" diff --git a/src/service/openrc/config.cr b/src/service/openrc/config.cr index 2138f95..58c43d7 100644 --- a/src/service/openrc/config.cr +++ b/src/service/openrc/config.cr @@ -4,7 +4,7 @@ struct Service::OpenRC::Config def initialize(@section : Hash(String, String | Array(String) | Hash(String, Array(String)))) end - def self.new(file : String? = nil) + def self.new(file : String? = nil) : Config if file && File.exists? file parse File.read(file) else diff --git a/src/service/system.cr b/src/service/system.cr deleted file mode 100644 index b1f1b1a..0000000 --- a/src/service/system.cr +++ /dev/null @@ -1,96 +0,0 @@ -module Service::System - getter name : String, - file : String, - boot_file : String - - def type : String - self.class.type - end - - def boot? : Bool - File.exists? @boot_file - end - - def exists? : Bool - File.symlink?(@file) || File.exists?(@file) - end - - def writable? : Bool - File.writable? @file - end - - def boot(value : Bool) : Bool - case value - when boot? # nothing to do - when true then File.symlink @file, @boot_file - when false then File.delete boot_file - end - value - end - - def real_file : String - File.real_path file - end - - private def delete_internal - stop if run? - boot false if boot? - File.delete @file if exists? - end - - def check_availability - if exists? - raise "system service already exist: " + name - elsif !File.writable? File.dirname(@file) - Log.warn "service creation unavailable, root permissions required", name - else - Log.info "service available for creation", name - end - end - - def check_delete - if !writable? - raise "root execution needed for system service deletion: " + name - elsif !exists? - raise "service doesn't exist: " + name - end - end - - def create(app : Prefix::App, user : String, group : String) - sysinit_hash = app.service.config - (exec = app.pkg_file.exec) || raise "exec key not present in #{app.pkg_file.path}" - - Dir.mkdir_p app.service_dir - - Log.info "creating system service", name - - # Set service options - {description: app.pkg_file.description, - directory: app.path, - command: app.path + exec["start"], - user: user, - group: group, - restart_delay: "9", - umask: "007", - log_output: app.log_file_output, - log_error: app.log_file_error, - }.each do |key, value| - sysinit_hash.set key.to_s, value - end - - # add a reload directive if available - if exec_reload = exec["reload"]? - sysinit_hash.set("reload", exec_reload) - end - - # Add a PATH environment variable if not empty - path = app.env_vars - sysinit_hash.env_set("PATH", path) if !path.empty? - if pkg_env = app.pkg_file.env - pkg_env.each { |var, value| sysinit_hash.env_set var, value } - end - - # Convert back hashes to service files - File.write app.service_file, sysinit_hash.build - end -end diff --git a/src/service/systemd.cr b/src/service/systemd.cr index 983254a..eacc235 100644 --- a/src/service/systemd.cr +++ b/src/service/systemd.cr @@ -1,9 +1,16 @@ -require "./system" +require "./init_system" struct Service::Systemd - include System + include InitSystem class_getter type : String = "systemd" + class_getter version : Int32 do + output, error = Exec.new "/bin/systemctl", {"--version"}, &.wait + output.to_s.lines[0].lstrip("systemd ").to_i + rescue + raise "can't retrieve the systemd version: #{output}#{error}" + end + getter config : Config do Config.new @file end @@ -32,8 +39,8 @@ struct Service::Systemd Service.exec? "/bin/systemctl", {"--no-ask-password", "daemon-reload"} end - def enable(app : Prefix::App) : Bool - File.symlink app.service_file, @file + def link(service_file : String) + File.symlink service_file, @file Service.exec? "/bin/systemctl", {"--no-ask-password", "daemon-reload"} end @@ -42,15 +49,6 @@ struct Service::Systemd Service.exec? "/bin/systemctl", {"-q", "--no-ask-password", {{action}}, @name} end {% end %} - - def self.version : Int32 - output, error = Exec.new "/bin/systemctl", {"--version"}, &.wait - if output.to_s =~ / ([0-9]+)\n/ - $1.to_i - else - raise "can't retrieve the systemd version: #{output}#{error}" - end - end end require "./systemd/*" diff --git a/src/service/systemd/config.cr b/src/service/systemd/config.cr index 3808839..2c1483b 100644 --- a/src/service/systemd/config.cr +++ b/src/service/systemd/config.cr @@ -4,7 +4,7 @@ struct Service::Systemd::Config def initialize(@section : Hash(String, Hash(String, String))) end - def self.new(file : String? = nil) + def self.new(file : String? = nil) : Config if file && File.exists? file parse File.read(file) else