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
This commit is contained in:
Jade
2024-06-07 12:17:45 -06:00
committed by GitHub
parent 2c12af5af8
commit 4d6cb091cc
5 changed files with 113 additions and 14 deletions

View File

@@ -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
}

View File

@@ -120,6 +120,10 @@ export type Effects = {
/// Returns the body as a json
json(): Promise<unknown>
}>
diskUsage(options?: {
volumeId: string
path: string
}): Promise<{ used: number; total: number }>
runRsync(options: {
srcVolume: string

View File

@@ -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<oet.ResultType<string>>
@@ -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<null>((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 }
}

View File

@@ -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"],

View File

@@ -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<string, number> = {
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]
}