Nix-Vibe public snapshot (squashed history)

This commit is contained in:
2026-09-19 13:54:34 +01:00
commit cff83a473b
118 changed files with 18716 additions and 0 deletions
+268
View File
@@ -0,0 +1,268 @@
# NixOS configuration for homeserver-1
# Headless server environment (Command Line Interface only)
{ config
, pkgs
, lib
, inputs
, ...
}:
{
imports = [
../../modules/core/common.nix
(import ../../modules/storage/disko.nix {
inherit inputs lib config;
diskoConfigPath = ./disko-config.nix;
})
./hardware-configuration.nix
../../modules/core/management.nix
../../modules/core/podman.nix
../../modules/hardware/nvidia.nix
../../modules/core/known-hosts.nix
../../modules/services/immich.nix
../../modules/services/jellyfin.nix
../../modules/services/backrest.nix
../../modules/services/homepage.nix
./homepage.nix
];
services.backrest = {
enable = true;
host = "0.0.0.0";
port = 9898;
dataDir = "/data/backrest";
};
systemd.services.backrest = {
after = [ "restic-ssh-key-format.service" ];
wants = [ "restic-ssh-key-format.service" ];
};
# Allow backrest to read backup source directories recursively.
# Uses ACLs with a default mask so newly created files also inherit access.
# Runs after immich and the postgresql backup so the dirs/files exist.
systemd.services.backrest-permissions = {
description = "Grant backrest read access to backup source dirs";
wantedBy = [ "multi-user.target" ];
after = [
"immich-server.service"
"postgresqlBackup-immich.service"
];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
# Recursive read+execute ACL for backrest on immich media
${pkgs.acl}/bin/setfacl -R -m u:backrest:rx -m m::r-x /data/immich
# Default ACL so future immich files are readable by backrest
${pkgs.acl}/bin/setfacl -R -m d:u:backrest:rx -m d:m::r-x /data/immich
# Recursive read for backrest on postgresql backups
${pkgs.acl}/bin/setfacl -R -m u:backrest:rx -m m::r-x /data/backup/postgresql
${pkgs.acl}/bin/setfacl -R -m d:u:backrest:rx -m d:m::r-x /data/backup/postgresql
'';
};
services.immich-server = {
enable = true;
port = 2283;
mediaLocation = "/data/immich";
};
services.jellyfin-server = {
enable = true;
port = 8096;
mediaLocation = "/data/jellyfin";
};
services.postgresqlBackup = {
enable = true;
databases = [ "immich" ];
location = "/data/backup/postgresql";
compression = "zstd";
startAt = "weekly";
};
# The services.postgresqlBackup module creates a tmpfiles `d` rule that sets
# the backup directory to 0700 on every rebuild/switch. On a directory with
# POSIX ACLs, `chmod 0700` resets the ACL mask to `---`, which nullifies the
# `user:backrest` read ACL that backrest uses to back up these dumps. Override
# the mode so rebuilds keep the directory group-traversable (mask stays rx).
# Note: systemd-tmpfiles dedupes conflicting rules and keeps the FIRST one for
# a path, so our override must appear before the module's rule.
systemd.tmpfiles.rules = lib.mkBefore [
"d /data/backup/postgresql 0750 postgres - - -"
];
sops.secrets = {
"users/petere-password" = {
neededForUsers = true;
};
"pocket-id-env" = {
neededForUsers = false;
};
"homeserver-1/restic-passphrase" = { };
"homeserver-1/restic-ssh-key" = { };
"homeserver-1/homepage-env" = { };
"homeserver-1/samba-petere-password" = { };
};
# Set petere's Samba password from SOPS at boot. Samba keeps its own password
# database (smbpasswd/tdbsam), separate from Linux login, so this must run
# smbpasswd. Idempotent: re-applied on every boot from the secret.
systemd.services.samba-set-petere-password = {
description = "Set petere's Samba password from SOPS";
wantedBy = [ "multi-user.target" ];
after = [ "sops-nix.service" ];
before = [ "samba-smbd.service" ];
serviceConfig.Type = "oneshot";
script = ''
PASS="$(cat ${config.sops.secrets."homeserver-1/samba-petere-password".path})"
${pkgs.samba}/bin/smbpasswd -s -a petere <<EOF
$PASS
$PASS
EOF
'';
};
# Install the restic SSH key (stored multi-line in sops) and set up SSH access
# for the backrest user so restic can reach mcf-server.
# Writes to a persistent location (NOT /run) because sops-nix clears /run/secrets.
systemd.services.restic-ssh-key-format = {
description = "Install restic SSH key and configure SSH for backrest";
wantedBy = [ "multi-user.target" ];
after = [ "sops-nix.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
KEY_FILE="${config.sops.secrets."homeserver-1/restic-ssh-key".path}"
FORMATTED="/data/backrest/restic-ssh-key"
${pkgs.coreutils}/bin/install -m 640 -o root -g backrest "$KEY_FILE" "$FORMATTED"
# SSH config for backrest so restic (via Backrest) uses the correct key
mkdir -p /data/backrest/.ssh
cat > /data/backrest/.ssh/config <<EOF
Host mcf-server
HostName mcf-server
User restic-homeserver1
IdentityFile /data/backrest/restic-ssh-key
IdentitiesOnly yes
Host richmond-server
HostName richmond-server
User restic-homeserver1
IdentityFile /data/backrest/restic-ssh-key
IdentitiesOnly yes
EOF
chown -R backrest:backrest /data/backrest/.ssh
chmod 700 /data/backrest/.ssh
chmod 600 /data/backrest/.ssh/config
'';
};
networking.hostName = "homeserver-1";
# SSH configuration
services.openssh.enable = true;
# Trusted host keys for restic backup targets (via Backrest).
my.knownHosts = {
mcfServer = true;
richmondServer = true;
};
# Glances system monitor - exposed to the tailnet so the Homepage
# dashboard can display real-time stats for this machine (localhost).
services.glances = {
enable = true;
port = 61208;
extraArgs = [ "--webserver" ];
};
# Expose services on Tailscale only (not the LAN).
# Immich (2283): accessed via nginx proxy on another machine over Tailscale.
# Backrest (9898), Pocket ID (8443), Homepage (8082), Glances (61208): admin services.
networking.firewall.interfaces.tailscale.allowedTCPPorts = lib.mkAfter [
2283 # Immich
8443 # Pocket ID
9898 # Backrest
8082 # Homepage
61208 # Glances
];
# Add Pocket ID package for tooling
environment.systemPackages = with pkgs; [ pocket-id ];
# Pocket ID service configuration
services.pocket-id = {
enable = true;
environmentFile = config.sops.secrets."pocket-id-env".path;
settings = {
APP_URL = "https://homeserver-1.gerbil-opah.ts.net:8443";
PORT = 8443;
TRUST_PROXY = true;
TLS_CERT_FILE = "/etc/ssl/certs/pocket-id.crt";
TLS_KEY_FILE = "/etc/ssl/private/pocket-id.key";
};
};
systemd.services.pocket-id = {
wants = [ "pocket-id-tailscale-cert.service" ];
after = [ "pocket-id-tailscale-cert.service" ];
};
# Systemd service to obtain TLS cert via Tailscale
systemd.services.pocket-id-tailscale-cert = {
description = "Obtain TLS cert for Pocket-ID via Tailscale";
wantedBy = [ "multi-user.target" ];
wants = [
"network-online.target"
"tailscaled.service"
];
after = [
"network-online.target"
"tailscaled.service"
];
serviceConfig = {
Type = "oneshot";
ExecStart = pkgs.writeShellScript "get-tailscale-cert" ''
set -eu
mkdir -p /etc/ssl/certs /etc/ssl/private
if [ ! -f /etc/ssl/certs/pocket-id.crt ] || [ ! -f /etc/ssl/private/pocket-id.key ]; then
${pkgs.tailscale}/bin/tailscale cert --cert-file /etc/ssl/certs/pocket-id.crt --key-file /etc/ssl/private/pocket-id.key homeserver-1.gerbil-opah.ts.net
fi
chown root:pocket-id /etc/ssl/private/pocket-id.key
chmod 640 /etc/ssl/private/pocket-id.key
chmod 644 /etc/ssl/certs/pocket-id.crt
'';
User = "root";
Group = "root";
};
};
# NVIDIA GPU Configuration for GeForce GTX 960 (Maxwell GM206)
my.hardware.nvidia = {
enable = true;
# GTX 960 (Maxwell) needs the 580.xx legacy driver branch; the default
# driver no longer supports it (NVRM: No NVIDIA GPU found).
package = config.boot.kernelPackages.nvidiaPackages.legacy_580;
};
boot.kernelModules = [ "sg" ];
# Root account is locked (no password login); access is via SSH key + sudo.
users.users.root.hashedPassword = "!";
# Standard user account (shared definition in modules/core/users.nix)
my.users.petere = {
hashedPasswordFile = config.sops.secrets."users/petere-password".path;
};
# Trusted users for Nix operations
nix.settings.trusted-users = [
"root"
"petere"
];
}
+75
View File
@@ -0,0 +1,75 @@
{
disk = {
main = {
type = "disk";
device = "/dev/nvme0n1";
content = {
type = "gpt";
partitions = {
boot = {
size = "1M";
type = "EF02"; # for GRUB MBR fallback
};
ESP = {
size = "512M";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
};
};
root = {
size = "100%";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
};
};
};
};
data1 = {
type = "disk";
device = "/dev/sda";
content = {
type = "gpt";
partitions = {
data = {
size = "100%";
content = {
type = "btrfs";
extraArgs = [ "-f" ];
mountOptions = [
"defaults"
"nofail"
];
mountpoint = "/data";
};
};
};
};
};
data2 = {
type = "disk";
device = "/dev/sdb";
content = {
type = "gpt";
partitions = {
data = {
size = "100%";
content = {
type = "btrfs";
extraArgs = [ "-f" ];
postCreateHook = ''
btrfs device add -f /dev/disk/by-partlabel/disk-data2-data /mnt/data || true
btrfs balance start -dconvert=raid1 -mconvert=raid1 /mnt/data || true
'';
};
};
};
};
};
};
}
@@ -0,0 +1,34 @@
{ config
, lib
, pkgs
, modulesPath
, ...
}:
{
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = [
"nvme"
"xhci_pci"
"ahci"
"usb_storage"
"usbhid"
"sd_mod"
];
boot.initrd.kernelModules = [ ];
boot.supportedFilesystems = [
"btrfs"
"ext4"
"vfat"
];
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
networking.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}
+516
View File
@@ -0,0 +1,516 @@
# Homepage dashboard (gethomepage.dev) configuration for homeserver-1
#
# This file holds the full dashboard definition (settings, services, widgets).
# It is imported by configuration.nix. Edit this file to change what the
# dashboard shows, then run:
# nixos-rebuild switch --target-host petere@homeserver-1 --flake .#homeserver-1 --use-remote-sudo
{ config
, lib
, ...
}:
let
# Build a Tailscale widget tile for the Tailnet tab. Shared so the
# highlight rules (expiry / last-seen) are defined once.
# Returns a service entry: { "<name>" = { icon; href; description; widget; } }
tailscaleTile = name: deviceid: description: {
${name} = {
icon = "sh-tailscale";
href = "https://login.tailscale.com/admin/machines";
inherit description;
widget = {
type = "tailscale";
inherit deviceid;
key = "{{HOMEPAGE_VAR_TAILSCALE_API_KEY}}";
# Highlight rules match the rendered field values (e.g. "24w", "Never",
# "8h Ago", "2w Ago"). Warn = expiring within a week, danger = offline >24h.
highlight = {
expires = {
string = [
{
level = "warn";
when = "regex";
# 1-7 days, or hours/minutes/seconds remaining (i.e. within a week)
value = "^\\d+[dhms]$";
}
];
};
last_seen = {
string = [
{
level = "danger";
when = "regex";
# days/weeks/years ago (i.e. not seen for more than 24 hours)
value = "^\\d+[dwy] Ago$";
}
];
};
};
};
};
};
in
{
services.homepage = {
enable = true;
port = 8082;
allowedHosts = [
"localhost"
"127.0.0.1"
"homeserver-1"
"homeserver-1.gerbil-opah.ts.net"
];
openFirewall = false; # Exposed on Tailscale only (see firewall in configuration.nix)
# API keys / secrets for service widgets (HOMEPAGE_VAR_* vars)
environmentFiles = [ config.sops.secrets."homeserver-1/homepage-env".path ];
settings = {
title = "HomeServer";
language = "en";
theme = "dark";
color = "slate";
statusStyle = "dot";
# Tabs: each layout group's `tab` value controls which tab it appears on.
# Groups without a `tab` (or with no layout entry) show on every tab.
layout = {
# ---- Monitoring tab ----
"Homeserver-1 Monitoring" = {
tab = "Monitoring";
style = "row";
columns = 1;
"HS1 System" = {
style = "row";
columns = 2;
};
"HS1 Disks" = {
style = "row";
columns = 2; # NVMe + SDA side by side
};
};
"MCF Server Monitoring" = {
tab = "Monitoring";
style = "row";
columns = 1;
"MCF System" = {
style = "row";
columns = 2;
};
"MCF Disks" = {
style = "row";
columns = 2; # SDB (system) + SDA (data) side by side
};
};
"Richmond Server Monitoring" = {
tab = "Monitoring";
style = "row";
columns = 1;
"Richmond System" = {
style = "row";
columns = 2;
};
"Richmond Disks" = {
style = "row";
columns = 2; # SDB (system) + SDA (data) side by side
};
};
# ---- Homeserver-1 tab ----
Media = {
tab = "Homeserver-1";
style = "row";
columns = 2;
};
System = {
tab = "Homeserver-1";
style = "row";
columns = 3;
};
# ---- MCF Server tab (populate with future mcf-server services) ----
# "MCF Server Apps" = {
# tab = "MCF Server";
# style = "row";
# columns = 4;
# };
# ---- Richmond Server tab ----
"Richmond Server Apps" = {
tab = "Richmond Server";
style = "row";
columns = 3;
};
# ---- Tailnet tab (one Tailscale widget per machine) ----
Tailnet = {
tab = "Tailnet";
style = "row";
columns = 3;
};
};
};
services = [
{
"Homeserver-1 Monitoring" = [
{
"HS1 System" = [
{
"System" = {
widget = {
type = "glances";
url = "http://127.0.0.1:61208";
version = 4; # Glances v4.x
metric = "info";
};
};
}
{
"CPU" = {
widget = {
type = "glances";
url = "http://127.0.0.1:61208";
version = 4;
metric = "cpu";
};
};
}
{
"Memory" = {
widget = {
type = "glances";
url = "http://127.0.0.1:61208";
version = 4;
metric = "memory";
};
};
}
{
"Processes" = {
widget = {
type = "glances";
url = "http://127.0.0.1:61208";
version = 4;
metric = "process";
};
};
}
];
}
{
"HS1 Disks" = [
{
"NVMe - System" = {
widget = {
type = "glances";
url = "http://127.0.0.1:61208";
version = 4;
metric = "disk:nvme0n1";
};
};
}
{
"SDA - Data" = {
widget = {
type = "glances";
url = "http://127.0.0.1:61208";
version = 4;
metric = "disk:sda";
};
};
}
];
}
];
}
{
"MCF Server Monitoring" = [
{
"MCF System" = [
{
"System" = {
widget = {
type = "glances";
url = "http://mcf-server.gerbil-opah.ts.net:61208";
version = 4; # Glances v4.x
metric = "info";
};
};
}
{
"CPU" = {
widget = {
type = "glances";
url = "http://mcf-server.gerbil-opah.ts.net:61208";
version = 4;
metric = "cpu";
};
};
}
{
"Memory" = {
widget = {
type = "glances";
url = "http://mcf-server.gerbil-opah.ts.net:61208";
version = 4;
metric = "memory";
};
};
}
{
"Processes" = {
widget = {
type = "glances";
url = "http://mcf-server.gerbil-opah.ts.net:61208";
version = 4;
metric = "process";
};
};
}
];
}
{
"MCF Disks" = [
{
"SDB - System" = {
widget = {
type = "glances";
url = "http://mcf-server.gerbil-opah.ts.net:61208";
version = 4;
metric = "disk:sdb";
};
};
}
{
"SDA - Data" = {
widget = {
type = "glances";
url = "http://mcf-server.gerbil-opah.ts.net:61208";
version = 4;
metric = "disk:sda";
};
};
}
];
}
];
}
{
"Richmond Server Monitoring" = [
{
"Richmond System" = [
{
"System" = {
widget = {
type = "glances";
url = "http://richmond-server.gerbil-opah.ts.net:61208";
version = 4; # Glances v4.x
metric = "info";
};
};
}
{
"CPU" = {
widget = {
type = "glances";
url = "http://richmond-server.gerbil-opah.ts.net:61208";
version = 4;
metric = "cpu";
};
};
}
{
"Memory" = {
widget = {
type = "glances";
url = "http://richmond-server.gerbil-opah.ts.net:61208";
version = 4;
metric = "memory";
};
};
}
{
"Processes" = {
widget = {
type = "glances";
url = "http://richmond-server.gerbil-opah.ts.net:61208";
version = 4;
metric = "process";
};
};
}
];
}
{
"Richmond Disks" = [
{
"SDB - System" = {
widget = {
type = "glances";
url = "http://richmond-server.gerbil-opah.ts.net:61208";
version = 4;
metric = "disk:sdb";
};
};
}
{
"SDA - Data" = {
widget = {
type = "glances";
url = "http://richmond-server.gerbil-opah.ts.net:61208";
version = 4;
metric = "disk:sda";
};
};
}
];
}
];
}
{
Media = [
{
Jellyfin = {
icon = "sh-jellyfin";
href = "http://jellyfin.edley.me";
description = "Movies & TV";
siteMonitor = "http://127.0.0.1:8096";
widget = {
type = "jellyfin";
url = "http://127.0.0.1:8096";
key = "{{HOMEPAGE_VAR_JELLYFIN_API_KEY}}";
enableBlocks = true;
enableNowPlaying = true;
};
};
}
{
Immich = {
icon = "sh-immich";
href = "http://immich.edley.me";
description = "Photo & Video";
siteMonitor = "http://127.0.0.1:2283";
widget = {
type = "immich";
url = "http://127.0.0.1:2283";
key = "{{HOMEPAGE_VAR_IMMICH_API_KEY}}";
version = 2; # Immich >= 1.118
};
};
}
];
}
{
System = [
{
Backrest = {
icon = "sh-backrest";
href = "http://homeserver-1.gerbil-opah.ts.net:9898";
description = "Restic backup UI";
siteMonitor = "http://127.0.0.1:9898";
widget = {
type = "backrest";
url = "http://127.0.0.1:9898";
};
};
}
{
"Pocket ID" = {
icon = "sh-pocketbase";
href = "https://homeserver-1.gerbil-opah.ts.net:8443";
description = "SSO / Identity";
};
}
];
}
# ---- MCF Server tab: add future mcf-server services here ----
# {
# "MCF Server Apps" = [
# {
# "MyApp" = {
# icon = "sh-myservice";
# href = "http://mcf-server.gerbil-opah.ts.net:<port>";
# siteMonitor = "http://mcf-server.gerbil-opah.ts.net:<port>";
# };
# }
# ];
# }
{
"Richmond Server Apps" = [
{
PiHole = {
icon = "sh-pihole";
href = "http://richmond-server.gerbil-opah.ts.net/admin";
description = "Network-wide ad blocking";
siteMonitor = "http://richmond-server.gerbil-opah.ts.net";
widget = {
type = "pihole";
url = "http://richmond-server.gerbil-opah.ts.net";
version = 6; # Pi-hole v6
key = "{{HOMEPAGE_VAR_PIHOLE_API_KEY}}";
};
};
}
{
Castopod = {
icon = "sh-castopod";
href = "http://richmond-server.gerbil-opah.ts.net:8080";
description = "Podcasting platform";
siteMonitor = "http://richmond-server.gerbil-opah.ts.net:8080";
};
}
{
Ntfy = {
icon = "sh-ntfy";
href = "https://ntfy.edley.me";
description = "Push notifications";
siteMonitor = "http://richmond-server.gerbil-opah.ts.net:8085";
};
}
];
}
# ---- Tailnet tab: one Tailscale widget per machine ----
{
Tailnet = [
(tailscaleTile "Homeserver-1" "7629334038136604" "homeserver-1.gerbil-opah.ts.net")
(tailscaleTile "MCF Server" "6531912398509392" "mcf-server.gerbil-opah.ts.net")
(tailscaleTile "Richmond Server" "4591058654038528" "richmond-server.gerbil-opah.ts.net")
(tailscaleTile "x1carbon" "3486385364789872" "x1carbon.gerbil-opah.ts.net")
(tailscaleTile "x470" "210979648507396" "x470.gerbil-opah.ts.net")
(tailscaleTile "caitlin-x1" "4406025993575891" "caitlin-x1.gerbil-opah.ts.net")
(tailscaleTile "Mary Laptop" "5830538092696610" "mary-laptop.gerbil-opah.ts.net")
(tailscaleTile "Pluto" "4170550062707667" "pluto.gerbil-opah.ts.net")
(tailscaleTile "TheBorg" "2268885677960290" "theborg.gerbil-opah.ts.net")
(tailscaleTile "Server" "3605859775136634" "server.gerbil-opah.ts.net")
(tailscaleTile "Homeserver" "5665047953117744" "homeserver.gerbil-opah.ts.net")
(tailscaleTile "MCF Projector" "5131578183597553" "mcf-projector.gerbil-opah.ts.net")
(tailscaleTile "MCF Stream" "6804850380243723" "mcf-stream.gerbil-opah.ts.net")
(tailscaleTile "Yoga 12" "1716848030756573" "yoga12.gerbil-opah.ts.net")
(tailscaleTile "Pixel 9 Pro XL" "6766405315162342" "pixel-9-pro-xl.gerbil-opah.ts.net")
];
}
];
widgets = [
{
resources = {
cpu = true;
memory = true;
disk = "/";
};
}
{
search = {
provider = "duckduckgo";
target = "_blank";
};
}
{
datetime = {
text_size = "xl";
locale = "en-GB"; # UK date format (dd/mm/yy)
format = {
dateStyle = "short";
timeStyle = "short";
};
};
}
];
};
}