Compare commits
No commits in common. "ad003c4ae053f2a31a09c6608aaa76d752602b32" and "36891699c4bf86c948319e4169d14b5866ce7bcc" have entirely different histories.
ad003c4ae0
...
36891699c4
6 changed files with 71 additions and 41 deletions
|
@ -1,7 +1,7 @@
|
||||||
#!/bin/env -S deno run -A --unstable
|
#!/bin/env -S deno run -A --unstable
|
||||||
|
|
||||||
import { daemon_send, new_cmd_disable, new_cmd_enable, new_cmd_status, new_cmd_stop } from "./src/lib.ts";
|
import { daemon_send, new_cmd_disable, new_cmd_enable, new_cmd_status, new_cmd_stop } from "./src/lib.ts";
|
||||||
import { load_all_container_configs, new_container_config, save_container_config } from "./src/lib/config.ts";
|
import { load_all_configs, new_container_config, save_container_config } from "./src/lib/config.ts";
|
||||||
import { load_base, new_container_context } from "./src/lib/create.ts";
|
import { load_base, new_container_context } from "./src/lib/create.ts";
|
||||||
import { container_paths, socket_path } from "./src/lib/paths.ts";
|
import { container_paths, socket_path } from "./src/lib/paths.ts";
|
||||||
import { log_from, run } from "./src/lib/utils.ts";
|
import { log_from, run } from "./src/lib/utils.ts";
|
||||||
|
@ -78,7 +78,7 @@ Commands:
|
||||||
export async function create(name: string, base_name: string) {
|
export async function create(name: string, base_name: string) {
|
||||||
log("loading base", base_name);
|
log("loading base", base_name);
|
||||||
const base = await load_base(base_name);
|
const base = await load_base(base_name);
|
||||||
const known_containers = await load_all_container_configs();
|
const known_containers = await load_all_configs();
|
||||||
|
|
||||||
const paths = container_paths(name);
|
const paths = container_paths(name);
|
||||||
await Deno.mkdir(paths.base);
|
await Deno.mkdir(paths.base);
|
||||||
|
|
1
instance/local/.gitignore
vendored
1
instance/local/.gitignore
vendored
|
@ -1,3 +1,2 @@
|
||||||
/containers/*
|
/containers/*
|
||||||
/config.json
|
|
||||||
/state.json
|
/state.json
|
||||||
|
|
32
instance/src/bin/proxy.ts
Executable file
32
instance/src/bin/proxy.ts
Executable file
|
@ -0,0 +1,32 @@
|
||||||
|
#!/bin/env -S deno run -A --unstable
|
||||||
|
|
||||||
|
if (import.meta.main) await main();
|
||||||
|
async function main() {
|
||||||
|
const [from_port, to_ip, to_port] = Deno.args;
|
||||||
|
if (to_port === undefined) {
|
||||||
|
console.error("[proxy] usage: ./proxy.ts <from_port> <to_ip> <to_port>");
|
||||||
|
Deno.exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
const server = Deno.listen({ transport: "tcp", port: parseInt(from_port) });
|
||||||
|
console.log("[proxy] listening on port", from_port, "redirecting to", to_ip, "port", to_port);
|
||||||
|
|
||||||
|
for await (const connection of server) {
|
||||||
|
serve(to_ip, to_port, connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function serve(to_ip: string, to_port: string, connection: Deno.Conn) {
|
||||||
|
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 */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function proxy_command(from_port: number, to_ip: string, to_port: number) {
|
||||||
|
const path = new URL("", import.meta.url).pathname;
|
||||||
|
const args = [from_port, to_ip, to_port].map((v) => v.toString());
|
||||||
|
return new Deno.Command(path, { args });
|
||||||
|
}
|
|
@ -1,10 +1,11 @@
|
||||||
export type { Base, BaseContext } from "./lib/create.ts";
|
export type { Base, BaseContext } from "./lib/create.ts";
|
||||||
|
|
||||||
import { toText } from "https://deno.land/std@0.208.0/streams/to_text.ts";
|
import { toText } from "https://deno.land/std@0.208.0/streams/to_text.ts";
|
||||||
import { lines, log_from, loop_process, run } from "./lib/utils.ts";
|
import { lines, log_from, loop_process, LoopProcess, run, sleep } from "./lib/utils.ts";
|
||||||
import { ContainerConfig } from "./lib/config.ts";
|
import { ContainerConfig } from "./lib/config.ts";
|
||||||
import { container_paths } from "./lib/paths.ts";
|
import { container_paths } from "./lib/paths.ts";
|
||||||
import { container_command } from "./lib/nspawn.ts";
|
import { container_command, get_container_addresses } from "./lib/nspawn.ts";
|
||||||
|
import { proxy_command } from "./bin/proxy.ts";
|
||||||
|
|
||||||
const log = log_from("lib");
|
const log = log_from("lib");
|
||||||
|
|
||||||
|
@ -73,21 +74,45 @@ export function start_runner(config: ContainerConfig) {
|
||||||
const command = container_command(name, paths.root, {
|
const command = container_command(name, paths.root, {
|
||||||
boot: true,
|
boot: true,
|
||||||
veth: true,
|
veth: true,
|
||||||
ports: config.redirects,
|
|
||||||
cmd_opts: {
|
cmd_opts: {
|
||||||
stdin: "null",
|
stdin: "null",
|
||||||
stdout: "null",
|
stdout: "null",
|
||||||
},
|
},
|
||||||
syscall_filter: ["add_key", "keyctl", "bpf"],
|
|
||||||
});
|
});
|
||||||
|
const proxies = [] as LoopProcess[];
|
||||||
const container_loop = loop_process(command, {
|
const container_loop = loop_process(command, {
|
||||||
on_start: () => log("container", name, "started"),
|
on_start: () => {
|
||||||
on_stop: () => log("container", name, "stopped"),
|
log("container", name, "started");
|
||||||
|
(async () => {
|
||||||
|
await sleep(1_000);
|
||||||
|
const [address] = await get_container_addresses(name);
|
||||||
|
proxies.push(...start_proxies(address, config.redirects));
|
||||||
|
})();
|
||||||
|
},
|
||||||
|
on_stop: async () => {
|
||||||
|
log("container", name, "stopped");
|
||||||
|
for (const p of proxies) await p.kill();
|
||||||
|
await sleep(500);
|
||||||
|
proxies.splice(0, proxies.length);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
config,
|
config,
|
||||||
container_loop,
|
container_loop,
|
||||||
stop: () => container_loop.kill(),
|
stop: async () => {
|
||||||
|
for (const p of proxies) await p.kill();
|
||||||
|
await container_loop.kill();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function start_proxies(address: string, redirects: [number, number][]) {
|
||||||
|
const redirections = redirects
|
||||||
|
.map(([from_port, to_port]) => {
|
||||||
|
return loop_process(proxy_command(from_port, address, to_port), {
|
||||||
|
on_start: () => console.log("starting proxy", from_port, "to", address, "port", to_port),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return redirections;
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { container_paths, containers_path, state_config_path } from "./paths.ts";
|
import { container_paths, containers_path } from "./paths.ts";
|
||||||
import { exists, log_from } from "./utils.ts";
|
import { exists, log_from } from "./utils.ts";
|
||||||
import { z } from "https://deno.land/x/zod@v3.22.4/mod.ts";
|
|
||||||
|
|
||||||
const log = log_from("config");
|
const log = log_from("config");
|
||||||
|
|
||||||
export function new_container_config(name: string): ContainerConfig {
|
export type ContainerConfig = ReturnType<typeof new_container_config>;
|
||||||
|
export function new_container_config(name: string) {
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
version: 0,
|
version: 0,
|
||||||
|
@ -12,32 +12,12 @@ export function new_container_config(name: string): ContainerConfig {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ContainerConfig = ReturnType<typeof parse_container_config>;
|
|
||||||
export function parse_container_config(json: string) {
|
|
||||||
return z.object({
|
|
||||||
name: z.string(),
|
|
||||||
version: z.number(),
|
|
||||||
redirects: z.array(
|
|
||||||
z
|
|
||||||
.tuple([
|
|
||||||
z.number(),
|
|
||||||
z.number(),
|
|
||||||
])
|
|
||||||
.or(z.tuple([
|
|
||||||
z.number(),
|
|
||||||
z.number(),
|
|
||||||
z.literal("tcp").or(z.literal("udp")),
|
|
||||||
])),
|
|
||||||
),
|
|
||||||
}).parse(JSON.parse(json));
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function load_container_config(name: string) {
|
export async function load_container_config(name: string) {
|
||||||
const config_path = container_paths(name).configuration;
|
const config_path = container_paths(name).configuration;
|
||||||
log("loading config for", name);
|
log("loading config for", name);
|
||||||
if (!exists(config_path)) return null;
|
if (!exists(config_path)) return null;
|
||||||
const content = await Deno.readTextFile(config_path);
|
const content = await Deno.readTextFile(config_path);
|
||||||
const read = parse_container_config(content);
|
const read = JSON.parse(content);
|
||||||
const default_ = new_container_config(name);
|
const default_ = new_container_config(name);
|
||||||
if (read.version < default_.version) throw new Error("read conf version is outdated");
|
if (read.version < default_.version) throw new Error("read conf version is outdated");
|
||||||
return { ...default_, ...read } as ContainerConfig;
|
return { ...default_, ...read } as ContainerConfig;
|
||||||
|
@ -50,7 +30,7 @@ export async function save_container_config(config: ContainerConfig) {
|
||||||
await Deno.writeTextFile(config_path, serialized);
|
await Deno.writeTextFile(config_path, serialized);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function load_all_container_configs() {
|
export async function load_all_configs() {
|
||||||
const names = Array.from(Deno.readDirSync(containers_path()))
|
const names = Array.from(Deno.readDirSync(containers_path()))
|
||||||
.filter((e) => e.isDirectory)
|
.filter((e) => e.isDirectory)
|
||||||
.map((e) => e.name);
|
.map((e) => e.name);
|
||||||
|
|
|
@ -7,9 +7,7 @@ const log = log_from("nspawn");
|
||||||
export function container_command(name: string, directory: string, opts?: {
|
export function container_command(name: string, directory: string, opts?: {
|
||||||
veth?: boolean;
|
veth?: boolean;
|
||||||
boot?: boolean;
|
boot?: boolean;
|
||||||
ports?: ([number, number] | [number, number, "tcp" | "udp"])[];
|
|
||||||
cmd_opts?: Deno.CommandOptions;
|
cmd_opts?: Deno.CommandOptions;
|
||||||
syscall_filter?: string[];
|
|
||||||
}) {
|
}) {
|
||||||
const args = [
|
const args = [
|
||||||
`--machine=${name}`,
|
`--machine=${name}`,
|
||||||
|
@ -17,10 +15,6 @@ export function container_command(name: string, directory: string, opts?: {
|
||||||
];
|
];
|
||||||
if (opts?.veth ?? false) args.push("--network-veth");
|
if (opts?.veth ?? false) args.push("--network-veth");
|
||||||
if (opts?.boot ?? false) args.push("--boot");
|
if (opts?.boot ?? false) args.push("--boot");
|
||||||
for (const [from, to, proto] of opts?.ports ?? []) {
|
|
||||||
args.push(proto === undefined ? `--port=${from}:${to}` : `--port=${proto}:${from}:${to}`);
|
|
||||||
}
|
|
||||||
for (const call of opts?.syscall_filter ?? []) args.push(`--system-call-filter=${call}`);
|
|
||||||
const command = new Deno.Command("systemd-nspawn", { ...opts?.cmd_opts, args });
|
const command = new Deno.Command("systemd-nspawn", { ...opts?.cmd_opts, args });
|
||||||
return command;
|
return command;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue