systemd-ts 261.2.0 → 261.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +502 -0
- package/dist/{index-gV6lNweL.d.mts → index-6lsBWH4h.d.mts} +8 -8
- package/dist/index-6lsBWH4h.d.mts.map +1 -0
- package/dist/index.d.mts +1 -1
- package/dist/main-DqJ5f7Al.mjs.map +1 -1
- package/dist/test.d.mts +15 -15
- package/dist/test.d.mts.map +1 -1
- package/dist/test.mjs +4 -4
- package/dist/test.mjs.map +1 -1
- package/package.json +6 -6
- package/src/main/types.ts +10 -7
- package/dist/index-gV6lNweL.d.mts.map +0 -1
package/dist/test.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test.mjs","names":["delay","shellQuote","shellQuote"],"sources":["../src/test/host.ts","../src/test/sandbox.ts","../src/test/harness.ts"],"sourcesContent":["import { execFile, spawn, type ChildProcessWithoutNullStreams } from \"node:child_process\";\nimport { createHash, randomUUID } from \"node:crypto\";\nimport { setTimeout as delay } from \"node:timers/promises\";\nimport { fileURLToPath } from \"node:url\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\nconst commandMaxBufferBytes = 16 * 1024 * 1024;\nconst dockerBuildTimeoutMs = 5 * 60 * 1_000;\nconst dockerExecTimeoutMs = 60 * 1_000;\nconst guestCommandTimeoutMs = 10 * 1_000;\nconst repoRoot = fileURLToPath(new URL(`../../../../`, import.meta.url));\nconst dockerfilePath = fileURLToPath(new URL(`./systemd-container.Dockerfile`, import.meta.url));\nconst selectedBackendName = resolveBackendName(process.env[`SYSTEMD_TS_TEST_HOST_BACKEND`]);\nconst colimaHome = process.env[`COLIMA_HOME`] ?? `${repoRoot}.colima`;\nconst limaHome = process.env[`LIMA_HOME`] ?? `${colimaHome}/_lima`;\nconst colimaProfile = process.env[`COLIMA_PROFILE`] ?? `systemd-ts`;\nconst dockerStateRoot = process.env[`SYSTEMD_TS_TEST_HOST_ROOT`] ?? `${repoRoot}.docker`;\nconst dockerImage = process.env[`SYSTEMD_TS_TEST_DOCKER_IMAGE`] ?? `systemd-ts-test-host:local`;\nconst dockerUser = process.env[`SYSTEMD_TS_TEST_DOCKER_USER`] ?? `runner`;\nconst dockerConfiguredUserId = process.env[`SYSTEMD_TS_TEST_DOCKER_UID`];\nconst dockerContainer =\n process.env[`SYSTEMD_TS_TEST_DOCKER_CONTAINER`] ?? defaultDockerContainerName();\n\nexport interface TestHostInfo {\n readonly backend: TestHostBackendName;\n readonly colimaHome?: string;\n readonly colimaProfile?: string;\n readonly limaHome?: string;\n readonly repoRoot: string;\n readonly stateRoot: string;\n}\n\ntype TestHostBackendName = `colima` | `docker`;\n\ninterface TestHostBackend {\n readonly info: TestHostInfo;\n ensureHost(): Promise<void>;\n createGuestShellProcess(): ChildProcessWithoutNullStreams;\n runIsolatedCommand(script: string): Promise<string>;\n}\n\nlet ensuredHost: Promise<TestHostInfo> | undefined;\nlet guestShell: GuestShell | undefined;\n\nexport function getTestHostInfo(): TestHostInfo {\n return backend.info;\n}\n\nexport function ensureTestHost(): Promise<TestHostInfo> {\n ensuredHost ??= ensureTestHostInner();\n return ensuredHost;\n}\n\nexport function closeTestHost(): void {\n closeGuestShell(`warmup complete`);\n}\n\nexport async function runGuestCommand(script: string): Promise<string> {\n const shell = getGuestShell();\n return shell.run(script);\n}\n\nexport async function runIsolatedGuestCommand(script: string): Promise<string> {\n return backend.runIsolatedCommand(script);\n}\n\nasync function ensureTestHostInner(): Promise<TestHostInfo> {\n logTestHost(`Ensuring ${backend.info.backend} test host`);\n await backend.ensureHost();\n logTestHost(`Waiting for systemd --user readiness`);\n await waitForUserSystemd();\n logTestHost(`Test host ready`);\n return getTestHostInfo();\n}\n\nasync function waitForUserSystemd(): Promise<void> {\n for (let attempt = 0; attempt < 30; attempt += 1) {\n const state = await runGuestCommandWithTimeout(\n `printf 'pid1=%s\\n' \"$(ps -p 1 -o comm=)\"; systemctl --user is-system-running || true; printf 'xdg=%s\\n' \"$XDG_RUNTIME_DIR\"`,\n guestCommandTimeoutMs,\n );\n\n if (\n state.includes(`pid1=systemd`) &&\n state.includes(`xdg=/run/user/`) &&\n [`running`, `degraded`].some((status) => state.includes(status))\n ) {\n logTestHost(\n `systemd --user ready after ${attempt + 1} attempt${attempt === 0 ? `` : `s`}: ${summarizeSystemdState(state)}`,\n );\n return;\n }\n\n if (attempt === 0 || (attempt + 1) % 5 === 0) {\n logTestHost(\n `systemd --user not ready yet (${attempt + 1}/30): ${summarizeSystemdState(state)}`,\n );\n }\n\n await delay(1_000);\n }\n\n throw new Error(`Timed out waiting for systemd --user in the ${backend.info.backend} guest`);\n}\n\nfunction getGuestShell(): GuestShell {\n guestShell ??= new GuestShell(\n () => backend.createGuestShellProcess(),\n () => {\n if (guestShell !== undefined) {\n guestShell = undefined;\n }\n },\n );\n return guestShell;\n}\n\nfunction closeGuestShell(reason: string): void {\n if (guestShell === undefined) {\n return;\n }\n\n logTestHost(`Resetting guest shell: ${reason}`);\n guestShell.close();\n guestShell = undefined;\n}\n\nasync function runGuestCommandWithTimeout(script: string, timeoutMs: number): Promise<string> {\n const shell = getGuestShell();\n try {\n return await shell.run(script, timeoutMs);\n } catch (error) {\n if (error instanceof Error && error.message.includes(`timed out`)) {\n closeGuestShell(error.message);\n }\n\n throw error;\n }\n}\n\nfunction mergeOutput(stdout: string, stderr: string): string {\n return [stdout, stderr].filter((output) => output.length > 0).join(`\\n`);\n}\n\nfunction summarizeSystemdState(state: string): string {\n return state\n .split(`\\n`)\n .map((line) => line.trim())\n .filter((line) => line.length > 0)\n .join(`; `);\n}\n\nfunction inferBackendName(): TestHostBackendName {\n if (process.platform === `darwin`) {\n return `colima`;\n }\n\n if (process.platform === `linux`) {\n return `docker`;\n }\n\n throw new Error(\n `Unsupported test host platform: ${process.platform}. Set SYSTEMD_TS_TEST_HOST_BACKEND explicitly if you have a compatible backend.`,\n );\n}\n\nfunction resolveBackendName(value: string | undefined): TestHostBackendName {\n if (value === undefined) {\n return inferBackendName();\n }\n\n if (value === `colima` || value === `docker`) {\n return value;\n }\n\n throw new Error(\n `Unsupported SYSTEMD_TS_TEST_HOST_BACKEND value: ${value}. Expected \"colima\" or \"docker\".`,\n );\n}\n\nfunction defaultDockerContainerName(): string {\n const digest = createHash(`sha256`).update(repoRoot).digest(`hex`).slice(0, 12);\n return `systemd-ts-${digest}`;\n}\n\nfunction logTestHost(message: string): void {\n console.info(`[systemd-ts:test-host] ${message}`);\n}\n\nfunction createBackend(name: TestHostBackendName): TestHostBackend {\n switch (name) {\n case `colima`:\n return new ColimaBackend();\n case `docker`:\n return new DockerBackend();\n }\n}\n\nclass ColimaBackend implements TestHostBackend {\n public readonly info: TestHostInfo = {\n backend: `colima`,\n colimaHome,\n colimaProfile,\n limaHome,\n repoRoot,\n stateRoot: colimaHome,\n };\n\n public async ensureHost(): Promise<void> {\n if (await this.isRunning()) {\n return;\n }\n\n console.info(`Starting repo-local Colima host for integration tests...`);\n await this.execColima([\n `start`,\n `--cpu`,\n `2`,\n `--memory`,\n `4`,\n `--disk`,\n `30`,\n `--runtime`,\n `containerd`,\n ]);\n }\n\n public createGuestShellProcess(): ChildProcessWithoutNullStreams {\n return spawn(`colima`, [`ssh`, `--`, `bash`, `--noprofile`, `--norc`, `-s`], {\n env: this.hostEnv,\n stdio: [`pipe`, `pipe`, `pipe`],\n });\n }\n\n public async runIsolatedCommand(script: string): Promise<string> {\n return this.execColima([`ssh`, `--`, `bash`, `-lc`, script]);\n }\n\n private readonly hostEnv = {\n ...process.env,\n COLIMA_HOME: colimaHome,\n COLIMA_PROFILE: colimaProfile,\n LIMA_HOME: limaHome,\n };\n\n private async isRunning(): Promise<boolean> {\n try {\n const status = await this.execColima([`status`]);\n return status.includes(`is running`);\n } catch {\n return false;\n }\n }\n\n private async execColima(args: readonly string[]): Promise<string> {\n const result = await execFileAsync(`colima`, args, {\n env: this.hostEnv,\n maxBuffer: commandMaxBufferBytes,\n });\n return mergeOutput(result.stdout, result.stderr);\n }\n}\n\nclass DockerBackend implements TestHostBackend {\n public readonly info: TestHostInfo = {\n backend: `docker`,\n repoRoot,\n stateRoot: dockerStateRoot,\n };\n\n private runtimeInfo:\n | {\n readonly home: string;\n readonly runtimeDir: string;\n readonly userId: number;\n }\n | undefined;\n\n public async ensureHost(): Promise<void> {\n logTestHost(`Building Docker test host image ${dockerImage}`);\n await this.execDocker(\n [`build`, `--quiet`, `--tag`, dockerImage, `--file`, dockerfilePath, repoRoot],\n { timeout: dockerBuildTimeoutMs },\n );\n logTestHost(`Docker test host image ready`);\n\n if (!(await this.isContainerRunning())) {\n await this.removeExistingContainer();\n logTestHost(`Starting Docker test host container ${dockerContainer}`);\n await this.execDocker([\n `run`,\n `--detach`,\n `--name`,\n dockerContainer,\n `--hostname`,\n `systemd-ts-test-host`,\n `--privileged`,\n `--cgroupns=host`,\n `--tmpfs`,\n `/run`,\n `--tmpfs`,\n `/run/lock`,\n `--volume`,\n `/sys/fs/cgroup:/sys/fs/cgroup:rw`,\n `--volume`,\n `${repoRoot}:${repoRoot}`,\n `--volume`,\n `${dockerStateRoot}:${dockerStateRoot}`,\n `--workdir`,\n repoRoot,\n dockerImage,\n ]);\n logTestHost(`Docker test host container started`);\n } else {\n logTestHost(`Reusing running Docker test host container ${dockerContainer}`);\n }\n\n await this.ensureUserSession();\n }\n\n public createGuestShellProcess(): ChildProcessWithoutNullStreams {\n const runtimeInfo = this.requireRuntimeInfo();\n return spawn(\n `docker`,\n [\n `exec`,\n `--interactive`,\n `--user`,\n dockerUser,\n `--env`,\n `HOME=${runtimeInfo.home}`,\n `--env`,\n `XDG_RUNTIME_DIR=${runtimeInfo.runtimeDir}`,\n `--env`,\n `DBUS_SESSION_BUS_ADDRESS=unix:path=${runtimeInfo.runtimeDir}/bus`,\n dockerContainer,\n `bash`,\n `--noprofile`,\n `--norc`,\n `-s`,\n ],\n {\n stdio: [`pipe`, `pipe`, `pipe`],\n },\n );\n }\n\n public async runIsolatedCommand(script: string): Promise<string> {\n const runtimeInfo = this.requireRuntimeInfo();\n const result = await this.execDockerAsUser(runtimeInfo, [`bash`, `-lc`, script]);\n\n return mergeOutput(result.stdout, result.stderr);\n }\n\n private async isContainerRunning(): Promise<boolean> {\n try {\n const status = await this.execDocker([\n `inspect`,\n `--format`,\n `{{.State.Running}}`,\n dockerContainer,\n ]);\n return status.trim() === `true`;\n } catch {\n return false;\n }\n }\n\n private async removeExistingContainer(): Promise<void> {\n try {\n await this.execDocker([`rm`, `--force`, dockerContainer]);\n } catch {\n // Ignore missing containers so the first run can proceed.\n }\n }\n\n private async ensureUserSession(): Promise<void> {\n const runtimeInfo = await this.resolveRuntimeInfo();\n logTestHost(\n `Ensuring user session for ${dockerUser} (uid=${runtimeInfo.userId}, home=${runtimeInfo.home})`,\n );\n await this.execDocker(\n [\n `exec`,\n dockerContainer,\n `bash`,\n `-lc`,\n [\n `mkdir -p ${shellQuote(dockerStateRoot)}`,\n `chown ${runtimeInfo.userId}:${runtimeInfo.userId} ${shellQuote(dockerStateRoot)}`,\n `loginctl enable-linger ${shellQuote(dockerUser)}`,\n `systemctl start user@${runtimeInfo.userId}.service`,\n ].join(`\\n`),\n ],\n { timeout: dockerExecTimeoutMs },\n );\n await this.execDockerAsUser(runtimeInfo, [\n `bash`,\n `-lc`,\n `mkdir -p ${shellQuote(`${dockerStateRoot}/tests`)}`,\n ]);\n logTestHost(`User session started for ${dockerUser}`);\n }\n\n private async execDocker(\n args: readonly string[],\n options?: {\n readonly timeout?: number;\n },\n ): Promise<string> {\n const result = await execFileAsync(`docker`, args, {\n maxBuffer: commandMaxBufferBytes,\n timeout: options?.timeout,\n });\n return mergeOutput(result.stdout, result.stderr);\n }\n\n private execDockerAsUser(\n runtimeInfo: {\n readonly home: string;\n readonly runtimeDir: string;\n readonly userId: number;\n },\n command: readonly string[],\n ) {\n return execFileAsync(\n `docker`,\n [\n `exec`,\n `--user`,\n dockerUser,\n `--env`,\n `HOME=${runtimeInfo.home}`,\n `--env`,\n `XDG_RUNTIME_DIR=${runtimeInfo.runtimeDir}`,\n `--env`,\n `DBUS_SESSION_BUS_ADDRESS=unix:path=${runtimeInfo.runtimeDir}/bus`,\n dockerContainer,\n ...command,\n ],\n { maxBuffer: commandMaxBufferBytes },\n );\n }\n\n private requireRuntimeInfo(): {\n readonly home: string;\n readonly runtimeDir: string;\n readonly userId: number;\n } {\n if (this.runtimeInfo === undefined) {\n throw new Error(`Docker test host runtime info is unavailable before ensureHost() completes`);\n }\n\n return this.runtimeInfo;\n }\n\n private async resolveRuntimeInfo(): Promise<{\n readonly home: string;\n readonly runtimeDir: string;\n readonly userId: number;\n }> {\n if (this.runtimeInfo !== undefined) {\n return this.runtimeInfo;\n }\n\n const userId =\n dockerConfiguredUserId === undefined\n ? await this.lookupUserId()\n : Number(dockerConfiguredUserId);\n const home = await this.lookupUserHome();\n\n this.runtimeInfo = {\n home,\n runtimeDir: `/run/user/${userId}`,\n userId,\n };\n\n return this.runtimeInfo;\n }\n\n private async lookupUserHome(): Promise<string> {\n logTestHost(`Resolving home directory for Docker test user ${dockerUser}`);\n const output = await this.execDocker([\n `exec`,\n dockerContainer,\n `bash`,\n `-lc`,\n `getent passwd ${shellQuote(dockerUser)} | cut -d: -f6`,\n ]);\n\n const home = output.trim();\n if (home.length === 0) {\n throw new Error(`Could not resolve home directory for Docker test user ${dockerUser}`);\n }\n\n logTestHost(`Resolved home directory for ${dockerUser}: ${home}`);\n return home;\n }\n\n private async lookupUserId(): Promise<number> {\n logTestHost(`Resolving uid for Docker test user ${dockerUser}`);\n const output = await this.execDocker([\n `exec`,\n dockerContainer,\n `bash`,\n `-lc`,\n `id -u ${shellQuote(dockerUser)}`,\n ]);\n\n const parsed = Number(output.trim());\n if (!Number.isInteger(parsed)) {\n throw new Error(`Could not resolve uid for Docker test user ${dockerUser}`);\n }\n\n logTestHost(`Resolved uid for ${dockerUser}: ${parsed}`);\n return parsed;\n }\n}\n\nclass GuestShell {\n private readonly process: ChildProcessWithoutNullStreams;\n private readonly onExitCleanup: () => void;\n private closed = false;\n private buffer = ``;\n private current:\n | {\n marker: string;\n timeoutId: NodeJS.Timeout | undefined;\n reject: (error: Error) => void;\n resolve: (output: string) => void;\n }\n | undefined;\n private readonly queue: Array<{\n script: string;\n reject: (error: Error) => void;\n resolve: (output: string) => void;\n timeoutMs: number | undefined;\n }> = [];\n\n public constructor(\n createProcess: () => ChildProcessWithoutNullStreams,\n onExitCleanup: () => void,\n ) {\n this.process = createProcess();\n this.onExitCleanup = onExitCleanup;\n this.process.stdout.setEncoding(`utf8`);\n this.process.stderr.setEncoding(`utf8`);\n this.process.stdout.on(`data`, (chunk: string) => this.onData(chunk));\n this.process.stderr.on(`data`, (chunk: string) => this.onData(chunk));\n this.process.on(`exit`, (code, signal) => {\n const reason = new Error(\n `Persistent guest shell exited unexpectedly (code=${code ?? `null`}, signal=${signal ?? `null`})`,\n );\n this.failCurrent(reason);\n while (this.queue.length > 0) {\n this.queue.shift()?.reject(reason);\n }\n this.onExitCleanup();\n });\n\n process.once(`exit`, () => {\n this.process.kill();\n });\n }\n\n public run(script: string, timeoutMs?: number): Promise<string> {\n return new Promise((resolve, reject) => {\n this.queue.push({ script, resolve, reject, timeoutMs });\n this.drain();\n });\n }\n\n public close(): void {\n if (this.closed) {\n return;\n }\n\n this.closed = true;\n this.process.kill();\n }\n\n private drain(): void {\n if (this.current !== undefined) {\n return;\n }\n\n const next = this.queue.shift();\n if (next === undefined) {\n return;\n }\n\n const marker = `__SYSTEMD_TS_END_${randomUUID()}__`;\n const timeoutId =\n next.timeoutMs === undefined\n ? undefined\n : setTimeout(() => {\n if (this.current?.marker !== marker) {\n return;\n }\n\n const reason = new Error(`Guest command timed out after ${next.timeoutMs}ms`);\n this.failCurrent(reason);\n this.close();\n }, next.timeoutMs);\n this.current = {\n marker,\n timeoutId,\n reject: next.reject,\n resolve: next.resolve,\n };\n\n this.buffer = ``;\n const command = `(\n${next.script}\n) 2>&1\nstatus=$?\nprintf '\\\\n${marker}:%s\\\\n' \"$status\"\n`;\n\n this.process.stdin.write(command);\n }\n\n private failCurrent(error: Error): void {\n const current = this.current;\n this.current = undefined;\n if (current?.timeoutId !== undefined) {\n clearTimeout(current.timeoutId);\n }\n current?.reject(error);\n }\n\n private onData(chunk: string): void {\n if (this.current === undefined) {\n return;\n }\n\n this.buffer += chunk;\n const markerIndex = this.buffer.indexOf(this.current.marker);\n if (markerIndex === -1) {\n return;\n }\n\n const output = this.buffer.slice(0, markerIndex).replace(/\\n$/, ``);\n const statusMatch = this.buffer\n .slice(markerIndex)\n .match(/^__SYSTEMD_TS_END_[^:]+__:(\\d+)\\r?\\n?/u);\n\n if (statusMatch === null) {\n return;\n }\n\n const status = Number(statusMatch[1]);\n const remainder = this.buffer.slice(markerIndex + statusMatch[0].length);\n const current = this.current;\n this.current = undefined;\n this.buffer = remainder;\n if (current.timeoutId !== undefined) {\n clearTimeout(current.timeoutId);\n }\n\n if (status === 0) {\n current.resolve(output);\n } else {\n current.reject(new Error(output.length > 0 ? output : `Guest command failed`));\n }\n\n this.drain();\n }\n}\n\nfunction shellQuote(value: string): string {\n return `'${value.replaceAll(`'`, `'\\\\''`)}'`;\n}\n\nconst backend = createBackend(selectedBackendName);\n","import { randomUUID } from \"node:crypto\";\n\nimport { getTestHostInfo, runGuestCommand } from \"./host.ts\";\n\nexport interface TestSandbox {\n readonly id: string;\n readonly linkedUnitDir: string;\n readonly namePrefix: string;\n readonly rootDir: string;\n readonly workDir: string;\n}\n\nexport interface DestroyTestSandboxOptions {\n readonly noisy?: boolean;\n}\n\nlet currentSandbox: TestSandbox | undefined;\n\nexport async function createTestSandbox(testName?: string): Promise<TestSandbox> {\n const id = randomUUID().slice(0, 8);\n const slug = slugify(testName ?? `sandbox`);\n const namePrefix = `systemd-ts-${slug}-${id}`;\n const rootDir = `${getTestHostInfo().stateRoot}/tests/${namePrefix}`;\n const linkedUnitDir = `${rootDir}/linked-units`;\n const workDir = `${rootDir}/work`;\n\n await runGuestCommand(\n `set -euo pipefail\nrm -rf ${shellQuote(rootDir)}\nmkdir -p ${shellQuote(linkedUnitDir)} ${shellQuote(workDir)}`,\n );\n\n currentSandbox = {\n id,\n linkedUnitDir,\n namePrefix,\n rootDir,\n workDir,\n };\n\n return currentSandbox;\n}\n\nexport async function destroyCurrentTestSandbox(): Promise<void> {\n if (currentSandbox === undefined) {\n return;\n }\n\n const sandbox = currentSandbox;\n currentSandbox = undefined;\n\n const unitsOutput = await runGuestCommand(\n `systemctl --user list-unit-files --all --no-legend ${shellQuote(`${sandbox.namePrefix}*`)} 2>/dev/null | awk '{print $1}' || true`,\n );\n\n const units = unitsOutput\n .split(`\\n`)\n .map((unit) => unit.trim())\n .filter((unit) => unit.length > 0);\n\n if (units.length > 0) {\n await runGuestCommand(`systemctl --user stop ${units.map(shellQuote).join(` `)} || true`);\n\n await runGuestCommand(`systemctl --user disable ${units.map(shellQuote).join(` `)} || true`);\n\n await runGuestCommand(\n `systemctl --user reset-failed ${units.map(shellQuote).join(` `)} || true`,\n );\n }\n\n await runGuestCommand(`systemctl --user daemon-reload || true`);\n\n await runGuestCommand(`rm -rf ${shellQuote(sandbox.rootDir)}`);\n}\n\nexport function useCurrentTestSandbox(): TestSandbox {\n if (currentSandbox === undefined) {\n throw new Error(`No active test sandbox is available`);\n }\n\n return currentSandbox;\n}\n\nfunction shellQuote(value: string): string {\n return `'${value.replaceAll(`'`, `'\\\\''`)}'`;\n}\n\nfunction slugify(value: string): string {\n return value\n .toLowerCase()\n .replaceAll(/[^a-z0-9]+/g, `-`)\n .replaceAll(/^-+|-+$/g, ``)\n .slice(0, 32);\n}\n","import {\n Systemd,\n type CommandExecutionOptions,\n type CommandExecutor,\n type CommandOutput,\n} from \"../main/index.ts\";\n\nimport { runGuestCommand, runIsolatedGuestCommand } from \"./host.ts\";\nimport { useCurrentTestSandbox } from \"./sandbox.ts\";\n\n/**\n * Creates a command executor that runs argv-style commands inside the current\n * test host guest shell.\n */\nexport function createGuestCommandExecutor(\n runCommand: (script: string) => Promise<string>,\n): CommandExecutor {\n return async (\n command: string,\n args: readonly string[],\n options: CommandExecutionOptions = {},\n ): Promise<CommandOutput> => {\n try {\n const stdout = await runCommand(buildGuestCommand(command, args, options));\n return {\n stderr: ``,\n stdout,\n };\n } catch (cause) {\n throw enrichGuestCommandError(cause);\n }\n };\n}\n\n/**\n * Executor that reuses the warm guest shell for fast sandbox-backed tests.\n */\nexport const guestCommandExecutor: CommandExecutor = createGuestCommandExecutor(runGuestCommand);\n\n/**\n * Executor that runs each guest command in a fresh isolated guest shell.\n */\nexport const isolatedGuestCommandExecutor: CommandExecutor =\n createGuestCommandExecutor(runIsolatedGuestCommand);\n\n/**\n * Creates a `Systemd` instance scoped to the active test sandbox and backed by\n * the warm guest-shell executor.\n */\nexport function sandboxSystemd(): Systemd {\n const sandbox = useCurrentTestSandbox();\n return new Systemd({\n executor: guestCommandExecutor,\n linkUnits: true,\n scope: `user`,\n unitDir: sandbox.linkedUnitDir,\n });\n}\n\n/**\n * Creates a `Systemd` instance scoped to the active test sandbox and backed by\n * a fresh guest shell per command.\n */\nexport function isolatedSandboxSystemd(): Systemd {\n const sandbox = useCurrentTestSandbox();\n return new Systemd({\n executor: isolatedGuestCommandExecutor,\n linkUnits: true,\n scope: `user`,\n unitDir: sandbox.linkedUnitDir,\n });\n}\n\nfunction buildGuestCommand(\n command: string,\n args: readonly string[],\n options: CommandExecutionOptions,\n): string {\n const envArgs =\n options.env === undefined\n ? []\n : Object.entries(options.env).flatMap(([key, value]) =>\n value === undefined ? [`-u`, key] : [`${key}=${value}`],\n );\n\n return [`env`, ...envArgs, command, ...args].map((part) => shellQuote(part)).join(` `);\n}\n\nfunction enrichGuestCommandError(cause: unknown): Error {\n if (cause instanceof Error) {\n return Object.assign(cause, {\n code: 1,\n stderr: ``,\n stdout: cause.message,\n });\n }\n\n return Object.assign(new Error(`Guest command failed`), {\n code: 1,\n stderr: ``,\n stdout: ``,\n });\n}\n\nfunction shellQuote(value: string): string {\n return `'${value.replaceAll(`'`, `'\\\\''`)}'`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"test.mjs","names":["delay","shellQuote","shellQuote"],"sources":["../src/test/host.ts","../src/test/sandbox.ts","../src/test/harness.ts"],"sourcesContent":["import { execFile, spawn, type ChildProcessWithoutNullStreams } from \"node:child_process\";\nimport { createHash, randomUUID } from \"node:crypto\";\nimport { setTimeout as delay } from \"node:timers/promises\";\nimport { fileURLToPath } from \"node:url\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\nconst commandMaxBufferBytes = 16 * 1024 * 1024;\nconst dockerBuildTimeoutMs = 5 * 60 * 1_000;\nconst dockerExecTimeoutMs = 60 * 1_000;\nconst guestCommandTimeoutMs = 10 * 1_000;\nconst repoRoot = fileURLToPath(new URL(`../../../../`, import.meta.url));\nconst dockerfilePath = fileURLToPath(new URL(`./systemd-container.Dockerfile`, import.meta.url));\nconst selectedBackendName = resolveBackendName(process.env[`SYSTEMD_TS_TEST_HOST_BACKEND`]);\nconst colimaHome = process.env[`COLIMA_HOME`] ?? `${repoRoot}.colima`;\nconst limaHome = process.env[`LIMA_HOME`] ?? `${colimaHome}/_lima`;\nconst colimaProfile = process.env[`COLIMA_PROFILE`] ?? `systemd-ts`;\nconst dockerStateRoot = process.env[`SYSTEMD_TS_TEST_HOST_ROOT`] ?? `${repoRoot}.docker`;\nconst dockerImage = process.env[`SYSTEMD_TS_TEST_DOCKER_IMAGE`] ?? `systemd-ts-test-host:local`;\nconst dockerUser = process.env[`SYSTEMD_TS_TEST_DOCKER_USER`] ?? `runner`;\nconst dockerConfiguredUserId = process.env[`SYSTEMD_TS_TEST_DOCKER_UID`];\nconst dockerContainer =\n process.env[`SYSTEMD_TS_TEST_DOCKER_CONTAINER`] ?? defaultDockerContainerName();\n\nexport interface TestHostInfo {\n readonly backend: TestHostBackendName;\n readonly colimaHome?: string;\n readonly colimaProfile?: string;\n readonly limaHome?: string;\n readonly repoRoot: string;\n readonly stateRoot: string;\n}\n\ntype TestHostBackendName = `colima` | `docker`;\n\ninterface TestHostBackend {\n readonly info: TestHostInfo;\n ensureHost(): Promise<void>;\n createGuestShellProcess(): ChildProcessWithoutNullStreams;\n runIsolatedCommand(script: string): Promise<string>;\n}\n\nlet ensuredHost: Promise<TestHostInfo> | undefined;\nlet guestShell: GuestShell | undefined;\n\nexport function getTestHostInfo(): TestHostInfo {\n return backend.info;\n}\n\nexport function ensureTestHost(): Promise<TestHostInfo> {\n ensuredHost ??= ensureTestHostInner();\n return ensuredHost;\n}\n\nexport function closeTestHost(): void {\n closeGuestShell(`warmup complete`);\n}\n\nexport async function runGuestCommand(script: string): Promise<string> {\n const shell = getGuestShell();\n return shell.run(script);\n}\n\nexport async function runIsolatedGuestCommand(script: string): Promise<string> {\n return backend.runIsolatedCommand(script);\n}\n\nasync function ensureTestHostInner(): Promise<TestHostInfo> {\n logTestHost(`Ensuring ${backend.info.backend} test host`);\n await backend.ensureHost();\n logTestHost(`Waiting for systemd --user readiness`);\n await waitForUserSystemd();\n logTestHost(`Test host ready`);\n return getTestHostInfo();\n}\n\nasync function waitForUserSystemd(): Promise<void> {\n for (let attempt = 0; attempt < 30; attempt += 1) {\n const state = await runGuestCommandWithTimeout(\n `printf 'pid1=%s\\n' \"$(ps -p 1 -o comm=)\"; systemctl --user is-system-running || true; printf 'xdg=%s\\n' \"$XDG_RUNTIME_DIR\"`,\n guestCommandTimeoutMs,\n );\n\n if (\n state.includes(`pid1=systemd`) &&\n state.includes(`xdg=/run/user/`) &&\n [`running`, `degraded`].some((status) => state.includes(status))\n ) {\n logTestHost(\n `systemd --user ready after ${attempt + 1} attempt${attempt === 0 ? `` : `s`}: ${summarizeSystemdState(state)}`,\n );\n return;\n }\n\n if (attempt === 0 || (attempt + 1) % 5 === 0) {\n logTestHost(\n `systemd --user not ready yet (${attempt + 1}/30): ${summarizeSystemdState(state)}`,\n );\n }\n\n await delay(1_000);\n }\n\n throw new Error(`Timed out waiting for systemd --user in the ${backend.info.backend} guest`);\n}\n\nfunction getGuestShell(): GuestShell {\n guestShell ??= new GuestShell(\n () => backend.createGuestShellProcess(),\n () => {\n if (guestShell !== undefined) {\n guestShell = undefined;\n }\n },\n );\n return guestShell;\n}\n\nfunction closeGuestShell(reason: string): void {\n if (guestShell === undefined) {\n return;\n }\n\n logTestHost(`Resetting guest shell: ${reason}`);\n guestShell.close();\n guestShell = undefined;\n}\n\nasync function runGuestCommandWithTimeout(script: string, timeoutMs: number): Promise<string> {\n const shell = getGuestShell();\n try {\n return await shell.run(script, timeoutMs);\n } catch (error) {\n if (error instanceof Error && error.message.includes(`timed out`)) {\n closeGuestShell(error.message);\n }\n\n throw error;\n }\n}\n\nfunction mergeOutput(stdout: string, stderr: string): string {\n return [stdout, stderr].filter((output) => output.length > 0).join(`\\n`);\n}\n\nfunction summarizeSystemdState(state: string): string {\n return state\n .split(`\\n`)\n .map((line) => line.trim())\n .filter((line) => line.length > 0)\n .join(`; `);\n}\n\nfunction inferBackendName(): TestHostBackendName {\n if (process.platform === `darwin`) {\n return `colima`;\n }\n\n if (process.platform === `linux`) {\n return `docker`;\n }\n\n throw new Error(\n `Unsupported test host platform: ${process.platform}. Set SYSTEMD_TS_TEST_HOST_BACKEND explicitly if you have a compatible backend.`,\n );\n}\n\nfunction resolveBackendName(value: string | undefined): TestHostBackendName {\n if (value === undefined) {\n return inferBackendName();\n }\n\n if (value === `colima` || value === `docker`) {\n return value;\n }\n\n throw new Error(\n `Unsupported SYSTEMD_TS_TEST_HOST_BACKEND value: ${value}. Expected \"colima\" or \"docker\".`,\n );\n}\n\nfunction defaultDockerContainerName(): string {\n const digest = createHash(`sha256`).update(repoRoot).digest(`hex`).slice(0, 12);\n return `systemd-ts-${digest}`;\n}\n\nfunction logTestHost(message: string): void {\n console.info(`[systemd-ts:test-host] ${message}`);\n}\n\nfunction createBackend(name: TestHostBackendName): TestHostBackend {\n switch (name) {\n case `colima`:\n return new ColimaBackend();\n case `docker`:\n return new DockerBackend();\n }\n}\n\nclass ColimaBackend implements TestHostBackend {\n public readonly info: TestHostInfo = {\n backend: `colima`,\n colimaHome,\n colimaProfile,\n limaHome,\n repoRoot,\n stateRoot: colimaHome,\n };\n\n public async ensureHost(): Promise<void> {\n if (await this.isRunning()) {\n return;\n }\n\n console.info(`Starting repo-local Colima host for integration tests...`);\n await this.execColima([\n `start`,\n `--cpu`,\n `2`,\n `--memory`,\n `4`,\n `--disk`,\n `30`,\n `--runtime`,\n `containerd`,\n ]);\n }\n\n public createGuestShellProcess(): ChildProcessWithoutNullStreams {\n return spawn(`colima`, [`ssh`, `--`, `bash`, `--noprofile`, `--norc`, `-s`], {\n env: this.hostEnv,\n stdio: [`pipe`, `pipe`, `pipe`],\n });\n }\n\n public async runIsolatedCommand(script: string): Promise<string> {\n return this.execColima([`ssh`, `--`, `bash`, `-lc`, script]);\n }\n\n private readonly hostEnv = {\n ...process.env,\n COLIMA_HOME: colimaHome,\n COLIMA_PROFILE: colimaProfile,\n LIMA_HOME: limaHome,\n };\n\n private async isRunning(): Promise<boolean> {\n try {\n const status = await this.execColima([`status`]);\n return status.includes(`is running`);\n } catch {\n return false;\n }\n }\n\n private async execColima(args: readonly string[]): Promise<string> {\n const result = await execFileAsync(`colima`, args, {\n env: this.hostEnv,\n maxBuffer: commandMaxBufferBytes,\n });\n return mergeOutput(result.stdout, result.stderr);\n }\n}\n\nclass DockerBackend implements TestHostBackend {\n public readonly info: TestHostInfo = {\n backend: `docker`,\n repoRoot,\n stateRoot: dockerStateRoot,\n };\n\n private runtimeInfo:\n | {\n readonly home: string;\n readonly runtimeDir: string;\n readonly userId: number;\n }\n | undefined;\n\n public async ensureHost(): Promise<void> {\n logTestHost(`Building Docker test host image ${dockerImage}`);\n await this.execDocker(\n [`build`, `--quiet`, `--tag`, dockerImage, `--file`, dockerfilePath, repoRoot],\n { timeout: dockerBuildTimeoutMs },\n );\n logTestHost(`Docker test host image ready`);\n\n if (!(await this.isContainerRunning())) {\n await this.removeExistingContainer();\n logTestHost(`Starting Docker test host container ${dockerContainer}`);\n await this.execDocker([\n `run`,\n `--detach`,\n `--name`,\n dockerContainer,\n `--hostname`,\n `systemd-ts-test-host`,\n `--privileged`,\n `--cgroupns=host`,\n `--tmpfs`,\n `/run`,\n `--tmpfs`,\n `/run/lock`,\n `--volume`,\n `/sys/fs/cgroup:/sys/fs/cgroup:rw`,\n `--volume`,\n `${repoRoot}:${repoRoot}`,\n `--volume`,\n `${dockerStateRoot}:${dockerStateRoot}`,\n `--workdir`,\n repoRoot,\n dockerImage,\n ]);\n logTestHost(`Docker test host container started`);\n } else {\n logTestHost(`Reusing running Docker test host container ${dockerContainer}`);\n }\n\n await this.ensureUserSession();\n }\n\n public createGuestShellProcess(): ChildProcessWithoutNullStreams {\n const runtimeInfo = this.requireRuntimeInfo();\n return spawn(\n `docker`,\n [\n `exec`,\n `--interactive`,\n `--user`,\n dockerUser,\n `--env`,\n `HOME=${runtimeInfo.home}`,\n `--env`,\n `XDG_RUNTIME_DIR=${runtimeInfo.runtimeDir}`,\n `--env`,\n `DBUS_SESSION_BUS_ADDRESS=unix:path=${runtimeInfo.runtimeDir}/bus`,\n dockerContainer,\n `bash`,\n `--noprofile`,\n `--norc`,\n `-s`,\n ],\n {\n stdio: [`pipe`, `pipe`, `pipe`],\n },\n );\n }\n\n public async runIsolatedCommand(script: string): Promise<string> {\n const runtimeInfo = this.requireRuntimeInfo();\n const result = await this.execDockerAsUser(runtimeInfo, [`bash`, `-lc`, script]);\n\n return mergeOutput(result.stdout, result.stderr);\n }\n\n private async isContainerRunning(): Promise<boolean> {\n try {\n const status = await this.execDocker([\n `inspect`,\n `--format`,\n `{{.State.Running}}`,\n dockerContainer,\n ]);\n return status.trim() === `true`;\n } catch {\n return false;\n }\n }\n\n private async removeExistingContainer(): Promise<void> {\n try {\n await this.execDocker([`rm`, `--force`, dockerContainer]);\n } catch {\n // Ignore missing containers so the first run can proceed.\n }\n }\n\n private async ensureUserSession(): Promise<void> {\n const runtimeInfo = await this.resolveRuntimeInfo();\n logTestHost(\n `Ensuring user session for ${dockerUser} (uid=${runtimeInfo.userId}, home=${runtimeInfo.home})`,\n );\n await this.execDocker(\n [\n `exec`,\n dockerContainer,\n `bash`,\n `-lc`,\n [\n `mkdir -p ${shellQuote(dockerStateRoot)}`,\n `chown ${runtimeInfo.userId}:${runtimeInfo.userId} ${shellQuote(dockerStateRoot)}`,\n `loginctl enable-linger ${shellQuote(dockerUser)}`,\n `systemctl start user@${runtimeInfo.userId}.service`,\n ].join(`\\n`),\n ],\n { timeout: dockerExecTimeoutMs },\n );\n await this.execDockerAsUser(runtimeInfo, [\n `bash`,\n `-lc`,\n `mkdir -p ${shellQuote(`${dockerStateRoot}/tests`)}`,\n ]);\n logTestHost(`User session started for ${dockerUser}`);\n }\n\n private async execDocker(\n args: readonly string[],\n options?: {\n readonly timeout?: number;\n },\n ): Promise<string> {\n const result = await execFileAsync(`docker`, args, {\n maxBuffer: commandMaxBufferBytes,\n timeout: options?.timeout,\n });\n return mergeOutput(result.stdout, result.stderr);\n }\n\n private execDockerAsUser(\n runtimeInfo: {\n readonly home: string;\n readonly runtimeDir: string;\n readonly userId: number;\n },\n command: readonly string[],\n ) {\n return execFileAsync(\n `docker`,\n [\n `exec`,\n `--user`,\n dockerUser,\n `--env`,\n `HOME=${runtimeInfo.home}`,\n `--env`,\n `XDG_RUNTIME_DIR=${runtimeInfo.runtimeDir}`,\n `--env`,\n `DBUS_SESSION_BUS_ADDRESS=unix:path=${runtimeInfo.runtimeDir}/bus`,\n dockerContainer,\n ...command,\n ],\n { maxBuffer: commandMaxBufferBytes },\n );\n }\n\n private requireRuntimeInfo(): {\n readonly home: string;\n readonly runtimeDir: string;\n readonly userId: number;\n } {\n if (this.runtimeInfo === undefined) {\n throw new Error(`Docker test host runtime info is unavailable before ensureHost() completes`);\n }\n\n return this.runtimeInfo;\n }\n\n private async resolveRuntimeInfo(): Promise<{\n readonly home: string;\n readonly runtimeDir: string;\n readonly userId: number;\n }> {\n if (this.runtimeInfo !== undefined) {\n return this.runtimeInfo;\n }\n\n const userId =\n dockerConfiguredUserId === undefined\n ? await this.lookupUserId()\n : Number(dockerConfiguredUserId);\n const home = await this.lookupUserHome();\n\n this.runtimeInfo = {\n home,\n runtimeDir: `/run/user/${userId}`,\n userId,\n };\n\n return this.runtimeInfo;\n }\n\n private async lookupUserHome(): Promise<string> {\n logTestHost(`Resolving home directory for Docker test user ${dockerUser}`);\n const output = await this.execDocker([\n `exec`,\n dockerContainer,\n `bash`,\n `-lc`,\n `getent passwd ${shellQuote(dockerUser)} | cut -d: -f6`,\n ]);\n\n const home = output.trim();\n if (home.length === 0) {\n throw new Error(`Could not resolve home directory for Docker test user ${dockerUser}`);\n }\n\n logTestHost(`Resolved home directory for ${dockerUser}: ${home}`);\n return home;\n }\n\n private async lookupUserId(): Promise<number> {\n logTestHost(`Resolving uid for Docker test user ${dockerUser}`);\n const output = await this.execDocker([\n `exec`,\n dockerContainer,\n `bash`,\n `-lc`,\n `id -u ${shellQuote(dockerUser)}`,\n ]);\n\n const parsed = Number(output.trim());\n if (!Number.isInteger(parsed)) {\n throw new Error(`Could not resolve uid for Docker test user ${dockerUser}`);\n }\n\n logTestHost(`Resolved uid for ${dockerUser}: ${parsed}`);\n return parsed;\n }\n}\n\nclass GuestShell {\n private readonly process: ChildProcessWithoutNullStreams;\n private readonly onExitCleanup: () => void;\n private closed = false;\n private buffer = ``;\n private current:\n | {\n marker: string;\n timeoutId: NodeJS.Timeout | undefined;\n reject: (error: Error) => void;\n resolve: (output: string) => void;\n }\n | undefined;\n private readonly queue: Array<{\n script: string;\n reject: (error: Error) => void;\n resolve: (output: string) => void;\n timeoutMs: number | undefined;\n }> = [];\n\n public constructor(\n createProcess: () => ChildProcessWithoutNullStreams,\n onExitCleanup: () => void,\n ) {\n this.process = createProcess();\n this.onExitCleanup = onExitCleanup;\n this.process.stdout.setEncoding(`utf8`);\n this.process.stderr.setEncoding(`utf8`);\n this.process.stdout.on(`data`, (chunk: string) => this.onData(chunk));\n this.process.stderr.on(`data`, (chunk: string) => this.onData(chunk));\n this.process.on(`exit`, (code, signal) => {\n const reason = new Error(\n `Persistent guest shell exited unexpectedly (code=${code ?? `null`}, signal=${signal ?? `null`})`,\n );\n this.failCurrent(reason);\n while (this.queue.length > 0) {\n this.queue.shift()?.reject(reason);\n }\n this.onExitCleanup();\n });\n\n process.once(`exit`, () => {\n this.process.kill();\n });\n }\n\n public run(script: string, timeoutMs?: number): Promise<string> {\n return new Promise((resolve, reject) => {\n this.queue.push({ script, resolve, reject, timeoutMs });\n this.drain();\n });\n }\n\n public close(): void {\n if (this.closed) {\n return;\n }\n\n this.closed = true;\n this.process.kill();\n }\n\n private drain(): void {\n if (this.current !== undefined) {\n return;\n }\n\n const next = this.queue.shift();\n if (next === undefined) {\n return;\n }\n\n const marker = `__SYSTEMD_TS_END_${randomUUID()}__`;\n const timeoutId =\n next.timeoutMs === undefined\n ? undefined\n : setTimeout(() => {\n if (this.current?.marker !== marker) {\n return;\n }\n\n const reason = new Error(`Guest command timed out after ${next.timeoutMs}ms`);\n this.failCurrent(reason);\n this.close();\n }, next.timeoutMs);\n this.current = {\n marker,\n timeoutId,\n reject: next.reject,\n resolve: next.resolve,\n };\n\n this.buffer = ``;\n const command = `(\n${next.script}\n) 2>&1\nstatus=$?\nprintf '\\\\n${marker}:%s\\\\n' \"$status\"\n`;\n\n this.process.stdin.write(command);\n }\n\n private failCurrent(error: Error): void {\n const current = this.current;\n this.current = undefined;\n if (current?.timeoutId !== undefined) {\n clearTimeout(current.timeoutId);\n }\n current?.reject(error);\n }\n\n private onData(chunk: string): void {\n if (this.current === undefined) {\n return;\n }\n\n this.buffer += chunk;\n const markerIndex = this.buffer.indexOf(this.current.marker);\n if (markerIndex === -1) {\n return;\n }\n\n const output = this.buffer.slice(0, markerIndex).replace(/\\n$/, ``);\n const statusMatch = this.buffer\n .slice(markerIndex)\n .match(/^__SYSTEMD_TS_END_[^:]+__:(\\d+)\\r?\\n?/u);\n\n if (statusMatch === null) {\n return;\n }\n\n const status = Number(statusMatch[1]);\n const remainder = this.buffer.slice(markerIndex + statusMatch[0].length);\n const current = this.current;\n this.current = undefined;\n this.buffer = remainder;\n if (current.timeoutId !== undefined) {\n clearTimeout(current.timeoutId);\n }\n\n if (status === 0) {\n current.resolve(output);\n } else {\n current.reject(new Error(output.length > 0 ? output : `Guest command failed`));\n }\n\n this.drain();\n }\n}\n\nfunction shellQuote(value: string): string {\n return `'${value.replaceAll(`'`, `'\\\\''`)}'`;\n}\n\nconst backend = createBackend(selectedBackendName);\n","import { randomUUID } from \"node:crypto\";\n\nimport { getTestHostInfo, runGuestCommand } from \"./host.ts\";\n\nexport interface TestSandbox {\n readonly id: string;\n readonly linkedUnitDir: string;\n readonly namePrefix: string;\n readonly rootDir: string;\n readonly workDir: string;\n}\n\nexport interface DestroyTestSandboxOptions {\n readonly noisy?: boolean;\n}\n\nlet currentSandbox: TestSandbox | undefined;\n\nexport async function createTestSandbox(testName?: string): Promise<TestSandbox> {\n const id = randomUUID().slice(0, 8);\n const slug = slugify(testName ?? `sandbox`);\n const namePrefix = `systemd-ts-${slug}-${id}`;\n const rootDir = `${getTestHostInfo().stateRoot}/tests/${namePrefix}`;\n const linkedUnitDir = `${rootDir}/linked-units`;\n const workDir = `${rootDir}/work`;\n\n await runGuestCommand(\n `set -euo pipefail\nrm -rf ${shellQuote(rootDir)}\nmkdir -p ${shellQuote(linkedUnitDir)} ${shellQuote(workDir)}`,\n );\n\n currentSandbox = {\n id,\n linkedUnitDir,\n namePrefix,\n rootDir,\n workDir,\n };\n\n return currentSandbox;\n}\n\nexport async function destroyCurrentTestSandbox(): Promise<void> {\n if (currentSandbox === undefined) {\n return;\n }\n\n const sandbox = currentSandbox;\n currentSandbox = undefined;\n\n const unitsOutput = await runGuestCommand(\n `systemctl --user list-unit-files --all --no-legend ${shellQuote(`${sandbox.namePrefix}*`)} 2>/dev/null | awk '{print $1}' || true`,\n );\n\n const units = unitsOutput\n .split(`\\n`)\n .map((unit) => unit.trim())\n .filter((unit) => unit.length > 0);\n\n if (units.length > 0) {\n await runGuestCommand(`systemctl --user stop ${units.map(shellQuote).join(` `)} || true`);\n\n await runGuestCommand(`systemctl --user disable ${units.map(shellQuote).join(` `)} || true`);\n\n await runGuestCommand(\n `systemctl --user reset-failed ${units.map(shellQuote).join(` `)} || true`,\n );\n }\n\n await runGuestCommand(`systemctl --user daemon-reload || true`);\n\n await runGuestCommand(`rm -rf ${shellQuote(sandbox.rootDir)}`);\n}\n\nexport function useCurrentTestSandbox(): TestSandbox {\n if (currentSandbox === undefined) {\n throw new Error(`No active test sandbox is available`);\n }\n\n return currentSandbox;\n}\n\nfunction shellQuote(value: string): string {\n return `'${value.replaceAll(`'`, `'\\\\''`)}'`;\n}\n\nfunction slugify(value: string): string {\n return value\n .toLowerCase()\n .replaceAll(/[^a-z0-9]+/g, `-`)\n .replaceAll(/^-+|-+$/g, ``)\n .slice(0, 32);\n}\n","import {\n Systemd,\n type CommandExecutionOptions,\n type CommandExecutor,\n type CommandOutput,\n} from \"../main/index.ts\";\n\nimport { runGuestCommand, runIsolatedGuestCommand } from \"./host.ts\";\nimport { useCurrentTestSandbox } from \"./sandbox.ts\";\n\n/**\n * Creates a command executor that runs argv-style commands inside the current\n * test host guest shell.\n */\nexport function createGuestCommandExecutor(\n runCommand: (script: string) => Promise<string>,\n): CommandExecutor {\n return async (\n command: string,\n args: readonly string[],\n options: CommandExecutionOptions = {},\n ): Promise<CommandOutput> => {\n try {\n const stdout = await runCommand(buildGuestCommand(command, args, options));\n return {\n stderr: ``,\n stdout,\n };\n } catch (cause) {\n throw enrichGuestCommandError(cause);\n }\n };\n}\n\n/**\n * Executor that reuses the warm guest shell for fast sandbox-backed tests.\n */\nexport const guestCommandExecutor: CommandExecutor = createGuestCommandExecutor(runGuestCommand);\n\n/**\n * Executor that runs each guest command in a fresh isolated guest shell.\n */\nexport const isolatedGuestCommandExecutor: CommandExecutor =\n createGuestCommandExecutor(runIsolatedGuestCommand);\n\n/**\n * Creates a `Systemd` instance scoped to the active test sandbox and backed by\n * the warm guest-shell executor.\n */\nexport function sandboxSystemd(): Systemd {\n const sandbox = useCurrentTestSandbox();\n return new Systemd({\n executor: guestCommandExecutor,\n linkUnits: true,\n scope: `user`,\n unitDir: sandbox.linkedUnitDir,\n });\n}\n\n/**\n * Creates a `Systemd` instance scoped to the active test sandbox and backed by\n * a fresh guest shell per command.\n */\nexport function isolatedSandboxSystemd(): Systemd {\n const sandbox = useCurrentTestSandbox();\n return new Systemd({\n executor: isolatedGuestCommandExecutor,\n linkUnits: true,\n scope: `user`,\n unitDir: sandbox.linkedUnitDir,\n });\n}\n\nfunction buildGuestCommand(\n command: string,\n args: readonly string[],\n options: CommandExecutionOptions,\n): string {\n const envArgs =\n options.env === undefined\n ? []\n : Object.entries(options.env).flatMap(([key, value]) =>\n value === undefined ? [`-u`, key] : [`${key}=${value}`],\n );\n\n return [`env`, ...envArgs, command, ...args].map((part) => shellQuote(part)).join(` `);\n}\n\nfunction enrichGuestCommandError(cause: unknown): Error {\n if (cause instanceof Error) {\n return Object.assign(cause, {\n code: 1,\n stderr: ``,\n stdout: cause.message,\n });\n }\n\n return Object.assign(new Error(`Guest command failed`), {\n code: 1,\n stderr: ``,\n stdout: ``,\n });\n}\n\nfunction shellQuote(value: string): string {\n return `'${value.replaceAll(`'`, `'\\\\''`)}'`;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "systemd-ts",
|
|
3
|
-
"version": "261.
|
|
3
|
+
"version": "261.3.0",
|
|
4
4
|
"description": "TypeScript builders and helpers for systemd services and timers.",
|
|
5
|
-
"license": "
|
|
5
|
+
"license": "LGPL-2.1-or-later",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/jeremybanka/systemd-ts.git",
|
|
@@ -22,12 +22,12 @@
|
|
|
22
22
|
"access": "public"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@types/node": "25.9.
|
|
25
|
+
"@types/node": "25.9.6",
|
|
26
26
|
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
27
|
-
"takua": "0.2.
|
|
27
|
+
"takua": "0.2.3",
|
|
28
28
|
"typescript": "7.0.2",
|
|
29
|
-
"vite-plus": "0.
|
|
30
|
-
"vitest": "
|
|
29
|
+
"vite-plus": "0.3.1",
|
|
30
|
+
"vitest": "5.0.0"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"build": "vp pack",
|
package/src/main/types.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|
2
|
+
// Directive documentation is adapted from systemd manuals and modified for TypeScript in 2026.
|
|
3
|
+
|
|
1
4
|
import type { Executable } from "./executable.ts";
|
|
2
5
|
import type { SystemdService } from "./systemd-service.ts";
|
|
3
6
|
import type { SystemdTimer } from "./systemd-timer.ts";
|
|
@@ -309,7 +312,7 @@ export interface SystemdUnitSection extends CustomDirectiveSection {
|
|
|
309
312
|
readonly Description?: string;
|
|
310
313
|
/** A space-separated list of URIs referencing documentation for this unit or its configuration. Accepted are only URIs of the types http://, https://, file:, info:, man:. For more information about the syntax of these URIs, see uri(7). The URIs should be listed in order of relevance, starting with the most relevant. It is a good idea to first reference documentation that explains what the unit's purpose is, followed by how it is configured, followed by any other related documentation. This option may be specified more than once, in which case the specified list of URIs is merged. If the empty string is assigned to this option, the list is reset and all prior assignments will have no effect. Source: systemd v260.1, `systemd.unit(5)`. */
|
|
311
314
|
readonly Documentation?: string | readonly string[];
|
|
312
|
-
/** Action to take when the unit stops in a failed state. Source: systemd
|
|
315
|
+
/** Action to take when the unit stops in a failed state. For `exit` and `exit-force`, the manager returns the main process exit status, or 255 if none is available, unless overridden by `FailureActionExitStatus=`. Source: systemd v261.3, `systemd.unit(5)`. */
|
|
313
316
|
readonly FailureAction?: UnitActionDirectiveValue;
|
|
314
317
|
/** Exit status to propagate when `FailureAction=` triggers `exit` or `exit-force`. Source: systemd v260.1, `systemd.unit(5)`. */
|
|
315
318
|
readonly FailureActionExitStatus?: string | number;
|
|
@@ -365,7 +368,7 @@ export interface SystemdUnitSection extends CustomDirectiveSection {
|
|
|
365
368
|
readonly StopPropagatedFrom?: string | readonly string[];
|
|
366
369
|
/** If true, this unit will be stopped when it is no longer used. Note that, in order to minimize the work to be executed, systemd will not stop units by default unless they are conflicting with other units, or the user explicitly requested their shut down. If this option is set, a unit will be automatically cleaned up if no other active unit requires it. Defaults to false. Source: systemd v260.1, `systemd.unit(5)`. */
|
|
367
370
|
readonly StopWhenUnneeded?: boolean;
|
|
368
|
-
/** Action to take when the unit stops in an inactive state. Source: systemd
|
|
371
|
+
/** Action to take when the unit stops in an inactive state. For `exit` and `exit-force`, the manager returns the main process exit status, or leaves its exit status unchanged if none is available, unless overridden by `SuccessActionExitStatus=`. Source: systemd v261.3, `systemd.unit(5)`. */
|
|
369
372
|
readonly SuccessAction?: UnitActionDirectiveValue;
|
|
370
373
|
/** Exit status to propagate when `SuccessAction=` triggers `exit` or `exit-force`. Source: systemd v260.1, `systemd.unit(5)`. */
|
|
371
374
|
readonly SuccessActionExitStatus?: string | number;
|
|
@@ -832,7 +835,7 @@ export interface SystemdServiceSection extends CustomDirectiveSection {
|
|
|
832
835
|
readonly RestrictNetworkInterfaces?: string | readonly string[];
|
|
833
836
|
/** If set, any attempts to enable realtime scheduling in a process of the unit are refused. This restricts access to realtime task scheduling policies such as SCHED_FIFO, SCHED_RR or SCHED_DEADLINE. See sched7 for details about these scheduling policies. Realtime scheduling policies may be used to monopolize CPU time for longer periods of time, and may hence be used to lock up or otherwise trigger Denial-of-Service situations on the system. It is hence recommended to restrict access to realtime scheduling to the few programs that actually require them. Defaults to off. Source: systemd v260.1, `systemd.exec(5)`. */
|
|
834
837
|
readonly RestrictRealtime?: boolean;
|
|
835
|
-
/** If set, any attempts to set the set-user-ID (SUID) or set-group-ID (SGID) bits on files or directories will be denied (for details on these bits see inode7). As the SUID/SGID bits are mechanisms to elevate privileges, and allow users to acquire the identity of other users, it is recommended to restrict creation of SUID/SGID files to the few programs that actually require them. Note that this restricts marking of any type of file system object with these bits, including both regular files and directories (where the SGID is a different meaning than for files, see documentation). This option is implied if DynamicUser= is enabled. Source: systemd
|
|
838
|
+
/** If set, any attempts to set the set-user-ID (SUID) or set-group-ID (SGID) bits on files or directories will be denied (for details on these bits see inode7). As the SUID/SGID bits are mechanisms to elevate privileges, and allow users to acquire the identity of other users, it is recommended to restrict creation of SUID/SGID files to the few programs that actually require them. Note that this restricts marking of any type of file system object with these bits, including both regular files and directories (where the SGID is a different meaning than for files, see documentation). This option is implied if DynamicUser= is enabled. Seccomp blocks calls with directly supplied mode bits only when SUID or SGID is requested, but blocks calls such as openat2() entirely because their mode bits cannot be inspected. Source: systemd v261.3, `systemd.exec(5)`. */
|
|
836
839
|
readonly RestrictSUIDSGID?: boolean;
|
|
837
840
|
/** Takes a directory path relative to the host's root directory (i.e. the root of the system running the service manager). Sets the root directory for executed processes, with the pivot_root2 or chroot2 system call. If this is used, it must be ensured that the process binary and all its auxiliary files are available in the new root. Note that setting this parameter might result in additional dependencies to be added to the unit (see above). Source: systemd v260.1, `systemd.exec(5)`. */
|
|
838
841
|
readonly RootDirectory?: string;
|
|
@@ -888,15 +891,15 @@ export interface SystemdServiceSection extends CustomDirectiveSection {
|
|
|
888
891
|
readonly SocketBindDeny?: string | readonly string[];
|
|
889
892
|
/** Specifies the name of the socket units this service shall inherit socket file descriptors from when the service is started. Normally, it should not be necessary to use this setting, as all socket file descriptors whose unit shares the same name as the service (subject to the different unit name suffix of course) are passed to the spawned process. Source: systemd v260.1, `systemd.service(5)`. */
|
|
890
893
|
readonly Sockets?: string | readonly string[];
|
|
891
|
-
/** Controls where file descriptor 2 (stderr) of the executed processes is connected to. The available options are identical to those of StandardOutput=, with some exceptions: if set to inherit the file descriptor used for standard output is duplicated for standard error, while fd:name will use a default file descriptor name of stderr. Source: systemd
|
|
894
|
+
/** Controls where file descriptor 2 (stderr) of the executed processes is connected to. The available options are identical to those of StandardOutput=, with some exceptions: if set to inherit the file descriptor used for standard output is duplicated for standard error, while fd:name will use a default file descriptor name of stderr. File paths are opened by the service manager before SELinuxContext= or policy-defined transitions take effect; the process inherits the resulting descriptor. Source: systemd v261.3, `systemd.exec(5)`. */
|
|
892
895
|
readonly StandardError?: StandardErrorDirectiveValue;
|
|
893
|
-
/** Controls where file descriptor 0 (STDIN) of the executed processes is connected to. Takes one of null, tty, tty-force, tty-fail, data, file:path, socket or fd:name. Source: systemd
|
|
896
|
+
/** Controls where file descriptor 0 (STDIN) of the executed processes is connected to. Takes one of null, tty, tty-force, tty-fail, data, file:path, socket or fd:name. For file:path, the service manager opens the path before SELinuxContext= or policy-defined transitions take effect; the process inherits the resulting descriptor. Source: systemd v261.3, `systemd.exec(5)`. */
|
|
894
897
|
readonly StandardInput?: StandardInputDirectiveValue;
|
|
895
898
|
/** Configures arbitrary textual or binary data to pass via file descriptor 0 (STDIN) to the executed processes. These settings have no effect unless StandardInput= is set to data (which is the default if StandardInput= is not set otherwise, but StandardInputText=/StandardInputData= is). Use this option to embed process input data directly in the unit file. Source: systemd v260.1, `systemd.exec(5)`. */
|
|
896
899
|
readonly StandardInputData?: string | readonly string[];
|
|
897
900
|
/** Configures arbitrary textual or binary data to pass via file descriptor 0 (STDIN) to the executed processes. These settings have no effect unless StandardInput= is set to data (which is the default if StandardInput= is not set otherwise, but StandardInputText=/StandardInputData= is). Use this option to embed process input data directly in the unit file. Source: systemd v260.1, `systemd.exec(5)`. */
|
|
898
901
|
readonly StandardInputText?: string | readonly string[];
|
|
899
|
-
/** Controls where file descriptor 1 (stdout) of the executed processes is connected to. Takes one of inherit, null, tty, journal, kmsg, journal+console, kmsg+console, file:path, append:path, truncate:path, socket or fd:name. Source: systemd
|
|
902
|
+
/** Controls where file descriptor 1 (stdout) of the executed processes is connected to. Takes one of inherit, null, tty, journal, kmsg, journal+console, kmsg+console, file:path, append:path, truncate:path, socket or fd:name. For file:path, append:path, and truncate:path, the service manager opens the path before SELinuxContext= or policy-defined transitions take effect; the process inherits the resulting descriptor. Source: systemd v261.3, `systemd.exec(5)`. */
|
|
900
903
|
readonly StandardOutput?: StandardOutputDirectiveValue;
|
|
901
904
|
/** This setting controls the cpuset controller in the unified hierarchy. Source: systemd v260.1, `systemd.resource-control(5)`. */
|
|
902
905
|
readonly StartupAllowedCPUs?: string;
|
|
@@ -984,7 +987,7 @@ export interface SystemdServiceSection extends CustomDirectiveSection {
|
|
|
984
987
|
readonly USBFunctionDescriptors?: string;
|
|
985
988
|
/** Configure the location of a file containing USB FunctionFS strings. Behavior is similar to USBFunctionDescriptors= above. Source: systemd v260.1, `systemd.service(5)`. */
|
|
986
989
|
readonly USBFunctionStrings?: string;
|
|
987
|
-
/** Explicitly unset environment variable assignments that would normally be passed from the service manager to invoked processes of this unit. Takes a space-separated list of variable names or variable assignments. This option may be specified more than once, in which case all listed variables/assignments will be unset. If the empty string is assigned to this option, the list of environment variables/assignments to unset is reset. If a variable assignment is specified (that is: a variable name, followed by =, followed by its value), then any environment variable matching this precise assignment is removed. If a variable name is specified (that is a variable name without any following = or value), then any assignment matching the variable name, regardless of its value is removed. Note that the effect of UnsetEnvironment= is applied as final step when the environment list passed to executed processes is compiled. That means it may undo assignments from any configuration source, including assignments made through Environment= or EnvironmentFile=, inherited from the system manager's global set of environment variables, inherited via PassEnvironment=, set by the service manager itself (such as $NOTIFY_SOCKET and such), or set by a PAM module (in case PAMName= is used). Source: systemd
|
|
990
|
+
/** Explicitly unset environment variable assignments that would normally be passed from the service manager to invoked processes of this unit. Takes a space-separated list of variable names or variable assignments. This option may be specified more than once, in which case all listed variables/assignments will be unset. If the empty string is assigned to this option, the list of environment variables/assignments to unset is reset. If a variable assignment is specified (that is: a variable name, followed by =, followed by its value), then any environment variable matching this precise assignment is removed. If a variable name is specified (that is a variable name without any following = or value), then any assignment matching the variable name, regardless of its value is removed. Note that the effect of UnsetEnvironment= is applied as final step when the environment list passed to executed processes is compiled. That means it may undo assignments from any configuration source, including assignments made through Environment= or EnvironmentFile=, inherited from the system manager's global set of environment variables, inherited via PassEnvironment=, set by the service manager itself (such as $NOTIFY_SOCKET and such), or set by a PAM module (in case PAMName= is used). Command-line variable expansion in ExecStart= and related directives happens before this removal and is unaffected. Source: systemd v261.3, `systemd.exec(5)`. */
|
|
988
991
|
readonly UnsetEnvironment?: string | readonly string[];
|
|
989
992
|
/** Set the UNIX user or group that the processes are executed as, respectively. Takes a single user or group name, or a numeric ID as argument. For system services (services run by the system service manager, i.e. managed by PID 1) and for user services of the root user (services managed by root's instance of systemd --user), the default is root, but this directive may be used to specify a different identity. For user services of any other user, switching user identity is not permitted, hence the only valid setting is the same user the user's service manager is running as. If no group is set, the default group of the user is used. This setting does not affect commands whose command line is prefixed with +. Source: systemd v260.1, `systemd.exec(5)`. */
|
|
990
993
|
readonly User?: string;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index-gV6lNweL.d.mts","names":[],"sources":["../src/main/internal.ts","../src/main/executable.ts","../src/main/systemd-service.ts","../src/main/systemd-timer.ts","../src/main/types.ts","../src/main/errors.ts","../src/main/notify.ts","../src/main/systemctl.ts","../src/main/systemd.ts","../src/main/index.ts"],"mappings":"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"}
|