add scripts
This commit is contained in:
parent
3be523891a
commit
e2a3d6e266
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,3 +0,0 @@
|
|||
/.vscode
|
||||
/target
|
||||
/Cargo.lock
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "toes-matter"
|
||||
version = "0.1.1"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
readme = "README.md"
|
||||
|
|
@ -19,7 +19,7 @@ env_logger = "0.11"
|
|||
futures-lite = "2"
|
||||
log = "0.4"
|
||||
rand = { version = "0.8", features = ["std", "std_rng"] }
|
||||
rs-matter = { git = "https://git.fishmt.net/nakajima/rs-matter", branch = "bluez-ios-reconnect", default-features = false, features = [
|
||||
rs-matter = { path = "../../rs-matter/rs-matter", default-features = false, features = [
|
||||
"log",
|
||||
"os",
|
||||
"rustcrypto",
|
||||
|
|
|
|||
21
README.md
21
README.md
|
|
@ -35,4 +35,23 @@ fn main() -> toes_matter::Result<()> {
|
|||
|
||||
## Development credentials
|
||||
|
||||
`generate_credentials()` writes rs-matter's built-in development/test DAC/PAI/CD plus setup QR/manual-code metadata. These are for local development, not production.
|
||||
`generate_credentials()` writes rs-matter's built-in development/test DAC/PAI/CD plus setup QR/manual-code metadata. It creates a random setup passcode/discriminator the first time and reuses an existing `setup.txt` on later runs. These credentials are for local development, not production.
|
||||
|
||||
## Post-flash headless provisioning
|
||||
|
||||
After flashing a Linux device reachable over SSH:
|
||||
|
||||
```bash
|
||||
toes-matter/scripts/provision-device.sh user@device-host device-001
|
||||
```
|
||||
|
||||
The script 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`, and restarts `toes-matter.service` if present.
|
||||
|
||||
Set these if your service uses different paths:
|
||||
|
||||
```bash
|
||||
REMOTE_CREDS_DIR=/var/lib/my-app/creds \
|
||||
REMOTE_STATE_DIR=/var/lib/my-app/state \
|
||||
SERVICE=my-app.service \
|
||||
toes-matter/scripts/provision-device.sh user@device-host device-001
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
[project]
|
||||
name = "toes-matter"
|
||||
auto-tag = true # detect Cargo.toml version, create/push git tag v<version> before releasing to git
|
||||
# binary = "toes-matter" # defaults to project name
|
||||
# package = "toes-matter" # optional workspace package override; auto-detected from binary when unique
|
||||
# binaries = ["toes-matter", "toes-matter-cli"] # optional extra release assets
|
||||
repo = "nakajima/toes-matter"
|
||||
# version-command = "git describe --tags --abbrev=0"
|
||||
|
||||
[build]
|
||||
command = "cargo build --release --target {target}"
|
||||
artifact = "target/{target}/release/{binary}"
|
||||
targets = [
|
||||
"aarch64-unknown-linux-gnu",
|
||||
]
|
||||
|
||||
[git]
|
||||
type = "gitea" # defaults to "github"
|
||||
base-url = "https://git.fishmt.net"
|
||||
# api-base-url = "https://git.example.com/api/v1" # defaults from type/base-url
|
||||
# token-env = "GITEA_TOKEN" # defaults: GITHUB_TOKEN or GITEA_TOKEN
|
||||
|
||||
[channels.git]
|
||||
enabled = true
|
||||
|
||||
# [channels.homebrew]
|
||||
# tap = "owner/homebrew-tap"
|
||||
# formula-name = "toes-matter"
|
||||
|
||||
# [channels.cargo]
|
||||
# crate-name = "toes-matter"
|
||||
|
||||
[channels.curl]
|
||||
|
||||
# [channels.nix]
|
||||
# flake-repo = "owner/nix-repo" # defaults to project repo
|
||||
87
scripts/provision-device.sh
Executable file
87
scripts/provision-device.sh
Executable file
|
|
@ -0,0 +1,87 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Post-flash headless provisioning helper.
|
||||
#
|
||||
# Generates the Matter setup payload on this host, prints the QR/manual code,
|
||||
# and copies the generated creds/config directory to a freshly flashed Linux device.
|
||||
#
|
||||
# Usage:
|
||||
# toes-matter/scripts/provision-device.sh user@device-host [device-id]
|
||||
#
|
||||
# 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_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.
|
||||
# SSH_OPTS Extra ssh/scp options, e.g. '-p 2222'
|
||||
|
||||
if [[ $# -lt 1 || $# -gt 2 ]]; then
|
||||
echo "Usage: $0 user@device-host [device-id]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
TARGET="$1"
|
||||
DEVICE_ID="${2:-$(date +%Y%m%d-%H%M%S)}"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CRATE_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
CREDS_DIR="${CREDS_DIR:-$CRATE_DIR/manufacturing/$DEVICE_ID/creds}"
|
||||
REMOTE_CREDS_DIR="${REMOTE_CREDS_DIR:-/var/lib/toes-matter/creds}"
|
||||
REMOTE_STATE_DIR="${REMOTE_STATE_DIR:-/var/lib/toes-matter/state}"
|
||||
SERVICE="${SERVICE-toes-matter.service}"
|
||||
SSH_OPTS="${SSH_OPTS:-}"
|
||||
REMOTE_TMP="/tmp/toes-matter-creds-$DEVICE_ID-$$"
|
||||
|
||||
# shellcheck disable=SC2206
|
||||
SSH_ARGS=($SSH_OPTS)
|
||||
|
||||
mkdir -p "$CREDS_DIR"
|
||||
|
||||
echo "==> Generating development Matter credentials in $CREDS_DIR"
|
||||
cargo run --quiet --manifest-path "$CRATE_DIR/Cargo.toml" --bin toes-matter-creds -- "$CREDS_DIR"
|
||||
|
||||
SETUP_FILE="$CREDS_DIR/setup.txt"
|
||||
MANUAL_CODE="$(awk -F= '$1 == "manual_code" { print $2 }' "$SETUP_FILE")"
|
||||
QR_CODE="$(awk -F= '$1 == "qr_code" { print $2 }' "$SETUP_FILE")"
|
||||
|
||||
echo
|
||||
echo "==> Pairing info for device $DEVICE_ID"
|
||||
echo "Manual code: $MANUAL_CODE"
|
||||
echo "QR payload : $QR_CODE"
|
||||
|
||||
if command -v qrencode >/dev/null 2>&1; then
|
||||
echo
|
||||
echo "==> QR code"
|
||||
qrencode -t ANSIUTF8 "$QR_CODE"
|
||||
else
|
||||
echo
|
||||
echo "Tip: install qrencode to render the QR in this terminal: sudo apt install qrencode"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "==> Copying creds to $TARGET:$REMOTE_CREDS_DIR"
|
||||
ssh "${SSH_ARGS[@]}" "$TARGET" "rm -rf '$REMOTE_TMP' && mkdir -p '$REMOTE_TMP'"
|
||||
scp "${SSH_ARGS[@]}" -r "$CREDS_DIR"/. "$TARGET:$REMOTE_TMP/"
|
||||
ssh "${SSH_ARGS[@]}" "$TARGET" "set -e
|
||||
sudo rm -rf '$REMOTE_CREDS_DIR'
|
||||
sudo mkdir -p '$(dirname "$REMOTE_CREDS_DIR")' '$REMOTE_STATE_DIR'
|
||||
sudo mv '$REMOTE_TMP' '$REMOTE_CREDS_DIR'
|
||||
sudo chmod -R go-rwx '$REMOTE_CREDS_DIR' '$REMOTE_STATE_DIR'
|
||||
if [ -n '$SERVICE' ]; then
|
||||
if systemctl list-unit-files '$SERVICE' >/dev/null 2>&1 || systemctl status '$SERVICE' >/dev/null 2>&1; then
|
||||
sudo systemctl restart '$SERVICE'
|
||||
else
|
||||
echo 'Service $SERVICE not found; skipping restart'
|
||||
fi
|
||||
fi
|
||||
"
|
||||
|
||||
echo
|
||||
echo "==> Done"
|
||||
echo "Local copy saved at: $CREDS_DIR"
|
||||
echo "Remote app env should use:"
|
||||
echo " TOES_MATTER_CREDS_DIR=$REMOTE_CREDS_DIR"
|
||||
echo " TOES_MATTER_STATE_DIR=$REMOTE_STATE_DIR"
|
||||
17
src/bin/toes-matter-creds.rs
Normal file
17
src/bin/toes-matter-creds.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#![recursion_limit = "256"]
|
||||
|
||||
fn main() -> toes_matter::Result<()> {
|
||||
env_logger::init_from_env(
|
||||
env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"),
|
||||
);
|
||||
|
||||
let mut args = std::env::args_os().skip(1);
|
||||
let dir = args.next().unwrap_or_else(|| "./creds".into());
|
||||
|
||||
if args.next().is_some() {
|
||||
eprintln!("Usage: toes-matter-creds [CREDS_DIR]");
|
||||
std::process::exit(2);
|
||||
}
|
||||
|
||||
futures_lite::future::block_on(toes_matter::generate_credentials(dir))
|
||||
}
|
||||
97
src/lib.rs
97
src/lib.rs
|
|
@ -42,7 +42,9 @@ use rs_matter::pairing::qr::{no_optional_data, CommFlowType, QrPayload, QrTextTy
|
|||
use rs_matter::pairing::DiscoveryCapabilities;
|
||||
use rs_matter::persist::{DirKvBlobStore, SharedKvBlobStore};
|
||||
use rs_matter::respond::DefaultResponder;
|
||||
use rs_matter::sc::pase::MAX_COMM_WINDOW_TIMEOUT_SECS;
|
||||
use rs_matter::sc::pase::{
|
||||
Spake2pVerifierPassword, Spake2pVerifierPasswordRef, MAX_COMM_WINDOW_TIMEOUT_SECS,
|
||||
};
|
||||
use rs_matter::tlv::Nullable;
|
||||
use rs_matter::transport::network::btp::bluez;
|
||||
use rs_matter::transport::network::btp::{AdvData, Btp};
|
||||
|
|
@ -56,7 +58,7 @@ use rs_matter::utils::storage::pooled::PooledBuffers;
|
|||
use rs_matter::utils::sync::blocking::Mutex;
|
||||
use rs_matter::utils::sync::DynBase;
|
||||
use rs_matter::utils::zbus::Connection;
|
||||
use rs_matter::{clusters, devices, root_endpoint, with, Matter, MATTER_PORT};
|
||||
use rs_matter::{clusters, devices, root_endpoint, with, BasicCommData, Matter, MATTER_PORT};
|
||||
|
||||
pub type Result<T> = core::result::Result<T, Error>;
|
||||
|
||||
|
|
@ -141,10 +143,12 @@ pub async fn generate_credentials(path: impl AsRef<Path>) -> Result<()> {
|
|||
path.join("dac-private-key.raw"),
|
||||
TEST_DEV_ATT.dac_priv_key().access(),
|
||||
)?;
|
||||
|
||||
let comm = read_comm_data(path)?.unwrap_or_else(random_comm_data);
|
||||
let payload = QrPayload::new_from_basic_info(
|
||||
DiscoveryCapabilities::BLE,
|
||||
CommFlowType::Standard,
|
||||
TEST_DEV_COMM,
|
||||
comm.clone(),
|
||||
&TEST_DEV_DET,
|
||||
no_optional_data,
|
||||
);
|
||||
|
|
@ -153,11 +157,13 @@ pub async fn generate_credentials(path: impl AsRef<Path>) -> Result<()> {
|
|||
let (qr_text, _) = payload.as_str(&mut qr_buf)?;
|
||||
|
||||
let setup = format!(
|
||||
"setup_passcode=20202021\n\
|
||||
discriminator=3840\n\
|
||||
"setup_passcode={}\n\
|
||||
discriminator={}\n\
|
||||
manual_code={}\n\
|
||||
qr_code={}\n",
|
||||
TEST_DEV_COMM.compute_pretty_pairing_code(),
|
||||
comm_passcode(&comm),
|
||||
comm.discriminator,
|
||||
comm.compute_pretty_pairing_code(),
|
||||
qr_text,
|
||||
);
|
||||
|
||||
|
|
@ -166,6 +172,77 @@ pub async fn generate_credentials(path: impl AsRef<Path>) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn load_comm_data(config: &Config) -> Result<BasicCommData> {
|
||||
match read_comm_data(&config.creds_dir)? {
|
||||
Some(comm) => Ok(comm),
|
||||
None => Ok(TEST_DEV_COMM.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_comm_data(path: &Path) -> Result<Option<BasicCommData>> {
|
||||
let setup_path = path.join("setup.txt");
|
||||
let setup = match std::fs::read_to_string(setup_path) {
|
||||
Ok(setup) => setup,
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
||||
let passcode = parse_setup_u32(&setup, "setup_passcode")?;
|
||||
let discriminator = parse_setup_u32(&setup, "discriminator")?;
|
||||
|
||||
if discriminator > 0x0fff || !valid_setup_passcode(passcode) {
|
||||
return Err(ErrorCode::InvalidData.into());
|
||||
}
|
||||
|
||||
Ok(Some(make_comm_data(passcode, discriminator as u16)))
|
||||
}
|
||||
|
||||
fn parse_setup_u32(setup: &str, key: &str) -> Result<u32> {
|
||||
let value = setup
|
||||
.lines()
|
||||
.find_map(|line| line.strip_prefix(key)?.strip_prefix('='))
|
||||
.ok_or(ErrorCode::InvalidData)?;
|
||||
|
||||
value.parse().map_err(|_| ErrorCode::InvalidData.into())
|
||||
}
|
||||
|
||||
fn random_comm_data() -> BasicCommData {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let passcode = loop {
|
||||
let passcode = (rng.next_u32() % 99_999_998) + 1;
|
||||
if valid_setup_passcode(passcode) {
|
||||
break passcode;
|
||||
}
|
||||
};
|
||||
|
||||
let discriminator = (rng.next_u32() & 0x0fff) as u16;
|
||||
|
||||
make_comm_data(passcode, discriminator)
|
||||
}
|
||||
|
||||
fn make_comm_data(passcode: u32, discriminator: u16) -> BasicCommData {
|
||||
BasicCommData {
|
||||
password: Spake2pVerifierPassword::new_from_ref(Spake2pVerifierPasswordRef::new(
|
||||
&passcode.to_le_bytes(),
|
||||
)),
|
||||
discriminator,
|
||||
}
|
||||
}
|
||||
|
||||
fn comm_passcode(comm: &BasicCommData) -> u32 {
|
||||
u32::from_le_bytes(*comm.password.access())
|
||||
}
|
||||
|
||||
fn valid_setup_passcode(passcode: u32) -> bool {
|
||||
const INVALID: &[u32] = &[
|
||||
0, 11111111, 22222222, 33333333, 44444444, 55555555, 66666666, 77777777, 88888888,
|
||||
99999999, 12345678, 87654321,
|
||||
];
|
||||
|
||||
(1..=99_999_998).contains(&passcode) && !INVALID.contains(&passcode)
|
||||
}
|
||||
|
||||
/// Provision this device over BLE Matter commissioning and Wi-Fi Network Commissioning.
|
||||
///
|
||||
/// Returns immediately if a fabric is already stored in the configured state dir.
|
||||
|
|
@ -218,7 +295,8 @@ async fn run_provision<N>(connection: &Connection, net_ctl: N, config: &Config)
|
|||
where
|
||||
N: NetCtl + WifiDiag + NetChangeNotif,
|
||||
{
|
||||
let mut matter = Matter::new_default(&TEST_DEV_DET, TEST_DEV_COMM, &TEST_DEV_ATT, MATTER_PORT);
|
||||
let comm = load_comm_data(config)?;
|
||||
let mut matter = Matter::new_default(&TEST_DEV_DET, comm.clone(), &TEST_DEV_ATT, MATTER_PORT);
|
||||
let mut networks = WifiNetworks::<3>::new();
|
||||
let mut events: Events = Events::new_default();
|
||||
let mut kv_buf = [0_u8; KV_BUF_SIZE];
|
||||
|
|
@ -281,7 +359,7 @@ where
|
|||
let btp = Btp::new();
|
||||
btp.set_relaxed_mtu_nego(true);
|
||||
|
||||
let adv_data = AdvData::new(&TEST_DEV_DET, TEST_DEV_COMM.discriminator);
|
||||
let adv_data = AdvData::new(&TEST_DEV_DET, comm.discriminator);
|
||||
let mut bluetooth = pin!(bluez::run_peripheral(
|
||||
connection, None, "TOES", &adv_data, &btp
|
||||
));
|
||||
|
|
@ -311,7 +389,8 @@ async fn run_listen<N>(net_ctl: N, config: &Config) -> Result<()>
|
|||
where
|
||||
N: NetCtl + WifiDiag + NetChangeNotif,
|
||||
{
|
||||
let mut matter = Matter::new_default(&TEST_DEV_DET, TEST_DEV_COMM, &TEST_DEV_ATT, MATTER_PORT);
|
||||
let comm = load_comm_data(config)?;
|
||||
let mut matter = Matter::new_default(&TEST_DEV_DET, comm, &TEST_DEV_ATT, MATTER_PORT);
|
||||
let mut networks = WifiNetworks::<3>::new();
|
||||
let mut events: Events = Events::new_default();
|
||||
let mut kv_buf = [0_u8; KV_BUF_SIZE];
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user