diff options
Diffstat (limited to 'nixos/modules')
20 files changed, 512 insertions, 44 deletions
diff --git a/nixos/modules/config/fonts/fonts.nix b/nixos/modules/config/fonts/fonts.nix index 3911196c101..60a0885103d 100644 --- a/nixos/modules/config/fonts/fonts.nix +++ b/nixos/modules/config/fonts/fonts.nix @@ -2,6 +2,52 @@ with lib; +let + # A scalable variant of the X11 "core" cursor + # + # If not running a fancy desktop environment, the cursor is likely set to + # the default `cursor.pcf` bitmap font. This is 17px wide, so it's very + # small and almost invisible on 4K displays. + fontcursormisc_hidpi = pkgs.xorg.fontcursormisc.overrideAttrs (old: + let + # The scaling constant is 230/96: the scalable `left_ptr` glyph at + # about 23 points is rendered as 17px, on a 96dpi display. + # Note: the XLFD font size is in decipoints. + size = 2.39583 * config.services.xserver.dpi; + sizeString = builtins.head (builtins.split "\\." (toString size)); + in + { + postInstall = '' + alias='cursor -xfree86-cursor-medium-r-normal--0-${sizeString}-0-0-p-0-adobe-fontspecific' + echo "$alias" > $out/lib/X11/fonts/Type1/fonts.alias + ''; + }); + + hasHidpi = + config.hardware.video.hidpi.enable && + config.services.xserver.dpi != null; + + defaultFonts = + [ pkgs.dejavu_fonts + pkgs.freefont_ttf + pkgs.gyre-fonts # TrueType substitutes for standard PostScript fonts + pkgs.liberation_ttf + pkgs.unifont + pkgs.noto-fonts-emoji + ]; + + defaultXFonts = + [ (if hasHidpi then fontcursormisc_hidpi else pkgs.xorg.fontcursormisc) + pkgs.xorg.fontmiscmisc + ] ++ optionals (config.nixpkgs.config.allowUnfree or false) + [ # these are unfree, and will make usage with xserver fail + pkgs.xorg.fontbhlucidatypewriter100dpi + pkgs.xorg.fontbhlucidatypewriter75dpi + pkgs.xorg.fontbh100dpi + ]; + +in + { imports = [ (mkRemovedOptionModule [ "fonts" "enableCoreFonts" ] "Use fonts.fonts = [ pkgs.corefonts ]; instead.") @@ -32,25 +78,9 @@ with lib; }; - config = { - - fonts.fonts = mkIf config.fonts.enableDefaultFonts - ([ - pkgs.dejavu_fonts - pkgs.freefont_ttf - pkgs.gyre-fonts # TrueType substitutes for standard PostScript fonts - pkgs.liberation_ttf - pkgs.xorg.fontmiscmisc - pkgs.xorg.fontcursormisc - pkgs.unifont - pkgs.noto-fonts-emoji - ] ++ lib.optionals (config.nixpkgs.config.allowUnfree or false) [ - # these are unfree, and will make usage with xserver fail - pkgs.xorg.fontbhlucidatypewriter100dpi - pkgs.xorg.fontbhlucidatypewriter75dpi - pkgs.xorg.fontbh100dpi - ]); - - }; + config = mkMerge [ + { fonts.fonts = mkIf config.fonts.enableDefaultFonts defaultFonts; } + { fonts.fonts = mkIf config.services.xserver.enable defaultXFonts; } + ]; } diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix index d5e7745c53f..f86be3be2c6 100644 --- a/nixos/modules/config/users-groups.nix +++ b/nixos/modules/config/users-groups.nix @@ -324,7 +324,7 @@ let }; - groupOpts = { name, ... }: { + groupOpts = { name, config, ... }: { options = { @@ -358,6 +358,10 @@ let config = { name = mkDefault name; + + members = mapAttrsToList (n: u: u.name) ( + filterAttrs (n: u: elem config.name u.extraGroups) cfg.users + ); }; }; @@ -419,12 +423,7 @@ let initialPassword initialHashedPassword; shell = utils.toShellPath u.shell; }) cfg.users; - groups = mapAttrsToList (n: g: - { inherit (g) name gid; - members = g.members ++ (mapAttrsToList (n: u: u.name) ( - filterAttrs (n: u: elem g.name u.extraGroups) cfg.users - )); - }) cfg.groups; + groups = attrValues cfg.groups; }); systemShells = diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 2473bf2f55f..71a1118fd38 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -121,6 +121,7 @@ ./programs/bash-my-aws.nix ./programs/bcc.nix ./programs/browserpass.nix + ./programs/calls.nix ./programs/captive-browser.nix ./programs/ccache.nix ./programs/cdemu.nix @@ -460,6 +461,7 @@ ./services/mail/opensmtpd.nix ./services/mail/pfix-srsd.nix ./services/mail/postfix.nix + ./services/mail/postfixadmin.nix ./services/mail/postsrsd.nix ./services/mail/postgrey.nix ./services/mail/spamassassin.nix @@ -1036,6 +1038,7 @@ ./services/x11/display-managers/sddm.nix ./services/x11/display-managers/slim.nix ./services/x11/display-managers/startx.nix + ./services/x11/display-managers/sx.nix ./services/x11/display-managers/xpra.nix ./services/x11/fractalart.nix ./services/x11/hardware/libinput.nix diff --git a/nixos/modules/programs/calls.nix b/nixos/modules/programs/calls.nix new file mode 100644 index 00000000000..59961625e5d --- /dev/null +++ b/nixos/modules/programs/calls.nix @@ -0,0 +1,25 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.programs.calls; +in { + options = { + programs.calls = { + enable = mkEnableOption '' + Whether to enable GNOME calls: a phone dialer and call handler. + ''; + }; + }; + + config = mkIf cfg.enable { + environment.systemPackages = [ + pkgs.calls + ]; + + services.dbus.packages = [ + pkgs.callaudiod + ]; + }; +} diff --git a/nixos/modules/security/apparmor/includes.nix b/nixos/modules/security/apparmor/includes.nix index e3dd410b3bb..f290e95a296 100644 --- a/nixos/modules/security/apparmor/includes.nix +++ b/nixos/modules/security/apparmor/includes.nix @@ -95,7 +95,7 @@ config.security.apparmor.includes = { include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/consoles" ''; "abstractions/cups-client" = '' - include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/cpus-client" + include "${pkgs.apparmor-profiles}/etc/apparmor.d/abstractions/cups-client" ${etcRule "cups/cups-client.conf"} ''; "abstractions/dbus-session-strict" = '' diff --git a/nixos/modules/services/continuous-integration/github-runner.nix b/nixos/modules/services/continuous-integration/github-runner.nix index 9627b723f8f..f951c155323 100644 --- a/nixos/modules/services/continuous-integration/github-runner.nix +++ b/nixos/modules/services/continuous-integration/github-runner.nix @@ -98,6 +98,14 @@ in ''; default = [ ]; }; + + package = mkOption { + type = types.package; + description = '' + Which github-runner derivation to use. + ''; + default = pkgs.github-runner; + }; }; config = mkIf cfg.enable { @@ -131,7 +139,7 @@ in ] ++ cfg.extraPackages; serviceConfig = rec { - ExecStart = "${pkgs.github-runner}/bin/runsvc.sh"; + ExecStart = "${cfg.package}/bin/runsvc.sh"; # Does the following, sequentially: # - Copy the current and the previous `tokenFile` to the $RUNTIME_DIRECTORY @@ -208,7 +216,7 @@ in if [[ -z "$empty" ]]; then echo "Configuring GitHub Actions Runner" token=$(< "$RUNTIME_DIRECTORY"/${newConfigTokenFilename}) - RUNNER_ROOT="$STATE_DIRECTORY" ${pkgs.github-runner}/bin/config.sh \ + RUNNER_ROOT="$STATE_DIRECTORY" ${cfg.package}/bin/config.sh \ --unattended \ --work "$RUNTIME_DIRECTORY" \ --url ${escapeShellArg cfg.url} \ diff --git a/nixos/modules/services/mail/postfixadmin.nix b/nixos/modules/services/mail/postfixadmin.nix new file mode 100644 index 00000000000..f5c8efb3076 --- /dev/null +++ b/nixos/modules/services/mail/postfixadmin.nix @@ -0,0 +1,199 @@ +{ lib, config, pkgs, ... }: + +with lib; + +let + cfg = config.services.postfixadmin; + fpm = config.services.phpfpm.pools.postfixadmin; + localDB = cfg.database.host == "localhost"; + user = if localDB then cfg.database.username else "nginx"; +in +{ + options.services.postfixadmin = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable postfixadmin. + + Also enables nginx virtual host management. + Further nginx configuration can be done by adapting <literal>services.nginx.virtualHosts.<name></literal>. + See <xref linkend="opt-services.nginx.virtualHosts"/> for further information. + ''; + }; + + hostName = mkOption { + type = types.str; + example = "postfixadmin.example.com"; + description = "Hostname to use for the nginx vhost"; + }; + + adminEmail = mkOption { + type = types.str; + example = "postmaster@example.com"; + description = '' + Defines the Site Admin's email address. + This will be used to send emails from to create mailboxes and + from Send Email / Broadcast message pages. + ''; + }; + + setupPasswordFile = mkOption { + type = types.path; + description = '' + Password file for the admin. + Generate with <literal>php -r "echo password_hash('some password here', PASSWORD_DEFAULT);"</literal> + ''; + }; + + database = { + username = mkOption { + type = types.str; + default = "postfixadmin"; + description = '' + Username for the postgresql connection. + If <literal>database.host</literal> is set to <literal>localhost</literal>, a unix user and group of the same name will be created as well. + ''; + }; + host = mkOption { + type = types.str; + default = "localhost"; + description = '' + Host of the postgresql server. If this is not set to + <literal>localhost</literal>, you have to create the + postgresql user and database yourself, with appropriate + permissions. + ''; + }; + passwordFile = mkOption { + type = types.path; + description = "Password file for the postgresql connection. Must be readable by user <literal>nginx</literal>."; + }; + dbname = mkOption { + type = types.str; + default = "postfixadmin"; + description = "Name of the postgresql database"; + }; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = "Extra configuration for the postfixadmin instance, see postfixadmin's config.inc.php for available options."; + }; + }; + + config = mkIf cfg.enable { + environment.etc."postfixadmin/config.local.php".text = '' + <?php + + $CONF['setup_password'] = file_get_contents('${cfg.setupPasswordFile}'); + + $CONF['database_type'] = 'pgsql'; + $CONF['database_host'] = ${if localDB then "null" else "'${cfg.database.host}'"}; + ${optionalString localDB "$CONF['database_user'] = '${cfg.database.username}';"} + $CONF['database_password'] = ${if localDB then "'dummy'" else "file_get_contents('${cfg.database.passwordFile}')"}; + $CONF['database_name'] = '${cfg.database.dbname}'; + $CONF['configured'] = true; + + ${cfg.extraConfig} + ''; + + systemd.tmpfiles.rules = [ "d /var/cache/postfixadmin/templates_c 700 ${user} ${user}" ]; + + services.nginx = { + enable = true; + virtualHosts = { + ${cfg.hostName} = { + forceSSL = mkDefault true; + enableACME = mkDefault true; + locations."/" = { + root = "${pkgs.postfixadmin}/public"; + index = "index.php"; + extraConfig = '' + location ~* \.php$ { + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass unix:${fpm.socket}; + include ${pkgs.nginx}/conf/fastcgi_params; + include ${pkgs.nginx}/conf/fastcgi.conf; + } + ''; + }; + }; + }; + }; + + services.postgresql = mkIf localDB { + enable = true; + ensureUsers = [ { + name = cfg.database.username; + } ]; + }; + # The postgresql module doesn't currently support concepts like + # objects owners and extensions; for now we tack on what's needed + # here. + systemd.services.postfixadmin-postgres = let pgsql = config.services.postgresql; in mkIf localDB { + after = [ "postgresql.service" ]; + bindsTo = [ "postgresql.service" ]; + wantedBy = [ "multi-user.target" ]; + path = [ + pgsql.package + pkgs.util-linux + ]; + script = '' + set -eu + + PSQL() { + psql --port=${toString pgsql.port} "$@" + } + + PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '${cfg.database.dbname}'" | grep -q 1 || PSQL -tAc 'CREATE DATABASE "${cfg.database.dbname}" OWNER "${cfg.database.username}"' + current_owner=$(PSQL -tAc "SELECT pg_catalog.pg_get_userbyid(datdba) FROM pg_catalog.pg_database WHERE datname = '${cfg.database.dbname}'") + if [[ "$current_owner" != "${cfg.database.username}" ]]; then + PSQL -tAc 'ALTER DATABASE "${cfg.database.dbname}" OWNER TO "${cfg.database.username}"' + if [[ -e "${config.services.postgresql.dataDir}/.reassigning_${cfg.database.dbname}" ]]; then + echo "Reassigning ownership of database ${cfg.database.dbname} to user ${cfg.database.username} failed on last boot. Failing..." + exit 1 + fi + touch "${config.services.postgresql.dataDir}/.reassigning_${cfg.database.dbname}" + PSQL "${cfg.database.dbname}" -tAc "REASSIGN OWNED BY \"$current_owner\" TO \"${cfg.database.username}\"" + rm "${config.services.postgresql.dataDir}/.reassigning_${cfg.database.dbname}" + fi + ''; + + serviceConfig = { + User = pgsql.superUser; + Type = "oneshot"; + RemainAfterExit = true; + }; + }; + + users.users.${user} = mkIf localDB { + group = user; + isSystemUser = true; + createHome = false; + }; + users.groups.${user} = mkIf localDB {}; + + services.phpfpm.pools.postfixadmin = { + user = user; + phpPackage = pkgs.php74; + phpOptions = '' + error_log = 'stderr' + log_errors = on + ''; + settings = mapAttrs (name: mkDefault) { + "listen.owner" = "nginx"; + "listen.group" = "nginx"; + "listen.mode" = "0660"; + "pm" = "dynamic"; + "pm.max_children" = 75; + "pm.start_servers" = 2; + "pm.min_spare_servers" = 1; + "pm.max_spare_servers" = 20; + "pm.max_requests" = 500; + "catch_workers_output" = true; + }; + }; + }; +} diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix index 1514cc0665d..805deeee0c0 100644 --- a/nixos/modules/services/misc/gitlab.nix +++ b/nixos/modules/services/misc/gitlab.nix @@ -117,6 +117,7 @@ let shared.path = "${cfg.statePath}/shared"; gitaly.client_path = "${cfg.packages.gitaly}/bin"; backup = { + gitaly_backup_path = "${cfg.packages.gitaly}/bin/gitaly-backup"; path = cfg.backup.path; keep_time = cfg.backup.keepTime; } // (optionalAttrs (cfg.backup.uploadOptions != {}) { @@ -1299,7 +1300,7 @@ in { Restart = "on-failure"; WorkingDirectory = gitlabEnv.HOME; ExecStart = - "${cfg.packages.gitlab-workhorse}/bin/gitlab-workhorse " + "${cfg.packages.gitlab-workhorse}/bin/workhorse " + "-listenUmask 0 " + "-listenNetwork unix " + "-listenAddr /run/gitlab/gitlab-workhorse.socket " @@ -1352,9 +1353,8 @@ in { procps gnupg ]; - serviceConfig = { - Type = "simple"; + Type = "notify"; User = cfg.user; Group = cfg.group; TimeoutSec = "infinity"; diff --git a/nixos/modules/services/misc/home-assistant.nix b/nixos/modules/services/misc/home-assistant.nix index f1a1a88580e..73ec3b9a17a 100644 --- a/nixos/modules/services/misc/home-assistant.nix +++ b/nixos/modules/services/misc/home-assistant.nix @@ -285,6 +285,7 @@ in { "alarmdecoder" "arduino" "blackbird" + "deconz" "dsmr" "edl21" "elkm1" diff --git a/nixos/modules/services/misc/octoprint.nix b/nixos/modules/services/misc/octoprint.nix index c926d889b37..7129ac69527 100644 --- a/nixos/modules/services/misc/octoprint.nix +++ b/nixos/modules/services/misc/octoprint.nix @@ -122,6 +122,9 @@ in ExecStart = "${pluginsEnv}/bin/octoprint serve -b ${cfg.stateDir}"; User = cfg.user; Group = cfg.group; + SupplementaryGroups = [ + "dialout" + ]; }; }; diff --git a/nixos/modules/services/misc/paperless-ng.nix b/nixos/modules/services/misc/paperless-ng.nix index 03773510018..4b7087e17f9 100644 --- a/nixos/modules/services/misc/paperless-ng.nix +++ b/nixos/modules/services/misc/paperless-ng.nix @@ -29,6 +29,7 @@ let "-/etc/nsswitch.conf" "-/etc/hosts" "-/etc/localtime" + "-/run/postgresql" ]; BindPaths = [ cfg.consumptionDir @@ -60,7 +61,7 @@ let ProtectKernelModules = true; ProtectKernelTunables = true; ProtectProc = "invisible"; - RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; + RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ]; RestrictNamespaces = true; RestrictRealtime = true; RestrictSUIDSGID = true; diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix index e0b2624b6ca..fb67bbfb842 100644 --- a/nixos/modules/services/monitoring/grafana.nix +++ b/nixos/modules/services/monitoring/grafana.nix @@ -6,6 +6,8 @@ let cfg = config.services.grafana; opt = options.services.grafana; declarativePlugins = pkgs.linkFarm "grafana-plugins" (builtins.map (pkg: { name = pkg.pname; path = pkg; }) cfg.declarativePlugins); + useMysql = cfg.database.type == "mysql"; + usePostgresql = cfg.database.type == "postgres"; envOptions = { PATHS_DATA = cfg.dataDir; @@ -635,7 +637,7 @@ in { systemd.services.grafana = { description = "Grafana Service Daemon"; wantedBy = ["multi-user.target"]; - after = ["networking.target"]; + after = ["networking.target"] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service"; environment = { QT_QPA_PLATFORM = "offscreen"; } // mapAttrs' (n: v: nameValuePair "GF_${n}" (toString v)) envOptions; diff --git a/nixos/modules/services/x11/display-managers/sx.nix b/nixos/modules/services/x11/display-managers/sx.nix new file mode 100644 index 00000000000..132531c0ddc --- /dev/null +++ b/nixos/modules/services/x11/display-managers/sx.nix @@ -0,0 +1,37 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let cfg = config.services.xserver.displayManager.sx; + +in { + options = { + services.xserver.displayManager.sx = { + enable = mkEnableOption "sx pseudo-display manager" // { + description = '' + Whether to enable the "sx" pseudo-display manager, which allows users + to start manually via the "sx" command from a vt shell. The X server + runs under the user's id, not as root. The user must provide a + ~/.config/sx/sxrc file containing session startup commands, see + sx(1). This is not automatically generated from the desktopManager + and windowManager settings. sx doesn't have a way to directly set + X server flags, but it can be done by overriding its xorgserver + dependency. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + environment.systemPackages = [ pkgs.sx ]; + services.xserver = { + exportConfiguration = true; + displayManager = { + job.execCmd = ""; + lightdm.enable = mkForce false; + }; + logFile = mkDefault null; + }; + systemd.services.display-manager.enable = false; + }; +} diff --git a/nixos/modules/services/x11/window-managers/awesome.nix b/nixos/modules/services/x11/window-managers/awesome.nix index 089e9f769f0..37a14e34f57 100644 --- a/nixos/modules/services/x11/window-managers/awesome.nix +++ b/nixos/modules/services/x11/window-managers/awesome.nix @@ -27,7 +27,7 @@ in default = []; type = types.listOf types.package; description = "List of lua packages available for being used in the Awesome configuration."; - example = literalExample "[ luaPackages.oocairo ]"; + example = literalExample "[ pkgs.luaPackages.vicious ]"; }; package = mkOption { diff --git a/nixos/modules/system/boot/stage-1-init.sh b/nixos/modules/system/boot/stage-1-init.sh index ddaf985878e..3dfcc010b64 100644 --- a/nixos/modules/system/boot/stage-1-init.sh +++ b/nixos/modules/system/boot/stage-1-init.sh @@ -542,7 +542,7 @@ while read -u 3 mountPoint; do # If copytoram is enabled: skip mounting the ISO and copy its content to a tmpfs. if [ -n "$copytoram" ] && [ "$device" = /dev/root ] && [ "$mountPoint" = /iso ]; then fsType=$(blkid -o value -s TYPE "$device") - fsSize=$(blockdev --getsize64 "$device") + fsSize=$(blockdev --getsize64 "$device" || stat -Lc '%s' "$device") mkdir -p /tmp-iso mount -t "$fsType" /dev/root /tmp-iso diff --git a/nixos/modules/tasks/filesystems.nix b/nixos/modules/tasks/filesystems.nix index ea13d396c46..4f56504f45e 100644 --- a/nixos/modules/tasks/filesystems.nix +++ b/nixos/modules/tasks/filesystems.nix @@ -264,6 +264,8 @@ in # # To make changes, edit the fileSystems and swapDevices NixOS options # in your /etc/nixos/configuration.nix file. + # + # <file system> <mount point> <type> <options> <dump> <pass> # Filesystems. ${concatMapStrings (fs: diff --git a/nixos/modules/tasks/filesystems/exfat.nix b/nixos/modules/tasks/filesystems/exfat.nix index 1527f993fdd..540b9b91c3e 100644 --- a/nixos/modules/tasks/filesystems/exfat.nix +++ b/nixos/modules/tasks/filesystems/exfat.nix @@ -4,8 +4,10 @@ with lib; { config = mkIf (any (fs: fs == "exfat") config.boot.supportedFilesystems) { - - system.fsPackages = [ pkgs.exfat ]; - + system.fsPackages = if config.boot.kernelPackages.kernelOlder "5.7" then [ + pkgs.exfat # FUSE + ] else [ + pkgs.exfatprogs # non-FUSE + ]; }; } diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix index 376d6530f36..cb0e6640247 100644 --- a/nixos/modules/tasks/filesystems/zfs.nix +++ b/nixos/modules/tasks/filesystems/zfs.nix @@ -8,6 +8,7 @@ with lib; let cfgZfs = config.boot.zfs; + cfgExpandOnBoot = config.services.zfs.expandOnBoot; cfgSnapshots = config.services.zfs.autoSnapshot; cfgSnapFlags = cfgSnapshots.flags; cfgScrub = config.services.zfs.autoScrub; @@ -200,7 +201,6 @@ in an interactive prompt (keylocation=prompt) and from a file (keylocation=file://). ''; }; - }; services.zfs.autoSnapshot = { @@ -327,6 +327,23 @@ in }; }; + services.zfs.expandOnBoot = mkOption { + type = types.either (types.enum [ "disabled" "all" ]) (types.listOf types.str); + default = "disabled"; + example = [ "tank" "dozer" ]; + description = '' + After importing, expand each device in the specified pools. + + Set the value to the plain string "all" to expand all pools on boot: + + services.zfs.expandOnBoot = "all"; + + or set the value to a list of pools to expand the disks of specific pools: + + services.zfs.expandOnBoot = [ "tank" "dozer" ]; + ''; + }; + services.zfs.zed = { enableMail = mkEnableOption "ZED's ability to send emails" // { default = cfgZfs.package.enableMail; @@ -586,6 +603,7 @@ in ${cfgZfs.package}/sbin/zfs set nixos:shutdown-time="$(date)" "${pool}" ''; }; + createZfsService = serv: nameValuePair serv { after = [ "systemd-modules-load.service" ]; @@ -609,6 +627,86 @@ in systemd.targets.zfs.wantedBy = [ "multi-user.target" ]; }) + (mkIf (cfgZfs.enabled && cfgExpandOnBoot != "disabled") { + systemd.services."zpool-expand@" = { + description = "Expand ZFS pools"; + after = [ "zfs.target" ]; + + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + + scriptArgs = "%i"; + path = [ pkgs.gawk cfgZfs.package ]; + + # ZFS has no way of enumerating just devices in a pool in a way + # that 'zpool online -e' supports. Thus, we've implemented a + # bit of a strange approach of highlighting just devices. + # See: https://github.com/openzfs/zfs/issues/12505 + script = let + # This UUID has been chosen at random and is to provide a + # collision-proof, predictable token to search for + magicIdentifier = "NIXOS-ZFS-ZPOOL-DEVICE-IDENTIFIER-37108bec-aff6-4b58-9e5e-53c7c9766f05"; + zpoolScripts = pkgs.writeShellScriptBin "device-highlighter" '' + echo "${magicIdentifier}" + ''; + in '' + pool=$1 + + echo "Expanding all devices for $pool." + + # Put our device-highlighter script it to the PATH + export ZPOOL_SCRIPTS_PATH=${zpoolScripts}/bin + + # Enable running our precisely specified zpool script as root + export ZPOOL_SCRIPTS_AS_ROOT=1 + + devices() ( + zpool status -c device-highlighter "$pool" \ + | awk '($2 == "ONLINE" && $6 == "${magicIdentifier}") { print $1; }' + ) + + for device in $(devices); do + echo "Attempting to expand $device of $pool..." + if ! zpool online -e "$pool" "$device"; then + echo "Failed to expand '$device' of '$pool'." + fi + done + ''; + }; + + systemd.services."zpool-expand-pools" = + let + # Create a string, to be interpolated in a bash script + # which enumerates all of the pools to expand. + # If the `pools` option is `true`, we want to dynamically + # expand every pool. Otherwise we want to enumerate + # just the specifically provided list of pools. + poolListProvider = if cfgExpandOnBoot == "all" + then "$(zpool list -H | awk '{print $1}')" + else lib.escapeShellArgs cfgExpandOnBoot; + in + { + description = "Expand specified ZFS pools"; + wantedBy = [ "default.target" ]; + after = [ "zfs.target" ]; + + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + + path = [ pkgs.gawk cfgZfs.package ]; + + script = '' + for pool in ${poolListProvider}; do + systemctl start --no-block "zpool-expand@$pool" + done + ''; + }; + }) + (mkIf (cfgZfs.enabled && cfgSnapshots.enable) { systemd.services = let descr = name: if name == "frequent" then "15 mins" diff --git a/nixos/modules/virtualisation/amazon-image.nix b/nixos/modules/virtualisation/amazon-image.nix index bf5c04543a7..fe248a94488 100644 --- a/nixos/modules/virtualisation/amazon-image.nix +++ b/nixos/modules/virtualisation/amazon-image.nix @@ -41,17 +41,23 @@ in boot.growPartition = cfg.hvm; - fileSystems."/" = { + fileSystems."/" = mkIf (!cfg.zfs.enable) { device = "/dev/disk/by-label/nixos"; fsType = "ext4"; autoResize = true; }; - fileSystems."/boot" = mkIf cfg.efi { + fileSystems."/boot" = mkIf (cfg.efi || cfg.zfs.enable) { + # The ZFS image uses a partition labeled ESP whether or not we're + # booting with EFI. device = "/dev/disk/by-label/ESP"; fsType = "vfat"; }; + services.zfs.expandOnBoot = mkIf cfg.zfs.enable "all"; + + boot.zfs.devNodes = mkIf cfg.zfs.enable "/dev/"; + boot.extraModulePackages = [ config.boot.kernelPackages.ena ]; diff --git a/nixos/modules/virtualisation/amazon-options.nix b/nixos/modules/virtualisation/amazon-options.nix index 2e807131e93..698edcd835a 100644 --- a/nixos/modules/virtualisation/amazon-options.nix +++ b/nixos/modules/virtualisation/amazon-options.nix @@ -1,7 +1,46 @@ { config, lib, pkgs, ... }: -{ +let + inherit (lib) types; +in { options = { ec2 = { + zfs = { + enable = lib.mkOption { + default = false; + internal = true; + description = '' + Whether the EC2 instance uses a ZFS root. + ''; + }; + + datasets = lib.mkOption { + description = '' + Datasets to create under the `tank` and `boot` zpools. + + **NOTE:** This option is used only at image creation time, and + does not attempt to declaratively create or manage datasets + on an existing system. + ''; + + default = {}; + + type = types.attrsOf (types.submodule { + options = { + mount = lib.mkOption { + description = "Where to mount this dataset."; + type = types.nullOr types.string; + default = null; + }; + + properties = lib.mkOption { + description = "Properties to set on this dataset."; + type = types.attrsOf types.string; + default = {}; + }; + }; + }); + }; + }; hvm = lib.mkOption { default = lib.versionAtLeast config.system.stateVersion "17.03"; internal = true; @@ -18,4 +57,17 @@ }; }; }; + + config = lib.mkIf config.ec2.zfs.enable { + networking.hostId = lib.mkDefault "00000000"; + + fileSystems = let + mountable = lib.filterAttrs (_: value: ((value.mount or null) != null)) config.ec2.zfs.datasets; + in lib.mapAttrs' + (dataset: opts: lib.nameValuePair opts.mount { + device = dataset; + fsType = "zfs"; + }) + mountable; + }; } |