From 6c37043638aebcf026c7097c660fb55c136b2d68 Mon Sep 17 00:00:00 2001 From: Pat Nakajima Date: Tue, 19 May 2026 06:54:25 +0000 Subject: [PATCH] clean up --- .gitignore | 1 + README.md | 4 +- scripts/provision-device.sh | 4 +- src/bin/toes-matter-credentials.rs | 151 +++-------------------------- 4 files changed, 16 insertions(+), 144 deletions(-) diff --git a/.gitignore b/.gitignore index cf6578b..09f81d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /.vscode /target /Cargo.lock +manufacturing/ diff --git a/README.md b/README.md index a0abe33..950c092 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ cargo run --no-default-features --manifest-path ~/apps/toes-matter/Cargo.toml -- user@device-host device-001 ``` -It generates `manufacturing/device-001/creds`, prints the manual code / QR payload, renders a terminal QR if `qrencode` is installed, copies the creds to `/var/lib/toes-matter/creds`, creates `/var/lib/toes-matter/state`, and restarts `toes-matter.service` if present. +It generates `manufacturing/device-001/creds`, prints the manual code / QR payload, renders a terminal QR if `qrencode` is installed, copies the creds to `/var/lib/toes-matter`, creates `/var/lib/toes-matter/state`, and restarts `toes-matter.service` if present. The older script is now just a wrapper around that command: @@ -58,7 +58,7 @@ Set these if your service uses different paths: ```bash cargo run --no-default-features --manifest-path ~/apps/toes-matter/Cargo.toml --bin toes-matter-credentials -- \ - --remote-creds-dir /var/lib/my-app/creds \ + --remote-creds-dir /var/lib/my-app \ --remote-state-dir /var/lib/my-app/state \ --service my-app.service \ user@device-host device-001 diff --git a/scripts/provision-device.sh b/scripts/provision-device.sh index b98ba34..6cf0dad 100755 --- a/scripts/provision-device.sh +++ b/scripts/provision-device.sh @@ -13,7 +13,7 @@ set -euo pipefail # # Environment: # CREDS_DIR Local output dir. Default: toes-matter/manufacturing//creds -# REMOTE_CREDS_DIR Remote creds dir. Default: /var/lib/toes-matter/creds +# REMOTE_CREDS_DIR Remote creds dir. Default: /var/lib/toes-matter # REMOTE_STATE_DIR Remote Matter state dir. Default: /var/lib/toes-matter/state # SERVICE Optional service to restart. Default: toes-matter.service # Set SERVICE= to skip restart. @@ -46,7 +46,7 @@ else fi CREDS_DIR="${CREDS_DIR:-$CRATE_DIR/manufacturing/$DEVICE_ID/creds}" -REMOTE_CREDS_DIR="${REMOTE_CREDS_DIR:-/var/lib/toes-matter/creds}" +REMOTE_CREDS_DIR="${REMOTE_CREDS_DIR:-/var/lib/toes-matter}" REMOTE_STATE_DIR="${REMOTE_STATE_DIR:-/var/lib/toes-matter/state}" SERVICE="${SERVICE-toes-matter.service}" diff --git a/src/bin/toes-matter-credentials.rs b/src/bin/toes-matter-credentials.rs index 12ad4ca..841cb6f 100644 --- a/src/bin/toes-matter-credentials.rs +++ b/src/bin/toes-matter-credentials.rs @@ -1,6 +1,5 @@ #![recursion_limit = "256"] -use std::ffi::OsString; use std::io::{Read, Write}; use std::path::{Path, PathBuf}; use std::process::Command; @@ -17,7 +16,9 @@ use rs_matter::BasicCommData; type Result = std::result::Result>; -const DEFAULT_REMOTE_CREDS_DIR: &str = "/var/lib/toes-matter/creds"; +// These defaults should match the on-device image built by `toes-image-builder`. +// See: `toes-image-builder/matter/toes-matter.service`. +const DEFAULT_REMOTE_CREDS_DIR: &str = "/var/lib/toes-matter"; const DEFAULT_REMOTE_STATE_DIR: &str = "/var/lib/toes-matter/state"; const DEFAULT_SERVICE: &str = "toes-matter.service"; const DEFAULT_BAUD: u32 = 115_200; @@ -38,18 +39,16 @@ fn main() -> Result<()> { if let Some(serial) = opts.serial.as_deref() { write_to_serial(serial, &opts)?; - } else if let Some(target) = opts.target.as_deref() { - copy_to_device(target, &opts)?; } else { println!(); - println!("==> No target/serial specified; generated local creds only"); + println!("==> No serial specified; generated local creds only"); } println!(); println!("==> Done"); println!("Local copy saved at: {}", opts.creds_dir.display()); - if opts.target.is_some() || opts.serial.is_some() { + if opts.serial.is_some() { println!("Remote app env should use:"); println!(" TOES_MATTER_CREDS_DIR={}", opts.remote_creds_dir); println!(" TOES_MATTER_STATE_DIR={}", opts.remote_state_dir); @@ -60,14 +59,12 @@ fn main() -> Result<()> { #[derive(Debug)] struct Options { - target: Option, serial: Option, device_id: String, creds_dir: PathBuf, remote_creds_dir: String, remote_state_dir: String, service: Option, - ssh_opts: Vec, baud: u32, serial_delay: Duration, serial_login: Option, @@ -80,7 +77,6 @@ impl Options { fn parse() -> Result { let mut args = std::env::args().skip(1); - let mut target = None; let mut serial = std::env::var("SERIAL_PORT").ok(); let mut device_id = None; let mut creds_dir = std::env::var_os("CREDS_DIR").map(PathBuf::from); @@ -93,7 +89,6 @@ impl Options { Ok(value) => Some(value), Err(_) => Some(DEFAULT_SERVICE.into()), }; - let mut ssh_opts = split_ssh_opts(std::env::var("SSH_OPTS").unwrap_or_default()); let mut baud = std::env::var("SERIAL_BAUD") .ok() .and_then(|value| value.parse().ok()) @@ -131,7 +126,6 @@ impl Options { "--service" => service = Some(next_arg(&mut args, "--service")?), "--no-restart" => service = None, "--device-id" => device_id = Some(next_arg(&mut args, "--device-id")?), - "--ssh-opt" => ssh_opts.push(next_arg(&mut args, "--ssh-opt")?), "--serial" => serial = Some(next_arg(&mut args, "--serial")?), "--baud" => baud = next_arg(&mut args, "--baud")?.parse()?, "--serial-delay-ms" => { @@ -146,25 +140,15 @@ impl Options { } _ if arg.starts_with('-') => return Err(format!("unknown option: {arg}").into()), _ => { - if target.is_none() { - target = Some(arg); - } else if device_id.is_none() { + if device_id.is_none() { device_id = Some(arg); } else { - return Err("too many positional arguments".into()); + return Err("unexpected positional argument".into()); } } } } - if serial.is_some() && target.is_some() && device_id.is_none() { - device_id = target.take(); - } - - if serial.is_some() && target.is_some() { - return Err("use either SSH target or --serial, not both".into()); - } - let device_id = device_id.unwrap_or_else(default_device_id); let creds_dir = creds_dir.unwrap_or_else(|| { PathBuf::from("manufacturing") @@ -173,14 +157,12 @@ impl Options { }); Ok(Self { - target, serial, device_id, creds_dir, remote_creds_dir, remote_state_dir, service, - ssh_opts, baud, serial_delay: Duration::from_millis(serial_delay_ms), serial_login, @@ -193,16 +175,15 @@ impl Options { fn print_usage() { eprintln!( - "Usage: toes-matter-credentials [OPTIONS] [user@device-host] [device-id]\n\n\ - Generates Matter setup data, prints the QR/manual code, and optionally copies it to a Linux device over SSH or serial console.\n\n\ + "Usage: toes-matter-credentials [OPTIONS] [device-id]\n\n\ + Generates Matter setup data, prints the QR/manual code, and optionally installs it onto a Linux device over a serial console.\n\n\ Options:\n\ --creds-dir DIR Local output dir [env: CREDS_DIR]\n\ --remote-creds-dir DIR Remote creds dir [default: {DEFAULT_REMOTE_CREDS_DIR}]\n\ --remote-state-dir DIR Remote state dir [default: {DEFAULT_REMOTE_STATE_DIR}]\n\ --service NAME Service to restart [default: {DEFAULT_SERVICE}]\n\ - --no-restart Do not restart a remote service\n\ - --device-id ID Device id for local-only generation\n\ - --ssh-opt ARG Extra ssh/scp option; may be repeated [env: SSH_OPTS]\n\ + --no-restart Do not restart the service after provisioning\n\ + --device-id ID Device id (if not provided positionally)\n\ --serial PORT Write install script to a serial shell [env: SERIAL_PORT]\n\ --baud BAUD Configure serial baud [default: 115200, env: SERIAL_BAUD]\n\ --serial-delay-ms MS Delay between serial lines [default: 15, env: SERIAL_DELAY_MS]\n\ @@ -227,13 +208,6 @@ fn default_device_id() -> String { format!("device-{secs}") } -fn split_ssh_opts(value: String) -> Vec { - value - .split_whitespace() - .filter(|part| !part.is_empty()) - .map(str::to_owned) - .collect() -} struct GeneratedCredentials { manual_code: String, @@ -648,109 +622,6 @@ fn base64_wrapped(data: &[u8]) -> String { out } -fn copy_to_device(target: &str, opts: &Options) -> Result<()> { - let remote_tmp = format!( - "/tmp/toes-matter-creds-{}-{}", - sanitize_path_component(&opts.device_id), - std::process::id(), - ); - - println!(); - println!("==> Copying creds to {target}:{}", opts.remote_creds_dir); - - run_status( - "ssh", - command_with_args( - "ssh", - opts.ssh_opts.iter().map(OsString::from).chain([ - OsString::from(target), - OsString::from(format!( - "rm -rf {} && mkdir -p {}", - shell_quote(&remote_tmp), - shell_quote(&remote_tmp), - )), - ]), - ), - )?; - - let mut scp_args = opts.ssh_opts.iter().map(OsString::from).collect::>(); - scp_args.push(OsString::from("-r")); - scp_args.push(opts.creds_dir.join(".").into_os_string()); - scp_args.push(OsString::from(format!("{target}:{remote_tmp}/"))); - - run_status("scp", command_with_args("scp", scp_args))?; - - let parent = remote_parent(&opts.remote_creds_dir); - let mut remote_script = format!( - "set -e\n\ - sudo rm -rf {remote_creds}\n\ - sudo mkdir -p {parent} {remote_state}\n\ - sudo mv {remote_tmp} {remote_creds}\n\ - sudo chmod -R go-rwx {remote_creds} {remote_state}\n", - remote_creds = shell_quote(&opts.remote_creds_dir), - parent = shell_quote(&parent), - remote_state = shell_quote(&opts.remote_state_dir), - remote_tmp = shell_quote(&remote_tmp), - ); - - if let Some(service) = opts.service.as_deref() { - remote_script.push_str(&format!( - "if systemctl list-unit-files {service} >/dev/null 2>&1 || systemctl status {service} >/dev/null 2>&1; then\n\ - sudo systemctl restart {service}\n\ - else\n\ - echo 'Service {service_display} not found; skipping restart'\n\ - fi\n", - service = shell_quote(service), - service_display = service, - )); - } - - run_status( - "ssh", - command_with_args( - "ssh", - opts.ssh_opts - .iter() - .map(OsString::from) - .chain([OsString::from(target), OsString::from(remote_script)]), - ), - )?; - - Ok(()) -} - -fn command_with_args(program: &str, args: impl IntoIterator) -> Command { - let mut command = Command::new(program); - command.args(args); - command -} - -fn run_status(label: &str, mut command: Command) -> Result<()> { - let status = command.status()?; - if status.success() { - Ok(()) - } else { - Err(format!("{label} failed with status {status}").into()) - } -} - -fn sanitize_path_component(value: &str) -> String { - value - .chars() - .map(|ch| match ch { - 'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' | '.' => ch, - _ => '_', - }) - .collect() -} - -fn remote_parent(path: &str) -> String { - path.rsplit_once('/') - .map(|(parent, _)| if parent.is_empty() { "/" } else { parent }) - .unwrap_or(".") - .to_owned() -} - fn shell_quote(value: &str) -> String { let mut quoted = String::with_capacity(value.len() + 2); quoted.push('\'');