diff --git a/.gitignore b/.gitignore index 8bd83e8..8435d84 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ pending_videos pending_thumbnails sample_data current_version +wifi_password example_app_data state test_data diff --git a/HOW_TO.md b/HOW_TO.md index 231a511..ba4a473 100644 --- a/HOW_TO.md +++ b/HOW_TO.md @@ -110,11 +110,11 @@ cd /path-to-secluso/server/ cargo run --release ``` -The server binds to 127.0.0.1 by default. -If you must use HTTP and need the server reachable on the network, run: +The server binds to 127.0.0.1:8000 by default. +If you need it reachable on the network, pass an explicit bind address and port: ``` -cargo run --release --network-type=http +cargo run --release -- --bind-address=0.0.0.0 --port=8000 ``` However, the server program might crash. @@ -123,7 +123,7 @@ Therefore, we suggest using a systemd service to ensure that the server program You can find instructions to do this online, e.g., ([here](https://www.shubhamdipt.com/blog/how-to-create-a-systemd-service-in-linux/)). Here is an example of what the service file could look like. -If you need HTTP, add --network-type=http to ExecStart. +Set the bind address and port directly in ExecStart. ``` [Unit] @@ -132,7 +132,7 @@ Description=secluso_server [Service] User=your-username WorkingDirectory=/absolute-path-to-secluso-source/server/ -ExecStart=/absolute-path-to-cargo-executable/cargo run --release --network-type=https +ExecStart=/absolute-path-to-cargo-executable/cargo run --release -- --bind-address=127.0.0.1 --port=8000 Restart=always RestartSec=1 diff --git a/camera_hub/src/pairing.rs b/camera_hub/src/pairing.rs index 614bde8..59eabe7 100644 --- a/camera_hub/src/pairing.rs +++ b/camera_hub/src/pairing.rs @@ -128,6 +128,13 @@ pub fn get_input_camera_secret() -> Vec { data.to_vec() } +// Read the WiFi password contents from file to use for the hotspot +pub fn get_input_wifi_password() -> String { + let pathname = "./wifi_password"; + let contents = fs::read_to_string(pathname).expect("Failed to read from \"wifi_password\" file. You can generate this in config tool"); + return contents; +} + fn invite( stream: &mut TcpStream, mls_client: &mut MlsClient, @@ -489,7 +496,7 @@ pub fn create_wifi_hotspot() { // less fragile than shell parsing to use argv let _ = Command::new("nmcli") .args([ - "device", "wifi", "hotspot", "ssid", "Secluso", "password", "12345678", + "device", "wifi", "hotspot", "ssid", "Secluso", "password", get_input_wifi_password().as_str(), ]) .stdout(Stdio::null()) .stderr(Stdio::null()) diff --git a/client_lib/src/pairing.rs b/client_lib/src/pairing.rs index 3840dcf..cfd185b 100644 --- a/client_lib/src/pairing.rs +++ b/client_lib/src/pairing.rs @@ -2,20 +2,26 @@ //! //! SPDX-License-Identifier: GPL-3.0-or-later +use std::fs; use openmls::prelude::KeyPackage; use serde::{Deserialize, Serialize}; use qrcode::QrCode; use image::Luma; use std::fs::create_dir; use std::io::Write; -use anyhow::Context; +use std::path::Path; +use anyhow::{anyhow, Context}; use openmls_rust_crypto::OpenMlsRustCrypto; use openmls_traits::random::OpenMlsRand; use openmls_traits::OpenMlsProvider; +use rand::distributions::Uniform; +use rand::{Rng, thread_rng}; + pub const NUM_SECRET_BYTES: usize = 72; pub const CAMERA_SECRET_VERSION: &str = "v1.1"; +const WIFI_PASSWORD_LEN: usize = 10; // We version the QR code, store secret bytes as well (base64-url-encoded) as the Wi-Fi passphrase for Raspberry Pi cameras. // Versioned QR codes can be helpful to ensure compatibility. @@ -29,6 +35,9 @@ pub struct CameraSecret { // But this shouldn't be "s" to maintain separation from the user credentials qr code #[serde(rename = "cs", alias = "secret")] pub secret: String, + + #[serde(rename = "wp", alias = "wiif_password")] + pub wifi_password: Option, } #[derive(Serialize, Deserialize, PartialEq)] @@ -63,6 +72,7 @@ pub fn generate_ip_camera_secret(camera_name: &str) -> anyhow::Result> { let camera_secret = CameraSecret { version: CAMERA_SECRET_VERSION.to_string(), secret: base64_url::encode(&secret), + wifi_password: None, }; let writeable_secret = serde_json::to_string(&camera_secret).context("Failed to serialize camera secret into JSON")?; @@ -79,33 +89,76 @@ pub fn generate_ip_camera_secret(camera_name: &str) -> anyhow::Result> { Ok(secret) } -pub fn generate_raspberry_camera_secret(dir: String) -> anyhow::Result<()> { +fn generate_wifi_password(dir: &Path) -> anyhow::Result { + // Generate the randomized WiFi password + let wifi_password = generate_random(WIFI_PASSWORD_LEN, false); //10 characters that are upper/low alphanumeric + fs::File::create(dir.join("wifi_password")).context("Could not create wifi_password file")?; + + fs::write(dir.join("wifi_password"), wifi_password.clone()).with_context(|| format!("Could not create {}", dir.display()))?; + + Ok(wifi_password) +} + +pub fn generate_random(num_chars: usize, special_characters: bool) -> String { + // We exclude : because that character has a special use in the http(s) auth header. + // We exclude / because that character is used within the Linux file system + let charset: &[u8] = if special_characters { + b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\ + abcdefghijklmnopqrstuvwxyz\ + 0123456789\ + !@#$%^&*()-_=+[]{}|;,.<>?" + } else { + b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\ + abcdefghijklmnopqrstuvwxyz\ + 0123456789" + }; + + let mut rng = thread_rng(); + (0..num_chars) + .map(|_| { + let idx = rng.sample(Uniform::new(0, charset.len())); + charset[idx] as char + }) + .collect() +} + +pub fn generate_raspberry_camera_secret(dir: &Path, error_on_folder_exist: bool) -> anyhow::Result<()> { + // If it already exists and we don't want to try re-generating credentials.. + if dir.exists() && error_on_folder_exist { + return Err(anyhow!("The directory exists!")); + } + + // Create the directory if it doesn't exist + if !dir.exists() { + create_dir(dir)?; + } + let crypto = OpenMlsRustCrypto::default(); let secret = crypto .crypto() .random_vec(NUM_SECRET_BYTES).context("Failed to generate camera secret bytes")?; + let wifi_password = generate_wifi_password(dir)?; let camera_secret = CameraSecret { version: CAMERA_SECRET_VERSION.to_string(), secret: base64_url::encode(&secret), + wifi_password: Some(wifi_password), }; - let writeable_secret = serde_json::to_string(&camera_secret).context("Failed to serialize camera secret into JSON")?; - - // Create the directory if it doesn't exist - create_dir(dir.clone()).context("Failed to create directory (it may already exist)")?; + let qr_content = serde_json::to_string(&camera_secret).context("Failed to serialize camera secret into JSON")?; // Save in a file to be given to the camera // The camera secret does not need to be versioned. We're not worried about the formatting ever changing. + // Just put the secret by itself in this file. let mut file = - std::fs::File::create(dir.clone() + "/camera_secret").context("Could not create file")?; + std::fs::File::create(dir.join("camera_secret")).context("Could not create file")?; file.write_all(&secret).context("Failed to write camera secret data to file")?; - // Save as QR code to be shown to the app - let code = QrCode::new(writeable_secret.clone()).context("Failed to generate QR code from camera secret bytes")?; + // Save as QR code to be shown to the app (with secret + version + wifi password) + let code = QrCode::new(qr_content.clone()).context("Failed to generate QR code from camera secret bytes")?; let image = code.render::>().build(); image - .save(dir.clone() + "/camera_secret_qrcode.png").context("Failed to save QR code image")?; + .save(dir.join("camera_secret_qrcode.png")).context("Failed to save QR code image")?; Ok(()) } diff --git a/client_server_lib/src/auth.rs b/client_server_lib/src/auth.rs index 7e77312..38cb889 100644 --- a/client_server_lib/src/auth.rs +++ b/client_server_lib/src/auth.rs @@ -2,10 +2,10 @@ //! //! SPDX-License-Identifier: GPL-3.0-or-later +use anyhow::Context; use rand::distributions::Uniform; use rand::{thread_rng, Rng}; use std::io; -use anyhow::Context; pub const NUM_USERNAME_CHARS: usize = 14; pub const NUM_PASSWORD_CHARS: usize = 14; @@ -14,7 +14,7 @@ pub const USER_CREDENTIALS_VERSION: &str = "uc-v1.0"; #[derive(serde::Serialize, serde::Deserialize)] pub struct UserCredentials { - #[serde(rename = "v", alias = "version")] + #[serde(rename = "v", alias = "version")] pub version: String, #[serde(rename = "u", alias = "username")] @@ -23,8 +23,8 @@ pub struct UserCredentials { #[serde(rename = "p", alias = "password")] pub password: String, - #[serde(rename="sa", alias="server_addr")] - pub server_addr: String + #[serde(rename = "sa", alias = "server_addr")] + pub server_addr: String, } pub fn parse_user_credentials(credentials: Vec) -> io::Result<(String, String)> { @@ -63,13 +63,19 @@ pub fn parse_user_credentials_full( )) } -fn generate_random(num_chars: usize) -> String { +pub fn generate_random(num_chars: usize, special_characters: bool) -> String { // We exclude : because that character has a special use in the http(s) auth header. // We exclude / because that character is used within the Linux file system - let charset: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\ + let charset: &[u8] = if special_characters { + b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\ abcdefghijklmnopqrstuvwxyz\ 0123456789\ - !@#$%^&*()-_=+[]{}|;,.<>?"; + !@#$%^&*()-_=+[]{}|;,.<>?" + } else { + b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\ + abcdefghijklmnopqrstuvwxyz\ + 0123456789" + }; let mut rng = thread_rng(); (0..num_chars) @@ -80,16 +86,21 @@ fn generate_random(num_chars: usize) -> String { .collect() } - pub fn create_user_credentials(server_addr: String) -> anyhow::Result<(Vec, Vec)> { - let username = generate_random(NUM_USERNAME_CHARS); - let password = generate_random(NUM_PASSWORD_CHARS); + let username = generate_random(NUM_USERNAME_CHARS, true); + let password = generate_random(NUM_PASSWORD_CHARS, true); let credentials_string = format!("{}{}", username, password); let credentials = credentials_string.into_bytes(); - let user_credentials = UserCredentials {version: USER_CREDENTIALS_VERSION.to_string(), username, password, server_addr}; - let credentials_full_string = serde_json::to_string(&user_credentials).context("Failed to serialize user credentials into JSON")?; + let user_credentials = UserCredentials { + version: USER_CREDENTIALS_VERSION.to_string(), + username, + password, + server_addr, + }; + let credentials_full_string = serde_json::to_string(&user_credentials) + .context("Failed to serialize user credentials into JSON")?; let credentials_full = credentials_full_string.into_bytes(); Ok((credentials, credentials_full)) diff --git a/client_server_lib/src/lib.rs b/client_server_lib/src/lib.rs index 63280dc..b771369 100644 --- a/client_server_lib/src/lib.rs +++ b/client_server_lib/src/lib.rs @@ -1,4 +1,4 @@ //! SPDX-License-Identifier: GPL-3.0-or-later pub mod auth; -pub mod fault; \ No newline at end of file +pub mod fault; diff --git a/config_tool/Cargo.lock b/config_tool/Cargo.lock index ada3547..7fcdc30 100644 --- a/config_tool/Cargo.lock +++ b/config_tool/Cargo.lock @@ -54,9 +54,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -90,9 +90,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -105,15 +105,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] @@ -265,18 +265,18 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64-url" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5b428e9fb429c6fda7316e9b006f993e6b4c33005e4659339fb5214479dddec" +checksum = "d3b761ea69df72d0cc9591ea39adfb0e3b922a27dc535227d7ffbed5702e8809" dependencies = [ "base64", ] [[package]] name = "base64ct" -version = "1.8.0" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bincode" @@ -408,9 +408,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "const-oid" @@ -501,9 +501,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "rand_core 0.6.4", @@ -674,9 +674,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "0.1.4" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" dependencies = [ "log", "regex", @@ -684,9 +684,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.8" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" dependencies = [ "anstream", "anstyle", @@ -797,6 +797,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -808,15 +814,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", @@ -825,9 +831,9 @@ dependencies = [ [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-timer" @@ -837,15 +843,14 @@ checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-core", "futures-macro", "futures-task", "pin-project-lite", - "pin-utils", "slab", ] @@ -862,9 +867,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", @@ -879,8 +884,21 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", "wasip2", + "wasip3", ] [[package]] @@ -937,6 +955,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + [[package]] name = "hashbrown" version = "0.16.1" @@ -980,6 +1007,12 @@ dependencies = [ "uuid", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hex" version = "0.4.3" @@ -1072,9 +1105,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", "potential_utf", @@ -1085,9 +1118,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", @@ -1098,11 +1131,10 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", @@ -1113,42 +1145,38 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.0.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ - "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", - "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.0.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", "icu_locale_core", - "stable_deref_trait", - "tinystr", "writeable", "yoke", "zerofrom", @@ -1156,6 +1184,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "idna" version = "1.1.0" @@ -1224,7 +1258,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] @@ -1264,15 +1300,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.18" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" dependencies = [ "jiff-static", "log", @@ -1283,9 +1319,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.18" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" dependencies = [ "proc-macro2", "quote", @@ -1304,9 +1340,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", @@ -1328,6 +1364,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "lebe" version = "0.5.3" @@ -1586,9 +1628,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "litemap" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "log" @@ -1955,15 +1997,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkcs8" @@ -2013,24 +2049,24 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" dependencies = [ "portable-atomic", ] [[package]] name = "potential_utf" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ "zerovec", ] @@ -2044,6 +2080,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "primeorder" version = "0.13.6" @@ -2055,9 +2101,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ "toml_edit", ] @@ -2157,6 +2203,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.8.5" @@ -2204,7 +2256,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] @@ -2288,9 +2340,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -2300,9 +2352,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.10" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -2311,9 +2363,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "relative-path" @@ -2478,9 +2530,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" @@ -2579,9 +2631,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" @@ -2675,9 +2727,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", "zerovec", @@ -2707,18 +2759,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.5+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.10+spec-1.0.0" +version = "0.25.8+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +checksum = "16bff38f1d86c47f9ff0647e6838d7bb362522bdf44006c7068c2b1e606f1f3c" dependencies = [ "indexmap", "toml_datetime", @@ -2728,18 +2780,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.6+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" dependencies = [ "winnow", ] [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" @@ -2747,6 +2799,12 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "universal-hash" version = "0.5.1" @@ -2759,9 +2817,9 @@ dependencies = [ [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", @@ -2783,11 +2841,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.18.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.2", "js-sys", "wasm-bindgen", ] @@ -2834,11 +2892,20 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", @@ -2849,9 +2916,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.58" +version = "0.4.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" dependencies = [ "cfg-if", "futures-util", @@ -2863,9 +2930,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2873,9 +2940,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ "bumpalo", "proc-macro2", @@ -2886,18 +2953,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-bindgen-test" -version = "0.3.58" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45649196a53b0b7a15101d845d44d2dda7374fc1b5b5e2bbf58b7577ff4b346d" +checksum = "6311c867385cc7d5602463b31825d454d0837a3aba7cdb5e56d5201792a3f7fe" dependencies = [ "async-trait", "cast", @@ -2917,9 +2984,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.58" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f579cdd0123ac74b94e1a4a72bd963cf30ebac343f2df347da0b8df24cdebed2" +checksum = "67008cdde4769831958536b0f11b3bdd0380bde882be17fff9c2f34bb4549abd" dependencies = [ "proc-macro2", "quote", @@ -2928,15 +2995,49 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-shared" -version = "0.2.108" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe29135b180b72b04c74aa97b2b4a2ef275161eff9a6c7955ea9eaedc7e1d4e" + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8145dd1593bf0fb137dbfa85b8be79ec560a447298955877804640e40c2d6ea" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] [[package]] name = "web-sys" -version = "0.3.85" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" dependencies = [ "js-sys", "wasm-bindgen", @@ -2970,7 +3071,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -3062,9 +3163,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" -version = "0.7.13" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" dependencies = [ "memchr", ] @@ -3074,12 +3175,94 @@ name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "writeable" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "x25519-dalek" @@ -3101,11 +3284,10 @@ checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" [[package]] name = "yoke" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -3113,9 +3295,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", @@ -3166,18 +3348,18 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", @@ -3186,9 +3368,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" dependencies = [ "displaydoc", "yoke", @@ -3197,9 +3379,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ "yoke", "zerofrom", @@ -3208,9 +3390,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", @@ -3219,9 +3401,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.17" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" [[package]] name = "zune-core" diff --git a/config_tool/src/main.rs b/config_tool/src/main.rs index 2ba64d8..bbc1cac 100644 --- a/config_tool/src/main.rs +++ b/config_tool/src/main.rs @@ -12,12 +12,12 @@ use std::fs; use std::io; use std::io::Write; use std::fs::create_dir; +use std::path::Path; use url::Url; -use secluso_client_server_lib::auth::create_user_credentials; +use secluso_client_server_lib::auth::{create_user_credentials}; use anyhow::Context; use anyhow::anyhow; - const USAGE: &str = " Helps configure the Secluso server, camera, and app. @@ -54,18 +54,16 @@ fn main() -> io::Result<()> { .unwrap_or_else(|e| e.exit()); if args.flag_generate_user_credentials { - if let Err(e) = generate_user_credentials(args.flag_dir, args.flag_server_addr) { + if let Err(e) = generate_user_credentials(Path::new(&args.flag_dir), &args.flag_server_addr) { println!("Failed to generate!"); println!("Error: {}", e); } else { println!("Successfully generated!"); } } else if args.flag_generate_camera_secret { - if let Err(e) = secluso_client_lib::pairing::generate_raspberry_camera_secret(args.flag_dir) { - println!("Failed to generate!"); + if let Err(e) = secluso_client_lib::pairing::generate_raspberry_camera_secret(Path::new(&args.flag_dir), true) { + println!("Failed to generate camera secret!"); println!("Error: {}", e); - } else { - println!("Successfully generated!"); } } else { println!("Unsupported command!"); @@ -74,35 +72,36 @@ fn main() -> io::Result<()> { Ok(()) } -fn generate_user_credentials(dir: String, mut server_addr: String) -> anyhow::Result<()> { - if let Ok(parsed_url) = Url::parse(&server_addr) { + +fn generate_user_credentials(dir: &Path, mut server_addr: &str) -> anyhow::Result<()> { + if let Ok(parsed_url) = Url::parse(server_addr) { if parsed_url.scheme() != "http" && parsed_url.scheme() != "https" { return Err(anyhow!("Invalid server URL scheme: {}", parsed_url.scheme())); } } else { - return Err(anyhow!("Invalid server URL")); + return Err(anyhow!("Invalid server URL")); } - if server_addr.ends_with('/') { - server_addr.pop(); - } + // Remove trailing slash. + server_addr = server_addr.trim_end_matches('/'); + let (credentials, credentials_full) = - create_user_credentials(server_addr)?; + create_user_credentials(server_addr.to_string())?; - // Create the directory if it doesn't exist - create_dir(dir.clone()).context("Failed to create directory (it may already exist)")?; + // Create the directory if it doesn't exist + create_dir(dir).context("Failed to create directory (it may already exist)")?; // Save the credentials in a file to be given to the server (delivery service) let mut file = - fs::File::create(dir.clone() + "/user_credentials").context("Could not create file")?; + fs::File::create(dir.join("/user_credentials")).context("Could not create user_credentials file")?; file.write_all(&credentials).context("Failed to write to file")?; // Save the credentials_full (which includes the server addr) as QR code to be shown to the app let code = QrCode::new(&credentials_full).context("Failed to generate QR code")?; let image = code.render::>().build(); image - .save(dir.clone() + "/user_credentials_qrcode.png") + .save(dir.join("/user_credentials_qrcode.png")) .context("Failed to save image")?; // Save the credentials_full in a file to be used for testing with the example app diff --git a/deploy/src-tauri/Cargo.lock b/deploy/src-tauri/Cargo.lock index 8762060..73c7456 100644 --- a/deploy/src-tauri/Cargo.lock +++ b/deploy/src-tauri/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + [[package]] name = "adler2" version = "2.0.1" @@ -53,6 +62,24 @@ dependencies = [ "memchr", ] +[[package]] +name = "aligned" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" +dependencies = [ + "as-slice", +] + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + [[package]] name = "alloc-no-stdlib" version = "2.0.4" @@ -77,12 +104,88 @@ dependencies = [ "libc", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "anyhow" version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "argon2" version = "0.5.3" @@ -95,6 +198,21 @@ dependencies = [ "password-hash", ] +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "ascii-canvas" version = "4.0.0" @@ -270,6 +388,49 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "av-scenechange" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" +dependencies = [ + "aligned", + "anyhow", + "arg_enum_proc_macro", + "arrayvec", + "log", + "num-rational", + "num-traits", + "pastey 0.1.1", + "rayon", + "thiserror 2.0.18", + "v_frame", + "y4m", +] + +[[package]] +name = "av1-grain" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375082f007bd67184fb9c0374614b29f9aaa604ec301635f72338bb65386a53d" +dependencies = [ + "arrayvec", +] + [[package]] name = "aws-lc-rs" version = "1.16.2" @@ -292,6 +453,21 @@ dependencies = [ "fs_extra", ] +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link 0.2.1", +] + [[package]] name = "base16ct" version = "0.2.0" @@ -310,12 +486,30 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64-url" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b761ea69df72d0cc9591ea39adfb0e3b922a27dc535227d7ffbed5702e8809" +dependencies = [ + "base64 0.22.1", +] + [[package]] name = "base64ct" version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -331,6 +525,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + [[package]] name = "bitflags" version = "1.3.2" @@ -346,6 +546,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "bitstream-io" +version = "4.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60d4bd9d1db2c6bdf285e223a7fa369d5ce98ec767dec949c6ca62863ce61757" +dependencies = [ + "core2", +] + [[package]] name = "blake2" version = "0.10.6" @@ -437,6 +646,12 @@ dependencies = [ "libc", ] +[[package]] +name = "built" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" + [[package]] name = "bumpalo" version = "3.20.2" @@ -455,6 +670,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.11.1" @@ -569,6 +790,12 @@ dependencies = [ "toml 0.9.12+spec-1.1.0", ] +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cast5" version = "0.11.1" @@ -647,6 +874,30 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.44" @@ -692,6 +943,18 @@ dependencies = [ "cc", ] +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "combine" version = "4.6.7" @@ -789,6 +1052,26 @@ dependencies = [ "libc", ] +[[package]] +name = "core-models" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "657f625ff361906f779745d08375ae3cc9fef87a35fba5f22874cf773010daf4" +dependencies = [ + "hax-lib", + "pastey 0.2.1", + "rand 0.9.2", +] + +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -816,12 +1099,37 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -1159,6 +1467,20 @@ dependencies = [ "serde", ] +[[package]] +name = "ds-lib" +version = "0.1.0" +source = "git+https://github.com/openmls/openmls?rev=openmls-v0.8.0#6b85f0edc560b4fe0f5b9266092947a774614f3f" +dependencies = [ + "openmls", + "openmls_basic_credential", + "openmls_memory_storage", + "openmls_rust_crypto", + "openmls_traits", + "rand 0.9.2", + "serde", +] + [[package]] name = "dsa" version = "0.6.3" @@ -1355,6 +1677,49 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "env_filter" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1403,12 +1768,47 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "exr" +version = "1.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "fdeflate" version = "0.3.7" @@ -1606,6 +2006,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + [[package]] name = "futures-util" version = "0.3.32" @@ -1815,7 +2221,23 @@ dependencies = [ ] [[package]] -name = "gio" +name = "gif" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5df2ba84018d80c213569363bdcd0c64e6933c67fe4c1d60ecf822971a3c35e" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "gio" version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" @@ -1992,6 +2414,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -2013,6 +2446,43 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +[[package]] +name = "hax-lib" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "543f93241d32b3f00569201bfce9d7a93c92c6421b23c77864ac929dc947b9fc" +dependencies = [ + "hax-lib-macros", + "num-bigint", + "num-traits", +] + +[[package]] +name = "hax-lib-macros" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8755751e760b11021765bb04cb4a6c4e24742688d9f3aa14c2079638f537b0f" +dependencies = [ + "hax-lib-macros-types", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "hax-lib-macros-types" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f177c9ae8ea456e2f71ff3c1ea47bf4464f772a05133fcbba56cd5ba169035a2" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_json", + "uuid", +] + [[package]] name = "heck" version = "0.4.1" @@ -2036,6 +2506,9 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] [[package]] name = "hkdf" @@ -2055,6 +2528,69 @@ dependencies = [ "digest", ] +[[package]] +name = "hpke-rs" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd4b22e7fc3318a1674085f943a35794023ecfe8b24a1691d1d1e016f869c8" +dependencies = [ + "hpke-rs-crypto", + "hpke-rs-libcrux", + "hpke-rs-rust-crypto", + "libcrux-sha3", + "log", + "rand_core 0.9.5", + "serde", + "tls_codec", + "zeroize", +] + +[[package]] +name = "hpke-rs-crypto" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dd92b7d7f0deaae59c152e01c01f5280ea92dfac82090e5c025879b32df9193" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "hpke-rs-libcrux" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd99129e6e5ab959fca63fe83aebbd1b5ff1107eeb549dca597b6d9484e51684" +dependencies = [ + "hpke-rs-crypto", + "libcrux-aead", + "libcrux-ecdh", + "libcrux-hkdf", + "libcrux-kem", + "libcrux-traits", + "rand 0.9.2", + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "hpke-rs-rust-crypto" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "019f9a15c71981dffb32882487c372d3e6e48557c1c1ac84f235cbded330a2ef" +dependencies = [ + "aes-gcm", + "chacha20poly1305", + "hkdf", + "hpke-rs-crypto", + "k256", + "p256", + "p384", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "sha2", + "x25519-dalek", +] + [[package]] name = "html5ever" version = "0.29.1" @@ -2210,7 +2746,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" dependencies = [ "byteorder", - "png", + "png 0.17.16", ] [[package]] @@ -2336,6 +2872,46 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "moxcms", + "num-traits", + "png 0.18.1", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imgref" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" + [[package]] name = "indexmap" version = "1.9.3" @@ -2378,6 +2954,17 @@ dependencies = [ "generic-array 0.14.7", ] +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -2413,6 +3000,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.14.0" @@ -2451,6 +3044,30 @@ dependencies = [ "system-deps", ] +[[package]] +name = "jiff" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "jni" version = "0.21.1" @@ -2537,6 +3154,16 @@ dependencies = [ "serde_json", ] +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "elliptic-curve", +] + [[package]] name = "keccak" version = "0.1.6" @@ -2615,6 +3242,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + [[package]] name = "libappindicator" version = "0.9.0" @@ -2651,6 +3284,244 @@ version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +[[package]] +name = "libcrux-aead" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31ca5c9cb6a0f4dcf2bab1b85aa302537f40b801fc5efe10b5b76fbd677e8161" +dependencies = [ + "libcrux-aesgcm", + "libcrux-chacha20poly1305", + "libcrux-secrets", + "libcrux-traits", +] + +[[package]] +name = "libcrux-aesgcm" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95d897badc420310155f90ed1ea48872809c3446c94ebb116e8a810b66651623" +dependencies = [ + "libcrux-intrinsics", + "libcrux-platform", + "libcrux-secrets", + "libcrux-traits", +] + +[[package]] +name = "libcrux-chacha20poly1305" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6070c5d3991e208511daaf0efae2c747b14a8c136718a3a0a474a82cc0c45522" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-macros", + "libcrux-poly1305", + "libcrux-secrets", + "libcrux-traits", +] + +[[package]] +name = "libcrux-curve25519" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552571ff92bcdf2992b61b600c74d2eaba2c42a14d478c1e9e29391c39db8761" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-macros", + "libcrux-secrets", + "libcrux-traits", +] + +[[package]] +name = "libcrux-ecdh" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1fceb737840ec67255068f6d90e9782ae17fad2337aeb7d7203d76560966216" +dependencies = [ + "libcrux-curve25519", + "libcrux-p256", + "rand 0.9.2", +] + +[[package]] +name = "libcrux-ed25519" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43ddff00e61c3f0778dfa0c0f373931eadb7958c94b4bbc6895e3066d0122b3b" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-macros", + "libcrux-sha2", + "rand_core 0.9.5", +] + +[[package]] +name = "libcrux-hacl-rs" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2637dc87d158e1f1b550fd9b226443e84153fded4de69028d897b534d16d22e6" +dependencies = [ + "libcrux-macros", +] + +[[package]] +name = "libcrux-hkdf" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "295d04515de24bb0f81e5c46d79949517b66ba6a4aaf24328764c6f999e01e36" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-hmac", + "libcrux-secrets", +] + +[[package]] +name = "libcrux-hmac" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d081af93c27d7cebc9a8cc4b3720cba5411186297f9adeddf853d994bba4e7b" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-macros", + "libcrux-sha2", +] + +[[package]] +name = "libcrux-intrinsics" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa4779454e853d1de200cd12f19a8185aac47d99a5ec404cea3295c943d48f1" +dependencies = [ + "core-models", + "hax-lib", +] + +[[package]] +name = "libcrux-kem" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34adb7fdaddd04136e7b4b7368e680f0bca8f1392dfafbb7cb809148c6eb48c7" +dependencies = [ + "libcrux-curve25519", + "libcrux-ecdh", + "libcrux-ml-kem", + "libcrux-p256", + "libcrux-sha3", + "libcrux-traits", + "rand 0.9.2", +] + +[[package]] +name = "libcrux-macros" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffd6aa2dcd5be681662001b81d493f1569c6d49a32361f470b0c955465cd0338" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "libcrux-ml-kem" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a930ff130a63e9d89648d0e22203ca034995191cbfa606b9f3c151ba67306963" +dependencies = [ + "hax-lib", + "libcrux-intrinsics", + "libcrux-platform", + "libcrux-secrets", + "libcrux-sha3", + "libcrux-traits", + "rand 0.9.2", + "tls_codec", +] + +[[package]] +name = "libcrux-p256" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94a3d3d7567b86434b34a98faf19ce5a4dd20f964e0d9a2d13f02792b4ad0109" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-macros", + "libcrux-secrets", + "libcrux-sha2", + "libcrux-traits", +] + +[[package]] +name = "libcrux-platform" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9e21d7ed31a92ac539bd69a8c970b183ee883872d2d19ce27036e24cb8ecc4" +dependencies = [ + "libc", +] + +[[package]] +name = "libcrux-poly1305" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccfb6399682b2dee13b728c779ab5dcc51afbe982b63508ca524806994336134" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-macros", +] + +[[package]] +name = "libcrux-secrets" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce650f3041b44ba40d4263852347d007cd2cd9d1cc856a6f6c8b2e10c3fd40b" +dependencies = [ + "hax-lib", +] + +[[package]] +name = "libcrux-sha2" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9b200262e529493e459609895f3a02434eadb58897352236ebde491b5d6d87" +dependencies = [ + "libcrux-hacl-rs", + "libcrux-macros", + "libcrux-traits", +] + +[[package]] +name = "libcrux-sha3" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3dabce2795479bd7294f853f7966a678cadf7a26d3d29f61cf15f5123e7ba4f" +dependencies = [ + "hax-lib", + "libcrux-intrinsics", + "libcrux-platform", + "libcrux-traits", +] + +[[package]] +name = "libcrux-traits" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "695ff2fb97627e4d57315a2fdfbfe50df1c80c6ef7d91ba34216169bd6f41c00" +dependencies = [ + "libcrux-secrets", + "rand 0.9.2", +] + +[[package]] +name = "libfuzzer-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d" +dependencies = [ + "arbitrary", + "cc", +] + [[package]] name = "libloading" version = "0.7.4" @@ -2729,6 +3600,15 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + [[package]] name = "lru-slab" version = "0.1.2" @@ -2792,6 +3672,16 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + [[package]] name = "md-5" version = "0.10.6" @@ -2829,6 +3719,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minicov" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4869b6a491569605d66d3952bcdf03df789e5b536e5f0cf7758a7f08a55ae24d" +dependencies = [ + "cc", + "walkdir", +] + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2839,6 +3739,18 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.48.0", +] + [[package]] name = "mio" version = "1.1.1" @@ -2850,6 +3762,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + [[package]] name = "muda" version = "0.17.1" @@ -2865,7 +3787,7 @@ dependencies = [ "objc2-core-foundation", "objc2-foundation", "once_cell", - "png", + "png 0.17.16", "serde", "thiserror 2.0.18", "windows-sys 0.60.2", @@ -2913,6 +3835,40 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint-dig" version = "0.8.6" @@ -2935,6 +3891,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -2955,6 +3922,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -3110,6 +4088,15 @@ dependencies = [ "objc2-foundation", ] +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + [[package]] name = "ocb3" version = "0.1.0" @@ -3128,6 +4115,18 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + [[package]] name = "opaque-debug" version = "0.3.1" @@ -3146,6 +4145,127 @@ dependencies = [ "pathdiff", ] +[[package]] +name = "openmls" +version = "0.8.0" +source = "git+https://github.com/openmls/openmls?rev=openmls-v0.8.0#6b85f0edc560b4fe0f5b9266092947a774614f3f" +dependencies = [ + "backtrace", + "itertools", + "log", + "once_cell", + "openmls_basic_credential", + "openmls_memory_storage", + "openmls_rust_crypto", + "openmls_test", + "openmls_traits", + "rand 0.9.2", + "rayon", + "serde", + "serde_bytes", + "serde_json", + "thiserror 2.0.18", + "tls_codec", + "wasm-bindgen-test", + "zeroize", +] + +[[package]] +name = "openmls_basic_credential" +version = "0.5.0" +source = "git+https://github.com/openmls/openmls?rev=openmls-v0.8.0#6b85f0edc560b4fe0f5b9266092947a774614f3f" +dependencies = [ + "ed25519-dalek", + "openmls_traits", + "p256", + "rand 0.8.5", + "serde", + "tls_codec", +] + +[[package]] +name = "openmls_libcrux_crypto" +version = "0.3.0" +source = "git+https://github.com/openmls/openmls?rev=openmls-v0.8.0#6b85f0edc560b4fe0f5b9266092947a774614f3f" +dependencies = [ + "hpke-rs", + "hpke-rs-crypto", + "hpke-rs-libcrux", + "libcrux-aead", + "libcrux-ed25519", + "libcrux-hkdf", + "libcrux-hmac", + "libcrux-sha2", + "libcrux-traits", + "openmls_memory_storage", + "openmls_traits", + "rand 0.9.2", + "rand_chacha 0.9.0", + "tls_codec", +] + +[[package]] +name = "openmls_memory_storage" +version = "0.5.0" +source = "git+https://github.com/openmls/openmls?rev=openmls-v0.8.0#6b85f0edc560b4fe0f5b9266092947a774614f3f" +dependencies = [ + "base64 0.22.1", + "hex", + "log", + "openmls_traits", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "openmls_rust_crypto" +version = "0.5.0" +source = "git+https://github.com/openmls/openmls?rev=openmls-v0.8.0#6b85f0edc560b4fe0f5b9266092947a774614f3f" +dependencies = [ + "aes-gcm", + "chacha20poly1305", + "ed25519-dalek", + "hkdf", + "hmac", + "hpke-rs", + "hpke-rs-crypto", + "hpke-rs-rust-crypto", + "openmls_memory_storage", + "openmls_traits", + "p256", + "rand 0.8.5", + "rand_chacha 0.3.1", + "serde", + "sha2", + "thiserror 2.0.18", + "tls_codec", +] + +[[package]] +name = "openmls_test" +version = "0.2.1" +source = "git+https://github.com/openmls/openmls?rev=openmls-v0.8.0#6b85f0edc560b4fe0f5b9266092947a774614f3f" +dependencies = [ + "ansi_term", + "openmls_rust_crypto", + "openmls_traits", + "proc-macro2", + "quote", + "rstest", + "rstest_reuse", + "syn 2.0.117", +] + +[[package]] +name = "openmls_traits" +version = "0.5.0" +source = "git+https://github.com/openmls/openmls?rev=openmls-v0.8.0#6b85f0edc560b4fe0f5b9266092947a774614f3f" +dependencies = [ + "serde", + "tls_codec", +] + [[package]] name = "openssl-probe" version = "0.2.1" @@ -3293,6 +4413,24 @@ dependencies = [ "subtle", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + +[[package]] +name = "pastey" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec" + [[package]] name = "pathdiff" version = "0.2.3" @@ -3597,6 +4735,19 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.11.0", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "polling" version = "3.11.0" @@ -3611,6 +4762,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "polyval" version = "0.6.2" @@ -3623,6 +4785,21 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" +dependencies = [ + "portable-atomic", +] + [[package]] name = "potential_utf" version = "0.1.4" @@ -3732,20 +4909,91 @@ dependencies = [ ] [[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "pxfm" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d" + +[[package]] +name = "qoi" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] [[package]] -name = "proc-macro2" -version = "1.0.106" +name = "qrcode" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "d68782463e408eb1e668cf6152704bd856c78c5b6417adaee3203d8f4c1fc9ec" dependencies = [ - "unicode-ident", + "image", ] +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quick-xml" version = "0.38.4" @@ -3942,12 +5190,82 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rav1e" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" +dependencies = [ + "aligned-vec", + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av-scenechange", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "paste", + "profiling", + "rand 0.9.2", + "rand_chacha 0.9.0", + "simd_helpers", + "thiserror 2.0.18", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + [[package]] name = "raw-window-handle" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -4017,6 +5335,12 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + [[package]] name = "reqwest" version = "0.13.2" @@ -4095,6 +5419,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "rgb" +version = "0.8.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" + [[package]] name = "ring" version = "0.17.14" @@ -4138,6 +5468,52 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rstest" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49" +dependencies = [ + "futures-timer", + "futures-util", + "rstest_macros", +] + +[[package]] +name = "rstest_macros" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.117", + "unicode-ident", +] + +[[package]] +name = "rstest_reuse" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a8fb4672e840a587a66fc577a5491375df51ddb88f2a2c2a792598c326fe14" +dependencies = [ + "quote", + "rand 0.8.5", + "syn 2.0.117", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + [[package]] name = "rustc-hash" version = "2.1.1" @@ -4336,11 +5712,55 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secluso-client-lib" +version = "0.1.0" +dependencies = [ + "anyhow", + "base64-url", + "bincode", + "docopt", + "ds-lib", + "env_logger", + "image", + "log", + "mio 0.8.11", + "openmls", + "openmls_basic_credential", + "openmls_libcrux_crypto", + "openmls_memory_storage", + "openmls_rust_crypto", + "openmls_traits", + "qrcode", + "rand 0.8.5", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "secluso-client-server-lib" +version = "0.1.0" +dependencies = [ + "anyhow", + "rand 0.8.5", + "serde", + "serde_json", +] + [[package]] name = "secluso-deploy" version = "0.1.0" dependencies = [ "anyhow", + "base64-url", + "image", + "openmls_rust_crypto", + "openmls_traits", + "qrcode", + "reqwest", + "secluso-client-lib", + "secluso-client-server-lib", "secluso-update", "serde", "serde_json", @@ -4351,7 +5771,8 @@ dependencies = [ "tauri-plugin-opener", "tempfile", "tokio", - "toml 1.0.7+spec-1.1.0", + "toml 1.1.0+spec-1.1.0", + "url", "uuid", ] @@ -4525,6 +5946,16 @@ dependencies = [ "typeid", ] +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -4591,9 +6022,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98" dependencies = [ "serde_core", ] @@ -4745,6 +6176,15 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + [[package]] name = "siphasher" version = "0.3.11" @@ -5154,7 +6594,7 @@ dependencies = [ "ico", "json-patch", "plist", - "png", + "png 0.17.16", "proc-macro2", "quote", "semver", @@ -5446,6 +6886,20 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + [[package]] name = "time" version = "0.3.47" @@ -5503,6 +6957,28 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tls_codec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de2e01245e2bb89d6f05801c564fa27624dbd7b1846859876c7dad82e90bf6b" +dependencies = [ + "serde", + "tls_codec_derive", + "zeroize", +] + +[[package]] +name = "tls_codec_derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "tokio" version = "1.50.0" @@ -5511,7 +6987,7 @@ checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", - "mio", + "mio 1.1.1", "pin-project-lite", "socket2", "tokio-macros", @@ -5572,7 +7048,7 @@ checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ "indexmap 2.13.0", "serde_core", - "serde_spanned 1.0.4", + "serde_spanned 1.1.0", "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", @@ -5581,14 +7057,14 @@ dependencies = [ [[package]] name = "toml" -version = "1.0.7+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd28d57d8a6f6e458bc0b8784f8fdcc4b99a437936056fa122cb234f18656a96" +checksum = "f8195ca05e4eb728f4ba94f3e3291661320af739c4e43779cbdfae82ab239fcc" dependencies = [ "indexmap 2.13.0", "serde_core", - "serde_spanned 1.0.4", - "toml_datetime 1.0.1+spec-1.1.0", + "serde_spanned 1.1.0", + "toml_datetime 1.1.0+spec-1.1.0", "toml_parser", "toml_writer", "winnow 1.0.0", @@ -5614,9 +7090,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.0.1+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9" +checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" dependencies = [ "serde_core", ] @@ -5652,25 +7128,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ca1a40644a28bce036923f6a431df0b34236949d111cc07cb6dca830c9ef2e1" dependencies = [ "indexmap 2.13.0", - "toml_datetime 1.0.1+spec-1.1.0", + "toml_datetime 1.1.0+spec-1.1.0", "toml_parser", "winnow 1.0.0", ] [[package]] name = "toml_parser" -version = "1.0.10+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" +checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" dependencies = [ "winnow 1.0.0", ] [[package]] name = "toml_writer" -version = "1.0.7+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17aaa1c6e3dc22b1da4b6bba97d066e354c7945cac2f7852d4e4e7ca7a6b56d" +checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed" [[package]] name = "tower" @@ -5764,7 +7240,7 @@ dependencies = [ "objc2-core-graphics", "objc2-foundation", "once_cell", - "png", + "png 0.17.16", "serde", "thiserror 2.0.18", "windows-sys 0.60.2", @@ -5926,6 +7402,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.22.0" @@ -5938,6 +7420,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + [[package]] name = "vcpkg" version = "0.2.15" @@ -6084,6 +7577,45 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-bindgen-test" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6311c867385cc7d5602463b31825d454d0837a3aba7cdb5e56d5201792a3f7fe" +dependencies = [ + "async-trait", + "cast", + "js-sys", + "libm", + "minicov", + "nu-ansi-term", + "num-traits", + "oorandom", + "serde", + "serde_json", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", + "wasm-bindgen-test-shared", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67008cdde4769831958536b0f11b3bdd0380bde882be17fff9c2f34bb4549abd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "wasm-bindgen-test-shared" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe29135b180b72b04c74aa97b2b4a2ef275161eff9a6c7955ea9eaedc7e1d4e" + [[package]] name = "wasm-encoder" version = "0.244.0" @@ -6252,6 +7784,12 @@ dependencies = [ "windows-core 0.61.2", ] +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + [[package]] name = "winapi" version = "0.3.9" @@ -6457,6 +7995,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -6508,6 +8055,21 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -6565,6 +8127,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -6583,6 +8151,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -6601,6 +8175,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -6631,6 +8211,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -6649,6 +8235,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -6667,6 +8259,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -6685,6 +8283,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -6901,6 +8505,7 @@ checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ "curve25519-dalek", "rand_core 0.6.4", + "serde", "zeroize", ] @@ -6910,6 +8515,12 @@ version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" +[[package]] +name = "y4m" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" + [[package]] name = "yoke" version = "0.8.1" @@ -7167,6 +8778,30 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "zune-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7a1c0af6e5d8d1363f4994b7a091ccf963d8b694f7da5b0b9cceb82da2c0a6" +dependencies = [ + "zune-core", +] + [[package]] name = "zvariant" version = "5.10.0" diff --git a/deploy/src-tauri/Cargo.toml b/deploy/src-tauri/Cargo.toml index 1fd7472..94ef0cd 100644 --- a/deploy/src-tauri/Cargo.toml +++ b/deploy/src-tauri/Cargo.toml @@ -4,6 +4,8 @@ name = "secluso-deploy" version = "0.1.0" edition = "2021" authors = ["John Kaczman "] +autobins = false +default-run = "Secluso" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -14,6 +16,10 @@ authors = ["John Kaczman "] name = "secluso_deploy_lib" crate-type = ["staticlib", "cdylib", "rlib"] +[[bin]] +name = "Secluso" +path = "src/main.rs" + [build-dependencies] tauri-build = { version = "2.5.6", features = [] } @@ -27,8 +33,17 @@ anyhow = "1" tempfile = "3" uuid = { version = "1", features = ["v4", "serde"] } tokio = { version = "1", features = ["rt-multi-thread", "macros"] } -toml = "1.0" +toml = "1.1" secluso-update = { path = "../../update" } +secluso-client-lib = { path = "../../client_lib" } +secluso-client-server-lib = { path = "../../client_server_lib" } +reqwest = { version = "0.13", default-features = false, features = ["blocking", "json"] } +url = "2" +image = "0.25.10" +qrcode = "0.14.1" +base64-url = "3.0.3" +openmls_traits = { git = "https://github.com/openmls/openmls", rev = "openmls-v0.8.0" } +openmls_rust_crypto = { git = "https://github.com/openmls/openmls", rev = "openmls-v0.8.0" } # Keep OpenSSL handling target-specific: diff --git a/deploy/src-tauri/assets/pi_hub/build_image.sh b/deploy/src-tauri/assets/pi_hub/build_image.sh index 1f47a2a..debe699 100755 --- a/deploy/src-tauri/assets/pi_hub/build_image.sh +++ b/deploy/src-tauri/assets/pi_hub/build_image.sh @@ -316,6 +316,11 @@ if [[ "$HAS_SECLUSO" == "true" ]]; then run install -m 600 "$WORK/camera_secret" "$ROOT/var/lib/secluso/camera_secret" fi + # copy wifi password into runtime dir + if [[ -f "$WORK/wifi_password" ]]; then + run install -m 600 "$WORK/wifi_password" "$ROOT/var/lib/secluso/wifi_password" + fi + [[ -f "$BUNDLE_ZIP" ]] || { emit "error" "secluso" "Missing required bundle at $BUNDLE_ZIP" exit 1 @@ -375,6 +380,7 @@ WorkingDirectory=/var/lib/secluso # fail fast if secrets or binary missing ExecStartPre=/usr/bin/test -x ${SECLUSO_INSTALL_DIR}/bin/secluso-raspberry-camera-hub ExecStartPre=/usr/bin/test -r /var/lib/secluso/camera_secret +ExecStartPre=/usr/bin/test -r /var/lib/secluso/wifi_password ExecStart=${SECLUSO_INSTALL_DIR}/bin/secluso-raspberry-camera-hub Environment="RUST_LOG=info" diff --git a/deploy/src-tauri/assets/server/provision_server.sh b/deploy/src-tauri/assets/server/provision_server.sh index f05e652..dc145f9 100644 --- a/deploy/src-tauri/assets/server/provision_server.sh +++ b/deploy/src-tauri/assets/server/provision_server.sh @@ -6,10 +6,11 @@ set -euo pipefail # install_prefix, owner_repo # server_unit, updater_service, update_interval_secs # sudo_cmd ("" or "sudo -S -p ''" or "sudo"), enable_updater ("1"/"0") +# bind_address, listen_port, first_install, overwrite +# release_tag emit() { local level="$1" step="$2" msg="$3" - # minimal json escaping for quotes msg="${msg//\"/\\\"}" printf '::SECLUSO_EVENT::{"level":"%s","step":"%s","msg":"%s"}\n' "$level" "$step" "$msg" } @@ -23,133 +24,88 @@ else emit "info" "sudo" "No sudo wrapper (running as root)" fi +INSTALL_PREFIX="${INSTALL_PREFIX:-/opt/secluso}" +STATE_DIR="${STATE_DIR:-/var/lib/secluso}" +SERVICE_USER="${SERVICE_USER:-secluso}" +RELEASE_TAG="${RELEASE_TAG:-unknown}" +UPDATE_INTERVAL_SECS="${UPDATE_INTERVAL_SECS:-1800}" +SIG_ARGS="" +if [[ -n "${SIG_KEYS:-}" ]]; then + IFS=',' read -r -a sig_list <<< "$SIG_KEYS" + for key in "${sig_list[@]}"; do + if [[ -n "$key" ]]; then + SIG_ARGS="$SIG_ARGS --sig-key $key" + fi + done +fi + emit "info" "config" "INSTALL_PREFIX=$INSTALL_PREFIX" +emit "info" "config" "STATE_DIR=$STATE_DIR" emit "info" "config" "OWNER_REPO=$OWNER_REPO" emit "info" "config" "SERVER_UNIT=$SERVER_UNIT" +emit "info" "config" "UPDATER_SERVICE=$UPDATER_SERVICE" emit "info" "config" "ENABLE_UPDATER=$ENABLE_UPDATER" emit "info" "config" "OVERWRITE=$OVERWRITE" emit "info" "config" "FIRST_INSTALL=$FIRST_INSTALL" -emit "info" "config" "SIG_KEYS=${SIG_KEYS:-}" -emit "info" "config" "NETWORK_TYPE=${NETWORK_TYPE:-https}" +emit "info" "config" "BIND_ADDRESS=${BIND_ADDRESS:-127.0.0.1}" +emit "info" "config" "LISTEN_PORT=${LISTEN_PORT:-8000}" +emit "info" "config" "RELEASE_TAG=$RELEASE_TAG" emit "info" "config" "GITHUB_TOKEN=${GITHUB_TOKEN:+set}" -UPDATE_INTERVAL_SECS="${UPDATE_INTERVAL_SECS:-1800}" - if [[ "${OVERWRITE:-0}" == "1" ]]; then - emit "warn" "overwrite" "Overwrite enabled: stopping services and deleting $INSTALL_PREFIX" + emit "warn" "overwrite" "Overwrite enabled: stopping services and deleting Secluso install directories" ${SUDO} systemctl stop "$UPDATER_SERVICE" 2>/dev/null || true ${SUDO} systemctl stop "$SERVER_UNIT" 2>/dev/null || true ${SUDO} systemctl disable "$UPDATER_SERVICE" 2>/dev/null || true ${SUDO} systemctl disable "$SERVER_UNIT" 2>/dev/null || true - ${SUDO} rm -rf "$INSTALL_PREFIX" - ${SUDO} mkdir -p "$INSTALL_PREFIX" + ${SUDO} rm -rf "$INSTALL_PREFIX" "$STATE_DIR" fi -emit "info" "deps" "Installing dependencies (apt-get)..." +emit "info" "deps" "Installing minimal runtime dependencies (apt-get)..." ${SUDO} apt-get update -${SUDO} apt-get install -y --no-install-recommends ca-certificates curl jq unzip coreutils git pkg-config libssl-dev build-essential - -emit "info" "install" "Ensuring install dirs..." -${SUDO} mkdir -p "$INSTALL_PREFIX/bin" "$INSTALL_PREFIX/server/user_credentials" "$INSTALL_PREFIX/manifest" - -GITHUB_CURL_ARGS=() -if [[ -n "${GITHUB_TOKEN:-}" ]]; then - GITHUB_CURL_ARGS=(-H "Authorization: Bearer ${GITHUB_TOKEN}") -fi +${SUDO} apt-get install -y --no-install-recommends ca-certificates libssl-dev -emit "info" "download" "Resolving latest release tag..." -tag="$(curl -fsSL "${GITHUB_CURL_ARGS[@]}" "https://api.github.com/repos/$OWNER_REPO/releases/latest" | jq -r '.tag_name // empty')" -if [[ -z "$tag" || "$tag" == "null" ]]; then - emit "error" "download" "Missing tag name for $OWNER_REPO" - exit 1 +if ! id -u "$SERVICE_USER" >/dev/null 2>&1; then + emit "info" "install" "Creating dedicated service user $SERVICE_USER" + ${SUDO} useradd --system --home-dir "$STATE_DIR" --create-home --shell /usr/sbin/nologin "$SERVICE_USER" fi -WORK=/tmp/secluso-src -rm -rf "$WORK" -git clone --depth 1 --branch "$tag" "https://github.com/$OWNER_REPO.git" "$WORK" - -emit "info" "build" "Building updater from source..." -cd "$WORK" -git -c protocol.file.allow=always submodule update --init --depth 1 update -cd "$WORK/update" -export RUSTUP_DISABLE_SELF_UPDATE=1 -curl https://sh.rustup.rs -sSf | sh -s -- -y --profile minimal -export PATH="$HOME/.cargo/bin:$PATH" -rustup toolchain install 1.85.0 -cargo +1.85.0 build --release -p secluso-update -updater_bin="target/release/secluso-update" -if [[ ! -x "$updater_bin" ]]; then - emit "error" "build" "Missing secluso-update binary after build" - exit 1 -fi -updater_name="$(basename "$updater_bin")" -${SUDO} install -m 0755 "$updater_bin" "$INSTALL_PREFIX/bin/$updater_name" +emit "info" "install" "Ensuring install and state directories..." +${SUDO} mkdir -p "$INSTALL_PREFIX/bin" "$INSTALL_PREFIX/current_version" "$STATE_DIR" "$STATE_DIR/user_credentials" -emit "info" "install" "Installing server with updater..." -if ! "$INSTALL_PREFIX/bin/$updater_name" --help 2>/dev/null | grep -q -- "--component"; then - emit "error" "install" "Updater does not support --component" +if [[ ! -x /tmp/secluso-server ]]; then + emit "error" "install" "Missing uploaded /tmp/secluso-server binary" exit 1 fi -SIG_ARGS="" -if [[ -n "${SIG_KEYS:-}" ]]; then - IFS=',' read -r -a sig_list <<< "$SIG_KEYS" - for key in "${sig_list[@]}"; do - if [[ -n "$key" ]]; then - SIG_ARGS="$SIG_ARGS --sig-key $key" - fi - done -fi - -${SUDO} env ${GITHUB_TOKEN:+GITHUB_TOKEN=$GITHUB_TOKEN} timeout 90s "$INSTALL_PREFIX/bin/$updater_name" --component server --interval-secs 60 --github-timeout-secs 20 --github-repo "$OWNER_REPO"$SIG_ARGS || true -if [[ ! -x "$INSTALL_PREFIX/bin/secluso-server" ]]; then - emit "error" "install" "secluso-server missing after updater run" +if [[ ! -x /tmp/secluso-update ]]; then + emit "error" "install" "Missing uploaded /tmp/secluso-update binary" exit 1 fi -emit "info" "install" "Installing bundled updater from release..." -arch="$(uname -m)" -case "$arch" in - x86_64) archdir="x86_64-unknown-linux-gnu" ;; - aarch64|arm64) archdir="aarch64-unknown-linux-gnu" ;; - *) emit "warn" "arch" "Unsupported arch for bundled updater: $arch"; archdir="" ;; -esac -if [[ -n "$archdir" ]]; then - rel_json="$(curl -fsSL "${GITHUB_CURL_ARGS[@]}" "https://api.github.com/repos/$OWNER_REPO/releases/tags/$tag")" - asset_name="$(echo "$rel_json" | jq -r ' - .assets | map(select(.name | test("^secluso-v.*\\.zip$"))) | if length==0 then empty else .[0].name end - ')" - if [[ -n "$asset_name" && "$asset_name" != "null" ]]; then - rm -rf /tmp/secluso_bundle && mkdir -p /tmp/secluso_bundle - curl -fL "${GITHUB_CURL_ARGS[@]}" -o /tmp/secluso_bundle.zip "https://github.com/$OWNER_REPO/releases/download/$tag/$asset_name" - unzip -o /tmp/secluso_bundle.zip -d /tmp/secluso_bundle >/dev/null - root="/tmp/secluso_bundle" - maybe="$(find /tmp/secluso_bundle -maxdepth 2 -type f -name manifest.json | head -n 1 || true)" - if [[ -n "$maybe" ]]; then - root="$(dirname "$maybe")" - fi - if [[ -x "$root/$archdir/secluso-update" ]]; then - ${SUDO} install -m 0755 "$root/$archdir/secluso-update" "$INSTALL_PREFIX/bin/secluso-update" - updater_name="secluso-update" - else - emit "warn" "install" "bundled secluso-update missing for $archdir" - fi - else - emit "warn" "install" "No release bundle asset found for updater" - fi +emit "info" "install" "Installing verified binaries..." +${SUDO} install -m 0755 /tmp/secluso-server "$INSTALL_PREFIX/bin/secluso-server" +${SUDO} install -m 0755 /tmp/secluso-update "$INSTALL_PREFIX/bin/secluso-update" +${SUDO} rm -f /tmp/secluso-server /tmp/secluso-update +printf '%s\n' "${RELEASE_TAG#v}" | ${SUDO} tee "$INSTALL_PREFIX/current_version/server" >/dev/null +printf '%s\n' "${RELEASE_TAG#v}" | ${SUDO} tee "$INSTALL_PREFIX/current_version/updater" >/dev/null + +if [[ -f /tmp/service_account_key.json ]]; then + emit "info" "secrets" "Installing service account key" + ${SUDO} install -m 0600 /tmp/service_account_key.json "$STATE_DIR/service_account_key.json" + ${SUDO} rm -f /tmp/service_account_key.json fi -rm -rf "$WORK" - if [[ "${FIRST_INSTALL:-0}" == "1" ]]; then - emit "info" "first_install" "Placing secrets + writing systemd units..." + emit "info" "secrets" "Installing freshly generated user credentials" + ${SUDO} install -m 0600 /tmp/user_credentials "$STATE_DIR/user_credentials/user_credentials" + ${SUDO} install -m 0600 /tmp/credentials_full "$STATE_DIR/credentials_full" + ${SUDO} rm -f /tmp/user_credentials /tmp/credentials_full +fi - ${SUDO} mkdir -p "$INSTALL_PREFIX/server/user_credentials" - ${SUDO} install -m 0600 /tmp/service_account_key.json "$INSTALL_PREFIX/server/service_account_key.json" - ${SUDO} install -m 0600 /tmp/user_credentials "$INSTALL_PREFIX/server/user_credentials/user_credentials" - ${SUDO} rm -f /tmp/service_account_key.json /tmp/user_credentials +${SUDO} chown -R "$SERVICE_USER:$SERVICE_USER" "$STATE_DIR" - emit "info" "systemd" "Writing server unit..." - ${SUDO} tee "/etc/systemd/system/$SERVER_UNIT" >/dev/null </dev/null </dev/null </dev/null </dev/null || true - emit "warn" "systemd" "updater disabled" - fi +emit "info" "systemd" "Reloading systemd units and restarting Secluso..." +${SUDO} systemctl daemon-reload +${SUDO} systemctl enable "$SERVER_UNIT" +${SUDO} systemctl restart "$SERVER_UNIT" + +if [[ "${ENABLE_UPDATER:-1}" == "1" ]]; then + ${SUDO} systemctl enable "$UPDATER_SERVICE" + ${SUDO} systemctl restart "$UPDATER_SERVICE" + emit "info" "systemd" "updater service enabled" else - emit "info" "restart" "Update-only path: restarting $SERVER_UNIT..." - ${SUDO} systemctl restart "$SERVER_UNIT" || true + ${SUDO} systemctl disable --now "$UPDATER_SERVICE" 2>/dev/null || true + emit "warn" "systemd" "updater disabled" fi emit "info" "done" "DONE" diff --git a/deploy/src-tauri/icons/128x128.png b/deploy/src-tauri/icons/128x128.png index 46d214d..3c09a51 100644 Binary files a/deploy/src-tauri/icons/128x128.png and b/deploy/src-tauri/icons/128x128.png differ diff --git a/deploy/src-tauri/icons/128x128@2x.png b/deploy/src-tauri/icons/128x128@2x.png index e7dcf00..cb524cd 100644 Binary files a/deploy/src-tauri/icons/128x128@2x.png and b/deploy/src-tauri/icons/128x128@2x.png differ diff --git a/deploy/src-tauri/icons/32x32.png b/deploy/src-tauri/icons/32x32.png index 93538a9..7a47028 100644 Binary files a/deploy/src-tauri/icons/32x32.png and b/deploy/src-tauri/icons/32x32.png differ diff --git a/deploy/src-tauri/icons/64x64.png b/deploy/src-tauri/icons/64x64.png new file mode 100644 index 0000000..96b750b Binary files /dev/null and b/deploy/src-tauri/icons/64x64.png differ diff --git a/deploy/src-tauri/icons/Square107x107Logo.png b/deploy/src-tauri/icons/Square107x107Logo.png index f13c70d..61b4f99 100644 Binary files a/deploy/src-tauri/icons/Square107x107Logo.png and b/deploy/src-tauri/icons/Square107x107Logo.png differ diff --git a/deploy/src-tauri/icons/Square142x142Logo.png b/deploy/src-tauri/icons/Square142x142Logo.png index 6f82a42..67cc600 100644 Binary files a/deploy/src-tauri/icons/Square142x142Logo.png and b/deploy/src-tauri/icons/Square142x142Logo.png differ diff --git a/deploy/src-tauri/icons/Square150x150Logo.png b/deploy/src-tauri/icons/Square150x150Logo.png index 0fedc37..5468a59 100644 Binary files a/deploy/src-tauri/icons/Square150x150Logo.png and b/deploy/src-tauri/icons/Square150x150Logo.png differ diff --git a/deploy/src-tauri/icons/Square284x284Logo.png b/deploy/src-tauri/icons/Square284x284Logo.png index 6fbde11..7fee5e6 100644 Binary files a/deploy/src-tauri/icons/Square284x284Logo.png and b/deploy/src-tauri/icons/Square284x284Logo.png differ diff --git a/deploy/src-tauri/icons/Square30x30Logo.png b/deploy/src-tauri/icons/Square30x30Logo.png index f00803d..8bed618 100644 Binary files a/deploy/src-tauri/icons/Square30x30Logo.png and b/deploy/src-tauri/icons/Square30x30Logo.png differ diff --git a/deploy/src-tauri/icons/Square310x310Logo.png b/deploy/src-tauri/icons/Square310x310Logo.png index 3037d3f..689cb59 100644 Binary files a/deploy/src-tauri/icons/Square310x310Logo.png and b/deploy/src-tauri/icons/Square310x310Logo.png differ diff --git a/deploy/src-tauri/icons/Square44x44Logo.png b/deploy/src-tauri/icons/Square44x44Logo.png index e2bac45..e83ebb0 100644 Binary files a/deploy/src-tauri/icons/Square44x44Logo.png and b/deploy/src-tauri/icons/Square44x44Logo.png differ diff --git a/deploy/src-tauri/icons/Square71x71Logo.png b/deploy/src-tauri/icons/Square71x71Logo.png index 51c2455..c6822b5 100644 Binary files a/deploy/src-tauri/icons/Square71x71Logo.png and b/deploy/src-tauri/icons/Square71x71Logo.png differ diff --git a/deploy/src-tauri/icons/Square89x89Logo.png b/deploy/src-tauri/icons/Square89x89Logo.png index f55ddb5..e42447b 100644 Binary files a/deploy/src-tauri/icons/Square89x89Logo.png and b/deploy/src-tauri/icons/Square89x89Logo.png differ diff --git a/deploy/src-tauri/icons/StoreLogo.png b/deploy/src-tauri/icons/StoreLogo.png index 61a94d5..b66a289 100644 Binary files a/deploy/src-tauri/icons/StoreLogo.png and b/deploy/src-tauri/icons/StoreLogo.png differ diff --git a/deploy/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml b/deploy/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..2ffbf24 --- /dev/null +++ b/deploy/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/deploy/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png b/deploy/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..879bff4 Binary files /dev/null and b/deploy/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png differ diff --git a/deploy/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png b/deploy/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..f863e85 Binary files /dev/null and b/deploy/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/deploy/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png b/deploy/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..e6381da Binary files /dev/null and b/deploy/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png differ diff --git a/deploy/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png b/deploy/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..f0faebc Binary files /dev/null and b/deploy/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png differ diff --git a/deploy/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png b/deploy/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..6611815 Binary files /dev/null and b/deploy/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/deploy/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png b/deploy/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..067d260 Binary files /dev/null and b/deploy/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png differ diff --git a/deploy/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png b/deploy/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..3e2960a Binary files /dev/null and b/deploy/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png differ diff --git a/deploy/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png b/deploy/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..acf56b8 Binary files /dev/null and b/deploy/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/deploy/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png b/deploy/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..bfc7305 Binary files /dev/null and b/deploy/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/deploy/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png b/deploy/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..6429699 Binary files /dev/null and b/deploy/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png differ diff --git a/deploy/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png b/deploy/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..966b4e3 Binary files /dev/null and b/deploy/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/deploy/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png b/deploy/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..cec01ef Binary files /dev/null and b/deploy/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/deploy/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png b/deploy/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..5e17127 Binary files /dev/null and b/deploy/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/deploy/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/deploy/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..1ecaa8f Binary files /dev/null and b/deploy/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/deploy/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png b/deploy/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..b237f22 Binary files /dev/null and b/deploy/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/deploy/src-tauri/icons/android/values/ic_launcher_background.xml b/deploy/src-tauri/icons/android/values/ic_launcher_background.xml new file mode 100644 index 0000000..ea9c223 --- /dev/null +++ b/deploy/src-tauri/icons/android/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #fff + \ No newline at end of file diff --git a/deploy/src-tauri/icons/icon.icns b/deploy/src-tauri/icons/icon.icns index ff16f4c..aef19e8 100644 Binary files a/deploy/src-tauri/icons/icon.icns and b/deploy/src-tauri/icons/icon.icns differ diff --git a/deploy/src-tauri/icons/icon.ico b/deploy/src-tauri/icons/icon.ico index ba870fc..2d2dc76 100644 Binary files a/deploy/src-tauri/icons/icon.ico and b/deploy/src-tauri/icons/icon.ico differ diff --git a/deploy/src-tauri/icons/icon.png b/deploy/src-tauri/icons/icon.png index ce00411..7b4b1e9 100644 Binary files a/deploy/src-tauri/icons/icon.png and b/deploy/src-tauri/icons/icon.png differ diff --git a/deploy/src-tauri/icons/ios/AppIcon-20x20@1x.png b/deploy/src-tauri/icons/ios/AppIcon-20x20@1x.png new file mode 100644 index 0000000..acb38af Binary files /dev/null and b/deploy/src-tauri/icons/ios/AppIcon-20x20@1x.png differ diff --git a/deploy/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/deploy/src-tauri/icons/ios/AppIcon-20x20@2x-1.png new file mode 100644 index 0000000..01cdd92 Binary files /dev/null and b/deploy/src-tauri/icons/ios/AppIcon-20x20@2x-1.png differ diff --git a/deploy/src-tauri/icons/ios/AppIcon-20x20@2x.png b/deploy/src-tauri/icons/ios/AppIcon-20x20@2x.png new file mode 100644 index 0000000..01cdd92 Binary files /dev/null and b/deploy/src-tauri/icons/ios/AppIcon-20x20@2x.png differ diff --git a/deploy/src-tauri/icons/ios/AppIcon-20x20@3x.png b/deploy/src-tauri/icons/ios/AppIcon-20x20@3x.png new file mode 100644 index 0000000..f042701 Binary files /dev/null and b/deploy/src-tauri/icons/ios/AppIcon-20x20@3x.png differ diff --git a/deploy/src-tauri/icons/ios/AppIcon-29x29@1x.png b/deploy/src-tauri/icons/ios/AppIcon-29x29@1x.png new file mode 100644 index 0000000..cd7366d Binary files /dev/null and b/deploy/src-tauri/icons/ios/AppIcon-29x29@1x.png differ diff --git a/deploy/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/deploy/src-tauri/icons/ios/AppIcon-29x29@2x-1.png new file mode 100644 index 0000000..74c19e6 Binary files /dev/null and b/deploy/src-tauri/icons/ios/AppIcon-29x29@2x-1.png differ diff --git a/deploy/src-tauri/icons/ios/AppIcon-29x29@2x.png b/deploy/src-tauri/icons/ios/AppIcon-29x29@2x.png new file mode 100644 index 0000000..74c19e6 Binary files /dev/null and b/deploy/src-tauri/icons/ios/AppIcon-29x29@2x.png differ diff --git a/deploy/src-tauri/icons/ios/AppIcon-29x29@3x.png b/deploy/src-tauri/icons/ios/AppIcon-29x29@3x.png new file mode 100644 index 0000000..90860ab Binary files /dev/null and b/deploy/src-tauri/icons/ios/AppIcon-29x29@3x.png differ diff --git a/deploy/src-tauri/icons/ios/AppIcon-40x40@1x.png b/deploy/src-tauri/icons/ios/AppIcon-40x40@1x.png new file mode 100644 index 0000000..01cdd92 Binary files /dev/null and b/deploy/src-tauri/icons/ios/AppIcon-40x40@1x.png differ diff --git a/deploy/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/deploy/src-tauri/icons/ios/AppIcon-40x40@2x-1.png new file mode 100644 index 0000000..62f859c Binary files /dev/null and b/deploy/src-tauri/icons/ios/AppIcon-40x40@2x-1.png differ diff --git a/deploy/src-tauri/icons/ios/AppIcon-40x40@2x.png b/deploy/src-tauri/icons/ios/AppIcon-40x40@2x.png new file mode 100644 index 0000000..62f859c Binary files /dev/null and b/deploy/src-tauri/icons/ios/AppIcon-40x40@2x.png differ diff --git a/deploy/src-tauri/icons/ios/AppIcon-40x40@3x.png b/deploy/src-tauri/icons/ios/AppIcon-40x40@3x.png new file mode 100644 index 0000000..3f97922 Binary files /dev/null and b/deploy/src-tauri/icons/ios/AppIcon-40x40@3x.png differ diff --git a/deploy/src-tauri/icons/ios/AppIcon-512@2x.png b/deploy/src-tauri/icons/ios/AppIcon-512@2x.png new file mode 100644 index 0000000..c2dc908 Binary files /dev/null and b/deploy/src-tauri/icons/ios/AppIcon-512@2x.png differ diff --git a/deploy/src-tauri/icons/ios/AppIcon-60x60@2x.png b/deploy/src-tauri/icons/ios/AppIcon-60x60@2x.png new file mode 100644 index 0000000..3f97922 Binary files /dev/null and b/deploy/src-tauri/icons/ios/AppIcon-60x60@2x.png differ diff --git a/deploy/src-tauri/icons/ios/AppIcon-60x60@3x.png b/deploy/src-tauri/icons/ios/AppIcon-60x60@3x.png new file mode 100644 index 0000000..01999bb Binary files /dev/null and b/deploy/src-tauri/icons/ios/AppIcon-60x60@3x.png differ diff --git a/deploy/src-tauri/icons/ios/AppIcon-76x76@1x.png b/deploy/src-tauri/icons/ios/AppIcon-76x76@1x.png new file mode 100644 index 0000000..6805a5a Binary files /dev/null and b/deploy/src-tauri/icons/ios/AppIcon-76x76@1x.png differ diff --git a/deploy/src-tauri/icons/ios/AppIcon-76x76@2x.png b/deploy/src-tauri/icons/ios/AppIcon-76x76@2x.png new file mode 100644 index 0000000..2950879 Binary files /dev/null and b/deploy/src-tauri/icons/ios/AppIcon-76x76@2x.png differ diff --git a/deploy/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/deploy/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png new file mode 100644 index 0000000..cbea4ff Binary files /dev/null and b/deploy/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png differ diff --git a/deploy/src-tauri/src/pi_hub_provision/build.rs b/deploy/src-tauri/src/pi_hub_provision/build.rs index 7d7fc07..f2c9ab9 100755 --- a/deploy/src-tauri/src/pi_hub_provision/build.rs +++ b/deploy/src-tauri/src/pi_hub_provision/build.rs @@ -6,6 +6,10 @@ use crate::pi_hub_provision::model::{Apt, Config, RuntimeConfig, Secluso, SigKey use crate::pi_hub_provision::temp::{shared_temp_root, shared_temp_dir}; use crate::pi_hub_provision::{BuildImageRequest, BuildImageResponse}; use anyhow::{anyhow, bail, Context, Result}; +use secluso_update::{ + build_github_client, default_signers, download_and_verify_component, fetch_latest_release, Component as ReleaseComponent, + Signer, +}; use std::fs; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; @@ -64,6 +68,42 @@ fn normalize_repo(input: &str) -> String { trimmed.trim_end_matches(".git").to_string() } +fn resolve_signers(sig_keys: Option<&[SigKey]>) -> Vec { + let Some(sig_keys) = sig_keys else { + return default_signers(); + }; + + if sig_keys.is_empty() { + return default_signers(); + } + + sig_keys + .iter() + .map(|key| Signer { + label: key.name.trim().to_string(), + github_user: key.github_user.trim().to_string(), + }) + .collect() +} + +fn download_verified_bundle(owner_repo: &str, sig_keys: Option<&[SigKey]>, github_token: Option<&str>) -> Result<(String, Vec)> { + let signers = resolve_signers(sig_keys); + let client = build_github_client(20, github_token, "secluso-deploy")?; + let release = fetch_latest_release(&client, owner_repo) + .with_context(|| format!("Fetching latest release metadata for {owner_repo}"))?; + let verified = download_and_verify_component( + &client, + &release, + ReleaseComponent::RaspberryCameraHub, + "aarch64", + None, + &signers, + ) + .context("Downloading and verifying Raspberry Pi bundle")?; + + Ok((verified.release_tag, verified.bundle_bytes)) +} + fn normalize_ssh_suffix(output_name: &str, ssh_enabled: bool) -> String { if !output_name.ends_with(".img") { return output_name.to_string(); @@ -204,6 +244,38 @@ pub fn run_build_image(app: &AppHandle, run_id: Uuid, req: BuildImageRequest) -> } step_ok(app, run_id, "credentials"); + if let Some(secluso) = &cfg.secluso { + step_start(app, run_id, "artifacts", "Downloading verified bundle"); + let repo = secluso + .repo + .as_deref() + .map(normalize_repo) + .unwrap_or_else(|| "secluso/secluso".to_string()); + let sig_keys = secluso.sig_keys.as_deref(); + let github_token = secluso.github_token.as_deref(); + let (release_tag, bundle_bytes) = download_verified_bundle(&repo, sig_keys, github_token) + .map_err(|e| { + let msg = format!("{e:#}"); + step_error(app, run_id, "artifacts", &msg); + anyhow!(msg) + })?; + fs::write(work_path.join("secluso_bundle.zip"), bundle_bytes) + .with_context(|| format!("writing {}", work_path.join("secluso_bundle.zip").display())) + .map_err(|e| { + let msg = format!("{e:#}"); + step_error(app, run_id, "artifacts", &msg); + anyhow!(msg) + })?; + log_line( + app, + run_id, + "info", + Some("artifacts"), + format!("Verified Raspberry Pi bundle {release_tag} for {repo}."), + ); + step_ok(app, run_id, "artifacts"); + } + let mut base_image = DEFAULT_BASE_IMAGE; let mut search_image_name: &str = DEFAULT_BASE_IMAGE.split('/').last().unwrap(); diff --git a/deploy/src-tauri/src/pi_hub_provision/credentials.rs b/deploy/src-tauri/src/pi_hub_provision/credentials.rs index fac24ca..7ad8bbf 100644 --- a/deploy/src-tauri/src/pi_hub_provision/credentials.rs +++ b/deploy/src-tauri/src/pi_hub_provision/credentials.rs @@ -1,288 +1,71 @@ //! SPDX-License-Identifier: GPL-3.0-or-later -use crate::pi_hub_provision::docker::{run_with_output, DockerCleanup}; use crate::pi_hub_provision::model::SigKey; -use anyhow::{bail, Result}; -use secluso_update::{ - build_github_client, default_signers, download_and_verify_component, fetch_latest_release, - parse_sig_keys, Component, DEFAULT_OWNER_REPO, -}; +use anyhow::{anyhow, Context, Result}; +use image::Luma; +use qrcode::QrCode; +use secluso_client_server_lib::auth::{create_user_credentials}; use std::fs; use std::path::Path; -use std::process::Command; use tauri::AppHandle; +use url::Url; use uuid::Uuid; -// Deploy accepts optional signer overrides from the UI/config as label:user pairs. -// We normalize and 'strictly' parse those values here so downstream verification can rely on -// non-empty signer identities. If no explicit signers are provided, we fall back -// to the updater's default signer policy so deploy and updater enforce the same trust set. -fn effective_signers(sig_keys: Option<&[SigKey]>) -> Result> { - let values = sig_keys - .unwrap_or(&[]) - .iter() - .map(|k| format!("{}:{}", k.name.trim(), k.github_user.trim())) - .filter(|v| !v.trim().is_empty()) - .collect::>(); - let parsed = parse_sig_keys(&values)?; - if parsed.is_empty() { - Ok(default_signers()) - } else { - Ok(parsed) - } -} - -// The config tool binary is selected by architecture inside the signed bundle. Deploy might run on -// one host architecture while Docker executes for another platform (ex: cross-arch builds). -// We prefer DOCKER_DEFAULT_PLATFORM when available so we fetch the same artifact architecture that -// the containerized execution path expects, and only fall back to host architecture when no explicit -// Docker platform is configured. -fn config_tool_arch() -> String { - if let Ok(platform) = std::env::var("DOCKER_DEFAULT_PLATFORM") { - let normalized = platform.to_ascii_lowercase(); - if normalized.contains("amd64") || normalized.contains("x86_64") { - return "x86_64".to_string(); - } - if normalized.contains("arm64") || normalized.contains("aarch64") { - return "aarch64".to_string(); - } - } - - match std::env::consts::ARCH { - "arm64" => "aarch64".to_string(), - arch => arch.to_string(), - } -} - -// This function is the trust anchor for deploy credential generation. Instead of bootstrapping and -// executing a separate updater binary, deploy directly performs the same signed-release verification -// pipeline, where it fetchs latest release metadata, enforcs signer policy, verifies bundle integrity/signatures, -// and extracts the exact config_tool artifact for the target architecture. The returned bytes are only -// accepted AFTER all cryptographic checks succeed. -fn fetch_verified_config_tool( - repo: &str, - sig_keys: Option<&[SigKey]>, - github_token: Option<&str>, -) -> Result { - let owner_repo = if repo.trim().is_empty() { - DEFAULT_OWNER_REPO.to_string() - } else { - repo.trim().to_string() - }; - let token = github_token.map(|v| v.trim()).filter(|v| !v.is_empty()); - - let client = build_github_client(20, token, "secluso-deploy")?; - let release = fetch_latest_release(&client, &owner_repo)?; - let signers = effective_signers(sig_keys)?; - let arch = config_tool_arch(); - - download_and_verify_component( - &client, - &release, - Component::ConfigTool, - &arch, - None, - &signers, - ) -} - -// We seed a fresh Docker volume with two verified inputs, the config tool executable and the -// release bundle zip that produced it. Copying these pre-verified bytes into the volume keeps runtime -// execution deterministic and **avoids introducing additional network/download steps inside the container** -// Helps to limit moving parts during credential generation and preserves the verified-bytes trust chain. -fn seed_volume_with_inputs( - app: &AppHandle, - run_id: Uuid, +// TODO: Placeholder for later... make config_tool a library +pub fn generate_secluso_credentials( + _app: &AppHandle, + _run_id: Uuid, work_path: &Path, - volume_name: &str, - container_name: &str, - config_tool_bytes: &[u8], - bundle_bytes: &[u8], + _repo: &str, + _sig_keys: Option<&[SigKey]>, + _github_token: Option<&str>, ) -> Result<()> { - let mut volume_cmd = Command::new("docker"); - volume_cmd.args(["volume", "create", volume_name]); - run_with_output(app, run_id, "credentials", &mut volume_cmd)?; - - let mut create_cmd = Command::new("docker"); - create_cmd.args([ - "create", - "--name", - container_name, - "-v", - &format!("{}:/out", volume_name), - "busybox", - ]); - run_with_output(app, run_id, "credentials", &mut create_cmd)?; - - let local_tool = work_path.join("secluso-config-tool"); - let local_bundle = work_path.join("secluso_bundle.zip"); - fs::write(&local_tool, config_tool_bytes)?; - fs::write(&local_bundle, bundle_bytes)?; - - let mut cp_tool = Command::new("docker"); - cp_tool.args([ - "cp", - &local_tool.display().to_string(), - &format!("{}:/out/secluso-config-tool", container_name), - ]); - run_with_output(app, run_id, "credentials", &mut cp_tool)?; - - let mut cp_bundle = Command::new("docker"); - cp_bundle.args([ - "cp", - &local_bundle.display().to_string(), - &format!("{}:/out/secluso_bundle.zip", container_name), - ]); - run_with_output(app, run_id, "credentials", &mut cp_bundle)?; + secluso_client_lib::pairing::generate_raspberry_camera_secret(work_path, false)?; Ok(()) } -// The container runtime is used as an isolated executor for the config tool commands. The script string -// is supplied by trusted deploy code paths and runs against the seeded volume, so the container is really only -// responsible for deterministic credential generation from already-verified binaries. -fn run_config_tool_in_volume( - app: &AppHandle, - run_id: Uuid, - volume_name: &str, - script: &str, -) -> Result<()> { - let mut cmd = Command::new("docker"); - cmd.args(["run", "--rm"]) - .args(["-v", &format!("{}:/out", volume_name)]) - .arg("rust:1.82-bookworm") - .args(["bash", "-lc", script]); - - run_with_output(app, run_id, "credentials", &mut cmd) -} - -// After credential generation, we copy all output artifacts from the isolated volume into the host -// work directory in one step. Helps to keep the host-side interface simple and allows subsequent explicit -// output checks to decide whether the run produced the required files. -fn copy_volume_outputs_to_work( - app: &AppHandle, - run_id: Uuid, - container_name: &str, - work_path: &Path, -) -> Result<()> { - let mut copy_cmd = Command::new("docker"); - copy_cmd.args([ - "cp", - &format!("{}:/out/.", container_name), - &work_path.display().to_string(), - ]); - run_with_output(app, run_id, "credentials", &mut copy_cmd) -} -// 1) Resolve and cryptographically verify config_tool from signed release artifacts. -// 2) Execute only that verified tool inside an isolated Docker volume. -// 3) Enforce expected outputs before returning success. -// This preserves the updater-grade verification guarantees -pub fn generate_secluso_credentials( - app: &AppHandle, - run_id: Uuid, +// TODO: Placeholder for later... make config_tool a library +pub fn generate_user_credentials_only( + _app: &AppHandle, + _run_id: Uuid, work_path: &Path, - repo: &str, - sig_keys: Option<&[SigKey]>, - github_token: Option<&str>, + server_url: &str, + _repo: &str, + _sig_keys: Option<&[SigKey]>, + _github_token: Option<&str>, ) -> Result<()> { - let verified = fetch_verified_config_tool(repo, sig_keys, github_token)?; - - let volume_name = format!("secluso-cred-{}", run_id); - let container_name = format!("secluso-cred-copy-{}", run_id); - let _cleanup = DockerCleanup::new(volume_name.clone(), container_name.clone()); + fs::create_dir_all(work_path).with_context(|| format!("creating work dir {}", work_path.display()))?; - seed_volume_with_inputs( - app, - run_id, - work_path, - &volume_name, - &container_name, - &verified.component_bytes, - &verified.bundle_bytes, - )?; + let normalized_url = normalize_server_url(server_url)?; + let (credentials, credentials_full) = create_user_credentials(normalized_url)?; - // The tool is copied in as bytes, so we explicitly mark it executable in the container before use. - run_config_tool_in_volume( - app, - run_id, - &volume_name, - r#" -set -euo pipefail -chmod 0755 /out/secluso-config-tool -tmp_out=/tmp/secluso-generated -rm -rf "$tmp_out" -/out/secluso-config-tool --generate-camera-secret --dir "$tmp_out" -cp -a "$tmp_out"/. /out/ -"#, - )?; + fs::write(work_path.join("user_credentials"), credentials) + .with_context(|| format!("writing {}", work_path.join("user_credentials").display()))?; + fs::write(work_path.join("credentials_full"), &credentials_full) + .with_context(|| format!("writing {}", work_path.join("credentials_full").display()))?; - copy_volume_outputs_to_work(app, run_id, &container_name, work_path)?; - - // if required outputs are missing, treat the entire run as invalid. - for f in ["camera_secret", "camera_secret_qrcode.png"] { - let p = work_path.join(f); - if !p.exists() { - bail!("Expected credential output missing: {}", p.display()); - } - } + let qr = QrCode::new(credentials_full).context("Failed to generate user credentials QR code")?; + let qr_image = qr.render::>().build(); + qr_image + .save(work_path.join("user_credentials_qrcode.png")) + .with_context(|| format!("saving {}", work_path.join("user_credentials_qrcode.png").display()))?; Ok(()) } -// mirrors camera secret generation and keeps the same trust model, verified binary bytes first, -// isolated execution second, strict output validation last. -pub fn generate_user_credentials_only( - app: &AppHandle, - run_id: Uuid, - work_path: &Path, - server_url: &str, - repo: &str, - sig_keys: Option<&[SigKey]>, - github_token: Option<&str>, -) -> Result<()> { - let verified = fetch_verified_config_tool(repo, sig_keys, github_token)?; - - let volume_name = format!("secluso-cred-{}", run_id); - let container_name = format!("secluso-cred-copy-{}", run_id); - let _cleanup = DockerCleanup::new(volume_name.clone(), container_name.clone()); - - seed_volume_with_inputs( - app, - run_id, - work_path, - &volume_name, - &container_name, - &verified.component_bytes, - &verified.bundle_bytes, - )?; - - // Escape single quotes for shell-safe embedding inside the non-interactive bash command. - let escaped_server_url = server_url.replace('\'', r#"'\''"#); - run_config_tool_in_volume( - app, - run_id, - &volume_name, - &format!( - r#" -set -euo pipefail -chmod 0755 /out/secluso-config-tool -tmp_out=/tmp/secluso-generated -rm -rf "$tmp_out" -/out/secluso-config-tool --generate-user-credentials --server-addr '{}' --dir "$tmp_out" -cp -a "$tmp_out"/. /out/ -"#, - escaped_server_url - ), - )?; - - copy_volume_outputs_to_work(app, run_id, &container_name, work_path)?; +fn normalize_server_url(server_url: &str) -> Result { + let trimmed = server_url.trim(); + if trimmed.is_empty() { + return Err(anyhow!("Server URL is empty.")); + } - // Fail closed on missing output to prevent partially-generated credential state. - let creds = work_path.join("user_credentials"); - if !creds.exists() { - bail!("Expected credential output missing: {}", creds.display()); + let parsed = Url::parse(trimmed).with_context(|| format!("Invalid server URL: {trimmed}"))?; + match parsed.scheme() { + "http" | "https" => {} + other => return Err(anyhow!("Invalid server URL scheme: {other}")), } - Ok(()) + Ok(trimmed.trim_end_matches('/').to_string()) } diff --git a/deploy/src-tauri/src/pi_hub_provision/docker.rs b/deploy/src-tauri/src/pi_hub_provision/docker.rs index f893fbe..506954e 100644 --- a/deploy/src-tauri/src/pi_hub_provision/docker.rs +++ b/deploy/src-tauri/src/pi_hub_provision/docker.rs @@ -102,29 +102,3 @@ pub fn run_with_output(app: &AppHandle, run_id: Uuid, step: &str, cmd: &mut Comm pub fn err_to_string(e: anyhow::Error) -> String { format!("{:#}", e) } - -pub struct DockerCleanup { - volume: String, - container: String, -} - -impl DockerCleanup { - pub fn new(volume: String, container: String) -> Self { - Self { volume, container } - } -} - -impl Drop for DockerCleanup { - fn drop(&mut self) { - let _ = Command::new("docker") - .args(["rm", "-f", &self.container]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status(); - let _ = Command::new("docker") - .args(["volume", "rm", "-f", &self.volume]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status(); - } -} diff --git a/deploy/src-tauri/src/provision_server/mod.rs b/deploy/src-tauri/src/provision_server/mod.rs index 151fb04..c27c9e3 100644 --- a/deploy/src-tauri/src/provision_server/mod.rs +++ b/deploy/src-tauri/src/provision_server/mod.rs @@ -1,54 +1,35 @@ //! SPDX-License-Identifier: GPL-3.0-or-later mod events; +mod preflight; mod provision; mod script; mod ssh; mod types; use crate::provision_server::events::{emit, log_line, step_error, step_ok, step_start, ProvisionEvent}; +use crate::provision_server::preflight::run_preflight; use crate::provision_server::provision::run_provision; use crate::provision_server::ssh::connect_ssh; -use crate::provision_server::types::{ServerPlan, SshTarget}; -use anyhow::{bail, Context, Result}; +use crate::provision_server::types::{ServerPlan, ServerRuntimePlan, SshTarget}; +use anyhow::Result; use serde::Serialize; -use std::io::Read; use tauri::AppHandle; use uuid::Uuid; // tauri commands #[tauri::command] -pub async fn test_server_ssh(app: AppHandle, target: SshTarget) -> Result<(), String> { +pub async fn test_server_ssh(app: AppHandle, target: SshTarget, runtime: Option, server_url: Option) -> Result<(), String> { let run_id = Uuid::new_v4(); - step_start(&app, run_id, "ssh_test", "Testing SSH connection"); + step_start(&app, run_id, "ssh_test", "Connecting via SSH"); let app2 = app.clone(); let res = tokio::task::spawn_blocking(move || -> Result<()> { let (sess, _temps) = connect_ssh(&target)?; - let mut channel = sess.channel_session().context("Failed to open SSH channel")?; - channel.exec("bash -lc 'echo SECLUSO_SSH_OK && uname -a'").context("Remote exec failed")?; - - let mut out = String::new(); - channel.read_to_string(&mut out).ok(); - let mut err = String::new(); - channel.stderr().read_to_string(&mut err).ok(); - - if !out.trim().is_empty() { - for line in out.lines() { - log_line(&app2, run_id, "info", Some("ssh_test"), line.to_string()); - } - } - if !err.trim().is_empty() { - for line in err.lines() { - log_line(&app2, run_id, "warn", Some("ssh_test"), line.to_string()); - } - } - - channel.wait_close().ok(); - let code = channel.exit_status().unwrap_or(255); - if code != 0 { - bail!("SSH test command failed (exit={code})"); - } + step_ok(&app2, run_id, "ssh_test"); + step_start(&app2, run_id, "preflight", "Checking server compatibility"); + run_preflight(&app2, run_id, "preflight", &sess, &target, runtime.as_ref(), server_url.as_deref())?; + step_ok(&app2, run_id, "preflight"); Ok(()) }) .await diff --git a/deploy/src-tauri/src/provision_server/preflight.rs b/deploy/src-tauri/src/provision_server/preflight.rs new file mode 100644 index 0000000..85725ed --- /dev/null +++ b/deploy/src-tauri/src/provision_server/preflight.rs @@ -0,0 +1,1021 @@ +//! SPDX-License-Identifier: GPL-3.0-or-later +use crate::provision_server::events::log_line; +use crate::provision_server::ssh::sudo_prefix; +use crate::provision_server::types::{ServerRuntimePlan, SshAuth, SshTarget}; +use anyhow::{bail, Context, Result}; +use reqwest::blocking::Client; +use ssh2::Session; +use std::io::{Read, Write}; +use std::thread::sleep; +use std::time::Duration; +use tauri::AppHandle; +use url::Url; +use uuid::Uuid; + +pub const DEFAULT_SERVER_HTTP_PORT: u16 = 8000; +const MIN_DISK_KB: u64 = 3 * 1024 * 1024; +const WARN_MEM_KB: u64 = 768 * 1024; +const PUBLIC_PROBE_ATTEMPTS: usize = 2; +const PUBLIC_PROBE_RETRY_DELAY: Duration = Duration::from_secs(1); +const PUBLIC_PROBE_HTTP_TIMEOUT: Duration = Duration::from_secs(3); +const REMOTE_HTTPS_PROBE_MAX_TIME_SECS: u64 = 3; +const REMOTE_PROBE_START_TIMEOUT_SECS: u64 = 8; + +struct TempHttpProbe { + unit_name: String, + root_dir: String, + uses_sudo: bool, +} + +pub struct PreflightReport { + pub remote_has_bin: bool, + pub remote_has_unit: bool, + pub service_active: bool, + pub installed_version: Option, + pub port_in_use: bool, + pub remote_has_credentials_full: bool, + pub remote_arch: String, +} + +struct ExecResult { + stdout: String, + stderr: String, + exit: i32, +} + +pub fn run_preflight( + app: &AppHandle, + run_id: Uuid, + step: &str, + sess: &Session, + target: &SshTarget, + runtime: Option<&ServerRuntimePlan>, + public_server_url: Option<&str>, +) -> Result { + log_line(app, run_id, "info", Some(step), "Starting server preflight checks."); + + let uname = remote_shell(sess, "uname -s", None)?; + let kernel = uname.stdout.trim(); + if kernel != "Linux" { + bail!("Unsupported remote OS: expected Linux, got {kernel}."); + } + log_line(app, run_id, "info", Some(step), format!("Remote OS kernel: {kernel}")); + + let os_release = remote_shell(sess, "if [ -f /etc/os-release ]; then cat /etc/os-release; fi", None)?; + let pretty_name = parse_os_release_field(&os_release.stdout, "PRETTY_NAME"); + let distro_id = parse_os_release_field(&os_release.stdout, "ID"); + let distro_like = parse_os_release_field(&os_release.stdout, "ID_LIKE"); + if let Some(name) = pretty_name { + log_line(app, run_id, "info", Some(step), format!("Remote distribution: {name}")); + } + if !remote_success(sess, "command -v apt-get >/dev/null 2>&1", None)? { + bail!("This server does not provide apt-get. Automatic provisioning currently expects a Debian/Ubuntu-style Linux server."); + } + if distro_id.as_deref() != Some("debian") + && distro_id.as_deref() != Some("ubuntu") + && !distro_like + .as_deref() + .unwrap_or_default() + .split_whitespace() + .any(|v| v == "debian" || v == "ubuntu") + { + log_line( + app, + run_id, + "warn", + Some(step), + "This distro is not clearly Debian/Ubuntu-like. Provisioning may still work because apt-get exists, but it is less certain.".to_string(), + ); + } + + let pid1 = remote_shell(sess, "ps -p 1 -o comm= | tr -d '[:space:]'", None)?; + if pid1.stdout.trim() != "systemd" { + bail!("PID 1 is not systemd. Secluso provisioning expects a systemd-based Linux server."); + } + if !remote_success(sess, "command -v systemctl >/dev/null 2>&1", None)? { + bail!("systemctl is missing. Secluso provisioning expects systemd utilities to be installed."); + } + + verify_sudo_access(app, run_id, step, sess, target)?; + + let arch = remote_shell(sess, "uname -m", None)?; + let arch = arch.stdout.trim(); + let remote_arch = match arch { + "x86_64" => "x86_64".to_string(), + "aarch64" | "arm64" => "aarch64".to_string(), + other => bail!("Unsupported CPU architecture: {other}. Supported server architectures are x86_64 and aarch64."), + }; + log_line( + app, + run_id, + "info", + Some(step), + format!("Remote CPU architecture: {arch} (using bundle target {remote_arch})"), + ); + + let disk = remote_shell(sess, "df -Pk / | awk 'NR==2 {print $4}'", None)?; + let avail_kb = parse_u64_field(disk.stdout.trim(), "available disk space")?; + if avail_kb < MIN_DISK_KB { + bail!( + "Not enough free disk space on /. Need at least about 3 GiB available, found {:.1} MiB.", + avail_kb as f64 / 1024.0 + ); + } + log_line( + app, + run_id, + "info", + Some(step), + format!("Free disk space on /: {:.1} GiB", avail_kb as f64 / 1024.0 / 1024.0), + ); + + let mem = remote_shell(sess, "awk '/MemAvailable:/ {print $2}' /proc/meminfo", None)?; + let mem_kb = parse_u64_field(mem.stdout.trim(), "available memory")?; + if mem_kb < WARN_MEM_KB { + log_line( + app, + run_id, + "warn", + Some(step), + format!( + "Low available memory detected: {:.0} MiB. Secluso may still install, but downloads, updates, and first startup may be slower.", + mem_kb as f64 / 1024.0 + ), + ); + } else { + log_line( + app, + run_id, + "info", + Some(step), + format!("Available memory: {:.0} MiB", mem_kb as f64 / 1024.0), + ); + } + + verify_outbound_network(app, run_id, step, sess)?; + + let remote_has_bin = remote_success(sess, "test -x /opt/secluso/bin/secluso-server", None)?; + let remote_has_unit = remote_success( + sess, + "systemctl list-unit-files --type=service | awk '{print $1}' | grep -qx 'secluso-server.service'", + None, + )?; + let service_active = remote_success(sess, "systemctl is-active --quiet secluso-server.service", None)?; + let remote_has_credentials_full = remote_success(sess, "test -f /var/lib/secluso/credentials_full", None)?; + let version = if remote_has_bin { + let out = remote_shell( + sess, + "if [ -x /opt/secluso/bin/secluso-server ]; then /opt/secluso/bin/secluso-server --version 2>/dev/null | head -n1; fi", + None, + )?; + let trimmed = out.stdout.trim(); + if trimmed.is_empty() { None } else { Some(trimmed.to_string()) } + } else { + None + }; + + if remote_has_bin || remote_has_unit { + log_line( + app, + run_id, + "info", + Some(step), + format!( + "Existing install detected: binary={}, unit={}, active={}, version={}", + remote_has_bin, + remote_has_unit, + service_active, + version.clone().unwrap_or_else(|| "unknown".to_string()) + ), + ); + if !remote_has_credentials_full { + log_line( + app, + run_id, + "warn", + Some(step), + "Existing install is missing /var/lib/secluso/credentials_full. This older layout is no longer upgraded in place; use Overwrite existing install for a clean reinstall.".to_string(), + ); + } + } else { + log_line(app, run_id, "info", Some(step), "No existing Secluso server install detected.".to_string()); + } + + let listen_port = runtime.map(|value| value.listen_port).unwrap_or(DEFAULT_SERVER_HTTP_PORT); + let removed_stale_probe = cleanup_stale_preflight_port_listener(app, run_id, step, sess, target, listen_port)?; + if removed_stale_probe { + log_line( + app, + run_id, + "info", + Some(step), + format!("Removed a stale temporary preflight listener from port {listen_port} before re-checking the port."), + ); + } + let port_probe = remote_with_optional_sudo( + sess, + target, + &format!("ss -ltnpH | awk '$4 ~ /:{}$/ {{print}}'", listen_port), + &format!("ss -ltnH | awk '$4 ~ /:{}$/ {{print}}'", listen_port), + )?; + let port_lines = port_probe + .stdout + .lines() + .map(str::trim) + .filter(|line| !line.is_empty()) + .collect::>(); + let port_in_use = !port_lines.is_empty(); + let mut occupied_by_secluso = + port_probe.stdout.contains("secluso-server") + || port_probe.stdout.contains("/opt/secluso/bin/secluso-server") + || service_active; + if port_in_use { + for line in &port_lines { + log_line(app, run_id, "warn", Some(step), format!("Port {listen_port} listener: {line}")); + } + if !occupied_by_secluso { + if let Some(status_url) = direct_status_url_for_preflight(runtime, public_server_url, listen_port)? { + match verify_existing_secluso_status_endpoint(app, run_id, step, status_url.as_ref()) { + Ok(()) => { + occupied_by_secluso = true; + log_line( + app, + run_id, + "info", + Some(step), + format!("Port {listen_port} is already serving a healthy Secluso endpoint."), + ); + } + Err(err) => { + log_line( + app, + run_id, + "warn", + Some(step), + format!("Port {listen_port} is in use, and the existing listener did not look like a healthy Secluso endpoint: {err:#}"), + ); + } + } + } + } + + if !occupied_by_secluso { + bail!( + "Port {listen_port} is already in use by another service or stale listener. Listener details: {}", + port_lines.join(" | ") + ); + } + log_line( + app, + run_id, + "warn", + Some(step), + format!("Port {listen_port} is already in use by an existing Secluso install or compatible listener."), + ); + } else { + log_line(app, run_id, "info", Some(step), format!("Port {listen_port} is free on the server.")); + } + + check_firewall(app, run_id, step, sess, runtime)?; + verify_public_http_reachability( + app, + run_id, + step, + sess, + target, + runtime, + public_server_url, + port_in_use, + occupied_by_secluso, + )?; + + Ok(PreflightReport { + remote_has_bin, + remote_has_unit, + service_active, + installed_version: version, + port_in_use, + remote_has_credentials_full, + remote_arch, + }) +} + +fn cleanup_stale_preflight_port_listener( + app: &AppHandle, + run_id: Uuid, + step: &str, + sess: &Session, + target: &SshTarget, + listen_port: u16, +) -> Result { + let probe = remote_with_optional_sudo( + sess, + target, + &format!("ss -ltnpH | awk '$4 ~ /:{}$/ {{print}}'", listen_port), + &format!("ss -ltnH | awk '$4 ~ /:{}$/ {{print}}'", listen_port), + )?; + let pids = extract_listener_pids(&probe.stdout); + if pids.is_empty() { + return Ok(false); + } + + let mut removed_any = false; + for pid in pids { + let inspect_cmd = format!( + "cwd=$(readlink -f /proc/{pid}/cwd 2>/dev/null || true)\n\ +cmd=$(tr '\\0' ' ' /dev/null || true)\n\ +printf 'CWD=%s\\nCMD=%s\\n' \"$cwd\" \"$cmd\"" + ); + let inspect = remote_with_optional_sudo(sess, target, &inspect_cmd, &inspect_cmd)?; + if inspect.exit != 0 { + continue; + } + + let cwd = parse_prefixed_output_field(&inspect.stdout, "CWD=").unwrap_or_default(); + let cmd = parse_prefixed_output_field(&inspect.stdout, "CMD=").unwrap_or_default(); + let is_stale_probe = looks_like_stale_preflight_listener(&cwd, &cmd, listen_port); + if !is_stale_probe { + continue; + } + + log_line( + app, + run_id, + "warn", + Some(step), + format!("Stopping stale preflight helper on port {listen_port} (pid {pid})."), + ); + + let cleanup_cmd = format!( + "kill {pid} >/dev/null 2>&1 || true\n\ +if [ -n '{cwd}' ] && [ -d '{cwd}' ]; then rm -rf '{cwd}'; fi", + cwd = shell_escape(&cwd) + ); + let cleanup = remote_with_optional_sudo(sess, target, &cleanup_cmd, &cleanup_cmd)?; + if cleanup.exit != 0 { + log_line( + app, + run_id, + "warn", + Some(step), + format!( + "Failed to fully clean up stale preflight helper pid {pid}. {}", + summarize_remote_failure(&cleanup) + ), + ); + continue; + } + + removed_any = true; + } + + Ok(removed_any) +} + +fn verify_sudo_access(app: &AppHandle, run_id: Uuid, step: &str, sess: &Session, target: &SshTarget) -> Result<()> { + if target.user == "root" { + log_line(app, run_id, "info", Some(step), "SSH user is root; sudo check not needed.".to_string()); + return Ok(()); + } + + let (cmd, stdin, mode_label) = match target.sudo.mode.as_str() { + "password" => { + let pw = target.sudo.password.clone().unwrap_or_default(); + if pw.is_empty() { + bail!("A sudo password is required for this login, but the sudo password field is empty."); + } + ("sudo -S -p '' true", Some(format!("{pw}\n")), "explicit sudo password") + } + "same" => match &target.auth { + SshAuth::Password { password } if !password.is_empty() => { + ("sudo -S -p '' true", Some(format!("{password}\n")), "same-as-login password") + } + _ => ("sudo -n true", None, "passwordless sudo"), + }, + _ => ("sudo -n true", None, "passwordless sudo"), + }; + + let sudo = remote_shell(sess, cmd, stdin.as_deref())?; + if sudo.exit != 0 { + bail!( + "sudo is not working with the current settings (mode: {mode_label}). {}", + summarize_remote_failure(&sudo) + ); + } + log_line(app, run_id, "info", Some(step), format!("Verified sudo access using {mode_label}.")); + Ok(()) +} + +fn verify_public_http_reachability( + app: &AppHandle, + run_id: Uuid, + step: &str, + sess: &Session, + target: &SshTarget, + runtime: Option<&ServerRuntimePlan>, + public_server_url: Option<&str>, + port_in_use: bool, + existing_secluso_listener: bool, +) -> Result<()> { + let exposure_mode = runtime.map(|value| value.exposure_mode.as_str()).unwrap_or("direct"); + let listen_port = runtime.map(|value| value.listen_port).unwrap_or(DEFAULT_SERVER_HTTP_PORT); + let bind_address = runtime.map(|value| value.bind_address.as_str()).unwrap_or("0.0.0.0"); + + if exposure_mode != "direct" { + log_line( + app, + run_id, + "info", + Some(step), + "Skipping public port probe during preflight because reverse proxy mode is selected.".to_string(), + ); + return Ok(()); + } + + let Some(public_server_url) = public_server_url.map(str::trim).filter(|value| !value.is_empty()) else { + log_line( + app, + run_id, + "warn", + Some(step), + "Skipping public port probe during preflight because no public URL was provided.".to_string(), + ); + return Ok(()); + }; + + let base_url = prepare_direct_probe_base_url(public_server_url, listen_port)?; + + if port_in_use { + if existing_secluso_listener { + let status_url = base_url.join("status").context("Preparing preflight /status probe URL")?; + log_line( + app, + run_id, + "info", + Some(step), + format!( + "Port {listen_port} is already serving Secluso. Reusing its public /status endpoint for reachability preflight." + ), + ); + verify_existing_secluso_status_endpoint(app, run_id, step, status_url.as_ref())?; + return Ok(()); + } + + log_line( + app, + run_id, + "warn", + Some(step), + format!( + "Skipping temporary public port probe because port {listen_port} is already in use and the Secluso service is not active." + ), + ); + return Ok(()); + } + + let probe_token = format!("secluso-preflight-{}", Uuid::new_v4()); + log_line( + app, + run_id, + "info", + Some(step), + format!("Starting a temporary public probe on port {listen_port}."), + ); + let probe = match start_temp_http_probe(sess, target, listen_port, bind_address, &probe_token)? { + Some(value) => value, + None => { + log_line( + app, + run_id, + "warn", + Some(step), + "Skipping public port probe because no temporary HTTP probe helper (python3 or busybox) is available on the server.".to_string(), + ); + return Ok(()); + } + }; + + let probe_url = base_url + .join("secluso-preflight-probe.txt") + .context("Preparing temporary HTTP probe URL")?; + + log_line( + app, + run_id, + "info", + Some(step), + format!( + "Checking whether {} is reachable from this computer.", + base_url.as_str().trim_end_matches('/') + ), + ); + + let probe_result = probe_public_demo_endpoint(probe_url.as_ref(), &probe_token, listen_port); + if let Err(err) = stop_temp_http_probe(sess, target, &probe) { + log_line( + app, + run_id, + "warn", + Some(step), + format!("Temporary public probe cleanup warning: {err:#}"), + ); + } + probe_result?; + + log_line( + app, + run_id, + "info", + Some(step), + format!("Public port probe succeeded on TCP port {listen_port}."), + ); + Ok(()) +} + +fn prepare_direct_probe_base_url(public_server_url: &str, listen_port: u16) -> Result { + let parsed = Url::parse(public_server_url) + .with_context(|| format!("Public URL '{public_server_url}' is not a valid URL for direct-mode preflight."))?; + + if parsed.scheme() != "http" { + bail!( + "Direct mode must use an http:// public URL during preflight. Received '{}'.", + public_server_url + ); + } + + if parsed.path() != "/" || parsed.query().is_some() || parsed.fragment().is_some() { + bail!( + "Direct mode public URL must be a plain host or host:port without a path, query, or fragment. Received '{}'.", + public_server_url + ); + } + + let effective_port = parsed.port_or_known_default().unwrap_or(80); + if effective_port != listen_port { + bail!( + "Direct mode public URL port {} does not match the configured Secluso listen port {}.", + effective_port, + listen_port + ); + } + + Ok(parsed) +} + +fn verify_existing_secluso_status_endpoint(app: &AppHandle, run_id: Uuid, step: &str, status_url: &str) -> Result<()> { + let client = Client::builder() + .timeout(PUBLIC_PROBE_HTTP_TIMEOUT) + .build() + .context("Creating HTTP client for preflight reachability check")?; + + let response = client + .get(status_url) + .send() + .with_context(|| format!("Existing Secluso /status endpoint is not reachable from this computer at {status_url}"))?; + + let server_version = response + .headers() + .get("X-Server-Version") + .and_then(|value| value.to_str().ok()) + .map(str::to_string); + + let Some(server_version) = server_version else { + bail!( + "Reached {}, but it did not return X-Server-Version. This does not look like a healthy Secluso /status response.", + status_url + ); + }; + + log_line( + app, + run_id, + "info", + Some(step), + format!("Existing public Secluso /status endpoint is reachable (X-Server-Version: {server_version})."), + ); + Ok(()) +} + +fn start_temp_http_probe( + sess: &Session, + target: &SshTarget, + listen_port: u16, + bind_address: &str, + probe_token: &str, +) -> Result> { + let uses_sudo = target.user != "root"; + let unit_name = format!("secluso-preflight-http-{}", Uuid::new_v4().simple()); + let inner_start_cmd = format!( + "set -eu\n\ +probe_root=\"$(mktemp -d /tmp/secluso-preflight-http.XXXXXX)\"\n\ +printf '%s\\n' '{probe_token}' > \"$probe_root/secluso-preflight-probe.txt\"\n\ +if ! command -v systemd-run >/dev/null 2>&1; then\n\ + echo 'SECLUSO_HTTP_PROBE_SYSTEMD_RUN_MISSING' >&2\n\ + exit 126\n\ +fi\n\ +if command -v python3 >/dev/null 2>&1; then\n\ + helper_cmd=\"cd '$probe_root' && exec python3 -m http.server {listen_port} --bind '{bind_address}' >> '$probe_root/server.log' 2>&1\"\n\ +elif command -v busybox >/dev/null 2>&1; then\n\ + helper_cmd=\"exec busybox httpd -f -p '{bind_address}:{listen_port}' -h '$probe_root' >> '$probe_root/server.log' 2>&1\"\n\ +else\n\ + echo 'SECLUSO_HTTP_PROBE_HELPER_MISSING' >&2\n\ + exit 127\n\ +fi\n\ +systemd-run --quiet --unit '{unit_name}' bash -lc \"$helper_cmd\"\n\ +sleep 1\n\ +if ! systemctl is-active --quiet '{unit_name}'; then\n\ + systemctl status '{unit_name}' --no-pager >/dev/null 2>&1 || true\n\ + exit 125\n\ +fi\n\ +printf 'UNIT=%s\\nROOT=%s\\n' '{unit_name}' \"$probe_root\"" + ); + let start_cmd = format!( + "if command -v timeout >/dev/null 2>&1; then timeout {REMOTE_PROBE_START_TIMEOUT_SECS}s bash -lc '{}'; else bash -lc '{}'; fi", + shell_escape(&inner_start_cmd), + shell_escape(&inner_start_cmd) + ); + let result = remote_shell_for_probe(sess, target, &start_cmd, uses_sudo)?; + + if result.exit == 127 && result.stderr.contains("SECLUSO_HTTP_PROBE_HELPER_MISSING") { + return Ok(None); + } + if result.exit == 126 && result.stderr.contains("SECLUSO_HTTP_PROBE_SYSTEMD_RUN_MISSING") { + return Ok(None); + } + if result.exit == 124 { + bail!( + "Timed out while starting the temporary HTTP probe on port {listen_port}. The remote helper did not detach cleanly within {} seconds.", + REMOTE_PROBE_START_TIMEOUT_SECS + ); + } + if result.exit != 0 { + bail!( + "Failed to start a temporary HTTP probe on port {listen_port}. {}", + summarize_remote_failure(&result) + ); + } + + let unit_name = result + .stdout + .lines() + .find_map(|line| line.strip_prefix("UNIT=")) + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(str::to_string) + .context("Temporary HTTP probe did not report its systemd unit.")?; + let root_dir = result + .stdout + .lines() + .find_map(|line| line.strip_prefix("ROOT=")) + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(str::to_string) + .context("Temporary HTTP probe did not report its temp directory.")?; + + Ok(Some(TempHttpProbe { unit_name, root_dir, uses_sudo })) +} + +fn stop_temp_http_probe(sess: &Session, target: &SshTarget, probe: &TempHttpProbe) -> Result<()> { + let stop_cmd = format!( + "systemctl stop '{}' >/dev/null 2>&1 || true\n\ +systemctl reset-failed '{}' >/dev/null 2>&1 || true\n\ +rm -rf '{}'", + shell_escape(&probe.unit_name), + shell_escape(&probe.unit_name), + shell_escape(&probe.root_dir) + ); + let result = remote_shell_for_probe(sess, target, &stop_cmd, probe.uses_sudo)?; + if result.exit != 0 { + bail!("Temporary HTTP probe cleanup failed. {}", summarize_remote_failure(&result)); + } + Ok(()) +} + +fn probe_public_demo_endpoint(probe_url: &str, probe_token: &str, listen_port: u16) -> Result<()> { + let client = Client::builder() + .timeout(PUBLIC_PROBE_HTTP_TIMEOUT) + .build() + .context("Creating HTTP client for public port probe")?; + let probe_display_url = Url::parse(probe_url) + .ok() + .map(|url| { + let mut trimmed = url.clone(); + trimmed.set_path(""); + trimmed.set_query(None); + trimmed.set_fragment(None); + trimmed.to_string().trim_end_matches('/').to_string() + }) + .unwrap_or_else(|| probe_url.to_string()); + let probe_timeout_budget_secs = + (PUBLIC_PROBE_ATTEMPTS as u64 * PUBLIC_PROBE_HTTP_TIMEOUT.as_secs()) + + ((PUBLIC_PROBE_ATTEMPTS.saturating_sub(1)) as u64 * PUBLIC_PROBE_RETRY_DELAY.as_secs()); + + let mut last_error = None; + for attempt in 1..=PUBLIC_PROBE_ATTEMPTS { + match client.get(probe_url).send() { + Ok(response) => { + let status = response.status(); + let body = response + .text() + .with_context(|| format!("Reading HTTP probe response body from {probe_url}"))?; + if status.is_success() && body.contains(probe_token) { + return Ok(()); + } + + last_error = Some(anyhow::anyhow!( + "Reached {}, but the response did not match the expected temporary probe (HTTP {}).", + probe_url, + status + )); + } + Err(err) => { + last_error = Some(anyhow::anyhow!( + "Could not reach the temporary public probe at {} from this computer within about {} seconds: {}. Check that TCP port {} is open in the server firewall, cloud security group, and any home-router port forwarding rules.", + probe_display_url, + probe_timeout_budget_secs, + err, + listen_port + )); + } + } + + if attempt < PUBLIC_PROBE_ATTEMPTS { + sleep(PUBLIC_PROBE_RETRY_DELAY); + } + } + + Err(last_error.context("Public HTTP probe failed without an error.")?) +} + +fn direct_status_url_for_preflight( + runtime: Option<&ServerRuntimePlan>, + public_server_url: Option<&str>, + listen_port: u16, +) -> Result> { + let exposure_mode = runtime.map(|value| value.exposure_mode.as_str()).unwrap_or("direct"); + if exposure_mode != "direct" { + return Ok(None); + } + + let Some(public_server_url) = public_server_url.map(str::trim).filter(|value| !value.is_empty()) else { + return Ok(None); + }; + + let base_url = prepare_direct_probe_base_url(public_server_url, listen_port)?; + Ok(Some(base_url.join("status").context("Preparing preflight /status probe URL")?)) +} + +fn verify_outbound_network(app: &AppHandle, run_id: Uuid, step: &str, sess: &Session) -> Result<()> { + let dns_checks = [ + ( + "api.github.com", + "Future auto-updates may fail until the server can resolve api.github.com.", + ), + ( + "oauth2.googleapis.com", + "Firebase push setup may fail until the server can resolve oauth2.googleapis.com.", + ), + ( + "fcm.googleapis.com", + "Push notifications may fail until the server can resolve fcm.googleapis.com.", + ), + ]; + for (host, warning) in dns_checks { + let probe = format!("getent ahostsv4 {host} >/dev/null 2>&1 || getent hosts {host} >/dev/null 2>&1"); + if remote_success(sess, &probe, None)? { + log_line(app, run_id, "info", Some(step), format!("DNS lookup for {host} succeeded.")); + } else { + log_line(app, run_id, "warn", Some(step), format!("DNS lookup for {host} failed. {warning}")); + } + } + + let https_checks = [ + ( + "https://api.github.com", + "Future auto-updates may fail until the server can reach GitHub over HTTPS.", + ), + ( + "https://oauth2.googleapis.com", + "Firebase push setup may fail until the server can reach Google OAuth over HTTPS.", + ), + ]; + for (url, warning) in https_checks { + let probe = format!( + "if command -v curl >/dev/null 2>&1; then curl -fsSI --connect-timeout {timeout} --max-time {timeout} {url} >/dev/null; else exit 42; fi", + timeout = REMOTE_HTTPS_PROBE_MAX_TIME_SECS + ); + let result = remote_shell(sess, &probe, None)?; + match result.exit { + 0 => log_line(app, run_id, "info", Some(step), format!("Outbound HTTPS to {url} succeeded.")), + 42 => log_line( + app, + run_id, + "warn", + Some(step), + format!("curl is not installed yet, so the remote HTTPS probe for {url} was skipped."), + ), + _ => log_line(app, run_id, "warn", Some(step), format!("Outbound HTTPS probe for {url} failed. {warning}")), + } + } + + Ok(()) +} + +fn check_firewall(app: &AppHandle, run_id: Uuid, step: &str, sess: &Session, runtime: Option<&ServerRuntimePlan>) -> Result<()> { + let exposure_mode = runtime.map(|value| value.exposure_mode.as_str()).unwrap_or("direct"); + let listen_port = runtime.map(|value| value.listen_port).unwrap_or(DEFAULT_SERVER_HTTP_PORT); + if exposure_mode == "proxy" { + if remote_success(sess, "command -v nginx >/dev/null 2>&1 || command -v caddy >/dev/null 2>&1 || command -v apache2 >/dev/null 2>&1", None)? { + log_line( + app, + run_id, + "info", + Some(step), + format!("Reverse proxy mode selected. Make sure your existing proxy forwards to 127.0.0.1:{listen_port}."), + ); + } else { + log_line( + app, + run_id, + "warn", + Some(step), + format!("Reverse proxy mode selected, but no common proxy binary was detected. Make sure something forwards traffic to 127.0.0.1:{listen_port}."), + ); + } + return Ok(()); + } + + if !remote_success(sess, "command -v ufw >/dev/null 2>&1", None)? { + log_line( + app, + run_id, + "warn", + Some(step), + format!("ufw is not installed. If your provider uses a cloud firewall or security group, make sure TCP port {listen_port} is allowed."), + ); + return Ok(()); + } + + let ufw = remote_shell(sess, "ufw status", None)?; + let status = ufw.stdout.to_lowercase(); + if status.contains("inactive") { + log_line( + app, + run_id, + "warn", + Some(step), + format!( + "ufw is inactive. That is fine locally, but you may still need to open TCP port {listen_port} in your provider firewall or security group." + ), + ); + } else if status.contains(&listen_port.to_string()) && status.contains("allow") { + log_line(app, run_id, "info", Some(step), format!("ufw appears to allow TCP port {listen_port}.")); + } else { + log_line( + app, + run_id, + "warn", + Some(step), + format!("ufw is active and does not obviously allow TCP port {listen_port}. Remote app access may fail until you open that port."), + ); + } + Ok(()) +} + +fn parse_os_release_field(contents: &str, key: &str) -> Option { + contents + .lines() + .find_map(|line| line.strip_prefix(&format!("{key}="))) + .map(|value| value.trim_matches('"').to_string()) +} + +fn parse_u64_field(raw: &str, label: &str) -> Result { + raw.parse::() + .with_context(|| format!("Failed to parse {label} from '{raw}'")) +} + +fn parse_prefixed_output_field(contents: &str, prefix: &str) -> Option { + contents + .lines() + .find_map(|line| line.strip_prefix(prefix)) + .map(str::trim) + .map(str::to_string) +} + +fn looks_like_stale_preflight_listener(cwd: &str, cmd: &str, listen_port: u16) -> bool { + if cwd.starts_with("/tmp/secluso-preflight-http.") || cmd.contains("/tmp/secluso-preflight-http.") { + return true; + } + + let python_pattern = format!("-m http.server {listen_port}"); + if cmd.contains("python3") && cmd.contains(&python_pattern) { + return true; + } + + let busybox_pattern = format!(":{listen_port}"); + if cmd.contains("busybox httpd") && cmd.contains(&busybox_pattern) { + return true; + } + + false +} + +fn extract_listener_pids(contents: &str) -> Vec { + let mut out = Vec::new(); + for line in contents.lines() { + let mut rest = line; + while let Some(idx) = rest.find("pid=") { + let after = &rest[idx + 4..]; + let pid = after + .chars() + .take_while(|c| c.is_ascii_digit()) + .collect::(); + if !pid.is_empty() && !out.iter().any(|existing| existing == &pid) { + out.push(pid.clone()); + } + rest = &after[pid.len()..]; + } + } + out +} + +fn shell_escape(cmd: &str) -> String { + cmd.replace('\'', r"'\''") +} + +fn remote_success(sess: &Session, cmd: &str, stdin: Option<&str>) -> Result { + Ok(remote_shell(sess, cmd, stdin)?.exit == 0) +} + +fn remote_with_optional_sudo(sess: &Session, target: &SshTarget, sudo_cmd: &str, fallback_cmd: &str) -> Result { + if target.user == "root" { + return remote_shell(sess, sudo_cmd, None); + } + + match target.sudo.mode.as_str() { + "password" => { + let pw = target.sudo.password.clone().unwrap_or_default(); + if pw.is_empty() { + return remote_shell(sess, fallback_cmd, None); + } + remote_shell(sess, sudo_cmd, Some(&format!("{pw}\n"))) + } + "same" => match &target.auth { + SshAuth::Password { password } if !password.is_empty() => { + remote_shell(sess, sudo_cmd, Some(&format!("{password}\n"))) + } + _ => { + let res = remote_shell(sess, &format!("sudo -n {sudo_cmd}"), None)?; + if res.exit == 0 { Ok(res) } else { remote_shell(sess, fallback_cmd, None) } + } + }, + _ => remote_shell(sess, fallback_cmd, None), + } +} + +fn remote_shell_for_probe(sess: &Session, target: &SshTarget, cmd: &str, uses_sudo: bool) -> Result { + if !uses_sudo { + return remote_shell(sess, cmd, None); + } + + let (sudo_cmd, sudo_pw) = sudo_prefix(target); + if sudo_cmd.is_empty() { + return remote_shell(sess, cmd, None); + } + + let wrapped = format!("{sudo_cmd} bash -lc '{}'", shell_escape(cmd)); + let stdin = sudo_pw.map(|value| format!("{value}\n")); + remote_shell(sess, &wrapped, stdin.as_deref()) +} + +fn remote_shell(sess: &Session, cmd: &str, stdin: Option<&str>) -> Result { + let full = format!("bash -lc '{}'", shell_escape(cmd)); + let mut channel = sess.channel_session().context("Failed to open SSH channel")?; + channel.exec(&full).with_context(|| format!("Remote exec failed: {cmd}"))?; + if let Some(stdin) = stdin { + channel.write_all(stdin.as_bytes()).ok(); + channel.flush().ok(); + } + channel.send_eof().ok(); + + let mut stdout = String::new(); + let mut stderr = String::new(); + channel.read_to_string(&mut stdout).ok(); + channel.stderr().read_to_string(&mut stderr).ok(); + channel.wait_close().ok(); + let exit = channel.exit_status().unwrap_or(255); + + Ok(ExecResult { stdout, stderr, exit }) +} + +fn summarize_remote_failure(result: &ExecResult) -> String { + let stderr = result.stderr.trim(); + if !stderr.is_empty() { + return stderr.to_string(); + } + let stdout = result.stdout.trim(); + if !stdout.is_empty() { + return stdout.to_string(); + } + format!("command exited with status {}", result.exit) +} diff --git a/deploy/src-tauri/src/provision_server/provision.rs b/deploy/src-tauri/src/provision_server/provision.rs index 68207fb..3e75579 100644 --- a/deploy/src-tauri/src/provision_server/provision.rs +++ b/deploy/src-tauri/src/provision_server/provision.rs @@ -2,62 +2,177 @@ use crate::pi_hub_provision::credentials::generate_user_credentials_only; use crate::pi_hub_provision::temp::shared_temp_dir; use crate::provision_server::events::{log_line, step_ok, step_start}; +use crate::provision_server::preflight::run_preflight; use crate::provision_server::script::remote_provision_script; use crate::provision_server::ssh::{connect_ssh, exec_remote_script_streaming, scp_upload_bytes, sudo_prefix}; -use crate::provision_server::types::{ServerPlan, SshTarget}; +use crate::provision_server::types::{ServerPlan, ServerSecrets, SshTarget}; use anyhow::{bail, Context, Result}; -use ssh2::Session; -use std::io::Read; +use reqwest::blocking::Client; +use secluso_update::{ + build_github_client, default_signers, download_and_verify_component, fetch_latest_release, Component as ReleaseComponent, + Signer, +}; +use secluso_client_server_lib::auth::parse_user_credentials; +use std::fs; use std::path::PathBuf; +use std::thread::sleep; +use std::time::Duration; use tauri::AppHandle; use uuid::Uuid; -pub fn run_provision(app: &AppHandle, run_id: Uuid, target: SshTarget, plan: ServerPlan) -> Result<()> { - fn normalize_repo(input: &str) -> String { - let trimmed = input.trim().trim_end_matches('/'); - if let Some(idx) = trimmed.find("github.com/") { - let repo = &trimmed[idx + "github.com/".len()..]; - return repo.trim_end_matches(".git").to_string(); - } - trimmed.trim_end_matches(".git").to_string() +const INSTALL_PREFIX: &str = "/opt/secluso"; +const SERVER_UNIT: &str = "secluso-server.service"; +const UPDATER_SERVICE: &str = "secluso-updater.service"; +const UPDATE_INTERVAL_SECS: &str = "1800"; + +struct DownloadedArtifacts { + release_tag: String, + server_manifest_version: String, + server_bytes: Vec, + updater_bytes: Vec, +} + +fn normalize_repo(input: &str) -> String { + let trimmed = input.trim().trim_end_matches('/'); + if let Some(idx) = trimmed.find("github.com/") { + let repo = &trimmed[idx + "github.com/".len()..]; + return repo.trim_end_matches(".git").to_string(); } + trimmed.trim_end_matches(".git").to_string() +} + +fn resolve_signers(sig_keys: &[crate::provision_server::types::SigKey]) -> Vec { + if sig_keys.is_empty() { + return default_signers(); + } + + sig_keys + .iter() + .map(|key| Signer { + label: key.name.trim().to_string(), + github_user: key.github_user.trim().to_string(), + }) + .collect() +} + +fn download_verified_artifacts( + app: &AppHandle, + run_id: Uuid, + owner_repo: &str, + remote_arch: &str, + sig_keys: &[crate::provision_server::types::SigKey], + github_token: Option<&str>, +) -> Result { + let signers = resolve_signers(sig_keys); + let client = build_github_client(20, github_token, "secluso-deploy")?; + let release = fetch_latest_release(&client, owner_repo) + .with_context(|| format!("Fetching latest release metadata for {owner_repo}"))?; + log_line( + app, + run_id, + "info", + Some("artifacts"), + format!("Latest immutable release for {owner_repo}: {}", release.tag_name), + ); - // constants aligned with the bash script - let install_prefix = "/opt/secluso"; - let server_unit = "secluso-server.service"; - let updater_service = "secluso-updater.service"; - let update_interval_secs = "1800"; + let server_verified = download_and_verify_component( + &client, + &release, + ReleaseComponent::Server, + remote_arch, + None, + &signers, + ) + .with_context(|| format!("Downloading and verifying secluso-server for {remote_arch}"))?; + + let bundle_dir = shared_temp_dir("secluso-server-bundle").context("creating temp bundle dir")?; + let bundle_path = bundle_dir.path().join("release.zip"); + fs::write(&bundle_path, &server_verified.bundle_bytes) + .with_context(|| format!("writing temporary bundle {}", bundle_path.display()))?; + + let updater_verified = download_and_verify_component( + &client, + &release, + ReleaseComponent::Updater, + remote_arch, + Some(bundle_path.to_str().context("bundle path is not valid UTF-8")?), + &signers, + ) + .with_context(|| format!("Downloading and verifying secluso-update for {remote_arch}"))?; + + log_line( + app, + run_id, + "info", + Some("artifacts"), + format!( + "Verified release bundle {} for {} (server={} bytes, updater={} bytes).", + release.tag_name, + remote_arch, + server_verified.component_bytes.len(), + updater_verified.component_bytes.len() + ), + ); + + Ok(DownloadedArtifacts { + release_tag: server_verified.release_tag, + server_manifest_version: server_verified.manifest_version, + server_bytes: server_verified.component_bytes, + updater_bytes: updater_verified.component_bytes, + }) +} + +pub fn run_provision(app: &AppHandle, run_id: Uuid, target: SshTarget, plan: ServerPlan) -> Result<()> { let owner_repo = plan .binaries_repo .as_ref() .map(|repo| normalize_repo(repo)) .unwrap_or_else(|| "secluso/secluso".to_string()); - if plan.use_docker { - // this code uses the bundle zip flow today because it matches the script - // keep the ui moving by warning and continuing - log_line(app, run_id, "warn", Some("plan"), "useDocker=true is currently ignored; proceeding with bundle-zip installer.".to_string()); - } - if plan.protect_packages { - log_line(app, run_id, "warn", Some("plan"), "protectPackages requested; not implemented in this backend yet (noop).".to_string()); - } - step_start(app, run_id, "ssh_connect", "Connecting via SSH"); let (sess, _temps) = connect_ssh(&target)?; step_ok(app, run_id, "ssh_connect"); let (sudo_cmd, sudo_pw) = sudo_prefix(&target); - // detect remote state - step_start(app, run_id, "detect", "Detecting remote install state"); - let remote_has_bin = remote_test(&sess, &sudo_cmd, &format!("test -x {install_prefix}/bin/secluso-server"))?; - let remote_has_unit = remote_test( + step_start(app, run_id, "preflight", "Checking server compatibility"); + let preflight = run_preflight( + app, + run_id, + "preflight", &sess, - &sudo_cmd, - &format!("systemctl list-unit-files --type=service | awk '{{print $1}}' | grep -qx '{server_unit}'"), + &target, + Some(&plan.runtime), + plan.secrets.as_ref().map(|value| value.server_url.as_str()), )?; + step_ok(app, run_id, "preflight"); + + cleanup_preflight_helpers(app, run_id, &sess, &sudo_cmd, sudo_pw.as_deref())?; + + // detect remote state + step_start(app, run_id, "detect", "Detecting remote install state"); + let remote_has_bin = preflight.remote_has_bin; + let remote_has_unit = preflight.remote_has_unit; log_line(app, run_id, "info", Some("detect"), format!("REMOTE_HAS_BIN={remote_has_bin}")); log_line(app, run_id, "info", Some("detect"), format!("REMOTE_HAS_UNIT={remote_has_unit}")); + log_line(app, run_id, "info", Some("detect"), format!("REMOTE_SERVICE_ACTIVE={}", preflight.service_active)); + log_line( + app, + run_id, + "info", + Some("detect"), + format!( + "REMOTE_SERVER_VERSION={}", + preflight.installed_version.clone().unwrap_or_else(|| "unknown".to_string()) + ), + ); + log_line( + app, + run_id, + "info", + Some("detect"), + format!("REMOTE_PORT_{}_IN_USE={}", plan.runtime.listen_port, preflight.port_in_use), + ); step_ok(app, run_id, "detect"); let overwrite = plan.overwrite.unwrap_or(false); @@ -65,20 +180,35 @@ pub fn run_provision(app: &AppHandle, run_id: Uuid, target: SshTarget, plan: Ser // decide if this is a first install let first_install = overwrite || !(remote_has_bin && remote_has_unit); - - // step 2 generate and upload secrets on first install - step_start(app, run_id, "secrets", "Generating and uploading secrets"); - let mut network_type = "https".to_string(); - if let Some(secrets) = plan.secrets.as_ref() { - let url = secrets.server_url.trim(); - if url.starts_with("http://") { - network_type = "http".to_string(); - } + if !first_install && !preflight.remote_has_credentials_full { + bail!( + "Existing install is missing /var/lib/secluso/credentials_full. That older server layout is no longer updated in place. Turn on Overwrite existing install to replace it cleanly." + ); } - if first_install { - let secrets = plan.secrets.as_ref().context("Missing secrets config")?; + let mut generated_user_credentials: Option> = None; + + step_start(app, run_id, "artifacts", "Downloading verified release binaries"); + let artifacts = download_verified_artifacts( + app, + run_id, + &owner_repo, + &preflight.remote_arch, + &sig_keys, + plan.github_token.as_deref(), + )?; + scp_upload_bytes(&sess, "/tmp/secluso-server", 0o755, &artifacts.server_bytes)?; + scp_upload_bytes(&sess, "/tmp/secluso-update", 0o755, &artifacts.updater_bytes)?; + step_ok(app, run_id, "artifacts"); + + // step 2 generate and upload secrets + step_start(app, run_id, "secrets", "Preparing runtime secrets"); + let secrets = plan.secrets.as_ref().context("Missing secrets config")?; + let sa_path = PathBuf::from(&secrets.service_account_key_path); + let sa = std::fs::read(&sa_path).with_context(|| format!("Missing service account key at {}", sa_path.display()))?; + scp_upload_bytes(&sess, "/tmp/service_account_key.json", 0o600, &sa)?; + if first_install { let work_dir = shared_temp_dir("secluso-server-creds").context("creating temp work dir")?; let work_path = work_dir.path(); let sig_keys = plan.sig_keys.as_ref().map(|keys| { @@ -100,11 +230,11 @@ pub fn run_provision(app: &AppHandle, run_id: Uuid, target: SshTarget, plan: Ser plan.github_token.as_deref(), )?; - let sa_path = PathBuf::from(&secrets.service_account_key_path); - let sa = std::fs::read(&sa_path).with_context(|| format!("Missing service account key at {}", sa_path.display()))?; - let uc_path = work_path.join("user_credentials"); let uc = std::fs::read(&uc_path).with_context(|| format!("Missing user credentials at {}", uc_path.display()))?; + let credentials_full_path = work_path.join("credentials_full"); + let credentials_full = std::fs::read(&credentials_full_path) + .with_context(|| format!("Missing credentials_full at {}", credentials_full_path.display()))?; let qr_src = work_path.join("user_credentials_qrcode.png"); if !qr_src.exists() { @@ -118,27 +248,35 @@ pub fn run_provision(app: &AppHandle, run_id: Uuid, target: SshTarget, plan: Ser } std::fs::copy(&qr_src, &qr_path).with_context(|| format!("Saving QR code to {}", qr_path.display()))?; - scp_upload_bytes(&sess, "/tmp/service_account_key.json", 0o600, &sa)?; scp_upload_bytes(&sess, "/tmp/user_credentials", 0o600, &uc)?; - step_ok(app, run_id, "secrets"); + scp_upload_bytes(&sess, "/tmp/credentials_full", 0o600, &credentials_full)?; + generated_user_credentials = Some(uc); } else { - log_line(app, run_id, "info", Some("secrets"), "Skipping secrets upload for non-first install.".to_string()); - step_ok(app, run_id, "secrets"); + log_line( + app, + run_id, + "info", + Some("secrets"), + "Existing install detected. Leaving the current server credentials unchanged.".to_string(), + ); } + step_ok(app, run_id, "secrets"); // step 3 run the remote provision script step_start(app, run_id, "remote", "Running remote installer"); let mut envs = vec![ - ("INSTALL_PREFIX", install_prefix.to_string()), + ("INSTALL_PREFIX", INSTALL_PREFIX.to_string()), ("OWNER_REPO", owner_repo.to_string()), - ("SERVER_UNIT", server_unit.to_string()), - ("UPDATER_SERVICE", updater_service.to_string()), - ("UPDATE_INTERVAL_SECS", update_interval_secs.to_string()), - ("NETWORK_TYPE", network_type), + ("SERVER_UNIT", SERVER_UNIT.to_string()), + ("UPDATER_SERVICE", UPDATER_SERVICE.to_string()), + ("UPDATE_INTERVAL_SECS", UPDATE_INTERVAL_SECS.to_string()), + ("BIND_ADDRESS", plan.runtime.bind_address.clone()), + ("LISTEN_PORT", plan.runtime.listen_port.to_string()), ("SUDO_CMD", sudo_cmd.clone()), ("ENABLE_UPDATER", if plan.auto_updater.enable { "1".to_string() } else { "0".to_string() }), ("OVERWRITE", if overwrite { "1".to_string() } else { "0".to_string() }), ("FIRST_INSTALL", if first_install { "1".to_string() } else { "0".to_string() }), + ("RELEASE_TAG", artifacts.release_tag.clone()), ( "SIG_KEYS", sig_keys @@ -152,7 +290,6 @@ pub fn run_provision(app: &AppHandle, run_id: Uuid, target: SshTarget, plan: Ser if let Some(token) = plan.github_token.as_ref().map(|v| v.trim().to_string()).filter(|v| !v.is_empty()) { envs.push(("GITHUB_TOKEN", token)); } - exec_remote_script_streaming( app, run_id, @@ -164,27 +301,230 @@ pub fn run_provision(app: &AppHandle, run_id: Uuid, target: SshTarget, plan: Ser )?; step_ok(app, run_id, "remote"); + + step_start(app, run_id, "health", "Checking public server health"); + if first_install { + if let Some(uc) = generated_user_credentials.as_ref() { + let probe_version = plan + .manifest_version_override + .as_deref() + .map(str::trim) + .filter(|value| !value.is_empty()) + .unwrap_or(&artifacts.server_manifest_version); + verify_public_server_health(app, run_id, &plan, secrets, probe_version, uc)?; + } else { + log_line(app, run_id, "warn", Some("health"), "Skipping public health check because generated credentials are unavailable.".to_string()); + } + } else { + log_line( + app, + run_id, + "info", + Some("health"), + "Skipping public health check for update-only runs because no new credentials were generated.".to_string(), + ); + } + step_ok(app, run_id, "health"); + Ok(()) +} + +fn cleanup_preflight_helpers( + app: &AppHandle, + run_id: Uuid, + sess: &ssh2::Session, + sudo_cmd: &str, + sudo_pw: Option<&str>, +) -> Result<()> { + let systemctl_prefix = if sudo_cmd.is_empty() { + "systemctl".to_string() + } else { + format!("{sudo_cmd} systemctl") + }; + let shell_prefix = if sudo_cmd.is_empty() { + "".to_string() + } else { + format!("{sudo_cmd} ") + }; + + let script = format!( + "set +e\n\ +if command -v systemctl >/dev/null 2>&1; then\n\ + units=\"$({systemctl_prefix} list-units --all --plain --no-legend 'secluso-preflight-http-*' 2>/dev/null | awk '{{print $1}}')\"\n\ + if [ -n \"$units\" ]; then\n\ + while IFS= read -r unit; do\n\ + [ -z \"$unit\" ] && continue\n\ + {systemctl_prefix} stop \"$unit\" >/dev/null 2>&1 || true\n\ + {systemctl_prefix} reset-failed \"$unit\" >/dev/null 2>&1 || true\n\ + done <<'EOF'\n\ +$units\n\ +EOF\n\ + fi\n\ +fi\n\ +{shell_prefix}rm -rf /tmp/secluso-preflight-http.* >/dev/null 2>&1 || true\n\ +exit 0\n" + ); + + exec_remote_script_streaming( + app, + run_id, + "preflight", + sess, + &[], + sudo_pw.map(str::to_string), + &script, + )?; + Ok(()) +} + +fn verify_public_server_health( + app: &AppHandle, + run_id: Uuid, + plan: &ServerPlan, + secrets: &ServerSecrets, + probe_version: &str, + user_credentials: &[u8], +) -> Result<()> { + let (username, password) = parse_user_credentials(user_credentials.to_vec()).context("Parsing generated user credentials")?; + let status_url = format!("{}/status", secrets.server_url.trim_end_matches('/')); + let client = Client::builder() + .timeout(Duration::from_secs(15)) + .build() + .context("Creating HTTP client for post-install health check")?; + + log_line( + app, + run_id, + "info", + Some("health"), + "Checking the public /status endpoint from this computer.".to_string(), + ); + + let mut discovered_version = None; + probe_public_server_health( + &client, + &status_url, + &plan.runtime.exposure_mode, + plan.runtime.listen_port, + probe_version, + &username, + &password, + 8, + Duration::from_secs(2), + |attempt| { + log_line( + app, + run_id, + "warn", + Some("health"), + format!("Public /status probe not ready yet (attempt {attempt}/8). Retrying..."), + ); + }, + |server_version| { + discovered_version = Some(server_version.to_string()); + }, + )?; + + if let Some(server_version) = discovered_version { + log_line(app, run_id, "info", Some("health"), format!("Remote server version header: {server_version}")); + } + + log_line( + app, + run_id, + "info", + Some("health"), + "Authenticated public health check succeeded.".to_string(), + ); Ok(()) } -fn remote_test(sess: &Session, sudo_cmd: &str, cmd: &str) -> Result { - // returns true if the command exits 0 - let full = if sudo_cmd.is_empty() { - format!("bash -lc '{}'", cmd.replace('\'', r"'\''")) +fn unreachable_public_status_error(exposure_mode: &str, listen_port: u16, err: &reqwest::Error) -> anyhow::Error { + if exposure_mode == "proxy" { + anyhow::anyhow!( + "Secluso finished installing, but the public /status endpoint is not reachable from this computer yet: {}. Check your reverse proxy route, TLS setup, and whether it forwards to 127.0.0.1:{}.", + err, + listen_port + ) } else { - // sudo wrapper may include -s so do not use it here since tests should not prompt - let sudo_plain = if sudo_cmd.contains("-S") { "sudo".to_string() } else { sudo_cmd.to_string() }; - format!("bash -lc '{} {}'", sudo_plain, cmd.replace('\'', r"'\''")) + anyhow::anyhow!( + "Secluso finished installing, but the public /status endpoint is not reachable from this computer yet: {}. Check that port {} is open in the server firewall and your provider security group.", + err, + listen_port + ) + } +} + +fn probe_public_server_health( + client: &Client, + status_url: &str, + exposure_mode: &str, + listen_port: u16, + probe_version: &str, + username: &str, + password: &str, + max_attempts: usize, + retry_delay: Duration, + mut on_retry: F, + mut on_version: G, +) -> Result<()> +where + F: FnMut(usize), + G: FnMut(&str), +{ + let mut discover = None; + let mut last_discover_err = None; + for attempt in 1..=max_attempts { + match client + .get(status_url) + .header("Client-Version", probe_version) + .send() + { + Ok(response) => { + discover = Some(response); + break; + } + Err(err) => { + last_discover_err = Some(err); + if attempt < max_attempts { + on_retry(attempt); + sleep(retry_delay); + } + } + } + } + + let discover = match discover { + Some(response) => response, + None => { + let err = last_discover_err.context("Public /status probe failed without an error.")?; + return Err(unreachable_public_status_error(exposure_mode, listen_port, &err)); + } + }; + + let server_version = discover + .headers() + .get("X-Server-Version") + .and_then(|value| value.to_str().ok()) + .map(|value| value.to_string()); + + let Some(server_version) = server_version else { + bail!("Reached the server, but it did not return X-Server-Version. This does not look like a healthy Secluso server response."); }; + on_version(&server_version); + + let auth = client + .get(status_url) + .header("Client-Version", &server_version) + .basic_auth(username, Some(password)) + .send() + .context("Authenticated health check failed.")?; - let mut channel = sess.channel_session().context("Failed to open SSH channel")?; - channel.exec(&full).context("Remote test exec failed")?; - // consume output and ignore - let mut sink = String::new(); - channel.read_to_string(&mut sink).ok(); - let mut sink2 = String::new(); - channel.stderr().read_to_string(&mut sink2).ok(); - channel.wait_close().ok(); - let code = channel.exit_status().unwrap_or(255); - Ok(code == 0) + if !auth.status().is_success() { + bail!( + "The server is reachable, but the authenticated /status check failed with HTTP {}.", + auth.status() + ); + } + + Ok(()) } diff --git a/deploy/src-tauri/src/provision_server/ssh.rs b/deploy/src-tauri/src/provision_server/ssh.rs index ddf5a08..4bb1c03 100644 --- a/deploy/src-tauri/src/provision_server/ssh.rs +++ b/deploy/src-tauri/src/provision_server/ssh.rs @@ -4,13 +4,16 @@ use crate::provision_server::types::{SshAuth, SshTarget}; use anyhow::{bail, Context, Result}; use ssh2::Session; use std::io::{Read, Write}; -use std::net::TcpStream; +use std::net::{TcpStream, ToSocketAddrs}; use std::path::Path; use std::time::Duration; use tauri::AppHandle; use tempfile::NamedTempFile; use uuid::Uuid; +const SSH_CONNECT_TIMEOUT: Duration = Duration::from_secs(10); +const SSH_IO_TIMEOUT: Duration = Duration::from_secs(30); + pub struct TempKeyFiles { pub key_file: Option, } @@ -22,12 +25,50 @@ impl TempKeyFiles { } pub fn connect_ssh(target: &SshTarget) -> Result<(Session, TempKeyFiles)> { - let tcp = TcpStream::connect((target.host.as_str(), target.port)) - .with_context(|| format!("Failed to connect to {}:{}", target.host, target.port))?; - tcp.set_read_timeout(Some(Duration::from_secs(30))).ok(); - tcp.set_write_timeout(Some(Duration::from_secs(30))).ok(); + let target_addr = format!("{}:{}", target.host, target.port); + let addrs = target_addr + .to_socket_addrs() + .with_context(|| format!("Failed to resolve SSH target {}", target_addr))? + .collect::>(); + if addrs.is_empty() { + bail!("Failed to resolve SSH target {}.", target_addr); + } + + let mut last_err = None; + let mut tcp = None; + for addr in addrs { + match TcpStream::connect_timeout(&addr, SSH_CONNECT_TIMEOUT) { + Ok(stream) => { + tcp = Some(stream); + break; + } + Err(err) => { + last_err = Some((addr, err)); + } + } + } + + let tcp = match tcp { + Some(stream) => stream, + None => { + let detail = match last_err { + Some((addr, err)) => format!("{addr} ({err})"), + None => "no resolved addresses".to_string(), + }; + bail!( + "Failed to connect to {} within {} seconds: {}", + target_addr, + SSH_CONNECT_TIMEOUT.as_secs(), + detail + ); + } + }; + + tcp.set_read_timeout(Some(SSH_IO_TIMEOUT)).ok(); + tcp.set_write_timeout(Some(SSH_IO_TIMEOUT)).ok(); let mut sess = Session::new().context("Failed to create SSH session")?; + sess.set_timeout(SSH_IO_TIMEOUT.as_millis() as u32); sess.set_tcp_stream(tcp); sess.handshake().context("SSH handshake failed")?; @@ -41,12 +82,12 @@ pub fn connect_ssh(target: &SshTarget) -> Result<(Session, TempKeyFiles)> { sess.userauth_password(&target.user, password) .context("SSH password authentication failed")?; } - SshAuth::KeyFile { path } => { + SshAuth::KeyFile { path, passphrase } => { let p = Path::new(path); - sess.userauth_pubkey_file(&target.user, None, p, None) + sess.userauth_pubkey_file(&target.user, None, p, passphrase.as_deref()) .with_context(|| format!("SSH keyfile authentication failed (path={})", path))?; } - SshAuth::KeyText { text } => { + SshAuth::KeyText { text, passphrase } => { // write key text to a temp file and use it as a keyfile // most openssh private keys work with libssh2 if the format is supported let mut f = NamedTempFile::new().context("Failed to create temp key file")?; @@ -64,7 +105,7 @@ pub fn connect_ssh(target: &SshTarget) -> Result<(Session, TempKeyFiles)> { let p = f.path().to_path_buf(); temps.key_file = Some(f); - sess.userauth_pubkey_file(&target.user, None, &p, None) + sess.userauth_pubkey_file(&target.user, None, &p, passphrase.as_deref()) .context("SSH pasted-key authentication failed")?; } } diff --git a/deploy/src-tauri/src/provision_server/types.rs b/deploy/src-tauri/src/provision_server/types.rs index d8d7fdc..d0f7476 100644 --- a/deploy/src-tauri/src/provision_server/types.rs +++ b/deploy/src-tauri/src/provision_server/types.rs @@ -8,10 +8,10 @@ pub enum SshAuth { Password { password: String }, #[serde(rename = "keyfile")] - KeyFile { path: String }, + KeyFile { path: String, passphrase: Option }, #[serde(rename = "keytext")] - KeyText { text: String }, + KeyText { text: String, passphrase: Option }, } #[derive(Debug, Deserialize, Clone)] @@ -52,15 +52,23 @@ pub struct ServerSecrets { pub user_credentials_qr_path: String, } +#[derive(Debug, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ServerRuntimePlan { + pub exposure_mode: String, + pub bind_address: String, + pub listen_port: u16, +} + #[derive(Debug, Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub struct ServerPlan { - pub use_docker: bool, - pub protect_packages: bool, pub auto_updater: AutoUpdaterPlan, + pub runtime: ServerRuntimePlan, pub secrets: Option, pub overwrite: Option, pub sig_keys: Option>, pub binaries_repo: Option, pub github_token: Option, + pub manifest_version_override: Option, } diff --git a/deploy/src-tauri/tauri.conf.json b/deploy/src-tauri/tauri.conf.json index 2b605e1..3d0d734 100644 --- a/deploy/src-tauri/tauri.conf.json +++ b/deploy/src-tauri/tauri.conf.json @@ -1,6 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", - "productName": "secluso-deploy", + "productName": "Secluso Deploy", + "mainBinaryName": "Secluso Deploy", "version": "0.1.0", "identifier": "com.secluso.deploy", "build": { @@ -12,9 +13,11 @@ "app": { "windows": [ { - "title": "secluso-deploy", + "title": "Secluso Deploy", "width": 800, - "height": 600 + "height": 630, + "resizable": false, + "maximizable": false } ], "security": { diff --git a/deploy/src/lib/api.ts b/deploy/src/lib/api.ts index 8ea1c0b..8fe306c 100644 --- a/deploy/src/lib/api.ts +++ b/deploy/src/lib/api.ts @@ -4,8 +4,8 @@ import { listen, type UnlistenFn } from "@tauri-apps/api/event"; export type SshAuth = | { kind: "password"; password: string } - | { kind: "keyfile"; path: string } - | { kind: "keytext"; text: string }; + | { kind: "keyfile"; path: string; passphrase?: string } + | { kind: "keytext"; text: string; passphrase?: string }; export interface SudoSpec { mode: "same" | "password"; @@ -20,15 +20,21 @@ export interface SshTarget { sudo: SudoSpec; } +export interface ServerRuntimePlan { + exposureMode: "direct" | "proxy"; + bindAddress: string; + listenPort: number; +} + export interface ServerPlan { - useDocker: boolean; - protectPackages: boolean; autoUpdater: { enable: boolean }; + runtime: ServerRuntimePlan; secrets?: { serviceAccountKeyPath: string; serverUrl: string; userCredentialsQrPath: string }; overwrite?: boolean; sigKeys?: { name: string; githubUser: string }[]; binariesRepo?: string; githubToken?: string; + manifestVersionOverride?: string; } export interface JobStart { @@ -67,8 +73,8 @@ export interface ImageBuildRequest { githubToken?: string; } -export async function testServerSsh(target: SshTarget): Promise { - await invoke("test_server_ssh", { target }); +export async function testServerSsh(target: SshTarget, runtime?: ServerRuntimePlan, serverUrl?: string): Promise { + await invoke("test_server_ssh", { target, runtime, serverUrl }); } export async function provisionServer( diff --git a/deploy/src/lib/components/AppHeader.svelte b/deploy/src/lib/components/AppHeader.svelte new file mode 100644 index 0000000..9725fc4 --- /dev/null +++ b/deploy/src/lib/components/AppHeader.svelte @@ -0,0 +1,204 @@ + + + +
+ +
+ + diff --git a/deploy/src/lib/demoDisplay.ts b/deploy/src/lib/demoDisplay.ts new file mode 100644 index 0000000..5a1425a --- /dev/null +++ b/deploy/src/lib/demoDisplay.ts @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +const DEV_SETTINGS_STORAGE_KEY = "secluso-dev-settings"; +const MACOS_HOME_PATH_PATTERN = /\/Users\/[^/]+(?=\/|$)/g; + +type DemoDisplaySettings = { + enabled?: boolean; + maskUserPathsWithDemo?: boolean; +}; + +function shouldMaskUserPathsWithDemo(): boolean { + if (typeof localStorage === "undefined") return false; + + try { + const raw = localStorage.getItem(DEV_SETTINGS_STORAGE_KEY); + if (!raw) return false; + + const settings = JSON.parse(raw) as DemoDisplaySettings; + return !!(settings.enabled && settings.maskUserPathsWithDemo); + } catch { + return false; + } +} + +// Temporary demo-only redaction for paths rendered in the UI. +export function maskDemoText(value: string | null | undefined): string { + if (!value) return ""; + if (!shouldMaskUserPathsWithDemo()) return value; + + return value.replace(MACOS_HOME_PATH_PATTERN, "/Users/demo"); +} diff --git a/deploy/src/routes/+layout.svelte b/deploy/src/routes/+layout.svelte index b3da94d..8828024 100644 --- a/deploy/src/routes/+layout.svelte +++ b/deploy/src/routes/+layout.svelte @@ -4,15 +4,17 @@ let checked = false; let isTauri = false; + let browserPreview = false; onMount(() => { const w = window as any; isTauri = Boolean(w?.__TAURI__ || w?.__TAURI_INTERNALS__ || w?.__TAURI_IPC__); + browserPreview = new URLSearchParams(window.location.search).get("browserPreview") === "1"; checked = true; }); -{#if checked && !isTauri} +{#if checked && !isTauri && !browserPreview}

Desktop app only

diff --git a/deploy/src/routes/+page.svelte b/deploy/src/routes/+page.svelte index 0aaedac..640205d 100644 --- a/deploy/src/routes/+page.svelte +++ b/deploy/src/routes/+page.svelte @@ -1,33 +1,41 @@ - function isInteractiveTarget(target: EventTarget | null): boolean { - return target instanceof Element && !!target.closest("a, button, input, label, textarea, select"); - } +
+
+ - function onToggleCardClick(event: MouseEvent) { - if (isInteractiveTarget(event.target)) return; - toggleFirstTime(); - } +
+
+ - function onToggleKey(event: KeyboardEvent) { - if (event.key === "Enter" || event.key === " " || event.key === "Spacebar") { - event.preventDefault(); - toggleFirstTime(); - } - } +
+ + +
- async function openExternal(url: string) { - try { - await openExternalUrl(url); - } catch { - window.open(url, "_blank", "noopener,noreferrer"); - } - } +

Secluso Deploy

- +
+ Private cameras in + 2 min + with + E2EE +
-
-
-
-

Secluso Deploy v1.0.0

-
- - Settings +
-
-

Get your encrypted camera system online in two easy steps.

-
-
-
-

Welcome to Secluso

-

Achieve true privacy with an easy, non-compromising setup. Follow the steps below to get everything online.

+ + + + + + + 1 +
+
+

Raspberry Pi

+ Build image +
+

Generate a Pi OS image and camera pairing QR code.

-
+ + + + {#if firstTimeOn} +
+
    +
  1. Install the Secluso app on your phone.
  2. +
  3. Build the Raspberry Pi image and keep the camera QR code.
  4. +
  5. Provision your Linux server and save the user credentials QR code.
  6. +
  7. Scan the server QR code in the app, then scan the camera QR code.
  8. +
+ + +
+ {/if}
+
- {#if firstTimeOn} -
-
-

First time?

- -
-

No scripts or command line steps needed.

-
    -
  1. Install the Secluso app from your app store.
  2. -
  3. Build the Raspberry Pi image and save the camera QR code.
  4. -
  5. Set up your server and save the server QR code.
  6. -
  7. Open the app and scan the server QR code, then the camera QR code.
  8. -
-

Need a server? A low cost option is Ionos VPS for around $2 per month. Just copy the login details from your provider and the app handles the setup. We are not affiliated with Ionos.

- -
- {:else} -
-
-

First time?

- -
-

Turn on the toggle to see the step-by-step guide.

-
- {/if} - - -
-
-

- Step A - Raspberry Pi -

-

Flash a fresh image or to an SD card to prepare your Pi.

-
+ diff --git a/deploy/src/routes/hardware-help/+page.svelte b/deploy/src/routes/hardware-help/+page.svelte index ba5910f..d772ae0 100644 --- a/deploy/src/routes/hardware-help/+page.svelte +++ b/deploy/src/routes/hardware-help/+page.svelte @@ -79,7 +79,7 @@
diff --git a/deploy/src/routes/ionos-help/+page.svelte b/deploy/src/routes/ionos-help/+page.svelte index 7f0bb13..8d937de 100644 --- a/deploy/src/routes/ionos-help/+page.svelte +++ b/deploy/src/routes/ionos-help/+page.svelte @@ -91,7 +91,7 @@
diff --git a/deploy/src/routes/service-account-help/+page.svelte b/deploy/src/routes/service-account-help/+page.svelte index eacf9d6..4f66c2c 100644 --- a/deploy/src/routes/service-account-help/+page.svelte +++ b/deploy/src/routes/service-account-help/+page.svelte @@ -61,7 +61,7 @@ diff --git a/deploy/src/routes/status/+page.svelte b/deploy/src/routes/status/+page.svelte index bcdcc98..0be8032 100644 --- a/deploy/src/routes/status/+page.svelte +++ b/deploy/src/routes/status/+page.svelte @@ -1,27 +1,32 @@ -
- {#if doneOk !== null} +
+ {#if doneOk === false}
- {/if} + {#if updaterWarning}
{/if} -
- -

{mode === "image" ? "Image Build Status" : "Server Provision Status"}

-
-
- - {#if firstTimeOn} -
-
-

First time?

- -
-

Keep this window open while Secluso runs the steps in the background.

-
    -
  1. Keep this screen open while the steps run.
  2. -
  3. Check logs if anything looks stuck.
  4. -
  5. When done, go back and continue the next step in the app.
  6. -
-
- {:else} -
-
-

First time?

- -
-

Turn on the toggle to see the step-by-step guide.

-
- {/if} - {#if !runId} -
-
-

Missing run ID

-

Start a provisioning or build task first, then return here with a run ID.

-
- {:else} -
-
-
-
Current step
-
{currentTitle || "Waiting for events…"}
-
- {completedSteps}/{totalSteps} steps complete - Run ID: {runId} +
+
+ +
+ + {#if !runId} +
+

Missing run ID

+

Start a provisioning or image build task first, then return here.

+
+ {:else if showLogs} +
+
+
+ + + +
+

{logsTitle}

+

{logs.length} entries

+
+ +
-
-
- {doneOk === true ? "Completed" : doneOk === false ? "Failed" : "Running"} + +
+
+
+ + + +
+ {logsFileName}
-
-
+ +
+ {#if logs.length === 0} +
No log output yet.
+ {:else} + {#each logs as log} +
+ {log.time} + + {maskDemoText(log.line)} +
+ {/each} +
+ {/if}
+
+
+ {:else} +
+ + + + {statusLeadLabel} +
+ +
+
+

{heroTitle}

+

+ Run ID: + {runId} +

+
-
-
-
-
-

Steps

- {completedSteps}/{totalSteps} +
+
+ + Show guided tips
-
- {#each steps as s} -
- - {s.title} - {stepStatus[s.key]} -
- {/each} + + +
+ + {#if firstTimeOn} +
+ +

{guidanceText}

+
+ {/if} + +
+
+
+ {progressValue}% + {completedSteps} of {totalSteps} steps +
+ +
-
-
-
-

Logs

- {logs.length} +
+
- {#if logs.length === 0} -

No log output yet.

- {:else} -
- {#each logs as log} -
- {log.time} - {log.step ?? "general"} - {log.line} -
- {/each} +
+ +
+ {#each steps as s} +
+ {#if stepStatus[s.key] === "ok"} + + + + {:else} + + {/if} + + {s.title} + {stepStateLabel(stepStatus[s.key])}
- {/if} + {/each}
-
- {/if} + {/if} +
diff --git a/deploy/static/deploy-assets/app-store-icon.svg b/deploy/static/deploy-assets/app-store-icon.svg new file mode 100644 index 0000000..a2227ce --- /dev/null +++ b/deploy/static/deploy-assets/app-store-icon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/deploy/static/deploy-assets/google-play-icon.svg b/deploy/static/deploy-assets/google-play-icon.svg new file mode 100644 index 0000000..b330b2f --- /dev/null +++ b/deploy/static/deploy-assets/google-play-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/deploy/static/deploy-assets/header-android-latest.svg b/deploy/static/deploy-assets/header-android-latest.svg new file mode 100644 index 0000000..21e278d --- /dev/null +++ b/deploy/static/deploy-assets/header-android-latest.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/deploy/static/deploy-assets/header-ios-latest.svg b/deploy/static/deploy-assets/header-ios-latest.svg new file mode 100644 index 0000000..0189e3a --- /dev/null +++ b/deploy/static/deploy-assets/header-ios-latest.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/deploy/static/deploy-assets/header-mark.jpeg b/deploy/static/deploy-assets/header-mark.jpeg new file mode 100644 index 0000000..ed6d404 Binary files /dev/null and b/deploy/static/deploy-assets/header-mark.jpeg differ diff --git a/deploy/static/deploy-assets/header-settings-latest.svg b/deploy/static/deploy-assets/header-settings-latest.svg new file mode 100644 index 0000000..acf4671 --- /dev/null +++ b/deploy/static/deploy-assets/header-settings-latest.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/deploy/static/deploy-assets/home-app-store-latest.svg b/deploy/static/deploy-assets/home-app-store-latest.svg new file mode 100644 index 0000000..b16941b --- /dev/null +++ b/deploy/static/deploy-assets/home-app-store-latest.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/deploy/static/deploy-assets/home-app-store.svg b/deploy/static/deploy-assets/home-app-store.svg new file mode 100644 index 0000000..a2227ce --- /dev/null +++ b/deploy/static/deploy-assets/home-app-store.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/deploy/static/deploy-assets/home-backdrop-fill-latest.svg b/deploy/static/deploy-assets/home-backdrop-fill-latest.svg new file mode 100644 index 0000000..9627631 --- /dev/null +++ b/deploy/static/deploy-assets/home-backdrop-fill-latest.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/deploy/static/deploy-assets/home-backdrop-latest.svg b/deploy/static/deploy-assets/home-backdrop-latest.svg new file mode 100644 index 0000000..8e54733 --- /dev/null +++ b/deploy/static/deploy-assets/home-backdrop-latest.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/deploy/static/deploy-assets/home-backdrop-mask-latest.svg b/deploy/static/deploy-assets/home-backdrop-mask-latest.svg new file mode 100644 index 0000000..d2079e1 --- /dev/null +++ b/deploy/static/deploy-assets/home-backdrop-mask-latest.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/deploy/static/deploy-assets/home-chip-e2ee-latest.svg b/deploy/static/deploy-assets/home-chip-e2ee-latest.svg new file mode 100644 index 0000000..d88c2d3 --- /dev/null +++ b/deploy/static/deploy-assets/home-chip-e2ee-latest.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/deploy/static/deploy-assets/home-chip-shield-latest.svg b/deploy/static/deploy-assets/home-chip-shield-latest.svg new file mode 100644 index 0000000..8880c6b --- /dev/null +++ b/deploy/static/deploy-assets/home-chip-shield-latest.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/deploy/static/deploy-assets/home-chip-shield.svg b/deploy/static/deploy-assets/home-chip-shield.svg new file mode 100644 index 0000000..8880c6b --- /dev/null +++ b/deploy/static/deploy-assets/home-chip-shield.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/deploy/static/deploy-assets/home-chip-time-latest.svg b/deploy/static/deploy-assets/home-chip-time-latest.svg new file mode 100644 index 0000000..569a265 --- /dev/null +++ b/deploy/static/deploy-assets/home-chip-time-latest.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/deploy/static/deploy-assets/home-chip-zap-latest.svg b/deploy/static/deploy-assets/home-chip-zap-latest.svg new file mode 100644 index 0000000..16d0e0a --- /dev/null +++ b/deploy/static/deploy-assets/home-chip-zap-latest.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/deploy/static/deploy-assets/home-chip-zap.svg b/deploy/static/deploy-assets/home-chip-zap.svg new file mode 100644 index 0000000..5e5fd46 --- /dev/null +++ b/deploy/static/deploy-assets/home-chip-zap.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/deploy/static/deploy-assets/home-first-time-latest.svg b/deploy/static/deploy-assets/home-first-time-latest.svg new file mode 100644 index 0000000..93f5d0f --- /dev/null +++ b/deploy/static/deploy-assets/home-first-time-latest.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/deploy/static/deploy-assets/home-google-play-latest.svg b/deploy/static/deploy-assets/home-google-play-latest.svg new file mode 100644 index 0000000..b330b2f --- /dev/null +++ b/deploy/static/deploy-assets/home-google-play-latest.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/deploy/static/deploy-assets/home-google-play.svg b/deploy/static/deploy-assets/home-google-play.svg new file mode 100644 index 0000000..b330b2f --- /dev/null +++ b/deploy/static/deploy-assets/home-google-play.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/deploy/static/deploy-assets/home-logo.jpeg b/deploy/static/deploy-assets/home-logo.jpeg new file mode 100644 index 0000000..ed6d404 Binary files /dev/null and b/deploy/static/deploy-assets/home-logo.jpeg differ diff --git a/deploy/static/deploy-assets/home-settings-latest.svg b/deploy/static/deploy-assets/home-settings-latest.svg new file mode 100644 index 0000000..acf4671 --- /dev/null +++ b/deploy/static/deploy-assets/home-settings-latest.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/deploy/static/deploy-assets/home-settings.svg b/deploy/static/deploy-assets/home-settings.svg new file mode 100644 index 0000000..acf4671 --- /dev/null +++ b/deploy/static/deploy-assets/home-settings.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/deploy/static/deploy-assets/home-signal-icon.svg b/deploy/static/deploy-assets/home-signal-icon.svg new file mode 100644 index 0000000..e5e0c72 --- /dev/null +++ b/deploy/static/deploy-assets/home-signal-icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/deploy/static/deploy-assets/home-signal-latest.svg b/deploy/static/deploy-assets/home-signal-latest.svg new file mode 100644 index 0000000..91cbb76 --- /dev/null +++ b/deploy/static/deploy-assets/home-signal-latest.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/deploy/static/deploy-assets/home-step-1-bg-latest.svg b/deploy/static/deploy-assets/home-step-1-bg-latest.svg new file mode 100644 index 0000000..acc12c3 --- /dev/null +++ b/deploy/static/deploy-assets/home-step-1-bg-latest.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/deploy/static/deploy-assets/home-step-1-bg.svg b/deploy/static/deploy-assets/home-step-1-bg.svg new file mode 100644 index 0000000..acc12c3 --- /dev/null +++ b/deploy/static/deploy-assets/home-step-1-bg.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/deploy/static/deploy-assets/home-step-1-icon-latest.svg b/deploy/static/deploy-assets/home-step-1-icon-latest.svg new file mode 100644 index 0000000..a5f71f9 --- /dev/null +++ b/deploy/static/deploy-assets/home-step-1-icon-latest.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/deploy/static/deploy-assets/home-step-1-icon.svg b/deploy/static/deploy-assets/home-step-1-icon.svg new file mode 100644 index 0000000..282baf9 --- /dev/null +++ b/deploy/static/deploy-assets/home-step-1-icon.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/deploy/static/deploy-assets/home-step-2-bg-latest.svg b/deploy/static/deploy-assets/home-step-2-bg-latest.svg new file mode 100644 index 0000000..765a75b --- /dev/null +++ b/deploy/static/deploy-assets/home-step-2-bg-latest.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/deploy/static/deploy-assets/home-step-2-bg.svg b/deploy/static/deploy-assets/home-step-2-bg.svg new file mode 100644 index 0000000..765a75b --- /dev/null +++ b/deploy/static/deploy-assets/home-step-2-bg.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/deploy/static/deploy-assets/home-step-2-icon-latest.svg b/deploy/static/deploy-assets/home-step-2-icon-latest.svg new file mode 100644 index 0000000..58e547f --- /dev/null +++ b/deploy/static/deploy-assets/home-step-2-icon-latest.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/deploy/static/deploy-assets/home-step-2-icon.svg b/deploy/static/deploy-assets/home-step-2-icon.svg new file mode 100644 index 0000000..58e547f --- /dev/null +++ b/deploy/static/deploy-assets/home-step-2-icon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/deploy/static/deploy-assets/home-step-arrow-latest.svg b/deploy/static/deploy-assets/home-step-arrow-latest.svg new file mode 100644 index 0000000..edee60b --- /dev/null +++ b/deploy/static/deploy-assets/home-step-arrow-latest.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/deploy/static/deploy-assets/home-step-arrow.svg b/deploy/static/deploy-assets/home-step-arrow.svg new file mode 100644 index 0000000..edee60b --- /dev/null +++ b/deploy/static/deploy-assets/home-step-arrow.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/deploy/static/deploy-assets/image-back-latest.svg b/deploy/static/deploy-assets/image-back-latest.svg new file mode 100644 index 0000000..510d0c4 --- /dev/null +++ b/deploy/static/deploy-assets/image-back-latest.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/deploy/static/deploy-assets/image-build-arrow-latest.svg b/deploy/static/deploy-assets/image-build-arrow-latest.svg new file mode 100644 index 0000000..0e0ba65 --- /dev/null +++ b/deploy/static/deploy-assets/image-build-arrow-latest.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/deploy/static/deploy-assets/image-diy-icon-latest.svg b/deploy/static/deploy-assets/image-diy-icon-latest.svg new file mode 100644 index 0000000..bf4df50 --- /dev/null +++ b/deploy/static/deploy-assets/image-diy-icon-latest.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/deploy/static/deploy-assets/image-hero-latest.svg b/deploy/static/deploy-assets/image-hero-latest.svg new file mode 100644 index 0000000..099e828 --- /dev/null +++ b/deploy/static/deploy-assets/image-hero-latest.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/deploy/static/deploy-assets/image-hero.svg b/deploy/static/deploy-assets/image-hero.svg new file mode 100644 index 0000000..099e828 --- /dev/null +++ b/deploy/static/deploy-assets/image-hero.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/deploy/static/deploy-assets/image-official-icon-latest.svg b/deploy/static/deploy-assets/image-official-icon-latest.svg new file mode 100644 index 0000000..fad739f --- /dev/null +++ b/deploy/static/deploy-assets/image-official-icon-latest.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/deploy/static/deploy-assets/image-output-help-icon-latest.svg b/deploy/static/deploy-assets/image-output-help-icon-latest.svg new file mode 100644 index 0000000..db113e6 --- /dev/null +++ b/deploy/static/deploy-assets/image-output-help-icon-latest.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/deploy/static/deploy-assets/image-output-icon-latest.svg b/deploy/static/deploy-assets/image-output-icon-latest.svg new file mode 100644 index 0000000..f51eb2d --- /dev/null +++ b/deploy/static/deploy-assets/image-output-icon-latest.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/deploy/static/deploy-assets/image-picker-icon-latest.svg b/deploy/static/deploy-assets/image-picker-icon-latest.svg new file mode 100644 index 0000000..ff80ae6 --- /dev/null +++ b/deploy/static/deploy-assets/image-picker-icon-latest.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/deploy/static/deploy-assets/image-qr-icon-latest.svg b/deploy/static/deploy-assets/image-qr-icon-latest.svg new file mode 100644 index 0000000..6f29f9b --- /dev/null +++ b/deploy/static/deploy-assets/image-qr-icon-latest.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/deploy/static/deploy-assets/image-selected-icon-latest.svg b/deploy/static/deploy-assets/image-selected-icon-latest.svg new file mode 100644 index 0000000..211a323 --- /dev/null +++ b/deploy/static/deploy-assets/image-selected-icon-latest.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/deploy/static/deploy-assets/image-tip-icon-latest.svg b/deploy/static/deploy-assets/image-tip-icon-latest.svg new file mode 100644 index 0000000..d558d3e --- /dev/null +++ b/deploy/static/deploy-assets/image-tip-icon-latest.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/deploy/static/deploy-assets/landing-logo.jpg b/deploy/static/deploy-assets/landing-logo.jpg new file mode 100644 index 0000000..ed6d404 Binary files /dev/null and b/deploy/static/deploy-assets/landing-logo.jpg differ diff --git a/deploy/static/deploy-assets/secluso-mark.jpg b/deploy/static/deploy-assets/secluso-mark.jpg new file mode 100644 index 0000000..ed6d404 Binary files /dev/null and b/deploy/static/deploy-assets/secluso-mark.jpg differ diff --git a/deploy/static/deploy-assets/server-auth-keyfile.svg b/deploy/static/deploy-assets/server-auth-keyfile.svg new file mode 100644 index 0000000..72c5159 --- /dev/null +++ b/deploy/static/deploy-assets/server-auth-keyfile.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/deploy/static/deploy-assets/server-auth-password.svg b/deploy/static/deploy-assets/server-auth-password.svg new file mode 100644 index 0000000..0e3e124 --- /dev/null +++ b/deploy/static/deploy-assets/server-auth-password.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/deploy/static/deploy-assets/server-auth-paste.svg b/deploy/static/deploy-assets/server-auth-paste.svg new file mode 100644 index 0000000..5b8bcd7 --- /dev/null +++ b/deploy/static/deploy-assets/server-auth-paste.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/deploy/static/deploy-assets/server-back.svg b/deploy/static/deploy-assets/server-back.svg new file mode 100644 index 0000000..510d0c4 --- /dev/null +++ b/deploy/static/deploy-assets/server-back.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/deploy/static/deploy-assets/server-button-arrow.svg b/deploy/static/deploy-assets/server-button-arrow.svg new file mode 100644 index 0000000..0e0ba65 --- /dev/null +++ b/deploy/static/deploy-assets/server-button-arrow.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/deploy/static/deploy-assets/server-external-link.svg b/deploy/static/deploy-assets/server-external-link.svg new file mode 100644 index 0000000..88ed5d4 --- /dev/null +++ b/deploy/static/deploy-assets/server-external-link.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/deploy/static/deploy-assets/server-hero-exact.svg b/deploy/static/deploy-assets/server-hero-exact.svg new file mode 100644 index 0000000..a99eca9 --- /dev/null +++ b/deploy/static/deploy-assets/server-hero-exact.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/deploy/static/deploy-assets/server-hero.svg b/deploy/static/deploy-assets/server-hero.svg new file mode 100644 index 0000000..a99eca9 --- /dev/null +++ b/deploy/static/deploy-assets/server-hero.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/deploy/static/deploy-assets/server-info.svg b/deploy/static/deploy-assets/server-info.svg new file mode 100644 index 0000000..3e6135c --- /dev/null +++ b/deploy/static/deploy-assets/server-info.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/deploy/static/deploy-assets/settings-back-latest.svg b/deploy/static/deploy-assets/settings-back-latest.svg new file mode 100644 index 0000000..510d0c4 --- /dev/null +++ b/deploy/static/deploy-assets/settings-back-latest.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/deploy/static/deploy-assets/settings-dev-options-latest.svg b/deploy/static/deploy-assets/settings-dev-options-latest.svg new file mode 100644 index 0000000..4b6baa2 --- /dev/null +++ b/deploy/static/deploy-assets/settings-dev-options-latest.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/deploy/static/deploy-assets/settings-gear-ghost-latest.svg b/deploy/static/deploy-assets/settings-gear-ghost-latest.svg new file mode 100644 index 0000000..d6f8d5a --- /dev/null +++ b/deploy/static/deploy-assets/settings-gear-ghost-latest.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/deploy/static/deploy-assets/shield-chip-icon.svg b/deploy/static/deploy-assets/shield-chip-icon.svg new file mode 100644 index 0000000..8880c6b --- /dev/null +++ b/deploy/static/deploy-assets/shield-chip-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/deploy/static/deploy-assets/status-back-latest.svg b/deploy/static/deploy-assets/status-back-latest.svg new file mode 100644 index 0000000..510d0c4 --- /dev/null +++ b/deploy/static/deploy-assets/status-back-latest.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/deploy/static/deploy-assets/status-copy-icon-latest.svg b/deploy/static/deploy-assets/status-copy-icon-latest.svg new file mode 100644 index 0000000..b570ed2 --- /dev/null +++ b/deploy/static/deploy-assets/status-copy-icon-latest.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/deploy/static/deploy-assets/status-done-icon-latest.svg b/deploy/static/deploy-assets/status-done-icon-latest.svg new file mode 100644 index 0000000..22a2ff5 --- /dev/null +++ b/deploy/static/deploy-assets/status-done-icon-latest.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/deploy/static/deploy-assets/status-guided-tips-icon-latest.svg b/deploy/static/deploy-assets/status-guided-tips-icon-latest.svg new file mode 100644 index 0000000..c2ac153 --- /dev/null +++ b/deploy/static/deploy-assets/status-guided-tips-icon-latest.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/deploy/static/deploy-assets/status-hero-latest.svg b/deploy/static/deploy-assets/status-hero-latest.svg new file mode 100644 index 0000000..acc12c3 --- /dev/null +++ b/deploy/static/deploy-assets/status-hero-latest.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/deploy/static/deploy-assets/status-hero.svg b/deploy/static/deploy-assets/status-hero.svg new file mode 100644 index 0000000..acc12c3 --- /dev/null +++ b/deploy/static/deploy-assets/status-hero.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/deploy/static/deploy-assets/status-info-icon-latest.svg b/deploy/static/deploy-assets/status-info-icon-latest.svg new file mode 100644 index 0000000..37a3bb7 --- /dev/null +++ b/deploy/static/deploy-assets/status-info-icon-latest.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/deploy/static/deploy-assets/status-logs-header-icon-latest.svg b/deploy/static/deploy-assets/status-logs-header-icon-latest.svg new file mode 100644 index 0000000..ffc13bb --- /dev/null +++ b/deploy/static/deploy-assets/status-logs-header-icon-latest.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/deploy/static/deploy-assets/status-state-icon-latest.svg b/deploy/static/deploy-assets/status-state-icon-latest.svg new file mode 100644 index 0000000..a8cdf3d --- /dev/null +++ b/deploy/static/deploy-assets/status-state-icon-latest.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/deploy/static/deploy-assets/status-view-logs-icon-latest.svg b/deploy/static/deploy-assets/status-view-logs-icon-latest.svg new file mode 100644 index 0000000..a5c2072 --- /dev/null +++ b/deploy/static/deploy-assets/status-view-logs-icon-latest.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/deploy/static/deploy-assets/zap-chip-icon.svg b/deploy/static/deploy-assets/zap-chip-icon.svg new file mode 100644 index 0000000..5e5fd46 --- /dev/null +++ b/deploy/static/deploy-assets/zap-chip-icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/server/src/main.rs b/server/src/main.rs index d5ec27e..a03b449 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -918,28 +918,50 @@ pub fn build_rocket() -> rocket::Rocket { let failure_store: FailStore = Arc::new(DashMap::new()); let mut network_type: Option = None; + let mut bind_address: Option = None; + let mut listen_port: Option = None; let mut args = std::env::args().skip(1); while let Some(arg) = args.next() { if arg == "--network-type" { if let Some(value) = args.next() { network_type = Some(value); } + } else if arg == "--bind-address" { + if let Some(value) = args.next() { + bind_address = Some(value); + } + } else if arg == "--port" { + if let Some(value) = args.next() { + if let Ok(parsed) = value.parse::() { + listen_port = Some(parsed); + } else { + eprintln!("Invalid --port={value}. Falling back to default 8000."); + } + } } else if let Some(value) = arg.strip_prefix("--network-type=") { network_type = Some(value.to_string()); + } else if let Some(value) = arg.strip_prefix("--bind-address=") { + bind_address = Some(value.to_string()); + } else if let Some(value) = arg.strip_prefix("--port=") { + if let Ok(parsed) = value.parse::() { + listen_port = Some(parsed); + } else { + eprintln!("Invalid --port={value}. Falling back to default 8000."); + } } } - let address = match network_type.as_deref() { - Some("http") => "0.0.0.0", - Some("https") | None => "127.0.0.1", + let address = bind_address.unwrap_or_else(|| match network_type.as_deref() { + Some("http") => "0.0.0.0".to_string(), + Some("https") | None => "127.0.0.1".to_string(), Some(other) => { eprintln!("Unknown --network-type={other}. Use http or https."); - "127.0.0.1" + "127.0.0.1".to_string() } - }; + }); let config = rocket::Config { - port: 8000, + port: listen_port.unwrap_or(8000), address: address.parse().unwrap(), ..rocket::Config::default() }; diff --git a/update/src/lib.rs b/update/src/lib.rs index 65f1f19..8b1fbf5 100644 --- a/update/src/lib.rs +++ b/update/src/lib.rs @@ -34,7 +34,6 @@ pub const DEFAULT_OWNER_REPO: &str = "secluso/secluso"; const MANIFEST_PATH: &str = "manifest.json"; -// The length of the username and password for the user credentials file. pub const NUM_USERNAME_CHARS: usize = 14; pub const NUM_PASSWORD_CHARS: usize = 14; @@ -81,6 +80,7 @@ pub struct GhAsset { #[derive(Debug, Clone, Copy)] pub enum Component { Server, + Updater, RaspberryCameraHub, ConfigTool, } @@ -116,44 +116,77 @@ struct Artifact { sha256: String, } +#[derive(Debug, Deserialize)] +struct StoredUserCredentials { + #[serde(rename = "u", alias = "username")] + username: String, + #[serde(rename = "p", alias = "password")] + password: String, + #[serde(rename = "sa", alias = "server_addr")] + server_addr: String, +} + #[derive(Debug, Clone)] pub struct VerifiedComponent { pub release_tag: String, pub latest_version: Version, + pub manifest_version: String, pub component_path: String, pub component_bytes: Vec, pub bundle_bytes: Vec, } -pub fn server_version() -> Result> { +pub fn server_version(client_version: &str) -> Result> { let file_path = format!("{}/{}", WORKING_DIRECTORY, "credentials_full"); + server_version_from_path(Path::new(&file_path), client_version) +} - let credentials_exist = fs::exists(&file_path)?; - if !credentials_exist { +fn parse_credentials_full(contents: &str) -> Result> { + if contents.trim().is_empty() { return Ok(None); } - let user_credentials_contents = fs::read_to_string(&file_path)?; - if user_credentials_contents.len() < NUM_USERNAME_CHARS + NUM_PASSWORD_CHARS + 4 { + if let Ok(parsed) = serde_json::from_str::(contents) { + return Ok(Some((parsed.username, parsed.password, parsed.server_addr))); + } + + if contents.len() <= NUM_USERNAME_CHARS + NUM_PASSWORD_CHARS { return Ok(None); } - let server_username = &user_credentials_contents[..NUM_USERNAME_CHARS - 1]; - let server_password = - &user_credentials_contents[NUM_USERNAME_CHARS..NUM_USERNAME_CHARS + NUM_PASSWORD_CHARS - 1]; - let server_addr = &user_credentials_contents[NUM_USERNAME_CHARS + NUM_PASSWORD_CHARS..]; + Ok(Some(( + contents[0..NUM_USERNAME_CHARS].to_string(), + contents[NUM_USERNAME_CHARS..NUM_USERNAME_CHARS + NUM_PASSWORD_CHARS].to_string(), + contents[NUM_USERNAME_CHARS + NUM_PASSWORD_CHARS..].to_string(), + ))) +} - let client = reqwest::blocking::Client::new(); - let response = client +fn server_version_from_path(file_path: &Path, client_version: &str) -> Result> { + let credentials_exist = fs::exists(file_path)?; + if !credentials_exist { + return Ok(None); + } + + let user_credentials_contents = fs::read_to_string(file_path)?; + let Some((server_username, server_password, server_addr)) = + parse_credentials_full(&user_credentials_contents)? + else { + return Ok(None); + }; + + let response = reqwest::blocking::Client::new() .get(format!("{}/status", server_addr.trim_end_matches('/'))) + .header("Client-Version", client_version) .basic_auth(server_username, Some(server_password)) .send()?; - if response.status().is_success() { - if let Some(server_version) = response.headers().get("X-Server-Version") { - if let Ok(version_str) = server_version.to_str() { - return Ok(Some(version_str.to_string())); - } + if !(response.status().is_success() || response.status() == reqwest::StatusCode::CONFLICT) { + return Ok(None); + } + + if let Some(server_version) = response.headers().get("X-Server-Version") { + if let Ok(version_str) = server_version.to_str() { + return Ok(Some(version_str.to_string())); } } @@ -164,10 +197,11 @@ impl Component { pub fn parse(s: &str) -> Result { match s { "server" => Ok(Self::Server), + "updater" => Ok(Self::Updater), "raspberry_camera_hub" => Ok(Self::RaspberryCameraHub), "config_tool" => Ok(Self::ConfigTool), _ => bail!( - "Unknown component {}. Use one of: server | raspberry_camera_hub | config_tool", + "Unknown component {}. Use one of: server | updater | raspberry_camera_hub | config_tool", s ), } @@ -180,6 +214,10 @@ impl Component { (Self::Server, "aarch64") => Ok("aarch64-unknown-linux-gnu/secluso-server"), (Self::Server, _) => bail!("component=server not supported on arch={}", arch), + (Self::Updater, "x86_64") => Ok("x86_64-unknown-linux-gnu/secluso-update"), + (Self::Updater, "aarch64") => Ok("aarch64-unknown-linux-gnu/secluso-update"), + (Self::Updater, _) => bail!("component=updater not supported on arch={}", arch), + (Self::RaspberryCameraHub, "aarch64") => { Ok("aarch64-unknown-linux-gnu/secluso-raspberry-camera-hub") } @@ -200,6 +238,7 @@ impl Component { pub fn install_path(self) -> String { let bin = match self { Self::Server => "secluso-server", + Self::Updater => "secluso-update", Self::RaspberryCameraHub => "secluso-raspberry-camera-hub", Self::ConfigTool => "secluso-config-tool", }; @@ -211,6 +250,7 @@ impl Component { pub fn version_file(self) -> String { let name = match self { Self::Server => "server", + Self::Updater => "updater", Self::RaspberryCameraHub => "raspberry_camera_hub", Self::ConfigTool => "config_tool", }; @@ -333,6 +373,26 @@ pub fn download_and_verify_component( arch: &str, bundle_path: Option<&str>, signers: &[Signer], +) -> Result { + download_and_verify_component_with_key_base( + client, + release, + component, + arch, + bundle_path, + signers, + "https://github.com", + ) +} + +fn download_and_verify_component_with_key_base( + client: &Client, + release: &GhRelease, + component: Component, + arch: &str, + bundle_path: Option<&str>, + signers: &[Signer], + key_base_url: &str, ) -> Result { // Refuse mutable or unpublished releases up front. This prevents installing from states that can // still change after metadata is fetched. @@ -395,7 +455,7 @@ pub fn download_and_verify_component( let (certs, allowed_fprs) = match key_cache.get(&signer.github_user) { Some(v) => v.clone(), None => { - let v = fetch_github_user_keyring(client, &signer.github_user)?; + let v = fetch_github_user_keyring(client, &signer.github_user, key_base_url)?; key_cache.insert(signer.github_user.clone(), v.clone()); v } @@ -460,6 +520,7 @@ pub fn download_and_verify_component( Ok(VerifiedComponent { release_tag: release.tag_name.clone(), latest_version, + manifest_version: art.version.trim().to_string(), component_path: target_path, component_bytes: target_bytes, bundle_bytes: zip_bytes.to_vec(), @@ -478,7 +539,7 @@ fn manifest_sig_path_for(label: &str) -> String { // immutable=true plus non-draft/non-null published_at prevents update/install decisions from using // mutable pre-release states. Essentially a defense against race conditions where release assets or // metadata could change between discovery and installation. -fn require_release_is_immutable(release: &GhRelease) -> Result<()> { +pub fn require_release_is_immutable(release: &GhRelease) -> Result<()> { if release.draft { bail!( "Refusing update: latest release {} is a draft.", @@ -593,8 +654,9 @@ fn read_zip_file(zip: &mut ZipArchive>, path: &str) -> Result Result<(Vec, HashSet)> { - let url = format!("https://github.com/{user}.gpg"); + let url = format!("{}/{}.gpg", key_base_url.trim_end_matches('/'), user); let body = client.get(&url).send()?.error_for_status()?.bytes()?; let mut certs = Vec::new(); diff --git a/update/src/main.rs b/update/src/main.rs index d453368..47a9a94 100644 --- a/update/src/main.rs +++ b/update/src/main.rs @@ -12,9 +12,10 @@ use std::thread::sleep; use std::time::Duration; use secluso_update::{ - build_github_client, default_signers, download_and_verify_component, fetch_versioned_release, - get_current_version, github_token_from_env, parse_sig_keys, server_version, - write_current_version, Component, DEFAULT_OWNER_REPO, + build_github_client, default_signers, download_and_verify_component, fetch_latest_release, + fetch_versioned_release, get_current_version, github_token_from_env, parse_sig_keys, + require_release_is_immutable, server_version, write_current_version, Component, + DEFAULT_OWNER_REPO, }; const USAGE: &str = r#" @@ -27,7 +28,7 @@ Usage: Options: --component COMPONENT Which single binary to update: - server | raspberry_camera_hub | config_tool + server | updater | raspberry_camera_hub | config_tool --restart-unit UNIT systemd unit to restart after install (optional). If omitted, no service is restarted. --interval-secs N Poll interval seconds [default: 60]. @@ -52,6 +53,18 @@ struct Args { flag_bundle_path: Option, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ReleaseSource { + LatestImmutableGitHub, + ServerCoordinated, +} + +#[derive(Debug, Clone)] +struct SelectedRelease { + release: secluso_update::GhRelease, + source: ReleaseSource, +} + fn main() -> ! { let version = format!( "{}, version: {}", @@ -98,22 +111,6 @@ fn check_update(args: &Args) -> Result<()> { let current_version = get_current_version(component).unwrap_or_else(|_| Version::new(0, 0, 0)); println!("Current Version = {current_version}"); - let server_version = server_version().ok().flatten(); - let parsed_server_version = server_version - .as_deref() - .map(|s| s.trim().trim_start_matches('v')) - .and_then(|s| Version::parse(s).ok()); - - let proceed = parsed_server_version - .is_some_and(|parsed_version| current_version < parsed_version); - - // We don't proceed unless the server version is greater than the component version - if !proceed { - let shown_version = server_version.as_deref().unwrap_or("UNKNOWN"); - println!("Server version is {}; update not needed yet", shown_version); - return Ok(()); - } - let github_token = github_token_from_env(); let client = build_github_client( args.flag_github_timeout_secs, @@ -128,12 +125,27 @@ fn check_update(args: &Args) -> Result<()> { args.flag_github_repo.clone() }; - // Fetch the server's matching version release from github, so that we stay consistent. - let release = fetch_versioned_release(&client, &github_repo, &server_version.unwrap())?; - println!("Found Server Github Release Tag = {}", release.tag_name); - if let Some(p) = &release.published_at { - println!("Published At = {}", p); - } + let Some(selected_release) = select_release_for_component( + component, + ¤t_version, + || fetch_latest_release(&client, &github_repo), + require_release_is_immutable, + |client_version| server_version(client_version), + |version| fetch_versioned_release(&client, &github_repo, version), + )? else { + return Ok(()); + }; + + match selected_release.source { + ReleaseSource::LatestImmutableGitHub => { + println!("Using the latest immutable GitHub release."); + } + ReleaseSource::ServerCoordinated => { + println!("Using the server-coordinated release."); + } + }; + + let release = selected_release.release; // download_and_verify_component performs the full cryptographic verification pipeline and returns // only authenticated component bytes. allows focus below on atomic file placement. @@ -189,13 +201,66 @@ fn check_update(args: &Args) -> Result<()> { // Persist version only after install has succeeded. Acts to gate future update checks (via the marker). write_current_version(component, verified.latest_version.clone())?; - println!( - "Updated to version {} (component={})", - verified.latest_version, args.flag_component - ); + println!("Update completed successfully (component={})", args.flag_component); Ok(()) } +fn select_release_for_component< + FLatest, + FRequireImmutable, + FServerVersion, + FFetchVersioned, +>( + component: Component, + current_version: &Version, + fetch_latest_release_fn: FLatest, + require_release_is_immutable_fn: FRequireImmutable, + server_version_fn: FServerVersion, + fetch_versioned_release_fn: FFetchVersioned, +) -> Result> +where + FLatest: FnOnce() -> Result, + FRequireImmutable: FnOnce(&secluso_update::GhRelease) -> Result<()>, + FServerVersion: FnOnce(&str) -> Result>, + FFetchVersioned: FnOnce(&str) -> Result, +{ + match component { + Component::Server => { + let release = fetch_latest_release_fn()?; + require_release_is_immutable_fn(&release)?; + + let latest_version = release.parsed_version()?; + if current_version >= &latest_version { + println!("Already on the latest immutable GitHub release."); + return Ok(None); + } + + Ok(Some(SelectedRelease { + release, + source: ReleaseSource::LatestImmutableGitHub, + })) + } + Component::Updater | Component::RaspberryCameraHub | Component::ConfigTool => { + let Some(server_version) = server_version_fn(¤t_version.to_string())? else { + println!("Server version unavailable; update not needed yet"); + return Ok(None); + }; + + let latest_version = Version::parse(server_version.trim_start_matches('v'))?; + if current_version >= &latest_version { + println!("Already on the server-coordinated release."); + return Ok(None); + } + + let release = fetch_versioned_release_fn(&server_version)?; + Ok(Some(SelectedRelease { + release, + source: ReleaseSource::ServerCoordinated, + })) + } + } +} + // "best-effort" command runner used for service stop/start so updater can still finish installation fn run(cmd: &str) { let _ = Command::new("sh")