clean up
This commit is contained in:
parent
50dd6a459a
commit
6c37043638
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
||||||
/.vscode
|
/.vscode
|
||||||
/target
|
/target
|
||||||
/Cargo.lock
|
/Cargo.lock
|
||||||
|
manufacturing/
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ cargo run --no-default-features --manifest-path ~/apps/toes-matter/Cargo.toml --
|
||||||
user@device-host device-001
|
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:
|
The older script is now just a wrapper around that command:
|
||||||
|
|
||||||
|
|
@ -58,7 +58,7 @@ Set these if your service uses different paths:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo run --no-default-features --manifest-path ~/apps/toes-matter/Cargo.toml --bin toes-matter-credentials -- \
|
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 \
|
--remote-state-dir /var/lib/my-app/state \
|
||||||
--service my-app.service \
|
--service my-app.service \
|
||||||
user@device-host device-001
|
user@device-host device-001
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ set -euo pipefail
|
||||||
#
|
#
|
||||||
# Environment:
|
# Environment:
|
||||||
# CREDS_DIR Local output dir. Default: toes-matter/manufacturing/<device-id>/creds
|
# 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
|
# REMOTE_STATE_DIR Remote Matter state dir. Default: /var/lib/toes-matter/state
|
||||||
# SERVICE Optional service to restart. Default: toes-matter.service
|
# SERVICE Optional service to restart. Default: toes-matter.service
|
||||||
# Set SERVICE= to skip restart.
|
# Set SERVICE= to skip restart.
|
||||||
|
|
@ -46,7 +46,7 @@ else
|
||||||
fi
|
fi
|
||||||
|
|
||||||
CREDS_DIR="${CREDS_DIR:-$CRATE_DIR/manufacturing/$DEVICE_ID/creds}"
|
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}"
|
REMOTE_STATE_DIR="${REMOTE_STATE_DIR:-/var/lib/toes-matter/state}"
|
||||||
SERVICE="${SERVICE-toes-matter.service}"
|
SERVICE="${SERVICE-toes-matter.service}"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
#![recursion_limit = "256"]
|
#![recursion_limit = "256"]
|
||||||
|
|
||||||
use std::ffi::OsString;
|
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
@ -17,7 +16,9 @@ use rs_matter::BasicCommData;
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
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_REMOTE_STATE_DIR: &str = "/var/lib/toes-matter/state";
|
||||||
const DEFAULT_SERVICE: &str = "toes-matter.service";
|
const DEFAULT_SERVICE: &str = "toes-matter.service";
|
||||||
const DEFAULT_BAUD: u32 = 115_200;
|
const DEFAULT_BAUD: u32 = 115_200;
|
||||||
|
|
@ -38,18 +39,16 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
if let Some(serial) = opts.serial.as_deref() {
|
if let Some(serial) = opts.serial.as_deref() {
|
||||||
write_to_serial(serial, &opts)?;
|
write_to_serial(serial, &opts)?;
|
||||||
} else if let Some(target) = opts.target.as_deref() {
|
|
||||||
copy_to_device(target, &opts)?;
|
|
||||||
} else {
|
} else {
|
||||||
println!();
|
println!();
|
||||||
println!("==> No target/serial specified; generated local creds only");
|
println!("==> No serial specified; generated local creds only");
|
||||||
}
|
}
|
||||||
|
|
||||||
println!();
|
println!();
|
||||||
println!("==> Done");
|
println!("==> Done");
|
||||||
println!("Local copy saved at: {}", opts.creds_dir.display());
|
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!("Remote app env should use:");
|
||||||
println!(" TOES_MATTER_CREDS_DIR={}", opts.remote_creds_dir);
|
println!(" TOES_MATTER_CREDS_DIR={}", opts.remote_creds_dir);
|
||||||
println!(" TOES_MATTER_STATE_DIR={}", opts.remote_state_dir);
|
println!(" TOES_MATTER_STATE_DIR={}", opts.remote_state_dir);
|
||||||
|
|
@ -60,14 +59,12 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Options {
|
struct Options {
|
||||||
target: Option<String>,
|
|
||||||
serial: Option<String>,
|
serial: Option<String>,
|
||||||
device_id: String,
|
device_id: String,
|
||||||
creds_dir: PathBuf,
|
creds_dir: PathBuf,
|
||||||
remote_creds_dir: String,
|
remote_creds_dir: String,
|
||||||
remote_state_dir: String,
|
remote_state_dir: String,
|
||||||
service: Option<String>,
|
service: Option<String>,
|
||||||
ssh_opts: Vec<String>,
|
|
||||||
baud: u32,
|
baud: u32,
|
||||||
serial_delay: Duration,
|
serial_delay: Duration,
|
||||||
serial_login: Option<String>,
|
serial_login: Option<String>,
|
||||||
|
|
@ -80,7 +77,6 @@ impl Options {
|
||||||
fn parse() -> Result<Self> {
|
fn parse() -> Result<Self> {
|
||||||
let mut args = std::env::args().skip(1);
|
let mut args = std::env::args().skip(1);
|
||||||
|
|
||||||
let mut target = None;
|
|
||||||
let mut serial = std::env::var("SERIAL_PORT").ok();
|
let mut serial = std::env::var("SERIAL_PORT").ok();
|
||||||
let mut device_id = None;
|
let mut device_id = None;
|
||||||
let mut creds_dir = std::env::var_os("CREDS_DIR").map(PathBuf::from);
|
let mut creds_dir = std::env::var_os("CREDS_DIR").map(PathBuf::from);
|
||||||
|
|
@ -93,7 +89,6 @@ impl Options {
|
||||||
Ok(value) => Some(value),
|
Ok(value) => Some(value),
|
||||||
Err(_) => Some(DEFAULT_SERVICE.into()),
|
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")
|
let mut baud = std::env::var("SERIAL_BAUD")
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|value| value.parse().ok())
|
.and_then(|value| value.parse().ok())
|
||||||
|
|
@ -131,7 +126,6 @@ impl Options {
|
||||||
"--service" => service = Some(next_arg(&mut args, "--service")?),
|
"--service" => service = Some(next_arg(&mut args, "--service")?),
|
||||||
"--no-restart" => service = None,
|
"--no-restart" => service = None,
|
||||||
"--device-id" => device_id = Some(next_arg(&mut args, "--device-id")?),
|
"--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")?),
|
"--serial" => serial = Some(next_arg(&mut args, "--serial")?),
|
||||||
"--baud" => baud = next_arg(&mut args, "--baud")?.parse()?,
|
"--baud" => baud = next_arg(&mut args, "--baud")?.parse()?,
|
||||||
"--serial-delay-ms" => {
|
"--serial-delay-ms" => {
|
||||||
|
|
@ -146,25 +140,15 @@ impl Options {
|
||||||
}
|
}
|
||||||
_ if arg.starts_with('-') => return Err(format!("unknown option: {arg}").into()),
|
_ if arg.starts_with('-') => return Err(format!("unknown option: {arg}").into()),
|
||||||
_ => {
|
_ => {
|
||||||
if target.is_none() {
|
if device_id.is_none() {
|
||||||
target = Some(arg);
|
|
||||||
} else if device_id.is_none() {
|
|
||||||
device_id = Some(arg);
|
device_id = Some(arg);
|
||||||
} else {
|
} 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 device_id = device_id.unwrap_or_else(default_device_id);
|
||||||
let creds_dir = creds_dir.unwrap_or_else(|| {
|
let creds_dir = creds_dir.unwrap_or_else(|| {
|
||||||
PathBuf::from("manufacturing")
|
PathBuf::from("manufacturing")
|
||||||
|
|
@ -173,14 +157,12 @@ impl Options {
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
target,
|
|
||||||
serial,
|
serial,
|
||||||
device_id,
|
device_id,
|
||||||
creds_dir,
|
creds_dir,
|
||||||
remote_creds_dir,
|
remote_creds_dir,
|
||||||
remote_state_dir,
|
remote_state_dir,
|
||||||
service,
|
service,
|
||||||
ssh_opts,
|
|
||||||
baud,
|
baud,
|
||||||
serial_delay: Duration::from_millis(serial_delay_ms),
|
serial_delay: Duration::from_millis(serial_delay_ms),
|
||||||
serial_login,
|
serial_login,
|
||||||
|
|
@ -193,16 +175,15 @@ impl Options {
|
||||||
|
|
||||||
fn print_usage() {
|
fn print_usage() {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"Usage: toes-matter-credentials [OPTIONS] [user@device-host] [device-id]\n\n\
|
"Usage: toes-matter-credentials [OPTIONS] [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\
|
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\
|
Options:\n\
|
||||||
--creds-dir DIR Local output dir [env: CREDS_DIR]\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-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\
|
--remote-state-dir DIR Remote state dir [default: {DEFAULT_REMOTE_STATE_DIR}]\n\
|
||||||
--service NAME Service to restart [default: {DEFAULT_SERVICE}]\n\
|
--service NAME Service to restart [default: {DEFAULT_SERVICE}]\n\
|
||||||
--no-restart Do not restart a remote service\n\
|
--no-restart Do not restart the service after provisioning\n\
|
||||||
--device-id ID Device id for local-only generation\n\
|
--device-id ID Device id (if not provided positionally)\n\
|
||||||
--ssh-opt ARG Extra ssh/scp option; may be repeated [env: SSH_OPTS]\n\
|
|
||||||
--serial PORT Write install script to a serial shell [env: SERIAL_PORT]\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\
|
--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\
|
--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}")
|
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 {
|
struct GeneratedCredentials {
|
||||||
manual_code: String,
|
manual_code: String,
|
||||||
|
|
@ -648,109 +622,6 @@ fn base64_wrapped(data: &[u8]) -> String {
|
||||||
out
|
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 {
|
fn shell_quote(value: &str) -> String {
|
||||||
let mut quoted = String::with_capacity(value.len() + 2);
|
let mut quoted = String::with_capacity(value.len() + 2);
|
||||||
quoted.push('\'');
|
quoted.push('\'');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user