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" 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. --> - +