Resolve data and binary folders before starting the server (0.1.2)

onvif-dashboard install-video put MediaMTX in ./bin relative to where it
was run, but start launches the server from inside the package, so the
app looked for it there and reported it missing. serverEnvironment() now
settles CAMERAS_DATA_DIR and CAMERAS_BIN_DIR while still in the user's
directory, and start passes them on. Checked from an installed tarball:
MediaMTX in ./bin is found and the video bridge comes up.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Michael Mainguy 2026-09-19 19:35:50 -05:00
parent daaecb7452
commit 731206be8e
4 changed files with 38 additions and 4 deletions

View File

@ -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))) {

View File

@ -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",

View File

@ -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<string, string | undefined> = {};
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" });
});
});

View File

@ -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<string, string | undefined>;
@ -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<Env> {
await applyEnvironment(env, home);
env.CAMERAS_DATA_DIR ??= defaultDataDir(env);
env.CAMERAS_BIN_DIR ??= defaultBinDir(env);
return env;
}