From 4d6cb091cca261e17f5f84ea81c190968ecbadd0 Mon Sep 17 00:00:00 2001 From: Jade <2364004+Blu-J@users.noreply.github.com> Date: Fri, 7 Jun 2024 12:17:45 -0600 Subject: [PATCH] Feature/disk usage (#2637) * feat: Add disk usage * Fixed: let the set config work with nesting. * chore: Changes * chore: Add default route * fix: Tor only config * chore --- .../Systems/SystemForEmbassy/index.ts | 29 +++++++-- .../SystemForEmbassy/oldEmbassyTypes.ts | 4 ++ .../SystemForEmbassy/polyfillEffects.ts | 64 +++++++++++++++++-- .../src/Models/DockerProcedure.ts | 4 +- container-runtime/src/Models/Duration.ts | 26 +++++++- 5 files changed, 113 insertions(+), 14 deletions(-) diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts index 8e0cb28c5..0bdb0d769 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts @@ -2,7 +2,7 @@ import { types as T, utils, EmVer } from "@start9labs/start-sdk" import * as fs from "fs/promises" import { PolyfillEffects } from "./polyfillEffects" -import { Duration, duration } from "../../../Models/Duration" +import { Duration, duration, fromDuration } from "../../../Models/Duration" import { System } from "../../../Interfaces/System" import { matchManifest, Manifest, Procedure } from "./matchManifest" import * as childProcess from "node:child_process" @@ -478,10 +478,13 @@ export class SystemForEmbassy implements System { delete this.currentRunning if (currentRunning) { await currentRunning.clean({ - timeout: this.manifest.main["sigterm-timeout"], + timeout: fromDuration(this.manifest.main["sigterm-timeout"]), }) } - const durationValue = duration(this.manifest.main["sigterm-timeout"], "s") + const durationValue = duration( + fromDuration(this.manifest.main["sigterm-timeout"]), + "s", + ) return durationValue } private async createBackup( @@ -967,7 +970,7 @@ async function updateConfig( const newConfigValue = mutConfigValue[key] if (matchSpec.test(specValue)) { - const updateObject = { spec: null } + const updateObject = { spec: newConfigValue } await updateConfig( effects, manifest, @@ -1001,6 +1004,10 @@ async function updateConfig( manifest, specInterface, ) + if (!serviceInterfaceId) { + mutConfigValue[key] = "" + return + } const filled = await utils .getServiceInterface(effects, { packageId: specValue["package-id"], @@ -1035,8 +1042,16 @@ async function updateConfig( } } function extractServiceInterfaceId(manifest: Manifest, specInterface: string) { - let serviceInterfaceId - const lanConfig = manifest.interfaces[specInterface]?.["lan-config"] || {} - serviceInterfaceId = `${specInterface}-${Object.entries(lanConfig)[0]?.[1]?.internal}` + const internalPort = + Object.entries( + manifest.interfaces[specInterface]?.["lan-config"] || {}, + )[0]?.[1]?.internal || + Object.entries( + manifest.interfaces[specInterface]?.["tor-config"]?.["port-mapping"] || + {}, + )?.[0]?.[1] + + if (!internalPort) return null + const serviceInterfaceId = `${specInterface}-${internalPort}` return serviceInterfaceId } diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/oldEmbassyTypes.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/oldEmbassyTypes.ts index 0d7521626..73d130c9a 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/oldEmbassyTypes.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/oldEmbassyTypes.ts @@ -120,6 +120,10 @@ export type Effects = { /// Returns the body as a json json(): Promise }> + diskUsage(options?: { + volumeId: string + path: string + }): Promise<{ used: number; total: number }> runRsync(options: { srcVolume: string diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/polyfillEffects.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/polyfillEffects.ts index 12c0a58d5..3a8d5f624 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/polyfillEffects.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/polyfillEffects.ts @@ -8,7 +8,8 @@ import { HostSystemStartOs } from "../../HostSystemStartOs" import "isomorphic-fetch" import { Manifest } from "./matchManifest" import { DockerProcedureContainer } from "./DockerProcedureContainer" - +import * as cp from "child_process" +export const execFile = promisify(cp.execFile) export class PolyfillEffects implements oet.Effects { constructor( readonly effects: HostSystemStartOs, @@ -104,7 +105,9 @@ export class PolyfillEffects implements oet.Effects { stderr: x.stderr.toString(), stdout: x.stdout.toString(), })) - .then((x) => (!!x.stderr ? { error: x.stderr } : { result: x.stdout })) + .then((x: any) => + !!x.stderr ? { error: x.stderr } : { result: x.stdout }, + ) } runDaemon(input: { command: string; args?: string[] | undefined }): { wait(): Promise> @@ -163,7 +166,7 @@ export class PolyfillEffects implements oet.Effects { stderr: x.stderr.toString(), stdout: x.stdout.toString(), })) - .then((x) => { + .then((x: any) => { if (!!x.stderr) { throw new Error(x.stderr) } @@ -198,7 +201,7 @@ export class PolyfillEffects implements oet.Effects { stderr: x.stderr.toString(), stdout: x.stdout.toString(), })) - .then((x) => { + .then((x: any) => { if (!!x.stderr) { throw new Error(x.stderr) } @@ -352,7 +355,7 @@ export class PolyfillEffects implements oet.Effects { return String(pid) } const waitPromise = new Promise((resolve, reject) => { - spawned.on("exit", (code) => { + spawned.on("exit", (code: any) => { if (code === 0) { resolve(null) } else { @@ -364,4 +367,55 @@ export class PolyfillEffects implements oet.Effects { const progress = () => Promise.resolve(percentage) return { id, wait, progress } } + async diskUsage( + options?: { volumeId: string; path: string } | undefined, + ): Promise<{ used: number; total: number }> { + const output = await execFile("df", ["--block-size=1", "-P", "/"]) + .then((x: any) => ({ + stderr: x.stderr.toString(), + stdout: x.stdout.toString(), + })) + .then((x: any) => { + if (!!x.stderr) { + throw new Error(x.stderr) + } + return parseDfOutput(x.stdout) + }) + if (!!options) { + const used = await execFile("du", [ + "-s", + "--block-size=1", + "-P", + new Volume(options.volumeId, options.path).path, + ]) + .then((x: any) => ({ + stderr: x.stderr.toString(), + stdout: x.stdout.toString(), + })) + .then((x: any) => { + if (!!x.stderr) { + throw new Error(x.stderr) + } + return Number.parseInt(x.stdout.split(/\s+/)[0]) + }) + return { + ...output, + used, + } + } + return output + } +} + +function parseDfOutput(output: string): { used: number; total: number } { + const lines = output + .split("\n") + .filter((x) => x.length) + .map((x) => x.split(/\s+/)) + const index = lines.splice(0, 1)[0].map((x) => x.toLowerCase()) + const usedIndex = index.indexOf("used") + const availableIndex = index.indexOf("available") + const used = lines.map((x) => Number.parseInt(x[usedIndex]))[0] || 0 + const total = lines.map((x) => Number.parseInt(x[availableIndex]))[0] || 0 + return { used, total } } diff --git a/container-runtime/src/Models/DockerProcedure.ts b/container-runtime/src/Models/DockerProcedure.ts index 91ae73b5f..20c8145ab 100644 --- a/container-runtime/src/Models/DockerProcedure.ts +++ b/container-runtime/src/Models/DockerProcedure.ts @@ -8,7 +8,9 @@ import { literals, number, Parser, + some, } from "ts-matches" +import { matchDuration } from "./Duration" const VolumeId = string const Path = string @@ -31,7 +33,7 @@ export const matchDockerProcedure = object( "toml", "toml-pretty", ), - "sigterm-timeout": number, + "sigterm-timeout": some(number, matchDuration), inject: boolean, }, ["io-format", "sigterm-timeout", "system", "args", "inject", "mounts"], diff --git a/container-runtime/src/Models/Duration.ts b/container-runtime/src/Models/Duration.ts index 75154c782..5f61c362a 100644 --- a/container-runtime/src/Models/Duration.ts +++ b/container-runtime/src/Models/Duration.ts @@ -1,6 +1,30 @@ -export type TimeUnit = "d" | "h" | "s" | "ms" +import { string } from "ts-matches" + +export type TimeUnit = "d" | "h" | "s" | "ms" | "m" | "µs" | "ns" export type Duration = `${number}${TimeUnit}` +const durationRegex = /^([0-9]*(\.[0-9]+)?)(ns|µs|ms|s|m|d)$/ + +export const matchDuration = string.refine(isDuration) +export function isDuration(value: string): value is Duration { + return durationRegex.test(value) +} + export function duration(timeValue: number, timeUnit: TimeUnit = "s") { return `${timeValue > 0 ? timeValue : 0}${timeUnit}` as Duration } +const unitsToSeconds: Record = { + ns: 1e-9, + µs: 1e-6, + ms: 0.001, + s: 1, + m: 60, + h: 3600, + d: 86400, +} + +export function fromDuration(duration: Duration | number): number { + if (typeof duration === "number") return duration + const [, num, , unit] = duration.match(durationRegex) || [] + return Number(num) * unitsToSeconds[unit] +}