diff options
Diffstat (limited to 'nixos')
27 files changed, 1105 insertions, 93 deletions
diff --git a/nixos/doc/manual/development/replace-modules.section.md b/nixos/doc/manual/development/replace-modules.section.md index 0c0d6a7ac2f1..ac9f5adbaf98 100644 --- a/nixos/doc/manual/development/replace-modules.section.md +++ b/nixos/doc/manual/development/replace-modules.section.md @@ -8,8 +8,15 @@ the system on a stable release. `disabledModules` is a top level attribute like `imports`, `options` and `config`. It contains a list of modules that will be disabled. This can -either be the full path to the module or a string with the filename -relative to the modules path (eg. \<nixpkgs/nixos/modules> for nixos). +either be: + - the full path to the module, + - or a string with the filename relative to the modules path (eg. \<nixpkgs/nixos/modules> for nixos), + - or an attribute set containing a specific `key` attribute. + +The latter allows some modules to be disabled, despite them being distributed +via attributes instead of file paths. The `key` should be globally unique, so +it is recommended to include a file path in it, or rely on a framework to do it +for you. This example will replace the existing postgresql module with the version defined in the nixos-unstable channel while keeping the rest of diff --git a/nixos/doc/manual/release-notes/rl-2305.section.md b/nixos/doc/manual/release-notes/rl-2305.section.md index 3e63ddced611..1bd8906cf64f 100644 --- a/nixos/doc/manual/release-notes/rl-2305.section.md +++ b/nixos/doc/manual/release-notes/rl-2305.section.md @@ -34,6 +34,8 @@ In addition to numerous new and upgraded packages, this release has the followin - [fzf](https://github.com/junegunn/fzf), a command line fuzzyfinder. Available as [programs.fzf](#opt-programs.fzf.fuzzyCompletion). +- [gemstash](https://github.com/rubygems/gemstash), a RubyGems.org cache and private gem server. Available as [services.gemstash](#opt-services.gemstash.enable). + - [gmediarender](https://github.com/hzeller/gmrender-resurrect), a simple, headless UPnP/DLNA renderer. Available as [services.gmediarender](options.html#opt-services.gmediarender.enable). - [stevenblack-blocklist](https://github.com/StevenBlack/hosts), A unified hosts file with base extensions for blocking unwanted websites. Available as [networking.stevenblack](options.html#opt-networking.stevenblack.enable). @@ -58,6 +60,8 @@ In addition to numerous new and upgraded packages, this release has the followin - [ulogd](https://www.netfilter.org/projects/ulogd/index.html), a userspace logging daemon for netfilter/iptables related logging. Available as [services.ulogd](options.html#opt-services.ulogd.enable). +- [jellyseerr](https://github.com/Fallenbagel/jellyseerr), a web-based requests manager for Jellyfin, forked from Overseerr. Available as [services.jellyseerr](#opt-services.jellyseerr.enable). + - [photoprism](https://photoprism.app/), a AI-Powered Photos App for the Decentralized Web. Available as [services.photoprism](options.html#opt-services.photoprism.enable). - [autosuspend](https://github.com/languitar/autosuspend), a python daemon that suspends a system if certain conditions are met, or not met. diff --git a/nixos/modules/config/no-x-libs.nix b/nixos/modules/config/no-x-libs.nix index 763c2038f6d4..3ebe2fa9f164 100644 --- a/nixos/modules/config/no-x-libs.nix +++ b/nixos/modules/config/no-x-libs.nix @@ -61,7 +61,7 @@ with lib; pinentry = super.pinentry.override { enabledFlavors = [ "curses" "tty" "emacs" ]; withLibsecret = false; }; qemu = super.qemu.override { gtkSupport = false; spiceSupport = false; sdlSupport = false; }; qrencode = super.qrencode.overrideAttrs (_: { doCheck = false; }); - qt5 = super.qt5.overrideScope' (const (super': { + qt5 = super.qt5.overrideScope (const (super': { qtbase = super'.qtbase.override { withGtk3 = false; }; })); stoken = super.stoken.override { withGTK3 = false; }; diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 2b046d19d758..e1e30c99a447 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -440,6 +440,7 @@ ./services/development/blackfire.nix ./services/development/bloop.nix ./services/development/distccd.nix + ./services/development/gemstash.nix ./services/development/hoogle.nix ./services/development/jupyter/default.nix ./services/development/jupyterhub/default.nix @@ -624,6 +625,7 @@ ./services/misc/irkerd.nix ./services/misc/jackett.nix ./services/misc/jellyfin.nix + ./services/misc/jellyseerr.nix ./services/misc/klipper.nix ./services/misc/languagetool.nix ./services/misc/leaps.nix @@ -1129,6 +1131,7 @@ ./services/web-apps/baget.nix ./services/web-apps/bookstack.nix ./services/web-apps/calibre-web.nix + ./services/web-apps/coder.nix ./services/web-apps/changedetection-io.nix ./services/web-apps/cloudlog.nix ./services/web-apps/code-server.nix diff --git a/nixos/modules/programs/java.nix b/nixos/modules/programs/java.nix index 4f03c1f3ff25..c5f83858d06a 100644 --- a/nixos/modules/programs/java.nix +++ b/nixos/modules/programs/java.nix @@ -8,7 +8,6 @@ with lib; let cfg = config.programs.java; in - { options = { @@ -40,12 +39,35 @@ in type = types.package; }; + binfmt = mkEnableOption (lib.mdDoc "binfmt to execute java jar's and classes"); + }; }; config = mkIf cfg.enable { + boot.binfmt.registrations = mkIf cfg.binfmt { + java-class = { + recognitionType = "extension"; + magicOrExtension = "class"; + interpreter = pkgs.writeShellScript "java-class-wrapper" '' + test -e ${cfg.package}/nix-support/setup-hook && source ${cfg.package}/nix-support/setup-hook + classpath=$(dirname "$1") + class=$(basename "''${1%%.class}") + $JAVA_HOME/bin/java -classpath "$classpath" "$class" "''${@:2}" + ''; + }; + java-jar = { + recognitionType = "extension"; + magicOrExtension = "jar"; + interpreter = pkgs.writeShellScript "java-jar-wrapper" '' + test -e ${cfg.package}/nix-support/setup-hook && source ${cfg.package}/nix-support/setup-hook + $JAVA_HOME/bin/java -jar "$@" + ''; + }; + }; + environment.systemPackages = [ cfg.package ]; environment.shellInit = '' diff --git a/nixos/modules/programs/waybar.nix b/nixos/modules/programs/waybar.nix index 4697d0f7a622..2c49ae140813 100644 --- a/nixos/modules/programs/waybar.nix +++ b/nixos/modules/programs/waybar.nix @@ -2,17 +2,22 @@ with lib; +let + cfg = config.programs.waybar; +in { options.programs.waybar = { enable = mkEnableOption (lib.mdDoc "waybar"); + package = mkPackageOptionMD pkgs "waybar" { }; }; - config = mkIf config.programs.waybar.enable { + config = mkIf cfg.enable { + environment.systemPackages = [ cfg.package ]; systemd.user.services.waybar = { description = "Waybar as systemd service"; wantedBy = [ "graphical-session.target" ]; partOf = [ "graphical-session.target" ]; - script = "${pkgs.waybar}/bin/waybar"; + script = "${cfg.package}/bin/waybar"; }; }; diff --git a/nixos/modules/services/backup/btrbk.nix b/nixos/modules/services/backup/btrbk.nix index b6eb68cc43f1..b838c174553d 100644 --- a/nixos/modules/services/backup/btrbk.nix +++ b/nixos/modules/services/backup/btrbk.nix @@ -47,7 +47,12 @@ let then [ "${name} ${value}" ] else concatLists (mapAttrsToList (genSection name) value); - addDefaults = settings: { backend = "btrfs-progs-sudo"; } // settings; + sudo_doas = + if config.security.sudo.enable then "sudo" + else if config.security.doas.enable then "doas" + else throw "The btrbk nixos module needs either sudo or doas enabled in the configuration"; + + addDefaults = settings: { backend = "btrfs-progs-${sudo_doas}"; } // settings; mkConfigFile = name: settings: pkgs.writeTextFile { name = "btrbk-${name}.conf"; @@ -152,20 +157,41 @@ in }; config = mkIf (sshEnabled || serviceEnabled) { environment.systemPackages = [ pkgs.btrbk ] ++ cfg.extraPackages; - security.sudo.extraRules = [ - { - users = [ "btrbk" ]; - commands = [ - { command = "${pkgs.btrfs-progs}/bin/btrfs"; options = [ "NOPASSWD" ]; } - { command = "${pkgs.coreutils}/bin/mkdir"; options = [ "NOPASSWD" ]; } - { command = "${pkgs.coreutils}/bin/readlink"; options = [ "NOPASSWD" ]; } - # for ssh, they are not the same than the one hard coded in ${pkgs.btrbk} - { command = "/run/current-system/bin/btrfs"; options = [ "NOPASSWD" ]; } - { command = "/run/current-system/sw/bin/mkdir"; options = [ "NOPASSWD" ]; } - { command = "/run/current-system/sw/bin/readlink"; options = [ "NOPASSWD" ]; } + security.sudo = mkIf (sudo_doas == "sudo") { + extraRules = [ + { + users = [ "btrbk" ]; + commands = [ + { command = "${pkgs.btrfs-progs}/bin/btrfs"; options = [ "NOPASSWD" ]; } + { command = "${pkgs.coreutils}/bin/mkdir"; options = [ "NOPASSWD" ]; } + { command = "${pkgs.coreutils}/bin/readlink"; options = [ "NOPASSWD" ]; } + # for ssh, they are not the same than the one hard coded in ${pkgs.btrbk} + { command = "/run/current-system/bin/btrfs"; options = [ "NOPASSWD" ]; } + { command = "/run/current-system/sw/bin/mkdir"; options = [ "NOPASSWD" ]; } + { command = "/run/current-system/sw/bin/readlink"; options = [ "NOPASSWD" ]; } + ]; + } + ]; + }; + security.doas = mkIf (sudo_doas == "doas") { + extraRules = let + doasCmdNoPass = cmd: { users = [ "btrbk" ]; cmd = cmd; noPass = true; }; + in + [ + (doasCmdNoPass "${pkgs.btrfs-progs}/bin/btrfs") + (doasCmdNoPass "${pkgs.coreutils}/bin/mkdir") + (doasCmdNoPass "${pkgs.coreutils}/bin/readlink") + # for ssh, they are not the same than the one hard coded in ${pkgs.btrbk} + (doasCmdNoPass "/run/current-system/bin/btrfs") + (doasCmdNoPass "/run/current-system/sw/bin/mkdir") + (doasCmdNoPass "/run/current-system/sw/bin/readlink") + + # doas matches command, not binary + (doasCmdNoPass "btrfs") + (doasCmdNoPass "mkdir") + (doasCmdNoPass "readlink") ]; - } - ]; + }; users.users.btrbk = { isSystemUser = true; # ssh needs a home directory @@ -183,8 +209,9 @@ in "best-effort" = 2; "realtime" = 1; }.${cfg.ioSchedulingClass}; + sudo_doas_flag = "--${sudo_doas}"; in - ''command="${pkgs.util-linux}/bin/ionice -t -c ${toString ioniceClass} ${optionalString (cfg.niceness >= 1) "${pkgs.coreutils}/bin/nice -n ${toString cfg.niceness}"} ${pkgs.btrbk}/share/btrbk/scripts/ssh_filter_btrbk.sh --sudo ${options}" ${v.key}'' + ''command="${pkgs.util-linux}/bin/ionice -t -c ${toString ioniceClass} ${optionalString (cfg.niceness >= 1) "${pkgs.coreutils}/bin/nice -n ${toString cfg.niceness}"} ${pkgs.btrbk}/share/btrbk/scripts/ssh_filter_btrbk.sh ${sudo_doas_flag} ${options}" ${v.key}'' ) cfg.sshAccess; }; diff --git a/nixos/modules/services/continuous-integration/hydra/default.nix b/nixos/modules/services/continuous-integration/hydra/default.nix index 564bcd37dec5..83078706fcae 100644 --- a/nixos/modules/services/continuous-integration/hydra/default.nix +++ b/nixos/modules/services/continuous-integration/hydra/default.nix @@ -398,7 +398,7 @@ in systemd.services.hydra-evaluator = { wantedBy = [ "multi-user.target" ]; requires = [ "hydra-init.service" ]; - after = [ "hydra-init.service" "network.target" ]; + after = [ "hydra-init.service" "network.target" "network-online.target" ]; path = with pkgs; [ hydra-package nettools jq ]; restartTriggers = [ hydraConf ]; environment = env // { diff --git a/nixos/modules/services/development/gemstash.nix b/nixos/modules/services/development/gemstash.nix new file mode 100644 index 000000000000..eb7ccb98bde8 --- /dev/null +++ b/nixos/modules/services/development/gemstash.nix @@ -0,0 +1,103 @@ +{ lib, pkgs, config, ... }: +with lib; + +let + settingsFormat = pkgs.formats.yaml { }; + + # gemstash uses a yaml config where the keys are ruby symbols, + # which means they start with ':'. This would be annoying to use + # on the nix side, so we rewrite plain names instead. + prefixColon = s: listToAttrs (map + (attrName: { + name = ":${attrName}"; + value = + if isAttrs s.${attrName} + then prefixColon s."${attrName}" + else s."${attrName}"; + }) + (attrNames s)); + + # parse the port number out of the tcp://ip:port bind setting string + parseBindPort = bind: strings.toInt (last (strings.splitString ":" bind)); + + cfg = config.services.gemstash; +in +{ + options.services.gemstash = { + enable = mkEnableOption (lib.mdDoc "gemstash service"); + + openFirewall = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Whether to open the firewall for the port in {option}`services.gemstash.bind`. + ''; + }; + + settings = mkOption { + default = {}; + description = lib.mdDoc '' + Configuration for Gemstash. The details can be found at in + [gemstash documentation](https://github.com/rubygems/gemstash/blob/master/man/gemstash-configuration.5.md). + Each key set here is automatically prefixed with ":" to match the gemstash expectations. + ''; + type = types.submodule { + freeformType = settingsFormat.type; + options = { + base_path = mkOption { + type = types.path; + default = "/var/lib/gemstash"; + description = lib.mdDoc "Path to store the gem files and the sqlite database. If left unchanged, the directory will be created."; + }; + bind = mkOption { + type = types.str; + default = "tcp://0.0.0.0:9292"; + description = lib.mdDoc "Host and port combination for the server to listen on."; + }; + db_adapter = mkOption { + type = types.nullOr (types.enum [ "sqlite3" "postgres" "mysql" "mysql2" ]); + default = null; + description = lib.mdDoc "Which database type to use. For choices other than sqlite3, the dbUrl has to be specified as well."; + }; + db_url = mkOption { + type = types.nullOr types.str; + default = null; + description = lib.mdDoc "The database to connect to when using postgres, mysql, or mysql2."; + }; + }; + }; + }; + }; + + config = + mkIf cfg.enable { + users = { + users.gemstash = { + group = "gemstash"; + isSystemUser = true; + }; + groups.gemstash = { }; + }; + + networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ (parseBindPort cfg.settings.bind) ]; + + systemd.services.gemstash = { + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + serviceConfig = mkMerge [ + { + ExecStart = "${pkgs.gemstash}/bin/gemstash start --no-daemonize --config-file ${settingsFormat.generate "gemstash.yaml" (prefixColon cfg.settings)}"; + NoNewPrivileges = true; + User = "gemstash"; + Group = "gemstash"; + PrivateTmp = true; + RestrictSUIDSGID = true; + LockPersonality = true; + } + (mkIf (cfg.settings.base_path == "/var/lib/gemstash") { + StateDirectory = "gemstash"; + }) + ]; + }; + }; +} diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix index 179359c97a3a..c7299c1ccad8 100644 --- a/nixos/modules/services/misc/gitlab.nix +++ b/nixos/modules/services/misc/gitlab.nix @@ -89,11 +89,6 @@ let }; }; - pagesArgs = [ - "-pages-domain" gitlabConfig.production.pages.host - "-pages-root" "${gitlabConfig.production.shared.path}/pages" - ] ++ cfg.pagesExtraArgs; - gitlabConfig = { # These are the default settings from config/gitlab.example.yml production = flip recursiveUpdate cfg.extraConfig { @@ -161,6 +156,12 @@ let }; extra = {}; uploads.storage_path = cfg.statePath; + pages = { + enabled = cfg.pages.enable; + port = 8090; + host = cfg.pages.settings.pages-domain; + secret_file = cfg.pages.settings.api-secret-key; + }; }; }; @@ -246,6 +247,7 @@ in { (mkRenamedOptionModule [ "services" "gitlab" "backupPath" ] [ "services" "gitlab" "backup" "path" ]) (mkRemovedOptionModule [ "services" "gitlab" "satelliteDir" ] "") (mkRemovedOptionModule [ "services" "gitlab" "logrotate" "extraConfig" ] "Modify services.logrotate.settings.gitlab directly instead") + (mkRemovedOptionModule [ "services" "gitlab" "pagesExtraArgs" ] "Use services.gitlab.pages.settings instead") ]; options = { @@ -667,10 +669,127 @@ in { }; }; - pagesExtraArgs = mkOption { - type = types.listOf types.str; - default = [ "-listen-proxy" "127.0.0.1:8090" ]; - description = lib.mdDoc "Arguments to pass to the gitlab-pages daemon"; + pages.enable = mkEnableOption (lib.mdDoc "the GitLab Pages service"); + + pages.settings = mkOption { + example = literalExpression '' + { + pages-domain = "example.com"; + auth-client-id = "generated-id-xxxxxxx"; + auth-client-secret = { _secret = "/var/keys/auth-client-secret"; }; + auth-redirect-uri = "https://projects.example.com/auth"; + auth-secret = { _secret = "/var/keys/auth-secret"; }; + auth-server = "https://gitlab.example.com"; + } + ''; + + description = lib.mdDoc '' + Configuration options to set in the GitLab Pages config + file. + + Options containing secret data should be set to an attribute + set containing the attribute `_secret` - a string pointing + to a file containing the value the option should be set + to. See the example to get a better picture of this: in the + resulting configuration file, the `auth-client-secret` and + `auth-secret` keys will be set to the contents of the + {file}`/var/keys/auth-client-secret` and + {file}`/var/keys/auth-secret` files respectively. + ''; + + type = types.submodule { + freeformType = with types; attrsOf (nullOr (oneOf [ str int bool attrs ])); + + options = { + listen-http = mkOption { + type = with types; listOf str; + apply = x: if x == [] then null else lib.concatStringsSep "," x; + default = []; + description = lib.mdDoc '' + The address(es) to listen on for HTTP requests. + ''; + }; + + listen-https = mkOption { + type = with types; listOf str; + apply = x: if x == [] then null else lib.concatStringsSep "," x; + default = []; + description = lib.mdDoc '' + The address(es) to listen on for HTTPS requests. + ''; + }; + + listen-proxy = mkOption { + type = with types; listOf str; + apply = x: if x == [] then null else lib.concatStringsSep "," x; + default = [ "127.0.0.1:8090" ]; + description = lib.mdDoc '' + The address(es) to listen on for proxy requests. + ''; + }; + + artifacts-server = mkOption { + type = with types; nullOr str; + default = "http${optionalString cfg.https "s"}://${cfg.host}/api/v4"; + defaultText = "http(s)://<services.gitlab.host>/api/v4"; + example = "https://gitlab.example.com/api/v4"; + description = lib.mdDoc '' + API URL to proxy artifact requests to. + ''; + }; + + gitlab-server = mkOption { + type = with types; nullOr str; + default = "http${optionalString cfg.https "s"}://${cfg.host}"; + defaultText = "http(s)://<services.gitlab.host>"; + example = "https://gitlab.example.com"; + description = lib.mdDoc '' + Public GitLab server URL. + ''; + }; + + internal-gitlab-server = mkOption { + type = with types; nullOr str; + default = null; + defaultText = "http(s)://<services.gitlab.host>"; + example = "https://gitlab.example.internal"; + description = lib.mdDoc '' + Internal GitLab server used for API requests, useful + if you want to send that traffic over an internal load + balancer. By default, the value of + `services.gitlab.pages.settings.gitlab-server` is + used. + ''; + }; + + api-secret-key = mkOption { + type = with types; nullOr str; + default = "${cfg.statePath}/gitlab_pages_secret"; + internal = true; + description = lib.mdDoc '' + File with secret key used to authenticate with the + GitLab API. + ''; + }; + + pages-domain = mkOption { + type = with types; nullOr str; + example = "example.com"; + description = lib.mdDoc '' + The domain to serve static pages on. + ''; + }; + + pages-root = mkOption { + type = types.str; + default = "${gitlabConfig.production.shared.path}/pages"; + defaultText = literalExpression ''config.${opt.extraConfig}.production.shared.path + "/pages"''; + description = lib.mdDoc '' + The directory where pages are stored. + ''; + }; + }; + }; }; secrets.secretFile = mkOption { @@ -1210,6 +1329,9 @@ in { umask u=rwx,g=,o= openssl rand -hex 32 > ${cfg.statePath}/gitlab_shell_secret + ${optionalString cfg.pages.enable '' + openssl rand -base64 32 > ${cfg.pages.settings.api-secret-key} + ''} rm -f '${cfg.statePath}/config/database.yml' @@ -1359,28 +1481,66 @@ in { }; }; - systemd.services.gitlab-pages = mkIf (gitlabConfig.production.pages.enabled or false) { - description = "GitLab static pages daemon"; - after = [ "network.target" "gitlab-config.service" ]; - bindsTo = [ "gitlab-config.service" ]; - wantedBy = [ "gitlab.target" ]; - partOf = [ "gitlab.target" ]; - - path = [ pkgs.unzip ]; - - serviceConfig = { - Type = "simple"; - TimeoutSec = "infinity"; - Restart = "on-failure"; - - User = cfg.user; - Group = cfg.group; - - ExecStart = "${cfg.packages.pages}/bin/gitlab-pages ${escapeShellArgs pagesArgs}"; - WorkingDirectory = gitlabEnv.HOME; - }; + services.gitlab.pages.settings = { + api-secret-key = "${cfg.statePath}/gitlab_pages_secret"; }; + systemd.services.gitlab-pages = + let + filteredConfig = filterAttrs (_: v: v != null) cfg.pages.settings; + isSecret = v: isAttrs v && v ? _secret && isString v._secret; + mkPagesKeyValue = lib.generators.toKeyValue { + mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" rec { + mkValueString = v: + if isInt v then toString v + else if isString v then v + else if true == v then "true" + else if false == v then "false" + else if isSecret v then builtins.hashString "sha256" v._secret + else throw "unsupported type ${builtins.typeOf v}: ${(lib.generators.toPretty {}) v}"; + }; + }; + secretPaths = lib.catAttrs "_secret" (lib.collect isSecret filteredConfig); + mkSecretReplacement = file: '' + replace-secret ${lib.escapeShellArgs [ (builtins.hashString "sha256" file) file "/run/gitlab-pages/gitlab-pages.conf" ]} + ''; + secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths; + configFile = pkgs.writeText "gitlab-pages.conf" (mkPagesKeyValue filteredConfig); + in + mkIf cfg.pages.enable { + description = "GitLab static pages daemon"; + after = [ "network.target" "gitlab-config.service" "gitlab.service" ]; + bindsTo = [ "gitlab-config.service" "gitlab.service" ]; + wantedBy = [ "gitlab.target" ]; + partOf = [ "gitlab.target" ]; + + path = with pkgs; [ + unzip + replace-secret + ]; + + serviceConfig = { + Type = "simple"; + TimeoutSec = "infinity"; + Restart = "on-failure"; + + User = cfg.user; + Group = cfg.group; + + ExecStartPre = pkgs.writeShellScript "gitlab-pages-pre-start" '' + set -o errexit -o pipefail -o nounset + shopt -s dotglob nullglob inherit_errexit + + install -m u=rw ${configFile} /run/gitlab-pages/gitlab-pages.conf + ${secretReplacements} + ''; + ExecStart = "${cfg.packages.pages}/bin/gitlab-pages -config=/run/gitlab-pages/gitlab-pages.conf"; + WorkingDirectory = gitlabEnv.HOME; + RuntimeDirectory = "gitlab-pages"; + RuntimeDirectoryMode = "0700"; + }; + }; + systemd.services.gitlab-workhorse = { after = [ "network.target" ]; wantedBy = [ "gitlab.target" ]; diff --git a/nixos/modules/services/misc/jellyseerr.nix b/nixos/modules/services/misc/jellyseerr.nix new file mode 100644 index 000000000000..31e0c5beb673 --- /dev/null +++ b/nixos/modules/services/misc/jellyseerr.nix @@ -0,0 +1,62 @@ +{ config, pkgs, lib, ... }: + +with lib; +let + cfg = config.services.jellyseerr; +in +{ + meta.maintainers = [ maintainers.camillemndn ]; + + options.services.jellyseerr = { + enable = mkEnableOption (mdDoc ''Jellyseerr, a requests manager for Jellyfin''); + + openFirewall = mkOption { + type = types.bool; + default = false; + description = mdDoc ''Open port in the firewall for the Jellyseerr web interface.''; + }; + + port = mkOption { + type = types.port; + default = 5055; + description = mdDoc ''The port which the Jellyseerr web UI should listen to.''; + }; + }; + + config = mkIf cfg.enable { + systemd.services.jellyseerr = { + description = "Jellyseerr, a requests manager for Jellyfin"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + environment.PORT = toString cfg.port; + serviceConfig = { + Type = "exec"; + StateDirectory = "jellyseerr"; + WorkingDirectory = "${pkgs.jellyseerr}/libexec/jellyseerr/deps/jellyseerr"; + DynamicUser = true; + ExecStart = "${pkgs.jellyseerr}/bin/jellyseerr"; + BindPaths = [ "/var/lib/jellyseerr/:${pkgs.jellyseerr}/libexec/jellyseerr/deps/jellyseerr/config/" ]; + Restart = "on-failure"; + ProtectHome = true; + ProtectSystem = "strict"; + PrivateTmp = true; + PrivateDevices = true; + ProtectHostname = true; + ProtectClock = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectKernelLogs = true; + ProtectControlGroups = true; + NoNewPrivileges = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + RemoveIPC = true; + PrivateMounts = true; + }; + }; + + networking.firewall = mkIf cfg.openFirewall { + allowedTCPPorts = [ cfg.port ]; + }; + }; +} diff --git a/nixos/modules/services/monitoring/prometheus/default.nix b/nixos/modules/services/monitoring/prometheus/default.nix index f516b75ab10f..4f197b9b5820 100644 --- a/nixos/modules/services/monitoring/prometheus/default.nix +++ b/nixos/modules/services/monitoring/prometheus/default.nix @@ -1408,7 +1408,7 @@ let ''; action = - mkDefOpt (types.enum [ "replace" "keep" "drop" "hashmod" "labelmap" "labeldrop" "labelkeep" ]) "replace" '' + mkDefOpt (types.enum [ "replace" "lowercase" "uppercase" "keep" "drop" "hashmod" "labelmap" "labeldrop" "labelkeep" ]) "replace" '' Action to perform based on regex matching. ''; }; diff --git a/nixos/modules/services/networking/murmur.nix b/nixos/modules/services/networking/murmur.nix index 32498ca25ea8..9ec4f57ca43e 100644 --- a/nixos/modules/services/networking/murmur.nix +++ b/nixos/modules/services/networking/murmur.nix @@ -42,6 +42,8 @@ let ${if cfg.sslKey == "" then "" else "sslKey="+cfg.sslKey} ${if cfg.sslCa == "" then "" else "sslCA="+cfg.sslCa} + ${lib.optionalString (cfg.dbus != null) "dbus=${cfg.dbus}"} + ${cfg.extraConfig} ''; in @@ -282,6 +284,12 @@ in `murmur` is running. ''; }; + + dbus = mkOption { + type = types.enum [ null "session" "system" ]; + default = null; + description = lib.mdDoc "Enable D-Bus remote control. Set to the bus you want Murmur to connect to."; + }; }; }; @@ -325,5 +333,27 @@ in Group = "murmur"; }; }; + + # currently not included in upstream package, addition requested at + # https://github.com/mumble-voip/mumble/issues/6078 + services.dbus.packages = mkIf (cfg.dbus == "system") [(pkgs.writeTextFile { + name = "murmur-dbus-policy"; + text = '' + <!DOCTYPE busconfig PUBLIC + "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> + <busconfig> + <policy user="murmur"> + <allow own="net.sourceforge.mumble.murmur"/> + </policy> + + <policy context="default"> + <allow send_destination="net.sourceforge.mumble.murmur"/> + <allow receive_sender="net.sourceforge.mumble.murmur"/> + </policy> + </busconfig> + ''; + destination = "/share/dbus-1/system.d/murmur.conf"; + })]; }; } diff --git a/nixos/modules/services/networking/networkd-dispatcher.nix b/nixos/modules/services/networking/networkd-dispatcher.nix index d13ca23368c5..c5319ca7b88a 100644 --- a/nixos/modules/services/networking/networkd-dispatcher.nix +++ b/nixos/modules/services/networking/networkd-dispatcher.nix @@ -3,8 +3,11 @@ with lib; let + cfg = config.services.networkd-dispatcher; + in { + options = { services.networkd-dispatcher = { @@ -14,14 +17,49 @@ in { for usage. ''); - scriptDir = mkOption { - type = types.path; - default = "/var/lib/networkd-dispatcher"; - description = mdDoc '' - This directory is used for keeping various scripts read and run by - networkd-dispatcher. See [https://gitlab.com/craftyguy/networkd-dispatcher](upstream instructions) - for directory structure and script usage. + rules = mkOption { + default = {}; + example = lib.literalExpression '' + { "restart-tor" = { + onState = ["routable" "off"]; + script = ''' + #!''${pkgs.runtimeShell} + if [[ $IFACE == "wlan0" && $AdministrativeState == "configured" ]]; then + echo "Restarting Tor ..." + systemctl restart tor + fi + exit 0 + '''; + }; + }; + ''; + description = lib.mdDoc '' + Declarative configuration of networkd-dispatcher rules. See + [https://gitlab.com/craftyguy/networkd-dispatcher](upstream instructions) + for an introduction and example scripts. ''; + type = types.attrsOf (types.submodule { + options = { + onState = mkOption { + type = types.listOf (types.enum [ + "routable" "dormant" "no-carrier" "off" "carrier" "degraded" + "configuring" "configured" + ]); + default = null; + description = lib.mdDoc '' + List of names of the systemd-networkd operational states which + should trigger the script. See <https://www.freedesktop.org/software/systemd/man/networkctl.html> + for a description of the specific state type. + ''; + }; + script = mkOption { + type = types.lines; + description = lib.mdDoc '' + Shell commands executed on specified operational states. + ''; + }; + }; + }); }; }; @@ -30,34 +68,31 @@ in { config = mkIf cfg.enable { systemd = { - packages = [ pkgs.networkd-dispatcher ]; services.networkd-dispatcher = { wantedBy = [ "multi-user.target" ]; # Override existing ExecStart definition - serviceConfig.ExecStart = [ + serviceConfig.ExecStart = let + scriptDir = pkgs.symlinkJoin { + name = "networkd-dispatcher-script-dir"; + paths = lib.mapAttrsToList (name: cfg: + (map(state: + pkgs.writeTextFile { + inherit name; + text = cfg.script; + destination = "/${state}.d/${name}"; + executable = true; + } + ) cfg.onState) + ) cfg.rules; + }; + in [ "" - "${pkgs.networkd-dispatcher}/bin/networkd-dispatcher -v --script-dir ${cfg.scriptDir} $networkd_dispatcher_args" + "${pkgs.networkd-dispatcher}/bin/networkd-dispatcher -v --script-dir ${scriptDir} $networkd_dispatcher_args" ]; }; - - # Directory structure required according to upstream instructions - # https://gitlab.com/craftyguy/networkd-dispatcher - tmpfiles.rules = [ - "d '${cfg.scriptDir}' 0750 root root - -" - "d '${cfg.scriptDir}/routable.d' 0750 root root - -" - "d '${cfg.scriptDir}/dormant.d' 0750 root root - -" - "d '${cfg.scriptDir}/no-carrier.d' 0750 root root - -" - "d '${cfg.scriptDir}/off.d' 0750 root root - -" - "d '${cfg.scriptDir}/carrier.d' 0750 root root - -" - "d '${cfg.scriptDir}/degraded.d' 0750 root root - -" - "d '${cfg.scriptDir}/configuring.d' 0750 root root - -" - "d '${cfg.scriptDir}/configured.d' 0750 root root - -" - ]; - }; - }; } diff --git a/nixos/modules/services/web-apps/coder.nix b/nixos/modules/services/web-apps/coder.nix new file mode 100644 index 000000000000..469a29bc3aa8 --- /dev/null +++ b/nixos/modules/services/web-apps/coder.nix @@ -0,0 +1,217 @@ +{ config, lib, options, pkgs, ... }: + +with lib; + +let + cfg = config.services.coder; + name = "coder"; +in { + options = { + services.coder = { + enable = mkEnableOption (lib.mdDoc "Coder service"); + + user = mkOption { + type = types.str; + default = "coder"; + description = lib.mdDoc '' + User under which the coder service runs. + + ::: {.note} + If left as the default value this user will automatically be created + on system activation, otherwise it needs to be configured manually. + ::: + ''; + }; + + group = mkOption { + type = types.str; + default = "coder"; + description = lib.mdDoc '' + Group under which the coder service runs. + + ::: {.note} + If left as the default value this group will automatically be created + on system activation, otherwise it needs to be configured manually. + ::: + ''; + }; + + package = mkOption { + type = types.package; + default = pkgs.coder; + description = lib.mdDoc '' + Package to use for the service. + ''; + defaultText = literalExpression "pkgs.coder"; + }; + + homeDir = mkOption { + type = types.str; + description = lib.mdDoc '' + Home directory for coder user. + ''; + default = "/var/lib/coder"; + }; + + listenAddress = mkOption { + type = types.str; + description = lib.mdDoc '' + Listen address. + ''; + default = "127.0.0.1:3000"; + }; + + accessUrl = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc '' + Access URL should be a external IP address or domain with DNS records pointing to Coder. + ''; + default = null; + example = "https://coder.example.com"; + }; + + wildcardAccessUrl = mkOption { + type = types.nullOr types.str; + description = lib.mdDoc '' + If you are providing TLS certificates directly to the Coder server, you must use a single certificate for the root and wildcard domains. + ''; + default = null; + example = "*.coder.example.com"; + }; + + database = { + createLocally = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc '' + Create the database and database user locally. + ''; + }; + + host = mkOption { + type = types.str; + default = "/run/postgresql"; + description = lib.mdDoc '' + Hostname hosting the database. + ''; + }; + + database = mkOption { + type = types.str; + default = "coder"; + description = lib.mdDoc '' + Name of database. + ''; + }; + + username = mkOption { + type = types.str; + default = "coder"; + description = lib.mdDoc '' + Username for accessing the database. + ''; + }; + + password = mkOption { + type = types.nullOr types.str; + default = null; + description = lib.mdDoc '' + Password for accessing the database. + ''; + }; + + sslmode = mkOption { + type = types.nullOr types.str; + default = "disable"; + description = lib.mdDoc '' + Password for accessing the database. + ''; + }; + }; + + tlsCert = mkOption { + type = types.nullOr types.path; + description = lib.mdDoc '' + The path to the TLS certificate. + ''; + default = null; + }; + + tlsKey = mkOption { + type = types.nullOr types.path; + description = lib.mdDoc '' + The path to the TLS key. + ''; + default = null; + }; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { assertion = cfg.database.createLocally -> cfg.database.username == name; + message = "services.coder.database.username must be set to ${user} if services.coder.database.createLocally is set true"; + } + ]; + + systemd.services.coder = { + description = "Coder - Self-hosted developer workspaces on your infra"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + environment = { + CODER_ACCESS_URL = cfg.accessUrl; + CODER_WILDCARD_ACCESS_URL = cfg.wildcardAccessUrl; + CODER_PG_CONNECTION_URL = "user=${cfg.database.username} ${optionalString (cfg.database.password != null) "password=${cfg.database.password}"} database=${cfg.database.database} host=${cfg.database.host} ${optionalString (cfg.database.sslmode != null) "sslmode=${cfg.database.sslmode}"}"; + CODER_ADDRESS = cfg.listenAddress; + CODER_TLS_ENABLE = optionalString (cfg.tlsCert != null) "1"; + CODER_TLS_CERT_FILE = cfg.tlsCert; + CODER_TLS_KEY_FILE = cfg.tlsKey; + }; + + serviceConfig = { + ProtectSystem = "full"; + PrivateTmp = "yes"; + PrivateDevices = "yes"; + SecureBits = "keep-caps"; + AmbientCapabilities = "CAP_IPC_LOCK CAP_NET_BIND_SERVICE"; + CacheDirectory = "coder"; + CapabilityBoundingSet = "CAP_SYSLOG CAP_IPC_LOCK CAP_NET_BIND_SERVICE"; + KillSignal = "SIGINT"; + KillMode = "mixed"; + NoNewPrivileges = "yes"; + Restart = "on-failure"; + ExecStart = "${cfg.package}/bin/coder server"; + User = cfg.user; + Group = cfg.group; + }; + }; + + services.postgresql = lib.mkIf cfg.database.createLocally { + enable = true; + ensureDatabases = [ + cfg.database.database + ]; + ensureUsers = [{ + name = cfg.database.username; + ensurePermissions = { + "DATABASE \"${cfg.database.database}\"" = "ALL PRIVILEGES"; + }; + } + ]; + }; + + users.groups = optionalAttrs (cfg.group == name) { + "${cfg.group}" = {}; + }; + users.users = optionalAttrs (cfg.user == name) { + ${name} = { + description = "Coder service user"; + group = cfg.group; + home = cfg.homeDir; + createHome = true; + isSystemUser = true; + }; + }; + }; +} diff --git a/nixos/modules/services/x11/desktop-managers/phosh.nix b/nixos/modules/services/x11/desktop-managers/phosh.nix index e889c0e34e7d..3cfa6e044b73 100644 --- a/nixos/modules/services/x11/desktop-managers/phosh.nix +++ b/nixos/modules/services/x11/desktop-managers/phosh.nix @@ -173,7 +173,7 @@ in systemd.services.phosh = { wantedBy = [ "graphical.target" ]; serviceConfig = { - ExecStart = "${cfg.package}/bin/phosh"; + ExecStart = "${cfg.package}/bin/phosh-session"; User = cfg.user; Group = cfg.group; PAMName = "login"; diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix index 73322696aeac..f0c4b2172f9d 100644 --- a/nixos/modules/services/x11/desktop-managers/plasma5.nix +++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix @@ -448,6 +448,7 @@ in kio-extras ]; optionalPackages = [ + ark elisa gwenview okular diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix index 679a663362b6..8b20f9a7e87f 100644 --- a/nixos/modules/system/boot/systemd.nix +++ b/nixos/modules/system/boot/systemd.nix @@ -614,7 +614,7 @@ in # Avoid potentially degraded system state due to # "Userspace Out-Of-Memory (OOM) Killer was skipped because of a failed condition check (ConditionControlGroupController=v2)." - systemd.services.systemd-oomd.enable = mkIf (!cfg.enableUnifiedCgroupHierarchy) false; + systemd.oomd.enable = mkIf (!cfg.enableUnifiedCgroupHierarchy) false; services.logrotate.settings = { "/var/log/btmp" = mapAttrs (_: mkDefault) { diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix index c98916f60fa3..f7e82963fbad 100644 --- a/nixos/modules/virtualisation/qemu-vm.nix +++ b/nixos/modules/virtualisation/qemu-vm.nix @@ -895,7 +895,7 @@ in ${optionalString cfg.writableStore '' echo "mounting overlay filesystem on /nix/store..." - mkdir -p 0755 $targetRoot/nix/.rw-store/store $targetRoot/nix/.rw-store/work $targetRoot/nix/store + mkdir -p -m 0755 $targetRoot/nix/.rw-store/store $targetRoot/nix/.rw-store/work $targetRoot/nix/store mount -t overlay overlay $targetRoot/nix/store \ -o lowerdir=$targetRoot/nix/.ro-store,upperdir=$targetRoot/nix/.rw-store/store,workdir=$targetRoot/nix/.rw-store/work || fail ''} @@ -1097,7 +1097,7 @@ in unitConfig.DefaultDependencies = false; serviceConfig = { Type = "oneshot"; - ExecStart = "/bin/mkdir -p 0755 /sysroot/nix/.rw-store/store /sysroot/nix/.rw-store/work /sysroot/nix/store"; + ExecStart = "/bin/mkdir -p -m 0755 /sysroot/nix/.rw-store/store /sysroot/nix/.rw-store/work /sysroot/nix/store"; }; }; }; diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 07adf3dbeba5..0c3310cabe42 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -108,6 +108,7 @@ in { breitbandmessung = handleTest ./breitbandmessung.nix {}; brscan5 = handleTest ./brscan5.nix {}; btrbk = handleTest ./btrbk.nix {}; + btrbk-doas = handleTest ./btrbk-doas.nix {}; btrbk-no-timer = handleTest ./btrbk-no-timer.nix {}; btrbk-section-order = handleTest ./btrbk-section-order.nix {}; buildbot = handleTest ./buildbot.nix {}; @@ -137,6 +138,7 @@ in { cntr = handleTestOn ["aarch64-linux" "x86_64-linux"] ./cntr.nix {}; cockpit = handleTest ./cockpit.nix {}; cockroachdb = handleTestOn ["x86_64-linux"] ./cockroachdb.nix {}; + coder = handleTest ./coder.nix {}; collectd = handleTest ./collectd.nix {}; connman = handleTest ./connman.nix {}; consul = handleTest ./consul.nix {}; @@ -238,6 +240,7 @@ in { ft2-clone = handleTest ./ft2-clone.nix {}; mimir = handleTest ./mimir.nix {}; garage = handleTest ./garage {}; + gemstash = handleTest ./gemstash.nix {}; gerrit = handleTest ./gerrit.nix {}; geth = handleTest ./geth.nix {}; ghostunnel = handleTest ./ghostunnel.nix {}; @@ -687,6 +690,7 @@ in { terminal-emulators = handleTest ./terminal-emulators.nix {}; tiddlywiki = handleTest ./tiddlywiki.nix {}; tigervnc = handleTest ./tigervnc.nix {}; + timescaledb = handleTest ./timescaledb.nix {}; timezone = handleTest ./timezone.nix {}; tinc = handleTest ./tinc {}; tinydns = handleTest ./tinydns.nix {}; diff --git a/nixos/tests/btrbk-doas.nix b/nixos/tests/btrbk-doas.nix new file mode 100644 index 000000000000..1e3f8d56addb --- /dev/null +++ b/nixos/tests/btrbk-doas.nix @@ -0,0 +1,114 @@ +import ./make-test-python.nix ({ pkgs, ... }: + + let + privateKey = '' + -----BEGIN OPENSSH PRIVATE KEY----- + b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW + QyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrwAAAJB+cF5HfnBe + RwAAAAtzc2gtZWQyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrw + AAAEBN75NsJZSpt63faCuaD75Unko0JjlSDxMhYHAPJk2/xXHxQHThDpD9/AMWNqQer3Tg + 9gXMb2lTZMn0pelo8xyvAAAADXJzY2h1ZXR6QGt1cnQ= + -----END OPENSSH PRIVATE KEY----- + ''; + publicKey = '' + ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHHxQHThDpD9/AMWNqQer3Tg9gXMb2lTZMn0pelo8xyv + ''; + in + { + name = "btrbk-doas"; + meta = with pkgs.lib; { + maintainers = with maintainers; [ symphorien tu-maurice ]; + }; + + nodes = { + archive = { ... }: { + security.sudo.enable = false; + security.doas.enable = true; + environment.systemPackages = with pkgs; [ btrfs-progs ]; + # note: this makes the privateKey world readable. + # don't do it with real ssh keys. + environment.etc."btrbk_key".text = privateKey; + services.btrbk = { + extraPackages = [ pkgs.lz4 ]; + instances = { + remote = { + onCalendar = "minutely"; + settings = { + ssh_identity = "/etc/btrbk_key"; + ssh_user = "btrbk"; + stream_compress = "lz4"; + volume = { + "ssh://main/mnt" = { + target = "/mnt"; + snapshot_dir = "btrbk/remote"; + subvolume = "to_backup"; + }; + }; + }; + }; + }; + }; + }; + + main = { ... }: { + security.sudo.enable = false; + security.doas.enable = true; + environment.systemPackages = with pkgs; [ btrfs-progs ]; + services.openssh = { + enable = true; + passwordAuthentication = false; + kbdInteractiveAuthentication = false; + }; + services.btrbk = { + extraPackages = [ pkgs.lz4 ]; + sshAccess = [ + { + key = publicKey; + roles = [ "source" "send" "info" "delete" ]; + } + ]; + instances = { + local = { + onCalendar = "minutely"; + settings = { + volume = { + "/mnt" = { + snapshot_dir = "btrbk/local"; + subvolume = "to_backup"; + }; + }; + }; + }; + }; + }; + }; + }; + + testScript = '' + start_all() + + # create btrfs partition at /mnt + for machine in (archive, main): + machine.succeed("dd if=/dev/zero of=/data_fs bs=120M count=1") + machine.succeed("mkfs.btrfs /data_fs") + machine.succeed("mkdir /mnt") + machine.succeed("mount /data_fs /mnt") + + # what to backup and where + main.succeed("btrfs subvolume create /mnt/to_backup") + main.succeed("mkdir -p /mnt/btrbk/{local,remote}") + + # check that local snapshots work + with subtest("local"): + main.succeed("echo foo > /mnt/to_backup/bar") + main.wait_until_succeeds("cat /mnt/btrbk/local/*/bar | grep foo") + main.succeed("echo bar > /mnt/to_backup/bar") + main.succeed("cat /mnt/btrbk/local/*/bar | grep foo") + + # check that btrfs send/receive works and ssh access works + with subtest("remote"): + archive.wait_until_succeeds("cat /mnt/*/bar | grep bar") + main.succeed("echo baz > /mnt/to_backup/bar") + archive.succeed("cat /mnt/*/bar | grep bar") + ''; + }) diff --git a/nixos/tests/coder.nix b/nixos/tests/coder.nix new file mode 100644 index 000000000000..12813827284b --- /dev/null +++ b/nixos/tests/coder.nix @@ -0,0 +1,24 @@ +import ./make-test-python.nix ({ pkgs, ... }: { + name = "coder"; + meta = with pkgs.lib.maintainers; { + maintainers = [ shyim ghuntley ]; + }; + + nodes.machine = + { pkgs, ... }: + { + services.coder = { + enable = true; + accessUrl = "http://localhost:3000"; + }; + }; + + testScript = '' + machine.start() + machine.wait_for_unit("postgresql.service") + machine.wait_for_unit("coder.service") + machine.wait_for_open_port(3000) + + machine.succeed("curl --fail http://localhost:3000") + ''; +}) diff --git a/nixos/tests/docker-tools.nix b/nixos/tests/docker-tools.nix index 98704ecb2fb6..44b583ebcea5 100644 --- a/nixos/tests/docker-tools.nix +++ b/nixos/tests/docker-tools.nix @@ -1,6 +1,52 @@ # this test creates a simple GNU image with docker tools and sees if it executes -import ./make-test-python.nix ({ pkgs, ... }: { +import ./make-test-python.nix ({ pkgs, ... }: +let + # nixpkgs#214434: dockerTools.buildImage fails to unpack base images + # containing duplicate layers when those duplicate tarballs + # appear under the manifest's 'Layers'. Docker can generate images + # like this even though dockerTools does not. + repeatedLayerTestImage = + let + # Rootfs diffs for layers 1 and 2 are identical (and empty) + layer1 = pkgs.dockerTools.buildImage { name = "empty"; }; + layer2 = layer1.overrideAttrs (_: { fromImage = layer1; }); + repeatedRootfsDiffs = pkgs.runCommandNoCC "image-with-links.tar" { + nativeBuildInputs = [pkgs.jq]; + } '' + mkdir contents + tar -xf "${layer2}" -C contents + cd contents + first_rootfs=$(jq -r '.[0].Layers[0]' manifest.json) + second_rootfs=$(jq -r '.[0].Layers[1]' manifest.json) + target_rootfs=$(sha256sum "$first_rootfs" | cut -d' ' -f 1).tar + + # Replace duplicated rootfs diffs with symlinks to one tarball + chmod -R ug+w . + mv "$first_rootfs" "$target_rootfs" + rm "$second_rootfs" + ln -s "../$target_rootfs" "$first_rootfs" + ln -s "../$target_rootfs" "$second_rootfs" + + # Update manifest's layers to use the symlinks' target + cat manifest.json | \ + jq ".[0].Layers[0] = \"$target_rootfs\"" | + jq ".[0].Layers[1] = \"$target_rootfs\"" > manifest.json.new + mv manifest.json.new manifest.json + + tar --sort=name --hard-dereference -cf $out . + ''; + in pkgs.dockerTools.buildImage { + fromImage = repeatedRootfsDiffs; + name = "repeated-layer-test"; + tag = "latest"; + copyToRoot = pkgs.bash; + # A runAsRoot script is required to force previous layers to be unpacked + runAsRoot = '' + echo 'runAsRoot has run.' + ''; + }; +in { name = "docker-tools"; meta = with pkgs.lib.maintainers; { maintainers = [ lnl7 roberth ]; @@ -221,6 +267,12 @@ import ./make-test-python.nix ({ pkgs, ... }: { "docker run --rm ${examples.layersUnpackOrder.imageName} cat /layer-order" ) + with subtest("Ensure repeated base layers handled by buildImage"): + docker.succeed( + "docker load --input='${repeatedLayerTestImage}'", + "docker run --rm ${repeatedLayerTestImage.imageName} /bin/bash -c 'exit 0'" + ) + with subtest("Ensure environment variables are correctly inherited"): docker.succeed( "docker load --input='${examples.environmentVariables}'" diff --git a/nixos/tests/gemstash.nix b/nixos/tests/gemstash.nix new file mode 100644 index 000000000000..bc152e42e92e --- /dev/null +++ b/nixos/tests/gemstash.nix @@ -0,0 +1,51 @@ +{ system ? builtins.currentSystem, config ? { } +, pkgs ? import ../.. { inherit system config; } }: + +with import ../lib/testing-python.nix { inherit system pkgs; }; +with pkgs.lib; + +let common_meta = { maintainers = [ maintainers.viraptor ]; }; +in +{ + gemstash_works = makeTest { + name = "gemstash-works"; + meta = common_meta; + + nodes.machine = { config, pkgs, ... }: { + services.gemstash = { + enable = true; + }; + }; + + # gemstash responds to http requests + testScript = '' + machine.wait_for_unit("gemstash.service") + machine.wait_for_file("/var/lib/gemstash") + machine.wait_for_open_port(9292) + machine.succeed("curl http://localhost:9292") + ''; + }; + + gemstash_custom_port = makeTest { + name = "gemstash-custom-port"; + meta = common_meta; + + nodes.machine = { config, pkgs, ... }: { + services.gemstash = { + enable = true; + openFirewall = true; + settings = { + bind = "tcp://0.0.0.0:12345"; + }; + }; + }; + + # gemstash responds to http requests + testScript = '' + machine.wait_for_unit("gemstash.service") + machine.wait_for_file("/var/lib/gemstash") + machine.wait_for_open_port(12345) + machine.succeed("curl http://localhost:12345") + ''; + }; +} diff --git a/nixos/tests/gitlab.nix b/nixos/tests/gitlab.nix index 59dbc99052a6..c2a11bada0a3 100644 --- a/nixos/tests/gitlab.nix +++ b/nixos/tests/gitlab.nix @@ -69,6 +69,10 @@ in { databasePasswordFile = pkgs.writeText "dbPassword" "xo0daiF4"; initialRootPasswordFile = pkgs.writeText "rootPassword" initialRootPassword; smtp.enable = true; + pages = { + enable = true; + settings.pages-domain = "localhost"; + }; extraConfig = { incoming_email = { enabled = true; @@ -79,11 +83,6 @@ in { host = "localhost"; port = 143; }; - # https://github.com/NixOS/nixpkgs/issues/132295 - # pages = { - # enabled = true; - # host = "localhost"; - # }; }; secrets = { secretFile = pkgs.writeText "secret" "Aig5zaic"; @@ -171,10 +170,9 @@ in { waitForServices = '' gitlab.wait_for_unit("gitaly.service") gitlab.wait_for_unit("gitlab-workhorse.service") - # https://github.com/NixOS/nixpkgs/issues/132295 - # gitlab.wait_for_unit("gitlab-pages.service") gitlab.wait_for_unit("gitlab-mailroom.service") gitlab.wait_for_unit("gitlab.service") + gitlab.wait_for_unit("gitlab-pages.service") gitlab.wait_for_unit("gitlab-sidekiq.service") gitlab.wait_for_file("${nodes.gitlab.config.services.gitlab.statePath}/tmp/sockets/gitlab.socket") gitlab.wait_until_succeeds("curl -sSf http://gitlab/users/sign_in") diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix index 3adfa979edcc..d441765fe194 100644 --- a/nixos/tests/installer.nix +++ b/nixos/tests/installer.nix @@ -77,9 +77,9 @@ let let iface = if grubVersion == 1 then "ide" else "virtio"; isEfi = bootLoader == "systemd-boot" || (bootLoader == "grub" && grubUseEfi); bios = if pkgs.stdenv.isAarch64 then "QEMU_EFI.fd" else "OVMF.fd"; - in if !isEfi && !pkgs.stdenv.hostPlatform.isx86 then - throw "Non-EFI boot methods are only supported on i686 / x86_64" - else '' + in if !isEfi && !pkgs.stdenv.hostPlatform.isx86 then '' + machine.succeed("true") + '' else '' def assemble_qemu_flags(): flags = "-cpu max" ${if (system == "x86_64-linux" || system == "i686-linux") diff --git a/nixos/tests/timescaledb.nix b/nixos/tests/timescaledb.nix new file mode 100644 index 000000000000..00a7f9af09fb --- /dev/null +++ b/nixos/tests/timescaledb.nix @@ -0,0 +1,93 @@ +# mostly copied from ./postgresql.nix as it seemed unapproriate to +# test additional extensions for postgresql there. + +{ system ? builtins.currentSystem +, config ? { } +, pkgs ? import ../.. { inherit system config; } +}: + +with import ../lib/testing-python.nix { inherit system pkgs; }; +with pkgs.lib; + +let + postgresql-versions = import ../../pkgs/servers/sql/postgresql pkgs; + test-sql = pkgs.writeText "postgresql-test" '' + CREATE EXTENSION timescaledb; + CREATE EXTENSION timescaledb_toolkit; + + CREATE TABLE sth ( + time TIMESTAMPTZ NOT NULL, + value DOUBLE PRECISION + ); + + SELECT create_hypertable('sth', 'time'); + + INSERT INTO sth (time, value) VALUES + ('2003-04-12 04:05:06 America/New_York', 1.0), + ('2003-04-12 04:05:07 America/New_York', 2.0), + ('2003-04-12 04:05:08 America/New_York', 3.0), + ('2003-04-12 04:05:09 America/New_York', 4.0), + ('2003-04-12 04:05:10 America/New_York', 5.0) + ; + + WITH t AS ( + SELECT + time_bucket('1 day'::interval, time) AS dt, + stats_agg(value) AS stats + FROM sth + GROUP BY time_bucket('1 day'::interval, time) + ) + SELECT + average(stats) + FROM t; + ''; + make-postgresql-test = postgresql-name: postgresql-package: makeTest { + name = postgresql-name; + meta = with pkgs.lib.maintainers; { + maintainers = [ typetetris ]; + }; + + nodes.machine = { ... }: + { + services.postgresql = { + enable = true; + package = postgresql-package; + extraPlugins = with postgresql-package.pkgs; [ + timescaledb + timescaledb_toolkit + ]; + settings = { shared_preload_libraries = "timescaledb, timescaledb_toolkit"; }; + }; + }; + + testScript = '' + def check_count(statement, lines): + return 'test $(sudo -u postgres psql postgres -tAc "{}"|wc -l) -eq {}'.format( + statement, lines + ) + + + machine.start() + machine.wait_for_unit("postgresql") + + with subtest("Postgresql with extensions timescaledb and timescaledb_toolkit is available just after unit start"): + machine.succeed( + "sudo -u postgres psql -f ${test-sql}" + ) + + machine.fail(check_count("SELECT * FROM sth;", 3)) + machine.succeed(check_count("SELECT * FROM sth;", 5)) + machine.fail(check_count("SELECT * FROM sth;", 4)) + + machine.shutdown() + ''; + + }; + applicablePostgresqlVersions = filterAttrs (_: value: versionAtLeast value.version "12") postgresql-versions; +in +mapAttrs' + (name: package: { + inherit name; + value = make-postgresql-test name package; + }) + applicablePostgresqlVersions |