From 7007da177572c387d31f3b396e84eada5c0664f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20=E2=80=9CNiols=E2=80=9D=20Jeannerod?= Date: Mon, 11 Nov 2024 17:25:42 +0100 Subject: [PATCH] Format everything, RFC-style --- deploy.nix | 2 +- fediversity/default.nix | 7 +- fediversity/garage.nix | 221 ++++++++++++++++++------------ fediversity/mastodon.nix | 14 +- fediversity/peertube.nix | 14 +- fediversity/pixelfed.nix | 12 +- flake.nix | 206 ++++++++++++++-------------- installer.nix | 65 ++++----- tests/mastodon-garage.nix | 232 ++++++++++++++++---------------- tests/pixelfed-garage.nix | 276 ++++++++++++++++++++------------------ tests/rebuildableTest.nix | 118 +++++++++------- vm/garage-vm.nix | 18 ++- vm/interactive-vm.nix | 13 +- vm/mastodon-vm.nix | 8 +- vm/peertube-vm.nix | 3 +- vm/pixelfed-vm.nix | 10 +- 16 files changed, 684 insertions(+), 535 deletions(-) diff --git a/deploy.nix b/deploy.nix index 5604488..f927baa 100644 --- a/deploy.nix +++ b/deploy.nix @@ -10,4 +10,4 @@ writeShellApplication { "$result"/bin/switch-to-configuration switch EOF ''; -} +} diff --git a/fediversity/default.nix b/fediversity/default.nix index 90f7a02..6fe796a 100644 --- a/fediversity/default.nix +++ b/fediversity/default.nix @@ -5,7 +5,8 @@ let inherit (lib) mkOption mkEnableOption mkForce; inherit (lib.types) types; -in { +in +{ imports = [ ./garage.nix ./mastodon.nix @@ -33,7 +34,7 @@ in { temp = mkOption { description = "options that are only used while developing; should be removed eventually"; - default = {}; + default = { }; type = types.submodule { options = { cores = mkOption { @@ -51,7 +52,7 @@ in { internal = mkOption { description = "options that are only meant to be used internally; change at your own risk"; - default = {}; + default = { }; type = types.submodule { options = { garage = { diff --git a/fediversity/garage.nix b/fediversity/garage.nix index 3badc0c..758a151 100644 --- a/fediversity/garage.nix +++ b/fediversity/garage.nix @@ -8,27 +8,49 @@ let in # TODO: expand to a multi-machine setup -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: let inherit (builtins) toString; - inherit (lib) types mkOption mkEnableOption optionalString concatStringsSep; + inherit (lib) + types + mkOption + mkEnableOption + 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); - ensureBucketScriptFn = bucket: { website, aliases, corsRules }: + ensureBucketScriptFn = + bucket: + { + website, + aliases, + corsRules, + }: let bucketArg = escapeShellArg bucket; - corsRulesJSON = escapeShellArg (builtins.toJSON { - CORSRules = [{ - AllowedHeaders = corsRules.allowedHeaders; - AllowedMethods = corsRules.allowedMethods; - AllowedOrigins = corsRules.allowedOrigins; - }]; - }); - in '' + corsRulesJSON = escapeShellArg ( + builtins.toJSON { + CORSRules = [ + { + AllowedHeaders = corsRules.allowedHeaders; + AllowedMethods = corsRules.allowedMethods; + AllowedOrigins = corsRules.allowedOrigins; + } + ]; + } + ); + in + '' # garage bucket info tells us if the bucket already exists garage bucket info ${bucketArg} || garage bucket create ${bucketArg} @@ -37,9 +59,11 @@ let garage bucket website --allow ${bucketArg} ''} - ${concatStringsSep "\n" (map (alias: '' - garage bucket alias ${bucketArg} ${escapeShellArg alias} - '') aliases)} + ${concatStringsSep "\n" ( + map (alias: '' + garage bucket alias ${bucketArg} ${escapeShellArg alias} + '') aliases + )} ${optionalString corsRules.enable '' garage bucket allow --read --write --owner ${bucketArg} --key tmp @@ -49,15 +73,29 @@ let ''} ''; ensureBucketsScript = concatMapAttrs ensureBucketScriptFn cfg.ensureBuckets; - ensureAccessScriptFn = key: bucket: { read, write, owner }: '' - garage bucket allow ${optionalString read "--read"} ${optionalString write "--write"} ${optionalString owner "--owner"} \ - ${escapeShellArg bucket} --key ${escapeShellArg key} - ''; - ensureKeyScriptFn = key: {id, secret, 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} || : - ${concatMapAttrs (ensureAccessScriptFn key) ensureAccess} - ''; + ensureAccessScriptFn = + key: bucket: + { + read, + write, + owner, + }: + '' + garage bucket allow ${optionalString read "--read"} ${optionalString write "--write"} ${optionalString owner "--owner"} \ + ${escapeShellArg bucket} --key ${escapeShellArg key} + ''; + ensureKeyScriptFn = + key: + { + id, + secret, + 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} || : + ${concatMapAttrs (ensureAccessScriptFn key) ensureAccess} + ''; ensureKeysScript = concatMapAttrs ensureKeyScriptFn cfg.ensureKeys; in @@ -66,76 +104,85 @@ 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 = []; + type = types.attrsOf ( + types.submodule { + options = { + website = mkOption { + type = types.bool; + default = false; }; - allowedMethods = 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 = [ ]; + }; }; - allowedOrigins = mkOption { + aliases = mkOption { type = types.listOf types.str; - default = []; + 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; + 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 = [ ]; + }; }; - 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 = {}; + } + ); + default = { }; }; }; }; config = lib.mkIf config.fediversity.enable { - environment.systemPackages = [ pkgs.minio-client pkgs.awscli ]; + environment.systemPackages = [ + pkgs.minio-client + pkgs.awscli + ]; networking.firewall.allowedTCPPorts = [ fedicfg.rpc.port @@ -178,9 +225,11 @@ in ''; }; }; - in mapAttrs' - (bucket: _: {name = fedicfg.web.domainForBucket bucket; inherit value;}) - (filterAttrs (_: {website, ...}: website) cfg.ensureBuckets); + in + mapAttrs' (bucket: _: { + name = fedicfg.web.domainForBucket bucket; + inherit value; + }) (filterAttrs (_: { website, ... }: website) cfg.ensureBuckets); systemd.services.ensure-garage = { after = [ "garage.service" ]; @@ -188,7 +237,11 @@ in serviceConfig = { Type = "oneshot"; }; - path = [ cfg.package pkgs.perl pkgs.awscli ]; + path = [ + cfg.package + pkgs.perl + pkgs.awscli + ]; script = '' set -xeuo pipefail diff --git a/fediversity/mastodon.nix b/fediversity/mastodon.nix index 7c465fe..8583c60 100644 --- a/fediversity/mastodon.nix +++ b/fediversity/mastodon.nix @@ -5,10 +5,15 @@ let }; in -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: lib.mkIf (config.fediversity.enable && config.fediversity.mastodon.enable) { - #### garage setup + #### garage setup services.garage = { ensureBuckets = { mastodon = { @@ -58,7 +63,10 @@ lib.mkIf (config.fediversity.enable && config.fediversity.mastodon.enable) { #### mastodon setup # open up access to the mastodon web interface. 80 is necessary if only for ACME - networking.firewall.allowedTCPPorts = [ 80 443 ]; + networking.firewall.allowedTCPPorts = [ + 80 + 443 + ]; services.mastodon = { enable = true; diff --git a/fediversity/peertube.nix b/fediversity/peertube.nix index fe26232..96e256c 100644 --- a/fediversity/peertube.nix +++ b/fediversity/peertube.nix @@ -5,10 +5,18 @@ let }; in -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: lib.mkIf (config.fediversity.enable && config.fediversity.peertube.enable) { - networking.firewall.allowedTCPPorts = [ 80 443 ]; + networking.firewall.allowedTCPPorts = [ + 80 + 443 + ]; services.garage = { ensureBuckets = { @@ -22,7 +30,7 @@ lib.mkIf (config.fediversity.enable && config.fediversity.peertube.enable) { allowedOrigins = [ "*" ]; }; }; - # TODO: these are too broad, after getting everything works narrow it down to the domain we actually want + # TODO: these are too broad, after getting everything works narrow it down to the domain we actually want peertube-playlists = { website = true; corsRules = { diff --git a/fediversity/pixelfed.nix b/fediversity/pixelfed.nix index cef5d6d..279445e 100644 --- a/fediversity/pixelfed.nix +++ b/fediversity/pixelfed.nix @@ -5,7 +5,12 @@ let }; in -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: lib.mkIf (config.fediversity.enable && config.fediversity.pixelfed.enable) { services.garage = { @@ -80,5 +85,8 @@ lib.mkIf (config.fediversity.enable && config.fediversity.pixelfed.enable) { after = [ "ensure-garage.service" ]; }; - networking.firewall.allowedTCPPorts = [ 80 443 ]; + networking.firewall.allowedTCPPorts = [ + 80 + 443 + ]; } diff --git a/flake.nix b/flake.nix index dc4c149..da8cdaa 100644 --- a/flake.nix +++ b/flake.nix @@ -9,113 +9,123 @@ disko.url = "github:nix-community/disko"; }; - outputs = { self, nixpkgs, nixpkgs-latest, pixelfed, disko }: - let - system = "x86_64-linux"; - lib = nixpkgs.lib; - 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; + outputs = + { + self, + nixpkgs, + nixpkgs-latest, + pixelfed, + disko, + }: + let + system = "x86_64-linux"; + lib = nixpkgs.lib; + 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; + ## 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; - disk-layout = import ./disk-layout.nix; - }; - - nixosConfigurations = { - mastodon = nixpkgs.lib.nixosSystem { - inherit system; - modules = with self.nixosModules; [ - disko.nixosModules.default - disk-layout - bleedingFediverse - fediversity - interactive-vm - garage-vm - mastodon-vm - ]; + disk-layout = import ./disk-layout.nix; }; - peertube = nixpkgs.lib.nixosSystem { - inherit system; - modules = with self.nixosModules; [ - disko.nixosModules.default - disk-layout - bleedingFediverse - fediversity - interactive-vm - garage-vm - peertube-vm - ]; + nixosConfigurations = { + mastodon = nixpkgs.lib.nixosSystem { + inherit system; + modules = with self.nixosModules; [ + disko.nixosModules.default + disk-layout + bleedingFediverse + fediversity + interactive-vm + garage-vm + mastodon-vm + ]; + }; + + peertube = nixpkgs.lib.nixosSystem { + inherit system; + modules = with self.nixosModules; [ + disko.nixosModules.default + disk-layout + bleedingFediverse + fediversity + interactive-vm + garage-vm + peertube-vm + ]; + }; + + pixelfed = nixpkgs.lib.nixosSystem { + inherit system; + modules = with self.nixosModules; [ + disko.nixosModules.default + disk-layout + bleedingFediverse + fediversity + interactive-vm + garage-vm + pixelfed-vm + ]; + }; + + all = nixpkgs.lib.nixosSystem { + inherit system; + modules = with self.nixosModules; [ + disko.nixosModules.default + disk-layout + bleedingFediverse + fediversity + interactive-vm + garage-vm + peertube-vm + pixelfed-vm + mastodon-vm + ]; + }; }; - pixelfed = nixpkgs.lib.nixosSystem { - inherit system; - modules = with self.nixosModules; [ - disko.nixosModules.default - disk-layout - bleedingFediverse - fediversity - interactive-vm - garage-vm - pixelfed-vm - ]; + ## Fully-feature ISO installer + mkInstaller = import ./installer.nix; + installers = lib.mapAttrs (_: config: self.mkInstaller nixpkgs config) self.nixosConfigurations; + + deploy = + let + deployCommand = (pkgs.callPackage ./deploy.nix { }); + in + lib.mapAttrs (name: config: deployCommand name config) self.nixosConfigurations; + + checks.${system} = { + mastodon-garage = import ./tests/mastodon-garage.nix { inherit pkgs self; }; + pixelfed-garage = import ./tests/pixelfed-garage.nix { inherit pkgs self; }; }; - all = nixpkgs.lib.nixosSystem { - inherit system; - modules = with self.nixosModules; [ - disko.nixosModules.default - disk-layout - bleedingFediverse - fediversity - interactive-vm - garage-vm - peertube-vm - pixelfed-vm - mastodon-vm + devShells.${system}.default = pkgs.mkShell { + inputs = with pkgs; [ + nil ]; }; }; - - ## Fully-feature ISO installer - mkInstaller = import ./installer.nix; - installers = lib.mapAttrs (_: config: self.mkInstaller nixpkgs config) self.nixosConfigurations; - - deploy = - let - deployCommand = (pkgs.callPackage ./deploy.nix { }); - in - lib.mapAttrs (name: config: deployCommand name config) self.nixosConfigurations; - - checks.${system} = { - mastodon-garage = import ./tests/mastodon-garage.nix { inherit pkgs self; }; - pixelfed-garage = import ./tests/pixelfed-garage.nix { inherit pkgs self; }; - }; - - devShells.${system}.default = pkgs.mkShell { - inputs = with pkgs; [ - nil - ]; - }; - }; } diff --git a/installer.nix b/installer.nix index dcf6f20..f87be58 100644 --- a/installer.nix +++ b/installer.nix @@ -4,15 +4,22 @@ WARNING: Running this installer will format the target disk! */ -{ nixpkgs, - hostKeys ? {} +{ + nixpkgs, + hostKeys ? { }, }: machine: let inherit (builtins) concatStringsSep attrValues mapAttrs; - installer = { config, pkgs, lib, ... }: + installer = + { + config, + pkgs, + lib, + ... + }: let bootstrap = pkgs.writeShellApplication { name = "bootstrap"; @@ -20,39 +27,35 @@ let text = '' ${machine.config.system.build.diskoScript} nixos-install --no-root-password --no-channel-copy --system ${machine.config.system.build.toplevel} - ${ - concatStringsSep "\n" ( - attrValues ( - mapAttrs - (kind: keys: '' - cp ${keys.private} /mnt/etc/ssh/ssh_host_${kind}_key - chmod 600 /mnt/etc/ssh/ssh_host_${kind}_key - cp ${keys.public} /mnt/etc/ssh/ssh_host_${kind}_key.pub - chmod 644 /mnt/etc/ssh/ssh_host_${kind}_key.pub - '') - hostKeys - ) + ${concatStringsSep "\n" ( + attrValues ( + mapAttrs (kind: keys: '' + cp ${keys.private} /mnt/etc/ssh/ssh_host_${kind}_key + chmod 600 /mnt/etc/ssh/ssh_host_${kind}_key + cp ${keys.public} /mnt/etc/ssh/ssh_host_${kind}_key.pub + chmod 644 /mnt/etc/ssh/ssh_host_${kind}_key.pub + '') hostKeys ) - } + )} poweroff ''; }; in - { - imports = [ - "${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix" - ]; - nixpkgs.hostPlatform = "x86_64-linux"; - services.getty.autologinUser = lib.mkForce "root"; - programs.bash.loginShellInit = nixpkgs.lib.getExe bootstrap; + { + imports = [ + "${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix" + ]; + nixpkgs.hostPlatform = "x86_64-linux"; + services.getty.autologinUser = lib.mkForce "root"; + programs.bash.loginShellInit = nixpkgs.lib.getExe bootstrap; - isoImage = { - compressImage = false; - squashfsCompression = "lz4"; - isoName = lib.mkForce "installer.iso"; - ## ^^ FIXME: Use a more interesting name or keep the default name and - ## use `isoImage.isoName` in the tests. - }; + isoImage = { + compressImage = false; + squashfsCompression = "lz4"; + isoName = lib.mkForce "installer.iso"; + ## ^^ FIXME: Use a more interesting name or keep the default name and + ## use `isoImage.isoName` in the tests. }; + }; in -(nixpkgs.lib.nixosSystem { modules = [installer]; }).config.system.build.isoImage +(nixpkgs.lib.nixosSystem { modules = [ installer ]; }).config.system.build.isoImage diff --git a/tests/mastodon-garage.nix b/tests/mastodon-garage.nix index 5111b61..b2e5574 100644 --- a/tests/mastodon-garage.nix +++ b/tests/mastodon-garage.nix @@ -2,143 +2,149 @@ let lib = pkgs.lib; rebuildableTest = import ./rebuildableTest.nix pkgs; - seleniumScript = pkgs.writers.writePython3Bin "selenium-script" - { - libraries = with pkgs.python3Packages; [ selenium ]; - } '' - from selenium import webdriver - from selenium.webdriver.common.by import By - from selenium.webdriver.firefox.options import Options - from selenium.webdriver.support.ui import WebDriverWait + seleniumScript = + pkgs.writers.writePython3Bin "selenium-script" + { + libraries = with pkgs.python3Packages; [ selenium ]; + } + '' + from selenium import webdriver + from selenium.webdriver.common.by import By + from selenium.webdriver.firefox.options import Options + from selenium.webdriver.support.ui import WebDriverWait - print(1) + print(1) - options = Options() - options.add_argument("--headless") - # devtools don't show up in headless screenshots - # options.add_argument("-devtools") - service = webdriver.FirefoxService(executable_path="${lib.getExe pkgs.geckodriver}") # noqa: E501 + options = Options() + options.add_argument("--headless") + # devtools don't show up in headless screenshots + # options.add_argument("-devtools") + service = webdriver.FirefoxService(executable_path="${lib.getExe pkgs.geckodriver}") # noqa: E501 - driver = webdriver.Firefox(options=options, service=service) - driver.get("http://mastodon.localhost:55001/public/local") + driver = webdriver.Firefox(options=options, service=service) + driver.get("http://mastodon.localhost:55001/public/local") - # wait until the statuses load - WebDriverWait(driver, 90).until( - lambda x: x.find_element(By.CLASS_NAME, "status")) + # wait until the statuses load + WebDriverWait(driver, 90).until( + lambda x: x.find_element(By.CLASS_NAME, "status")) - driver.save_screenshot("/mastodon-screenshot.png") + driver.save_screenshot("/mastodon-screenshot.png") - driver.close() - ''; + driver.close() + ''; in pkgs.nixosTest { name = "test-mastodon-garage"; nodes = { - server = { config, ... }: { - virtualisation.memorySize = lib.mkVMOverride 4096; - imports = with self.nixosModules; [ - bleedingFediverse - fediversity - garage-vm - mastodon-vm - ]; - # TODO: pair down - environment.systemPackages = with pkgs; [ - python3 - firefox-unwrapped - geckodriver - toot - xh - seleniumScript - helix - imagemagick - ]; - environment.variables = { - POST_MEDIA = ./green.png; - AWS_ACCESS_KEY_ID = config.services.garage.ensureKeys.mastodon.id; - AWS_SECRET_ACCESS_KEY = config.services.garage.ensureKeys.mastodon.secret; + server = + { config, ... }: + { + virtualisation.memorySize = lib.mkVMOverride 4096; + imports = with self.nixosModules; [ + bleedingFediverse + fediversity + garage-vm + mastodon-vm + ]; + # TODO: pair down + environment.systemPackages = with pkgs; [ + python3 + firefox-unwrapped + geckodriver + toot + xh + seleniumScript + helix + imagemagick + ]; + environment.variables = { + POST_MEDIA = ./green.png; + AWS_ACCESS_KEY_ID = config.services.garage.ensureKeys.mastodon.id; + AWS_SECRET_ACCESS_KEY = config.services.garage.ensureKeys.mastodon.secret; + }; }; - }; }; - testScript = { nodes, ... }: '' - import re - import time + testScript = + { nodes, ... }: + '' + import re + import time - server.start() + server.start() - with subtest("Mastodon starts"): - server.wait_for_unit("mastodon-web.service") + with subtest("Mastodon starts"): + server.wait_for_unit("mastodon-web.service") - # make sure mastodon is fully up and running before we interact with it - # TODO: is there a way to test for this? - time.sleep(180) + # make sure mastodon is fully up and running before we interact with it + # TODO: is there a way to test for this? + time.sleep(180) - with subtest("Account creation"): - account_creation_output = server.succeed("mastodon-tootctl accounts create test --email test@test.com --confirmed --approve") - password_match = re.match('.*New password: ([^\n]*).*', account_creation_output, re.S) - if password_match is None: - raise Exception(f"account creation did not generate a password.\n{account_creation_output}") - password = password_match.group(1) + with subtest("Account creation"): + account_creation_output = server.succeed("mastodon-tootctl accounts create test --email test@test.com --confirmed --approve") + password_match = re.match('.*New password: ([^\n]*).*', account_creation_output, re.S) + if password_match is None: + raise Exception(f"account creation did not generate a password.\n{account_creation_output}") + password = password_match.group(1) - with subtest("TTY Login"): - server.wait_until_tty_matches("1", "login: ") - server.send_chars("root\n"); + with subtest("TTY Login"): + server.wait_until_tty_matches("1", "login: ") + server.send_chars("root\n"); - with subtest("Log in with toot"): - # toot doesn't provide a way to just specify our login details as arguments, so we have to pretend we're typing them in at the prompt - server.send_chars("toot login_cli --instance http://mastodon.localhost:55001 --email test@test.com\n") - server.wait_until_tty_matches("1", "Password: ") - server.send_chars(password + "\n") - server.wait_until_tty_matches("1", "Successfully logged in.") + with subtest("Log in with toot"): + # toot doesn't provide a way to just specify our login details as arguments, so we have to pretend we're typing them in at the prompt + server.send_chars("toot login_cli --instance http://mastodon.localhost:55001 --email test@test.com\n") + server.wait_until_tty_matches("1", "Password: ") + server.send_chars(password + "\n") + server.wait_until_tty_matches("1", "Successfully logged in.") - with subtest("post text"): - server.succeed("echo 'hello mastodon' | toot post") + with subtest("post text"): + server.succeed("echo 'hello mastodon' | toot post") - with subtest("post image"): - server.succeed("toot post --media $POST_MEDIA") + with subtest("post image"): + server.succeed("toot post --media $POST_MEDIA") - 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 ls garage/mastodon") + 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 ls garage/mastodon") - with subtest("access image in garage"): - image = server.succeed("mc find garage --regex original") - image = image.rstrip() - if image == "": - raise Exception("image posted to mastodon did not get stored in garage") - server.succeed(f"mc cat {image} >/garage-image.webp") - garage_image_hash = server.succeed("identify -quiet -format '%#' /garage-image.webp") - image_hash = server.succeed("identify -quiet -format '%#' $POST_MEDIA") - if garage_image_hash != image_hash: - raise Exception("image stored in garage did not match image uploaded") + with subtest("access image in garage"): + image = server.succeed("mc find garage --regex original") + image = image.rstrip() + if image == "": + raise Exception("image posted to mastodon did not get stored in garage") + server.succeed(f"mc cat {image} >/garage-image.webp") + garage_image_hash = server.succeed("identify -quiet -format '%#' /garage-image.webp") + image_hash = server.succeed("identify -quiet -format '%#' $POST_MEDIA") + if garage_image_hash != image_hash: + raise Exception("image stored in garage did not match image uploaded") - with subtest("Content security policy allows garage images"): - headers = server.succeed("xh -h http://mastodon.localhost:55001/public/local") - csp_match = None - # I can't figure out re.MULTILINE - for header in headers.split("\n"): - csp_match = re.match('^Content-Security-Policy: (.*)$', header) - if csp_match is not None: - break - if csp_match is None: - raise Exception("mastodon did not send a content security policy header") - csp = csp_match.group(1) - # the img-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. - 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 server. image will not be displayed properly on mastodon.") + with subtest("Content security policy allows garage images"): + headers = server.succeed("xh -h http://mastodon.localhost:55001/public/local") + csp_match = None + # I can't figure out re.MULTILINE + for header in headers.split("\n"): + csp_match = re.match('^Content-Security-Policy: (.*)$', header) + if csp_match is not None: + break + if csp_match is None: + raise Exception("mastodon did not send a content security policy header") + csp = csp_match.group(1) + # the img-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. + 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 server. image will not be displayed properly on mastodon.") - # this could in theory give a false positive if mastodon changes it's colorscheme to include pure green. - with subtest("image displays"): - server.succeed("selenium-script") - server.copy_from_vm("/mastodon-screenshot.png", "") - displayed_colors = server.succeed("convert /mastodon-screenshot.png -define histogram:unique-colors=true -format %c histogram:info:") - # check that the green image displayed somewhere - green_check = re.match(".*#00FF00.*", displayed_colors, re.S) - if green_check is None: - raise Exception("cannot detect the uploaded image on mastodon page.") - ''; + # this could in theory give a false positive if mastodon changes it's colorscheme to include pure green. + with subtest("image displays"): + server.succeed("selenium-script") + server.copy_from_vm("/mastodon-screenshot.png", "") + displayed_colors = server.succeed("convert /mastodon-screenshot.png -define histogram:unique-colors=true -format %c histogram:info:") + # check that the green image displayed somewhere + green_check = re.match(".*#00FF00.*", displayed_colors, re.S) + if green_check is None: + raise Exception("cannot detect the uploaded image on mastodon page.") + ''; } diff --git a/tests/pixelfed-garage.nix b/tests/pixelfed-garage.nix index b301dd3..d4b13c5 100644 --- a/tests/pixelfed-garage.nix +++ b/tests/pixelfed-garage.nix @@ -50,166 +50,176 @@ let driver.quit() ''; - seleniumScriptPostPicture = pkgs.writers.writePython3Bin "selenium-script-post-picture" - { - libraries = with pkgs.python3Packages; [ selenium ]; - } '' - import os - import time - ${seleniumImports} - from selenium.webdriver.support.wait import WebDriverWait + seleniumScriptPostPicture = + pkgs.writers.writePython3Bin "selenium-script-post-picture" + { + libraries = with pkgs.python3Packages; [ selenium ]; + } + '' + import os + import time + ${seleniumImports} + from selenium.webdriver.support.wait import WebDriverWait - ${seleniumSetup} - ${seleniumPixelfedLogin} - time.sleep(3) + ${seleniumSetup} + ${seleniumPixelfedLogin} + time.sleep(3) - media_path = os.environ['POST_MEDIA'] + media_path = os.environ['POST_MEDIA'] - # Find the new post form, fill it in with our pictureand a caption. - print("Click on “Create New Post”...", file=sys.stderr) - driver.find_element(By.LINK_TEXT, "Create New Post").click() - print("Add file to input element...", file=sys.stderr) - driver.find_element(By.XPATH, "//input[@type='file']").send_keys(media_path) - print("Add a caption", file=sys.stderr) - driver.find_element(By.CSS_SELECTOR, ".media-body textarea").send_keys( - "Fediversity test of image upload to pixelfed with garage storage." - ) - time.sleep(3) - print("Click on “Post” button...", file=sys.stderr) - driver.find_element(By.LINK_TEXT, "Post").click() + # Find the new post form, fill it in with our pictureand a caption. + print("Click on “Create New Post”...", file=sys.stderr) + driver.find_element(By.LINK_TEXT, "Create New Post").click() + print("Add file to input element...", file=sys.stderr) + driver.find_element(By.XPATH, "//input[@type='file']").send_keys(media_path) + print("Add a caption", file=sys.stderr) + driver.find_element(By.CSS_SELECTOR, ".media-body textarea").send_keys( + "Fediversity test of image upload to pixelfed with garage storage." + ) + time.sleep(3) + print("Click on “Post” button...", file=sys.stderr) + driver.find_element(By.LINK_TEXT, "Post").click() - # Wait until the post loads, and in particular its picture, then take a - # screenshot of the whole page. - print("Wait for post and image to be loaded...", file=sys.stderr) - img = driver.find_element( - By.XPATH, - "//div[@class='timeline-status-component-content']//img" - ) - WebDriverWait(driver, timeout=10).until( - lambda d: d.execute_script("return arguments[0].complete", img) - ) - time.sleep(3) + # Wait until the post loads, and in particular its picture, then take a + # screenshot of the whole page. + print("Wait for post and image to be loaded...", file=sys.stderr) + img = driver.find_element( + By.XPATH, + "//div[@class='timeline-status-component-content']//img" + ) + WebDriverWait(driver, timeout=10).until( + lambda d: d.execute_script("return arguments[0].complete", img) + ) + time.sleep(3) - ${seleniumTakeScreenshot "\"/home/selenium/screenshot.png\""} - ${seleniumQuit}''; + ${seleniumTakeScreenshot "\"/home/selenium/screenshot.png\""} + ${seleniumQuit}''; - seleniumScriptGetSrc = pkgs.writers.writePython3Bin "selenium-script-get-src" - { - libraries = with pkgs.python3Packages; [ selenium ]; - } '' - ${seleniumImports} - ${seleniumSetup} - ${seleniumPixelfedLogin} + seleniumScriptGetSrc = + pkgs.writers.writePython3Bin "selenium-script-get-src" + { + libraries = with pkgs.python3Packages; [ selenium ]; + } + '' + ${seleniumImports} + ${seleniumSetup} + ${seleniumPixelfedLogin} - img = driver.find_element( - By.XPATH, - "//div[@class='timeline-status-component-content']//img" - ) - # REVIEW: Need to wait for it to be loaded? - print(img.get_attribute('src')) + img = driver.find_element( + By.XPATH, + "//div[@class='timeline-status-component-content']//img" + ) + # REVIEW: Need to wait for it to be loaded? + print(img.get_attribute('src')) - ${seleniumQuit}''; + ${seleniumQuit}''; in pkgs.nixosTest { name = "test-pixelfed-garage"; nodes = { - server = { config, ... }: { + server = + { config, ... }: + { - services = { - xserver = { - enable = true; - displayManager.lightdm.enable = true; - desktopManager.lxqt.enable = true; + services = { + xserver = { + enable = true; + displayManager.lightdm.enable = true; + desktopManager.lxqt.enable = true; + }; + + displayManager.autoLogin = { + enable = true; + user = "selenium"; + }; + }; + virtualisation.resolution = { + x = 1680; + y = 1050; }; - displayManager.autoLogin = { - enable = true; - user = "selenium"; + virtualisation = { + memorySize = lib.mkVMOverride 8192; + cores = 8; + }; + imports = with self.nixosModules; [ + bleedingFediverse + fediversity + garage-vm + pixelfed-vm + ]; + # TODO: pair down + environment.systemPackages = with pkgs; [ + python3 + chromium + chromedriver + xh + seleniumScriptPostPicture + seleniumScriptGetSrc + helix + imagemagick + ]; + 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; + ## without this we get frivolous errors in the logs + MC_REGION = "garage"; + }; + # chrome does not like being run as root + users.users.selenium = { + isNormalUser = true; }; }; - virtualisation.resolution = { x = 1680; y = 1050; }; - - - virtualisation = { - memorySize = lib.mkVMOverride 8192; - cores = 8; - }; - imports = with self.nixosModules; [ - bleedingFediverse - fediversity - garage-vm - pixelfed-vm - ]; - # TODO: pair down - environment.systemPackages = with pkgs; [ - python3 - chromium - chromedriver - xh - seleniumScriptPostPicture - seleniumScriptGetSrc - helix - imagemagick - ]; - 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; - ## without this we get frivolous errors in the logs - MC_REGION = "garage"; - }; - # chrome does not like being run as root - users.users.selenium = { - isNormalUser = true; - }; - }; }; - testScript = { nodes, ... }: '' - import re + testScript = + { nodes, ... }: + '' + import re - server.start() + server.start() - with subtest("Pixelfed starts"): - server.wait_for_unit("phpfpm-pixelfed.service") + with subtest("Pixelfed starts"): + server.wait_for_unit("phpfpm-pixelfed.service") - with subtest("Account creation"): - server.succeed("pixelfed-manage user:create --name=test --username=test --email=${email} --password=${password} --confirm_email=1") + with subtest("Account creation"): + server.succeed("pixelfed-manage user:create --name=test --username=test --email=${email} --password=${password} --confirm_email=1") - # NOTE: This could in theory give a false positive if pixelfed changes it's - # colorscheme to include pure green. (see same problem in pixelfed-garage.nix). - # TODO: For instance: post a red image and check that the green pixel IS NOT - # there, then post a green image and check that the green pixel IS there. + # NOTE: This could in theory give a false positive if pixelfed changes it's + # colorscheme to include pure green. (see same problem in pixelfed-garage.nix). + # TODO: For instance: post a red image and check that the green pixel IS NOT + # there, then post a green image and check that the green pixel IS there. - with subtest("Image displays"): - server.succeed("su - selenium -c 'selenium-script-post-picture ${email} ${password}'") - server.copy_from_vm("/home/selenium/screenshot.png", "") - displayed_colors = server.succeed("magick /home/selenium/screenshot.png -define histogram:unique-colors=true -format %c histogram:info:") - # check that the green image displayed somewhere - image_check = re.match(".*#FF0500.*", displayed_colors, re.S) - if image_check is None: - raise Exception("cannot detect the uploaded image on pixelfed page.") + with subtest("Image displays"): + server.succeed("su - selenium -c 'selenium-script-post-picture ${email} ${password}'") + server.copy_from_vm("/home/selenium/screenshot.png", "") + displayed_colors = server.succeed("magick /home/selenium/screenshot.png -define histogram:unique-colors=true -format %c histogram:info:") + # check that the green image displayed somewhere + image_check = re.match(".*#FF0500.*", displayed_colors, re.S) + if image_check is None: + 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 ls garage/pixelfed") + 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 ls garage/pixelfed") - with subtest("access image in garage"): - image = server.succeed("mc find garage --regex '\\.png' --ignore '*_thumb.png'") - image = image.rstrip() - if image == "": - raise Exception("image posted to Pixelfed did not get stored in garage") - server.succeed(f"mc cat {image} >/garage-image.png") - garage_image_hash = server.succeed("identify -quiet -format '%#' /garage-image.png") - image_hash = server.succeed("identify -quiet -format '%#' $POST_MEDIA") - if garage_image_hash != image_hash: - raise Exception("image stored in garage did not match image uploaded") + with subtest("access image in garage"): + image = server.succeed("mc find garage --regex '\\.png' --ignore '*_thumb.png'") + image = image.rstrip() + if image == "": + raise Exception("image posted to Pixelfed did not get stored in garage") + server.succeed(f"mc cat {image} >/garage-image.png") + garage_image_hash = server.succeed("identify -quiet -format '%#' /garage-image.png") + image_hash = server.succeed("identify -quiet -format '%#' $POST_MEDIA") + if garage_image_hash != image_hash: + raise Exception("image stored in garage did not match image uploaded") - 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"}"): - raise Exception("image does not come from garage") - ''; + 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"}"): + raise Exception("image does not come from garage") + ''; } diff --git a/tests/rebuildableTest.nix b/tests/rebuildableTest.nix index 9513ca2..ec32193 100644 --- a/tests/rebuildableTest.nix +++ b/tests/rebuildableTest.nix @@ -1,32 +1,42 @@ pkgs: test: let - inherit (pkgs.lib) mapAttrsToList concatStringsSep genAttrs mkIf; + inherit (pkgs.lib) + mapAttrsToList + concatStringsSep + genAttrs + mkIf + ; inherit (builtins) attrNames; - interactiveConfig = ({ config, ... }: { - # so we can run `nix shell nixpkgs#foo` on the machines - nix.extraOptions = '' - extra-experimental-features = nix-command flakes - ''; + interactiveConfig = ( + { config, ... }: + { + # so we can run `nix shell nixpkgs#foo` on the machines + nix.extraOptions = '' + extra-experimental-features = nix-command flakes + ''; - # so we can ssh in and rebuild them - services.openssh = { - enable = true; - settings = { - PermitRootLogin = "yes"; - PermitEmptyPasswords = "yes"; - UsePAM = false; + # so we can ssh in and rebuild them + services.openssh = { + enable = true; + settings = { + PermitRootLogin = "yes"; + PermitEmptyPasswords = "yes"; + UsePAM = false; + }; }; - }; - virtualisation = mkIf (config.networking.hostName == "jumphost") { - forwardPorts = [{ - from = "host"; - host.port = 2222; - guest.port = 22; - }]; - }; - }); + virtualisation = mkIf (config.networking.hostName == "jumphost") { + forwardPorts = [ + { + from = "host"; + host.port = 2222; + guest.port = 22; + } + ]; + }; + } + ); sshConfig = pkgs.writeText "ssh-config" '' Host * @@ -50,10 +60,11 @@ let # create an association array from machine names to the path to their # configuration in the nix store declare -A configPaths=(${ - concatStringsSep " " - (mapAttrsToList - (n: v: ''["${n}"]="${v.system.build.toplevel}"'') - rebuildableTest.driverInteractive.nodes) + concatStringsSep " " ( + mapAttrsToList ( + n: v: ''["${n}"]="${v.system.build.toplevel}"'' + ) rebuildableTest.driverInteractive.nodes + ) }) rebuild_one() { @@ -113,37 +124,40 @@ let # we're at it) rebuildableTest = let - preOverride = pkgs.nixosTest (test // { - interactive = (test.interactive or { }) // { - # no need to // with test.interactive.nodes here, since we are iterating - # over all of them, and adding back in the config via `imports` - nodes = genAttrs - ( - attrNames test.nodes or { } ++ - attrNames test.interactive.nodes or { } ++ - [ "jumphost" ] - ) - (n: { - imports = [ - (test.interactive.${n} or { }) - interactiveConfig - ]; - }); - }; - # override with test.passthru in case someone wants to overwrite us. - passthru = { inherit rebuildScript sshConfig; } // (test.passthru or { }); - }); + preOverride = pkgs.nixosTest ( + test + // { + interactive = (test.interactive or { }) // { + # no need to // with test.interactive.nodes here, since we are iterating + # over all of them, and adding back in the config via `imports` + nodes = + genAttrs (attrNames test.nodes or { } ++ attrNames test.interactive.nodes or { } ++ [ "jumphost" ]) + (n: { + imports = [ + (test.interactive.${n} or { }) + interactiveConfig + ]; + }); + }; + # override with test.passthru in case someone wants to overwrite us. + passthru = { + inherit rebuildScript sshConfig; + } // (test.passthru or { }); + } + ); in - preOverride // { + preOverride + // { driverInteractive = preOverride.driverInteractive.overrideAttrs (old: { # this comes from runCommand, not mkDerivation, so this is the only # hook we have to override - buildCommand = old.buildCommand + '' - ln -s ${sshConfig} $out/ssh-config - ln -s ${rebuildScript}/bin/rebuild $out/bin/rebuild - ''; + buildCommand = + old.buildCommand + + '' + ln -s ${sshConfig} $out/ssh-config + ln -s ${rebuildScript}/bin/rebuild $out/bin/rebuild + ''; }); }; in rebuildableTest - diff --git a/vm/garage-vm.nix b/vm/garage-vm.nix index 0ad6998..aca295e 100644 --- a/vm/garage-vm.nix +++ b/vm/garage-vm.nix @@ -1,4 +1,9 @@ -{ lib, config, modulesPath, ... }: +{ + lib, + config, + modulesPath, + ... +}: let inherit (lib) mkVMOverride mapAttrs' filterAttrs; @@ -7,7 +12,8 @@ let fedicfg = config.fediversity.internal.garage; -in { +in +{ imports = [ (modulesPath + "/virtualisation/qemu-vm.nix") ]; services.nginx.virtualHosts = @@ -16,9 +22,11 @@ in { forceSSL = mkVMOverride false; enableACME = mkVMOverride false; }; - in mapAttrs' - (bucket: _: {name = fedicfg.web.domainForBucket bucket; inherit value;}) - (filterAttrs (_: {website, ...}: website) cfg.ensureBuckets); + in + mapAttrs' (bucket: _: { + name = fedicfg.web.domainForBucket bucket; + inherit value; + }) (filterAttrs (_: { website, ... }: website) cfg.ensureBuckets); virtualisation.diskSize = 2048; virtualisation.forwardPorts = [ diff --git a/vm/interactive-vm.nix b/vm/interactive-vm.nix index cf9cbd9..449864a 100644 --- a/vm/interactive-vm.nix +++ b/vm/interactive-vm.nix @@ -1,5 +1,6 @@ # customize nixos-rebuild build-vm to be a bit more convenient -{ pkgs, ... }: { +{ pkgs, ... }: +{ # let us log in users.mutableUsers = false; users.users.root.hashedPassword = ""; @@ -34,7 +35,10 @@ # no graphics. see nixos-shell virtualisation = { graphics = false; - qemu.consoles = [ "tty0" "hvc0" ]; + qemu.consoles = [ + "tty0" + "hvc0" + ]; qemu.options = [ "-serial null" "-device virtio-serial" @@ -45,7 +49,10 @@ }; # we can't forward port 80 or 443, so let's run nginx on a different port - networking.firewall.allowedTCPPorts = [ 8443 8080 ]; + networking.firewall.allowedTCPPorts = [ + 8443 + 8080 + ]; services.nginx.defaultSSLListenPort = 8443; services.nginx.defaultHTTPListenPort = 8080; virtualisation.forwardPorts = [ diff --git a/vm/mastodon-vm.nix b/vm/mastodon-vm.nix index ecf880c..fdaf71e 100644 --- a/vm/mastodon-vm.nix +++ b/vm/mastodon-vm.nix @@ -1,4 +1,10 @@ -{ modulesPath, lib, config, ... }: { +{ + modulesPath, + lib, + config, + ... +}: +{ imports = [ (modulesPath + "/virtualisation/qemu-vm.nix") ]; diff --git a/vm/peertube-vm.nix b/vm/peertube-vm.nix index 7bf1783..c8ff9b9 100644 --- a/vm/peertube-vm.nix +++ b/vm/peertube-vm.nix @@ -1,4 +1,5 @@ -{ pkgs, modulesPath, ... }: { +{ pkgs, modulesPath, ... }: +{ imports = [ (modulesPath + "/virtualisation/qemu-vm.nix") ]; diff --git a/vm/pixelfed-vm.nix b/vm/pixelfed-vm.nix index 76fbb59..b3f6bb3 100644 --- a/vm/pixelfed-vm.nix +++ b/vm/pixelfed-vm.nix @@ -1,9 +1,15 @@ -{ pkgs, lib, modulesPath, ... }: +{ + pkgs, + lib, + modulesPath, + ... +}: let inherit (lib) mkVMOverride; -in { +in +{ imports = [ (modulesPath + "/virtualisation/qemu-vm.nix") ]; fediversity = {