refactor(beszel): move KEY and TOKEN fully into enable-agent.nix
Check NixOS configurations / eval-hosts (pull_request) Successful in 10m59s

KEY (hub's SSH public key) is now stored in variables.nix as beszelHubKey
and set by enable-agent.nix — no host file ever needs to touch it.
TOKEN was already universal; this removes the last per-host beszel config.

Importing enable-agent.nix in a build type is now the only step needed to
add a new host to beszel monitoring.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-29 12:37:54 +10:00
co-authored by Claude Sonnet 4.6
parent 24c6469f10
commit f22ff7db79
10 changed files with 32 additions and 88 deletions
+1 -2
View File
@@ -21,8 +21,7 @@ machines when deployed.
`modules/installer/common.nix` (the auto-installer's own root/nixos login — `modules/installer/common.nix` (the auto-installer's own root/nixos login —
a deliberate, documented choice, see `docs/auto-installer.md`, not a deliberate, documented choice, see `docs/auto-installer.md`, not
accidental tech debt) and **SSH public keys** in `variables.nix` accidental tech debt) and **SSH public keys** in `variables.nix`
(`vars.adminSshKey`, `vars.remoteBuilderAuthorizedKeys`) plus per-host (`vars.adminSshKey`, `vars.remoteBuilderAuthorizedKeys`, `vars.beszelHubKey`). Don't use the installer's hardcoded hash as a
`KEY` values for beszel-agent auth (see `docs/beszel.md`). Don't use the installer's hardcoded hash as a
template for a *real* host — every other host uses sops-nix template for a *real* host — every other host uses sops-nix
(`hashedPasswordFile`, see "Security Notes" in `README.md`). Flag any *new* (`hashedPasswordFile`, see "Security Notes" in `README.md`). Flag any *new*
secret-like string you encounter instead of committing it. secret-like string you encounter instead of committing it.
+24 -66
View File
@@ -14,13 +14,16 @@ Everything is handled by a single module:
**`modules/beszel/enable-agent.nix`** — imported by a build type. It: **`modules/beszel/enable-agent.nix`** — imported by a build type. It:
- Enables `beszel-agent` - Enables `beszel-agent`
- Sets `HUB_URL` to `docker.sweet.home:8090` - Sets `HUB_URL` to `docker.sweet.home:8090`
- Reads the universal `beszel-token` from `secrets/common.yaml` via sops and - Sets `KEY` from `vars.beszelHubKey` (`variables.nix`) — the hub's SSH
passes it to the agent as `TOKEN` in an env file public key, shared by every agent. Update `beszelHubKey` if the docker
host is ever rebuilt and the hub generates a new keypair.
- Reads the universal `beszel-token` from `secrets/common.yaml` via sops
and passes it to the agent as `TOKEN` in an env file
- Fixes an upstream bug where the agent couldn't persist its hub-pairing - Fixes an upstream bug where the agent couldn't persist its hub-pairing
fingerprint across restarts (adds a real `StateDirectory`) fingerprint across restarts (adds a real `StateDirectory`)
The only thing a host file ever needs to add is `KEY` — the hub's public key A host file needs no beszel configuration at all — just import the module
for that agent slot, which comes from the beszel hub UI after first pairing. in the build type and add the system in the hub UI.
--- ---
@@ -36,30 +39,23 @@ imports = [
]; ];
``` ```
That's the only build-type change required. That's the only change required. The host file needs nothing.
--- ---
## Wiring the host file ## Adding a new system to the hub
No `imports` are needed in the host file. Just set `KEY` once you've paired 1. Rebuild and deploy the host with its build type importing `enable-agent.nix`.
the agent with the hub: 2. Open the beszel hub (`http://docker.sweet.home:8090`).
3. Go to **Systems → Add system**, enter the host's IP and the default port
```nix (45876). The agent will connect and the system will appear as active.
services.beszel.agent.environment = {
KEY = "ssh-ed25519 AAAA...";
};
```
Leave `KEY` commented out until after the first pairing (see "Pairing with
the hub" below).
--- ---
## One-time setup: add the token to `secrets/common.yaml` ## One-time setup: add the token to `secrets/common.yaml`
The universal token is stored once in the common secrets file, shared by all The universal token is stored once in the common secrets file, shared by all
agents. You only need to do this once, not per-host: agents. Only needed once, not per-host:
```sh ```sh
sops secrets/common.yaml sops secrets/common.yaml
@@ -67,26 +63,21 @@ sops secrets/common.yaml
Add: Add:
```yaml ```yaml
beszel-token: <token from the beszel hub UI> beszel-token: <token from the beszel hub Settings → Keys>
``` ```
The token is found in the beszel hub under **Settings → Keys** or in the
"Add system" flow.
`secrets/common.yaml` is already a sops recipient for every host via their `secrets/common.yaml` is already a sops recipient for every host via their
SSH host keys, so no additional sops recipient setup is needed for hosts that SSH host keys, so no additional sops recipient setup is needed.
are already provisioned.
--- ---
## Optional: monitoring extra filesystems ## Optional: monitoring extra filesystems
To report disk usage for a mount beyond the root filesystem, add To report disk usage for a mount beyond the root filesystem, add
`EXTRA_FILESYSTEMS` alongside `KEY` in the host file: `EXTRA_FILESYSTEMS` in the host file:
```nix ```nix
services.beszel.agent.environment = { services.beszel.agent.environment = {
KEY = "ssh-ed25519 AAAA...";
EXTRA_FILESYSTEMS = "/mnt/data"; # colon-separated for multiple paths EXTRA_FILESYSTEMS = "/mnt/data"; # colon-separated for multiple paths
}; };
``` ```
@@ -94,7 +85,10 @@ services.beszel.agent.environment = {
The `server` host uses this to expose its ZFS data pool: The `server` host uses this to expose its ZFS data pool:
```nix ```nix
services.beszel.agent.environment = {
EXTRA_FILESYSTEMS = "${vars.storageRoot}/${vars.nfsShares.dockerVolumes.subpath}"; EXTRA_FILESYSTEMS = "${vars.storageRoot}/${vars.nfsShares.dockerVolumes.subpath}";
LOG_LEVEL = "debug";
};
``` ```
--- ---
@@ -112,44 +106,8 @@ stats. Hosts without Docker should leave it commented out.
--- ---
## Pairing with the hub ## If the hub key changes
1. Deploy the host with its build type importing `enable-agent.nix`. Leave If the docker host is ever rebuilt and beszel generates a new SSH keypair,
`KEY` unset (commented out) for now. update `beszelHubKey` in `variables.nix` and rebuild all beszel-enabled hosts.
2. Open the beszel hub (`http://docker.sweet.home:8090`). The new key is visible in the beszel hub under **Settings → Keys**.
3. Go to **Systems → Add system**. The new host should appear as an unpaired
entry — copy the `KEY` value shown there.
4. In `hosts/<name>/host.nix`, set:
```nix
services.beszel.agent.environment.KEY = "<copied key>";
```
5. Rebuild and deploy the host. The agent will now pair permanently.
---
## Example: complete host file
Minimal case (`hosts/tor-relay/host.nix` — one filesystem, no Docker, LXC):
```nix
{ vars, ... }:
{
networking = { ... };
services.beszel.agent.environment = {
KEY = "ssh-ed25519 AAAA...";
};
system.stateVersion = "26.05";
}
```
Fuller case (`hosts/server/host.nix` — extra filesystem, debug logging):
```nix
services.beszel.agent.environment = {
KEY = "ssh-ed25519 AAAA...";
EXTRA_FILESYSTEMS = "${vars.storageRoot}/${vars.nfsShares.dockerVolumes.subpath}";
LOG_LEVEL = "debug";
};
```
-3
View File
@@ -16,8 +16,5 @@
nameservers = [ vars.domainControllerIp ]; nameservers = [ vars.domainControllerIp ];
}; };
# Set KEY after pairing this host with the beszel hub (see docs/beszel.md).
# services.beszel.agent.environment.KEY = "";
system.stateVersion = "26.05"; system.stateVersion = "26.05";
} }
-3
View File
@@ -16,8 +16,5 @@
nameservers = [ vars.domainControllerIp ]; nameservers = [ vars.domainControllerIp ];
}; };
# Set KEY after pairing this host with the beszel hub (see docs/beszel.md).
# services.beszel.agent.environment.KEY = "";
system.stateVersion = "26.05"; system.stateVersion = "26.05";
} }
-4
View File
@@ -12,10 +12,6 @@
nameservers = [ vars.domainControllerIp ]; nameservers = [ vars.domainControllerIp ];
}; };
services.beszel.agent.environment = {
KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFPR9kwtC4TAeTRu46A7+opZsYpxqkRJ+x/ZyB2GWCeG";
};
# Preserved from the pre-refactor `nix-cache` target — stateVersion must # Preserved from the pre-refactor `nix-cache` target — stateVersion must
# never be bumped on an already-installed machine. # never be bumped on an already-installed machine.
system.stateVersion = "25.05"; system.stateVersion = "25.05";
-1
View File
@@ -14,7 +14,6 @@
}; };
services.beszel.agent.environment = { services.beszel.agent.environment = {
KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFPR9kwtC4TAeTRu46A7+opZsYpxqkRJ+x/ZyB2GWCeG";
EXTRA_FILESYSTEMS = "${vars.storageRoot}/${vars.nfsShares.dockerVolumes.subpath}"; EXTRA_FILESYSTEMS = "${vars.storageRoot}/${vars.nfsShares.dockerVolumes.subpath}";
LOG_LEVEL = "debug"; LOG_LEVEL = "debug";
}; };
-4
View File
@@ -12,10 +12,6 @@
nameservers = [ vars.domainControllerIp ]; nameservers = [ vars.domainControllerIp ];
}; };
services.beszel.agent.environment = {
KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFPR9kwtC4TAeTRu46A7+opZsYpxqkRJ+x/ZyB2GWCeG";
};
# No networking.hostId: only ZFS-touching hosts (server, docker) need one # No networking.hostId: only ZFS-touching hosts (server, docker) need one
# for pool-import safety, and this host does neither. # for pool-import safety, and this host does neither.
-4
View File
@@ -15,10 +15,6 @@
# No networking.hostId: only ZFS-touching hosts need one for pool-import # No networking.hostId: only ZFS-touching hosts need one for pool-import
# safety, and this host does neither. # safety, and this host does neither.
services.beszel.agent.environment = {
KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFPR9kwtC4TAeTRu46A7+opZsYpxqkRJ+x/ZyB2GWCeG";
};
# A genuinely new host (not a pre-refactor carry-over), so it tracks the # A genuinely new host (not a pre-refactor carry-over), so it tracks the
# flake's current nixpkgs release rather than being pinned to an older one. # flake's current nixpkgs release rather than being pinned to an older one.
system.stateVersion = "26.05"; system.stateVersion = "26.05";
+1
View File
@@ -16,6 +16,7 @@
environment = { environment = {
#DOCKER_HOST = "tcp://docker-socket-proxy:2375"; #DOCKER_HOST = "tcp://docker-socket-proxy:2375";
HUB_URL = "http://${vars.dockerHost}.${vars.homeDomain}:${toString vars.ports.beszelHub}"; HUB_URL = "http://${vars.dockerHost}.${vars.homeDomain}:${toString vars.ports.beszelHub}";
KEY = vars.beszelHubKey;
}; };
}; };
+5
View File
@@ -40,6 +40,11 @@
# from scratch. # from scratch.
nixCacheHostKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICuHUxGNH6ei3BZD+EfZs3l4X8uJNcjQiOsM/G4yo4O/ lxc-nix-cache"; nixCacheHostKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICuHUxGNH6ei3BZD+EfZs3l4X8uJNcjQiOsM/G4yo4O/ lxc-nix-cache";
# Beszel hub's SSH public key — used by every agent to authenticate the
# hub's incoming connection. Update if the docker host is ever rebuilt and
# the hub generates a new keypair.
beszelHubKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFPR9kwtC4TAeTRu46A7+opZsYpxqkRJ+x/ZyB2GWCeG";
# Public keys authorized to SSH in as remoteBuilderUser on the nix-cache # Public keys authorized to SSH in as remoteBuilderUser on the nix-cache
# host (modules/nix-cache/server.nix) — one per client host that's allowed # host (modules/nix-cache/server.nix) — one per client host that's allowed
# to use it as a distributed builder. # to use it as a distributed builder.