diff --git a/README.md b/README.md index 2fef8aa..2803939 100644 --- a/README.md +++ b/README.md @@ -1,109 +1,83 @@ -

- Secluso +

+ +

+ Private home security without cloud surveillance

# Secluso -Secluso is a privacy-preserving home security camera solution that uses end-to-end encryption. -In Secluso, the camera encrypts the videos end-to-end for the app on the user's smartphone. -Videos are relayed by a server, but the server cannot decrypt them. - -Secluso has two key benefits: - -* **End-to-end encryption** using the OpenMLS implementation of the Messaging Layer Security (MLS) protocol. -* **Rust** implementation. - -## Components - -Secluso has three components: - -* A **camera hub** that records, encrypts, and sends videos. -* A **mobile app** that allows one to receive event notifications (e.g., person or motion) from the camera as well as livestream the camera remotely. -* An **untrusted server** that relays (encrypted) messages between the hub and the app. In addition, Secluso uses the Google Firebase Cloud Messaging (FCM) for notifications. Similar to the server, FCM is untrusted. - -## Camera types +Private DIY home security for Raspberry Pi, with encrypted remote access and a 2-minute setup. -Secluso supports two types of cameras. +[Download Secluso Deploy](https://github.com/secluso/secluso/releases) • [Build Your Own Guide](https://secluso.com/build-your-own) • [Security Model](SECURITY.md) • [Website](https://secluso.com) -* **Standalone camera** using a Raspberry Pi. In this case, the camera hub runs directly on the Raspberry Pi. -* Commercial **IP cameras**. In this case, the camera hub runs on another machine and works with existing IP cameras with minimal trust assumptions about these cameras. +
-### Plug-and-play camera +Secluso is a private home security camera system for Raspberry Pi. Watch live video, get alerts, and open recordings from your phone without handing your footage to a cloud provider. -We are also working on a plug-and-play camera based on our Raspberry Pi prototype. The goal is to make our camera more accessible to people who need a private camera but don't have time to set up our open-source project. Below is a photo of this plug-and-play camera. If you're interested, check out our website [here](https://secluso.com) and join our mailing list on the website. We will be sending updates on our plug-and-play camera as well as our progress on our open source software. +Secluso is developed by Secluso, Inc. and co-founded by Ardalan Amiri Sani, a UC Irvine professor with expertise in computer security and privacy. -

- Secluso plug-and-play camera -

- -## Security - -Secluso has been carefully designed to strongly protect the user's videos against an attacker that might try to access and view them. -It provides advanced encryption guarantees, namely **forward secrecy** and **post-compromise security**. -For a more accurate and detailed discussion of its security guarantees, please see [here](SECURITY.md). +## Features -## Event detection +- **End-to-end encrypted remote access:** Watch live video, get alerts, and open recordings from your phone. +- **2-minute setup:** Secluso Deploy handles image building, pairing, and relay setup in the normal path. +- **Open source:** Inspect the code, self-host it, and contribute. +- **Reproducible releases:** Verify the released binaries against the public source code. -The camera hub is capable of detecting various events and sending a notification to the mobile app. +## Requirements -* Events supported for the standalone camera: motion, person, pet, vehicle +- **Raspberry Pi:** Raspberry Pi Zero 2W or Raspberry Pi 4 +- **Camera:** Raspberry Pi Camera Module +- **Relay:** your own Linux VPS login, or an email to us for free beta relay hosting while testing +- **Phone:** Android or iPhone for pairing, alerts, and playback -* Events supported for IP cameras: motion +## Set up in 5 minutes (Quick Start) -## Supported Cameras +1. Download **Secluso Deploy** from the [latest releases](https://github.com/secluso/secluso/releases). +2. Choose your Raspberry Pi target, build the image locally, and save the pairing QR code. +3. Let Secluso Deploy provision your relay over SSH, or email us if you want free beta relay hosting while testing. +4. Boot the Pi and pair it in the mobile app. -* Standalone camera: Secluso should be able to run on any Raspberry Pi boards that is capable of running its event detection pipeline. -So far, the following boards have been successfully tested: +If you are still choosing hardware or a VPS, [Build Your Own Guide](https://secluso.com/build-your-own) gives you hardware suggestions and a simple starting path. - * Raspberry Pi Zero 2W - * Raspberry Pi 4 + -* IP camera: Secluso camera can theoretically support any IP camera (or any other camera that has an open interface). -The current prototype relies on RTSP and MJPEG support by the camera. -The former is used for streaming videos from the camera and the latter is used for a custom motion detection implementation. -So far, the following cameras have been tested: +## Mobile App - * Amcrest, model: IP4M-1041W ([Link](https://www.amazon.com/Amcrest-UltraHD-Security-4-Megapixel-IP4M-1041W/dp/B095XD17K5/) on Amazon) - * Software Version: V2.800.00AC006.0.R, Build Date: 2023-10-27 - * WEB Version: V3.2.1.18144 +After setup, use the mobile app to check in remotely, review recent events, and open encrypted clips. -## Supported mobile OSes + -* Android -* iOS - -## Tested smartphones (OS version) - -* Google Pixel 8 Pro (Android 15) -* Google Pixel 7 (Android 14) -* Moto G 5G (2024) (Android 14) -* iPhone 16 Pro (iOS 18.5) +## Security -## (Current) key limitations +See [SECURITY.md](SECURITY.md) for the full security model, including the untrusted-relay design, forward secrecy, and post-compromise security. -* The camera hub pairs with one app instance only. -* Performance may become a bottleneck for high camera resolutions and frame rates. +## Reproducible Builds -## Instructions +We do not distribute a prebuilt Raspberry Pi image. Secluso Deploy assembles the image on your machine from released binaries produced by Secluso's deterministic build pipeline, so the software you run can be checked against the public source code instead of taken on faith. -See [here](HOW_TO.md) for instructions for setting up Secluso. +See [releases/README.md](releases/README.md) for the reproducibility checker. -## Contributions +## Documentation and Help -We welcome contributions to the project. Before working on a contribution, please check with us via email: secluso@proton.me +Need hardware suggestions, a starter VPS, or a short BYO walkthrough? See [Build Your Own](https://secluso.com/build-your-own). If you get stuck, email [secluso@proton.me](mailto:secluso@proton.me). We are happy to help whether or not you ever buy from us. -Contributions are made under Secluso's [license](LICENSE). +## Contributing +Questions and contributions are welcome. Contributions are made under the project license in [LICENSE](LICENSE). ## Project Founders -* Ardalan Amiri Sani (Ph.D., Computer Science professor at UC Irvine with expertise in computer security and privacy) -* John Kaczman (Open source and privacy enthusiast. Experienced in automation, systems and AI) +- Ardalan Amiri Sani (UC Irvine professor focused on computer security and privacy) +- John Kaczman (Open source and privacy enthusiast with experience in automation, systems, and AI) -Note: this is a side project of Ardalan Amiri Sani and John Kaczman, who work on it in their spare time. +Secluso is developed and supported by Secluso, Inc. ## Disclaimers -This project uses cryptography libraries/software. Before using it, check your country's laws and regulations. +This project uses cryptography. Check your local laws before use. -Use at your own risks. The project authors do not provide any guarantees of privacy or home security. +Use at your own risk. The project authors provide no guarantees of privacy or home security. diff --git a/deploy/src-tauri/src/requirements.rs b/deploy/src-tauri/src/requirements.rs index 418fff1..2cb0975 100644 --- a/deploy/src-tauri/src/requirements.rs +++ b/deploy/src-tauri/src/requirements.rs @@ -26,25 +26,6 @@ fn check_cmd(cmd: &str, args: &[&str]) -> (bool, Option) { } } -fn check_cmd_allow_nonzero(cmd: &str, args: &[&str], must_contain: &[&str]) -> (bool, Option) { - let out = Command::new(cmd).args(args).output(); - match out { - Ok(res) => { - let stdout = String::from_utf8_lossy(&res.stdout).trim().to_string(); - let stderr = String::from_utf8_lossy(&res.stderr).trim().to_string(); - let combined = if !stdout.is_empty() { stdout } else { stderr }; - let ok = must_contain.iter().any(|needle| combined.contains(needle)); - if ok { - let version = if combined.is_empty() { None } else { Some(combined) }; - (true, version) - } else { - (false, None) - } - } - Err(_) => (false, None), - } -} - #[tauri::command] pub async fn check_requirements() -> Result, String> { tauri::async_runtime::spawn_blocking(|| { @@ -58,9 +39,6 @@ pub async fn check_requirements() -> Result, String> { ("pnpm", "pnpm", vec!["--version"], "Needed for UI dev."), ("Rust (1.85)", "rustc", vec!["--version"], "Needed for Tauri backend builds."), ("Cargo", "cargo", vec!["--version"], "Needed for Rust builds."), - ("curl", "curl", vec!["--version"], "Used by setup scripts."), - ("jq", "jq", vec!["--version"], "Used by setup scripts."), - ("unzip", "unzip", vec!["-v"], "Used by setup scripts."), ]; for (name, cmd, args, hint) in checks { @@ -73,22 +51,6 @@ pub async fn check_requirements() -> Result, String> { }); } - let (ssh_ok, ssh_version) = check_cmd_allow_nonzero("ssh", &["-V"], &["OpenSSH"]); - statuses.push(RequirementStatus { - name: "SSH".to_string(), - ok: ssh_ok, - version: ssh_version, - hint: "Needed to reach the server.".to_string(), - }); - - let (scp_ok, scp_version) = check_cmd_allow_nonzero("scp", &["-V"], &["OpenSSH", "usage: scp"]); - statuses.push(RequirementStatus { - name: "SCP".to_string(), - ok: scp_ok, - version: scp_version, - hint: "Needed to upload files over SSH.".to_string(), - }); - Ok(statuses) }) .await diff --git a/deploy/src/routes/image/+page.svelte b/deploy/src/routes/image/+page.svelte index b53a349..5259973 100644 --- a/deploy/src/routes/image/+page.svelte +++ b/deploy/src/routes/image/+page.svelte @@ -3,7 +3,7 @@ import { onMount } from "svelte"; import { save } from "@tauri-apps/plugin-dialog"; import { goto } from "$app/navigation"; - import { buildImage } from "$lib/api"; + import { buildImage, checkRequirements, type RequirementStatus } from "$lib/api"; // variants data model type VariantKey = "official" | "diy"; @@ -71,6 +71,11 @@ let building = false; let errorMsg = ""; let firstTimeOn = false; + let requirements: RequirementStatus[] = []; + let missingRequirements: RequirementStatus[] = []; + let checkingRequirements = true; + $: dockerMissing = missingRequirements.some((req) => req.name === "Docker"); + $: buildxMissing = missingRequirements.some((req) => req.name === "Docker Buildx"); async function pickQrOutput() { const path = await save({ @@ -125,6 +130,14 @@ async function startBuild() { errorMsg = ""; + if (checkingRequirements) { + errorMsg = "Checking required tools. Try again in a moment."; + return; + } + if (missingRequirements.length > 0) { + errorMsg = `Missing required tools: ${missingRequirements.map((req) => req.name).join(", ")}.`; + return; + } const err = validate(); if (err) { errorMsg = err; return; } @@ -201,6 +214,18 @@ firstTimeOn = !firstTimeOn; localStorage.setItem(FIRST_TIME_KEY, String(firstTimeOn)); } + + onMount(async () => { + try { + requirements = await checkRequirements(); + missingRequirements = requirements.filter((req) => !req.ok); + } catch { + requirements = []; + missingRequirements = []; + } finally { + checkingRequirements = false; + } + });
@@ -210,6 +235,43 @@
+ {#if checkingRequirements} +
+

Setup checks

+

Checking local tools…

+
+ {:else if missingRequirements.length > 0} +
+

Missing tools

+
    + {#each missingRequirements as req} +
  • + {req.name} + Missing + {req.hint} +
  • + {/each} +
+
+ {/if} + + {#if dockerMissing || buildxMissing} +
+

Install Docker

+

Docker is required for image builds.

+
    +
  • Windows: install Docker Desktop and enable the WSL 2 backend.
  • +
  • macOS: install Docker Desktop for Mac.
  • +
  • Linux: install Docker Engine and the Buildx plugin.
  • +
+ +
+ {/if} + {#if firstTimeOn}
@@ -292,7 +354,7 @@ {/if}
-
@@ -332,6 +394,19 @@ .row { display: flex; gap: 10px; align-items: end; } .grow { flex: 1; } +/* requirements */ +.requirements h2 { margin: 0 0 10px 0; } +.req-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 10px; } +.req-item { display: grid; grid-template-columns: 1fr auto; gap: 4px 12px; align-items: center; } +.req-name { font-weight: 600; } +.req-status.missing { color: #b91c1c; font-weight: 700; font-size: 0.92rem; } +.req-detail { grid-column: 1 / -1; color: #666; font-size: 0.9rem; } +.req-steps { margin: 6px 0 10px; padding-left: 18px; color: #555; } +.req-steps li { margin: 4px 0; } +.req-links { display: flex; flex-wrap: wrap; gap: 10px; } +.req-links a { color: #396cd8; text-decoration: none; font-size: 0.95rem; } +.req-links a:hover { text-decoration: underline; } + /* buttons */ button { border: 1px solid #d7d7d7; background: #fff; color: #111; padding: 10px 14px; border-radius: 10px; cursor: pointer; } button:hover { border-color: #c6c6c6; } diff --git a/deploy/src/routes/server-ssh/+page.svelte b/deploy/src/routes/server-ssh/+page.svelte index f1d116b..9f869bf 100644 --- a/deploy/src/routes/server-ssh/+page.svelte +++ b/deploy/src/routes/server-ssh/+page.svelte @@ -6,6 +6,8 @@ import { testServerSsh, provisionServer, + checkRequirements, + type RequirementStatus, type SshTarget, type ServerPlan } from "$lib/api"; @@ -61,6 +63,11 @@ let errorMsg = ""; let testResult: "ok" | "error" | null = null; let testMessage = ""; + let requirements: RequirementStatus[] = []; + let missingRequirements: RequirementStatus[] = []; + let checkingRequirements = true; + $: dockerMissing = missingRequirements.some((req) => req.name === "Docker"); + $: buildxMissing = missingRequirements.some((req) => req.name === "Docker Buildx"); function goBack() { goto("/"); @@ -111,6 +118,14 @@ errorMsg = ""; testResult = null; testMessage = ""; + if (checkingRequirements) { + errorMsg = "Checking required tools. Try again in a moment."; + return; + } + if (missingRequirements.length > 0) { + errorMsg = `Missing required tools: ${missingRequirements.map((req) => req.name).join(", ")}.`; + return; + } const err = validateTarget(); if (err) { errorMsg = err; return; } @@ -145,6 +160,14 @@ async function onProvision() { errorMsg = ""; + if (checkingRequirements) { + errorMsg = "Checking required tools. Try again in a moment."; + return; + } + if (missingRequirements.length > 0) { + errorMsg = `Missing required tools: ${missingRequirements.map((req) => req.name).join(", ")}.`; + return; + } const tErr = validateTarget(); if (tErr) { errorMsg = tErr; return; } if (!serviceAccountKeyPath.trim()) { errorMsg = "Service account key is required."; return; } @@ -271,6 +294,18 @@ firstTimeOn = !firstTimeOn; localStorage.setItem(FIRST_TIME_KEY, String(firstTimeOn)); } + + onMount(async () => { + try { + requirements = await checkRequirements(); + missingRequirements = requirements.filter((req) => !req.ok); + } catch { + requirements = []; + missingRequirements = []; + } finally { + checkingRequirements = false; + } + });
@@ -289,6 +324,43 @@
+ {#if checkingRequirements} +
+

Setup checks

+

Checking local tools…

+
+ {:else if missingRequirements.length > 0} +
+

Missing tools

+
    + {#each missingRequirements as req} +
  • + {req.name} + Missing + {req.hint} +
  • + {/each} +
+
+ {/if} + + {#if dockerMissing || buildxMissing} +
+

Install Docker

+

Docker is required to continue.

+
    +
  • Windows: install Docker Desktop and enable the WSL 2 backend.
  • +
  • macOS: install Docker Desktop for Mac.
  • +
  • Linux: install Docker Engine and the Buildx plugin.
  • +
+ +
+ {/if} + {#if firstTimeOn}
@@ -357,7 +429,7 @@
-
@@ -397,7 +469,7 @@ {#if errorMsg}
{errorMsg}
{/if}
-
@@ -424,6 +496,17 @@ .label-row { display: flex; align-items: center; gap: 10px; } .help-link { font-size: 0.9rem; color: #396cd8; text-decoration: none; } .help-link:hover { text-decoration: underline; } +.requirements h2 { margin: 0 0 10px 0; } +.req-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 10px; } +.req-item { display: grid; grid-template-columns: 1fr auto; gap: 4px 12px; align-items: center; } +.req-name { font-weight: 600; } +.req-status.missing { color: #b91c1c; font-weight: 700; font-size: 0.92rem; } +.req-detail { grid-column: 1 / -1; color: #666; font-size: 0.9rem; } +.req-steps { margin: 6px 0 10px; padding-left: 18px; color: #555; } +.req-steps li { margin: 4px 0; } +.req-links { display: flex; flex-wrap: wrap; gap: 10px; } +.req-links a { color: #396cd8; text-decoration: none; font-size: 0.95rem; } +.req-links a:hover { text-decoration: underline; } button { border: 1px solid #d7d7d7; background: #fff; color: #111; padding: 10px 14px; border-radius: 10px; cursor: pointer; } button:hover { border-color: #c6c6c6; } button.primary { background: #396cd8; color: #fff; border-color: #396cd8; } diff --git a/media/banner-private-home-security.png b/media/banner-private-home-security.png new file mode 100644 index 0000000..765cad0 Binary files /dev/null and b/media/banner-private-home-security.png differ