127 lines
3.1 KiB
TypeScript
Executable file
127 lines
3.1 KiB
TypeScript
Executable file
#!/bin/env -S deno run -A --unstable
|
|
|
|
import { daemon_listen, Runner, start_runner } from "./src/lib.ts";
|
|
import { load_container_config } 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 create_state();
|
|
const server = await daemon_listen(socket_path());
|
|
log("listening to", socket_path());
|
|
|
|
async function finish() {
|
|
log("stopping");
|
|
server.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);
|
|
}
|
|
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");
|
|
}
|
|
}
|
|
|
|
async function create_state() {
|
|
const self = {
|
|
enabled: new Map<string, Runner>(),
|
|
async enable(name: string) {
|
|
if (self.enabled.has(name)) throw new Error("Container already enabled");
|
|
log("loading container", name);
|
|
const config = await load_container_config(name); // TODO
|
|
if (config === null) throw new Error("can't read config");
|
|
log("starting container", name);
|
|
self.enabled.set(name, await start_runner(config));
|
|
log("container", name, "started");
|
|
},
|
|
async disable(name: string) {
|
|
const container = self.enabled.get(name);
|
|
if (container === undefined) throw new Error("Container not found");
|
|
await container.stop();
|
|
self.enabled.delete(name);
|
|
},
|
|
list() {
|
|
return Array.from(self.enabled.keys());
|
|
},
|
|
async save() {
|
|
const content = JSON.stringify({ enabled: self.list() });
|
|
await Deno.writeTextFile(state_path(), content);
|
|
},
|
|
};
|
|
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 self.enable(name);
|
|
}
|
|
log("successfully loaded from", state_path());
|
|
} catch (error) {
|
|
log("experienced failure", error);
|
|
}
|
|
return self;
|
|
}
|