Changes to rpi setup
This commit is contained in:
@@ -19,7 +19,7 @@ modules/
|
|||||||
storage.nix # External HD mount + per-service directory layout
|
storage.nix # External HD mount + per-service directory layout
|
||||||
caddy.nix # Caddy reverse proxy (DNS-01 ACME, forward_auth)
|
caddy.nix # Caddy reverse proxy (DNS-01 ACME, forward_auth)
|
||||||
cloudflared.nix # Cloudflare Tunnel for remote access
|
cloudflared.nix # Cloudflare Tunnel for remote access
|
||||||
backup.nix # Restic daily backups
|
backup.nix # Restic daily backups (S3 primary + manual offload)
|
||||||
services/
|
services/
|
||||||
openldap.nix # OpenLDAP — central identity provider
|
openldap.nix # OpenLDAP — central identity provider
|
||||||
authelia.nix # Authelia — SSO gateway
|
authelia.nix # Authelia — SSO gateway
|
||||||
@@ -226,19 +226,54 @@ production-ready:
|
|||||||
- [ ] **`hosts/pi-main/default.nix` — fill in real values**:
|
- [ ] **`hosts/pi-main/default.nix` — fill in real values**:
|
||||||
- SSH public key in `users.users.admin.openssh.authorizedKeys.keys`
|
- SSH public key in `users.users.admin.openssh.authorizedKeys.keys`
|
||||||
- External HD device path in `homey.storage.device`
|
- External HD device path in `homey.storage.device`
|
||||||
- Backup repository URL in `homey.backup.repository`
|
- Backup repository URL in `homey.backup.repository` — must be an S3-compatible
|
||||||
|
URL, e.g. `"s3:https://s3.us-west-002.backblazeb2.com/your-bucket-name"`
|
||||||
|
|
||||||
- [ ] **`secrets/secrets.yaml` — populate and encrypt**: Fill in all secret
|
- [ ] **`secrets/secrets.yaml` — populate and encrypt**: Fill in all secret
|
||||||
values (old passwords from k8s + freshly generated ones), then run
|
values (old passwords from k8s + freshly generated ones, including
|
||||||
|
`restic/s3_access_key_id` and `restic/s3_secret_access_key`), then run
|
||||||
`sops --encrypt --in-place secrets/secrets.yaml` before committing.
|
`sops --encrypt --in-place secrets/secrets.yaml` before committing.
|
||||||
|
|
||||||
- [ ] **`secrets/.sops.yaml` — add real age keys**: Replace both
|
- [x] **`secrets/.sops.yaml` — PGP key**: The encryption subkey
|
||||||
`AGE-PUBLIC-KEY-*` placeholders with actual public keys (workstation + Pi).
|
`076AA297579A0064` is already in `.sops.yaml`.
|
||||||
|
|
||||||
- [ ] **Cloudflare Tunnel**: Create the tunnel in the Zero Trust dashboard,
|
- [ ] **Cloudflare Tunnel**: Create the tunnel in the Zero Trust dashboard,
|
||||||
copy the tunnel token into secrets, and configure public hostnames. See
|
copy the tunnel token into secrets, and configure public hostnames. See
|
||||||
`modules/cloudflared.nix` and Phase 3 of `PORTING.md` for details.
|
`modules/cloudflared.nix` and Phase 3 of `PORTING.md` for details.
|
||||||
|
|
||||||
|
- [ ] **Second machine**: When ready, add `hosts/pi-secondary/` and uncomment
|
||||||
|
the `pi-secondary` entry in `flake.nix`. Services communicating cross-machine
|
||||||
|
should reference the primary Pi's LAN IP instead of `127.0.0.1`.
|
||||||
|
|
||||||
|
- [ ] **Jellyfin and Transmission**: Both modules are written and importable
|
||||||
|
but disabled. Enable in `hosts/pi-main/default.nix` when ready:
|
||||||
|
```nix
|
||||||
|
homey.jellyfin.enable = true;
|
||||||
|
homey.transmission.enable = true;
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Backup — S3 credentials**: Add `restic/s3_access_key_id` and
|
||||||
|
`restic/s3_secret_access_key` to secrets, and set `homey.backup.repository`
|
||||||
|
to your S3-compatible bucket URL in `hosts/pi-main/default.nix`.
|
||||||
|
|
||||||
|
- [ ] **Backup — offload script**: Write `scripts/offload-backup.sh` for
|
||||||
|
manually copying snapshots to a local disk (USB attached to Pi, or a disk
|
||||||
|
on your workstation). Uses `restic copy` to clone from the S3 repo into a
|
||||||
|
local restic repo on the target path. See `TODO.org` for design notes.
|
||||||
|
|
||||||
|
### Post- Pi first boot
|
||||||
|
|
||||||
|
These items require the Pi to be built, flashed, and booted at least once.
|
||||||
|
|
||||||
|
- [ ] **`secrets/.sops.yaml` — add Pi age key**: After generating the age key
|
||||||
|
on the Pi (`age-keygen -o /var/lib/sops-nix/key.txt`), add the public key
|
||||||
|
to `.sops.yaml` alongside the existing PGP key, then run
|
||||||
|
`sops updatekeys secrets/secrets.yaml`.
|
||||||
|
|
||||||
|
- [ ] **`hosts/pi-main/hardware.nix` — verify SD card labels**: The file
|
||||||
|
assumes partition labels `NIXOS_SD` (root) and `FIRMWARE` (boot). Relabel
|
||||||
|
after flashing if they differ, or update the `fileSystems` entries.
|
||||||
|
|
||||||
- [ ] **Gitea LDAP auth**: After first start, configure LDAP authentication
|
- [ ] **Gitea LDAP auth**: After first start, configure LDAP authentication
|
||||||
in Gitea's admin panel (Admin → Authentication Sources → Add LDAP source).
|
in Gitea's admin panel (Admin → Authentication Sources → Add LDAP source).
|
||||||
The old Helm chart had this commented out; it must be done manually once.
|
The old Helm chart had this commented out; it must be done manually once.
|
||||||
@@ -250,18 +285,3 @@ production-ready:
|
|||||||
- [ ] **Nextcloud LDAP app**: After restoring the Nextcloud volume, verify
|
- [ ] **Nextcloud LDAP app**: After restoring the Nextcloud volume, verify
|
||||||
the LDAP Users and Contacts app is still configured correctly
|
the LDAP Users and Contacts app is still configured correctly
|
||||||
(Admin → LDAP/AD Integration).
|
(Admin → LDAP/AD Integration).
|
||||||
|
|
||||||
- [ ] **`hosts/pi-main/hardware.nix` — verify SD card labels**: The file
|
|
||||||
assumes partition labels `NIXOS_SD` (root) and `FIRMWARE` (boot). Relabel
|
|
||||||
after flashing if they differ, or update the `fileSystems` entries.
|
|
||||||
|
|
||||||
- [ ] **Second machine**: When ready, add `hosts/pi-secondary/` and uncomment
|
|
||||||
the `pi-secondary` entry in `flake.nix`. Services communicating cross-machine
|
|
||||||
should reference the primary Pi's LAN IP instead of `127.0.0.1`.
|
|
||||||
|
|
||||||
- [ ] **Jellyfin and Transmission**: Both modules are written and importable
|
|
||||||
but disabled. Enable in `hosts/pi-main/default.nix` when ready:
|
|
||||||
```nix
|
|
||||||
homey.jellyfin.enable = true;
|
|
||||||
homey.transmission.enable = true;
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -1,8 +1,20 @@
|
|||||||
{
|
{
|
||||||
description = "Homey - self-hosted home server NixOS configuration";
|
description = "Homey - self-hosted home server NixOS configuration";
|
||||||
|
|
||||||
|
# Binary cache for pre-built Raspberry Pi kernel + firmware packages.
|
||||||
|
# nixos-raspberrypi builds against its own pinned nixpkgs and publishes
|
||||||
|
# to this cache — using it avoids compiling linuxPackages_rpi4 from source.
|
||||||
|
nixConfig = {
|
||||||
|
extra-substituters = [
|
||||||
|
"https://nixos-raspberrypi.cachix.org"
|
||||||
|
];
|
||||||
|
extra-trusted-public-keys = [
|
||||||
|
"nixos-raspberrypi.cachix.org-1:4iMO9LXa8BqhU+Rpg6LQKiGa2lsNh/j2oiYLNOQ5sPI="
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
|
||||||
|
|
||||||
# sops-nix for secret management
|
# sops-nix for secret management
|
||||||
sops-nix = {
|
sops-nix = {
|
||||||
@@ -10,17 +22,18 @@
|
|||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
# Caddy with Cloudflare DNS plugin (not in nixpkgs mainline)
|
# Raspberry Pi hardware support — provides vendor kernel, firmware,
|
||||||
caddy-cloudflare = {
|
# bootloader management, and a binary cache for pre-built aarch64 packages.
|
||||||
url = "github:NixOS/nixpkgs/nixos-24.11"; # see modules/caddy.nix for override
|
# Intentionally NOT following our nixpkgs: the cache is built against the
|
||||||
};
|
# flake's own pinned nixpkgs, so following would invalidate all cache hits.
|
||||||
|
nixos-raspberrypi.url = "github:nvmd/nixos-raspberrypi/main";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, sops-nix, ... }@inputs:
|
outputs = { self, nixpkgs, sops-nix, nixos-raspberrypi, ... }@inputs:
|
||||||
let
|
let
|
||||||
# Shared specialArgs passed to every host
|
# Shared specialArgs passed to every host
|
||||||
commonArgs = {
|
commonArgs = {
|
||||||
inherit inputs;
|
inherit inputs nixos-raspberrypi;
|
||||||
# Top-level site config — override per-host if needed
|
# Top-level site config — override per-host if needed
|
||||||
homeyConfig = {
|
homeyConfig = {
|
||||||
domain = "home.zakobar.com"; # base domain for all services
|
domain = "home.zakobar.com"; # base domain for all services
|
||||||
@@ -31,12 +44,24 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
mkHost = { system, hostPath, extraModules ? [] }:
|
# nixos-raspberrypi.lib.nixosSystem is a drop-in replacement for
|
||||||
nixpkgs.lib.nixosSystem {
|
# nixpkgs.lib.nixosSystem that:
|
||||||
inherit system;
|
# - injects vendor kernel/firmware overlays
|
||||||
|
# - wires up the trusted cache substituters
|
||||||
|
# - passes nixos-raspberrypi into specialArgs automatically
|
||||||
|
# It uses the flake's own pinned nixpkgs by default (currently 25.11).
|
||||||
|
mkHost = { hostPath, extraModules ? [] }:
|
||||||
|
nixos-raspberrypi.lib.nixosSystem {
|
||||||
specialArgs = commonArgs;
|
specialArgs = commonArgs;
|
||||||
modules = [
|
modules = [
|
||||||
sops-nix.nixosModules.sops
|
sops-nix.nixosModules.sops
|
||||||
|
# RPi 4 base: vendor kernel (linuxPackages_rpi4), firmware,
|
||||||
|
# bootloader (u-boot), initrd modules, config.txt management
|
||||||
|
nixos-raspberrypi.nixosModules.raspberry-pi-4.base
|
||||||
|
# SD image target — provides system.build.sdImage
|
||||||
|
({ modulesPath, ... }: {
|
||||||
|
imports = [ "${modulesPath}/installer/sd-card/sd-image-aarch64.nix" ];
|
||||||
|
})
|
||||||
hostPath
|
hostPath
|
||||||
./modules/common.nix
|
./modules/common.nix
|
||||||
./modules/storage.nix
|
./modules/storage.nix
|
||||||
@@ -56,15 +81,72 @@
|
|||||||
in {
|
in {
|
||||||
nixosConfigurations = {
|
nixosConfigurations = {
|
||||||
|
|
||||||
|
# Bootstrap image — flash this first.
|
||||||
|
# Minimal: SSH key, WiFi, static IP. No sops, no services.
|
||||||
|
# Purpose: boot the Pi, generate the age key, then deploy pi-main.
|
||||||
|
pi-main-bootstrap = nixos-raspberrypi.lib.nixosSystem {
|
||||||
|
specialArgs = commonArgs;
|
||||||
|
modules = [
|
||||||
|
nixos-raspberrypi.nixosModules.raspberry-pi-4.base
|
||||||
|
({ modulesPath, ... }: {
|
||||||
|
imports = [ "${modulesPath}/installer/sd-card/sd-image-aarch64.nix" ];
|
||||||
|
})
|
||||||
|
./hosts/pi-main/hardware.nix
|
||||||
|
({ pkgs, lib, ... }: {
|
||||||
|
networking.hostName = "pi-main";
|
||||||
|
time.timeZone = commonArgs.homeyConfig.timezone;
|
||||||
|
i18n.defaultLocale = "en_US.UTF-8";
|
||||||
|
system.stateVersion = "25.05";
|
||||||
|
|
||||||
|
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
||||||
|
nixpkgs.config.allowUnfree = true;
|
||||||
|
|
||||||
|
# WiFi — PSK inline (bootstrap only, not in Nix store long-term)
|
||||||
|
networking.wireless = {
|
||||||
|
enable = true;
|
||||||
|
networks."Zakobar".psk = "0502711157";
|
||||||
|
};
|
||||||
|
networking.interfaces.wlan0.ipv4.addresses = [{
|
||||||
|
address = "192.168.1.100";
|
||||||
|
prefixLength = 24;
|
||||||
|
}];
|
||||||
|
networking.useDHCP = false;
|
||||||
|
networking.interfaces.wlan0.useDHCP = false;
|
||||||
|
networking.defaultGateway = "192.168.1.1";
|
||||||
|
networking.nameservers = [ "1.1.1.1" "8.8.8.8" ];
|
||||||
|
networking.firewall.allowedTCPPorts = [ 22 ];
|
||||||
|
|
||||||
|
# SSH — key only, no passwords, no root
|
||||||
|
services.openssh = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
PasswordAuthentication = false;
|
||||||
|
PermitRootLogin = "no";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
users.mutableUsers = false;
|
||||||
|
users.users.admin = {
|
||||||
|
isNormalUser = true;
|
||||||
|
extraGroups = [ "wheel" ];
|
||||||
|
openssh.authorizedKeys.keys = [
|
||||||
|
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDBFZRqiTsOCAJPMqUyMeLd2MbyjdGoyqDVq5/Inhb6EOaM1NUGG4b6FPmYgFLyJIm5LC9BOo6M7npiaiOs/zMqp+hoGLNQUNwm5/G0uy1bjkEfKdUTdGnJ2+M9rkxrR1c+KXrjkiqECqTbnPE4mJbGyVxBW2MwMeP5w8c0DB5KO528PetvHMPPQuEdXyZzDI4kKtVpMlJoPIrIGlNFX0G/wrgXcM4zU1snOTuYGqZnWW++4kBsgIlRKpf/bLJyUMTp30eLVr0fQ6OMBtj1tzUUBaaowU6VGYQQDU/rIh/NpkA2cEVPXZegM4OohkAqrJBFPIAg90WD9Z/SyQlz0Jn8PpAloP0Cuq2vVRr+QLEwxqGiFq91YQ2VtwksMHwJGVrXRCNegpxTZQijWMEd+o0FD2cEd7Ftw6v2L6g12GJ3QGX/q0d/u0GongLLa9fPXl4VoAu7AL+cUcbX/SS7RCG8kYAR3DwOazVbK0NWEdwvWdoSU4lZ3j2at1xqMGjHjyLiTeUqZBjm+Sl5MJWIYNg+8hnONljvggg4SzDFDAkgVLZtOCaZibsMA1ucGR7VRCM09uoaEI4/ZS5pCBtYcp8X67Bv67Og8s2NFf5sUfYBPPKpdBSs+dEPycNVff6JlmzfNiyzLawacGKIDWYSgkOl43N/5ehtpsL3HMZ+5SVNIw=="
|
||||||
|
];
|
||||||
|
};
|
||||||
|
security.sudo.wheelNeedsPassword = false;
|
||||||
|
|
||||||
|
environment.systemPackages = [ pkgs.age pkgs.vim ];
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
# Primary Raspberry Pi 4
|
# Primary Raspberry Pi 4
|
||||||
pi-main = mkHost {
|
pi-main = mkHost {
|
||||||
system = "aarch64-linux";
|
|
||||||
hostPath = ./hosts/pi-main/default.nix;
|
hostPath = ./hosts/pi-main/default.nix;
|
||||||
};
|
};
|
||||||
|
|
||||||
# Future second machine (placeholder — uncomment and configure when ready)
|
# Future second machine (placeholder — uncomment and configure when ready)
|
||||||
# pi-secondary = mkHost {
|
# pi-secondary = mkHost {
|
||||||
# system = "x86_64-linux"; # or aarch64-linux for another Pi
|
|
||||||
# hostPath = ./hosts/pi-secondary/default.nix;
|
# hostPath = ./hosts/pi-secondary/default.nix;
|
||||||
# };
|
# };
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,34 @@
|
|||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
networking.hostName = "pi-main";
|
networking.hostName = "pi-main";
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# WiFi — static IP, always connect to home network
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
networking.wireless = {
|
||||||
|
enable = true;
|
||||||
|
# secretsFile is read by wpa_supplicant at runtime; values are literal
|
||||||
|
# (not env vars). The key name after "ext:" must match a line in the file
|
||||||
|
# formatted as: key_name=the-actual-password
|
||||||
|
secretsFile = config.sops.secrets."wifi/psk".path;
|
||||||
|
networks."Zakobar".pskRaw = "ext:wifi_psk";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Static IP on wlan0
|
||||||
|
networking.interfaces.wlan0.ipv4.addresses = [{
|
||||||
|
address = "192.168.1.100";
|
||||||
|
prefixLength = 24;
|
||||||
|
}];
|
||||||
|
networking.defaultGateway = "192.168.1.1";
|
||||||
|
networking.nameservers = [ "1.1.1.1" "8.8.8.8" ];
|
||||||
|
|
||||||
|
# Disable DHCP on wlan0 — we're using a static address
|
||||||
|
networking.useDHCP = false;
|
||||||
|
networking.interfaces.wlan0.useDHCP = false;
|
||||||
|
|
||||||
|
# The secret file must contain exactly one line: wifi_psk=<your-password>
|
||||||
|
# Add it with: sops secrets/secrets.yaml → wifi/psk: "wifi_psk=YourPassword"
|
||||||
|
sops.secrets."wifi/psk" = { owner = "root"; mode = "0400"; };
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Admin user
|
# Admin user
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
@@ -22,7 +50,7 @@
|
|||||||
extraGroups = [ "wheel" "podman" ];
|
extraGroups = [ "wheel" "podman" ];
|
||||||
# Paste your SSH public key here
|
# Paste your SSH public key here
|
||||||
openssh.authorizedKeys.keys = [
|
openssh.authorizedKeys.keys = [
|
||||||
"ssh-ed25519 AAAA... your-key-here"
|
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDBFZRqiTsOCAJPMqUyMeLd2MbyjdGoyqDVq5/Inhb6EOaM1NUGG4b6FPmYgFLyJIm5LC9BOo6M7npiaiOs/zMqp+hoGLNQUNwm5/G0uy1bjkEfKdUTdGnJ2+M9rkxrR1c+KXrjkiqECqTbnPE4mJbGyVxBW2MwMeP5w8c0DB5KO528PetvHMPPQuEdXyZzDI4kKtVpMlJoPIrIGlNFX0G/wrgXcM4zU1snOTuYGqZnWW++4kBsgIlRKpf/bLJyUMTp30eLVr0fQ6OMBtj1tzUUBaaowU6VGYQQDU/rIh/NpkA2cEVPXZegM4OohkAqrJBFPIAg90WD9Z/SyQlz0Jn8PpAloP0Cuq2vVRr+QLEwxqGiFq91YQ2VtwksMHwJGVrXRCNegpxTZQijWMEd+o0FD2cEd7Ftw6v2L6g12GJ3QGX/q0d/u0GongLLa9fPXl4VoAu7AL+cUcbX/SS7RCG8kYAR3DwOazVbK0NWEdwvWdoSU4lZ3j2at1xqMGjHjyLiTeUqZBjm+Sl5MJWIYNg+8hnONljvggg4SzDFDAkgVLZtOCaZibsMA1ucGR7VRCM09uoaEI4/ZS5pCBtYcp8X67Bv67Og8s2NFf5sUfYBPPKpdBSs+dEPycNVff6JlmzfNiyzLawacGKIDWYSgkOl43N/5ehtpsL3HMZ+5SVNIw=="
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -34,7 +62,7 @@
|
|||||||
homey.storage = {
|
homey.storage = {
|
||||||
# Replace with the actual by-id path of your USB drive.
|
# Replace with the actual by-id path of your USB drive.
|
||||||
# Find it: ls -la /dev/disk/by-id/ | grep -v part
|
# Find it: ls -la /dev/disk/by-id/ | grep -v part
|
||||||
device = "/dev/disk/by-id/REPLACE-WITH-YOUR-DRIVE-ID";
|
device = "/dev/disk/by-id/usb-WD_Ext_HDD_1021_5743415A4146313531393031-0:0-part1";
|
||||||
mountPoint = "/mnt/data";
|
mountPoint = "/mnt/data";
|
||||||
fsType = "ext4";
|
fsType = "ext4";
|
||||||
};
|
};
|
||||||
@@ -66,7 +94,7 @@
|
|||||||
# "sftp:user@nas.local:/backups/homey"
|
# "sftp:user@nas.local:/backups/homey"
|
||||||
# "b2:your-bucket-name:homey"
|
# "b2:your-bucket-name:homey"
|
||||||
# "rclone:remote:homey"
|
# "rclone:remote:homey"
|
||||||
homey.backup.repository = "sftp:REPLACE-WITH-BACKUP-DESTINATION";
|
homey.backup.repository = "s3:https://s3.us-east-005.backblazeb2.com/zakobar-home-backup";
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Local DNS overrides (optional — makes LAN clients hit the Pi directly
|
# Local DNS overrides (optional — makes LAN clients hit the Pi directly
|
||||||
|
|||||||
+16
-50
@@ -2,56 +2,37 @@
|
|||||||
|
|
||||||
# Hardware configuration for the primary Raspberry Pi 4 (8 GB).
|
# Hardware configuration for the primary Raspberry Pi 4 (8 GB).
|
||||||
#
|
#
|
||||||
# SD card layout assumed:
|
# nixos-raspberrypi's raspberry-pi-4.base module (imported in flake.nix)
|
||||||
# /dev/mmcblk0p1 — /boot/firmware (FAT32, ~256 MB)
|
# provides everything that nixos-hardware.raspberry-pi-4 previously did:
|
||||||
# /dev/mmcblk0p2 — / (ext4)
|
# - linuxPackages_rpi4 vendor kernel + matching firmware
|
||||||
|
# - u-boot bootloader with /boot/firmware partition management
|
||||||
|
# - initrd modules (xhci_pci, usbhid, usb_storage, vc4, pcie_brcmstb, etc.)
|
||||||
|
# - config.txt generation
|
||||||
|
#
|
||||||
|
# This file adds only host-specific overrides on top of that.
|
||||||
#
|
#
|
||||||
# External HD:
|
# External HD:
|
||||||
# Set homey.storage.device to the by-id path of your USB drive.
|
# Set homey.storage.device to the by-id path of your USB drive.
|
||||||
# Example: /dev/disk/by-id/usb-WD_Elements_12345-0:0-part1
|
|
||||||
# Find it with: ls -la /dev/disk/by-id/
|
# Find it with: ls -la /dev/disk/by-id/
|
||||||
#
|
#
|
||||||
# To generate this file fresh after installing NixOS on the Pi, run:
|
# TODO: Verify SD card partition labels after first flash.
|
||||||
# nixos-generate-config --show-hardware-config
|
# The config assumes labels NIXOS_SD (root) and FIRMWARE (boot).
|
||||||
# and merge the output here.
|
# Check with: lsblk -o NAME,LABEL
|
||||||
|
# Update fileSystems entries below if they differ.
|
||||||
|
|
||||||
{
|
{
|
||||||
imports = [
|
# tmpfs for /tmp — keep the SD card writes down
|
||||||
(modulesPath + "/installer/scan/not-detected.nix")
|
boot.tmp.useTmpfs = true;
|
||||||
];
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
# Boot loader — Raspberry Pi 4 uses U-Boot / extlinux
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
boot = {
|
|
||||||
loader = {
|
|
||||||
grub.enable = false;
|
|
||||||
generic-extlinux-compatible.enable = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
# Pi 4 kernel — use the mainline kernel with RPi patches
|
|
||||||
kernelPackages = pkgs.linuxPackages_rpi4;
|
|
||||||
|
|
||||||
# tmpfs for /tmp — keep the SD card writes down
|
|
||||||
tmp.useTmpfs = true;
|
|
||||||
|
|
||||||
# Modules needed for USB storage (external HD)
|
|
||||||
initrd.availableKernelModules = [ "xhci_pci" "usbhid" "usb_storage" "uas" ];
|
|
||||||
kernelModules = [];
|
|
||||||
extraModulePackages = [];
|
|
||||||
};
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
# Filesystems
|
# Filesystems
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
fileSystems."/" = {
|
fileSystems."/" = {
|
||||||
device = "/dev/disk/by-label/NIXOS_SD"; # label the root partition NIXOS_SD when flashing
|
device = "/dev/disk/by-label/NIXOS_SD";
|
||||||
fsType = "ext4";
|
fsType = "ext4";
|
||||||
options = [ "noatime" ];
|
options = [ "noatime" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
fileSystems."/boot/firmware" = {
|
fileSystems."/boot/firmware" = {
|
||||||
device = "/dev/disk/by-label/FIRMWARE"; # FAT32 boot partition
|
device = "/dev/disk/by-label/FIRMWARE";
|
||||||
fsType = "vfat";
|
fsType = "vfat";
|
||||||
options = [ "fmask=0022" "dmask=0022" ];
|
options = [ "fmask=0022" "dmask=0022" ];
|
||||||
};
|
};
|
||||||
@@ -61,24 +42,9 @@
|
|||||||
|
|
||||||
swapDevices = [];
|
swapDevices = [];
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
# Hardware
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
hardware = {
|
|
||||||
# Enable the RPi firmware (needed for GPU, WiFi, Bluetooth)
|
|
||||||
raspberry-pi."4".apply-overlays-dtmerge.enable = true;
|
|
||||||
|
|
||||||
# Disable GPU memory split for a headless server (gives more RAM to OS)
|
|
||||||
# Set via config.txt if needed: gpu_mem=16
|
|
||||||
};
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
# Platform
|
# Platform
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
nixpkgs.hostPlatform = lib.mkDefault "aarch64-linux";
|
nixpkgs.hostPlatform = lib.mkDefault "aarch64-linux";
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
# Power management
|
# Power management
|
||||||
# -------------------------------------------------------------------------
|
|
||||||
powerManagement.cpuFreqGovernor = lib.mkDefault "ondemand";
|
powerManagement.cpuFreqGovernor = lib.mkDefault "ondemand";
|
||||||
}
|
}
|
||||||
|
|||||||
+45
-4
@@ -8,11 +8,27 @@
|
|||||||
# Before a backup, Nextcloud is put into maintenance mode and postgres is
|
# Before a backup, Nextcloud is put into maintenance mode and postgres is
|
||||||
# pg_dump'd to a file. This ensures consistent DB backups.
|
# pg_dump'd to a file. This ensures consistent DB backups.
|
||||||
#
|
#
|
||||||
|
# Backup strategy — two tiers:
|
||||||
|
#
|
||||||
|
# 1. Automatic daily backup to an S3-compatible bucket (primary offsite copy).
|
||||||
|
# Set the repository URL to your bucket in hosts/pi-main/default.nix, e.g.:
|
||||||
|
# homey.backup.repository = "s3:https://s3.us-west-002.backblazeb2.com/your-bucket";
|
||||||
|
# S3 credentials are injected via environment variables from sops secrets:
|
||||||
|
# restic/s3_access_key_id → AWS_ACCESS_KEY_ID
|
||||||
|
# restic/s3_secret_access_key → AWS_SECRET_ACCESS_KEY
|
||||||
|
#
|
||||||
|
# 2. Manual offload to a local disk (USB drive plugged into Pi, or workstation disk).
|
||||||
|
# Use scripts/offload-backup.sh --target /path/to/mounted/disk
|
||||||
|
# That script uses `restic copy` to clone snapshots from the S3 repo into a
|
||||||
|
# local restic repo on the target disk, preserving deduplication.
|
||||||
|
#
|
||||||
# Secrets consumed from sops:
|
# Secrets consumed from sops:
|
||||||
# restic/password
|
# restic/password
|
||||||
|
# restic/s3_access_key_id (if using S3 backend)
|
||||||
|
# restic/s3_secret_access_key (if using S3 backend)
|
||||||
#
|
#
|
||||||
# The backup repository URL is set per-host in default.nix:
|
# The backup repository URL is set per-host in default.nix:
|
||||||
# homey.backup.repository = "sftp:user@nas:/backups/homey";
|
# homey.backup.repository = "s3:https://s3.us-west-002.backblazeb2.com/bucket";
|
||||||
#
|
#
|
||||||
# Restore:
|
# Restore:
|
||||||
# restic -r <repo> restore latest --target /mnt/data
|
# restic -r <repo> restore latest --target /mnt/data
|
||||||
@@ -58,7 +74,9 @@ in
|
|||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Secrets
|
# Secrets
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
sops.secrets."restic/password" = { owner = "root"; };
|
sops.secrets."restic/password" = { owner = "root"; };
|
||||||
|
sops.secrets."restic/s3_access_key_id" = { owner = "root"; };
|
||||||
|
sops.secrets."restic/s3_secret_access_key" = { owner = "root"; };
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Pre-backup hook: pg_dump + nextcloud maintenance mode
|
# Pre-backup hook: pg_dump + nextcloud maintenance mode
|
||||||
@@ -105,7 +123,9 @@ in
|
|||||||
services.restic.backups.homey = {
|
services.restic.backups.homey = {
|
||||||
repository = cfg.repository;
|
repository = cfg.repository;
|
||||||
passwordFile = config.sops.secrets."restic/password".path;
|
passwordFile = config.sops.secrets."restic/password".path;
|
||||||
cacheDir = "${dataDir}/restic-cache";
|
|
||||||
|
# Runtime env file written by ExecStartPre (see systemd override below)
|
||||||
|
environmentFile = "/run/restic-homey-secrets.env";
|
||||||
|
|
||||||
paths = [
|
paths = [
|
||||||
"${dataDir}/openldap"
|
"${dataDir}/openldap"
|
||||||
@@ -136,10 +156,31 @@ in
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
# Wire the pre/post hooks around the restic job
|
# Wire the pre/post hooks around the restic job and inject secrets
|
||||||
systemd.services."restic-backups-homey" = {
|
systemd.services."restic-backups-homey" = {
|
||||||
requires = [ "homey-backup-pre.service" ];
|
requires = [ "homey-backup-pre.service" ];
|
||||||
after = [ "homey-backup-pre.service" ];
|
after = [ "homey-backup-pre.service" ];
|
||||||
|
serviceConfig = {
|
||||||
|
# Write runtime env file with actual secret values (restic needs the
|
||||||
|
# raw values; it does not support _FILE suffix env vars).
|
||||||
|
ExecStartPre = [
|
||||||
|
(pkgs.writeShellScript "restic-inject-secrets" ''
|
||||||
|
install -m 0600 /dev/null /run/restic-homey-secrets.env
|
||||||
|
{
|
||||||
|
printf 'AWS_ACCESS_KEY_ID=%s\n' \
|
||||||
|
"$(cat ${config.sops.secrets."restic/s3_access_key_id".path})"
|
||||||
|
printf 'AWS_SECRET_ACCESS_KEY=%s\n' \
|
||||||
|
"$(cat ${config.sops.secrets."restic/s3_secret_access_key".path})"
|
||||||
|
printf 'RESTIC_CACHE_DIR=%s\n' "${dataDir}/restic-cache"
|
||||||
|
} >> /run/restic-homey-secrets.env
|
||||||
|
'')
|
||||||
|
];
|
||||||
|
ExecStopPost = [
|
||||||
|
(pkgs.writeShellScript "restic-cleanup-secrets" ''
|
||||||
|
rm -f /run/restic-homey-secrets.env
|
||||||
|
'')
|
||||||
|
];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.services."homey-backup-post" = {
|
systemd.services."homey-backup-post" = {
|
||||||
|
|||||||
+27
-22
@@ -18,16 +18,14 @@ let
|
|||||||
cfg = config.homey.caddy;
|
cfg = config.homey.caddy;
|
||||||
domain = homeyConfig.domain;
|
domain = homeyConfig.domain;
|
||||||
|
|
||||||
# Build Caddy with the Cloudflare DNS plugin.
|
# Build Caddy with the Cloudflare DNS plugin using the nixos-25.05 API.
|
||||||
# This compiles on the Pi (slow once, cached after).
|
# `withPlugins` is a passthru function on the caddy package; it uses xcaddy
|
||||||
caddyWithCloudflare = pkgs.caddy.override {
|
# under the hood to produce a fixed-output derivation.
|
||||||
externalPlugins = [
|
caddyWithCloudflare = pkgs.caddy.withPlugins {
|
||||||
{
|
plugins = [
|
||||||
name = "github.com/caddy-dns/cloudflare";
|
"github.com/caddy-dns/cloudflare@v0.2.2-0.20250724223520-f589a18c0f5d"
|
||||||
version = "89f16b99c18ef49c8bb470a82f895bce01cbaece";
|
|
||||||
}
|
|
||||||
];
|
];
|
||||||
vendorHash = lib.fakeHash; # replace with real hash after first build
|
hash = "sha256-2Fb2fgM7YhWk9kBnnNGb85MJkAkgzXiI1fb6eK3ykIE=";
|
||||||
};
|
};
|
||||||
|
|
||||||
# Reusable Authelia forward_auth snippet
|
# Reusable Authelia forward_auth snippet
|
||||||
@@ -147,34 +145,41 @@ in
|
|||||||
"torrent.${domain}" = {
|
"torrent.${domain}" = {
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
${autheliaForwardAuth}
|
${autheliaForwardAuth}
|
||||||
reverse_proxy localhost:9091_transmission
|
reverse_proxy localhost:9092
|
||||||
'';
|
'';
|
||||||
# NOTE: transmission uses 9091 too; we'll bind it to 9092 in its
|
# NOTE: transmission is bound to 9092 to avoid clash with authelia on 9091.
|
||||||
# module to avoid a clash with authelia.
|
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Pass Cloudflare token as env var to the caddy systemd unit
|
# Pass Cloudflare token as env var to the caddy systemd unit.
|
||||||
|
#
|
||||||
|
# The caddy-dns/cloudflare plugin reads CLOUDFLARE_API_TOKEN directly.
|
||||||
|
# sops decrypts the secret to a file at runtime; we write a transient
|
||||||
|
# env file to /run/ in ExecStartPre so systemd picks it up via
|
||||||
|
# EnvironmentFile. The file is removed in ExecStopPost.
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
systemd.services.caddy = {
|
systemd.services.caddy = {
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
EnvironmentFile = pkgs.writeText "caddy-cf-env"
|
EnvironmentFile = "/run/caddy-secrets.env";
|
||||||
"CLOUDFLARE_API_TOKEN_FILE=${config.sops.secrets."cloudflare/api_token".path}";
|
|
||||||
# Caddy supports _FILE suffix for env vars via its secret file reader,
|
|
||||||
# but cloudflare plugin reads CLOUDFLARE_API_TOKEN directly.
|
|
||||||
# We write a wrapper ExecStartPre to populate the env var from the file:
|
|
||||||
ExecStartPre = [
|
ExecStartPre = [
|
||||||
(pkgs.writeShellScript "caddy-inject-cf-token" ''
|
(pkgs.writeShellScript "caddy-inject-cf-token" ''
|
||||||
export CLOUDFLARE_API_TOKEN=$(cat ${config.sops.secrets."cloudflare/api_token".path})
|
install -m 0600 /dev/null /run/caddy-secrets.env
|
||||||
systemctl set-environment CLOUDFLARE_API_TOKEN="$CLOUDFLARE_API_TOKEN"
|
printf 'CLOUDFLARE_API_TOKEN=%s\n' \
|
||||||
|
"$(cat ${config.sops.secrets."cloudflare/api_token".path})" \
|
||||||
|
> /run/caddy-secrets.env
|
||||||
|
'')
|
||||||
|
];
|
||||||
|
ExecStopPost = [
|
||||||
|
(pkgs.writeShellScript "caddy-cleanup-env" ''
|
||||||
|
rm -f /run/caddy-secrets.env
|
||||||
'')
|
'')
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
after = lib.mkAfter [ "podman-authelia.service" ];
|
after = lib.mkAfter [ "podman-authelia.service" ];
|
||||||
wants = lib.mkAfter [ "podman-authelia.service" ];
|
wants = lib.mkAfter [ "podman-authelia.service" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
|
|||||||
+28
-17
@@ -46,32 +46,43 @@ in
|
|||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# cloudflared service
|
# cloudflared service
|
||||||
# NixOS 24.11 ships services.cloudflared natively.
|
#
|
||||||
|
# We use the token-based tunnel approach (cloudflared tunnel run --token).
|
||||||
|
# This needs no credentials file and no local tunnel config — just the
|
||||||
|
# token from the Cloudflare dashboard.
|
||||||
|
#
|
||||||
|
# Rather than using services.cloudflared.tunnels (which requires a
|
||||||
|
# credentialsFile), we create a plain systemd service that runs cloudflared
|
||||||
|
# directly with the token read from the sops secret.
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
services.cloudflared = {
|
users.users.cloudflared = {
|
||||||
enable = true;
|
isSystemUser = true;
|
||||||
tunnels = {
|
group = "cloudflared";
|
||||||
"pi-main" = {
|
description = "cloudflared tunnel daemon";
|
||||||
# credentialsFile is not used with token-based auth;
|
|
||||||
# the token is passed via environment variable instead.
|
|
||||||
# We override the systemd unit below to inject it.
|
|
||||||
default = "http_status:404";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
users.groups.cloudflared = {};
|
||||||
|
|
||||||
# Inject the tunnel token from the sops secret file
|
systemd.services."cloudflared-tunnel" = {
|
||||||
systemd.services."cloudflared-tunnel-pi-main" = {
|
description = "Cloudflare Tunnel (token-based)";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
after = [ "network-online.target" "caddy.service" ];
|
||||||
|
wants = [ "network-online.target" "caddy.service" ];
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
ExecStart = lib.mkForce (pkgs.writeShellScript "cloudflared-start" ''
|
Type = "simple";
|
||||||
|
User = "cloudflared";
|
||||||
|
Group = "cloudflared";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = "5s";
|
||||||
|
ExecStart = pkgs.writeShellScript "cloudflared-start" ''
|
||||||
exec ${pkgs.cloudflared}/bin/cloudflared tunnel \
|
exec ${pkgs.cloudflared}/bin/cloudflared tunnel \
|
||||||
--no-autoupdate \
|
--no-autoupdate \
|
||||||
run \
|
run \
|
||||||
--token "$(cat ${config.sops.secrets."cloudflare/tunnel_token".path})"
|
--token "$(cat ${config.sops.secrets."cloudflare/tunnel_token".path})"
|
||||||
'');
|
'';
|
||||||
|
# Hardening
|
||||||
|
NoNewPrivileges = true;
|
||||||
|
PrivateTmp = true;
|
||||||
};
|
};
|
||||||
after = lib.mkAfter [ "caddy.service" ];
|
|
||||||
wants = lib.mkAfter [ "caddy.service" ];
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-3
@@ -11,10 +11,20 @@
|
|||||||
nix = {
|
nix = {
|
||||||
settings = {
|
settings = {
|
||||||
experimental-features = [ "nix-command" "flakes" ];
|
experimental-features = [ "nix-command" "flakes" ];
|
||||||
# Save disk space on Pi
|
|
||||||
auto-optimise-store = true;
|
auto-optimise-store = true;
|
||||||
|
# Extra binary caches — speeds up aarch64-linux builds significantly
|
||||||
|
substituters = [
|
||||||
|
"https://cache.nixos.org"
|
||||||
|
"https://nix-community.cachix.org"
|
||||||
|
# Pre-built RPi vendor kernel + firmware (linuxPackages_rpi4, etc.)
|
||||||
|
"https://nixos-raspberrypi.cachix.org"
|
||||||
|
];
|
||||||
|
trusted-public-keys = [
|
||||||
|
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
|
||||||
|
"nix-community.cachix.org-1:mB9FkXj6Q3Q4ohOcbM4FJ9Z1X2kCrVK4vZOqsDqqNqk="
|
||||||
|
"nixos-raspberrypi.cachix.org-1:4iMO9LXa8BqhU+Rpg6LQKiGa2lsNh/j2oiYLNOQ5sPI="
|
||||||
|
];
|
||||||
};
|
};
|
||||||
# Weekly garbage collection — keeps the system from filling the SD card
|
|
||||||
gc = {
|
gc = {
|
||||||
automatic = true;
|
automatic = true;
|
||||||
dates = "weekly";
|
dates = "weekly";
|
||||||
@@ -113,5 +123,5 @@
|
|||||||
# System state version — do not change after first install
|
# System state version — do not change after first install
|
||||||
# (tracks NixOS backwards-compat markers)
|
# (tracks NixOS backwards-compat markers)
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
system.stateVersion = "24.11";
|
system.stateVersion = "25.05";
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -17,8 +17,8 @@
|
|||||||
creation_rules:
|
creation_rules:
|
||||||
- path_regex: secrets/secrets\.yaml$
|
- path_regex: secrets/secrets\.yaml$
|
||||||
key_groups:
|
key_groups:
|
||||||
- age:
|
- pgp
|
||||||
|
- 076AA297579A0064
|
||||||
|
# - age:
|
||||||
# Pi main host key — replace with output of `age-keygen -y /var/lib/sops-nix/key.txt`
|
# Pi main host key — replace with output of `age-keygen -y /var/lib/sops-nix/key.txt`
|
||||||
- AGE-PUBLIC-KEY-PI-MAIN-REPLACE-ME
|
# - AGE-PUBLIC-KEY-PI-MAIN-REPLACE-ME
|
||||||
# (Optional) your workstation key for offline editing:
|
|
||||||
# - AGE-PUBLIC-KEY-YOUR-WORKSTATION
|
|
||||||
|
|||||||
+55
-51
@@ -1,54 +1,58 @@
|
|||||||
# =============================================================================
|
#ENC[AES256_GCM,data:+YPw0Wd8uB5cwY/cszPqLohVhIEbju4QwYVS9vT/ci2MxO7Glw2uJbtS6dOLHqpzfU0b0mJ4IqUIaM08yMEM/TeB7hG1ASHWe3+qxyw8,iv:V49PRq4bEcYvcPMUIvBQkhuq28pVxrlRfVXUSzPxnb4=,tag:OSNXutH5EUwA3BujNZ3FoA==,type:comment]
|
||||||
# Homey secrets — managed by sops-nix
|
#ENC[AES256_GCM,data:QVC3QP3em1O3SYTAuK4kBchpTiwXH10f2R4YgK+t9QaiqZ1PWvo=,iv:R0lFtvg2T/Rllt1uiriTQvNbSw54jr0otU3E6XsIs00=,tag:9fAQCmuZZPUPLuDY8LZEUA==,type:comment]
|
||||||
#
|
#
|
||||||
# THIS FILE MUST BE ENCRYPTED WITH SOPS BEFORE COMMITTING.
|
#ENC[AES256_GCM,data:IT6BEo5CjYm+15aeWl+S8M3B+SSmjPnhBRvYToWzezIweTl3MGBXtalvkV3NWkxH0EaHpueOMe6r,iv:7BDTiljEa59F13Pephw6MM+sZgL4jbfQafJyt0UU3hY=,tag:ia+7WUAl/45jrYrv3Pylxg==,type:comment]
|
||||||
# It is shown here as a plaintext template so you know what to fill in.
|
|
||||||
#
|
#
|
||||||
# Workflow:
|
#ENC[AES256_GCM,data:zqAQYQCg/TRNtjDIdWTsgtRnQbijjYyLdQIAe9GkTubG9PSj7E8m7HFXmfG4eFNZR4S/Ql0dsM5gvLCu,iv:xSH8LMS7vqe2N9L/TOepKWhuIhVxmKN6kuB1iqUEOUw=,tag:rFYurrqfp1Zxggr5tiPKkQ==,type:comment]
|
||||||
# 1. Complete the .sops.yaml age key setup.
|
#ENC[AES256_GCM,data:+YPw0Wd8uB5cwY/cszPqLohVhIEbju4QwYVS9vT/ci2MxO7Glw2uJbtS6dOLHqpzfU0b0mJ4IqUIaM08yMEM/TeB7hG1ASHWe3+qxyw8,iv:V49PRq4bEcYvcPMUIvBQkhuq28pVxrlRfVXUSzPxnb4=,tag:OSNXutH5EUwA3BujNZ3FoA==,type:comment]
|
||||||
# 2. Fill in the values below.
|
#ENC[AES256_GCM,data:yj4R8Yetc6EHWvQDu2/eaoY=,iv:Zbqfg9NRHy6ab10kxzq6qsLb7VHfLxhcpP3vUt2i4ns=,tag:udBGjJUupeADD78JQ8BwuQ==,type:comment]
|
||||||
# 3. Run: sops -e -i secrets/secrets.yaml
|
openldap/admin_password: ENC[AES256_GCM,data:DtVthpJqLdkI+5wxOMnCfBdqWkg0GSwUtsUeop24kd8=,iv:4e2Xn7B0M8yYEbs0V9ozn8WHJJMCBv6G46bdThufSXc=,tag:BsjKzh8teul6yLEKbvr93g==,type:str]
|
||||||
# This encrypts the file in-place. The encrypted version is safe to commit.
|
openldap/config_password: ENC[AES256_GCM,data:6b9TIgOcmZfMDAVbJuqOoNS9kyrss/LMvySLyNonlRk=,iv:Jf9/triFouIDv7MY2J9W8ji7E5lUHqzwgBMqrcPuK1g=,tag:zQYZSesPiPVeNVBN1oEiHA==,type:str]
|
||||||
# 4. To edit later: sops secrets/secrets.yaml
|
openldap/ro_password: ENC[AES256_GCM,data:EHYUlIY24kY9K8opMi9MxSSosReZm5mEmbPFz+NdaXE=,iv:3pfVn4QDvJAVmWYWyX/Kko+K7nsE1yunLXN5uao+ea0=,tag:J954cH7a7Ey6Xq24ut5Jxw==,type:str]
|
||||||
#
|
#ENC[AES256_GCM,data:upG3X+Z7di17BaWBQ/P0ohY=,iv:k3Kin642n4cJYwfPsQYE/4FokELFNDmMzxJ2D8S28HI=,tag:uYRnpeoCrwGQOEYWo2cBiw==,type:comment]
|
||||||
# Ports from old deployment:
|
authelia/jwt_secret: ENC[AES256_GCM,data:pXTQ06OGEP1oYFM0mkyL+c/zNRUMgL9x1fCQsMo2bak=,iv:mnOBWBrSn4gTfMXR5PCThs0v9QRDR5pfOQA8u0cuGnI=,tag:YXGq6Hmv/chw8fcEQoNlGA==,type:str]
|
||||||
# - openldap/admin_password ← from k8s secret openldap-admin
|
authelia/session_secret: ENC[AES256_GCM,data:EgIyGv/K6xDCxOZWA9tzGoNS4m+p/EOPHL64/eN1oqwar2iJFSanbUfq8doHmN8n9sADmPIKUKaL8+WJWfyjtBBcCn74q5FL+kDu6ZYo4V5cjkj8jUhRC97TIJ+e0lVKFJ4s+i+/OcOsv2TPS/haylGHVn1fnlwvEd3kn/mO73w=,iv:6VPxOkriecJdtm2EBCiKkZBTzmas3DkQuYhivfygCT8=,tag:uXW1tcyAFSkiwMGNiZ663w==,type:str]
|
||||||
# - openldap/config_password ← from k8s secret openldap-config
|
authelia/storage_encryption_key: ENC[AES256_GCM,data:pM8oQ4t0HQLdUvuRayLOpEwdxzRQlvCOrMtSPIU8Ryo=,iv:AK2jR3Ij/dBplDc1PYXXLK8P327CYRx3kVZUCcIkO5k=,tag:kJSuyOIzT4/RNQXEal1ODA==,type:str]
|
||||||
# - openldap/ro_password ← from k8s secret openldap-ro
|
#ENC[AES256_GCM,data:teUPyCgpHCpIb0hXRUg=,iv:lTdYkYxQKHcJGE7lkkcsa8u9ZsZAVqpfauf5SzTv6G0=,tag:uKydCL14BvAaOpUHAMBirg==,type:comment]
|
||||||
# - gitea/admin_password ← from k8s secret gitea-admin-pass
|
gitea/admin_password: ENC[AES256_GCM,data:/39FQYn5GQoq/a5chLd4JUvSXTU8tOdzc9uXxNqViiw=,iv:Ysq2QUgkmONGsfj6xHKN3G/eitBX1rm9LLH9REF2h8g=,tag:eiVtlaB/6VdNMEBy4mSrTg==,type:str]
|
||||||
# - nextcloud/admin_password ← from k8s secret nextcloud-admin-pass
|
gitea/lfs_jwt_secret: ENC[AES256_GCM,data:gyd2OV0qcaaD6FTT9UwLV5vGJ4b/SNtG86oCQqUqB+DlZFLYe91YFNG/wA==,iv:fxD2NFbEYAsmrXaZT030f0MiAol2cwln0mIzLPCE+Lg=,tag:xQtehnHuj18WYeR2UyYeXw==,type:str]
|
||||||
# - nextcloud/postgres_password← from k8s secret nextcloud-postgres-pass
|
gitea/oauth2_jwt_secret: ENC[AES256_GCM,data:M5CzWG1FbjheX4QwDajVsAMl2nyfe4Z1u30D5hjCQbScDBtuw123ZMZjGQ==,iv:vOnMShn9nmLPzxXJqTNnCIf6GT6CrV3lAKrepmI7btc=,tag:pTdrbmZ+hntuwaLiLyUNHQ==,type:str]
|
||||||
# The remaining secrets (authelia JWT, session key, encryption key, gitea
|
gitea/internal_token: ENC[AES256_GCM,data:ZbwvPcOseUHAGDr4dwNu9u+qcr0yYYGdH2OjcuXPtgUt7HFq1a9f0Faxiphsh+3OXb1KqLj8USB/1AxSvt5kSYM/vqzSLZ+e1OKy0oO3o8YouCJLhPNkNO6q0eguQF6+,iv:E3APR8h+iNECoThrvy6v4SEdAsfnPITXvhIFT1Ug5qA=,tag:lCxReGAxJyVhwMjxNenvxg==,type:str]
|
||||||
# LFS/OAuth2/internal tokens) are regenerated fresh — see notes below.
|
#ENC[AES256_GCM,data:r/uPlqg+7UGrM0G2xhmD6Bm1,iv:m/Ineh/mNfo1yUS+B8qtbMr1zRwiE6vw3EZIepB4QUA=,tag:/tB1W2JgyUQNvVWFM9478w==,type:comment]
|
||||||
# =============================================================================
|
nextcloud/admin_password: ENC[AES256_GCM,data:KwS0kEjTKn+IAtYTD17X4Y/3hT9bUgqKBQ0vfhDK99A=,iv:AbJfw6NWRnnB8zXIO6l3sIWiXXWfM1ePJ5bodNlgjgI=,tag:XSQM8SSnuh3wjyN3IQdArA==,type:str]
|
||||||
|
nextcloud/postgres_password: ENC[AES256_GCM,data:dsdqeQhWFvidqOXopetb3G54Ft56ZhPheTB7uG2JuVc=,iv:ubKH3ihlPXZjPSkvgEYn/teG5SNSh04nb4Lh1e2cX8o=,tag:DWNXJXWjpCU8QEcnt0+phA==,type:str]
|
||||||
|
#ENC[AES256_GCM,data:riBX18BPE4XMBBv20JIEJbM6JS80e1jwiDq44KXMB6T/4Eehf2bgcFUm,iv:lDYdL1IvaBuixcw1BzPQxnM4HYZGA3YSDrJTxvz0QWs=,tag:tux8Mt56yw+7hE7BfgOXVw==,type:comment]
|
||||||
|
cloudflare/api_token: ENC[AES256_GCM,data:te8SJz3sjnWX0MsacbEwYb0IC+SAlUBcSthLmHxpURTdpE3GfeNvjj5Z+il43cpFA33PaUY=,iv:XG2dt0Wc5jDcfGvKtRB1f6CAWXBmgnw+qqzMxDtmOok=,tag:PmEqZoKvqZm2vBxYSNH3Qg==,type:str]
|
||||||
|
cloudflare/tunnel_token: ENC[AES256_GCM,data:HupdN2MFeQ+NPwynI1SM07E7yA5b66lbudKt/pNOemf9Q3l4zrYidLFpiQk6L6ajQpM0WQbEDYG2I1sxybu4fUah79MSZO7BoolYy6l/NDE5G35e3Kw9Yu1cFAyNZJ9s/RU8nG24OAMX+pMOkjk4bX4tzrWUkHmebRJf7iBZxsSys6o83arpyKcucLOfTyyLSRemXF8IXr2MGMypHkPrx+4w5MnY9tyY8JcclaiLDkpbVVDUTarbkg==,iv:sVAnAqAMdTn8HpEwcIz2B57SrPlYqV2/Oi3sYHanYzo=,tag:BmhemprKvn33Wt595MjKcQ==,type:str]
|
||||||
|
#ENC[AES256_GCM,data:GpnZDeOAyr2pZxWHVd++1TMm230hvQ==,iv:jo8kWdd0Pm3d3xewCcyhauiBhI+SYIlWvczKn0PPZTg=,tag:INK0gZhKynkiOgi2ayrSMA==,type:comment]
|
||||||
|
restic/password: ENC[AES256_GCM,data:iZNRA8qNspy7WnK+Dg1OOZj9Gt2Y/AXUG1gKTBGUt+6q7T6Lv5AqbVkN8khwlKyQWK6FNLh3/9ejsM7mybiyog==,iv:XMxMAgVMdCWnDCkdTxL72pbrg8Dy0xz2EYou7AaNgS0=,tag:KW9Tjhql0yF6h81Il1htbw==,type:str]
|
||||||
|
restic/s3_access_key_id: ENC[AES256_GCM,data:XK8GqLHSC76K6z86RbqI4uNwZgcfl5R0Bg==,iv:t9+fGwwGX8PLwr30MJMYdOm02f/+XTcnMhSY1DP+nU0=,tag:fauNjH4lVtHa+L8Bfj8TOg==,type:str]
|
||||||
|
restic/s3_secret_access_key: ENC[AES256_GCM,data:GUx4FPaHWuzNwOju7CQoZc5U2SLG+3GOn0zJvvRXzQ==,iv:Oq0q9a+esPkLygMkGaFFNZOOfMGMFVPeb+yHUcLcNZE=,tag:Rwd0NNyXt+L8IJCCiDJh8w==,type:str]
|
||||||
|
#ENC[AES256_GCM,data:H+rGxOM6euNaSOval0ZXgKlRKQ==,iv:o0kU37iQzWAvTl5T9MK5RpHJ1eqhFftfVMEGMR40Hw8=,tag:rFcrmYZXpOpVdvW/zTul1A==,type:comment]
|
||||||
|
wifi/psk: ENC[AES256_GCM,data:bkZnP8S7yQlaEfH+kN1FfjQqJw==,iv:n1wOv6rXDbGucKryV9qV0fgqXNC/GwDeDlY2k9/hSOI=,tag:LdC2ahrXVBcqLWU5nFHMlQ==,type:str]
|
||||||
|
sops:
|
||||||
|
lastmodified: "2026-04-18T20:53:59Z"
|
||||||
|
mac: ENC[AES256_GCM,data:nEP5XRzdYdFBWp9tqIgxcjjR7+X9ScpUew6SGfE6bKSQjvbwKTCGW6dSOTe7FmpUKrOS+dJnwpPsWKu0jbX/Qm5EtfXaB0GWiiMjfejwshmyULuJKipuq1rC+YX+DmOXoWIiNwKIwd4tBEOfYFBJVLFcoP8DSFjettymT0idvAQ=,iv:RnWzW+2hUScofJVom+csqEhYME8/roIzdRC/YC8opyk=,tag:22rjZO28mjPsp9p3iuoHSQ==,type:str]
|
||||||
|
pgp:
|
||||||
|
- created_at: "2026-04-18T20:12:39Z"
|
||||||
|
enc: |-
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
|
||||||
# --- OpenLDAP ---
|
hQIMAwdqopdXmgBkAQ/+OOgkrBhQBXcbxH2Rj3yQ5cDTkH3LZdbBH+vLvEFfoXLk
|
||||||
openldap/admin_password: "REPLACE-WITH-OLD-VALUE"
|
RI12n3y+gQo5Gbs1eD9tJOuBIqYZwG9JTHiv43d6DXRFdY9PlMWaL6HeG6le/dj7
|
||||||
openldap/config_password: "REPLACE-WITH-OLD-VALUE"
|
/JpirCofXhbL+GzLxQXnEOeMYm0Rhh5a9FbvqOwVkx2cCYlaWDYrZRPXFkjTw0et
|
||||||
openldap/ro_password: "REPLACE-WITH-OLD-VALUE"
|
DYv9a/ZUMAEKwSEJO7kRMpWYiPGI6KkArJrPBm7C6M4j5+KBv29FRSpw/IJiOMtT
|
||||||
|
CFWepDk+RJq+pMRNB91p/OO6YdrwMQJdCRcqC94I3TdxhVKoCCagULoE3vwHzxGQ
|
||||||
# --- Authelia (regenerated fresh — these are random strings) ---
|
O5kDDc1GuQbIcNg2bfyWyKv6L9A30JaQT+8t3UMSHxAoWlvZes1y3tvquQeI8m+N
|
||||||
authelia/jwt_secret: "GENERATE-random-64-chars"
|
JILTmMWHjAplals4u+8BX7MCVolh4zJRNr1xiFy/UamYB70UORf2rjjGvMqOHsM+
|
||||||
authelia/session_secret: "GENERATE-random-64-chars"
|
IPJ2pIqbXDYs3syjKvWQFpxZczGgSPxHPlF9Tm+hu972ub9Ex2uVWntvjnt26H6+
|
||||||
authelia/storage_encryption_key: "GENERATE-random-64-chars"
|
/JbdV/7gW95AEkJ+HPjynDvYZ1tRBFGmwBOCsOkOfKmmopKcAooT6qDzC5hZBhBE
|
||||||
|
Yvl9TlC5GEBPnV4dtIxTZrqRqvbt5CvikmCI2h3/pcMWGM8a0iN2K0iNvlKGnKey
|
||||||
# --- Gitea ---
|
jlGC+0nQzwLllFtGBgOGKeqG1HQ5yPf2W4Ic7uSVGI3xPHkd5gG1MAHORw/3cP3S
|
||||||
gitea/admin_password: "REPLACE-WITH-OLD-VALUE"
|
XgHadJRTvnNnDsZjT7P8rIYTBnpe2zx+I8N21r+Jh5/hCv8wSl819QaBA4IMC5kt
|
||||||
# These three are regenerated — gitea will re-derive on first start:
|
Os9nSYc1KzodkJR35O8Bdy/7H8SF34tXjpyhWvE4OEqEwN7AdI0L0PfOiGMBjms=
|
||||||
gitea/lfs_jwt_secret: "GENERATE-random-43-chars-base64url"
|
=7asV
|
||||||
gitea/oauth2_jwt_secret: "GENERATE-random-43-chars-base64url"
|
-----END PGP MESSAGE-----
|
||||||
gitea/internal_token: "GENERATE-random-100-alphanum"
|
fp: 076AA297579A0064
|
||||||
|
unencrypted_suffix: _unencrypted
|
||||||
# --- Nextcloud ---
|
version: 3.12.2
|
||||||
nextcloud/admin_password: "REPLACE-WITH-OLD-VALUE"
|
|
||||||
nextcloud/postgres_password: "REPLACE-WITH-OLD-VALUE"
|
|
||||||
|
|
||||||
# --- Cloudflare (DNS-01 ACME + tunnel) ---
|
|
||||||
cloudflare/api_token: "REPLACE-WITH-CF-DNS-EDIT-TOKEN"
|
|
||||||
cloudflare/tunnel_token: "REPLACE-WITH-CF-TUNNEL-TOKEN"
|
|
||||||
|
|
||||||
# --- Restic backup ---
|
|
||||||
restic/password: "GENERATE-random-passphrase"
|
|
||||||
# Repository destination — e.g. "sftp:user@nas:/backups/homey"
|
|
||||||
# or "b2:bucketname:homey" for Backblaze B2
|
|
||||||
# Set the actual repo URL in modules/backup.nix or override per-host.
|
|
||||||
|
|||||||
Reference in New Issue
Block a user