feat: deploy onto the pi and add scripts for boot

This commit is contained in:
Jet 2026-03-22 23:24:05 -07:00
parent f4d95c595e
commit 16ad3c6181
No known key found for this signature in database
13 changed files with 399 additions and 175 deletions

View file

@ -14,7 +14,7 @@ Pi (door sensor) ──webhook──> Cache ──webhook──> Discord
| Directory | What it is |
|-----------|------------|
| [`pi/`](pi/) | NixOS config + Rust service for the Pi |
| [`pi/`](pi/) | Raspberry Pi OS base with laptop-built Noisebell deploy |
| [`remote/`](remote/) | Server-side services (cache and Discord bot) |
| [`secrets/`](secrets/) | Shared agenix-encrypted secrets and recipient rules |

155
flake.nix
View file

@ -74,8 +74,17 @@
overlays = [ rust-overlay.overlays.default ];
};
muslPkgs = import nixpkgs {
inherit system;
crossSystem.config = "aarch64-unknown-linux-musl";
overlays = [ rust-overlay.overlays.default ];
};
piRustToolchain = pkgs.rust-bin.stable.latest.default.override {
targets = [ "aarch64-unknown-linux-gnu" ];
targets = [
"aarch64-unknown-linux-gnu"
"aarch64-unknown-linux-musl"
];
};
piCraneLib = (crane.mkLib pkgs).overrideToolchain piRustToolchain;
@ -106,7 +115,33 @@
}
);
bootstrapModule =
piStaticArgs = {
inherit src;
pname = "noisebell-pi-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";
};
piStaticArtifacts = piCraneLib.buildDepsOnly piStaticArgs;
noisebell-pi-static = piCraneLib.buildPackage (
piStaticArgs
// {
cargoArtifacts = piStaticArtifacts;
}
);
piImageBaseModule =
{
lib,
nixos-raspberrypi,
@ -132,66 +167,72 @@
];
boot.kernelParams = lib.mkAfter [ "cfg80211.ieee80211_regdom=US" ];
networking.hostName = "noisebridge-pi";
networking.networkmanager.enable = lib.mkForce false;
networking.wireless = {
enable = true;
networks."Noisebridge".psk = "noisebridge";
};
services.avahi = {
enable = true;
nssmdns4 = true;
openFirewall = true;
};
services.openssh = {
enable = true;
settings = {
PasswordAuthentication = false;
PermitRootLogin = "prohibit-password";
};
};
users.users.root.openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE40ISu3ydCqfdpb26JYD5cIN0Fu0id/FDS+xjB5zpqu"
];
};
flash-bootstrap-sd = pkgs.writeShellApplication {
name = "flash-bootstrap-sd";
flash-pi-sd = pkgs.writeShellApplication {
name = "flash-pi-sd";
runtimeInputs = [
agenix.packages.${system}.default
pkgs.coreutils
pkgs.nix
pkgs.parted
pkgs.systemd
pkgs.util-linux
pkgs.xz
pkgs.zstd
];
text = ''
set -euo pipefail
PARTPROBE=${pkgs.parted}/bin/partprobe
MOUNT=${pkgs.util-linux}/bin/mount
UMOUNT=${pkgs.util-linux}/bin/umount
MOUNTPOINT=${pkgs.util-linux}/bin/mountpoint
FINDMNT=${pkgs.util-linux}/bin/findmnt
UDEVADM=${pkgs.systemd}/bin/udevadm
if [ "$#" -ne 1 ]; then
echo "usage: flash-bootstrap-sd /dev/sdX" >&2
echo "usage: flash-pi-sd /dev/sdX" >&2
exit 1
fi
device="$1"
flake_path=${builtins.toString ./.}
zstd_bin=${pkgs.zstd}/bin/zstd
secrets_dir=${builtins.toString ./secrets}
rules_file=${builtins.toString ./secrets/secrets.nix}
key_name="bootstrap-identity.age"
boot_mount_dir="$(mktemp -d)"
cleanup() {
if "$MOUNTPOINT" -q "$boot_mount_dir"; then
sudo "$UMOUNT" "$boot_mount_dir"
fi
rm -rf "$boot_mount_dir"
}
trap cleanup EXIT
if [ ! -b "$device" ]; then
echo "not a block device: $device" >&2
exit 1
fi
boot_part="''${device}1"
case "$device" in
*[0-9]) boot_part="''${device}p1" ;;
esac
echo "Requesting sudo access before build and flash..."
sudo -v
echo "Sudo authentication successful."
echo "Building bootstrap NixOS Raspberry Pi Zero 2 W image..."
echo "Building full Pi NixOS image..."
image_out="$(nix build \
--print-out-paths \
--cores 0 \
--max-jobs auto \
"$flake_path#nixosConfigurations.bootstrap.config.system.build.sdImage")"
"$flake_path#nixosConfigurations.pi.config.system.build.sdImage")"
image="$(echo "$image_out"/sd-image/*.img*)"
if [ ! -f "$image" ]; then
@ -202,12 +243,30 @@
echo "Flashing $image to $device..."
if [ "''${image##*.}" = "zst" ]; then
"$zstd_bin" -d --stdout "$image" | sudo dd of="$device" bs=16M conv=fsync status=progress
elif [ "''${image##*.}" = "xz" ]; then
xz -d -c "$image" | sudo dd of="$device" bs=16M conv=fsync status=progress
else
sudo dd if="$image" of="$device" bs=16M conv=fsync status=progress
fi
sync
echo "Done. This is the custom bootstrap NixOS image."
sudo "$PARTPROBE" "$device"
sudo "$UDEVADM" settle
if "$FINDMNT" -rn "$boot_part" >/dev/null 2>&1; then
sudo "$UMOUNT" "$boot_part"
fi
echo "Installing bootstrap age identity onto $boot_part..."
sudo "$MOUNT" "$boot_part" "$boot_mount_dir"
(
cd "$secrets_dir"
RULES="$rules_file" agenix -d "$key_name"
) | sudo tee "$boot_mount_dir/noisebell-bootstrap.agekey" >/dev/null
sudo chmod 600 "$boot_mount_dir/noisebell-bootstrap.agekey"
sync
echo "Done. This is the full Pi NixOS image."
'';
};
@ -288,7 +347,7 @@
inherit
noisebell-cache
noisebell-discord
flash-bootstrap-sd
flash-pi-sd
pi-serial
;
default = noisebell-cache;
@ -296,6 +355,7 @@
packages.aarch64-linux = {
noisebell = noisebell-pi;
noisebell-static = noisebell-pi-static;
default = noisebell-pi;
};
@ -313,33 +373,26 @@
};
};
nixosConfigurations.pi = nixpkgs.lib.nixosSystem {
system = "aarch64-linux";
modules = [
agenix.nixosModules.default
nixos-hardware.nixosModules.raspberry-pi-3
(import ./pi/module.nix {
pkg = noisebell-pi;
rev = self.shortRev or "dirty";
})
./pi/configuration.nix
./pi/hardware-configuration.nix
];
};
nixosConfigurations.bootstrap = nixos-raspberrypi.lib.nixosSystem {
nixosConfigurations.pi = nixos-raspberrypi.lib.nixosSystem {
specialArgs = {
inherit nixos-raspberrypi;
};
modules = [
nixos-raspberrypi.nixosModules.sd-image
bootstrapModule
agenix.nixosModules.default
piImageBaseModule
(import ./pi/module.nix {
pkg = noisebell-pi;
rev = self.shortRev or "dirty";
})
./pi/configuration.nix
];
};
devShells.${system}.default = craneLib.devShell {
packages = [
flash-bootstrap-sd
agenix.packages.${system}.default
flash-pi-sd
pi-serial
pkgs.nix
pkgs.parted
@ -350,9 +403,9 @@
};
apps.${system} = {
flash-bootstrap-sd = {
flash-pi-sd = {
type = "app";
program = "${flash-bootstrap-sd}/bin/flash-bootstrap-sd";
program = "${flash-pi-sd}/bin/flash-pi-sd";
};
pi-serial = {

View file

@ -1,145 +1,181 @@
# Pi
Rust service and NixOS config for the Raspberry Pi at Noisebridge. Reads a magnetic door sensor via GPIO, serves the current state over HTTP, and pushes changes to the cache service.
Rust service and deployment workflow for the Raspberry Pi at Noisebridge.
Runs NixOS with Tailscale for remote access and agenix for secrets.
The current recommended setup is:
## How it works
1. run Raspberry Pi OS Lite on the Pi
2. keep the Pi itself free of Nix
3. build a static `aarch64` Noisebell binary on your laptop with Nix
4. copy the binary, secrets, and systemd service to the Pi over SSH
The service watches a GPIO pin for rising/falling edges with a configurable debounce. When the door state changes, it:
This avoids the Raspberry Pi Zero 2 W NixOS boot issues while still keeping the application build reproducible.
1. Updates in-memory state (atomics)
2. POSTs `{"status": "open", "timestamp": ...}` to the cache service with a Bearer token
3. Retries with exponential backoff on failure
## What stays on Raspberry Pi OS
On startup it also syncs the initial state.
- bootloader
- kernel
- firmware
- Wi-Fi and local networking
- SSH base access
- Tailscale package/runtime
- Avahi package/runtime
## Setup
## What Nix manages
### Prerequisites
- building a static `noisebell` binary for `aarch64-linux`
- the exact app binary you deploy
- encrypted secrets in the repo
- repeatable deployment from your laptop
If you're building on an x86_64 machine, you need binfmt emulation for aarch64. On NixOS, add this to your system config and rebuild:
## Initial Pi OS setup
```nix
boot.binfmt.emulatedSystems = [ "aarch64-linux" ];
```
### 1. Flash the SD card
Preferred: one command builds the bootstrap image, writes it to the SD card, and installs the
bootstrap agenix identity onto the boot partition so the full Pi system can come up on first boot:
### 1. Flash Raspberry Pi OS Lite
```sh
nix run .#flash-pi-sd -- /dev/sdX
curl -L "https://downloads.raspberrypi.org/raspios_lite_arm64_latest" | xz -d -c | sudo dd of=/dev/sdb bs=16M conv=fsync status=progress && sync
```
This bootstrap image already includes the normal Noisebell service, Tailscale, and the Pi config.
### 2. Configure the flashed SD card
Manual build if you need it:
Configure it for:
- Wi-Fi on `Noisebridge`
- SSH enabled
- serial enabled if you want a recovery console
The helper script is:
```sh
nix build .#nixosConfigurations.bootstrap.config.system.build.sdImage
dd if=result/sd-image/*.img of=/dev/sdX bs=4M status=progress
sudo scripts/configure-pios-sd.sh /run/media/jet/bootfs /run/media/jet/rootfs
```
Boot the Pi. It connects to the Noisebridge WiFi automatically.
This setup expects SSH key login for user `pi`; it does not configure a password.
### 2. SSH host key
### 3. Boot the Pi and verify SSH
Grab the key and add it to `secrets/secrets.nix`:
After boot, verify SSH works:
```sh
ssh pi@noisebridge-pi.local
```
## Add the Pi host key to age recipients
The deploy flow decrypts secrets locally on your laptop, but the Pi host key should still be a recipient for the Pi-facing secrets so the repo stays accurate.
Grab the Pi host key:
```sh
ssh-keyscan noisebridge-pi.local 2>/dev/null | grep ed25519
```
```nix
# secrets/secrets.nix
let
pi = "ssh-ed25519 AAAA...";
in
{
"api-key.age".publicKeys = [ pi ];
"inbound-api-key.age".publicKeys = [ pi ];
"tailscale-auth-key.age".publicKeys = [ pi ];
}
```
Add that key to `secrets/secrets.nix` for:
### 3. Create secrets
- `pi-to-cache-key.age`
- `cache-to-pi-key.age`
- `tailscale-auth-key.age`
```sh
cd secrets
agenix -e api-key.age # key for POSTing to the cache
agenix -e inbound-api-key.age # key the cache uses to poll us
agenix -e tailscale-auth-key.age # tailscale auth key
```
### 4. Bootstrap agenix identity
The Pi uses a dedicated bootstrap age identity stored at `/boot/noisebell-bootstrap.agekey` to
decrypt its runtime secrets, so first boot does not depend on the machine's freshly generated SSH
host key.
To refresh recipients after changing `secrets/secrets.nix`:
Then refresh recipients if needed:
```sh
cd secrets
agenix -r
```
If you use `nix run .#flash-pi-sd -- /dev/sdX`, this file is installed automatically.
To install the bootstrap identity manually onto a flashed card before first boot:
## Edit secrets
```sh
cd secrets
agenix -d bootstrap-identity.age > /boot/noisebell-bootstrap.agekey
chmod 600 /boot/noisebell-bootstrap.agekey
agenix -e pi-to-cache-key.age
agenix -e cache-to-pi-key.age
agenix -e tailscale-auth-key.age
```
### 5. SSH access
These stay encrypted in git. The deploy script decrypts them locally on your laptop and copies the plaintext files to the Pi as root-only files.
Add your public key to `configuration.nix`:
## Deploy to Raspberry Pi OS
```nix
users.users.root.openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAA..."
];
```
### 6. Deploy
After first boot, the Pi should already be running the normal service stack from the flashed image.
Use this only for later updates:
From your laptop:
```sh
nixos-rebuild switch --flake .#pi --target-host root@noisebell
scripts/deploy-pios-pi.sh pi@noisebridge-pi.local
```
## Configuration
If you only know the IP:
Options under `services.noisebell` in `flake.nix`:
```sh
scripts/deploy-pios-pi.sh pi@10.21.x.x
```
| Option | Default | Description |
That script:
1. builds `.#packages.aarch64-linux.noisebell-static` locally
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/current` -> current release symlink
- `/etc/noisebell/pi-to-cache-key`
- `/etc/noisebell/cache-to-pi-key`
- `/etc/noisebell/tailscale-auth-key`
- `/etc/noisebell/noisebell.env`
- `/etc/systemd/system/noisebell.service`
All secret files are root-only.
## Tailscale
Tailscale is kept on Raspberry Pi OS rather than NixOS.
The deploy script:
- installs the Tailscale package if missing
- enables `tailscaled`
- runs `tailscale up --auth-key=... --hostname=noisebridge-pi`
So Tailscale stays part of the base OS, while its auth key is still managed as an encrypted `age` secret in this repo.
## Later updates
Normal iteration is just rerunning the deploy script:
```sh
scripts/deploy-pios-pi.sh pi@noisebridge-pi.local
```
That rebuilds the binary locally, uploads a new release, refreshes secrets, and restarts the service.
## Service configuration
The deployed service uses these environment variables:
| Variable | Default | Description |
|---|---|---|
| `endpointUrl` | required | Webhook URL to POST state changes to |
| `apiKeyFile` | required | Outbound API key file (agenix secret) |
| `inboundApiKeyFile` | required | Inbound API key file for GET auth |
| `gpioPin` | `17` | GPIO pin number |
| `debounceSecs` | `5` | Debounce delay in seconds |
| `port` | `8080` | HTTP server port |
| `retryAttempts` | `3` | Webhook retry count |
| `retryBaseDelaySecs` | `1` | Exponential backoff base delay |
| `httpTimeoutSecs` | `10` | Outbound request timeout |
| `bindAddress` | `0.0.0.0` | HTTP bind address |
| `activeLow` | `true` | Low GPIO = door open (depends on wiring) |
| `restartDelaySecs` | `5` | systemd restart delay on failure |
| `watchdogSecs` | `30` | systemd watchdog timeout |
| `NOISEBELL_GPIO_PIN` | `17` | GPIO pin number |
| `NOISEBELL_DEBOUNCE_SECS` | `5` | Debounce delay in seconds |
| `NOISEBELL_PORT` | `80` | HTTP server port |
| `NOISEBELL_ENDPOINT_URL` | required | Webhook URL to POST state changes to |
| `NOISEBELL_RETRY_ATTEMPTS` | `3` | Webhook retry count |
| `NOISEBELL_RETRY_BASE_DELAY_SECS` | `1` | Exponential backoff base delay |
| `NOISEBELL_HTTP_TIMEOUT_SECS` | `10` | Outbound request timeout |
| `NOISEBELL_BIND_ADDRESS` | `0.0.0.0` | HTTP bind address |
| `NOISEBELL_ACTIVE_LOW` | `true` | Low GPIO = door open |
## API
All endpoints require `Authorization: Bearer <token>`.
**`GET /`** — door state
**`GET /`**
```json
{"status": "open", "timestamp": 1710000000}

View file

@ -7,7 +7,6 @@ HOSTNAME=noisebridge-pi
WIFI_SSID=Noisebridge
WIFI_PASSWORD=noisebridge
PI_USERNAME=pi
PI_PASSWORD=noisebridge
SSH_KEY='ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE40ISu3ydCqfdpb26JYD5cIN0Fu0id/FDS+xjB5zpqu'
if [[ $EUID -ne 0 ]]; then
@ -15,14 +14,26 @@ if [[ $EUID -ne 0 ]]; then
exit 1
fi
if [[ ! -d "$BOOTFS" || ! -d "$ROOTFS" ]]; then
echo "Expected mounted boot and root partitions." >&2
if [[ ! -d "$BOOTFS" ]]; then
echo "Expected mounted boot partition." >&2
echo "Boot: $BOOTFS" >&2
echo "Root: $ROOTFS" >&2
exit 1
fi
PASSWORD_HASH=$(mkpasswd -m sha-512 "$PI_PASSWORD")
CONFIG_TXT="$BOOTFS/config.txt"
if [[ ! -f "$CONFIG_TXT" && -f "$BOOTFS/firmware/config.txt" ]]; then
CONFIG_TXT="$BOOTFS/firmware/config.txt"
fi
if [[ ! -f "$CONFIG_TXT" ]]; then
echo "Could not find config.txt in $BOOTFS" >&2
exit 1
fi
ROOTFS_READY=0
if [[ -d "$ROOTFS" && -f "$ROOTFS/etc/shadow" ]]; then
ROOTFS_READY=1
fi
cat > "$BOOTFS/network-config" <<EOF
network:
@ -59,12 +70,10 @@ dsmode: local
instance_id: ${HOSTNAME}-bootstrap-1
EOF
grep -q '^enable_uart=1$' "$BOOTFS/config.txt" || printf '\nenable_uart=1\n' >> "$BOOTFS/config.txt"
grep -q '^enable_uart=1$' "$CONFIG_TXT" || printf '\nenable_uart=1\n' >> "$CONFIG_TXT"
: > "$BOOTFS/ssh"
cat > "$BOOTFS/userconf.txt" <<EOF
${PI_USERNAME}:${PASSWORD_HASH}
EOF
if [[ "$ROOTFS_READY" -eq 1 ]]; then
cat > "$ROOTFS/etc/hostname" <<EOF
${HOSTNAME}
EOF
@ -85,6 +94,7 @@ EOF
chown -R 1000:1000 "$ROOTFS/home/pi/.ssh"
chmod 700 "$ROOTFS/home/pi/.ssh"
chmod 600 "$ROOTFS/home/pi/.ssh/authorized_keys"
fi
sync
@ -94,5 +104,7 @@ echo "- Wi-Fi: ${WIFI_SSID}"
echo "- SSH enabled on first boot"
echo "- Serial UART enabled"
echo "- Username: ${PI_USERNAME}"
echo "- Password: ${PI_PASSWORD}"
echo "- Pi user authorized key installed"
if [[ "$ROOTFS_READY" -ne 1 ]]; then
echo "- Note: rootfs was not mounted; only boot partition config was updated"
fi

6
scripts/deploy-full-pi.sh Executable file
View file

@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
TARGET_HOST=${1:-root@noisebridge-pi.local}
exec nixos-rebuild switch --flake ".#pi" --target-host "$TARGET_HOST"

117
scripts/deploy-pios-pi.sh Executable file
View file

@ -0,0 +1,117 @@
#!/usr/bin/env bash
set -euo pipefail
TARGET_HOST=${1:-pi@noisebridge-pi.local}
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
REPO_ROOT=$(cd -- "$SCRIPT_DIR/.." && pwd)
RELEASE_ID=${RELEASE_ID:-$(date +%Y%m%d-%H%M%S)}
REMOTE_RELEASE_DIR=${REMOTE_RELEASE_DIR:-/opt/noisebell/releases/$RELEASE_ID}
REMOTE_CURRENT_LINK=${REMOTE_CURRENT_LINK:-/opt/noisebell/current}
REMOTE_TMP_DIR=${REMOTE_TMP_DIR:-/home/pi/noisebell-deploy-tmp}
TMP_DIR=$(mktemp -d)
cleanup() {
rm -rf "$TMP_DIR"
}
trap cleanup EXIT
SSH_OPTS=(
-o StrictHostKeyChecking=accept-new
)
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"
if [[ ! -x "$BIN_PATH" ]]; then
echo "built binary not found: $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
fi
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 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/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[@]}" "$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/tailscale-auth-key" "$TARGET_HOST:$REMOTE_TMP_DIR/tailscale-auth-key"
echo "Installing service and Tailscale on $TARGET_HOST..."
ssh "${SSH_OPTS[@]}" "$TARGET_HOST" "REMOTE_RELEASE_DIR='$REMOTE_RELEASE_DIR' REMOTE_CURRENT_LINK='$REMOTE_CURRENT_LINK' REMOTE_TMP_DIR='$REMOTE_TMP_DIR' bash -s" <<'EOF'
set -euo pipefail
sudo apt-get update
sudo apt-get install -y curl rsync avahi-daemon
if ! command -v tailscale >/dev/null 2>&1; then
curl -fsSL https://tailscale.com/install.sh | sh
fi
sudo systemctl enable --now ssh avahi-daemon tailscaled
sudo install -m 755 "$REMOTE_TMP_DIR/noisebell" "$REMOTE_RELEASE_DIR/noisebell"
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/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/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
NOISEBELL_DEBOUNCE_SECS=5
NOISEBELL_PORT=80
NOISEBELL_RETRY_ATTEMPTS=3
NOISEBELL_RETRY_BASE_DELAY_SECS=1
NOISEBELL_HTTP_TIMEOUT_SECS=10
NOISEBELL_ENDPOINT_URL=https://noisebell.extremist.software/webhook
NOISEBELL_BIND_ADDRESS=0.0.0.0
NOISEBELL_ACTIVE_LOW=true
RUST_LOG=info
ENVEOF
sudo chmod 600 /etc/noisebell/noisebell.env
sudo tee /etc/systemd/system/noisebell.service >/dev/null <<UNITEOF
[Unit]
Description=Noisebell GPIO door monitor
After=network-online.target tailscaled.service
Wants=network-online.target
[Service]
Type=notify
NotifyAccess=all
EnvironmentFile=/etc/noisebell/noisebell.env
ExecStart=/bin/bash -lc 'export NOISEBELL_API_KEY="$$(cat /etc/noisebell/pi-to-cache-key)"; export NOISEBELL_INBOUND_API_KEY="$$(cat /etc/noisebell/cache-to-pi-key)"; exec ${REMOTE_CURRENT_LINK}/noisebell'
Restart=on-failure
RestartSec=5
WatchdogSec=30
[Install]
WantedBy=multi-user.target
UNITEOF
sudo ln -sfn "$REMOTE_RELEASE_DIR" "$REMOTE_CURRENT_LINK"
sudo systemctl daemon-reload
sudo systemctl enable --now noisebell.service
sudo tailscale up --auth-key="$(sudo cat /etc/noisebell/tailscale-auth-key)" --hostname=noisebridge-pi || true
rmdir "$REMOTE_TMP_DIR" 2>/dev/null || true
echo "Noisebell deployed on Raspberry Pi OS."
EOF
echo "Done."

Binary file not shown.

Binary file not shown.

View file

@ -1,8 +1,7 @@
age-encryption.org/v1
-> ssh-ed25519 Ziw7aw W4nd317o0ON6n006hwUNIops3L7VngNqDqJ1bG7tCyg
xyjvZOOzA0u3e+Cba99LR8J5JAkl6muHuspJ+b8dEog
-> ssh-ed25519 uKftJg bX0GHUPdD4hR2yxLNx8ho689or7FNHXPA8iV9Af1q2s
B91XIdsnHfAKdKfu8jmHKKptA5OomQ5sHvXfLLUeNbs
--- 8zoH1w5ywsbDN5Xt50sE4BUgXoq12mr5b4rSVYMLEB8
CŽb;*œã‚´*;í5œ#BL^ñƒT¯úE¢<45>%ÚÎçáZ8†r‰wݧ#r-îG¢éÖKÔ—t…|Ê
³ÿÑòâÙ_ßÄÊ»\s»N´CUG |º‡*i(6ÇÔA•k/µy
-> ssh-ed25519 Ziw7aw 756HU1sPe5g3sa0YYfzMnXiToT5K+nfAPhfABEetgRI
E8dqhn7hN77qM0PhHMEAsZySd1hfk0w1tlsiWj4aEOQ
-> ssh-ed25519 uKftJg eGfmzvHseauAFPOR1QXfdmaQy5TjpNsoBWq27mbO50w
KRuGUW65uQ5+IdREyg6X1oj0P5IkuuxFEl1WylGpAHc
--- 2Ya08payqNiMCEqBXrbKEA53ETupxwgUNRcMNu9IP6k
&°‰¢ÏvÄ<76>tBšL˜.¬HéÚ“Y P®Sç3Tó¹•4䱟D¬ s,9Ñeã4TpåÌ,Gë?KtJmwF' ®0³ÉXy»<á÷<E28099>î>½\Áç^þ°m»ÜrÊÏùµŸÏü

Binary file not shown.

View file

@ -1,11 +1,12 @@
age-encryption.org/v1
-> ssh-ed25519 Ziw7aw TMz4xBHmy5btMk5ETWyvjZcjj0QwF3F6Iqt2QmkwVwk
KvcY409ZakEUO7OrmDrwseMQv/w7i3B7uDiXjls/5qE
-> ssh-ed25519 jcT/MQ Y5TqVlHy3fM+CmQIBu1x18C3wjHtRZCHJ9dhFyVAR0I
WPqxEZrpAm0wLYT4s9e1uxSuDjhwUwIOL4BvDtvqytU
-> X25519 ZqSHgIStt6Ru3osVvMA1M5sydoY+CeZ56temQyCFIVY
4tPA6VITlAzJxCFGVreKK1B6rrHm+ka4ELwnzYrMKbQ
-> ssh-ed25519 uKftJg OR8VgPUuEvS/0Gc5c92IlAp4DKKYcRzBbSh1tX9ddzg
DZYYx9ngwEUTmj/JaP2XnCQHjpPY8WYgOEDlOfZPLeA
--- 4iDfaqdSLiW0doVijoZC5ckxiCmsmVWJi5Kvaxic2Ng
è÷VV;2†h<>&e@sÏx#d=Y?iðw½´ÉÍF<15>OÝc×/ûš(™¨àœ›¼føkØíƒ—¿}[¡¤'=B@ô‡¨Â!wÔ
-> ssh-ed25519 Ziw7aw ZaLRvgj6V9ukim0lfxHftVUvCXi7tIXPn5O/2nzQqCE
cem5AxKMkYOs8iifYP80hkbr5km7bFOdjCt7Ym6lQcs
-> ssh-ed25519 NFB4qA ssMeOzGjehzTeppIGHpzPViIKObSwnXw6OZ1DfXs6Ew
Y813udN4YGDMszEC8FVZz7Na6XQigVNFTdusLomMusg
-> X25519 qmoLWSdRljn6daPlUyqk9TOOvBaUx42CvqcpXe/xUCE
7xMN5RbYnpgw3+/pHyCiEyEhyUmQOwa1zSlAbuVwlQo
-> ssh-ed25519 uKftJg Fv8M0RogkcYWd46bJY3OJCoCFAW8QMjzLueDZowylSA
R3w6E2RvDmgaKKhxqWHjEeIQxNSCHzX7+nLb3Ls+iHs
--- 13dp1N6I6pPdDx+FrxsT+ZS5rsFfrK3x0F7Rs6vN6/I
ÖBI¬8Ż
9”<39>úŠ/ěšÂÚqXžŕÄÇ[𮠼™Í ;ž§Ž†*Ć·¨»ńJ2|ŃS(đ‚$Ó©ŘÎ<>SÁ*m

View file

@ -1,6 +1,6 @@
let
jet = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE40ISu3ydCqfdpb26JYD5cIN0Fu0id/FDS+xjB5zpqu";
pi = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKmJFZAVJUjDziQ7XytVhQIv+Pm9hnbVU0flxZK17K5E";
pi = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEfZfAQEFy8QU5P7deC2vWPN76YpUKcBF8fiWwuANumG";
server = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAING219cDKTDLaZefmqvOHfXvYloA/ErsCGE0pM022vlB";
piBootstrap = "age1sfqn46dgztr35dtyhpzyzzam5m6kcqu495qs7fcsdxtac56pc4dsj3t862";
in

Binary file not shown.