diff --git a/fediversity/default.nix b/fediversity/default.nix index 46ee05d..379b1b2 100644 --- a/fediversity/default.nix +++ b/fediversity/default.nix @@ -2,7 +2,7 @@ let inherit (builtins) toString; - inherit (lib) mkOption mkEnableOption; + inherit (lib) mkOption mkEnableOption mkForce; inherit (lib.types) types; in { @@ -64,17 +64,17 @@ in { type = types.str; default = "web.garage.${config.fediversity.domain}"; }; - port = mkOption { + internalPort = mkOption { type = types.int; default = 3902; }; - rootDomainAndPort = mkOption { - type = types.str; - default = "${config.fediversity.internal.garage.web.rootDomain}:${toString config.fediversity.internal.garage.web.port}"; - }; - urlFor = mkOption { + domainForBucket = mkOption { type = types.functionTo types.str; - default = bucket: "http://${bucket}.${config.fediversity.internal.garage.web.rootDomainAndPort}"; + 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}"; }; }; }; @@ -100,4 +100,19 @@ in { }; }; }; + + config = { + ## FIXME: This should clearly go somewhere else; and we should have a + ## `staging` vs. `production` setting somewhere. + security.acme = { + acceptTerms = true; + 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. However, this will quickly stop working once + ## we go to multi-machines deployment. + fediversity.internal.garage.api.domain = mkForce "s3.garage.localhost"; + }; } diff --git a/fediversity/garage.nix b/fediversity/garage.nix index 84af662..0dd0d7f 100644 --- a/fediversity/garage.nix +++ b/fediversity/garage.nix @@ -15,6 +15,7 @@ let inherit (lib) types mkOption mkEnableOption optionalString concatStringsSep; inherit (lib.strings) escapeShellArg; cfg = config.services.garage; + fedicfg = config.fediversity.internal.garage; concatMapAttrs = scriptFn: attrset: concatStringsSep "\n" (lib.mapAttrsToList scriptFn attrset); ensureBucketScriptFn = bucket: { website, aliases, corsRules }: let @@ -42,7 +43,7 @@ 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 ${config.fediversity.internal.garage.api.url} s3api put-bucket-cors --bucket ${bucketArg} --cors-configuration ${corsRulesJSON} + aws --region ${cfg.settings.s3_api.s3_region} --endpoint-url ${fedicfg.api.url} s3api put-bucket-cors --bucket ${bucketArg} --cors-configuration ${corsRulesJSON} garage bucket deny --read --write --owner ${bucketArg} --key tmp ''} ''; @@ -52,7 +53,8 @@ let ${escapeShellArg bucket} --key ${escapeShellArg key} ''; ensureKeyScriptFn = key: {id, secret, ensureAccess}: '' - garage key import --yes -n ${escapeShellArg key} ${escapeShellArg id} ${escapeShellArg secret} + ## 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} || : ${concatMapAttrs (ensureAccessScriptFn key) ensureAccess} ''; ensureKeysScript = concatMapAttrs ensureKeyScriptFn cfg.ensureKeys; @@ -132,25 +134,10 @@ in }; config = lib.mkIf config.fediversity.enable { - virtualisation.diskSize = 2048; - virtualisation.forwardPorts = [ - { - from = "host"; - host.port = config.fediversity.internal.garage.rpc.port; - guest.port = config.fediversity.internal.garage.rpc.port; - } - { - from = "host"; - host.port = config.fediversity.internal.garage.web.port; - guest.port = config.fediversity.internal.garage.web.port; - } - ]; - environment.systemPackages = [ pkgs.minio-client pkgs.awscli ]; networking.firewall.allowedTCPPorts = [ - config.fediversity.internal.garage.rpc.port - config.fediversity.internal.garage.web.port + fedicfg.rpc.port ]; services.garage = { enable = true; @@ -160,17 +147,30 @@ 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 config.fediversity.internal.garage.rpc.port}"; - rpc_public_addr = "[::1]:${toString config.fediversity.internal.garage.rpc.port}"; - s3_api.api_bind_addr = "[::]:${toString config.fediversity.internal.garage.api.port}"; - s3_web.bind_addr = "[::]:${toString config.fediversity.internal.garage.web.port}"; - s3_web.root_domain = ".${config.fediversity.internal.garage.web.rootDomain}"; + 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}"; index = "index.html"; s3_api.s3_region = "garage"; - s3_api.root_domain = ".${config.fediversity.internal.garage.api.domain}"; + s3_api.root_domain = ".${fedicfg.api.domain}"; }; }; + + services.nginx.virtualHosts.${fedicfg.web.rootDomain} = { + forceSSL = true; + enableACME = true; + serverAliases = lib.mapAttrsToList (bucket: _: fedicfg.web.domainForBucket bucket) cfg.ensureBuckets; ## TODO: use wildcard certificates? + locations."/" = { + proxyPass = "http://localhost:3902"; + extraConfig = '' + proxy_set_header Host $host; + ''; + }; + }; + systemd.services.ensure-garage = { after = [ "garage.service" ]; wantedBy = [ "garage.service" ]; @@ -183,7 +183,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 ${config.fediversity.internal.garage.api.url}; do sleep 1; done + until ${pkgs.curl}/bin/curl -sio /dev/null ${fedicfg.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) @@ -197,7 +197,8 @@ in # XXX: this is a hack because we want to write to the buckets here but we're not guaranteed any access keys # TODO: generate this key here rather than using a well-known key - garage key import --yes -n tmp ${snakeoil_key.id} ${snakeoil_key.secret} + # TODO: if the key already exists, we get an error; hacked with this `|| :` which needs to be removed + garage key import --yes -n tmp ${snakeoil_key.id} ${snakeoil_key.secret} || : export AWS_ACCESS_KEY_ID=${snakeoil_key.id}; export AWS_SECRET_ACCESS_KEY=${snakeoil_key.secret}; diff --git a/fediversity/mastodon.nix b/fediversity/mastodon.nix index 62599b5..d19edca 100644 --- a/fediversity/mastodon.nix +++ b/fediversity/mastodon.nix @@ -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_HOSTNAME = config.fediversity.internal.garage.web.rootDomainAndPort; + S3_HOSTNAME = config.fediversity.internal.garage.web.rootDomain; # by default it tries to use "/" S3_ALIAS_HOST = "${S3_BUCKET}.${S3_HOSTNAME}"; # SEE: the last section in https://docs.joinmastodon.org/admin/optional/object-storage/ diff --git a/fediversity/peertube.nix b/fediversity/peertube.nix index 88d26e1..03e9e71 100644 --- a/fediversity/peertube.nix +++ b/fediversity/peertube.nix @@ -74,17 +74,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.urlFor bucket_name; + base_url = config.fediversity.internal.garage.web.urlForBucket bucket_name; }; videos = rec { bucket_name = "peertube-videos"; prefix = ""; - base_url = config.fediversity.internal.garage.web.urlFor bucket_name; + base_url = config.fediversity.internal.garage.web.urlForBucket bucket_name; }; streaming_playlists = rec { bucket_name = "peertube-playlists"; prefix = ""; - base_url = config.fediversity.internal.garage.web.urlFor bucket_name; + base_url = config.fediversity.internal.garage.web.urlForBucket bucket_name; }; }; }; diff --git a/fediversity/pixelfed.nix b/fediversity/pixelfed.nix index 1edc914..cef5d6d 100644 --- a/fediversity/pixelfed.nix +++ b/fediversity/pixelfed.nix @@ -38,16 +38,37 @@ lib.mkIf (config.fediversity.enable && config.fediversity.pixelfed.enable) { 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.internal.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.internal.garage.web.urlFor "pixelfed"; + AWS_URL = config.fediversity.internal.garage.web.urlForBucket "pixelfed"; AWS_BUCKET = "pixelfed"; AWS_ENDPOINT = config.fediversity.internal.garage.api.url; AWS_USE_PATH_STYLE_ENDPOINT = false; @@ -59,7 +80,5 @@ lib.mkIf (config.fediversity.enable && config.fediversity.pixelfed.enable) { after = [ "ensure-garage.service" ]; }; - services.pixelfed.package = pkgs.pixelfed.overrideAttrs (old: { - patches = (old.patches or [ ]) ++ [ ./pixelfed-group-permissions.patch ]; - }); + networking.firewall.allowedTCPPorts = [ 80 443 ]; } diff --git a/flake.lock b/flake.lock index 60b501f..d25c01c 100644 --- a/flake.lock +++ b/flake.lock @@ -16,9 +16,43 @@ "type": "github" } }, + "nixpkgs-latest": { + "locked": { + "lastModified": 1727220152, + "narHash": "sha256-6ezRTVBZT25lQkvaPrfJSxYLwqcbNWm6feD/vG1FO0o=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "24959f933187217890b206788a85bfa73ba75949", + "type": "github" + }, + "original": { + "owner": "nixos", + "repo": "nixpkgs", + "type": "github" + } + }, + "pixelfed": { + "flake": false, + "locked": { + "lastModified": 1719823820, + "narHash": "sha256-CKjqnxp7p2z/13zfp4HQ1OAmaoUtqBKS6HFm6TV8Jwg=", + "owner": "pixelfed", + "repo": "pixelfed", + "rev": "4c245cf429330d01fcb8ebeb9aa8c84a9574a645", + "type": "github" + }, + "original": { + "owner": "pixelfed", + "ref": "v0.12.3", + "repo": "pixelfed", + "type": "github" + } + }, "root": { "inputs": { - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs", + "nixpkgs-latest": "nixpkgs-latest", + "pixelfed": "pixelfed" } } }, diff --git a/flake.nix b/flake.nix index 737ad31..08d1cdc 100644 --- a/flake.nix +++ b/flake.nix @@ -3,20 +3,38 @@ inputs = { nixpkgs.url = "github:radvendii/nixpkgs/nixos_rebuild_tests"; + nixpkgs-latest.url = "github:nixos/nixpkgs"; + pixelfed = { + url = "github:pixelfed/pixelfed?ref=v0.12.3"; + flake = false; + }; }; - outputs = { self, nixpkgs }: + outputs = { self, nixpkgs, nixpkgs-latest, pixelfed }: let system = "x86_64-linux"; pkgs = nixpkgs.legacyPackages.${system}; + pkgsLatest = nixpkgs-latest.legacyPackages.${system}; + bleedingFediverseOverlay = (self: super: { + pixelfed = pkgsLatest.pixelfed.overrideAttrs (old: { + src = pixelfed; + patches = (old.patches or [ ]) ++ [ ./fediversity/pixelfed-group-permissions.patch ]; + }); + ## TODO: give mastodon, peertube the same treatment + }); in { nixosModules = { + ## Bleeding-edge fediverse packages + bleedingFediverse = { + nixpkgs.overlays = [ bleedingFediverseOverlay ]; + }; ## Fediversity modules fediversity = import ./fediversity; ## VM-specific modules interactive-vm = import ./vm/interactive-vm.nix; + garage-vm = import ./vm/garage-vm.nix; mastodon-vm = import ./vm/mastodon-vm.nix; peertube-vm = import ./vm/peertube-vm.nix; pixelfed-vm = import ./vm/pixelfed-vm.nix; @@ -25,24 +43,44 @@ nixosConfigurations = { mastodon = nixpkgs.lib.nixosSystem { inherit system; - modules = with self.nixosModules; [ fediversity interactive-vm mastodon-vm ]; + modules = with self.nixosModules; [ + bleedingFediverse + fediversity + interactive-vm + garage-vm + mastodon-vm + ]; }; peertube = nixpkgs.lib.nixosSystem { inherit system; - modules = with self.nixosModules; [ fediversity interactive-vm peertube-vm ]; + modules = with self.nixosModules; [ + bleedingFediverse + fediversity + interactive-vm + garage-vm + peertube-vm + ]; }; pixelfed = nixpkgs.lib.nixosSystem { inherit system; - modules = with self.nixosModules; [ fediversity interactive-vm pixelfed-vm ]; + modules = with self.nixosModules; [ + bleedingFediverse + fediversity + interactive-vm + garage-vm + pixelfed-vm + ]; }; all = nixpkgs.lib.nixosSystem { inherit system; modules = with self.nixosModules; [ + bleedingFediverse fediversity interactive-vm + garage-vm peertube-vm pixelfed-vm mastodon-vm diff --git a/tests/mastodon-garage.nix b/tests/mastodon-garage.nix index 672b70f..c02fe7d 100644 --- a/tests/mastodon-garage.nix +++ b/tests/mastodon-garage.nix @@ -37,7 +37,7 @@ pkgs.nixosTest { nodes = { server = { config, ... }: { virtualisation.memorySize = lib.mkVMOverride 4096; - imports = with self.nixosModules; [ mastodon-vm ]; + imports = with self.nixosModules; [ bleedingFediverse garage-vm mastodon-vm ]; # TODO: pair down environment.systemPackages = with pkgs; [ python3 diff --git a/tests/pixelfed-garage.nix b/tests/pixelfed-garage.nix index b25bc66..92ef721 100644 --- a/tests/pixelfed-garage.nix +++ b/tests/pixelfed-garage.nix @@ -136,7 +136,7 @@ pkgs.nixosTest { memorySize = lib.mkVMOverride 8192; cores = 8; }; - imports = with self.nixosModules; [ pixelfed-vm ]; + imports = with self.nixosModules; [ bleedingFediverse garage-vm pixelfed-vm ]; # TODO: pair down environment.systemPackages = with pkgs; [ python3 @@ -152,6 +152,8 @@ pkgs.nixosTest { POST_MEDIA = ./fediversity.png; AWS_ACCESS_KEY_ID = config.services.garage.ensureKeys.pixelfed.id; AWS_SECRET_ACCESS_KEY = config.services.garage.ensureKeys.pixelfed.secret; + ## without this we get frivolous errors in the logs + MC_REGION = "garage"; }; # chrome does not like being run as root users.users.selenium = { @@ -202,7 +204,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.urlFor "pixelfed"}"): + if not src.startswith("${nodes.server.fediversity.internal.garage.web.urlForBucket "pixelfed"}"): raise Exception("image does not come from garage") ''; } diff --git a/vm/garage-vm.nix b/vm/garage-vm.nix new file mode 100644 index 0000000..8deb49f --- /dev/null +++ b/vm/garage-vm.nix @@ -0,0 +1,32 @@ +{ lib, config, modulesPath, ... }: + +let + inherit (lib) mkVMOverride; + + fedicfg = config.fediversity.internal.garage; + +in { + imports = [ + ../fediversity + (modulesPath + "/virtualisation/qemu-vm.nix") + ]; + + services.nginx.virtualHosts.${fedicfg.web.rootDomain} = { + forceSSL = mkVMOverride false; + enableACME = mkVMOverride false; + }; + + virtualisation.diskSize = 2048; + virtualisation.forwardPorts = [ + { + from = "host"; + host.port = fedicfg.rpc.port; + guest.port = fedicfg.rpc.port; + } + { + from = "host"; + host.port = fedicfg.web.internalPort; + guest.port = fedicfg.web.internalPort; + } + ]; +} diff --git a/vm/pixelfed-vm.nix b/vm/pixelfed-vm.nix index 8f97180..3353648 100644 --- a/vm/pixelfed-vm.nix +++ b/vm/pixelfed-vm.nix @@ -1,5 +1,9 @@ -{ pkgs, modulesPath, ... }: { +{ pkgs, lib, modulesPath, ... }: +let + inherit (lib) mkVMOverride; + +in { imports = [ ../fediversity (modulesPath + "/virtualisation/qemu-vm.nix") @@ -11,22 +15,16 @@ pixelfed.enable = true; }; - networking.firewall.allowedTCPPorts = [ 80 ]; services.pixelfed = { - # TODO: secrets management! - secretFile = pkgs.writeText "secrets.env" '' - APP_KEY=adKK9EcY8Hcj3PLU7rzG9rJ6KKTOtYfA - ''; settings = { - OPEN_REGISTRATION = true; FORCE_HTTPS_URLS = false; }; - # I feel like this should have an `enable` option and be configured via `services.nginx` rather than mirroring those options in services.pixelfed.nginx - # TODO: If that indeed makes sense, upstream it. nginx = { - # locations."/public/".proxyPass = "${config.fediversity.internal.garage.web.urlFor "pixelfed"}/public/"; + forceSSL = mkVMOverride false; + enableACME = mkVMOverride false; }; }; + virtualisation.memorySize = 2048; virtualisation.forwardPorts = [ {