module L4dServer class SystemdManager SYSTEMD_USER_DIR = File.expand_path("~/.config/systemd/user").freeze def self.start(server) new(server).start end def self.stop(server) new(server).stop end def self.restart(server) new(server).restart end def self.status(server) new(server).status end def self.cleanup(server) new(server).cleanup end def initialize(server) @server = server end def start execute("systemctl --user start #{unit_name}") end def stop execute("systemctl --user stop #{unit_name}") end def restart execute("systemctl --user restart #{unit_name}") end def status output = `systemctl --user is-active #{unit_name} 2>&1` output.strip rescue StandardError => e Rails.logger.error("Failed to get status: #{e.message}") "unknown" end def cleanup begin stop execute("systemctl --user disable #{unit_name}") execute("systemctl --user daemon-reload") File.delete(unit_file_path) if File.exist?(unit_file_path) rescue StandardError => e Rails.logger.error("Failed to cleanup systemd unit: #{e.message}") end end def create_unit_file FileUtils.mkdir_p(SYSTEMD_USER_DIR) unless Dir.exist?(SYSTEMD_USER_DIR) File.write(unit_file_path, render_unit) @server.update(unit_file_path: unit_file_path) execute("systemctl --user daemon-reload") end def enable_and_start execute("systemctl --user enable #{unit_name}") start end private def unit_name "left4dead2-#{@server.id}.service" end def unit_file_path File.join(SYSTEMD_USER_DIR, unit_name) end def render_unit template = @server.server_template overlays_str = template.template_overlays.ordered.map { |to| "/opt/l4d2/overlays/#{to.overlay.name}" }.join(":") startup_args = template.startup_params.map { |param| "#{param.param_key} #{param.param_value}" }.join(" ") ExecStart = "/opt/l4d2/servers/#{@server.id}/merged/srcds_run -norestart -pidfile /opt/l4d2/servers/#{@server.id}/pid -game left4dead2 -ip 0.0.0.0 -port #{@server.port} +hostname \"#{@server.name}\" +map c1m1_hotel #{startup_args}".strip <<~UNIT [Unit] Description=L4D2 Server #{@server.name} After=network-online.target Wants=network-online.target [Service] Type=simple User=steam ExecStart=/opt/l4d2/bin/start-server #{@server.id} ExecStop=/opt/l4d2/bin/stop-server #{@server.id} Restart=on-failure RestartSec=10 Nice=-10 [Install] WantedBy=default.target UNIT end def execute(command) output = `#{command} 2>&1` raise "Command failed: #{command}\n#{output}" unless $?.success? output rescue StandardError => e Rails.logger.error("Systemd command failed: #{e.message}") raise end end end