| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- /*
- ArozOS test-instance launcher for the full-stack E2E specs.
- Boots a disposable, real ArozOS server (the Go binary built from src/)
- inside an isolated instance folder, so specs can exercise genuine
- authentication, desktop, file system and admin APIs without touching
- the developer's own runtime data under src/.
- Instance layout (recreated from scratch on every start):
- .instance/
- ├── web symlink to ../../../src/web (static assets, read-only)
- ├── system private copy of src/system (ao.db + runtime state)
- ├── files/ user home directories (created by the server)
- ├── tmp/ scratch space (created by the server)
- └── server.log combined stdout/stderr of the server process
- Because the instance is wiped each start, the server always boots in
- the zero-user state and bootstrapAdmin() creates a deterministic
- administrator account through the same public endpoint the first-boot
- wizard (user.html) posts to.
- Env:
- AROZOS_BIN path to a prebuilt arozos binary (default: src/arozos,
- built automatically with `go build` when missing)
- */
- "use strict";
- const path = require("path");
- const fs = require("fs");
- const { spawn, spawnSync } = require("child_process");
- const REPO_ROOT = path.resolve(__dirname, "../../../..");
- const SRC_DIR = path.join(REPO_ROOT, "src");
- const WEB_ROOT = path.join(SRC_DIR, "web");
- const SYSTEM_TEMPLATE = path.join(SRC_DIR, "system");
- const INSTANCE_DIR = path.resolve(__dirname, "../.instance");
- const DEFAULT_PORT = 8126;
- const ADMIN_USER = "admin";
- const ADMIN_PASS = "e2e-Admin-Passw0rd";
- function binaryName() {
- return process.platform === "win32" ? "arozos.exe" : "arozos";
- }
- // Locate the server binary, building it with `go build` when missing.
- function resolveBinary() {
- if (process.env.AROZOS_BIN && fs.existsSync(process.env.AROZOS_BIN)) {
- return path.resolve(process.env.AROZOS_BIN);
- }
- const builtBin = path.join(SRC_DIR, binaryName());
- if (fs.existsSync(builtBin)) {
- return builtBin;
- }
- console.log(" arozos binary not found, building it (go build)...");
- const res = spawnSync("go", ["build", "-o", binaryName(), "."], {
- cwd: SRC_DIR,
- stdio: "inherit"
- });
- if (res.status !== 0 || !fs.existsSync(builtBin)) {
- throw new Error("Failed to build the arozos binary. Install Go or set AROZOS_BIN.");
- }
- return builtBin;
- }
- // Wipe and recreate the isolated instance directory.
- function prepareInstanceDir() {
- fs.rmSync(INSTANCE_DIR, { recursive: true, force: true });
- fs.mkdirSync(INSTANCE_DIR, { recursive: true });
- fs.symlinkSync(WEB_ROOT, path.join(INSTANCE_DIR, "web"), "dir");
- fs.cpSync(SYSTEM_TEMPLATE, path.join(INSTANCE_DIR, "system"), { recursive: true });
- }
- function sleep(ms) {
- return new Promise(function (resolve) { setTimeout(resolve, ms); });
- }
- async function waitUntilReady(baseURL, timeoutMs) {
- const deadline = Date.now() + timeoutMs;
- let lastError = null;
- while (Date.now() < deadline) {
- try {
- const res = await fetch(baseURL + "/system/auth/checkLogin");
- if (res.ok) { return; }
- lastError = new Error("HTTP " + res.status);
- } catch (e) {
- lastError = e;
- }
- await sleep(250);
- }
- throw new Error("ArozOS server did not become ready in time: " + (lastError ? lastError.message : "unknown"));
- }
- // Create the first (admin) account on a freshly wiped instance.
- async function bootstrapAdmin(baseURL) {
- const res = await fetch(baseURL + "/system/auth/register", {
- method: "POST",
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
- body: new URLSearchParams({
- username: ADMIN_USER,
- password: ADMIN_PASS,
- group: "administrator"
- })
- });
- const text = (await res.text()).trim().toLowerCase();
- if (text.indexOf("ok") === -1) {
- throw new Error("Admin bootstrap failed: " + text);
- }
- }
- /*
- Start a disposable ArozOS server.
- Returns { baseURL, admin: {username, password}, stop() }.
- */
- async function start(options) {
- options = options || {};
- const port = options.port || Number(process.env.AROZ_PORT) || DEFAULT_PORT;
- const binary = resolveBinary();
- prepareInstanceDir();
- const logStream = fs.createWriteStream(path.join(INSTANCE_DIR, "server.log"));
- const args = [
- "-port", String(port),
- "-hostname", "E2E ArozOS",
- // Keep the test instance quiet and self-contained: no LAN discovery
- // broadcasts, no hardware/power hooks, no package auto-install and
- // no child subservice processes.
- "-allow_mdns=false",
- "-allow_ssdp=false",
- "-allow_upnp=false",
- "-allow_iot=false",
- "-disable_subservice",
- "-enable_hwman=false",
- "-enable_pwman=false",
- "-allow_pkg_install=false",
- "-enable_docker=false",
- "-arozcast_turn=false"
- ];
- const proc = spawn(binary, args, {
- cwd: INSTANCE_DIR,
- stdio: ["ignore", "pipe", "pipe"]
- });
- proc.stdout.pipe(logStream);
- proc.stderr.pipe(logStream);
- let exited = false;
- proc.on("exit", function () { exited = true; });
- const baseURL = "http://127.0.0.1:" + port;
- try {
- await waitUntilReady(baseURL, 120000);
- await bootstrapAdmin(baseURL);
- } catch (e) {
- proc.kill("SIGKILL");
- throw e;
- }
- function stop() {
- return new Promise(function (resolve) {
- if (exited) { return resolve(); }
- proc.on("exit", function () { resolve(); });
- proc.kill("SIGTERM");
- // Escalate if the server ignores SIGTERM.
- setTimeout(function () {
- if (!exited) { proc.kill("SIGKILL"); }
- }, 8000).unref();
- });
- }
- return {
- baseURL: baseURL,
- admin: { username: ADMIN_USER, password: ADMIN_PASS },
- instanceDir: INSTANCE_DIR,
- stop: stop
- };
- }
- module.exports = {
- start,
- ADMIN_USER,
- ADMIN_PASS,
- INSTANCE_DIR
- };
|