# Cameras A web interface for discovering and managing Hikvision and Annke IP cameras on your local network. ## Securing the web interface The dashboard and its API are protected by a single admin login. The login is stored in a file, **never** in plain text: the password is hashed with [scrypt](https://nodejs.org/api/crypto.html#cryptoscryptpassword-salt-keylen-options-callback) (random salt, N=65536, r=8, p=1), so it can't be recovered from the file. | Setting | Default | | --- | --- | | `ADMIN_AUTH_FILE` (environment variable, e.g. in `.env.local`) | `./.data/admin.json` | If the file doesn't exist, the app starts **unsecured**: anyone who can reach it can view your cameras and change their logins. To avoid ever running it that way, create the admin file before the first start (below). ### Setting it up from the browser instead With no admin file, the server prints a **one-time setup code** in its console at startup: ``` ==================================================================== No admin login is set up: the camera dashboard is NOT secured. To secure it from a browser, open /setup and enter this one-time code: XLZ7-Q4MB ... ``` Opening the app sends you to `/setup`, which asks for that code plus the new admin username and password. Only someone who can see the server console can claim the login. After 5 wrong codes a new one is printed. You can also choose **Skip for now and run unsecured**. Every page then shows a red warning banner, the API stays open to anyone on the network, and the setup prompt returns the next time the browser is restarted. ### Signing in Once an admin exists, every page and API call needs a session: pages redirect to `/login`, and the API answers `401`. A session lasts **12 hours from your last activity**. After 10 wrong passwords from one address (or 100 from all addresses) within 15 minutes, logins are paused for up to 15 minutes. **Sign out** (top right) ends the session in that browser; to sign out everywhere, change the password. ### Create the admin login (recommended) ```bash npm run admin:create ``` It asks for a username and a password (at least 12 characters; typing is hidden, and you confirm it), then writes the file with owner-only permissions (`0600`). Options: ```bash npm run admin:create -- --username admin # only ask for the password npm run admin:create -- --force # replace the existing admin (reset the password) printf '%s\n' "$PW" | npm run admin:create -- --username admin # non-interactive, e.g. provisioning ``` ### Without npm: a Node one-liner Any Node 24+ can produce the same file. The password is read from the terminal without echoing, so it never appears in your shell history or the process list: ```bash (read -rs PW && export PW && umask 077 && mkdir -p .data && node -e ' const c = require("node:crypto"), N = 65536, r = 8, p = 1, salt = c.randomBytes(16); const key = c.scryptSync(process.env.PW, salt, 64, { N, r, p, maxmem: 256 * N * r }); const passwordHash = ["scrypt", N, r, p, salt.toString("base64"), key.toString("base64")].join("$"); console.log(JSON.stringify({ version: 1, username: "admin", passwordHash }, null, 2)); ' > .data/admin.json) ``` The parentheses run it in a subshell, so the password variable and the stricter `umask` end with it. Change `username: "admin"` to taste. Usernames are 1–64 letters, digits, or `. _ @ -`. ### File format ```json { "version": 1, "username": "admin", "passwordHash": "scrypt$65536$8$1$$<64-byte key, base64>" } ``` A file that exists but is malformed stops the app from authenticating anyone rather than quietly turning security off; fix it or delete it. ### Forgot the password? Run `npm run admin:create -- --force` (or delete the file and create it again). Changing the password signs out every existing session. ## Live video (MediaMTX) Browsers can't play the cameras' RTSP streams, so live video goes through [MediaMTX](https://github.com/bluenviron/mediamtx), a single MIT-licensed binary that the app will run on this machine only (localhost). Install the pinned build with: ```bash npm run video:install ``` This downloads the MediaMTX release pinned in `src/lib/mediamtx-install.ts` for your platform (macOS or Linux, Intel or ARM), checks its SHA-256 against the value committed there, and puts the binary in `./bin/` (not checked in). A download that doesn't match is refused and nothing is written. Re-running it does nothing if that build is already installed. To upgrade, change `version` in `MEDIAMTX_RELEASE` and replace every `sha256` with the values from that release's `checksums.sha256` on GitHub (cross-check them against the per-file digests on the release page), then run `npm run video:install` again. ## Motion recording When a camera reports motion over ONVIF, the app records a clip and stops a few seconds after the motion ends. Clips are fragmented MP4 and play in the browser. | Variable | Default | Meaning | | --- | --- | --- | | `RECORDINGS_DIR` | `.data/recordings` | Where clips are written; point it at an external drive or NAS if you like. One folder per camera. | | `RECORD_STREAM` | `sub` | Which stream is recorded. `sub` is about 5 MB per minute of motion, `main` about 38 MB. | | `RECORD_KEEP_DAYS` | `7` | Clips older than this are deleted by an hourly sweep. | | `MOTION_RECORDING` | on | Set to `off` to record nothing. | There is no pre-roll: a clip starts when the camera reports motion, which is a second or two after movement begins. Recording pulls the camera only while motion lasts. --- This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). ## Getting Started First, run the development server: ```bash npm run dev # or yarn dev # or pnpm dev # or bun dev ``` Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. ## Learn More To learn more about Next.js, take a look at the following resources: - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! ## Deploy on Vercel The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.