From d86f96c3b48db0168e53aab86f7fd8f93d96679e Mon Sep 17 00:00:00 2001 From: Ben Allfree Date: Fri, 1 Mar 2024 06:29:12 -0800 Subject: [PATCH] feat: mothership isolation (Docker vs spawn) --- cspell.json | 1 + package.json | 2 + pnpm-lock.yaml | 14 +++ .../MothershipCommand/ServeCommand/index.ts | 6 +- .../ServeCommand/mothership.ts | 119 ++++++++++++++---- src/cli/commands/ServeCommand/index.ts | 7 +- src/constants.ts | 10 +- 7 files changed, 125 insertions(+), 34 deletions(-) diff --git a/cspell.json b/cspell.json index 0b1ddae1..67579f84 100644 --- a/cspell.json +++ b/cspell.json @@ -36,6 +36,7 @@ "oneline", "opengraph", "PASV", + "pbgo", "pbincache", "PBOUNCE", "pexec", diff --git a/package.json b/package.json index 96656144..e4a128d4 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,9 @@ "nanoid": "^5.0.2", "node-fetch": "^3.3.2", "node-os-utils": "^1.3.7", + "pbgo": "1.0.0-alpha.1", "pocketbase": "^0.20.1", + "rimraf": "^5.0.5", "semver": "^7.5.4", "sqlite3": "^5.1.6", "syslog-parse": "^2.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a253688f..639cde54 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -107,9 +107,15 @@ importers: node-os-utils: specifier: ^1.3.7 version: 1.3.7 + pbgo: + specifier: 1.0.0-alpha.1 + version: link:../pbgo pocketbase: specifier: ^0.20.1 version: 0.20.1 + rimraf: + specifier: ^5.0.5 + version: 5.0.5 semver: specifier: ^7.5.4 version: 7.5.4 @@ -7211,6 +7217,14 @@ packages: dependencies: glob: 7.2.3 + /rimraf@5.0.5: + resolution: {integrity: sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==} + engines: {node: '>=14'} + hasBin: true + dependencies: + glob: 10.3.10 + dev: false + /rizzdown@0.0.7: resolution: {integrity: sha512-gL+Vz1qMNAIoEgtYzJ+PbB0rZFtI+GJ/C+vXC9/IM1O3F21AgboBWrqGM7Ls5L7GiG1o933vApjulun/hdto2g==} engines: {node: '>=16'} diff --git a/src/cli/commands/MothershipCommand/ServeCommand/index.ts b/src/cli/commands/MothershipCommand/ServeCommand/index.ts index ee877459..419487ce 100644 --- a/src/cli/commands/MothershipCommand/ServeCommand/index.ts +++ b/src/cli/commands/MothershipCommand/ServeCommand/index.ts @@ -2,14 +2,16 @@ import { Command } from 'commander' import { mothership } from './mothership' type Options = { - debug: boolean + isolate: boolean } export const ServeCommand = () => { const cmd = new Command(`serve`) .description(`Run the PocketHost mothership`) + .option(`--isolate`, `Use Docker for process isolation.`, false) .action(async (options: Options) => { - await mothership() + console.log({ options }) + await mothership(options) }) return cmd } diff --git a/src/cli/commands/MothershipCommand/ServeCommand/mothership.ts b/src/cli/commands/MothershipCommand/ServeCommand/mothership.ts index b345310a..d1a7dcac 100644 --- a/src/cli/commands/MothershipCommand/ServeCommand/mothership.ts +++ b/src/cli/commands/MothershipCommand/ServeCommand/mothership.ts @@ -1,9 +1,11 @@ import { DATA_ROOT, DEBUG, + IS_DEV, LS_WEBHOOK_SECRET, mkContainerHomePath, MOTHERSHIP_APP_DIR, + MOTHERSHIP_DATA_ROOT, MOTHERSHIP_HOOKS_DIR, MOTHERSHIP_MIGRATIONS_DIR, MOTHERSHIP_NAME, @@ -18,38 +20,105 @@ import { } from '$services' import { LoggerService } from '$shared' import { gracefulExit } from '$util' +import copyfiles from 'copyfiles' +import { run } from 'pbgo' +import { rimraf } from 'rimraf' -export async function mothership() { +export type MothershipConfig = { isolate: boolean } + +const _copy = (src: string, dst: string) => { + const { error } = LoggerService().create(`copy`) + + return new Promise((resolve) => { + copyfiles( + [src, dst], + { + verbose: DEBUG(), + up: true, + }, + (err) => { + if (err) { + error(err) + throw err + } + resolve() + }, + ) + }) +} + +export async function mothership(cfg: MothershipConfig) { + const { isolate } = cfg const logger = LoggerService().create(`Mothership`) const { dbg, error, info, warn } = logger info(`Starting`) + dbg(`Isolation mode:`, { isolate }) + await PortService({}) - await PocketbaseReleaseVersionService({}) - const pbService = await PocketbaseService({}) /** Launch central database */ info(`Serving`) - const { url, exitCode } = await pbService.spawn({ - version: MOTHERSHIP_SEMVER(), - subdomain: MOTHERSHIP_NAME(), - instanceId: MOTHERSHIP_NAME(), - port: MOTHERSHIP_PORT(), - dev: DEBUG(), - env: { - DATA_ROOT: mkContainerHomePath(`data`), - LS_WEBHOOK_SECRET: LS_WEBHOOK_SECRET(), - }, - extraBinds: [ - `${DATA_ROOT()}:${mkContainerHomePath(`data`)}`, - `${MOTHERSHIP_HOOKS_DIR()}:${mkContainerHomePath(`pb_hooks`)}`, - `${PH_VERSIONS()}:${mkContainerHomePath(`pb_hooks`, `versions.js`)}`, - `${MOTHERSHIP_MIGRATIONS_DIR()}:${mkContainerHomePath(`pb_migrations`)}`, - `${MOTHERSHIP_APP_DIR()}:${mkContainerHomePath(`ph_app`)}`, - ], - }) - info(`Mothership URL for this session is ${url}`) - exitCode.then((c) => { - gracefulExit(c) - }) + if (isolate) { + await PocketbaseReleaseVersionService({}) + const pbService = await PocketbaseService({}) + const { url, exitCode } = await pbService.spawn({ + version: MOTHERSHIP_SEMVER(), + subdomain: MOTHERSHIP_NAME(), + instanceId: MOTHERSHIP_NAME(), + port: MOTHERSHIP_PORT(), + dev: DEBUG(), + env: { + DATA_ROOT: mkContainerHomePath(`data`), + LS_WEBHOOK_SECRET: LS_WEBHOOK_SECRET(), + }, + extraBinds: [ + `${DATA_ROOT()}:${mkContainerHomePath(`data`)}`, + `${MOTHERSHIP_HOOKS_DIR()}:${mkContainerHomePath(`pb_hooks`)}`, + `${PH_VERSIONS()}:${mkContainerHomePath(`pb_hooks`, `versions.js`)}`, + `${MOTHERSHIP_MIGRATIONS_DIR()}:${mkContainerHomePath( + `pb_migrations`, + )}`, + `${MOTHERSHIP_APP_DIR()}:${mkContainerHomePath(`ph_app`)}`, + ], + }) + info(`Mothership URL for this session is ${url}`) + exitCode.then((c) => { + gracefulExit(c) + }) + } else { + await rimraf(MOTHERSHIP_DATA_ROOT(`pb_hooks`)) + await _copy(MOTHERSHIP_HOOKS_DIR(`**/*`), MOTHERSHIP_DATA_ROOT(`pb_hooks`)) + await _copy(PH_VERSIONS(), MOTHERSHIP_DATA_ROOT(`pb_hooks`)) + await rimraf(MOTHERSHIP_DATA_ROOT(`pb_migrations`)) + await _copy( + MOTHERSHIP_MIGRATIONS_DIR(`**/*`), + MOTHERSHIP_DATA_ROOT(`pb_migrations`), + ) + const args = [ + `serve`, + `--http`, + `0.0.0.0:${MOTHERSHIP_PORT()}`, + `--dir`, + MOTHERSHIP_DATA_ROOT(`pb_data`), + `--hooksDir`, + MOTHERSHIP_DATA_ROOT(`pb_hooks`), + `--migrationsDir`, + MOTHERSHIP_DATA_ROOT(`pb_migrations`), + `--publicDir`, + MOTHERSHIP_DATA_ROOT(`pb_public`), + ] + if (IS_DEV()) { + args.push(`--dev`) + } + dbg(args) + const process = run(args, { + env: { + DATA_ROOT: DATA_ROOT(), + LS_WEBHOOK_SECRET: LS_WEBHOOK_SECRET(), + }, + version: MOTHERSHIP_SEMVER(), + debug: DEBUG(), + }) + } } diff --git a/src/cli/commands/ServeCommand/index.ts b/src/cli/commands/ServeCommand/index.ts index 148fd944..a48ec6c5 100644 --- a/src/cli/commands/ServeCommand/index.ts +++ b/src/cli/commands/ServeCommand/index.ts @@ -6,19 +6,20 @@ import { firewall } from '../FirewallCommand/ServeCommand/firewall/server' import { mothership } from '../MothershipCommand/ServeCommand/mothership' type Options = { - debug: boolean + isolate: boolean } export const ServeCommand = () => { const cmd = new Command(`serve`) .description(`Run the entire PocketHost stack`) + .option(`--isolate`, `Use Docker for process isolation.`, false) .action(async (options: Options) => { - const logger = LoggerService().create(`ServeComand`) + const logger = LoggerService().create(`ServeCommand`) const { dbg, error, info, warn } = logger info(`Starting`) await syslog() - await mothership() + await mothership(options) await daemon() await firewall() }) diff --git a/src/constants.ts b/src/constants.ts index 20720b31..1e406ce8 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -216,9 +216,10 @@ export const MOTHERSHIP_ADMIN_USERNAME = () => settings().MOTHERSHIP_ADMIN_USERNAME export const MOTHERSHIP_ADMIN_PASSWORD = () => settings().MOTHERSHIP_ADMIN_PASSWORD -export const MOTHERSHIP_MIGRATIONS_DIR = () => - settings().MOTHERSHIP_MIGRATIONS_DIR -export const MOTHERSHIP_HOOKS_DIR = () => settings().MOTHERSHIP_HOOKS_DIR +export const MOTHERSHIP_MIGRATIONS_DIR = (...paths: string[]) => + join(settings().MOTHERSHIP_MIGRATIONS_DIR, ...paths) +export const MOTHERSHIP_HOOKS_DIR = (...paths: string[]) => + join(settings().MOTHERSHIP_HOOKS_DIR, ...paths) export const MOTHERSHIP_APP_DIR = () => settings().MOTHERSHIP_APP_DIR export const MOTHERSHIP_SEMVER = () => settings().MOTHERSHIP_SEMVER export const MOTHERSHIP_PORT = () => settings().MOTHERSHIP_PORT @@ -264,7 +265,8 @@ export const DOCKER_CONTAINER_HOST = () => settings().DOCKER_CONTAINER_HOST /** Helpers */ -export const MOTHERSHIP_DATA_ROOT = () => INSTANCE_DATA_ROOT(MOTHERSHIP_NAME()) +export const MOTHERSHIP_DATA_ROOT = (...paths: string[]) => + join(INSTANCE_DATA_ROOT(MOTHERSHIP_NAME()), ...paths) export const MOTHERSHIP_DATA_DB = () => join(MOTHERSHIP_DATA_ROOT(), `pb_data`, `data.db`) export const MOTHERSHIP_INTERNAL_URL = (path = '') =>