From 48e62ea2258dae0b60ef395fad3ff0d7c92fa589 Mon Sep 17 00:00:00 2001 From: S'Bussiso Dube <80188685+Sbussiso@users.noreply.github.com> Date: Wed, 9 Sep 2026 14:29:42 -0700 Subject: [PATCH 1/2] =?UTF-8?q?Watch=20dependencies=20=E2=80=94=20this=20b?= =?UTF-8?q?inary=20ships=20to=20hardware=20we=20don't=20control?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No Dependabot config existed here, and this is the repo where that matters most. CameraNode ships as a BINARY onto customers' own machines, so a vulnerable dependency is not something we fix by redeploying — it sits on hardware we do not control until each operator updates. The lag between "advisory published" and "our build is clean" is the part that actually costs something. The gap was concrete, not theoretical: RUSTSEC advisories in this tree were cleared BY HAND earlier today (reqwest, tokio-tungstenite and warp were all moved), and nothing has watched them since. `cargo audit` in CI catches a known advisory against what is already pinned; it does nothing about a lockfile ageing out of support. Three ecosystems, and the npm one is easy to overlook: web/ is built INTO the binary at compile time, so a vulnerable frontend dependency ships to customer hardware too rather than being served from somewhere we can patch centrally. github-actions is on the supply path as well — the Release workflow builds the artifacts customers download. No auto-merge: a Cargo bump changes a binary customers install. Co-Authored-By: Claude Opus 5 --- .github/dependabot.yml | 62 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..29d5ea0 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,62 @@ +# Dependabot configuration. +# +# This repo had NO dependency watching until 2026-09-09, and it is the +# one where that matters most: CameraNode ships as a BINARY onto +# customers' own hardware. A vulnerable dependency here is not something +# we can fix by redeploying — it sits on machines we do not control +# until each operator updates, so the lag between "advisory published" +# and "our build is clean" is the part that actually costs something. +# +# The gap was concrete, not theoretical. RUSTSEC advisories in this +# tree were cleared BY HAND earlier on 2026-09-09 (reqwest, tokio- +# tungstenite and warp were all moved), and nothing has watched them +# since. `cargo audit` in CI catches a known advisory against what is +# already pinned; it does nothing about a lockfile ageing out of +# support. That is what this is for. +# +# NO AUTO-MERGE WORKFLOW HERE, DELIBERATELY — a Cargo bump changes a +# binary that customers install. These get reviewed. (The sibling +# services skip auto-merge for a different reason: GITHUB_TOKEN merges +# do not trigger their deploy workflows.) +version: 2 +updates: + # Rust — Cargo.toml + Cargo.lock at the repo root. This is the one + # that ends up in the shipped binary. + - package-ecosystem: "cargo" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + open-pull-requests-limit: 5 + commit-message: + prefix: "build(deps)" + labels: + - "dependencies" + - "rust" + + # The embedded web UI, built into the binary at compile time — so a + # vulnerable frontend dependency ships to customer hardware too, + # rather than being served from somewhere we can patch centrally. + - package-ecosystem: "npm" + directory: "/web" + schedule: + interval: "weekly" + day: "monday" + open-pull-requests-limit: 5 + commit-message: + prefix: "build(deps-dev)" + labels: + - "dependencies" + - "frontend" + + # GitHub Actions — this repo's Release workflow builds the artifacts + # customers download, so a stale action here is on the supply path. + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + open-pull-requests-limit: 5 + labels: + - "dependencies" + - "ci" From 6ab6d48412005255729d09c64e6e8ea2773a2e41 Mon Sep 17 00:00:00 2001 From: S'Bussiso Dube <80188685+Sbussiso@users.noreply.github.com> Date: Wed, 9 Sep 2026 19:26:06 -0700 Subject: [PATCH 2/2] =?UTF-8?q?Retire=20the=20`cloudnode`=20name=20?= =?UTF-8?q?=E2=80=94=20the=20binary=20finally=20matches=20the=20product?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `cloudnode` was the name minted under the SourceBox Sentry brand. `cameranode` replaced it when the repo was renamed in May 2026, and it is the better word regardless: this binary drives cameras. It was never a cloud node. The code never followed. Cargo.toml still declared `sourcebox-sentry-cloudnode`, so every release asset shipped under the dead name, and the split identity had started costing real things: - install.sh carried a workaround — extract the archive, then RENAME the binary to cameranode before installing, purely to hide this. - The MSI does no such rename, so Windows installs landed as "Sentinel CloudNode" running sourcebox-sentry-cloudnode.exe while the README documented the Linux name. Every Windows instruction was wrong: the file you download, the Start-menu entry you click, where the binary lives, the command you run. Cargo.toml's own comment deferred this to "probably alongside a v1.0.0 release" because renaming orphans systemd units, Windows service registrations and Docker volumes. There is nothing to orphan — no customers, no installs — so the deferral was protecting against a cost that does not exist yet and would only have grown. Done now instead. 255 occurrences, 62 files. Three things were NOT renamed, deliberately: - `SOURCEBOX_SENTRY_*` and `C:\ProgramData\SourceBoxSentry\` — these never contained "cloudnode" and are correctly preserved. - `opensentry-cloud-node` in two brand-history comments — that really was the old GitHub repo name; falsifying history to satisfy a grep would be worse than the inconsistency. The two AES key-derivation domains in storage/database.rs WERE renamed, which is the one edit here that could have destroyed data: they are domain separators, not display strings, and changing them means an existing encrypted node.db silently will not open — no error, it just fails. Safe only because there are no installs. Their doc comments used to say "kept verbatim through the rebrands", which would have been a lie sitting next to a changed constant; they now record that this was a one-time exception taken in the only window where it was free, and that the window is closed. Verified: release build clean, 237 lib tests pass including every key-derivation round-trip (roundtrip_with_known_key, derive_key_is_deterministic_within_process, legacy_and_new_keys_are_distinct), integration tests and doc-tests pass, clippy no errors, embedded web UI builds. `git grep -i cloudnode` now returns only the two intentional historical references. Also aligned three README Windows names with wix/main.wxs byte for byte — it said "Sentinel Camera Node" where Add/Remove shows "Sentinel CameraNode", so a user uninstalling would search for a name that is not there. Co-Authored-By: Claude Opus 5 --- .github/workflows/release.yml | 22 +++--- Cargo.lock | 2 +- Cargo.toml | 40 +++++----- Dockerfile | 8 +- README.md | 12 +-- docker-compose.yml | 6 +- examples/wsl_preflight_probe.rs | 2 +- src/api/client.rs | 4 +- src/api/commands.rs | 2 +- src/api/mod.rs | 2 +- src/api/types.rs | 12 +-- src/api/websocket.rs | 10 +-- src/camera/capture.rs | 2 +- src/camera/detector.rs | 6 +- src/camera/mod.rs | 4 +- src/camera/platform/linux.rs | 2 +- src/camera/platform/macos.rs | 2 +- src/camera/platform/mod.rs | 2 +- src/camera/platform/windows.rs | 2 +- src/camera/types.rs | 2 +- src/config/mod.rs | 6 +- src/config/settings.rs | 2 +- src/dashboard/commands.rs | 2 +- src/dashboard/mod.rs | 4 +- src/dashboard/render.rs | 4 +- src/error.rs | 4 +- src/lib.rs | 4 +- src/logging.rs | 2 +- src/main.rs | 118 +++++++++++++++--------------- src/node/mod.rs | 2 +- src/node/runner.rs | 12 +-- src/paths.rs | 8 +- src/server/api.rs | 2 +- src/server/auth.rs | 2 +- src/server/http.rs | 2 +- src/server/mod.rs | 2 +- src/service.rs | 40 +++++----- src/setup/animations.rs | 2 +- src/setup/mod.rs | 20 ++--- src/setup/platform.rs | 2 +- src/setup/recovery.rs | 4 +- src/setup/tui.rs | 34 ++++----- src/setup/ui.rs | 2 +- src/setup/validator.rs | 4 +- src/setup/wsl_preflight.rs | 6 +- src/storage/database.rs | 39 ++++++---- src/storage/mod.rs | 2 +- src/storage/stats.rs | 10 +-- src/streaming/codec_detector.rs | 2 +- src/streaming/hls_generator.rs | 2 +- src/streaming/hls_uploader.rs | 2 +- src/streaming/mod.rs | 4 +- src/streaming/motion_detector.rs | 2 +- src/streaming/segment_uploader.rs | 2 +- src/streaming/supervisor.rs | 4 +- tests/integration.rs | 10 +-- web/package-lock.json | 4 +- web/package.json | 4 +- web/src/lib/api.ts | 2 +- web/src/main.tsx | 2 +- web/vite.config.ts | 2 +- wix/main.wxs | 50 ++++++------- 62 files changed, 296 insertions(+), 281 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5264255..bad850b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -138,7 +138,7 @@ jobs: if: matrix.platform != 'windows' run: | cd target/${{ matrix.target }}/release - tar czf ../../../sourcebox-sentry-cloudnode-${{ matrix.platform }}-${{ matrix.arch }}.tar.gz sourcebox-sentry-cloudnode + tar czf ../../../sourcebox-sentry-cameranode-${{ matrix.platform }}-${{ matrix.arch }}.tar.gz sourcebox-sentry-cameranode cd ../../.. - name: Package (Windows) @@ -146,7 +146,7 @@ jobs: shell: pwsh run: | cd target/${{ matrix.target }}/release - Compress-Archive -Path sourcebox-sentry-cloudnode.exe -DestinationPath ../../../sourcebox-sentry-cloudnode-${{ matrix.platform }}-${{ matrix.arch }}.zip + Compress-Archive -Path sourcebox-sentry-cameranode.exe -DestinationPath ../../../sourcebox-sentry-cameranode-${{ matrix.platform }}-${{ matrix.arch }}.zip cd ../../.. # ── Build the Windows MSI ────────────────────────────────────── @@ -187,7 +187,7 @@ jobs: shell: pwsh run: | # --no-build because the cargo build step above already - # produced target\release\sourcebox-sentry-cloudnode.exe with the + # produced target\release\sourcebox-sentry-cameranode.exe with the # right release-profile flags (LTO, strip). Letting cargo-wix # rebuild would also use a stripped exe but pays the cost twice. cargo wix --no-build --nocapture --output target/wix/ @@ -196,7 +196,7 @@ jobs: if: matrix.platform == 'windows' shell: pwsh run: | - # cargo-wix names the MSI `sourcebox-sentry-cloudnode--.msi` + # cargo-wix names the MSI `sourcebox-sentry-cameranode--.msi` # by default. Move it to the predictable name the install # script + landing page link to. We keep the version-stamped # name in the GitHub release URL by also uploading the @@ -204,13 +204,13 @@ jobs: # the install scripts pull. $generated = Get-ChildItem target/wix -Filter '*.msi' | Select-Object -First 1 if (-not $generated) { throw "No MSI generated under target/wix/" } - Copy-Item $generated.FullName -Destination "sourcebox-sentry-cloudnode-${{ matrix.platform }}-${{ matrix.arch }}.msi" + Copy-Item $generated.FullName -Destination "sourcebox-sentry-cameranode-${{ matrix.platform }}-${{ matrix.arch }}.msi" - name: Upload artifact uses: actions/upload-artifact@v4 with: - name: sourcebox-sentry-cloudnode-${{ matrix.platform }}-${{ matrix.arch }} - path: sourcebox-sentry-cloudnode-${{ matrix.platform }}-${{ matrix.arch }}.${{ matrix.ext }} + name: sourcebox-sentry-cameranode-${{ matrix.platform }}-${{ matrix.arch }} + path: sourcebox-sentry-cameranode-${{ matrix.platform }}-${{ matrix.arch }}.${{ matrix.ext }} # Separate upload for the MSI so the `release` job's # `download-artifact` step picks it up alongside the .zip. @@ -220,8 +220,8 @@ jobs: if: matrix.platform == 'windows' uses: actions/upload-artifact@v4 with: - name: sourcebox-sentry-cloudnode-${{ matrix.platform }}-${{ matrix.arch }}-msi - path: sourcebox-sentry-cloudnode-${{ matrix.platform }}-${{ matrix.arch }}.msi + name: sourcebox-sentry-cameranode-${{ matrix.platform }}-${{ matrix.arch }}-msi + path: sourcebox-sentry-cameranode-${{ matrix.platform }}-${{ matrix.arch }}.msi release: name: Create Release @@ -300,10 +300,10 @@ jobs: # `SourceBox-LLC/Sentinel-CameraNode` → `sourcebox-llc/sentinel-cameranode`. # The GitHub repo was renamed `opensentry-cloud-node` → `Sentinel-CameraNode` # in May 2026; ghcr.io image name follows the repo, so existing pulls of - # `ghcr.io/sourcebox-llc/opensentry-cloudnode:` will keep working + # `ghcr.io/sourcebox-llc/opensentry-cameranode:` will keep working # only via GitHub's package-redirect window — re-pull against the new # image name once you next update your compose/k8s manifests. The - # binary inside is still `sourcebox-sentry-cloudnode` per Cargo.toml. + # binary inside is still `sourcebox-sentry-cameranode` per Cargo.toml. # `latest=auto` tags :latest only on stable semver tag pushes. - name: Extract metadata id: meta diff --git a/Cargo.lock b/Cargo.lock index ec15527..e01e86c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2076,7 +2076,7 @@ dependencies = [ ] [[package]] -name = "sourcebox-sentry-cloudnode" +name = "sourcebox-sentry-cameranode" version = "0.1.76" dependencies = [ "aes-gcm", diff --git a/Cargo.toml b/Cargo.toml index 8364894..c9bfbdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,24 +1,30 @@ [package] -# Package name = binary name. Brand history (display copy + repo URL -# have been updated to match the latest line; the BINARY name below -# has NOT, on purpose, because changing it breaks existing installs): -# v0 : OpenSentry → original brand -# v0 : SourceBox Sentry → mid-life rename; binary name was minted -# here as `sourcebox-sentry-cloudnode` -# v0+: Sentinel by SourceBox (current — repo renamed -# `opensentry-cloud-node` → `Sentinel-CameraNode` in May 2026, -# GitHub auto-redirects the old URL; binary name preserved -# verbatim so existing systemd units / Windows services / -# install scripts / Docker volumes keep working). -# Renaming the binary would orphan existing systemd units, Windows -# service registrations, install scripts, and Docker volumes. When we -# eventually do the breaking rename (probably alongside a v1.0.0 -# release), this is the line that changes. -name = "sourcebox-sentry-cloudnode" +# Package name = binary name. Brand history: +# v0 : OpenSentry → original brand; repo was +# `opensentry-cloud-node` +# v0 : SourceBox Sentry → mid-life rename; binary was minted here as +# `sourcebox-sentry-cloudnode` +# v0+: Sentinel by SourceBox (current — repo renamed to +# `Sentinel-CameraNode` in May 2026; GitHub auto-redirects the +# old URL) +# +# The binary lagged the repo by one brand for years. This comment used +# to say the rename was deferred until "probably alongside a v1.0.0 +# release" because renaming would orphan existing systemd units, Windows +# service registrations, and Docker volumes. +# +# Done 2026-09-09 instead, while the product had **zero installs** — +# there was nothing to orphan, and that is the only moment this costs +# nothing. The deferral was protecting against a cost that did not +# exist yet and would only have grown. +# +# `cameranode` is also simply the more accurate word: this binary drives +# cameras. It was never a cloud node. +name = "sourcebox-sentry-cameranode" version = "0.1.76" edition = "2021" authors = ["SourceBox LLC"] -description = "Sentinel CloudNode — turns a USB webcam into a cloud-connected security camera." +description = "Sentinel CameraNode — turns a USB webcam into a cloud-connected security camera." license = "GPL-3.0" repository = "https://github.com/SourceBox-LLC/Sentinel-CameraNode" homepage = "https://sentinel-command.com" diff --git a/Dockerfile b/Dockerfile index 3a9c95c..553340c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,7 @@ WORKDIR /app COPY Cargo.toml Cargo.lock build.rs ./ RUN mkdir src && echo "fn main() {}" > src/main.rs && \ cargo build --release && \ - rm -rf src target/release/sourcebox-sentry-cloudnode* + rm -rf src target/release/sourcebox-sentry-cameranode* # Pull the freshly-built SPA bundle from the web-builder stage so # rust-embed picks it up at compile time. build.rs would otherwise @@ -59,7 +59,7 @@ RUN adduser -D -s /bin/sh sentry WORKDIR /app # Copy binary -COPY --from=builder /app/target/release/sourcebox-sentry-cloudnode /usr/local/bin/ +COPY --from=builder /app/target/release/sourcebox-sentry-cameranode /usr/local/bin/ # Create storage directories RUN mkdir -p /app/data/hls && \ @@ -79,10 +79,10 @@ HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ # Volume for persistence VOLUME ["/app/data"] -# Tell CloudNode where to persist its fallback machine-ID when /etc/machine-id +# Tell CameraNode where to persist its fallback machine-ID when /etc/machine-id # isn't bind-mounted from the host. The ID lives in the volume so it survives # container rebuilds. For stronger encryption (key tied to host, not data # volume), run with `-v /etc/machine-id:/etc/machine-id:ro`. ENV SOURCEBOX_SENTRY_DATA_DIR=/app/data -ENTRYPOINT ["sourcebox-sentry-cloudnode"] \ No newline at end of file +ENTRYPOINT ["sourcebox-sentry-cameranode"] \ No newline at end of file diff --git a/README.md b/README.md index 6945b97..e62469c 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ curl -fsSL https://app.sentinel-command.com/install.sh | bash 1. Download `sourcebox-sentry-cameranode-windows-x86_64.msi` from the [latest release](https://github.com/SourceBox-LLC/Sentinel-CameraNode/releases/latest). 2. Run the MSI (UAC prompt). SmartScreen will warn "Windows protected your PC" because the installer is unsigned — click **More info → Run anyway**. (Code signing is on the roadmap.) -3. From the Start menu, click **Sentinel Camera Node**. First launch runs the setup wizard interactively, then drops into the foreground TUI dashboard with cameras streaming. Every launch after just streams. +3. From the Start menu, click **Sentinel CameraNode**. First launch runs the setup wizard interactively, then drops into the foreground TUI dashboard with cameras streaming. Every launch after just streams. Config + recordings live under `C:\ProgramData\SourceBoxSentry\`. The setup wizard checks for FFmpeg and offers to install it via `winget install Gyan.FFmpeg` if it isn't already on PATH. @@ -88,7 +88,7 @@ cd Sentinel-CameraNode cargo build --release # Run the interactive setup wizard -./target/release/sourcebox-sentry-cloudnode setup +./target/release/sourcebox-sentry-cameranode setup ``` @@ -104,7 +104,7 @@ The setup wizard handles everything automatically: After setup, start the node: ```bash -./target/release/sourcebox-sentry-cloudnode +./target/release/sourcebox-sentry-cameranode ``` The TUI status bar prints the local browser-dashboard URL (e.g. @@ -270,7 +270,7 @@ This is the everyday-use path: you can see what's happening, hit a slash command | Path | Purpose | |------|---------| -| `C:\Program Files\Sentinel Camera Node\sourcebox-sentry-cameranode.exe` | Binary (read-only after install) | +| `C:\Program Files\Sentinel CameraNode\sourcebox-sentry-cameranode.exe` | Binary (read-only after install) | | `C:\ProgramData\SourceBoxSentry\node.db` | Encrypted SQLite — config + (optionally) recordings | | `C:\ProgramData\SourceBoxSentry\logs\` | Log files (only written when running unattended; the foreground TUI logs to the console) | @@ -285,7 +285,7 @@ sourcebox-sentry-cameranode setup ### Uninstalling -Use **Settings → Apps → Sentinel Camera Node → Uninstall**. The MSI uninstaller removes the binary and wipes `C:\ProgramData\SourceBoxSentry\` — including your encrypted config and recordings. FFmpeg installed via `winget` stays put because it's a separately-managed package. +Use **Settings → Apps → Sentinel CameraNode → Uninstall**. The MSI uninstaller removes the binary and wipes `C:\ProgramData\SourceBoxSentry\` — including your encrypted config and recordings. FFmpeg installed via `winget` stays put because it's a separately-managed package. > **Heads up:** the MSI is **unsigned** today. SmartScreen flags unsigned installers with "Windows protected your PC" — click **More info → Run anyway** to proceed. Code signing is deferred until we have a sustained release cadence worth the EV-cert fee; the binary itself is open source and reproducibly buildable from this repository if you want to verify. @@ -656,7 +656,7 @@ source "$HOME/.cargo/env" git clone https://github.com/SourceBox-LLC/Sentinel-CameraNode.git cd Sentinel-CameraNode cargo build --release -./target/release/sourcebox-sentry-cloudnode setup +./target/release/sourcebox-sentry-cameranode setup ``` The first `cargo build --release` on a Pi 4 takes 15–20 minutes. Subsequent incremental builds after `git pull` are 1–3 minutes. diff --git a/docker-compose.yml b/docker-compose.yml index 1bc3683..f7df9aa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,10 +1,10 @@ version: "3.8" services: - sourcebox-sentry-cloudnode: + sourcebox-sentry-cameranode: build: . - image: sourcebox-sentry-cloudnode:latest - container_name: sourcebox-sentry-cloudnode + image: sourcebox-sentry-cameranode:latest + container_name: sourcebox-sentry-cameranode # Required for USB camera access privileged: true diff --git a/examples/wsl_preflight_probe.rs b/examples/wsl_preflight_probe.rs index c27856e..f71d366 100644 --- a/examples/wsl_preflight_probe.rs +++ b/examples/wsl_preflight_probe.rs @@ -5,7 +5,7 @@ // // Useful for eyeballing the output when developing the preflight UI. -use sourcebox_sentry_cloudnode::setup::wsl_preflight::{ +use sourcebox_sentry_cameranode::setup::wsl_preflight::{ is_internal_distro, is_likely_camera, probe_distro, probe_usbipd, probe_wsl, }; diff --git a/src/api/client.rs b/src/api/client.rs index 43fd01d..7c49e46 100644 --- a/src/api/client.rs +++ b/src/api/client.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify @@ -179,7 +179,7 @@ impl ApiClient { lan_streaming, cameras, // Sent on every heartbeat (not just register) so the backend - // sees in-place CloudNode upgrades without us needing to + // sees in-place CameraNode upgrades without us needing to // re-register. Read from the build at compile time so a // tampered runtime config can't claim a fake version. version: env!("CARGO_PKG_VERSION").to_string(), diff --git a/src/api/commands.rs b/src/api/commands.rs index c449c6f..e2cd7ff 100644 --- a/src/api/commands.rs +++ b/src/api/commands.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify diff --git a/src/api/mod.rs b/src/api/mod.rs index adc611e..e2940c2 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify diff --git a/src/api/types.rs b/src/api/types.rs index b480e98..19d6da2 100644 --- a/src/api/types.rs +++ b/src/api/types.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify @@ -192,7 +192,7 @@ pub struct HeartbeatRequest { #[serde(skip_serializing_if = "Option::is_none")] pub cameras: Option>, - /// CloudNode build version (`env!("CARGO_PKG_VERSION")`). + /// CameraNode build version (`env!("CARGO_PKG_VERSION")`). /// /// The backend uses this to gate too-old nodes (HTTP 426) and to flag /// "update available" when we ship a newer release. Always sent — old @@ -248,10 +248,10 @@ pub struct HeartbeatResponse { // the fresh key" (what CC's rotation modal instructs); the 403 // error path in client.rs carries that hint. - /// Newer CloudNode release available (e.g. "0.2.0"). + /// Newer CameraNode release available (e.g. "0.2.0"). /// /// Set when the backend's `LATEST_NODE_VERSION` is ahead of what we - /// reported. CloudNode logs a one-line "update available" warning when + /// reported. CameraNode logs a one-line "update available" warning when /// this changes; we deliberately do NOT auto-update because operators /// are running this on their own hardware. `None` means we're current. #[serde(default)] @@ -280,7 +280,7 @@ pub struct HeartbeatResponse { /// Per-camera recording state, authoritative. `{camera_id: bool}`. /// Computed server-side from each camera's `continuous_24_7` / /// `scheduled_recording` policy + the current wall-clock time, so - /// the answer is fresh as of THIS heartbeat tick. CloudNode + /// the answer is fresh as of THIS heartbeat tick. CameraNode /// reconciles its in-memory `recording_state: HashSet` /// to exactly match this map: cameras with `true` get inserted, /// cameras with `false` (or omitted) get removed. @@ -362,7 +362,7 @@ mod tests { fn register_request_includes_version() { // Wire key MUST be `node_version` so the backend's Pydantic schema // picks it up. The historical `version` key was silently dropped - // by Pydantic's default extra="ignore", so every CloudNode looked + // by Pydantic's default extra="ignore", so every CameraNode looked // legacy at register time and the 426 gate never fired. Pin the // correct name here so the bug can't come back. let req = RegisterRequest { diff --git a/src/api/websocket.rs b/src/api/websocket.rs index 12c2d23..5bfcd5c 100644 --- a/src/api/websocket.rs +++ b/src/api/websocket.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify @@ -334,7 +334,7 @@ fn build_heartbeat(camera_ids: &[String], dash: &Dashboard, lan_streaming: bool) /// `update_available` fires once per *distinct* version we see (so a long /// uptime doesn't repeat the same nudge every 30s). `unsupported` fires /// at most once per process — once the operator has been told to update -/// they don't need to keep being told until they restart CloudNode. +/// they don't need to keep being told until they restart CameraNode. fn handle_ack_version_hints(payload: &serde_json::Value, dash: &Dashboard) { static LAST_HINT: OnceLock>> = OnceLock::new(); static UNSUPPORTED_LOGGED: OnceLock> = OnceLock::new(); @@ -350,7 +350,7 @@ fn handle_ack_version_hints(payload: &serde_json::Value, dash: &Dashboard) { if *guard != update_hint { if let Some(latest) = &update_hint { dash.log_warn(format!( - "CloudNode update available: {} (running {}). Re-run the installer to update.", + "CameraNode update available: {} (running {}). Re-run the installer to update.", latest, env!("CARGO_PKG_VERSION"), )); @@ -367,7 +367,7 @@ fn handle_ack_version_hints(payload: &serde_json::Value, dash: &Dashboard) { if let Ok(mut guard) = unsupported_logged.lock() { if !*guard { dash.log_warn(format!( - "Backend reports CloudNode {} is below the minimum supported version. Update to keep streaming.", + "Backend reports CameraNode {} is below the minimum supported version. Update to keep streaming.", env!("CARGO_PKG_VERSION"), )); *guard = true; @@ -462,7 +462,7 @@ async fn dispatch_command( // `start_recording` / `stop_recording` WS commands were retired in // v0.1.43 when the heartbeat-driven recording_state reconciler // replaced them. Recording state now flows: operator click → - // Camera.continuous_24_7 in DB → heartbeat response → CloudNode + // Camera.continuous_24_7 in DB → heartbeat response → CameraNode // recording_state set. See `node::runner::start_heartbeat_loop` // for the reconciler. The backend stopped sending these commands // in the same release; we keep the unknown-command branch below diff --git a/src/camera/capture.rs b/src/camera/capture.rs index 146d850..04ef525 100644 --- a/src/camera/capture.rs +++ b/src/camera/capture.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify diff --git a/src/camera/detector.rs b/src/camera/detector.rs index 849d87d..eab688f 100644 --- a/src/camera/detector.rs +++ b/src/camera/detector.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify @@ -65,7 +65,7 @@ pub struct DetectedCamera { /// # Example /// /// ```rust,no_run -/// use sourcebox_sentry_cloudnode::camera::detect_cameras; +/// use sourcebox_sentry_cameranode::camera::detect_cameras; /// /// fn main() -> Result<(), Box> { /// let cameras = detect_cameras()?; @@ -98,7 +98,7 @@ mod tests { // and reads /dev/video* on Linux. We assert only that it // doesn't panic — Err is a perfectly valid outcome (e.g. // FFmpeg not installed on this machine, no cameras attached, - // CI environment without v4l2). v0.1.35 onward CloudNode no + // CI environment without v4l2). v0.1.35 onward CameraNode no // longer bundles its own FFmpeg, so the previous `is_ok()` // assertion was over-strict — it would now fail in any test // environment without FFmpeg on PATH. diff --git a/src/camera/mod.rs b/src/camera/mod.rs index 40d8abe..761dc63 100644 --- a/src/camera/mod.rs +++ b/src/camera/mod.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ //! Camera handling - detection, capture, and streaming //! //! This module provides platform-agnostic camera detection and video capture -//! functionality for Sentinel CloudNode. +//! functionality for Sentinel CameraNode. //! //! # Platform Support //! diff --git a/src/camera/platform/linux.rs b/src/camera/platform/linux.rs index dfcd29e..2b4c4d3 100644 --- a/src/camera/platform/linux.rs +++ b/src/camera/platform/linux.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify diff --git a/src/camera/platform/macos.rs b/src/camera/platform/macos.rs index 59a43bd..3339dcb 100644 --- a/src/camera/platform/macos.rs +++ b/src/camera/platform/macos.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify diff --git a/src/camera/platform/mod.rs b/src/camera/platform/mod.rs index a0f93ed..d971efe 100644 --- a/src/camera/platform/mod.rs +++ b/src/camera/platform/mod.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify diff --git a/src/camera/platform/windows.rs b/src/camera/platform/windows.rs index d5ab3d1..10fdac3 100644 --- a/src/camera/platform/windows.rs +++ b/src/camera/platform/windows.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify diff --git a/src/camera/types.rs b/src/camera/types.rs index 923a1ef..f22274a 100644 --- a/src/camera/types.rs +++ b/src/camera/types.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify diff --git a/src/config/mod.rs b/src/config/mod.rs index d436844..242d74e 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify @@ -41,7 +41,7 @@ pub struct CliOverrides { /// (`docker-proxy`) was already publishing an unrelated service on /// 8080, and the old code never checked — the setup wizard just /// assumed 8080 was free, so the operator's browser loaded the -/// stranger's app instead of CloudNode's dashboard with no indication +/// stranger's app instead of CameraNode's dashboard with no indication /// anything was wrong. /// /// Always probes `0.0.0.0` regardless of the node's own eventual bind @@ -223,7 +223,7 @@ impl Config { /// The API key is encrypted at rest. pub fn save_to_db(&self, db: &NodeDatabase) -> Result<()> { // Persist mode so a freshly-installed Local-mode node's - // run_cloudnode_once doesn't fall back to Connected validation. + // run_cameranode_once doesn't fall back to Connected validation. db.set_config("mode", self.mode.as_str())?; if let Some(ref id) = self.node.node_id { db.set_config("node_id", id)?; diff --git a/src/config/settings.rs b/src/config/settings.rs index 39bdb42..3e058e3 100644 --- a/src/config/settings.rs +++ b/src/config/settings.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify diff --git a/src/dashboard/commands.rs b/src/dashboard/commands.rs index 0c2503d..29569d6 100644 --- a/src/dashboard/commands.rs +++ b/src/dashboard/commands.rs @@ -549,7 +549,7 @@ impl Dashboard { } self.set_output(vec![ "Credentials cleared. Shutting down…".to_string(), - "Run: sourcebox-sentry-cloudnode setup".to_string(), + "Run: sourcebox-sentry-cameranode setup".to_string(), ]); self.log_warn("Credentials cleared — shutting down"); stop.store(true, std::sync::atomic::Ordering::Relaxed); diff --git a/src/dashboard/mod.rs b/src/dashboard/mod.rs index 53dcb89..7090dd0 100644 --- a/src/dashboard/mod.rs +++ b/src/dashboard/mod.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify @@ -22,7 +22,7 @@ //! # Layout //! //! ```text -//! ╔══ ▸ SENTINEL CLOUDNODE ═════════════════════════════════════════════════╗ +//! ╔══ ▸ SENTINEL CAMERANODE ═════════════════════════════════════════════════╗ //! ║ Node: abc12345 │ API: sentinel-command.com │ ↑ 142 segments ║ //! ╠══ CAMERAS ══════════════════════════════════════════════════════════════╣ //! ║ ● MEE USB Camera 1920×1080 avc1.42e01e / mp4a.40.2 streaming ║ diff --git a/src/dashboard/render.rs b/src/dashboard/render.rs index dba1e4c..31f78fd 100644 --- a/src/dashboard/render.rs +++ b/src/dashboard/render.rs @@ -44,7 +44,7 @@ impl Dashboard { out.push_str("\x1B[?25l\x1B[H"); // ── Header ─────────────────────────────────────────────────────────── - let title = " ▸ SENTINEL CLOUDNODE "; + let title = " ▸ SENTINEL CAMERANODE "; let title_len = title.chars().count(); let fill = w.saturating_sub(2 + title_len); out.push_str(&format!( @@ -458,7 +458,7 @@ impl Dashboard { }; let mut lines = Vec::new(); - lines.push("Sentinel CloudNode — Log Export".to_string()); + lines.push("Sentinel CameraNode — Log Export".to_string()); lines.push(format!("Node: {} | API: {}", state.node_id, state.api_url)); lines.push(format!("Total segments: {} | Uptime: {}", state.total_segments, state.uptime())); lines.push(String::new()); diff --git a/src/error.rs b/src/error.rs index 3c47c60..afe4378 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify @@ -13,7 +13,7 @@ // // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Error types for Sentinel CloudNode +//! Error types for Sentinel CameraNode use thiserror::Error; diff --git a/src/lib.rs b/src/lib.rs index 5de0184..5ec184e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify @@ -13,7 +13,7 @@ // // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Sentinel CloudNode Library +//! Sentinel CameraNode Library //! //! Core functionality for camera detection, capture, streaming, and cloud communication. diff --git a/src/logging.rs b/src/logging.rs index a6a5aa6..295749f 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify diff --git a/src/main.rs b/src/main.rs index eb2a1ee..95ecfa0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Sentinel CloudNode - Camera streaming node for Sentinel Command Center +//! Sentinel CameraNode - Camera streaming node for Sentinel Command Center //! //! This node runs on a local device (Raspberry Pi, Mini PC, etc.) and: //! - Detects USB cameras @@ -27,14 +27,14 @@ use std::process::ExitCode; use clap::{Parser, Subcommand}; use colored::Colorize; -use sourcebox_sentry_cloudnode::{Config, Node, Result}; -use sourcebox_sentry_cloudnode::logging::DashboardLayer; +use sourcebox_sentry_cameranode::{Config, Node, Result}; +use sourcebox_sentry_cameranode::logging::DashboardLayer; use tracing::{info, Level}; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; #[derive(Parser)] -#[command(name = "sourcebox-sentry-cloudnode")] +#[command(name = "sourcebox-sentry-cameranode")] #[command(version)] #[command(about = "Sentinel camera node - stream cameras to Sentinel")] struct Args { @@ -68,7 +68,7 @@ struct Args { #[derive(Subcommand)] enum Commands { - /// Start CloudNode (default) + /// Start CameraNode (default) Run { /// Node ID (required for registration) #[arg(long, env = "SOURCEBOX_SENTRY_NODE_ID")] @@ -87,7 +87,7 @@ enum Commands { once: bool, }, - /// Setup CloudNode (interactive wizard, or one-command with --url/--node-id/--key) + /// Setup CameraNode (interactive wizard, or one-command with --url/--node-id/--key) Setup { /// Command Center URL #[arg(long, env = "SOURCEBOX_SENTRY_API_URL")] @@ -117,7 +117,7 @@ enum Commands { password: Option, }, - /// Uninstall CloudNode + /// Uninstall CameraNode Uninstall { /// Force uninstall without confirmation #[arg(long)] @@ -126,7 +126,7 @@ enum Commands { /// Run as a Windows Service. Invoked by the Service Control Manager — /// not intended for direct use. The MSI registers - /// `sourcebox-sentry-cloudnode service` as the service binary path. + /// `sourcebox-sentry-cameranode service` as the service binary path. /// See src/service.rs for the SCM handshake details. #[command(hide = true)] Service, @@ -137,8 +137,8 @@ fn main() -> ExitCode { Ok(()) => ExitCode::SUCCESS, // Already-reported errors came through a formatted TUI path // (e.g. show_registration_error); do not print a second debug line. - Err(sourcebox_sentry_cloudnode::Error::AlreadyReported) - | Err(sourcebox_sentry_cloudnode::Error::ResetRequested) => ExitCode::from(1), + Err(sourcebox_sentry_cameranode::Error::AlreadyReported) + | Err(sourcebox_sentry_cameranode::Error::ResetRequested) => ExitCode::from(1), Err(e) => { eprintln!(); eprintln!(" {} {}", "Error:".red().bold(), e); @@ -198,7 +198,7 @@ fn run() -> Result<()> { let args = Args::parse(); // ── Windows Service short-circuit ──────────────────────────────── - // SCM invokes us as `sourcebox-sentry-cloudnode.exe service`. From here we + // SCM invokes us as `sourcebox-sentry-cameranode.exe service`. From here we // hand off to the windows-service dispatcher which blocks until the // service exits. Don't reach the terminal-check or interactive flow. // @@ -220,14 +220,14 @@ fn run() -> Result<()> { .map(|d| d.as_secs()) .unwrap_or(0); let line = format!( - "[{}] sourcebox-sentry-cloudnode service path: {}\n", + "[{}] sourcebox-sentry-cameranode service path: {}\n", timestamp, message ); let candidates = [ std::path::PathBuf::from(r"C:\ProgramData\SourceBoxSentry") .join("fatal-startup-error.txt"), std::env::temp_dir() - .join("sourcebox-sentry-cloudnode-fatal-startup-error.txt"), + .join("sourcebox-sentry-cameranode-fatal-startup-error.txt"), ]; for path in &candidates { if let Some(parent) = path.parent() { @@ -269,7 +269,7 @@ fn run() -> Result<()> { write_service_diag(&format!("PANIC: {}{}", msg, location)); })); - match sourcebox_sentry_cloudnode::service::run() { + match sourcebox_sentry_cameranode::service::run() { Ok(()) => { write_service_diag("service::run returned Ok — clean shutdown"); return Ok(()); @@ -277,16 +277,16 @@ fn run() -> Result<()> { Err(e) => { let msg = format!("service::run returned Err: {}", e); write_service_diag(&msg); - return Err(sourcebox_sentry_cloudnode::Error::Unknown(msg)); + return Err(sourcebox_sentry_cameranode::Error::Unknown(msg)); } } } #[cfg(not(target_os = "windows"))] if matches!(args.command, Some(Commands::Service)) { - return Err(sourcebox_sentry_cloudnode::Error::Config( + return Err(sourcebox_sentry_cameranode::Error::Config( "The `service` subcommand is Windows-only — \ use systemd / launchd / your platform's service manager \ - to run CloudNode as a daemon on Linux/macOS." + to run CameraNode as a daemon on Linux/macOS." .to_string(), )); } @@ -362,7 +362,7 @@ fn run() -> Result<()> { { eprintln!("Error: Quick setup requires all three flags: --url, --node-id, and --key"); eprintln!(" (--lan-streaming/--password only work together with them — the interactive wizard has no LAN-streaming step)"); - eprintln!(" Example: sourcebox-sentry-cloudnode setup --url https://... --node-id abc12345 --key xxxxxxxx-... [--lan-streaming --password ...]"); + eprintln!(" Example: sourcebox-sentry-cameranode setup --url https://... --node-id abc12345 --key xxxxxxxx-... [--lan-streaming --password ...]"); std::process::exit(1); } _ => None, @@ -375,36 +375,36 @@ fn run() -> Result<()> { // Non-interactive quick setup. Save config, then run the // node in the foreground in the same console. init_logging(&args.log_level); - sourcebox_sentry_cloudnode::setup::run_quick_setup( + sourcebox_sentry_cameranode::setup::run_quick_setup( &url, &node_id, &key, lan_streaming, password.as_deref(), )?; - return run_cloudnode(None, None, None, args.once, args.config); + return run_cameranode(None, None, None, args.once, args.config); } // Interactive TUI setup with logging completely suppressed. // - // After the wizard completes, hand control to `run_cloudnode` + // After the wizard completes, hand control to `run_cameranode` // which takes over the same console with the live TUI dashboard // (cameras, logs, streaming counters). Foreground TUI is the // recommended runtime on every platform (cargo-build, install.sh, // and now the MSI Start menu shortcut). The MSI still - // registers a Windows Service named `SourceBoxSentryCloudNode` + // registers a Windows Service named `SourceBoxSentryCameraNode` // for unattended-operation use cases, but it's manual-start by // default — see README "Running as a Windows Service". - let auto_start = sourcebox_sentry_cloudnode::setup::run_setup()?; + let auto_start = sourcebox_sentry_cameranode::setup::run_setup()?; if !auto_start { - println!("\n Press Enter to start CloudNode..."); + println!("\n Press Enter to start CameraNode..."); let mut input = String::new(); std::io::stdin().read_line(&mut input)?; } // Now safe to init logging before running the node. init_logging(&args.log_level); - return run_cloudnode(args.node_id, args.api_key, args.api_url, args.once, args.config); + return run_cameranode(args.node_id, args.api_key, args.api_url, args.once, args.config); } // No setup needed — init logging and handle remaining subcommands. @@ -412,10 +412,10 @@ fn run() -> Result<()> { match args.command { Some(Commands::Run { node_id, api_key, api_url, once }) => { - run_cloudnode(node_id.or(args.node_id), api_key.or(args.api_key), api_url.or(args.api_url), once, args.config)?; + run_cameranode(node_id.or(args.node_id), api_key.or(args.api_key), api_url.or(args.api_url), once, args.config)?; } Some(Commands::Uninstall { force }) => { - uninstall_cloudnode(force)?; + uninstall_cameranode(force)?; } Some(Commands::Setup { .. }) => { // Already handled above via needs_setup path. @@ -433,7 +433,7 @@ fn run() -> Result<()> { // / Linux / Docker installs already use. The TUI dashboard // takes over the console. // - // Print a clear "starting" banner before run_cloudnode so + // Print a clear "starting" banner before run_cameranode so // the user has visual feedback that the binary is doing // something, even if Node::new takes a couple seconds to // build the tokio runtime + open the SQLite DB + register @@ -442,45 +442,45 @@ fn run() -> Result<()> { // which feels like the binary hung. use colored::Colorize; println!(); - println!(" {} Loading Sentinel CloudNode...", "📡".cyan()); + println!(" {} Loading Sentinel CameraNode...", "📡".cyan()); println!(" {}", "Configuration found. Starting camera node...".dimmed()); println!(); - run_cloudnode(args.node_id, args.api_key, args.api_url, args.once, args.config)?; + run_cameranode(args.node_id, args.api_key, args.api_url, args.once, args.config)?; } } Ok(()) } -fn run_cloudnode( +fn run_cameranode( node_id: Option, api_key: Option, api_url: Option, once: bool, config_path: Option, ) -> Result<()> { - info!("Starting Sentinel CloudNode v{}", env!("CARGO_PKG_VERSION")); + info!("Starting Sentinel CameraNode v{}", env!("CARGO_PKG_VERSION")); // Retry loop: if the user confirms a credential reset after a registration // failure, the node returns `Error::ResetRequested`. We then re-run the // setup wizard and loop to re-attempt with the fresh credentials. Ok / // other errors exit immediately. loop { - match run_cloudnode_once( + match run_cameranode_once( node_id.clone(), api_key.clone(), api_url.clone(), once, config_path.clone(), ) { - Err(sourcebox_sentry_cloudnode::Error::ResetRequested) => { + Err(sourcebox_sentry_cameranode::Error::ResetRequested) => { // Unhook the previous node's dashboard from the tracing layer // so the setup wizard's events don't flow to an orphaned TUI. - sourcebox_sentry_cloudnode::logging::clear_dashboard(); + sourcebox_sentry_cameranode::logging::clear_dashboard(); // Relaunch the interactive setup wizard synchronously. On // success it writes fresh credentials to data/node.db, which // the next loop iteration picks up via Config::load. - sourcebox_sentry_cloudnode::setup::run_setup()?; + sourcebox_sentry_cameranode::setup::run_setup()?; continue; } other => return other, @@ -488,7 +488,7 @@ fn run_cloudnode( } } -fn run_cloudnode_once( +fn run_cameranode_once( node_id: Option, api_key: Option, api_url: Option, @@ -499,7 +499,7 @@ fn run_cloudnode_once( let config = Config::load(config_path.as_deref())?; // Apply CLI overrides - let config = config.with_overrides(sourcebox_sentry_cloudnode::config::CliOverrides { + let config = config.with_overrides(sourcebox_sentry_cameranode::config::CliOverrides { node_id, api_key, api_url, @@ -512,13 +512,13 @@ fn run_cloudnode_once( // when we're actually going to talk to a Command Center. if config.mode.is_connected() { if config.cloud.api_key.is_empty() { - return Err(sourcebox_sentry_cloudnode::Error::Config( + return Err(sourcebox_sentry_cameranode::Error::Config( "API key required. Set SOURCEBOX_SENTRY_API_KEY env var or use --api-key flag".to_string() )); } if config.node.node_id.is_none() { - return Err(sourcebox_sentry_cloudnode::Error::Config( + return Err(sourcebox_sentry_cameranode::Error::Config( "Node ID required. Set SOURCEBOX_SENTRY_NODE_ID env var or use --node-id flag".to_string() )); } @@ -567,13 +567,13 @@ fn init_logging(log_level: &str) { /// Detect whether the running binary was installed by the Windows MSI. /// -/// Heuristic: the MSI installs `sourcebox-sentry-cloudnode.exe` under -/// `C:\Program Files\Sentinel CloudNode\` (or the `(x86)` mirror +/// Heuristic: the MSI installs `sourcebox-sentry-cameranode.exe` under +/// `C:\Program Files\Sentinel CameraNode\` (or the `(x86)` mirror /// on 32-bit emulation, though we only build x86_64 today). Legacy -/// v0.1.x installs landed under `C:\Program Files\OpenSentry CloudNode\` +/// v0.1.x installs landed under `C:\Program Files\OpenSentry CameraNode\` /// — both paths are matched below for diagnostic continuity. /// -/// Used by `uninstall_cloudnode` to redirect MSI-installed users to +/// Used by `uninstall_cameranode` to redirect MSI-installed users to /// Settings → Apps instead of running the dev-cleanup logic, which /// would do nothing useful for an MSI install (cwd is unrelated to /// the install path; the service stays registered; ProgramData isn't @@ -593,23 +593,23 @@ fn is_msi_install() -> bool { // three is one extra string compare per setup invocation; tiny cost // for preserving the "you're on an old test install" signal. let path = exe.to_string_lossy().to_lowercase(); - path.contains(r"\program files\sentinel cloudnode\") - || path.contains(r"\program files (x86)\sentinel cloudnode\") - || path.contains(r"\program files\sourcebox sentry cloudnode\") - || path.contains(r"\program files (x86)\sourcebox sentry cloudnode\") - || path.contains(r"\program files\opensentry cloudnode\") - || path.contains(r"\program files (x86)\opensentry cloudnode\") + path.contains(r"\program files\sentinel cameranode\") + || path.contains(r"\program files (x86)\sentinel cameranode\") + || path.contains(r"\program files\sourcebox sentry cameranode\") + || path.contains(r"\program files (x86)\sourcebox sentry cameranode\") + || path.contains(r"\program files\opensentry cameranode\") + || path.contains(r"\program files (x86)\opensentry cameranode\") } #[cfg(not(target_os = "windows"))] fn is_msi_install() -> bool { - // No MSI on Linux/macOS — `cloudnode uninstall` always falls + // No MSI on Linux/macOS — `cameranode uninstall` always falls // through to the dev-cleanup path on those platforms. false } -fn uninstall_cloudnode(force: bool) -> Result<()> { +fn uninstall_cameranode(force: bool) -> Result<()> { use colored::Colorize; // Detect the MSI-install case first and bail with a Settings → @@ -622,7 +622,7 @@ fn uninstall_cloudnode(force: bool) -> Result<()> { println!(); println!(" This is an MSI install — uninstall via Windows Settings:"); println!(); - println!(" Settings → Apps → Installed apps → Sentinel CloudNode → Uninstall"); + println!(" Settings → Apps → Installed apps → Sentinel CameraNode → Uninstall"); println!(); println!(" Settings → Apps cleanly stops the service, removes the binary,"); println!(" removes the Windows Service registration, and (on a real"); @@ -644,7 +644,7 @@ fn uninstall_cloudnode(force: bool) -> Result<()> { } println!("{}", "╔════════════════════════════════════════════════════╗".red()); - println!("{}", "║ Sentinel CloudNode Uninstall ║".red()); + println!("{}", "║ Sentinel CameraNode Uninstall ║".red()); println!("{}", "╚════════════════════════════════════════════════════╝".red()); println!(); @@ -663,7 +663,7 @@ fn uninstall_cloudnode(force: bool) -> Result<()> { // v0.1.35. System FFmpeg (winget / brew / apt) is owned by the // OS package manager and isn't ours to remove on uninstall. let env_path = std::env::current_dir()?.join(".env"); - let data_dir = sourcebox_sentry_cloudnode::paths::data_dir(); + let data_dir = sourcebox_sentry_cameranode::paths::data_dir(); println!(" The following will be removed:"); if env_path.exists() { @@ -703,7 +703,7 @@ fn uninstall_cloudnode(force: bool) -> Result<()> { println!(); println!(" {}", "Uninstall complete.".green()); println!(" To reinstall:"); - println!(" {} sourcebox-sentry-cloudnode setup", "→".cyan()); + println!(" {} sourcebox-sentry-cameranode setup", "→".cyan()); Ok(()) } @@ -715,7 +715,7 @@ fn has_terminal() -> bool { std::io::stdin().is_terminal() && std::io::stdout().is_terminal() } -/// Launch CloudNode in a new terminal window on Windows +/// Launch CameraNode in a new terminal window on Windows /// Returns Ok(true) if successfully launched, Ok(false) if failed #[cfg(target_os = "windows")] fn launch_in_terminal() -> std::result::Result { @@ -744,7 +744,7 @@ fn launch_in_terminal() -> std::result::Result { #[cfg(target_os = "windows")] fn show_terminal_required_message() { eprintln!(); - eprintln!(" Sentinel CloudNode"); + eprintln!(" Sentinel CameraNode"); eprintln!(" ────────────────────────────────────────"); eprintln!(); eprintln!(" This application requires a terminal window."); diff --git a/src/node/mod.rs b/src/node/mod.rs index f8138a1..0dfbac5 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify diff --git a/src/node/runner.rs b/src/node/runner.rs index d26764e..8db10eb 100644 --- a/src/node/runner.rs +++ b/src/node/runner.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify @@ -729,7 +729,7 @@ impl Node { // the stop flag directly, so we poll it alongside the OS signal. // In headless mode, the caller (service main) flips stop_flag from // the SCM event handler — Ctrl+C is still installed as a fallback - // for cases where the binary is run as `sourcebox-sentry-cloudnode service` + // for cases where the binary is run as `sourcebox-sentry-cameranode service` // from a console for debugging. tokio::select! { _ = tokio::signal::ctrl_c() => { @@ -768,9 +768,9 @@ impl Node { // redirected to it. The shutdown is already reflected in the // SQLite log buffer + tracing-appender file. if render_tui { - println!("\n {}", "CloudNode stopped.".yellow()); + println!("\n {}", "CameraNode stopped.".yellow()); } else { - tracing::info!("CloudNode stopped (headless)."); + tracing::info!("CameraNode stopped (headless)."); } Ok(()) @@ -843,7 +843,7 @@ impl Node { // Track the last "update available" hint we logged so we don't // spam the dashboard with the same warning every 30s. We log // when the value first appears AND when it changes (e.g. a - // newer release lands while CloudNode is still running). + // newer release lands while CameraNode is still running). let mut last_update_hint: Option = None; // Diagnostic for "I clicked Record in CC, did the node hear about it?": @@ -1035,7 +1035,7 @@ impl Node { if r.update_available != last_update_hint { if let Some(latest) = &r.update_available { dash.log_warn(format!( - "CloudNode update available: {} (running {}). Re-run the installer to update.", + "CameraNode update available: {} (running {}). Re-run the installer to update.", latest, env!("CARGO_PKG_VERSION"), )); diff --git a/src/paths.rs b/src/paths.rs index ea8333b..a474069 100644 --- a/src/paths.rs +++ b/src/paths.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify @@ -16,13 +16,13 @@ //! Centralised data-directory resolution. //! -//! CloudNode persists three things to disk: the encrypted SQLite config +//! CameraNode persists three things to disk: the encrypted SQLite config //! database (`node.db`), recordings (`recordings/`), and the machine-id //! fallback file used as the AES key seed on minimal Linux images. All //! three need to agree on *where* "the data dir" is. //! //! Historically the answer was just `./data/` relative to the process's -//! working directory. That worked when CloudNode was launched manually +//! working directory. That worked when CameraNode was launched manually //! from a terminal, but the moment we ship a Windows MSI that registers //! a Windows Service, the cwd becomes `C:\Windows\System32` (Service //! Control Manager's default) and the relative path resolves to a @@ -47,7 +47,7 @@ use std::path::PathBuf; -/// Where CloudNode stores its config DB, recordings, and any other +/// Where CameraNode stores its config DB, recordings, and any other /// persistent state. See module docs for the resolution order. pub fn data_dir() -> PathBuf { // 1. Explicit override. diff --git a/src/server/api.rs b/src/server/api.rs index 9165f2a..9a79bd0 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify diff --git a/src/server/auth.rs b/src/server/auth.rs index 393725b..fbc1a06 100644 --- a/src/server/auth.rs +++ b/src/server/auth.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify diff --git a/src/server/http.rs b/src/server/http.rs index 5b2c6b2..49ba706 100644 --- a/src/server/http.rs +++ b/src/server/http.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify diff --git a/src/server/mod.rs b/src/server/mod.rs index fa6425b..c038674 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify diff --git a/src/service.rs b/src/service.rs index 2ea3c70..12be3c2 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify @@ -33,7 +33,7 @@ //! `node.db` to the path returned by [`crate::paths::config_db_path`]. //! On a fresh MSI install with no console-side setup, the service //! fails on `Config::load` and exits — the user runs setup, then -//! `Start-Service SourceBoxSentryCloudNode`. +//! `Start-Service SourceBoxSentryCameraNode`. //! //! 3. **Graceful shutdown is best-effort.** SCM Stop flips the shared //! `stop_flag` that node supervisors poll. In-flight HLS segment @@ -71,9 +71,9 @@ use windows_service::{ /// installer side, since SCM uses this string as the primary key. /// /// Spaces aren't allowed in the SCM key; use the spaceless form here -/// and put the user-visible "Sentinel CloudNode" in the +/// and put the user-visible "Sentinel CameraNode" in the /// `DisplayName` attribute on `ServiceInstall` instead. -pub const SERVICE_NAME: &str = "SourceBoxSentryCloudNode"; +pub const SERVICE_NAME: &str = "SourceBoxSentryCameraNode"; /// One process owns the service. We don't run multiple SCM-managed /// services from a single binary, so `OWN_PROCESS` is correct. @@ -82,7 +82,7 @@ const SERVICE_TYPE: ServiceType = ServiceType::OWN_PROCESS; define_windows_service!(ffi_service_main, service_main); /// Entry point invoked from `main.rs::run` when the user (or SCM) runs -/// `sourcebox-sentry-cloudnode service`. +/// `sourcebox-sentry-cameranode service`. /// /// `service_dispatcher::start` blocks for the lifetime of the service: /// it spins up SCM communication, calls into [`service_main`] via the @@ -128,7 +128,7 @@ fn service_main(arguments: Vec) { // Visible in DebugView even when filesystem writes fail (e.g. // disk full, ACL deny). Pure syscall, no Rust runtime // dependencies. Last-resort observability channel. - emit_debug_string("[sourcebox-sentry-cloudnode] service_main entered"); + emit_debug_string("[sourcebox-sentry-cameranode] service_main entered"); // ── Layer 3: standard diag (paths::data_dir-based, structured) ── write_diag_step(&format!( @@ -158,7 +158,7 @@ fn service_main(arguments: Vec) { match result { Ok(Ok(())) => { write_diag_step("run_service returned Ok (clean SCM shutdown)"); - emit_debug_string("[sourcebox-sentry-cloudnode] run_service returned Ok"); + emit_debug_string("[sourcebox-sentry-cameranode] run_service returned Ok"); } Ok(Err(e)) => { tracing::error!("Service exited with error: {}", e); @@ -166,7 +166,7 @@ fn service_main(arguments: Vec) { write_diag_step(&format!("run_service returned Err: {}", e)); write_fatal_startup_error(&format!("{}", e)); emit_debug_string(&format!( - "[sourcebox-sentry-cloudnode] run_service Err: {}", + "[sourcebox-sentry-cameranode] run_service Err: {}", e )); } @@ -184,7 +184,7 @@ fn service_main(arguments: Vec) { write_diag_step(&format!("run_service PANICKED: {}", msg)); write_fatal_startup_error(&format!("PANIC in run_service: {}", msg)); emit_debug_string(&format!( - "[sourcebox-sentry-cloudnode] run_service PANIC: {}", + "[sourcebox-sentry-cameranode] run_service PANIC: {}", msg )); // Don't re-raise — let service_main return normally so the @@ -264,7 +264,7 @@ fn write_diag_step(message: &str) { let candidates = [ crate::paths::data_dir().join("fatal-startup-error.txt"), - std::env::temp_dir().join("sourcebox-sentry-cloudnode-fatal-startup-error.txt"), + std::env::temp_dir().join("sourcebox-sentry-cameranode-fatal-startup-error.txt"), ]; for path in &candidates { @@ -290,7 +290,7 @@ fn write_diag_step(message: &str) { /// "right" place; any operator already looking at our data dir /// will find it. Fails if the dir creation itself was the cause /// of the original error (ACL denial on ProgramData write). -/// 2. `%TEMP%\sourcebox-sentry-cloudnode-fatal-startup-error.txt` — the +/// 2. `%TEMP%\sourcebox-sentry-cameranode-fatal-startup-error.txt` — the /// "always works" fallback; %TEMP% is writable for every account /// including LocalSystem. /// @@ -307,13 +307,13 @@ fn write_fatal_startup_error(message: &str) { .unwrap_or(0); let line = format!( - "[{}] Sentinel CloudNode service failed to start: {}\n", + "[{}] Sentinel CameraNode service failed to start: {}\n", timestamp, message ); let candidates = [ crate::paths::data_dir().join("fatal-startup-error.txt"), - std::env::temp_dir().join("sourcebox-sentry-cloudnode-fatal-startup-error.txt"), + std::env::temp_dir().join("sourcebox-sentry-cameranode-fatal-startup-error.txt"), ]; for path in &candidates { @@ -354,7 +354,7 @@ fn run_service() -> Result<(), Box> { write_diag_step("run_service: init_file_logging OK"); tracing::info!( - "Starting Sentinel CloudNode service (version {})", + "Starting Sentinel CameraNode service (version {})", env!("CARGO_PKG_VERSION") ); @@ -498,7 +498,7 @@ fn load_and_validate_config() -> Result Result Result. -//! Setup wizard for Sentinel CloudNode +//! Setup wizard for Sentinel CameraNode //! //! Beautiful animated terminal-based setup experience pub mod animations; -// `ffmpeg_installer` removed in v0.1.35. CloudNode now uses the system +// `ffmpeg_installer` removed in v0.1.35. CameraNode now uses the system // FFmpeg (winget / brew / apt), not a bundled copy. The setup wizard's // pre-flight FFmpeg handling lives in `tui::prompt_and_install_ffmpeg`. pub mod platform; @@ -119,7 +119,7 @@ pub fn run_setup() -> Result { if is_not_found { eprintln!(); eprintln!(" A required external program was not found. The most likely"); - eprintln!(" cause is that FFmpeg is missing — CloudNode shells out to"); + eprintln!(" cause is that FFmpeg is missing — CameraNode shells out to"); eprintln!(" ffmpeg for camera detection and HLS encoding."); eprintln!(); eprintln!(" Install FFmpeg, then re-run setup in a fresh terminal so"); @@ -133,17 +133,17 @@ pub fn run_setup() -> Result { eprintln!(" sudo apt install ffmpeg # Debian/Ubuntu"); eprintln!(); eprintln!(" Then:"); - eprintln!(" sourcebox-sentry-cloudnode setup"); + eprintln!(" sourcebox-sentry-cameranode setup"); } else { // Generic fallback. Avoid the old "without a proper terminal" // claim — that was only true for one of N possible causes // and confused users hitting the others. eprintln!(); eprintln!(" To retry, open a terminal and run:"); - eprintln!(" sourcebox-sentry-cloudnode setup"); + eprintln!(" sourcebox-sentry-cameranode setup"); eprintln!(); eprintln!(" If the error above is unclear, run with debug logs:"); - eprintln!(" RUST_LOG=debug sourcebox-sentry-cloudnode setup"); + eprintln!(" RUST_LOG=debug sourcebox-sentry-cameranode setup"); } // Pause on Windows so user sees the error before window closes @@ -164,7 +164,7 @@ pub fn run_setup() -> Result { /// auto-detects GPU encoder, and creates data directories — all without /// any user prompts. Designed to be invoked as: /// -/// sourcebox-sentry-cloudnode setup --url --node-id --key +/// sourcebox-sentry-cameranode setup --url --node-id --key /// /// `lan_streaming` (the `--lan-streaming` flag) binds the local HLS/ /// dashboard server to 0.0.0.0 so Home Assistant on the same LAN can @@ -189,7 +189,7 @@ pub fn run_quick_setup( println!(); println!( - " {} Sentinel CloudNode — Quick Setup", + " {} Sentinel CameraNode — Quick Setup", "⚡".cyan() ); println!(" ────────────────────────────────────────"); @@ -379,7 +379,7 @@ pub fn run_quick_setup( // ── Done ───────────────────────────────────────────────────── println!(); - println!(" {} Setup complete — starting CloudNode...", "✓".green().bold()); + println!(" {} Setup complete — starting CameraNode...", "✓".green().bold()); println!(); Ok(()) diff --git a/src/setup/platform.rs b/src/setup/platform.rs index cdfa7f6..e76c8fe 100644 --- a/src/setup/platform.rs +++ b/src/setup/platform.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify diff --git a/src/setup/recovery.rs b/src/setup/recovery.rs index 571ca0e..ae28758 100644 --- a/src/setup/recovery.rs +++ b/src/setup/recovery.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify @@ -15,7 +15,7 @@ // along with this program. If not, see . //! Error recovery and user-friendly error messages //! -//! Full-width bordered panels that match the CloudNode setup wizard aesthetic +//! Full-width bordered panels that match the CameraNode setup wizard aesthetic //! (`ui::panel_*`), themed red for failure and yellow/green for warning/success. use colored::{Color, Colorize}; diff --git a/src/setup/tui.rs b/src/setup/tui.rs index 131e3c3..db903ae 100644 --- a/src/setup/tui.rs +++ b/src/setup/tui.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify @@ -76,8 +76,8 @@ pub fn run_tui_setup() -> Result { fn show_animated_header() -> Result<()> { // Wordmark for the "Sentinel by SourceBox" brand. Big block-letter // "SOURCEBOX" (the company) sits above the subtitle that names the - // specific product ("Sentinel CloudNode Setup"), so the visual - // hierarchy reads as "Sentinel CloudNode, by SourceBox". Keeps + // specific product ("Sentinel CameraNode Setup"), so the visual + // hierarchy reads as "Sentinel CameraNode, by SourceBox". Keeps // the banner narrow enough to fit a standard 80-col terminal // (~78 cols incl. indent). let header_lines = vec![ @@ -99,7 +99,7 @@ fn show_animated_header() -> Result<()> { draw_expanding_border(Duration::from_millis(350))?; println!(); fade_in( - " 📹 Sentinel CloudNode Setup — Your camera, connected to the cloud.", + " 📹 Sentinel CameraNode Setup — Your camera, connected to the cloud.", Duration::from_millis(400), )?; thread::sleep(Duration::from_millis(250)); @@ -204,11 +204,11 @@ fn check_prerequisites() -> Result { /// Pre-flight FFmpeg handling when `check_ffmpeg()` returns false. /// -/// CloudNode no longer ships its own FFmpeg copy (v0.1.35 onward). The +/// CameraNode no longer ships its own FFmpeg copy (v0.1.35 onward). The /// canonical answer on every supported platform is "install FFmpeg via /// the OS package manager". This function detects the user's package /// manager, offers to run the install command for them, and tells them -/// to re-launch CloudNode after the install finishes (PATH changes +/// to re-launch CameraNode after the install finishes (PATH changes /// don't apply to the running process — a fresh process is required). /// /// Why we don't just keep the install running and re-check PATH after: @@ -217,15 +217,15 @@ fn check_prerequisites() -> Result { /// SYSTEM-wide PATH (or user PATH on Windows), but our running process /// is stuck with the snapshot it inherited at launch. Spawning a new /// shell to detect ffmpeg works but is fragile. The cleanest UX is: -/// install runs to completion, user re-launches CloudNode, fresh +/// install runs to completion, user re-launches CameraNode, fresh /// process sees the updated PATH, prereq check passes. fn prompt_and_install_ffmpeg(platform: &PlatformInfo) -> Result<()> { println!(); println!(" ⚠ FFmpeg not found"); println!(); - println!(" CloudNode uses the system FFmpeg installation for camera capture"); + println!(" CameraNode uses the system FFmpeg installation for camera capture"); println!(" and HLS encoding. Install it once via your OS package manager and"); - println!(" CloudNode will pick it up automatically."); + println!(" CameraNode will pick it up automatically."); println!(); let (label, command, args, install_url) = if platform.is_windows { @@ -296,7 +296,7 @@ fn prompt_and_install_ffmpeg(platform: &PlatformInfo) -> Result<()> { // here propagates up to run_setup which prints the error and // exits — the message is the user's next action. Err(anyhow::anyhow!( - "FFmpeg installed. Close this window and re-launch Sentinel CloudNode \ + "FFmpeg installed. Close this window and re-launch Sentinel CameraNode \ from the Start menu. PATH changes don't apply to the current process — a fresh \ launch is required for the prereq check to see the new install." )) @@ -576,7 +576,7 @@ fn configure_node(platform: &PlatformInfo) -> Result { println!(); println!( " {}", - "Run 'sourcebox-sentry-cloudnode setup' to try again.".yellow() + "Run 'sourcebox-sentry-cameranode setup' to try again.".yellow() ); std::process::exit(1); } @@ -721,7 +721,7 @@ fn configure_node(platform: &PlatformInfo) -> Result { } println!(); - let auto_start = Confirm::new(" Auto-start CloudNode after setup?") + let auto_start = Confirm::new(" Auto-start CameraNode after setup?") .with_default(true) .prompt()?; @@ -778,7 +778,7 @@ fn configure_node(platform: &PlatformInfo) -> Result { fn select_deployment_method(platform: &PlatformInfo) -> Result { let options = vec!["Build from Source (Recommended)", "Docker"]; let choice = if platform.is_windows || platform.is_linux || platform.is_macos { - Select::new(" How would you like to run CloudNode?", options) + Select::new(" How would you like to run CameraNode?", options) .with_starting_cursor(0) .prompt()? } else { @@ -1077,7 +1077,7 @@ fn show_success_screen(config: &SetupConfig) -> Result<()> { panel_blank(); // Pulse the tagline inside the panel - let tagline = "🎉 Your Sentinel CloudNode is ready!"; + let tagline = "🎉 Your Sentinel CameraNode is ready!"; for _ in 0..2 { panel_row(&format!(" {}", tagline.green().bold())); thread::sleep(Duration::from_millis(450)); @@ -1143,7 +1143,7 @@ fn show_success_screen(config: &SetupConfig) -> Result<()> { panel_row(&format!( " {} {}", "Start with:".white().bold(), - "./target/release/sourcebox-sentry-cloudnode".cyan() + "./target/release/sourcebox-sentry-cameranode".cyan() )); } } @@ -1176,11 +1176,11 @@ fn show_success_screen(config: &SetupConfig) -> Result<()> { panel_blank(); if config.auto_start { - panel_row(&format!(" {}", "🚀 Starting CloudNode...".green().bold())); + panel_row(&format!(" {}", "🚀 Starting CameraNode...".green().bold())); } else { panel_row(&format!( " {}", - "Press Enter to start CloudNode...".yellow().bold() + "Press Enter to start CameraNode...".yellow().bold() )); } diff --git a/src/setup/ui.rs b/src/setup/ui.rs index d11d469..6ad311a 100644 --- a/src/setup/ui.rs +++ b/src/setup/ui.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify diff --git a/src/setup/validator.rs b/src/setup/validator.rs index 4498f36..d47a2b3 100644 --- a/src/setup/validator.rs +++ b/src/setup/validator.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify @@ -13,7 +13,7 @@ // // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Connection validation for CloudNode setup +//! Connection validation for CameraNode setup //! //! Validates API URL, Node ID, and API Key before saving configuration diff --git a/src/setup/wsl_preflight.rs b/src/setup/wsl_preflight.rs index 6d13fa0..4e26d8e 100644 --- a/src/setup/wsl_preflight.rs +++ b/src/setup/wsl_preflight.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify @@ -400,7 +400,7 @@ pub fn run_wsl_preflight_interactive(config: &SetupConfig) -> Result<()> { panel_sub("Open PowerShell as Administrator and run:"); panel_sub(&format!(" {}", "wsl --install".cyan())); panel_sub("A reboot is required after first-time install."); - panel_sub("Re-run CloudNode setup once WSL and a distro are ready."); + panel_sub("Re-run CameraNode setup once WSL and a distro are ready."); return Ok(()); } panel_check("WSL is installed"); @@ -593,7 +593,7 @@ fn pick_distro(wsl: &WslStatus, usable: &[&DistroInfo]) -> Result { panel_bottom(); println!(); let names: Vec = usable.iter().map(|d| d.name.clone()).collect(); - let selection = Select::new(" Which WSL distro should CloudNode run in?", names) + let selection = Select::new(" Which WSL distro should CameraNode run in?", names) .prompt() .map_err(|e| anyhow::anyhow!("Distro selection cancelled: {}", e))?; println!(); diff --git a/src/storage/database.rs b/src/storage/database.rs index 39d35c8..f409c06 100644 --- a/src/storage/database.rs +++ b/src/storage/database.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify @@ -737,7 +737,7 @@ impl NodeDatabase { /// Read and decrypt a config value. /// - /// If the ciphertext was written by a CloudNode version that derived its + /// If the ciphertext was written by a CameraNode version that derived its /// key from the hostname, the value is transparently re-encrypted with /// the current machine-id-derived key so subsequent loads take the fast /// path and the weak legacy key is retired from the DB. @@ -855,19 +855,28 @@ use std::sync::OnceLock; /// never produces the same key as some unrelated tool that happens to hash /// the same input. `v2` marks the switch from hostname-derived (v1) keys. /// -/// **Do not rename across brand changes.** This string is part of the key -/// derivation; changing it would brick every existing install's encrypted -/// SQLite DB. The `opensentry-` prefix is a historical artifact from -/// when the product was called "OpenSentry" — kept verbatim through the -/// later "SourceBox Sentry" and "Sentinel by SourceBox" rebrands so -/// nodes can still decrypt their data after an upgrade. -const KEY_DOMAIN_V2: &[u8] = b"opensentry-cloudnode-machine-id-v2"; +/// **Do not rename. This is a key-derivation domain separator, not a +/// brand string.** Changing it re-derives a different key, so every +/// existing encrypted SQLite DB becomes undecryptable — silently: no +/// error fires, the database simply will not open. +/// +/// It was `opensentry-cloudnode-machine-id-v2` until 2026-09-09, and +/// renaming it was a deliberate, one-time exception taken while the +/// product had **zero installs** — the only moment the change costs +/// nothing. That window is closed. From the first real install +/// onward, changing this string requires a migration path (derive with +/// the old domain, re-encrypt with the new), not an edit. +/// +/// The `opensentry-` prefix is a genuine historical artifact from the +/// original brand and stays: it carries no meaning beyond "this is the +/// domain we have always hashed with". +const KEY_DOMAIN_V2: &[u8] = b"opensentry-cameranode-machine-id-v2"; /// Legacy domain tag — kept so `derive_key_legacy` still reproduces the -/// pre-migration key for DBs written with the old code. Same brand- -/// history note as `KEY_DOMAIN_V2`: this string is load-bearing for -/// decryption, not a brand reference. -const KEY_DOMAIN_V1_LEGACY: &[u8] = b"opensentry-cloudnode-v1"; +/// pre-migration key for DBs written with the old code. Renamed +/// alongside `KEY_DOMAIN_V2` on 2026-09-09 under the same zero-installs +/// exception, and load-bearing for decryption in exactly the same way. +const KEY_DOMAIN_V1_LEGACY: &[u8] = b"opensentry-cameranode-v1"; /// Cached derived key for this process — `machine_id()` on Linux reads a /// file and on Windows / macOS shells out, so we avoid re-deriving per op. @@ -1040,7 +1049,7 @@ fn derive_key() -> std::result::Result<[u8; 32], String> { /// Derive the pre-migration key from the hostname. /// /// Only used by `decrypt_value` to transparently migrate DBs written by -/// earlier CloudNode versions. Kept byte-for-byte identical to the old +/// earlier CameraNode versions. Kept byte-for-byte identical to the old /// implementation so existing ciphertexts still decrypt. fn derive_key_legacy() -> [u8; 32] { let host = sysinfo::System::host_name() @@ -1250,7 +1259,7 @@ pub(crate) fn decrypt_bytes(blob: &[u8], aad: &[u8]) -> std::result::Result String { /// /// # Lookup precedence (v0.1.35+) /// -/// CloudNode no longer bundles its own copy of FFmpeg. The canonical +/// CameraNode no longer bundles its own copy of FFmpeg. The canonical /// install pattern is "use the system FFmpeg" — installed via winget, /// Homebrew, apt, dnf, pacman, etc. The setup wizard guides the user /// to install it via their OS package manager and refuses to proceed diff --git a/src/streaming/motion_detector.rs b/src/streaming/motion_detector.rs index 9b7c44e..765c604 100644 --- a/src/streaming/motion_detector.rs +++ b/src/streaming/motion_detector.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify diff --git a/src/streaming/segment_uploader.rs b/src/streaming/segment_uploader.rs index e25e0a6..5756842 100644 --- a/src/streaming/segment_uploader.rs +++ b/src/streaming/segment_uploader.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify diff --git a/src/streaming/supervisor.rs b/src/streaming/supervisor.rs index 9f15fb1..ce42b5b 100644 --- a/src/streaming/supervisor.rs +++ b/src/streaming/supervisor.rs @@ -1,4 +1,4 @@ -// Sentinel CloudNode - Camera streaming node for Sentinel Command Center +// Sentinel CameraNode - Camera streaming node for Sentinel Command Center // Copyright (C) 2026 SourceBox LLC // // This program is free software: you can redistribute it and/or modify @@ -37,7 +37,7 @@ //! a closed V4L2 fd, a segment-writer failure) would silently leave the //! camera offline from the browser's point of view while the node still //! reported `status: streaming` in every heartbeat. The backend MCP -//! tools would then tell users "update CloudNode to latest version" +//! tools would then tell users "update CameraNode to latest version" //! when the real failure was upstream in FFmpeg. use std::collections::VecDeque; diff --git a/tests/integration.rs b/tests/integration.rs index 5a5df19..73fb27b 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1,6 +1,6 @@ -//! Integration tests for Sentinel CloudNode +//! Integration tests for Sentinel CameraNode -use sourcebox_sentry_cloudnode::{Config, Result}; +use sourcebox_sentry_cameranode::{Config, Result}; #[test] fn test_config_load_default() -> Result<()> { @@ -13,11 +13,11 @@ fn test_config_load_default() -> Result<()> { #[test] fn test_camera_detect() { // detect_cameras() shells out to FFmpeg on Windows + macOS for - // device enumeration. v0.1.35 onward CloudNode uses the system + // device enumeration. v0.1.35 onward CameraNode uses the system // FFmpeg (no bundled fallback), so on a test environment without // FFmpeg on PATH the call returns Err — that's fine, we're just // verifying it doesn't panic. - match sourcebox_sentry_cloudnode::camera::detect_cameras() { + match sourcebox_sentry_cameranode::camera::detect_cameras() { Ok(cameras) => println!("Detected {} cameras", cameras.len()), Err(e) => println!("Camera detect skipped (no FFmpeg on PATH?): {}", e), } @@ -54,7 +54,7 @@ async fn boxed_filter_binds_and_serves_over_tcp() { // warp 0.4 dropped bind_ephemeral, so pick a free port with the // node's own helper — the same one the real server uses at boot. - let port = sourcebox_sentry_cloudnode::config::find_available_port(18771); + let port = sourcebox_sentry_cameranode::config::find_available_port(18771); let addr = std::net::SocketAddr::from(([127, 0, 0, 1], port)); let handle = tokio::spawn(warp::serve(route).run(addr)); diff --git a/web/package-lock.json b/web/package-lock.json index 90a1052..aa6b49d 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,11 +1,11 @@ { - "name": "sourcebox-sentry-cloudnode-web", + "name": "sourcebox-sentry-cameranode-web", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "sourcebox-sentry-cloudnode-web", + "name": "sourcebox-sentry-cameranode-web", "version": "0.1.0", "dependencies": { "hls.js": "^1.5.17", diff --git a/web/package.json b/web/package.json index ab29619..48cad91 100644 --- a/web/package.json +++ b/web/package.json @@ -1,9 +1,9 @@ { - "name": "sourcebox-sentry-cloudnode-web", + "name": "sourcebox-sentry-cameranode-web", "private": true, "version": "0.1.0", "type": "module", - "description": "Local web UI for Sentinel CloudNode (Phase C). Embedded into the Rust binary via rust-embed; served by the warp HTTP server at /. Don't share code with the Command Center frontend — keep this self-contained.", + "description": "Local web UI for Sentinel CameraNode (Phase C). Embedded into the Rust binary via rust-embed; served by the warp HTTP server at /. Don't share code with the Command Center frontend — keep this self-contained.", "scripts": { "dev": "vite", "build": "tsc -b && vite build", diff --git a/web/src/lib/api.ts b/web/src/lib/api.ts index d2ad79e..57477e0 100644 --- a/web/src/lib/api.ts +++ b/web/src/lib/api.ts @@ -1,4 +1,4 @@ -// Typed wrappers for the CloudNode local web API (Phase B). +// Typed wrappers for the CameraNode local web API (Phase B). // Same-origin: every request lands on the warp server that's also // serving this SPA, so no base URL or CORS dance. diff --git a/web/src/main.tsx b/web/src/main.tsx index c81650b..3e28566 100644 --- a/web/src/main.tsx +++ b/web/src/main.tsx @@ -1,4 +1,4 @@ -// Sentinel CloudNode local web UI — React entry point. +// Sentinel CameraNode local web UI — React entry point. // Embedded into the Rust binary via rust-embed; served by the warp // HTTP server at /. Don't share code with the Command Center // frontend — keep this self-contained. diff --git a/web/vite.config.ts b/web/vite.config.ts index 1c66b89..0d60a41 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -18,7 +18,7 @@ export default defineConfig({ }, server: { port: 5174, - // dev-mode only: proxy /api and /hls to a running CloudNode so + // dev-mode only: proxy /api and /hls to a running CameraNode so // `npm run dev` works against a live backend. CI / production // never hits this — vite is build-time only there. proxy: { diff --git a/wix/main.wxs b/wix/main.wxs index 529443e..d7ef346 100644 --- a/wix/main.wxs +++ b/wix/main.wxs @@ -1,9 +1,9 @@ @@ -354,10 +354,10 @@ subcommand, which hits the bare-invocation path in main.rs. That path checks Config::load for existing credentials and chooses: - - creds present → run_cloudnode (foreground TUI + - creds present → run_cameranode (foreground TUI dashboard, cameras stream) - creds missing → run setup wizard, then - run_cloudnode + run_cameranode So the same shortcut handles both first-launch (runs the wizard then streams) and every-launch-after (just streams). @@ -373,19 +373,19 @@ dropped the argument so the shortcut becomes a normal app launcher. --> - +