Compare commits
No commits in common. "460864912c71937178bc258e32510cafd6f6479e" and "3a0d464234b2df01be85da317b2dbbf54e6f4344" have entirely different histories.
460864912c
...
3a0d464234
18 changed files with 81 additions and 595 deletions
13
Cargo.lock
generated
13
Cargo.lock
generated
|
|
@ -906,19 +906,6 @@ dependencies = [
|
|||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "noisebell-relay"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
"noisebell-common",
|
||||
"reqwest",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "noisebell-rss"
|
||||
version = "0.1.0"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"pi/pi-service",
|
||||
"pi/pi-relay",
|
||||
"remote/noisebell-common",
|
||||
"remote/cache-service",
|
||||
"remote/rss-service",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
# Noisebell
|
||||
|
||||
[](https://your-cache-domain.example.com/status)
|
||||
|
||||
Monitors the door at [Noisebridge](https://www.noisebridge.net) and tells you whether it's open or closed.
|
||||
|
||||
A Raspberry Pi reads a magnetic sensor on the door and pushes state changes to a cache server. The cache keeps the latest state and fans updates out to chat integrations such as Discord and Zulip.
|
||||
|
|
|
|||
54
flake.nix
54
flake.nix
|
|
@ -108,23 +108,6 @@
|
|||
cargoExtraArgs = "-p noisebell";
|
||||
};
|
||||
|
||||
piRelayArgs = {
|
||||
inherit src;
|
||||
pname = "noisebell-pi-relay";
|
||||
version = "0.1.0";
|
||||
strictDeps = true;
|
||||
doCheck = false;
|
||||
|
||||
CARGO_BUILD_TARGET = "aarch64-unknown-linux-gnu";
|
||||
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER = "${crossPkgs.stdenv.cc.targetPrefix}cc";
|
||||
TARGET_CC = "${crossPkgs.stdenv.cc.targetPrefix}cc";
|
||||
CC_aarch64_unknown_linux_gnu = "${crossPkgs.stdenv.cc.targetPrefix}cc";
|
||||
HOST_CC = "${pkgs.stdenv.cc.nativePrefix}cc";
|
||||
|
||||
depsBuildBuild = [ crossPkgs.stdenv.cc ];
|
||||
cargoExtraArgs = "-p noisebell-relay";
|
||||
};
|
||||
|
||||
piArtifacts = piCraneLib.buildDepsOnly piArgs;
|
||||
|
||||
noisebell-pi = piCraneLib.buildPackage (
|
||||
|
|
@ -134,15 +117,6 @@
|
|||
}
|
||||
);
|
||||
|
||||
piRelayArtifacts = piCraneLib.buildDepsOnly piRelayArgs;
|
||||
|
||||
noisebell-pi-relay = piCraneLib.buildPackage (
|
||||
piRelayArgs
|
||||
// {
|
||||
cargoArtifacts = piRelayArtifacts;
|
||||
}
|
||||
);
|
||||
|
||||
piStaticArgs = {
|
||||
inherit src;
|
||||
pname = "noisebell-pi-static";
|
||||
|
|
@ -160,23 +134,6 @@
|
|||
cargoExtraArgs = "-p noisebell";
|
||||
};
|
||||
|
||||
piRelayStaticArgs = {
|
||||
inherit src;
|
||||
pname = "noisebell-pi-relay-static";
|
||||
version = "0.1.0";
|
||||
strictDeps = true;
|
||||
doCheck = false;
|
||||
|
||||
CARGO_BUILD_TARGET = "aarch64-unknown-linux-musl";
|
||||
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER = "${muslPkgs.stdenv.cc.targetPrefix}cc";
|
||||
TARGET_CC = "${muslPkgs.stdenv.cc.targetPrefix}cc";
|
||||
CC_aarch64_unknown_linux_musl = "${muslPkgs.stdenv.cc.targetPrefix}cc";
|
||||
HOST_CC = "${pkgs.stdenv.cc.nativePrefix}cc";
|
||||
|
||||
depsBuildBuild = [ muslPkgs.stdenv.cc ];
|
||||
cargoExtraArgs = "-p noisebell-relay";
|
||||
};
|
||||
|
||||
piStaticArtifacts = piCraneLib.buildDepsOnly piStaticArgs;
|
||||
|
||||
noisebell-pi-static = piCraneLib.buildPackage (
|
||||
|
|
@ -186,15 +143,6 @@
|
|||
}
|
||||
);
|
||||
|
||||
piRelayStaticArtifacts = piCraneLib.buildDepsOnly piRelayStaticArgs;
|
||||
|
||||
noisebell-pi-relay-static = piCraneLib.buildPackage (
|
||||
piRelayStaticArgs
|
||||
// {
|
||||
cargoArtifacts = piRelayStaticArtifacts;
|
||||
}
|
||||
);
|
||||
|
||||
piImageBaseModule =
|
||||
{
|
||||
lib,
|
||||
|
|
@ -411,9 +359,7 @@
|
|||
|
||||
packages.aarch64-linux = {
|
||||
noisebell = noisebell-pi;
|
||||
noisebell-relay = noisebell-pi-relay;
|
||||
noisebell-static = noisebell-pi-static;
|
||||
noisebell-relay-static = noisebell-pi-relay-static;
|
||||
default = noisebell-pi;
|
||||
};
|
||||
|
||||
|
|
|
|||
49
pi/README.md
49
pi/README.md
|
|
@ -111,32 +111,25 @@ scripts/deploy-pios-pi.sh pi@10.21.x.x
|
|||
That script:
|
||||
|
||||
1. builds `.#packages.aarch64-linux.noisebell-static` locally
|
||||
2. builds `.#packages.aarch64-linux.noisebell-relay-static` locally
|
||||
3. decrypts the Pi-facing secrets locally with `agenix`
|
||||
4. uploads the binaries and secrets to the Pi
|
||||
5. installs Tailscale and Avahi if needed
|
||||
6. writes `/etc/noisebell/noisebell.env`
|
||||
7. writes `/etc/noisebell/noisebell-relay.env`
|
||||
8. installs `noisebell.service` and `noisebell-relay.service`
|
||||
9. enables and starts both services
|
||||
10. runs `tailscale up` with the decrypted auth key
|
||||
2. decrypts the Pi-facing secrets locally with `agenix`
|
||||
3. uploads the binary and secrets to the Pi
|
||||
4. installs Tailscale and Avahi if needed
|
||||
5. writes `/etc/noisebell/noisebell.env`
|
||||
6. installs `noisebell.service`
|
||||
7. enables and starts the service
|
||||
8. runs `tailscale up` with the decrypted auth key
|
||||
|
||||
## Files written on the Pi
|
||||
|
||||
The deploy script creates:
|
||||
|
||||
- `/opt/noisebell/releases/<timestamp>/noisebell`
|
||||
- `/opt/noisebell/releases/<timestamp>/noisebell-relay`
|
||||
- `/opt/noisebell/current` -> current release symlink
|
||||
- `/etc/noisebell/pi-to-cache-key`
|
||||
- `/etc/noisebell/cache-to-pi-key`
|
||||
- `/etc/noisebell/relay-webhook-secret`
|
||||
- `/etc/noisebell/homeassistant-webhook-id`
|
||||
- `/etc/noisebell/tailscale-auth-key`
|
||||
- `/etc/noisebell/noisebell.env`
|
||||
- `/etc/noisebell/noisebell-relay.env`
|
||||
- `/etc/systemd/system/noisebell.service`
|
||||
- `/etc/systemd/system/noisebell-relay.service`
|
||||
|
||||
All secret files are root-only.
|
||||
|
||||
|
|
@ -178,34 +171,6 @@ The deployed service uses these environment variables:
|
|||
| `NOISEBELL_BIND_ADDRESS` | `0.0.0.0` | HTTP bind address |
|
||||
| `NOISEBELL_ACTIVE_LOW` | `true` | Low GPIO = door open |
|
||||
|
||||
## Relay service configuration
|
||||
|
||||
The optional relay service accepts authenticated webhooks from cache-service and forwards them to Home Assistant on the local network.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `NOISEBELL_RELAY_PORT` | `8090` | HTTP port for the relay webhook endpoint |
|
||||
| `NOISEBELL_RELAY_BIND_ADDRESS` | `0.0.0.0` | HTTP bind address |
|
||||
| `NOISEBELL_RELAY_TARGET_BASE_URL` | `http://homeassistant.local:8123` | Base URL for Home Assistant |
|
||||
| `NOISEBELL_RELAY_TARGET_WEBHOOK_ID` | required | Home Assistant webhook ID |
|
||||
| `NOISEBELL_RELAY_INBOUND_API_KEY` | required | Bearer token expected from cache-service |
|
||||
| `NOISEBELL_RELAY_RETRY_ATTEMPTS` | `3` | Forward retry count |
|
||||
| `NOISEBELL_RELAY_RETRY_BASE_DELAY_SECS` | `1` | Exponential backoff base delay |
|
||||
| `NOISEBELL_RELAY_HTTP_TIMEOUT_SECS` | `10` | Outbound request timeout |
|
||||
|
||||
Example cache target for the relay:
|
||||
|
||||
```nix
|
||||
{
|
||||
services.noisebell-cache.outboundWebhooks = [
|
||||
{
|
||||
url = "http://noisebell-pi.local:8090/webhook";
|
||||
secretFile = /run/agenix/noisebell-relay-webhook-secret;
|
||||
}
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
All endpoints require `Authorization: Bearer <token>`.
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
[package]
|
||||
name = "noisebell-relay"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
axum = "0.8"
|
||||
noisebell-common = { path = "../../remote/noisebell-common" }
|
||||
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "macros", "net", "signal", "time"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use axum::extract::State;
|
||||
use axum::http::{HeaderMap, StatusCode};
|
||||
use axum::routing::{get, post};
|
||||
use axum::{Json, Router};
|
||||
use noisebell_common::{validate_bearer, WebhookPayload};
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppState {
|
||||
client: reqwest::Client,
|
||||
inbound_api_key: String,
|
||||
target_url: String,
|
||||
target_secret: Option<String>,
|
||||
retry_attempts: u32,
|
||||
retry_base_delay_secs: u64,
|
||||
}
|
||||
|
||||
async fn post_webhook(
|
||||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
Json(payload): Json<WebhookPayload>,
|
||||
) -> StatusCode {
|
||||
if !validate_bearer(&headers, &state.inbound_api_key) {
|
||||
return StatusCode::UNAUTHORIZED;
|
||||
}
|
||||
|
||||
info!(status = %payload.status, timestamp = payload.timestamp, "relay received webhook");
|
||||
|
||||
for attempt in 0..=state.retry_attempts {
|
||||
let mut req = state.client.post(&state.target_url).json(&payload);
|
||||
if let Some(secret) = &state.target_secret {
|
||||
req = req.bearer_auth(secret);
|
||||
}
|
||||
|
||||
match req.send().await {
|
||||
Ok(resp) if resp.status().is_success() => {
|
||||
info!(status = %payload.status, "relay forwarded webhook");
|
||||
return StatusCode::OK;
|
||||
}
|
||||
result => {
|
||||
let err_msg = match &result {
|
||||
Ok(resp) => format!("HTTP {}", resp.status()),
|
||||
Err(err) => err.to_string(),
|
||||
};
|
||||
if attempt == state.retry_attempts {
|
||||
error!(error = %err_msg, "relay failed to forward webhook after {} attempts", state.retry_attempts + 1);
|
||||
return StatusCode::BAD_GATEWAY;
|
||||
}
|
||||
|
||||
let delay = Duration::from_secs(state.retry_base_delay_secs * 2u64.pow(attempt));
|
||||
warn!(error = %err_msg, attempt = attempt + 1, "relay forward failed, retrying in {:?}", delay);
|
||||
tokio::time::sleep(delay).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StatusCode::BAD_GATEWAY
|
||||
}
|
||||
|
||||
async fn health() -> StatusCode {
|
||||
StatusCode::OK
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
|
||||
.init();
|
||||
|
||||
let port: u16 = std::env::var("NOISEBELL_RELAY_PORT")
|
||||
.unwrap_or_else(|_| "8090".into())
|
||||
.parse()
|
||||
.context("NOISEBELL_RELAY_PORT must be a valid u16")?;
|
||||
|
||||
let bind_address =
|
||||
std::env::var("NOISEBELL_RELAY_BIND_ADDRESS").unwrap_or_else(|_| "0.0.0.0".into());
|
||||
|
||||
let inbound_api_key = std::env::var("NOISEBELL_RELAY_INBOUND_API_KEY")
|
||||
.context("NOISEBELL_RELAY_INBOUND_API_KEY is required")?;
|
||||
|
||||
let target_base_url = std::env::var("NOISEBELL_RELAY_TARGET_BASE_URL")
|
||||
.unwrap_or_else(|_| "http://homeassistant.local:8123".into())
|
||||
.trim_end_matches('/')
|
||||
.to_string();
|
||||
|
||||
let target_webhook_id = std::env::var("NOISEBELL_RELAY_TARGET_WEBHOOK_ID")
|
||||
.context("NOISEBELL_RELAY_TARGET_WEBHOOK_ID is required")?;
|
||||
|
||||
let target_secret =
|
||||
std::env::var("NOISEBELL_RELAY_TARGET_SECRET").ok().filter(|value| !value.is_empty());
|
||||
|
||||
let retry_attempts: u32 = std::env::var("NOISEBELL_RELAY_RETRY_ATTEMPTS")
|
||||
.unwrap_or_else(|_| "3".into())
|
||||
.parse()
|
||||
.context("NOISEBELL_RELAY_RETRY_ATTEMPTS must be a valid u32")?;
|
||||
|
||||
let retry_base_delay_secs: u64 = std::env::var("NOISEBELL_RELAY_RETRY_BASE_DELAY_SECS")
|
||||
.unwrap_or_else(|_| "1".into())
|
||||
.parse()
|
||||
.context("NOISEBELL_RELAY_RETRY_BASE_DELAY_SECS must be a valid u64")?;
|
||||
|
||||
let http_timeout_secs: u64 = std::env::var("NOISEBELL_RELAY_HTTP_TIMEOUT_SECS")
|
||||
.unwrap_or_else(|_| "10".into())
|
||||
.parse()
|
||||
.context("NOISEBELL_RELAY_HTTP_TIMEOUT_SECS must be a valid u64")?;
|
||||
|
||||
let target_url = format!("{target_base_url}/api/webhook/{target_webhook_id}");
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(http_timeout_secs))
|
||||
.build()
|
||||
.context("failed to build HTTP client")?;
|
||||
|
||||
let state = Arc::new(AppState {
|
||||
client,
|
||||
inbound_api_key,
|
||||
target_url,
|
||||
target_secret,
|
||||
retry_attempts,
|
||||
retry_base_delay_secs,
|
||||
});
|
||||
|
||||
let app = Router::new()
|
||||
.route("/health", get(health))
|
||||
.route("/webhook", post(post_webhook))
|
||||
.with_state(state);
|
||||
|
||||
let listener = tokio::net::TcpListener::bind((&*bind_address, port))
|
||||
.await
|
||||
.context(format!("failed to bind to {bind_address}:{port}"))?;
|
||||
|
||||
info!(port, "relay listening");
|
||||
|
||||
let mut sigterm = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
|
||||
.context("failed to register SIGTERM handler")?;
|
||||
axum::serve(listener, app)
|
||||
.with_graceful_shutdown(async move {
|
||||
sigterm.recv().await;
|
||||
})
|
||||
.await
|
||||
.context("relay server error")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -69,7 +69,6 @@ The flake exports a NixOS module for the hosted remote machine. It imports `agen
|
|||
| `secrets/pi-to-cache-key.age` | Pi + remote | Pi authenticates to cache `/webhook` |
|
||||
| `secrets/cache-to-pi-key.age` | Pi + remote | cache authenticates to Pi GET endpoints |
|
||||
| `secrets/discord-webhook-secret.age` | remote | cache authenticates to Discord bot `/webhook` |
|
||||
| `secrets/relay-webhook-secret.age` | Pi + remote | cache authenticates to the Pi relay `/webhook` |
|
||||
| `secrets/zulip-webhook-secret.age` | remote | cache authenticates to Zulip bridge `/webhook` |
|
||||
| `secrets/discord-token.age` | remote | Discord bot login |
|
||||
| `secrets/zulip-api-key.age` | remote | Zulip bot API authentication |
|
||||
|
|
|
|||
|
|
@ -11,24 +11,11 @@ If the Pi stops responding to polls (configurable threshold, default 3 misses),
|
|||
| Method | Path | Auth | Description |
|
||||
|--------|------|------|-------------|
|
||||
| `GET` | `/status` | — | Current door status (`status`, `since`, `last_checked`) |
|
||||
| `GET` | `/badge.svg` | — | Live README badge with Noisebridge logo |
|
||||
| `POST` | `/webhook` | Bearer | Inbound webhook from the Pi |
|
||||
| `GET` | `/health` | — | Health check |
|
||||
|
||||
`since` is the Pi-reported time when the current state began. `last_checked` is when the cache most recently attempted a poll.
|
||||
|
||||
## Badge
|
||||
|
||||
`/badge.svg` serves a classic shields.io-style SVG badge with the Noisebridge logo and the current cache status (`open`, `closed`, or `offline`).
|
||||
|
||||
Use it in a GitHub README like this:
|
||||
|
||||
```md
|
||||
[](https://your-cache-domain.example.com/status)
|
||||
```
|
||||
|
||||
That keeps the badge clickable and sends readers to the live `/status` endpoint.
|
||||
|
||||
## Configuration
|
||||
|
||||
NixOS options under `services.noisebell-cache`:
|
||||
|
|
|
|||
|
|
@ -18,86 +18,6 @@ static OPEN_PNG: &[u8] = include_bytes!("../assets/open.png");
|
|||
static CLOSED_PNG: &[u8] = include_bytes!("../assets/closed.png");
|
||||
static OFFLINE_PNG: &[u8] = include_bytes!("../assets/offline.png");
|
||||
|
||||
const BADGE_LABEL: &str = "space";
|
||||
const BADGE_HEIGHT: usize = 20;
|
||||
const BADGE_LOGO_WIDTH: usize = 21;
|
||||
const BADGE_LEFT_PADDING: usize = 6;
|
||||
const BADGE_RIGHT_PADDING: usize = 10;
|
||||
const BADGE_FONT_FAMILY: &str = "Verdana,Geneva,DejaVu Sans,sans-serif";
|
||||
const BADGE_LOGO_PATH: &str = "M215.863,155.875V65.776l-8.2,5.819l-22.357,15.869h-5.008V56.782c0.002-5.218-2.145-9.984-5.566-13.397c-3.412-3.421-8.177-5.567-13.396-5.565h-25.241c-1.08-6-3.964-11.391-8.092-15.517c-5.284-5.292-12.638-8.58-20.709-8.577c-8.072-0.003-15.427,3.286-20.71,8.579c-4.13,4.124-7.012,9.515-8.09,15.515H53.25c-5.218-0.001-9.983,2.144-13.396,5.565c-3.421,3.413-5.566,8.179-5.565,13.397v15.939L0.498,81.89l39.388,11.699L1.257,105.063l38.629,11.471L1.257,128.007L39.95,139.5l-5.661,1.694v23.675c-0.001,5.22,2.145,9.985,5.565,13.398c3.414,3.42,8.179,5.566,13.397,5.564h35.144v-5.194c0.001-5.234,2.105-9.927,5.533-13.366c3.437-3.429,8.129-5.533,13.365-5.533c5.234,0.004,9.927,2.107,13.362,5.533c3.429,3.439,5.533,8.132,5.536,13.366v5.194h35.143c5.221,0.002,9.985-2.145,13.397-5.564c3.421-3.416,5.566-8.181,5.566-13.398v-30.688h5.006L215.863,155.875z M192.152,126.306V95.344l13.321-9.455v49.872L192.152,126.306z M181.764,123.796h-21.126V97.854h21.126V123.796z M169.908,164.869c-0.002,2.356-0.954,4.476-2.523,6.053c-1.575,1.57-3.696,2.52-6.051,2.521h-25.241c-1.078-6.002-3.962-11.394-8.091-15.517c-5.287-5.29-12.641-8.58-20.71-8.576c-8.072-0.004-15.426,3.285-20.711,8.576c-4.128,4.123-7.012,9.515-8.09,15.517H53.25c-2.354-0.002-4.473-0.952-6.05-2.521c-1.57-1.577-2.521-3.699-2.523-6.053v-15.94l31.632-9.469l-38.564-11.453l38.628-11.473l-38.628-11.471l38.628-11.474L38.504,82.341l6.173-1.674V56.782c0.002-2.354,0.953-4.473,2.523-6.05c1.577-1.57,3.697-2.522,6.05-2.523h35.145v-5.194c0.001-5.236,2.105-9.927,5.533-13.364c3.437-3.428,8.129-5.532,13.364-5.535c5.235,0.001,9.925,2.107,13.361,5.535c3.431,3.438,5.535,8.13,5.535,13.364v5.194h35.145c2.354,0.001,4.474,0.953,6.051,2.523c1.57,1.578,2.522,3.696,2.522,6.05v30.684h-19.66v46.719h19.66v30.685H169.908z";
|
||||
|
||||
fn escape_xml(text: &str) -> String {
|
||||
text.replace('&', "&")
|
||||
.replace('<', "<")
|
||||
.replace('>', ">")
|
||||
.replace('"', """)
|
||||
.replace('\'', "'")
|
||||
}
|
||||
|
||||
fn badge_color(status: DoorStatus) -> &'static str {
|
||||
match status {
|
||||
DoorStatus::Open => "#34a853",
|
||||
DoorStatus::Closed => "#e05d44",
|
||||
DoorStatus::Offline => "#9f9f9f",
|
||||
}
|
||||
}
|
||||
|
||||
fn badge_text_width(text: &str) -> usize {
|
||||
text.chars().count() * 7 + BADGE_RIGHT_PADDING
|
||||
}
|
||||
|
||||
fn render_badge_svg(status: DoorStatus, summary: &str) -> String {
|
||||
let message = status.as_str();
|
||||
let label_width = BADGE_LEFT_PADDING + BADGE_LOGO_WIDTH + badge_text_width(BADGE_LABEL);
|
||||
let message_width = badge_text_width(message);
|
||||
let total_width = label_width + message_width;
|
||||
let label_text_x = BADGE_LEFT_PADDING + BADGE_LOGO_WIDTH;
|
||||
let label_center = label_text_x + (badge_text_width(BADGE_LABEL) / 2);
|
||||
let message_center = label_width + (message_width / 2);
|
||||
let escaped_summary = escape_xml(summary);
|
||||
|
||||
format!(
|
||||
concat!(
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"{total_width}\" height=\"{height}\" role=\"img\" aria-label=\"space status: {message}\">",
|
||||
"<title>{summary}</title>",
|
||||
"<linearGradient id=\"b\" x2=\"0\" y2=\"100%\">",
|
||||
"<stop offset=\"0\" stop-color=\"#fff\" stop-opacity=\".7\"/>",
|
||||
"<stop offset=\".1\" stop-opacity=\".1\"/>",
|
||||
"<stop offset=\".9\" stop-opacity=\".3\"/>",
|
||||
"<stop offset=\"1\" stop-opacity=\".5\"/>",
|
||||
"</linearGradient>",
|
||||
"<mask id=\"a\"><rect width=\"{total_width}\" height=\"{height}\" rx=\"3\" fill=\"#fff\"/></mask>",
|
||||
"<g mask=\"url(#a)\">",
|
||||
"<rect width=\"{label_width}\" height=\"{height}\" fill=\"#555\"/>",
|
||||
"<rect x=\"{label_width}\" width=\"{message_width}\" height=\"{height}\" fill=\"{color}\"/>",
|
||||
"<rect width=\"{total_width}\" height=\"{height}\" fill=\"url(#b)\"/>",
|
||||
"</g>",
|
||||
"<g fill=\"#fff\" text-anchor=\"middle\" font-family=\"{font_family}\" font-size=\"11\">",
|
||||
"<g transform=\"translate({logo_x} 3) scale(0.055)\">",
|
||||
"<path fill=\"#eb2026\" d=\"{logo_path}\"/>",
|
||||
"</g>",
|
||||
"<text x=\"{label_center}\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">{label}</text>",
|
||||
"<text x=\"{label_center}\" y=\"14\">{label}</text>",
|
||||
"<text x=\"{message_center}\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">{message}</text>",
|
||||
"<text x=\"{message_center}\" y=\"14\">{message}</text>",
|
||||
"</g></svg>",
|
||||
),
|
||||
total_width = total_width,
|
||||
height = BADGE_HEIGHT,
|
||||
label = BADGE_LABEL,
|
||||
message = message,
|
||||
summary = escaped_summary,
|
||||
label_width = label_width,
|
||||
message_width = message_width,
|
||||
color = badge_color(status),
|
||||
font_family = BADGE_FONT_FAMILY,
|
||||
logo_x = BADGE_LEFT_PADDING,
|
||||
logo_path = BADGE_LOGO_PATH,
|
||||
label_center = label_center,
|
||||
message_center = message_center,
|
||||
)
|
||||
}
|
||||
|
||||
pub struct AppState {
|
||||
pub db: Arc<Mutex<rusqlite::Connection>>,
|
||||
pub client: reqwest::Client,
|
||||
|
|
@ -324,35 +244,6 @@ pub async fn get_image(State(state): State<Arc<AppState>>) -> Response {
|
|||
.into_response()
|
||||
}
|
||||
|
||||
pub async fn get_badge(State(state): State<Arc<AppState>>) -> Response {
|
||||
let db = state.db.clone();
|
||||
let status = match tokio::task::spawn_blocking(move || {
|
||||
let conn = db.blocking_lock();
|
||||
db::get_status(&conn)
|
||||
})
|
||||
.await
|
||||
.expect("db task panicked")
|
||||
{
|
||||
Ok(status) => status,
|
||||
Err(e) => {
|
||||
error!(error = %e, "failed to get status for badge");
|
||||
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
|
||||
}
|
||||
};
|
||||
|
||||
let summary = status_summary(status.status, status.since, status.last_checked, unix_now());
|
||||
let badge = render_badge_svg(status.status, &summary);
|
||||
|
||||
(
|
||||
[
|
||||
(header::CONTENT_TYPE, "image/svg+xml; charset=utf-8"),
|
||||
(header::CACHE_CONTROL, "public, max-age=5"),
|
||||
],
|
||||
badge,
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -373,16 +264,4 @@ mod tests {
|
|||
assert!(summary.contains("Last checked"));
|
||||
assert!(summary.contains("55 seconds ago"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_badge_svg_includes_status_and_summary() {
|
||||
let svg = render_badge_svg(DoorStatus::Offline, "Cache can't reach the space right now.");
|
||||
|
||||
assert!(svg.contains("aria-label=\"space status: offline\""));
|
||||
assert!(svg.contains(">space<"));
|
||||
assert!(svg.contains(">offline<"));
|
||||
assert!(svg.contains("Cache can't reach the space right now."));
|
||||
assert!(svg.contains("#9f9f9f"));
|
||||
assert!(svg.contains("#eb2026"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,7 +127,6 @@ async fn main() -> Result<()> {
|
|||
.route("/health", get(api::health))
|
||||
.route("/webhook", post(api::post_webhook))
|
||||
.route("/status", get(api::get_status))
|
||||
.route("/badge.svg", get(api::get_badge))
|
||||
.route("/image", get(api::get_image))
|
||||
.route("/image/open.png", get(api::get_image_open))
|
||||
.route("/image/closed.png", get(api::get_image_closed))
|
||||
|
|
|
|||
|
|
@ -12,20 +12,10 @@ in
|
|||
|
||||
users.groups.noisebell = { };
|
||||
|
||||
users.users = lib.mkMerge [
|
||||
(lib.mkIf cfgCache.enable {
|
||||
noisebell-cache.extraGroups = [ "noisebell" ];
|
||||
})
|
||||
(lib.mkIf cfgRss.enable {
|
||||
noisebell-rss.extraGroups = [ "noisebell" ];
|
||||
})
|
||||
(lib.mkIf cfgDiscord.enable {
|
||||
noisebell-discord.extraGroups = [ "noisebell" ];
|
||||
})
|
||||
(lib.mkIf cfgZulip.enable {
|
||||
noisebell-zulip.extraGroups = [ "noisebell" ];
|
||||
})
|
||||
];
|
||||
users.users.noisebell-cache.extraGroups = lib.mkIf cfgCache.enable [ "noisebell" ];
|
||||
users.users.noisebell-rss.extraGroups = lib.mkIf cfgRss.enable [ "noisebell" ];
|
||||
users.users.noisebell-discord.extraGroups = lib.mkIf cfgDiscord.enable [ "noisebell" ];
|
||||
users.users.noisebell-zulip.extraGroups = lib.mkIf cfgZulip.enable [ "noisebell" ];
|
||||
|
||||
age.secrets.noisebell-pi-to-cache-key = {
|
||||
file = "${self}/secrets/pi-to-cache-key.age";
|
||||
|
|
@ -57,12 +47,6 @@ in
|
|||
mode = "0440";
|
||||
};
|
||||
|
||||
age.secrets.noisebell-relay-webhook-secret = {
|
||||
file = "${self}/secrets/relay-webhook-secret.age";
|
||||
group = "noisebell";
|
||||
mode = "0440";
|
||||
};
|
||||
|
||||
age.secrets.noisebell-zulip-webhook-secret = {
|
||||
file = "${self}/secrets/zulip-webhook-secret.age";
|
||||
group = "noisebell";
|
||||
|
|
@ -84,30 +68,30 @@ in
|
|||
);
|
||||
};
|
||||
|
||||
services.noisebell-rss.cacheUrl = lib.mkIf (cfgRss.enable && cfgCache.enable) (
|
||||
lib.mkDefault "http://127.0.0.1:${toString cfgCache.port}"
|
||||
services.noisebell-rss = lib.mkIf cfgRss.enable (
|
||||
lib.optionalAttrs cfgCache.enable {
|
||||
cacheUrl = lib.mkDefault "http://127.0.0.1:${toString cfgCache.port}";
|
||||
}
|
||||
);
|
||||
|
||||
services.noisebell-discord.discordTokenFile = lib.mkIf cfgDiscord.enable (
|
||||
lib.mkDefault config.age.secrets.noisebell-discord-token.path
|
||||
);
|
||||
services.noisebell-discord.webhookSecretFile = lib.mkIf cfgDiscord.enable (
|
||||
lib.mkDefault config.age.secrets.noisebell-discord-webhook-secret.path
|
||||
);
|
||||
services.noisebell-discord.cacheUrl = lib.mkIf (cfgDiscord.enable && cfgCache.enable) (
|
||||
lib.mkDefault "http://127.0.0.1:${toString cfgCache.port}"
|
||||
);
|
||||
services.noisebell-discord.imageBaseUrl = lib.mkIf (cfgDiscord.enable && cfgCache.enable) (
|
||||
lib.mkDefault "https://${cfgCache.domain}/image"
|
||||
services.noisebell-discord = lib.mkIf cfgDiscord.enable (
|
||||
{
|
||||
discordTokenFile = lib.mkDefault config.age.secrets.noisebell-discord-token.path;
|
||||
webhookSecretFile = lib.mkDefault config.age.secrets.noisebell-discord-webhook-secret.path;
|
||||
}
|
||||
// lib.optionalAttrs cfgCache.enable {
|
||||
cacheUrl = lib.mkDefault "http://127.0.0.1:${toString cfgCache.port}";
|
||||
imageBaseUrl = lib.mkDefault "https://${cfgCache.domain}/image";
|
||||
}
|
||||
);
|
||||
|
||||
services.noisebell-zulip.apiKeyFile = lib.mkIf cfgZulip.enable (
|
||||
lib.mkDefault config.age.secrets.noisebell-zulip-api-key.path
|
||||
);
|
||||
services.noisebell-zulip.webhookSecretFile = lib.mkIf cfgZulip.enable (
|
||||
lib.mkDefault config.age.secrets.noisebell-zulip-webhook-secret.path
|
||||
);
|
||||
services.noisebell-zulip.imageBaseUrl = lib.mkIf (cfgZulip.enable && cfgCache.enable) (
|
||||
lib.mkDefault "https://${cfgCache.domain}/image"
|
||||
services.noisebell-zulip = lib.mkIf cfgZulip.enable (
|
||||
{
|
||||
apiKeyFile = lib.mkDefault config.age.secrets.noisebell-zulip-api-key.path;
|
||||
webhookSecretFile = lib.mkDefault config.age.secrets.noisebell-zulip-webhook-secret.path;
|
||||
}
|
||||
// lib.optionalAttrs cfgCache.enable {
|
||||
imageBaseUrl = lib.mkDefault "https://${cfgCache.domain}/image";
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,9 +10,8 @@ in
|
|||
enable = lib.mkEnableOption "noisebell Zulip bridge";
|
||||
|
||||
domain = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Optional domain for the Caddy virtual host.";
|
||||
type = lib.types.str;
|
||||
description = "Domain for the Caddy virtual host.";
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
|
|
@ -58,54 +57,51 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable (
|
||||
{
|
||||
users.users.noisebell-zulip = {
|
||||
isSystemUser = true;
|
||||
group = "noisebell-zulip";
|
||||
};
|
||||
users.groups.noisebell-zulip = { };
|
||||
config = lib.mkIf cfg.enable {
|
||||
users.users.noisebell-zulip = {
|
||||
isSystemUser = true;
|
||||
group = "noisebell-zulip";
|
||||
};
|
||||
users.groups.noisebell-zulip = { };
|
||||
|
||||
systemd.services.noisebell-zulip = {
|
||||
description = "Noisebell Zulip bridge";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
environment = {
|
||||
NOISEBELL_ZULIP_PORT = toString cfg.port;
|
||||
NOISEBELL_ZULIP_SITE_URL = cfg.zulipUrl;
|
||||
NOISEBELL_ZULIP_BOT_EMAIL = cfg.botEmail;
|
||||
NOISEBELL_ZULIP_STREAM = cfg.stream;
|
||||
NOISEBELL_ZULIP_TOPIC = cfg.topic;
|
||||
NOISEBELL_ZULIP_IMAGE_BASE_URL = cfg.imageBaseUrl;
|
||||
RUST_LOG = "info";
|
||||
};
|
||||
script = ''
|
||||
export NOISEBELL_ZULIP_API_KEY="$(cat ${cfg.apiKeyFile})"
|
||||
export NOISEBELL_ZULIP_WEBHOOK_SECRET="$(cat ${cfg.webhookSecretFile})"
|
||||
exec ${bin}
|
||||
'';
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
Restart = "on-failure";
|
||||
RestartSec = 5;
|
||||
User = "noisebell-zulip";
|
||||
Group = "noisebell-zulip";
|
||||
NoNewPrivileges = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
RestrictSUIDSGID = true;
|
||||
};
|
||||
services.caddy.virtualHosts.${cfg.domain}.extraConfig = ''
|
||||
reverse_proxy localhost:${toString cfg.port}
|
||||
'';
|
||||
|
||||
systemd.services.noisebell-zulip = {
|
||||
description = "Noisebell Zulip bridge";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
environment = {
|
||||
NOISEBELL_ZULIP_PORT = toString cfg.port;
|
||||
NOISEBELL_ZULIP_SITE_URL = cfg.zulipUrl;
|
||||
NOISEBELL_ZULIP_BOT_EMAIL = cfg.botEmail;
|
||||
NOISEBELL_ZULIP_STREAM = cfg.stream;
|
||||
NOISEBELL_ZULIP_TOPIC = cfg.topic;
|
||||
NOISEBELL_ZULIP_IMAGE_BASE_URL = cfg.imageBaseUrl;
|
||||
RUST_LOG = "info";
|
||||
};
|
||||
}
|
||||
// lib.mkIf (cfg.domain != null) {
|
||||
services.caddy.virtualHosts.${cfg.domain}.extraConfig = ''
|
||||
reverse_proxy localhost:${toString cfg.port}
|
||||
script = ''
|
||||
export NOISEBELL_ZULIP_API_KEY="$(cat ${cfg.apiKeyFile})"
|
||||
export NOISEBELL_ZULIP_WEBHOOK_SECRET="$(cat ${cfg.webhookSecretFile})"
|
||||
exec ${bin}
|
||||
'';
|
||||
}
|
||||
);
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
Restart = "on-failure";
|
||||
RestartSec = 5;
|
||||
User = "noisebell-zulip";
|
||||
Group = "noisebell-zulip";
|
||||
NoNewPrivileges = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
RestrictSUIDSGID = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,19 +23,12 @@ SSH_OPTS=(
|
|||
echo "Building static aarch64 Noisebell binary locally..."
|
||||
PACKAGE_PATH=$(nix build .#packages.aarch64-linux.noisebell-static --print-out-paths --no-link)
|
||||
BIN_PATH="$PACKAGE_PATH/bin/noisebell"
|
||||
RELAY_PACKAGE_PATH=$(nix build .#packages.aarch64-linux.noisebell-relay-static --print-out-paths --no-link)
|
||||
RELAY_BIN_PATH="$RELAY_PACKAGE_PATH/bin/noisebell-relay"
|
||||
|
||||
if [[ ! -x "$BIN_PATH" ]]; then
|
||||
echo "built binary not found: $BIN_PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -x "$RELAY_BIN_PATH" ]]; then
|
||||
echo "built relay binary not found: $RELAY_BIN_PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v agenix >/dev/null 2>&1; then
|
||||
echo "agenix is required in your shell to decrypt secrets locally" >&2
|
||||
exit 1
|
||||
|
|
@ -46,22 +39,17 @@ echo "Decrypting Pi secrets locally..."
|
|||
cd "$REPO_ROOT/secrets"
|
||||
RULES="$REPO_ROOT/secrets/secrets.nix" agenix -d pi-to-cache-key.age > "$TMP_DIR/pi-to-cache-key"
|
||||
RULES="$REPO_ROOT/secrets/secrets.nix" agenix -d cache-to-pi-key.age > "$TMP_DIR/cache-to-pi-key"
|
||||
RULES="$REPO_ROOT/secrets/secrets.nix" agenix -d relay-webhook-secret.age > "$TMP_DIR/relay-webhook-secret"
|
||||
RULES="$REPO_ROOT/secrets/secrets.nix" agenix -d homeassistant-webhook-id.age > "$TMP_DIR/homeassistant-webhook-id"
|
||||
RULES="$REPO_ROOT/secrets/secrets.nix" agenix -d tailscale-auth-key.age > "$TMP_DIR/tailscale-auth-key"
|
||||
)
|
||||
chmod 600 "$TMP_DIR"/*
|
||||
|
||||
echo "Preparing remote directories on $TARGET_HOST..."
|
||||
ssh "${SSH_OPTS[@]}" "$TARGET_HOST" "mkdir -p '$REMOTE_TMP_DIR' && rm -f '$REMOTE_TMP_DIR/noisebell' '$REMOTE_TMP_DIR/noisebell-relay' '$REMOTE_TMP_DIR/pi-to-cache-key' '$REMOTE_TMP_DIR/cache-to-pi-key' '$REMOTE_TMP_DIR/relay-webhook-secret' '$REMOTE_TMP_DIR/homeassistant-webhook-id' '$REMOTE_TMP_DIR/tailscale-auth-key' && sudo mkdir -p '$REMOTE_RELEASE_DIR' /etc/noisebell /opt/noisebell/releases /var/lib/noisebell"
|
||||
ssh "${SSH_OPTS[@]}" "$TARGET_HOST" "mkdir -p '$REMOTE_TMP_DIR' && rm -f '$REMOTE_TMP_DIR/noisebell' '$REMOTE_TMP_DIR/pi-to-cache-key' '$REMOTE_TMP_DIR/cache-to-pi-key' '$REMOTE_TMP_DIR/tailscale-auth-key' && sudo mkdir -p '$REMOTE_RELEASE_DIR' /etc/noisebell /opt/noisebell/releases /var/lib/noisebell"
|
||||
|
||||
echo "Uploading binary and secret files..."
|
||||
scp "${SSH_OPTS[@]}" "$BIN_PATH" "$TARGET_HOST:$REMOTE_TMP_DIR/noisebell"
|
||||
scp "${SSH_OPTS[@]}" "$RELAY_BIN_PATH" "$TARGET_HOST:$REMOTE_TMP_DIR/noisebell-relay"
|
||||
scp "${SSH_OPTS[@]}" "$TMP_DIR/pi-to-cache-key" "$TARGET_HOST:$REMOTE_TMP_DIR/pi-to-cache-key"
|
||||
scp "${SSH_OPTS[@]}" "$TMP_DIR/cache-to-pi-key" "$TARGET_HOST:$REMOTE_TMP_DIR/cache-to-pi-key"
|
||||
scp "${SSH_OPTS[@]}" "$TMP_DIR/relay-webhook-secret" "$TARGET_HOST:$REMOTE_TMP_DIR/relay-webhook-secret"
|
||||
scp "${SSH_OPTS[@]}" "$TMP_DIR/homeassistant-webhook-id" "$TARGET_HOST:$REMOTE_TMP_DIR/homeassistant-webhook-id"
|
||||
scp "${SSH_OPTS[@]}" "$TMP_DIR/tailscale-auth-key" "$TARGET_HOST:$REMOTE_TMP_DIR/tailscale-auth-key"
|
||||
|
||||
echo "Installing service and Tailscale on $TARGET_HOST..."
|
||||
|
|
@ -88,14 +76,11 @@ fi
|
|||
sudo systemctl enable --now ssh avahi-daemon tailscaled
|
||||
|
||||
sudo install -m 755 "$REMOTE_TMP_DIR/noisebell" "$REMOTE_RELEASE_DIR/noisebell"
|
||||
sudo install -m 755 "$REMOTE_TMP_DIR/noisebell-relay" "$REMOTE_RELEASE_DIR/noisebell-relay"
|
||||
sudo mv "$REMOTE_TMP_DIR/pi-to-cache-key" /etc/noisebell/pi-to-cache-key
|
||||
sudo mv "$REMOTE_TMP_DIR/cache-to-pi-key" /etc/noisebell/cache-to-pi-key
|
||||
sudo mv "$REMOTE_TMP_DIR/relay-webhook-secret" /etc/noisebell/relay-webhook-secret
|
||||
sudo mv "$REMOTE_TMP_DIR/homeassistant-webhook-id" /etc/noisebell/homeassistant-webhook-id
|
||||
sudo mv "$REMOTE_TMP_DIR/tailscale-auth-key" /etc/noisebell/tailscale-auth-key
|
||||
sudo chown root:root /etc/noisebell/pi-to-cache-key /etc/noisebell/cache-to-pi-key /etc/noisebell/relay-webhook-secret /etc/noisebell/homeassistant-webhook-id /etc/noisebell/tailscale-auth-key
|
||||
sudo chmod 600 /etc/noisebell/pi-to-cache-key /etc/noisebell/cache-to-pi-key /etc/noisebell/relay-webhook-secret /etc/noisebell/homeassistant-webhook-id /etc/noisebell/tailscale-auth-key
|
||||
sudo chown root:root /etc/noisebell/pi-to-cache-key /etc/noisebell/cache-to-pi-key /etc/noisebell/tailscale-auth-key
|
||||
sudo chmod 600 /etc/noisebell/pi-to-cache-key /etc/noisebell/cache-to-pi-key /etc/noisebell/tailscale-auth-key
|
||||
|
||||
sudo tee /etc/noisebell/noisebell.env >/dev/null <<'ENVEOF'
|
||||
NOISEBELL_GPIO_PIN=17
|
||||
|
|
@ -111,17 +96,6 @@ RUST_LOG=info
|
|||
ENVEOF
|
||||
sudo chmod 600 /etc/noisebell/noisebell.env
|
||||
|
||||
sudo tee /etc/noisebell/noisebell-relay.env >/dev/null <<'ENVEOF'
|
||||
NOISEBELL_RELAY_PORT=8090
|
||||
NOISEBELL_RELAY_BIND_ADDRESS=0.0.0.0
|
||||
NOISEBELL_RELAY_TARGET_BASE_URL=http://homeassistant.local:8123
|
||||
NOISEBELL_RELAY_RETRY_ATTEMPTS=3
|
||||
NOISEBELL_RELAY_RETRY_BASE_DELAY_SECS=1
|
||||
NOISEBELL_RELAY_HTTP_TIMEOUT_SECS=10
|
||||
RUST_LOG=info
|
||||
ENVEOF
|
||||
sudo chmod 600 /etc/noisebell/noisebell-relay.env
|
||||
|
||||
sudo tee /etc/systemd/system/noisebell.service >/dev/null <<'UNITEOF'
|
||||
[Unit]
|
||||
Description=Noisebell GPIO door monitor
|
||||
|
|
@ -141,29 +115,10 @@ WatchdogSec=30
|
|||
WantedBy=multi-user.target
|
||||
UNITEOF
|
||||
|
||||
sudo tee /etc/systemd/system/noisebell-relay.service >/dev/null <<'UNITEOF'
|
||||
[Unit]
|
||||
Description=Noisebell relay webhook bridge
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
EnvironmentFile=/etc/noisebell/noisebell-relay.env
|
||||
ExecStart=/bin/bash -lc 'export NOISEBELL_RELAY_INBOUND_API_KEY="$$(cat /etc/noisebell/relay-webhook-secret)"; export NOISEBELL_RELAY_TARGET_WEBHOOK_ID="$$(cat /etc/noisebell/homeassistant-webhook-id)"; exec /opt/noisebell/current/noisebell-relay'
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
UNITEOF
|
||||
|
||||
sudo ln -sfn "$REMOTE_RELEASE_DIR" "$REMOTE_CURRENT_LINK"
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable noisebell.service
|
||||
sudo systemctl enable noisebell-relay.service
|
||||
sudo systemctl restart noisebell.service
|
||||
sudo systemctl restart noisebell-relay.service
|
||||
sudo systemctl restart avahi-daemon
|
||||
|
||||
sudo tailscale up --auth-key="$(sudo cat /etc/noisebell/tailscale-auth-key)" --hostname=noisebell-pi || true
|
||||
|
|
|
|||
12
scripts/nhs
12
scripts/nhs
|
|
@ -1,12 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||
REPO_ROOT=$(cd -- "$SCRIPT_DIR/.." && pwd)
|
||||
|
||||
exec nixos-rebuild switch \
|
||||
--flake /home/jet/Documents/extremist-software#extremist-software \
|
||||
--target-host jet@extremist-software \
|
||||
--sudo \
|
||||
--override-input noisebell "$REPO_ROOT" \
|
||||
"$@"
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 Ziw7aw o2OepqUVulb7kJMNRMs3MK4HjD9ufgXgPahtYydBbXk
|
||||
gFdMCu/Wk4q9g7xByhXyRIbdx7V45cwmxVn580WuFwM
|
||||
-> ssh-ed25519 NFB4qA fQRwLbNWTx/FWZBPjpbt4P0a2sZY3QR2szdRe1cfTE4
|
||||
DR0fSdupUNJfpnbB3Ogkp27me4J0IcQ6VVQmBKXlNoI
|
||||
-> X25519 fZhP22fLYaVseoaWRa+VPKqZ4aZFNibHgMp//vOp7AE
|
||||
JtwFvGYJEOD26cBhmSWrCNmFkigb/ku56xNXkYn4xMM
|
||||
--- pPugQ6fRUfnW6dG+GcIHDhAbn6/Za5g1vZmWZGXk3Wk
|
||||
Càü§<EFBFBD>
ìÖW™åQ]Ý1¼<31>`7ü"?‰ú]Uà9z²
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 Ziw7aw s7p6bAEzWqyyF4yGBGyYi4IAVMofpY+vofEdnAUASAo
|
||||
3iFJpniAgjy9r5oSDO0w288S80iKWniV36RUfMRRubc
|
||||
-> ssh-ed25519 NFB4qA eP/M902eua/ytCKABTrgcCV3vKDfuMlvg31b9jIc7E4
|
||||
OI/iXdRD/Km1qMRpx9h2Kabn6HWtwnHstushKZfyi+E
|
||||
-> X25519 dNnIn/Tq/llh8NBvrAMcVeKVMl8C1nq6gCvI+TpxOQI
|
||||
vOLTD/uROCiPnG42MioEqA1A8Ei/acmI75Eg7CIJuL4
|
||||
-> ssh-ed25519 uKftJg K2ZIMhMIEzvaowRkHk90FK9Xkq01qvBh+xZ1tZ5H8Cc
|
||||
QE/BLIS+2W+kdKEaJdgJHoGWMJ3TsVmx3DZ2+B3spDw
|
||||
--- qY2aDrsCm4Xs2AchoDxhQzfhWrGWLJ6Z5t8/4Gc5lF4
|
||||
4!£2ýF³ì[(C}
|
||||
ål<EFBFBD>m‰"×#kæNæL·è1
|
||||
|
|
@ -35,17 +35,6 @@ in
|
|||
jet
|
||||
server
|
||||
];
|
||||
"homeassistant-webhook-id.age".publicKeys = [
|
||||
jet
|
||||
pi
|
||||
piBootstrap
|
||||
];
|
||||
"relay-webhook-secret.age".publicKeys = [
|
||||
jet
|
||||
pi
|
||||
piBootstrap
|
||||
server
|
||||
];
|
||||
"zulip-webhook-secret.age".publicKeys = [
|
||||
jet
|
||||
server
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue