diff --git a/cli/onvif-dashboard.mjs b/cli/onvif-dashboard.mjs index bf3fbb8..0f51a25 100755 --- a/cli/onvif-dashboard.mjs +++ b/cli/onvif-dashboard.mjs @@ -6,7 +6,7 @@ import { access } from "node:fs/promises"; import { createInterface } from "node:readline/promises"; import { parseArgs } from "node:util"; import path from "node:path"; -import { applyEnvironment, firstRun, SECRETS_FILE } from "../dist/lib/first-run.js"; +import { applyEnvironment, firstRun, SECRETS_FILE, serverEnvironment } from "../dist/lib/first-run.js"; import { installMediamtx } from "../dist/lib/mediamtx-install.js"; import { adminFilePath, readAdminFile } from "../dist/lib/admin-file.js"; @@ -70,8 +70,9 @@ async function start() { if (!process.env.CAMERA_CREDENTIALS_KEY) { // Never started before: take the defaults quietly rather than refuse to run. await firstRun(); - await applyEnvironment(); } + // Settle every path now: the server runs from inside the package, not from here. + await serverEnvironment(); const server = path.join(import.meta.dirname, "..", ".next", "standalone", "server.js"); if (!(await access(server).then(() => true, () => false))) { diff --git a/package.json b/package.json index e16aa48..f850e62 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "onvif-dashboard", - "version": "0.1.1", + "version": "0.1.2", "description": "Web dashboard for ONVIF cameras (Hikvision, Annke): discovery, live video over WebRTC, motion recording and settings, all on your own network.", "keywords": [ "onvif", diff --git a/src/lib/first-run.test.ts b/src/lib/first-run.test.ts index 808e57b..33f5bdf 100644 --- a/src/lib/first-run.test.ts +++ b/src/lib/first-run.test.ts @@ -4,6 +4,7 @@ import path from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { applyEnvironment, + serverEnvironment, configFile, ensureSecrets, firstRun, @@ -188,3 +189,23 @@ describe("applyEnvironment", () => { expect(env.CAMERA_CREDENTIALS_KEY).toBeUndefined(); }); }); + +describe("serverEnvironment", () => { + it("pins the data and binary folders before the server starts elsewhere", async () => { + await writeConfig(work, {}, home); + await ensureSecrets(work); + const env: Record = {}; + await serverEnvironment(env, home); + + expect(env.CAMERAS_DATA_DIR).toBe(work); + // Resolved from where the command was typed, not from wherever the server will run. + expect(env.CAMERAS_BIN_DIR).toBe(path.join(cwd, "bin")); + expect(env.CAMERA_CREDENTIALS_KEY).toMatch(/^[0-9a-f]{64}$/); + }); + + it("leaves anything the environment already set alone", async () => { + const env = { CAMERAS_DATA_DIR: "/srv/data", CAMERAS_BIN_DIR: "/opt/bin" }; + await serverEnvironment(env, home); + expect(env).toMatchObject({ CAMERAS_DATA_DIR: "/srv/data", CAMERAS_BIN_DIR: "/opt/bin" }); + }); +}); diff --git a/src/lib/first-run.ts b/src/lib/first-run.ts index a318b1c..a32169f 100644 --- a/src/lib/first-run.ts +++ b/src/lib/first-run.ts @@ -14,7 +14,7 @@ import { randomBytes } from "node:crypto"; import { chmod, mkdir, readFile, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { dataDir as defaultDataDir, mediamtxBinary } from "./paths.ts"; +import { binDir as defaultBinDir, dataDir as defaultDataDir, mediamtxBinary } from "./paths.ts"; type Env = Record; @@ -151,3 +151,15 @@ export async function applyEnvironment(env: Env = process.env, home = os.homedir const stored = parseEnvFile(await readFile(path.join(dir, SECRETS_FILE), "utf8").catch(() => "")); for (const [key, value] of Object.entries(stored)) env[key] ??= value; } + +/** + * The environment the server is started with. The server runs from inside the package, so + * anything resolved from the working directory has to be settled here, while we are still + * in the directory the user typed the command in (vrek iss-9zcawn7). + */ +export async function serverEnvironment(env: Env = process.env, home = os.homedir()): Promise { + await applyEnvironment(env, home); + env.CAMERAS_DATA_DIR ??= defaultDataDir(env); + env.CAMERAS_BIN_DIR ??= defaultBinDir(env); + return env; +}