feat: unify Docker entrypoint

This commit is contained in:
Pi Web Agent
2026-06-29 11:27:47 +00:00
parent 4a1136eac2
commit bd28c93cfe
35 changed files with 2316 additions and 118 deletions
+90 -2
View File
@@ -1,4 +1,4 @@
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { chmod, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
@@ -8,10 +8,20 @@ import type { PiWebComponentStatus } from "../shared/apiTypes.js";
const originalSkipVersionCheck = process.env["PI_WEB_SKIP_VERSION_CHECK"];
const originalHome = process.env["HOME"];
const originalPath = process.env["PATH"];
const originalDockerRuntime = process.env["PI_WEB_DOCKER_RUNTIME"];
const originalDockerMode = process.env["PI_WEB_DOCKER_MODE"];
const originalDockerInstallDir = process.env["PI_WEB_DOCKER_INSTALL_DIR"];
const originalDockerDevRepoRoot = process.env["PI_WEB_DOCKER_DEV_REPO_ROOT"];
afterEach(() => {
restoreEnv("PI_WEB_SKIP_VERSION_CHECK", originalSkipVersionCheck);
restoreEnv("HOME", originalHome);
restoreEnv("PATH", originalPath);
restoreEnv("PI_WEB_DOCKER_RUNTIME", originalDockerRuntime);
restoreEnv("PI_WEB_DOCKER_MODE", originalDockerMode);
restoreEnv("PI_WEB_DOCKER_INSTALL_DIR", originalDockerInstallDir);
restoreEnv("PI_WEB_DOCKER_DEV_REPO_ROOT", originalDockerDevRepoRoot);
vi.restoreAllMocks();
});
@@ -42,6 +52,7 @@ describe("PI WEB status", () => {
it("reports stale session daemon versions as messages", async () => {
process.env["PI_WEB_SKIP_VERSION_CHECK"] = "1";
disableDockerRuntimeEnv();
const daemon = daemonWithComponent({
component: "sessiond",
label: "Session daemon",
@@ -63,9 +74,13 @@ describe("PI WEB status", () => {
it("suggests native systemd commands for local development services", async () => {
if (process.platform !== "linux") return;
process.env["PI_WEB_SKIP_VERSION_CHECK"] = "1";
disableDockerRuntimeEnv();
const home = await tempHome();
const binDir = await tempHome();
try {
process.env["HOME"] = home;
await installExecutable(binDir, "systemctl");
process.env["PATH"] = `${binDir}:${process.env["PATH"] ?? ""}`;
await installSystemdServiceFiles(home, ["pi-web-sessiond.service", "pi-web-ui-dev.service"]);
const daemon = daemonWithComponent(staleLocalSessiond());
@@ -76,12 +91,72 @@ describe("PI WEB status", () => {
expect(status.commands.restartSessiond).toBe("systemd-run --user --collect --unit=pi-web-restart-sessiond -- systemctl --user restart pi-web-sessiond.service");
expect(status.messages.find((message) => message.id === "sessiond-stale")?.command).toBe("systemd-run --user --collect --unit=pi-web-restart-sessiond -- systemctl --user restart pi-web-sessiond.service");
} finally {
await rm(home, { recursive: true, force: true });
await Promise.all([
rm(home, { recursive: true, force: true }),
rm(binDir, { recursive: true, force: true }),
]);
}
});
it("suggests Docker commands when running inside the Docker runtime", async () => {
process.env["PI_WEB_SKIP_VERSION_CHECK"] = "1";
process.env["PI_WEB_DOCKER_RUNTIME"] = "1";
process.env["PI_WEB_DOCKER_MODE"] = "runtime";
process.env["PI_WEB_DOCKER_INSTALL_DIR"] = "/srv/pi-web-docker";
process.env["PATH"] = "";
const daemon = daemonWithComponent({ ...staleLocalSessiond(), installation: { kind: "docker", path: "/srv/pi-web-docker", dockerMode: "runtime" } });
const status = await getPiWebStatus(daemon);
expect(status.components.web.installation).toEqual({ kind: "docker", path: "/srv/pi-web-docker", dockerMode: "runtime" });
expect(status.commands).toEqual({
update: "pi-web-docker update",
restart: "pi-web-docker restart",
restartWeb: "pi-web-docker restart-web",
restartSessiond: "pi-web-docker restart-sessiond",
status: "pi-web-docker status",
});
expect(JSON.stringify(status)).not.toContain("npm install -g");
expect(JSON.stringify(status)).not.toContain("pi-web restart");
});
it("suggests explicit Docker development commands when running inside the Docker dev runtime", async () => {
process.env["PI_WEB_SKIP_VERSION_CHECK"] = "1";
process.env["PI_WEB_DOCKER_RUNTIME"] = "1";
process.env["PI_WEB_DOCKER_MODE"] = "dev";
process.env["PI_WEB_DOCKER_DEV_REPO_ROOT"] = "/workspace/pi-web";
process.env["PATH"] = "";
const daemon = daemonWithComponent({ ...staleLocalSessiond(), installation: { kind: "docker", path: "/workspace/pi-web", dockerMode: "dev" } });
const status = await getPiWebStatus(daemon);
expect(status.commands).toEqual({
update: "pi-web-docker --dev update",
restart: "pi-web-docker --dev restart",
restartWeb: "pi-web-docker --dev restart-web",
restartSessiond: "pi-web-docker --dev restart-sessiond",
status: "pi-web-docker --dev status",
});
});
it("infers explicit Docker development commands from the generated dev root when mode is omitted", async () => {
process.env["PI_WEB_SKIP_VERSION_CHECK"] = "1";
process.env["PI_WEB_DOCKER_RUNTIME"] = "1";
Reflect.deleteProperty(process.env, "PI_WEB_DOCKER_MODE");
process.env["PI_WEB_DOCKER_DEV_REPO_ROOT"] = "/workspace/pi-web";
process.env["PATH"] = "";
const daemon = daemonWithComponent(staleLocalSessiond());
const status = await getPiWebStatus(daemon);
expect(status.components.web.installation).toEqual({ kind: "docker", path: "/workspace/pi-web", dockerMode: "dev" });
expect(status.commands.update).toBe("pi-web-docker --dev update");
expect(status.commands.status).toBe("pi-web-docker --dev status");
});
it("omits local restart commands when no native service command is known", async () => {
process.env["PI_WEB_SKIP_VERSION_CHECK"] = "1";
disableDockerRuntimeEnv();
const home = await tempHome();
try {
process.env["HOME"] = home;
@@ -131,6 +206,19 @@ async function installSystemdServiceFiles(home: string, names: string[]): Promis
await Promise.all(names.map((name) => writeFile(join(dir, name), "")));
}
async function installExecutable(dir: string, name: string): Promise<void> {
const path = join(dir, name);
await writeFile(path, "#!/usr/bin/env sh\nexit 0\n");
await chmod(path, 0o755);
}
function disableDockerRuntimeEnv(): void {
process.env["PI_WEB_DOCKER_RUNTIME"] = "0";
Reflect.deleteProperty(process.env, "PI_WEB_DOCKER_MODE");
Reflect.deleteProperty(process.env, "PI_WEB_DOCKER_INSTALL_DIR");
Reflect.deleteProperty(process.env, "PI_WEB_DOCKER_DEV_REPO_ROOT");
}
function restoreEnv(key: string, value: string | undefined): void {
if (value === undefined) Reflect.deleteProperty(process.env, key);
else process.env[key] = value;