Compare commits
No commits in common. "16ad3c61813ede8308921ac37fce15c761979794" and "cf3c5ef1f54820c8d713391f3f3eb1197f345b77" have entirely different histories.
16ad3c6181
...
cf3c5ef1f5
22 changed files with 173 additions and 795 deletions
1
.envrc
1
.envrc
|
|
@ -1,2 +1 @@
|
|||
export NIX_CONFIG="eval-cache = false"
|
||||
use flake
|
||||
|
|
|
|||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -2,5 +2,3 @@ target/
|
|||
result
|
||||
result-*
|
||||
.direnv
|
||||
pi-serial-*.log
|
||||
serial-*.log
|
||||
|
|
|
|||
|
|
@ -14,10 +14,7 @@ Pi (door sensor) ──webhook──> Cache ──webhook──> Discord
|
|||
|
||||
| Directory | What it is |
|
||||
|-----------|------------|
|
||||
| [`pi/`](pi/) | Raspberry Pi OS base with laptop-built Noisebell deploy |
|
||||
| [`pi/`](pi/) | NixOS config + Rust service for the Pi |
|
||||
| [`remote/`](remote/) | Server-side services (cache and Discord bot) |
|
||||
| [`secrets/`](secrets/) | Shared agenix-encrypted secrets and recipient rules |
|
||||
|
||||
Each directory has its own README with setup and configuration details.
|
||||
|
||||
For hosted deployment, another repo such as `../extremist-software` imports `noisebell.nixosModules.default`. That host repo provides deployment-specific values like domains, ports, and the Pi address, while the Noisebell module itself points `agenix` at the encrypted files in `secrets/` and consumes the decrypted runtime files on the target machine.
|
||||
|
|
|
|||
111
flake.lock
generated
111
flake.lock
generated
|
|
@ -23,22 +23,6 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"argononed": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1729566243,
|
||||
"narHash": "sha256-DPNI0Dpk5aym3Baf5UbEe5GENDrSmmXVdriRSWE+rgk=",
|
||||
"owner": "nvmd",
|
||||
"repo": "argononed",
|
||||
"rev": "16dbee54d49b66d5654d228d1061246b440ef7cf",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nvmd",
|
||||
"repo": "argononed",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"crane": {
|
||||
"locked": {
|
||||
"lastModified": 1773857772,
|
||||
|
|
@ -76,21 +60,6 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat": {
|
||||
"locked": {
|
||||
"lastModified": 1767039857,
|
||||
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"home-manager": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
|
|
@ -112,79 +81,13 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixos-hardware": {
|
||||
"locked": {
|
||||
"lastModified": 1774018263,
|
||||
"narHash": "sha256-HHYEwK1A22aSaxv2ibhMMkKvrDGKGlA/qObG4smrSqc=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixos-hardware",
|
||||
"rev": "2d4b4717b2534fad5c715968c1cece04a172b365",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "master",
|
||||
"repo": "nixos-hardware",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixos-images": {
|
||||
"inputs": {
|
||||
"nixos-stable": [
|
||||
"nixos-raspberrypi",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixos-unstable": [
|
||||
"nixos-raspberrypi",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1747747741,
|
||||
"narHash": "sha256-LUOH27unNWbGTvZFitHonraNx0JF/55h30r9WxqrznM=",
|
||||
"owner": "nvmd",
|
||||
"repo": "nixos-images",
|
||||
"rev": "cbbd6db325775096680b65e2a32fb6187c09bbb4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nvmd",
|
||||
"ref": "sdimage-installer",
|
||||
"repo": "nixos-images",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixos-raspberrypi": {
|
||||
"inputs": {
|
||||
"argononed": "argononed",
|
||||
"flake-compat": "flake-compat",
|
||||
"nixos-images": "nixos-images",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1773704510,
|
||||
"narHash": "sha256-Kq0WPitNekYzouyd8ROlZb63cpSg/+Ep2XxkV0YlABU=",
|
||||
"owner": "nvmd",
|
||||
"repo": "nixos-raspberrypi",
|
||||
"rev": "b5c77d506bed55250a4642ce6c8b395dd29ef06b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nvmd",
|
||||
"ref": "main",
|
||||
"repo": "nixos-raspberrypi",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1773821835,
|
||||
"narHash": "sha256-TJ3lSQtW0E2JrznGVm8hOQGVpXjJyXY2guAxku2O9A4=",
|
||||
"lastModified": 1773646010,
|
||||
"narHash": "sha256-iYrs97hS7p5u4lQzuNWzuALGIOdkPXvjz7bviiBjUu8=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b40629efe5d6ec48dd1efba650c797ddbd39ace0",
|
||||
"rev": "5b2c2d84341b2afb5647081c1386a80d7a8d8605",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -198,8 +101,6 @@
|
|||
"inputs": {
|
||||
"agenix": "agenix",
|
||||
"crane": "crane",
|
||||
"nixos-hardware": "nixos-hardware",
|
||||
"nixos-raspberrypi": "nixos-raspberrypi",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-overlay": "rust-overlay"
|
||||
}
|
||||
|
|
@ -211,11 +112,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1774149071,
|
||||
"narHash": "sha256-SYp8NyzwfCO3Guqmu9hPRHR1hwESlQia5nNz3lYo2qA=",
|
||||
"lastModified": 1773803479,
|
||||
"narHash": "sha256-GD6i1F2vrSxbsmbS92+8+x3DbHOJ+yrS78Pm4xigW4M=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "6a031966eab3bfaa19be9e261eed5b8a79c04b18",
|
||||
"rev": "f17186f52e82ec5cf40920b58eac63b78692ac7c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
|||
243
flake.nix
243
flake.nix
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
nixos-hardware.url = "github:NixOS/nixos-hardware/master";
|
||||
agenix = {
|
||||
url = "github:ryantm/agenix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
|
|
@ -13,18 +12,12 @@
|
|||
url = "github:oxalica/rust-overlay";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
nixos-raspberrypi = {
|
||||
url = "github:nvmd/nixos-raspberrypi/main";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
nixos-hardware,
|
||||
nixos-raspberrypi,
|
||||
agenix,
|
||||
crane,
|
||||
rust-overlay,
|
||||
|
|
@ -74,17 +67,8 @@
|
|||
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"
|
||||
"aarch64-unknown-linux-musl"
|
||||
];
|
||||
targets = [ "aarch64-unknown-linux-gnu" ];
|
||||
};
|
||||
|
||||
piCraneLib = (crane.mkLib pkgs).overrideToolchain piRustToolchain;
|
||||
|
|
@ -115,61 +99,6 @@
|
|||
}
|
||||
);
|
||||
|
||||
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,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
{
|
||||
imports = with nixos-raspberrypi.nixosModules; [
|
||||
default
|
||||
usb-gadget-ethernet
|
||||
];
|
||||
|
||||
system.stateVersion = "24.11";
|
||||
|
||||
boot.loader.raspberry-pi = {
|
||||
variant = "02";
|
||||
firmwarePackage = nixos-raspberrypi.packages.${pkgs.stdenv.hostPlatform.system}.raspberrypifw;
|
||||
bootloader = "kernel";
|
||||
};
|
||||
boot.supportedFilesystems = lib.mkForce [
|
||||
"ext4"
|
||||
"vfat"
|
||||
];
|
||||
boot.kernelParams = lib.mkAfter [ "cfg80211.ieee80211_regdom=US" ];
|
||||
|
||||
networking.networkmanager.enable = lib.mkForce false;
|
||||
};
|
||||
|
||||
flash-pi-sd = pkgs.writeShellApplication {
|
||||
name = "flash-pi-sd";
|
||||
runtimeInputs = [
|
||||
|
|
@ -179,7 +108,6 @@
|
|||
pkgs.parted
|
||||
pkgs.systemd
|
||||
pkgs.util-linux
|
||||
pkgs.xz
|
||||
pkgs.zstd
|
||||
];
|
||||
text = ''
|
||||
|
|
@ -191,6 +119,7 @@
|
|||
MOUNTPOINT=${pkgs.util-linux}/bin/mountpoint
|
||||
FINDMNT=${pkgs.util-linux}/bin/findmnt
|
||||
UDEVADM=${pkgs.systemd}/bin/udevadm
|
||||
ZSTD=${pkgs.zstd}/bin/zstd
|
||||
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "usage: flash-pi-sd /dev/sdX" >&2
|
||||
|
|
@ -199,17 +128,18 @@
|
|||
|
||||
device="$1"
|
||||
flake_path=${builtins.toString ./.}
|
||||
zstd_bin=${pkgs.zstd}/bin/zstd
|
||||
secrets_dir=${builtins.toString ./secrets}
|
||||
rules_file=${builtins.toString ./secrets/secrets.nix}
|
||||
image_link="$(mktemp -u /tmp/noisebell-sd-image.XXXXXX)"
|
||||
mount_dir="$(mktemp -d)"
|
||||
secrets_dir="${builtins.toString ./secrets}"
|
||||
key_name="bootstrap-identity.age"
|
||||
boot_mount_dir="$(mktemp -d)"
|
||||
rules_file="${builtins.toString ./secrets/secrets.nix}"
|
||||
|
||||
cleanup() {
|
||||
if "$MOUNTPOINT" -q "$boot_mount_dir"; then
|
||||
sudo "$UMOUNT" "$boot_mount_dir"
|
||||
if "$MOUNTPOINT" -q "$mount_dir"; then
|
||||
sudo "$UMOUNT" "$mount_dir"
|
||||
fi
|
||||
rm -rf "$boot_mount_dir"
|
||||
rm -rf "$mount_dir"
|
||||
rm -f "$image_link"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
|
|
@ -223,30 +153,20 @@
|
|||
*[0-9]) boot_part="''${device}p1" ;;
|
||||
esac
|
||||
|
||||
echo "Requesting sudo access before build and flash..."
|
||||
sudo -v
|
||||
echo "Sudo authentication successful."
|
||||
echo "Building bootstrap SD image..."
|
||||
nix build "$flake_path#nixosConfigurations.bootstrap.config.system.build.sdImage" -o "$image_link"
|
||||
|
||||
echo "Building full Pi NixOS image..."
|
||||
image_out="$(nix build \
|
||||
--print-out-paths \
|
||||
--cores 0 \
|
||||
--max-jobs auto \
|
||||
"$flake_path#nixosConfigurations.pi.config.system.build.sdImage")"
|
||||
|
||||
image="$(echo "$image_out"/sd-image/*.img*)"
|
||||
image="$(echo "$image_link"/sd-image/*.img*)"
|
||||
if [ ! -f "$image" ]; then
|
||||
echo "failed to locate SD image under $image_out/sd-image" >&2
|
||||
echo "failed to locate SD image under $image_link/sd-image" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
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
|
||||
"$ZSTD" -d --stdout "$image" | sudo dd of="$device" bs=4M conv=fsync status=progress
|
||||
else
|
||||
sudo dd if="$image" of="$device" bs=16M conv=fsync status=progress
|
||||
sudo dd if="$image" of="$device" bs=4M conv=fsync status=progress
|
||||
fi
|
||||
sync
|
||||
|
||||
|
|
@ -258,104 +178,26 @@
|
|||
fi
|
||||
|
||||
echo "Installing bootstrap age identity onto $boot_part..."
|
||||
sudo "$MOUNT" "$boot_part" "$boot_mount_dir"
|
||||
sudo "$MOUNT" "$boot_part" "$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"
|
||||
) | sudo tee "$mount_dir/noisebell-bootstrap.agekey" >/dev/null
|
||||
sudo chmod 600 "$mount_dir/noisebell-bootstrap.agekey"
|
||||
sync
|
||||
|
||||
echo "Done. This is the full Pi NixOS image."
|
||||
'';
|
||||
};
|
||||
|
||||
pi-serial = pkgs.writeShellApplication {
|
||||
name = "pi-serial";
|
||||
runtimeInputs = [
|
||||
pkgs.coreutils
|
||||
pkgs.procps
|
||||
pkgs.tio
|
||||
pkgs.util-linux
|
||||
];
|
||||
text = ''
|
||||
set -euo pipefail
|
||||
|
||||
baud_rate=115200
|
||||
data_bits=8
|
||||
stop_bits=1
|
||||
parity=none
|
||||
flow_control=none
|
||||
serial_tools="screen tio minicom picocom"
|
||||
|
||||
port=""
|
||||
|
||||
if [ "$#" -gt 1 ]; then
|
||||
echo "usage: pi-serial [device]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$#" -eq 1 ]; then
|
||||
port="$1"
|
||||
else
|
||||
for candidate in /dev/serial/by-id/* /dev/ttyUSB* /dev/ttyACM*; do
|
||||
if [ -e "$candidate" ]; then
|
||||
port="$candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -z "$port" ]; then
|
||||
echo "No serial device found." >&2
|
||||
echo "Check the adapter and run: ls -l /dev/serial/by-id /dev/ttyUSB* /dev/ttyACM* 2>/dev/null" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_file="pi-serial-$(date +%Y%m%d-%H%M%S).log"
|
||||
|
||||
echo "Stopping old serial sessions for this user"
|
||||
for tool in $serial_tools; do
|
||||
pkill -x -u "$USER" "$tool" 2>/dev/null || true
|
||||
done
|
||||
sleep 1
|
||||
|
||||
echo "Waiting for port to become free: $port"
|
||||
while fuser "$port" >/dev/null 2>&1; do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "Using serial port: $port"
|
||||
echo "Logging to: $log_file"
|
||||
echo "Start this before powering the Pi."
|
||||
|
||||
exec sudo tio \
|
||||
-b "$baud_rate" \
|
||||
-d "$data_bits" \
|
||||
-s "$stop_bits" \
|
||||
-p "$parity" \
|
||||
-f "$flow_control" \
|
||||
-t \
|
||||
--log \
|
||||
--log-file "$log_file" \
|
||||
"$port"
|
||||
echo "Done. You can now move the card to the Pi and boot it."
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
packages.${system} = {
|
||||
inherit
|
||||
noisebell-cache
|
||||
noisebell-discord
|
||||
flash-pi-sd
|
||||
pi-serial
|
||||
;
|
||||
inherit noisebell-cache noisebell-discord flash-pi-sd;
|
||||
default = noisebell-cache;
|
||||
};
|
||||
|
||||
packages.aarch64-linux = {
|
||||
noisebell = noisebell-pi;
|
||||
noisebell-static = noisebell-pi-static;
|
||||
default = noisebell-pi;
|
||||
};
|
||||
|
||||
|
|
@ -366,52 +208,51 @@
|
|||
imports = [
|
||||
(import ./remote/cache-service/module.nix noisebell-cache)
|
||||
(import ./remote/discord-bot/module.nix noisebell-discord)
|
||||
(import ./remote/hosted-module.nix {
|
||||
inherit self agenix;
|
||||
})
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
nixosConfigurations.pi = nixos-raspberrypi.lib.nixosSystem {
|
||||
specialArgs = {
|
||||
inherit nixos-raspberrypi;
|
||||
};
|
||||
nixosConfigurations.pi = nixpkgs.lib.nixosSystem {
|
||||
system = "aarch64-linux";
|
||||
modules = [
|
||||
nixos-raspberrypi.nixosModules.sd-image
|
||||
agenix.nixosModules.default
|
||||
piImageBaseModule
|
||||
(import ./pi/module.nix {
|
||||
pkg = noisebell-pi;
|
||||
rev = self.shortRev or "dirty";
|
||||
})
|
||||
./pi/configuration.nix
|
||||
./pi/hardware-configuration.nix
|
||||
];
|
||||
};
|
||||
|
||||
nixosConfigurations.bootstrap = nixpkgs.lib.nixosSystem {
|
||||
system = "aarch64-linux";
|
||||
modules = [
|
||||
agenix.nixosModules.default
|
||||
(import ./pi/module.nix {
|
||||
pkg = noisebell-pi;
|
||||
rev = self.shortRev or "dirty";
|
||||
})
|
||||
./pi/bootstrap.nix
|
||||
];
|
||||
};
|
||||
|
||||
devShells.${system}.default = craneLib.devShell {
|
||||
packages = [
|
||||
agenix.packages.${system}.default
|
||||
flash-pi-sd
|
||||
pi-serial
|
||||
pkgs.nix
|
||||
pkgs.parted
|
||||
pkgs.rust-analyzer
|
||||
pkgs.tio
|
||||
pkgs.systemd
|
||||
pkgs.util-linux
|
||||
pkgs.zstd
|
||||
agenix.packages.${system}.default
|
||||
];
|
||||
};
|
||||
|
||||
apps.${system} = {
|
||||
flash-pi-sd = {
|
||||
type = "app";
|
||||
program = "${flash-pi-sd}/bin/flash-pi-sd";
|
||||
};
|
||||
|
||||
pi-serial = {
|
||||
type = "app";
|
||||
program = "${pi-serial}/bin/pi-serial";
|
||||
};
|
||||
apps.${system}.flash-pi-sd = {
|
||||
type = "app";
|
||||
program = "${flash-pi-sd}/bin/flash-pi-sd";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
206
pi/README.md
206
pi/README.md
|
|
@ -1,181 +1,145 @@
|
|||
# Pi
|
||||
|
||||
Rust service and deployment workflow for the Raspberry Pi at Noisebridge.
|
||||
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.
|
||||
|
||||
The current recommended setup is:
|
||||
Runs NixOS with Tailscale for remote access and agenix for secrets.
|
||||
|
||||
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
|
||||
## How it works
|
||||
|
||||
This avoids the Raspberry Pi Zero 2 W NixOS boot issues while still keeping the application build reproducible.
|
||||
The service watches a GPIO pin for rising/falling edges with a configurable debounce. When the door state changes, it:
|
||||
|
||||
## What stays on Raspberry Pi OS
|
||||
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
|
||||
|
||||
- bootloader
|
||||
- kernel
|
||||
- firmware
|
||||
- Wi-Fi and local networking
|
||||
- SSH base access
|
||||
- Tailscale package/runtime
|
||||
- Avahi package/runtime
|
||||
On startup it also syncs the initial state.
|
||||
|
||||
## What Nix manages
|
||||
## Setup
|
||||
|
||||
- building a static `noisebell` binary for `aarch64-linux`
|
||||
- the exact app binary you deploy
|
||||
- encrypted secrets in the repo
|
||||
- repeatable deployment from your laptop
|
||||
### Prerequisites
|
||||
|
||||
## Initial Pi OS setup
|
||||
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:
|
||||
|
||||
### 1. Flash Raspberry Pi OS Lite
|
||||
|
||||
```sh
|
||||
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
|
||||
```nix
|
||||
boot.binfmt.emulatedSystems = [ "aarch64-linux" ];
|
||||
```
|
||||
|
||||
### 2. Configure the flashed SD card
|
||||
### 1. Flash the SD card
|
||||
|
||||
Configure it for:
|
||||
|
||||
- Wi-Fi on `Noisebridge`
|
||||
- SSH enabled
|
||||
- serial enabled if you want a recovery console
|
||||
|
||||
The helper script is:
|
||||
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:
|
||||
|
||||
```sh
|
||||
sudo scripts/configure-pios-sd.sh /run/media/jet/bootfs /run/media/jet/rootfs
|
||||
nix run .#flash-pi-sd -- /dev/sdX
|
||||
```
|
||||
|
||||
This setup expects SSH key login for user `pi`; it does not configure a password.
|
||||
This bootstrap image already includes the normal Noisebell service, Tailscale, and the Pi config.
|
||||
|
||||
### 3. Boot the Pi and verify SSH
|
||||
|
||||
After boot, verify SSH works:
|
||||
Manual build if you need it:
|
||||
|
||||
```sh
|
||||
ssh pi@noisebridge-pi.local
|
||||
nix build .#nixosConfigurations.bootstrap.config.system.build.sdImage
|
||||
dd if=result/sd-image/*.img of=/dev/sdX bs=4M status=progress
|
||||
```
|
||||
|
||||
## Add the Pi host key to age recipients
|
||||
Boot the Pi. It connects to the Noisebridge WiFi automatically.
|
||||
|
||||
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.
|
||||
### 2. SSH host key
|
||||
|
||||
Grab the Pi host key:
|
||||
Grab the key and add it to `secrets/secrets.nix`:
|
||||
|
||||
```sh
|
||||
ssh-keyscan noisebridge-pi.local 2>/dev/null | grep ed25519
|
||||
```
|
||||
|
||||
Add that key to `secrets/secrets.nix` for:
|
||||
```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 ];
|
||||
}
|
||||
```
|
||||
|
||||
- `pi-to-cache-key.age`
|
||||
- `cache-to-pi-key.age`
|
||||
- `tailscale-auth-key.age`
|
||||
### 3. Create secrets
|
||||
|
||||
Then refresh recipients if needed:
|
||||
```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`:
|
||||
|
||||
```sh
|
||||
cd secrets
|
||||
agenix -r
|
||||
```
|
||||
|
||||
## Edit secrets
|
||||
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:
|
||||
|
||||
```sh
|
||||
cd secrets
|
||||
agenix -e pi-to-cache-key.age
|
||||
agenix -e cache-to-pi-key.age
|
||||
agenix -e tailscale-auth-key.age
|
||||
agenix -d bootstrap-identity.age > /boot/noisebell-bootstrap.agekey
|
||||
chmod 600 /boot/noisebell-bootstrap.agekey
|
||||
```
|
||||
|
||||
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.
|
||||
### 5. SSH access
|
||||
|
||||
## Deploy to Raspberry Pi OS
|
||||
Add your public key to `configuration.nix`:
|
||||
|
||||
From your laptop:
|
||||
```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:
|
||||
|
||||
```sh
|
||||
scripts/deploy-pios-pi.sh pi@noisebridge-pi.local
|
||||
nixos-rebuild switch --flake .#pi --target-host root@noisebell
|
||||
```
|
||||
|
||||
If you only know the IP:
|
||||
## Configuration
|
||||
|
||||
```sh
|
||||
scripts/deploy-pios-pi.sh pi@10.21.x.x
|
||||
```
|
||||
Options under `services.noisebell` in `flake.nix`:
|
||||
|
||||
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 |
|
||||
| Option | Default | Description |
|
||||
|---|---|---|
|
||||
| `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 |
|
||||
| `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 |
|
||||
|
||||
## API
|
||||
|
||||
All endpoints require `Authorization: Bearer <token>`.
|
||||
|
||||
**`GET /`**
|
||||
**`GET /`** — door state
|
||||
|
||||
```json
|
||||
{"status": "open", "timestamp": 1710000000}
|
||||
|
|
|
|||
8
pi/bootstrap.nix
Normal file
8
pi/bootstrap.nix
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{ modulesPath, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
"${modulesPath}/installer/sd-card/sd-image-aarch64.nix"
|
||||
./configuration.nix
|
||||
];
|
||||
}
|
||||
|
|
@ -10,11 +10,7 @@
|
|||
networks."Noisebridge".psk = "noisebridge";
|
||||
};
|
||||
|
||||
services.avahi = {
|
||||
enable = true;
|
||||
nssmdns4 = true;
|
||||
openFirewall = true;
|
||||
};
|
||||
services.avahi.enable = false;
|
||||
|
||||
# Decrypted at runtime by agenix
|
||||
age.identityPaths = [
|
||||
|
|
@ -34,23 +30,11 @@
|
|||
inboundApiKeyFile = config.age.secrets.cache-to-pi-key.path;
|
||||
};
|
||||
|
||||
hardware.enableRedistributableFirmware = true;
|
||||
|
||||
nix.settings.experimental-features = [
|
||||
"nix-command"
|
||||
"flakes"
|
||||
];
|
||||
|
||||
boot.kernelParams = [
|
||||
"console=ttyS0,115200n8"
|
||||
"console=ttyAMA0,115200n8"
|
||||
"console=tty0"
|
||||
"boot.shell_on_fail"
|
||||
"loglevel=7"
|
||||
"systemd.log_level=debug"
|
||||
"systemd.log_target=console"
|
||||
];
|
||||
|
||||
services.tailscale = {
|
||||
enable = true;
|
||||
authKeyFile = config.age.secrets.tailscale-auth-key.path;
|
||||
|
|
@ -58,18 +42,6 @@
|
|||
|
||||
services.openssh.enable = true;
|
||||
|
||||
system.activationScripts.pi-zero-2-dtb-compat.text = ''
|
||||
for dtb_dir in /boot/nixos/*-dtbs/broadcom; do
|
||||
if [ -d "$dtb_dir" ]; then
|
||||
if [ -f "$dtb_dir/bcm2837-rpi-zero-2-w.dtb" ] && [ ! -e "$dtb_dir/bcm2837-rpi-zero-2.dtb" ]; then
|
||||
cp "$dtb_dir/bcm2837-rpi-zero-2-w.dtb" "$dtb_dir/bcm2837-rpi-zero-2.dtb"
|
||||
elif [ -f "$dtb_dir/bcm2837-rpi-3-b.dtb" ] && [ ! -e "$dtb_dir/bcm2837-rpi-zero-2.dtb" ]; then
|
||||
cp "$dtb_dir/bcm2837-rpi-3-b.dtb" "$dtb_dir/bcm2837-rpi-zero-2.dtb"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
'';
|
||||
|
||||
networking.firewall = {
|
||||
trustedInterfaces = [ "tailscale0" ];
|
||||
allowedUDPPorts = [ config.services.tailscale.port ];
|
||||
|
|
|
|||
|
|
@ -1,10 +1,4 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
modulesPath,
|
||||
...
|
||||
}:
|
||||
{ config, lib, pkgs, modulesPath, ... }:
|
||||
|
||||
{
|
||||
imports = [ ];
|
||||
|
|
|
|||
1
pi/result
Symbolic link
1
pi/result
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
/nix/store/pmrzmz2b2hsffk62icl3c0ni56gpi3qs-nixos-image-sd-card-26.05.20260308.9dcb002-aarch64-linux.img.zst
|
||||
|
|
@ -25,11 +25,11 @@ nix build .#noisebell-discord
|
|||
|
||||
## NixOS deployment
|
||||
|
||||
The flake exports a NixOS module for the hosted remote machine. It imports `agenix`, declares the Noisebell secrets from `secrets/*.age`, and wires the cache and Discord services together with sensible defaults. Each service runs as a hardened systemd unit behind Caddy.
|
||||
The flake exports NixOS modules. Each service runs as a hardened systemd unit behind Caddy.
|
||||
|
||||
```nix
|
||||
{
|
||||
inputs.noisebell.url = "git+https://git.extremist.software/jet/noisebell";
|
||||
inputs.noisebell.url = "git+https://git.extremist.software/jet/noisebell?dir=remote";
|
||||
|
||||
outputs = { self, nixpkgs, noisebell, ... }: {
|
||||
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
|
||||
|
|
@ -41,11 +41,19 @@ The flake exports a NixOS module for the hosted remote machine. It imports `agen
|
|||
enable = true;
|
||||
domain = "cache.noisebell.example.com";
|
||||
piAddress = "http://noisebell-pi:80";
|
||||
piApiKeyFile = "/run/secrets/noisebell-pi-api-key";
|
||||
inboundApiKeyFile = "/run/secrets/noisebell-inbound-api-key";
|
||||
outboundWebhooks = [{
|
||||
url = "http://localhost:3001/webhook";
|
||||
secretFile = "/run/secrets/noisebell-discord-webhook-secret";
|
||||
}];
|
||||
};
|
||||
services.noisebell-discord = {
|
||||
enable = true;
|
||||
domain = "discord.noisebell.example.com";
|
||||
discordTokenFile = "/run/secrets/noisebell-discord-token";
|
||||
channelId = "123456789012345678";
|
||||
webhookSecretFile = "/run/secrets/noisebell-discord-webhook-secret";
|
||||
};
|
||||
})
|
||||
];
|
||||
|
|
@ -53,14 +61,3 @@ The flake exports a NixOS module for the hosted remote machine. It imports `agen
|
|||
};
|
||||
}
|
||||
```
|
||||
|
||||
`nixosModules.default` handles these secrets automatically:
|
||||
|
||||
| Secret file | Deployed on | Used for |
|
||||
|-------------|-------------|----------|
|
||||
| `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/discord-token.age` | remote | Discord bot login |
|
||||
|
||||
When `extremist-software` builds a system using the Noisebell flake input, Nix uses the checked-out flake source for that input. The module points `agenix` at encrypted files inside that Noisebell source tree, such as `${inputs.noisebell}/secrets/discord-token.age`. At activation time `agenix` decrypts them locally on the target host into runtime paths like `/run/agenix/noisebell-discord-token`. The service modules then read those local decrypted files when systemd starts them.
|
||||
|
|
|
|||
|
|
@ -1,61 +0,0 @@
|
|||
{ self, agenix }:
|
||||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cfgCache = config.services.noisebell-cache;
|
||||
cfgDiscord = config.services.noisebell-discord;
|
||||
in
|
||||
{
|
||||
imports = [ agenix.nixosModules.default ];
|
||||
|
||||
users.groups.noisebell = { };
|
||||
|
||||
users.users.noisebell-cache.extraGroups = lib.mkIf cfgCache.enable [ "noisebell" ];
|
||||
users.users.noisebell-discord.extraGroups = lib.mkIf cfgDiscord.enable [ "noisebell" ];
|
||||
|
||||
age.secrets.noisebell-pi-to-cache-key = {
|
||||
file = "${self}/secrets/pi-to-cache-key.age";
|
||||
group = "noisebell";
|
||||
mode = "0440";
|
||||
};
|
||||
|
||||
age.secrets.noisebell-cache-to-pi-key = {
|
||||
file = "${self}/secrets/cache-to-pi-key.age";
|
||||
group = "noisebell";
|
||||
mode = "0440";
|
||||
};
|
||||
|
||||
age.secrets.noisebell-discord-token = {
|
||||
file = "${self}/secrets/discord-token.age";
|
||||
group = "noisebell";
|
||||
mode = "0440";
|
||||
};
|
||||
|
||||
age.secrets.noisebell-discord-webhook-secret = {
|
||||
file = "${self}/secrets/discord-webhook-secret.age";
|
||||
group = "noisebell";
|
||||
mode = "0440";
|
||||
};
|
||||
|
||||
services.noisebell-cache = lib.mkIf cfgCache.enable {
|
||||
piApiKeyFile = lib.mkDefault config.age.secrets.noisebell-cache-to-pi-key.path;
|
||||
inboundApiKeyFile = lib.mkDefault config.age.secrets.noisebell-pi-to-cache-key.path;
|
||||
outboundWebhooks = lib.mkDefault (
|
||||
lib.optional cfgDiscord.enable {
|
||||
url = "http://127.0.0.1:${toString cfgDiscord.port}/webhook";
|
||||
secretFile = config.age.secrets.noisebell-discord-webhook-secret.path;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
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";
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
BOOTFS=${1:-/run/media/jet/bootfs}
|
||||
ROOTFS=${2:-/run/media/jet/rootfs}
|
||||
HOSTNAME=noisebridge-pi
|
||||
WIFI_SSID=Noisebridge
|
||||
WIFI_PASSWORD=noisebridge
|
||||
PI_USERNAME=pi
|
||||
SSH_KEY='ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE40ISu3ydCqfdpb26JYD5cIN0Fu0id/FDS+xjB5zpqu'
|
||||
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo "Run with sudo: sudo $0 [bootfs] [rootfs]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -d "$BOOTFS" ]]; then
|
||||
echo "Expected mounted boot partition." >&2
|
||||
echo "Boot: $BOOTFS" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
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:
|
||||
version: 2
|
||||
|
||||
wifis:
|
||||
wlan0:
|
||||
dhcp4: true
|
||||
optional: true
|
||||
access-points:
|
||||
${WIFI_SSID}:
|
||||
password: "${WIFI_PASSWORD}"
|
||||
regulatory-domain: US
|
||||
EOF
|
||||
|
||||
cat > "$BOOTFS/user-data" <<EOF
|
||||
#cloud-config
|
||||
hostname: ${HOSTNAME}
|
||||
manage_etc_hosts: true
|
||||
ssh_pwauth: false
|
||||
package_update: false
|
||||
packages:
|
||||
- avahi-daemon
|
||||
users:
|
||||
- name: pi
|
||||
ssh_authorized_keys:
|
||||
- ${SSH_KEY}
|
||||
runcmd:
|
||||
- [ systemctl, enable, --now, avahi-daemon ]
|
||||
EOF
|
||||
|
||||
cat > "$BOOTFS/meta-data" <<EOF
|
||||
dsmode: local
|
||||
instance_id: ${HOSTNAME}-bootstrap-1
|
||||
EOF
|
||||
|
||||
grep -q '^enable_uart=1$' "$CONFIG_TXT" || printf '\nenable_uart=1\n' >> "$CONFIG_TXT"
|
||||
: > "$BOOTFS/ssh"
|
||||
|
||||
if [[ "$ROOTFS_READY" -eq 1 ]]; then
|
||||
cat > "$ROOTFS/etc/hostname" <<EOF
|
||||
${HOSTNAME}
|
||||
EOF
|
||||
|
||||
cat > "$ROOTFS/etc/hosts" <<EOF
|
||||
127.0.0.1 localhost
|
||||
::1 localhost ip6-localhost ip6-loopback
|
||||
ff02::1 ip6-allnodes
|
||||
ff02::2 ip6-allrouters
|
||||
|
||||
127.0.1.1 ${HOSTNAME}
|
||||
EOF
|
||||
|
||||
mkdir -p "$ROOTFS/home/pi/.ssh"
|
||||
cat > "$ROOTFS/home/pi/.ssh/authorized_keys" <<EOF
|
||||
${SSH_KEY}
|
||||
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
|
||||
|
||||
echo "Configured Raspberry Pi OS SD card."
|
||||
echo "- Hostname: ${HOSTNAME}"
|
||||
echo "- Wi-Fi: ${WIFI_SSID}"
|
||||
echo "- SSH enabled on first boot"
|
||||
echo "- Serial UART enabled"
|
||||
echo "- Username: ${PI_USERNAME}"
|
||||
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
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
TARGET_HOST=${1:-root@noisebridge-pi.local}
|
||||
|
||||
exec nixos-rebuild switch --flake ".#pi" --target-host "$TARGET_HOST"
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
#!/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.
|
|
@ -1,7 +1,8 @@
|
|||
age-encryption.org/v1
|
||||
-> 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ë?KtJm’wF' –®0³ÉXy»<á’÷<E28099>î>½\Áç^þ°m»ÜrÊÏùµŸÏü
|
||||
-> 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…|Ê
|
||||
pí³ÿÑòâÙ_ßÄÊ»\s»N´C‚UG
|º‡*i(6ÇÔA•k/µy
|
||||
Binary file not shown.
|
|
@ -1,12 +1,11 @@
|
|||
age-encryption.org/v1
|
||||
-> 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>úŠ/tĆěšÂÚqXžŕÄÇ[𮠼™Í;ž§Ž†*Ć·¨»ńJ2|ŃS(đ‚’$Ó©ŘÎ<>SÁ*m
|
||||
-> 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Ô
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
let
|
||||
jet = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE40ISu3ydCqfdpb26JYD5cIN0Fu0id/FDS+xjB5zpqu";
|
||||
pi = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEfZfAQEFy8QU5P7deC2vWPN76YpUKcBF8fiWwuANumG";
|
||||
pi = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKmJFZAVJUjDziQ7XytVhQIv+Pm9hnbVU0flxZK17K5E";
|
||||
server = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAING219cDKTDLaZefmqvOHfXvYloA/ErsCGE0pM022vlB";
|
||||
piBootstrap = "age1sfqn46dgztr35dtyhpzyzzam5m6kcqu495qs7fcsdxtac56pc4dsj3t862";
|
||||
in
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue