From 3a881979f9a59d4f150a58ceadc0d00dffe77297 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nicolas=20=E2=80=9CNiols=E2=80=9D=20Jeannerod?=
 <nicolas.jeannerod@moduscreate.com>
Date: Thu, 13 Feb 2025 11:38:45 +0100
Subject: [PATCH 01/16] Move old services README to a HACKING file

---
 services/{README.md => HACKING.md} | 3 +++
 1 file changed, 3 insertions(+)
 rename services/{README.md => HACKING.md} (97%)

diff --git a/services/README.md b/services/HACKING.md
similarity index 97%
rename from services/README.md
rename to services/HACKING.md
index 1aa2901..14cc752 100644
--- a/services/README.md
+++ b/services/HACKING.md
@@ -1,3 +1,6 @@
+NOTE[Niols]: This file looks like a README file but is in fact development notes
+from a previous engineer of the project. Needs an overhaul.
+
 # Fediverse VMs
 
 This repo is, for now, an attempt to familiarize myself with NixOS options for Fediverse applications, and build up a configuration layer that will set most of the relevant options for you (in a semi-opinionated way) given some high-level configuration. The goal is something in the same vein as [nixos-mailserver](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver) but for fediversity.

From 237c56791f84a80a967a56c6da89089a3800a72e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nicolas=20=E2=80=9CNiols=E2=80=9D=20Jeannerod?=
 <nicolas.jeannerod@moduscreate.com>
Date: Fri, 14 Feb 2025 15:03:07 +0100
Subject: [PATCH 02/16] Basic services README

---
 services/README.md | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)
 create mode 100644 services/README.md

diff --git a/services/README.md b/services/README.md
new file mode 100644
index 0000000..1a73652
--- /dev/null
+++ b/services/README.md
@@ -0,0 +1,31 @@
+# Services
+
+This directory contains the definition of a NixOS module allowing to easily set
+up the Fediverse services that our project cares about. Those services are
+already packaged in nixpkgs, which arguably already provides this. Here is
+therefore the important distinction:
+
+- The goal of nixpkgs is to be generic, bring all the building blocks and let
+  you do whatever it is you want with them. You get to choose all the options,
+  if to use a reverse proxy, which one, if to use an S3 backend, which one,
+  which database, etc.
+
+- This module aims at being straightforward to use by being opinionated. It only
+  supports the use case of the Fediversity project and strives to hide as much
+  of the nitty-gritty details as possible.
+
+For those that know it, we could say that the current module is an analogous of
+[simple-nixos-mailserver] for Fediverse services.
+
+[simple-nixos-mailserver]: https://gitlab.com/simple-nixos-mailserver/nixos-mailserver
+
+## Content of this directory
+
+- [fediversity][./fediversity] contains the definition of the services. Look in
+  particular at its `default.nix` that contains the definition of the options.
+
+- [vm][./vm] contains options specific to making the service run in local QEMU
+  VMs. These modules will for instance override the defaults to disable SSL, and
+  they will add virtualisation options to forward ports, for instance.
+
+- [tests][./tests] contain full NixOS tests of the services.

From ff03d12dc10295fa0d30da283478ada77f18f18f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nicolas=20=E2=80=9CNiols=E2=80=9D=20Jeannerod?=
 <nicolas.jeannerod@moduscreate.com>
Date: Fri, 14 Feb 2025 16:13:43 +0100
Subject: [PATCH 03/16] Move Garage configuration to own directory

---
 services/fediversity/default.nix                        | 2 +-
 services/fediversity/{garage.nix => garage/default.nix} | 0
 2 files changed, 1 insertion(+), 1 deletion(-)
 rename services/fediversity/{garage.nix => garage/default.nix} (100%)

diff --git a/services/fediversity/default.nix b/services/fediversity/default.nix
index 6fe796a..0747e2d 100644
--- a/services/fediversity/default.nix
+++ b/services/fediversity/default.nix
@@ -8,7 +8,7 @@ let
 in
 {
   imports = [
-    ./garage.nix
+    ./garage
     ./mastodon.nix
     ./pixelfed.nix
     ./peertube.nix
diff --git a/services/fediversity/garage.nix b/services/fediversity/garage/default.nix
similarity index 100%
rename from services/fediversity/garage.nix
rename to services/fediversity/garage/default.nix

From b68a821b7725b7e68a3de4863aa695f06d733b34 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nicolas=20=E2=80=9CNiols=E2=80=9D=20Jeannerod?=
 <nicolas.jeannerod@moduscreate.com>
Date: Fri, 14 Feb 2025 16:17:07 +0100
Subject: [PATCH 04/16] Searate Garage config and options

---
 services/fediversity/garage/default.nix | 82 +------------------------
 services/fediversity/garage/options.nix | 80 ++++++++++++++++++++++++
 2 files changed, 82 insertions(+), 80 deletions(-)
 create mode 100644 services/fediversity/garage/options.nix

diff --git a/services/fediversity/garage/default.nix b/services/fediversity/garage/default.nix
index f43fe11..f9015cf 100644
--- a/services/fediversity/garage/default.nix
+++ b/services/fediversity/garage/default.nix
@@ -17,13 +17,7 @@ in
 
 let
   inherit (builtins) toString;
-  inherit (lib)
-    types
-    mkOption
-    mkEnableOption
-    optionalString
-    concatStringsSep
-    ;
+  inherit (lib) optionalString concatStringsSep;
   inherit (lib.strings) escapeShellArg;
   inherit (lib.attrsets) filterAttrs mapAttrs';
   cfg = config.services.garage;
@@ -100,79 +94,7 @@ let
 in
 
 {
-  # add in options to ensure creation of buckets and keys
-  options = {
-    services.garage = {
-      ensureBuckets = mkOption {
-        type = types.attrsOf (
-          types.submodule {
-            options = {
-              website = mkOption {
-                type = types.bool;
-                default = false;
-              };
-              # I think setting corsRules should allow another website to show images from your bucket
-              corsRules = {
-                enable = mkEnableOption "CORS Rules";
-                allowedHeaders = mkOption {
-                  type = types.listOf types.str;
-                  default = [ ];
-                };
-                allowedMethods = mkOption {
-                  type = types.listOf types.str;
-                  default = [ ];
-                };
-                allowedOrigins = mkOption {
-                  type = types.listOf types.str;
-                  default = [ ];
-                };
-              };
-              aliases = mkOption {
-                type = types.listOf types.str;
-                default = [ ];
-              };
-            };
-          }
-        );
-        default = { };
-      };
-      ensureKeys = mkOption {
-        type = types.attrsOf (
-          types.submodule {
-            # TODO: these should be managed as secrets, not in the nix store
-            options = {
-              id = mkOption { type = types.str; };
-              secret = mkOption { type = types.str; };
-              # TODO: assert at least one of these is true
-              # NOTE: this currently needs to be done at the top level module
-              ensureAccess = mkOption {
-                type = types.attrsOf (
-                  types.submodule {
-                    options = {
-                      read = mkOption {
-                        type = types.bool;
-                        default = false;
-                      };
-                      write = mkOption {
-                        type = types.bool;
-                        default = false;
-                      };
-                      owner = mkOption {
-                        type = types.bool;
-                        default = false;
-                      };
-                    };
-                  }
-                );
-                default = [ ];
-              };
-            };
-          }
-        );
-        default = { };
-      };
-    };
-  };
+  imports = [ ./options.nix ];
 
   config = lib.mkIf config.fediversity.enable {
     environment.systemPackages = [
diff --git a/services/fediversity/garage/options.nix b/services/fediversity/garage/options.nix
new file mode 100644
index 0000000..69d9c4b
--- /dev/null
+++ b/services/fediversity/garage/options.nix
@@ -0,0 +1,80 @@
+{ lib, ... }:
+
+let
+  inherit (lib) types mkOption mkEnableOption;
+in
+
+{
+  options = {
+    services.garage = {
+      ensureBuckets = mkOption {
+        type = types.attrsOf (
+          types.submodule {
+            options = {
+              website = mkOption {
+                type = types.bool;
+                default = false;
+              };
+              # I think setting corsRules should allow another website to show images from your bucket
+              corsRules = {
+                enable = mkEnableOption "CORS Rules";
+                allowedHeaders = mkOption {
+                  type = types.listOf types.str;
+                  default = [ ];
+                };
+                allowedMethods = mkOption {
+                  type = types.listOf types.str;
+                  default = [ ];
+                };
+                allowedOrigins = mkOption {
+                  type = types.listOf types.str;
+                  default = [ ];
+                };
+              };
+              aliases = mkOption {
+                type = types.listOf types.str;
+                default = [ ];
+              };
+            };
+          }
+        );
+        default = { };
+      };
+      ensureKeys = mkOption {
+        type = types.attrsOf (
+          types.submodule {
+            # TODO: these should be managed as secrets, not in the nix store
+            options = {
+              id = mkOption { type = types.str; };
+              secret = mkOption { type = types.str; };
+              # TODO: assert at least one of these is true
+              # NOTE: this currently needs to be done at the top level module
+              ensureAccess = mkOption {
+                type = types.attrsOf (
+                  types.submodule {
+                    options = {
+                      read = mkOption {
+                        type = types.bool;
+                        default = false;
+                      };
+                      write = mkOption {
+                        type = types.bool;
+                        default = false;
+                      };
+                      owner = mkOption {
+                        type = types.bool;
+                        default = false;
+                      };
+                    };
+                  }
+                );
+                default = [ ];
+              };
+            };
+          }
+        );
+        default = { };
+      };
+    };
+  };
+}

From d9188427ede5a85c8bdd395bfcfb0e8c49a6762f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nicolas=20=E2=80=9CNiols=E2=80=9D=20Jeannerod?=
 <nicolas.jeannerod@moduscreate.com>
Date: Fri, 14 Feb 2025 16:25:52 +0100
Subject: [PATCH 05/16] `services.garage` -> `fediversity.garage`

for the options that are ours; we want to avoid clashes if possible
---
 services/fediversity/garage/default.nix |   7 +-
 services/fediversity/garage/options.nix | 125 ++++++++++++------------
 services/fediversity/mastodon.nix       |   2 +-
 services/fediversity/peertube.nix       |   2 +-
 services/fediversity/pixelfed.nix       |   2 +-
 services/tests/mastodon.nix             |   4 +-
 services/tests/peertube.nix             |   4 +-
 services/tests/pixelfed-garage.nix      |   5 +-
 services/vm/garage-vm.nix               |  16 ++-
 9 files changed, 81 insertions(+), 86 deletions(-)

diff --git a/services/fediversity/garage/default.nix b/services/fediversity/garage/default.nix
index f9015cf..f1638df 100644
--- a/services/fediversity/garage/default.nix
+++ b/services/fediversity/garage/default.nix
@@ -7,7 +7,6 @@ let
   };
 in
 
-# TODO: expand to a multi-machine setup
 {
   config,
   lib,
@@ -66,7 +65,7 @@ let
         garage bucket deny --read --write --owner ${bucketArg} --key tmp
       ''}
     '';
-  ensureBucketsScript = concatMapAttrs ensureBucketScriptFn cfg.ensureBuckets;
+  ensureBucketsScript = concatMapAttrs ensureBucketScriptFn config.fediversity.garage.ensureBuckets;
   ensureAccessScriptFn =
     key: bucket:
     {
@@ -90,7 +89,7 @@ let
       garage key import --yes -n ${escapeShellArg key} ${escapeShellArg id} ${escapeShellArg secret} || :
       ${concatMapAttrs (ensureAccessScriptFn key) ensureAccess}
     '';
-  ensureKeysScript = concatMapAttrs ensureKeyScriptFn cfg.ensureKeys;
+  ensureKeysScript = concatMapAttrs ensureKeyScriptFn config.fediversity.garage.ensureKeys;
 in
 
 {
@@ -149,7 +148,7 @@ in
       mapAttrs' (bucket: _: {
         name = fedicfg.web.domainForBucket bucket;
         inherit value;
-      }) (filterAttrs (_: { website, ... }: website) cfg.ensureBuckets);
+      }) (filterAttrs (_: { website, ... }: website) config.fediversity.garage.ensureBuckets);
 
     systemd.services.ensure-garage = {
       after = [ "garage.service" ];
diff --git a/services/fediversity/garage/options.nix b/services/fediversity/garage/options.nix
index 69d9c4b..56fec59 100644
--- a/services/fediversity/garage/options.nix
+++ b/services/fediversity/garage/options.nix
@@ -5,76 +5,75 @@ let
 in
 
 {
-  options = {
-    services.garage = {
-      ensureBuckets = mkOption {
-        type = types.attrsOf (
-          types.submodule {
-            options = {
-              website = mkOption {
-                type = types.bool;
-                default = false;
+  options.fediversity.garage = {
+    ensureBuckets = mkOption {
+      type = types.attrsOf (
+        types.submodule {
+          options = {
+            website = mkOption {
+              type = types.bool;
+              default = false;
+            };
+            # I think setting corsRules should allow another website to show images from your bucket
+            corsRules = {
+              enable = mkEnableOption "CORS Rules";
+              allowedHeaders = mkOption {
+                type = types.listOf types.str;
+                default = [ ];
               };
-              # I think setting corsRules should allow another website to show images from your bucket
-              corsRules = {
-                enable = mkEnableOption "CORS Rules";
-                allowedHeaders = mkOption {
-                  type = types.listOf types.str;
-                  default = [ ];
-                };
-                allowedMethods = mkOption {
-                  type = types.listOf types.str;
-                  default = [ ];
-                };
-                allowedOrigins = mkOption {
-                  type = types.listOf types.str;
-                  default = [ ];
-                };
+              allowedMethods = mkOption {
+                type = types.listOf types.str;
+                default = [ ];
               };
-              aliases = mkOption {
+              allowedOrigins = mkOption {
                 type = types.listOf types.str;
                 default = [ ];
               };
             };
-          }
-        );
-        default = { };
-      };
-      ensureKeys = mkOption {
-        type = types.attrsOf (
-          types.submodule {
-            # TODO: these should be managed as secrets, not in the nix store
-            options = {
-              id = mkOption { type = types.str; };
-              secret = mkOption { type = types.str; };
-              # TODO: assert at least one of these is true
-              # NOTE: this currently needs to be done at the top level module
-              ensureAccess = mkOption {
-                type = types.attrsOf (
-                  types.submodule {
-                    options = {
-                      read = mkOption {
-                        type = types.bool;
-                        default = false;
-                      };
-                      write = mkOption {
-                        type = types.bool;
-                        default = false;
-                      };
-                      owner = mkOption {
-                        type = types.bool;
-                        default = false;
-                      };
-                    };
-                  }
-                );
-                default = [ ];
-              };
+            aliases = mkOption {
+              type = types.listOf types.str;
+              default = [ ];
             };
-          }
-        );
-        default = { };
-      };
+          };
+        }
+      );
+      default = { };
+    };
+
+    ensureKeys = mkOption {
+      type = types.attrsOf (
+        types.submodule {
+          # TODO: these should be managed as secrets, not in the nix store
+          options = {
+            id = mkOption { type = types.str; };
+            secret = mkOption { type = types.str; };
+            # TODO: assert at least one of these is true
+            # NOTE: this currently needs to be done at the top level module
+            ensureAccess = mkOption {
+              type = types.attrsOf (
+                types.submodule {
+                  options = {
+                    read = mkOption {
+                      type = types.bool;
+                      default = false;
+                    };
+                    write = mkOption {
+                      type = types.bool;
+                      default = false;
+                    };
+                    owner = mkOption {
+                      type = types.bool;
+                      default = false;
+                    };
+                  };
+                }
+              );
+              default = [ ];
+            };
+          };
+        }
+      );
+      default = { };
     };
   };
 }
diff --git a/services/fediversity/mastodon.nix b/services/fediversity/mastodon.nix
index 2ed4b3e..7486559 100644
--- a/services/fediversity/mastodon.nix
+++ b/services/fediversity/mastodon.nix
@@ -9,7 +9,7 @@ in
 
 lib.mkIf (config.fediversity.enable && config.fediversity.mastodon.enable) {
   #### garage setup
-  services.garage = {
+  fediversity.garage = {
     ensureBuckets = {
       mastodon = {
         website = true;
diff --git a/services/fediversity/peertube.nix b/services/fediversity/peertube.nix
index bb2b618..ae973c1 100644
--- a/services/fediversity/peertube.nix
+++ b/services/fediversity/peertube.nix
@@ -17,7 +17,7 @@ lib.mkIf (config.fediversity.enable && config.fediversity.peertube.enable) {
     1936
   ];
 
-  services.garage = {
+  fediversity.garage = {
     ensureBuckets = {
       peertube-videos = {
         website = true;
diff --git a/services/fediversity/pixelfed.nix b/services/fediversity/pixelfed.nix
index 279445e..6c2a3eb 100644
--- a/services/fediversity/pixelfed.nix
+++ b/services/fediversity/pixelfed.nix
@@ -13,7 +13,7 @@ in
 }:
 
 lib.mkIf (config.fediversity.enable && config.fediversity.pixelfed.enable) {
-  services.garage = {
+  fediversity.garage = {
     ensureBuckets = {
       pixelfed = {
         website = true;
diff --git a/services/tests/mastodon.nix b/services/tests/mastodon.nix
index ef6a667..80ede0c 100644
--- a/services/tests/mastodon.nix
+++ b/services/tests/mastodon.nix
@@ -69,8 +69,8 @@ pkgs.nixosTest {
           expect
         ];
         environment.variables = {
-          AWS_ACCESS_KEY_ID = config.services.garage.ensureKeys.mastodon.id;
-          AWS_SECRET_ACCESS_KEY = config.services.garage.ensureKeys.mastodon.secret;
+          AWS_ACCESS_KEY_ID = config.fediversity.garage.ensureKeys.mastodon.id;
+          AWS_SECRET_ACCESS_KEY = config.fediversity.garage.ensureKeys.mastodon.secret;
         };
       };
   };
diff --git a/services/tests/peertube.nix b/services/tests/peertube.nix
index 6a5161b..c10e01a 100644
--- a/services/tests/peertube.nix
+++ b/services/tests/peertube.nix
@@ -197,8 +197,8 @@ pkgs.nixosTest {
         systemd.services.postgresql.serviceConfig.TimeoutSec = lib.mkForce 3600;
 
         environment.variables = {
-          AWS_ACCESS_KEY_ID = config.services.garage.ensureKeys.peertube.id;
-          AWS_SECRET_ACCESS_KEY = config.services.garage.ensureKeys.peertube.secret;
+          AWS_ACCESS_KEY_ID = config.fediversity.garage.ensureKeys.peertube.id;
+          AWS_SECRET_ACCESS_KEY = config.fediversity.garage.ensureKeys.peertube.secret;
           PT_INITIAL_ROOT_PASSWORD = "testtest";
         };
       };
diff --git a/services/tests/pixelfed-garage.nix b/services/tests/pixelfed-garage.nix
index 56c5d11..346ffe4 100644
--- a/services/tests/pixelfed-garage.nix
+++ b/services/tests/pixelfed-garage.nix
@@ -1,4 +1,5 @@
 { pkgs, self }:
+
 let
   lib = pkgs.lib;
 
@@ -160,8 +161,8 @@ pkgs.nixosTest {
         ];
         environment.variables = {
           POST_MEDIA = ./fediversity.png;
-          AWS_ACCESS_KEY_ID = config.services.garage.ensureKeys.pixelfed.id;
-          AWS_SECRET_ACCESS_KEY = config.services.garage.ensureKeys.pixelfed.secret;
+          AWS_ACCESS_KEY_ID = config.fediversity.garage.ensureKeys.pixelfed.id;
+          AWS_SECRET_ACCESS_KEY = config.fediversity.garage.ensureKeys.pixelfed.secret;
           ## without this we get frivolous errors in the logs
           MC_REGION = "garage";
         };
diff --git a/services/vm/garage-vm.nix b/services/vm/garage-vm.nix
index aca295e..b115b51 100644
--- a/services/vm/garage-vm.nix
+++ b/services/vm/garage-vm.nix
@@ -8,10 +8,6 @@
 let
   inherit (lib) mkVMOverride mapAttrs' filterAttrs;
 
-  cfg = config.services.garage;
-
-  fedicfg = config.fediversity.internal.garage;
-
 in
 {
   imports = [ (modulesPath + "/virtualisation/qemu-vm.nix") ];
@@ -24,21 +20,21 @@ in
       };
     in
     mapAttrs' (bucket: _: {
-      name = fedicfg.web.domainForBucket bucket;
+      name = config.fediversity.internal.garage.web.domainForBucket bucket;
       inherit value;
-    }) (filterAttrs (_: { website, ... }: website) cfg.ensureBuckets);
+    }) (filterAttrs (_: { website, ... }: website) config.fediversity.garage.ensureBuckets);
 
   virtualisation.diskSize = 2048;
   virtualisation.forwardPorts = [
     {
       from = "host";
-      host.port = fedicfg.rpc.port;
-      guest.port = fedicfg.rpc.port;
+      host.port = config.fediversity.internal.garage.rpc.port;
+      guest.port = config.fediversity.internal.garage.rpc.port;
     }
     {
       from = "host";
-      host.port = fedicfg.web.internalPort;
-      guest.port = fedicfg.web.internalPort;
+      host.port = config.fediversity.internal.garage.web.internalPort;
+      guest.port = config.fediversity.internal.garage.web.internalPort;
     }
   ];
 }

From f4babe38a8db9a7b33fefdec8f92bd81bfe15502 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nicolas=20=E2=80=9CNiols=E2=80=9D=20Jeannerod?=
 <nicolas.jeannerod@moduscreate.com>
Date: Fri, 14 Feb 2025 18:32:08 +0100
Subject: [PATCH 06/16] Consolidate Garage options

- `fediversity.internal.garage` -> `fediversity.garage`
- all in the same place
---
 services/fediversity/default.nix        | 46 +------------------------
 services/fediversity/garage/default.nix | 32 +++++++++--------
 services/fediversity/garage/options.nix | 43 ++++++++++++++++++++++-
 services/fediversity/mastodon.nix       |  4 +--
 services/fediversity/peertube.nix       |  8 ++---
 services/fediversity/pixelfed.nix       |  6 ++--
 services/tests/mastodon.nix             |  4 +--
 services/tests/peertube.nix             |  2 +-
 services/tests/pixelfed-garage.nix      |  4 +--
 services/vm/garage-vm.nix               | 10 +++---
 10 files changed, 80 insertions(+), 79 deletions(-)

diff --git a/services/fediversity/default.nix b/services/fediversity/default.nix
index 0747e2d..d643c8d 100644
--- a/services/fediversity/default.nix
+++ b/services/fediversity/default.nix
@@ -1,7 +1,6 @@
 { lib, config, ... }:
 
 let
-  inherit (builtins) toString;
   inherit (lib) mkOption mkEnableOption mkForce;
   inherit (lib.types) types;
 
@@ -55,49 +54,6 @@ in
         default = { };
         type = types.submodule {
           options = {
-            garage = {
-              api = {
-                domain = mkOption {
-                  type = types.str;
-                  default = "s3.garage.${config.fediversity.domain}";
-                };
-                port = mkOption {
-                  type = types.int;
-                  default = 3900;
-                };
-                url = mkOption {
-                  type = types.str;
-                  default = "http://${config.fediversity.internal.garage.api.domain}:${toString config.fediversity.internal.garage.api.port}";
-                };
-              };
-
-              rpc = {
-                port = mkOption {
-                  type = types.int;
-                  default = 3901;
-                };
-              };
-
-              web = {
-                rootDomain = mkOption {
-                  type = types.str;
-                  default = "web.garage.${config.fediversity.domain}";
-                };
-                internalPort = mkOption {
-                  type = types.int;
-                  default = 3902;
-                };
-                domainForBucket = mkOption {
-                  type = types.functionTo types.str;
-                  default = bucket: "${bucket}.${config.fediversity.internal.garage.web.rootDomain}";
-                };
-                urlForBucket = mkOption {
-                  type = types.functionTo types.str;
-                  default = bucket: "http://${config.fediversity.internal.garage.web.domainForBucket bucket}";
-                };
-              };
-            };
-
             ## REVIEW: Do we want to recreate options under
             ## `fediversity.internal` or would we rather use the options from
             ## the respective services? See Taeer's comment:
@@ -132,6 +88,6 @@ in
     ## NOTE: For a one-machine deployment, this removes the need to provide an
     ## `s3.garage.<domain>` domain. However, this will quickly stop working once
     ## we go to multi-machines deployment.
-    fediversity.internal.garage.api.domain = mkForce "s3.garage.localhost";
+    fediversity.garage.api.domain = mkForce "s3.garage.localhost";
   };
 }
diff --git a/services/fediversity/garage/default.nix b/services/fediversity/garage/default.nix
index f1638df..5191016 100644
--- a/services/fediversity/garage/default.nix
+++ b/services/fediversity/garage/default.nix
@@ -19,9 +19,10 @@ let
   inherit (lib) optionalString concatStringsSep;
   inherit (lib.strings) escapeShellArg;
   inherit (lib.attrsets) filterAttrs mapAttrs';
-  cfg = config.services.garage;
-  fedicfg = config.fediversity.internal.garage;
   concatMapAttrs = scriptFn: attrset: concatStringsSep "\n" (lib.mapAttrsToList scriptFn attrset);
+
+  cfg = config.services.garage;
+
   ensureBucketScriptFn =
     bucket:
     {
@@ -61,11 +62,13 @@ let
       ${optionalString corsRules.enable ''
         garage bucket allow --read --write --owner ${bucketArg} --key tmp
         # TODO: endpoin-url should not be hard-coded
-        aws --region ${cfg.settings.s3_api.s3_region} --endpoint-url ${fedicfg.api.url} s3api put-bucket-cors --bucket ${bucketArg} --cors-configuration ${corsRulesJSON}
+        aws --region ${cfg.settings.s3_api.s3_region} --endpoint-url ${config.fediversity.garage.api.url} s3api put-bucket-cors --bucket ${bucketArg} --cors-configuration ${corsRulesJSON}
         garage bucket deny --read --write --owner ${bucketArg} --key tmp
       ''}
     '';
+
   ensureBucketsScript = concatMapAttrs ensureBucketScriptFn config.fediversity.garage.ensureBuckets;
+
   ensureAccessScriptFn =
     key: bucket:
     {
@@ -89,9 +92,10 @@ let
       garage key import --yes -n ${escapeShellArg key} ${escapeShellArg id} ${escapeShellArg secret} || :
       ${concatMapAttrs (ensureAccessScriptFn key) ensureAccess}
     '';
-  ensureKeysScript = concatMapAttrs ensureKeyScriptFn config.fediversity.garage.ensureKeys;
-in
 
+  ensureKeysScript = concatMapAttrs ensureKeyScriptFn config.fediversity.garage.ensureKeys;
+
+in
 {
   imports = [ ./options.nix ];
 
@@ -101,7 +105,7 @@ in
       pkgs.awscli
     ];
 
-    networking.firewall.allowedTCPPorts = [ fedicfg.rpc.port ];
+    networking.firewall.allowedTCPPorts = [ config.fediversity.garage.rpc.port ];
     services.garage = {
       enable = true;
       package = pkgs.garage_0_9;
@@ -110,15 +114,15 @@ in
         # TODO: use a secret file
         rpc_secret = "d576c4478cc7d0d94cfc127138cbb82018b0155c037d1c827dfb6c36be5f6625";
         # TODO: why does this have to be set? is there not a sensible default?
-        rpc_bind_addr = "[::]:${toString fedicfg.rpc.port}";
-        rpc_public_addr = "[::1]:${toString fedicfg.rpc.port}";
-        s3_api.api_bind_addr = "[::]:${toString fedicfg.api.port}";
-        s3_web.bind_addr = "[::]:${toString fedicfg.web.internalPort}";
-        s3_web.root_domain = ".${fedicfg.web.rootDomain}";
+        rpc_bind_addr = "[::]:${toString config.fediversity.garage.rpc.port}";
+        rpc_public_addr = "[::1]:${toString config.fediversity.garage.rpc.port}";
+        s3_api.api_bind_addr = "[::]:${toString config.fediversity.garage.api.port}";
+        s3_web.bind_addr = "[::]:${toString config.fediversity.garage.web.internalPort}";
+        s3_web.root_domain = ".${config.fediversity.garage.web.rootDomain}";
         index = "index.html";
 
         s3_api.s3_region = "garage";
-        s3_api.root_domain = ".${fedicfg.api.domain}";
+        s3_api.root_domain = ".${config.fediversity.garage.api.domain}";
       };
     };
 
@@ -146,7 +150,7 @@ in
         };
       in
       mapAttrs' (bucket: _: {
-        name = fedicfg.web.domainForBucket bucket;
+        name = config.fediversity.garage.web.domainForBucket bucket;
         inherit value;
       }) (filterAttrs (_: { website, ... }: website) config.fediversity.garage.ensureBuckets);
 
@@ -166,7 +170,7 @@ in
 
         # Give Garage time to start up by waiting until somethings speaks HTTP
         # behind Garage's API URL.
-        until ${pkgs.curl}/bin/curl -sio /dev/null ${fedicfg.api.url}; do sleep 1; done
+        until ${pkgs.curl}/bin/curl -sio /dev/null ${config.fediversity.garage.api.url}; do sleep 1; done
 
         # XXX: this is very sensitive to being a single instance
         # (doing the bare minimum to get garage up and running)
diff --git a/services/fediversity/garage/options.nix b/services/fediversity/garage/options.nix
index 56fec59..ad3d555 100644
--- a/services/fediversity/garage/options.nix
+++ b/services/fediversity/garage/options.nix
@@ -1,4 +1,4 @@
-{ lib, ... }:
+{ config, lib, ... }:
 
 let
   inherit (lib) types mkOption mkEnableOption;
@@ -75,5 +75,46 @@ in
       );
       default = { };
     };
+
+    api = {
+      domain = mkOption {
+        type = types.str;
+        default = "s3.garage.${config.fediversity.domain}";
+      };
+      port = mkOption {
+        type = types.int;
+        default = 3900;
+      };
+      url = mkOption {
+        type = types.str;
+        default = "http://${config.fediversity.garage.api.domain}:${toString config.fediversity.garage.api.port}";
+      };
+    };
+
+    rpc = {
+      port = mkOption {
+        type = types.int;
+        default = 3901;
+      };
+    };
+
+    web = {
+      rootDomain = mkOption {
+        type = types.str;
+        default = "web.garage.${config.fediversity.domain}";
+      };
+      internalPort = mkOption {
+        type = types.int;
+        default = 3902;
+      };
+      domainForBucket = mkOption {
+        type = types.functionTo types.str;
+        default = bucket: "${bucket}.${config.fediversity.garage.web.rootDomain}";
+      };
+      urlForBucket = mkOption {
+        type = types.functionTo types.str;
+        default = bucket: "http://${config.fediversity.garage.web.domainForBucket bucket}";
+      };
+    };
   };
 }
diff --git a/services/fediversity/mastodon.nix b/services/fediversity/mastodon.nix
index 7486559..1d0e8c6 100644
--- a/services/fediversity/mastodon.nix
+++ b/services/fediversity/mastodon.nix
@@ -38,7 +38,7 @@ lib.mkIf (config.fediversity.enable && config.fediversity.mastodon.enable) {
     extraConfig = rec {
       S3_ENABLED = "true";
       # TODO: this shouldn't be hard-coded, it should come from the garage configuration
-      S3_ENDPOINT = config.fediversity.internal.garage.api.url;
+      S3_ENDPOINT = config.fediversity.garage.api.url;
       S3_REGION = "garage";
       S3_BUCKET = "mastodon";
       # use <S3_BUCKET>.<S3_ENDPOINT>
@@ -46,7 +46,7 @@ lib.mkIf (config.fediversity.enable && config.fediversity.mastodon.enable) {
       AWS_ACCESS_KEY_ID = snakeoil_key.id;
       AWS_SECRET_ACCESS_KEY = snakeoil_key.secret;
       S3_PROTOCOL = "http";
-      S3_ALIAS_HOST = "${S3_BUCKET}.${config.fediversity.internal.garage.web.rootDomain}";
+      S3_ALIAS_HOST = "${S3_BUCKET}.${config.fediversity.garage.web.rootDomain}";
       # SEE: the last section in https://docs.joinmastodon.org/admin/optional/object-storage/
       # TODO: can we set up ACLs with garage?
       S3_PERMISSION = "";
diff --git a/services/fediversity/peertube.nix b/services/fediversity/peertube.nix
index ae973c1..6105a7d 100644
--- a/services/fediversity/peertube.nix
+++ b/services/fediversity/peertube.nix
@@ -72,7 +72,7 @@ lib.mkIf (config.fediversity.enable && config.fediversity.peertube.enable) {
     settings = {
       object_storage = {
         enabled = true;
-        endpoint = config.fediversity.internal.garage.api.url;
+        endpoint = config.fediversity.garage.api.url;
         region = "garage";
         upload_acl.public = null; # Garage does not support ACL
         upload_acl.private = null; # Garage does not support ACL
@@ -84,17 +84,17 @@ lib.mkIf (config.fediversity.enable && config.fediversity.peertube.enable) {
         web_videos = rec {
           bucket_name = "peertube-videos";
           prefix = "";
-          base_url = config.fediversity.internal.garage.web.urlForBucket bucket_name;
+          base_url = config.fediversity.garage.web.urlForBucket bucket_name;
         };
         videos = rec {
           bucket_name = "peertube-videos";
           prefix = "";
-          base_url = config.fediversity.internal.garage.web.urlForBucket bucket_name;
+          base_url = config.fediversity.garage.web.urlForBucket bucket_name;
         };
         streaming_playlists = rec {
           bucket_name = "peertube-playlists";
           prefix = "";
-          base_url = config.fediversity.internal.garage.web.urlForBucket bucket_name;
+          base_url = config.fediversity.garage.web.urlForBucket bucket_name;
         };
       };
     };
diff --git a/services/fediversity/pixelfed.nix b/services/fediversity/pixelfed.nix
index 6c2a3eb..0b1be36 100644
--- a/services/fediversity/pixelfed.nix
+++ b/services/fediversity/pixelfed.nix
@@ -57,7 +57,7 @@ lib.mkIf (config.fediversity.enable && config.fediversity.pixelfed.enable) {
     nginx = {
       forceSSL = true;
       enableACME = true;
-      # locations."/public/".proxyPass = "${config.fediversity.internal.garage.web.urlForBucket "pixelfed"}/public/";
+      # locations."/public/".proxyPass = "${config.fediversity.garage.web.urlForBucket "pixelfed"}/public/";
     };
   };
 
@@ -73,9 +73,9 @@ lib.mkIf (config.fediversity.enable && config.fediversity.pixelfed.enable) {
     AWS_ACCESS_KEY_ID = snakeoil_key.id;
     AWS_SECRET_ACCESS_KEY = snakeoil_key.secret;
     AWS_DEFAULT_REGION = "garage";
-    AWS_URL = config.fediversity.internal.garage.web.urlForBucket "pixelfed";
+    AWS_URL = config.fediversity.garage.web.urlForBucket "pixelfed";
     AWS_BUCKET = "pixelfed";
-    AWS_ENDPOINT = config.fediversity.internal.garage.api.url;
+    AWS_ENDPOINT = config.fediversity.garage.api.url;
     AWS_USE_PATH_STYLE_ENDPOINT = false;
   };
 
diff --git a/services/tests/mastodon.nix b/services/tests/mastodon.nix
index 80ede0c..61629aa 100644
--- a/services/tests/mastodon.nix
+++ b/services/tests/mastodon.nix
@@ -118,7 +118,7 @@ pkgs.nixosTest {
         server.succeed("toot post --media ${testImage}")
 
       with subtest("Access garage"):
-        server.succeed("mc alias set garage ${nodes.server.fediversity.internal.garage.api.url} --api s3v4 --path off $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY")
+        server.succeed("mc alias set garage ${nodes.server.fediversity.garage.api.url} --api s3v4 --path off $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY")
         server.succeed("mc ls garage/mastodon")
 
       with subtest("Access image in garage"):
@@ -144,7 +144,7 @@ pkgs.nixosTest {
           raise Exception("mastodon did not send a content security policy header")
         csp = csp_match.group(1)
         # the connect-src content security policy should include the garage server
-        ## TODO: use `nodes.server.fediversity.internal.garage.api.url` same as above, but beware of escaping the regex. Be careful with port 80 though.
+        ## TODO: use `nodes.server.fediversity.garage.api.url` same as above, but beware of escaping the regex. Be careful with port 80 though.
         garage_csp = re.match(".*; img-src[^;]*web\.garage\.localhost.*", csp)
         if garage_csp is None:
           raise Exception("Mastodon's Content-Security-Policy does not include Garage.")
diff --git a/services/tests/peertube.nix b/services/tests/peertube.nix
index c10e01a..23b002f 100644
--- a/services/tests/peertube.nix
+++ b/services/tests/peertube.nix
@@ -220,7 +220,7 @@ pkgs.nixosTest {
         server.succeed(f"post-video-in-browser {root_password}")
 
       with subtest("Find video in garage"):
-        server.succeed("mc alias set garage ${nodes.server.fediversity.internal.garage.api.url} --api s3v4 --path off $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY")
+        server.succeed("mc alias set garage ${nodes.server.fediversity.garage.api.url} --api s3v4 --path off $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY")
         video = server.succeed("mc find garage --regex '\\.mp4'").rstrip()
         if video == "":
           raise Exception("Could not find any .mp4 video stored in Garage")
diff --git a/services/tests/pixelfed-garage.nix b/services/tests/pixelfed-garage.nix
index 346ffe4..fb658a3 100644
--- a/services/tests/pixelfed-garage.nix
+++ b/services/tests/pixelfed-garage.nix
@@ -201,7 +201,7 @@ pkgs.nixosTest {
           raise Exception("cannot detect the uploaded image on pixelfed page.")
 
       with subtest("access garage"):
-        server.succeed("mc alias set garage ${nodes.server.fediversity.internal.garage.api.url} --api s3v4 --path off $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY")
+        server.succeed("mc alias set garage ${nodes.server.fediversity.garage.api.url} --api s3v4 --path off $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY")
         server.succeed("mc ls garage/pixelfed")
 
       with subtest("access image in garage"):
@@ -217,7 +217,7 @@ pkgs.nixosTest {
 
       with subtest("Check that image comes from garage"):
         src = server.succeed("su - selenium -c 'selenium-script-get-src ${email} ${password}'")
-        if not src.startswith("${nodes.server.fediversity.internal.garage.web.urlForBucket "pixelfed"}"):
+        if not src.startswith("${nodes.server.fediversity.garage.web.urlForBucket "pixelfed"}"):
           raise Exception("image does not come from garage")
     '';
 }
diff --git a/services/vm/garage-vm.nix b/services/vm/garage-vm.nix
index b115b51..174d23c 100644
--- a/services/vm/garage-vm.nix
+++ b/services/vm/garage-vm.nix
@@ -20,7 +20,7 @@ in
       };
     in
     mapAttrs' (bucket: _: {
-      name = config.fediversity.internal.garage.web.domainForBucket bucket;
+      name = config.fediversity.garage.web.domainForBucket bucket;
       inherit value;
     }) (filterAttrs (_: { website, ... }: website) config.fediversity.garage.ensureBuckets);
 
@@ -28,13 +28,13 @@ in
   virtualisation.forwardPorts = [
     {
       from = "host";
-      host.port = config.fediversity.internal.garage.rpc.port;
-      guest.port = config.fediversity.internal.garage.rpc.port;
+      host.port = config.fediversity.garage.rpc.port;
+      guest.port = config.fediversity.garage.rpc.port;
     }
     {
       from = "host";
-      host.port = config.fediversity.internal.garage.web.internalPort;
-      guest.port = config.fediversity.internal.garage.web.internalPort;
+      host.port = config.fediversity.garage.web.internalPort;
+      guest.port = config.fediversity.garage.web.internalPort;
     }
   ];
 }

From ca6642c8d466e27e5300929ab09f5d259e3a5f88 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nicolas=20=E2=80=9CNiols=E2=80=9D=20Jeannerod?=
 <nicolas.jeannerod@moduscreate.com>
Date: Fri, 14 Feb 2025 18:37:48 +0100
Subject: [PATCH 07/16] Same treatment for Mastodon

---
 services/fediversity/default.nix          |  7 +-
 services/fediversity/mastodon.nix         | 87 ----------------------
 services/fediversity/mastodon/default.nix | 91 +++++++++++++++++++++++
 services/fediversity/mastodon/options.nix | 18 +++++
 4 files changed, 110 insertions(+), 93 deletions(-)
 delete mode 100644 services/fediversity/mastodon.nix
 create mode 100644 services/fediversity/mastodon/default.nix
 create mode 100644 services/fediversity/mastodon/options.nix

diff --git a/services/fediversity/default.nix b/services/fediversity/default.nix
index d643c8d..16b0c26 100644
--- a/services/fediversity/default.nix
+++ b/services/fediversity/default.nix
@@ -8,7 +8,7 @@ in
 {
   imports = [
     ./garage
-    ./mastodon.nix
+    ./mastodon
     ./pixelfed.nix
     ./peertube.nix
   ];
@@ -27,7 +27,6 @@ in
         '';
       };
 
-      mastodon.enable = mkEnableOption "default Fediversity Mastodon configuration";
       pixelfed.enable = mkEnableOption "default Fediversity Pixelfed configuration";
       peertube.enable = mkEnableOption "default Fediversity PeerTube configuration";
 
@@ -62,10 +61,6 @@ in
               type = types.str;
               default = "pixelfed.${config.fediversity.domain}";
             };
-            mastodon.domain = mkOption {
-              type = types.str;
-              default = "mastodon.${config.fediversity.domain}";
-            };
             peertube.domain = mkOption {
               type = types.str;
               default = "peertube.${config.fediversity.domain}";
diff --git a/services/fediversity/mastodon.nix b/services/fediversity/mastodon.nix
deleted file mode 100644
index 1d0e8c6..0000000
--- a/services/fediversity/mastodon.nix
+++ /dev/null
@@ -1,87 +0,0 @@
-let
-  snakeoil_key = {
-    id = "GK3515373e4c851ebaad366558";
-    secret = "7d37d093435a41f2aab8f13c19ba067d9776c90215f56614adad6ece597dbb34";
-  };
-in
-
-{ config, lib, ... }:
-
-lib.mkIf (config.fediversity.enable && config.fediversity.mastodon.enable) {
-  #### garage setup
-  fediversity.garage = {
-    ensureBuckets = {
-      mastodon = {
-        website = true;
-        corsRules = {
-          enable = true;
-          allowedHeaders = [ "*" ];
-          allowedMethods = [ "GET" ];
-          allowedOrigins = [ "*" ];
-        };
-      };
-    };
-    ensureKeys = {
-      mastodon = {
-        inherit (snakeoil_key) id secret;
-        ensureAccess = {
-          mastodon = {
-            read = true;
-            write = true;
-            owner = true;
-          };
-        };
-      };
-    };
-  };
-  services.mastodon = {
-    extraConfig = rec {
-      S3_ENABLED = "true";
-      # TODO: this shouldn't be hard-coded, it should come from the garage configuration
-      S3_ENDPOINT = config.fediversity.garage.api.url;
-      S3_REGION = "garage";
-      S3_BUCKET = "mastodon";
-      # use <S3_BUCKET>.<S3_ENDPOINT>
-      S3_OVERRIDE_PATH_STLE = "true";
-      AWS_ACCESS_KEY_ID = snakeoil_key.id;
-      AWS_SECRET_ACCESS_KEY = snakeoil_key.secret;
-      S3_PROTOCOL = "http";
-      S3_ALIAS_HOST = "${S3_BUCKET}.${config.fediversity.garage.web.rootDomain}";
-      # SEE: the last section in https://docs.joinmastodon.org/admin/optional/object-storage/
-      # TODO: can we set up ACLs with garage?
-      S3_PERMISSION = "";
-    };
-  };
-
-  #### mastodon setup
-
-  # open up access to the mastodon web interface. 80 is necessary if only for ACME
-  networking.firewall.allowedTCPPorts = [
-    80
-    443
-  ];
-
-  services.mastodon = {
-    enable = true;
-
-    localDomain = config.fediversity.internal.mastodon.domain;
-    configureNginx = true;
-
-    # from the documentation: recommended is the amount of your CPU cores minus
-    # one. but it also must be a positive integer
-    streamingProcesses = lib.max 1 (config.fediversity.temp.cores - 1);
-
-    # TODO: configure a mailserver so this works
-    smtp = {
-      fromAddress = "noreply@${config.fediversity.internal.mastodon.domain}";
-      createLocally = false;
-    };
-  };
-
-  security.acme = {
-    acceptTerms = true;
-    preliminarySelfsigned = true;
-    # TODO: configure a mailserver so we can set up acme
-    # defaults.email = "test@example.com";
-  };
-}
diff --git a/services/fediversity/mastodon/default.nix b/services/fediversity/mastodon/default.nix
new file mode 100644
index 0000000..083cf91
--- /dev/null
+++ b/services/fediversity/mastodon/default.nix
@@ -0,0 +1,91 @@
+let
+  snakeoil_key = {
+    id = "GK3515373e4c851ebaad366558";
+    secret = "7d37d093435a41f2aab8f13c19ba067d9776c90215f56614adad6ece597dbb34";
+  };
+in
+
+{ config, lib, ... }:
+
+{
+  imports = [ ./options.nix ];
+
+  config = lib.mkIf (config.fediversity.enable && config.fediversity.mastodon.enable) {
+    #### garage setup
+    fediversity.garage = {
+      ensureBuckets = {
+        mastodon = {
+          website = true;
+          corsRules = {
+            enable = true;
+            allowedHeaders = [ "*" ];
+            allowedMethods = [ "GET" ];
+            allowedOrigins = [ "*" ];
+          };
+        };
+      };
+      ensureKeys = {
+        mastodon = {
+          inherit (snakeoil_key) id secret;
+          ensureAccess = {
+            mastodon = {
+              read = true;
+              write = true;
+              owner = true;
+            };
+          };
+        };
+      };
+    };
+    services.mastodon = {
+      extraConfig = rec {
+        S3_ENABLED = "true";
+        # TODO: this shouldn't be hard-coded, it should come from the garage configuration
+        S3_ENDPOINT = config.fediversity.garage.api.url;
+        S3_REGION = "garage";
+        S3_BUCKET = "mastodon";
+        # use <S3_BUCKET>.<S3_ENDPOINT>
+        S3_OVERRIDE_PATH_STLE = "true";
+        AWS_ACCESS_KEY_ID = snakeoil_key.id;
+        AWS_SECRET_ACCESS_KEY = snakeoil_key.secret;
+        S3_PROTOCOL = "http";
+        S3_ALIAS_HOST = "${S3_BUCKET}.${config.fediversity.garage.web.rootDomain}";
+        # SEE: the last section in https://docs.joinmastodon.org/admin/optional/object-storage/
+        # TODO: can we set up ACLs with garage?
+        S3_PERMISSION = "";
+      };
+    };
+
+    #### mastodon setup
+
+    # open up access to the mastodon web interface. 80 is necessary if only for ACME
+    networking.firewall.allowedTCPPorts = [
+      80
+      443
+    ];
+
+    services.mastodon = {
+      enable = true;
+
+      localDomain = config.fediversity.mastodon.domain;
+      configureNginx = true;
+
+      # from the documentation: recommended is the amount of your CPU cores minus
+      # one. but it also must be a positive integer
+      streamingProcesses = lib.max 1 (config.fediversity.temp.cores - 1);
+
+      # TODO: configure a mailserver so this works
+      smtp = {
+        fromAddress = "noreply@${config.fediversity.mastodon.domain}";
+        createLocally = false;
+      };
+    };
+
+    security.acme = {
+      acceptTerms = true;
+      preliminarySelfsigned = true;
+      # TODO: configure a mailserver so we can set up acme
+      # defaults.email = "test@example.com";
+    };
+  };
+}
diff --git a/services/fediversity/mastodon/options.nix b/services/fediversity/mastodon/options.nix
new file mode 100644
index 0000000..882b96e
--- /dev/null
+++ b/services/fediversity/mastodon/options.nix
@@ -0,0 +1,18 @@
+{ config, lib, ... }:
+
+let
+  inherit (lib) mkOption mkEnableOption;
+  inherit (lib.types) types;
+
+in
+{
+  options.fediversity.mastodon = {
+    enable = mkEnableOption "Enable a Mastodon server on the machine";
+
+    domain = mkOption {
+      type = types.str;
+      description = "Internal option — change at your own risk";
+      default = "mastodon.${config.fediversity.domain}";
+    };
+  };
+}

From a1cfcf1d71685ec85b1103ba7df0d54624c7784f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nicolas=20=E2=80=9CNiols=E2=80=9D=20Jeannerod?=
 <nicolas.jeannerod@moduscreate.com>
Date: Fri, 14 Feb 2025 18:44:35 +0100
Subject: [PATCH 08/16] Same treatment for Peertube

---
 deployment/flake-part.nix                 |  11 +-
 services/fediversity/default.nix          |  12 +--
 services/fediversity/peertube.nix         | 119 ---------------------
 services/fediversity/peertube/default.nix | 123 ++++++++++++++++++++++
 services/fediversity/peertube/options.nix |  28 +++++
 services/vm/peertube-vm.nix               |  11 +-
 6 files changed, 164 insertions(+), 140 deletions(-)
 delete mode 100644 services/fediversity/peertube.nix
 create mode 100644 services/fediversity/peertube/default.nix
 create mode 100644 services/fediversity/peertube/options.nix

diff --git a/deployment/flake-part.nix b/deployment/flake-part.nix
index 7905286..a0e42ae 100644
--- a/deployment/flake-part.nix
+++ b/deployment/flake-part.nix
@@ -100,11 +100,12 @@ in
             fediversity = {
               enable = true;
               domain = "fedi103.abundos.eu";
-              peertube.enable = true;
-
-              temp.peertubeSecretsFile = pkgs.writeText "secret" ''
-                574e093907d1157ac0f8e760a6deb1035402003af5763135bae9cbd6abe32b24
-              '';
+              peertube = {
+                enable = true;
+                secretsFile = pkgs.writeText "secret" ''
+                  574e093907d1157ac0f8e760a6deb1035402003af5763135bae9cbd6abe32b24
+                '';
+              };
             };
           }
         );
diff --git a/services/fediversity/default.nix b/services/fediversity/default.nix
index 16b0c26..a8c9f69 100644
--- a/services/fediversity/default.nix
+++ b/services/fediversity/default.nix
@@ -10,7 +10,7 @@ in
     ./garage
     ./mastodon
     ./pixelfed.nix
-    ./peertube.nix
+    ./peertube
   ];
 
   options = {
@@ -28,7 +28,6 @@ in
       };
 
       pixelfed.enable = mkEnableOption "default Fediversity Pixelfed configuration";
-      peertube.enable = mkEnableOption "default Fediversity PeerTube configuration";
 
       temp = mkOption {
         description = "options that are only used while developing; should be removed eventually";
@@ -39,11 +38,6 @@ in
               description = "number of cores; should be obtained from NixOps4";
               type = types.int;
             };
-
-            peertubeSecretsFile = mkOption {
-              description = "should it be provided by NixOps4? or maybe we should just ask for a main secret from which to derive all the others?";
-              type = types.path;
-            };
           };
         };
       };
@@ -61,10 +55,6 @@ in
               type = types.str;
               default = "pixelfed.${config.fediversity.domain}";
             };
-            peertube.domain = mkOption {
-              type = types.str;
-              default = "peertube.${config.fediversity.domain}";
-            };
           };
         };
       };
diff --git a/services/fediversity/peertube.nix b/services/fediversity/peertube.nix
deleted file mode 100644
index 6105a7d..0000000
--- a/services/fediversity/peertube.nix
+++ /dev/null
@@ -1,119 +0,0 @@
-let
-  snakeoil_key = {
-    id = "GK1f9feea9960f6f95ff404c9b";
-    secret = "7295c4201966a02c2c3d25b5cea4a5ff782966a2415e3a196f91924631191395";
-  };
-in
-
-{ config, lib, ... }:
-
-lib.mkIf (config.fediversity.enable && config.fediversity.peertube.enable) {
-  networking.firewall.allowedTCPPorts = [
-    80
-    443
-
-    ## For Live streaming and Live streaming when RTMPS is enabled.
-    1935
-    1936
-  ];
-
-  fediversity.garage = {
-    ensureBuckets = {
-      peertube-videos = {
-        website = true;
-        # TODO: these are too broad, after getting everything works narrow it down to the domain we actually want
-        corsRules = {
-          enable = true;
-          allowedHeaders = [ "*" ];
-          allowedMethods = [ "GET" ];
-          allowedOrigins = [ "*" ];
-        };
-      };
-      # TODO: these are too broad, after getting everything works narrow it down to the domain we actually want
-      peertube-playlists = {
-        website = true;
-        corsRules = {
-          enable = true;
-          allowedHeaders = [ "*" ];
-          allowedMethods = [ "GET" ];
-          allowedOrigins = [ "*" ];
-        };
-      };
-    };
-    ensureKeys = {
-      peertube = {
-        inherit (snakeoil_key) id secret;
-        ensureAccess = {
-          peertube-videos = {
-            read = true;
-            write = true;
-            owner = true;
-          };
-          peertube-playlists = {
-            read = true;
-            write = true;
-            owner = true;
-          };
-        };
-      };
-    };
-  };
-
-  services.peertube = {
-    enable = true;
-    localDomain = config.fediversity.internal.peertube.domain;
-
-    # TODO: in most of nixpkgs, these are true by default. upstream that unless there's a good reason not to.
-    redis.createLocally = true;
-    database.createLocally = true;
-
-    secrets.secretsFile = config.fediversity.temp.peertubeSecretsFile;
-
-    settings = {
-      object_storage = {
-        enabled = true;
-        endpoint = config.fediversity.garage.api.url;
-        region = "garage";
-        upload_acl.public = null; # Garage does not support ACL
-        upload_acl.private = null; # Garage does not support ACL
-
-        # not supported by garage
-        # SEE: https://garagehq.deuxfleurs.fr/documentation/connect/apps/#peertube
-        proxy.proxyify_private_files = false;
-
-        web_videos = rec {
-          bucket_name = "peertube-videos";
-          prefix = "";
-          base_url = config.fediversity.garage.web.urlForBucket bucket_name;
-        };
-        videos = rec {
-          bucket_name = "peertube-videos";
-          prefix = "";
-          base_url = config.fediversity.garage.web.urlForBucket bucket_name;
-        };
-        streaming_playlists = rec {
-          bucket_name = "peertube-playlists";
-          prefix = "";
-          base_url = config.fediversity.garage.web.urlForBucket bucket_name;
-        };
-      };
-    };
-    serviceEnvironmentFile = "/etc/peertube-env";
-  };
-  environment.etc.peertube-env.text = ''
-    AWS_ACCESS_KEY_ID=${snakeoil_key.id}
-    AWS_SECRET_ACCESS_KEY=${snakeoil_key.secret}
-  '';
-
-  ## Proxying through Nginx
-
-  services.peertube = {
-    configureNginx = true;
-    listenWeb = 443;
-    enableWebHttps = true;
-  };
-  services.nginx.virtualHosts.${config.services.peertube.localDomain} = {
-    forceSSL = true;
-    enableACME = true;
-  };
-}
diff --git a/services/fediversity/peertube/default.nix b/services/fediversity/peertube/default.nix
new file mode 100644
index 0000000..e649c56
--- /dev/null
+++ b/services/fediversity/peertube/default.nix
@@ -0,0 +1,123 @@
+let
+  snakeoil_key = {
+    id = "GK1f9feea9960f6f95ff404c9b";
+    secret = "7295c4201966a02c2c3d25b5cea4a5ff782966a2415e3a196f91924631191395";
+  };
+in
+
+{ config, lib, ... }:
+
+{
+  imports = [ ./options.nix ];
+
+  config = lib.mkIf (config.fediversity.enable && config.fediversity.peertube.enable) {
+    networking.firewall.allowedTCPPorts = [
+      80
+      443
+
+      ## For Live streaming and Live streaming when RTMPS is enabled.
+      1935
+      1936
+    ];
+
+    fediversity.garage = {
+      ensureBuckets = {
+        peertube-videos = {
+          website = true;
+          # TODO: these are too broad, after getting everything works narrow it down to the domain we actually want
+          corsRules = {
+            enable = true;
+            allowedHeaders = [ "*" ];
+            allowedMethods = [ "GET" ];
+            allowedOrigins = [ "*" ];
+          };
+        };
+        # TODO: these are too broad, after getting everything works narrow it down to the domain we actually want
+        peertube-playlists = {
+          website = true;
+          corsRules = {
+            enable = true;
+            allowedHeaders = [ "*" ];
+            allowedMethods = [ "GET" ];
+            allowedOrigins = [ "*" ];
+          };
+        };
+      };
+      ensureKeys = {
+        peertube = {
+          inherit (snakeoil_key) id secret;
+          ensureAccess = {
+            peertube-videos = {
+              read = true;
+              write = true;
+              owner = true;
+            };
+            peertube-playlists = {
+              read = true;
+              write = true;
+              owner = true;
+            };
+          };
+        };
+      };
+    };
+
+    services.peertube = {
+      enable = true;
+      localDomain = config.fediversity.peertube.domain;
+
+      # TODO: in most of nixpkgs, these are true by default. upstream that unless there's a good reason not to.
+      redis.createLocally = true;
+      database.createLocally = true;
+
+      secrets.secretsFile = config.fediversity.peertube.secretsFile;
+
+      settings = {
+        object_storage = {
+          enabled = true;
+          endpoint = config.fediversity.garage.api.url;
+          region = "garage";
+          upload_acl.public = null; # Garage does not support ACL
+          upload_acl.private = null; # Garage does not support ACL
+
+          # not supported by garage
+          # SEE: https://garagehq.deuxfleurs.fr/documentation/connect/apps/#peertube
+          proxy.proxyify_private_files = false;
+
+          web_videos = rec {
+            bucket_name = "peertube-videos";
+            prefix = "";
+            base_url = config.fediversity.garage.web.urlForBucket bucket_name;
+          };
+          videos = rec {
+            bucket_name = "peertube-videos";
+            prefix = "";
+            base_url = config.fediversity.garage.web.urlForBucket bucket_name;
+          };
+          streaming_playlists = rec {
+            bucket_name = "peertube-playlists";
+            prefix = "";
+            base_url = config.fediversity.garage.web.urlForBucket bucket_name;
+          };
+        };
+      };
+      serviceEnvironmentFile = "/etc/peertube-env";
+    };
+    environment.etc.peertube-env.text = ''
+      AWS_ACCESS_KEY_ID=${snakeoil_key.id}
+      AWS_SECRET_ACCESS_KEY=${snakeoil_key.secret}
+    '';
+
+    ## Proxying through Nginx
+
+    services.peertube = {
+      configureNginx = true;
+      listenWeb = 443;
+      enableWebHttps = true;
+    };
+    services.nginx.virtualHosts.${config.services.peertube.localDomain} = {
+      forceSSL = true;
+      enableACME = true;
+    };
+  };
+}
diff --git a/services/fediversity/peertube/options.nix b/services/fediversity/peertube/options.nix
new file mode 100644
index 0000000..feedcad
--- /dev/null
+++ b/services/fediversity/peertube/options.nix
@@ -0,0 +1,28 @@
+{ config, lib, ... }:
+
+let
+  inherit (lib) mkOption mkEnableOption;
+  inherit (lib.types) types;
+
+in
+{
+  options.fediversity.peertube = {
+    enable = mkEnableOption "Enable a PeerTube server on the machine";
+
+    domain = mkOption {
+      type = types.str;
+      description = "Internal option — change at your own risk";
+      default = "peertube.${config.fediversity.domain}";
+    };
+
+    secretsFile = mkOption {
+      type = types.path;
+      description = ''
+        Internal option — change at your own risk
+
+        FIXME: should it be provided by NixOps4?
+        or maybe we should just ask for a main secret from which to derive all the others?
+      '';
+    };
+  };
+}
diff --git a/services/vm/peertube-vm.nix b/services/vm/peertube-vm.nix
index 0e2c992..758d64b 100644
--- a/services/vm/peertube-vm.nix
+++ b/services/vm/peertube-vm.nix
@@ -10,11 +10,12 @@
   fediversity = {
     enable = true;
     domain = "localhost";
-    peertube.enable = true;
-
-    temp.peertubeSecretsFile = pkgs.writeText "secret" ''
-      574e093907d1157ac0f8e760a6deb1035402003af5763135bae9cbd6abe32b24
-    '';
+    peertube = {
+      enable = true;
+      secretsFile = pkgs.writeText "secret" ''
+        574e093907d1157ac0f8e760a6deb1035402003af5763135bae9cbd6abe32b24
+      '';
+    };
   };
 
   services.peertube = {

From 1965e83e5d2b7ee8362ad81ddeff07ff4a7527cb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nicolas=20=E2=80=9CNiols=E2=80=9D=20Jeannerod?=
 <nicolas.jeannerod@moduscreate.com>
Date: Fri, 14 Feb 2025 18:51:38 +0100
Subject: [PATCH 09/16] Same treatment for Pixelfed

---
 services/fediversity/default.nix              | 21 +---
 services/fediversity/pixelfed.nix             | 92 ------------------
 services/fediversity/pixelfed/default.nix     | 96 +++++++++++++++++++
 .../group-permissions.patch}                  |  0
 services/fediversity/pixelfed/options.nix     | 18 ++++
 5 files changed, 115 insertions(+), 112 deletions(-)
 delete mode 100644 services/fediversity/pixelfed.nix
 create mode 100644 services/fediversity/pixelfed/default.nix
 rename services/fediversity/{pixelfed-group-permissions.patch => pixelfed/group-permissions.patch} (100%)
 create mode 100644 services/fediversity/pixelfed/options.nix

diff --git a/services/fediversity/default.nix b/services/fediversity/default.nix
index a8c9f69..5147c8f 100644
--- a/services/fediversity/default.nix
+++ b/services/fediversity/default.nix
@@ -9,7 +9,7 @@ in
   imports = [
     ./garage
     ./mastodon
-    ./pixelfed.nix
+    ./pixelfed
     ./peertube
   ];
 
@@ -27,8 +27,6 @@ in
         '';
       };
 
-      pixelfed.enable = mkEnableOption "default Fediversity Pixelfed configuration";
-
       temp = mkOption {
         description = "options that are only used while developing; should be removed eventually";
         default = { };
@@ -41,23 +39,6 @@ in
           };
         };
       };
-
-      internal = mkOption {
-        description = "options that are only meant to be used internally; change at your own risk";
-        default = { };
-        type = types.submodule {
-          options = {
-            ## REVIEW: Do we want to recreate options under
-            ## `fediversity.internal` or would we rather use the options from
-            ## the respective services? See Taeer's comment:
-            ## https://git.fediversity.eu/taeer/simple-nixos-fediverse/pulls/22#issuecomment-124
-            pixelfed.domain = mkOption {
-              type = types.str;
-              default = "pixelfed.${config.fediversity.domain}";
-            };
-          };
-        };
-      };
     };
   };
 
diff --git a/services/fediversity/pixelfed.nix b/services/fediversity/pixelfed.nix
deleted file mode 100644
index 0b1be36..0000000
--- a/services/fediversity/pixelfed.nix
+++ /dev/null
@@ -1,92 +0,0 @@
-let
-  snakeoil_key = {
-    id = "GKb5615457d44214411e673b7b";
-    secret = "5be6799a88ca9b9d813d1a806b64f15efa49482dbe15339ddfaf7f19cf434987";
-  };
-in
-
-{
-  config,
-  lib,
-  pkgs,
-  ...
-}:
-
-lib.mkIf (config.fediversity.enable && config.fediversity.pixelfed.enable) {
-  fediversity.garage = {
-    ensureBuckets = {
-      pixelfed = {
-        website = true;
-        # TODO: these are too broad, after getting everything works narrow it down to the domain we actually want
-        corsRules = {
-          enable = true;
-          allowedHeaders = [ "*" ];
-          allowedMethods = [ "GET" ];
-          allowedOrigins = [ "*" ];
-        };
-      };
-    };
-    ensureKeys = {
-      pixelfed = {
-        inherit (snakeoil_key) id secret;
-        ensureAccess = {
-          pixelfed = {
-            read = true;
-            write = true;
-            owner = true;
-          };
-        };
-      };
-    };
-  };
-
-  services.pixelfed = {
-    enable = true;
-    domain = config.fediversity.internal.pixelfed.domain;
-
-    # TODO: secrets management!!!
-    secretFile = pkgs.writeText "secrets.env" ''
-      APP_KEY=adKK9EcY8Hcj3PLU7rzG9rJ6KKTOtYfA
-    '';
-
-    ## Taeer feels like this way of configuring Nginx is odd; there should
-    ## instead be a `services.pixefed.nginx.enable` option and the actual Nginx
-    ## configuration should be in `services.nginx`. See eg. `pretix`.
-    ##
-    ## TODO: If that indeed makes sense, upstream.
-    nginx = {
-      forceSSL = true;
-      enableACME = true;
-      # locations."/public/".proxyPass = "${config.fediversity.garage.web.urlForBucket "pixelfed"}/public/";
-    };
-  };
-
-  services.pixelfed.settings = {
-    ## NOTE: This depends on the targets, eg. universities might want control
-    ## over who has an account. We probably want a universal
-    ## `fediversity.openRegistration` option.
-    OPEN_REGISTRATION = true;
-
-    # DANGEROUSLY_SET_FILESYSTEM_DRIVER = "s3";
-    FILESYSTEM_CLOUD = "s3";
-    PF_ENABLE_CLOUD = true;
-    AWS_ACCESS_KEY_ID = snakeoil_key.id;
-    AWS_SECRET_ACCESS_KEY = snakeoil_key.secret;
-    AWS_DEFAULT_REGION = "garage";
-    AWS_URL = config.fediversity.garage.web.urlForBucket "pixelfed";
-    AWS_BUCKET = "pixelfed";
-    AWS_ENDPOINT = config.fediversity.garage.api.url;
-    AWS_USE_PATH_STYLE_ENDPOINT = false;
-  };
-
-  ## Only ever run `pixelfed-data-setup` after `ensure-garage` has done its job.
-  ## Otherwise, everything crashed dramatically.
-  systemd.services.pixelfed-data-setup = {
-    after = [ "ensure-garage.service" ];
-  };
-
-  networking.firewall.allowedTCPPorts = [
-    80
-    443
-  ];
-}
diff --git a/services/fediversity/pixelfed/default.nix b/services/fediversity/pixelfed/default.nix
new file mode 100644
index 0000000..4ce2887
--- /dev/null
+++ b/services/fediversity/pixelfed/default.nix
@@ -0,0 +1,96 @@
+let
+  snakeoil_key = {
+    id = "GKb5615457d44214411e673b7b";
+    secret = "5be6799a88ca9b9d813d1a806b64f15efa49482dbe15339ddfaf7f19cf434987";
+  };
+in
+
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+{
+  imports = [ ./options.nix ];
+
+  config = lib.mkIf (config.fediversity.enable && config.fediversity.pixelfed.enable) {
+    fediversity.garage = {
+      ensureBuckets = {
+        pixelfed = {
+          website = true;
+          # TODO: these are too broad, after getting everything works narrow it down to the domain we actually want
+          corsRules = {
+            enable = true;
+            allowedHeaders = [ "*" ];
+            allowedMethods = [ "GET" ];
+            allowedOrigins = [ "*" ];
+          };
+        };
+      };
+      ensureKeys = {
+        pixelfed = {
+          inherit (snakeoil_key) id secret;
+          ensureAccess = {
+            pixelfed = {
+              read = true;
+              write = true;
+              owner = true;
+            };
+          };
+        };
+      };
+    };
+
+    services.pixelfed = {
+      enable = true;
+      domain = config.fediversity.pixelfed.domain;
+
+      # TODO: secrets management!!!
+      secretFile = pkgs.writeText "secrets.env" ''
+        APP_KEY=adKK9EcY8Hcj3PLU7rzG9rJ6KKTOtYfA
+      '';
+
+      ## Taeer feels like this way of configuring Nginx is odd; there should
+      ## instead be a `services.pixefed.nginx.enable` option and the actual Nginx
+      ## configuration should be in `services.nginx`. See eg. `pretix`.
+      ##
+      ## TODO: If that indeed makes sense, upstream.
+      nginx = {
+        forceSSL = true;
+        enableACME = true;
+        # locations."/public/".proxyPass = "${config.fediversity.garage.web.urlForBucket "pixelfed"}/public/";
+      };
+    };
+
+    services.pixelfed.settings = {
+      ## NOTE: This depends on the targets, eg. universities might want control
+      ## over who has an account. We probably want a universal
+      ## `fediversity.openRegistration` option.
+      OPEN_REGISTRATION = true;
+
+      # DANGEROUSLY_SET_FILESYSTEM_DRIVER = "s3";
+      FILESYSTEM_CLOUD = "s3";
+      PF_ENABLE_CLOUD = true;
+      AWS_ACCESS_KEY_ID = snakeoil_key.id;
+      AWS_SECRET_ACCESS_KEY = snakeoil_key.secret;
+      AWS_DEFAULT_REGION = "garage";
+      AWS_URL = config.fediversity.garage.web.urlForBucket "pixelfed";
+      AWS_BUCKET = "pixelfed";
+      AWS_ENDPOINT = config.fediversity.garage.api.url;
+      AWS_USE_PATH_STYLE_ENDPOINT = false;
+    };
+
+    ## Only ever run `pixelfed-data-setup` after `ensure-garage` has done its job.
+    ## Otherwise, everything crashed dramatically.
+    systemd.services.pixelfed-data-setup = {
+      after = [ "ensure-garage.service" ];
+    };
+
+    networking.firewall.allowedTCPPorts = [
+      80
+      443
+    ];
+  };
+}
diff --git a/services/fediversity/pixelfed-group-permissions.patch b/services/fediversity/pixelfed/group-permissions.patch
similarity index 100%
rename from services/fediversity/pixelfed-group-permissions.patch
rename to services/fediversity/pixelfed/group-permissions.patch
diff --git a/services/fediversity/pixelfed/options.nix b/services/fediversity/pixelfed/options.nix
new file mode 100644
index 0000000..1a36ea2
--- /dev/null
+++ b/services/fediversity/pixelfed/options.nix
@@ -0,0 +1,18 @@
+{ config, lib, ... }:
+
+let
+  inherit (lib) mkOption mkEnableOption;
+  inherit (lib.types) types;
+
+in
+{
+  options.fediversity.pixelfed = {
+    enable = mkEnableOption "Enable a Pixelfed server on the machine";
+
+    domain = mkOption {
+      type = types.str;
+      description = "Internal option — change at your own risk";
+      default = "pixelfed.${config.fediversity.domain}";
+    };
+  };
+}

From b547912794b09a47c0d3ce1ad71f084ee68bab0a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nicolas=20=E2=80=9CNiols=E2=80=9D=20Jeannerod?=
 <nicolas.jeannerod@moduscreate.com>
Date: Fri, 14 Feb 2025 19:01:54 +0100
Subject: [PATCH 10/16] Make access and secret keys parameters

---
 deployment/flake-part.nix                 | 24 ++++++++++++++++++-----
 services/fediversity/garage/default.nix   |  6 +++---
 services/fediversity/garage/options.nix   |  4 ++--
 services/fediversity/mastodon/default.nix | 15 +++++---------
 services/fediversity/mastodon/options.nix | 17 ++++++++++++++++
 services/fediversity/peertube/default.nix | 14 ++++---------
 services/fediversity/peertube/options.nix | 17 ++++++++++++++++
 services/fediversity/pixelfed/default.nix | 21 +++++++++-----------
 services/fediversity/pixelfed/options.nix | 17 ++++++++++++++++
 9 files changed, 93 insertions(+), 42 deletions(-)

diff --git a/deployment/flake-part.nix b/deployment/flake-part.nix
index a0e42ae..d215a22 100644
--- a/deployment/flake-part.nix
+++ b/deployment/flake-part.nix
@@ -80,7 +80,13 @@ in
           fediversity = {
             enable = true;
             domain = "fedi101.abundos.eu";
-            pixelfed.enable = true;
+            pixelfed = {
+              enable = true;
+
+              ## NOTE: Only ever used for testing anyway.
+              s3AccessKey = "GKb5615457d44214411e673b7b";
+              s3SecretKey = "5be6799a88ca9b9d813d1a806b64f15efa49482dbe15339ddfaf7f19cf434987";
+            };
           };
         };
 
@@ -88,7 +94,13 @@ in
           fediversity = {
             enable = true;
             domain = "fedi102.abundos.eu";
-            mastodon.enable = true;
+            mastodon = {
+              enable = true;
+
+              ## NOTE: Only ever used for testing anyway.
+              s3AccessKey = "GK3515373e4c851ebaad366558";
+              s3SecretKey = "7d37d093435a41f2aab8f13c19ba067d9776c90215f56614adad6ece597dbb34";
+            };
 
             temp.cores = 1; # FIXME: should come from NixOps4 eventually
           };
@@ -102,9 +114,11 @@ in
               domain = "fedi103.abundos.eu";
               peertube = {
                 enable = true;
-                secretsFile = pkgs.writeText "secret" ''
-                  574e093907d1157ac0f8e760a6deb1035402003af5763135bae9cbd6abe32b24
-                '';
+
+                ## NOTE: Only ever used for testing anyway.
+                secretsFile = pkgs.writeText "secret" "574e093907d1157ac0f8e760a6deb1035402003af5763135bae9cbd6abe32b24";
+                s3AccessKey = "GK1f9feea9960f6f95ff404c9b";
+                s3SecretKey = "7295c4201966a02c2c3d25b5cea4a5ff782966a2415e3a196f91924631191395";
               };
             };
           }
diff --git a/services/fediversity/garage/default.nix b/services/fediversity/garage/default.nix
index 5191016..da0a7e4 100644
--- a/services/fediversity/garage/default.nix
+++ b/services/fediversity/garage/default.nix
@@ -83,13 +83,13 @@ let
   ensureKeyScriptFn =
     key:
     {
-      id,
-      secret,
+      s3AccessKey,
+      s3SecretKey,
       ensureAccess,
     }:
     ''
       ## FIXME: Check whether the key exist and skip this step if that is the case. Get rid of this `|| :`
-      garage key import --yes -n ${escapeShellArg key} ${escapeShellArg id} ${escapeShellArg secret} || :
+      garage key import --yes -n ${escapeShellArg key} ${escapeShellArg s3AccessKey} ${escapeShellArg s3SecretKey} || :
       ${concatMapAttrs (ensureAccessScriptFn key) ensureAccess}
     '';
 
diff --git a/services/fediversity/garage/options.nix b/services/fediversity/garage/options.nix
index ad3d555..c93534e 100644
--- a/services/fediversity/garage/options.nix
+++ b/services/fediversity/garage/options.nix
@@ -45,8 +45,8 @@ in
         types.submodule {
           # TODO: these should be managed as secrets, not in the nix store
           options = {
-            id = mkOption { type = types.str; };
-            secret = mkOption { type = types.str; };
+            s3AccessKey = mkOption { type = types.str; };
+            s3SecretKey = mkOption { type = types.str; };
             # TODO: assert at least one of these is true
             # NOTE: this currently needs to be done at the top level module
             ensureAccess = mkOption {
diff --git a/services/fediversity/mastodon/default.nix b/services/fediversity/mastodon/default.nix
index 083cf91..cd1846a 100644
--- a/services/fediversity/mastodon/default.nix
+++ b/services/fediversity/mastodon/default.nix
@@ -1,10 +1,3 @@
-let
-  snakeoil_key = {
-    id = "GK3515373e4c851ebaad366558";
-    secret = "7d37d093435a41f2aab8f13c19ba067d9776c90215f56614adad6ece597dbb34";
-  };
-in
-
 { config, lib, ... }:
 
 {
@@ -24,9 +17,10 @@ in
           };
         };
       };
+
       ensureKeys = {
         mastodon = {
-          inherit (snakeoil_key) id secret;
+          inherit (config.fediversity.mastodon) s3AccessKey s3SecretKey;
           ensureAccess = {
             mastodon = {
               read = true;
@@ -37,6 +31,7 @@ in
         };
       };
     };
+
     services.mastodon = {
       extraConfig = rec {
         S3_ENABLED = "true";
@@ -46,8 +41,8 @@ in
         S3_BUCKET = "mastodon";
         # use <S3_BUCKET>.<S3_ENDPOINT>
         S3_OVERRIDE_PATH_STLE = "true";
-        AWS_ACCESS_KEY_ID = snakeoil_key.id;
-        AWS_SECRET_ACCESS_KEY = snakeoil_key.secret;
+        AWS_ACCESS_KEY_ID = config.fediversity.mastodon.s3AccessKey;
+        AWS_SECRET_ACCESS_KEY = config.fediversity.mastodon.s3SecretKey;
         S3_PROTOCOL = "http";
         S3_ALIAS_HOST = "${S3_BUCKET}.${config.fediversity.garage.web.rootDomain}";
         # SEE: the last section in https://docs.joinmastodon.org/admin/optional/object-storage/
diff --git a/services/fediversity/mastodon/options.nix b/services/fediversity/mastodon/options.nix
index 882b96e..16b148d 100644
--- a/services/fediversity/mastodon/options.nix
+++ b/services/fediversity/mastodon/options.nix
@@ -9,6 +9,23 @@ in
   options.fediversity.mastodon = {
     enable = mkEnableOption "Enable a Mastodon server on the machine";
 
+    s3AccessKey = mkOption {
+      type = types.str;
+      description = ''
+        S3 access key
+
+        In AWS CLI, this would be AWS_ACCESS_KEY_ID.
+      '';
+    };
+
+    s3SecretKey = mkOption {
+      description = ''
+        S3 secret key
+
+        In AWS CLI, this would be AWS_SECRET_ACCESS_KEY.
+      '';
+    };
+
     domain = mkOption {
       type = types.str;
       description = "Internal option — change at your own risk";
diff --git a/services/fediversity/peertube/default.nix b/services/fediversity/peertube/default.nix
index e649c56..cf7c620 100644
--- a/services/fediversity/peertube/default.nix
+++ b/services/fediversity/peertube/default.nix
@@ -1,10 +1,3 @@
-let
-  snakeoil_key = {
-    id = "GK1f9feea9960f6f95ff404c9b";
-    secret = "7295c4201966a02c2c3d25b5cea4a5ff782966a2415e3a196f91924631191395";
-  };
-in
-
 { config, lib, ... }:
 
 {
@@ -43,9 +36,10 @@ in
           };
         };
       };
+
       ensureKeys = {
         peertube = {
-          inherit (snakeoil_key) id secret;
+          inherit (config.fediversity.peertube) s3AccessKey s3SecretKey;
           ensureAccess = {
             peertube-videos = {
               read = true;
@@ -104,8 +98,8 @@ in
       serviceEnvironmentFile = "/etc/peertube-env";
     };
     environment.etc.peertube-env.text = ''
-      AWS_ACCESS_KEY_ID=${snakeoil_key.id}
-      AWS_SECRET_ACCESS_KEY=${snakeoil_key.secret}
+      AWS_ACCESS_KEY_ID=${config.fediversity.peertube.s3AccessKey}
+      AWS_SECRET_ACCESS_KEY=${config.fediversity.peertube.s3SecretKey}
     '';
 
     ## Proxying through Nginx
diff --git a/services/fediversity/peertube/options.nix b/services/fediversity/peertube/options.nix
index feedcad..b486437 100644
--- a/services/fediversity/peertube/options.nix
+++ b/services/fediversity/peertube/options.nix
@@ -9,6 +9,23 @@ in
   options.fediversity.peertube = {
     enable = mkEnableOption "Enable a PeerTube server on the machine";
 
+    s3AccessKey = mkOption {
+      type = types.str;
+      description = ''
+        S3 access key
+
+        In AWS CLI, this would be AWS_ACCESS_KEY_ID.
+      '';
+    };
+
+    s3SecretKey = mkOption {
+      description = ''
+        S3 secret key
+
+        In AWS CLI, this would be AWS_SECRET_ACCESS_KEY.
+      '';
+    };
+
     domain = mkOption {
       type = types.str;
       description = "Internal option — change at your own risk";
diff --git a/services/fediversity/pixelfed/default.nix b/services/fediversity/pixelfed/default.nix
index 4ce2887..e12a5e1 100644
--- a/services/fediversity/pixelfed/default.nix
+++ b/services/fediversity/pixelfed/default.nix
@@ -1,10 +1,3 @@
-let
-  snakeoil_key = {
-    id = "GKb5615457d44214411e673b7b";
-    secret = "5be6799a88ca9b9d813d1a806b64f15efa49482dbe15339ddfaf7f19cf434987";
-  };
-in
-
 {
   config,
   lib,
@@ -12,10 +5,14 @@ in
   ...
 }:
 
+let
+  inherit (lib) mkIf;
+
+in
 {
   imports = [ ./options.nix ];
 
-  config = lib.mkIf (config.fediversity.enable && config.fediversity.pixelfed.enable) {
+  config = mkIf (config.fediversity.enable && config.fediversity.pixelfed.enable) {
     fediversity.garage = {
       ensureBuckets = {
         pixelfed = {
@@ -29,9 +26,10 @@ in
           };
         };
       };
+
       ensureKeys = {
         pixelfed = {
-          inherit (snakeoil_key) id secret;
+          inherit (config.fediversity.pixelfed) s3AccessKey s3SecretKey;
           ensureAccess = {
             pixelfed = {
               read = true;
@@ -70,11 +68,10 @@ in
       ## `fediversity.openRegistration` option.
       OPEN_REGISTRATION = true;
 
-      # DANGEROUSLY_SET_FILESYSTEM_DRIVER = "s3";
       FILESYSTEM_CLOUD = "s3";
       PF_ENABLE_CLOUD = true;
-      AWS_ACCESS_KEY_ID = snakeoil_key.id;
-      AWS_SECRET_ACCESS_KEY = snakeoil_key.secret;
+      AWS_ACCESS_KEY_ID = config.fediversity.pixelfed.s3AccessKey;
+      AWS_SECRET_ACCESS_KEY = config.fediversity.pixelfed.s3SecretKey;
       AWS_DEFAULT_REGION = "garage";
       AWS_URL = config.fediversity.garage.web.urlForBucket "pixelfed";
       AWS_BUCKET = "pixelfed";
diff --git a/services/fediversity/pixelfed/options.nix b/services/fediversity/pixelfed/options.nix
index 1a36ea2..27eff69 100644
--- a/services/fediversity/pixelfed/options.nix
+++ b/services/fediversity/pixelfed/options.nix
@@ -9,6 +9,23 @@ in
   options.fediversity.pixelfed = {
     enable = mkEnableOption "Enable a Pixelfed server on the machine";
 
+    s3AccessKey = mkOption {
+      type = types.str;
+      description = ''
+        S3 access key
+
+        In AWS CLI, this would be AWS_ACCESS_KEY_ID.
+      '';
+    };
+
+    s3SecretKey = mkOption {
+      description = ''
+        S3 secret key
+
+        In AWS CLI, this would be AWS_SECRET_ACCESS_KEY.
+      '';
+    };
+
     domain = mkOption {
       type = types.str;
       description = "Internal option — change at your own risk";

From 8c5bf79ba274d28fc1f737e3c17ee3c7ccc1a2bf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nicolas=20=E2=80=9CNiols=E2=80=9D=20Jeannerod?=
 <nicolas.jeannerod@moduscreate.com>
Date: Fri, 14 Feb 2025 19:21:31 +0100
Subject: [PATCH 11/16] Share options that can be shared

---
 services/fediversity/mastodon/options.nix | 37 ++++------------
 services/fediversity/peertube/options.nix | 51 ++++++++---------------
 services/fediversity/pixelfed/options.nix | 37 ++++------------
 services/fediversity/sharedOptions.nix    | 38 +++++++++++++++++
 4 files changed, 71 insertions(+), 92 deletions(-)
 create mode 100644 services/fediversity/sharedOptions.nix

diff --git a/services/fediversity/mastodon/options.nix b/services/fediversity/mastodon/options.nix
index 16b148d..841284b 100644
--- a/services/fediversity/mastodon/options.nix
+++ b/services/fediversity/mastodon/options.nix
@@ -1,35 +1,14 @@
 { config, lib, ... }:
 
-let
-  inherit (lib) mkOption mkEnableOption;
-  inherit (lib.types) types;
-
-in
 {
-  options.fediversity.mastodon = {
-    enable = mkEnableOption "Enable a Mastodon server on the machine";
+  options.fediversity.mastodon =
+    (import ../sharedOptions.nix {
+      inherit config lib;
+      serviceName = "mastodon";
+      serviceDocName = "Mastodon";
+    })
+    //
 
-    s3AccessKey = mkOption {
-      type = types.str;
-      description = ''
-        S3 access key
-
-        In AWS CLI, this would be AWS_ACCESS_KEY_ID.
-      '';
+    {
     };
-
-    s3SecretKey = mkOption {
-      description = ''
-        S3 secret key
-
-        In AWS CLI, this would be AWS_SECRET_ACCESS_KEY.
-      '';
-    };
-
-    domain = mkOption {
-      type = types.str;
-      description = "Internal option — change at your own risk";
-      default = "mastodon.${config.fediversity.domain}";
-    };
-  };
 }
diff --git a/services/fediversity/peertube/options.nix b/services/fediversity/peertube/options.nix
index b486437..21eaa02 100644
--- a/services/fediversity/peertube/options.nix
+++ b/services/fediversity/peertube/options.nix
@@ -1,45 +1,28 @@
 { config, lib, ... }:
 
 let
-  inherit (lib) mkOption mkEnableOption;
+  inherit (lib) mkOption;
   inherit (lib.types) types;
 
 in
 {
-  options.fediversity.peertube = {
-    enable = mkEnableOption "Enable a PeerTube server on the machine";
+  options.fediversity.peertube =
+    (import ../sharedOptions.nix {
+      inherit config lib;
+      serviceName = "peertube";
+      serviceDocName = "PeerTube";
+    })
+    //
 
-    s3AccessKey = mkOption {
-      type = types.str;
-      description = ''
-        S3 access key
+    {
+      secretsFile = mkOption {
+        type = types.path;
+        description = ''
+          Internal option — change at your own risk
 
-        In AWS CLI, this would be AWS_ACCESS_KEY_ID.
-      '';
+          FIXME: should it be provided by NixOps4?
+          or maybe we should just ask for a main secret from which to derive all the others?
+        '';
+      };
     };
-
-    s3SecretKey = mkOption {
-      description = ''
-        S3 secret key
-
-        In AWS CLI, this would be AWS_SECRET_ACCESS_KEY.
-      '';
-    };
-
-    domain = mkOption {
-      type = types.str;
-      description = "Internal option — change at your own risk";
-      default = "peertube.${config.fediversity.domain}";
-    };
-
-    secretsFile = mkOption {
-      type = types.path;
-      description = ''
-        Internal option — change at your own risk
-
-        FIXME: should it be provided by NixOps4?
-        or maybe we should just ask for a main secret from which to derive all the others?
-      '';
-    };
-  };
 }
diff --git a/services/fediversity/pixelfed/options.nix b/services/fediversity/pixelfed/options.nix
index 27eff69..4248c01 100644
--- a/services/fediversity/pixelfed/options.nix
+++ b/services/fediversity/pixelfed/options.nix
@@ -1,35 +1,14 @@
 { config, lib, ... }:
 
-let
-  inherit (lib) mkOption mkEnableOption;
-  inherit (lib.types) types;
-
-in
 {
-  options.fediversity.pixelfed = {
-    enable = mkEnableOption "Enable a Pixelfed server on the machine";
+  options.fediversity.pixelfed =
+    (import ../sharedOptions.nix {
+      inherit config lib;
+      serviceName = "pixelfed";
+      serviceDocName = "Pixelfed";
+    })
+    //
 
-    s3AccessKey = mkOption {
-      type = types.str;
-      description = ''
-        S3 access key
-
-        In AWS CLI, this would be AWS_ACCESS_KEY_ID.
-      '';
+    {
     };
-
-    s3SecretKey = mkOption {
-      description = ''
-        S3 secret key
-
-        In AWS CLI, this would be AWS_SECRET_ACCESS_KEY.
-      '';
-    };
-
-    domain = mkOption {
-      type = types.str;
-      description = "Internal option — change at your own risk";
-      default = "pixelfed.${config.fediversity.domain}";
-    };
-  };
 }
diff --git a/services/fediversity/sharedOptions.nix b/services/fediversity/sharedOptions.nix
new file mode 100644
index 0000000..0d6317e
--- /dev/null
+++ b/services/fediversity/sharedOptions.nix
@@ -0,0 +1,38 @@
+{
+  config,
+  lib,
+  serviceName,
+  serviceDocName,
+}:
+
+let
+  inherit (lib) mkOption mkEnableOption;
+  inherit (lib.types) types;
+
+in
+{
+  enable = mkEnableOption "Enable a ${serviceDocName} server on the machine";
+
+  s3AccessKey = mkOption {
+    type = types.str;
+    description = ''
+      S3 access key for ${serviceDocName}'s bucket/s
+
+      In AWS CLI, this would be AWS_ACCESS_KEY_ID.
+    '';
+  };
+
+  s3SecretKey = mkOption {
+    description = ''
+      S3 secret key for ${serviceDocName}'s bucket/s
+
+      In AWS CLI, this would be AWS_SECRET_ACCESS_KEY.
+    '';
+  };
+
+  domain = mkOption {
+    type = types.str;
+    description = "Internal option — change at your own risk";
+    default = "${serviceName}.${config.fediversity.domain}";
+  };
+}

From 78a85b27ffe8c4765002ed107b30514eb169a782 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nicolas=20=E2=80=9CNiols=E2=80=9D=20Jeannerod?=
 <nicolas.jeannerod@moduscreate.com>
Date: Fri, 14 Feb 2025 19:46:12 +0100
Subject: [PATCH 12/16] Put the S3 secrets into files

...but not everywhere, there remains some FIXMEs where ultimately the
secrets do get into the store.
---
 deployment/flake-part.nix                 | 54 ++++++++++++----------
 services/fediversity/garage/default.nix   |  6 +--
 services/fediversity/garage/options.nix   |  7 +--
 services/fediversity/mastodon/default.nix | 55 ++++++++++++++---------
 services/fediversity/peertube/default.nix | 15 +++++--
 services/fediversity/pixelfed/default.nix | 11 ++---
 services/fediversity/sharedOptions.nix    | 10 +++--
 7 files changed, 96 insertions(+), 62 deletions(-)

diff --git a/deployment/flake-part.nix b/deployment/flake-part.nix
index d215a22..c903f8a 100644
--- a/deployment/flake-part.nix
+++ b/deployment/flake-part.nix
@@ -76,35 +76,41 @@ in
       providers = { inherit (inputs.nixops4.modules.nixops4Provider) local; };
 
       resources = {
-        fedi101 = makeProcolixVmResource 101 {
-          fediversity = {
-            enable = true;
-            domain = "fedi101.abundos.eu";
-            pixelfed = {
+        fedi101 = makeProcolixVmResource 101 (
+          { pkgs, ... }:
+          {
+            fediversity = {
               enable = true;
+              domain = "fedi101.abundos.eu";
+              pixelfed = {
+                enable = true;
 
-              ## NOTE: Only ever used for testing anyway.
-              s3AccessKey = "GKb5615457d44214411e673b7b";
-              s3SecretKey = "5be6799a88ca9b9d813d1a806b64f15efa49482dbe15339ddfaf7f19cf434987";
+                ## NOTE: Only ever used for testing anyway.
+                s3AccessKeyFile = pkgs.writeText "s3AccessKey" "GKb5615457d44214411e673b7b";
+                s3SecretKeyFile = pkgs.writeText "s3SecretKey" "5be6799a88ca9b9d813d1a806b64f15efa49482dbe15339ddfaf7f19cf434987";
+              };
             };
-          };
-        };
+          }
+        );
 
-        fedi102 = makeProcolixVmResource 102 {
-          fediversity = {
-            enable = true;
-            domain = "fedi102.abundos.eu";
-            mastodon = {
+        fedi102 = makeProcolixVmResource 102 (
+          { pkgs, ... }:
+          {
+            fediversity = {
               enable = true;
+              domain = "fedi102.abundos.eu";
+              mastodon = {
+                enable = true;
 
-              ## NOTE: Only ever used for testing anyway.
-              s3AccessKey = "GK3515373e4c851ebaad366558";
-              s3SecretKey = "7d37d093435a41f2aab8f13c19ba067d9776c90215f56614adad6ece597dbb34";
+                ## NOTE: Only ever used for testing anyway.
+                s3AccessKeyFile = pkgs.writeText "s3AccessKey" "GK3515373e4c851ebaad366558";
+                s3SecretKeyFile = pkgs.writeText "s3SecretKey" "7d37d093435a41f2aab8f13c19ba067d9776c90215f56614adad6ece597dbb34";
+              };
+
+              temp.cores = 1; # FIXME: should come from NixOps4 eventually
             };
-
-            temp.cores = 1; # FIXME: should come from NixOps4 eventually
-          };
-        };
+          }
+        );
 
         fedi103 = makeProcolixVmResource 103 (
           { pkgs, ... }:
@@ -117,8 +123,8 @@ in
 
                 ## NOTE: Only ever used for testing anyway.
                 secretsFile = pkgs.writeText "secret" "574e093907d1157ac0f8e760a6deb1035402003af5763135bae9cbd6abe32b24";
-                s3AccessKey = "GK1f9feea9960f6f95ff404c9b";
-                s3SecretKey = "7295c4201966a02c2c3d25b5cea4a5ff782966a2415e3a196f91924631191395";
+                s3AccessKeyFile = pkgs.writeText "s3AccessKey" "GK1f9feea9960f6f95ff404c9b";
+                s3SecretKeyFile = pkgs.writeText "s3SecretKey" "7295c4201966a02c2c3d25b5cea4a5ff782966a2415e3a196f91924631191395";
               };
             };
           }
diff --git a/services/fediversity/garage/default.nix b/services/fediversity/garage/default.nix
index da0a7e4..6915cb5 100644
--- a/services/fediversity/garage/default.nix
+++ b/services/fediversity/garage/default.nix
@@ -83,13 +83,13 @@ let
   ensureKeyScriptFn =
     key:
     {
-      s3AccessKey,
-      s3SecretKey,
+      s3AccessKeyFile,
+      s3SecretKeyFile,
       ensureAccess,
     }:
     ''
       ## FIXME: Check whether the key exist and skip this step if that is the case. Get rid of this `|| :`
-      garage key import --yes -n ${escapeShellArg key} ${escapeShellArg s3AccessKey} ${escapeShellArg s3SecretKey} || :
+      garage key import --yes -n ${escapeShellArg key} $(cat ${escapeShellArg s3AccessKeyFile}) $(cat ${escapeShellArg s3SecretKeyFile}) || :
       ${concatMapAttrs (ensureAccessScriptFn key) ensureAccess}
     '';
 
diff --git a/services/fediversity/garage/options.nix b/services/fediversity/garage/options.nix
index c93534e..baeda2c 100644
--- a/services/fediversity/garage/options.nix
+++ b/services/fediversity/garage/options.nix
@@ -43,10 +43,11 @@ in
     ensureKeys = mkOption {
       type = types.attrsOf (
         types.submodule {
-          # TODO: these should be managed as secrets, not in the nix store
           options = {
-            s3AccessKey = mkOption { type = types.str; };
-            s3SecretKey = mkOption { type = types.str; };
+            s3AccessKeyFile = mkOption { type = types.path; };
+
+            s3SecretKeyFile = mkOption { type = types.path; };
+
             # TODO: assert at least one of these is true
             # NOTE: this currently needs to be done at the top level module
             ensureAccess = mkOption {
diff --git a/services/fediversity/mastodon/default.nix b/services/fediversity/mastodon/default.nix
index cd1846a..63e536d 100644
--- a/services/fediversity/mastodon/default.nix
+++ b/services/fediversity/mastodon/default.nix
@@ -1,5 +1,15 @@
-{ config, lib, ... }:
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
 
+let
+  inherit (lib) readFile;
+  inherit (pkgs) writeText;
+
+in
 {
   imports = [ ./options.nix ];
 
@@ -20,7 +30,7 @@
 
       ensureKeys = {
         mastodon = {
-          inherit (config.fediversity.mastodon) s3AccessKey s3SecretKey;
+          inherit (config.fediversity.mastodon) s3AccessKeyFile s3SecretKeyFile;
           ensureAccess = {
             mastodon = {
               read = true;
@@ -32,26 +42,31 @@
       };
     };
 
-    services.mastodon = {
-      extraConfig = rec {
-        S3_ENABLED = "true";
-        # TODO: this shouldn't be hard-coded, it should come from the garage configuration
-        S3_ENDPOINT = config.fediversity.garage.api.url;
-        S3_REGION = "garage";
-        S3_BUCKET = "mastodon";
-        # use <S3_BUCKET>.<S3_ENDPOINT>
-        S3_OVERRIDE_PATH_STLE = "true";
-        AWS_ACCESS_KEY_ID = config.fediversity.mastodon.s3AccessKey;
-        AWS_SECRET_ACCESS_KEY = config.fediversity.mastodon.s3SecretKey;
-        S3_PROTOCOL = "http";
-        S3_ALIAS_HOST = "${S3_BUCKET}.${config.fediversity.garage.web.rootDomain}";
-        # SEE: the last section in https://docs.joinmastodon.org/admin/optional/object-storage/
-        # TODO: can we set up ACLs with garage?
-        S3_PERMISSION = "";
-      };
+    services.mastodon.extraConfig = rec {
+      S3_ENABLED = "true";
+      # TODO: this shouldn't be hard-coded, it should come from the garage configuration
+      S3_ENDPOINT = config.fediversity.garage.api.url;
+      S3_REGION = "garage";
+      S3_BUCKET = "mastodon";
+      # use <S3_BUCKET>.<S3_ENDPOINT>
+      S3_OVERRIDE_PATH_STLE = "true";
+      S3_PROTOCOL = "http";
+      S3_ALIAS_HOST = config.fediversity.garage.web.domainForBucket S3_BUCKET;
+      # SEE: the last section in https://docs.joinmastodon.org/admin/optional/object-storage/
+      # TODO: can we set up ACLs with garage?
+      S3_PERMISSION = "";
     };
 
-    #### mastodon setup
+    ## FIXME: secrets management; we should have a service that writes the
+    ## `.env` files based on all the secrets that we need to put there.
+    services.mastodon.extraEnvFiles = [
+      (writeText "s3AccessKey" ''
+        AWS_ACCESS_KEY_ID=${readFile config.fediversity.mastodon.s3AccessKeyFile}
+      '')
+      (writeText "s3SecretKey" ''
+        AWS_SECRET_ACCESS_KEY=${readFile config.fediversity.mastodon.s3SecretKeyFile}
+      '')
+    ];
 
     # open up access to the mastodon web interface. 80 is necessary if only for ACME
     networking.firewall.allowedTCPPorts = [
diff --git a/services/fediversity/peertube/default.nix b/services/fediversity/peertube/default.nix
index cf7c620..3c18486 100644
--- a/services/fediversity/peertube/default.nix
+++ b/services/fediversity/peertube/default.nix
@@ -1,9 +1,13 @@
 { config, lib, ... }:
 
+let
+  inherit (lib) mkIf readFile;
+
+in
 {
   imports = [ ./options.nix ];
 
-  config = lib.mkIf (config.fediversity.enable && config.fediversity.peertube.enable) {
+  config = mkIf (config.fediversity.enable && config.fediversity.peertube.enable) {
     networking.firewall.allowedTCPPorts = [
       80
       443
@@ -39,7 +43,7 @@
 
       ensureKeys = {
         peertube = {
-          inherit (config.fediversity.peertube) s3AccessKey s3SecretKey;
+          inherit (config.fediversity.peertube) s3AccessKeyFile s3SecretKeyFile;
           ensureAccess = {
             peertube-videos = {
               read = true;
@@ -97,9 +101,12 @@
       };
       serviceEnvironmentFile = "/etc/peertube-env";
     };
+
+    ## FIXME: secrets management; we should have a service that writes the
+    ## `.env` files based on all the secrets that we need to put there.
     environment.etc.peertube-env.text = ''
-      AWS_ACCESS_KEY_ID=${config.fediversity.peertube.s3AccessKey}
-      AWS_SECRET_ACCESS_KEY=${config.fediversity.peertube.s3SecretKey}
+      AWS_ACCESS_KEY_ID=${readFile config.fediversity.peertube.s3AccessKeyFile}
+      AWS_SECRET_ACCESS_KEY=${readFile config.fediversity.peertube.s3SecretKeyFile}
     '';
 
     ## Proxying through Nginx
diff --git a/services/fediversity/pixelfed/default.nix b/services/fediversity/pixelfed/default.nix
index e12a5e1..b845d92 100644
--- a/services/fediversity/pixelfed/default.nix
+++ b/services/fediversity/pixelfed/default.nix
@@ -6,7 +6,7 @@
 }:
 
 let
-  inherit (lib) mkIf;
+  inherit (lib) mkIf readFile;
 
 in
 {
@@ -29,7 +29,7 @@ in
 
       ensureKeys = {
         pixelfed = {
-          inherit (config.fediversity.pixelfed) s3AccessKey s3SecretKey;
+          inherit (config.fediversity.pixelfed) s3AccessKeyFile s3SecretKeyFile;
           ensureAccess = {
             pixelfed = {
               read = true;
@@ -45,9 +45,12 @@ in
       enable = true;
       domain = config.fediversity.pixelfed.domain;
 
-      # TODO: secrets management!!!
+      ## FIXME: secrets management; we should have a service that writes the
+      ## `.env` file based on all the secrets that we need to put there.
       secretFile = pkgs.writeText "secrets.env" ''
         APP_KEY=adKK9EcY8Hcj3PLU7rzG9rJ6KKTOtYfA
+        AWS_ACCESS_KEY_ID=${readFile config.fediversity.pixelfed.s3AccessKeyFile}
+        AWS_SECRET_ACCESS_KEY=${readFile config.fediversity.pixelfed.s3SecretKeyFile}
       '';
 
       ## Taeer feels like this way of configuring Nginx is odd; there should
@@ -70,8 +73,6 @@ in
 
       FILESYSTEM_CLOUD = "s3";
       PF_ENABLE_CLOUD = true;
-      AWS_ACCESS_KEY_ID = config.fediversity.pixelfed.s3AccessKey;
-      AWS_SECRET_ACCESS_KEY = config.fediversity.pixelfed.s3SecretKey;
       AWS_DEFAULT_REGION = "garage";
       AWS_URL = config.fediversity.garage.web.urlForBucket "pixelfed";
       AWS_BUCKET = "pixelfed";
diff --git a/services/fediversity/sharedOptions.nix b/services/fediversity/sharedOptions.nix
index 0d6317e..10ddd5c 100644
--- a/services/fediversity/sharedOptions.nix
+++ b/services/fediversity/sharedOptions.nix
@@ -1,3 +1,6 @@
+## NOTE: Not a module, but a helper function to create options for Fediversity
+## services, as they tend to require the same ones.
+
 {
   config,
   lib,
@@ -13,8 +16,8 @@ in
 {
   enable = mkEnableOption "Enable a ${serviceDocName} server on the machine";
 
-  s3AccessKey = mkOption {
-    type = types.str;
+  s3AccessKeyFile = mkOption {
+    type = types.path;
     description = ''
       S3 access key for ${serviceDocName}'s bucket/s
 
@@ -22,7 +25,8 @@ in
     '';
   };
 
-  s3SecretKey = mkOption {
+  s3SecretKeyFile = mkOption {
+    type = types.path;
     description = ''
       S3 secret key for ${serviceDocName}'s bucket/s
 

From a5d226ed224d0a08520a10935db6f25d046d1129 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nicolas=20=E2=80=9CNiols=E2=80=9D=20Jeannerod?=
 <nicolas.jeannerod@moduscreate.com>
Date: Fri, 14 Feb 2025 20:08:01 +0100
Subject: [PATCH 13/16] Get rid of `fediversity.enable`

---
 deployment/flake-part.nix                 | 3 ---
 services/fediversity/default.nix          | 9 +--------
 services/fediversity/garage/default.nix   | 2 +-
 services/fediversity/mastodon/default.nix | 2 +-
 services/fediversity/peertube/default.nix | 2 +-
 services/fediversity/pixelfed/default.nix | 2 +-
 6 files changed, 5 insertions(+), 15 deletions(-)

diff --git a/deployment/flake-part.nix b/deployment/flake-part.nix
index c903f8a..0eeba75 100644
--- a/deployment/flake-part.nix
+++ b/deployment/flake-part.nix
@@ -80,7 +80,6 @@ in
           { pkgs, ... }:
           {
             fediversity = {
-              enable = true;
               domain = "fedi101.abundos.eu";
               pixelfed = {
                 enable = true;
@@ -97,7 +96,6 @@ in
           { pkgs, ... }:
           {
             fediversity = {
-              enable = true;
               domain = "fedi102.abundos.eu";
               mastodon = {
                 enable = true;
@@ -116,7 +114,6 @@ in
           { pkgs, ... }:
           {
             fediversity = {
-              enable = true;
               domain = "fedi103.abundos.eu";
               peertube = {
                 enable = true;
diff --git a/services/fediversity/default.nix b/services/fediversity/default.nix
index 5147c8f..d935499 100644
--- a/services/fediversity/default.nix
+++ b/services/fediversity/default.nix
@@ -1,7 +1,7 @@
 { lib, config, ... }:
 
 let
-  inherit (lib) mkOption mkEnableOption mkForce;
+  inherit (lib) mkOption;
   inherit (lib.types) types;
 
 in
@@ -15,8 +15,6 @@ in
 
   options = {
     fediversity = {
-      enable = mkEnableOption "the collection of services bundled under Fediversity";
-
       domain = mkOption {
         type = types.str;
         description = ''
@@ -50,10 +48,5 @@ in
       defaults.email = "nicolas.jeannerod+fediversity@moduscreate.com";
       # defaults.server = "https://acme-staging-v02.api.letsencrypt.org/directory";
     };
-
-    ## NOTE: For a one-machine deployment, this removes the need to provide an
-    ## `s3.garage.<domain>` domain. However, this will quickly stop working once
-    ## we go to multi-machines deployment.
-    fediversity.garage.api.domain = mkForce "s3.garage.localhost";
   };
 }
diff --git a/services/fediversity/garage/default.nix b/services/fediversity/garage/default.nix
index 6915cb5..e3a6012 100644
--- a/services/fediversity/garage/default.nix
+++ b/services/fediversity/garage/default.nix
@@ -99,7 +99,7 @@ in
 {
   imports = [ ./options.nix ];
 
-  config = lib.mkIf config.fediversity.enable {
+  config = {
     environment.systemPackages = [
       pkgs.minio-client
       pkgs.awscli
diff --git a/services/fediversity/mastodon/default.nix b/services/fediversity/mastodon/default.nix
index 63e536d..4fe5ef3 100644
--- a/services/fediversity/mastodon/default.nix
+++ b/services/fediversity/mastodon/default.nix
@@ -13,7 +13,7 @@ in
 {
   imports = [ ./options.nix ];
 
-  config = lib.mkIf (config.fediversity.enable && config.fediversity.mastodon.enable) {
+  config = lib.mkIf config.fediversity.mastodon.enable {
     #### garage setup
     fediversity.garage = {
       ensureBuckets = {
diff --git a/services/fediversity/peertube/default.nix b/services/fediversity/peertube/default.nix
index 3c18486..cb46969 100644
--- a/services/fediversity/peertube/default.nix
+++ b/services/fediversity/peertube/default.nix
@@ -7,7 +7,7 @@ in
 {
   imports = [ ./options.nix ];
 
-  config = mkIf (config.fediversity.enable && config.fediversity.peertube.enable) {
+  config = mkIf config.fediversity.peertube.enable {
     networking.firewall.allowedTCPPorts = [
       80
       443
diff --git a/services/fediversity/pixelfed/default.nix b/services/fediversity/pixelfed/default.nix
index b845d92..3198929 100644
--- a/services/fediversity/pixelfed/default.nix
+++ b/services/fediversity/pixelfed/default.nix
@@ -12,7 +12,7 @@ in
 {
   imports = [ ./options.nix ];
 
-  config = mkIf (config.fediversity.enable && config.fediversity.pixelfed.enable) {
+  config = mkIf config.fediversity.pixelfed.enable {
     fediversity.garage = {
       ensureBuckets = {
         pixelfed = {

From 1eeaa04df6d567ebd6964f5c424a2a278494d42a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nicolas=20=E2=80=9CNiols=E2=80=9D=20Jeannerod?=
 <nicolas.jeannerod@moduscreate.com>
Date: Fri, 14 Feb 2025 20:10:55 +0100
Subject: [PATCH 14/16] Introduce `fediversity.garage.enable`

---
 deployment/flake-part.nix               | 3 +++
 services/fediversity/garage/default.nix | 4 ++--
 services/fediversity/garage/options.nix | 2 ++
 3 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/deployment/flake-part.nix b/deployment/flake-part.nix
index 0eeba75..8560e9c 100644
--- a/deployment/flake-part.nix
+++ b/deployment/flake-part.nix
@@ -88,6 +88,7 @@ in
                 s3AccessKeyFile = pkgs.writeText "s3AccessKey" "GKb5615457d44214411e673b7b";
                 s3SecretKeyFile = pkgs.writeText "s3SecretKey" "5be6799a88ca9b9d813d1a806b64f15efa49482dbe15339ddfaf7f19cf434987";
               };
+              garage.enable = true;
             };
           }
         );
@@ -104,6 +105,7 @@ in
                 s3AccessKeyFile = pkgs.writeText "s3AccessKey" "GK3515373e4c851ebaad366558";
                 s3SecretKeyFile = pkgs.writeText "s3SecretKey" "7d37d093435a41f2aab8f13c19ba067d9776c90215f56614adad6ece597dbb34";
               };
+              garage.enable = true;
 
               temp.cores = 1; # FIXME: should come from NixOps4 eventually
             };
@@ -123,6 +125,7 @@ in
                 s3AccessKeyFile = pkgs.writeText "s3AccessKey" "GK1f9feea9960f6f95ff404c9b";
                 s3SecretKeyFile = pkgs.writeText "s3SecretKey" "7295c4201966a02c2c3d25b5cea4a5ff782966a2415e3a196f91924631191395";
               };
+              garage.enable = true;
             };
           }
         );
diff --git a/services/fediversity/garage/default.nix b/services/fediversity/garage/default.nix
index e3a6012..4d7dc69 100644
--- a/services/fediversity/garage/default.nix
+++ b/services/fediversity/garage/default.nix
@@ -16,7 +16,7 @@ in
 
 let
   inherit (builtins) toString;
-  inherit (lib) optionalString concatStringsSep;
+  inherit (lib) optionalString concatStringsSep mkIf;
   inherit (lib.strings) escapeShellArg;
   inherit (lib.attrsets) filterAttrs mapAttrs';
   concatMapAttrs = scriptFn: attrset: concatStringsSep "\n" (lib.mapAttrsToList scriptFn attrset);
@@ -99,7 +99,7 @@ in
 {
   imports = [ ./options.nix ];
 
-  config = {
+  config = mkIf config.fediversity.garage.enable {
     environment.systemPackages = [
       pkgs.minio-client
       pkgs.awscli
diff --git a/services/fediversity/garage/options.nix b/services/fediversity/garage/options.nix
index baeda2c..e528158 100644
--- a/services/fediversity/garage/options.nix
+++ b/services/fediversity/garage/options.nix
@@ -6,6 +6,8 @@ in
 
 {
   options.fediversity.garage = {
+    enable = mkEnableOption "Enable a Garage server on the machine";
+
     ensureBuckets = mkOption {
       type = types.attrsOf (
         types.submodule {

From cd83536e2f36e8d9e6f8e0ff6efb66b9adbaf70e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nicolas=20=E2=80=9CNiols=E2=80=9D=20Jeannerod?=
 <nicolas.jeannerod@moduscreate.com>
Date: Sat, 15 Feb 2025 11:19:10 +0100
Subject: [PATCH 15/16] Allow Garage and services to run on different machines

---
 deployment/flake-part.nix                 |  61 ++++---
 services/fediversity/garage/default.nix   |  12 +-
 services/fediversity/mastodon/default.nix | 157 +++++++++--------
 services/fediversity/peertube/default.nix | 205 ++++++++++++----------
 services/fediversity/pixelfed/default.nix | 151 ++++++++--------
 services/fediversity/sharedOptions.nix    |  12 +-
 services/tests/peertube.nix               |   4 +-
 services/vm/garage-vm.nix                 |   2 +
 services/vm/mastodon-vm.nix               |   9 +-
 services/vm/peertube-vm.nix               |   7 +-
 services/vm/pixelfed-vm.nix               |  15 +-
 11 files changed, 359 insertions(+), 276 deletions(-)

diff --git a/deployment/flake-part.nix b/deployment/flake-part.nix
index 8560e9c..208aebb 100644
--- a/deployment/flake-part.nix
+++ b/deployment/flake-part.nix
@@ -71,24 +71,54 @@ in
         };
       };
 
+      ## NOTE: All of these secrets are publicly available in this source file
+      ## and will end up in the Nix store. We don't care as they are only ever
+      ## used for testing anyway.
+      pixelfedS3KeyConfig =
+        { pkgs, ... }:
+        {
+          s3AccessKeyFile = pkgs.writeText "s3AccessKey" "GKb5615457d44214411e673b7b";
+          s3SecretKeyFile = pkgs.writeText "s3SecretKey" "5be6799a88ca9b9d813d1a806b64f15efa49482dbe15339ddfaf7f19cf434987";
+        };
+      mastodonS3KeyConfig =
+        { pkgs, ... }:
+        {
+          s3AccessKeyFile = pkgs.writeText "s3AccessKey" "GK3515373e4c851ebaad366558";
+          s3SecretKeyFile = pkgs.writeText "s3SecretKey" "7d37d093435a41f2aab8f13c19ba067d9776c90215f56614adad6ece597dbb34";
+        };
+      peertubeS3KeyConfig =
+        { pkgs, ... }:
+        {
+          s3AccessKeyFile = pkgs.writeText "s3AccessKey" "GK1f9feea9960f6f95ff404c9b";
+          s3SecretKeyFile = pkgs.writeText "s3SecretKey" "7295c4201966a02c2c3d25b5cea4a5ff782966a2415e3a196f91924631191395";
+        };
+
     in
     {
       providers = { inherit (inputs.nixops4.modules.nixops4Provider) local; };
 
       resources = {
+        fedi100 = makeProcolixVmResource 100 (
+          { pkgs, ... }:
+          {
+            fediversity = {
+              domain = "abundos.eu";
+              garage.enable = true;
+              pixelfed = pixelfedS3KeyConfig { inherit pkgs; };
+              mastodon = mastodonS3KeyConfig { inherit pkgs; };
+              peertube = peertubeS3KeyConfig { inherit pkgs; };
+            };
+          }
+        );
+
         fedi101 = makeProcolixVmResource 101 (
           { pkgs, ... }:
           {
             fediversity = {
-              domain = "fedi101.abundos.eu";
-              pixelfed = {
+              domain = "abundos.eu";
+              pixelfed = pixelfedS3KeyConfig { inherit pkgs; } // {
                 enable = true;
-
-                ## NOTE: Only ever used for testing anyway.
-                s3AccessKeyFile = pkgs.writeText "s3AccessKey" "GKb5615457d44214411e673b7b";
-                s3SecretKeyFile = pkgs.writeText "s3SecretKey" "5be6799a88ca9b9d813d1a806b64f15efa49482dbe15339ddfaf7f19cf434987";
               };
-              garage.enable = true;
             };
           }
         );
@@ -97,15 +127,10 @@ in
           { pkgs, ... }:
           {
             fediversity = {
-              domain = "fedi102.abundos.eu";
-              mastodon = {
+              domain = "abundos.eu";
+              mastodon = mastodonS3KeyConfig { inherit pkgs; } // {
                 enable = true;
-
-                ## NOTE: Only ever used for testing anyway.
-                s3AccessKeyFile = pkgs.writeText "s3AccessKey" "GK3515373e4c851ebaad366558";
-                s3SecretKeyFile = pkgs.writeText "s3SecretKey" "7d37d093435a41f2aab8f13c19ba067d9776c90215f56614adad6ece597dbb34";
               };
-              garage.enable = true;
 
               temp.cores = 1; # FIXME: should come from NixOps4 eventually
             };
@@ -116,16 +141,12 @@ in
           { pkgs, ... }:
           {
             fediversity = {
-              domain = "fedi103.abundos.eu";
-              peertube = {
+              domain = "abundos.eu";
+              peertube = peertubeS3KeyConfig { inherit pkgs; } // {
                 enable = true;
-
                 ## NOTE: Only ever used for testing anyway.
                 secretsFile = pkgs.writeText "secret" "574e093907d1157ac0f8e760a6deb1035402003af5763135bae9cbd6abe32b24";
-                s3AccessKeyFile = pkgs.writeText "s3AccessKey" "GK1f9feea9960f6f95ff404c9b";
-                s3SecretKeyFile = pkgs.writeText "s3SecretKey" "7295c4201966a02c2c3d25b5cea4a5ff782966a2415e3a196f91924631191395";
               };
-              garage.enable = true;
             };
           }
         );
diff --git a/services/fediversity/garage/default.nix b/services/fediversity/garage/default.nix
index 4d7dc69..c8133b7 100644
--- a/services/fediversity/garage/default.nix
+++ b/services/fediversity/garage/default.nix
@@ -105,7 +105,15 @@ in
       pkgs.awscli
     ];
 
-    networking.firewall.allowedTCPPorts = [ config.fediversity.garage.rpc.port ];
+    ## REVIEW: Do we want to reverse proxy the RPC and API ports? In fact,
+    ## shouldn't we just get rid of RPC at all, we're not using it.
+    networking.firewall.allowedTCPPorts = [
+      80
+      443
+      config.fediversity.garage.api.port
+      config.fediversity.garage.rpc.port
+    ];
+
     services.garage = {
       enable = true;
       package = pkgs.garage_0_9;
@@ -126,6 +134,8 @@ in
       };
     };
 
+    services.nginx.enable = true;
+
     ## Create a proxy from <bucket>.web.garage.<domain> to localhost:3902 for
     ## each bucket that has `website = true`.
     services.nginx.virtualHosts =
diff --git a/services/fediversity/mastodon/default.nix b/services/fediversity/mastodon/default.nix
index 4fe5ef3..8c022ea 100644
--- a/services/fediversity/mastodon/default.nix
+++ b/services/fediversity/mastodon/default.nix
@@ -6,96 +6,107 @@
 }:
 
 let
-  inherit (lib) readFile;
+  inherit (lib) mkIf mkMerge readFile;
   inherit (pkgs) writeText;
 
 in
 {
   imports = [ ./options.nix ];
 
-  config = lib.mkIf config.fediversity.mastodon.enable {
-    #### garage setup
-    fediversity.garage = {
-      ensureBuckets = {
-        mastodon = {
-          website = true;
-          corsRules = {
-            enable = true;
-            allowedHeaders = [ "*" ];
-            allowedMethods = [ "GET" ];
-            allowedOrigins = [ "*" ];
-          };
-        };
-      };
-
-      ensureKeys = {
-        mastodon = {
-          inherit (config.fediversity.mastodon) s3AccessKeyFile s3SecretKeyFile;
-          ensureAccess = {
+  config = mkMerge [
+    (mkIf
+      (
+        config.fediversity.garage.enable
+        && config.fediversity.mastodon.s3AccessKeyFile != null
+        && config.fediversity.mastodon.s3SecretKeyFile != null
+      )
+      {
+        fediversity.garage = {
+          ensureBuckets = {
             mastodon = {
-              read = true;
-              write = true;
-              owner = true;
+              website = true;
+              corsRules = {
+                enable = true;
+                allowedHeaders = [ "*" ];
+                allowedMethods = [ "GET" ];
+                allowedOrigins = [ "*" ];
+              };
+            };
+          };
+
+          ensureKeys = {
+            mastodon = {
+              inherit (config.fediversity.mastodon) s3AccessKeyFile s3SecretKeyFile;
+              ensureAccess = {
+                mastodon = {
+                  read = true;
+                  write = true;
+                  owner = true;
+                };
+              };
             };
           };
         };
+      }
+    )
+
+    (mkIf config.fediversity.mastodon.enable {
+
+      services.mastodon.extraConfig = rec {
+        S3_ENABLED = "true";
+        # TODO: this shouldn't be hard-coded, it should come from the garage configuration
+        S3_ENDPOINT = config.fediversity.garage.api.url;
+        S3_REGION = "garage";
+        S3_BUCKET = "mastodon";
+        # use <S3_BUCKET>.<S3_ENDPOINT>
+        S3_OVERRIDE_PATH_STLE = "true";
+        S3_PROTOCOL = "http";
+        S3_ALIAS_HOST = config.fediversity.garage.web.domainForBucket S3_BUCKET;
+        # SEE: the last section in https://docs.joinmastodon.org/admin/optional/object-storage/
+        # TODO: can we set up ACLs with garage?
+        S3_PERMISSION = "";
       };
-    };
 
-    services.mastodon.extraConfig = rec {
-      S3_ENABLED = "true";
-      # TODO: this shouldn't be hard-coded, it should come from the garage configuration
-      S3_ENDPOINT = config.fediversity.garage.api.url;
-      S3_REGION = "garage";
-      S3_BUCKET = "mastodon";
-      # use <S3_BUCKET>.<S3_ENDPOINT>
-      S3_OVERRIDE_PATH_STLE = "true";
-      S3_PROTOCOL = "http";
-      S3_ALIAS_HOST = config.fediversity.garage.web.domainForBucket S3_BUCKET;
-      # SEE: the last section in https://docs.joinmastodon.org/admin/optional/object-storage/
-      # TODO: can we set up ACLs with garage?
-      S3_PERMISSION = "";
-    };
+      ## FIXME: secrets management; we should have a service that writes the
+      ## `.env` files based on all the secrets that we need to put there.
+      services.mastodon.extraEnvFiles = [
+        (writeText "s3AccessKey" ''
+          AWS_ACCESS_KEY_ID=${readFile config.fediversity.mastodon.s3AccessKeyFile}
+        '')
+        (writeText "s3SecretKey" ''
+          AWS_SECRET_ACCESS_KEY=${readFile config.fediversity.mastodon.s3SecretKeyFile}
+        '')
+      ];
 
-    ## FIXME: secrets management; we should have a service that writes the
-    ## `.env` files based on all the secrets that we need to put there.
-    services.mastodon.extraEnvFiles = [
-      (writeText "s3AccessKey" ''
-        AWS_ACCESS_KEY_ID=${readFile config.fediversity.mastodon.s3AccessKeyFile}
-      '')
-      (writeText "s3SecretKey" ''
-        AWS_SECRET_ACCESS_KEY=${readFile config.fediversity.mastodon.s3SecretKeyFile}
-      '')
-    ];
+      # open up access to the mastodon web interface. 80 is necessary if only for ACME
+      networking.firewall.allowedTCPPorts = [
+        80
+        443
+      ];
 
-    # open up access to the mastodon web interface. 80 is necessary if only for ACME
-    networking.firewall.allowedTCPPorts = [
-      80
-      443
-    ];
+      services.mastodon = {
+        enable = true;
 
-    services.mastodon = {
-      enable = true;
+        localDomain = config.fediversity.mastodon.domain;
+        configureNginx = true;
 
-      localDomain = config.fediversity.mastodon.domain;
-      configureNginx = true;
+        # from the documentation: recommended is the amount of your CPU cores minus
+        # one. but it also must be a positive integer
+        streamingProcesses = lib.max 1 (config.fediversity.temp.cores - 1);
 
-      # from the documentation: recommended is the amount of your CPU cores minus
-      # one. but it also must be a positive integer
-      streamingProcesses = lib.max 1 (config.fediversity.temp.cores - 1);
-
-      # TODO: configure a mailserver so this works
-      smtp = {
-        fromAddress = "noreply@${config.fediversity.mastodon.domain}";
-        createLocally = false;
+        # TODO: configure a mailserver so this works
+        smtp = {
+          fromAddress = "noreply@${config.fediversity.mastodon.domain}";
+          createLocally = false;
+        };
       };
-    };
 
-    security.acme = {
-      acceptTerms = true;
-      preliminarySelfsigned = true;
-      # TODO: configure a mailserver so we can set up acme
-      # defaults.email = "test@example.com";
-    };
-  };
+      security.acme = {
+        acceptTerms = true;
+        preliminarySelfsigned = true;
+        # TODO: configure a mailserver so we can set up acme
+        # defaults.email = "test@example.com";
+      };
+    })
+  ];
 }
diff --git a/services/fediversity/peertube/default.nix b/services/fediversity/peertube/default.nix
index cb46969..9fa86cf 100644
--- a/services/fediversity/peertube/default.nix
+++ b/services/fediversity/peertube/default.nix
@@ -1,124 +1,135 @@
 { config, lib, ... }:
 
 let
-  inherit (lib) mkIf readFile;
+  inherit (lib) mkIf mkMerge readFile;
 
 in
 {
   imports = [ ./options.nix ];
 
-  config = mkIf config.fediversity.peertube.enable {
-    networking.firewall.allowedTCPPorts = [
-      80
-      443
-
-      ## For Live streaming and Live streaming when RTMPS is enabled.
-      1935
-      1936
-    ];
-
-    fediversity.garage = {
-      ensureBuckets = {
-        peertube-videos = {
-          website = true;
-          # TODO: these are too broad, after getting everything works narrow it down to the domain we actually want
-          corsRules = {
-            enable = true;
-            allowedHeaders = [ "*" ];
-            allowedMethods = [ "GET" ];
-            allowedOrigins = [ "*" ];
-          };
-        };
-        # TODO: these are too broad, after getting everything works narrow it down to the domain we actually want
-        peertube-playlists = {
-          website = true;
-          corsRules = {
-            enable = true;
-            allowedHeaders = [ "*" ];
-            allowedMethods = [ "GET" ];
-            allowedOrigins = [ "*" ];
-          };
-        };
-      };
-
-      ensureKeys = {
-        peertube = {
-          inherit (config.fediversity.peertube) s3AccessKeyFile s3SecretKeyFile;
-          ensureAccess = {
+  config = mkMerge [
+    (mkIf
+      (
+        config.fediversity.garage.enable
+        && config.fediversity.peertube.s3AccessKeyFile != null
+        && config.fediversity.peertube.s3SecretKeyFile != null
+      )
+      {
+        fediversity.garage = {
+          ensureBuckets = {
             peertube-videos = {
-              read = true;
-              write = true;
-              owner = true;
+              website = true;
+              # TODO: these are too broad, after getting everything works narrow it down to the domain we actually want
+              corsRules = {
+                enable = true;
+                allowedHeaders = [ "*" ];
+                allowedMethods = [ "GET" ];
+                allowedOrigins = [ "*" ];
+              };
             };
+            # TODO: these are too broad, after getting everything works narrow it down to the domain we actually want
             peertube-playlists = {
-              read = true;
-              write = true;
-              owner = true;
+              website = true;
+              corsRules = {
+                enable = true;
+                allowedHeaders = [ "*" ];
+                allowedMethods = [ "GET" ];
+                allowedOrigins = [ "*" ];
+              };
+            };
+          };
+
+          ensureKeys = {
+            peertube = {
+              inherit (config.fediversity.peertube) s3AccessKeyFile s3SecretKeyFile;
+              ensureAccess = {
+                peertube-videos = {
+                  read = true;
+                  write = true;
+                  owner = true;
+                };
+                peertube-playlists = {
+                  read = true;
+                  write = true;
+                  owner = true;
+                };
+              };
             };
           };
         };
-      };
-    };
+      }
+    )
 
-    services.peertube = {
-      enable = true;
-      localDomain = config.fediversity.peertube.domain;
+    (mkIf config.fediversity.peertube.enable {
+      networking.firewall.allowedTCPPorts = [
+        80
+        443
 
-      # TODO: in most of nixpkgs, these are true by default. upstream that unless there's a good reason not to.
-      redis.createLocally = true;
-      database.createLocally = true;
+        ## For Live streaming and Live streaming when RTMPS is enabled.
+        1935
+        1936
+      ];
 
-      secrets.secretsFile = config.fediversity.peertube.secretsFile;
+      services.peertube = {
+        enable = true;
+        localDomain = config.fediversity.peertube.domain;
 
-      settings = {
-        object_storage = {
-          enabled = true;
-          endpoint = config.fediversity.garage.api.url;
-          region = "garage";
-          upload_acl.public = null; # Garage does not support ACL
-          upload_acl.private = null; # Garage does not support ACL
+        # TODO: in most of nixpkgs, these are true by default. upstream that unless there's a good reason not to.
+        redis.createLocally = true;
+        database.createLocally = true;
 
-          # not supported by garage
-          # SEE: https://garagehq.deuxfleurs.fr/documentation/connect/apps/#peertube
-          proxy.proxyify_private_files = false;
+        secrets.secretsFile = config.fediversity.peertube.secretsFile;
 
-          web_videos = rec {
-            bucket_name = "peertube-videos";
-            prefix = "";
-            base_url = config.fediversity.garage.web.urlForBucket bucket_name;
-          };
-          videos = rec {
-            bucket_name = "peertube-videos";
-            prefix = "";
-            base_url = config.fediversity.garage.web.urlForBucket bucket_name;
-          };
-          streaming_playlists = rec {
-            bucket_name = "peertube-playlists";
-            prefix = "";
-            base_url = config.fediversity.garage.web.urlForBucket bucket_name;
+        settings = {
+          object_storage = {
+            enabled = true;
+            endpoint = config.fediversity.garage.api.url;
+            region = "garage";
+            upload_acl.public = null; # Garage does not support ACL
+            upload_acl.private = null; # Garage does not support ACL
+
+            # not supported by garage
+            # SEE: https://garagehq.deuxfleurs.fr/documentation/connect/apps/#peertube
+            proxy.proxyify_private_files = false;
+
+            web_videos = rec {
+              bucket_name = "peertube-videos";
+              prefix = "";
+              base_url = config.fediversity.garage.web.urlForBucket bucket_name;
+            };
+            videos = rec {
+              bucket_name = "peertube-videos";
+              prefix = "";
+              base_url = config.fediversity.garage.web.urlForBucket bucket_name;
+            };
+            streaming_playlists = rec {
+              bucket_name = "peertube-playlists";
+              prefix = "";
+              base_url = config.fediversity.garage.web.urlForBucket bucket_name;
+            };
           };
         };
+        serviceEnvironmentFile = "/etc/peertube-env";
       };
-      serviceEnvironmentFile = "/etc/peertube-env";
-    };
 
-    ## FIXME: secrets management; we should have a service that writes the
-    ## `.env` files based on all the secrets that we need to put there.
-    environment.etc.peertube-env.text = ''
-      AWS_ACCESS_KEY_ID=${readFile config.fediversity.peertube.s3AccessKeyFile}
-      AWS_SECRET_ACCESS_KEY=${readFile config.fediversity.peertube.s3SecretKeyFile}
-    '';
+      ## FIXME: secrets management; we should have a service that writes the
+      ## `.env` files based on all the secrets that we need to put there.
+      environment.etc.peertube-env.text = ''
+        AWS_ACCESS_KEY_ID=${readFile config.fediversity.peertube.s3AccessKeyFile}
+        AWS_SECRET_ACCESS_KEY=${readFile config.fediversity.peertube.s3SecretKeyFile}
+      '';
 
-    ## Proxying through Nginx
+      ## Proxying through Nginx
 
-    services.peertube = {
-      configureNginx = true;
-      listenWeb = 443;
-      enableWebHttps = true;
-    };
-    services.nginx.virtualHosts.${config.services.peertube.localDomain} = {
-      forceSSL = true;
-      enableACME = true;
-    };
-  };
+      services.peertube = {
+        configureNginx = true;
+        listenWeb = 443;
+        enableWebHttps = true;
+      };
+      services.nginx.virtualHosts.${config.services.peertube.localDomain} = {
+        forceSSL = true;
+        enableACME = true;
+      };
+    })
+  ];
 }
diff --git a/services/fediversity/pixelfed/default.nix b/services/fediversity/pixelfed/default.nix
index 3198929..2c3c2b7 100644
--- a/services/fediversity/pixelfed/default.nix
+++ b/services/fediversity/pixelfed/default.nix
@@ -6,89 +6,100 @@
 }:
 
 let
-  inherit (lib) mkIf readFile;
+  inherit (lib) mkIf mkMerge readFile;
 
 in
 {
   imports = [ ./options.nix ];
 
-  config = mkIf config.fediversity.pixelfed.enable {
-    fediversity.garage = {
-      ensureBuckets = {
-        pixelfed = {
-          website = true;
-          # TODO: these are too broad, after getting everything works narrow it down to the domain we actually want
-          corsRules = {
-            enable = true;
-            allowedHeaders = [ "*" ];
-            allowedMethods = [ "GET" ];
-            allowedOrigins = [ "*" ];
-          };
-        };
-      };
-
-      ensureKeys = {
-        pixelfed = {
-          inherit (config.fediversity.pixelfed) s3AccessKeyFile s3SecretKeyFile;
-          ensureAccess = {
+  config = mkMerge [
+    (mkIf
+      (
+        config.fediversity.garage.enable
+        && config.fediversity.pixelfed.s3AccessKeyFile != null
+        && config.fediversity.pixelfed.s3SecretKeyFile != null
+      )
+      {
+        fediversity.garage = {
+          ensureBuckets = {
             pixelfed = {
-              read = true;
-              write = true;
-              owner = true;
+              website = true;
+              # TODO: these are too broad, after getting everything works narrow it down to the domain we actually want
+              corsRules = {
+                enable = true;
+                allowedHeaders = [ "*" ];
+                allowedMethods = [ "GET" ];
+                allowedOrigins = [ "*" ];
+              };
+            };
+          };
+
+          ensureKeys = {
+            pixelfed = {
+              inherit (config.fediversity.pixelfed) s3AccessKeyFile s3SecretKeyFile;
+              ensureAccess = {
+                pixelfed = {
+                  read = true;
+                  write = true;
+                  owner = true;
+                };
+              };
             };
           };
         };
+      }
+    )
+
+    (mkIf config.fediversity.pixelfed.enable {
+      services.pixelfed = {
+        enable = true;
+        domain = config.fediversity.pixelfed.domain;
+
+        ## FIXME: secrets management; we should have a service that writes the
+        ## `.env` file based on all the secrets that we need to put there.
+        secretFile = pkgs.writeText "secrets.env" ''
+          APP_KEY=adKK9EcY8Hcj3PLU7rzG9rJ6KKTOtYfA
+          AWS_ACCESS_KEY_ID=${readFile config.fediversity.pixelfed.s3AccessKeyFile}
+          AWS_SECRET_ACCESS_KEY=${readFile config.fediversity.pixelfed.s3SecretKeyFile}
+        '';
+
+        ## Taeer feels like this way of configuring Nginx is odd; there should
+        ## instead be a `services.pixefed.nginx.enable` option and the actual Nginx
+        ## configuration should be in `services.nginx`. See eg. `pretix`.
+        ##
+        ## TODO: If that indeed makes sense, upstream.
+        nginx = {
+          forceSSL = true;
+          enableACME = true;
+          # locations."/public/".proxyPass = "${config.fediversity.garage.web.urlForBucket "pixelfed"}/public/";
+        };
       };
-    };
 
-    services.pixelfed = {
-      enable = true;
-      domain = config.fediversity.pixelfed.domain;
+      services.pixelfed.settings = {
+        ## NOTE: This depends on the targets, eg. universities might want control
+        ## over who has an account. We probably want a universal
+        ## `fediversity.openRegistration` option.
+        OPEN_REGISTRATION = true;
 
-      ## FIXME: secrets management; we should have a service that writes the
-      ## `.env` file based on all the secrets that we need to put there.
-      secretFile = pkgs.writeText "secrets.env" ''
-        APP_KEY=adKK9EcY8Hcj3PLU7rzG9rJ6KKTOtYfA
-        AWS_ACCESS_KEY_ID=${readFile config.fediversity.pixelfed.s3AccessKeyFile}
-        AWS_SECRET_ACCESS_KEY=${readFile config.fediversity.pixelfed.s3SecretKeyFile}
-      '';
-
-      ## Taeer feels like this way of configuring Nginx is odd; there should
-      ## instead be a `services.pixefed.nginx.enable` option and the actual Nginx
-      ## configuration should be in `services.nginx`. See eg. `pretix`.
-      ##
-      ## TODO: If that indeed makes sense, upstream.
-      nginx = {
-        forceSSL = true;
-        enableACME = true;
-        # locations."/public/".proxyPass = "${config.fediversity.garage.web.urlForBucket "pixelfed"}/public/";
+        FILESYSTEM_CLOUD = "s3";
+        PF_ENABLE_CLOUD = true;
+        AWS_DEFAULT_REGION = "garage";
+        AWS_URL = config.fediversity.garage.web.urlForBucket "pixelfed";
+        AWS_BUCKET = "pixelfed";
+        AWS_ENDPOINT = config.fediversity.garage.api.url;
+        AWS_USE_PATH_STYLE_ENDPOINT = false;
       };
-    };
 
-    services.pixelfed.settings = {
-      ## NOTE: This depends on the targets, eg. universities might want control
-      ## over who has an account. We probably want a universal
-      ## `fediversity.openRegistration` option.
-      OPEN_REGISTRATION = true;
+      ## Only ever run `pixelfed-data-setup` after `ensure-garage` has done its job.
+      ## Otherwise, everything crashed dramatically.
+      systemd.services.pixelfed-data-setup = {
+        after = [ "ensure-garage.service" ];
+      };
 
-      FILESYSTEM_CLOUD = "s3";
-      PF_ENABLE_CLOUD = true;
-      AWS_DEFAULT_REGION = "garage";
-      AWS_URL = config.fediversity.garage.web.urlForBucket "pixelfed";
-      AWS_BUCKET = "pixelfed";
-      AWS_ENDPOINT = config.fediversity.garage.api.url;
-      AWS_USE_PATH_STYLE_ENDPOINT = false;
-    };
-
-    ## Only ever run `pixelfed-data-setup` after `ensure-garage` has done its job.
-    ## Otherwise, everything crashed dramatically.
-    systemd.services.pixelfed-data-setup = {
-      after = [ "ensure-garage.service" ];
-    };
-
-    networking.firewall.allowedTCPPorts = [
-      80
-      443
-    ];
-  };
+      networking.firewall.allowedTCPPorts = [
+        80
+        443
+      ];
+    })
+  ];
 }
diff --git a/services/fediversity/sharedOptions.nix b/services/fediversity/sharedOptions.nix
index 10ddd5c..413e2a7 100644
--- a/services/fediversity/sharedOptions.nix
+++ b/services/fediversity/sharedOptions.nix
@@ -17,21 +17,25 @@ in
   enable = mkEnableOption "Enable a ${serviceDocName} server on the machine";
 
   s3AccessKeyFile = mkOption {
-    type = types.path;
+    type = types.nullOr types.path;
     description = ''
       S3 access key for ${serviceDocName}'s bucket/s
 
-      In AWS CLI, this would be AWS_ACCESS_KEY_ID.
+      In AWS CLI, this would be AWS_ACCESS_KEY_ID. The S3 bucket is only created
+      when non-`null`.
     '';
+    default = null;
   };
 
   s3SecretKeyFile = mkOption {
-    type = types.path;
+    type = types.nullOr types.path;
     description = ''
       S3 secret key for ${serviceDocName}'s bucket/s
 
-      In AWS CLI, this would be AWS_SECRET_ACCESS_KEY.
+      In AWS CLI, this would be AWS_SECRET_ACCESS_KEY. The S3 bucket is only
+      created when non-`null`.
     '';
+    default = null;
   };
 
   domain = mkOption {
diff --git a/services/tests/peertube.nix b/services/tests/peertube.nix
index 23b002f..2671138 100644
--- a/services/tests/peertube.nix
+++ b/services/tests/peertube.nix
@@ -197,8 +197,8 @@ pkgs.nixosTest {
         systemd.services.postgresql.serviceConfig.TimeoutSec = lib.mkForce 3600;
 
         environment.variables = {
-          AWS_ACCESS_KEY_ID = config.fediversity.garage.ensureKeys.peertube.id;
-          AWS_SECRET_ACCESS_KEY = config.fediversity.garage.ensureKeys.peertube.secret;
+          AWS_ACCESS_KEY_ID = "$(cat ${config.fediversity.peertube.s3AccessKeyFile})";
+          AWS_SECRET_ACCESS_KEY = "$(cat ${config.fediversity.peertube.s3SecretKeyFile})";
           PT_INITIAL_ROOT_PASSWORD = "testtest";
         };
       };
diff --git a/services/vm/garage-vm.nix b/services/vm/garage-vm.nix
index 174d23c..8cad659 100644
--- a/services/vm/garage-vm.nix
+++ b/services/vm/garage-vm.nix
@@ -12,6 +12,8 @@ in
 {
   imports = [ (modulesPath + "/virtualisation/qemu-vm.nix") ];
 
+  fediversity.garage.enable = true;
+
   services.nginx.virtualHosts =
     let
       value = {
diff --git a/services/vm/mastodon-vm.nix b/services/vm/mastodon-vm.nix
index 5a9daf2..095f822 100644
--- a/services/vm/mastodon-vm.nix
+++ b/services/vm/mastodon-vm.nix
@@ -1,6 +1,7 @@
 {
   modulesPath,
   lib,
+  pkgs,
   config,
   ...
 }:
@@ -11,9 +12,13 @@
   config = lib.mkMerge [
     {
       fediversity = {
-        enable = true;
         domain = "localhost";
-        mastodon.enable = true;
+        mastodon = {
+          enable = true;
+
+          s3AccessKeyFile = pkgs.writeText "s3AccessKey" "GK3515373e4c851ebaad366558";
+          s3SecretKeyFile = pkgs.writeText "s3SecretKey" "7d37d093435a41f2aab8f13c19ba067d9776c90215f56614adad6ece597dbb34";
+        };
 
         temp.cores = config.virtualisation.cores;
       };
diff --git a/services/vm/peertube-vm.nix b/services/vm/peertube-vm.nix
index 758d64b..7aedbc3 100644
--- a/services/vm/peertube-vm.nix
+++ b/services/vm/peertube-vm.nix
@@ -8,13 +8,12 @@
   imports = [ (modulesPath + "/virtualisation/qemu-vm.nix") ];
 
   fediversity = {
-    enable = true;
     domain = "localhost";
     peertube = {
       enable = true;
-      secretsFile = pkgs.writeText "secret" ''
-        574e093907d1157ac0f8e760a6deb1035402003af5763135bae9cbd6abe32b24
-      '';
+      secretsFile = pkgs.writeText "secret" "574e093907d1157ac0f8e760a6deb1035402003af5763135bae9cbd6abe32b24";
+      s3AccessKeyFile = pkgs.writeText "s3AccessKey" "GK1f9feea9960f6f95ff404c9b";
+      s3SecretKeyFile = pkgs.writeText "s3SecretKey" "7295c4201966a02c2c3d25b5cea4a5ff782966a2415e3a196f91924631191395";
     };
   };
 
diff --git a/services/vm/pixelfed-vm.nix b/services/vm/pixelfed-vm.nix
index 927d042..8c35aee 100644
--- a/services/vm/pixelfed-vm.nix
+++ b/services/vm/pixelfed-vm.nix
@@ -1,4 +1,9 @@
-{ lib, modulesPath, ... }:
+{
+  lib,
+  pkgs,
+  modulesPath,
+  ...
+}:
 
 let
   inherit (lib) mkVMOverride;
@@ -9,9 +14,13 @@ in
   imports = [ (modulesPath + "/virtualisation/qemu-vm.nix") ];
 
   fediversity = {
-    enable = true;
     domain = "localhost";
-    pixelfed.enable = true;
+    pixelfed = {
+      enable = true;
+
+      s3AccessKeyFile = pkgs.writeText "s3AccessKey" "GKb5615457d44214411e673b7b";
+      s3SecretKeyFile = pkgs.writeText "s3SecretKey" "5be6799a88ca9b9d813d1a806b64f15efa49482dbe15339ddfaf7f19cf434987";
+    };
   };
 
   services.pixelfed = {

From 2ee563f5d9987e76b25dc7a7e08df964e27e8834 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nicolas=20=E2=80=9CNiols=E2=80=9D=20Jeannerod?=
 <nicolas.jeannerod@moduscreate.com>
Date: Sat, 15 Feb 2025 11:22:55 +0100
Subject: [PATCH 16/16] Fix Pixelfed

---
 services/fediversity/pixelfed/default.nix | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/services/fediversity/pixelfed/default.nix b/services/fediversity/pixelfed/default.nix
index 2c3c2b7..7df5352 100644
--- a/services/fediversity/pixelfed/default.nix
+++ b/services/fediversity/pixelfed/default.nix
@@ -51,6 +51,14 @@ in
     )
 
     (mkIf config.fediversity.pixelfed.enable {
+      ## NOTE: Pixelfed as packaged in nixpkgs has a permission issue that prevents Nginx
+      ## from being able to serving the images. We fix it here, but this should be
+      ## upstreamed. See https://github.com/NixOS/nixpkgs/issues/235147
+      services.pixelfed.package = pkgs.pixelfed.overrideAttrs (old: {
+        patches = (old.patches or [ ]) ++ [ ./group-permissions.patch ];
+      });
+      users.users.nginx.extraGroups = [ "pixelfed" ];
+
       services.pixelfed = {
         enable = true;
         domain = config.fediversity.pixelfed.domain;