#!/bin/env -S deno run -A --unstable import { daemon_listen, Runner } from "./src/lib.ts"; import { ContainerConfig } from "./src/lib/config.ts"; import { socket_path, state_path } from "./src/lib/paths.ts"; import { log_from, sleep } from "./src/lib/utils.ts"; const log = log_from("daemon"); if (import.meta.main) await main(); async function main() { const state = await State.load(); const server = await daemon_listen(socket_path()); log("listening to", socket_path()); const finish = termination_function(state, server.server); Deno.addSignalListener("SIGINT", finish); for await (const { cmd, respond } of server) { log("received", cmd); if (cmd.kind === "status") { await respond(JSON.stringify(state.list())); continue; } if (cmd.kind === "enable") { (async () => { try { await state.enable(cmd.name); await state.save(); await respond("enabled"); } catch (error) { log("experienced failure", error); await respond(`${error}`); } })(); continue; } if (cmd.kind === "disable") { (async () => { try { await state.disable(cmd.name); await state.save(); await respond("disabled"); } catch (error) { log("experienced failure", error); await respond(`${error}`); } })(); continue; } if (cmd.kind === "reload") { (async () => { try { await state.disable(cmd.name); await sleep(500); await state.enable(cmd.name); await respond("reloaded"); } catch (error) { log("experienced failure", error); await respond(`${error}`); } })(); continue; } if (cmd.kind === "stop") { await state.save(); await respond("adios"); await finish(); } await respond("unknown command"); } } class State { enabled; constructor() { this.enabled = new Map(); } static async load() { const result = new State(); try { log("trying to recover state from", state_path()); const loaded = JSON.parse(await Deno.readTextFile(state_path())); for (const name of loaded.enabled ?? []) { await result.enable(name); } log("successfully loaded from", state_path()); } catch (error) { log("experienced failure", error); } return result; } async enable(name: string) { if (this.enabled.has(name)) throw new Error("Container already enabled"); log("loading container", name); const config = await ContainerConfig.load(name); // TODO if (config === null) throw new Error("can't read config"); log("starting container", name); const runner = new Runner(config); runner.start(); this.enabled.set(name, runner); log("container", name, "started"); } async disable(name: string) { const container = this.enabled.get(name); if (container === undefined) throw new Error("Container not found"); await container.stop(); this.enabled.delete(name); } list() { return Array.from(this.enabled.keys()); } async save() { const content = JSON.stringify({ enabled: this.list() }); await Deno.writeTextFile(state_path(), content); } } function termination_function(state: State, server: Deno.Listener) { return async function finish() { log("stopping"); server.close(); for (const runner of state.enabled.values()) { try { await runner.stop(); } catch (_) { /* on s'en fou */ } } await Deno.remove(socket_path()); Deno.exit(0); }; }