diff options
Diffstat (limited to 'nixos/modules/services')
72 files changed, 1711 insertions, 1733 deletions
diff --git a/nixos/modules/services/backup/restic.nix b/nixos/modules/services/backup/restic.nix index bc24e13aa050e..ca796cf7797e6 100644 --- a/nixos/modules/services/backup/restic.nix +++ b/nixos/modules/services/backup/restic.nix @@ -303,8 +303,8 @@ in then if (backup.paths != null) then concatStringsSep " " backup.paths else "" else "--files-from ${filesFromTmpFile}"; pruneCmd = optionals (builtins.length backup.pruneOpts > 0) [ - (resticCmd + " forget --prune --cache-dir=%C/restic-backups-${name} " + (concatStringsSep " " backup.pruneOpts)) - (resticCmd + " check --cache-dir=%C/restic-backups-${name} " + (concatStringsSep " " backup.checkOpts)) + (resticCmd + " forget --prune " + (concatStringsSep " " backup.pruneOpts)) + (resticCmd + " check " + (concatStringsSep " " backup.checkOpts)) ]; # Helper functions for rclone remotes rcloneRemoteName = builtins.elemAt (splitString ":" backup.repository) 1; @@ -314,6 +314,7 @@ in in nameValuePair "restic-backups-${name}" ({ environment = { + RESTIC_CACHE_DIR = "%C/restic-backups-${name}"; RESTIC_PASSWORD_FILE = backup.passwordFile; RESTIC_REPOSITORY = backup.repository; RESTIC_REPOSITORY_FILE = backup.repositoryFile; @@ -332,7 +333,7 @@ in restartIfChanged = false; serviceConfig = { Type = "oneshot"; - ExecStart = (optionals (backupPaths != "") [ "${resticCmd} backup --cache-dir=%C/restic-backups-${name} ${concatStringsSep " " (backup.extraBackupArgs ++ excludeFlags)} ${backupPaths}" ]) + ExecStart = (optionals (backupPaths != "") [ "${resticCmd} backup ${concatStringsSep " " (backup.extraBackupArgs ++ excludeFlags)} ${backupPaths}" ]) ++ pruneCmd; User = backup.user; RuntimeDirectory = "restic-backups-${name}"; diff --git a/nixos/modules/services/cluster/hadoop/hbase.nix b/nixos/modules/services/cluster/hadoop/hbase.nix index 97951ebfe3343..a39da2a84ecad 100644 --- a/nixos/modules/services/cluster/hadoop/hbase.nix +++ b/nixos/modules/services/cluster/hadoop/hbase.nix @@ -5,11 +5,95 @@ let cfg = config.services.hadoop; hadoopConf = "${import ./conf.nix { inherit cfg pkgs lib; }}/"; mkIfNotNull = x: mkIf (x != null) x; + # generic hbase role options + hbaseRoleOption = name: extraOpts: { + enable = mkEnableOption (mdDoc "HBase ${name}"); + + openFirewall = mkOption { + type = types.bool; + default = false; + description = mdDoc "Open firewall ports for HBase ${name}."; + }; + + restartIfChanged = mkOption { + type = types.bool; + default = false; + description = mdDoc "Restart ${name} con config change."; + }; + + extraFlags = mkOption { + type = with types; listOf str; + default = []; + example = literalExpression ''[ "--backup" ]''; + description = mdDoc "Extra flags for the ${name} service."; + }; + + environment = mkOption { + type = with types; attrsOf str; + default = {}; + example = literalExpression '' + { + HBASE_MASTER_OPTS = "-Dcom.sun.management.jmxremote.ssl=true"; + } + ''; + description = mdDoc "Environment variables passed to ${name}."; + }; + } // extraOpts; + # generic hbase role configs + hbaseRoleConfig = name: ports: (mkIf cfg.hbase."${name}".enable { + services.hadoop.gatewayRole = { + enable = true; + enableHbaseCli = mkDefault true; + }; + + systemd.services."hbase-${toLower name}" = { + description = "HBase ${name}"; + wantedBy = [ "multi-user.target" ]; + path = with cfg; [ hbase.package ] ++ optional + (with cfg.hbase.master; enable && initHDFS) package; + preStart = mkIf (with cfg.hbase.master; enable && initHDFS) + (concatStringsSep "\n" ( + map (x: "HADOOP_USER_NAME=hdfs hdfs --config /etc/hadoop-conf ${x}")[ + "dfsadmin -safemode wait" + "dfs -mkdir -p ${cfg.hbase.rootdir}" + "dfs -chown hbase ${cfg.hbase.rootdir}" + ] + )); + + inherit (cfg.hbase."${name}") environment; + script = concatStringsSep " " ( + [ + "hbase --config /etc/hadoop-conf/" + "${toLower name} start" + ] + ++ cfg.hbase."${name}".extraFlags + ++ map (x: "--${toLower x} ${toString cfg.hbase.${name}.${x}}") + (filter (x: hasAttr x cfg.hbase.${name}) ["port" "infoPort"]) + ); + + serviceConfig = { + User = "hbase"; + SyslogIdentifier = "hbase-${toLower name}"; + Restart = "always"; + }; + }; + + services.hadoop.hbaseSiteInternal."hbase.rootdir" = cfg.hbase.rootdir; + + networking = { + firewall.allowedTCPPorts = mkIf cfg.hbase."${name}".openFirewall ports; + hosts = mkIf (with cfg.hbase.regionServer; enable && overrideHosts) { + "127.0.0.2" = mkForce [ ]; + "::1" = mkForce [ ]; + }; + }; + + }); in { options.services.hadoop = { - gatewayRole.enableHbaseCli = mkEnableOption (lib.mdDoc "HBase CLI tools"); + gatewayRole.enableHbaseCli = mkEnableOption (mdDoc "HBase CLI tools"); hbaseSiteDefault = mkOption { default = { @@ -21,7 +105,7 @@ in "hbase.cluster.distributed" = "true"; }; type = types.attrsOf types.anything; - description = lib.mdDoc '' + description = mdDoc '' Default options for hbase-site.xml ''; }; @@ -29,8 +113,12 @@ in default = {}; type = with types; attrsOf anything; example = literalExpression '' + { + "hbase.hregion.max.filesize" = 20*1024*1024*1024; + "hbase.table.normalization.enabled" = "true"; + } ''; - description = lib.mdDoc '' + description = mdDoc '' Additional options and overrides for hbase-site.xml <https://github.com/apache/hbase/blob/rel/2.4.11/hbase-common/src/main/resources/hbase-default.xml> ''; @@ -39,7 +127,7 @@ in default = {}; type = with types; attrsOf anything; internal = true; - description = lib.mdDoc '' + description = mdDoc '' Internal option to add configs to hbase-site.xml based on module options ''; }; @@ -50,11 +138,11 @@ in type = types.package; default = pkgs.hbase; defaultText = literalExpression "pkgs.hbase"; - description = lib.mdDoc "HBase package"; + description = mdDoc "HBase package"; }; rootdir = mkOption { - description = lib.mdDoc '' + description = mdDoc '' This option will set "hbase.rootdir" in hbase-site.xml and determine the directory shared by region servers and into which HBase persists. The URL should be 'fully-qualified' to include the filesystem scheme. @@ -68,7 +156,7 @@ in default = "/hbase"; }; zookeeperQuorum = mkOption { - description = lib.mdDoc '' + description = mdDoc '' This option will set "hbase.zookeeper.quorum" in hbase-site.xml. Comma separated list of servers in the ZooKeeper ensemble. ''; @@ -76,107 +164,36 @@ in example = "zk1.internal,zk2.internal,zk3.internal"; default = null; }; - master = { - enable = mkEnableOption (lib.mdDoc "HBase Master"); - initHDFS = mkEnableOption (lib.mdDoc "initialization of the hbase directory on HDFS"); - - openFirewall = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc '' - Open firewall ports for HBase master. - ''; + } // (let + ports = port: infoPort: { + port = mkOption { + type = types.int; + default = port; + description = mdDoc "RPC port"; }; - }; - regionServer = { - enable = mkEnableOption (lib.mdDoc "HBase RegionServer"); - - overrideHosts = mkOption { - type = types.bool; - default = true; - description = lib.mdDoc '' - Remove /etc/hosts entries for "127.0.0.2" and "::1" defined in nixos/modules/config/networking.nix - Regionservers must be able to resolve their hostnames to their IP addresses, through PTR records - or /etc/hosts entries. - - ''; - }; - - openFirewall = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc '' - Open firewall ports for HBase master. - ''; + infoPort = mkOption { + type = types.int; + default = infoPort; + description = mdDoc "web UI port"; }; }; - }; - }; - - config = mkMerge [ - (mkIf cfg.hbase.master.enable { - services.hadoop.gatewayRole = { - enable = true; - enableHbaseCli = mkDefault true; - }; - - systemd.services.hbase-master = { - description = "HBase master"; - wantedBy = [ "multi-user.target" ]; - - preStart = mkIf cfg.hbase.master.initHDFS '' - HADOOP_USER_NAME=hdfs ${cfg.package}/bin/hdfs --config ${hadoopConf} dfsadmin -safemode wait - HADOOP_USER_NAME=hdfs ${cfg.package}/bin/hdfs --config ${hadoopConf} dfs -mkdir -p ${cfg.hbase.rootdir} - HADOOP_USER_NAME=hdfs ${cfg.package}/bin/hdfs --config ${hadoopConf} dfs -chown hbase ${cfg.hbase.rootdir} + in mapAttrs hbaseRoleOption { + master.initHDFS = mkEnableOption (mdDoc "initialization of the hbase directory on HDFS"); + regionServer.overrideHosts = mkOption { + type = types.bool; + default = true; + description = mdDoc '' + Remove /etc/hosts entries for "127.0.0.2" and "::1" defined in nixos/modules/config/networking.nix + Regionservers must be able to resolve their hostnames to their IP addresses, through PTR records + or /etc/hosts entries. ''; - - serviceConfig = { - User = "hbase"; - SyslogIdentifier = "hbase-master"; - ExecStart = "${cfg.hbase.package}/bin/hbase --config ${hadoopConf} " + - "master start"; - Restart = "always"; - }; - }; - - services.hadoop.hbaseSiteInternal."hbase.rootdir" = cfg.hbase.rootdir; - - networking.firewall.allowedTCPPorts = mkIf cfg.hbase.master.openFirewall [ - 16000 16010 - ]; - - }) - - (mkIf cfg.hbase.regionServer.enable { - services.hadoop.gatewayRole = { - enable = true; - enableHbaseCli = mkDefault true; - }; - - systemd.services.hbase-regionserver = { - description = "HBase RegionServer"; - wantedBy = [ "multi-user.target" ]; - serviceConfig = { - User = "hbase"; - SyslogIdentifier = "hbase-regionserver"; - ExecStart = "${cfg.hbase.package}/bin/hbase --config /etc/hadoop-conf/ " + - "regionserver start"; - Restart = "always"; - }; }; + thrift = ports 9090 9095; + rest = ports 8080 8085; + }); + }; - services.hadoop.hbaseSiteInternal."hbase.rootdir" = cfg.hbase.rootdir; - - networking = { - firewall.allowedTCPPorts = mkIf cfg.hbase.regionServer.openFirewall [ - 16020 16030 - ]; - hosts = mkIf cfg.hbase.regionServer.overrideHosts { - "127.0.0.2" = mkForce [ ]; - "::1" = mkForce [ ]; - }; - }; - }) + config = mkMerge ([ (mkIf cfg.gatewayRole.enable { @@ -192,5 +209,10 @@ in isSystemUser = true; }; }) - ]; + ] ++ (mapAttrsToList hbaseRoleConfig { + master = [ 16000 16010 ]; + regionServer = [ 16020 16030 ]; + thrift = with cfg.hbase.thrift; [ port infoPort ]; + rest = with cfg.hbase.rest; [ port infoPort ]; + })); } diff --git a/nixos/modules/services/cluster/kubernetes/addons/dns.nix b/nixos/modules/services/cluster/kubernetes/addons/dns.nix index 3d41b5f008537..1c00329e6ccff 100644 --- a/nixos/modules/services/cluster/kubernetes/addons/dns.nix +++ b/nixos/modules/services/cluster/kubernetes/addons/dns.nix @@ -3,7 +3,7 @@ with lib; let - version = "1.7.1"; + version = "1.10.1"; cfg = config.services.kubernetes.addons.dns; ports = { dns = 10053; @@ -59,9 +59,9 @@ in { type = types.attrs; default = { imageName = "coredns/coredns"; - imageDigest = "sha256:4a6e0769130686518325b21b0c1d0688b54e7c79244d48e1b15634e98e40c6ef"; + imageDigest = "sha256:a0ead06651cf580044aeb0a0feba63591858fb2e43ade8c9dea45a6a89ae7e5e"; finalImageTag = version; - sha256 = "02r440xcdsgi137k5lmmvp0z5w5fmk8g9mysq5pnysq1wl8sj6mw"; + sha256 = "0wg696920smmal7552a2zdhfncndn5kfammfa8bk8l7dz9bhk0y1"; }; }; @@ -136,6 +136,11 @@ in { resources = [ "nodes" ]; verbs = [ "get" ]; } + { + apiGroups = [ "discovery.k8s.io" ]; + resources = [ "endpointslices" ]; + verbs = [ "list" "watch" ]; + } ]; }; diff --git a/nixos/modules/services/cluster/kubernetes/pki.nix b/nixos/modules/services/cluster/kubernetes/pki.nix index 26fe0f5e9e097..38682701ea151 100644 --- a/nixos/modules/services/cluster/kubernetes/pki.nix +++ b/nixos/modules/services/cluster/kubernetes/pki.nix @@ -270,7 +270,7 @@ in ''; })]); - environment.etc.${cfg.etcClusterAdminKubeconfig}.source = mkIf (!isNull cfg.etcClusterAdminKubeconfig) + environment.etc.${cfg.etcClusterAdminKubeconfig}.source = mkIf (cfg.etcClusterAdminKubeconfig != null) clusterAdminKubeconfig; environment.systemPackages = mkIf (top.kubelet.enable || top.proxy.enable) [ diff --git a/nixos/modules/services/continuous-integration/woodpecker/agents.nix b/nixos/modules/services/continuous-integration/woodpecker/agents.nix new file mode 100644 index 0000000000000..caf6c85093424 --- /dev/null +++ b/nixos/modules/services/continuous-integration/woodpecker/agents.nix @@ -0,0 +1,144 @@ +{ config +, lib +, pkgs +, ... +}: + +let + cfg = config.services.woodpecker-agents; + + agentModule = lib.types.submodule { + options = { + enable = lib.mkEnableOption (lib.mdDoc "this Woodpecker-Agent. Agents execute tasks generated by a Server, every install will need one server and at least one agent"); + + package = lib.mkPackageOptionMD pkgs "woodpecker-agent" { }; + + environment = lib.mkOption { + default = { }; + type = lib.types.attrsOf lib.types.str; + example = lib.literalExpression '' + { + WOODPECKER_SERVER = "localhost:9000"; + WOODPECKER_BACKEND = "docker"; + DOCKER_HOST = "unix:///run/podman/podman.sock"; + } + ''; + description = lib.mdDoc "woodpecker-agent config envrionment variables, for other options read the [documentation](https://woodpecker-ci.org/docs/administration/agent-config)"; + }; + + extraGroups = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "podman" ]; + description = lib.mdDoc '' + Additional groups for the systemd service. + ''; + }; + + environmentFile = lib.mkOption { + type = lib.types.listOf lib.types.path; + default = [ ]; + example = [ "/var/secrets/woodpecker-agent.env" ]; + description = lib.mdDoc '' + File to load environment variables + from. This is helpful for specifying secrets. + Example content of environmentFile: + ``` + WOODPECKER_AGENT_SECRET=your-shared-secret-goes-here + ``` + ''; + }; + }; + }; + + mkAgentService = name: agentCfg: { + name = "woodpecker-agent-${name}"; + value = { + description = "Woodpecker-Agent Service - ${name}"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + serviceConfig = { + DynamicUser = true; + SupplementaryGroups = agentCfg.extraGroups; + EnvironmentFile = agentCfg.environmentFile; + ExecStart = lib.getExe agentCfg.package; + Restart = "on-failure"; + RestartSec = 15; + CapabilityBoundingSet = ""; + NoNewPrivileges = true; + ProtectSystem = "strict"; + PrivateTmp = true; + PrivateDevices = true; + PrivateUsers = true; + ProtectHostname = true; + ProtectClock = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectKernelLogs = true; + ProtectControlGroups = true; + RestrictAddressFamilies = [ "AF_UNIX AF_INET AF_INET6" ]; + LockPersonality = true; + MemoryDenyWriteExecute = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + PrivateMounts = true; + SystemCallArchitectures = "native"; + SystemCallFilter = "~@clock @privileged @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @reboot @setuid @swap"; + BindReadOnlyPaths = [ + "-/etc/resolv.conf" + "-/etc/nsswitch.conf" + "-/etc/ssl/certs" + "-/etc/static/ssl/certs" + "-/etc/hosts" + "-/etc/localtime" + ]; + }; + inherit (agentCfg) environment; + }; + }; +in +{ + meta.maintainers = with lib.maintainers; [ janik ambroisie ]; + + options = { + services.woodpecker-agents = { + agents = lib.mkOption { + default = { }; + type = lib.types.attrsOf agentModule; + example = { + docker = { + environment = { + WOODPECKER_SERVER = "localhost:9000"; + WOODPECKER_BACKEND = "docker"; + DOCKER_HOST = "unix:///run/podman/podman.sock"; + }; + + extraGroups = [ "docker" ]; + + environmentFile = "/run/secrets/woodpecker/agent-secret.txt"; + }; + + exec = { + environment = { + WOODPECKER_SERVER = "localhost:9000"; + WOODPECKER_BACKEND = "exec"; + }; + + environmentFile = "/run/secrets/woodpecker/agent-secret.txt"; + }; + }; + description = lib.mdDoc "woodpecker-agents configurations"; + }; + }; + }; + + config = { + systemd.services = + let + mkServices = lib.mapAttrs' mkAgentService; + enabledAgents = lib.filterAttrs (_: agent: agent.enable) cfg.agents; + in + mkServices enabledAgents; + }; +} diff --git a/nixos/modules/services/continuous-integration/woodpecker/server.nix b/nixos/modules/services/continuous-integration/woodpecker/server.nix new file mode 100644 index 0000000000000..be7786da8505f --- /dev/null +++ b/nixos/modules/services/continuous-integration/woodpecker/server.nix @@ -0,0 +1,98 @@ +{ config +, lib +, pkgs +, ... +}: + +let + cfg = config.services.woodpecker-server; +in +{ + meta.maintainers = with lib.maintainers; [ janik ambroisie ]; + + + options = { + services.woodpecker-server = { + enable = lib.mkEnableOption (lib.mdDoc "the Woodpecker-Server, a CI/CD application for automatic builds, deployments and tests"); + package = lib.mkPackageOptionMD pkgs "woodpecker-server" { }; + environment = lib.mkOption { + default = { }; + type = lib.types.attrsOf lib.types.str; + example = lib.literalExpression + '' + { + WOODPECKER_HOST = "https://woodpecker.example.com"; + WOODPECKER_OPEN = "true"; + WOODPECKER_GITEA = "true"; + WOODPECKER_GITEA_CLIENT = "ffffffff-ffff-ffff-ffff-ffffffffffff"; + WOODPECKER_GITEA_URL = "https://git.example.com"; + } + ''; + description = lib.mdDoc "woodpecker-server config envrionment variables, for other options read the [documentation](https://woodpecker-ci.org/docs/administration/server-config)"; + }; + environmentFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + example = "/root/woodpecker-server.env"; + description = lib.mdDoc '' + File to load environment variables + from. This is helpful for specifying secrets. + Example content of environmentFile: + ``` + WOODPECKER_AGENT_SECRET=your-shared-secret-goes-here + WOODPECKER_GITEA_SECRET=gto_************************************** + ``` + ''; + }; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services = { + woodpecker-server = { + description = "Woodpecker-Server Service"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + serviceConfig = { + DynamicUser = true; + WorkingDirectory = "%S/woodpecker-server"; + StateDirectory = "woodpecker-server"; + StateDirectoryMode = "0700"; + UMask = "0007"; + ConfigurationDirectory = "woodpecker-server"; + EnvironmentFile = lib.optional (cfg.environmentFile != null) cfg.environmentFile; + ExecStart = "${cfg.package}/bin/woodpecker-server"; + Restart = "on-failure"; + RestartSec = 15; + CapabilityBoundingSet = ""; + # Security + NoNewPrivileges = true; + # Sandboxing + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + PrivateDevices = true; + PrivateUsers = true; + ProtectHostname = true; + ProtectClock = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectKernelLogs = true; + ProtectControlGroups = true; + RestrictAddressFamilies = [ "AF_UNIX AF_INET AF_INET6" ]; + LockPersonality = true; + MemoryDenyWriteExecute = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + PrivateMounts = true; + # System Call Filtering + SystemCallArchitectures = "native"; + SystemCallFilter = "~@clock @privileged @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @reboot @setuid @swap"; + }; + inherit (cfg) environment; + }; + }; + }; +} + diff --git a/nixos/modules/services/databases/dgraph.nix b/nixos/modules/services/databases/dgraph.nix index 887164fa5b943..7f005a9971a69 100644 --- a/nixos/modules/services/databases/dgraph.nix +++ b/nixos/modules/services/databases/dgraph.nix @@ -12,7 +12,7 @@ let '' mkdir -p $out/bin makeWrapper ${cfg.package}/bin/dgraph $out/bin/dgraph \ - --set PATH '${lib.makeBinPath [ pkgs.nodejs ]}:$PATH' \ + --prefix PATH : "${lib.makeBinPath [ pkgs.nodejs ]}" \ ''; securityOptions = { NoNewPrivileges = true; diff --git a/nixos/modules/services/desktops/pipewire/daemon/client-rt.conf.json b/nixos/modules/services/desktops/pipewire/daemon/client-rt.conf.json deleted file mode 100644 index c204606193af5..0000000000000 --- a/nixos/modules/services/desktops/pipewire/daemon/client-rt.conf.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "context.properties": { - "log.level": 0 - }, - "context.spa-libs": { - "audio.convert.*": "audioconvert/libspa-audioconvert", - "support.*": "support/libspa-support" - }, - "context.modules": [ - { - "name": "libpipewire-module-rt", - "args": {}, - "flags": [ - "ifexists", - "nofail" - ] - }, - { - "name": "libpipewire-module-protocol-native" - }, - { - "name": "libpipewire-module-client-node" - }, - { - "name": "libpipewire-module-client-device" - }, - { - "name": "libpipewire-module-adapter" - }, - { - "name": "libpipewire-module-metadata" - }, - { - "name": "libpipewire-module-session-manager" - } - ], - "filter.properties": {}, - "stream.properties": {}, - "alsa.properties": {}, - "alsa.rules": [ - { - "matches": [ - { - "application.process.binary": "resolve" - } - ], - "actions": { - "update-props": { - "alsa.buffer-bytes": 131072 - } - } - } - ] -} diff --git a/nixos/modules/services/desktops/pipewire/daemon/client.conf.json b/nixos/modules/services/desktops/pipewire/daemon/client.conf.json deleted file mode 100644 index 71294a0e78a2d..0000000000000 --- a/nixos/modules/services/desktops/pipewire/daemon/client.conf.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "context.properties": { - "log.level": 0 - }, - "context.spa-libs": { - "audio.convert.*": "audioconvert/libspa-audioconvert", - "support.*": "support/libspa-support" - }, - "context.modules": [ - { - "name": "libpipewire-module-protocol-native" - }, - { - "name": "libpipewire-module-client-node" - }, - { - "name": "libpipewire-module-client-device" - }, - { - "name": "libpipewire-module-adapter" - }, - { - "name": "libpipewire-module-metadata" - }, - { - "name": "libpipewire-module-session-manager" - } - ], - "filter.properties": {}, - "stream.properties": {} -} diff --git a/nixos/modules/services/desktops/pipewire/daemon/filter-chain.conf.json b/nixos/modules/services/desktops/pipewire/daemon/filter-chain.conf.json deleted file mode 100644 index 689fca88359ba..0000000000000 --- a/nixos/modules/services/desktops/pipewire/daemon/filter-chain.conf.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "context.properties": { - "log.level": 0 - }, - "context.spa-libs": { - "audio.convert.*": "audioconvert/libspa-audioconvert", - "support.*": "support/libspa-support" - }, - "context.modules": [ - { - "name": "libpipewire-module-rt", - "args": {}, - "flags": [ - "ifexists", - "nofail" - ] - }, - { - "name": "libpipewire-module-protocol-native" - }, - { - "name": "libpipewire-module-client-node" - }, - { - "name": "libpipewire-module-adapter" - } - ] -} diff --git a/nixos/modules/services/desktops/pipewire/daemon/jack.conf.json b/nixos/modules/services/desktops/pipewire/daemon/jack.conf.json deleted file mode 100644 index f2e396dd28d76..0000000000000 --- a/nixos/modules/services/desktops/pipewire/daemon/jack.conf.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "context.properties": { - "log.level": 0 - }, - "context.spa-libs": { - "support.*": "support/libspa-support" - }, - "context.modules": [ - { - "name": "libpipewire-module-rt", - "args": {}, - "flags": [ - "ifexists", - "nofail" - ] - }, - { - "name": "libpipewire-module-protocol-native" - }, - { - "name": "libpipewire-module-client-node" - }, - { - "name": "libpipewire-module-metadata" - } - ], - "jack.properties": {}, - "jack.rules": [ - { - "matches": [ - {} - ], - "actions": { - "update-props": {} - } - }, - { - "matches": [ - { - "application.process.binary": "jack_bufsize" - } - ], - "actions": { - "update-props": { - "jack.global-buffer-size": true - } - } - }, - { - "matches": [ - { - "application.process.binary": "qsynth" - } - ], - "actions": { - "update-props": { - "node.pause-on-idle": false, - "node.passive": true - } - } - }, - { - "matches": [ - { - "client.name": "Mixxx" - } - ], - "actions": { - "update-props": { - "jack.merge-monitor": false - } - } - } - ] -} diff --git a/nixos/modules/services/desktops/pipewire/daemon/minimal.conf.json b/nixos/modules/services/desktops/pipewire/daemon/minimal.conf.json deleted file mode 100644 index 0f1ebe5749c67..0000000000000 --- a/nixos/modules/services/desktops/pipewire/daemon/minimal.conf.json +++ /dev/null @@ -1,120 +0,0 @@ -{ - "context.properties": { - "link.max-buffers": 16, - "core.daemon": true, - "core.name": "pipewire-0", - "settings.check-quantum": true, - "settings.check-rate": true, - "vm.overrides": { - "default.clock.min-quantum": 1024 - } - }, - "context.spa-libs": { - "audio.convert.*": "audioconvert/libspa-audioconvert", - "api.alsa.*": "alsa/libspa-alsa", - "support.*": "support/libspa-support" - }, - "context.modules": [ - { - "name": "libpipewire-module-rt", - "args": { - "nice.level": -11 - }, - "flags": [ - "ifexists", - "nofail" - ] - }, - { - "name": "libpipewire-module-protocol-native" - }, - { - "name": "libpipewire-module-profiler" - }, - { - "name": "libpipewire-module-metadata" - }, - { - "name": "libpipewire-module-spa-node-factory" - }, - { - "name": "libpipewire-module-client-node" - }, - { - "name": "libpipewire-module-access", - "args": {} - }, - { - "name": "libpipewire-module-adapter" - }, - { - "name": "libpipewire-module-link-factory" - } - ], - "context.objects": [ - { - "factory": "metadata", - "args": { - "metadata.name": "default" - } - }, - { - "factory": "spa-node-factory", - "args": { - "factory.name": "support.node.driver", - "node.name": "Dummy-Driver", - "node.group": "pipewire.dummy", - "priority.driver": 20000 - } - }, - { - "factory": "spa-node-factory", - "args": { - "factory.name": "support.node.driver", - "node.name": "Freewheel-Driver", - "priority.driver": 19000, - "node.group": "pipewire.freewheel", - "node.freewheel": true - } - }, - { - "factory": "adapter", - "args": { - "factory.name": "api.alsa.pcm.source", - "node.name": "system", - "node.description": "system", - "media.class": "Audio/Source", - "api.alsa.path": "hw:0", - "node.suspend-on-idle": true, - "resample.disable": true, - "channelmix.disable": true, - "adapter.auto-port-config": { - "mode": "dsp", - "monitor": false, - "control": false, - "position": "unknown" - } - } - }, - { - "factory": "adapter", - "args": { - "factory.name": "api.alsa.pcm.sink", - "node.name": "system", - "node.description": "system", - "media.class": "Audio/Sink", - "api.alsa.path": "hw:0", - "node.suspend-on-idle": true, - "resample.disable": true, - "channelmix.disable": true, - "adapter.auto-port-config": { - "mode": "dsp", - "monitor": false, - "control": false, - "position": "unknown" - } - } - } - ], - "context.exec": [] -} diff --git a/nixos/modules/services/desktops/pipewire/daemon/pipewire-aes67.conf.json b/nixos/modules/services/desktops/pipewire/daemon/pipewire-aes67.conf.json deleted file mode 100644 index aaffa93ca964c..0000000000000 --- a/nixos/modules/services/desktops/pipewire/daemon/pipewire-aes67.conf.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "context.properties": {}, - "context.modules": [ - { - "name": "libpipewire-module-rt", - "args": { - "nice.level": -11 - }, - "flags": [ - "ifexists", - "nofail" - ] - }, - { - "name": "libpipewire-module-protocol-native" - }, - { - "name": "libpipewire-module-client-node" - }, - { - "name": "libpipewire-module-adapter" - }, - { - "name": "libpipewire-module-rtp-source", - "args": { - "sap.ip": "239.255.255.255", - "sap.port": 9875, - "sess.latency.msec": 10, - "local.ifname": "eth0", - "stream.props": { - "media.class": "Audio/Source", - "node.virtual": false, - "device.api": "aes67" - } - } - } - ] -} diff --git a/nixos/modules/services/desktops/pipewire/daemon/pipewire-avb.conf.json b/nixos/modules/services/desktops/pipewire/daemon/pipewire-avb.conf.json deleted file mode 100644 index 4f669895d87b6..0000000000000 --- a/nixos/modules/services/desktops/pipewire/daemon/pipewire-avb.conf.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "context.properties": {}, - "context.spa-libs": { - "audio.convert.*": "audioconvert/libspa-audioconvert", - "support.*": "support/libspa-support" - }, - "context.modules": [ - { - "name": "libpipewire-module-rt", - "args": { - "nice.level": -11 - }, - "flags": [ - "ifexists", - "nofail" - ] - }, - { - "name": "libpipewire-module-protocol-native" - }, - { - "name": "libpipewire-module-client-node" - }, - { - "name": "libpipewire-module-adapter" - }, - { - "name": "libpipewire-module-avb", - "args": {} - } - ], - "context.exec": [], - "stream.properties": {}, - "avb.properties": { - "ifname": "enp3s0", - "vm.overrides": {} - } -} diff --git a/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json b/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json deleted file mode 100644 index b1a864853325c..0000000000000 --- a/nixos/modules/services/desktops/pipewire/daemon/pipewire-pulse.conf.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "context.properties": {}, - "context.spa-libs": { - "audio.convert.*": "audioconvert/libspa-audioconvert", - "support.*": "support/libspa-support" - }, - "context.modules": [ - { - "name": "libpipewire-module-rt", - "args": { - "nice.level": -11 - }, - "flags": [ - "ifexists", - "nofail" - ] - }, - { - "name": "libpipewire-module-protocol-native" - }, - { - "name": "libpipewire-module-client-node" - }, - { - "name": "libpipewire-module-adapter" - }, - { - "name": "libpipewire-module-metadata" - }, - { - "name": "libpipewire-module-protocol-pulse", - "args": {} - } - ], - "context.exec": [], - "pulse.cmd": [ - { - "cmd": "load-module", - "args": "module-always-sink", - "flags": [] - } - ], - "stream.properties": {}, - "pulse.properties": { - "server.address": [ - "unix:native" - ], - "vm.overrides": { - "pulse.min.quantum": "1024/48000" - } - }, - "pulse.rules": [ - { - "matches": [ - {} - ], - "actions": { - "update-props": {} - } - }, - { - "matches": [ - { - "application.process.binary": "teams" - }, - { - "application.process.binary": "teams-insiders" - }, - { - "application.process.binary": "skypeforlinux" - } - ], - "actions": { - "quirks": [ - "force-s16-info" - ] - } - }, - { - "matches": [ - { - "application.process.binary": "firefox" - } - ], - "actions": { - "quirks": [ - "remove-capture-dont-move" - ] - } - }, - { - "matches": [ - { - "application.name": "~speech-dispatcher.*" - } - ], - "actions": { - "update-props": { - "pulse.min.req": "512/48000", - "pulse.min.quantum": "512/48000", - "pulse.idle.timeout": 5 - } - } - } - ] -} diff --git a/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json b/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json deleted file mode 100644 index a47abe2213d94..0000000000000 --- a/nixos/modules/services/desktops/pipewire/daemon/pipewire.conf.json +++ /dev/null @@ -1,110 +0,0 @@ -{ - "context.properties": { - "link.max-buffers": 16, - "core.daemon": true, - "core.name": "pipewire-0", - "vm.overrides": { - "default.clock.min-quantum": 1024 - }, - "module.x11.bell": true - }, - "context.spa-libs": { - "audio.convert.*": "audioconvert/libspa-audioconvert", - "avb.*": "avb/libspa-avb", - "api.alsa.*": "alsa/libspa-alsa", - "api.v4l2.*": "v4l2/libspa-v4l2", - "api.libcamera.*": "libcamera/libspa-libcamera", - "api.bluez5.*": "bluez5/libspa-bluez5", - "api.vulkan.*": "vulkan/libspa-vulkan", - "api.jack.*": "jack/libspa-jack", - "support.*": "support/libspa-support" - }, - "context.modules": [ - { - "name": "libpipewire-module-rt", - "args": { - "nice.level": -11 - }, - "flags": [ - "ifexists", - "nofail" - ] - }, - { - "name": "libpipewire-module-protocol-native" - }, - { - "name": "libpipewire-module-profiler" - }, - { - "name": "libpipewire-module-metadata" - }, - { - "name": "libpipewire-module-spa-device-factory" - }, - { - "name": "libpipewire-module-spa-node-factory" - }, - { - "name": "libpipewire-module-client-node" - }, - { - "name": "libpipewire-module-client-device" - }, - { - "name": "libpipewire-module-portal", - "flags": [ - "ifexists", - "nofail" - ] - }, - { - "name": "libpipewire-module-access", - "args": {} - }, - { - "name": "libpipewire-module-adapter" - }, - { - "name": "libpipewire-module-link-factory" - }, - { - "name": "libpipewire-module-session-manager" - }, - { - "name": "libpipewire-module-x11-bell", - "args": {}, - "flags": [ - "ifexists", - "nofail" - ], - "condition": [ - { - "module.x11.bell": true - } - ] - } - ], - "context.objects": [ - { - "factory": "spa-node-factory", - "args": { - "factory.name": "support.node.driver", - "node.name": "Dummy-Driver", - "node.group": "pipewire.dummy", - "priority.driver": 20000 - } - }, - { - "factory": "spa-node-factory", - "args": { - "factory.name": "support.node.driver", - "node.name": "Freewheel-Driver", - "priority.driver": 19000, - "node.group": "pipewire.freewheel", - "node.freewheel": true - } - } - ], - "context.exec": [] -} diff --git a/nixos/modules/services/desktops/pipewire/media-session/alsa-monitor.conf.json b/nixos/modules/services/desktops/pipewire/media-session/alsa-monitor.conf.json deleted file mode 100644 index 53fc9cc96343b..0000000000000 --- a/nixos/modules/services/desktops/pipewire/media-session/alsa-monitor.conf.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "properties": {}, - "rules": [ - { - "matches": [ - { - "device.name": "~alsa_card.*" - } - ], - "actions": { - "update-props": { - "api.alsa.use-acp": true, - "api.acp.auto-profile": false, - "api.acp.auto-port": false - } - } - }, - { - "matches": [ - { - "node.name": "~alsa_input.*" - }, - { - "node.name": "~alsa_output.*" - } - ], - "actions": { - "update-props": { - "node.pause-on-idle": false - } - } - } - ] -} diff --git a/nixos/modules/services/desktops/pipewire/media-session/bluez-monitor.conf.json b/nixos/modules/services/desktops/pipewire/media-session/bluez-monitor.conf.json deleted file mode 100644 index 6d1c23e825699..0000000000000 --- a/nixos/modules/services/desktops/pipewire/media-session/bluez-monitor.conf.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "properties": {}, - "rules": [ - { - "matches": [ - { - "device.name": "~bluez_card.*" - } - ], - "actions": { - "update-props": { - "bluez5.auto-connect": [ - "hfp_hf", - "hsp_hs", - "a2dp_sink" - ] - } - } - }, - { - "matches": [ - { - "node.name": "~bluez_input.*" - }, - { - "node.name": "~bluez_output.*" - } - ], - "actions": { - "update-props": { - "node.pause-on-idle": false - } - } - } - ] -} diff --git a/nixos/modules/services/desktops/pipewire/media-session/media-session.conf.json b/nixos/modules/services/desktops/pipewire/media-session/media-session.conf.json deleted file mode 100644 index 4b4e302af3876..0000000000000 --- a/nixos/modules/services/desktops/pipewire/media-session/media-session.conf.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "context.properties": {}, - "context.spa-libs": { - "api.bluez5.*": "bluez5/libspa-bluez5", - "api.alsa.*": "alsa/libspa-alsa", - "api.v4l2.*": "v4l2/libspa-v4l2", - "api.libcamera.*": "libcamera/libspa-libcamera" - }, - "context.modules": [ - { - "name": "libpipewire-module-rtkit", - "args": {}, - "flags": [ - "ifexists", - "nofail" - ] - }, - { - "name": "libpipewire-module-protocol-native" - }, - { - "name": "libpipewire-module-client-node" - }, - { - "name": "libpipewire-module-client-device" - }, - { - "name": "libpipewire-module-adapter" - }, - { - "name": "libpipewire-module-metadata" - }, - { - "name": "libpipewire-module-session-manager" - } - ], - "session.modules": { - "default": [ - "flatpak", - "portal", - "v4l2", - "suspend-node", - "policy-node" - ], - "with-audio": [ - "metadata", - "default-nodes", - "default-profile", - "default-routes", - "alsa-seq", - "alsa-monitor" - ], - "with-alsa": [ - "with-audio" - ], - "with-jack": [ - "with-audio" - ], - "with-pulseaudio": [ - "with-audio", - "bluez5", - "bluez5-autoswitch", - "logind", - "restore-stream", - "streams-follow-default" - ] - } -} diff --git a/nixos/modules/services/desktops/pipewire/media-session/v4l2-monitor.conf.json b/nixos/modules/services/desktops/pipewire/media-session/v4l2-monitor.conf.json deleted file mode 100644 index b08cba1b604b5..0000000000000 --- a/nixos/modules/services/desktops/pipewire/media-session/v4l2-monitor.conf.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "properties": {}, - "rules": [ - { - "matches": [ - { - "device.name": "~v4l2_device.*" - } - ], - "actions": { - "update-props": {} - } - }, - { - "matches": [ - { - "node.name": "~v4l2_input.*" - }, - { - "node.name": "~v4l2_output.*" - } - ], - "actions": { - "update-props": { - "node.pause-on-idle": false - } - } - } - ] -} diff --git a/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix b/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix deleted file mode 100644 index 203139294c6b5..0000000000000 --- a/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix +++ /dev/null @@ -1,141 +0,0 @@ -# pipewire example session manager. -{ config, lib, pkgs, ... }: - -with lib; - -let - json = pkgs.formats.json {}; - cfg = config.services.pipewire.media-session; - enable32BitAlsaPlugins = cfg.alsa.support32Bit - && pkgs.stdenv.isx86_64 - && pkgs.pkgsi686Linux.pipewire != null; - - # Use upstream config files passed through spa-json-dump as the base - # Patched here as necessary for them to work with this module - defaults = { - alsa-monitor = lib.importJSON ./media-session/alsa-monitor.conf.json; - bluez-monitor = lib.importJSON ./media-session/bluez-monitor.conf.json; - media-session = lib.importJSON ./media-session/media-session.conf.json; - v4l2-monitor = lib.importJSON ./media-session/v4l2-monitor.conf.json; - }; - - configs = { - alsa-monitor = recursiveUpdate defaults.alsa-monitor cfg.config.alsa-monitor; - bluez-monitor = recursiveUpdate defaults.bluez-monitor cfg.config.bluez-monitor; - media-session = recursiveUpdate defaults.media-session cfg.config.media-session; - v4l2-monitor = recursiveUpdate defaults.v4l2-monitor cfg.config.v4l2-monitor; - }; -in { - - meta = { - maintainers = teams.freedesktop.members; - # uses attributes of the linked package - buildDocsInSandbox = false; - }; - - ###### interface - options = { - services.pipewire.media-session = { - enable = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc "Whether to enable the deprecated example Pipewire session manager"; - }; - - package = mkOption { - type = types.package; - default = pkgs.pipewire-media-session; - defaultText = literalExpression "pkgs.pipewire-media-session"; - description = lib.mdDoc '' - The pipewire-media-session derivation to use. - ''; - }; - - config = { - media-session = mkOption { - type = json.type; - description = lib.mdDoc '' - Configuration for the media session core. For details see - https://gitlab.freedesktop.org/pipewire/media-session/-/blob/${cfg.package.version}/src/daemon/media-session.d/media-session.conf - ''; - default = defaults.media-session; - }; - - alsa-monitor = mkOption { - type = json.type; - description = lib.mdDoc '' - Configuration for the alsa monitor. For details see - https://gitlab.freedesktop.org/pipewire/media-session/-/blob/${cfg.package.version}/src/daemon/media-session.d/alsa-monitor.conf - ''; - default = defaults.alsa-monitor; - }; - - bluez-monitor = mkOption { - type = json.type; - description = lib.mdDoc '' - Configuration for the bluez5 monitor. For details see - https://gitlab.freedesktop.org/pipewire/media-session/-/blob/${cfg.package.version}/src/daemon/media-session.d/bluez-monitor.conf - ''; - default = defaults.bluez-monitor; - }; - - v4l2-monitor = mkOption { - type = json.type; - description = lib.mdDoc '' - Configuration for the V4L2 monitor. For details see - https://gitlab.freedesktop.org/pipewire/media-session/-/blob/${cfg.package.version}/src/daemon/media-session.d/v4l2-monitor.conf - ''; - default = defaults.v4l2-monitor; - }; - }; - }; - }; - - ###### implementation - config = mkIf cfg.enable { - environment.systemPackages = [ cfg.package ]; - systemd.packages = [ cfg.package ]; - - # Enable either system or user units. - systemd.services.pipewire-media-session.enable = config.services.pipewire.systemWide; - systemd.user.services.pipewire-media-session.enable = !config.services.pipewire.systemWide; - - systemd.services.pipewire-media-session.wantedBy = [ "pipewire.service" ]; - systemd.user.services.pipewire-media-session.wantedBy = [ "pipewire.service" ]; - - environment.etc."pipewire/media-session.d/media-session.conf" = { - source = json.generate "media-session.conf" configs.media-session; - }; - environment.etc."pipewire/media-session.d/v4l2-monitor.conf" = { - source = json.generate "v4l2-monitor.conf" configs.v4l2-monitor; - }; - - environment.etc."pipewire/media-session.d/with-audio" = - mkIf config.services.pipewire.audio.enable { - text = ""; - }; - - environment.etc."pipewire/media-session.d/with-alsa" = - mkIf config.services.pipewire.alsa.enable { - text = ""; - }; - environment.etc."pipewire/media-session.d/alsa-monitor.conf" = - mkIf config.services.pipewire.alsa.enable { - source = json.generate "alsa-monitor.conf" configs.alsa-monitor; - }; - - environment.etc."pipewire/media-session.d/with-pulseaudio" = - mkIf config.services.pipewire.pulse.enable { - text = ""; - }; - environment.etc."pipewire/media-session.d/bluez-monitor.conf" = - mkIf config.services.pipewire.pulse.enable { - source = json.generate "bluez-monitor.conf" configs.bluez-monitor; - }; - - environment.etc."pipewire/media-session.d/with-jack" = - mkIf config.services.pipewire.jack.enable { - text = ""; - }; - }; -} diff --git a/nixos/modules/services/desktops/pipewire/pipewire.nix b/nixos/modules/services/desktops/pipewire/pipewire.nix index 09cec9a791091..ae695baf42c60 100644 --- a/nixos/modules/services/desktops/pipewire/pipewire.nix +++ b/nixos/modules/services/desktops/pipewire/pipewire.nix @@ -4,7 +4,6 @@ with lib; let - json = pkgs.formats.json {}; cfg = config.services.pipewire; enable32BitAlsaPlugins = cfg.alsa.support32Bit && pkgs.stdenv.isx86_64 @@ -18,34 +17,8 @@ let mkdir -p "$out/lib" ln -s "${cfg.package.jack}/lib" "$out/lib/pipewire" ''; - - # Use upstream config files passed through spa-json-dump as the base - # Patched here as necessary for them to work with this module - defaults = { - client = lib.importJSON ./daemon/client.conf.json; - client-rt = lib.importJSON ./daemon/client-rt.conf.json; - jack = lib.importJSON ./daemon/jack.conf.json; - minimal = lib.importJSON ./daemon/minimal.conf.json; - pipewire = lib.importJSON ./daemon/pipewire.conf.json; - pipewire-pulse = lib.importJSON ./daemon/pipewire-pulse.conf.json; - }; - - useSessionManager = cfg.wireplumber.enable || cfg.media-session.enable; - - configs = { - client = recursiveUpdate defaults.client cfg.config.client; - client-rt = recursiveUpdate defaults.client-rt cfg.config.client-rt; - jack = recursiveUpdate defaults.jack cfg.config.jack; - pipewire = recursiveUpdate (if useSessionManager then defaults.pipewire else defaults.minimal) cfg.config.pipewire; - pipewire-pulse = recursiveUpdate defaults.pipewire-pulse cfg.config.pipewire-pulse; - }; in { - - meta = { - maintainers = teams.freedesktop.members ++ [ lib.maintainers.k900 ]; - # uses attributes of the linked package - buildDocsInSandbox = false; - }; + meta.maintainers = teams.freedesktop.members ++ [ lib.maintainers.k900 ]; ###### interface options = { @@ -69,53 +42,6 @@ in { ''; }; - config = { - client = mkOption { - type = json.type; - default = {}; - description = lib.mdDoc '' - Configuration for pipewire clients. For details see - https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/client.conf.in - ''; - }; - - client-rt = mkOption { - type = json.type; - default = {}; - description = lib.mdDoc '' - Configuration for realtime pipewire clients. For details see - https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/client-rt.conf.in - ''; - }; - - jack = mkOption { - type = json.type; - default = {}; - description = lib.mdDoc '' - Configuration for the pipewire daemon's jack module. For details see - https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/jack.conf.in - ''; - }; - - pipewire = mkOption { - type = json.type; - default = {}; - description = lib.mdDoc '' - Configuration for the pipewire daemon. For details see - https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/pipewire.conf.in - ''; - }; - - pipewire-pulse = mkOption { - type = json.type; - default = {}; - description = lib.mdDoc '' - Configuration for the pipewire-pulse daemon. For details see - https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/pipewire-pulse.conf.in - ''; - }; - }; - audio = { enable = lib.mkOption { type = lib.types.bool; @@ -153,10 +79,20 @@ in { https://github.com/PipeWire/pipewire/blob/master/NEWS ''; }; - }; }; + imports = [ + (lib.mkRemovedOptionModule ["services" "pipewire" "config"] '' + Overriding default Pipewire configuration through NixOS options never worked correctly and is no longer supported. + Please create drop-in files in /etc/pipewire/pipewire.conf.d/ to make the desired setting changes instead. + '') + + (lib.mkRemovedOptionModule ["services" "pipewire" "media-session"] '' + pipewire-media-session is no longer supported upstream and has been removed. + Please switch to `services.pipewire.wireplumber` instead. + '') + ]; ###### implementation config = mkIf cfg.enable { @@ -222,22 +158,6 @@ in { source = "${cfg.package}/share/alsa/alsa.conf.d/99-pipewire-default.conf"; }; - environment.etc."pipewire/client.conf" = { - source = json.generate "client.conf" configs.client; - }; - environment.etc."pipewire/client-rt.conf" = { - source = json.generate "client-rt.conf" configs.client-rt; - }; - environment.etc."pipewire/jack.conf" = { - source = json.generate "jack.conf" configs.jack; - }; - environment.etc."pipewire/pipewire.conf" = { - source = json.generate "pipewire.conf" configs.pipewire; - }; - environment.etc."pipewire/pipewire-pulse.conf" = mkIf cfg.pulse.enable { - source = json.generate "pipewire-pulse.conf" configs.pipewire-pulse; - }; - environment.sessionVariables.LD_LIBRARY_PATH = lib.mkIf cfg.jack.enable [ "${cfg.package.jack}/lib" ]; @@ -256,12 +176,5 @@ in { }; groups.pipewire.gid = config.ids.gids.pipewire; }; - - # https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/464#note_723554 - systemd.services.pipewire.environment."PIPEWIRE_LINK_PASSIVE" = "1"; - systemd.user.services.pipewire.environment."PIPEWIRE_LINK_PASSIVE" = "1"; - - # pipewire-pulse default config expects pactl to be in PATH - systemd.user.services.pipewire-pulse.path = lib.mkIf cfg.pulse.enable [ pkgs.pulseaudio ]; }; } diff --git a/nixos/modules/services/desktops/pipewire/wireplumber.nix b/nixos/modules/services/desktops/pipewire/wireplumber.nix index 4b36b99aa7c1e..95a7ece26c5d2 100644 --- a/nixos/modules/services/desktops/pipewire/wireplumber.nix +++ b/nixos/modules/services/desktops/pipewire/wireplumber.nix @@ -29,10 +29,6 @@ in config = lib.mkIf cfg.enable { assertions = [ { - assertion = !config.services.pipewire.media-session.enable; - message = "WirePlumber and pipewire-media-session can't be enabled at the same time."; - } - { assertion = !config.hardware.bluetooth.hsphfpd.enable; message = "Using Wireplumber conflicts with hsphfpd, as it provides the same functionality. `hardware.bluetooth.hsphfpd.enable` needs be set to false"; } diff --git a/nixos/modules/services/hardware/kanata.nix b/nixos/modules/services/hardware/kanata.nix index bb730037277b8..7d544050130b9 100644 --- a/nixos/modules/services/hardware/kanata.nix +++ b/nixos/modules/services/hardware/kanata.nix @@ -86,6 +86,7 @@ let mkService = name: keyboard: nameValuePair (mkName name) { wantedBy = [ "multi-user.target" ]; serviceConfig = { + Type = "notify"; ExecStart = '' ${getExe cfg.package} \ --cfg ${mkConfig name keyboard} \ @@ -123,8 +124,7 @@ let ProtectKernelModules = true; ProtectKernelTunables = true; ProtectProc = "invisible"; - RestrictAddressFamilies = - if (keyboard.port == null) then "none" else [ "AF_INET" ]; + RestrictAddressFamilies = [ "AF_UNIX" ] ++ optional (keyboard.port != null) "AF_INET"; RestrictNamespaces = true; RestrictRealtime = true; SystemCallArchitectures = [ "native" ]; diff --git a/nixos/modules/services/hardware/keyd.nix b/nixos/modules/services/hardware/keyd.nix new file mode 100644 index 0000000000000..64c769405fabc --- /dev/null +++ b/nixos/modules/services/hardware/keyd.nix @@ -0,0 +1,112 @@ +{ config, lib, pkgs, ... }: +with lib; +let + cfg = config.services.keyd; + settingsFormat = pkgs.formats.ini { }; +in +{ + options = { + services.keyd = { + enable = mkEnableOption (lib.mdDoc "keyd, a key remapping daemon"); + + ids = mkOption { + type = types.listOf types.string; + default = [ "*" ]; + example = [ "*" "-0123:0456" ]; + description = lib.mdDoc '' + Device identifiers, as shown by {manpage}`keyd(1)`. + ''; + }; + + settings = mkOption { + type = settingsFormat.type; + default = { }; + example = { + main = { + capslock = "overload(control, esc)"; + rightalt = "layer(rightalt)"; + }; + + rightalt = { + j = "down"; + k = "up"; + h = "left"; + l = "right"; + }; + }; + description = lib.mdDoc '' + Configuration, except `ids` section, that is written to {file}`/etc/keyd/default.conf`. + See <https://github.com/rvaiya/keyd> how to configure. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + environment.etc."keyd/default.conf".source = pkgs.runCommand "default.conf" + { + ids = '' + [ids] + ${concatStringsSep "\n" cfg.ids} + ''; + passAsFile = [ "ids" ]; + } '' + cat $idsPath <(echo) ${settingsFormat.generate "keyd-main.conf" cfg.settings} >$out + ''; + + hardware.uinput.enable = lib.mkDefault true; + + systemd.services.keyd = { + description = "Keyd remapping daemon"; + documentation = [ "man:keyd(1)" ]; + + wantedBy = [ "multi-user.target" ]; + + restartTriggers = [ + config.environment.etc."keyd/default.conf".source + ]; + + # this is configurable in 2.4.2, later versions seem to remove this option. + # post-2.4.2 may need to set makeFlags in the derivation: + # + # makeFlags = [ "SOCKET_PATH/run/keyd/keyd.socket" ]; + environment.KEYD_SOCKET = "/run/keyd/keyd.sock"; + + serviceConfig = { + ExecStart = "${pkgs.keyd}/bin/keyd"; + Restart = "always"; + + DynamicUser = true; + SupplementaryGroups = [ + config.users.groups.input.name + config.users.groups.uinput.name + ]; + + RuntimeDirectory = "keyd"; + + # Hardening + CapabilityBoundingSet = ""; + DeviceAllow = [ + "char-input rw" + "/dev/uinput rw" + ]; + ProtectClock = true; + PrivateNetwork = true; + ProtectHome = true; + ProtectHostname = true; + PrivateUsers = true; + PrivateMounts = true; + RestrictNamespaces = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectControlGroups = true; + MemoryDenyWriteExecute = true; + RestrictRealtime = true; + LockPersonality = true; + ProtectProc = "noaccess"; + UMask = "0077"; + }; + }; + }; +} diff --git a/nixos/modules/services/hardware/supergfxd.nix b/nixos/modules/services/hardware/supergfxd.nix index df339e4ba011f..5ea05ac277167 100644 --- a/nixos/modules/services/hardware/supergfxd.nix +++ b/nixos/modules/services/hardware/supergfxd.nix @@ -32,6 +32,7 @@ in systemd.packages = [ pkgs.supergfxctl ]; systemd.services.supergfxd.wantedBy = [ "multi-user.target" ]; + systemd.services.supergfxd.path = [ pkgs.kmod ]; services.dbus.packages = [ pkgs.supergfxctl ]; services.udev.packages = [ pkgs.supergfxctl ]; diff --git a/nixos/modules/services/hardware/undervolt.nix b/nixos/modules/services/hardware/undervolt.nix index c49d944cdc188..944777475401b 100644 --- a/nixos/modules/services/hardware/undervolt.nix +++ b/nixos/modules/services/hardware/undervolt.nix @@ -5,8 +5,8 @@ let cfg = config.services.undervolt; mkPLimit = limit: window: - if (isNull limit && isNull window) then null - else assert asserts.assertMsg (!isNull limit && !isNull window) "Both power limit and window must be set"; + if (limit == null && window == null) then null + else assert asserts.assertMsg (limit != null && window != null) "Both power limit and window must be set"; "${toString limit} ${toString window}"; cliArgs = lib.cli.toGNUCommandLine {} { inherit (cfg) diff --git a/nixos/modules/services/home-automation/home-assistant.nix b/nixos/modules/services/home-automation/home-assistant.nix index 6adc58ec58ec4..cea8a2b14cc22 100644 --- a/nixos/modules/services/home-automation/home-assistant.nix +++ b/nixos/modules/services/home-automation/home-assistant.nix @@ -362,7 +362,7 @@ in { config = mkIf cfg.enable { assertions = [ { - assertion = cfg.openFirewall -> !isNull cfg.config; + assertion = cfg.openFirewall -> cfg.config != null; message = "openFirewall can only be used with a declarative config"; } ]; diff --git a/nixos/modules/services/logging/logrotate.nix b/nixos/modules/services/logging/logrotate.nix index 1799e9282b3b0..b056f96c3630b 100644 --- a/nixos/modules/services/logging/logrotate.nix +++ b/nixos/modules/services/logging/logrotate.nix @@ -187,7 +187,7 @@ in A configuration file automatically generated by NixOS. ''; description = lib.mdDoc '' - Override the configuration file used by MySQL. By default, + Override the configuration file used by logrotate. By default, NixOS generates one automatically from [](#opt-services.logrotate.settings). ''; example = literalExpression '' diff --git a/nixos/modules/services/mail/roundcube.nix b/nixos/modules/services/mail/roundcube.nix index 95dc2f6aa2c92..7b6d82219298c 100644 --- a/nixos/modules/services/mail/roundcube.nix +++ b/nixos/modules/services/mail/roundcube.nix @@ -132,6 +132,8 @@ in $config['plugins'] = [${concatMapStringsSep "," (p: "'${p}'") cfg.plugins}]; $config['des_key'] = file_get_contents('/var/lib/roundcube/des_key'); $config['mime_types'] = '${pkgs.nginx}/conf/mime.types'; + # Roundcube uses PHP-FPM which has `PrivateTmp = true;` + $config['temp_dir'] = '/tmp'; $config['enable_spellcheck'] = ${if cfg.dicts == [] then "false" else "true"}; # by default, spellchecking uses a third-party cloud services $config['spellcheck_engine'] = 'pspell'; diff --git a/nixos/modules/services/matrix/synapse.md b/nixos/modules/services/matrix/synapse.md index 7a9ddf8c9daf3..cad91ebf58d56 100644 --- a/nixos/modules/services/matrix/synapse.md +++ b/nixos/modules/services/matrix/synapse.md @@ -27,10 +27,7 @@ please refer to the { pkgs, lib, config, ... }: let fqdn = "${config.networking.hostName}.${config.networking.domain}"; - clientConfig = { - "m.homeserver".base_url = "https://${fqdn}"; - "m.identity_server" = {}; - }; + clientConfig."m.homeserver".base_url = "https://${fqdn}"; serverConfig."m.server" = "${fqdn}:443"; mkWellKnown = data: '' add_header Content-Type application/json; diff --git a/nixos/modules/services/matrix/synapse.nix b/nixos/modules/services/matrix/synapse.nix index aee275dab1ec1..b6b51b21c796f 100644 --- a/nixos/modules/services/matrix/synapse.nix +++ b/nixos/modules/services/matrix/synapse.nix @@ -60,7 +60,7 @@ in { '') (mkRemovedOptionModule [ "services" "matrix-synapse" "create_local_database" ] '' Database configuration must be done manually. An exemplary setup is demonstrated in - <nixpkgs/nixos/tests/matrix-synapse.nix> + <nixpkgs/nixos/tests/matrix/synapse.nix> '') (mkRemovedOptionModule [ "services" "matrix-synapse" "web_client" ] "") (mkRemovedOptionModule [ "services" "matrix-synapse" "room_invite_state_types" ] '' @@ -711,7 +711,7 @@ in { If you - try to deploy a fresh synapse, you need to configure the database yourself. An example - for this can be found in <nixpkgs/nixos/tests/matrix-synapse.nix> + for this can be found in <nixpkgs/nixos/tests/matrix/synapse.nix> - update your existing matrix-synapse instance, you simply need to add `services.postgresql.enable = true` to your configuration. diff --git a/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix index 014c5b16097c9..e019e431a1890 100644 --- a/nixos/modules/services/misc/gitea.nix +++ b/nixos/modules/services/misc/gitea.nix @@ -365,6 +365,8 @@ in ]; services.gitea.settings = { + "cron.update_checker".ENABLED = lib.mkDefault false; + database = mkMerge [ { DB_TYPE = cfg.database.type; diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix index c7299c1ccad86..d278b571a6410 100644 --- a/nixos/modules/services/misc/gitlab.nix +++ b/nixos/modules/services/misc/gitlab.nix @@ -156,7 +156,7 @@ let }; extra = {}; uploads.storage_path = cfg.statePath; - pages = { + pages = optionalAttrs cfg.pages.enable { enabled = cfg.pages.enable; port = 8090; host = cfg.pages.settings.pages-domain; diff --git a/nixos/modules/services/misc/portunus.nix b/nixos/modules/services/misc/portunus.nix index f60cbe3477132..5504fb942968f 100644 --- a/nixos/modules/services/misc/portunus.nix +++ b/nixos/modules/services/misc/portunus.nix @@ -238,7 +238,7 @@ in PORTUNUS_SERVER_BINARY = "${cfg.package}/bin/portunus-server"; PORTUNUS_SERVER_GROUP = cfg.group; PORTUNUS_SERVER_USER = cfg.user; - PORTUNUS_SERVER_HTTP_LISTEN = "[::]:${toString cfg.port}"; + PORTUNUS_SERVER_HTTP_LISTEN = "127.0.0.1:${toString cfg.port}"; PORTUNUS_SERVER_STATE_DIR = cfg.stateDir; PORTUNUS_SLAPD_BINARY = "${cfg.ldap.package}/libexec/slapd"; PORTUNUS_SLAPD_GROUP = cfg.ldap.group; diff --git a/nixos/modules/services/misc/sssd.nix b/nixos/modules/services/misc/sssd.nix index edd5750a4a478..7c7a3b464a836 100644 --- a/nixos/modules/services/misc/sssd.nix +++ b/nixos/modules/services/misc/sssd.nix @@ -77,6 +77,10 @@ in { }; config = mkMerge [ (mkIf cfg.enable { + # For `sssctl` to work. + environment.etc."sssd/sssd.conf".source = settingsFile; + environment.etc."sssd/conf.d".source = "${dataDir}/conf.d"; + systemd.services.sssd = { description = "System Security Services Daemon"; wantedBy = [ "multi-user.target" ]; @@ -101,6 +105,7 @@ in { EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile; }; preStart = '' + mkdir -p "${dataDir}/conf.d" [ -f ${settingsFile} ] && rm -f ${settingsFile} old_umask=$(umask) umask 0177 diff --git a/nixos/modules/services/misc/zoneminder.nix b/nixos/modules/services/misc/zoneminder.nix index 109415a20ee63..11722979851c2 100644 --- a/nixos/modules/services/misc/zoneminder.nix +++ b/nixos/modules/services/misc/zoneminder.nix @@ -283,7 +283,8 @@ in { phpfpm = lib.mkIf useNginx { pools.zoneminder = { inherit user group; - phpPackage = pkgs.php.withExtensions ({ enabled, all }: enabled ++ [ all.apcu ]); + phpPackage = pkgs.php.withExtensions ( + { enabled, all }: enabled ++ [ all.apcu all.sysvsem ]); phpOptions = '' date.timezone = "${config.time.timeZone}" ''; @@ -326,6 +327,15 @@ in { fi ${zoneminder}/bin/zmupdate.pl -nointeractive + ${zoneminder}/bin/zmupdate.pl --nointeractive -f + + # Update ZM's Nix store path in the configuration table. Do nothing if the config doesn't + # contain ZM's Nix store path. + ${config.services.mysql.package}/bin/mysql -u zoneminder zm << EOF + UPDATE Config + SET Value = REGEXP_REPLACE(Value, "^/nix/store/[^-/]+-zoneminder-[^/]+", "${pkgs.zoneminder}") + WHERE Name = "ZM_FONT_FILE_LOCATION"; + EOF ''; serviceConfig = { User = user; diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix index ba3f89e24dd45..5a8c65b9dc3fb 100644 --- a/nixos/modules/services/monitoring/grafana.nix +++ b/nixos/modules/services/monitoring/grafana.nix @@ -1300,7 +1300,7 @@ in { SystemCallFilter = [ "@system-service" "~@privileged" - ] ++ lib.optional (cfg.settings.server.protocol == "socket") [ "@chown" ]; + ] ++ lib.optionals (cfg.settings.server.protocol == "socket") [ "@chown" ]; UMask = "0027"; }; preStart = '' diff --git a/nixos/modules/services/monitoring/prometheus/alertmanager-irc-relay.nix b/nixos/modules/services/monitoring/prometheus/alertmanager-irc-relay.nix new file mode 100644 index 0000000000000..b81d5f6db5e08 --- /dev/null +++ b/nixos/modules/services/monitoring/prometheus/alertmanager-irc-relay.nix @@ -0,0 +1,107 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.prometheus.alertmanagerIrcRelay; + + configFormat = pkgs.formats.yaml { }; + configFile = configFormat.generate "alertmanager-irc-relay.yml" cfg.settings; +in +{ + options.services.prometheus.alertmanagerIrcRelay = { + enable = mkEnableOption (mdDoc "Alertmanager IRC Relay"); + + package = mkOption { + type = types.package; + default = pkgs.alertmanager-irc-relay; + defaultText = literalExpression "pkgs.alertmanager-irc-relay"; + description = mdDoc "Alertmanager IRC Relay package to use."; + }; + + extraFlags = mkOption { + type = types.listOf types.str; + default = []; + description = mdDoc "Extra command line options to pass to alertmanager-irc-relay."; + }; + + settings = mkOption { + type = configFormat.type; + example = literalExpression '' + { + http_host = "localhost"; + http_port = 8000; + + irc_host = "irc.example.com"; + irc_port = 7000; + irc_nickname = "myalertbot"; + + irc_channels = [ + { name = "#mychannel"; } + ]; + } + ''; + description = mdDoc '' + Configuration for Alertmanager IRC Relay as a Nix attribute set. + For a reference, check out the + [example configuration](https://github.com/google/alertmanager-irc-relay#configuring-and-running-the-bot) + and the + [source code](https://github.com/google/alertmanager-irc-relay/blob/master/config.go). + + Note: The webhook's URL MUST point to the IRC channel where the message + should be posted. For `#mychannel` from the example, this would be + `http://localhost:8080/mychannel`. + ''; + }; + }; + + config = mkIf cfg.enable { + systemd.services.alertmanager-irc-relay = { + description = "Alertmanager IRC Relay"; + + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + + serviceConfig = { + ExecStart = '' + ${cfg.package}/bin/alertmanager-irc-relay \ + -config ${configFile} \ + ${escapeShellArgs cfg.extraFlags} + ''; + + DynamicUser = true; + NoNewPrivileges = true; + + ProtectProc = "invisible"; + ProtectSystem = "strict"; + ProtectHome = "tmpfs"; + + PrivateTmp = true; + PrivateDevices = true; + PrivateIPC = true; + + ProtectHostname = true; + ProtectClock = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectKernelLogs = true; + ProtectControlGroups = true; + + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; + RestrictRealtime = true; + RestrictSUIDSGID = true; + + SystemCallFilter = [ + "@system-service" + "~@cpu-emulation" + "~@privileged" + "~@reboot" + "~@setuid" + "~@swap" + ]; + }; + }; + }; + + meta.maintainers = [ maintainers.oxzi ]; +} diff --git a/nixos/modules/services/monitoring/prometheus/default.nix b/nixos/modules/services/monitoring/prometheus/default.nix index 4f197b9b5820e..fb3bab7963ea8 100644 --- a/nixos/modules/services/monitoring/prometheus/default.nix +++ b/nixos/modules/services/monitoring/prometheus/default.nix @@ -31,7 +31,7 @@ let if checkConfigEnabled then pkgs.runCommandLocal "${name}-${replaceStrings [" "] [""] what}-checked" - { buildInputs = [ cfg.package ]; } '' + { buildInputs = [ cfg.package.cli ]; } '' ln -s ${file} $out promtool ${what} $out '' else file; diff --git a/nixos/modules/services/network-filesystems/kubo.nix b/nixos/modules/services/network-filesystems/kubo.nix index 4d423c9059866..0cb0e126d4c50 100644 --- a/nixos/modules/services/network-filesystems/kubo.nix +++ b/nixos/modules/services/network-filesystems/kubo.nix @@ -171,7 +171,11 @@ in "/ip4/0.0.0.0/tcp/4001" "/ip6/::/tcp/4001" "/ip4/0.0.0.0/udp/4001/quic" + "/ip4/0.0.0.0/udp/4001/quic-v1" + "/ip4/0.0.0.0/udp/4001/quic-v1/webtransport" "/ip6/::/udp/4001/quic" + "/ip6/::/udp/4001/quic-v1" + "/ip6/::/udp/4001/quic-v1/webtransport" ]; description = lib.mdDoc "Where Kubo listens for incoming p2p connections"; }; diff --git a/nixos/modules/services/networking/avahi-daemon.nix b/nixos/modules/services/networking/avahi-daemon.nix index 103f73fdaa685..3a7519c7230b3 100644 --- a/nixos/modules/services/networking/avahi-daemon.nix +++ b/nixos/modules/services/networking/avahi-daemon.nix @@ -5,7 +5,7 @@ with lib; let cfg = config.services.avahi; - yesNo = yes : if yes then "yes" else "no"; + yesNo = yes: if yes then "yes" else "no"; avahiDaemonConf = with cfg; pkgs.writeText "avahi-daemon.conf" '' [server] @@ -17,7 +17,8 @@ let browse-domains=${concatStringsSep ", " browseDomains} use-ipv4=${yesNo ipv4} use-ipv6=${yesNo ipv6} - ${optionalString (interfaces!=null) "allow-interfaces=${concatStringsSep "," interfaces}"} + ${optionalString (allowInterfaces!=null) "allow-interfaces=${concatStringsSep "," allowInterfaces}"} + ${optionalString (denyInterfaces!=null) "deny-interfaces=${concatStringsSep "," denyInterfaces}"} ${optionalString (domainName!=null) "domain-name=${domainName}"} allow-point-to-point=${yesNo allowPointToPoint} ${optionalString (cacheEntriesMax!=null) "cache-entries-max=${toString cacheEntriesMax}"} @@ -39,6 +40,10 @@ let ''; in { + imports = [ + (lib.mkRenamedOptionModule [ "services" "avahi" "interfaces" ] [ "services" "avahi" "allowInterfaces" ]) + ]; + options.services.avahi = { enable = mkOption { type = types.bool; @@ -91,7 +96,7 @@ in description = lib.mdDoc "Whether to use IPv6."; }; - interfaces = mkOption { + allowInterfaces = mkOption { type = types.nullOr (types.listOf types.str); default = null; description = lib.mdDoc '' @@ -101,6 +106,17 @@ in ''; }; + denyInterfaces = mkOption { + type = types.nullOr (types.listOf types.str); + default = null; + description = lib.mdDoc '' + List of network interfaces that should be ignored by the + {command}`avahi-daemon`. Other unspecified interfaces will be used, + unless {option}`allowInterfaces` is set. This option takes precedence + over {option}`allowInterfaces`. + ''; + }; + openFirewall = mkOption { type = types.bool; default = true; @@ -134,7 +150,7 @@ in extraServiceFiles = mkOption { type = with types; attrsOf (either str path); - default = {}; + default = { }; example = literalExpression '' { ssh = "''${pkgs.avahi}/etc/avahi/services/ssh.service"; @@ -236,7 +252,7 @@ in isSystemUser = true; }; - users.groups.avahi = {}; + users.groups.avahi = { }; system.nssModules = optional cfg.nssmdns pkgs.nssmdns; system.nssDatabases.hosts = optionals cfg.nssmdns (mkMerge [ @@ -246,10 +262,12 @@ in environment.systemPackages = [ pkgs.avahi ]; - environment.etc = (mapAttrs' (n: v: nameValuePair - "avahi/services/${n}.service" - { ${if types.path.check v then "source" else "text"} = v; } - ) cfg.extraServiceFiles); + environment.etc = (mapAttrs' + (n: v: nameValuePair + "avahi/services/${n}.service" + { ${if types.path.check v then "source" else "text"} = v; } + ) + cfg.extraServiceFiles); systemd.sockets.avahi-daemon = { description = "Avahi mDNS/DNS-SD Stack Activation Socket"; diff --git a/nixos/modules/services/networking/firewall-nftables.nix b/nixos/modules/services/networking/firewall-nftables.nix index 0ed3c228075d3..452dd97d89d29 100644 --- a/nixos/modules/services/networking/firewall-nftables.nix +++ b/nixos/modules/services/networking/firewall-nftables.nix @@ -94,7 +94,13 @@ in ${optionalString (ifaceSet != "") ''iifname { ${ifaceSet} } accept comment "trusted interfaces"''} # Some ICMPv6 types like NDP is untracked - ct state vmap { invalid : drop, established : accept, related : accept, * : jump input-allow } comment "*: new and untracked" + ct state vmap { + invalid : drop, + established : accept, + related : accept, + new : jump input-allow, + untracked: jump input-allow, + } ${optionalString cfg.logRefusedConnections '' tcp flags syn / fin,syn,rst,ack log level info prefix "refused connection: " @@ -143,7 +149,13 @@ in chain forward { type filter hook forward priority filter; policy drop; - ct state vmap { invalid : drop, established : accept, related : accept, * : jump forward-allow } comment "*: new and untracked" + ct state vmap { + invalid : drop, + established : accept, + related : accept, + new : jump forward-allow, + untracked : jump forward-allow, + } } diff --git a/nixos/modules/services/networking/headscale.nix b/nixos/modules/services/networking/headscale.nix index 390a448ab5842..d2851e72a0dd6 100644 --- a/nixos/modules/services/networking/headscale.nix +++ b/nixos/modules/services/networking/headscale.nix @@ -291,11 +291,11 @@ in { ''; }; - client_secret_file = mkOption { + client_secret_path = mkOption { type = types.nullOr types.path; default = null; description = lib.mdDoc '' - Path to OpenID Connect client secret file. + Path to OpenID Connect client secret file. Expands environment variables in format ''${VAR}. ''; }; @@ -425,7 +425,7 @@ in { (mkRenamedOptionModule ["services" "headscale" "dns" "baseDomain"] ["services" "headscale" "settings" "dns_config" "base_domain"]) (mkRenamedOptionModule ["services" "headscale" "openIdConnect" "issuer"] ["services" "headscale" "settings" "oidc" "issuer"]) (mkRenamedOptionModule ["services" "headscale" "openIdConnect" "clientId"] ["services" "headscale" "settings" "oidc" "client_id"]) - (mkRenamedOptionModule ["services" "headscale" "openIdConnect" "clientSecretFile"] ["services" "headscale" "settings" "oidc" "client_secret_file"]) + (mkRenamedOptionModule ["services" "headscale" "openIdConnect" "clientSecretFile"] ["services" "headscale" "settings" "oidc" "client_secret_path"]) (mkRenamedOptionModule ["services" "headscale" "tls" "letsencrypt" "hostname"] ["services" "headscale" "settings" "tls_letsencrypt_hostname"]) (mkRenamedOptionModule ["services" "headscale" "tls" "letsencrypt" "challengeType"] ["services" "headscale" "settings" "tls_letsencrypt_challenge_type"]) (mkRenamedOptionModule ["services" "headscale" "tls" "letsencrypt" "httpListen"] ["services" "headscale" "settings" "tls_letsencrypt_listen"]) @@ -478,9 +478,6 @@ in { export HEADSCALE_DB_PASS="$(head -n1 ${escapeShellArg cfg.settings.db_password_file})" ''} - ${optionalString (cfg.settings.oidc.client_secret_file != null) '' - export HEADSCALE_OIDC_CLIENT_SECRET="$(head -n1 ${escapeShellArg cfg.settings.oidc.client_secret_file})" - ''} exec ${cfg.package}/bin/headscale serve ''; diff --git a/nixos/modules/services/networking/jicofo.nix b/nixos/modules/services/networking/jicofo.nix index 5e97889607363..0886bbe004c46 100644 --- a/nixos/modules/services/networking/jicofo.nix +++ b/nixos/modules/services/networking/jicofo.nix @@ -4,6 +4,15 @@ with lib; let cfg = config.services.jicofo; + + # HOCON is a JSON superset that some jitsi-meet components use for configuration + toHOCON = x: if isAttrs x && x ? __hocon_envvar then ("\${" + x.__hocon_envvar + "}") + else if isAttrs x && x ? __hocon_unquoted_string then x.__hocon_unquoted_string + else if isAttrs x then "{${ concatStringsSep "," (mapAttrsToList (k: v: ''"${k}":${toHOCON v}'') x) }}" + else if isList x then "[${ concatMapStringsSep "," toHOCON x }]" + else builtins.toJSON x; + + configFile = pkgs.writeText "jicofo.conf" (toHOCON cfg.config); in { options.services.jicofo = with types; { @@ -68,22 +77,34 @@ in }; config = mkOption { - type = attrsOf str; + type = (pkgs.formats.json {}).type; default = { }; example = literalExpression '' { - "org.jitsi.jicofo.auth.URL" = "XMPP:jitsi-meet.example.com"; + jicofo.bridge.max-bridge-participants = 42; } ''; description = lib.mdDoc '' - Contents of the {file}`sip-communicator.properties` configuration file for jicofo. + Contents of the {file}`jicofo.conf` configuration file. ''; }; }; config = mkIf cfg.enable { - services.jicofo.config = mapAttrs (_: v: mkDefault v) { - "org.jitsi.jicofo.BRIDGE_MUC" = cfg.bridgeMuc; + services.jicofo.config = { + jicofo = { + bridge.brewery-jid = cfg.bridgeMuc; + xmpp = rec { + client = { + hostname = cfg.xmppHost; + username = cfg.userName; + domain = cfg.userDomain; + password = { __hocon_envvar = "JICOFO_AUTH_PASS"; }; + xmpp-domain = if cfg.xmppDomain == null then cfg.xmppHost else cfg.xmppDomain; + }; + service = client; + }; + }; }; users.groups.jitsi-meet = {}; @@ -93,6 +114,7 @@ in "-Dnet.java.sip.communicator.SC_HOME_DIR_LOCATION" = "/etc/jitsi"; "-Dnet.java.sip.communicator.SC_HOME_DIR_NAME" = "jicofo"; "-Djava.util.logging.config.file" = "/etc/jitsi/jicofo/logging.properties"; + "-Dconfig.file" = configFile; }; in { @@ -101,18 +123,13 @@ in after = [ "network.target" ]; restartTriggers = [ - config.environment.etc."jitsi/jicofo/sip-communicator.properties".source + configFile ]; environment.JAVA_SYS_PROPS = concatStringsSep " " (mapAttrsToList (k: v: "${k}=${toString v}") jicofoProps); script = '' - ${pkgs.jicofo}/bin/jicofo \ - --host=${cfg.xmppHost} \ - --domain=${if cfg.xmppDomain == null then cfg.xmppHost else cfg.xmppDomain} \ - --secret=$(cat ${cfg.componentPasswordFile}) \ - --user_name=${cfg.userName} \ - --user_domain=${cfg.userDomain} \ - --user_password=$(cat ${cfg.userPasswordFile}) + export JICOFO_AUTH_PASS="$(<${cfg.userPasswordFile})" + exec "${pkgs.jicofo}/bin/jicofo" ''; serviceConfig = { @@ -140,10 +157,7 @@ in }; }; - environment.etc."jitsi/jicofo/sip-communicator.properties".source = - pkgs.writeText "sip-communicator.properties" ( - generators.toKeyValue {} cfg.config - ); + environment.etc."jitsi/jicofo/sip-communicator.properties".text = ""; environment.etc."jitsi/jicofo/logging.properties".source = mkDefault "${pkgs.jicofo}/etc/jitsi/jicofo/logging.properties-journal"; }; diff --git a/nixos/modules/services/networking/multipath.nix b/nixos/modules/services/networking/multipath.nix index b20ec76ddf594..bd403e109c2af 100644 --- a/nixos/modules/services/networking/multipath.nix +++ b/nixos/modules/services/networking/multipath.nix @@ -513,22 +513,22 @@ in { ${indentLines 2 devices} } - ${optionalString (!isNull defaults) '' + ${optionalString (defaults != null) '' defaults { ${indentLines 2 defaults} } ''} - ${optionalString (!isNull blacklist) '' + ${optionalString (blacklist != null) '' blacklist { ${indentLines 2 blacklist} } ''} - ${optionalString (!isNull blacklist_exceptions) '' + ${optionalString (blacklist_exceptions != null) '' blacklist_exceptions { ${indentLines 2 blacklist_exceptions} } ''} - ${optionalString (!isNull overrides) '' + ${optionalString (overrides != null) '' overrides { ${indentLines 2 overrides} } diff --git a/nixos/modules/services/networking/peroxide.nix b/nixos/modules/services/networking/peroxide.nix new file mode 100644 index 0000000000000..6cac4bf2f89a1 --- /dev/null +++ b/nixos/modules/services/networking/peroxide.nix @@ -0,0 +1,131 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.peroxide; + settingsFormat = pkgs.formats.yaml { }; + stateDir = "peroxide"; +in +{ + options.services.peroxide = { + enable = mkEnableOption (lib.mdDoc "enable"); + + package = mkPackageOptionMD pkgs "peroxide" { + default = [ "peroxide" ]; + }; + + logLevel = mkOption { + # https://github.com/sirupsen/logrus#level-logging + type = types.enum [ "Panic" "Fatal" "Error" "Warning" "Info" "Debug" "Trace" ]; + default = "Warning"; + example = "Info"; + description = lib.mdDoc "Only log messages of this priority or higher."; + }; + + settings = mkOption { + type = types.submodule { + freeformType = settingsFormat.type; + + options = { + UserPortImap = mkOption { + type = types.port; + default = 1143; + description = lib.mdDoc "The port on which to listen for IMAP connections."; + }; + + UserPortSmtp = mkOption { + type = types.port; + default = 1025; + description = lib.mdDoc "The port on which to listen for SMTP connections."; + }; + + ServerAddress = mkOption { + type = types.str; + default = "[::0]"; + example = "localhost"; + description = lib.mdDoc "The address on which to listen for connections."; + }; + }; + }; + default = { }; + description = lib.mdDoc '' + Configuration for peroxide. See + [config.example.yaml](https://github.com/ljanyst/peroxide/blob/master/config.example.yaml) + for an example configuration. + ''; + }; + }; + + config = mkIf cfg.enable { + services.peroxide.settings = { + # peroxide deletes the cache directory on startup, which requires write + # permission on the parent directory, so we can't use + # /var/cache/peroxide + CacheDir = "/var/cache/peroxide/cache"; + X509Key = mkDefault "/var/lib/${stateDir}/key.pem"; + X509Cert = mkDefault "/var/lib/${stateDir}/cert.pem"; + CookieJar = "/var/lib/${stateDir}/cookies.json"; + CredentialsStore = "/var/lib/${stateDir}/credentials.json"; + }; + + users.users.peroxide = { + isSystemUser = true; + group = "peroxide"; + }; + users.groups.peroxide = { }; + + systemd.services.peroxide = { + description = "Peroxide ProtonMail bridge"; + requires = [ "network.target" ]; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + restartTriggers = [ config.environment.etc."peroxide.conf".source ]; + + serviceConfig = { + Type = "simple"; + User = "peroxide"; + LogsDirectory = "peroxide"; + LogsDirectoryMode = "0750"; + # Specify just "peroxide" so that the user has write permission, because + # peroxide deletes and recreates the cache directory on startup. + CacheDirectory = [ "peroxide" "peroxide/cache" ]; + CacheDirectoryMode = "0700"; + StateDirectory = stateDir; + StateDirectoryMode = "0700"; + ExecStart = "${cfg.package}/bin/peroxide -log-file=/var/log/peroxide/peroxide.log -log-level ${cfg.logLevel}"; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + }; + + preStart = '' + # Create a self-signed certificate if no certificate exists. + if [[ ! -e "${cfg.settings.X509Key}" && ! -e "${cfg.settings.X509Cert}" ]]; then + ${cfg.package}/bin/peroxide-cfg -action gen-x509 \ + -x509-org 'N/A' \ + -x509-cn 'nixos' \ + -x509-cert "${cfg.settings.X509Cert}" \ + -x509-key "${cfg.settings.X509Key}" + fi + ''; + }; + + # https://github.com/ljanyst/peroxide/blob/master/peroxide.logrotate + services.logrotate.settings.peroxide = { + files = "/var/log/peroxide/peroxide.log"; + rotate = 31; + frequency = "daily"; + compress = true; + delaycompress = true; + missingok = true; + notifempty = true; + su = "peroxide peroxide"; + postrotate = "systemctl reload peroxide"; + }; + + environment.etc."peroxide.conf".source = settingsFormat.generate "peroxide.conf" cfg.settings; + environment.systemPackages = [ cfg.package ]; + }; + + meta.maintainers = with maintainers; [ aanderse aidalgol ]; +} diff --git a/nixos/modules/services/networking/radicale.nix b/nixos/modules/services/networking/radicale.nix index 8e4789c7ca597..00dbd6bbe386d 100644 --- a/nixos/modules/services/networking/radicale.nix +++ b/nixos/modules/services/networking/radicale.nix @@ -9,7 +9,7 @@ let listToValue = concatMapStringsSep ", " (generators.mkValueStringDefault { }); }; - pkg = if isNull cfg.package then + pkg = if cfg.package == null then pkgs.radicale else cfg.package; @@ -117,13 +117,13 @@ in { } ]; - warnings = optional (isNull cfg.package && versionOlder config.system.stateVersion "17.09") '' + warnings = optional (cfg.package == null && versionOlder config.system.stateVersion "17.09") '' The configuration and storage formats of your existing Radicale installation might be incompatible with the newest version. For upgrade instructions see https://radicale.org/2.1.html#documentation/migration-from-1xx-to-2xx. Set services.radicale.package to suppress this warning. - '' ++ optional (isNull cfg.package && versionOlder config.system.stateVersion "20.09") '' + '' ++ optional (cfg.package == null && versionOlder config.system.stateVersion "20.09") '' The configuration format of your existing Radicale installation might be incompatible with the newest version. For upgrade instructions see https://github.com/Kozea/Radicale/blob/3.0.6/NEWS.md#upgrade-checklist. diff --git a/nixos/modules/services/networking/ssh/sshd.nix b/nixos/modules/services/networking/ssh/sshd.nix index 095c7de0b7aa1..5f225682b7779 100644 --- a/nixos/modules/services/networking/ssh/sshd.nix +++ b/nixos/modules/services/networking/ssh/sshd.nix @@ -19,7 +19,7 @@ let else if true == v then "yes" else if false == v then "no" else if isList v then concatStringsSep "," v - else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}"; + else throw "unsupported type ${builtins.typeOf v}: ${(lib.generators.toPretty {}) v}"; # dont use the "=" operator settingsFormat = (pkgs.formats.keyValue { diff --git a/nixos/modules/services/networking/yggdrasil.nix b/nixos/modules/services/networking/yggdrasil.nix index fd7193154c6c7..55a6002d61af1 100644 --- a/nixos/modules/services/networking/yggdrasil.nix +++ b/nixos/modules/services/networking/yggdrasil.nix @@ -8,7 +8,8 @@ let configFileProvided = cfg.configFile != null; format = pkgs.formats.json { }; -in { +in +{ imports = [ (mkRenamedOptionModule [ "services" "yggdrasil" "config" ] @@ -21,7 +22,7 @@ in { settings = mkOption { type = format.type; - default = {}; + default = { }; example = { Peers = [ "tcp://aa.bb.cc.dd:eeeee" @@ -45,7 +46,7 @@ in { If no keys are specified then ephemeral keys are generated and the Yggdrasil interface will have a random IPv6 address - each time the service is started, this is the default. + each time the service is started. This is the default. If both {option}`configFile` and {option}`settings` are supplied, they will be combined, with values from @@ -61,8 +62,13 @@ in { default = null; example = "/run/keys/yggdrasil.conf"; description = lib.mdDoc '' - A file which contains JSON configuration for yggdrasil. - See the {option}`settings` option for more information. + A file which contains JSON or HJSON configuration for yggdrasil. See + the {option}`settings` option for more information. + + Note: This file must not be larger than 1 MB because it is passed to + the yggdrasil process via systemd‘s LoadCredential mechanism. For + details, see <https://systemd.io/CREDENTIALS/> and `man 5 + systemd.exec`. ''; }; @@ -77,20 +83,20 @@ in { type = bool; default = false; description = lib.mdDoc '' - Whether to open the UDP port used for multicast peer - discovery. The NixOS firewall blocks link-local - communication, so in order to make local peering work you - will also need to set `LinkLocalTCPPort` in your - yggdrasil configuration ({option}`settings` or - {option}`configFile`) to a port number other than 0, - and then add that port to - {option}`networking.firewall.allowedTCPPorts`. + Whether to open the UDP port used for multicast peer discovery. The + NixOS firewall blocks link-local communication, so in order to make + incoming local peering work you will also need to configure + `MulticastInterfaces` in your Yggdrasil configuration + ({option}`settings` or {option}`configFile`). You will then have to + add the ports that you configure there to your firewall configuration + ({option}`networking.firewall.allowedTCPPorts` or + {option}`networking.firewall.interfaces.<name>.allowedTCPPorts`). ''; }; denyDhcpcdInterfaces = mkOption { type = listOf str; - default = []; + default = [ ]; example = [ "tap*" ]; description = lib.mdDoc '' Disable the DHCP client for any interface whose name matches @@ -118,80 +124,102 @@ in { }; }; - config = mkIf cfg.enable (let binYggdrasil = cfg.package + "/bin/yggdrasil"; - in { - assertions = [{ - assertion = config.networking.enableIPv6; - message = "networking.enableIPv6 must be true for yggdrasil to work"; - }]; - - system.activationScripts.yggdrasil = mkIf cfg.persistentKeys '' - if [ ! -e ${keysPath} ] - then - mkdir --mode=700 -p ${builtins.dirOf keysPath} - ${binYggdrasil} -genconf -json \ - | ${pkgs.jq}/bin/jq \ - 'to_entries|map(select(.key|endswith("Key")))|from_entries' \ - > ${keysPath} - fi - ''; - - systemd.services.yggdrasil = { - description = "Yggdrasil Network Service"; - after = [ "network-pre.target" ]; - wants = [ "network.target" ]; - before = [ "network.target" ]; - wantedBy = [ "multi-user.target" ]; - - preStart = - (if settingsProvided || configFileProvided || cfg.persistentKeys then - "echo " - - + (lib.optionalString settingsProvided - "'${builtins.toJSON cfg.settings}'") - + (lib.optionalString configFileProvided "$(cat ${cfg.configFile})") - + (lib.optionalString cfg.persistentKeys "$(cat ${keysPath})") - + " | ${pkgs.jq}/bin/jq -s add | ${binYggdrasil} -normaliseconf -useconf" - else - "${binYggdrasil} -genconf") + " > /run/yggdrasil/yggdrasil.conf"; - - serviceConfig = { - ExecStart = - "${binYggdrasil} -useconffile /run/yggdrasil/yggdrasil.conf"; - ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; - Restart = "always"; - - DynamicUser = true; - StateDirectory = "yggdrasil"; - RuntimeDirectory = "yggdrasil"; - RuntimeDirectoryMode = "0750"; - BindReadOnlyPaths = lib.optional configFileProvided cfg.configFile - ++ lib.optional cfg.persistentKeys keysPath; - ReadWritePaths = "/run/yggdrasil"; - - AmbientCapabilities = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE"; - CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE"; - MemoryDenyWriteExecute = true; - ProtectControlGroups = true; - ProtectHome = "tmpfs"; - ProtectKernelModules = true; - ProtectKernelTunables = true; - RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK"; - RestrictNamespaces = true; - RestrictRealtime = true; - SystemCallArchitectures = "native"; - SystemCallFilter = [ "@system-service" "~@privileged @keyring" ]; - } // (if (cfg.group != null) then { - Group = cfg.group; - } else {}); - }; + config = mkIf cfg.enable ( + let + binYggdrasil = "${cfg.package}/bin/yggdrasil"; + binHjson = "${pkgs.hjson-go}/bin/hjson-cli"; + in + { + assertions = [{ + assertion = config.networking.enableIPv6; + message = "networking.enableIPv6 must be true for yggdrasil to work"; + }]; + + system.activationScripts.yggdrasil = mkIf cfg.persistentKeys '' + if [ ! -e ${keysPath} ] + then + mkdir --mode=700 -p ${builtins.dirOf keysPath} + ${binYggdrasil} -genconf -json \ + | ${pkgs.jq}/bin/jq \ + 'to_entries|map(select(.key|endswith("Key")))|from_entries' \ + > ${keysPath} + fi + ''; + + systemd.services.yggdrasil = { + description = "Yggdrasil Network Service"; + after = [ "network-pre.target" ]; + wants = [ "network.target" ]; + before = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + # This script first prepares the config file, then it starts Yggdrasil. + # The preparation could also be done in ExecStartPre/preStart but only + # systemd versions >= v252 support reading credentials in ExecStartPre. As + # of February 2023, systemd v252 is not yet in the stable branch of NixOS. + # + # This could be changed in the future once systemd version v252 has + # reached NixOS but it does not have to be. Config file preparation is + # fast enough, it does not need elevated privileges, and `set -euo + # pipefail` should make sure that the service is not started if the + # preparation fails. Therefore, it is not necessary to move the + # preparation to ExecStartPre. + script = '' + set -euo pipefail + + # prepare config file + ${(if settingsProvided || configFileProvided || cfg.persistentKeys then + "echo " + + + (lib.optionalString settingsProvided + "'${builtins.toJSON cfg.settings}'") + + (lib.optionalString configFileProvided + "$(${binHjson} -c \"$CREDENTIALS_DIRECTORY/yggdrasil.conf\")") + + (lib.optionalString cfg.persistentKeys "$(cat ${keysPath})") + + " | ${pkgs.jq}/bin/jq -s add | ${binYggdrasil} -normaliseconf -useconf" + else + "${binYggdrasil} -genconf") + " > /run/yggdrasil/yggdrasil.conf"} + + # start yggdrasil + ${binYggdrasil} -useconffile /run/yggdrasil/yggdrasil.conf + ''; + + serviceConfig = { + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + Restart = "always"; + + DynamicUser = true; + StateDirectory = "yggdrasil"; + RuntimeDirectory = "yggdrasil"; + RuntimeDirectoryMode = "0750"; + BindReadOnlyPaths = lib.optional cfg.persistentKeys keysPath; + LoadCredential = + mkIf configFileProvided "yggdrasil.conf:${cfg.configFile}"; + + AmbientCapabilities = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE"; + CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE"; + MemoryDenyWriteExecute = true; + ProtectControlGroups = true; + ProtectHome = "tmpfs"; + ProtectKernelModules = true; + ProtectKernelTunables = true; + RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK"; + RestrictNamespaces = true; + RestrictRealtime = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ "@system-service" "~@privileged @keyring" ]; + } // (if (cfg.group != null) then { + Group = cfg.group; + } else { }); + }; - networking.dhcpcd.denyInterfaces = cfg.denyDhcpcdInterfaces; - networking.firewall.allowedUDPPorts = mkIf cfg.openMulticastPort [ 9001 ]; + networking.dhcpcd.denyInterfaces = cfg.denyDhcpcdInterfaces; + networking.firewall.allowedUDPPorts = mkIf cfg.openMulticastPort [ 9001 ]; - # Make yggdrasilctl available on the command line. - environment.systemPackages = [ cfg.package ]; - }); + # Make yggdrasilctl available on the command line. + environment.systemPackages = [ cfg.package ]; + } + ); meta = { doc = ./yggdrasil.md; maintainers = with lib.maintainers; [ gazally ehmry ]; diff --git a/nixos/modules/services/search/solr.nix b/nixos/modules/services/search/solr.nix deleted file mode 100644 index 05592e9fa247d..0000000000000 --- a/nixos/modules/services/search/solr.nix +++ /dev/null @@ -1,110 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; - -let - - cfg = config.services.solr; - -in - -{ - options = { - services.solr = { - enable = mkEnableOption (lib.mdDoc "Solr"); - - package = mkOption { - type = types.package; - default = pkgs.solr; - defaultText = literalExpression "pkgs.solr"; - description = lib.mdDoc "Which Solr package to use."; - }; - - port = mkOption { - type = types.port; - default = 8983; - description = lib.mdDoc "Port on which Solr is ran."; - }; - - stateDir = mkOption { - type = types.path; - default = "/var/lib/solr"; - description = lib.mdDoc "The solr home directory containing config, data, and logging files."; - }; - - extraJavaOptions = mkOption { - type = types.listOf types.str; - default = []; - description = lib.mdDoc "Extra command line options given to the java process running Solr."; - }; - - user = mkOption { - type = types.str; - default = "solr"; - description = lib.mdDoc "User under which Solr is ran."; - }; - - group = mkOption { - type = types.str; - default = "solr"; - description = lib.mdDoc "Group under which Solr is ran."; - }; - }; - }; - - config = mkIf cfg.enable { - - environment.systemPackages = [ cfg.package ]; - - systemd.services.solr = { - after = [ "network.target" "remote-fs.target" "nss-lookup.target" "systemd-journald-dev-log.socket" ]; - wantedBy = [ "multi-user.target" ]; - - environment = { - SOLR_HOME = "${cfg.stateDir}/data"; - LOG4J_PROPS = "${cfg.stateDir}/log4j2.xml"; - SOLR_LOGS_DIR = "${cfg.stateDir}/logs"; - SOLR_PORT = "${toString cfg.port}"; - }; - path = with pkgs; [ - gawk - procps - ]; - preStart = '' - mkdir -p "${cfg.stateDir}/data"; - mkdir -p "${cfg.stateDir}/logs"; - - if ! test -e "${cfg.stateDir}/data/solr.xml"; then - install -D -m0640 ${cfg.package}/server/solr/solr.xml "${cfg.stateDir}/data/solr.xml" - install -D -m0640 ${cfg.package}/server/solr/zoo.cfg "${cfg.stateDir}/data/zoo.cfg" - fi - - if ! test -e "${cfg.stateDir}/log4j2.xml"; then - install -D -m0640 ${cfg.package}/server/resources/log4j2.xml "${cfg.stateDir}/log4j2.xml" - fi - ''; - - serviceConfig = { - User = cfg.user; - Group = cfg.group; - ExecStart="${cfg.package}/bin/solr start -f -a \"${concatStringsSep " " cfg.extraJavaOptions}\""; - ExecStop="${cfg.package}/bin/solr stop"; - }; - }; - - users.users = optionalAttrs (cfg.user == "solr") { - solr = { - group = cfg.group; - home = cfg.stateDir; - createHome = true; - uid = config.ids.uids.solr; - }; - }; - - users.groups = optionalAttrs (cfg.group == "solr") { - solr.gid = config.ids.gids.solr; - }; - - }; - -} diff --git a/nixos/modules/services/security/authelia.nix b/nixos/modules/services/security/authelia.nix new file mode 100644 index 0000000000000..143c441c7e153 --- /dev/null +++ b/nixos/modules/services/security/authelia.nix @@ -0,0 +1,401 @@ +{ lib +, pkgs +, config +, ... +}: + +let + cfg = config.services.authelia; + + format = pkgs.formats.yaml { }; + configFile = format.generate "config.yml" cfg.settings; + + autheliaOpts = with lib; { name, ... }: { + options = { + enable = mkEnableOption (mdDoc "Authelia instance"); + + name = mkOption { + type = types.str; + default = name; + description = mdDoc '' + Name is used as a suffix for the service name, user, and group. + By default it takes the value you use for `<instance>` in: + {option}`services.authelia.<instance>` + ''; + }; + + package = mkOption { + default = pkgs.authelia; + type = types.package; + defaultText = literalExpression "pkgs.authelia"; + description = mdDoc "Authelia derivation to use."; + }; + + user = mkOption { + default = "authelia-${name}"; + type = types.str; + description = mdDoc "The name of the user for this authelia instance."; + }; + + group = mkOption { + default = "authelia-${name}"; + type = types.str; + description = mdDoc "The name of the group for this authelia instance."; + }; + + secrets = mkOption { + description = mdDoc '' + It is recommended you keep your secrets separate from the configuration. + It's especially important to keep the raw secrets out of your nix configuration, + as the values will be preserved in your nix store. + This attribute allows you to configure the location of secret files to be loaded at runtime. + + https://www.authelia.com/configuration/methods/secrets/ + ''; + default = { }; + type = types.submodule { + options = { + manual = mkOption { + default = false; + example = true; + description = mdDoc '' + Configuring authelia's secret files via the secrets attribute set + is intended to be convenient and help catch cases where values are required + to run at all. + If a user wants to set these values themselves and bypass the validation they can set this value to true. + ''; + type = types.bool; + }; + + # required + jwtSecretFile = mkOption { + type = types.nullOr types.path; + default = null; + description = mdDoc '' + Path to your JWT secret used during identity verificaiton. + ''; + }; + + oidcIssuerPrivateKeyFile = mkOption { + type = types.nullOr types.path; + default = null; + description = mdDoc '' + Path to your private key file used to encrypt OIDC JWTs. + ''; + }; + + oidcHmacSecretFile = mkOption { + type = types.nullOr types.path; + default = null; + description = mdDoc '' + Path to your HMAC secret used to sign OIDC JWTs. + ''; + }; + + sessionSecretFile = mkOption { + type = types.nullOr types.path; + default = null; + description = mdDoc '' + Path to your session secret. Only used when redis is used as session storage. + ''; + }; + + # required + storageEncryptionKeyFile = mkOption { + type = types.nullOr types.path; + default = null; + description = mdDoc '' + Path to your storage encryption key. + ''; + }; + }; + }; + }; + + environmentVariables = mkOption { + type = types.attrsOf types.str; + description = mdDoc '' + Additional environment variables to provide to authelia. + If you are providing secrets please consider the options under {option}`services.authelia.<instance>.secrets` + or make sure you use the `_FILE` suffix. + If you provide the raw secret rather than the location of a secret file that secret will be preserved in the nix store. + For more details: https://www.authelia.com/configuration/methods/secrets/ + ''; + default = { }; + }; + + settings = mkOption { + description = mdDoc '' + Your Authelia config.yml as a Nix attribute set. + There are several values that are defined and documented in nix such as `default_2fa_method`, + but additional items can also be included. + + https://github.com/authelia/authelia/blob/master/config.template.yml + ''; + default = { }; + example = '' + { + theme = "light"; + default_2fa_method = "totp"; + log.level = "debug"; + server.disable_healthcheck = true; + } + ''; + type = types.submodule { + freeformType = format.type; + options = { + theme = mkOption { + type = types.enum [ "light" "dark" "grey" "auto" ]; + default = "light"; + example = "dark"; + description = mdDoc "The theme to display."; + }; + + default_2fa_method = mkOption { + type = types.enum [ "" "totp" "webauthn" "mobile_push" ]; + default = ""; + example = "webauthn"; + description = mdDoc '' + Default 2FA method for new users and fallback for preferred but disabled methods. + ''; + }; + + server = { + host = mkOption { + type = types.str; + default = "localhost"; + example = "0.0.0.0"; + description = mdDoc "The address to listen on."; + }; + + port = mkOption { + type = types.port; + default = 9091; + description = mdDoc "The port to listen on."; + }; + }; + + log = { + level = mkOption { + type = types.enum [ "info" "debug" "trace" ]; + default = "debug"; + example = "info"; + description = mdDoc "Level of verbosity for logs: info, debug, trace."; + }; + + format = mkOption { + type = types.enum [ "json" "text" ]; + default = "json"; + example = "text"; + description = mdDoc "Format the logs are written as."; + }; + + file_path = mkOption { + type = types.nullOr types.path; + default = null; + example = "/var/log/authelia/authelia.log"; + description = mdDoc "File path where the logs will be written. If not set logs are written to stdout."; + }; + + keep_stdout = mkOption { + type = types.bool; + default = false; + example = true; + description = mdDoc "Whether to also log to stdout when a `file_path` is defined."; + }; + }; + + telemetry = { + metrics = { + enabled = mkOption { + type = types.bool; + default = false; + example = true; + description = mdDoc "Enable Metrics."; + }; + + address = mkOption { + type = types.str; + default = "tcp://127.0.0.1:9959"; + example = "tcp://0.0.0.0:8888"; + description = mdDoc "The address to listen on for metrics. This should be on a different port to the main `server.port` value."; + }; + }; + }; + }; + }; + }; + + settingsFiles = mkOption { + type = types.listOf types.path; + default = [ ]; + example = [ "/etc/authelia/config.yml" "/etc/authelia/access-control.yml" "/etc/authelia/config/" ]; + description = mdDoc '' + Here you can provide authelia with configuration files or directories. + It is possible to give authelia multiple files and use the nix generated configuration + file set via {option}`services.authelia.<instance>.settings`. + ''; + }; + }; + }; +in +{ + options.services.authelia.instances = with lib; mkOption { + default = { }; + type = types.attrsOf (types.submodule autheliaOpts); + description = mdDoc '' + Multi-domain protection currently requires multiple instances of Authelia. + If you don't require multiple instances of Authelia you can define just the one. + + https://www.authelia.com/roadmap/active/multi-domain-protection/ + ''; + example = '' + { + main = { + enable = true; + secrets.storageEncryptionKeyFile = "/etc/authelia/storageEncryptionKeyFile"; + secrets.jwtSecretFile = "/etc/authelia/jwtSecretFile"; + settings = { + theme = "light"; + default_2fa_method = "totp"; + log.level = "debug"; + server.disable_healthcheck = true; + }; + }; + preprod = { + enable = false; + secrets.storageEncryptionKeyFile = "/mnt/pre-prod/authelia/storageEncryptionKeyFile"; + secrets.jwtSecretFile = "/mnt/pre-prod/jwtSecretFile"; + settings = { + theme = "dark"; + default_2fa_method = "webauthn"; + server.host = "0.0.0.0"; + }; + }; + test.enable = true; + test.secrets.manual = true; + test.settings.theme = "grey"; + test.settings.server.disable_healthcheck = true; + test.settingsFiles = [ "/mnt/test/authelia" "/mnt/test-authelia.conf" ]; + }; + } + ''; + }; + + config = + let + mkInstanceServiceConfig = instance: + let + execCommand = "${instance.package}/bin/authelia"; + configFile = format.generate "config.yml" instance.settings; + configArg = "--config ${builtins.concatStringsSep "," (lib.concatLists [[configFile] instance.settingsFiles])}"; + in + { + description = "Authelia authentication and authorization server"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + environment = + (lib.filterAttrs (_: v: v != null) { + AUTHELIA_JWT_SECRET_FILE = instance.secrets.jwtSecretFile; + AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE = instance.secrets.storageEncryptionKeyFile; + AUTHELIA_SESSION_SECRET_FILE = instance.secrets.sessionSecretFile; + AUTHELIA_IDENTITY_PROVIDERS_OIDC_ISSUER_PRIVATE_KEY_FILE = instance.secrets.oidcIssuerPrivateKeyFile; + AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE = instance.secrets.oidcHmacSecretFile; + }) + // instance.environmentVariables; + + preStart = "${execCommand} ${configArg} validate-config"; + serviceConfig = { + User = instance.user; + Group = instance.group; + ExecStart = "${execCommand} ${configArg}"; + Restart = "always"; + RestartSec = "5s"; + StateDirectory = "authelia-${instance.name}"; + StateDirectoryMode = "0700"; + + # Security options: + AmbientCapabilities = ""; + CapabilityBoundingSet = ""; + DeviceAllow = ""; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + + PrivateTmp = true; + PrivateDevices = true; + PrivateUsers = true; + + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = "read-only"; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "noaccess"; + ProtectSystem = "strict"; + + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + + SystemCallArchitectures = "native"; + SystemCallErrorNumber = "EPERM"; + SystemCallFilter = [ + "@system-service" + "~@cpu-emulation" + "~@debug" + "~@keyring" + "~@memlock" + "~@obsolete" + "~@privileged" + "~@setuid" + ]; + }; + }; + mkInstanceUsersConfig = instance: { + groups."authelia-${instance.name}" = + lib.mkIf (instance.group == "authelia-${instance.name}") { + name = "authelia-${instance.name}"; + }; + users."authelia-${instance.name}" = + lib.mkIf (instance.user == "authelia-${instance.name}") { + name = "authelia-${instance.name}"; + isSystemUser = true; + group = instance.group; + }; + }; + instances = lib.attrValues cfg.instances; + in + { + assertions = lib.flatten (lib.flip lib.mapAttrsToList cfg.instances (name: instance: + [ + { + assertion = instance.secrets.manual || (instance.secrets.jwtSecretFile != null && instance.secrets.storageEncryptionKeyFile != null); + message = '' + Authelia requires a JWT Secret and a Storage Encryption Key to work. + Either set them like so: + services.authelia.${name}.secrets.jwtSecretFile = /my/path/to/jwtsecret; + services.authelia.${name}.secrets.storageEncryptionKeyFile = /my/path/to/encryptionkey; + Or set services.authelia.${name}.secrets.manual = true and provide them yourself via + environmentVariables or settingsFiles. + Do not include raw secrets in nix settings. + ''; + } + ] + )); + + systemd.services = lib.mkMerge + (map + (instance: lib.mkIf instance.enable { + "authelia-${instance.name}" = mkInstanceServiceConfig instance; + }) + instances); + users = lib.mkMerge + (map + (instance: lib.mkIf instance.enable (mkInstanceUsersConfig instance)) + instances); + }; +} diff --git a/nixos/modules/services/security/fail2ban.nix b/nixos/modules/services/security/fail2ban.nix index 3c4bcd1ac2659..ead24d1470717 100644 --- a/nixos/modules/services/security/fail2ban.nix +++ b/nixos/modules/services/security/fail2ban.nix @@ -273,26 +273,16 @@ in "fail2ban/filter.d".source = "${cfg.package}/etc/fail2ban/filter.d/*.conf"; }; + systemd.packages = [ cfg.package ]; systemd.services.fail2ban = { - description = "Fail2ban Intrusion Prevention System"; - wantedBy = [ "multi-user.target" ]; - after = [ "network.target" ]; partOf = optional config.networking.firewall.enable "firewall.service"; restartTriggers = [ fail2banConf jailConf pathsConf ]; path = [ cfg.package cfg.packageFirewall pkgs.iproute2 ] ++ cfg.extraPackages; - unitConfig.Documentation = "man:fail2ban(1)"; - serviceConfig = { - ExecStart = "${cfg.package}/bin/fail2ban-server -xf start"; - ExecStop = "${cfg.package}/bin/fail2ban-server stop"; - ExecReload = "${cfg.package}/bin/fail2ban-server reload"; - Type = "simple"; - Restart = "on-failure"; - PIDFile = "/run/fail2ban/fail2ban.pid"; # Capabilities CapabilityBoundingSet = [ "CAP_AUDIT_READ" "CAP_DAC_READ_SEARCH" "CAP_NET_ADMIN" "CAP_NET_RAW" ]; # Security diff --git a/nixos/modules/services/system/cachix-watch-store.nix b/nixos/modules/services/system/cachix-watch-store.nix index ec73c0bcdcfe5..85e9509bcc82d 100644 --- a/nixos/modules/services/system/cachix-watch-store.nix +++ b/nixos/modules/services/system/cachix-watch-store.nix @@ -25,7 +25,7 @@ in compressionLevel = mkOption { type = types.nullOr types.int; - description = lib.mdDoc "The compression level for XZ compression (between 0 and 9)"; + description = lib.mdDoc "The compression level for ZSTD compression (between 0 and 16)"; default = null; }; diff --git a/nixos/modules/services/system/self-deploy.nix b/nixos/modules/services/system/self-deploy.nix index 16a793a42253c..5f9ee06124cb4 100644 --- a/nixos/modules/services/system/self-deploy.nix +++ b/nixos/modules/services/system/self-deploy.nix @@ -132,7 +132,7 @@ in requires = lib.mkIf (!(isPathType cfg.repository)) [ "network-online.target" ]; - environment.GIT_SSH_COMMAND = lib.mkIf (!(isNull cfg.sshKeyFile)) + environment.GIT_SSH_COMMAND = lib.mkIf (cfg.sshKeyFile != null) "${pkgs.openssh}/bin/ssh -i ${lib.escapeShellArg cfg.sshKeyFile}"; restartIfChanged = false; diff --git a/nixos/modules/services/web-apps/baget.nix b/nixos/modules/services/web-apps/baget.nix deleted file mode 100644 index e4d5a1faddb2e..0000000000000 --- a/nixos/modules/services/web-apps/baget.nix +++ /dev/null @@ -1,170 +0,0 @@ -{ config, lib, pkgs, ... }: - -with lib; - -let - - cfg = config.services.baget; - - defaultConfig = { - "PackageDeletionBehavior" = "Unlist"; - "AllowPackageOverwrites" = false; - - "Database" = { - "Type" = "Sqlite"; - "ConnectionString" = "Data Source=baget.db"; - }; - - "Storage" = { - "Type" = "FileSystem"; - "Path" = ""; - }; - - "Search" = { - "Type" = "Database"; - }; - - "Mirror" = { - "Enabled" = false; - "PackageSource" = "https://api.nuget.org/v3/index.json"; - }; - - "Logging" = { - "IncludeScopes" = false; - "Debug" = { - "LogLevel" = { - "Default" = "Warning"; - }; - }; - "Console" = { - "LogLevel" = { - "Microsoft.Hosting.Lifetime" = "Information"; - "Default" = "Warning"; - }; - }; - }; - }; - - configAttrs = recursiveUpdate defaultConfig cfg.extraConfig; - - configFormat = pkgs.formats.json {}; - configFile = configFormat.generate "appsettings.json" configAttrs; - -in -{ - options.services.baget = { - enable = mkEnableOption (lib.mdDoc "BaGet NuGet-compatible server"); - - apiKeyFile = mkOption { - type = types.path; - example = "/root/baget.key"; - description = lib.mdDoc '' - Private API key for BaGet. - ''; - }; - - extraConfig = mkOption { - type = configFormat.type; - default = {}; - example = { - "Database" = { - "Type" = "PostgreSql"; - "ConnectionString" = "Server=/run/postgresql;Port=5432;"; - }; - }; - defaultText = literalExpression '' - { - "PackageDeletionBehavior" = "Unlist"; - "AllowPackageOverwrites" = false; - - "Database" = { - "Type" = "Sqlite"; - "ConnectionString" = "Data Source=baget.db"; - }; - - "Storage" = { - "Type" = "FileSystem"; - "Path" = ""; - }; - - "Search" = { - "Type" = "Database"; - }; - - "Mirror" = { - "Enabled" = false; - "PackageSource" = "https://api.nuget.org/v3/index.json"; - }; - - "Logging" = { - "IncludeScopes" = false; - "Debug" = { - "LogLevel" = { - "Default" = "Warning"; - }; - }; - "Console" = { - "LogLevel" = { - "Microsoft.Hosting.Lifetime" = "Information"; - "Default" = "Warning"; - }; - }; - }; - } - ''; - description = lib.mdDoc '' - Extra configuration options for BaGet. Refer to <https://loic-sharma.github.io/BaGet/configuration/> for details. - Default value is merged with values from here. - ''; - }; - }; - - # implementation - - config = mkIf cfg.enable { - - systemd.services.baget = { - description = "BaGet server"; - wantedBy = [ "multi-user.target" ]; - wants = [ "network-online.target" ]; - after = [ "network.target" "network-online.target" ]; - path = [ pkgs.jq ]; - serviceConfig = { - WorkingDirectory = "/var/lib/baget"; - DynamicUser = true; - StateDirectory = "baget"; - StateDirectoryMode = "0700"; - LoadCredential = "api_key:${cfg.apiKeyFile}"; - - CapabilityBoundingSet = ""; - NoNewPrivileges = true; - PrivateDevices = true; - PrivateTmp = true; - PrivateUsers = true; - PrivateMounts = true; - ProtectHome = true; - ProtectClock = true; - ProtectProc = "noaccess"; - ProcSubset = "pid"; - ProtectKernelLogs = true; - ProtectKernelModules = true; - ProtectKernelTunables = true; - ProtectControlGroups = true; - ProtectHostname = true; - RestrictSUIDSGID = true; - RestrictRealtime = true; - RestrictNamespaces = true; - LockPersonality = true; - RemoveIPC = true; - RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; - SystemCallFilter = [ "@system-service" "~@privileged" ]; - }; - script = '' - jq --slurpfile apiKeys <(jq -R . "$CREDENTIALS_DIRECTORY/api_key") '.ApiKey = $apiKeys[0]' ${configFile} > appsettings.json - ln -snf ${pkgs.baget}/lib/BaGet/wwwroot wwwroot - exec ${pkgs.baget}/bin/BaGet - ''; - }; - - }; -} diff --git a/nixos/modules/services/web-apps/dolibarr.nix b/nixos/modules/services/web-apps/dolibarr.nix index a9df391128ee5..453229c130c22 100644 --- a/nixos/modules/services/web-apps/dolibarr.nix +++ b/nixos/modules/services/web-apps/dolibarr.nix @@ -16,7 +16,7 @@ let if (any (str: k == str) secretKeys) then v else if isString v then "'${v}'" else if isBool v then boolToString v - else if isNull v then "null" + else if v == null then "null" else toString v ; in diff --git a/nixos/modules/services/web-apps/jitsi-meet.nix b/nixos/modules/services/web-apps/jitsi-meet.nix index 28be3a3702eb6..3825b03c24496 100644 --- a/nixos/modules/services/web-apps/jitsi-meet.nix +++ b/nixos/modules/services/web-apps/jitsi-meet.nix @@ -411,11 +411,14 @@ in componentPasswordFile = "/var/lib/jitsi-meet/jicofo-component-secret"; bridgeMuc = "jvbbrewery@internal.${cfg.hostName}"; config = mkMerge [{ - "org.jitsi.jicofo.ALWAYS_TRUST_MODE_ENABLED" = "true"; + jicofo.xmpp.service.disable-certificate-verification = true; + jicofo.xmpp.client.disable-certificate-verification = true; #} (lib.mkIf cfg.jibri.enable { } (lib.mkIf (config.services.jibri.enable || cfg.jibri.enable) { - "org.jitsi.jicofo.jibri.BREWERY" = "JibriBrewery@internal.${cfg.hostName}"; - "org.jitsi.jicofo.jibri.PENDING_TIMEOUT" = "90"; + jicofo.jibri = { + brewery-jid = "JibriBrewery@internal.${cfg.hostName}"; + pending-timeout = "90"; + }; })]; }; diff --git a/nixos/modules/services/web-apps/limesurvey.nix b/nixos/modules/services/web-apps/limesurvey.nix index dd51174c8b8e0..8e6b39cbdebcb 100644 --- a/nixos/modules/services/web-apps/limesurvey.nix +++ b/nixos/modules/services/web-apps/limesurvey.nix @@ -34,6 +34,24 @@ in options.services.limesurvey = { enable = mkEnableOption (lib.mdDoc "Limesurvey web application"); + encryptionKey = mkOption { + type = types.str; + default = "E17687FC77CEE247F0E22BB3ECF27FDE8BEC310A892347EC13013ABA11AA7EB5"; + description = lib.mdDoc '' + This is a 32-byte key used to encrypt variables in the database. + You _must_ change this from the default value. + ''; + }; + + encryptionNonce = mkOption { + type = types.str; + default = "1ACC8555619929DB91310BE848025A427B0F364A884FFA77"; + description = lib.mdDoc '' + This is a 24-byte nonce used to encrypt variables in the database. + You _must_ change this from the default value. + ''; + }; + database = { type = mkOption { type = types.enum [ "mysql" "pgsql" "odbc" "mssql" ]; @@ -42,6 +60,12 @@ in description = lib.mdDoc "Database engine to use."; }; + dbEngine = mkOption { + type = types.enum [ "MyISAM" "InnoDB" ]; + default = "InnoDB"; + description = lib.mdDoc "Database storage engine to use."; + }; + host = mkOption { type = types.str; default = "localhost"; @@ -180,6 +204,8 @@ in config = { tempdir = "${stateDir}/tmp"; uploaddir = "${stateDir}/upload"; + encryptionnonce = cfg.encryptionNonce; + encryptionsecretboxkey = cfg.encryptionKey; force_ssl = mkIf (cfg.virtualHost.addSSL || cfg.virtualHost.forceSSL || cfg.virtualHost.onlySSL) "on"; config.defaultlang = "en"; }; @@ -200,6 +226,8 @@ in services.phpfpm.pools.limesurvey = { inherit user group; + phpPackage = pkgs.php80; + phpEnv.DBENGINE = "${cfg.database.dbEngine}"; phpEnv.LIMESURVEY_CONFIG = "${limesurveyConfig}"; settings = { "listen.owner" = config.services.httpd.user; @@ -256,11 +284,12 @@ in wantedBy = [ "multi-user.target" ]; before = [ "phpfpm-limesurvey.service" ]; after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service"; + environment.DBENGINE = "${cfg.database.dbEngine}"; environment.LIMESURVEY_CONFIG = limesurveyConfig; script = '' # update or install the database as required - ${pkgs.php}/bin/php ${pkg}/share/limesurvey/application/commands/console.php updatedb || \ - ${pkgs.php}/bin/php ${pkg}/share/limesurvey/application/commands/console.php install admin password admin admin@example.com verbose + ${pkgs.php80}/bin/php ${pkg}/share/limesurvey/application/commands/console.php updatedb || \ + ${pkgs.php80}/bin/php ${pkg}/share/limesurvey/application/commands/console.php install admin password admin admin@example.com verbose ''; serviceConfig = { User = user; diff --git a/nixos/modules/services/web-apps/mattermost.nix b/nixos/modules/services/web-apps/mattermost.nix index 56a53198b3fbf..db5122e79f006 100644 --- a/nixos/modules/services/web-apps/mattermost.nix +++ b/nixos/modules/services/web-apps/mattermost.nix @@ -184,6 +184,22 @@ in .tar.gz files. ''; }; + environmentFile = mkOption { + type = types.nullOr types.path; + default = null; + description = lib.mdDoc '' + Environment file (see {manpage}`systemd.exec(5)` + "EnvironmentFile=" section for the syntax) which sets config options + for mattermost (see [the mattermost documentation](https://docs.mattermost.com/configure/configuration-settings.html#environment-variables)). + + Settings defined in the environment file will overwrite settings + set via nix or via the {option}`services.mattermost.extraConfig` + option. + + Useful for setting config options without their value ending up in the + (world-readable) nix store, e.g. for a database password. + ''; + }; localDatabaseCreate = mkOption { type = types.bool; @@ -321,6 +337,7 @@ in Restart = "always"; RestartSec = "10"; LimitNOFILE = "49152"; + EnvironmentFile = cfg.environmentFile; }; unitConfig.JoinsNamespaceOf = mkIf cfg.localDatabaseCreate "postgresql.service"; }; diff --git a/nixos/modules/services/web-apps/nextcloud.md b/nixos/modules/services/web-apps/nextcloud.md index 014807f3da23c..7ef3cca281f9e 100644 --- a/nixos/modules/services/web-apps/nextcloud.md +++ b/nixos/modules/services/web-apps/nextcloud.md @@ -5,7 +5,7 @@ self-hostable cloud platform. The server setup can be automated using [services.nextcloud](#opt-services.nextcloud.enable). A desktop client is packaged at `pkgs.nextcloud-client`. -The current default by NixOS is `nextcloud25` which is also the latest +The current default by NixOS is `nextcloud26` which is also the latest major version available. ## Basic usage {#module-services-nextcloud-basic-usage} diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix index c5e161c2516ad..76a0172747ffd 100644 --- a/nixos/modules/services/web-apps/nextcloud.nix +++ b/nixos/modules/services/web-apps/nextcloud.nix @@ -204,7 +204,7 @@ in { package = mkOption { type = types.package; description = lib.mdDoc "Which package to use for the Nextcloud instance."; - relatedPackages = [ "nextcloud24" "nextcloud25" ]; + relatedPackages = [ "nextcloud24" "nextcloud25" "nextcloud26" ]; }; phpPackage = mkOption { type = types.package; @@ -514,6 +514,27 @@ in { `http://hostname.domain/bucket` instead. ''; }; + sseCKeyFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/var/nextcloud-objectstore-s3-sse-c-key"; + description = lib.mdDoc '' + If provided this is the full path to a file that contains the key + to enable [server-side encryption with customer-provided keys][1] + (SSE-C). + + The file must contain a random 32-byte key encoded as a base64 + string, e.g. generated with the command + + ``` + openssl rand 32 | base64 + ``` + + Must be readable by user `nextcloud`. + + [1]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerSideEncryptionCustomerKeys.html + ''; + }; }; }; }; @@ -652,7 +673,7 @@ in { config = mkIf cfg.enable (mkMerge [ { warnings = let - latest = 25; + latest = 26; upgradeWarning = major: nixos: '' A legacy Nextcloud install (from before NixOS ${nixos}) may be installed. @@ -667,20 +688,6 @@ in { `services.nextcloud.package`. ''; - # FIXME(@Ma27) remove as soon as nextcloud properly supports - # mariadb >=10.6. - isUnsupportedMariadb = - # All currently supported Nextcloud versions are affected (https://github.com/nextcloud/server/issues/25436). - (versionOlder cfg.package.version "24") - # This module uses mysql - && (cfg.config.dbtype == "mysql") - # MySQL is managed via NixOS - && config.services.mysql.enable - # We're using MariaDB - && (getName config.services.mysql.package) == "mariadb-server" - # MariaDB is at least 10.6 and thus not supported - && (versionAtLeast (getVersion config.services.mysql.package) "10.6"); - in (optional (cfg.poolConfig != null) '' Using config.services.nextcloud.poolConfig is deprecated and will become unsupported in a future release. Please migrate your configuration to config.services.nextcloud.poolSettings. @@ -688,6 +695,7 @@ in { ++ (optional (versionOlder cfg.package.version "23") (upgradeWarning 22 "22.05")) ++ (optional (versionOlder cfg.package.version "24") (upgradeWarning 23 "22.05")) ++ (optional (versionOlder cfg.package.version "25") (upgradeWarning 24 "22.11")) + ++ (optional (versionOlder cfg.package.version "26") (upgradeWarning 25 "23.05")) ++ (optional cfg.enableBrokenCiphersForSSE '' You're using PHP's openssl extension built against OpenSSL 1.1 for Nextcloud. This is only necessary if you're using Nextcloud's server-side encryption. @@ -704,18 +712,7 @@ in { See <https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/encryption_configuration.html#disabling-encryption> on how to achieve this. For more context, here is the implementing pull request: https://github.com/NixOS/nixpkgs/pull/198470 - '') - ++ (optional isUnsupportedMariadb '' - You seem to be using MariaDB at an unsupported version (i.e. at least 10.6)! - Please note that this isn't supported officially by Nextcloud. You can either - - * Switch to `pkgs.mysql` - * Downgrade MariaDB to at least 10.5 - * Work around Nextcloud's problems by specifying `innodb_read_only_compressed=0` - - For further context, please read - https://help.nextcloud.com/t/update-to-next-cloud-21-0-2-has-get-an-error/117028/15 - ''); + ''); services.nextcloud.package = with pkgs; mkDefault ( @@ -726,12 +723,13 @@ in { `pkgs.nextcloud`. '' else if versionOlder stateVersion "22.11" then nextcloud24 - else nextcloud25 + else if versionOlder stateVersion "23.05" then nextcloud25 + else nextcloud26 ); services.nextcloud.phpPackage = - if versionOlder cfg.package.version "24" then pkgs.php80 - else pkgs.php81; + if versionOlder cfg.package.version "26" then pkgs.php81 + else pkgs.php82; } { assertions = [ @@ -773,6 +771,7 @@ in { 'use_ssl' => ${boolToString s3.useSsl}, ${optionalString (s3.region != null) "'region' => '${s3.region}',"} 'use_path_style' => ${boolToString s3.usePathStyle}, + ${optionalString (s3.sseCKeyFile != null) "'sse_c_key' => nix_read_secret('${s3.sseCKeyFile}'),"} ], ] ''; @@ -958,6 +957,9 @@ in { ''; serviceConfig.Type = "oneshot"; serviceConfig.User = "nextcloud"; + # On Nextcloud ≥ 26, it is not necessary to patch the database files to prevent + # an automatic creation of the database user. + environment.NC_setup_create_db_user = lib.mkIf (nextcloudGreaterOrEqualThan "26") "false"; }; nextcloud-cron = { after = [ "nextcloud-setup.service" ]; @@ -1009,14 +1011,6 @@ in { name = cfg.config.dbuser; ensurePermissions = { "${cfg.config.dbname}.*" = "ALL PRIVILEGES"; }; }]; - # FIXME(@Ma27) Nextcloud isn't compatible with mariadb 10.6, - # this is a workaround. - # See https://help.nextcloud.com/t/update-to-next-cloud-21-0-2-has-get-an-error/117028/22 - settings = mkIf (versionOlder cfg.package.version "24") { - mysqld = { - innodb_read_only_compressed = 0; - }; - }; initialScript = pkgs.writeText "mysql-init" '' CREATE USER '${cfg.config.dbname}'@'localhost' IDENTIFIED BY '${builtins.readFile( cfg.config.dbpassFile )}'; CREATE DATABASE IF NOT EXISTS ${cfg.config.dbname}; @@ -1118,7 +1112,7 @@ in { ${optionalString (cfg.nginx.recommendedHttpHeaders) '' add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; - add_header X-Robots-Tag none; + add_header X-Robots-Tag "noindex, nofollow"; add_header X-Download-Options noopen; add_header X-Permitted-Cross-Domain-Policies none; add_header X-Frame-Options sameorigin; diff --git a/nixos/modules/services/web-apps/writefreely.nix b/nixos/modules/services/web-apps/writefreely.nix index dec00b46f335e..a7671aa717f43 100644 --- a/nixos/modules/services/web-apps/writefreely.nix +++ b/nixos/modules/services/web-apps/writefreely.nix @@ -10,12 +10,11 @@ let format = pkgs.formats.ini { mkKeyValue = key: value: let - value' = if builtins.isNull value then - "" - else if builtins.isBool value then - if value == true then "true" else "false" - else - toString value; + value' = lib.optionalString (value != null) + (if builtins.isBool value then + if value == true then "true" else "false" + else + toString value); in "${key} = ${value'}"; }; diff --git a/nixos/modules/services/web-servers/garage.nix b/nixos/modules/services/web-servers/garage.nix index 2491c788d6c51..df8ed96f8d908 100644 --- a/nixos/modules/services/web-servers/garage.nix +++ b/nixos/modules/services/web-servers/garage.nix @@ -60,7 +60,7 @@ in package = mkOption { # TODO: when 23.05 is released and if Garage 0.9 is the default, put a stateVersion check. - default = if versionAtLeast stateVersion "23.05" then pkgs.garage_0_8_0 + default = if versionAtLeast config.system.stateVersion "23.05" then pkgs.garage_0_8 else pkgs.garage_0_7; defaultText = literalExpression "pkgs.garage_0_7"; type = types.package; diff --git a/nixos/modules/services/web-servers/minio.nix b/nixos/modules/services/web-servers/minio.nix index 1a9eacb431b3c..21bec4f63a879 100644 --- a/nixos/modules/services/web-servers/minio.nix +++ b/nixos/modules/services/web-servers/minio.nix @@ -60,7 +60,7 @@ in ''; }; - rootCredentialsFile = mkOption { + rootCredentialsFile = mkOption { type = types.nullOr types.path; default = null; description = lib.mdDoc '' @@ -96,29 +96,62 @@ in config = mkIf cfg.enable { warnings = optional ((cfg.accessKey != "") || (cfg.secretKey != "")) "services.minio.`accessKey` and services.minio.`secretKey` are deprecated, please use services.minio.`rootCredentialsFile` instead."; - systemd.tmpfiles.rules = [ - "d '${cfg.configDir}' - minio minio - -" - ] ++ (map (x: "d '" + x + "' - minio minio - - ") cfg.dataDir); - - systemd.services.minio = { - description = "Minio Object Storage"; - after = [ "network-online.target" ]; - wantedBy = [ "multi-user.target" ]; - serviceConfig = { - ExecStart = "${cfg.package}/bin/minio server --json --address ${cfg.listenAddress} --console-address ${cfg.consoleAddress} --config-dir=${cfg.configDir} ${toString cfg.dataDir}"; - Type = "simple"; - User = "minio"; - Group = "minio"; - LimitNOFILE = 65536; - EnvironmentFile = if (cfg.rootCredentialsFile != null) then cfg.rootCredentialsFile - else if ((cfg.accessKey != "") || (cfg.secretKey != "")) then (legacyCredentials cfg) - else null; + systemd = lib.mkMerge [{ + tmpfiles.rules = [ + "d '${cfg.configDir}' - minio minio - -" + ] ++ (map (x: "d '" + x + "' - minio minio - - ") cfg.dataDir); + + services.minio = { + description = "Minio Object Storage"; + after = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = "${cfg.package}/bin/minio server --json --address ${cfg.listenAddress} --console-address ${cfg.consoleAddress} --config-dir=${cfg.configDir} ${toString cfg.dataDir}"; + Type = "simple"; + User = "minio"; + Group = "minio"; + LimitNOFILE = 65536; + EnvironmentFile = + if (cfg.rootCredentialsFile != null) then cfg.rootCredentialsFile + else if ((cfg.accessKey != "") || (cfg.secretKey != "")) then (legacyCredentials cfg) + else null; + }; + environment = { + MINIO_REGION = "${cfg.region}"; + MINIO_BROWSER = "${if cfg.browser then "on" else "off"}"; + }; }; - environment = { - MINIO_REGION = "${cfg.region}"; - MINIO_BROWSER = "${if cfg.browser then "on" else "off"}"; - }; - }; + } + + (lib.mkIf (cfg.rootCredentialsFile != null) { + # The service will fail if the credentials file is missing + services.minio.unitConfig.ConditionPathExists = cfg.rootCredentialsFile; + + # The service will not restart if the credentials file has + # been changed. This can cause stale root credentials. + paths.minio-root-credentials = { + wantedBy = [ "multi-user.target" ]; + + pathConfig = { + PathChanged = [ cfg.rootCredentialsFile ]; + Unit = "minio-restart.service"; + }; + }; + + services.minio-restart = { + description = "Restart MinIO"; + + script = '' + systemctl restart minio.service + ''; + + serviceConfig = { + Type = "oneshot"; + Restart = "on-failure"; + RestartSec = 5; + }; + }; + })]; users.users.minio = { group = "minio"; diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix index 905dd5bef1f71..064c86a9a7e29 100644 --- a/nixos/modules/services/web-servers/nginx/default.nix +++ b/nixos/modules/services/web-servers/nginx/default.nix @@ -184,8 +184,8 @@ let brotli_types ${lib.concatStringsSep " " compressMimeTypes}; ''} - # https://docs.nginx.com/nginx/admin-guide/web-server/compression/ ${optionalString cfg.recommendedGzipSettings '' + # https://docs.nginx.com/nginx/admin-guide/web-server/compression/ gzip on; gzip_static on; gzip_vary on; @@ -195,6 +195,14 @@ let gzip_types ${lib.concatStringsSep " " compressMimeTypes}; ''} + ${optionalString cfg.recommendedZstdSettings '' + zstd on; + zstd_comp_level 9; + zstd_min_length 256; + zstd_static on; + zstd_types ${lib.concatStringsSep " " compressMimeTypes}; + ''} + ${optionalString cfg.recommendedProxySettings '' proxy_redirect off; proxy_connect_timeout ${cfg.proxyTimeout}; @@ -490,6 +498,16 @@ in ''; }; + recommendedZstdSettings = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc '' + Enable recommended zstd settings. Learn more about compression in Zstd format [here](https://github.com/tokers/zstd-nginx-module). + + This adds `pkgs.nginxModules.zstd` to `services.nginx.additionalModules`. + ''; + }; + proxyTimeout = mkOption { type = types.str; default = "60s"; @@ -1015,7 +1033,8 @@ in groups = config.users.groups; }) dependentCertNames; - services.nginx.additionalModules = optional cfg.recommendedBrotliSettings pkgs.nginxModules.brotli; + services.nginx.additionalModules = optional cfg.recommendedBrotliSettings pkgs.nginxModules.brotli + ++ lib.optional cfg.recommendedZstdSettings pkgs.nginxModules.zstd; systemd.services.nginx = { description = "Nginx Web Server"; diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix index f0c4b2172f9d2..73a864bb95fe8 100644 --- a/nixos/modules/services/x11/desktop-managers/plasma5.nix +++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix @@ -81,99 +81,90 @@ let in { - options.services.xserver.desktopManager.plasma5 = { - enable = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc "Enable the Plasma 5 (KDE 5) desktop environment."; - }; - - phononBackend = mkOption { - type = types.enum [ "gstreamer" "vlc" ]; - default = "vlc"; - example = "gstreamer"; - description = lib.mdDoc "Phonon audio backend to install."; - }; - - supportDDC = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc '' - Support setting monitor brightness via DDC. - - This is not needed for controlling brightness of the internal monitor - of a laptop and as it is considered experimental by upstream, it is - disabled by default. - ''; - }; + options = { + services.xserver.desktopManager.plasma5 = { + enable = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc "Enable the Plasma 5 (KDE 5) desktop environment."; + }; - useQtScaling = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc "Enable HiDPI scaling in Qt."; - }; + phononBackend = mkOption { + type = types.enum [ "gstreamer" "vlc" ]; + default = "vlc"; + example = "gstreamer"; + description = lib.mdDoc "Phonon audio backend to install."; + }; - runUsingSystemd = mkOption { - description = lib.mdDoc "Use systemd to manage the Plasma session"; - type = types.bool; - default = true; - }; + useQtScaling = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc "Enable HiDPI scaling in Qt."; + }; - excludePackages = mkOption { - description = lib.mdDoc "List of default packages to exclude from the configuration"; - type = types.listOf types.package; - default = []; - example = literalExpression "[ pkgs.plasma5Packages.oxygen ]"; - }; + runUsingSystemd = mkOption { + description = lib.mdDoc "Use systemd to manage the Plasma session"; + type = types.bool; + default = true; + }; - notoPackage = mkPackageOptionMD pkgs "Noto fonts" { - default = [ "noto-fonts" ]; - example = "noto-fonts-lgc-plus"; - }; + notoPackage = mkPackageOptionMD pkgs "Noto fonts" { + default = [ "noto-fonts" ]; + example = "noto-fonts-lgc-plus"; + }; - # Internally allows configuring kdeglobals globally - kdeglobals = mkOption { - internal = true; - default = {}; - type = kdeConfigurationType; - }; + # Internally allows configuring kdeglobals globally + kdeglobals = mkOption { + internal = true; + default = {}; + type = kdeConfigurationType; + }; - # Internally allows configuring kwin globally - kwinrc = mkOption { - internal = true; - default = {}; - type = kdeConfigurationType; - }; + # Internally allows configuring kwin globally + kwinrc = mkOption { + internal = true; + default = {}; + type = kdeConfigurationType; + }; - mobile.enable = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc '' - Enable support for running the Plasma Mobile shell. - ''; - }; + mobile.enable = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Enable support for running the Plasma Mobile shell. + ''; + }; - mobile.installRecommendedSoftware = mkOption { - type = types.bool; - default = true; - description = lib.mdDoc '' - Installs software recommended for use with Plasma Mobile, but which - is not strictly required for Plasma Mobile to run. - ''; - }; + mobile.installRecommendedSoftware = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc '' + Installs software recommended for use with Plasma Mobile, but which + is not strictly required for Plasma Mobile to run. + ''; + }; - bigscreen.enable = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc '' - Enable support for running the Plasma Bigscreen session. - ''; + bigscreen.enable = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Enable support for running the Plasma Bigscreen session. + ''; + }; }; + environment.plasma5.excludePackages = mkOption { + description = lib.mdDoc "List of default packages to exclude from the configuration"; + type = types.listOf types.package; + default = []; + example = literalExpression "[ pkgs.plasma5Packages.oxygen ]"; + }; }; imports = [ (mkRemovedOptionModule [ "services" "xserver" "desktopManager" "plasma5" "enableQt4Support" ] "Phonon no longer supports Qt 4.") + (mkRemovedOptionModule [ "services" "xserver" "desktopManager" "plasma5" "supportDDC" ] "DDC/CI is no longer supported upstream.") (mkRenamedOptionModule [ "services" "xserver" "desktopManager" "kde5" ] [ "services" "xserver" "desktopManager" "plasma5" ]) + (mkRenamedOptionModule [ "services" "xserver" "desktopManager" "plasma5" "excludePackages" ] [ "environment" "plasma5" "excludePackages" ]) ]; config = mkMerge [ @@ -201,12 +192,6 @@ in }; }; - # DDC support - boot.kernelModules = lib.optional cfg.supportDDC "i2c_dev"; - services.udev.extraRules = lib.optionalString cfg.supportDDC '' - KERNEL=="i2c-[0-9]*", TAG+="uaccess" - ''; - environment.systemPackages = with libsForQt5; with plasma5; with kdeGear; with kdeFrameworks; @@ -301,7 +286,7 @@ in ]; in requiredPackages - ++ utils.removePackagesByName optionalPackages cfg.excludePackages + ++ utils.removePackagesByName optionalPackages config.environment.plasma5.excludePackages # Phonon audio backend ++ lib.optional (cfg.phononBackend == "gstreamer") libsForQt5.phonon-backend-gstreamer @@ -455,7 +440,7 @@ in khelpcenter print-manager ]; - in requiredPackages ++ utils.removePackagesByName optionalPackages cfg.excludePackages; + in requiredPackages ++ utils.removePackagesByName optionalPackages config.environment.plasma5.excludePackages; systemd.user.services = { plasma-run-with-systemd = { diff --git a/nixos/modules/services/x11/display-managers/gdm.nix b/nixos/modules/services/x11/display-managers/gdm.nix index 1c3881bef2de2..f8f82bda3fa43 100644 --- a/nixos/modules/services/x11/display-managers/gdm.nix +++ b/nixos/modules/services/x11/display-managers/gdm.nix @@ -323,7 +323,7 @@ in account sufficient pam_unix.so - password requisite pam_unix.so nullok sha512 + password requisite pam_unix.so nullok yescrypt session optional pam_keyinit.so revoke session include login diff --git a/nixos/modules/services/x11/display-managers/lightdm.nix b/nixos/modules/services/x11/display-managers/lightdm.nix index 65f414705fc51..548d3c5bc46a5 100644 --- a/nixos/modules/services/x11/display-managers/lightdm.nix +++ b/nixos/modules/services/x11/display-managers/lightdm.nix @@ -302,7 +302,7 @@ in account sufficient pam_unix.so - password requisite pam_unix.so nullok sha512 + password requisite pam_unix.so nullok yescrypt session optional pam_keyinit.so revoke session include login diff --git a/nixos/modules/services/x11/gdk-pixbuf.nix b/nixos/modules/services/x11/gdk-pixbuf.nix index 2105224f92ff3..9c088e4cc4237 100644 --- a/nixos/modules/services/x11/gdk-pixbuf.nix +++ b/nixos/modules/services/x11/gdk-pixbuf.nix @@ -21,7 +21,7 @@ in # loaders.cache based on that and set the environment variable # GDK_PIXBUF_MODULE_FILE to point to it. config = lib.mkIf (cfg.modulePackages != []) { - environment.variables = { + environment.sessionVariables = { GDK_PIXBUF_MODULE_FILE = "${loadersCache}"; }; }; diff --git a/nixos/modules/services/x11/window-managers/qtile.nix b/nixos/modules/services/x11/window-managers/qtile.nix index fc27566d49ee6..cc24522970f3a 100644 --- a/nixos/modules/services/x11/window-managers/qtile.nix +++ b/nixos/modules/services/x11/window-managers/qtile.nix @@ -1,23 +1,63 @@ -{ config, lib, pkgs, ... }: +{ config, pkgs, lib, ... }: with lib; let cfg = config.services.xserver.windowManager.qtile; + pyEnv = pkgs.python3.withPackages (p: [ (cfg.package.unwrapped or cfg.package) ] ++ (cfg.extraPackages p)); in { options.services.xserver.windowManager.qtile = { enable = mkEnableOption (lib.mdDoc "qtile"); - package = mkPackageOptionMD pkgs "qtile" { }; + package = mkPackageOptionMD pkgs "qtile-unwrapped" { }; + + configFile = mkOption { + type = with types; nullOr path; + default = null; + example = literalExpression "./your_config.py"; + description = lib.mdDoc '' + Path to the qtile configuration file. + If null, $XDG_CONFIG_HOME/qtile/config.py will be used. + ''; + }; + + backend = mkOption { + type = types.enum [ "x11" "wayland" ]; + default = "x11"; + description = lib.mdDoc '' + Backend to use in qtile: + <option>x11</option> or <option>wayland</option>. + ''; + }; + + extraPackages = mkOption { + type = types.functionTo (types.listOf types.package); + default = _: []; + defaultText = literalExpression '' + python3Packages: with python3Packages; []; + ''; + description = lib.mdDoc '' + Extra Python packages available to Qtile. + An example would be to include `python3Packages.qtile-extras` + for additional unoffical widgets. + ''; + example = literalExpression '' + python3Packages: with python3Packages; [ + qtile-extras + ]; + ''; + }; }; config = mkIf cfg.enable { services.xserver.windowManager.session = [{ name = "qtile"; start = '' - ${cfg.package}/bin/qtile start & + ${pyEnv}/bin/qtile start -b ${cfg.backend} \ + ${optionalString (cfg.configFile != null) + "--config \"${cfg.configFile}\""} & waitPID=$! ''; }]; diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix index adb079c87a8b8..fcc18c9a26fd0 100644 --- a/nixos/modules/services/x11/xserver.nix +++ b/nixos/modules/services/x11/xserver.nix @@ -138,6 +138,26 @@ let concatMapStringsSep "\n" (line: prefix + line) (splitString "\n" str); indent = prefixStringLines " "; + + # 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.fontxfree86type1.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 * cfg.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 + ''; + }); in { @@ -576,6 +596,15 @@ in Whether to terminate X upon server reset. ''; }; + + upscaleDefaultCursor = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Upscale the default X cursor to be more visible on high-density displays. + Requires `config.services.xserver.dpi` to be set. + ''; + }; }; }; @@ -592,7 +621,8 @@ in || dmConf.sddm.enable || dmConf.xpra.enable || dmConf.sx.enable - || dmConf.startx.enable); + || dmConf.startx.enable + || config.services.greetd.enable); in mkIf (default) (mkDefault true); # so that the service won't be enabled when only startx is used @@ -626,6 +656,10 @@ in + "${toString (length primaryHeads)} heads set to primary: " + concatMapStringsSep ", " (x: x.output) primaryHeads; }) + { + assertion = cfg.upscaleDefaultCursor -> cfg.dpi != null; + message = "Specify `config.services.xserver.dpi` to upscale the default cursor."; + } ]; environment.etc = @@ -850,6 +884,10 @@ in ''; fonts.enableDefaultFonts = mkDefault true; + fonts.fonts = [ + (if cfg.upscaleDefaultCursor then fontcursormisc_hidpi else pkgs.xorg.fontcursormisc) + pkgs.xorg.fontmiscmisc + ]; }; |