Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
336bc68
refactor(camera_hub): replace OpenSSL dep with rustls; update Rust to…
jkaczman May 4, 2026
91cfa05
refactor(motion_ai): initialize ort with onnxruntime dylib
jkaczman May 4, 2026
3db06f8
refactor(camera_hub): use /provision for camera_secret/wifi_password …
jkaczman May 6, 2026
498dfde
refactor(releases): remove config_tool & updater from release profile
jkaczman May 6, 2026
d2bb0e1
feat(update): use top-level signatures for release-binaries
jkaczman May 6, 2026
0bb3453
feat(deploy): use Secluso OS image via secret injection
jkaczman May 6, 2026
fd15dd2
fix(motion_ai): use onnxruntime dylib symlink
jkaczman May 6, 2026
5ee83ed
feat(camera_hub): continuously try to create wifi hotspot upon failur…
jkaczman May 6, 2026
9791143
feat(camera_hub/app_native): pass Secluso OS version to app
jkaczman May 6, 2026
612953b
fix(camera_hub): emit 0 when Secluso OS version is unknown
jkaczman May 7, 2026
5036e7a
feat(server): ask to add ufw rule
jkaczman May 7, 2026
727780d
feat(server): remove OpenSSL dependency in web-push
jkaczman May 7, 2026
10428c3
refactor(update): use secluso-<tag>-sha256sums.txt format
jkaczman May 7, 2026
e7c891a
refactor(releases): don't emit IP-camera hub; use 'secluso-camera-hub…
jkaczman May 7, 2026
8d21852
feat(deploy/server): make auto-updater mandatory (for now); pass user…
jkaczman May 7, 2026
1a41c2b
refactor(camera_hub): use semantic versioning for Secluso OS version …
jkaczman May 7, 2026
fd84430
feat(camera_hub): attempt to send pairing token multiple times
jkaczman May 7, 2026
183642c
fix(camera_hub): write component-scope version marker
jkaczman May 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
camera_hub/pending_videos/
camera_hub/state/
releases/builds/
release_work/
motion_ai/cli/output
camera_hub/output
**/target
**/target
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ test_data
cameras.yaml
credentials_full
*.sh
!update/scripts/secluso_sign_checksums.sh

# Firebase Service Files
google-services.json
Expand All @@ -36,4 +37,6 @@ service_account_key.json
*.zip
release_work
*.asc
secluso-v*
secluso-v*
*.p12
__pycache__
16 changes: 4 additions & 12 deletions app_native/examples/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,19 +344,11 @@ fn heartbeat(
config_response.clone(),
timestamp,
) {
Ok(response) if response.contains("healthy") => {
Ok(response) if response.contains("\"status\":\"healthy\"") => {
println!("Healthy heartbeat");

if let Some((_, firmware_version)) = response.split_once('_') {
println!("firmware_version = {firmware_version}");
} else {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("Error: unknown firmware version."),
));
}
println!("{response}");
}
Ok(response) if response == "invalid ciphertext".to_string() => {
Ok(response) if response.contains("\"status\":\"invalid ciphertext\"") => {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("The connection to the camera is corrupted. Pair the app with the camera again."),
Expand Down Expand Up @@ -586,4 +578,4 @@ fn read_varying_len(stream: &mut TcpStream) -> io::Result<Vec<u8>> {
}

Ok(msg)
}
}
67 changes: 50 additions & 17 deletions app_native/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use log::{debug, error, info};
use rand::distr::Alphanumeric;
use rand::Rng;
use secluso_client_lib::config::{
Heartbeat, HeartbeatRequest, HeartbeatResult, OPCODE_HEARTBEAT_REQUEST, OPCODE_HEARTBEAT_RESPONSE,
CameraVersionInfo, Heartbeat, HeartbeatRequest, HeartbeatResult, OPCODE_HEARTBEAT_REQUEST, OPCODE_HEARTBEAT_RESPONSE,
AddAppRequest, AddAppResponseCommon, AddAppResponseDedicated, OPCODE_ADD_APP_REQUEST, OPCODE_ADD_APP_RESPONSE,
};
use secluso_client_lib::mls_client::{Contact, MlsClient, ClientType};
Expand Down Expand Up @@ -44,6 +44,13 @@ const CAMERA_IO_TIMEOUT: Duration = Duration::from_secs(12);
const CAMERA_CONNECT_RETRIES: usize = 3;
const CAMERA_CONNECT_RETRY_DELAY: Duration = Duration::from_millis(350);

#[derive(Serialize)]
struct HeartbeatStatus {
status: String,
#[serde(skip_serializing_if = "Option::is_none")]
version_info: Option<CameraVersionInfo>,
}

#[flutter_rust_bridge::frb]
pub struct Clients {
mls_clients: MlsClients,
Expand Down Expand Up @@ -199,14 +206,14 @@ fn send_credentials_full(
Ok(())
}

fn receive_firmware_version(
fn receive_camera_version_info(
stream: &mut TcpStream,
) -> anyhow::Result<String> {
info!("Sending credentials_full");
let firmware_version_bytes = read_varying_len(stream)?;
let firmware_version = String::from_utf8(firmware_version_bytes)?;
) -> anyhow::Result<CameraVersionInfo> {
info!("Receiving camera version info");
let version_info_bytes = read_varying_len(stream)?;
let version_info = serde_json::from_slice::<CameraVersionInfo>(&version_info_bytes)?;

Ok(firmware_version)
Ok(version_info)
}

fn send_timestamp(
Expand Down Expand Up @@ -371,18 +378,21 @@ pub fn add_camera(
}

info!("Waiting for firmware version from camera");
let firmware_version =
match receive_firmware_version(&mut stream) {
Ok(version) => version,
let version_info =
match receive_camera_version_info(&mut stream) {
Ok(version_info) => version_info,
Err(e) => {
info!("Error (firmware): {e}");
return "Error".to_string();
}
};

let app_native_version = format!("v{}", env!("CARGO_PKG_VERSION"));
info!("Camera version = {}, app native version = {}", firmware_version, app_native_version);
if app_native_version != firmware_version {
info!(
"Camera firmware version = {}, camera OS version = {}, app native version = {}",
version_info.firmware_version, version_info.os_version, app_native_version
);
if app_native_version != version_info.firmware_version {
return "PairVersionIncompatible".to_string();
}

Expand Down Expand Up @@ -425,7 +435,13 @@ pub fn add_camera(
}
}

firmware_version
match serde_json::to_string(&version_info) {
Ok(version_info_json) => version_info_json,
Err(e) => {
info!("Error (version-info-json): {e}");
"Error".to_string()
}
}
}

pub fn initialize(
Expand Down Expand Up @@ -725,11 +741,28 @@ pub fn process_heartbeat_config_response(

match heartbeat_result {
HeartbeatResult::HealthyHeartbeat(_timestamp) => {
Ok(format!("healthy_{}", heartbeat.firmware_version))
let status = HeartbeatStatus {
status: "healthy".to_string(),
version_info: Some(CameraVersionInfo {
firmware_version: heartbeat.firmware_version,
os_version: heartbeat.os_version,
}),
};
serde_json::to_string(&status)
.map_err(|e| io::Error::other(e.to_string()))
}
HeartbeatResult::InvalidTimestamp => Ok("invalid timestamp".to_string()),
HeartbeatResult::InvalidCiphertext => Ok("invalid ciphertext".to_string()),
HeartbeatResult::InvalidEpoch => Ok("invalid epoch".to_string()),
HeartbeatResult::InvalidTimestamp => Ok(serde_json::to_string(&HeartbeatStatus {
status: "invalid timestamp".to_string(),
version_info: None,
}).unwrap()),
HeartbeatResult::InvalidCiphertext => Ok(serde_json::to_string(&HeartbeatStatus {
status: "invalid ciphertext".to_string(),
version_info: None,
}).unwrap()),
HeartbeatResult::InvalidEpoch => Ok(serde_json::to_string(&HeartbeatStatus {
status: "invalid epoch".to_string(),
version_info: None,
}).unwrap()),
}
}
_ => {
Expand Down
Loading
Loading