From 3a535a57c1b5fa48d5495c31d308b42943f92583 Mon Sep 17 00:00:00 2001 From: Luke Hoersten Date: Tue, 28 Jul 2026 20:13:03 -0500 Subject: Rename project to mattertimectl; default fabric label to Matter Time Controller - Rename crate, binary, and repo from mattertimesync to mattertimectl - Change default fabricLabel to "Matter Time Controller" - Point README at src.nth.io as the main repo, GitHub as the mirror --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 45 ++++++++++++++++++++++------------------- examples/config.example.json | 4 ++-- examples/mattertimectl.service | 15 ++++++++++++++ examples/mattertimectl.timer | 10 +++++++++ examples/mattertimesync.service | 15 -------------- examples/mattertimesync.timer | 10 --------- src/config.rs | 14 ++++++------- src/controller/mod.rs | 4 ++-- src/main.rs | 4 ++-- 11 files changed, 64 insertions(+), 61 deletions(-) create mode 100644 examples/mattertimectl.service create mode 100644 examples/mattertimectl.timer delete mode 100644 examples/mattertimesync.service delete mode 100644 examples/mattertimesync.timer diff --git a/Cargo.lock b/Cargo.lock index d402e52..3ae21ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -946,7 +946,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] -name = "mattertimesync" +name = "mattertimectl" version = "0.1.0" dependencies = [ "anyhow", diff --git a/Cargo.toml b/Cargo.toml index df47edf..0f43824 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "mattertimesync" +name = "mattertimectl" version = "0.1.0" edition = "2024" description = "CLI Matter controller that synchronizes the clocks of Matter devices via the standard Time Synchronization cluster" diff --git a/README.md b/README.md index 0b33e60..85492f4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ -# mattertimesync +# mattertimectl + +> Main repository: [src.nth.io/luke/mattertimectl](https://src.nth.io/luke/mattertimectl). The +> [GitHub mirror](https://github.com/lukehoersten/mattertimectl) is a backup. A small CLI Matter controller that sets the clocks of Matter devices via the standard Time Synchronization cluster. Rust implementation, built on [rs-matter](https://crates.io/crates/rs-matter) @@ -66,21 +69,21 @@ The same principles as the TypeScript implementation this replaces, plus the Rus Local, read-only (no device contact): ```bash -mattertimesync status [--node ] [--json] # config, identity, and per-device sync state +mattertimectl status [--node ] [--json] # config, identity, and per-device sync state ``` Fabric interrogation (connects to the device, reads only): ```bash -mattertimesync inspect [--node ] [--json] +mattertimectl inspect [--node ] [--json] ``` Fabric-writing: ```bash -mattertimesync commission # writes the device's fabric table + local storage -mattertimesync sync [--node ] [--time ] [--json] # set clock, time zone, DST offsets -mattertimesync decommission [--node ] # device(s) drop this controller's fabric +mattertimectl commission # writes the device's fabric table + local storage +mattertimectl sync [--node ] [--time ] [--json] # set clock, time zone, DST offsets +mattertimectl decommission [--node ] # device(s) drop this controller's fabric ``` No command writes local state alone; the registry and sync results change only as part of a @@ -93,12 +96,12 @@ Exit codes: 0 success, 1 runtime failure (timer retries), 2 usage/config error. ## Configuration -`/etc/mattertimesync/config.json` by default; override with `--config `. Same file format as +`/etc/mattertimectl/config.json` by default; override with `--config `. Same file format as the TypeScript implementation (see `examples/config.example.json`): ```json { - "storagePath": "/var/lib/mattertimesync", + "storagePath": "/var/lib/mattertimectl", "timezone": "America/Chicago", "logLevel": "info", "fabricLabel": "My Home" @@ -110,7 +113,7 @@ the TypeScript implementation (see `examples/config.example.json`): | `storagePath` | (required) | Directory for persistent Matter fabric state. Contains secrets. | | `timezone` | `America/Chicago` | IANA time-zone name. Never a fixed UTC offset; DST is derived. | | `logLevel` | `info` | `debug`, `info`, `warn`, or `error`. | -| `fabricLabel` | `Matter Time Sync` | Label other ecosystems show for this controller (max 32 chars). | +| `fabricLabel` | `Matter Time Controller` | Label other ecosystems show for this controller (max 32 chars). | Unknown fields are rejected loudly. Matter requires fabric labels to be unique per device and Apple Home labels its own entry with the home's name, so use a variant like "Lakeside Time Sync" rather @@ -119,7 +122,7 @@ than the home name itself; `sync` pushes label changes to each device. ## Build and deploy ```bash -cargo build --release # target/release/mattertimesync, one self-contained binary +cargo build --release # target/release/mattertimectl, one self-contained binary cargo test ``` @@ -129,30 +132,30 @@ The binary must match the target's architecture; the simplest path is building o ```bash # on the Pi, one-time toolchain install (user-local, ~/.rustup and ~/.cargo) curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal -git clone && cd mattertimesync +git clone && cd mattertimectl ~/.cargo/bin/cargo build --release ``` The target machine needs nothing but the binary (and a tzdb, present on any Linux). On the target: ```bash -sudo install -D -m 0755 mattertimesync /opt/mattertimesync/mattertimesync -sudo useradd --system --home /var/lib/mattertimesync --shell /usr/sbin/nologin mattertimesync -sudo mkdir -p /etc/mattertimesync /var/lib/mattertimesync -sudo chown mattertimesync:mattertimesync /var/lib/mattertimesync -sudo chmod 700 /var/lib/mattertimesync -sudo cp examples/mattertimesync.service examples/mattertimesync.timer /etc/systemd/system/ +sudo install -D -m 0755 mattertimectl /opt/mattertimectl/mattertimectl +sudo useradd --system --home /var/lib/mattertimectl --shell /usr/sbin/nologin mattertimectl +sudo mkdir -p /etc/mattertimectl /var/lib/mattertimectl +sudo chown mattertimectl:mattertimectl /var/lib/mattertimectl +sudo chmod 700 /var/lib/mattertimectl +sudo cp examples/mattertimectl.service examples/mattertimectl.timer /etc/systemd/system/ sudo systemctl daemon-reload ``` Commission (as the service user, within the pairing window opened in the primary ecosystem): ```bash -sudo -u mattertimesync /opt/mattertimesync/mattertimesync \ - --config /etc/mattertimesync/config.json commission 12345678901 +sudo -u mattertimectl /opt/mattertimectl/mattertimectl \ + --config /etc/mattertimectl/config.json commission 12345678901 ``` -Then `sudo systemctl enable --now mattertimesync.timer` (2 minutes after boot, hourly thereafter). +Then `sudo systemctl enable --now mattertimectl.timer` (2 minutes after boot, hourly thereafter). ## Security notes @@ -179,7 +182,7 @@ key, not a storage path. Migration is one re-pairing: additional admin (Matter allows several; the fabric table has at least 5 slots). Both implementations can sync side by side while validating. 2. Once satisfied, `decommission` with the **old** implementation so the device drops its fabric, - point `mattertimesync.service` at this binary, and delete the old storage directory. + point `mattertimectl.service` at this binary, and delete the old storage directory. ## Troubleshooting diff --git a/examples/config.example.json b/examples/config.example.json index 831630c..5409179 100644 --- a/examples/config.example.json +++ b/examples/config.example.json @@ -1,6 +1,6 @@ { - "storagePath": "/var/lib/mattertimesync", + "storagePath": "/var/lib/mattertimectl", "timezone": "America/Chicago", "logLevel": "info", - "fabricLabel": "Rust Matter Time Sync" + "fabricLabel": "Rust Matter Time Controller" } diff --git a/examples/mattertimectl.service b/examples/mattertimectl.service new file mode 100644 index 0000000..43a662b --- /dev/null +++ b/examples/mattertimectl.service @@ -0,0 +1,15 @@ +[Unit] +Description=Matter device clock synchronization +Wants=network-online.target +After=network-online.target time-sync.target + +[Service] +Type=oneshot +User=mattertimectl +Group=mattertimectl +ExecStart=/opt/mattertimectl/mattertimectl --config /etc/mattertimectl/config.json sync +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=strict +ProtectHome=true +ReadWritePaths=/var/lib/mattertimectl diff --git a/examples/mattertimectl.timer b/examples/mattertimectl.timer new file mode 100644 index 0000000..4d7b64a --- /dev/null +++ b/examples/mattertimectl.timer @@ -0,0 +1,10 @@ +[Unit] +Description=Periodic Matter device clock synchronization + +[Timer] +OnBootSec=2min +OnUnitActiveSec=1h +RandomizedDelaySec=5min + +[Install] +WantedBy=timers.target diff --git a/examples/mattertimesync.service b/examples/mattertimesync.service deleted file mode 100644 index 8fc48c1..0000000 --- a/examples/mattertimesync.service +++ /dev/null @@ -1,15 +0,0 @@ -[Unit] -Description=Matter device clock synchronization -Wants=network-online.target -After=network-online.target time-sync.target - -[Service] -Type=oneshot -User=mattertimesync -Group=mattertimesync -ExecStart=/opt/mattertimesync/mattertimesync --config /etc/mattertimesync/config.json sync -NoNewPrivileges=true -PrivateTmp=true -ProtectSystem=strict -ProtectHome=true -ReadWritePaths=/var/lib/mattertimesync diff --git a/examples/mattertimesync.timer b/examples/mattertimesync.timer deleted file mode 100644 index 4d7b64a..0000000 --- a/examples/mattertimesync.timer +++ /dev/null @@ -1,10 +0,0 @@ -[Unit] -Description=Periodic Matter device clock synchronization - -[Timer] -OnBootSec=2min -OnUnitActiveSec=1h -RandomizedDelaySec=5min - -[Install] -WantedBy=timers.target diff --git a/src/config.rs b/src/config.rs index 3e53975..8288d4e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,5 @@ //! Validated JSON configuration, file-compatible with the TypeScript -//! implementation (`/etc/mattertimesync/config.json`, camelCase keys). +//! implementation (`/etc/mattertimectl/config.json`, camelCase keys). //! //! Unknown fields are rejected loudly via serde's `deny_unknown_fields`; //! device membership deliberately lives in controller storage, not here. @@ -10,7 +10,7 @@ use std::path::PathBuf; use jiff::tz::TimeZone; use serde::Deserialize; -pub const DEFAULT_CONFIG_PATH: &str = "/etc/mattertimesync/config.json"; +pub const DEFAULT_CONFIG_PATH: &str = "/etc/mattertimectl/config.json"; /// Matter FabricDescriptorStruct label limit. const FABRIC_LABEL_MAX_LENGTH: usize = 32; @@ -94,7 +94,7 @@ fn default_timezone() -> String { } fn default_fabric_label() -> String { - "Matter Time Sync".into() + "Matter Time Controller".into() } impl Config { @@ -168,18 +168,18 @@ impl Config { mod tests { use super::*; - const MINIMAL: &str = r#"{ "storagePath": "/var/lib/mattertimesync" }"#; + const MINIMAL: &str = r#"{ "storagePath": "/var/lib/mattertimectl" }"#; #[test] fn minimal_config_applies_defaults() { let config = Config::parse(MINIMAL).unwrap(); assert_eq!( config.storage_path, - PathBuf::from("/var/lib/mattertimesync") + PathBuf::from("/var/lib/mattertimectl") ); assert_eq!(config.timezone, "America/Chicago"); assert_eq!(config.log_level, LogLevel::Info); - assert_eq!(config.fabric_label, "Matter Time Sync"); + assert_eq!(config.fabric_label, "Matter Time Controller"); } #[test] @@ -220,7 +220,7 @@ mod tests { let config = Config::parse(r#"{ "storagePath": "/var/lib/$USER/mts" }"#).unwrap(); assert!(config.path_warnings()[0].contains("unexpanded shell variable")); - let config = Config::parse(r#"{ "storagePath": "/var/lib/mattertimesync" }"#).unwrap(); + let config = Config::parse(r#"{ "storagePath": "/var/lib/mattertimectl" }"#).unwrap(); assert!(config.path_warnings().is_empty()); } diff --git a/src/controller/mod.rs b/src/controller/mod.rs index 739615c..c712c68 100644 --- a/src/controller/mod.rs +++ b/src/controller/mod.rs @@ -558,7 +558,7 @@ fn lock_storage(storage: &Path) -> anyhow::Result { let rc = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) }; if rc != 0 { bail!( - "another mattertimesync instance is already running against {}", + "another mattertimectl instance is already running against {}", storage.display() ); } @@ -590,7 +590,7 @@ fn prepare_storage_dir(path: &Path) -> anyhow::Result<()> { .context("chmod storage directory")?; } else { log::warn!( - "storagePath {} already exists and does not look like a mattertimesync storage \ + "storagePath {} already exists and does not look like a mattertimectl storage \ directory; leaving its permissions unchanged (it should be mode 0700, owned by the \ service user)", path.display() diff --git a/src/main.rs b/src/main.rs index f372f8e..d245578 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -//! mattertimesync: a one-shot CLI Matter controller that sets the clocks of +//! mattertimectl: a one-shot CLI Matter controller that sets the clocks of //! Matter devices via the standard Time Synchronization cluster. //! //! # CLI invariants @@ -54,7 +54,7 @@ use crate::time::MatterMicros; #[derive(Parser)] #[command( - name = "mattertimesync", + name = "mattertimectl", version, about, disable_help_subcommand = true -- cgit v1.2.3