From 44825c4c319076e4540b5f6472248cabfec20039 Mon Sep 17 00:00:00 2001 From: JOLIMAITRE Matthieu Date: Sat, 9 Dec 2023 00:07:17 +0100 Subject: [PATCH] init --- .gitignore | 2 ++ control.ts | 31 +++++++++++++++++++++++++ daemon.ts | 42 +++++++++++++++++++++++++++++++++ deno.json | 6 +++++ local/.gitignore | 2 ++ src/bin/proxy.ts | 18 +++++++++++++++ src/lib.ts | 59 +++++++++++++++++++++++++++++++++++++++++++++++ src/lib/config.ts | 21 +++++++++++++++++ src/lib/nspawn.ts | 1 + src/lib/paths.ts | 22 ++++++++++++++++++ src/lib/utils.ts | 43 ++++++++++++++++++++++++++++++++++ 11 files changed, 247 insertions(+) create mode 100644 .gitignore create mode 100755 control.ts create mode 100755 daemon.ts create mode 100644 deno.json create mode 100644 local/.gitignore create mode 100644 src/bin/proxy.ts create mode 100644 src/lib.ts create mode 100644 src/lib/config.ts create mode 100644 src/lib/nspawn.ts create mode 100644 src/lib/paths.ts create mode 100644 src/lib/utils.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..752f185 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/socket.socket +/deno.lock \ No newline at end of file diff --git a/control.ts b/control.ts new file mode 100755 index 0000000..f67bbf2 --- /dev/null +++ b/control.ts @@ -0,0 +1,31 @@ +#!/bin/env -S deno run -A --unstable + +import { daemon_send, new_cmd_disable, new_cmd_enable, new_cmd_status } from "./src/lib.ts"; +import { socket_path } from "./src/lib/paths.ts"; + +await main(); +async function main() { + const [kind, ...rest] = Deno.args; + + if (kind === "status") { + const res = await daemon_send(socket_path(), new_cmd_status()); + console.log({ res }); + } + + if (kind === "enable") { + const [name] = rest; + const res = await daemon_send(socket_path(), new_cmd_enable(name)); + console.log({ res }); + } + + if (kind === "disable") { + const [name] = rest; + const res = await daemon_send(socket_path(), new_cmd_disable(name)); + console.log({ res }); + } + + if (kind === "stop") { + const res = await daemon_send(socket_path(), new_cmd_status()); + console.log({ res }); + } +} diff --git a/daemon.ts b/daemon.ts new file mode 100755 index 0000000..a9132ec --- /dev/null +++ b/daemon.ts @@ -0,0 +1,42 @@ +#!/bin/env -S deno run -A --unstable + +import { daemon_listen, Runner, start_runner } from "./src/lib.ts"; +import { new_container_config } from "./src/lib/config.ts"; +import { socket_path } from "./src/lib/paths.ts"; + +await main(); +async function main() { + const enabled = new Map(); + + for await (const { cmd, respond } of daemon_listen(socket_path())) { + console.log("received", { cmd }); + + if (cmd.kind === "status") { + await respond(JSON.stringify(Array.from(enabled.keys()))); + continue; + } + + if (cmd.kind === "enable") { + const config = new_container_config(cmd.name); // TODO + const runner = start_runner(config); + enabled.set(cmd.name, runner); + await respond("enabled"); + continue; + } + + if (cmd.kind === "disable") { + await enabled.get(cmd.name)?.stop(); + enabled.delete(cmd.name); + await respond("disabled"); + continue; + } + + if (cmd.kind === "stop") { + await respond(JSON.stringify("stopping, ++")); + for (const runner of enabled.values()) await runner.stop(); + Deno.exit(0); + } + + await respond("unknown"); + } +} diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..92b61b1 --- /dev/null +++ b/deno.json @@ -0,0 +1,6 @@ +{ + "fmt": { + "useTabs": true, + "lineWidth": 120 + } +} diff --git a/local/.gitignore b/local/.gitignore new file mode 100644 index 0000000..299138c --- /dev/null +++ b/local/.gitignore @@ -0,0 +1,2 @@ +/base/* +/container/* diff --git a/src/bin/proxy.ts b/src/bin/proxy.ts new file mode 100644 index 0000000..2b7464b --- /dev/null +++ b/src/bin/proxy.ts @@ -0,0 +1,18 @@ +#!/bin/env -S deno run -A --unstable + +await main(); +async function main() { + const [from_port, to_ip, to_port] = Deno.args; + if (to_port === undefined) Deno.exit(2); + + const server = Deno.listen({ transport: "tcp", port: parseInt(from_port) }); + + for await (const connection of server) { + try { + const client = await Deno.connect({ transport: "tcp", hostname: to_ip, port: parseInt(to_port) }); + const promise_connection_done = connection.readable.pipeTo(client.writable); + const promise_client_done = client.readable.pipeTo(connection.writable); + await Promise.all([promise_client_done, promise_connection_done]); + } catch (_) { /* isok */ } + } +} diff --git a/src/lib.ts b/src/lib.ts new file mode 100644 index 0000000..82bbdf6 --- /dev/null +++ b/src/lib.ts @@ -0,0 +1,59 @@ +import { toText } from "https://deno.land/std@0.208.0/streams/to_text.ts"; +import { lines } from "./lib/utils.ts"; +import { ContainerConfig } from "./lib/config.ts"; + +export type CmdStatus = ReturnType; +export function new_cmd_status() { + return { kind: "status" as const }; +} + +export type CmdEnable = ReturnType; +export function new_cmd_enable(name: string) { + return { kind: "enable" as const, name }; +} + +export type CmdDisable = ReturnType; +export function new_cmd_disable(name: string) { + return { kind: "disable" as const, name }; +} + +export type CmdStop = ReturnType; +export function new_cmd_stop() { + return { kind: "stop" as const }; +} + +export type Cmd = CmdStatus | CmdEnable | CmdDisable | CmdStop; + +export async function* daemon_listen(sock_path: string) { + const server = Deno.listen({ transport: "unix", path: sock_path }); + for await (const request of server) { + const respond = async (message: string) => { + try { + await request.write(new TextEncoder().encode(message)); + } catch (_) { /* bof mais bon */ } + }; + try { + for await (const line of lines(request.readable)) { + const cmd = JSON.parse(line) as Cmd; + yield { cmd, respond }; + } + } catch (_) { /* ok tier */ } + } +} + +export async function daemon_send(sock_path: string, command: Cmd) { + const request = await Deno.connect({ transport: "unix", path: sock_path }); + const text = JSON.stringify(command) + "\r\n\r\n"; + await request.write(new TextEncoder().encode(text)); + return await toText(request.readable); +} + +export type Runner = ReturnType; +export function start_runner(config: ContainerConfig) { + return { + name: config.name, + stop: async () => { + // TODO + }, + }; +} diff --git a/src/lib/config.ts b/src/lib/config.ts new file mode 100644 index 0000000..0ec0c44 --- /dev/null +++ b/src/lib/config.ts @@ -0,0 +1,21 @@ +import { container_paths } from "./paths.ts"; +import { exists } from "./utils.ts"; + +export type ContainerConfig = ReturnType; +export function new_container_config(name: string) { + return { + name, + version: 0, + redirects: [] as [number, number][], + }; +} + +export async function load_container_config(name: string) { + const config_path = container_paths(name).config; + if (!exists(config_path)) return null; + const content = await Deno.readTextFile(config_path); + const read = JSON.parse(content); + const default_ = new_container_config(name); + if (read.version < default_.version) throw new Error("read conf version is outdated"); + return { ...default_, ...read }; +} diff --git a/src/lib/nspawn.ts b/src/lib/nspawn.ts new file mode 100644 index 0000000..9f58fae --- /dev/null +++ b/src/lib/nspawn.ts @@ -0,0 +1 @@ +// wrapper diff --git a/src/lib/paths.ts b/src/lib/paths.ts new file mode 100644 index 0000000..abdfe6b --- /dev/null +++ b/src/lib/paths.ts @@ -0,0 +1,22 @@ +import { dirname } from "https://deno.land/std@0.208.0/path/mod.ts"; + +// '/bdmgrdts' +export function instance_root_path() { + const file_path = new URL("", import.meta.url).pathname; + return dirname(dirname(dirname(file_path))); +} + +export function socket_path() { + return instance_root_path() + "/socket.socket"; +} + +export function containers_path() { + return instance_root_path() + "/local/containers"; +} + +export function container_paths(name: string) { + const base = containers_path() + "/" + name; + const conf = base + "/config.json"; + const root = base + "/root"; + return { base, configuration: conf, root }; +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..8421f50 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,43 @@ +import { TextLineStream } from "https://deno.land/std@0.208.0/streams/mod.ts"; + +export function channel() { + const inner = { + queue: [] as T[], + resolver: null as (() => void) | null, + }; + const send = (item: T) => { + inner.queue.push(item); + if (inner.resolver === null) return; + inner.resolver(); + inner.resolver = null; + }; + const receive = async () => { + if (inner.queue.length < 1) await new Promise((res) => inner.resolver = res); + const [head] = inner.queue.splice(0, 1); + return head; + }; + return { send, receive }; +} + +export function* range(from: number, to: number) { + while (from < to) yield from++; +} + +export async function exists(path: string) { + try { + await Deno.stat(path); + return true; + } catch (_) { + return false; + } +} + +export async function next(readable: ReadableStream) { + for await (const item of readable) return item; +} + +export function lines(readable: ReadableStream) { + return readable + .pipeThrough(new TextDecoderStream()) + .pipeThrough(new TextLineStream()); +}