feat: update ot synchronous gpio and rotate keys
This commit is contained in:
parent
16ad3c6181
commit
7d538f1942
10 changed files with 61 additions and 48 deletions
10
pi/README.md
10
pi/README.md
|
|
@ -57,7 +57,7 @@ This setup expects SSH key login for user `pi`; it does not configure a password
|
|||
After boot, verify SSH works:
|
||||
|
||||
```sh
|
||||
ssh pi@noisebridge-pi.local
|
||||
ssh pi@noisebell-pi.local
|
||||
```
|
||||
|
||||
## Add the Pi host key to age recipients
|
||||
|
|
@ -67,7 +67,7 @@ The deploy flow decrypts secrets locally on your laptop, but the Pi host key sho
|
|||
Grab the Pi host key:
|
||||
|
||||
```sh
|
||||
ssh-keyscan noisebridge-pi.local 2>/dev/null | grep ed25519
|
||||
ssh-keyscan noisebell-pi.local 2>/dev/null | grep ed25519
|
||||
```
|
||||
|
||||
Add that key to `secrets/secrets.nix` for:
|
||||
|
|
@ -99,7 +99,7 @@ These stay encrypted in git. The deploy script decrypts them locally on your lap
|
|||
From your laptop:
|
||||
|
||||
```sh
|
||||
scripts/deploy-pios-pi.sh pi@noisebridge-pi.local
|
||||
scripts/deploy-pios-pi.sh pi@noisebell-pi.local
|
||||
```
|
||||
|
||||
If you only know the IP:
|
||||
|
|
@ -141,7 +141,7 @@ The deploy script:
|
|||
|
||||
- installs the Tailscale package if missing
|
||||
- enables `tailscaled`
|
||||
- runs `tailscale up --auth-key=... --hostname=noisebridge-pi`
|
||||
- runs `tailscale up --auth-key=... --hostname=noisebell-pi`
|
||||
|
||||
So Tailscale stays part of the base OS, while its auth key is still managed as an encrypted `age` secret in this repo.
|
||||
|
||||
|
|
@ -150,7 +150,7 @@ So Tailscale stays part of the base OS, while its auth key is still managed as a
|
|||
Normal iteration is just rerunning the deploy script:
|
||||
|
||||
```sh
|
||||
scripts/deploy-pios-pi.sh pi@noisebridge-pi.local
|
||||
scripts/deploy-pios-pi.sh pi@noisebell-pi.local
|
||||
```
|
||||
|
||||
That rebuilds the binary locally, uploads a new release, refreshes secrets, and restarts the service.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
{
|
||||
system.stateVersion = "24.11";
|
||||
|
||||
networking.hostName = "noisebridge-pi";
|
||||
networking.hostName = "noisebell-pi";
|
||||
|
||||
networking.wireless = {
|
||||
enable = true;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ edition = "2021"
|
|||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
axum = "0.8"
|
||||
gpiod = "0.3.0"
|
||||
libc = "0.2"
|
||||
noisebell-common = { path = "../../remote/noisebell-common" }
|
||||
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
|
||||
|
|
@ -13,6 +14,5 @@ sd-notify = "0.4"
|
|||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "macros", "net", "sync", "signal", "time"] }
|
||||
tokio-gpiod = "0.3.0"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ use axum::extract::State;
|
|||
use axum::http::{HeaderMap, StatusCode};
|
||||
use axum::routing::get;
|
||||
use axum::{Json, Router};
|
||||
use gpiod::{Bias, Chip, Edge, EdgeDetect, Options};
|
||||
use noisebell_common::{
|
||||
validate_bearer, DoorStatus, PiStatusResponse, SignalLevel, WebhookPayload,
|
||||
};
|
||||
use tokio_gpiod::{Bias, Chip, Edge, EdgeDetect, Options};
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
|
|
@ -135,9 +135,7 @@ async fn main() -> Result<()> {
|
|||
|
||||
info!(gpio_pin, debounce_secs, port, %endpoint_url, "starting noisebell");
|
||||
|
||||
let chip = Chip::new("gpiochip0")
|
||||
.await
|
||||
.context("failed to open gpiochip0")?;
|
||||
let chip = Chip::new("gpiochip0").context("failed to open gpiochip0")?;
|
||||
|
||||
let bias = if active_level == SignalLevel::Low {
|
||||
Bias::PullUp
|
||||
|
|
@ -152,13 +150,11 @@ async fn main() -> Result<()> {
|
|||
.consumer("noisebell");
|
||||
let mut inputs = chip
|
||||
.request_lines(opts)
|
||||
.await
|
||||
.context(format!("failed to request GPIO line {gpio_pin}"))?;
|
||||
|
||||
// Read initial value
|
||||
let initial_values = inputs
|
||||
.get_values([false])
|
||||
.await
|
||||
.context("failed to read initial GPIO value")?;
|
||||
// Value is true when line is active. With Active::High (default),
|
||||
// true means the physical level is high.
|
||||
|
|
@ -187,19 +183,22 @@ async fn main() -> Result<()> {
|
|||
// Sync initial state with the cache on startup
|
||||
let _ = tx.send((initial_state.as_door_status(), now));
|
||||
|
||||
// Spawn async edge detection task
|
||||
// Spawn blocking edge detection task. The async tokio-gpiod path was
|
||||
// returning repeated EAGAIN on Raspberry Pi OS even when no real GPIO
|
||||
// error occurred.
|
||||
let state_for_edges = state.clone();
|
||||
let edge_tx = tx.clone();
|
||||
let edge_handle = tokio::spawn(async move {
|
||||
let mut last_event_time = std::time::Instant::now();
|
||||
let _edge_handle = std::thread::spawn(move || {
|
||||
let mut last_event_time = std::time::Instant::now() - Duration::from_secs(debounce_secs);
|
||||
let debounce = Duration::from_secs(debounce_secs);
|
||||
|
||||
loop {
|
||||
let event = match inputs.read_event().await {
|
||||
let event = match inputs.read_event() {
|
||||
Ok(event) => event,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
|
||||
Err(e) => {
|
||||
error!(error = %e, "failed to read GPIO event");
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
std::thread::sleep(Duration::from_secs(1));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
|
@ -303,7 +302,6 @@ async fn main() -> Result<()> {
|
|||
.context("server error")?;
|
||||
|
||||
info!("shutting down, draining notification queue");
|
||||
edge_handle.abort();
|
||||
let _ = notify_handle.await;
|
||||
|
||||
info!("shutdown complete");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue