diff --git a/lib/services.nix b/lib/services.nix index 53e625f..c2a8cd8 100644 --- a/lib/services.nix +++ b/lib/services.nix @@ -201,12 +201,12 @@ health_url = "https://${config.services.n8n.environment.N8N_HOST or "n8n.dunkirk.sh"}/healthz"; } )) - (lib.optional (config.services.minio.enable or false) ( - mkCustom "minio" { + (lib.optional (config.services.garage.enable or false) ( + mkCustom "garage" { description = "S3-compatible object storage"; domain = "s3.dunkirk.sh"; - port = 9001; - runtime = "go"; + port = 3900; + runtime = "rust"; repository = null; health_url = null; } diff --git a/machines/prattle/default.nix b/machines/prattle/default.nix index b111c3e..1eb09c4 100644 --- a/machines/prattle/default.nix +++ b/machines/prattle/default.nix @@ -105,9 +105,6 @@ protonvpn-wg = { file = ../../secrets/protonvpn-wg.age; }; - minio = { - file = ../../secrets/minio.age; - }; }; programs.nh = { @@ -299,7 +296,9 @@ "d /storage/torrents 2775 root media -" "d /storage/torrents/.incomplete 2775 root media -" "d /storage/.trash 2775 root media -" - "d /storage/s3 0750 minio minio -" + "d /storage/s3 0750 root root -" + "d /storage/s3/meta 0750 garage garage -" + "d /storage/s3/data 0750 garage garage -" ]; # ── Recyclarr (TRaSH Guides sync) ───────────────────────────────────── @@ -422,8 +421,11 @@ handle /transmission/* { reverse_proxy localhost:9091 } - handle /minio/* { - reverse_proxy localhost:9001 + handle /garage/* { + reverse_proxy localhost:3902 + } + handle /garage-ui/* { + reverse_proxy localhost:3909 } ''; }; @@ -473,13 +475,105 @@ openFirewall = true; }; - # ── MinIO (S3-compatible) ───────────────────────────────────────────── - services.minio = { + # ── Garage (S3-compatible object store) ───────────────────────────── + services.garage = { enable = true; - dataDir = [ "/storage/s3" ]; - rootCredentialsFile = config.age.secrets.minio.path; + package = pkgs.garage_2; + settings = { + metadata_dir = "/storage/s3/meta"; + data_dir = "/storage/s3/data"; + db_engine = "lmdb"; + replication_factor = 1; + consistency_mode = "none"; + rpc_bind_addr = "[::]:3901"; + rpc_public_addr = "127.0.0.1:3901"; + s3_api = { + s3_region = "garage"; + api_bind_addr = "[::]:3900"; + root_domain = ".s3.garage.localhost"; + }; + s3_web = { + bind_addr = "[::]:3902"; + root_domain = ".web.garage.localhost"; + }; + admin = { + api_bind_addr = "127.0.0.1:3903"; + }; + }; }; + # Bootstrap garage: generate RPC secret, assign layout, create default key/bucket + systemd.services.garage-bootstrap = { + description = "Bootstrap Garage cluster (one-time setup)"; + after = [ "garage.service" ]; + requires = [ "garage.service" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + EnvironmentFile = config.services.garage.environmentFile or null; + }; + script = '' + set -euo pipefail + GARAGE="${config.services.garage.package}/bin/garage" + MARKER="/var/lib/garage/.bootstrapped" + + if [ -f "$MARKER" ]; then + echo "Garage already bootstrapped" + exit 0 + fi + + # Wait for garage to be ready + for i in $(seq 1 30); do + if $GARAGE status 2>/dev/null; then break; fi + sleep 1 + done + + # Get or generate node ID + NODE_ID=$($GARAGE node id 2>/dev/null | head -1 || true) + if [ -z "$NODE_ID" ]; then + echo "Waiting for node ID..." + sleep 2 + NODE_ID=$($GARAGE node id 2>/dev/null | head -1) + fi + + # Assign layout + $GARAGE layout assign -z dc1 -c 1G "$NODE_ID" || true + $GARAGE layout apply --version 1 || true + + # Create default bucket and key + $GARAGE bucket create default || true + $GARAGE key create default || true + $GARAGE bucket allow --read --write --owner default --key default || true + + touch "$MARKER" + echo "Garage bootstrap complete" + ''; + }; + + # Garage Web UI + systemd.services.garage-webui = { + description = "Garage Web UI"; + after = [ "garage-bootstrap.service" ]; + wants = [ "garage.service" ]; + wantedBy = [ "multi-user.target" ]; + environment = { + API_BASE_URL = "http://127.0.0.1:3903"; + PORT = "3909"; + BASE_PATH = "/"; + }; + serviceConfig = { + Type = "simple"; + ExecStart = "${pkgs.garage-webui}/bin/garage-webui"; + Restart = "on-failure"; + RestartSec = "5s"; + PrivateTmp = true; + NoNewPrivileges = true; + }; + }; + + boot.zfs.forceImportRoot = false; + boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = true; zramSwap = { diff --git a/machines/prattle/media-dashboard/index.html b/machines/prattle/media-dashboard/index.html index aa77172..f7f8f9c 100644 --- a/machines/prattle/media-dashboard/index.html +++ b/machines/prattle/media-dashboard/index.html @@ -116,9 +116,9 @@ Transmission - - - MinIO + + + Garage diff --git a/machines/terebithia/default.nix b/machines/terebithia/default.nix index 0b066c2..239ace9 100644 --- a/machines/terebithia/default.nix +++ b/machines/terebithia/default.nix @@ -288,7 +288,7 @@ "github.com/caddy-dns/cloudflare@v0.2.2" "github.com/mholt/caddy-ratelimit@v0.1.0" ]; - hash = "sha256-IZv06ZFUUDSa+Oj4De5F1Nq9+EAC9HXlhcA0r+Qwmqg="; + hash = "sha256-gthp6bGn6QJImbuz9C2KSXrXQqvRxbFr/YcGr0VoEIU="; }; email = "kieran@dunkirk.sh"; globalConfig = '' @@ -618,7 +618,7 @@ header { Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" } - reverse_proxy prattle:9001 + reverse_proxy prattle:3900 ''; }; diff --git a/modules/nixos/system/authentication.nix b/modules/nixos/system/authentication.nix index 532ca37..9489d4c 100644 --- a/modules/nixos/system/authentication.nix +++ b/modules/nixos/system/authentication.nix @@ -92,7 +92,7 @@ in session required pam_env.so conffile=/etc/pam/environment readenv=0 # env (order 10100) session required pam_unix.so # unix (order 10200) session required pam_unix.so # unix (order 10200) - session optional pam_xauth.so systemuser=99 xauthpath=${pkgs.xorg.xauth}/bin/xauth # xauth (order 12100) + session optional pam_xauth.so systemuser=99 xauthpath=${pkgs.xauth}/bin/xauth # xauth (order 12100) ''; }; };