clean up
This commit is contained in:
parent
50dd6a459a
commit
6c37043638
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
|||
/.vscode
|
||||
/target
|
||||
/Cargo.lock
|
||||
manufacturing/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ set -euo pipefail
|
|||
#
|
||||
# Environment:
|
||||
# CREDS_DIR Local output dir. Default: toes-matter/manufacturing/<device-id>/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}"
|
||||
|
||||
|
|
|
|||
|
|
@ -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<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||
|
||||
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<String>,
|
||||
serial: Option<String>,
|
||||
device_id: String,
|
||||
creds_dir: PathBuf,
|
||||
remote_creds_dir: String,
|
||||
remote_state_dir: String,
|
||||
service: Option<String>,
|
||||
ssh_opts: Vec<String>,
|
||||
baud: u32,
|
||||
serial_delay: Duration,
|
||||
serial_login: Option<String>,
|
||||
|
|
@ -80,7 +77,6 @@ impl Options {
|
|||
fn parse() -> Result<Self> {
|
||||
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<String> {
|
||||
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::<Vec<_>>();
|
||||
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<Item = OsString>) -> 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('\'');
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user