import assert from "node:assert/strict"; import { test } from "node:test"; import { spawnSync } from "node:child_process"; import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync, existsSync, } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { certificateFixture } from "../certificate-fixture"; // Execute the generated shell in a disposable filesystem. Only systemctl and // network effects are mocked; bash, jq, openssl, permissions and file writes run. for (const scenario of [ "success", "health-failure", "occupied", "bad-domain", ]) { test(`installer shell: ${scenario}`, () => { const dir = mkdtempSync(path.join(tmpdir(), "hy2-shell-test-")); const fake = path.join(dir, "bin"); mkdirSync(fake); mkdirSync(path.join(dir, "systemd")); const executable = (name: string, body: string) => writeFileSync(path.join(fake, name), `#!/usr/bin/env bash\n${body}\n`, { mode: 0o700, }); const fixture = certificateFixture(); writeFileSync( path.join(dir, "bundle.json"), JSON.stringify({ ...fixture, config: "listen: :443\n", statsSecret: "test-stats", }), ); executable("id", "echo 0"); executable("uname", "echo x86_64"); executable("sleep", "exit 0"); executable( "systemctl", `echo "$*" >> "$SANDBOX/systemctl.log" if [ "$1" = is-active ]; then [ "$3" = hy2-panel-node.service ] && [ "$SCENARIO" != health-failure ]; exit $? fi exit 0`, ); executable( "curl", `url=""; output=""; request="" while [ "$#" -gt 0 ]; do case "$1" in -o) output=$2; shift 2;; --data-binary) request=$2; shift 2;; http*) url=$1; shift;; *) shift;; esac done case "$url" in https://download.hysteria.network/*) printf '#!/usr/bin/env bash\\nexit 0\\n' > "$output";; */api/install) cp "\${request#@}" "$SANDBOX/last-request.json" if jq -e '.action == "prepare"' "\${request#@}" >/dev/null; then cp "\${request#@}" "$SANDBOX/prepare.json" cp "$SANDBOX/bundle.json" "$output" else echo '{"ok":true}' > "$output"; touch "$SANDBOX/completed"; fi printf 200;; */traffic) [ "$SCENARIO" != health-failure ];; *) exit 1;; esac`, ); const template = readFileSync("lib/templates/install-node.sh", "utf8") .replaceAll("__PANEL__", "'https://panel.example'") .replaceAll("__TOKEN__", "'test-token'") .replaceAll("__INSTALL_ID__", "'test-job'") .replaceAll("__SLUG__", "'test-node'") .replaceAll("/run/systemd/system", path.join(dir, "systemd")) .replaceAll("/etc/hysteria", path.join(dir, "hysteria")) .replaceAll("/etc/systemd/system", path.join(dir, "systemd")) .replaceAll("/usr/local/lib/hy2-panel", path.join(dir, "lib")); const script = path.join(dir, "install.sh"); writeFileSync(script, template); if (scenario === "occupied") { mkdirSync(path.join(dir, "hysteria")); writeFileSync(path.join(dir, "hysteria/config.yaml"), "original-config"); } try { const result = spawnSync( "bash", [ script, "--domain", scenario === "bad-domain" ? "wrong.example" : "test", "--hy2-port", "23456", "--stats-port", "23457", "--hy2-listen-port", "443", "--stats-listen-port", "9999", ], { encoding: "utf8", env: { ...process.env, PATH: `${fake}:${process.env.PATH}`, SANDBOX: dir, SCENARIO: scenario, }, timeout: 20000, }, ); assert.equal( result.status, scenario === "success" ? 0 : 1, result.stderr + result.stdout, ); assert.equal( existsSync(path.join(dir, "completed")), scenario === "success", ); if (scenario === "success") { assert.equal( readFileSync(path.join(dir, "hysteria/server.key"), "utf8").trim(), fixture.privateKey.trim(), ); assert.equal( JSON.parse(readFileSync(path.join(dir, "prepare.json"), "utf8")).ports .domain, "test.p.xn--876a.net", ); assert.match( readFileSync(path.join(dir, "systemctl.log"), "utf8"), /enable hy2-panel-node/, ); } if (scenario === "health-failure") assert.match( readFileSync(path.join(dir, "systemctl.log"), "utf8"), /stop hy2-panel-node.service/, ); if (scenario === "occupied") assert.equal( readFileSync(path.join(dir, "hysteria/config.yaml"), "utf8"), "original-config", ); } finally { rmSync(dir, { recursive: true, force: true }); } }); }