From 44a529b460de2b17e0d715df484140efa8e44088 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20N=C3=BCtzi?= Date: Tue, 19 May 2026 17:19:06 +0200 Subject: [PATCH 1/8] fix: garage service --- src/modules/services/garage.nix | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/modules/services/garage.nix b/src/modules/services/garage.nix index 2dcc72c1b..028661bc4 100644 --- a/src/modules/services/garage.nix +++ b/src/modules/services/garage.nix @@ -1,7 +1,13 @@ -{ pkgs, lib, config, ... }: +# Devenv module for Garage: +# The shipped module has a bug, as the configure does not get executed. +{ pkgs +, lib +, config +, ... +}: let - cfg = config.services.garage; + cfg = config.custodian.services.garage; types = lib.types; parsePort = addr: lib.toInt (lib.last (lib.splitString ":" addr)); @@ -41,6 +47,7 @@ let configureScript = '' set -euo pipefail + GARAGE="${cfg.package}/bin/garage -c ${configFile}" # Apply the cluster layout once. Garage rejects S3 traffic until at @@ -60,7 +67,7 @@ let ''; in { - options.services.garage = { + options.custodian.services.garage = { enable = lib.mkEnableOption "Garage S3-compatible object storage"; package = lib.mkOption { @@ -166,11 +173,10 @@ in ports.rpc.allocate = 3901; exec = "exec ${cfg.package}/bin/garage -c ${configFile} server"; ready.exec = '' - ${pkgs.curl}/bin/curl -sf \ - -H "Authorization: Bearer ${cfg.adminToken}" \ - "http://${adminHost}:${toString allocatedAdminPort}/v1/health" + ${pkgs.curl}/bin/curl -f -H 'Authorization: Bearer ${cfg.adminToken}' 'http://${adminHost}:${toString allocatedAdminPort}/v1/health' ''; - before = [ "devenv:garage:configure" ]; + + after = [ "devenv:garage:setup" ]; }; env = { @@ -184,13 +190,11 @@ in exec = '' mkdir -p "${config.env.DEVENV_STATE}/garage/meta" "${config.env.DEVENV_STATE}/garage/data" ''; - before = [ "devenv:processes:garage" ]; }; - # Apply the cluster layout once garage is accepting connections so - # bucket commands can't race against a node with no role assigned. - tasks."devenv:garage:configure" = { + processes.garage-configure = { exec = configureScript; + after = [ "devenv:processes:garage@ready" ]; }; }; } From 5974bb1eb3f1067a5f178dccf1881907f5baa9bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20N=C3=BCtzi?= Date: Tue, 19 May 2026 17:20:58 +0200 Subject: [PATCH 2/8] fix: garage service --- src/modules/services/garage.nix | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/modules/services/garage.nix b/src/modules/services/garage.nix index 028661bc4..fc333aea0 100644 --- a/src/modules/services/garage.nix +++ b/src/modules/services/garage.nix @@ -1,5 +1,3 @@ -# Devenv module for Garage: -# The shipped module has a bug, as the configure does not get executed. { pkgs , lib , config @@ -7,7 +5,7 @@ }: let - cfg = config.custodian.services.garage; + cfg = config.services.garage; types = lib.types; parsePort = addr: lib.toInt (lib.last (lib.splitString ":" addr)); @@ -67,7 +65,7 @@ let ''; in { - options.custodian.services.garage = { + options.services.garage = { enable = lib.mkEnableOption "Garage S3-compatible object storage"; package = lib.mkOption { From e36c8f8654c823d6b4a656f11b0513d2be96da85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20N=C3=BCtzi?= Date: Tue, 19 May 2026 17:39:57 +0200 Subject: [PATCH 3/8] fix: move to examples --- examples/garage/.test.sh | 25 +++++++++++++++++++++++++ {tests => examples}/garage/devenv.nix | 1 + tests/garage/.test.sh | 8 -------- 3 files changed, 26 insertions(+), 8 deletions(-) create mode 100755 examples/garage/.test.sh rename {tests => examples}/garage/devenv.nix (52%) delete mode 100755 tests/garage/.test.sh diff --git a/examples/garage/.test.sh b/examples/garage/.test.sh new file mode 100755 index 000000000..1227d2f72 --- /dev/null +++ b/examples/garage/.test.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -ex + +wait_for_port "$GARAGE_ADMIN_PORT" +wait_for_port "$GARAGE_S3_PORT" + +curl -sf -H "Authorization: Bearer devtoken" \ + "http://127.0.0.1:$GARAGE_ADMIN_PORT/v1/health" >/dev/null + +# Test if a bucket exists +function assert_bucket_exists() { + BUCKET_NAME="test-bucket" + export GARAGE_RPC_SECRET="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + until + garage bucket info "$BUCKET_NAME" + do sleep 0.5; done +} + +if timeout 20 assert_bucket_exists; then + echo "Bucket '$BUCKET_NAME' exists" +else + echo "Bucket '$BUCKET_NAME' does not exist" + exit 1 +fi diff --git a/tests/garage/devenv.nix b/examples/garage/devenv.nix similarity index 52% rename from tests/garage/devenv.nix rename to examples/garage/devenv.nix index a5c9f24ce..9c0dfa8ce 100644 --- a/tests/garage/devenv.nix +++ b/examples/garage/devenv.nix @@ -3,6 +3,7 @@ { services.garage = { enable = true; + rpcSecret = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; buckets = [ "test-bucket" ]; }; } diff --git a/tests/garage/.test.sh b/tests/garage/.test.sh deleted file mode 100755 index 75efa0a13..000000000 --- a/tests/garage/.test.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash -set -ex - -wait_for_port "$GARAGE_ADMIN_PORT" -wait_for_port "$GARAGE_S3_PORT" - -curl -sf -H "Authorization: Bearer devtoken" \ - "http://127.0.0.1:$GARAGE_ADMIN_PORT/v1/health" >/dev/null From 16af061cd78e9862052e5244e9cabfd3f50e7f23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20N=C3=BCtzi?= Date: Tue, 19 May 2026 17:42:58 +0200 Subject: [PATCH 4/8] fix: tests --- examples/garage/.test.sh | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/examples/garage/.test.sh b/examples/garage/.test.sh index 1227d2f72..bf325b2be 100755 --- a/examples/garage/.test.sh +++ b/examples/garage/.test.sh @@ -7,17 +7,20 @@ wait_for_port "$GARAGE_S3_PORT" curl -sf -H "Authorization: Bearer devtoken" \ "http://127.0.0.1:$GARAGE_ADMIN_PORT/v1/health" >/dev/null +BUCKET_NAME="test-bucket" +export GARAGE_RPC_SECRET="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + # Test if a bucket exists function assert_bucket_exists() { - BUCKET_NAME="test-bucket" - export GARAGE_RPC_SECRET="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - - until - garage bucket info "$BUCKET_NAME" - do sleep 0.5; done + for _ in $(seq 1 20); do + if ! garage bucket info "$BUCKET_NAME"; then + sleep 1 + continue + fi + done } -if timeout 20 assert_bucket_exists; then +if assert_bucket_exists; then echo "Bucket '$BUCKET_NAME' exists" else echo "Bucket '$BUCKET_NAME' does not exist" From cc25d4bb8e0a183bfba2670997f972a58660cbe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20N=C3=BCtzi?= Date: Wed, 20 May 2026 13:01:44 +0200 Subject: [PATCH 5/8] fix: review changes --- src/modules/services/garage.nix | 125 ++++++++++++++++++++++++-------- 1 file changed, 95 insertions(+), 30 deletions(-) diff --git a/src/modules/services/garage.nix b/src/modules/services/garage.nix index fc333aea0..8601d84a6 100644 --- a/src/modules/services/garage.nix +++ b/src/modules/services/garage.nix @@ -43,26 +43,101 @@ let ${cfg.extraConfig} ''; - configureScript = '' - set -euo pipefail + garageFunctions = '' + function garageHealthEndpoint() { + ${lib.getExe pkgs.curl} \ + -f -H 'Authorization: Bearer ${cfg.adminToken}' \ + 'http://${adminHost}:${toString allocatedAdminPort}/v1/health' 2>/dev/null + } + + function garageHealthy() { + if garageHealthEndpoint | ${lib.getExe pkgs.jq} -e '.status == "healthy"'; then + return 0 + fi + + return 1 + } + + function garageRunning() { + garageHealthEndpoint + } + ''; - GARAGE="${cfg.package}/bin/garage -c ${configFile}" + configureScript = + pkgs.writeShellScriptBin "configure" + # bash + '' + set -euo pipefail + ${garageFunctions} + + GARAGE="${cfg.package}/bin/garage -c ${configFile}" + + until garageRunning; do + echo "Garage not ready, waiting..." + sleep 1 + done + + echo "Garage ready." + echo "Configuring layout ..." + # Apply the cluster layout once. Garage rejects S3 traffic until at + # least one node has a role, so this step is mandatory before the + # buckets can be touched. + if $GARAGE status 2>/dev/null | grep -q "NO ROLE ASSIGNED"; then + NODE_ID=$($GARAGE node id 2>/dev/null | cut -d@ -f1) + $GARAGE layout assign -z dc1 -c 1G "$NODE_ID" + $GARAGE layout apply --version 1 + fi + + echo "Create all buckets ..." + for bucket in ${lib.concatMapStringsSep " " (x: "'${x}'") cfg.buckets}; do + if $GARAGE bucket info "$bucket" &>/dev/null; then + echo "Bucket '$bucket' already exists, skipping" + else + echo "Create bucket '$bucket'." + $GARAGE bucket create "$bucket" + fi + done + + ${cfg.afterStart} + ''; - # Apply the cluster layout once. Garage rejects S3 traffic until at - # least one node has a role, so this step is mandatory before the - # buckets can be touched. - if $GARAGE status 2>/dev/null | grep -q "NO ROLE ASSIGNED"; then - NODE_ID=$($GARAGE node id 2>/dev/null | cut -d@ -f1) - $GARAGE layout assign -z dc1 -c 1G "$NODE_ID" - $GARAGE layout apply --version 1 - fi + readyScript = + pkgs.writeShellScriptBin "ready" + # bash + '' + set -euo pipefail + ${garageFunctions} + + GARAGE="${cfg.package}/bin/garage -c ${configFile}" + + garageHealthy || { + echo "Garage not ready." + exit 1 + } + + # Check that all buckets are created. + for bucket in ${lib.concatMapStringsSep " " (x: "'${x}'") cfg.buckets}; do + if $GARAGE bucket info "$bucket" &>/dev/null; then + echo "Bucket '$bucket' exists." + else + echo "Bucket '$bucket' does not yet exist, waiting..." + exit 1 + fi + done + + echo "Garage ready and setup." + ''; - for bucket in ${lib.escapeShellArgs cfg.buckets}; do - $GARAGE bucket create "$bucket" 2>/dev/null || true - done + startScript = + pkgs.writeShellScriptBin "start" + # bash + '' + echo "Setup folders." + mkdir -p "${config.env.DEVENV_STATE}/garage/meta" \ + "${config.env.DEVENV_STATE}/garage/data" - ${cfg.afterStart} - ''; + exec ${cfg.package}/bin/garage -c ${configFile} server + ''; in { options.services.garage = { @@ -169,12 +244,8 @@ in ports.s3.allocate = baseS3Port; ports.admin.allocate = baseAdminPort; ports.rpc.allocate = 3901; - exec = "exec ${cfg.package}/bin/garage -c ${configFile} server"; - ready.exec = '' - ${pkgs.curl}/bin/curl -f -H 'Authorization: Bearer ${cfg.adminToken}' 'http://${adminHost}:${toString allocatedAdminPort}/v1/health' - ''; - - after = [ "devenv:garage:setup" ]; + exec = "exec ${startScript}/bin/start"; + ready.exec = "exec ${readyScript}/bin/ready"; }; env = { @@ -184,15 +255,9 @@ in GARAGE_CONFIG_FILE = "${configFile}"; }; - tasks."devenv:garage:setup" = { - exec = '' - mkdir -p "${config.env.DEVENV_STATE}/garage/meta" "${config.env.DEVENV_STATE}/garage/data" - ''; - }; - processes.garage-configure = { - exec = configureScript; - after = [ "devenv:processes:garage@ready" ]; + exec = "exec ${configureScript}/bin/configure"; + after = [ "devenv:processes:garage@started" ]; }; }; } From fb37496628f9ff999f3e812712145fe3d9aa13a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20N=C3=BCtzi?= Date: Thu, 21 May 2026 17:15:03 +0200 Subject: [PATCH 6/8] fix: add garage web-ui --- src/modules/services/garage.nix | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/modules/services/garage.nix b/src/modules/services/garage.nix index 8601d84a6..c3a730387 100644 --- a/src/modules/services/garage.nix +++ b/src/modules/services/garage.nix @@ -224,6 +224,27 @@ in Additional `garage.toml` snippet appended to the generated config. ''; }; + + ui = { + enable = lib.mkEnableOption "Enable a simple web UI."; + + start = lib.mkOption { + type = types.bool; + default = false; + description = '' + If the service is by default started or must be manually started. + ''; + }; + + port = lib.mkOption { + type = types.port; + default = 3919; + example = 3919; + description = '' + On which port the UI should run. + ''; + }; + }; }; config = lib.mkIf cfg.enable { @@ -259,5 +280,15 @@ in exec = "exec ${configureScript}/bin/configure"; after = [ "devenv:processes:garage@started" ]; }; + + processes.garage-web-ui = lib.mkIf cfg.ui.enable { + exec = "${lib.getExe pkgs.garage-webui}"; + start.enable = cfg.ui.start; + env = { + CONFIG_PATH = "${configFile}"; + PORT = lib.toString cfg.ui.port; + }; + after = [ "devenv:processes:garage@ready" ]; + }; }; } From 77ad925fd4f6ddae97c0edc722ac887031e23da2 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 15:17:28 +0000 Subject: [PATCH 7/8] Auto generate docs/src/reference/options.md --- docs/src/reference/options.md | 88 +++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/docs/src/reference/options.md b/docs/src/reference/options.md index 29f09a19a..a8faf749f 100644 --- a/docs/src/reference/options.md +++ b/docs/src/reference/options.md @@ -28505,6 +28505,94 @@ string +## services.garage.ui.enable + + + +Whether to enable Enable a simple web UI… + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + + + +*Example:* + +```nix +true +``` + +*Declared by:* + - [https://github.com/cachix/devenv/blob/main/src/modules/services/garage.nix](https://github.com/cachix/devenv/blob/main/src/modules/services/garage.nix) + + + +## services.garage.ui.port + + + +On which port the UI should run. + + + +*Type:* +16 bit unsigned integer; between 0 and 65535 (both inclusive) + + + +*Default:* + +```nix +3919 +``` + + + +*Example:* + +```nix +3919 +``` + +*Declared by:* + - [https://github.com/cachix/devenv/blob/main/src/modules/services/garage.nix](https://github.com/cachix/devenv/blob/main/src/modules/services/garage.nix) + + + +## services.garage.ui.start + + + +If the service is by default started or must be manually started. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + +*Declared by:* + - [https://github.com/cachix/devenv/blob/main/src/modules/services/garage.nix](https://github.com/cachix/devenv/blob/main/src/modules/services/garage.nix) + + + ## services.httpbin.enable From a70655822bdccdbf2cb80ff2a8f93bcc38cc5099 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 15:18:23 +0000 Subject: [PATCH 8/8] Auto generate missing individual markdowns --- docs/src/services/garage.md | 88 +++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/docs/src/services/garage.md b/docs/src/services/garage.md index 83ef9eaae..143448c94 100644 --- a/docs/src/services/garage.md +++ b/docs/src/services/garage.md @@ -287,3 +287,91 @@ string *Declared by:* - [https://github\.com/cachix/devenv/blob/main/src/modules/services/garage\.nix](https://github.com/cachix/devenv/blob/main/src/modules/services/garage.nix) + + + +### services\.garage\.ui\.enable + + + +Whether to enable Enable a simple web UI… + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + + + +*Example:* + +```nix +true +``` + +*Declared by:* + - [https://github\.com/cachix/devenv/blob/main/src/modules/services/garage\.nix](https://github.com/cachix/devenv/blob/main/src/modules/services/garage.nix) + + + +### services\.garage\.ui\.port + + + +On which port the UI should run\. + + + +*Type:* +16 bit unsigned integer; between 0 and 65535 (both inclusive) + + + +*Default:* + +```nix +3919 +``` + + + +*Example:* + +```nix +3919 +``` + +*Declared by:* + - [https://github\.com/cachix/devenv/blob/main/src/modules/services/garage\.nix](https://github.com/cachix/devenv/blob/main/src/modules/services/garage.nix) + + + +### services\.garage\.ui\.start + + + +If the service is by default started or must be manually started\. + + + +*Type:* +boolean + + + +*Default:* + +```nix +false +``` + +*Declared by:* + - [https://github\.com/cachix/devenv/blob/main/src/modules/services/garage\.nix](https://github.com/cachix/devenv/blob/main/src/modules/services/garage.nix)