scripts/deploy-local.mjs
Rama main · réplica de lectura del repositorio original.
import { createLocalAgentClient } from "./local-agent-client.mjs";
import { spawnSync } from "node:child_process";
import { readFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { parseAutostartArgs, windowsAutostartPaths, WINDOWS_TASK_NAME } from "./autostart-core.mjs";
const root = fileURLToPath(new URL("../", import.meta.url));
function runPnpm(args) {
const cli = process.env.npm_execpath;
if (!cli || !path.isAbsolute(cli)) throw new Error("Run this command through pnpm deploy:local.");
const result = spawnSync(process.execPath, [cli, ...args], { cwd: root, stdio: "inherit", windowsHide: true });
if (result.status !== 0) throw new Error(`Deployment step failed: ${args[0]}`);
}
function startupCommand(action) {
if (process.platform === "win32") {
return spawnSync("schtasks.exe", [action === "stop" ? "/End" : "/Run", "/TN", WINDOWS_TASK_NAME], { windowsHide: true, stdio: "ignore" });
}
return spawnSync("systemctl", ["--user", action === "stop" ? "stop" : "start", "palmtty.service"], { stdio: "ignore" });
}
function hasInstalledStartup() {
const result = process.platform === "win32"
? spawnSync("schtasks.exe", ["/Query", "/TN", WINDOWS_TASK_NAME], { windowsHide: true, stdio: "ignore" })
: spawnSync("systemctl", ["--user", "cat", "palmtty.service"], { stdio: "ignore" });
return result.status === 0;
}
async function main() {
let args = process.argv.slice(2);
if (args.length === 1 && ["-h", "--help"].includes(args[0])) {
console.log("pnpm deploy:local [--config <path>] [--env-file <path>]\nBuild, preflight, install current-user startup, and verify configured applications.\nWindows: without arguments reuse the existing installation's config/env-file paths.");
return;
}
if (!args.length && process.platform === "win32") {
try {
const manifest = JSON.parse(await readFile(windowsAutostartPaths().installation, "utf8"));
args = ["--config", manifest.configPath, ...(manifest.envFile ? ["--env-file", manifest.envFile] : [])];
} catch { /* A first installation uses explicit options or the standard config location. */ }
}
parseAutostartArgs(["install", ...args]);
runPnpm(["install", "--frozen-lockfile"]);
runPnpm(["build"]);
const { configPathFromEnvironment, loadConfig, exposureOrigins } = await import("../packages/config/dist/index.js");
const { loadEnvironmentFile } = await import("../apps/agent/dist/environment-file.js");
const parsed = parseAutostartArgs(["install", ...args]);
if (parsed.envFile) await loadEnvironmentFile(parsed.envFile);
const config = await loadConfig(parsed.configPath ?? configPathFromEnvironment());
const { provisionPrivateNetwork, exportPrivateNetworkSetup } = await import("./private-network.mjs");
await provisionPrivateNetwork(config);
const agentArgs = ["--config", parsed.configPath ?? configPathFromEnvironment(), ...(parsed.envFile ? ["--env-file", parsed.envFile] : [])];
const preflight = () => spawnSync(process.execPath, [path.join(root, "apps/agent/dist/index.js"), "--preflight", ...agentArgs], { cwd: root, encoding: "utf8", windowsHide: true });
let probe = preflight(), stopped = false;
// Upgrade the registered user service, never kill a guessed port owner/PID.
// Other preflight errors leave the current service untouched.
const problems = (probe.stderr ?? "").split(/\r?\n/u).filter((line) => line.startsWith("- "));
if (probe.status !== 0 && problems.length === 1 && problems[0].includes("already in use") && hasInstalledStartup()) {
if (startupCommand("stop").status !== 0) throw new Error("Could not stop the registered user startup instance.");
stopped = true;
for (let i = 0; i < 10; i++) {
await new Promise((resolve) => setTimeout(resolve, 250));
probe = preflight();
if (probe.status === 0) break;
}
}
if (probe.status !== 0) {
if (stopped) startupCommand("start");
process.stderr.write(probe.stderr ?? "");
throw new Error("Preflight failed; no unknown listener was stopped.");
}
try { runPnpm(["autostart", "install", ...args]); }
catch (error) { if (stopped) startupCommand("start"); throw error; }
const request = await createLocalAgentClient(config), origin = exposureOrigins(config)[0];
const deadline = Date.now() + 11 * 60_000;
let cookie;
const seen = new Map();
try {
while (Date.now() < deadline) {
try {
if (!cookie && config.auth.enabled) {
const response = await request("/api/v1/auth/login", { method: "POST", headers: { origin, "content-type": "application/json" }, body: JSON.stringify({ token: process.env[config.auth.tokenEnv] }), signal: AbortSignal.timeout(5000) });
if (response.ok) cookie = response.headers.get("set-cookie")?.split(";")[0];
else if (response.status === 401) throw new Error("deployment_authentication_failed");
}
const response = await request("/api/v1/integrations", { headers: { origin, ...(cookie ? { cookie } : {}) }, signal: AbortSignal.timeout(5000) });
if (response.ok) {
const { apps } = await response.json();
let ready = true;
for (const [id, entry] of Object.entries(config.integrations)) {
if (entry.mode !== "managed" || !entry.autoStart) continue;
const state = apps[id];
const label = state.phase + (state.detail ? "/" + state.detail : "");
if (seen.get(id) !== label) { console.log(`[PalmTTY] ${id}: ${label}`); seen.set(id, label); }
if (state.phase === "failed") throw new Error("managed_application_failed:" + id + ":" + (state.detail ?? "unknown"));
if (state.phase !== "running") ready = false;
}
if (ready) { await exportPrivateNetworkSetup(config); console.log("[PalmTTY] Local deployment verified. Test the same IP entries over home Wi-Fi and WireGuard on the actual device."); return; }
}
} catch (error) {
if (/^(managed_application_failed|deployment_authentication_failed|local_agent_tls_validation_failed)/u.test(error.message)) throw error;
}
await new Promise((resolve) => setTimeout(resolve, 2000));
}
throw new Error("Deployment health check timed out.");
} finally {
if (cookie) await request("/api/v1/auth/logout", { method: "POST", headers: { origin, cookie }, signal: AbortSignal.timeout(3000) }).catch(() => undefined);
}
}
main().catch((error) => { console.error("[PalmTTY] " + error.message); process.exitCode = 1; });