diff --git a/.vrek/log.ndjson b/.vrek/log.ndjson index 052df3f..1c4249a 100644 --- a/.vrek/log.ndjson +++ b/.vrek/log.ndjson @@ -1024,3 +1024,56 @@ {"id":"evt-3e3qc35ee2ck","type":"node.updated","subject":"gol-9jctv92","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"patch":{"target":1,"direction":"up","unit":"check"}},"at":"2026-09-19T23:42:25.670Z","parents":["evt-hfez48y4kc4k"],"hash":"0a4effde1d59c92963de889b3caf29aa71c7be5a1be526b3a223be0b74dce146"} {"id":"evt-v71kqfm3x5ex","type":"node.updated","subject":"gol-k56hc69","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"patch":{"body":"Someone with Node installed can run this dashboard with a single command, e.g. npx cameras or a global install with a cameras start entry point, and get discovery, live video, recording and settings without git or a build step. User's answer 2026-09-19: the whole runnable app, not a library; a library split can come later if the lib layer's shape settles. Priority 1.\n\nTarget: 1 check — the published package installed and run on a machine that has never held this repo, reaching a working dashboard with a camera visible. Recorded as a measurement once the user has seen it.\n\nWhat this forces us to settle: what ships (a built .next output plus server, rather than sources); the package name, licence and whether it is scoped; how the app finds its data, since .data and recordings currently sit in the working directory and a globally installed tool needs a sensible location and environment overrides; how MediaMTX is obtained, since the installer downloads a pinned binary and npm install scripts are often disabled, so it likely belongs in a first-run step with a clear message rather than postinstall; the admin setup flow for someone who has never seen the repo; Node version support; and how upgrades work. Nothing here changes what the app does, so the risk is in packaging decisions rather than features."}},"at":"2026-09-19T23:42:34.303Z","parents":["evt-3e3qc35ee2ck"],"hash":"11e8cb932f0c54497592dad92186852433cfccb4a7054a7268c99ac1309320bd"} {"id":"evt-64tn42sjy299","type":"node.updated","subject":"gol-9jctv92","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"patch":{"body":"apt install puts this app on a Debian or Ubuntu machine as a proper service: it starts at boot, runs as its own unprivileged user, keeps its data under /var/lib, its configuration under /etc and its logs in the journal. Priority 1. User's answer 2026-09-19: whether the package also carries MediaMTX, or leaves it to the existing first-run download, is to be decided during the work; carrying it means shipping someone else's binary and the licence and update questions that brings, while leaving it out means an install needs network access before video works.\n\nTarget: 1 check — the .deb installed on a clean Debian or Ubuntu machine, the service running after a reboot, and the dashboard reachable with a camera visible. Recorded as a measurement once the user has seen it.\n\nTo settle while doing it: which distributions and architectures; how Node itself is required or bundled; the systemd unit and its hardening; where the admin file, credential key and recordings live, and how the first-run setup is presented on a headless box; how the package is built and signed, and whether it is hosted in our own apt repository or only as a downloadable .deb; and upgrade and removal behaviour, in particular never deleting recordings or credentials on purge without asking."}},"at":"2026-09-19T23:42:38.048Z","parents":["evt-v71kqfm3x5ex"],"hash":"712092551f720619100d0aba39c57c0d42b5e39574d34106eb26be204dba4bad"} +{"id":"evt-yg0fn3rkmp78","type":"node.created","subject":"dec-wx1j9gj","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"decision","title":"Publish as onvif-dashboard under MIT; data location prompted on first run, defaulting to the working directory","body":"User's answers on 2026-09-19 for gol-k56hc69. Package name: onvif-dashboard (free on npm; \"cameras\" is taken). Licence: MIT, which needs a LICENSE file and the field in package.json, and package.json must stop being private. Data location: rather than inventing a home for the app's files, ask on first run when the environment doesn't say, offering the current working directory as the default, so today's behaviour stays the default answer. All paths move behind one module instead of each file calling process.cwd(), keeping every existing environment override (ADMIN_AUTH_FILE, CAMERA_REGISTRY_FILE, AUDIT_LOG_FILE, RECORDINGS_DIR, MEDIAMTX_BIN). A prompt must never block a service, so when there is no terminal the default is taken and the choice is logged. First run also has to produce CAMERA_CREDENTIALS_KEY, explain the admin setup code, and fetch MediaMTX with a clear message rather than a postinstall script. What ships is Next's standalone output plus a bin entry, so installing does not drag in React and Next as dependencies.","status":"recorded","owner":"prn-q80g8mz","attrs":{}},"at":"2026-09-19T23:50:01.566Z","parents":["evt-64tn42sjy299"],"hash":"d7b5f79ee7f0670979a74d00417074070ef435aca954262eb40b1cb6799fa447"} +{"id":"evt-ynpkdxtwyn35","type":"edge.added","subject":"dec-wx1j9gj","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"serves","from":"dec-wx1j9gj","to":"gol-k56hc69"},"at":"2026-09-19T23:50:01.568Z","parents":["evt-yg0fn3rkmp78"],"hash":"ee973df5a8b9e6521f49deb4217d8e2b6577a1349d6c53559b67d1ef823364e8"} +{"id":"evt-tyfj1a5xedjc","type":"edge.added","subject":"dec-wx1j9gj","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"serves","from":"dec-wx1j9gj","to":"gol-9jctv92"},"at":"2026-09-19T23:50:01.569Z","parents":["evt-ynpkdxtwyn35"],"hash":"73b2539563d22014b21dc4eb3efa58884c8ecb563a893e8bb9d5a77f56ba6d85"} +{"id":"evt-mg2g11gexc9e","type":"node.created","subject":"iss-57eqhn2","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"issue","title":"Package identity: name, MIT licence and publishable package.json","body":"Per dec-wx1j9gj. Rename the package to onvif-dashboard, drop \"private\": true, add \"license\": \"MIT\" and a LICENSE file with the user's copyright line, plus description, keywords, repository, homepage, bugs and an engines range for the Node version the app needs. Add a files list so only what is needed is published. Nothing is published in this issue: it ends with npm pack --dry-run showing a sensible file list.","status":"open","owner":null,"attrs":{}},"at":"2026-09-19T23:50:09.491Z","parents":["evt-tyfj1a5xedjc"],"hash":"2a72bc7c4f6f06fa0580e72425d72d466686a9361839e566e5147802e6c27a6d"} +{"id":"evt-0yvc5yzghh68","type":"edge.added","subject":"iss-57eqhn2","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"serves","from":"iss-57eqhn2","to":"gol-k56hc69"},"at":"2026-09-19T23:50:09.492Z","parents":["evt-mg2g11gexc9e"],"hash":"e91a6dedf327e4f16c40fbbf165dfed893da2222c6fb1ddf73036ab9666ce04b"} +{"id":"evt-ff5h16pvzhk6","type":"edge.added","subject":"iss-57eqhn2","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"touches","from":"iss-57eqhn2","to":"package.json"},"at":"2026-09-19T23:50:09.493Z","parents":["evt-0yvc5yzghh68"],"hash":"69a7b3cedfd0876e1ef750fa8f5ec92025fd2b94be85b6d44fbc25329c10d245"} +{"id":"evt-yj82na47szsv","type":"edge.added","subject":"iss-57eqhn2","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"touches","from":"iss-57eqhn2","to":"LICENSE"},"at":"2026-09-19T23:50:09.494Z","parents":["evt-ff5h16pvzhk6"],"hash":"e2e1d4afe88fc4a256e3d3284f1e4fd6b9decfd44e68d7824874a87b49139bde"} +{"id":"evt-42paven1jcjy","type":"edge.added","subject":"iss-57eqhn2","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"tagged","from":"iss-57eqhn2","to":"area:packaging"},"at":"2026-09-19T23:50:09.495Z","parents":["evt-yj82na47szsv"],"hash":"f0fe03300a6eb620d0e68dc84b69e55b701151d330bb4fccba3ccfb89290ba30"} +{"id":"evt-8g8059zambxt","type":"node.created","subject":"iss-rab8hnz","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"issue","title":"Put every data path behind one module","body":"Per dec-wx1j9gj. Today admin-file.ts, camera-registry.ts, credential-store.ts, audit-log.ts, recordings.ts, video.ts and mediamtx-install.ts each build paths from process.cwd(), so running the app from anywhere else scatters .data and bin. Introduce src/lib/paths.ts as the one place that answers where things live: a data directory (CAMERAS_DATA_DIR, else the answer chosen on first run, else ./.data) with the admin file, registry, credentials, audit log and recordings under it, and the MediaMTX binary beside it. Every existing override keeps working unchanged (ADMIN_AUTH_FILE, CAMERA_REGISTRY_FILE, AUDIT_LOG_FILE, RECORDINGS_DIR, MEDIAMTX_BIN). No behaviour change for someone running from the repo: the defaults must resolve exactly as they do now, and the tests prove it.","status":"open","owner":null,"attrs":{}},"at":"2026-09-19T23:50:14.957Z","parents":["evt-42paven1jcjy"],"hash":"73c7eacb75ea78bd60da2a52040aa6c313814e24f12a10d082b7f15b7ba81472"} +{"id":"evt-41n33hsjkd2v","type":"edge.added","subject":"iss-rab8hnz","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"serves","from":"iss-rab8hnz","to":"gol-k56hc69"},"at":"2026-09-19T23:50:14.959Z","parents":["evt-8g8059zambxt"],"hash":"270fdc6d17498bd15144be673f0d985162c037371c126e226b52d93a989c8a4f"} +{"id":"evt-3enmv7b4h1hh","type":"edge.added","subject":"iss-rab8hnz","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"serves","from":"iss-rab8hnz","to":"gol-9jctv92"},"at":"2026-09-19T23:50:14.960Z","parents":["evt-41n33hsjkd2v"],"hash":"ac5fc884af5111ac4657186f3b31bceaaff7d8cb800980191b1509e431dcf74a"} +{"id":"evt-jrrks4c86bk5","type":"edge.added","subject":"iss-rab8hnz","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"touches","from":"iss-rab8hnz","to":"src/lib/paths.ts"},"at":"2026-09-19T23:50:14.961Z","parents":["evt-3enmv7b4h1hh"],"hash":"51b837d7ccd06e4082a1be0324c16d992f70929471980887007e76175633cded"} +{"id":"evt-dpj0qxn5ebqr","type":"edge.added","subject":"iss-rab8hnz","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"touches","from":"iss-rab8hnz","to":"src/lib/admin-file.ts"},"at":"2026-09-19T23:50:14.962Z","parents":["evt-jrrks4c86bk5"],"hash":"689d8f95deb2767e8836c52b83fdfba67fe7d8219530e4cf8180c24e8909395a"} +{"id":"evt-s51taers3878","type":"edge.added","subject":"iss-rab8hnz","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"touches","from":"iss-rab8hnz","to":"src/lib/camera-registry.ts"},"at":"2026-09-19T23:50:14.963Z","parents":["evt-dpj0qxn5ebqr"],"hash":"c9bfe8163ae2060f01b1a5390e1bdc859e8638a63415a87e51ebf2868eb06b57"} +{"id":"evt-9zn4v968y7xe","type":"edge.added","subject":"iss-rab8hnz","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"touches","from":"iss-rab8hnz","to":"src/lib/credential-store.ts"},"at":"2026-09-19T23:50:14.964Z","parents":["evt-s51taers3878"],"hash":"7354876b457bf23614ac08df45ea8c31c5c9e1e6f37329e18ce0096f878041ff"} +{"id":"evt-8rm5bcasemv3","type":"edge.added","subject":"iss-rab8hnz","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"touches","from":"iss-rab8hnz","to":"src/lib/audit-log.ts"},"at":"2026-09-19T23:50:14.965Z","parents":["evt-9zn4v968y7xe"],"hash":"fc8f8d1f6a487a9b5cb58a709e360ebab06c2c7d17fb2df3ba3ba6bc4423b5cc"} +{"id":"evt-mjvgzfsjk1sc","type":"edge.added","subject":"iss-rab8hnz","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"touches","from":"iss-rab8hnz","to":"src/lib/recordings.ts"},"at":"2026-09-19T23:50:14.966Z","parents":["evt-8rm5bcasemv3"],"hash":"dd1dbccd1982e6d4dd2f032d98ac956edd97f9eb4e0542e2700c175a528008a5"} +{"id":"evt-64306h5xbzrx","type":"edge.added","subject":"iss-rab8hnz","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"touches","from":"iss-rab8hnz","to":"src/lib/video.ts"},"at":"2026-09-19T23:50:14.967Z","parents":["evt-mjvgzfsjk1sc"],"hash":"cea77695ca6a254a1709bad2d4b9d1d5b266288d1e88ab33bc0853b165caf530"} +{"id":"evt-wat14dts3e43","type":"edge.added","subject":"iss-rab8hnz","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"tagged","from":"iss-rab8hnz","to":"area:packaging"},"at":"2026-09-19T23:50:14.968Z","parents":["evt-64306h5xbzrx"],"hash":"82034e7f1fe1c5265dcb3a814b64d10c11b958bbc361830a7bf146bd0545da79"} +{"id":"evt-1gd70vahnb38","type":"node.created","subject":"iss-kz5c88a","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"issue","title":"First-run experience: choose where data lives, make the keys, fetch MediaMTX","body":"Per dec-wx1j9gj, the part a stranger meets. On first start, when the environment doesn't already say: ask where data should live, offering the current working directory as the default, and remember the answer; generate CAMERA_CREDENTIALS_KEY and write it, owner-only, so saved camera logins survive restarts; say plainly whether MediaMTX is installed and offer to fetch it, since live video and recording need it; print the admin setup code and the URL to open, with the existing severe warning if setup is skipped. With no terminal, as in a service, nothing prompts: defaults are taken, the choices are logged, and the app still starts. Running it again must be harmless: nothing is regenerated or overwritten. Tests cover the prompted path, the non-interactive path and the second run.","status":"open","owner":null,"attrs":{}},"at":"2026-09-19T23:50:29.896Z","parents":["evt-wat14dts3e43"],"hash":"3a83a6356efc08f04b1c4d6b9317fb078ab4e91c765764ba2a94191910c8b3dd"} +{"id":"evt-ex7fmsbaq6wr","type":"edge.added","subject":"iss-kz5c88a","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"serves","from":"iss-kz5c88a","to":"gol-k56hc69"},"at":"2026-09-19T23:50:29.897Z","parents":["evt-1gd70vahnb38"],"hash":"c2f5d6bb9ef297eedbedef62d073b1e3f45c2ce46a1344155f50491dd1e8cf34"} +{"id":"evt-py560xd57scq","type":"edge.added","subject":"iss-kz5c88a","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"serves","from":"iss-kz5c88a","to":"gol-9jctv92"},"at":"2026-09-19T23:50:29.898Z","parents":["evt-ex7fmsbaq6wr"],"hash":"d33200906981fa653d46e613417c2b95e2af873a115327d5fb642c84f6d9dcda"} +{"id":"evt-ks55d7y3vrdc","type":"edge.added","subject":"iss-kz5c88a","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"touches","from":"iss-kz5c88a","to":"scripts/"},"at":"2026-09-19T23:50:29.899Z","parents":["evt-py560xd57scq"],"hash":"a90c35d99715b2c7035c6ae411e8724ae7b7100691a2f0cc9270268f1dae1871"} +{"id":"evt-zwhgn2svavp6","type":"edge.added","subject":"iss-kz5c88a","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"touches","from":"iss-kz5c88a","to":"src/lib/paths.ts"},"at":"2026-09-19T23:50:29.900Z","parents":["evt-ks55d7y3vrdc"],"hash":"e306307293a54e9483cbda88eecec2093f6257173f9652e61db4627c5213d5bf"} +{"id":"evt-avmwrdmh7sfa","type":"edge.added","subject":"iss-rab8hnz","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"blocks","from":"iss-rab8hnz","to":"iss-kz5c88a"},"at":"2026-09-19T23:50:29.901Z","parents":["evt-zwhgn2svavp6"],"hash":"b41ef43cd368cc8a50d50a18364929c978893613bcaad075412c63f4b508aff9"} +{"id":"evt-bsny1nb9hxce","type":"edge.added","subject":"iss-kz5c88a","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"tagged","from":"iss-kz5c88a","to":"area:packaging"},"at":"2026-09-19T23:50:29.902Z","parents":["evt-avmwrdmh7sfa"],"hash":"a5d537b9904ef3e91143f478a4f0f5bab7f95b5d33038306cf427aa2df529477"} +{"id":"evt-0jt4xkt2jzqb","type":"node.created","subject":"iss-ej4ahga","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"issue","title":"Standalone build and a command to start it","body":"Per dec-wx1j9gj. Switch the build to Next's standalone output, so what ships is a minimal server plus only the dependencies it needs, and copy .next/static and public beside it as the Next docs require. Add a bin entry, onvif-dashboard, with subcommands: start (the server, honouring PORT and HOST), setup (the first-run experience), and the existing admin and MediaMTX helpers. Running it from any directory must work. Ends with the packed tarball started from a temporary directory on this machine, serving the dashboard.","status":"open","owner":null,"attrs":{}},"at":"2026-09-19T23:50:33.776Z","parents":["evt-bsny1nb9hxce"],"hash":"5644f269dee369f096373958d306bb4a2d8c539401a507c82c8222e941e3d12f"} +{"id":"evt-qg0xd0chcdqd","type":"edge.added","subject":"iss-ej4ahga","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"serves","from":"iss-ej4ahga","to":"gol-k56hc69"},"at":"2026-09-19T23:50:33.778Z","parents":["evt-0jt4xkt2jzqb"],"hash":"5d4b4ad1b00df87f6b06730bced0b1c6ae2348c0c66a4dffe22d042f6878a659"} +{"id":"evt-pbgznnjzttcb","type":"edge.added","subject":"iss-ej4ahga","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"touches","from":"iss-ej4ahga","to":"next.config.ts"},"at":"2026-09-19T23:50:33.779Z","parents":["evt-qg0xd0chcdqd"],"hash":"a36b7a0309a87b7cc30c836d6fa2fe4091022f544f6d0a48ed80c81ffda9967f"} +{"id":"evt-hs14sc8qsgzm","type":"edge.added","subject":"iss-ej4ahga","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"touches","from":"iss-ej4ahga","to":"package.json"},"at":"2026-09-19T23:50:33.780Z","parents":["evt-pbgznnjzttcb"],"hash":"219269e8d61e671f819ddce02c6fd2239f85325cb9d94a1e0d1be47fba899065"} +{"id":"evt-x6r595dgyr6p","type":"edge.added","subject":"iss-ej4ahga","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"touches","from":"iss-ej4ahga","to":"bin/"},"at":"2026-09-19T23:50:33.781Z","parents":["evt-hs14sc8qsgzm"],"hash":"8a211c361ed324b44aa25987909c9132b0b0555c6677ec9375c79de9a170fea5"} +{"id":"evt-y0wr9esz7599","type":"edge.added","subject":"iss-57eqhn2","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"blocks","from":"iss-57eqhn2","to":"iss-ej4ahga"},"at":"2026-09-19T23:50:33.782Z","parents":["evt-x6r595dgyr6p"],"hash":"3199b7ba5d30d1a95685ea4ef29105d850aafed97f4d122248874814d2409ad5"} +{"id":"evt-amdszktyzpt5","type":"edge.added","subject":"iss-rab8hnz","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"blocks","from":"iss-rab8hnz","to":"iss-ej4ahga"},"at":"2026-09-19T23:50:33.783Z","parents":["evt-y0wr9esz7599"],"hash":"96aed3d18c9c33f14852bb76fd17dc6c5dde2a8cb14663bd07f2e448466382b4"} +{"id":"evt-z2019vtp5hf7","type":"edge.added","subject":"iss-ej4ahga","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"tagged","from":"iss-ej4ahga","to":"area:packaging"},"at":"2026-09-19T23:50:33.784Z","parents":["evt-amdszktyzpt5"],"hash":"1a2a8ba820a90bc9639e43c14d6af7c65f128951a4906db66c858596cb77c541"} +{"id":"evt-xx39xb46aj62","type":"node.created","subject":"iss-9zcawn7","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"issue","title":"Publish onvif-dashboard and install it on a clean machine","body":"The goal's final check. Publish a release candidate to npm under the user's account (the user publishes; the app has no credentials for that), then install it somewhere that has never held this repo, run setup, add a camera and watch it. Also check what the published tarball contains, that the fork dependency installs from a clean cache, and that uninstalling leaves the data directory alone. If the onvif pull request (agsh/onvif#500) has been released by then, depend on the published package instead of the fork (iss-pkp09k8). Records the measurement that meets gol-k56hc69.","status":"open","owner":null,"attrs":{}},"at":"2026-09-19T23:50:42.192Z","parents":["evt-z2019vtp5hf7"],"hash":"c38f1745987d9a343373557822e128b25fe023c3410c38dd6ac2886678133964"} +{"id":"evt-fveqg37yn6mj","type":"edge.added","subject":"iss-9zcawn7","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"serves","from":"iss-9zcawn7","to":"gol-k56hc69"},"at":"2026-09-19T23:50:42.193Z","parents":["evt-xx39xb46aj62"],"hash":"3b738160a1fd2727d1ad5c2b63d415539a97f3605c1d48a6a152cd1e7ac4066a"} +{"id":"evt-prdywnascbd6","type":"edge.added","subject":"iss-ej4ahga","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"blocks","from":"iss-ej4ahga","to":"iss-9zcawn7"},"at":"2026-09-19T23:50:42.194Z","parents":["evt-fveqg37yn6mj"],"hash":"f8a75c643518fc63bd07cc862ca0cb2fb6be504f58dfe93dc628ff3d9cefcc55"} +{"id":"evt-6yyzx2d4dh1v","type":"edge.added","subject":"iss-kz5c88a","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"blocks","from":"iss-kz5c88a","to":"iss-9zcawn7"},"at":"2026-09-19T23:50:42.195Z","parents":["evt-prdywnascbd6"],"hash":"a3e8f768078f33e460137a03ad02e0c1af1d352a65687f971a39281ea9571c1f"} +{"id":"evt-d4acaz9qsx2c","type":"edge.added","subject":"iss-9zcawn7","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"tagged","from":"iss-9zcawn7","to":"area:packaging"},"at":"2026-09-19T23:50:42.196Z","parents":["evt-6yyzx2d4dh1v"],"hash":"e483e96f8348cce4c57b00894e5dda4ad996ad519123291c1929119cd1baa71d"} +{"id":"evt-g8gkqtcrk7h9","type":"node.updated","subject":"gol-k56hc69","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"patch":{"target":5,"direction":"up","unit":"issues done"}},"at":"2026-09-19T23:50:50.445Z","parents":["evt-d4acaz9qsx2c"],"hash":"3fa12e83fcd919155898ae84018b46a3e5890cdebe94a62b5ae9b57177f13346"} +{"id":"evt-dh6y5bhh7vj1","type":"node.updated","subject":"gol-k56hc69","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"patch":{"body":"Someone with Node installed can run this dashboard with a single command, e.g. npx onvif-dashboard, and get discovery, live video, recording and settings without git or a build step. User's answers 2026-09-19 (dec-wx1j9gj): the whole runnable app rather than a library; package name onvif-dashboard; MIT licence; where data lives is asked on first run, defaulting to the working directory. Priority 1.\n\nTarget: 5 issues done, the work this breaks into as of 2026-09-19: iss-57eqhn2 package identity (name, MIT, publishable package.json), iss-rab8hnz every data path behind one module, iss-kz5c88a the first-run experience, iss-ej4ahga standalone build and a start command, and iss-9zcawn7 publish and install on a machine that has never held this repo. The last one carries the real proof: a working dashboard with a camera visible, on a clean machine. If the work turns out to need more issues, the target moves with it and the change is recorded.\n\nWhat this forces us to settle, and where each is handled: what ships (standalone output, iss-ej4ahga); name, licence and metadata (iss-57eqhn2); where data lives now that it can't assume the working directory (iss-rab8hnz); how MediaMTX is obtained, since npm install scripts are often disabled (iss-kz5c88a); the credential key and admin setup for someone who has never seen the repo (iss-kz5c88a); and the dependency on the onvif fork, which should become the published package once agsh/onvif#500 is released (iss-9zcawn7, iss-pkp09k8)."}},"at":"2026-09-19T23:50:56.866Z","parents":["evt-g8gkqtcrk7h9"],"hash":"793c7f4fe3d3ecd84a3549bddd1777641eb56954b7cd6e0d540407213ff57f73"} +{"id":"evt-0wzcts22k31h","type":"node.created","subject":"mea-9a31xkj","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"measurement","title":"npm packaging: 0 of 5 issues done","body":"Starting point on 2026-09-19, when the work was broken down: none of the five issues under gol-k56hc69 is closed. Counted from vrek, not estimated.","status":"recorded","owner":null,"attrs":{"value":0,"applies_at":"2026-09-19"}},"at":"2026-09-19T23:50:58.813Z","parents":["evt-dh6y5bhh7vj1"],"hash":"7f02887d8d4232b14a7f64f960c621a028b55c2771f7125fe6b0d83c7ff5f0e9"} +{"id":"evt-9pgxq502sxr1","type":"edge.added","subject":"mea-9a31xkj","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"measures","from":"mea-9a31xkj","to":"gol-k56hc69"},"at":"2026-09-19T23:50:58.814Z","parents":["evt-0wzcts22k31h"],"hash":"eabf48665cd96e15fd1b7bfcb0dd25ac15e6c39793da56777a1d3ddff493d93d"} +{"id":"evt-jg5cpmw6yjxk","type":"node.created","subject":"ver-9gbabvx","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"verification","title":"The package is publishable as onvif-dashboard under MIT, with a LICENSE file and metadata, and npm pack ships only what is needed","body":"","status":"pending","owner":"prn-q80g8mz","attrs":{}},"at":"2026-09-19T23:54:41.146Z","parents":["evt-9pgxq502sxr1"],"hash":"9de770f442db086e0b6d545dfbf99d198d52114f91445482ea7fdca8a34cc098"} +{"id":"evt-j8nqeh6f2zp8","type":"edge.added","subject":"ver-9gbabvx","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"evidence_for","from":"ver-9gbabvx","to":"iss-57eqhn2"},"at":"2026-09-19T23:54:41.148Z","parents":["evt-jg5cpmw6yjxk"],"hash":"d395a0f0522de00c7d523cce7738ad615e4bafadfe3c104f4c61decf3e5f519a"} +{"id":"evt-n4zmd4aqm5vq","type":"verification.recorded","subject":"ver-9gbabvx","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"result":"pass","evidence":"package.json renamed to onvif-dashboard, \"private\" removed, license MIT with description, keywords, author, repository, engines (node >=24) and a files list; LICENSE added (MIT, Michael Mainguy, 2026). npm pack --dry-run: 43 files, 472.6 kB packed, 1.3 MB unpacked. The first attempt was 27.4 MB because the files list swept in the downloaded MediaMTX binary; bin/ is now excluded and the CLI will live in cli/."},"at":"2026-09-19T23:54:41.149Z","parents":["evt-j8nqeh6f2zp8"],"hash":"6003d5ddaa1147699f85f37ee8d3410c819ee96190d8bc5e740159e9daf1d473"} +{"id":"evt-b7vfg9e1fgzc","type":"node.created","subject":"ver-4596467","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"verification","title":"One module decides where the app's files live; CAMERAS_DATA_DIR moves them all, every per-file override still wins, and the defaults are exactly what running from the repo meant before","body":"","status":"pending","owner":"prn-q80g8mz","attrs":{}},"at":"2026-09-19T23:54:43.653Z","parents":["evt-n4zmd4aqm5vq"],"hash":"3336dcbad6d08e8283606625225ee6c760a79da062051d6d2fa28005c77ec142"} +{"id":"evt-tbx082w480gd","type":"edge.added","subject":"ver-4596467","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"evidence_for","from":"ver-4596467","to":"iss-rab8hnz"},"at":"2026-09-19T23:54:43.656Z","parents":["evt-b7vfg9e1fgzc"],"hash":"162c9243975b26a18f60a8bf9984f885a9fd02b113a1ed0b4ec1566e352d868a"} +{"id":"evt-928phbmmmsz8","type":"verification.recorded","subject":"ver-4596467","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"result":"pass","evidence":"src/lib/paths.ts with admin-file, camera-registry, credential-store, audit-log, recordings, video and mediamtx-install all reading from it. src/lib/paths.test.ts pins the defaults (./.data/*, ./bin/mediamtx), CAMERAS_DATA_DIR moving everything the app writes, and each of ADMIN_AUTH_FILE, CAMERA_REGISTRY_FILE, CAMERA_CREDENTIALS_FILE, AUDIT_LOG_FILE, RECORDINGS_DIR and MEDIAMTX_BIN still winning. Full suite 912 tests pass, 99.69% lines; tsc, eslint and next build clean; npm run video:install still finds the existing binary and .data is untouched."},"at":"2026-09-19T23:54:43.657Z","parents":["evt-tbx082w480gd"],"hash":"a65c047e4e87aa4e01e0442e4c0e1fafccdca00457d3bda04d85a5c52692dccc"} +{"id":"evt-zy1a4sk0tc37","type":"node.status_changed","subject":"iss-57eqhn2","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"from":"open","to":"done"},"at":"2026-09-19T23:54:45.770Z","parents":["evt-928phbmmmsz8"],"hash":"5862722129d2a58d4c33aa39b4691621b1c839cd2daf1b791bddee4f9ab493a0"} +{"id":"evt-xvzexr3d4fyh","type":"node.status_changed","subject":"iss-rab8hnz","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"from":"open","to":"done"},"at":"2026-09-19T23:54:47.875Z","parents":["evt-zy1a4sk0tc37"],"hash":"07560237cef3dc19f3dc9c905f46306c4a2371c023cf778ab9b74e7c6792a215"} +{"id":"evt-2x3g58fk7cwy","type":"node.created","subject":"mea-kha11x6","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"measurement","title":"npm packaging: 2 of 5 issues done","body":"Counted from vrek on 2026-09-19: iss-57eqhn2 (package identity, MIT, publishable package.json) and iss-rab8hnz (every data path behind src/lib/paths.ts) are closed with evidence. Left: iss-kz5c88a first-run experience, iss-ej4ahga standalone build and start command, iss-9zcawn7 publish and install on a clean machine.","status":"recorded","owner":null,"attrs":{"value":2,"applies_at":"2026-09-19"}},"at":"2026-09-19T23:54:54.082Z","parents":["evt-xvzexr3d4fyh"],"hash":"b99544debb7dfefe2f317b7b3a1c96afe1c6567dfe3847ed952f1de8fe798668"} +{"id":"evt-7t57v5scysa9","type":"edge.added","subject":"mea-kha11x6","actor":"prn-q80g8mz","actor_kind":"agent","session":null,"payload":{"kind":"measures","from":"mea-kha11x6","to":"gol-k56hc69"},"at":"2026-09-19T23:54:54.083Z","parents":["evt-2x3g58fk7cwy"],"hash":"8efd00dee34eac7287d7ff89cd51d3d1b03014d1baec7a0049e254587885439d"} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..95584e9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Michael Mainguy + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/package.json b/package.json index ca44e21..e5ae24a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,37 @@ { - "name": "cameras", + "name": "onvif-dashboard", "version": "0.1.0", - "private": true, + "description": "Web dashboard for ONVIF cameras (Hikvision, Annke): discovery, live video over WebRTC, motion recording and settings, all on your own network.", + "keywords": [ + "onvif", + "hikvision", + "annke", + "ip-camera", + "cctv", + "webrtc", + "nvr", + "surveillance", + "dashboard" + ], + "license": "MIT", + "author": "Michael Mainguy", + "repository": { + "type": "git", + "url": "git+https://git.dasfad.com/mmainguy/cameras.git" + }, + "engines": { + "node": ">=24.0.0" + }, + "files": [ + ".next/standalone", + ".next/static", + "public", + "cli", + "scripts/create-admin.mts", + "scripts/install-mediamtx.mts", + "LICENSE", + "README.md" + ], "scripts": { "dev": "next dev", "build": "next build", diff --git a/src/lib/admin-file.test.ts b/src/lib/admin-file.test.ts index e2679c0..6c3f5dc 100644 --- a/src/lib/admin-file.test.ts +++ b/src/lib/admin-file.test.ts @@ -6,7 +6,6 @@ import { AdminFileError, adminFilePath, createAdminRecord, - DEFAULT_ADMIN_FILE, hashPassword, isValidPasswordHash, passwordSchema, @@ -43,8 +42,7 @@ describe("adminFilePath", () => { }); it.each([[{}], [{ ADMIN_AUTH_FILE: "" }]])("defaults to ./.data/admin.json for %j", (env) => { - expect(adminFilePath(env)).toBe(DEFAULT_ADMIN_FILE); - expect(DEFAULT_ADMIN_FILE).toBe(path.join(process.cwd(), ".data", "admin.json")); + expect(adminFilePath(env)).toBe(path.join(process.cwd(), ".data", "admin.json")); }); }); diff --git a/src/lib/admin-file.ts b/src/lib/admin-file.ts index 639247c..541b70d 100644 --- a/src/lib/admin-file.ts +++ b/src/lib/admin-file.ts @@ -9,18 +9,13 @@ import { randomBytes, scrypt as scryptCb, timingSafeEqual, type ScryptOptions } import { link, mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; +import { adminFile } from "./paths.ts"; // Runtime data, not a build input: the ignore comment keeps it out of Next's output file // tracing (see camera-registry.ts). -export const DEFAULT_ADMIN_FILE = path.join( - /* turbopackIgnore: true */ process.cwd(), - ".data", - "admin.json", -); - -/** ADMIN_AUTH_FILE if set, otherwise ./.data/admin.json. */ +/** ADMIN_AUTH_FILE if set, otherwise admin.json in the data folder (see lib/paths.ts). */ export function adminFilePath(env: Record = process.env): string { - return env.ADMIN_AUTH_FILE || DEFAULT_ADMIN_FILE; + return adminFile(env); } export const usernameSchema = z diff --git a/src/lib/audit-log.ts b/src/lib/audit-log.ts index edf8185..4432ed9 100644 --- a/src/lib/audit-log.ts +++ b/src/lib/audit-log.ts @@ -1,6 +1,7 @@ import "server-only"; import { appendFile, mkdir } from "node:fs/promises"; import path from "node:path"; +import { auditLogFile } from "./paths"; /** * Append-only record of configuration changes (vrek dec-n4e8jah): who changed which @@ -11,10 +12,9 @@ import path from "node:path"; // Runtime data, not a build input: the ignore comment keeps it out of Next's output file // tracing (see camera-registry.ts). -const DEFAULT_AUDIT_LOG = path.join(/* turbopackIgnore: true */ process.cwd(), ".data", "audit.jsonl"); export function auditLogPath(env: Record = process.env): string { - return env.AUDIT_LOG_FILE || DEFAULT_AUDIT_LOG; + return auditLogFile(env); } export interface AuditChange { diff --git a/src/lib/camera-registry.ts b/src/lib/camera-registry.ts index 7cd4ddd..0c1ecce 100644 --- a/src/lib/camera-registry.ts +++ b/src/lib/camera-registry.ts @@ -4,6 +4,7 @@ import { mkdir, readFile, rename, writeFile } from "node:fs/promises"; import path from "node:path"; import { connection } from "next/server"; import { z } from "zod"; +import { registryFile } from "./paths"; import { CredentialStoreError, moveCredentials } from "./credential-store"; /** @@ -34,9 +35,7 @@ export interface CameraSummary { // Runtime data, not a build input. The ignore comment keeps it out of Next's output file // tracing, which would otherwise copy this machine's .data/ (or, for an env-dependent path, // the whole project) into the server bundle. -const REGISTRY_PATH = - process.env.CAMERA_REGISTRY_FILE ?? - path.join(/* turbopackIgnore: true */ process.cwd(), ".data", "cameras.json"); +const REGISTRY_PATH = registryFile(); const UUID_URN = /^urn:uuid:([0-9a-f-]{36})$/i; diff --git a/src/lib/credential-store.ts b/src/lib/credential-store.ts index b340d1f..59541f6 100644 --- a/src/lib/credential-store.ts +++ b/src/lib/credential-store.ts @@ -3,6 +3,7 @@ import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "node: import { mkdir, readFile, rename, writeFile } from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; +import { credentialsFile } from "./paths"; export interface Credentials { username: string; @@ -21,9 +22,7 @@ export const credentialsSchema = z.object({ // Runtime data, not a build input. The ignore comment keeps it out of Next's output file // tracing, which would otherwise copy this machine's .data/ (or, for an env-dependent path, // the whole project) into the server bundle. -const STORE_PATH = - process.env.CAMERA_CREDENTIALS_FILE ?? - path.join(/* turbopackIgnore: true */ process.cwd(), ".data", "credentials.json"); +const STORE_PATH = credentialsFile(); type Store = Record; diff --git a/src/lib/mediamtx-install.ts b/src/lib/mediamtx-install.ts index 962d2d2..526d730 100644 --- a/src/lib/mediamtx-install.ts +++ b/src/lib/mediamtx-install.ts @@ -12,6 +12,7 @@ import { access, chmod, copyFile, mkdir, mkdtemp, readFile, rename, rm, writeFil import os from "node:os"; import path from "node:path"; import { promisify } from "node:util"; +import { binDir as defaultBinDir } from "./paths.ts"; /** * To upgrade: bump the version and replace every sha256 with the values from that @@ -111,7 +112,7 @@ async function installedMarker(binDir: string): Promise<{ version: string; sha25 * Verifies the archive's SHA-256 before extracting, and swaps the binary in atomically. */ export async function installMediamtx({ - binDir = path.join(process.cwd(), "bin"), + binDir = defaultBinDir(), key = platformKey(), release = MEDIAMTX_RELEASE, deps = defaultDeps, diff --git a/src/lib/paths.test.ts b/src/lib/paths.test.ts new file mode 100644 index 0000000..7fd1b26 --- /dev/null +++ b/src/lib/paths.test.ts @@ -0,0 +1,68 @@ +import path from "node:path"; +import { describe, expect, it } from "vitest"; +import { + adminFile, + auditLogFile, + binDir, + credentialsFile, + dataDir, + mediamtxBinary, + recordingsDir, + registryFile, + videoBridgeDir, +} from "./paths"; + +/** + * These paths are what an upgrade could quietly move, so the defaults are pinned: running + * from the repo must keep meaning ./.data and ./bin (vrek iss-rab8hnz). + */ +const here = (...parts: string[]) => path.join(process.cwd(), ...parts); + +describe("defaults, unchanged from running in the repo", () => { + it.each([ + [dataDir, here(".data")], + [binDir, here("bin")], + [adminFile, here(".data", "admin.json")], + [registryFile, here(".data", "cameras.json")], + [credentialsFile, here(".data", "credentials.json")], + [auditLogFile, here(".data", "audit.jsonl")], + [recordingsDir, here(".data", "recordings")], + [videoBridgeDir, here(".data", "mediamtx")], + [mediamtxBinary, here("bin", "mediamtx")], + ])("%# resolves to the usual place", (fn, expected) => { + expect(fn({})).toBe(expected); + }); +}); + +describe("CAMERAS_DATA_DIR moves everything the app writes", () => { + const env = { CAMERAS_DATA_DIR: "/var/lib/onvif-dashboard" }; + + it.each([ + [adminFile, "/var/lib/onvif-dashboard/admin.json"], + [registryFile, "/var/lib/onvif-dashboard/cameras.json"], + [credentialsFile, "/var/lib/onvif-dashboard/credentials.json"], + [auditLogFile, "/var/lib/onvif-dashboard/audit.jsonl"], + [recordingsDir, "/var/lib/onvif-dashboard/recordings"], + [videoBridgeDir, "/var/lib/onvif-dashboard/mediamtx"], + ])("%# follows it", (fn, expected) => { + expect(fn(env)).toBe(expected); + }); + + it("leaves the binary alone, which lives with the app rather than the data", () => { + expect(mediamtxBinary(env)).toBe(here("bin", "mediamtx")); + expect(binDir({ CAMERAS_BIN_DIR: "/opt/onvif-dashboard/bin" })).toBe("/opt/onvif-dashboard/bin"); + }); +}); + +describe("each file keeps its own override", () => { + it.each([ + [adminFile, "ADMIN_AUTH_FILE", "/etc/admin.json"], + [registryFile, "CAMERA_REGISTRY_FILE", "/etc/cameras.json"], + [credentialsFile, "CAMERA_CREDENTIALS_FILE", "/etc/creds.json"], + [auditLogFile, "AUDIT_LOG_FILE", "/var/log/audit.jsonl"], + [recordingsDir, "RECORDINGS_DIR", "/mnt/nas/clips"], + [mediamtxBinary, "MEDIAMTX_BIN", "/usr/local/bin/mediamtx"], + ])("%# wins over the data folder", (fn, key, value) => { + expect(fn({ CAMERAS_DATA_DIR: "/var/lib/x", [key]: value })).toBe(value); + }); +}); diff --git a/src/lib/paths.ts b/src/lib/paths.ts new file mode 100644 index 0000000..8845db1 --- /dev/null +++ b/src/lib/paths.ts @@ -0,0 +1,54 @@ +/** + * Where the app keeps its files (vrek iss-rab8hnz, dec-wx1j9gj). One module answers this, + * so the app can run from anywhere, not only from its own source folder. + * + * CAMERAS_DATA_DIR names the folder holding the admin file, camera registry, encrypted + * credentials, audit log, recordings and the video bridge's config. Without it the folder + * is ./.data, which is what running from the repo has always meant. Each file also keeps + * its own long-standing override, so nothing that worked before stops working. + * + * Plain Node on purpose (no "server-only", no path aliases, no TS-only syntax) so the + * scripts in scripts/ can use it directly. + */ +import path from "node:path"; + +type Env = Record; + +/** The folder that holds everything the app writes. */ +export function dataDir(env: Env = process.env): string { + return env.CAMERAS_DATA_DIR || path.join(/* turbopackIgnore: true */ process.cwd(), ".data"); +} + +/** Where downloaded helpers live, MediaMTX today. Kept beside the app, not in the data folder. */ +export function binDir(env: Env = process.env): string { + return env.CAMERAS_BIN_DIR || path.join(/* turbopackIgnore: true */ process.cwd(), "bin"); +} + +export function adminFile(env: Env = process.env): string { + return env.ADMIN_AUTH_FILE || path.join(dataDir(env), "admin.json"); +} + +export function registryFile(env: Env = process.env): string { + return env.CAMERA_REGISTRY_FILE || path.join(dataDir(env), "cameras.json"); +} + +export function credentialsFile(env: Env = process.env): string { + return env.CAMERA_CREDENTIALS_FILE || path.join(dataDir(env), "credentials.json"); +} + +export function auditLogFile(env: Env = process.env): string { + return env.AUDIT_LOG_FILE || path.join(dataDir(env), "audit.jsonl"); +} + +export function recordingsDir(env: Env = process.env): string { + return env.RECORDINGS_DIR || path.join(dataDir(env), "recordings"); +} + +/** The video bridge's generated config and pid file. */ +export function videoBridgeDir(env: Env = process.env): string { + return path.join(dataDir(env), "mediamtx"); +} + +export function mediamtxBinary(env: Env = process.env): string { + return env.MEDIAMTX_BIN || path.join(binDir(env), "mediamtx"); +} diff --git a/src/lib/recordings.ts b/src/lib/recordings.ts index c5de5c9..d6ffaff 100644 --- a/src/lib/recordings.ts +++ b/src/lib/recordings.ts @@ -2,6 +2,7 @@ import "server-only"; import { readdir, rm, stat } from "node:fs/promises"; import path from "node:path"; import { recordPathName, type StreamKind } from "./mediamtx-config"; +import { recordingsDir as recordingsPath } from "./paths"; /** * Where motion clips live and how long they are kept (vrek dec-9z8vdmj). Retention is our @@ -13,10 +14,7 @@ export const DEFAULT_KEEP_DAYS = 7; export const CLIP_EXTENSION = ".mp4"; export function recordingsDir(): string { - return ( - process.env.RECORDINGS_DIR || - path.join(/* turbopackIgnore: true */ process.cwd(), ".data", "recordings") - ); + return recordingsPath(); } /** Which stream is recorded; the sub stream is about an eighth the size of the main one. */ diff --git a/src/lib/video.ts b/src/lib/video.ts index ecdd909..9e86fc4 100644 --- a/src/lib/video.ts +++ b/src/lib/video.ts @@ -15,6 +15,7 @@ import { type StreamKind, } from "./mediamtx-config"; import { recordingsDir } from "./recordings"; +import { mediamtxBinary as mediamtxBinaryPath, videoBridgeDir } from "./paths"; import { forgetPid, PID_FILE, recordPid, stopLeftoverMediamtx } from "./mediamtx-leftover"; import { MediamtxSupervisor, type BridgeStatus } from "./mediamtx-supervisor"; @@ -42,11 +43,11 @@ const KEY = Symbol.for("cameras.videoBridge"); const g = globalThis as { [KEY]?: Bridge }; export function mediamtxBinary(): string { - return process.env.MEDIAMTX_BIN || path.join(/* turbopackIgnore: true */ process.cwd(), "bin", "mediamtx"); + return mediamtxBinaryPath(); } function bridgeDir(): string { - return path.join(/* turbopackIgnore: true */ process.cwd(), ".data", "mediamtx"); + return videoBridgeDir(); } function authHeader(login: ApiLogin): string {