Compare commits
2 commits
cf3c5ef1f5
...
16ad3c6181
| Author | SHA1 | Date | |
|---|---|---|---|
| 16ad3c6181 | |||
| f4d95c595e |
22 changed files with 798 additions and 176 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
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,10 @@ Pi (door sensor) ──webhook──> Cache ──webhook──> Discord
|
||||||
|
|
||||||
| Directory | What it is |
|
| 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) |
|
| [`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": {
|
||||||
|
|
|
||||||
239
flake.nix
239
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,
|
||||||
|
|
@ -67,8 +74,17 @@
|
||||||
overlays = [ rust-overlay.overlays.default ];
|
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 {
|
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;
|
piCraneLib = (crane.mkLib pkgs).overrideToolchain piRustToolchain;
|
||||||
|
|
@ -99,6 +115,61 @@
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
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 {
|
flash-pi-sd = pkgs.writeShellApplication {
|
||||||
name = "flash-pi-sd";
|
name = "flash-pi-sd";
|
||||||
runtimeInputs = [
|
runtimeInputs = [
|
||||||
|
|
@ -108,6 +179,7 @@
|
||||||
pkgs.parted
|
pkgs.parted
|
||||||
pkgs.systemd
|
pkgs.systemd
|
||||||
pkgs.util-linux
|
pkgs.util-linux
|
||||||
|
pkgs.xz
|
||||||
pkgs.zstd
|
pkgs.zstd
|
||||||
];
|
];
|
||||||
text = ''
|
text = ''
|
||||||
|
|
@ -119,7 +191,6 @@
|
||||||
MOUNTPOINT=${pkgs.util-linux}/bin/mountpoint
|
MOUNTPOINT=${pkgs.util-linux}/bin/mountpoint
|
||||||
FINDMNT=${pkgs.util-linux}/bin/findmnt
|
FINDMNT=${pkgs.util-linux}/bin/findmnt
|
||||||
UDEVADM=${pkgs.systemd}/bin/udevadm
|
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-pi-sd /dev/sdX" >&2
|
||||||
|
|
@ -128,18 +199,17 @@
|
||||||
|
|
||||||
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}
|
||||||
secrets_dir="${builtins.toString ./secrets}"
|
rules_file=${builtins.toString ./secrets/secrets.nix}
|
||||||
key_name="bootstrap-identity.age"
|
key_name="bootstrap-identity.age"
|
||||||
rules_file="${builtins.toString ./secrets/secrets.nix}"
|
boot_mount_dir="$(mktemp -d)"
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
if "$MOUNTPOINT" -q "$mount_dir"; then
|
if "$MOUNTPOINT" -q "$boot_mount_dir"; then
|
||||||
sudo "$UMOUNT" "$mount_dir"
|
sudo "$UMOUNT" "$boot_mount_dir"
|
||||||
fi
|
fi
|
||||||
rm -rf "$mount_dir"
|
rm -rf "$boot_mount_dir"
|
||||||
rm -f "$image_link"
|
|
||||||
}
|
}
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
|
@ -153,20 +223,30 @@
|
||||||
*[0-9]) boot_part="''${device}p1" ;;
|
*[0-9]) boot_part="''${device}p1" ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
echo "Building bootstrap SD image..."
|
echo "Requesting sudo access before build and flash..."
|
||||||
nix build "$flake_path#nixosConfigurations.bootstrap.config.system.build.sdImage" -o "$image_link"
|
sudo -v
|
||||||
|
echo "Sudo authentication successful."
|
||||||
|
|
||||||
image="$(echo "$image_link"/sd-image/*.img*)"
|
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*)"
|
||||||
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
|
||||||
|
elif [ "''${image##*.}" = "xz" ]; then
|
||||||
|
xz -d -c "$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
|
||||||
|
|
||||||
|
|
@ -178,26 +258,104 @@
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Installing bootstrap age identity onto $boot_part..."
|
echo "Installing bootstrap age identity onto $boot_part..."
|
||||||
sudo "$MOUNT" "$boot_part" "$mount_dir"
|
sudo "$MOUNT" "$boot_part" "$boot_mount_dir"
|
||||||
(
|
(
|
||||||
cd "$secrets_dir"
|
cd "$secrets_dir"
|
||||||
RULES="$rules_file" agenix -d "$key_name"
|
RULES="$rules_file" agenix -d "$key_name"
|
||||||
) | sudo tee "$mount_dir/noisebell-bootstrap.agekey" >/dev/null
|
) | sudo tee "$boot_mount_dir/noisebell-bootstrap.agekey" >/dev/null
|
||||||
sudo chmod 600 "$mount_dir/noisebell-bootstrap.agekey"
|
sudo chmod 600 "$boot_mount_dir/noisebell-bootstrap.agekey"
|
||||||
sync
|
sync
|
||||||
|
|
||||||
echo "Done. You can now move the card to the Pi and boot it."
|
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"
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
packages.${system} = {
|
packages.${system} = {
|
||||||
inherit noisebell-cache noisebell-discord flash-pi-sd;
|
inherit
|
||||||
|
noisebell-cache
|
||||||
|
noisebell-discord
|
||||||
|
flash-pi-sd
|
||||||
|
pi-serial
|
||||||
|
;
|
||||||
default = noisebell-cache;
|
default = noisebell-cache;
|
||||||
};
|
};
|
||||||
|
|
||||||
packages.aarch64-linux = {
|
packages.aarch64-linux = {
|
||||||
noisebell = noisebell-pi;
|
noisebell = noisebell-pi;
|
||||||
|
noisebell-static = noisebell-pi-static;
|
||||||
default = noisebell-pi;
|
default = noisebell-pi;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -208,51 +366,52 @@
|
||||||
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;
|
||||||
|
})
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
nixosConfigurations.pi = nixpkgs.lib.nixosSystem {
|
nixosConfigurations.pi = nixos-raspberrypi.lib.nixosSystem {
|
||||||
system = "aarch64-linux";
|
specialArgs = {
|
||||||
|
inherit nixos-raspberrypi;
|
||||||
|
};
|
||||||
modules = [
|
modules = [
|
||||||
|
nixos-raspberrypi.nixosModules.sd-image
|
||||||
agenix.nixosModules.default
|
agenix.nixosModules.default
|
||||||
|
piImageBaseModule
|
||||||
(import ./pi/module.nix {
|
(import ./pi/module.nix {
|
||||||
pkg = noisebell-pi;
|
pkg = noisebell-pi;
|
||||||
rev = self.shortRev or "dirty";
|
rev = self.shortRev or "dirty";
|
||||||
})
|
})
|
||||||
./pi/configuration.nix
|
./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 {
|
devShells.${system}.default = craneLib.devShell {
|
||||||
packages = [
|
packages = [
|
||||||
|
agenix.packages.${system}.default
|
||||||
flash-pi-sd
|
flash-pi-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-pi-sd = {
|
||||||
type = "app";
|
type = "app";
|
||||||
program = "${flash-pi-sd}/bin/flash-pi-sd";
|
program = "${flash-pi-sd}/bin/flash-pi-sd";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pi-serial = {
|
||||||
|
type = "app";
|
||||||
|
program = "${pi-serial}/bin/pi-serial";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
212
pi/README.md
212
pi/README.md
|
|
@ -1,145 +1,181 @@
|
||||||
# Pi
|
# 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)
|
## What stays on Raspberry Pi OS
|
||||||
2. POSTs `{"status": "open", "timestamp": ...}` to the cache service with a Bearer token
|
|
||||||
3. Retries with exponential backoff on failure
|
|
||||||
|
|
||||||
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
|
### 1. Flash Raspberry Pi OS Lite
|
||||||
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:
|
|
||||||
|
|
||||||
```sh
|
```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
|
```sh
|
||||||
nix build .#nixosConfigurations.bootstrap.config.system.build.sdImage
|
sudo scripts/configure-pios-sd.sh /run/media/jet/bootfs /run/media/jet/rootfs
|
||||||
dd if=result/sd-image/*.img of=/dev/sdX bs=4M status=progress
|
|
||||||
```
|
```
|
||||||
|
|
||||||
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
|
```sh
|
||||||
ssh-keyscan noisebridge-pi.local 2>/dev/null | grep ed25519
|
ssh-keyscan noisebridge-pi.local 2>/dev/null | grep ed25519
|
||||||
```
|
```
|
||||||
|
|
||||||
```nix
|
Add that key to `secrets/secrets.nix` for:
|
||||||
# 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 ];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Create secrets
|
- `pi-to-cache-key.age`
|
||||||
|
- `cache-to-pi-key.age`
|
||||||
|
- `tailscale-auth-key.age`
|
||||||
|
|
||||||
```sh
|
Then refresh recipients if needed:
|
||||||
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
|
```sh
|
||||||
cd secrets
|
cd secrets
|
||||||
agenix -r
|
agenix -r
|
||||||
```
|
```
|
||||||
|
|
||||||
If you use `nix run .#flash-pi-sd -- /dev/sdX`, this file is installed automatically.
|
## Edit secrets
|
||||||
|
|
||||||
To install the bootstrap identity manually onto a flashed card before first boot:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd secrets
|
cd secrets
|
||||||
agenix -d bootstrap-identity.age > /boot/noisebell-bootstrap.agekey
|
agenix -e pi-to-cache-key.age
|
||||||
chmod 600 /boot/noisebell-bootstrap.agekey
|
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
|
From your laptop:
|
||||||
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
|
```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 |
|
| `NOISEBELL_GPIO_PIN` | `17` | GPIO pin number |
|
||||||
| `apiKeyFile` | required | Outbound API key file (agenix secret) |
|
| `NOISEBELL_DEBOUNCE_SECS` | `5` | Debounce delay in seconds |
|
||||||
| `inboundApiKeyFile` | required | Inbound API key file for GET auth |
|
| `NOISEBELL_PORT` | `80` | HTTP server port |
|
||||||
| `gpioPin` | `17` | GPIO pin number |
|
| `NOISEBELL_ENDPOINT_URL` | required | Webhook URL to POST state changes to |
|
||||||
| `debounceSecs` | `5` | Debounce delay in seconds |
|
| `NOISEBELL_RETRY_ATTEMPTS` | `3` | Webhook retry count |
|
||||||
| `port` | `8080` | HTTP server port |
|
| `NOISEBELL_RETRY_BASE_DELAY_SECS` | `1` | Exponential backoff base delay |
|
||||||
| `retryAttempts` | `3` | Webhook retry count |
|
| `NOISEBELL_HTTP_TIMEOUT_SECS` | `10` | Outbound request timeout |
|
||||||
| `retryBaseDelaySecs` | `1` | Exponential backoff base delay |
|
| `NOISEBELL_BIND_ADDRESS` | `0.0.0.0` | HTTP bind address |
|
||||||
| `httpTimeoutSecs` | `10` | Outbound request timeout |
|
| `NOISEBELL_ACTIVE_LOW` | `true` | Low GPIO = door open |
|
||||||
| `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
|
## API
|
||||||
|
|
||||||
All endpoints require `Authorization: Bearer <token>`.
|
All endpoints require `Authorization: Bearer <token>`.
|
||||||
|
|
||||||
**`GET /`** — door state
|
**`GET /`**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{"status": "open", "timestamp": 1710000000}
|
{"status": "open", "timestamp": 1710000000}
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
110
scripts/configure-pios-sd.sh
Executable file
110
scripts/configure-pios-sd.sh
Executable file
|
|
@ -0,0 +1,110 @@
|
||||||
|
#!/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
|
||||||
6
scripts/deploy-full-pi.sh
Executable file
6
scripts/deploy-full-pi.sh
Executable 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
117
scripts/deploy-pios-pi.sh
Executable 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.
|
|
@ -1,8 +1,7 @@
|
||||||
age-encryption.org/v1
|
age-encryption.org/v1
|
||||||
-> ssh-ed25519 Ziw7aw W4nd317o0ON6n006hwUNIops3L7VngNqDqJ1bG7tCyg
|
-> ssh-ed25519 Ziw7aw 756HU1sPe5g3sa0YYfzMnXiToT5K+nfAPhfABEetgRI
|
||||||
xyjvZOOzA0u3e+Cba99LR8J5JAkl6muHuspJ+b8dEog
|
E8dqhn7hN77qM0PhHMEAsZySd1hfk0w1tlsiWj4aEOQ
|
||||||
-> ssh-ed25519 uKftJg bX0GHUPdD4hR2yxLNx8ho689or7FNHXPA8iV9Af1q2s
|
-> ssh-ed25519 uKftJg eGfmzvHseauAFPOR1QXfdmaQy5TjpNsoBWq27mbO50w
|
||||||
B91XIdsnHfAKdKfu8jmHKKptA5OomQ5sHvXfLLUeNbs
|
KRuGUW65uQ5+IdREyg6X1oj0P5IkuuxFEl1WylGpAHc
|
||||||
--- 8zoH1w5ywsbDN5Xt50sE4BUgXoq12mr5b4rSVYMLEB8
|
--- 2Ya08payqNiMCEqBXrbKEA53ETupxwgUNRcMNu9IP6k
|
||||||
CŽb;*œã‚´*;í5œ#BL^ñƒT¯úE¢<45>%ÚÎçáZ8†r‰wݧ#‚r-îG¢éÖKÔ—t…|Ê
|
&°‰¢ÏvÄ<76>tBšL˜‚.¬HéÚ“Y P®Sç3Tó¹•4䱟D¬s,9Ñeã4TpåÌ,Gë?KtJm’wF' –®0³ÉXy»<á’÷<E28099>î>½\Áç^þ°m»ÜrÊÏùµŸÏü
|
||||||
pí³ÿÑòâÙ_ßÄÊ»\s»N´C‚UG
|º‡*i(6ÇÔA•k/µy
|
|
||||||
Binary file not shown.
|
|
@ -1,11 +1,12 @@
|
||||||
age-encryption.org/v1
|
age-encryption.org/v1
|
||||||
-> ssh-ed25519 Ziw7aw TMz4xBHmy5btMk5ETWyvjZcjj0QwF3F6Iqt2QmkwVwk
|
-> ssh-ed25519 Ziw7aw ZaLRvgj6V9ukim0lfxHftVUvCXi7tIXPn5O/2nzQqCE
|
||||||
KvcY409ZakEUO7OrmDrwseMQv/w7i3B7uDiXjls/5qE
|
cem5AxKMkYOs8iifYP80hkbr5km7bFOdjCt7Ym6lQcs
|
||||||
-> ssh-ed25519 jcT/MQ Y5TqVlHy3fM+CmQIBu1x18C3wjHtRZCHJ9dhFyVAR0I
|
-> ssh-ed25519 NFB4qA ssMeOzGjehzTeppIGHpzPViIKObSwnXw6OZ1DfXs6Ew
|
||||||
WPqxEZrpAm0wLYT4s9e1uxSuDjhwUwIOL4BvDtvqytU
|
Y813udN4YGDMszEC8FVZz7Na6XQigVNFTdusLomMusg
|
||||||
-> X25519 ZqSHgIStt6Ru3osVvMA1M5sydoY+CeZ56temQyCFIVY
|
-> X25519 qmoLWSdRljn6daPlUyqk9TOOvBaUx42CvqcpXe/xUCE
|
||||||
4tPA6VITlAzJxCFGVreKK1B6rrHm+ka4ELwnzYrMKbQ
|
7xMN5RbYnpgw3+/pHyCiEyEhyUmQOwa1zSlAbuVwlQo
|
||||||
-> ssh-ed25519 uKftJg OR8VgPUuEvS/0Gc5c92IlAp4DKKYcRzBbSh1tX9ddzg
|
-> ssh-ed25519 uKftJg Fv8M0RogkcYWd46bJY3OJCoCFAW8QMjzLueDZowylSA
|
||||||
DZYYx9ngwEUTmj/JaP2XnCQHjpPY8WYgOEDlOfZPLeA
|
R3w6E2RvDmgaKKhxqWHjEeIQxNSCHzX7+nLb3Ls+iHs
|
||||||
--- 4iDfaqdSLiW0doVijoZC5ckxiCmsmVWJi5Kvaxic2Ng
|
--- 13dp1N6I6pPdDx+FrxsT+ZS5rsFfrK3x0F7Rs6vN6/I
|
||||||
è÷VV;2†h<>&e@sÏx#d=Y?iðw½´ÉÍF<15>OÝc×/ûš(™¨àœ›¼føkØí‹ƒ—¿}[¡¤'=B@ô‡¨Â!wÔ
|
ÖBI¬8Ż‹
|
||||||
|
|đ9”<39>úŠ/tĆěšÂÚqXžŕÄÇ[𮠼™Í;ž§Ž†*Ć·¨»ńJ2|ŃS(đ‚’$Ó©ŘÎ<>SÁ*m
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
let
|
let
|
||||||
jet = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE40ISu3ydCqfdpb26JYD5cIN0Fu0id/FDS+xjB5zpqu";
|
jet = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE40ISu3ydCqfdpb26JYD5cIN0Fu0id/FDS+xjB5zpqu";
|
||||||
pi = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKmJFZAVJUjDziQ7XytVhQIv+Pm9hnbVU0flxZK17K5E";
|
pi = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEfZfAQEFy8QU5P7deC2vWPN76YpUKcBF8fiWwuANumG";
|
||||||
server = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAING219cDKTDLaZefmqvOHfXvYloA/ErsCGE0pM022vlB";
|
server = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAING219cDKTDLaZefmqvOHfXvYloA/ErsCGE0pM022vlB";
|
||||||
piBootstrap = "age1sfqn46dgztr35dtyhpzyzzam5m6kcqu495qs7fcsdxtac56pc4dsj3t862";
|
piBootstrap = "age1sfqn46dgztr35dtyhpzyzzam5m6kcqu495qs7fcsdxtac56pc4dsj3t862";
|
||||||
in
|
in
|
||||||
|
|
|
||||||
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue