feat!: make declarative version
This commit is contained in:
parent
cf3c5ef1f5
commit
f4d95c595e
13 changed files with 493 additions and 95 deletions
1
.envrc
1
.envrc
|
|
@ -1 +1,2 @@
|
||||||
|
export NIX_CONFIG="eval-cache = false"
|
||||||
use flake
|
use flake
|
||||||
|
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -2,3 +2,5 @@ target/
|
||||||
result
|
result
|
||||||
result-*
|
result-*
|
||||||
.direnv
|
.direnv
|
||||||
|
pi-serial-*.log
|
||||||
|
serial-*.log
|
||||||
|
|
|
||||||
|
|
@ -16,5 +16,8 @@ Pi (door sensor) ──webhook──> Cache ──webhook──> Discord
|
||||||
|-----------|------------|
|
|-----------|------------|
|
||||||
| [`pi/`](pi/) | NixOS config + Rust service for the Pi |
|
| [`pi/`](pi/) | NixOS config + Rust service for the Pi |
|
||||||
| [`remote/`](remote/) | Server-side services (cache and Discord bot) |
|
| [`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.
|
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,6 +23,22 @@
|
||||||
"type": "github"
|
"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": {
|
"crane": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1773857772,
|
"lastModified": 1773857772,
|
||||||
|
|
@ -60,6 +76,21 @@
|
||||||
"type": "github"
|
"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": {
|
"home-manager": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
|
|
@ -81,13 +112,79 @@
|
||||||
"type": "github"
|
"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": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1773646010,
|
"lastModified": 1773821835,
|
||||||
"narHash": "sha256-iYrs97hS7p5u4lQzuNWzuALGIOdkPXvjz7bviiBjUu8=",
|
"narHash": "sha256-TJ3lSQtW0E2JrznGVm8hOQGVpXjJyXY2guAxku2O9A4=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "5b2c2d84341b2afb5647081c1386a80d7a8d8605",
|
"rev": "b40629efe5d6ec48dd1efba650c797ddbd39ace0",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -101,6 +198,8 @@
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"agenix": "agenix",
|
"agenix": "agenix",
|
||||||
"crane": "crane",
|
"crane": "crane",
|
||||||
|
"nixos-hardware": "nixos-hardware",
|
||||||
|
"nixos-raspberrypi": "nixos-raspberrypi",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
"rust-overlay": "rust-overlay"
|
"rust-overlay": "rust-overlay"
|
||||||
}
|
}
|
||||||
|
|
@ -112,11 +211,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1773803479,
|
"lastModified": 1774149071,
|
||||||
"narHash": "sha256-GD6i1F2vrSxbsmbS92+8+x3DbHOJ+yrS78Pm4xigW4M=",
|
"narHash": "sha256-SYp8NyzwfCO3Guqmu9hPRHR1hwESlQia5nNz3lYo2qA=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "f17186f52e82ec5cf40920b58eac63b78692ac7c",
|
"rev": "6a031966eab3bfaa19be9e261eed5b8a79c04b18",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
||||||
240
flake.nix
240
flake.nix
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
nixos-hardware.url = "github:NixOS/nixos-hardware/master";
|
||||||
agenix = {
|
agenix = {
|
||||||
url = "github:ryantm/agenix";
|
url = "github:ryantm/agenix";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
|
@ -12,12 +13,18 @@
|
||||||
url = "github:oxalica/rust-overlay";
|
url = "github:oxalica/rust-overlay";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
nixos-raspberrypi = {
|
||||||
|
url = "github:nvmd/nixos-raspberrypi/main";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
{
|
{
|
||||||
self,
|
self,
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
|
nixos-hardware,
|
||||||
|
nixos-raspberrypi,
|
||||||
agenix,
|
agenix,
|
||||||
crane,
|
crane,
|
||||||
rust-overlay,
|
rust-overlay,
|
||||||
|
|
@ -99,100 +106,191 @@
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
flash-pi-sd = pkgs.writeShellApplication {
|
bootstrapModule =
|
||||||
name = "flash-pi-sd";
|
{
|
||||||
|
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.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";
|
||||||
runtimeInputs = [
|
runtimeInputs = [
|
||||||
agenix.packages.${system}.default
|
|
||||||
pkgs.coreutils
|
pkgs.coreutils
|
||||||
pkgs.nix
|
pkgs.nix
|
||||||
pkgs.parted
|
|
||||||
pkgs.systemd
|
|
||||||
pkgs.util-linux
|
|
||||||
pkgs.zstd
|
pkgs.zstd
|
||||||
];
|
];
|
||||||
text = ''
|
text = ''
|
||||||
set -euo pipefail
|
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
|
|
||||||
ZSTD=${pkgs.zstd}/bin/zstd
|
|
||||||
|
|
||||||
if [ "$#" -ne 1 ]; then
|
if [ "$#" -ne 1 ]; then
|
||||||
echo "usage: flash-pi-sd /dev/sdX" >&2
|
echo "usage: flash-bootstrap-sd /dev/sdX" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
device="$1"
|
device="$1"
|
||||||
flake_path=${builtins.toString ./.}
|
flake_path=${builtins.toString ./.}
|
||||||
image_link="$(mktemp -u /tmp/noisebell-sd-image.XXXXXX)"
|
zstd_bin=${pkgs.zstd}/bin/zstd
|
||||||
mount_dir="$(mktemp -d)"
|
|
||||||
secrets_dir="${builtins.toString ./secrets}"
|
|
||||||
key_name="bootstrap-identity.age"
|
|
||||||
rules_file="${builtins.toString ./secrets/secrets.nix}"
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
if "$MOUNTPOINT" -q "$mount_dir"; then
|
|
||||||
sudo "$UMOUNT" "$mount_dir"
|
|
||||||
fi
|
|
||||||
rm -rf "$mount_dir"
|
|
||||||
rm -f "$image_link"
|
|
||||||
}
|
|
||||||
trap cleanup EXIT
|
|
||||||
|
|
||||||
if [ ! -b "$device" ]; then
|
if [ ! -b "$device" ]; then
|
||||||
echo "not a block device: $device" >&2
|
echo "not a block device: $device" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
boot_part="''${device}1"
|
echo "Requesting sudo access before build and flash..."
|
||||||
case "$device" in
|
sudo -v
|
||||||
*[0-9]) boot_part="''${device}p1" ;;
|
echo "Sudo authentication successful."
|
||||||
esac
|
|
||||||
|
|
||||||
echo "Building bootstrap SD image..."
|
echo "Building bootstrap NixOS Raspberry Pi Zero 2 W image..."
|
||||||
nix build "$flake_path#nixosConfigurations.bootstrap.config.system.build.sdImage" -o "$image_link"
|
image_out="$(nix build \
|
||||||
|
--print-out-paths \
|
||||||
|
--cores 0 \
|
||||||
|
--max-jobs auto \
|
||||||
|
"$flake_path#nixosConfigurations.bootstrap.config.system.build.sdImage")"
|
||||||
|
|
||||||
image="$(echo "$image_link"/sd-image/*.img*)"
|
image="$(echo "$image_out"/sd-image/*.img*)"
|
||||||
if [ ! -f "$image" ]; then
|
if [ ! -f "$image" ]; then
|
||||||
echo "failed to locate SD image under $image_link/sd-image" >&2
|
echo "failed to locate SD image under $image_out/sd-image" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Flashing $image to $device..."
|
echo "Flashing $image to $device..."
|
||||||
if [ "''${image##*.}" = "zst" ]; then
|
if [ "''${image##*.}" = "zst" ]; then
|
||||||
"$ZSTD" -d --stdout "$image" | sudo dd of="$device" bs=4M conv=fsync status=progress
|
"$zstd_bin" -d --stdout "$image" | sudo dd of="$device" bs=16M conv=fsync status=progress
|
||||||
else
|
else
|
||||||
sudo dd if="$image" of="$device" bs=4M conv=fsync status=progress
|
sudo dd if="$image" of="$device" bs=16M conv=fsync status=progress
|
||||||
fi
|
fi
|
||||||
sync
|
sync
|
||||||
|
|
||||||
sudo "$PARTPROBE" "$device"
|
echo "Done. This is the custom bootstrap NixOS image."
|
||||||
sudo "$UDEVADM" settle
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
if "$FINDMNT" -rn "$boot_part" >/dev/null 2>&1; then
|
pi-serial = pkgs.writeShellApplication {
|
||||||
sudo "$UMOUNT" "$boot_part"
|
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
|
fi
|
||||||
|
|
||||||
echo "Installing bootstrap age identity onto $boot_part..."
|
if [ "$#" -eq 1 ]; then
|
||||||
sudo "$MOUNT" "$boot_part" "$mount_dir"
|
port="$1"
|
||||||
(
|
else
|
||||||
cd "$secrets_dir"
|
for candidate in /dev/serial/by-id/* /dev/ttyUSB* /dev/ttyACM*; do
|
||||||
RULES="$rules_file" agenix -d "$key_name"
|
if [ -e "$candidate" ]; then
|
||||||
) | sudo tee "$mount_dir/noisebell-bootstrap.agekey" >/dev/null
|
port="$candidate"
|
||||||
sudo chmod 600 "$mount_dir/noisebell-bootstrap.agekey"
|
break
|
||||||
sync
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Done. You can now move the card to the Pi and boot it."
|
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"
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
packages.${system} = {
|
packages.${system} = {
|
||||||
inherit noisebell-cache noisebell-discord flash-pi-sd;
|
inherit
|
||||||
|
noisebell-cache
|
||||||
|
noisebell-discord
|
||||||
|
flash-bootstrap-sd
|
||||||
|
pi-serial
|
||||||
|
;
|
||||||
default = noisebell-cache;
|
default = noisebell-cache;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -208,6 +306,9 @@
|
||||||
imports = [
|
imports = [
|
||||||
(import ./remote/cache-service/module.nix noisebell-cache)
|
(import ./remote/cache-service/module.nix noisebell-cache)
|
||||||
(import ./remote/discord-bot/module.nix noisebell-discord)
|
(import ./remote/discord-bot/module.nix noisebell-discord)
|
||||||
|
(import ./remote/hosted-module.nix {
|
||||||
|
inherit self agenix;
|
||||||
|
})
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -216,6 +317,7 @@
|
||||||
system = "aarch64-linux";
|
system = "aarch64-linux";
|
||||||
modules = [
|
modules = [
|
||||||
agenix.nixosModules.default
|
agenix.nixosModules.default
|
||||||
|
nixos-hardware.nixosModules.raspberry-pi-3
|
||||||
(import ./pi/module.nix {
|
(import ./pi/module.nix {
|
||||||
pkg = noisebell-pi;
|
pkg = noisebell-pi;
|
||||||
rev = self.shortRev or "dirty";
|
rev = self.shortRev or "dirty";
|
||||||
|
|
@ -225,34 +327,38 @@
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
nixosConfigurations.bootstrap = nixpkgs.lib.nixosSystem {
|
nixosConfigurations.bootstrap = nixos-raspberrypi.lib.nixosSystem {
|
||||||
system = "aarch64-linux";
|
specialArgs = {
|
||||||
|
inherit nixos-raspberrypi;
|
||||||
|
};
|
||||||
modules = [
|
modules = [
|
||||||
agenix.nixosModules.default
|
nixos-raspberrypi.nixosModules.sd-image
|
||||||
(import ./pi/module.nix {
|
bootstrapModule
|
||||||
pkg = noisebell-pi;
|
|
||||||
rev = self.shortRev or "dirty";
|
|
||||||
})
|
|
||||||
./pi/bootstrap.nix
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
devShells.${system}.default = craneLib.devShell {
|
devShells.${system}.default = craneLib.devShell {
|
||||||
packages = [
|
packages = [
|
||||||
flash-pi-sd
|
flash-bootstrap-sd
|
||||||
|
pi-serial
|
||||||
pkgs.nix
|
pkgs.nix
|
||||||
pkgs.parted
|
pkgs.parted
|
||||||
pkgs.rust-analyzer
|
pkgs.rust-analyzer
|
||||||
pkgs.systemd
|
pkgs.tio
|
||||||
pkgs.util-linux
|
|
||||||
pkgs.zstd
|
pkgs.zstd
|
||||||
agenix.packages.${system}.default
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
apps.${system}.flash-pi-sd = {
|
apps.${system} = {
|
||||||
|
flash-bootstrap-sd = {
|
||||||
type = "app";
|
type = "app";
|
||||||
program = "${flash-pi-sd}/bin/flash-pi-sd";
|
program = "${flash-bootstrap-sd}/bin/flash-bootstrap-sd";
|
||||||
|
};
|
||||||
|
|
||||||
|
pi-serial = {
|
||||||
|
type = "app";
|
||||||
|
program = "${pi-serial}/bin/pi-serial";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
{ modulesPath, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
"${modulesPath}/installer/sd-card/sd-image-aarch64.nix"
|
|
||||||
./configuration.nix
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
@ -10,7 +10,11 @@
|
||||||
networks."Noisebridge".psk = "noisebridge";
|
networks."Noisebridge".psk = "noisebridge";
|
||||||
};
|
};
|
||||||
|
|
||||||
services.avahi.enable = false;
|
services.avahi = {
|
||||||
|
enable = true;
|
||||||
|
nssmdns4 = true;
|
||||||
|
openFirewall = true;
|
||||||
|
};
|
||||||
|
|
||||||
# Decrypted at runtime by agenix
|
# Decrypted at runtime by agenix
|
||||||
age.identityPaths = [
|
age.identityPaths = [
|
||||||
|
|
@ -30,11 +34,23 @@
|
||||||
inboundApiKeyFile = config.age.secrets.cache-to-pi-key.path;
|
inboundApiKeyFile = config.age.secrets.cache-to-pi-key.path;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
hardware.enableRedistributableFirmware = true;
|
||||||
|
|
||||||
nix.settings.experimental-features = [
|
nix.settings.experimental-features = [
|
||||||
"nix-command"
|
"nix-command"
|
||||||
"flakes"
|
"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 = {
|
services.tailscale = {
|
||||||
enable = true;
|
enable = true;
|
||||||
authKeyFile = config.age.secrets.tailscale-auth-key.path;
|
authKeyFile = config.age.secrets.tailscale-auth-key.path;
|
||||||
|
|
@ -42,6 +58,18 @@
|
||||||
|
|
||||||
services.openssh.enable = true;
|
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 = {
|
networking.firewall = {
|
||||||
trustedInterfaces = [ "tailscale0" ];
|
trustedInterfaces = [ "tailscale0" ];
|
||||||
allowedUDPPorts = [ config.services.tailscale.port ];
|
allowedUDPPorts = [ config.services.tailscale.port ];
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,10 @@
|
||||||
{ config, lib, pkgs, modulesPath, ... }:
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
modulesPath,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
{
|
{
|
||||||
imports = [ ];
|
imports = [ ];
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
/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
|
## NixOS deployment
|
||||||
|
|
||||||
The flake exports NixOS modules. Each service runs as a hardened systemd unit behind Caddy.
|
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.
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
{
|
{
|
||||||
inputs.noisebell.url = "git+https://git.extremist.software/jet/noisebell?dir=remote";
|
inputs.noisebell.url = "git+https://git.extremist.software/jet/noisebell";
|
||||||
|
|
||||||
outputs = { self, nixpkgs, noisebell, ... }: {
|
outputs = { self, nixpkgs, noisebell, ... }: {
|
||||||
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
|
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
|
||||||
|
|
@ -41,19 +41,11 @@ The flake exports NixOS modules. Each service runs as a hardened systemd unit be
|
||||||
enable = true;
|
enable = true;
|
||||||
domain = "cache.noisebell.example.com";
|
domain = "cache.noisebell.example.com";
|
||||||
piAddress = "http://noisebell-pi:80";
|
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 = {
|
services.noisebell-discord = {
|
||||||
enable = true;
|
enable = true;
|
||||||
domain = "discord.noisebell.example.com";
|
domain = "discord.noisebell.example.com";
|
||||||
discordTokenFile = "/run/secrets/noisebell-discord-token";
|
|
||||||
channelId = "123456789012345678";
|
channelId = "123456789012345678";
|
||||||
webhookSecretFile = "/run/secrets/noisebell-discord-webhook-secret";
|
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
|
@ -61,3 +53,14 @@ The flake exports NixOS modules. Each service runs as a hardened systemd unit be
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`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.
|
||||||
|
|
|
||||||
61
remote/hosted-module.nix
Normal file
61
remote/hosted-module.nix
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
{ 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";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
98
scripts/configure-pios-sd.sh
Executable file
98
scripts/configure-pios-sd.sh
Executable file
|
|
@ -0,0 +1,98 @@
|
||||||
|
#!/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
|
||||||
|
PI_PASSWORD=noisebridge
|
||||||
|
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" || ! -d "$ROOTFS" ]]; then
|
||||||
|
echo "Expected mounted boot and root partitions." >&2
|
||||||
|
echo "Boot: $BOOTFS" >&2
|
||||||
|
echo "Root: $ROOTFS" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
PASSWORD_HASH=$(mkpasswd -m sha-512 "$PI_PASSWORD")
|
||||||
|
|
||||||
|
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$' "$BOOTFS/config.txt" || printf '\nenable_uart=1\n' >> "$BOOTFS/config.txt"
|
||||||
|
: > "$BOOTFS/ssh"
|
||||||
|
cat > "$BOOTFS/userconf.txt" <<EOF
|
||||||
|
${PI_USERNAME}:${PASSWORD_HASH}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
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 "- Password: ${PI_PASSWORD}"
|
||||||
|
echo "- Pi user authorized key installed"
|
||||||
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue