add nginx wrapper and refactor
This commit is contained in:
parent
9216063aa0
commit
6f92727bb3
9 changed files with 530 additions and 92 deletions
|
@ -1,61 +1,9 @@
|
|||
import { container_paths, containers_path, state_config_path } from "./paths.ts";
|
||||
import { exists, log_from } from "./utils.ts";
|
||||
import { log_from } from "./utils.ts";
|
||||
import { z } from "https://deno.land/x/zod@v3.22.4/mod.ts";
|
||||
|
||||
const log = log_from("config");
|
||||
|
||||
export function new_container_config(name: string): ContainerConfig {
|
||||
return {
|
||||
name,
|
||||
version: 0,
|
||||
redirects: [] as [number, number][],
|
||||
};
|
||||
}
|
||||
|
||||
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) {
|
||||
const config_path = container_paths(name).configuration;
|
||||
log("loading config for", name);
|
||||
if (!exists(config_path)) return null;
|
||||
const content = await Deno.readTextFile(config_path);
|
||||
const read = parse_container_config(content);
|
||||
const default_ = new_container_config(name);
|
||||
if (read.version < default_.version) throw new Error("read conf version is outdated");
|
||||
return { ...default_, ...read } as ContainerConfig;
|
||||
}
|
||||
|
||||
export async function save_container_config(config: ContainerConfig) {
|
||||
log("saving config of", config.name);
|
||||
const config_path = container_paths(config.name).configuration;
|
||||
const serialized = JSON.stringify(config, null, 4);
|
||||
await Deno.writeTextFile(config_path, serialized);
|
||||
}
|
||||
|
||||
export async function load_all_container_configs() {
|
||||
const names = Array.from(Deno.readDirSync(containers_path()))
|
||||
.filter((e) => e.isDirectory)
|
||||
.map((e) => e.name);
|
||||
const results = await Promise.all(names.map((name) => load_container_config(name)));
|
||||
return results.filter((item) => item != null) as ContainerConfig[];
|
||||
}
|
||||
|
||||
export type StateConfig = ReturnType<typeof new_config>;
|
||||
export function new_config(app_key: string, app_secret: string, consumer_key: string) {
|
||||
return { app_key, app_secret, consumer_key };
|
||||
|
@ -72,3 +20,87 @@ export async function load_state_config() {
|
|||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
const CONTAINER_CONFIG_VERSION = 1;
|
||||
|
||||
export class ContainerConfig {
|
||||
name;
|
||||
redirections;
|
||||
|
||||
constructor(name: string, redirections: ContainerConfigRedirection[]) {
|
||||
this.name = name;
|
||||
this.redirections = redirections;
|
||||
}
|
||||
|
||||
public static new_default(name: string) {
|
||||
return new ContainerConfig(name, []);
|
||||
}
|
||||
|
||||
public static async load(name: string) {
|
||||
const path = container_paths(name).configuration;
|
||||
const content = await Deno.readTextFile(path);
|
||||
return ContainerConfig.deserialize(content);
|
||||
}
|
||||
|
||||
public async save() {
|
||||
const path = container_paths(this.name).configuration;
|
||||
await Deno.writeTextFile(path, this.serialize());
|
||||
}
|
||||
|
||||
public static async *load_all() {
|
||||
for await (const { isDirectory, name } of Deno.readDir(containers_path())) {
|
||||
if (!isDirectory) continue;
|
||||
yield await ContainerConfig.load(name);
|
||||
}
|
||||
}
|
||||
|
||||
public static deserialize(raw: string) {
|
||||
const unknown = JSON.parse(raw);
|
||||
const parsed = parse(unknown);
|
||||
return new ContainerConfig(parsed.name, parsed.redirections);
|
||||
}
|
||||
|
||||
public serialize() {
|
||||
const raw: SerializedContainerConfig = {
|
||||
version: CONTAINER_CONFIG_VERSION,
|
||||
name: this.name,
|
||||
redirections: this.redirections,
|
||||
};
|
||||
return JSON.stringify(raw);
|
||||
}
|
||||
|
||||
public *used_host_ports() {
|
||||
for (const redir of this.redirections) {
|
||||
if (redir.kind === "http") yield redir.port;
|
||||
if (redir.kind === "port") yield redir.from;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parse(input: unknown) {
|
||||
const redir_port = z.object({
|
||||
kind: z.literal("port"),
|
||||
from: z.number(),
|
||||
to: z.number(),
|
||||
});
|
||||
|
||||
const redir_http = z.object({
|
||||
kind: z.literal("http"),
|
||||
tls: z.boolean(),
|
||||
port: z.number(),
|
||||
domain: z.string(),
|
||||
});
|
||||
|
||||
// TODO : redir DNS SRV
|
||||
|
||||
const redirection = redir_http.or(redir_port);
|
||||
|
||||
return z.object({
|
||||
name: z.string(),
|
||||
version: z.number(),
|
||||
redirections: z.array(redirection),
|
||||
}).parse(input);
|
||||
}
|
||||
|
||||
type SerializedContainerConfig = ReturnType<typeof parse>;
|
||||
export type ContainerConfigRedirection = SerializedContainerConfig["redirections"][0];
|
||||
|
|
|
@ -37,11 +37,11 @@ export function new_container_context(root_dir: string, config: ContainerConfig,
|
|||
},
|
||||
redirect: (from: number, to: number) => {
|
||||
log("redirects", from, "to", to);
|
||||
config.redirects.push([from, to]);
|
||||
config.redirections.push({ kind: "port", from, to });
|
||||
},
|
||||
available_port: (target: number) => {
|
||||
const used_by_known_containers = known_containers.map((c) => c.redirects[0]).flat();
|
||||
const used_by_self = config.redirects.map((r) => r[0]);
|
||||
const used_by_known_containers = known_containers.map((c) => [...c.used_host_ports()]).flat();
|
||||
const used_by_self = config.used_host_ports();
|
||||
const used = [...used_by_known_containers, ...used_by_self];
|
||||
let result = target;
|
||||
while (used.includes(result)) result += 1;
|
||||
|
|
42
instance/src/lib/nginx.ts
Normal file
42
instance/src/lib/nginx.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import { run } from "./utils.ts";
|
||||
import * as path from "https://deno.land/std@0.214.0/path/mod.ts";
|
||||
|
||||
class NginxController {
|
||||
proxy_target_url;
|
||||
enabled_conf_dir;
|
||||
|
||||
constructor(proxy_target_url: string, enabled_conf_dir: string) {
|
||||
this.proxy_target_url = proxy_target_url;
|
||||
this.enabled_conf_dir = enabled_conf_dir;
|
||||
}
|
||||
|
||||
public async add_proxy(url: string, port: number, conf_dir: string) {
|
||||
const conf_file_content = `
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name ${url};
|
||||
location / {
|
||||
proxy_pass http://${this.proxy_target_url}:${port};
|
||||
}
|
||||
}
|
||||
`;
|
||||
const conf_file_path = path.join(conf_dir, url + ".conf");
|
||||
const enabled_conf_file_path = path.join(this.enabled_conf_dir, url + ".conf");
|
||||
await Deno.writeTextFile(conf_file_path, conf_file_content);
|
||||
await run("ln", "-s", await Deno.realPath(conf_file_path), enabled_conf_file_path);
|
||||
await this.reload();
|
||||
}
|
||||
|
||||
public async remove_proxy(url: string, conf_dir: string) {
|
||||
const conf_file_path = path.join(conf_dir, url + ".conf");
|
||||
const enabled_conf_file_path = path.join(this.enabled_conf_dir, url + ".conf");
|
||||
await Deno.remove(enabled_conf_file_path);
|
||||
await Deno.remove(conf_file_path);
|
||||
await this.reload();
|
||||
}
|
||||
|
||||
private async reload() {
|
||||
await run("systemctl", "restart", "nginx");
|
||||
}
|
||||
}
|
|
@ -1,13 +1,14 @@
|
|||
// wrapper
|
||||
|
||||
import { log_from, sleep } from "./utils.ts";
|
||||
import { ContainerConfigRedirection } from "./config.ts";
|
||||
|
||||
const log = log_from("nspawn");
|
||||
|
||||
export function container_command(name: string, directory: string, opts?: {
|
||||
veth?: boolean;
|
||||
boot?: boolean;
|
||||
ports?: ([number, number] | [number, number, "tcp" | "udp"])[];
|
||||
redirections?: ContainerConfigRedirection[];
|
||||
cmd_opts?: Deno.CommandOptions;
|
||||
syscall_filter?: string[];
|
||||
}) {
|
||||
|
@ -17,8 +18,12 @@ export function container_command(name: string, directory: string, opts?: {
|
|||
];
|
||||
if (opts?.veth ?? false) args.push("--network-veth");
|
||||
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 redir of opts?.redirections ?? []) {
|
||||
if (redir.kind === "http") args.push(`--port=${redir.port}`);
|
||||
if (redir.kind === "port") {
|
||||
args.push(`--port=tcp:${redir.from}:${redir.to}`);
|
||||
args.push(`--port=udp:${redir.from}:${redir.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 });
|
||||
|
|
|
@ -93,3 +93,9 @@ export function log_from(...prefixes: string[]) {
|
|||
console.log(prefix, ...args);
|
||||
};
|
||||
}
|
||||
|
||||
export async function async_collect<T>(gen: AsyncIterable<T>) {
|
||||
const result = [] as T[];
|
||||
for await (const item of gen) result.push(item);
|
||||
return result;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue