#!/usr/bin/env ruby require "sinatra/base" require "dotenv" require "slim" module RS APP_ENV = ENV.fetch("REMOTE_SYSTEMCTL_ENV") { "development" } LOADED_ENV = Dotenv.load(".env.#{APP_ENV}.local", ".env.local", ".env") PASSWORDS = LOADED_ENV.filter { |k, _| k =~ /^PASSWORD_/ }.to_h SERVICE_NAMES = PASSWORDS.map { |k, _| k.gsub(/^PASSWORD_/, '') } class Service attr_reader :name, :state def initialize(name:) @name = name @state = "default" end def password = PASSWORDS["PASSWORD_#{@name}"] def start! = act! :start def stop! = act! :stop private def act!(action) `systemctl --user #{action} #{@name}` @state = action end end class ServiceIndex def initialize(service_list) @index = service_list.map { [_1, Service.new(name: _1)] }.to_h end def [](service_name) = @index[service_name] def each(&block) = @index.values.each(&block) end SERVICES = ServiceIndex.new(SERVICE_NAMES) class App < Sinatra::Base get "/" do slim :index, locals: { services: SERVICES } end post "/:service/:action" do service = SERVICES[params["service"]] if !service status_code 404 return "Service not found" end if params["password"] != service.password status_code 403 return "Invalid password" end case params["action"] when "start" service.start! when "stop" service.stop! end redirect "/" end set :bind, ENV["REMOTE_SYSTEMCTL_BIND"] || "127.0.0.1" set :port, ENV["REMOTE_SYSTEMCTL_PORT"] || "10001" set :environment, APP_ENV ENV["RACK_ENV"] = APP_ENV run! if app_file == $PROGRAM_NAME end end