diff options
Diffstat (limited to 'nixpkgs/nixos/modules/services')
143 files changed, 6632 insertions, 1020 deletions
diff --git a/nixpkgs/nixos/modules/services/audio/jmusicbot.nix b/nixpkgs/nixos/modules/services/audio/jmusicbot.nix new file mode 100644 index 000000000000..f573bd2ab8dd --- /dev/null +++ b/nixpkgs/nixos/modules/services/audio/jmusicbot.nix @@ -0,0 +1,41 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + cfg = config.services.jmusicbot; +in +{ + options = { + services.jmusicbot = { + enable = mkEnableOption "jmusicbot, a Discord music bot that's easy to set up and run yourself"; + + stateDir = mkOption { + type = types.path; + description = '' + The directory where config.txt and serversettings.json is saved. + If left as the default value this directory will automatically be created before JMusicBot starts, otherwise the sysadmin is responsible for ensuring the directory exists with appropriate ownership and permissions. + Untouched by the value of this option config.txt needs to be placed manually into this directory. + ''; + default = "/var/lib/jmusicbot/"; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.services.jmusicbot = { + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + description = "Discord music bot that's easy to set up and run yourself!"; + serviceConfig = mkMerge [{ + ExecStart = "${pkgs.jmusicbot}/bin/JMusicBot"; + WorkingDirectory = cfg.stateDir; + Restart = "always"; + RestartSec = 20; + DynamicUser = true; + } + (mkIf (cfg.stateDir == "/var/lib/jmusicbot") { StateDirectory = "jmusicbot"; })]; + }; + }; + + meta.maintainers = with maintainers; [ SuperSandro2000 ]; +} diff --git a/nixpkgs/nixos/modules/services/audio/mpd.nix b/nixpkgs/nixos/modules/services/audio/mpd.nix index 9f01e29dd0e9..eee6c5f423d5 100644 --- a/nixpkgs/nixos/modules/services/audio/mpd.nix +++ b/nixpkgs/nixos/modules/services/audio/mpd.nix @@ -213,7 +213,9 @@ in { description = "Music Player Daemon Socket"; wantedBy = [ "sockets.target" ]; listenStreams = [ - "${optionalString (cfg.network.listenAddress != "any") "${cfg.network.listenAddress}:"}${toString cfg.network.port}" + (if pkgs.lib.hasPrefix "/" cfg.network.listenAddress + then cfg.network.listenAddress + else "${optionalString (cfg.network.listenAddress != "any") "${cfg.network.listenAddress}:"}${toString cfg.network.port}") ]; socketConfig = { Backlog = 5; diff --git a/nixpkgs/nixos/modules/services/audio/snapserver.nix b/nixpkgs/nixos/modules/services/audio/snapserver.nix index a261b8760780..f96b5f3e1942 100644 --- a/nixpkgs/nixos/modules/services/audio/snapserver.nix +++ b/nixpkgs/nixos/modules/services/audio/snapserver.nix @@ -65,7 +65,7 @@ let in { imports = [ - (mkRenamedOptionModule [ "services" "snapserver" "controlPort"] [ "services" "snapserver" "tcp" "port" ]) + (mkRenamedOptionModule [ "services" "snapserver" "controlPort" ] [ "services" "snapserver" "tcp" "port" ]) ]; ###### interface @@ -200,12 +200,21 @@ in { location = mkOption { type = types.oneOf [ types.path types.str ]; description = '' - The location of the pipe, file, Librespot/Airplay/process binary, or a TCP address. - Use an empty string for alsa. + For type <literal>pipe</literal> or <literal>file</literal>, the path to the pipe or file. + For type <literal>librespot</literal>, <literal>airplay</literal> or <literal>process</literal>, the path to the corresponding binary. + For type <literal>tcp</literal>, the <literal>host:port</literal> address to connect to or listen on. + For type <literal>meta</literal>, a list of stream names in the form <literal>/one/two/...</literal>. Don't forget the leading slash. + For type <literal>alsa</literal>, use an empty string. + ''; + example = literalExample '' + "/path/to/pipe" + "/path/to/librespot" + "192.168.1.2:4444" + "/MyTCP/Spotify/MyPipe" ''; }; type = mkOption { - type = types.enum [ "pipe" "librespot" "airplay" "file" "process" "tcp" "alsa" "spotify" ]; + type = types.enum [ "pipe" "librespot" "airplay" "file" "process" "tcp" "alsa" "spotify" "meta" ]; default = "pipe"; description = '' The type of input stream. diff --git a/nixpkgs/nixos/modules/services/audio/spotifyd.nix b/nixpkgs/nixos/modules/services/audio/spotifyd.nix index a589153248fe..9279a03aed4e 100644 --- a/nixpkgs/nixos/modules/services/audio/spotifyd.nix +++ b/nixpkgs/nixos/modules/services/audio/spotifyd.nix @@ -27,6 +27,7 @@ in wantedBy = [ "multi-user.target" ]; after = [ "network-online.target" "sound.target" ]; description = "spotifyd, a Spotify playing daemon"; + environment.SHELL = "/bin/sh"; serviceConfig = { ExecStart = "${pkgs.spotifyd}/bin/spotifyd --no-daemon --cache-path /var/cache/spotifyd --config-path ${spotifydConf}"; Restart = "always"; diff --git a/nixpkgs/nixos/modules/services/backup/borgmatic.nix b/nixpkgs/nixos/modules/services/backup/borgmatic.nix new file mode 100644 index 000000000000..5e5c0bbeccca --- /dev/null +++ b/nixpkgs/nixos/modules/services/backup/borgmatic.nix @@ -0,0 +1,57 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.borgmatic; + cfgfile = pkgs.writeText "config.yaml" (builtins.toJSON cfg.settings); +in { + options.services.borgmatic = { + enable = mkEnableOption "borgmatic"; + + settings = mkOption { + description = '' + See https://torsion.org/borgmatic/docs/reference/configuration/ + ''; + type = types.submodule { + freeformType = with lib.types; attrsOf anything; + options.location = { + source_directories = mkOption { + type = types.listOf types.str; + description = '' + List of source directories to backup (required). Globs and + tildes are expanded. + ''; + example = [ "/home" "/etc" "/var/log/syslog*" ]; + }; + repositories = mkOption { + type = types.listOf types.str; + description = '' + Paths to local or remote repositories (required). Tildes are + expanded. Multiple repositories are backed up to in + sequence. Borg placeholders can be used. See the output of + "borg help placeholders" for details. See ssh_command for + SSH options like identity file or port. If systemd service + is used, then add local repository paths in the systemd + service file to the ReadWritePaths list. + ''; + example = [ + "user@backupserver:sourcehostname.borg" + "user@backupserver:{fqdn}" + ]; + }; + }; + }; + }; + }; + + config = mkIf cfg.enable { + + environment.systemPackages = [ pkgs.borgmatic ]; + + environment.etc."borgmatic/config.yaml".source = cfgfile; + + systemd.packages = [ pkgs.borgmatic ]; + + }; +} diff --git a/nixpkgs/nixos/modules/services/backup/restic.nix b/nixpkgs/nixos/modules/services/backup/restic.nix index 573f0efa9da4..ac57f271526f 100644 --- a/nixpkgs/nixos/modules/services/backup/restic.nix +++ b/nixpkgs/nixos/modules/services/backup/restic.nix @@ -93,10 +93,12 @@ in }; paths = mkOption { - type = types.listOf types.str; - default = []; + type = types.nullOr (types.listOf types.str); + default = null; description = '' - Which paths to backup. + Which paths to backup. If null or an empty array, no + backup command will be run. This can be used to create a + prune-only job. ''; example = [ "/var/lib/postgresql" @@ -217,7 +219,7 @@ in resticCmd = "${pkgs.restic}/bin/restic${extraOptions}"; filesFromTmpFile = "/run/restic-backups-${name}/includes"; backupPaths = if (backup.dynamicFilesFrom == null) - then concatStringsSep " " backup.paths + then if (backup.paths != null) then concatStringsSep " " backup.paths else "" else "--files-from ${filesFromTmpFile}"; pruneCmd = optionals (builtins.length backup.pruneOpts > 0) [ ( resticCmd + " forget --prune " + (concatStringsSep " " backup.pruneOpts) ) @@ -243,7 +245,8 @@ in restartIfChanged = false; serviceConfig = { Type = "oneshot"; - ExecStart = [ "${resticCmd} backup --cache-dir=%C/restic-backups-${name} ${concatStringsSep " " backup.extraBackupArgs} ${backupPaths}" ] ++ pruneCmd; + ExecStart = (optionals (backupPaths != "") [ "${resticCmd} backup --cache-dir=%C/restic-backups-${name} ${concatStringsSep " " backup.extraBackupArgs} ${backupPaths}" ]) + ++ pruneCmd; User = backup.user; RuntimeDirectory = "restic-backups-${name}"; CacheDirectory = "restic-backups-${name}"; diff --git a/nixpkgs/nixos/modules/services/backup/zrepl.nix b/nixpkgs/nixos/modules/services/backup/zrepl.nix new file mode 100644 index 000000000000..4356479b6635 --- /dev/null +++ b/nixpkgs/nixos/modules/services/backup/zrepl.nix @@ -0,0 +1,54 @@ +{ config, pkgs, lib, ... }: + +with lib; +let + cfg = config.services.zrepl; + format = pkgs.formats.yaml { }; + configFile = format.generate "zrepl.yml" cfg.settings; +in +{ + meta.maintainers = with maintainers; [ cole-h ]; + + options = { + services.zrepl = { + enable = mkEnableOption "zrepl"; + + settings = mkOption { + default = { }; + description = '' + Configuration for zrepl. See <link + xlink:href="https://zrepl.github.io/configuration.html"/> + for more information. + ''; + type = types.submodule { + freeformType = format.type; + }; + }; + }; + }; + + ### Implementation ### + + config = mkIf cfg.enable { + environment.systemPackages = [ pkgs.zrepl ]; + + # zrepl looks for its config in this location by default. This + # allows the use of e.g. `zrepl signal wakeup <job>` without having + # to specify the storepath of the config. + environment.etc."zrepl/zrepl.yml".source = configFile; + + systemd.packages = [ pkgs.zrepl ]; + systemd.services.zrepl = { + requires = [ "local-fs.target" ]; + wantedBy = [ "zfs.target" ]; + after = [ "zfs.target" ]; + + path = [ config.boot.zfs.package ]; + restartTriggers = [ configFile ]; + + serviceConfig = { + Restart = "on-failure"; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/blockchain/ethereum/geth.nix b/nixpkgs/nixos/modules/services/blockchain/ethereum/geth.nix new file mode 100644 index 000000000000..be3f40f6bd86 --- /dev/null +++ b/nixpkgs/nixos/modules/services/blockchain/ethereum/geth.nix @@ -0,0 +1,178 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + eachGeth = config.services.geth; + + gethOpts = { config, lib, name, ...}: { + + options = { + + enable = lib.mkEnableOption "Go Ethereum Node"; + + port = mkOption { + type = types.port; + default = 30303; + description = "Port number Go Ethereum will be listening on, both TCP and UDP."; + }; + + http = { + enable = lib.mkEnableOption "Go Ethereum HTTP API"; + address = mkOption { + type = types.str; + default = "127.0.0.1"; + description = "Listen address of Go Ethereum HTTP API."; + }; + + port = mkOption { + type = types.port; + default = 8545; + description = "Port number of Go Ethereum HTTP API."; + }; + + apis = mkOption { + type = types.nullOr (types.listOf types.str); + default = null; + description = "APIs to enable over WebSocket"; + example = ["net" "eth"]; + }; + }; + + websocket = { + enable = lib.mkEnableOption "Go Ethereum WebSocket API"; + address = mkOption { + type = types.str; + default = "127.0.0.1"; + description = "Listen address of Go Ethereum WebSocket API."; + }; + + port = mkOption { + type = types.port; + default = 8546; + description = "Port number of Go Ethereum WebSocket API."; + }; + + apis = mkOption { + type = types.nullOr (types.listOf types.str); + default = null; + description = "APIs to enable over WebSocket"; + example = ["net" "eth"]; + }; + }; + + metrics = { + enable = lib.mkEnableOption "Go Ethereum prometheus metrics"; + address = mkOption { + type = types.str; + default = "127.0.0.1"; + description = "Listen address of Go Ethereum metrics service."; + }; + + port = mkOption { + type = types.port; + default = 6060; + description = "Port number of Go Ethereum metrics service."; + }; + }; + + network = mkOption { + type = types.nullOr (types.enum [ "goerli" "rinkeby" "yolov2" "ropsten" ]); + default = null; + description = "The network to connect to. Mainnet (null) is the default ethereum network."; + }; + + syncmode = mkOption { + type = types.enum [ "fast" "full" "light" ]; + default = "fast"; + description = "Blockchain sync mode."; + }; + + gcmode = mkOption { + type = types.enum [ "full" "archive" ]; + default = "full"; + description = "Blockchain garbage collection mode."; + }; + + maxpeers = mkOption { + type = types.int; + default = 50; + description = "Maximum peers to connect to."; + }; + + extraArgs = mkOption { + type = types.listOf types.str; + description = "Additional arguments passed to Go Ethereum."; + default = []; + }; + + package = mkOption { + default = pkgs.go-ethereum.geth; + type = types.package; + description = "Package to use as Go Ethereum node."; + }; + }; + }; +in + +{ + + ###### interface + + options = { + services.geth = mkOption { + type = types.attrsOf (types.submodule gethOpts); + default = {}; + description = "Specification of one or more geth instances."; + }; + }; + + ###### implementation + + config = mkIf (eachGeth != {}) { + + environment.systemPackages = flatten (mapAttrsToList (gethName: cfg: [ + cfg.package + ]) eachGeth); + + systemd.services = mapAttrs' (gethName: cfg: ( + nameValuePair "geth-${gethName}" (mkIf cfg.enable { + description = "Go Ethereum node (${gethName})"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + serviceConfig = { + DynamicUser = true; + Restart = "always"; + StateDirectory = "goethereum/${gethName}/${if (cfg.network == null) then "mainnet" else cfg.network}"; + + # Hardening measures + PrivateTmp = "true"; + ProtectSystem = "full"; + NoNewPrivileges = "true"; + PrivateDevices = "true"; + MemoryDenyWriteExecute = "true"; + }; + + script = '' + ${cfg.package}/bin/geth \ + --nousb \ + --ipcdisable \ + ${optionalString (cfg.network != null) ''--${cfg.network}''} \ + --syncmode ${cfg.syncmode} \ + --gcmode ${cfg.gcmode} \ + --port ${toString cfg.port} \ + --maxpeers ${toString cfg.maxpeers} \ + ${if cfg.http.enable then ''--http --http.addr ${cfg.http.address} --http.port ${toString cfg.http.port}'' else ""} \ + ${optionalString (cfg.http.apis != null) ''--http.api ${lib.concatStringsSep "," cfg.http.apis}''} \ + ${if cfg.websocket.enable then ''--ws --ws.addr ${cfg.websocket.address} --ws.port ${toString cfg.websocket.port}'' else ""} \ + ${optionalString (cfg.websocket.apis != null) ''--ws.api ${lib.concatStringsSep "," cfg.websocket.apis}''} \ + ${optionalString cfg.metrics.enable ''--metrics --metrics.addr ${cfg.metrics.address} --metrics.port ${toString cfg.metrics.port}''} \ + ${lib.escapeShellArgs cfg.extraArgs} \ + --datadir /var/lib/goethereum/${gethName}/${if (cfg.network == null) then "mainnet" else cfg.network} + ''; + }))) eachGeth; + + }; + +} diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dns.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dns.nix index f12e866930da..24d86628b211 100644 --- a/nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dns.nix +++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dns.nix @@ -3,7 +3,7 @@ with lib; let - version = "1.6.4"; + version = "1.7.1"; cfg = config.services.kubernetes.addons.dns; ports = { dns = 10053; @@ -55,9 +55,9 @@ in { type = types.attrs; default = { imageName = "coredns/coredns"; - imageDigest = "sha256:493ee88e1a92abebac67cbd4b5658b4730e0f33512461442d8d9214ea6734a9b"; + imageDigest = "sha256:4a6e0769130686518325b21b0c1d0688b54e7c79244d48e1b15634e98e40c6ef"; finalImageTag = version; - sha256 = "0fm9zdjavpf5hni8g7fkdd3csjbhd7n7py7llxjc66sbii087028"; + sha256 = "02r440xcdsgi137k5lmmvp0z5w5fmk8g9mysq5pnysq1wl8sj6mw"; }; }; }; @@ -156,7 +156,6 @@ in { health :${toString ports.health} kubernetes ${cfg.clusterDomain} in-addr.arpa ip6.arpa { pods insecure - upstream fallthrough in-addr.arpa ip6.arpa } prometheus :${toString ports.metrics} diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/apiserver.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/apiserver.nix index 95bdb4c0d14e..a5b132154766 100644 --- a/nixpkgs/nixos/modules/services/cluster/kubernetes/apiserver.nix +++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/apiserver.nix @@ -238,14 +238,40 @@ in type = int; }; + apiAudiences = mkOption { + description = '' + Kubernetes apiserver ServiceAccount issuer. + ''; + default = "api,https://kubernetes.default.svc"; + type = str; + }; + + serviceAccountIssuer = mkOption { + description = '' + Kubernetes apiserver ServiceAccount issuer. + ''; + default = "https://kubernetes.default.svc"; + type = str; + }; + + serviceAccountSigningKeyFile = mkOption { + description = '' + Path to the file that contains the current private key of the service + account token issuer. The issuer will sign issued ID tokens with this + private key. + ''; + type = path; + }; + serviceAccountKeyFile = mkOption { description = '' - Kubernetes apiserver PEM-encoded x509 RSA private or public key file, - used to verify ServiceAccount tokens. By default tls private key file - is used. + File containing PEM-encoded x509 RSA or ECDSA private or public keys, + used to verify ServiceAccount tokens. The specified file can contain + multiple keys, and the flag can be specified multiple times with + different files. If unspecified, --tls-private-key-file is used. + Must be specified when --service-account-signing-key is provided ''; - default = null; - type = nullOr path; + type = path; }; serviceClusterIpRange = mkOption { @@ -357,8 +383,10 @@ in ${optionalString (cfg.runtimeConfig != "") "--runtime-config=${cfg.runtimeConfig}"} \ --secure-port=${toString cfg.securePort} \ - ${optionalString (cfg.serviceAccountKeyFile!=null) - "--service-account-key-file=${cfg.serviceAccountKeyFile}"} \ + --api-audiences=${toString cfg.apiAudiences} \ + --service-account-issuer=${toString cfg.serviceAccountIssuer} \ + --service-account-signing-key-file=${cfg.serviceAccountSigningKeyFile} \ + --service-account-key-file=${cfg.serviceAccountKeyFile} \ --service-cluster-ip-range=${cfg.serviceClusterIpRange} \ --storage-backend=${cfg.storageBackend} \ ${optionalString (cfg.tlsCertFile != null) diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/default.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/default.nix index 3a11a6513a49..19edc338bba1 100644 --- a/nixpkgs/nixos/modules/services/cluster/kubernetes/default.nix +++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/default.nix @@ -5,6 +5,29 @@ with lib; let cfg = config.services.kubernetes; + defaultContainerdConfigFile = pkgs.writeText "containerd.toml" '' + version = 2 + root = "/var/lib/containerd/daemon" + state = "/var/run/containerd/daemon" + oom_score = 0 + + [grpc] + address = "/var/run/containerd/containerd.sock" + + [plugins."io.containerd.grpc.v1.cri"] + sandbox_image = "pause:latest" + + [plugins."io.containerd.grpc.v1.cri".cni] + bin_dir = "/opt/cni/bin" + max_conf_num = 0 + + [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc] + runtime_type = "io.containerd.runc.v2" + + [plugins."io.containerd.grpc.v1.cri".containerd.runtimes."io.containerd.runc.v2".options] + SystemdCgroup = true + ''; + mkKubeConfig = name: conf: pkgs.writeText "${name}-kubeconfig" (builtins.toJSON { apiVersion = "v1"; kind = "Config"; @@ -222,14 +245,9 @@ in { }) (mkIf cfg.kubelet.enable { - virtualisation.docker = { + virtualisation.containerd = { enable = mkDefault true; - - # kubernetes needs access to logs - logDriver = mkDefault "json-file"; - - # iptables must be disabled for kubernetes - extraOptions = "--iptables=false --ip-masq=false"; + configFile = mkDefault defaultContainerdConfigFile; }; }) @@ -269,7 +287,6 @@ in { users.users.kubernetes = { uid = config.ids.uids.kubernetes; description = "Kubernetes user"; - extraGroups = [ "docker" ]; group = "kubernetes"; home = cfg.dataDir; createHome = true; diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/flannel.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/flannel.nix index 548ffed1ddb5..3f55719027f0 100644 --- a/nixpkgs/nixos/modules/services/cluster/kubernetes/flannel.nix +++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/flannel.nix @@ -8,16 +8,6 @@ let # we want flannel to use kubernetes itself as configuration backend, not direct etcd storageBackend = "kubernetes"; - - # needed for flannel to pass options to docker - mkDockerOpts = pkgs.runCommand "mk-docker-opts" { - buildInputs = [ pkgs.makeWrapper ]; - } '' - mkdir -p $out - - # bashInteractive needed for `compgen` - makeWrapper ${pkgs.bashInteractive}/bin/bash $out/mk-docker-opts --add-flags "${pkgs.kubernetes}/bin/mk-docker-opts.sh" - ''; in { ###### interface @@ -43,43 +33,17 @@ in cniVersion = "0.3.1"; delegate = { isDefaultGateway = true; - bridge = "docker0"; + bridge = "mynet"; }; }]; }; - systemd.services.mk-docker-opts = { - description = "Pre-Docker Actions"; - path = with pkgs; [ gawk gnugrep ]; - script = '' - ${mkDockerOpts}/mk-docker-opts -d /run/flannel/docker - systemctl restart docker - ''; - serviceConfig.Type = "oneshot"; - }; - - systemd.paths.flannel-subnet-env = { - wantedBy = [ "flannel.service" ]; - pathConfig = { - PathModified = "/run/flannel/subnet.env"; - Unit = "mk-docker-opts.service"; - }; - }; - - systemd.services.docker = { - environment.DOCKER_OPTS = "-b none"; - serviceConfig.EnvironmentFile = "-/run/flannel/docker"; - }; - - # read environment variables generated by mk-docker-opts - virtualisation.docker.extraOptions = "$DOCKER_OPTS"; - networking = { firewall.allowedUDPPorts = [ 8285 # flannel udp 8472 # flannel vxlan ]; - dhcpcd.denyInterfaces = [ "docker*" "flannel*" ]; + dhcpcd.denyInterfaces = [ "mynet*" "flannel*" ]; }; services.kubernetes.pki.certs = { diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/kubelet.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/kubelet.nix index 479027f1b270..b5346b1cd44d 100644 --- a/nixpkgs/nixos/modules/services/cluster/kubernetes/kubelet.nix +++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/kubelet.nix @@ -23,7 +23,7 @@ let name = "pause"; tag = "latest"; contents = top.package.pause; - config.Cmd = "/bin/pause"; + config.Cmd = ["/bin/pause"]; }; kubeconfig = top.lib.mkKubeConfig "kubelet" cfg.kubeconfig; @@ -125,6 +125,18 @@ in }; }; + containerRuntime = mkOption { + description = "Which container runtime type to use"; + type = enum ["docker" "remote"]; + default = "remote"; + }; + + containerRuntimeEndpoint = mkOption { + description = "Endpoint at which to find the container runtime api interface/socket"; + type = str; + default = "unix:///var/run/containerd/containerd.sock"; + }; + enable = mkEnableOption "Kubernetes kubelet."; extraOpts = mkOption { @@ -235,18 +247,26 @@ in ###### implementation config = mkMerge [ (mkIf cfg.enable { + + environment.etc."cni/net.d".source = cniConfig; + services.kubernetes.kubelet.seedDockerImages = [infraContainer]; + boot.kernel.sysctl = { + "net.bridge.bridge-nf-call-iptables" = 1; + "net.ipv4.ip_forward" = 1; + "net.bridge.bridge-nf-call-ip6tables" = 1; + }; + systemd.services.kubelet = { description = "Kubernetes Kubelet Service"; wantedBy = [ "kubernetes.target" ]; - after = [ "network.target" "docker.service" "kube-apiserver.service" ]; + after = [ "containerd.service" "network.target" "kube-apiserver.service" ]; path = with pkgs; [ gitMinimal openssh - docker util-linux - iproute + iproute2 ethtool thin-provisioning-tools iptables @@ -254,8 +274,12 @@ in ] ++ lib.optional config.boot.zfs.enabled config.boot.zfs.package ++ top.path; preStart = '' ${concatMapStrings (img: '' - echo "Seeding docker image: ${img}" - docker load <${img} + echo "Seeding container image: ${img}" + ${if (lib.hasSuffix "gz" img) then + ''${pkgs.gzip}/bin/zcat "${img}" | ${pkgs.containerd}/bin/ctr -n k8s.io image import --all-platforms -'' + else + ''${pkgs.coreutils}/bin/cat "${img}" | ${pkgs.containerd}/bin/ctr -n k8s.io image import --all-platforms -'' + } '') cfg.seedDockerImages} rm /opt/cni/bin/* || true @@ -306,6 +330,9 @@ in ${optionalString (cfg.tlsKeyFile != null) "--tls-private-key-file=${cfg.tlsKeyFile}"} \ ${optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \ + --container-runtime=${cfg.containerRuntime} \ + --container-runtime-endpoint=${cfg.containerRuntimeEndpoint} \ + --cgroup-driver=systemd \ ${cfg.extraOpts} ''; WorkingDirectory = top.dataDir; @@ -315,7 +342,7 @@ in # Allways include cni plugins services.kubernetes.kubelet.cni.packages = [pkgs.cni-plugins]; - boot.kernelModules = ["br_netfilter"]; + boot.kernelModules = ["br_netfilter" "overlay"]; services.kubernetes.kubelet.hostname = with config.networking; mkDefault (hostName + optionalString (domain != null) ".${domain}"); diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/pki.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/pki.nix index 933ae481e968..8de6a3ba0d80 100644 --- a/nixpkgs/nixos/modules/services/cluster/kubernetes/pki.nix +++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/pki.nix @@ -361,6 +361,7 @@ in tlsCertFile = mkDefault cert; tlsKeyFile = mkDefault key; serviceAccountKeyFile = mkDefault cfg.certs.serviceAccount.cert; + serviceAccountSigningKeyFile = mkDefault cfg.certs.serviceAccount.key; kubeletClientCaFile = mkDefault caCert; kubeletClientCertFile = mkDefault cfg.certs.apiserverKubeletClient.cert; kubeletClientKeyFile = mkDefault cfg.certs.apiserverKubeletClient.key; diff --git a/nixpkgs/nixos/modules/services/computing/slurm/slurm.nix b/nixpkgs/nixos/modules/services/computing/slurm/slurm.nix index 7363441e5387..0b52f8afed83 100644 --- a/nixpkgs/nixos/modules/services/computing/slurm/slurm.nix +++ b/nixpkgs/nixos/modules/services/computing/slurm/slurm.nix @@ -274,6 +274,15 @@ in ''; }; + etcSlurm = mkOption { + type = types.path; + internal = true; + default = etcSlurm; + description = '' + Path to directory with slurm config files. This option is set by default from the + Slurm module and is meant to make the Slurm config file available to other modules. + ''; + }; }; @@ -308,7 +317,7 @@ in #!/bin/sh if [ -z "$SLURM_CONF" ] then - SLURM_CONF="${etcSlurm}/slurm.conf" "$EXE" "\$@" + SLURM_CONF="${cfg.etcSlurm}/slurm.conf" "$EXE" "\$@" else "$EXE" "\$0" fi diff --git a/nixpkgs/nixos/modules/services/continuous-integration/hydra/default.nix b/nixpkgs/nixos/modules/services/continuous-integration/hydra/default.nix index 887a0cbf9a7a..0103cd723d2f 100644 --- a/nixpkgs/nixos/modules/services/continuous-integration/hydra/default.nix +++ b/nixpkgs/nixos/modules/services/continuous-integration/hydra/default.nix @@ -89,6 +89,11 @@ in example = "dbi:Pg:dbname=hydra;host=postgres.example.org;user=foo;"; description = '' The DBI string for Hydra database connection. + + NOTE: Attempts to set `application_name` will be overridden by + `hydra-TYPE` (where TYPE is e.g. `evaluator`, `queue-runner`, + etc.) in all hydra services to more easily distinguish where + queries are coming from. ''; }; @@ -275,6 +280,8 @@ in keep-outputs = true keep-derivations = true + + '' + optionalString (versionOlder (getVersion config.nix.package.out) "2.4pre") '' # The default (`true') slows Nix down a lot since the build farm # has so many GC roots. gc-check-reachability = false @@ -284,7 +291,9 @@ in { wantedBy = [ "multi-user.target" ]; requires = optional haveLocalDB "postgresql.service"; after = optional haveLocalDB "postgresql.service"; - environment = env; + environment = env // { + HYDRA_DBI = "${env.HYDRA_DBI};application_name=hydra-init"; + }; preStart = '' mkdir -p ${baseDir} chown hydra.hydra ${baseDir} @@ -339,7 +348,9 @@ in { wantedBy = [ "multi-user.target" ]; requires = [ "hydra-init.service" ]; after = [ "hydra-init.service" ]; - environment = serverEnv; + environment = serverEnv // { + HYDRA_DBI = "${serverEnv.HYDRA_DBI};application_name=hydra-server"; + }; restartTriggers = [ hydraConf ]; serviceConfig = { ExecStart = @@ -361,6 +372,7 @@ in environment = env // { PGPASSFILE = "${baseDir}/pgpass-queue-runner"; # grrr IN_SYSTEMD = "1"; # to get log severity levels + HYDRA_DBI = "${env.HYDRA_DBI};application_name=hydra-queue-runner"; }; serviceConfig = { ExecStart = "@${hydra-package}/bin/hydra-queue-runner hydra-queue-runner -v"; @@ -380,7 +392,9 @@ in after = [ "hydra-init.service" "network.target" ]; path = with pkgs; [ hydra-package nettools jq ]; restartTriggers = [ hydraConf ]; - environment = env; + environment = env // { + HYDRA_DBI = "${env.HYDRA_DBI};application_name=hydra-evaluator"; + }; serviceConfig = { ExecStart = "@${hydra-package}/bin/hydra-evaluator hydra-evaluator"; User = "hydra"; @@ -392,7 +406,9 @@ in systemd.services.hydra-update-gc-roots = { requires = [ "hydra-init.service" ]; after = [ "hydra-init.service" ]; - environment = env; + environment = env // { + HYDRA_DBI = "${env.HYDRA_DBI};application_name=hydra-update-gc-roots"; + }; serviceConfig = { ExecStart = "@${hydra-package}/bin/hydra-update-gc-roots hydra-update-gc-roots"; User = "hydra"; @@ -403,7 +419,9 @@ in systemd.services.hydra-send-stats = { wantedBy = [ "multi-user.target" ]; after = [ "hydra-init.service" ]; - environment = env; + environment = env // { + HYDRA_DBI = "${env.HYDRA_DBI};application_name=hydra-send-stats"; + }; serviceConfig = { ExecStart = "@${hydra-package}/bin/hydra-send-stats hydra-send-stats"; User = "hydra"; @@ -417,6 +435,7 @@ in restartTriggers = [ hydraConf ]; environment = env // { PGPASSFILE = "${baseDir}/pgpass-queue-runner"; + HYDRA_DBI = "${env.HYDRA_DBI};application_name=hydra-notify"; }; serviceConfig = { ExecStart = "@${hydra-package}/bin/hydra-notify hydra-notify"; diff --git a/nixpkgs/nixos/modules/services/databases/mysql.nix b/nixpkgs/nixos/modules/services/databases/mysql.nix index 7d0a3f9afc48..cf105daeb04e 100644 --- a/nixpkgs/nixos/modules/services/databases/mysql.nix +++ b/nixpkgs/nixos/modules/services/databases/mysql.nix @@ -375,6 +375,18 @@ in fi ''; + script = '' + # https://mariadb.com/kb/en/getting-started-with-mariadb-galera-cluster/#systemd-and-galera-recovery + if test -n "''${_WSREP_START_POSITION}"; then + if test -e "${cfg.package}/bin/galera_recovery"; then + VAR=$(cd ${cfg.package}/bin/..; ${cfg.package}/bin/galera_recovery); [[ $? -eq 0 ]] && export _WSREP_START_POSITION=$VAR || exit 1 + fi + fi + + # The last two environment variables are used for starting Galera clusters + exec ${cfg.package}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} $_WSREP_NEW_CLUSTER $_WSREP_START_POSITION + ''; + postStart = let # The super user account to use on *first* run of MySQL server superUser = if isMariaDB then cfg.user else "root"; @@ -481,8 +493,7 @@ in Type = if hasNotify then "notify" else "simple"; Restart = "on-abort"; RestartSec = "5s"; - # The last two environment variables are used for starting Galera clusters - ExecStart = "${cfg.package}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} $_WSREP_NEW_CLUSTER $_WSREP_START_POSITION"; + # User and group User = cfg.user; Group = cfg.group; diff --git a/nixpkgs/nixos/modules/services/databases/postgresql.nix b/nixpkgs/nixos/modules/services/databases/postgresql.nix index f582b0592774..900185fbbdf7 100644 --- a/nixpkgs/nixos/modules/services/databases/postgresql.nix +++ b/nixpkgs/nixos/modules/services/databases/postgresql.nix @@ -18,7 +18,12 @@ let else toString value; # The main PostgreSQL configuration file. - configFile = pkgs.writeText "postgresql.conf" (concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${toStr v}") cfg.settings)); + configFile = pkgs.writeTextDir "postgresql.conf" (concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${toStr v}") cfg.settings)); + + configFileCheck = pkgs.runCommand "postgresql-configfile-check" {} '' + ${cfg.package}/bin/postgres -D${configFile} -C config_file >/dev/null + touch $out + ''; groupAccessAvailable = versionAtLeast postgresql.version "11.0"; @@ -53,6 +58,12 @@ in ''; }; + checkConfig = mkOption { + type = types.bool; + default = true; + description = "Check the syntax of the configuration file at compile time"; + }; + dataDir = mkOption { type = types.path; defaultText = "/var/lib/postgresql/\${config.services.postgresql.package.psqlSchema}"; @@ -314,6 +325,8 @@ in "/share/postgresql" ]; + system.extraDependencies = lib.optional (cfg.checkConfig && pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) configFileCheck; + systemd.services.postgresql = { description = "PostgreSQL Server"; @@ -337,7 +350,7 @@ in touch "${cfg.dataDir}/.first_startup" fi - ln -sfn "${configFile}" "${cfg.dataDir}/postgresql.conf" + ln -sfn "${configFile}/postgresql.conf" "${cfg.dataDir}/postgresql.conf" ${optionalString (cfg.recoveryConfig != null) '' ln -sfn "${pkgs.writeText "recovery.conf" cfg.recoveryConfig}" \ "${cfg.dataDir}/recovery.conf" diff --git a/nixpkgs/nixos/modules/services/desktops/geoclue2.nix b/nixpkgs/nixos/modules/services/desktops/geoclue2.nix index 6702bd395a03..0dc0643afbc9 100644 --- a/nixpkgs/nixos/modules/services/desktops/geoclue2.nix +++ b/nixpkgs/nixos/modules/services/desktops/geoclue2.nix @@ -188,7 +188,8 @@ in systemd.packages = [ package ]; - # we cannot use DynamicUser as we need the the geoclue user to exist for the dbus policy to work + # we cannot use DynamicUser as we need the the geoclue user to exist for the + # dbus policy to work users = { users.geoclue = { isSystemUser = true; @@ -217,6 +218,7 @@ in # we can't be part of a system service, and the agent should # be okay with the main service coming and going wantedBy = [ "default.target" ]; + unitConfig.ConditionUser = "!@system"; serviceConfig = { Type = "exec"; ExecStart = "${package}/libexec/geoclue-2.0/demos/agent"; diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/README.md b/nixpkgs/nixos/modules/services/desktops/pipewire/README.md new file mode 100644 index 000000000000..87288a81cfe1 --- /dev/null +++ b/nixpkgs/nixos/modules/services/desktops/pipewire/README.md @@ -0,0 +1,6 @@ +# Updating + +1. Update the version & hash in pkgs/development/libraries/pipewire/default.nix +2. run `nix build -f /path/to/nixpkgs/checkout pipewire pipewire.mediaSession` +3. copy all JSON files from result/etc/pipewire and result-mediaSession/etc/pipewire/media-session.d to this directory +4. add new files to the module config and passthru tests diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/alsa-monitor.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/alsa-monitor.conf.json new file mode 100644 index 000000000000..53fc9cc96343 --- /dev/null +++ b/nixpkgs/nixos/modules/services/desktops/pipewire/alsa-monitor.conf.json @@ -0,0 +1,34 @@ +{ + "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/nixpkgs/nixos/modules/services/desktops/pipewire/bluez-monitor.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/bluez-monitor.conf.json new file mode 100644 index 000000000000..bd00571bc35b --- /dev/null +++ b/nixpkgs/nixos/modules/services/desktops/pipewire/bluez-monitor.conf.json @@ -0,0 +1,36 @@ +{ + "properties": {}, + "rules": [ + { + "matches": [ + { + "device.name": "~bluez_card.*" + } + ], + "actions": { + "update-props": { + "bluez5.reconnect-profiles": [ + "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/nixpkgs/nixos/modules/services/desktops/pipewire/client-rt.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/client-rt.conf.json new file mode 100644 index 000000000000..284d8c394a61 --- /dev/null +++ b/nixpkgs/nixos/modules/services/desktops/pipewire/client-rt.conf.json @@ -0,0 +1,39 @@ +{ + "context.properties": { + "log.level": 0 + }, + "context.spa-libs": { + "audio.convert.*": "audioconvert/libspa-audioconvert", + "support.*": "support/libspa-support" + }, + "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" + } + ], + "filter.properties": {}, + "stream.properties": {} +} diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/client.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/client.conf.json new file mode 100644 index 000000000000..71294a0e78a2 --- /dev/null +++ b/nixpkgs/nixos/modules/services/desktops/pipewire/client.conf.json @@ -0,0 +1,31 @@ +{ + "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/nixpkgs/nixos/modules/services/desktops/pipewire/jack.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/jack.conf.json new file mode 100644 index 000000000000..a6bd34917851 --- /dev/null +++ b/nixpkgs/nixos/modules/services/desktops/pipewire/jack.conf.json @@ -0,0 +1,28 @@ +{ + "context.properties": { + "log.level": 0 + }, + "context.spa-libs": { + "support.*": "support/libspa-support" + }, + "context.modules": [ + { + "name": "libpipewire-module-rtkit", + "args": {}, + "flags": [ + "ifexists", + "nofail" + ] + }, + { + "name": "libpipewire-module-protocol-native" + }, + { + "name": "libpipewire-module-client-node" + }, + { + "name": "libpipewire-module-metadata" + } + ], + "jack.properties": {} +} diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/media-session.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/media-session.conf.json new file mode 100644 index 000000000000..62e59935dbe5 --- /dev/null +++ b/nixpkgs/nixos/modules/services/desktops/pipewire/media-session.conf.json @@ -0,0 +1,66 @@ +{ + "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", + "restore-stream", + "streams-follow-default" + ] + } +} diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix b/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix new file mode 100644 index 000000000000..17a2d49bb1f3 --- /dev/null +++ b/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix @@ -0,0 +1,129 @@ +# 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 = (builtins.fromJSON (builtins.readFile ./alsa-monitor.conf.json)); + bluez-monitor = (builtins.fromJSON (builtins.readFile ./bluez-monitor.conf.json)); + media-session = (builtins.fromJSON (builtins.readFile ./media-session.conf.json)); + v4l2-monitor = (builtins.fromJSON (builtins.readFile ./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; + }; + + ###### interface + options = { + services.pipewire.media-session = { + enable = mkOption { + type = types.bool; + default = config.services.pipewire.enable; + defaultText = "config.services.pipewire.enable"; + description = "Example pipewire session manager"; + }; + + package = mkOption { + type = types.package; + default = pkgs.pipewire.mediaSession; + example = literalExample "pkgs.pipewire.mediaSession"; + description = '' + The pipewire-media-session derivation to use. + ''; + }; + + config = { + media-session = mkOption { + type = json.type; + description = '' + Configuration for the media session core. For details see + https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/media-session.d/media-session.conf + ''; + default = {}; + }; + + alsa-monitor = mkOption { + type = json.type; + description = '' + Configuration for the alsa monitor. For details see + https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/media-session.d/alsa-monitor.conf + ''; + default = {}; + }; + + bluez-monitor = mkOption { + type = json.type; + description = '' + Configuration for the bluez5 monitor. For details see + https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/media-session.d/bluez-monitor.conf + ''; + default = {}; + }; + + v4l2-monitor = mkOption { + type = json.type; + description = '' + Configuration for the V4L2 monitor. For details see + https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/${cfg.package.version}/src/daemon/media-session.d/v4l2-monitor.conf + ''; + default = {}; + }; + }; + }; + }; + + ###### implementation + config = mkIf cfg.enable { + environment.systemPackages = [ cfg.package ]; + systemd.packages = [ cfg.package ]; + 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-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/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire-pulse.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire-pulse.conf.json new file mode 100644 index 000000000000..3e776fe75a2c --- /dev/null +++ b/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire-pulse.conf.json @@ -0,0 +1,38 @@ +{ + "context.properties": {}, + "context.spa-libs": { + "audio.convert.*": "audioconvert/libspa-audioconvert", + "support.*": "support/libspa-support" + }, + "context.modules": [ + { + "name": "libpipewire-module-rtkit", + "args": {}, + "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": { + "server.address": [ + "unix:native" + ] + } + } + ], + "stream.properties": {} +} diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire.conf.json new file mode 100644 index 000000000000..bae87dd66377 --- /dev/null +++ b/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire.conf.json @@ -0,0 +1,79 @@ +{ + "context.properties": { + "link.max-buffers": 16, + "core.daemon": true, + "core.name": "pipewire-0" + }, + "context.spa-libs": { + "audio.convert.*": "audioconvert/libspa-audioconvert", + "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-rtkit", + "args": {}, + "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" + } + ], + "context.objects": [ + { + "factory": "spa-node-factory", + "args": { + "factory.name": "support.node.driver", + "node.name": "Dummy-Driver", + "priority.driver": 8000 + } + } + ], + "context.exec": [] +} diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire.nix b/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire.nix index 134becf6b0c4..dbd6c5d87e1a 100644 --- a/nixpkgs/nixos/modules/services/desktops/pipewire.nix +++ b/nixpkgs/nixos/modules/services/desktops/pipewire/pipewire.nix @@ -4,6 +4,7 @@ with lib; let + json = pkgs.formats.json {}; cfg = config.services.pipewire; enable32BitAlsaPlugins = cfg.alsa.support32Bit && pkgs.stdenv.isx86_64 @@ -17,6 +18,25 @@ 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 = builtins.fromJSON (builtins.readFile ./client.conf.json); + client-rt = builtins.fromJSON (builtins.readFile ./client-rt.conf.json); + jack = builtins.fromJSON (builtins.readFile ./jack.conf.json); + # Remove session manager invocation from the upstream generated file, it points to the wrong path + pipewire = builtins.fromJSON (builtins.readFile ./pipewire.conf.json); + pipewire-pulse = builtins.fromJSON (builtins.readFile ./pipewire-pulse.conf.json); + }; + + 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 defaults.pipewire cfg.config.pipewire; + pipewire-pulse = recursiveUpdate defaults.pipewire-pulse cfg.config.pipewire-pulse; + }; in { meta = { @@ -46,30 +66,51 @@ in { ''; }; - extraConfig = mkOption { - type = types.lines; - default = ""; - description = '' - Literal string to append to /etc/pipewire/pipewire.conf. - ''; - }; - - sessionManager = mkOption { - type = types.nullOr types.string; - default = null; - example = literalExample ''"''${pipewire}/bin/pipewire-media-session"''; - description = '' - Path to the pipewire session manager executable. - ''; - }; - - sessionManagerArguments = mkOption { - type = types.listOf types.string; - default = []; - example = literalExample ''[ "-p" "bluez5.msbc-support=true" ]''; - description = '' - Arguments passed to the pipewire session manager. - ''; + config = { + client = mkOption { + type = json.type; + default = {}; + description = '' + 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 = '' + 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 = '' + 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 = '' + 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 = '' + 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 + ''; + }; }; alsa = { @@ -101,8 +142,6 @@ in { } ]; - services.pipewire.sessionManager = mkDefault "${cfg.package}/bin/pipewire-media-session"; - environment.systemPackages = [ cfg.package ] ++ lib.optional cfg.jack.enable jack-libs; @@ -137,47 +176,27 @@ in { environment.etc."alsa/conf.d/99-pipewire-default.conf" = mkIf cfg.alsa.enable { source = "${cfg.package}/share/alsa/alsa.conf.d/99-pipewire-default.conf"; }; - environment.sessionVariables.LD_LIBRARY_PATH = - lib.optional cfg.jack.enable "/run/current-system/sw/lib/pipewire"; + 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" = { - # Adapted from src/daemon/pipewire.conf.in - text = '' - set-prop link.max-buffers 16 # version < 3 clients can't handle more - - add-spa-lib audio.convert* audioconvert/libspa-audioconvert - add-spa-lib api.alsa.* alsa/libspa-alsa - add-spa-lib api.v4l2.* v4l2/libspa-v4l2 - add-spa-lib api.libcamera.* libcamera/libspa-libcamera - add-spa-lib api.bluez5.* bluez5/libspa-bluez5 - add-spa-lib api.vulkan.* vulkan/libspa-vulkan - add-spa-lib api.jack.* jack/libspa-jack - add-spa-lib support.* support/libspa-support - - load-module libpipewire-module-rtkit # rt.prio=20 rt.time.soft=200000 rt.time.hard=200000 - load-module libpipewire-module-protocol-native - load-module libpipewire-module-profiler - load-module libpipewire-module-metadata - load-module libpipewire-module-spa-device-factory - load-module libpipewire-module-spa-node-factory - load-module libpipewire-module-client-node - load-module libpipewire-module-client-device - load-module libpipewire-module-portal - load-module libpipewire-module-access - load-module libpipewire-module-adapter - load-module libpipewire-module-link-factory - load-module libpipewire-module-session-manager - - create-object spa-node-factory factory.name=support.node.driver node.name=Dummy priority.driver=8000 - - exec ${cfg.sessionManager} ${lib.concatStringsSep " " cfg.sessionManagerArguments} - - ${cfg.extraConfig} - ''; + source = json.generate "pipewire.conf" configs.pipewire; + }; + environment.etc."pipewire/pipewire-pulse.conf" = { + source = json.generate "pipewire-pulse.conf" configs.pipewire-pulse; }; - environment.etc."pipewire/media-session.d/with-alsa" = mkIf cfg.alsa.enable { text = ""; }; - environment.etc."pipewire/media-session.d/with-pulseaudio" = mkIf cfg.pulse.enable { text = ""; }; - environment.etc."pipewire/media-session.d/with-jack" = mkIf cfg.jack.enable { text = ""; }; + environment.sessionVariables.LD_LIBRARY_PATH = + lib.optional cfg.jack.enable "/run/current-system/sw/lib/pipewire"; + + # https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/464#note_723554 + systemd.user.services.pipewire.environment."PIPEWIRE_LINK_PASSIVE" = "1"; }; } diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire/v4l2-monitor.conf.json b/nixpkgs/nixos/modules/services/desktops/pipewire/v4l2-monitor.conf.json new file mode 100644 index 000000000000..b08cba1b604b --- /dev/null +++ b/nixpkgs/nixos/modules/services/desktops/pipewire/v4l2-monitor.conf.json @@ -0,0 +1,30 @@ +{ + "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/nixpkgs/nixos/modules/services/display-managers/greetd.nix b/nixpkgs/nixos/modules/services/display-managers/greetd.nix new file mode 100644 index 000000000000..c3072bf09964 --- /dev/null +++ b/nixpkgs/nixos/modules/services/display-managers/greetd.nix @@ -0,0 +1,106 @@ +{ config, lib, pkgs, ... }: +with lib; + +let + cfg = config.services.greetd; + tty = "tty${toString cfg.vt}"; + settingsFormat = pkgs.formats.toml {}; +in +{ + options.services.greetd = { + enable = mkEnableOption "greetd"; + + package = mkOption { + type = types.package; + default = pkgs.greetd.greetd; + defaultText = "pkgs.greetd.greetd"; + description = "The greetd package that should be used."; + }; + + settings = mkOption { + type = settingsFormat.type; + example = literalExample '' + { + default_session = { + command = "''${pkgs.greetd.greetd}/bin/agreety --cmd sway"; + }; + } + ''; + description = '' + greetd configuration (<link xlink:href="https://man.sr.ht/~kennylevinsen/greetd/">documentation</link>) + as a Nix attribute set. + ''; + }; + + vt = mkOption { + type = types.int; + default = 1; + description = '' + The virtual console (tty) that greetd should use. This option also disables getty on that tty. + ''; + }; + + restart = mkOption { + type = types.bool; + default = !(cfg.settings ? initial_session); + defaultText = "!(config.services.greetd.settings ? initial_session)"; + description = '' + Wether to restart greetd when it terminates (e.g. on failure). + This is usually desirable so a user can always log in, but should be disabled when using 'settings.initial_session' (autologin), + because every greetd restart will trigger the autologin again. + ''; + }; + }; + config = mkIf cfg.enable { + + services.greetd.settings.terminal.vt = mkDefault cfg.vt; + services.greetd.settings.default_session = mkDefault "greeter"; + + security.pam.services.greetd = { + allowNullPassword = true; + startSession = true; + }; + + # This prevents nixos-rebuild from killing greetd by activating getty again + systemd.services."autovt@${tty}".enable = false; + + systemd.services.greetd = { + unitConfig = { + Wants = [ + "systemd-user-sessions.service" + ]; + After = [ + "systemd-user-sessions.service" + "plymouth-quit-wait.service" + "getty@${tty}.service" + ]; + Conflicts = [ + "getty@${tty}.service" + ]; + }; + + serviceConfig = { + ExecStart = "${pkgs.greetd.greetd}/bin/greetd --config ${settingsFormat.generate "greetd.toml" cfg.settings}"; + + Restart = mkIf cfg.restart "always"; + + # Defaults from greetd upstream configuration + IgnoreSIGPIPE = false; + SendSIGHUP = true; + TimeoutStopSec = "30s"; + KeyringMode = "shared"; + }; + + # Don't kill a user session when using nixos-rebuild + restartIfChanged = false; + + wantedBy = [ "graphical.target" ]; + }; + + systemd.defaultUnit = "graphical.target"; + + users.users.greeter.isSystemUser = true; + }; + + meta.maintainers = with maintainers; [ queezle ]; +} diff --git a/nixpkgs/nixos/modules/services/games/factorio.nix b/nixpkgs/nixos/modules/services/games/factorio.nix index 73099ae33634..a1aa5739d06b 100644 --- a/nixpkgs/nixos/modules/services/games/factorio.nix +++ b/nixpkgs/nixos/modules/services/games/factorio.nix @@ -36,6 +36,7 @@ let only_admins_can_pause_the_game = true; autosave_only_on_server = true; admins = []; + non_blocking_saving = cfg.nonBlockingSaving; } // cfg.extraSettings; serverSettingsFile = pkgs.writeText "server-settings.json" (builtins.toJSON (filterAttrsRecursive (n: v: v != null) serverSettings)); modDir = pkgs.factorio-utils.mkModDirDrv cfg.mods; @@ -193,6 +194,15 @@ in Autosave interval in minutes. ''; }; + nonBlockingSaving = mkOption { + type = types.bool; + default = false; + description = '' + Highly experimental feature, enable only at your own risk of losing your saves. + On UNIX systems, server will fork itself to create an autosave. + Autosaving on connected Windows clients will be disabled regardless of autosave_only_on_server option. + ''; + }; }; }; diff --git a/nixpkgs/nixos/modules/services/games/minetest-server.nix b/nixpkgs/nixos/modules/services/games/minetest-server.nix index f52079fc1ef6..2111c970d4f2 100644 --- a/nixpkgs/nixos/modules/services/games/minetest-server.nix +++ b/nixpkgs/nixos/modules/services/games/minetest-server.nix @@ -4,7 +4,7 @@ with lib; let cfg = config.services.minetest-server; - flag = val: name: if val != null then "--${name} ${val} " else ""; + flag = val: name: if val != null then "--${name} ${toString val} " else ""; flags = [ (flag cfg.gameId "gameid") (flag cfg.world "world") diff --git a/nixpkgs/nixos/modules/services/hardware/acpid.nix b/nixpkgs/nixos/modules/services/hardware/acpid.nix index 4c97485d9726..3e619fe32ef1 100644 --- a/nixpkgs/nixos/modules/services/hardware/acpid.nix +++ b/nixpkgs/nixos/modules/services/hardware/acpid.nix @@ -3,21 +3,22 @@ with lib; let + cfg = config.services.acpid; canonicalHandlers = { powerEvent = { event = "button/power.*"; - action = config.services.acpid.powerEventCommands; + action = cfg.powerEventCommands; }; lidEvent = { event = "button/lid.*"; - action = config.services.acpid.lidEventCommands; + action = cfg.lidEventCommands; }; acEvent = { event = "ac_adapter.*"; - action = config.services.acpid.acEventCommands; + action = cfg.acEventCommands; }; }; @@ -33,7 +34,7 @@ let echo "event=${handler.event}" > $fn echo "action=${pkgs.writeShellScriptBin "${name}.sh" handler.action }/bin/${name}.sh '%e'" >> $fn ''; - in concatStringsSep "\n" (mapAttrsToList f (canonicalHandlers // config.services.acpid.handlers)) + in concatStringsSep "\n" (mapAttrsToList f (canonicalHandlers // cfg.handlers)) } ''; @@ -47,11 +48,7 @@ in services.acpid = { - enable = mkOption { - type = types.bool; - default = false; - description = "Whether to enable the ACPI daemon."; - }; + enable = mkEnableOption "the ACPI daemon"; logEvents = mkOption { type = types.bool; @@ -129,26 +126,28 @@ in ###### implementation - config = mkIf config.services.acpid.enable { + config = mkIf cfg.enable { systemd.services.acpid = { description = "ACPI Daemon"; + documentation = [ "man:acpid(8)" ]; wantedBy = [ "multi-user.target" ]; - after = [ "systemd-udev-settle.service" ]; - - path = [ pkgs.acpid ]; serviceConfig = { - Type = "forking"; + ExecStart = escapeShellArgs + ([ "${pkgs.acpid}/bin/acpid" + "--foreground" + "--netlink" + "--confdir" "${acpiConfDir}" + ] ++ optional cfg.logEvents "--logevents" + ); }; - unitConfig = { ConditionVirtualization = "!systemd-nspawn"; ConditionPathExists = [ "/proc/acpi" ]; }; - script = "acpid ${optionalString config.services.acpid.logEvents "--logevents"} --confdir ${acpiConfDir}"; }; }; diff --git a/nixpkgs/nixos/modules/services/hardware/bluetooth.nix b/nixpkgs/nixos/modules/services/hardware/bluetooth.nix index 6f5a6d3bf288..08ad90126b1d 100644 --- a/nixpkgs/nixos/modules/services/hardware/bluetooth.nix +++ b/nixpkgs/nixos/modules/services/hardware/bluetooth.nix @@ -1,12 +1,39 @@ { config, lib, pkgs, ... }: - -with lib; - let cfg = config.hardware.bluetooth; - bluez-bluetooth = cfg.package; + package = cfg.package; + + inherit (lib) + mkDefault mkEnableOption mkIf mkOption + mkRenamedOptionModule mkRemovedOptionModule + concatStringsSep escapeShellArgs + optional optionals optionalAttrs recursiveUpdate types; + + cfgFmt = pkgs.formats.ini { }; + + # bluez will complain if some of the sections are not found, so just make them + # empty (but present in the file) for now + defaults = { + General.ControllerMode = "dual"; + Controller = { }; + GATT = { }; + Policy.AutoEnable = cfg.powerOnBoot; + }; + + hasDisabledPlugins = builtins.length cfg.disabledPlugins > 0; -in { +in +{ + imports = [ + (mkRenamedOptionModule [ "hardware" "bluetooth" "config" ] [ "hardware" "bluetooth" "settings" ]) + (mkRemovedOptionModule [ "hardware" "bluetooth" "extraConfig" ] '' + Use hardware.bluetooth.settings instead. + + This is part of the general move to use structured settings instead of raw + text for config as introduced by RFC0042: + https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md + '') + ]; ###### interface @@ -18,7 +45,7 @@ in { hsphfpd.enable = mkEnableOption "support for hsphfpd[-prototype] implementation"; powerOnBoot = mkOption { - type = types.bool; + type = types.bool; default = true; description = "Whether to power up the default Bluetooth controller on boot."; }; @@ -38,8 +65,15 @@ in { ''; }; - config = mkOption { - type = with types; attrsOf (attrsOf (oneOf [ bool int str ])); + disabledPlugins = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "Built-in plugins to disable"; + }; + + settings = mkOption { + type = cfgFmt.type; + default = { }; example = { General = { ControllerMode = "bredr"; @@ -47,79 +81,65 @@ in { }; description = "Set configuration for system-wide bluetooth (/etc/bluetooth/main.conf)."; }; - - extraConfig = mkOption { - type = with types; nullOr lines; - default = null; - example = '' - [General] - ControllerMode = bredr - ''; - description = '' - Set additional configuration for system-wide bluetooth (/etc/bluetooth/main.conf). - ''; - }; }; - }; ###### implementation config = mkIf cfg.enable { - warnings = optional (cfg.extraConfig != null) "hardware.bluetooth.`extraConfig` is deprecated, please use hardware.bluetooth.`config`."; + environment.systemPackages = [ package ] + ++ optional cfg.hsphfpd.enable pkgs.hsphfpd; - hardware.bluetooth.config = { - Policy = { - AutoEnable = mkDefault cfg.powerOnBoot; - }; - }; - - environment.systemPackages = [ bluez-bluetooth ] - ++ optionals cfg.hsphfpd.enable [ pkgs.hsphfpd ]; - - environment.etc."bluetooth/main.conf"= { - source = pkgs.writeText "main.conf" - (generators.toINI { } cfg.config + optionalString (cfg.extraConfig != null) cfg.extraConfig); - }; - - services.udev.packages = [ bluez-bluetooth ]; - services.dbus.packages = [ bluez-bluetooth ] - ++ optionals cfg.hsphfpd.enable [ pkgs.hsphfpd ]; - systemd.packages = [ bluez-bluetooth ]; + environment.etc."bluetooth/main.conf".source = + cfgFmt.generate "main.conf" (recursiveUpdate defaults cfg.settings); + services.udev.packages = [ package ]; + services.dbus.packages = [ package ] + ++ optional cfg.hsphfpd.enable pkgs.hsphfpd; + systemd.packages = [ package ]; systemd.services = { - bluetooth = { + bluetooth = + let + # `man bluetoothd` will refer to main.conf in the nix store but bluez + # will in fact load the configuration file at /etc/bluetooth/main.conf + # so force it here to avoid any ambiguity and things suddenly breaking + # if/when the bluez derivation is changed. + args = [ "-f" "/etc/bluetooth/main.conf" ] + ++ optional hasDisabledPlugins + "--noplugin=${concatStringsSep "," cfg.disabledPlugins}"; + in + { + wantedBy = [ "bluetooth.target" ]; + aliases = [ "dbus-org.bluez.service" ]; + serviceConfig.ExecStart = [ + "" + "${package}/libexec/bluetooth/bluetoothd ${escapeShellArgs args}" + ]; + # restarting can leave people without a mouse/keyboard + unitConfig.X-RestartIfChanged = false; + }; + } + // (optionalAttrs cfg.hsphfpd.enable { + hsphfpd = { + after = [ "bluetooth.service" ]; + requires = [ "bluetooth.service" ]; wantedBy = [ "bluetooth.target" ]; - aliases = [ "dbus-org.bluez.service" ]; - # restarting can leave people without a mouse/keyboard - unitConfig.X-RestartIfChanged = false; + + description = "A prototype implementation used for connecting HSP/HFP Bluetooth devices"; + serviceConfig.ExecStart = "${pkgs.hsphfpd}/bin/hsphfpd.pl"; }; - } - // (optionalAttrs cfg.hsphfpd.enable { - hsphfpd = { - after = [ "bluetooth.service" ]; - requires = [ "bluetooth.service" ]; - wantedBy = [ "multi-user.target" ]; - - description = "A prototype implementation used for connecting HSP/HFP Bluetooth devices"; - serviceConfig.ExecStart = "${pkgs.hsphfpd}/bin/hsphfpd.pl"; - }; - }) - ; + }); systemd.user.services = { obex.aliases = [ "dbus-org.bluez.obex.service" ]; } - // (optionalAttrs cfg.hsphfpd.enable { - telephony_client = { - wantedBy = [ "default.target"]; - - description = "telephony_client for hsphfpd"; - serviceConfig.ExecStart = "${pkgs.hsphfpd}/bin/telephony_client.pl"; - }; - }) - ; + // optionalAttrs cfg.hsphfpd.enable { + telephony_client = { + wantedBy = [ "default.target" ]; + description = "telephony_client for hsphfpd"; + serviceConfig.ExecStart = "${pkgs.hsphfpd}/bin/telephony_client.pl"; + }; + }; }; - } diff --git a/nixpkgs/nixos/modules/services/hardware/sane.nix b/nixpkgs/nixos/modules/services/hardware/sane.nix index 03070a8f9e7c..e5a01a8a27d9 100644 --- a/nixpkgs/nixos/modules/services/hardware/sane.nix +++ b/nixpkgs/nixos/modules/services/hardware/sane.nix @@ -4,9 +4,7 @@ with lib; let - pkg = if config.hardware.sane.snapshot - then pkgs.sane-backends-git - else pkgs.sane-backends; + pkg = pkgs.sane-backends; sanedConf = pkgs.writeTextFile { name = "saned.conf"; @@ -32,7 +30,7 @@ let }; backends = [ pkg netConf ] ++ optional config.services.saned.enable sanedConf ++ config.hardware.sane.extraBackends; - saneConfig = pkgs.mkSaneConfig { paths = backends; }; + saneConfig = pkgs.mkSaneConfig { paths = backends; inherit (config.hardware.sane) disabledDefaultBackends; }; enabled = config.hardware.sane.enable || config.services.saned.enable; @@ -75,6 +73,16 @@ in example = literalExample "[ pkgs.hplipWithPlugin ]"; }; + hardware.sane.disabledDefaultBackends = mkOption { + type = types.listOf types.str; + default = []; + example = [ "v4l" ]; + description = '' + Names of backends which are enabled by default but should be disabled. + See <literal>$SANE_CONFIG_DIR/dll.conf</literal> for the list of possible names. + ''; + }; + hardware.sane.configDir = mkOption { type = types.str; internal = true; diff --git a/nixpkgs/nixos/modules/services/hardware/spacenavd.nix b/nixpkgs/nixos/modules/services/hardware/spacenavd.nix new file mode 100644 index 000000000000..cecc4d6f029b --- /dev/null +++ b/nixpkgs/nixos/modules/services/hardware/spacenavd.nix @@ -0,0 +1,26 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let cfg = config.hardware.spacenavd; + +in { + + options = { + hardware.spacenavd = { + enable = mkEnableOption "spacenavd to support 3DConnexion devices"; + }; + }; + + config = mkIf cfg.enable { + systemd.services.spacenavd = { + description = "Daemon for the Spacenavigator 6DOF mice by 3Dconnexion"; + after = [ "syslog.target" ]; + wantedBy = [ "graphical.target" ]; + serviceConfig = { + ExecStart = "${pkgs.spacenavd}/bin/spacenavd -d -l syslog"; + StandardError = "syslog"; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/hardware/tcsd.nix b/nixpkgs/nixos/modules/services/hardware/tcsd.nix index 68cb5d791aa3..0d36bce357ba 100644 --- a/nixpkgs/nixos/modules/services/hardware/tcsd.nix +++ b/nixpkgs/nixos/modules/services/hardware/tcsd.nix @@ -119,22 +119,31 @@ in environment.systemPackages = [ pkgs.trousers ]; -# system.activationScripts.tcsd = -# '' -# chown ${cfg.user}:${cfg.group} ${tcsdConf} -# ''; + services.udev.extraRules = '' + # Give tcsd ownership of all TPM devices + KERNEL=="tpm[0-9]*", MODE="0660", OWNER="${cfg.user}", GROUP="${cfg.group}" + # Tag TPM devices to create a .device unit for tcsd to depend on + ACTION=="add", KERNEL=="tpm[0-9]*", TAG+="systemd" + ''; + + systemd.tmpfiles.rules = [ + # Initialise the state directory + "d ${cfg.stateDir} 0770 ${cfg.user} ${cfg.group} - -" + ]; systemd.services.tcsd = { - description = "TCSD"; - after = [ "systemd-udev-settle.service" ]; + description = "Manager for Trusted Computing resources"; + documentation = [ "man:tcsd(8)" ]; + + requires = [ "dev-tpm0.device" ]; + after = [ "dev-tpm0.device" ]; wantedBy = [ "multi-user.target" ]; - path = [ pkgs.trousers ]; - preStart = - '' - mkdir -m 0700 -p ${cfg.stateDir} - chown -R ${cfg.user}:${cfg.group} ${cfg.stateDir} - ''; - serviceConfig.ExecStart = "${pkgs.trousers}/sbin/tcsd -f -c ${tcsdConf}"; + + serviceConfig = { + User = cfg.user; + Group = cfg.group; + ExecStart = "${pkgs.trousers}/sbin/tcsd -f -c ${tcsdConf}"; + }; }; users.users = optionalAttrs (cfg.user == "tss") { diff --git a/nixpkgs/nixos/modules/services/hardware/trezord.nix b/nixpkgs/nixos/modules/services/hardware/trezord.nix index 8c609bbf825b..a65d4250c2e5 100644 --- a/nixpkgs/nixos/modules/services/hardware/trezord.nix +++ b/nixpkgs/nixos/modules/services/hardware/trezord.nix @@ -48,7 +48,7 @@ in { systemd.services.trezord = { description = "Trezor Bridge"; - after = [ "systemd-udev-settle.service" "network.target" ]; + after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; path = []; serviceConfig = { diff --git a/nixpkgs/nixos/modules/services/hardware/udev.nix b/nixpkgs/nixos/modules/services/hardware/udev.nix index 63027f7744dc..d48b5444677c 100644 --- a/nixpkgs/nixos/modules/services/hardware/udev.nix +++ b/nixpkgs/nixos/modules/services/hardware/udev.nix @@ -202,13 +202,27 @@ in ''; }; - extraRules = mkOption { + initrdRules = mkOption { default = ""; example = '' SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:1D:60:B9:6D:4F", KERNEL=="eth*", NAME="my_fast_network_card" ''; type = types.lines; description = '' + <command>udev</command> rules to include in the initrd + <emphasis>only</emphasis>. They'll be written into file + <filename>99-local.rules</filename>. Thus they are read and applied + after the essential initrd rules. + ''; + }; + + extraRules = mkOption { + default = ""; + example = '' + ENV{ID_VENDOR_ID}=="046d", ENV{ID_MODEL_ID}=="0825", ENV{PULSE_IGNORE}="1" + ''; + type = types.lines; + description = '' Additional <command>udev</command> rules. They'll be written into file <filename>99-local.rules</filename>. Thus they are read and applied after all other rules. @@ -284,6 +298,13 @@ in boot.kernelParams = mkIf (!config.networking.usePredictableInterfaceNames) [ "net.ifnames=0" ]; + boot.initrd.extraUdevRulesCommands = optionalString (cfg.initrdRules != "") + '' + cat <<'EOF' > $out/99-local.rules + ${cfg.initrdRules} + EOF + ''; + environment.etc = { "udev/rules.d".source = udevRules; diff --git a/nixpkgs/nixos/modules/services/hardware/xow.nix b/nixpkgs/nixos/modules/services/hardware/xow.nix index a18d60ad83be..311181176bd8 100644 --- a/nixpkgs/nixos/modules/services/hardware/xow.nix +++ b/nixpkgs/nixos/modules/services/hardware/xow.nix @@ -10,7 +10,10 @@ in { config = lib.mkIf cfg.enable { hardware.uinput.enable = true; + boot.extraModprobeConfig = lib.readFile "${pkgs.xow}/lib/modprobe.d/xow-blacklist.conf"; + systemd.packages = [ pkgs.xow ]; + systemd.services.xow.wantedBy = [ "multi-user.target" ]; services.udev.packages = [ pkgs.xow ]; }; diff --git a/nixpkgs/nixos/modules/services/logging/graylog.nix b/nixpkgs/nixos/modules/services/logging/graylog.nix index a889a44d4b2b..af70d27fcf99 100644 --- a/nixpkgs/nixos/modules/services/logging/graylog.nix +++ b/nixpkgs/nixos/modules/services/logging/graylog.nix @@ -39,7 +39,6 @@ in type = types.package; default = pkgs.graylog; defaultText = "pkgs.graylog"; - example = literalExample "pkgs.graylog"; description = "Graylog package to use."; }; @@ -138,14 +137,13 @@ in "d '${cfg.messageJournalDir}' - ${cfg.user} - - -" ]; - systemd.services.graylog = with pkgs; { + systemd.services.graylog = { description = "Graylog Server"; wantedBy = [ "multi-user.target" ]; environment = { - JAVA_HOME = jre; GRAYLOG_CONF = "${confFile}"; }; - path = [ pkgs.jre_headless pkgs.which pkgs.procps ]; + path = [ pkgs.which pkgs.procps ]; preStart = '' rm -rf /var/lib/graylog/plugins || true mkdir -p /var/lib/graylog/plugins -m 755 diff --git a/nixpkgs/nixos/modules/services/logging/logstash.nix b/nixpkgs/nixos/modules/services/logging/logstash.nix index a4fc315d080d..7a2f5681612c 100644 --- a/nixpkgs/nixos/modules/services/logging/logstash.nix +++ b/nixpkgs/nixos/modules/services/logging/logstash.nix @@ -159,10 +159,9 @@ in ###### implementation config = mkIf cfg.enable { - systemd.services.logstash = with pkgs; { + systemd.services.logstash = { description = "Logstash Daemon"; wantedBy = [ "multi-user.target" ]; - environment = { JAVA_HOME = jre; }; path = [ pkgs.bash ]; serviceConfig = { ExecStartPre = ''${pkgs.coreutils}/bin/mkdir -p "${cfg.dataDir}" ; ${pkgs.coreutils}/bin/chmod 700 "${cfg.dataDir}"''; diff --git a/nixpkgs/nixos/modules/services/logging/vector.nix b/nixpkgs/nixos/modules/services/logging/vector.nix index a7c54ad75fde..be36b2a41bba 100644 --- a/nixpkgs/nixos/modules/services/logging/vector.nix +++ b/nixpkgs/nixos/modules/services/logging/vector.nix @@ -3,7 +3,8 @@ with lib; let cfg = config.services.vector; -in { +in +{ options.services.vector = { enable = mkEnableOption "Vector"; @@ -37,25 +38,27 @@ in { wantedBy = [ "multi-user.target" ]; after = [ "network-online.target" ]; requires = [ "network-online.target" ]; - serviceConfig = let - format = pkgs.formats.toml { }; - conf = format.generate "vector.toml" cfg.settings; - validateConfig = file: - pkgs.runCommand "validate-vector-conf" { } '' - ${pkgs.vector}/bin/vector validate --no-topology --no-environment "${file}" - ln -s "${file}" "$out" - ''; - in { - ExecStart = "${pkgs.vector}/bin/vector --config ${validateConfig conf}"; - User = "vector"; - Group = "vector"; - Restart = "no"; - StateDirectory = "vector"; - ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; - AmbientCapabilities = "CAP_NET_BIND_SERVICE"; - # This group is required for accessing journald. - SupplementaryGroups = mkIf cfg.journaldAccess "systemd-journal"; - }; + serviceConfig = + let + format = pkgs.formats.toml { }; + conf = format.generate "vector.toml" cfg.settings; + validateConfig = file: + pkgs.runCommand "validate-vector-conf" { } '' + ${pkgs.vector}/bin/vector validate --no-environment "${file}" + ln -s "${file}" "$out" + ''; + in + { + ExecStart = "${pkgs.vector}/bin/vector --config ${validateConfig conf}"; + User = "vector"; + Group = "vector"; + Restart = "no"; + StateDirectory = "vector"; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + AmbientCapabilities = "CAP_NET_BIND_SERVICE"; + # This group is required for accessing journald. + SupplementaryGroups = mkIf cfg.journaldAccess "systemd-journal"; + }; }; }; } diff --git a/nixpkgs/nixos/modules/services/mail/mlmmj.nix b/nixpkgs/nixos/modules/services/mail/mlmmj.nix index d58d93c4214c..fd74f2dc5f07 100644 --- a/nixpkgs/nixos/modules/services/mail/mlmmj.nix +++ b/nixpkgs/nixos/modules/services/mail/mlmmj.nix @@ -16,7 +16,14 @@ let alias = domain: list: "${list}: \"|${pkgs.mlmmj}/bin/mlmmj-receive -L ${listDir domain list}/\""; subjectPrefix = list: "[${list}]"; listAddress = domain: list: "${list}@${domain}"; - customHeaders = domain: list: [ "List-Id: ${list}" "Reply-To: ${list}@${domain}" ]; + customHeaders = domain: list: [ + "List-Id: ${list}" + "Reply-To: ${list}@${domain}" + "List-Post: <mailto:${list}@${domain}>" + "List-Help: <mailto:${list}+help@${domain}>" + "List-Subscribe: <mailto:${list}+subscribe@${domain}>" + "List-Unsubscribe: <mailto:${list}+unsubscribe@${domain}>" + ]; footer = domain: list: "To unsubscribe send a mail to ${list}+unsubscribe@${domain}"; createList = d: l: let ctlDir = listCtl d l; in @@ -110,17 +117,29 @@ in services.postfix = { enable = true; recipientDelimiter= "+"; - extraMasterConf = '' - mlmmj unix - n n - - pipe flags=ORhu user=mlmmj argv=${pkgs.mlmmj}/bin/mlmmj-receive -F -L ${spoolDir}/$nexthop - ''; + masterConfig.mlmmj = { + type = "unix"; + private = true; + privileged = true; + chroot = false; + wakeup = 0; + command = "pipe"; + args = [ + "flags=ORhu" + "user=mlmmj" + "argv=${pkgs.mlmmj}/bin/mlmmj-receive" + "-F" + "-L" + "${spoolDir}/$nexthop" + ]; + }; extraAliases = concatMapLines (alias cfg.listDomain) cfg.mailLists; - extraConfig = '' - transport_maps = hash:${stateDir}/transports - virtual_alias_maps = hash:${stateDir}/virtuals - propagate_unmatched_extensions = virtual - ''; + extraConfig = "propagate_unmatched_extensions = virtual"; + + virtual = concatMapLines (virtual cfg.listDomain) cfg.mailLists; + transport = concatMapLines (transport cfg.listDomain) cfg.mailLists; }; environment.systemPackages = [ pkgs.mlmmj ]; @@ -129,10 +148,8 @@ in ${pkgs.coreutils}/bin/mkdir -p ${stateDir} ${spoolDir}/${cfg.listDomain} ${pkgs.coreutils}/bin/chown -R ${cfg.user}:${cfg.group} ${spoolDir} ${concatMapLines (createList cfg.listDomain) cfg.mailLists} - echo "${concatMapLines (virtual cfg.listDomain) cfg.mailLists}" > ${stateDir}/virtuals - echo "${concatMapLines (transport cfg.listDomain) cfg.mailLists}" > ${stateDir}/transports - ${pkgs.postfix}/bin/postmap ${stateDir}/virtuals - ${pkgs.postfix}/bin/postmap ${stateDir}/transports + ${pkgs.postfix}/bin/postmap /etc/postfix/virtual + ${pkgs.postfix}/bin/postmap /etc/postfix/transport ''; systemd.services.mlmmj-maintd = { diff --git a/nixpkgs/nixos/modules/services/misc/apache-kafka.nix b/nixpkgs/nixos/modules/services/misc/apache-kafka.nix index f3a650a260f1..69dfadfe54e0 100644 --- a/nixpkgs/nixos/modules/services/misc/apache-kafka.nix +++ b/nixpkgs/nixos/modules/services/misc/apache-kafka.nix @@ -90,19 +90,7 @@ in { jvmOptions = mkOption { description = "Extra command line options for the JVM running Kafka."; - default = [ - "-server" - "-Xmx1G" - "-Xms1G" - "-XX:+UseCompressedOops" - "-XX:+UseParNewGC" - "-XX:+UseConcMarkSweepGC" - "-XX:+CMSClassUnloadingEnabled" - "-XX:+CMSScavengeBeforeRemark" - "-XX:+DisableExplicitGC" - "-Djava.awt.headless=true" - "-Djava.net.preferIPv4Stack=true" - ]; + default = []; type = types.listOf types.str; example = [ "-Djava.net.preferIPv4Stack=true" @@ -118,6 +106,13 @@ in { type = types.package; }; + jre = mkOption { + description = "The JRE with which to run Kafka"; + default = cfg.package.passthru.jre; + defaultText = "pkgs.apacheKafka.passthru.jre"; + type = types.package; + }; + }; config = mkIf cfg.enable { @@ -138,7 +133,7 @@ in { after = [ "network.target" ]; serviceConfig = { ExecStart = '' - ${pkgs.jre}/bin/java \ + ${cfg.jre}/bin/java \ -cp "${cfg.package}/libs/*" \ -Dlog4j.configuration=file:${logConfig} \ ${toString cfg.jvmOptions} \ diff --git a/nixpkgs/nixos/modules/services/misc/defaultUnicornConfig.rb b/nixpkgs/nixos/modules/services/misc/defaultUnicornConfig.rb deleted file mode 100644 index 0b58c59c7a51..000000000000 --- a/nixpkgs/nixos/modules/services/misc/defaultUnicornConfig.rb +++ /dev/null @@ -1,69 +0,0 @@ -worker_processes 3 - -listen ENV["UNICORN_PATH"] + "/tmp/sockets/gitlab.socket", :backlog => 1024 -listen "/run/gitlab/gitlab.socket", :backlog => 1024 - -working_directory ENV["GITLAB_PATH"] - -pid ENV["UNICORN_PATH"] + "/tmp/pids/unicorn.pid" - -timeout 60 - -# combine Ruby 2.0.0dev or REE with "preload_app true" for memory savings -# http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow -preload_app true -GC.respond_to?(:copy_on_write_friendly=) and - GC.copy_on_write_friendly = true - -check_client_connection false - -before_fork do |server, worker| - # the following is highly recommended for Rails + "preload_app true" - # as there's no need for the master process to hold a connection - defined?(ActiveRecord::Base) and - ActiveRecord::Base.connection.disconnect! - - # The following is only recommended for memory/DB-constrained - # installations. It is not needed if your system can house - # twice as many worker_processes as you have configured. - # - # This allows a new master process to incrementally - # phase out the old master process with SIGTTOU to avoid a - # thundering herd (especially in the "preload_app false" case) - # when doing a transparent upgrade. The last worker spawned - # will then kill off the old master process with a SIGQUIT. - old_pid = "#{server.config[:pid]}.oldbin" - if old_pid != server.pid - begin - sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU - Process.kill(sig, File.read(old_pid).to_i) - rescue Errno::ENOENT, Errno::ESRCH - end - end - - # Throttle the master from forking too quickly by sleeping. Due - # to the implementation of standard Unix signal handlers, this - # helps (but does not completely) prevent identical, repeated signals - # from being lost when the receiving process is busy. - # sleep 1 -end - -after_fork do |server, worker| - # per-process listener ports for debugging/admin/migrations - # addr = "127.0.0.1:#{9293 + worker.nr}" - # server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true) - - # the following is *required* for Rails + "preload_app true", - defined?(ActiveRecord::Base) and - ActiveRecord::Base.establish_connection - - # reset prometheus client, this will cause any opened metrics files to be closed - defined?(::Prometheus::Client.reinitialize_on_pid_change) && - Prometheus::Client.reinitialize_on_pid_change - - # if preload_app is true, then you may also want to check and - # restart any other shared sockets/descriptors such as Memcached, - # and Redis. TokyoCabinet file handles are safe to reuse - # between any number of forked children (assuming your kernel - # correctly implements pread()/pwrite() system calls) -end diff --git a/nixpkgs/nixos/modules/services/misc/disnix.nix b/nixpkgs/nixos/modules/services/misc/disnix.nix new file mode 100644 index 000000000000..41483d80a2dd --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/disnix.nix @@ -0,0 +1,98 @@ +# Disnix server +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.disnix; + +in + +{ + + ###### interface + + options = { + + services.disnix = { + + enable = mkEnableOption "Disnix"; + + enableMultiUser = mkOption { + type = types.bool; + default = true; + description = "Whether to support multi-user mode by enabling the Disnix D-Bus service"; + }; + + useWebServiceInterface = mkEnableOption "the DisnixWebService interface running on Apache Tomcat"; + + package = mkOption { + type = types.path; + description = "The Disnix package"; + default = pkgs.disnix; + defaultText = "pkgs.disnix"; + }; + + enableProfilePath = mkEnableOption "exposing the Disnix profiles in the system's PATH"; + + profiles = mkOption { + type = types.listOf types.string; + default = [ "default" ]; + example = [ "default" ]; + description = "Names of the Disnix profiles to expose in the system's PATH"; + }; + }; + + }; + + ###### implementation + + config = mkIf cfg.enable { + dysnomia.enable = true; + + environment.systemPackages = [ pkgs.disnix ] ++ optional cfg.useWebServiceInterface pkgs.DisnixWebService; + environment.variables.PATH = lib.optionals cfg.enableProfilePath (map (profileName: "/nix/var/nix/profiles/disnix/${profileName}/bin" ) cfg.profiles); + + services.dbus.enable = true; + services.dbus.packages = [ pkgs.disnix ]; + + services.tomcat.enable = cfg.useWebServiceInterface; + services.tomcat.extraGroups = [ "disnix" ]; + services.tomcat.javaOpts = "${optionalString cfg.useWebServiceInterface "-Djava.library.path=${pkgs.libmatthew_java}/lib/jni"} "; + services.tomcat.sharedLibs = optional cfg.useWebServiceInterface "${pkgs.DisnixWebService}/share/java/DisnixConnection.jar" + ++ optional cfg.useWebServiceInterface "${pkgs.dbus_java}/share/java/dbus.jar"; + services.tomcat.webapps = optional cfg.useWebServiceInterface pkgs.DisnixWebService; + + users.groups.disnix.gid = config.ids.gids.disnix; + + systemd.services = { + disnix = mkIf cfg.enableMultiUser { + description = "Disnix server"; + wants = [ "dysnomia.target" ]; + wantedBy = [ "multi-user.target" ]; + after = [ "dbus.service" ] + ++ optional config.services.httpd.enable "httpd.service" + ++ optional config.services.mysql.enable "mysql.service" + ++ optional config.services.postgresql.enable "postgresql.service" + ++ optional config.services.tomcat.enable "tomcat.service" + ++ optional config.services.svnserve.enable "svnserve.service" + ++ optional config.services.mongodb.enable "mongodb.service" + ++ optional config.services.influxdb.enable "influxdb.service"; + + restartIfChanged = false; + + path = [ config.nix.package cfg.package config.dysnomia.package "/run/current-system/sw" ]; + + environment = { + HOME = "/root"; + } + // (if config.environment.variables ? DYSNOMIA_CONTAINERS_PATH then { inherit (config.environment.variables) DYSNOMIA_CONTAINERS_PATH; } else {}) + // (if config.environment.variables ? DYSNOMIA_MODULES_PATH then { inherit (config.environment.variables) DYSNOMIA_MODULES_PATH; } else {}); + + serviceConfig.ExecStart = "${cfg.package}/bin/disnix-service"; + }; + + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/dysnomia.nix b/nixpkgs/nixos/modules/services/misc/dysnomia.nix new file mode 100644 index 000000000000..333ba651cde2 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/dysnomia.nix @@ -0,0 +1,259 @@ +{pkgs, lib, config, ...}: + +with lib; + +let + cfg = config.dysnomia; + + printProperties = properties: + concatMapStrings (propertyName: + let + property = properties.${propertyName}; + in + if isList property then "${propertyName}=(${lib.concatMapStrings (elem: "\"${toString elem}\" ") (properties.${propertyName})})\n" + else "${propertyName}=\"${toString property}\"\n" + ) (builtins.attrNames properties); + + properties = pkgs.stdenv.mkDerivation { + name = "dysnomia-properties"; + buildCommand = '' + cat > $out << "EOF" + ${printProperties cfg.properties} + EOF + ''; + }; + + containersDir = pkgs.stdenv.mkDerivation { + name = "dysnomia-containers"; + buildCommand = '' + mkdir -p $out + cd $out + + ${concatMapStrings (containerName: + let + containerProperties = cfg.containers.${containerName}; + in + '' + cat > ${containerName} <<EOF + ${printProperties containerProperties} + type=${containerName} + EOF + '' + ) (builtins.attrNames cfg.containers)} + ''; + }; + + linkMutableComponents = {containerName}: + '' + mkdir ${containerName} + + ${concatMapStrings (componentName: + let + component = cfg.components.${containerName}.${componentName}; + in + "ln -s ${component} ${containerName}/${componentName}\n" + ) (builtins.attrNames (cfg.components.${containerName} or {}))} + ''; + + componentsDir = pkgs.stdenv.mkDerivation { + name = "dysnomia-components"; + buildCommand = '' + mkdir -p $out + cd $out + + ${concatMapStrings (containerName: + linkMutableComponents { inherit containerName; } + ) (builtins.attrNames cfg.components)} + ''; + }; + + dysnomiaFlags = { + enableApacheWebApplication = config.services.httpd.enable; + enableAxis2WebService = config.services.tomcat.axis2.enable; + enableDockerContainer = config.virtualisation.docker.enable; + enableEjabberdDump = config.services.ejabberd.enable; + enableMySQLDatabase = config.services.mysql.enable; + enablePostgreSQLDatabase = config.services.postgresql.enable; + enableTomcatWebApplication = config.services.tomcat.enable; + enableMongoDatabase = config.services.mongodb.enable; + enableSubversionRepository = config.services.svnserve.enable; + enableInfluxDatabase = config.services.influxdb.enable; + }; +in +{ + options = { + dysnomia = { + + enable = mkOption { + type = types.bool; + default = false; + description = "Whether to enable Dysnomia"; + }; + + enableAuthentication = mkOption { + type = types.bool; + default = false; + description = "Whether to publish privacy-sensitive authentication credentials"; + }; + + package = mkOption { + type = types.path; + description = "The Dysnomia package"; + }; + + properties = mkOption { + description = "An attribute set in which each attribute represents a machine property. Optionally, these values can be shell substitutions."; + default = {}; + }; + + containers = mkOption { + description = "An attribute set in which each key represents a container and each value an attribute set providing its configuration properties"; + default = {}; + }; + + components = mkOption { + description = "An atttribute set in which each key represents a container and each value an attribute set in which each key represents a component and each value a derivation constructing its initial state"; + default = {}; + }; + + extraContainerProperties = mkOption { + description = "An attribute set providing additional container settings in addition to the default properties"; + default = {}; + }; + + extraContainerPaths = mkOption { + description = "A list of paths containing additional container configurations that are added to the search folders"; + default = []; + }; + + extraModulePaths = mkOption { + description = "A list of paths containing additional modules that are added to the search folders"; + default = []; + }; + + enableLegacyModules = mkOption { + type = types.bool; + default = true; + description = "Whether to enable Dysnomia legacy process and wrapper modules"; + }; + }; + }; + + config = mkIf cfg.enable { + + environment.etc = { + "dysnomia/containers" = { + source = containersDir; + }; + "dysnomia/components" = { + source = componentsDir; + }; + "dysnomia/properties" = { + source = properties; + }; + }; + + environment.variables = { + DYSNOMIA_STATEDIR = "/var/state/dysnomia-nixos"; + DYSNOMIA_CONTAINERS_PATH = "${lib.concatMapStrings (containerPath: "${containerPath}:") cfg.extraContainerPaths}/etc/dysnomia/containers"; + DYSNOMIA_MODULES_PATH = "${lib.concatMapStrings (modulePath: "${modulePath}:") cfg.extraModulePaths}/etc/dysnomia/modules"; + }; + + environment.systemPackages = [ cfg.package ]; + + dysnomia.package = pkgs.dysnomia.override (origArgs: dysnomiaFlags // lib.optionalAttrs (cfg.enableLegacyModules) { + enableLegacy = builtins.trace '' + WARNING: Dysnomia has been configured to use the legacy 'process' and 'wrapper' + modules for compatibility reasons! If you rely on these modules, consider + migrating to better alternatives. + + More information: https://raw.githubusercontent.com/svanderburg/dysnomia/f65a9a84827bcc4024d6b16527098b33b02e4054/README-legacy.md + + If you have migrated already or don't rely on these Dysnomia modules, you can + disable legacy mode with the following NixOS configuration option: + + dysnomia.enableLegacyModules = false; + + In a future version of Dysnomia (and NixOS) the legacy option will go away! + '' true; + }); + + dysnomia.properties = { + hostname = config.networking.hostName; + inherit (config.nixpkgs.localSystem) system; + + supportedTypes = [ + "echo" + "fileset" + "process" + "wrapper" + + # These are not base modules, but they are still enabled because they work with technology that are always enabled in NixOS + "systemd-unit" + "sysvinit-script" + "nixos-configuration" + ] + ++ optional (dysnomiaFlags.enableApacheWebApplication) "apache-webapplication" + ++ optional (dysnomiaFlags.enableAxis2WebService) "axis2-webservice" + ++ optional (dysnomiaFlags.enableDockerContainer) "docker-container" + ++ optional (dysnomiaFlags.enableEjabberdDump) "ejabberd-dump" + ++ optional (dysnomiaFlags.enableInfluxDatabase) "influx-database" + ++ optional (dysnomiaFlags.enableMySQLDatabase) "mysql-database" + ++ optional (dysnomiaFlags.enablePostgreSQLDatabase) "postgresql-database" + ++ optional (dysnomiaFlags.enableTomcatWebApplication) "tomcat-webapplication" + ++ optional (dysnomiaFlags.enableMongoDatabase) "mongo-database" + ++ optional (dysnomiaFlags.enableSubversionRepository) "subversion-repository"; + }; + + dysnomia.containers = lib.recursiveUpdate ({ + process = {}; + wrapper = {}; + } + // lib.optionalAttrs (config.services.httpd.enable) { apache-webapplication = { + documentRoot = config.services.httpd.virtualHosts.localhost.documentRoot; + }; } + // lib.optionalAttrs (config.services.tomcat.axis2.enable) { axis2-webservice = {}; } + // lib.optionalAttrs (config.services.ejabberd.enable) { ejabberd-dump = { + ejabberdUser = config.services.ejabberd.user; + }; } + // lib.optionalAttrs (config.services.mysql.enable) { mysql-database = { + mysqlPort = config.services.mysql.port; + mysqlSocket = "/run/mysqld/mysqld.sock"; + } // lib.optionalAttrs cfg.enableAuthentication { + mysqlUsername = "root"; + }; + } + // lib.optionalAttrs (config.services.postgresql.enable) { postgresql-database = { + } // lib.optionalAttrs (cfg.enableAuthentication) { + postgresqlUsername = "postgres"; + }; + } + // lib.optionalAttrs (config.services.tomcat.enable) { tomcat-webapplication = { + tomcatPort = 8080; + }; } + // lib.optionalAttrs (config.services.mongodb.enable) { mongo-database = {}; } + // lib.optionalAttrs (config.services.influxdb.enable) { + influx-database = { + influxdbUsername = config.services.influxdb.user; + influxdbDataDir = "${config.services.influxdb.dataDir}/data"; + influxdbMetaDir = "${config.services.influxdb.dataDir}/meta"; + }; + } + // lib.optionalAttrs (config.services.svnserve.enable) { subversion-repository = { + svnBaseDir = config.services.svnserve.svnBaseDir; + }; }) cfg.extraContainerProperties; + + boot.extraSystemdUnitPaths = [ "/etc/systemd-mutable/system" ]; + + system.activationScripts.dysnomia = '' + mkdir -p /etc/systemd-mutable/system + if [ ! -f /etc/systemd-mutable/system/dysnomia.target ] + then + ( echo "[Unit]" + echo "Description=Services that are activated and deactivated by Dysnomia" + echo "After=final.target" + ) > /etc/systemd-mutable/system/dysnomia.target + fi + ''; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/etebase-server.nix b/nixpkgs/nixos/modules/services/misc/etebase-server.nix index d9d12698d79d..31e0952b5b98 100644 --- a/nixpkgs/nixos/modules/services/misc/etebase-server.nix +++ b/nixpkgs/nixos/modules/services/misc/etebase-server.nix @@ -8,31 +8,28 @@ let pythonEnv = pkgs.python3.withPackages (ps: with ps; [ etebase-server daphne ]); - dbConfig = { - sqlite3 = '' - engine = django.db.backends.sqlite3 - name = ${cfg.dataDir}/db.sqlite3 - ''; - }; - - defaultConfigIni = toString (pkgs.writeText "etebase-server.ini" '' - [global] - debug = false - secret_file = ${if cfg.secretFile != null then cfg.secretFile else ""} - media_root = ${cfg.dataDir}/media - - [allowed_hosts] - allowed_host1 = ${cfg.host} + iniFmt = pkgs.formats.ini {}; - [database] - ${dbConfig."${cfg.database.type}"} - ''); - - configIni = if cfg.customIni != null then cfg.customIni else defaultConfigIni; + configIni = iniFmt.generate "etebase-server.ini" cfg.settings; defaultUser = "etebase-server"; in { + imports = [ + (mkRemovedOptionModule + [ "services" "etebase-server" "customIni" ] + "Set the option `services.etebase-server.settings' instead.") + (mkRemovedOptionModule + [ "services" "etebase-server" "database" ] + "Set the option `services.etebase-server.settings.database' instead.") + (mkRenamedOptionModule + [ "services" "etebase-server" "secretFile" ] + [ "services" "etebase-server" "settings" "secret_file" ]) + (mkRenamedOptionModule + [ "services" "etebase-server" "host" ] + [ "services" "etebase-server" "settings" "allowed_hosts" "allowed_host1" ]) + ]; + options = { services.etebase-server = { enable = mkOption { @@ -42,21 +39,13 @@ in description = '' Whether to enable the Etebase server. - Once enabled you need to create an admin user using the - shell command <literal>etebase-server createsuperuser</literal>. + Once enabled you need to create an admin user by invoking the + shell command <literal>etebase-server createsuperuser</literal> with + the user specified by the <literal>user</literal> option or a superuser. Then you can login and create accounts on your-etebase-server.com/admin ''; }; - secretFile = mkOption { - default = null; - type = with types; nullOr str; - description = '' - The path to a file containing the secret - used as django's SECRET_KEY. - ''; - }; - dataDir = mkOption { type = types.str; default = "/var/lib/etebase-server"; @@ -77,15 +66,6 @@ in ''; }; - host = mkOption { - type = types.str; - default = "0.0.0.0"; - example = "localhost"; - description = '' - Host to listen on. - ''; - }; - unixSocket = mkOption { type = with types; nullOr str; default = null; @@ -93,42 +73,81 @@ in example = "/run/etebase-server/etebase-server.sock"; }; - database = { - type = mkOption { - type = types.enum [ "sqlite3" ]; - default = "sqlite3"; - description = '' - Database engine to use. - Currently only sqlite3 is supported. - Other options can be configured using <literal>extraConfig</literal>. - ''; + settings = mkOption { + type = lib.types.submodule { + freeformType = iniFmt.type; + + options = { + global = { + debug = mkOption { + type = types.bool; + default = false; + description = '' + Whether to set django's DEBUG flag. + ''; + }; + secret_file = mkOption { + type = with types; nullOr str; + default = null; + description = '' + The path to a file containing the secret + used as django's SECRET_KEY. + ''; + }; + static_root = mkOption { + type = types.str; + default = "${cfg.dataDir}/static"; + defaultText = "\${config.services.etebase-server.dataDir}/static"; + description = "The directory for static files."; + }; + media_root = mkOption { + type = types.str; + default = "${cfg.dataDir}/media"; + defaultText = "\${config.services.etebase-server.dataDir}/media"; + description = "The media directory."; + }; + }; + allowed_hosts = { + allowed_host1 = mkOption { + type = types.str; + default = "0.0.0.0"; + example = "localhost"; + description = '' + The main host that is allowed access. + ''; + }; + }; + database = { + engine = mkOption { + type = types.enum [ "django.db.backends.sqlite3" "django.db.backends.postgresql" ]; + default = "django.db.backends.sqlite3"; + description = "The database engine to use."; + }; + name = mkOption { + type = types.str; + default = "${cfg.dataDir}/db.sqlite3"; + defaultText = "\${config.services.etebase-server.dataDir}/db.sqlite3"; + description = "The database name."; + }; + }; + }; }; - }; - - customIni = mkOption { - type = with types; nullOr str; - default = null; + default = {}; description = '' - Custom etebase-server.ini. - - See <literal>etebase-src/etebase-server.ini.example</literal> for available options. - - Setting this option overrides the default config which is generated from the options - <literal>secretFile</literal>, <literal>host</literal> and <literal>database</literal>. - ''; - example = literalExample '' - [global] - debug = false - secret_file = /path/to/secret - media_root = /path/to/media - - [allowed_hosts] - allowed_host1 = example.com - - [database] - engine = django.db.backends.sqlite3 - name = db.sqlite3 + Configuration for <package>etebase-server</package>. Refer to + <link xlink:href="https://github.com/etesync/server/blob/master/etebase-server.ini.example" /> + and <link xlink:href="https://github.com/etesync/server/wiki" /> + for details on supported values. ''; + example = { + global = { + debug = true; + media_root = "/path/to/media"; + }; + allowed_hosts = { + allowed_host2 = "localhost"; + }; + }; }; user = mkOption { @@ -166,14 +185,15 @@ in WorkingDirectory = cfg.dataDir; }; environment = { - PYTHONPATH="${pythonEnv}/${pkgs.python3.sitePackages}"; - ETEBASE_EASY_CONFIG_PATH="${configIni}"; + PYTHONPATH = "${pythonEnv}/${pkgs.python3.sitePackages}"; + ETEBASE_EASY_CONFIG_PATH = configIni; }; preStart = '' # Auto-migrate on first run or if the package has changed versionFile="${cfg.dataDir}/src-version" if [[ $(cat "$versionFile" 2>/dev/null) != ${pkgs.etebase-server} ]]; then ${pythonEnv}/bin/etebase-server migrate + ${pythonEnv}/bin/etebase-server collectstatic echo ${pkgs.etebase-server} > "$versionFile" fi ''; diff --git a/nixpkgs/nixos/modules/services/misc/etesync-dav.nix b/nixpkgs/nixos/modules/services/misc/etesync-dav.nix new file mode 100644 index 000000000000..9d7cfda371b1 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/etesync-dav.nix @@ -0,0 +1,92 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.etesync-dav; +in + { + options.services.etesync-dav = { + enable = mkEnableOption "etesync-dav"; + + host = mkOption { + type = types.str; + default = "localhost"; + description = "The server host address."; + }; + + port = mkOption { + type = types.port; + default = 37358; + description = "The server host port."; + }; + + apiUrl = mkOption { + type = types.str; + default = "https://api.etesync.com/"; + description = "The url to the etesync API."; + }; + + openFirewall = mkOption { + default = false; + type = types.bool; + description = "Whether to open the firewall for the specified port."; + }; + + sslCertificate = mkOption { + type = types.nullOr types.path; + default = null; + example = "/var/etesync.crt"; + description = '' + Path to server SSL certificate. It will be copied into + etesync-dav's data directory. + ''; + }; + + sslCertificateKey = mkOption { + type = types.nullOr types.path; + default = null; + example = "/var/etesync.key"; + description = '' + Path to server SSL certificate key. It will be copied into + etesync-dav's data directory. + ''; + }; + }; + + config = mkIf cfg.enable { + networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ]; + + systemd.services.etesync-dav = { + description = "etesync-dav - A CalDAV and CardDAV adapter for EteSync"; + after = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + path = [ pkgs.etesync-dav ]; + environment = { + ETESYNC_LISTEN_ADDRESS = cfg.host; + ETESYNC_LISTEN_PORT = toString cfg.port; + ETESYNC_URL = cfg.apiUrl; + ETESYNC_DATA_DIR = "/var/lib/etesync-dav"; + }; + + serviceConfig = { + Type = "simple"; + DynamicUser = true; + StateDirectory = "etesync-dav"; + ExecStart = "${pkgs.etesync-dav}/bin/etesync-dav"; + ExecStartPre = mkIf (cfg.sslCertificate != null || cfg.sslCertificateKey != null) ( + pkgs.writers.writeBash "etesync-dav-copy-keys" '' + ${optionalString (cfg.sslCertificate != null) '' + cp ${toString cfg.sslCertificate} $STATE_DIRECTORY/etesync.crt + ''} + ${optionalString (cfg.sslCertificateKey != null) '' + cp ${toString cfg.sslCertificateKey} $STATE_DIRECTORY/etesync.key + ''} + '' + ); + Restart = "on-failure"; + RestartSec = "30min 1s"; + }; + }; + }; + } diff --git a/nixpkgs/nixos/modules/services/misc/gitlab.nix b/nixpkgs/nixos/modules/services/misc/gitlab.nix index de4d1bf1987a..f86653f3ead2 100644 --- a/nixpkgs/nixos/modules/services/misc/gitlab.nix +++ b/nixpkgs/nixos/modules/services/misc/gitlab.nix @@ -116,7 +116,12 @@ let omniauth.enabled = false; shared.path = "${cfg.statePath}/shared"; gitaly.client_path = "${cfg.packages.gitaly}/bin"; - backup.path = "${cfg.backupPath}"; + backup = { + path = cfg.backup.path; + keep_time = cfg.backup.keepTime; + } // (optionalAttrs (cfg.backup.uploadOptions != {}) { + upload = cfg.backup.uploadOptions; + }); gitlab_shell = { path = "${cfg.packages.gitlab-shell}"; hooks_path = "${cfg.statePath}/shell/hooks"; @@ -142,7 +147,7 @@ let gitlabEnv = { HOME = "${cfg.statePath}/home"; - UNICORN_PATH = "${cfg.statePath}/"; + PUMA_PATH = "${cfg.statePath}/"; GITLAB_PATH = "${cfg.packages.gitlab}/share/gitlab/"; SCHEMA = "${cfg.statePath}/db/structure.sql"; GITLAB_UPLOADS_PATH = "${cfg.statePath}/uploads"; @@ -196,6 +201,7 @@ let domain: "${cfg.smtp.domain}", ${optionalString (cfg.smtp.authentication != null) "authentication: :${cfg.smtp.authentication},"} enable_starttls_auto: ${boolToString cfg.smtp.enableStartTLSAuto}, + tls: ${boolToString cfg.smtp.tls}, ca_file: "/etc/ssl/certs/ca-certificates.crt", openssl_verify_mode: '${cfg.smtp.opensslVerifyMode}' } @@ -206,6 +212,7 @@ in { imports = [ (mkRenamedOptionModule [ "services" "gitlab" "stateDir" ] [ "services" "gitlab" "statePath" ]) + (mkRenamedOptionModule [ "services" "gitlab" "backupPath" ] [ "services" "gitlab" "backup" "path" ]) (mkRemovedOptionModule [ "services" "gitlab" "satelliteDir" ] "") ]; @@ -259,7 +266,7 @@ in { type = types.str; default = "/var/gitlab/state"; description = '' - Gitlab state directory. Configuration, repositories and + GitLab state directory. Configuration, repositories and logs, among other things, are stored here. The directory will be created automatically if it doesn't @@ -269,17 +276,108 @@ in { ''; }; - backupPath = mkOption { + backup.startAt = mkOption { + type = with types; either str (listOf str); + default = []; + example = "03:00"; + description = '' + The time(s) to run automatic backup of GitLab + state. Specified in systemd's time format; see + <citerefentry><refentrytitle>systemd.time</refentrytitle> + <manvolnum>7</manvolnum></citerefentry>. + ''; + }; + + backup.path = mkOption { type = types.str; default = cfg.statePath + "/backup"; - description = "Gitlab path for backups."; + description = "GitLab path for backups."; + }; + + backup.keepTime = mkOption { + type = types.int; + default = 0; + example = 48; + apply = x: x * 60 * 60; + description = '' + How long to keep the backups around, in + hours. <literal>0</literal> means <quote>keep + forever</quote>. + ''; + }; + + backup.skip = mkOption { + type = with types; + let value = enum [ + "db" + "uploads" + "builds" + "artifacts" + "lfs" + "registry" + "pages" + "repositories" + "tar" + ]; + in + either value (listOf value); + default = []; + example = [ "artifacts" "lfs" ]; + apply = x: if isString x then x else concatStringsSep "," x; + description = '' + Directories to exclude from the backup. The example excludes + CI artifacts and LFS objects from the backups. The + <literal>tar</literal> option skips the creation of a tar + file. + + Refer to <link xlink:href="https://docs.gitlab.com/ee/raketasks/backup_restore.html#excluding-specific-directories-from-the-backup"/> + for more information. + ''; + }; + + backup.uploadOptions = mkOption { + type = types.attrs; + default = {}; + example = literalExample '' + { + # Fog storage connection settings, see http://fog.io/storage/ + connection = { + provider = "AWS"; + region = "eu-north-1"; + aws_access_key_id = "AKIAXXXXXXXXXXXXXXXX"; + aws_secret_access_key = { _secret = config.deployment.keys.aws_access_key.path; }; + }; + + # The remote 'directory' to store your backups in. + # For S3, this would be the bucket name. + remote_directory = "my-gitlab-backups"; + + # Use multipart uploads when file size reaches 100MB, see + # http://docs.aws.amazon.com/AmazonS3/latest/dev/uploadobjusingmpu.html + multipart_chunk_size = 104857600; + + # Turns on AWS Server-Side Encryption with Amazon S3-Managed Keys for backups, this is optional + encryption = "AES256"; + + # Specifies Amazon S3 storage class to use for backups, this is optional + storage_class = "STANDARD"; + }; + ''; + description = '' + GitLab automatic upload specification. Tells GitLab to + upload the backup to a remote location when done. + + Attributes specified here are added under + <literal>production -> backup -> upload</literal> in + <filename>config/gitlab.yml</filename>. + ''; }; databaseHost = mkOption { type = types.str; default = ""; description = '' - Gitlab database hostname. An empty string means <quote>use + GitLab database hostname. An empty string means <quote>use local unix socket connection</quote>. ''; }; @@ -288,7 +386,7 @@ in { type = with types; nullOr path; default = null; description = '' - File containing the Gitlab database user password. + File containing the GitLab database user password. This should be a string, not a nix path, since nix paths are copied into the world-readable nix store. @@ -309,13 +407,13 @@ in { databaseName = mkOption { type = types.str; default = "gitlab"; - description = "Gitlab database name."; + description = "GitLab database name."; }; databaseUsername = mkOption { type = types.str; default = "gitlab"; - description = "Gitlab database user."; + description = "GitLab database user."; }; databasePool = mkOption { @@ -359,14 +457,14 @@ in { host = mkOption { type = types.str; default = config.networking.hostName; - description = "Gitlab host name. Used e.g. for copy-paste URLs."; + description = "GitLab host name. Used e.g. for copy-paste URLs."; }; port = mkOption { type = types.int; default = 8080; description = '' - Gitlab server port for copy-paste URLs, e.g. 80 or 443 if you're + GitLab server port for copy-paste URLs, e.g. 80 or 443 if you're service over https. ''; }; @@ -419,26 +517,26 @@ in { address = mkOption { type = types.str; default = "localhost"; - description = "Address of the SMTP server for Gitlab."; + description = "Address of the SMTP server for GitLab."; }; port = mkOption { type = types.int; - default = 465; - description = "Port of the SMTP server for Gitlab."; + default = 25; + description = "Port of the SMTP server for GitLab."; }; username = mkOption { type = with types; nullOr str; default = null; - description = "Username of the SMTP server for Gitlab."; + description = "Username of the SMTP server for GitLab."; }; passwordFile = mkOption { type = types.nullOr types.path; default = null; description = '' - File containing the password of the SMTP server for Gitlab. + File containing the password of the SMTP server for GitLab. This should be a string, not a nix path, since nix paths are copied into the world-readable nix store. @@ -454,7 +552,7 @@ in { authentication = mkOption { type = with types; nullOr str; default = null; - description = "Authentitcation type to use, see http://api.rubyonrails.org/classes/ActionMailer/Base.html"; + description = "Authentication type to use, see http://api.rubyonrails.org/classes/ActionMailer/Base.html"; }; enableStartTLSAuto = mkOption { @@ -463,6 +561,12 @@ in { description = "Whether to try to use StartTLS."; }; + tls = mkOption { + type = types.bool; + default = false; + description = "Whether to use TLS wrapper-mode."; + }; + opensslVerifyMode = mkOption { type = types.str; default = "peer"; @@ -641,6 +745,11 @@ in { environment.systemPackages = [ pkgs.git gitlab-rake gitlab-rails cfg.packages.gitlab-shell ]; + systemd.targets.gitlab = { + description = "Common target for all GitLab services."; + wantedBy = [ "multi-user.target" ]; + }; + # Redis is required for the sidekiq queue runner. services.redis.enable = mkDefault true; @@ -655,36 +764,45 @@ in { # here. systemd.services.gitlab-postgresql = let pgsql = config.services.postgresql; in mkIf databaseActuallyCreateLocally { after = [ "postgresql.service" ]; - wantedBy = [ "multi-user.target" ]; - path = [ pgsql.package ]; + bindsTo = [ "postgresql.service" ]; + wantedBy = [ "gitlab.target" ]; + partOf = [ "gitlab.target" ]; + path = [ + pgsql.package + pkgs.util-linux + ]; script = '' set -eu - PSQL="${pkgs.util-linux}/bin/runuser -u ${pgsql.superUser} -- psql --port=${toString pgsql.port}" + PSQL() { + psql --port=${toString pgsql.port} "$@" + } - $PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '${cfg.databaseName}'" | grep -q 1 || $PSQL -tAc 'CREATE DATABASE "${cfg.databaseName}" OWNER "${cfg.databaseUsername}"' - current_owner=$($PSQL -tAc "SELECT pg_catalog.pg_get_userbyid(datdba) FROM pg_catalog.pg_database WHERE datname = '${cfg.databaseName}'") + PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '${cfg.databaseName}'" | grep -q 1 || PSQL -tAc 'CREATE DATABASE "${cfg.databaseName}" OWNER "${cfg.databaseUsername}"' + current_owner=$(PSQL -tAc "SELECT pg_catalog.pg_get_userbyid(datdba) FROM pg_catalog.pg_database WHERE datname = '${cfg.databaseName}'") if [[ "$current_owner" != "${cfg.databaseUsername}" ]]; then - $PSQL -tAc 'ALTER DATABASE "${cfg.databaseName}" OWNER TO "${cfg.databaseUsername}"' + PSQL -tAc 'ALTER DATABASE "${cfg.databaseName}" OWNER TO "${cfg.databaseUsername}"' if [[ -e "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}" ]]; then echo "Reassigning ownership of database ${cfg.databaseName} to user ${cfg.databaseUsername} failed on last boot. Failing..." exit 1 fi touch "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}" - $PSQL "${cfg.databaseName}" -tAc "REASSIGN OWNED BY \"$current_owner\" TO \"${cfg.databaseUsername}\"" + PSQL "${cfg.databaseName}" -tAc "REASSIGN OWNED BY \"$current_owner\" TO \"${cfg.databaseUsername}\"" rm "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}" fi - $PSQL '${cfg.databaseName}' -tAc "CREATE EXTENSION IF NOT EXISTS pg_trgm" - $PSQL '${cfg.databaseName}' -tAc "CREATE EXTENSION IF NOT EXISTS btree_gist;" + PSQL '${cfg.databaseName}' -tAc "CREATE EXTENSION IF NOT EXISTS pg_trgm" + PSQL '${cfg.databaseName}' -tAc "CREATE EXTENSION IF NOT EXISTS btree_gist;" ''; serviceConfig = { + User = pgsql.superUser; Type = "oneshot"; + RemainAfterExit = true; }; }; # Use postfix to send out mails. - services.postfix.enable = mkDefault true; + services.postfix.enable = mkDefault (cfg.smtp.enable && cfg.smtp.address == "localhost"); users.users.${cfg.user} = { group = cfg.group; @@ -699,11 +817,10 @@ in { "d /run/gitlab 0755 ${cfg.user} ${cfg.group} -" "d ${gitlabEnv.HOME} 0750 ${cfg.user} ${cfg.group} -" "z ${gitlabEnv.HOME}/.ssh/authorized_keys 0600 ${cfg.user} ${cfg.group} -" - "d ${cfg.backupPath} 0750 ${cfg.user} ${cfg.group} -" + "d ${cfg.backup.path} 0750 ${cfg.user} ${cfg.group} -" "d ${cfg.statePath} 0750 ${cfg.user} ${cfg.group} -" "d ${cfg.statePath}/builds 0750 ${cfg.user} ${cfg.group} -" "d ${cfg.statePath}/config 0750 ${cfg.user} ${cfg.group} -" - "d ${cfg.statePath}/config/initializers 0750 ${cfg.user} ${cfg.group} -" "d ${cfg.statePath}/db 0750 ${cfg.user} ${cfg.group} -" "d ${cfg.statePath}/log 0750 ${cfg.user} ${cfg.group} -" "d ${cfg.statePath}/repositories 2770 ${cfg.user} ${cfg.group} -" @@ -726,13 +843,156 @@ in { "L+ /run/gitlab/uploads - - - - ${cfg.statePath}/uploads" "L+ /run/gitlab/shell-config.yml - - - - ${pkgs.writeText "config.yml" (builtins.toJSON gitlabShellConfig)}" - - "L+ ${cfg.statePath}/config/unicorn.rb - - - - ${./defaultUnicornConfig.rb}" ]; + + systemd.services.gitlab-config = { + wantedBy = [ "gitlab.target" ]; + partOf = [ "gitlab.target" ]; + path = with pkgs; [ + jq + openssl + replace + git + ]; + serviceConfig = { + Type = "oneshot"; + User = cfg.user; + Group = cfg.group; + TimeoutSec = "infinity"; + Restart = "on-failure"; + WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab"; + RemainAfterExit = true; + + ExecStartPre = let + preStartFullPrivileges = '' + shopt -s dotglob nullglob + set -eu + + chown --no-dereference '${cfg.user}':'${cfg.group}' '${cfg.statePath}'/* + if [[ -n "$(ls -A '${cfg.statePath}'/config/)" ]]; then + chown --no-dereference '${cfg.user}':'${cfg.group}' '${cfg.statePath}'/config/* + fi + ''; + in "+${pkgs.writeShellScript "gitlab-pre-start-full-privileges" preStartFullPrivileges}"; + + ExecStart = pkgs.writeShellScript "gitlab-config" '' + set -eu + + umask u=rwx,g=rx,o= + + cp -f ${cfg.packages.gitlab}/share/gitlab/VERSION ${cfg.statePath}/VERSION + rm -rf ${cfg.statePath}/db/* + rm -f ${cfg.statePath}/lib + find '${cfg.statePath}/config/' -maxdepth 1 -mindepth 1 -type d -execdir rm -rf {} \; + cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/config.dist/* ${cfg.statePath}/config + cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/db/* ${cfg.statePath}/db + ln -sf ${extraGitlabRb} ${cfg.statePath}/config/initializers/extra-gitlab.rb + + ${cfg.packages.gitlab-shell}/bin/install + + ${optionalString cfg.smtp.enable '' + install -m u=rw ${smtpSettings} ${cfg.statePath}/config/initializers/smtp_settings.rb + ${optionalString (cfg.smtp.passwordFile != null) '' + smtp_password=$(<'${cfg.smtp.passwordFile}') + replace-literal -e '@smtpPassword@' "$smtp_password" '${cfg.statePath}/config/initializers/smtp_settings.rb' + ''} + ''} + + ( + umask u=rwx,g=,o= + + openssl rand -hex 32 > ${cfg.statePath}/gitlab_shell_secret + + rm -f '${cfg.statePath}/config/database.yml' + + ${if cfg.databasePasswordFile != null then '' + export db_password="$(<'${cfg.databasePasswordFile}')" + + if [[ -z "$db_password" ]]; then + >&2 echo "Database password was an empty string!" + exit 1 + fi + + jq <${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)} \ + '.production.password = $ENV.db_password' \ + >'${cfg.statePath}/config/database.yml' + '' + else '' + jq <${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)} \ + >'${cfg.statePath}/config/database.yml' + '' + } + + ${utils.genJqSecretsReplacementSnippet + gitlabConfig + "${cfg.statePath}/config/gitlab.yml" + } + + rm -f '${cfg.statePath}/config/secrets.yml' + + export secret="$(<'${cfg.secrets.secretFile}')" + export db="$(<'${cfg.secrets.dbFile}')" + export otp="$(<'${cfg.secrets.otpFile}')" + export jws="$(<'${cfg.secrets.jwsFile}')" + jq -n '{production: {secret_key_base: $ENV.secret, + otp_key_base: $ENV.otp, + db_key_base: $ENV.db, + openid_connect_signing_key: $ENV.jws}}' \ + > '${cfg.statePath}/config/secrets.yml' + ) + + # We remove potentially broken links to old gitlab-shell versions + rm -Rf ${cfg.statePath}/repositories/**/*.git/hooks + + git config --global core.autocrlf "input" + ''; + }; + }; + + systemd.services.gitlab-db-config = { + after = [ "gitlab-config.service" "gitlab-postgresql.service" "postgresql.service" ]; + bindsTo = [ + "gitlab-config.service" + ] ++ optional (cfg.databaseHost == "") "postgresql.service" + ++ optional databaseActuallyCreateLocally "gitlab-postgresql.service"; + wantedBy = [ "gitlab.target" ]; + partOf = [ "gitlab.target" ]; + serviceConfig = { + Type = "oneshot"; + User = cfg.user; + Group = cfg.group; + TimeoutSec = "infinity"; + Restart = "on-failure"; + WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab"; + RemainAfterExit = true; + + ExecStart = pkgs.writeShellScript "gitlab-db-config" '' + set -eu + umask u=rwx,g=rx,o= + + initial_root_password="$(<'${cfg.initialRootPasswordFile}')" + ${gitlab-rake}/bin/gitlab-rake gitlab:db:configure GITLAB_ROOT_PASSWORD="$initial_root_password" \ + GITLAB_ROOT_EMAIL='${cfg.initialRootEmail}' > /dev/null + ''; + }; + }; + systemd.services.gitlab-sidekiq = { - after = [ "network.target" "redis.service" "gitlab.service" ]; - wantedBy = [ "multi-user.target" ]; + after = [ + "network.target" + "redis.service" + "postgresql.service" + "gitlab-config.service" + "gitlab-db-config.service" + ]; + bindsTo = [ + "redis.service" + "gitlab-config.service" + "gitlab-db-config.service" + ] ++ optional (cfg.databaseHost == "") "postgresql.service"; + wantedBy = [ "gitlab.target" ]; + partOf = [ "gitlab.target" ]; environment = gitlabEnv; path = with pkgs; [ postgresqlPackage @@ -758,9 +1018,10 @@ in { }; systemd.services.gitaly = { - after = [ "network.target" "gitlab.service" ]; - bindsTo = [ "gitlab.service" ]; - wantedBy = [ "multi-user.target" ]; + after = [ "network.target" "gitlab-config.service" ]; + bindsTo = [ "gitlab-config.service" ]; + wantedBy = [ "gitlab.target" ]; + partOf = [ "gitlab.target" ]; path = with pkgs; [ openssh procps # See https://gitlab.com/gitlab-org/gitaly/issues/1562 @@ -783,8 +1044,10 @@ in { systemd.services.gitlab-pages = mkIf (gitlabConfig.production.pages.enabled or false) { description = "GitLab static pages daemon"; - after = [ "network.target" "redis.service" "gitlab.service" ]; # gitlab.service creates configs - wantedBy = [ "multi-user.target" ]; + after = [ "network.target" "gitlab-config.service" ]; + bindsTo = [ "gitlab-config.service" ]; + wantedBy = [ "gitlab.target" ]; + partOf = [ "gitlab.target" ]; path = [ pkgs.unzip ]; @@ -803,7 +1066,8 @@ in { systemd.services.gitlab-workhorse = { after = [ "network.target" ]; - wantedBy = [ "multi-user.target" ]; + wantedBy = [ "gitlab.target" ]; + partOf = [ "gitlab.target" ]; path = with pkgs; [ exiftool git @@ -832,8 +1096,10 @@ in { systemd.services.gitlab-mailroom = mkIf (gitlabConfig.production.incoming_email.enabled or false) { description = "GitLab incoming mail daemon"; - after = [ "network.target" "redis.service" "gitlab.service" ]; # gitlab.service creates configs - wantedBy = [ "multi-user.target" ]; + after = [ "network.target" "redis.service" "gitlab-config.service" ]; + bindsTo = [ "gitlab-config.service" ]; + wantedBy = [ "gitlab.target" ]; + partOf = [ "gitlab.target" ]; environment = gitlabEnv; serviceConfig = { Type = "simple"; @@ -842,15 +1108,26 @@ in { User = cfg.user; Group = cfg.group; - ExecStart = "${cfg.packages.gitlab.rubyEnv}/bin/bundle exec mail_room -c ${cfg.packages.gitlab}/share/gitlab/config.dist/mail_room.yml"; + ExecStart = "${cfg.packages.gitlab.rubyEnv}/bin/bundle exec mail_room -c ${cfg.statePath}/config/mail_room.yml"; WorkingDirectory = gitlabEnv.HOME; }; }; systemd.services.gitlab = { - after = [ "gitlab-workhorse.service" "network.target" "gitlab-postgresql.service" "redis.service" ]; - requires = [ "gitlab-sidekiq.service" ]; - wantedBy = [ "multi-user.target" ]; + after = [ + "gitlab-workhorse.service" + "network.target" + "redis.service" + "gitlab-config.service" + "gitlab-db-config.service" + ]; + bindsTo = [ + "redis.service" + "gitlab-config.service" + "gitlab-db-config.service" + ] ++ optional (cfg.databaseHost == "") "postgresql.service"; + wantedBy = [ "gitlab.target" ]; + partOf = [ "gitlab.target" ]; environment = gitlabEnv; path = with pkgs; [ postgresqlPackage @@ -868,98 +1145,26 @@ in { TimeoutSec = "infinity"; Restart = "on-failure"; WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab"; - ExecStartPre = let - preStartFullPrivileges = '' - shopt -s dotglob nullglob - set -eu - - chown --no-dereference '${cfg.user}':'${cfg.group}' '${cfg.statePath}'/* - chown --no-dereference '${cfg.user}':'${cfg.group}' '${cfg.statePath}'/config/* - ''; - preStart = '' - set -eu - - cp -f ${cfg.packages.gitlab}/share/gitlab/VERSION ${cfg.statePath}/VERSION - rm -rf ${cfg.statePath}/db/* - rm -rf ${cfg.statePath}/config/initializers/* - rm -f ${cfg.statePath}/lib - cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/config.dist/* ${cfg.statePath}/config - cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/db/* ${cfg.statePath}/db - ln -sf ${extraGitlabRb} ${cfg.statePath}/config/initializers/extra-gitlab.rb + ExecStart = "${cfg.packages.gitlab.rubyEnv}/bin/puma -C ${cfg.statePath}/config/puma.rb -e production"; + }; - ${cfg.packages.gitlab-shell}/bin/install + }; - ${optionalString cfg.smtp.enable '' - install -m u=rw ${smtpSettings} ${cfg.statePath}/config/initializers/smtp_settings.rb - ${optionalString (cfg.smtp.passwordFile != null) '' - smtp_password=$(<'${cfg.smtp.passwordFile}') - ${pkgs.replace}/bin/replace-literal -e '@smtpPassword@' "$smtp_password" '${cfg.statePath}/config/initializers/smtp_settings.rb' - ''} - ''} - - ( - umask u=rwx,g=,o= - - ${pkgs.openssl}/bin/openssl rand -hex 32 > ${cfg.statePath}/gitlab_shell_secret - - if [[ -h '${cfg.statePath}/config/database.yml' ]]; then - rm '${cfg.statePath}/config/database.yml' - fi - - ${if cfg.databasePasswordFile != null then '' - export db_password="$(<'${cfg.databasePasswordFile}')" - - if [[ -z "$db_password" ]]; then - >&2 echo "Database password was an empty string!" - exit 1 - fi - - ${pkgs.jq}/bin/jq <${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)} \ - '.production.password = $ENV.db_password' \ - >'${cfg.statePath}/config/database.yml' - '' - else '' - ${pkgs.jq}/bin/jq <${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)} \ - >'${cfg.statePath}/config/database.yml' - '' - } - - ${utils.genJqSecretsReplacementSnippet - gitlabConfig - "${cfg.statePath}/config/gitlab.yml" - } - - if [[ -h '${cfg.statePath}/config/secrets.yml' ]]; then - rm '${cfg.statePath}/config/secrets.yml' - fi - - export secret="$(<'${cfg.secrets.secretFile}')" - export db="$(<'${cfg.secrets.dbFile}')" - export otp="$(<'${cfg.secrets.otpFile}')" - export jws="$(<'${cfg.secrets.jwsFile}')" - ${pkgs.jq}/bin/jq -n '{production: {secret_key_base: $ENV.secret, - otp_key_base: $ENV.otp, - db_key_base: $ENV.db, - openid_connect_signing_key: $ENV.jws}}' \ - > '${cfg.statePath}/config/secrets.yml' - ) - - initial_root_password="$(<'${cfg.initialRootPasswordFile}')" - ${gitlab-rake}/bin/gitlab-rake gitlab:db:configure GITLAB_ROOT_PASSWORD="$initial_root_password" \ - GITLAB_ROOT_EMAIL='${cfg.initialRootEmail}' > /dev/null - - # We remove potentially broken links to old gitlab-shell versions - rm -Rf ${cfg.statePath}/repositories/**/*.git/hooks - - ${pkgs.git}/bin/git config --global core.autocrlf "input" - ''; - in [ - "+${pkgs.writeShellScript "gitlab-pre-start-full-privileges" preStartFullPrivileges}" - "${pkgs.writeShellScript "gitlab-pre-start" preStart}" - ]; - ExecStart = "${cfg.packages.gitlab.rubyEnv}/bin/unicorn -c ${cfg.statePath}/config/unicorn.rb -E production"; + systemd.services.gitlab-backup = { + after = [ "gitlab.service" ]; + bindsTo = [ "gitlab.service" ]; + startAt = cfg.backup.startAt; + environment = { + RAILS_ENV = "production"; + CRON = "1"; + } // optionalAttrs (stringLength cfg.backup.skip > 0) { + SKIP = cfg.backup.skip; + }; + serviceConfig = { + User = cfg.user; + Group = cfg.group; + ExecStart = "${gitlab-rake}/bin/gitlab-rake gitlab:backup:create"; }; - }; }; diff --git a/nixpkgs/nixos/modules/services/misc/gitlab.xml b/nixpkgs/nixos/modules/services/misc/gitlab.xml index 19a3df0a5f66..40424c5039a2 100644 --- a/nixpkgs/nixos/modules/services/misc/gitlab.xml +++ b/nixpkgs/nixos/modules/services/misc/gitlab.xml @@ -3,15 +3,15 @@ xmlns:xi="http://www.w3.org/2001/XInclude" version="5.0" xml:id="module-services-gitlab"> - <title>Gitlab</title> + <title>GitLab</title> <para> - Gitlab is a feature-rich git hosting service. + GitLab is a feature-rich git hosting service. </para> <section xml:id="module-services-gitlab-prerequisites"> <title>Prerequisites</title> <para> - The gitlab service exposes only an Unix socket at + The <literal>gitlab</literal> service exposes only an Unix socket at <literal>/run/gitlab/gitlab-workhorse.socket</literal>. You need to configure a webserver to proxy HTTP requests to the socket. </para> @@ -39,7 +39,7 @@ <title>Configuring</title> <para> - Gitlab depends on both PostgreSQL and Redis and will automatically enable + GitLab depends on both PostgreSQL and Redis and will automatically enable both services. In the case of PostgreSQL, a database and a role will be created. </para> @@ -85,20 +85,20 @@ services.gitlab = { </para> <para> - If you're setting up a new Gitlab instance, generate new + If you're setting up a new GitLab instance, generate new secrets. You for instance use <literal>tr -dc A-Za-z0-9 < /dev/urandom | head -c 128 > /var/keys/gitlab/db</literal> to generate a new db secret. Make sure the files can be read by, and only by, the user specified by <link - linkend="opt-services.gitlab.user">services.gitlab.user</link>. Gitlab + linkend="opt-services.gitlab.user">services.gitlab.user</link>. GitLab encrypts sensitive data stored in the database. If you're restoring - an existing Gitlab instance, you must specify the secrets secret - from <literal>config/secrets.yml</literal> located in your Gitlab + an existing GitLab instance, you must specify the secrets secret + from <literal>config/secrets.yml</literal> located in your GitLab state folder. </para> <para> - When <literal>icoming_mail.enabled</literal> is set to <literal>true</literal> + When <literal>incoming_mail.enabled</literal> is set to <literal>true</literal> in <link linkend="opt-services.gitlab.extraConfig">extraConfig</link> an additional service called <literal>gitlab-mailroom</literal> is enabled for fetching incoming mail. </para> @@ -112,21 +112,40 @@ services.gitlab = { <section xml:id="module-services-gitlab-maintenance"> <title>Maintenance</title> - <para> - You can run Gitlab's rake tasks with <literal>gitlab-rake</literal> which - will be available on the system when gitlab is enabled. You will have to run - the command as the user that you configured to run gitlab with. - </para> + <section xml:id="module-services-gitlab-maintenance-backups"> + <title>Backups</title> + <para> + Backups can be configured with the options in <link + linkend="opt-services.gitlab.backup.keepTime">services.gitlab.backup</link>. Use + the <link + linkend="opt-services.gitlab.backup.startAt">services.gitlab.backup.startAt</link> + option to configure regular backups. + </para> - <para> - For example, to backup a Gitlab instance: + <para> + To run a manual backup, start the <literal>gitlab-backup</literal> service: <screen> -<prompt>$ </prompt>sudo -u git -H gitlab-rake gitlab:backup:create +<prompt>$ </prompt>systemctl start gitlab-backup.service </screen> - A list of all availabe rake tasks can be obtained by running: + </para> + </section> + + <section xml:id="module-services-gitlab-maintenance-rake"> + <title>Rake tasks</title> + + <para> + You can run GitLab's rake tasks with <literal>gitlab-rake</literal> + which will be available on the system when GitLab is enabled. You + will have to run the command as the user that you configured to run + GitLab with. + </para> + + <para> + A list of all availabe rake tasks can be obtained by running: <screen> <prompt>$ </prompt>sudo -u git -H gitlab-rake -T </screen> - </para> + </para> + </section> </section> </chapter> diff --git a/nixpkgs/nixos/modules/services/misc/gollum.nix b/nixpkgs/nixos/modules/services/misc/gollum.nix index 0c9c7548305b..4053afa69be5 100644 --- a/nixpkgs/nixos/modules/services/misc/gollum.nix +++ b/nixpkgs/nixos/modules/services/misc/gollum.nix @@ -115,4 +115,6 @@ in }; }; }; + + meta.maintainers = with lib.maintainers; [ erictapen ]; } diff --git a/nixpkgs/nixos/modules/services/misc/home-assistant.nix b/nixpkgs/nixos/modules/services/misc/home-assistant.nix index 1f2e13f37325..2787c975b352 100644 --- a/nixpkgs/nixos/modules/services/misc/home-assistant.nix +++ b/nixpkgs/nixos/modules/services/misc/home-assistant.nix @@ -50,10 +50,15 @@ let # List of components used in config extraComponents = filter useComponent availableComponents; - package = if (cfg.autoExtraComponents && cfg.config != null) + testedPackage = if (cfg.autoExtraComponents && cfg.config != null) then (cfg.package.override { inherit extraComponents; }) else cfg.package; + # overridePythonAttrs has to be applied after override + package = testedPackage.overridePythonAttrs (oldAttrs: { + doCheck = false; + }); + # If you are changing this, please update the description in applyDefaultConfig defaultConfig = { homeassistant.time_zone = config.time.timeZone; @@ -63,7 +68,7 @@ let }; in { - meta.maintainers = with maintainers; [ dotlambda ]; + meta.maintainers = teams.home-assistant.members; options.services.home-assistant = { enable = mkEnableOption "Home Assistant"; @@ -184,7 +189,9 @@ in { package = mkOption { default = pkgs.home-assistant; - defaultText = "pkgs.home-assistant"; + defaultText = literalExample '' + pkgs.home-assistant + ''; type = types.package; example = literalExample '' pkgs.home-assistant.override { @@ -192,10 +199,12 @@ in { } ''; description = '' - Home Assistant package to use. + Home Assistant package to use. Tests are automatically disabled, as they take a considerable amout of time to complete. Override <literal>extraPackages</literal> or <literal>extraComponents</literal> in order to add additional dependencies. If you specify <option>config</option> and do not set <option>autoExtraComponents</option> to <literal>false</literal>, overriding <literal>extraComponents</literal> will have no effect. + Avoid <literal>home-assistant.overridePythonAttrs</literal> if you use + <literal>autoExtraComponents</literal>. ''; }; diff --git a/nixpkgs/nixos/modules/services/misc/lifecycled.nix b/nixpkgs/nixos/modules/services/misc/lifecycled.nix new file mode 100644 index 000000000000..1c8942998d6c --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/lifecycled.nix @@ -0,0 +1,164 @@ +{ config, pkgs, lib, ... }: + +with lib; +let + cfg = config.services.lifecycled; + + # TODO: Add the ability to extend this with an rfc 42-like interface. + # In the meantime, one can modify the environment (as + # long as it's not overriding anything from here) with + # systemd.services.lifecycled.serviceConfig.Environment + configFile = pkgs.writeText "lifecycled" '' + LIFECYCLED_HANDLER=${cfg.handler} + ${lib.optionalString (cfg.cloudwatchGroup != null) "LIFECYCLED_CLOUDWATCH_GROUP=${cfg.cloudwatchGroup}"} + ${lib.optionalString (cfg.cloudwatchStream != null) "LIFECYCLED_CLOUDWATCH_STREAM=${cfg.cloudwatchStream}"} + ${lib.optionalString cfg.debug "LIFECYCLED_DEBUG=${lib.boolToString cfg.debug}"} + ${lib.optionalString (cfg.instanceId != null) "LIFECYCLED_INSTANCE_ID=${cfg.instanceId}"} + ${lib.optionalString cfg.json "LIFECYCLED_JSON=${lib.boolToString cfg.json}"} + ${lib.optionalString cfg.noSpot "LIFECYCLED_NO_SPOT=${lib.boolToString cfg.noSpot}"} + ${lib.optionalString (cfg.snsTopic != null) "LIFECYCLED_SNS_TOPIC=${cfg.snsTopic}"} + ${lib.optionalString (cfg.awsRegion != null) "AWS_REGION=${cfg.awsRegion}"} + ''; +in +{ + meta.maintainers = with maintainers; [ cole-h grahamc ]; + + options = { + services.lifecycled = { + enable = mkEnableOption "lifecycled"; + + queueCleaner = { + enable = mkEnableOption "lifecycled-queue-cleaner"; + + frequency = mkOption { + type = types.str; + default = "hourly"; + description = '' + How often to trigger the queue cleaner. + + NOTE: This string should be a valid value for a systemd + timer's <literal>OnCalendar</literal> configuration. See + <citerefentry><refentrytitle>systemd.timer</refentrytitle><manvolnum>5</manvolnum></citerefentry> + for more information. + ''; + }; + + parallel = mkOption { + type = types.ints.unsigned; + default = 20; + description = '' + The number of parallel deletes to run. + ''; + }; + }; + + instanceId = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The instance ID to listen for events for. + ''; + }; + + snsTopic = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The SNS topic that receives events. + ''; + }; + + noSpot = mkOption { + type = types.bool; + default = false; + description = '' + Disable the spot termination listener. + ''; + }; + + handler = mkOption { + type = types.path; + description = '' + The script to invoke to handle events. + ''; + }; + + json = mkOption { + type = types.bool; + default = false; + description = '' + Enable JSON logging. + ''; + }; + + cloudwatchGroup = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Write logs to a specific Cloudwatch Logs group. + ''; + }; + + cloudwatchStream = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Write logs to a specific Cloudwatch Logs stream. Defaults to the instance ID. + ''; + }; + + debug = mkOption { + type = types.bool; + default = false; + description = '' + Enable debugging information. + ''; + }; + + # XXX: Can be removed if / when + # https://github.com/buildkite/lifecycled/pull/91 is merged. + awsRegion = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The region used for accessing AWS services. + ''; + }; + }; + }; + + ### Implementation ### + + config = mkMerge [ + (mkIf cfg.enable { + environment.etc."lifecycled".source = configFile; + + systemd.packages = [ pkgs.lifecycled ]; + systemd.services.lifecycled = { + wantedBy = [ "network-online.target" ]; + restartTriggers = [ configFile ]; + }; + }) + + (mkIf cfg.queueCleaner.enable { + systemd.services.lifecycled-queue-cleaner = { + description = "Lifecycle Daemon Queue Cleaner"; + environment = optionalAttrs (cfg.awsRegion != null) { AWS_REGION = cfg.awsRegion; }; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${pkgs.lifecycled}/bin/lifecycled-queue-cleaner -parallel ${toString cfg.queueCleaner.parallel}"; + }; + }; + + systemd.timers.lifecycled-queue-cleaner = { + description = "Lifecycle Daemon Queue Cleaner Timer"; + wantedBy = [ "timers.target" ]; + after = [ "network-online.target" ]; + timerConfig = { + Unit = "lifecycled-queue-cleaner.service"; + OnCalendar = "${cfg.queueCleaner.frequency}"; + }; + }; + }) + ]; +} diff --git a/nixpkgs/nixos/modules/services/misc/mame.nix b/nixpkgs/nixos/modules/services/misc/mame.nix index c5d5e9e48371..34a471ea4fe0 100644 --- a/nixpkgs/nixos/modules/services/misc/mame.nix +++ b/nixpkgs/nixos/modules/services/misc/mame.nix @@ -53,7 +53,7 @@ in description = "MAME TUN/TAP Ethernet interface"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; - path = [ pkgs.iproute ]; + path = [ pkgs.iproute2 ]; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; diff --git a/nixpkgs/nixos/modules/services/misc/matrix-appservice-irc.nix b/nixpkgs/nixos/modules/services/misc/matrix-appservice-irc.nix new file mode 100644 index 000000000000..63dc313ad10b --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/matrix-appservice-irc.nix @@ -0,0 +1,228 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.services.matrix-appservice-irc; + + pkg = pkgs.matrix-appservice-irc; + bin = "${pkg}/bin/matrix-appservice-irc"; + + jsonType = (pkgs.formats.json {}).type; + + configFile = pkgs.runCommandNoCC "matrix-appservice-irc.yml" { + # Because this program will be run at build time, we need `nativeBuildInputs` + nativeBuildInputs = [ (pkgs.python3.withPackages (ps: [ ps.pyyaml ps.jsonschema ])) ]; + preferLocalBuild = true; + + config = builtins.toJSON cfg.settings; + passAsFile = [ "config" ]; + } '' + # The schema is given as yaml, we need to convert it to json + python -c 'import json; import yaml; import sys; json.dump(yaml.safe_load(sys.stdin), sys.stdout)' \ + < ${pkg}/lib/node_modules/matrix-appservice-irc/config.schema.yml \ + > config.schema.json + python -m jsonschema config.schema.json -i $configPath + cp "$configPath" "$out" + ''; + registrationFile = "/var/lib/matrix-appservice-irc/registration.yml"; +in { + options.services.matrix-appservice-irc = with types; { + enable = mkEnableOption "the Matrix/IRC bridge"; + + port = mkOption { + type = port; + description = "The port to listen on"; + default = 8009; + }; + + needBindingCap = mkOption { + type = bool; + description = "Whether the daemon needs to bind to ports below 1024 (e.g. for the ident service)"; + default = false; + }; + + passwordEncryptionKeyLength = mkOption { + type = ints.unsigned; + description = "Length of the key to encrypt IRC passwords with"; + default = 4096; + example = 8192; + }; + + registrationUrl = mkOption { + type = str; + description = '' + The URL where the application service is listening for homeserver requests, + from the Matrix homeserver perspective. + ''; + example = "http://localhost:8009"; + }; + + localpart = mkOption { + type = str; + description = "The user_id localpart to assign to the appservice"; + default = "appservice-irc"; + }; + + settings = mkOption { + description = '' + Configuration for the appservice, see + <link xlink:href="https://github.com/matrix-org/matrix-appservice-irc/blob/${pkgs.matrix-appservice-irc.version}/config.sample.yaml"/> + for supported values + ''; + default = {}; + type = submodule { + freeformType = jsonType; + + options = { + homeserver = mkOption { + description = "Homeserver configuration"; + default = {}; + type = submodule { + freeformType = jsonType; + + options = { + url = mkOption { + type = str; + description = "The URL to the home server for client-server API calls"; + }; + + domain = mkOption { + type = str; + description = '' + The 'domain' part for user IDs on this home server. Usually + (but not always) is the "domain name" part of the homeserver URL. + ''; + }; + }; + }; + }; + + database = mkOption { + default = {}; + description = "Configuration for the database"; + type = submodule { + freeformType = jsonType; + + options = { + engine = mkOption { + type = str; + description = "Which database engine to use"; + default = "nedb"; + example = "postgres"; + }; + + connectionString = mkOption { + type = str; + description = "The database connection string"; + default = "nedb://var/lib/matrix-appservice-irc/data"; + example = "postgres://username:password@host:port/databasename"; + }; + }; + }; + }; + + ircService = mkOption { + default = {}; + description = "IRC bridge configuration"; + type = submodule { + freeformType = jsonType; + + options = { + passwordEncryptionKeyPath = mkOption { + type = str; + description = '' + Location of the key with which IRC passwords are encrypted + for storage. Will be generated on first run if not present. + ''; + default = "/var/lib/matrix-appservice-irc/passkey.pem"; + }; + + servers = mkOption { + type = submodule { freeformType = jsonType; }; + description = "IRC servers to connect to"; + }; + }; + }; + }; + }; + }; + }; + }; + config = mkIf cfg.enable { + systemd.services.matrix-appservice-irc = { + description = "Matrix-IRC bridge"; + before = [ "matrix-synapse.service" ]; # So the registration can be used by Synapse + wantedBy = [ "multi-user.target" ]; + + preStart = '' + umask 077 + # Generate key for crypting passwords + if ! [ -f "${cfg.settings.ircService.passwordEncryptionKeyPath}" ]; then + ${pkgs.openssl}/bin/openssl genpkey \ + -out "${cfg.settings.ircService.passwordEncryptionKeyPath}" \ + -outform PEM \ + -algorithm RSA \ + -pkeyopt "rsa_keygen_bits:${toString cfg.passwordEncryptionKeyLength}" + fi + # Generate registration file + if ! [ -f "${registrationFile}" ]; then + # The easy case: the file has not been generated yet + ${bin} --generate-registration --file ${registrationFile} --config ${configFile} --url ${cfg.registrationUrl} --localpart ${cfg.localpart} + else + # The tricky case: we already have a generation file. Because the NixOS configuration might have changed, we need to + # regenerate it. But this would give the service a new random ID and tokens, so we need to back up and restore them. + # 1. Backup + id=$(grep "^id:.*$" ${registrationFile}) + hs_token=$(grep "^hs_token:.*$" ${registrationFile}) + as_token=$(grep "^as_token:.*$" ${registrationFile}) + # 2. Regenerate + ${bin} --generate-registration --file ${registrationFile} --config ${configFile} --url ${cfg.registrationUrl} --localpart ${cfg.localpart} + # 3. Restore + sed -i "s/^id:.*$/$id/g" ${registrationFile} + sed -i "s/^hs_token:.*$/$hs_token/g" ${registrationFile} + sed -i "s/^as_token:.*$/$as_token/g" ${registrationFile} + fi + # Allow synapse access to the registration + if ${getBin pkgs.glibc}/bin/getent group matrix-synapse > /dev/null; then + chgrp matrix-synapse ${registrationFile} + chmod g+r ${registrationFile} + fi + ''; + + serviceConfig = rec { + Type = "simple"; + ExecStart = "${bin} --config ${configFile} --file ${registrationFile} --port ${toString cfg.port}"; + + ProtectHome = true; + PrivateDevices = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + StateDirectory = "matrix-appservice-irc"; + StateDirectoryMode = "755"; + + User = "matrix-appservice-irc"; + Group = "matrix-appservice-irc"; + + CapabilityBoundingSet = [ "CAP_CHOWN" ] ++ optional (cfg.needBindingCap) "CAP_NET_BIND_SERVICE"; + AmbientCapabilities = CapabilityBoundingSet; + NoNewPrivileges = true; + + LockPersonality = true; + RestrictRealtime = true; + PrivateMounts = true; + SystemCallFilter = "~@aio @clock @cpu-emulation @debug @keyring @memlock @module @mount @obsolete @raw-io @setuid @swap"; + SystemCallArchitectures = "native"; + RestrictAddressFamilies = "AF_INET AF_INET6"; + }; + }; + + users.groups.matrix-appservice-irc = {}; + users.users.matrix-appservice-irc = { + description = "Service user for the Matrix-IRC bridge"; + group = "matrix-appservice-irc"; + isSystemUser = true; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/mautrix-telegram.nix b/nixpkgs/nixos/modules/services/misc/mautrix-telegram.nix index caeb4b04164f..0ae5797fea04 100644 --- a/nixpkgs/nixos/modules/services/misc/mautrix-telegram.nix +++ b/nixpkgs/nixos/modules/services/misc/mautrix-telegram.nix @@ -6,8 +6,9 @@ let dataDir = "/var/lib/mautrix-telegram"; registrationFile = "${dataDir}/telegram-registration.yaml"; cfg = config.services.mautrix-telegram; - # TODO: switch to configGen.json once RFC42 is implemented - settingsFile = pkgs.writeText "mautrix-telegram-settings.json" (builtins.toJSON cfg.settings); + settingsFormat = pkgs.formats.json {}; + settingsFileUnsubstituted = settingsFormat.generate "mautrix-telegram-config-unsubstituted.json" cfg.settings; + settingsFile = "${dataDir}/config.json"; in { options = { @@ -15,9 +16,8 @@ in { enable = mkEnableOption "Mautrix-Telegram, a Matrix-Telegram hybrid puppeting/relaybot bridge"; settings = mkOption rec { - # TODO: switch to types.config.json as prescribed by RFC42 once it's implemented - type = types.attrs; apply = recursiveUpdate default; + inherit (settingsFormat) type; default = { appservice = rec { database = "sqlite:///${dataDir}/mautrix-telegram.db"; @@ -124,6 +124,16 @@ in { after = [ "network-online.target" ] ++ cfg.serviceDependencies; preStart = '' + # Not all secrets can be passed as environment variable (yet) + # https://github.com/tulir/mautrix-telegram/issues/584 + [ -f ${settingsFile} ] && rm -f ${settingsFile} + old_umask=$(umask) + umask 0277 + ${pkgs.envsubst}/bin/envsubst \ + -o ${settingsFile} \ + -i ${settingsFileUnsubstituted} + umask $old_umask + # generate the appservice's registration file if absent if [ ! -f '${registrationFile}' ]; then ${pkgs.mautrix-telegram}/bin/mautrix-telegram \ @@ -159,6 +169,8 @@ in { --config='${settingsFile}' ''; }; + + restartTriggers = [ settingsFileUnsubstituted ]; }; }; diff --git a/nixpkgs/nixos/modules/services/misc/nix-gc.nix b/nixpkgs/nixos/modules/services/misc/nix-gc.nix index 12bed05757ad..a7a6a3b59644 100644 --- a/nixpkgs/nixos/modules/services/misc/nix-gc.nix +++ b/nixpkgs/nixos/modules/services/misc/nix-gc.nix @@ -21,13 +21,45 @@ in }; dates = mkOption { + type = types.str; default = "03:15"; + example = "weekly"; + description = '' + How often or when garbage collection is performed. For most desktop and server systems + a sufficient garbage collection is once a week. + + The format is described in + <citerefentry><refentrytitle>systemd.time</refentrytitle> + <manvolnum>7</manvolnum></citerefentry>. + ''; + }; + + randomizedDelaySec = mkOption { + default = "0"; type = types.str; + example = "45min"; description = '' - Specification (in the format described by + Add a randomized delay before each automatic upgrade. + The delay will be chosen between zero and this value. + This value must be a time span in the format specified by <citerefentry><refentrytitle>systemd.time</refentrytitle> - <manvolnum>7</manvolnum></citerefentry>) of the time at - which the garbage collector will run. + <manvolnum>7</manvolnum></citerefentry> + ''; + }; + + persistent = mkOption { + default = true; + type = types.bool; + example = false; + description = '' + Takes a boolean argument. If true, the time when the service + unit was last triggered is stored on disk. When the timer is + activated, the service unit is triggered immediately if it + would have been triggered at least once during the time when + the timer was inactive. Such triggering is nonetheless + subject to the delay imposed by RandomizedDelaySec=. This is + useful to catch up on missed runs of the service when the + system was powered down. ''; }; @@ -50,11 +82,18 @@ in config = { - systemd.services.nix-gc = - { description = "Nix Garbage Collector"; - script = "exec ${config.nix.package.out}/bin/nix-collect-garbage ${cfg.options}"; - startAt = optional cfg.automatic cfg.dates; + systemd.services.nix-gc = { + description = "Nix Garbage Collector"; + script = "exec ${config.nix.package.out}/bin/nix-collect-garbage ${cfg.options}"; + startAt = optional cfg.automatic cfg.dates; + }; + + systemd.timers.nix-gc = lib.mkIf cfg.automatic { + timerConfig = { + RandomizedDelaySec = cfg.randomizedDelaySec; + Persistent = cfg.persistent; }; + }; }; diff --git a/nixpkgs/nixos/modules/services/misc/ombi.nix b/nixpkgs/nixos/modules/services/misc/ombi.nix new file mode 100644 index 000000000000..83f433e0be4a --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/ombi.nix @@ -0,0 +1,80 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let cfg = config.services.ombi; + +in { + options = { + services.ombi = { + enable = mkEnableOption '' + Ombi. + Optionally see <link xlink:href="https://docs.ombi.app/info/reverse-proxy"/> + on how to set up a reverse proxy + ''; + + dataDir = mkOption { + type = types.str; + default = "/var/lib/ombi"; + description = "The directory where Ombi stores its data files."; + }; + + port = mkOption { + type = types.port; + default = 5000; + description = "The port for the Ombi web interface."; + }; + + openFirewall = mkOption { + type = types.bool; + default = false; + description = "Open ports in the firewall for the Ombi web interface."; + }; + + user = mkOption { + type = types.str; + default = "ombi"; + description = "User account under which Ombi runs."; + }; + + group = mkOption { + type = types.str; + default = "ombi"; + description = "Group under which Ombi runs."; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.tmpfiles.rules = [ + "d '${cfg.dataDir}' 0700 ${cfg.user} ${cfg.group} - -" + ]; + + systemd.services.ombi = { + description = "Ombi"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + Type = "simple"; + User = cfg.user; + Group = cfg.group; + ExecStart = "${pkgs.ombi}/bin/Ombi --storage '${cfg.dataDir}' --host 'http://*:${toString cfg.port}'"; + Restart = "on-failure"; + }; + }; + + networking.firewall = mkIf cfg.openFirewall { + allowedTCPPorts = [ cfg.port ]; + }; + + users.users = mkIf (cfg.user == "ombi") { + ombi = { + group = cfg.group; + home = cfg.dataDir; + }; + }; + + users.groups = mkIf (cfg.group == "ombi") { ombi = { }; }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/packagekit.nix b/nixpkgs/nixos/modules/services/misc/packagekit.nix index 325c4e84e0d8..93bd206bd983 100644 --- a/nixpkgs/nixos/modules/services/misc/packagekit.nix +++ b/nixpkgs/nixos/modules/services/misc/packagekit.nix @@ -1,55 +1,60 @@ { config, lib, pkgs, ... }: -with lib; - let - cfg = config.services.packagekit; - packagekitConf = '' - [Daemon] - DefaultBackend=${cfg.backend} - KeepCache=false - ''; + inherit (lib) + mkEnableOption mkOption mkIf mkRemovedOptionModule types + listToAttrs recursiveUpdate; - vendorConf = '' - [PackagesNotFound] - DefaultUrl=https://github.com/NixOS/nixpkgs - CodecUrl=https://github.com/NixOS/nixpkgs - HardwareUrl=https://github.com/NixOS/nixpkgs - FontUrl=https://github.com/NixOS/nixpkgs - MimeUrl=https://github.com/NixOS/nixpkgs - ''; + iniFmt = pkgs.formats.ini { }; -in + confFiles = [ + (iniFmt.generate "PackageKit.conf" (recursiveUpdate + { + Daemon = { + DefaultBackend = "test_nop"; + KeepCache = false; + }; + } + cfg.settings)) + (iniFmt.generate "Vendor.conf" (recursiveUpdate + { + PackagesNotFound = rec { + DefaultUrl = "https://github.com/NixOS/nixpkgs"; + CodecUrl = DefaultUrl; + HardwareUrl = DefaultUrl; + FontUrl = DefaultUrl; + MimeUrl = DefaultUrl; + }; + } + cfg.vendorSettings)) + ]; + +in { + imports = [ + (mkRemovedOptionModule [ "services" "packagekit" "backend" ] "The only backend that doesn't blow up is `test_nop`.") + ]; - options = { + options.services.packagekit = { + enable = mkEnableOption '' + PackageKit provides a cross-platform D-Bus abstraction layer for + installing software. Software utilizing PackageKit can install + software regardless of the package manager. + ''; - services.packagekit = { - enable = mkEnableOption - '' - PackageKit provides a cross-platform D-Bus abstraction layer for - installing software. Software utilizing PackageKit can install - software regardless of the package manager. - ''; + settings = mkOption { + type = iniFmt.type; + default = { }; + description = "Additional settings passed straight through to PackageKit.conf"; + }; - # TODO: integrate with PolicyKit if the nix backend matures to the point - # where it will require elevated permissions - backend = mkOption { - type = types.enum [ "test_nop" ]; - default = "test_nop"; - description = '' - PackageKit supports multiple different backends and <literal>auto</literal> which - should do the right thing. - </para> - <para> - On NixOS however, we do not have a backend compatible with nix 2.0 - (refer to <link xlink:href="https://github.com/NixOS/nix/issues/233">this issue</link> so we have to force - it to <literal>test_nop</literal> for now. - ''; - }; + vendorSettings = mkOption { + type = iniFmt.type; + default = { }; + description = "Additional settings passed straight through to Vendor.conf"; }; }; @@ -59,7 +64,9 @@ in systemd.packages = with pkgs; [ packagekit ]; - environment.etc."PackageKit/PackageKit.conf".text = packagekitConf; - environment.etc."PackageKit/Vendor.conf".text = vendorConf; + environment.etc = listToAttrs (map + (e: + lib.nameValuePair "PackageKit/${e.name}" { source = e; }) + confFiles); }; } diff --git a/nixpkgs/nixos/modules/services/misc/plikd.nix b/nixpkgs/nixos/modules/services/misc/plikd.nix new file mode 100644 index 000000000000..a62dbef1d2af --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/plikd.nix @@ -0,0 +1,82 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.services.plikd; + + format = pkgs.formats.toml {}; + plikdCfg = format.generate "plikd.cfg" cfg.settings; +in +{ + options = { + services.plikd = { + enable = mkEnableOption "the plikd server"; + + openFirewall = mkOption { + type = types.bool; + default = false; + description = "Open ports in the firewall for the plikd."; + }; + + settings = mkOption { + type = format.type; + default = {}; + description = '' + Configuration for plikd, see <link xlink:href="https://github.com/root-gg/plik/blob/master/server/plikd.cfg"/> + for supported values. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + services.plikd.settings = mapAttrs (name: mkDefault) { + ListenPort = 8080; + ListenAddress = "localhost"; + DataBackend = "file"; + DataBackendConfig = { + Directory = "/var/lib/plikd"; + }; + MetadataBackendConfig = { + Driver = "sqlite3"; + ConnectionString = "/var/lib/plikd/plik.db"; + }; + }; + + systemd.services.plikd = { + description = "Plikd file sharing server"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "simple"; + ExecStart = "${pkgs.plikd}/bin/plikd --config ${plikdCfg}"; + Restart = "on-failure"; + StateDirectory = "plikd"; + LogsDirectory = "plikd"; + DynamicUser = true; + + # Basic hardening + NoNewPrivileges = "yes"; + PrivateTmp = "yes"; + PrivateDevices = "yes"; + DevicePolicy = "closed"; + ProtectSystem = "strict"; + ProtectHome = "read-only"; + ProtectControlGroups = "yes"; + ProtectKernelModules = "yes"; + ProtectKernelTunables = "yes"; + RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK"; + RestrictNamespaces = "yes"; + RestrictRealtime = "yes"; + RestrictSUIDSGID = "yes"; + MemoryDenyWriteExecute = "yes"; + LockPersonality = "yes"; + }; + }; + + networking.firewall = mkIf cfg.openFirewall { + allowedTCPPorts = [ cfg.settings.ListenPort ]; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/redmine.nix b/nixpkgs/nixos/modules/services/misc/redmine.nix index 8b53eb471db6..e0055576d6f6 100644 --- a/nixpkgs/nixos/modules/services/misc/redmine.nix +++ b/nixpkgs/nixos/modules/services/misc/redmine.nix @@ -28,7 +28,7 @@ let unpack = id: (name: source: pkgs.stdenv.mkDerivation { name = "redmine-${id}-${name}"; - buildInputs = [ pkgs.unzip ]; + nativeBuildInputs = [ pkgs.unzip ]; buildCommand = '' mkdir -p $out cd $out diff --git a/nixpkgs/nixos/modules/services/monitoring/alerta.nix b/nixpkgs/nixos/modules/services/monitoring/alerta.nix index 34f2d41706a5..7c6eff713cb1 100644 --- a/nixpkgs/nixos/modules/services/monitoring/alerta.nix +++ b/nixpkgs/nixos/modules/services/monitoring/alerta.nix @@ -95,13 +95,13 @@ in ALERTA_SVR_CONF_FILE = alertaConf; }; serviceConfig = { - ExecStart = "${pkgs.python36Packages.alerta-server}/bin/alertad run --port ${toString cfg.port} --host ${cfg.bind}"; + ExecStart = "${pkgs.alerta-server}/bin/alertad run --port ${toString cfg.port} --host ${cfg.bind}"; User = "alerta"; Group = "alerta"; }; }; - environment.systemPackages = [ pkgs.python36Packages.alerta ]; + environment.systemPackages = [ pkgs.alerta ]; users.users.alerta = { uid = config.ids.uids.alerta; diff --git a/nixpkgs/nixos/modules/services/monitoring/datadog-agent.nix b/nixpkgs/nixos/modules/services/monitoring/datadog-agent.nix index d97565f15d6c..b25a53435d06 100644 --- a/nixpkgs/nixos/modules/services/monitoring/datadog-agent.nix +++ b/nixpkgs/nixos/modules/services/monitoring/datadog-agent.nix @@ -225,7 +225,7 @@ in { }; }; config = mkIf cfg.enable { - environment.systemPackages = [ datadogPkg pkgs.sysstat pkgs.procps pkgs.iproute ]; + environment.systemPackages = [ datadogPkg pkgs.sysstat pkgs.procps pkgs.iproute2 ]; users.users.datadog = { description = "Datadog Agent User"; @@ -239,7 +239,7 @@ in { systemd.services = let makeService = attrs: recursiveUpdate { - path = [ datadogPkg pkgs.python pkgs.sysstat pkgs.procps pkgs.iproute ]; + path = [ datadogPkg pkgs.python pkgs.sysstat pkgs.procps pkgs.iproute2 ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { User = "datadog"; diff --git a/nixpkgs/nixos/modules/services/monitoring/grafana.nix b/nixpkgs/nixos/modules/services/monitoring/grafana.nix index c8515c4b8988..86e306ab404a 100644 --- a/nixpkgs/nixos/modules/services/monitoring/grafana.nix +++ b/nixpkgs/nixos/modules/services/monitoring/grafana.nix @@ -15,6 +15,7 @@ let SERVER_PROTOCOL = cfg.protocol; SERVER_HTTP_ADDR = cfg.addr; SERVER_HTTP_PORT = cfg.port; + SERVER_SOCKET = cfg.socket; SERVER_DOMAIN = cfg.domain; SERVER_ROOT_URL = cfg.rootUrl; SERVER_STATIC_ROOT_PATH = cfg.staticRootPath; @@ -65,10 +66,18 @@ let dashboardFile = pkgs.writeText "dashboard.yaml" (builtins.toJSON dashboardConfiguration); + notifierConfiguration = { + apiVersion = 1; + notifiers = cfg.provision.notifiers; + }; + + notifierFile = pkgs.writeText "notifier.yaml" (builtins.toJSON notifierConfiguration); + provisionConfDir = pkgs.runCommand "grafana-provisioning" { } '' - mkdir -p $out/{datasources,dashboards} + mkdir -p $out/{datasources,dashboards,notifiers} ln -sf ${datasourceFile} $out/datasources/datasource.yaml ln -sf ${dashboardFile} $out/dashboards/dashboard.yaml + ln -sf ${notifierFile} $out/notifiers/notifier.yaml ''; # Get a submodule without any embedded metadata: @@ -79,80 +88,80 @@ let options = { name = mkOption { type = types.str; - description = "Name of the datasource. Required"; + description = "Name of the datasource. Required."; }; type = mkOption { type = types.enum ["graphite" "prometheus" "cloudwatch" "elasticsearch" "influxdb" "opentsdb" "mysql" "mssql" "postgres" "loki"]; - description = "Datasource type. Required"; + description = "Datasource type. Required."; }; access = mkOption { type = types.enum ["proxy" "direct"]; default = "proxy"; - description = "Access mode. proxy or direct (Server or Browser in the UI). Required"; + description = "Access mode. proxy or direct (Server or Browser in the UI). Required."; }; orgId = mkOption { type = types.int; default = 1; - description = "Org id. will default to orgId 1 if not specified"; + description = "Org id. will default to orgId 1 if not specified."; }; url = mkOption { type = types.str; - description = "Url of the datasource"; + description = "Url of the datasource."; }; password = mkOption { type = types.nullOr types.str; default = null; - description = "Database password, if used"; + description = "Database password, if used."; }; user = mkOption { type = types.nullOr types.str; default = null; - description = "Database user, if used"; + description = "Database user, if used."; }; database = mkOption { type = types.nullOr types.str; default = null; - description = "Database name, if used"; + description = "Database name, if used."; }; basicAuth = mkOption { type = types.nullOr types.bool; default = null; - description = "Enable/disable basic auth"; + description = "Enable/disable basic auth."; }; basicAuthUser = mkOption { type = types.nullOr types.str; default = null; - description = "Basic auth username"; + description = "Basic auth username."; }; basicAuthPassword = mkOption { type = types.nullOr types.str; default = null; - description = "Basic auth password"; + description = "Basic auth password."; }; withCredentials = mkOption { type = types.bool; default = false; - description = "Enable/disable with credentials headers"; + description = "Enable/disable with credentials headers."; }; isDefault = mkOption { type = types.bool; default = false; - description = "Mark as default datasource. Max one per org"; + description = "Mark as default datasource. Max one per org."; }; jsonData = mkOption { type = types.nullOr types.attrs; default = null; - description = "Datasource specific configuration"; + description = "Datasource specific configuration."; }; secureJsonData = mkOption { type = types.nullOr types.attrs; default = null; - description = "Datasource specific secure configuration"; + description = "Datasource specific secure configuration."; }; version = mkOption { type = types.int; default = 1; - description = "Version"; + description = "Version."; }; editable = mkOption { type = types.bool; @@ -168,41 +177,99 @@ let name = mkOption { type = types.str; default = "default"; - description = "Provider name"; + description = "Provider name."; }; orgId = mkOption { type = types.int; default = 1; - description = "Organization ID"; + description = "Organization ID."; }; folder = mkOption { type = types.str; default = ""; - description = "Add dashboards to the specified folder"; + description = "Add dashboards to the specified folder."; }; type = mkOption { type = types.str; default = "file"; - description = "Dashboard provider type"; + description = "Dashboard provider type."; }; disableDeletion = mkOption { type = types.bool; default = false; - description = "Disable deletion when JSON file is removed"; + description = "Disable deletion when JSON file is removed."; }; updateIntervalSeconds = mkOption { type = types.int; default = 10; - description = "How often Grafana will scan for changed dashboards"; + description = "How often Grafana will scan for changed dashboards."; }; options = { path = mkOption { type = types.path; - description = "Path grafana will watch for dashboards"; + description = "Path grafana will watch for dashboards."; }; }; }; }; + + grafanaTypes.notifierConfig = types.submodule { + options = { + name = mkOption { + type = types.str; + default = "default"; + description = "Notifier name."; + }; + type = mkOption { + type = types.enum ["dingding" "discord" "email" "googlechat" "hipchat" "kafka" "line" "teams" "opsgenie" "pagerduty" "prometheus-alertmanager" "pushover" "sensu" "sensugo" "slack" "telegram" "threema" "victorops" "webhook"]; + description = "Notifier type."; + }; + uid = mkOption { + type = types.str; + description = "Unique notifier identifier."; + }; + org_id = mkOption { + type = types.int; + default = 1; + description = "Organization ID."; + }; + org_name = mkOption { + type = types.str; + default = "Main Org."; + description = "Organization name."; + }; + is_default = mkOption { + type = types.bool; + description = "Is the default notifier."; + default = false; + }; + send_reminder = mkOption { + type = types.bool; + default = true; + description = "Should the notifier be sent reminder notifications while alerts continue to fire."; + }; + frequency = mkOption { + type = types.str; + default = "5m"; + description = "How frequently should the notifier be sent reminders."; + }; + disable_resolve_message = mkOption { + type = types.bool; + default = false; + description = "Turn off the message that sends when an alert returns to OK."; + }; + settings = mkOption { + type = types.nullOr types.attrs; + default = null; + description = "Settings for the notifier type."; + }; + secure_settings = mkOption { + type = types.nullOr types.attrs; + default = null; + description = "Secure settings for the notifier type."; + }; + }; + }; in { options.services.grafana = { enable = mkEnableOption "grafana"; @@ -225,6 +292,12 @@ in { type = types.int; }; + socket = mkOption { + description = "Listening socket."; + default = "/run/grafana/grafana.sock"; + type = types.str; + }; + domain = mkOption { description = "The public facing domain name used to access grafana from a browser."; default = "localhost"; @@ -337,17 +410,23 @@ in { provision = { enable = mkEnableOption "provision"; datasources = mkOption { - description = "Grafana datasources configuration"; + description = "Grafana datasources configuration."; default = []; type = types.listOf grafanaTypes.datasourceConfig; apply = x: map _filter x; }; dashboards = mkOption { - description = "Grafana dashboard configuration"; + description = "Grafana dashboard configuration."; default = []; type = types.listOf grafanaTypes.dashboardConfig; apply = x: map _filter x; }; + notifiers = mkOption { + description = "Grafana notifier configuration."; + default = []; + type = types.listOf grafanaTypes.notifierConfig; + apply = x: map _filter x; + }; }; security = { @@ -391,12 +470,12 @@ in { smtp = { enable = mkEnableOption "smtp"; host = mkOption { - description = "Host to connect to"; + description = "Host to connect to."; default = "localhost:25"; type = types.str; }; user = mkOption { - description = "User used for authentication"; + description = "User used for authentication."; default = ""; type = types.str; }; @@ -417,7 +496,7 @@ in { type = types.nullOr types.path; }; fromAddress = mkOption { - description = "Email address used for sending"; + description = "Email address used for sending."; default = "admin@grafana.localhost"; type = types.str; }; @@ -425,7 +504,7 @@ in { users = { allowSignUp = mkOption { - description = "Disable user signup / registration"; + description = "Disable user signup / registration."; default = false; type = types.bool; }; @@ -451,17 +530,17 @@ in { auth.anonymous = { enable = mkOption { - description = "Whether to allow anonymous access"; + description = "Whether to allow anonymous access."; default = false; type = types.bool; }; org_name = mkOption { - description = "Which organization to allow anonymous access to"; + description = "Which organization to allow anonymous access to."; default = "Main Org."; type = types.str; }; org_role = mkOption { - description = "Which role anonymous users have in the organization"; + description = "Which role anonymous users have in the organization."; default = "Viewer"; type = types.str; }; @@ -470,7 +549,7 @@ in { analytics.reporting = { enable = mkOption { - description = "Whether to allow anonymous usage reporting to stats.grafana.net"; + description = "Whether to allow anonymous usage reporting to stats.grafana.net."; default = true; type = types.bool; }; @@ -496,6 +575,9 @@ in { (optional ( any (x: x.password != null || x.basicAuthPassword != null || x.secureJsonData != null) cfg.provision.datasources ) "Datasource passwords will be stored as plaintext in the Nix store!") + (optional ( + any (x: x.secure_settings != null) cfg.provision.notifiers + ) "Notifier secure settings will be stored as plaintext in the Nix store!") ]; environment.systemPackages = [ cfg.package ]; @@ -547,6 +629,8 @@ in { serviceConfig = { WorkingDirectory = cfg.dataDir; User = "grafana"; + RuntimeDirectory = "grafana"; + RuntimeDirectoryMode = "0755"; }; preStart = '' ln -fs ${cfg.package}/share/grafana/conf ${cfg.dataDir} diff --git a/nixpkgs/nixos/modules/services/monitoring/netdata.nix b/nixpkgs/nixos/modules/services/monitoring/netdata.nix index d5b679097b30..007024c04ce5 100644 --- a/nixpkgs/nixos/modules/services/monitoring/netdata.nix +++ b/nixpkgs/nixos/modules/services/monitoring/netdata.nix @@ -123,9 +123,20 @@ in { "error log" = "syslog"; }; ''; - }; + }; + + enableAnalyticsReporting = mkOption { + type = types.bool; + default = false; + description = '' + Enable reporting of anonymous usage statistics to Netdata Inc. via either + Google Analytics (in versions prior to 1.29.4), or Netdata Inc.'s + self-hosted PostHog (in versions 1.29.4 and later). + See: <link xlink:href="https://learn.netdata.cloud/docs/agent/anonymous-statistics"/> + ''; }; }; + }; config = mkIf cfg.enable { assertions = @@ -140,8 +151,12 @@ in { wantedBy = [ "multi-user.target" ]; path = (with pkgs; [ curl gawk which ]) ++ lib.optional cfg.python.enable (pkgs.python3.withPackages cfg.python.extraPackages); + environment = { + PYTHONPATH = "${cfg.package}/libexec/netdata/python.d/python_modules"; + } // lib.optionalAttrs (!cfg.enableAnalyticsReporting) { + DO_NOT_TRACK = "1"; + }; serviceConfig = { - Environment="PYTHONPATH=${cfg.package}/libexec/netdata/python.d/python_modules"; ExecStart = "${cfg.package}/bin/netdata -P /run/netdata/netdata.pid -D -c ${configFile}"; ExecReload = "${pkgs.util-linux}/bin/kill -s HUP -s USR1 -s USR2 $MAINPID"; TimeoutStopSec = 60; diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/default.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/default.nix index 9103a6f932db..bd74e1a9cdb5 100644 --- a/nixpkgs/nixos/modules/services/monitoring/prometheus/default.nix +++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/default.nix @@ -468,7 +468,7 @@ let ''; }; - value = mkOption { + values = mkOption { type = types.listOf types.str; default = []; description = '' diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix index 940f28189371..e0c5ceccfcc9 100644 --- a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix +++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix @@ -3,7 +3,7 @@ let inherit (lib) concatStrings foldl foldl' genAttrs literalExample maintainers mapAttrsToList mkDefault mkEnableOption mkIf mkMerge mkOption - optional types; + optional types mkOptionDefault flip attrNames; cfg = config.services.prometheus.exporters; @@ -22,15 +22,20 @@ let exporterOpts = genAttrs [ "apcupsd" + "artifactory" "bind" "bird" + "bitcoin" "blackbox" "collectd" "dnsmasq" + "domain" "dovecot" "fritzbox" "json" + "jitsi" "keylight" + "knot" "lnd" "mail" "mikrotik" @@ -40,6 +45,7 @@ let "nginx" "nginxlog" "node" + "openldap" "openvpn" "postfix" "postgres" @@ -51,6 +57,7 @@ let "smokeping" "sql" "surfboard" + "systemd" "tor" "unifi" "unifi-poller" @@ -64,7 +71,7 @@ let mkExporterOpts = ({ name, port }: { enable = mkEnableOption "the prometheus ${name} exporter"; port = mkOption { - type = types.int; + type = types.port; default = port; description = '' Port to listen on. @@ -92,9 +99,8 @@ let ''; }; firewallFilter = mkOption { - type = types.str; - default = "-p tcp -m tcp --dport ${toString cfg.${name}.port}"; - defaultText = "-p tcp -m tcp --dport ${toString port}"; + type = types.nullOr types.str; + default = null; example = literalExample '' "-i eth0 -p tcp -m tcp --dport ${toString port}" ''; @@ -122,12 +128,14 @@ let mkSubModule = { name, port, extraOpts, imports }: { ${name} = mkOption { - type = types.submodule { + type = types.submodule [{ inherit imports; options = (mkExporterOpts { inherit name port; } // extraOpts); - }; + } ({ config, ... }: mkIf config.openFirewall { + firewallFilter = mkDefault "-p tcp -m tcp --dport ${toString config.port}"; + })]; internal = true; default = {}; }; @@ -232,7 +240,13 @@ in Please specify either 'services.prometheus.exporters.sql.configuration' or 'services.prometheus.exporters.sql.configFile' ''; - } ]; + } ] ++ (flip map (attrNames cfg) (exporter: { + assertion = cfg.${exporter}.firewallFilter != null -> cfg.${exporter}.openFirewall; + message = '' + The `firewallFilter'-option of exporter ${exporter} doesn't have any effect unless + `openFirewall' is set to `true'! + ''; + })); }] ++ [(mkIf config.services.minio.enable { services.prometheus.exporters.minio.minioAddress = mkDefault "http://localhost:9000"; services.prometheus.exporters.minio.minioAccessKey = mkDefault config.services.minio.accessKey; diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/artifactory.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/artifactory.nix new file mode 100644 index 000000000000..2adcecc728bd --- /dev/null +++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/artifactory.nix @@ -0,0 +1,59 @@ +{ config, lib, pkgs, options }: + +with lib; + +let + cfg = config.services.prometheus.exporters.artifactory; +in +{ + port = 9531; + extraOpts = { + scrapeUri = mkOption { + type = types.str; + default = "http://localhost:8081/artifactory"; + description = '' + URI on which to scrape JFrog Artifactory. + ''; + }; + + artiUsername = mkOption { + type = types.str; + description = '' + Username for authentication against JFrog Artifactory API. + ''; + }; + + artiPassword = mkOption { + type = types.str; + default = ""; + description = '' + Password for authentication against JFrog Artifactory API. + One of the password or access token needs to be set. + ''; + }; + + artiAccessToken = mkOption { + type = types.str; + default = ""; + description = '' + Access token for authentication against JFrog Artifactory API. + One of the password or access token needs to be set. + ''; + }; + }; + serviceOpts = { + serviceConfig = { + ExecStart = '' + ${pkgs.prometheus-artifactory-exporter}/bin/artifactory_exporter \ + --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \ + --artifactory.scrape-uri ${cfg.scrapeUri} \ + ${concatStringsSep " \\\n " cfg.extraFlags} + ''; + Environment = [ + "ARTI_USERNAME=${cfg.artiUsername}" + "ARTI_PASSWORD=${cfg.artiPassword}" + "ARTI_ACCESS_TOKEN=${cfg.artiAccessToken}" + ]; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/bitcoin.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/bitcoin.nix new file mode 100644 index 000000000000..43721f70b499 --- /dev/null +++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/bitcoin.nix @@ -0,0 +1,82 @@ +{ config, lib, pkgs, options }: + +with lib; + +let + cfg = config.services.prometheus.exporters.bitcoin; +in +{ + port = 9332; + extraOpts = { + rpcUser = mkOption { + type = types.str; + default = "bitcoinrpc"; + description = '' + RPC user name. + ''; + }; + + rpcPasswordFile = mkOption { + type = types.path; + description = '' + File containing RPC password. + ''; + }; + + rpcScheme = mkOption { + type = types.enum [ "http" "https" ]; + default = "http"; + description = '' + Whether to connect to bitcoind over http or https. + ''; + }; + + rpcHost = mkOption { + type = types.str; + default = "localhost"; + description = '' + RPC host. + ''; + }; + + rpcPort = mkOption { + type = types.port; + default = 8332; + description = '' + RPC port number. + ''; + }; + + refreshSeconds = mkOption { + type = types.ints.unsigned; + default = 300; + description = '' + How often to ask bitcoind for metrics. + ''; + }; + + extraEnv = mkOption { + type = types.attrsOf types.str; + default = {}; + description = '' + Extra environment variables for the exporter. + ''; + }; + }; + serviceOpts = { + script = '' + export BITCOIN_RPC_PASSWORD=$(cat ${cfg.rpcPasswordFile}) + exec ${pkgs.prometheus-bitcoin-exporter}/bin/bitcoind-monitor.py + ''; + + environment = { + BITCOIN_RPC_USER = cfg.rpcUser; + BITCOIN_RPC_SCHEME = cfg.rpcScheme; + BITCOIN_RPC_HOST = cfg.rpcHost; + BITCOIN_RPC_PORT = toString cfg.rpcPort; + METRICS_ADDR = cfg.listenAddress; + METRICS_PORT = toString cfg.port; + REFRESH_SECONDS = toString cfg.refreshSeconds; + } // cfg.extraEnv; + }; +} diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/domain.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/domain.nix new file mode 100644 index 000000000000..61e2fc80afde --- /dev/null +++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/domain.nix @@ -0,0 +1,19 @@ +{ config, lib, pkgs, options }: + +with lib; + +let + cfg = config.services.prometheus.exporters.domain; +in +{ + port = 9222; + serviceOpts = { + serviceConfig = { + ExecStart = '' + ${pkgs.prometheus-domain-exporter}/bin/domain_exporter \ + --bind ${cfg.listenAddress}:${toString cfg.port} \ + ${concatStringsSep " \\\n " cfg.extraFlags} + ''; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/jitsi.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/jitsi.nix new file mode 100644 index 000000000000..c93a8f98e552 --- /dev/null +++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/jitsi.nix @@ -0,0 +1,40 @@ +{ config, lib, pkgs, options }: + +with lib; + +let + cfg = config.services.prometheus.exporters.jitsi; +in +{ + port = 9700; + extraOpts = { + url = mkOption { + type = types.str; + default = "http://localhost:8080/colibri/stats"; + description = '' + Jitsi Videobridge metrics URL to monitor. + This is usually /colibri/stats on port 8080 of the jitsi videobridge host. + ''; + }; + interval = mkOption { + type = types.str; + default = "30s"; + example = "1min"; + description = '' + How often to scrape new data + ''; + }; + }; + serviceOpts = { + serviceConfig = { + ExecStart = '' + ${pkgs.prometheus-jitsi-exporter}/bin/jitsiexporter \ + -url ${escapeShellArg cfg.url} \ + -host ${cfg.listenAddress} \ + -port ${toString cfg.port} \ + -interval ${toString cfg.interval} \ + ${concatStringsSep " \\\n " cfg.extraFlags} + ''; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/knot.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/knot.nix new file mode 100644 index 000000000000..46c28fe0a578 --- /dev/null +++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/knot.nix @@ -0,0 +1,50 @@ +{ config, lib, pkgs, options }: + +with lib; + +let + cfg = config.services.prometheus.exporters.knot; +in { + port = 9433; + extraOpts = { + knotLibraryPath = mkOption { + type = types.str; + default = "${pkgs.knot-dns.out}/lib/libknot.so"; + defaultText = "\${pkgs.knot-dns}/lib/libknot.so"; + description = '' + Path to the library of <package>knot-dns</package>. + ''; + }; + + knotSocketPath = mkOption { + type = types.str; + default = "/run/knot/knot.sock"; + description = '' + Socket path of <citerefentry><refentrytitle>knotd</refentrytitle> + <manvolnum>8</manvolnum></citerefentry>. + ''; + }; + + knotSocketTimeout = mkOption { + type = types.int; + default = 2000; + description = '' + Timeout in seconds. + ''; + }; + }; + serviceOpts = { + serviceConfig = { + ExecStart = '' + ${pkgs.prometheus-knot-exporter}/bin/knot_exporter \ + --web-listen-addr ${cfg.listenAddress} \ + --web-listen-port ${toString cfg.port} \ + --knot-library-path ${cfg.knotLibraryPath} \ + --knot-socket-path ${cfg.knotSocketPath} \ + --knot-socket-timeout ${toString cfg.knotSocketTimeout} \ + ${concatStringsSep " \\\n " cfg.extraFlags} + ''; + SupplementaryGroups = [ "knot" ]; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/openldap.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/openldap.nix new file mode 100644 index 000000000000..888611ee6fa1 --- /dev/null +++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/openldap.nix @@ -0,0 +1,67 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.prometheus.exporters.openldap; +in { + port = 9330; + extraOpts = { + ldapCredentialFile = mkOption { + type = types.path; + example = "/run/keys/ldap_pass"; + description = '' + Environment file to contain the credentials to authenticate against + <package>openldap</package>. + + The file should look like this: + <programlisting> + --- + ldapUser: "cn=monitoring,cn=Monitor" + ldapPass: "secret" + </programlisting> + ''; + }; + protocol = mkOption { + default = "tcp"; + example = "udp"; + type = types.str; + description = '' + Which protocol to use to connect against <package>openldap</package>. + ''; + }; + ldapAddr = mkOption { + default = "localhost:389"; + type = types.str; + description = '' + Address of the <package>openldap</package>-instance. + ''; + }; + metricsPath = mkOption { + default = "/metrics"; + type = types.str; + description = '' + URL path where metrics should be exposed. + ''; + }; + interval = mkOption { + default = "30s"; + type = types.str; + example = "1m"; + description = '' + Scrape interval of the exporter. + ''; + }; + }; + serviceOpts.serviceConfig = { + ExecStart = '' + ${pkgs.prometheus-openldap-exporter}/bin/openldap_exporter \ + --promAddr ${cfg.listenAddress}:${toString cfg.port} \ + --metrPath ${cfg.metricsPath} \ + --ldapNet ${cfg.protocol} \ + --interval ${cfg.interval} \ + --config ${cfg.ldapCredentialFile} \ + ${concatStringsSep " \\\n " cfg.extraFlags} + ''; + }; +} diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix index 1ece73a1159a..dd3bec8ec16c 100644 --- a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix +++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix @@ -30,12 +30,49 @@ in Whether to run the exporter as the local 'postgres' super user. ''; }; + + # TODO perhaps LoadCredential would be more appropriate + environmentFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/root/prometheus-postgres-exporter.env"; + description = '' + Environment file as defined in <citerefentry> + <refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum> + </citerefentry>. + + Secrets may be passed to the service without adding them to the + world-readable Nix store, by specifying placeholder variables as + the option value in Nix and setting these variables accordingly in the + environment file. + + Environment variables from this file will be interpolated into the + config file using envsubst with this syntax: + <literal>$ENVIRONMENT ''${VARIABLE}</literal> + + The main use is to set the DATA_SOURCE_NAME that contains the + postgres password + + note that contents from this file will override dataSourceName + if you have set it from nix. + + <programlisting> + # Content of the environment file + DATA_SOURCE_NAME=postgresql://username:password@localhost:5432/postgres?sslmode=disable + </programlisting> + + Note that this file needs to be available on the host on which + this exporter is running. + ''; + }; + }; serviceOpts = { environment.DATA_SOURCE_NAME = cfg.dataSourceName; serviceConfig = { DynamicUser = false; User = mkIf cfg.runAsLocalSuperUser (mkForce "postgres"); + EnvironmentFile = mkIf (cfg.environmentFile != null) [ cfg.environmentFile ]; ExecStart = '' ${pkgs.prometheus-postgres-exporter}/bin/postgres_exporter \ --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \ diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/systemd.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/systemd.nix new file mode 100644 index 000000000000..0514469b8a61 --- /dev/null +++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/systemd.nix @@ -0,0 +1,18 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let cfg = config.services.prometheus.exporters.systemd; + +in { + port = 9558; + + serviceOpts = { + serviceConfig = { + ExecStart = '' + ${pkgs.prometheus-systemd-exporter}/bin/systemd_exporter \ + --web.listen-address ${cfg.listenAddress}:${toString cfg.port} + ''; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/monitoring/scollector.nix b/nixpkgs/nixos/modules/services/monitoring/scollector.nix index 6f13ce889cba..ef535585e9be 100644 --- a/nixpkgs/nixos/modules/services/monitoring/scollector.nix +++ b/nixpkgs/nixos/modules/services/monitoring/scollector.nix @@ -113,7 +113,7 @@ in { description = "scollector metrics collector (part of Bosun)"; wantedBy = [ "multi-user.target" ]; - path = [ pkgs.coreutils pkgs.iproute ]; + path = [ pkgs.coreutils pkgs.iproute2 ]; serviceConfig = { User = cfg.user; diff --git a/nixpkgs/nixos/modules/services/monitoring/zabbix-agent.nix b/nixpkgs/nixos/modules/services/monitoring/zabbix-agent.nix index 73eed7aa66af..e7dd9e3393d0 100644 --- a/nixpkgs/nixos/modules/services/monitoring/zabbix-agent.nix +++ b/nixpkgs/nixos/modules/services/monitoring/zabbix-agent.nix @@ -128,11 +128,16 @@ in { LogType = "console"; Server = cfg.server; - ListenIP = cfg.listen.ip; ListenPort = cfg.listen.port; - LoadModule = builtins.attrNames cfg.modules; } - (mkIf (cfg.modules != {}) { LoadModulePath = "${moduleEnv}/lib"; }) + (mkIf (cfg.modules != {}) { + LoadModule = builtins.attrNames cfg.modules; + LoadModulePath = "${moduleEnv}/lib"; + }) + + # the default value for "ListenIP" is 0.0.0.0 but zabbix agent 2 cannot accept configuration files which + # explicitly set "ListenIP" to the default value... + (mkIf (cfg.listen.ip != "0.0.0.0") { ListenIP = cfg.listen.ip; }) ]; networking.firewall = mkIf cfg.openFirewall { diff --git a/nixpkgs/nixos/modules/services/network-filesystems/ceph.nix b/nixpkgs/nixos/modules/services/network-filesystems/ceph.nix index 632c3fb1059d..d833062c4737 100644 --- a/nixpkgs/nixos/modules/services/network-filesystems/ceph.nix +++ b/nixpkgs/nixos/modules/services/network-filesystems/ceph.nix @@ -316,7 +316,7 @@ in client = { enable = mkEnableOption "Ceph client configuration"; extraConfig = mkOption { - type = with types; attrsOf str; + type = with types; attrsOf (attrsOf str); default = {}; example = '' { diff --git a/nixpkgs/nixos/modules/services/network-filesystems/samba.nix b/nixpkgs/nixos/modules/services/network-filesystems/samba.nix index d6e2904b3c36..78ea245cb351 100644 --- a/nixpkgs/nixos/modules/services/network-filesystems/samba.nix +++ b/nixpkgs/nixos/modules/services/network-filesystems/samba.nix @@ -156,7 +156,6 @@ in securityType = mkOption { type = types.str; default = "user"; - example = "share"; description = "Samba security type"; }; diff --git a/nixpkgs/nixos/modules/services/networking/bird.nix b/nixpkgs/nixos/modules/services/networking/bird.nix index 4ae35875c0f0..6d7e7760d94e 100644 --- a/nixpkgs/nixos/modules/services/networking/bird.nix +++ b/nixpkgs/nixos/modules/services/networking/bird.nix @@ -1,7 +1,7 @@ { config, lib, pkgs, ... }: let - inherit (lib) mkEnableOption mkIf mkOption types; + inherit (lib) mkEnableOption mkIf mkOption optionalString types; generic = variant: let @@ -26,6 +26,14 @@ let <link xlink:href='http://bird.network.cz/'/> ''; }; + checkConfig = mkOption { + type = types.bool; + default = true; + description = '' + Whether the config should be checked at build time. + Disabling this might become necessary if the config includes files not present during build time. + ''; + }; }; }; @@ -36,7 +44,7 @@ let environment.etc."bird/${variant}.conf".source = pkgs.writeTextFile { name = "${variant}.conf"; text = cfg.config; - checkPhase = '' + checkPhase = optionalString cfg.checkConfig '' ${pkg}/bin/${birdBin} -d -p -c $out ''; }; @@ -50,7 +58,7 @@ let Type = "forking"; Restart = "on-failure"; ExecStart = "${pkg}/bin/${birdBin} -c /etc/bird/${variant}.conf -u ${variant} -g ${variant}"; - ExecReload = "${pkg}/bin/${birdc} configure"; + ExecReload = "/bin/sh -c '${pkg}/bin/${birdBin} -c /etc/bird/${variant}.conf -p && ${pkg}/bin/${birdc} configure'"; ExecStop = "${pkg}/bin/${birdc} down"; CapabilityBoundingSet = [ "CAP_CHOWN" "CAP_FOWNER" "CAP_DAC_OVERRIDE" "CAP_SETUID" "CAP_SETGID" # see bird/sysdep/linux/syspriv.h diff --git a/nixpkgs/nixos/modules/services/networking/cjdns.nix b/nixpkgs/nixos/modules/services/networking/cjdns.nix index f116d6392ea7..e9a0e5af1a47 100644 --- a/nixpkgs/nixos/modules/services/networking/cjdns.nix +++ b/nixpkgs/nixos/modules/services/networking/cjdns.nix @@ -245,7 +245,7 @@ in fi if [ -z "$CJDNS_ADMIN_PASSWORD" ]; then - echo "CJDNS_ADMIN_PASSWORD=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 96)" \ + echo "CJDNS_ADMIN_PASSWORD=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 32)" \ >> /etc/cjdns.keys fi ''; diff --git a/nixpkgs/nixos/modules/services/networking/consul.nix b/nixpkgs/nixos/modules/services/networking/consul.nix index bfaea4e167c2..ae7998913ee0 100644 --- a/nixpkgs/nixos/modules/services/networking/consul.nix +++ b/nixpkgs/nixos/modules/services/networking/consul.nix @@ -191,7 +191,7 @@ in ExecStop = "${cfg.package}/bin/consul leave"; }); - path = with pkgs; [ iproute gnugrep gawk consul ]; + path = with pkgs; [ iproute2 gnugrep gawk consul ]; preStart = '' mkdir -m 0700 -p ${dataDir} chown -R consul ${dataDir} diff --git a/nixpkgs/nixos/modules/services/networking/croc.nix b/nixpkgs/nixos/modules/services/networking/croc.nix new file mode 100644 index 000000000000..b218fab2196d --- /dev/null +++ b/nixpkgs/nixos/modules/services/networking/croc.nix @@ -0,0 +1,88 @@ +{ config, lib, pkgs, ... }: +let + inherit (lib) types; + cfg = config.services.croc; + rootDir = "/run/croc"; +in +{ + options.services.croc = { + enable = lib.mkEnableOption "croc relay"; + ports = lib.mkOption { + type = with types; listOf port; + default = [9009 9010 9011 9012 9013]; + description = "Ports of the relay."; + }; + pass = lib.mkOption { + type = with types; either path str; + default = "pass123"; + description = "Password or passwordfile for the relay."; + }; + openFirewall = lib.mkEnableOption "opening of the peer port(s) in the firewall"; + debug = lib.mkEnableOption "debug logs"; + }; + + config = lib.mkIf cfg.enable { + systemd.services.croc = { + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = "${pkgs.croc}/bin/croc --pass '${cfg.pass}' ${lib.optionalString cfg.debug "--debug"} relay --ports ${lib.concatMapStringsSep "," toString cfg.ports}"; + # The following options are only for optimizing: + # systemd-analyze security croc + AmbientCapabilities = ""; + CapabilityBoundingSet = ""; + DynamicUser = true; + # ProtectClock= adds DeviceAllow=char-rtc r + DeviceAllow = ""; + LockPersonality = true; + MemoryDenyWriteExecute = true; + MountAPIVFS = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateMounts = true; + PrivateNetwork = lib.mkDefault false; + PrivateTmp = true; + PrivateUsers = true; + ProcSubset = "pid"; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "noaccess"; + ProtectSystem = "strict"; + RemoveIPC = true; + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + RootDirectory = rootDir; + # Avoid mounting rootDir in the own rootDir of ExecStart='s mount namespace. + InaccessiblePaths = [ "-+${rootDir}" ]; + BindReadOnlyPaths = [ + builtins.storeDir + ] ++ lib.optional (types.path.check cfg.pass) cfg.pass; + # This is for BindReadOnlyPaths= + # to allow traversal of directories they create in RootDirectory=. + UMask = "0066"; + # Create rootDir in the host's mount namespace. + RuntimeDirectory = [(baseNameOf rootDir)]; + RuntimeDirectoryMode = "700"; + SystemCallFilter = [ + "@system-service" + "~@aio" "~@chown" "~@keyring" "~@memlock" + "~@privileged" "~@resources" "~@setuid" + "~@sync" "~@timer" + ]; + SystemCallArchitectures = "native"; + SystemCallErrorNumber = "EPERM"; + }; + }; + + networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall cfg.ports; + }; + + meta.maintainers = with lib.maintainers; [ hax404 julm ]; +} diff --git a/nixpkgs/nixos/modules/services/networking/dhcpcd.nix b/nixpkgs/nixos/modules/services/networking/dhcpcd.nix index d10bffd91474..31e4b6ad2988 100644 --- a/nixpkgs/nixos/modules/services/networking/dhcpcd.nix +++ b/nixpkgs/nixos/modules/services/networking/dhcpcd.nix @@ -191,9 +191,8 @@ in { description = "DHCP Client"; wantedBy = [ "multi-user.target" ] ++ optional (!hasDefaultGatewaySet) "network-online.target"; - wants = [ "network.target" "systemd-udev-settle.service" ]; + wants = [ "network.target" ]; before = [ "network-online.target" ]; - after = [ "systemd-udev-settle.service" ]; restartTriggers = [ exitHook ]; diff --git a/nixpkgs/nixos/modules/services/networking/dnscrypt-proxy2.nix b/nixpkgs/nixos/modules/services/networking/dnscrypt-proxy2.nix index afc2a6d1c757..72965c267a86 100644 --- a/nixpkgs/nixos/modules/services/networking/dnscrypt-proxy2.nix +++ b/nixpkgs/nixos/modules/services/networking/dnscrypt-proxy2.nix @@ -113,7 +113,6 @@ in "~@memlock" "~@resources" "~@setuid" - "~@sync" "~@timer" ]; }; diff --git a/nixpkgs/nixos/modules/services/networking/flannel.nix b/nixpkgs/nixos/modules/services/networking/flannel.nix index 4c040112d28d..32a7eb3ed69e 100644 --- a/nixpkgs/nixos/modules/services/networking/flannel.nix +++ b/nixpkgs/nixos/modules/services/networking/flannel.nix @@ -162,10 +162,7 @@ in { NODE_NAME = cfg.nodeName; }; path = [ pkgs.iptables ]; - preStart = '' - mkdir -p /run/flannel - touch /run/flannel/docker - '' + optionalString (cfg.storageBackend == "etcd") '' + preStart = optionalString (cfg.storageBackend == "etcd") '' echo "setting network configuration" until ${pkgs.etcdctl}/bin/etcdctl set /coreos.com/network/config '${builtins.toJSON networkConfig}' do @@ -177,6 +174,7 @@ in { ExecStart = "${cfg.package}/bin/flannel"; Restart = "always"; RestartSec = "10s"; + RuntimeDirectory = "flannel"; }; }; diff --git a/nixpkgs/nixos/modules/services/networking/gvpe.nix b/nixpkgs/nixos/modules/services/networking/gvpe.nix index b851facf1e32..4fad37ba15ee 100644 --- a/nixpkgs/nixos/modules/services/networking/gvpe.nix +++ b/nixpkgs/nixos/modules/services/networking/gvpe.nix @@ -27,7 +27,7 @@ let text = '' #! /bin/sh - export PATH=$PATH:${pkgs.iproute}/sbin + export PATH=$PATH:${pkgs.iproute2}/sbin ip link set $IFNAME up ip address add ${cfg.ipAddress} dev $IFNAME diff --git a/nixpkgs/nixos/modules/services/networking/inspircd.nix b/nixpkgs/nixos/modules/services/networking/inspircd.nix new file mode 100644 index 000000000000..8cb2b406ee28 --- /dev/null +++ b/nixpkgs/nixos/modules/services/networking/inspircd.nix @@ -0,0 +1,62 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.inspircd; + + configFile = pkgs.writeText "inspircd.conf" cfg.config; + +in { + meta = { + maintainers = [ lib.maintainers.sternenseemann ]; + }; + + options = { + services.inspircd = { + enable = lib.mkEnableOption "InspIRCd"; + + package = lib.mkOption { + type = lib.types.package; + default = pkgs.inspircd; + defaultText = lib.literalExample "pkgs.inspircd"; + example = lib.literalExample "pkgs.inspircdMinimal"; + description = '' + The InspIRCd package to use. This is mainly useful + to specify an overridden version of the + <literal>pkgs.inspircd</literal> dervivation, for + example if you want to use a more minimal InspIRCd + distribution with less modules enabled or with + modules enabled which can't be distributed in binary + form due to licensing issues. + ''; + }; + + config = lib.mkOption { + type = lib.types.lines; + description = '' + Verbatim <literal>inspircd.conf</literal> file. + For a list of options, consult the + <link xlink:href="https://docs.inspircd.org/3/configuration/">InspIRCd documentation</link>, the + <link xlink:href="https://docs.inspircd.org/3/modules/">Module documentation</link> + and the example configuration files distributed + with <literal>pkgs.inspircd.doc</literal> + ''; + }; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.inspircd = { + description = "InspIRCd - the stable, high-performance and modular Internet Relay Chat Daemon"; + wantedBy = [ "multi-user.target" ]; + requires = [ "network.target" ]; + + serviceConfig = { + Type = "simple"; + ExecStart = '' + ${lib.getBin cfg.package}/bin/inspircd start --config ${configFile} --nofork --nopid + ''; + DynamicUser = true; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/networking/ircd-hybrid/default.nix b/nixpkgs/nixos/modules/services/networking/ircd-hybrid/default.nix index 0781159b6ee7..1f5636e4e3a9 100644 --- a/nixpkgs/nixos/modules/services/networking/ircd-hybrid/default.nix +++ b/nixpkgs/nixos/modules/services/networking/ircd-hybrid/default.nix @@ -10,7 +10,7 @@ let name = "ircd-hybrid-service"; scripts = [ "=>/bin" ./control.in ]; substFiles = [ "=>/conf" ./ircd.conf ]; - inherit (pkgs) ircdHybrid coreutils su iproute gnugrep procps; + inherit (pkgs) ircdHybrid coreutils su iproute2 gnugrep procps; ipv6Enabled = boolToString config.networking.enableIPv6; diff --git a/nixpkgs/nixos/modules/services/networking/kresd.nix b/nixpkgs/nixos/modules/services/networking/kresd.nix index 4131ff8be5d0..3f9be6172f1b 100644 --- a/nixpkgs/nixos/modules/services/networking/kresd.nix +++ b/nixpkgs/nixos/modules/services/networking/kresd.nix @@ -8,14 +8,14 @@ let # Convert systemd-style address specification to kresd config line(s). # On Nix level we don't attempt to precisely validate the address specifications. mkListen = kind: addr: let - al_v4 = builtins.match "([0-9.]\+):([0-9]\+)" addr; - al_v6 = builtins.match "\\[(.\+)]:([0-9]\+)" addr; - al_portOnly = builtins.match "()([0-9]\+)" addr; + al_v4 = builtins.match "([0-9.]+):([0-9]+)" addr; + al_v6 = builtins.match "\\[(.+)]:([0-9]+)" addr; + al_portOnly = builtins.match "([0-9]+)" addr; al = findFirst (a: a != null) (throw "services.kresd.*: incorrect address specification '${addr}'") [ al_v4 al_v6 al_portOnly ]; port = last al; - addrSpec = if al_portOnly == null then "'${head al}'" else "{'::', '127.0.0.1'}"; + addrSpec = if al_portOnly == null then "'${head al}'" else "{'::', '0.0.0.0'}"; in # freebind is set for compatibility with earlier kresd services; # it could be configurable, for example. '' @@ -137,10 +137,5 @@ in { }; # We don't mind running stop phase from wrong version. It seems less racy. systemd.services."kresd@".stopIfChanged = false; - - # Try cleaning up the previously default location of cache file. - # Note that /var/cache/* should always be safe to remove. - # TODO: remove later, probably between 20.09 and 21.05 - systemd.tmpfiles.rules = [ "R /var/cache/kresd" ]; }; } diff --git a/nixpkgs/nixos/modules/services/networking/libreswan.nix b/nixpkgs/nixos/modules/services/networking/libreswan.nix index 280158b89f61..81bc4e1cf95c 100644 --- a/nixpkgs/nixos/modules/services/networking/libreswan.nix +++ b/nixpkgs/nixos/modules/services/networking/libreswan.nix @@ -85,13 +85,13 @@ in config = mkIf cfg.enable { - environment.systemPackages = [ pkgs.libreswan pkgs.iproute ]; + environment.systemPackages = [ pkgs.libreswan pkgs.iproute2 ]; systemd.services.ipsec = { description = "Internet Key Exchange (IKE) Protocol Daemon for IPsec"; path = [ "${pkgs.libreswan}" - "${pkgs.iproute}" + "${pkgs.iproute2}" "${pkgs.procps}" "${pkgs.nssTools}" "${pkgs.iptables}" @@ -115,8 +115,8 @@ in ExecStart = "${libexec}/pluto --config ${configFile} --nofork \$PLUTO_OPTIONS"; ExecStop = "${libexec}/whack --shutdown"; ExecStopPost = [ - "${pkgs.iproute}/bin/ip xfrm policy flush" - "${pkgs.iproute}/bin/ip xfrm state flush" + "${pkgs.iproute2}/bin/ip xfrm policy flush" + "${pkgs.iproute2}/bin/ip xfrm state flush" "${ipsec} --stopnflog" ]; ExecReload = "${libexec}/whack --listen"; diff --git a/nixpkgs/nixos/modules/services/networking/mullvad-vpn.nix b/nixpkgs/nixos/modules/services/networking/mullvad-vpn.nix index 6f595ca4be2b..8ce71f26b3ee 100644 --- a/nixpkgs/nixos/modules/services/networking/mullvad-vpn.nix +++ b/nixpkgs/nixos/modules/services/networking/mullvad-vpn.nix @@ -28,7 +28,7 @@ with lib; "systemd-resolved.service" ]; path = [ - pkgs.iproute + pkgs.iproute2 # Needed for ping "/run/wrappers" ]; diff --git a/nixpkgs/nixos/modules/services/networking/networkmanager.nix b/nixpkgs/nixos/modules/services/networking/networkmanager.nix index 2e680544ec24..119bd09e2fdb 100644 --- a/nixpkgs/nixos/modules/services/networking/networkmanager.nix +++ b/nixpkgs/nixos/modules/services/networking/networkmanager.nix @@ -465,7 +465,7 @@ in { restartTriggers = [ configFile overrideNameserversScript ]; # useful binaries for user-specified hooks - path = [ pkgs.iproute pkgs.util-linux pkgs.coreutils ]; + path = [ pkgs.iproute2 pkgs.util-linux pkgs.coreutils ]; aliases = [ "dbus-org.freedesktop.nm-dispatcher.service" ]; }; diff --git a/nixpkgs/nixos/modules/services/networking/nomad.nix b/nixpkgs/nixos/modules/services/networking/nomad.nix index 9f1b443b89bc..48689f1195c5 100644 --- a/nixpkgs/nixos/modules/services/networking/nomad.nix +++ b/nixpkgs/nixos/modules/services/networking/nomad.nix @@ -119,7 +119,7 @@ in path = cfg.extraPackages ++ (with pkgs; [ # Client mode requires at least the following: coreutils - iproute + iproute2 iptables ]); diff --git a/nixpkgs/nixos/modules/services/networking/openvpn.nix b/nixpkgs/nixos/modules/services/networking/openvpn.nix index 650f9c84ac72..b4c2c944b6e6 100644 --- a/nixpkgs/nixos/modules/services/networking/openvpn.nix +++ b/nixpkgs/nixos/modules/services/networking/openvpn.nix @@ -63,7 +63,7 @@ let wantedBy = optional cfg.autoStart "multi-user.target"; after = [ "network.target" ]; - path = [ pkgs.iptables pkgs.iproute pkgs.nettools ]; + path = [ pkgs.iptables pkgs.iproute2 pkgs.nettools ]; serviceConfig.ExecStart = "@${openvpn}/sbin/openvpn openvpn --suppress-timestamps --config ${configFile}"; serviceConfig.Restart = "always"; diff --git a/nixpkgs/nixos/modules/services/networking/privoxy.nix b/nixpkgs/nixos/modules/services/networking/privoxy.nix index 7caae3282032..7c22b7d09b9b 100644 --- a/nixpkgs/nixos/modules/services/networking/privoxy.nix +++ b/nixpkgs/nixos/modules/services/networking/privoxy.nix @@ -4,26 +4,46 @@ with lib; let - inherit (pkgs) privoxy; - cfg = config.services.privoxy; - confFile = pkgs.writeText "privoxy.conf" ('' - user-manual ${privoxy}/share/doc/privoxy/user-manual - confdir ${privoxy}/etc/ - listen-address ${cfg.listenAddress} - enable-edit-actions ${if (cfg.enableEditActions == true) then "1" else "0"} - ${concatMapStrings (f: "actionsfile ${f}\n") cfg.actionsFiles} - ${concatMapStrings (f: "filterfile ${f}\n") cfg.filterFiles} - '' + optionalString cfg.enableTor '' - forward-socks5t / 127.0.0.1:9063 . - toggle 1 - enable-remote-toggle 0 - enable-edit-actions 0 - enable-remote-http-toggle 0 - '' + '' - ${cfg.extraConfig} - ''); + serialise = name: val: + if isList val then concatMapStrings (serialise name) val + else if isBool val then serialise name (if val then "1" else "0") + else "${name} ${toString val}\n"; + + configType = with types; + let atom = oneOf [ int bool string path ]; + in attrsOf (either atom (listOf atom)) + // { description = '' + privoxy configuration type. The format consists of an attribute + set of settings. Each setting can be either a value (integer, string, + boolean or path) or a list of such values. + ''; + }; + + ageType = types.str // { + check = x: + isString x && + (builtins.match "([0-9]+([smhdw]|min|ms|us)*)+" x != null); + description = "tmpfiles.d(5) age format"; + }; + + configFile = pkgs.writeText "privoxy.conf" + (concatStrings ( + # Relative paths in some options are relative to confdir. Privoxy seems + # to parse the options in order of appearance, so this must come first. + # Nix however doesn't preserve the order in attrsets, so we have to + # hardcode confdir here. + [ "confdir ${pkgs.privoxy}/etc\n" ] + ++ mapAttrsToList serialise cfg.settings + )); + + inspectAction = pkgs.writeText "inspect-all-https.action" + '' + # Enable HTTPS inspection for all requests + {+https-inspection} + / + ''; in @@ -31,70 +51,144 @@ in ###### interface - options = { + options.services.privoxy = { - services.privoxy = { + enable = mkEnableOption "Privoxy, non-caching filtering proxy"; - enable = mkOption { - type = types.bool; - default = false; - description = '' - Whether to enable the Privoxy non-caching filtering proxy. - ''; - }; + enableTor = mkOption { + type = types.bool; + default = false; + description = '' + Whether to configure Privoxy to use Tor's faster SOCKS port, + suitable for HTTP. + ''; + }; - listenAddress = mkOption { - type = types.str; - default = "127.0.0.1:8118"; - description = '' - Address the proxy server is listening to. - ''; - }; + inspectHttps = mkOption { + type = types.bool; + default = false; + description = '' + Whether to configure Privoxy to inspect HTTPS requests, meaning all + encrypted traffic will be filtered as well. This works by decrypting + and re-encrypting the requests using a per-domain generated certificate. - actionsFiles = mkOption { - type = types.listOf types.str; - example = [ "match-all.action" "default.action" "/etc/privoxy/user.action" ]; - default = [ "match-all.action" "default.action" ]; - description = '' - List of paths to Privoxy action files. - These paths may either be absolute or relative to the privoxy configuration directory. - ''; - }; + To issue per-domain certificates, Privoxy must be provided with a CA + certificate, using the <literal>ca-cert-file</literal>, + <literal>ca-key-file</literal> settings. - filterFiles = mkOption { - type = types.listOf types.str; - example = [ "default.filter" "/etc/privoxy/user.filter" ]; - default = [ "default.filter" ]; - description = '' - List of paths to Privoxy filter files. - These paths may either be absolute or relative to the privoxy configuration directory. - ''; - }; + <warning><para> + The CA certificate must also be added to the system trust roots, + otherwise browsers will reject all Privoxy certificates as invalid. + You can do so by using the option + <option>security.pki.certificateFiles</option>. + </para></warning> + ''; + }; - enableEditActions = mkOption { - type = types.bool; - default = false; - description = '' - Whether or not the web-based actions file editor may be used. - ''; - }; + certsLifetime = mkOption { + type = ageType; + default = "10d"; + example = "12h"; + description = '' + If <literal>inspectHttps</literal> is enabled, the time generated HTTPS + certificates will be stored in a temporary directory for reuse. Once + the lifetime has expired the directory will cleared and the certificate + will have to be generated again, on-demand. - enableTor = mkOption { - type = types.bool; - default = false; - description = '' - Whether to configure Privoxy to use Tor's faster SOCKS port, - suitable for HTTP. - ''; - }; + Depending on the traffic, you may want to reduce the lifetime to limit + the disk usage, since Privoxy itself never deletes the certificates. - extraConfig = mkOption { - type = types.lines; - default = "" ; - description = '' - Extra configuration. Contents will be added verbatim to the configuration file. - ''; + <note><para>The format is that of the <literal>tmpfiles.d(5)</literal> + Age parameter.</para></note> + ''; + }; + + userActions = mkOption { + type = types.lines; + default = ""; + description = '' + Actions to be included in a <literal>user.action</literal> file. This + will have a higher priority and can be used to override all other + actions. + ''; + }; + + userFilters = mkOption { + type = types.lines; + default = ""; + description = '' + Filters to be included in a <literal>user.filter</literal> file. This + will have a higher priority and can be used to override all other + filters definitions. + ''; + }; + + settings = mkOption { + type = types.submodule { + freeformType = configType; + + options.listen-address = mkOption { + type = types.str; + default = "127.0.0.1:8118"; + description = "Pair of address:port the proxy server is listening to."; + }; + + options.enable-edit-actions = mkOption { + type = types.bool; + default = false; + description = "Whether the web-based actions file editor may be used."; + }; + + options.actionsfile = mkOption { + type = types.listOf types.str; + # This must come after all other entries, in order to override the + # other actions/filters installed by Privoxy or the user. + apply = x: x ++ optional (cfg.userActions != "") + (toString (pkgs.writeText "user.actions" cfg.userActions)); + default = [ "match-all.action" "default.action" ]; + description = '' + List of paths to Privoxy action files. These paths may either be + absolute or relative to the privoxy configuration directory. + ''; + }; + + options.filterfile = mkOption { + type = types.listOf types.str; + default = [ "default.filter" ]; + apply = x: x ++ optional (cfg.userFilters != "") + (toString (pkgs.writeText "user.filter" cfg.userFilters)); + description = '' + List of paths to Privoxy filter files. These paths may either be + absolute or relative to the privoxy configuration directory. + ''; + }; }; + default = {}; + example = literalExample '' + { # Listen on IPv6 only + listen-address = "[::]:8118"; + + # Forward .onion requests to Tor + forward-socks5 = ".onion localhost:9050 ."; + + # Log redirects and filters + debug = [ 128 64 ]; + # This is equivalent to writing these lines + # in the Privoxy configuration file: + # debug 128 + # debug 64 + } + ''; + description = '' + This option is mapped to the main Privoxy configuration file. + Check out the Privoxy user manual at + <link xlink:href="https://www.privoxy.org/user-manual/config.html"/> + for available settings and documentation. + + <note><para> + Repeated settings can be represented by using a list. + </para></note> + ''; }; }; @@ -104,23 +198,33 @@ in config = mkIf cfg.enable { users.users.privoxy = { + description = "Privoxy daemon user"; isSystemUser = true; - home = "/var/empty"; group = "privoxy"; }; users.groups.privoxy = {}; + systemd.tmpfiles.rules = optional cfg.inspectHttps + "d ${cfg.settings.certificate-directory} 0770 privoxy privoxy ${cfg.certsLifetime}"; + systemd.services.privoxy = { description = "Filtering web proxy"; after = [ "network.target" "nss-lookup.target" ]; wantedBy = [ "multi-user.target" ]; - serviceConfig.ExecStart = "${privoxy}/bin/privoxy --no-daemon --user privoxy ${confFile}"; - - serviceConfig.PrivateDevices = true; - serviceConfig.PrivateTmp = true; - serviceConfig.ProtectHome = true; - serviceConfig.ProtectSystem = "full"; + serviceConfig = { + User = "privoxy"; + Group = "privoxy"; + ExecStart = "${pkgs.privoxy}/bin/privoxy --no-daemon ${configFile}"; + PrivateDevices = true; + PrivateTmp = true; + ProtectHome = true; + ProtectSystem = "full"; + }; + unitConfig = mkIf cfg.inspectHttps { + ConditionPathExists = with cfg.settings; + [ ca-cert-file ca-key-file ]; + }; }; services.tor.settings.SOCKSPort = mkIf cfg.enableTor [ @@ -128,8 +232,48 @@ in { addr = "127.0.0.1"; port = 9063; IsolateDestAddr = false; } ]; + services.privoxy.settings = { + user-manual = "${pkgs.privoxy}/share/doc/privoxy/user-manual"; + # This is needed for external filters + temporary-directory = "/tmp"; + filterfile = [ "default.filter" ]; + actionsfile = + [ "match-all.action" + "default.action" + ] ++ optional cfg.inspectHttps (toString inspectAction); + } // (optionalAttrs cfg.enableTor { + forward-socks5 = "127.0.0.1:9063 ."; + toggle = true; + enable-remote-toggle = false; + enable-edit-actions = false; + enable-remote-http-toggle = false; + }) // (optionalAttrs cfg.inspectHttps { + # This allows setting absolute key/crt paths + ca-directory = "/var/empty"; + certificate-directory = "/run/privoxy/certs"; + trusted-cas-file = "/etc/ssl/certs/ca-certificates.crt"; + }); + }; + imports = + let + top = x: [ "services" "privoxy" x ]; + setting = x: [ "services" "privoxy" "settings" x ]; + in + [ (mkRenamedOptionModule (top "enableEditActions") (setting "enable-edit-actions")) + (mkRenamedOptionModule (top "listenAddress") (setting "listen-address")) + (mkRenamedOptionModule (top "actionsFiles") (setting "actionsfile")) + (mkRenamedOptionModule (top "filterFiles") (setting "filterfile")) + (mkRemovedOptionModule (top "extraConfig") + '' + Use services.privoxy.settings instead. + This is part of the general move to use structured settings instead of raw + text for config as introduced by RFC0042: + https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md + '') + ]; + meta.maintainers = with lib.maintainers; [ rnhmjoj ]; } diff --git a/nixpkgs/nixos/modules/services/networking/quagga.nix b/nixpkgs/nixos/modules/services/networking/quagga.nix index 5acdd5af8f8f..7c169fe62d8d 100644 --- a/nixpkgs/nixos/modules/services/networking/quagga.nix +++ b/nixpkgs/nixos/modules/services/networking/quagga.nix @@ -164,7 +164,7 @@ in preStart = '' install -m 0755 -o quagga -g quagga -d /run/quagga - ${pkgs.iproute}/bin/ip route flush proto zebra + ${pkgs.iproute2}/bin/ip route flush proto zebra ''; } else diff --git a/nixpkgs/nixos/modules/services/networking/rxe.nix b/nixpkgs/nixos/modules/services/networking/rxe.nix index c7d174a00de2..868e2c81ccbd 100644 --- a/nixpkgs/nixos/modules/services/networking/rxe.nix +++ b/nixpkgs/nixos/modules/services/networking/rxe.nix @@ -39,11 +39,11 @@ in { Type = "oneshot"; RemainAfterExit = true; ExecStart = map ( x: - "${pkgs.iproute}/bin/rdma link add rxe_${x} type rxe netdev ${x}" + "${pkgs.iproute2}/bin/rdma link add rxe_${x} type rxe netdev ${x}" ) cfg.interfaces; ExecStop = map ( x: - "${pkgs.iproute}/bin/rdma link delete rxe_${x}" + "${pkgs.iproute2}/bin/rdma link delete rxe_${x}" ) cfg.interfaces; }; }; diff --git a/nixpkgs/nixos/modules/services/networking/sslh.nix b/nixpkgs/nixos/modules/services/networking/sslh.nix index 4c2740d20192..abe96f60f811 100644 --- a/nixpkgs/nixos/modules/services/networking/sslh.nix +++ b/nixpkgs/nixos/modules/services/networking/sslh.nix @@ -132,7 +132,7 @@ in { table = "mangle"; command = "OUTPUT ! -o lo -p tcp -m connmark --mark 0x02/0x0f -j CONNMARK --restore-mark --mask 0x0f"; } ]; in { - path = [ pkgs.iptables pkgs.iproute pkgs.procps ]; + path = [ pkgs.iptables pkgs.iproute2 pkgs.procps ]; preStart = '' # Cleanup old iptables entries which might be still there diff --git a/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/module.nix b/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/module.nix index f67eedac2961..6e619f22546f 100644 --- a/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/module.nix +++ b/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/module.nix @@ -63,7 +63,7 @@ in { description = "strongSwan IPsec IKEv1/IKEv2 daemon using swanctl"; wantedBy = [ "multi-user.target" ]; after = [ "network-online.target" ]; - path = with pkgs; [ kmod iproute iptables util-linux ]; + path = with pkgs; [ kmod iproute2 iptables util-linux ]; environment = { STRONGSWAN_CONF = pkgs.writeTextFile { name = "strongswan.conf"; diff --git a/nixpkgs/nixos/modules/services/networking/strongswan.nix b/nixpkgs/nixos/modules/services/networking/strongswan.nix index f6170b813654..401f7be40288 100644 --- a/nixpkgs/nixos/modules/services/networking/strongswan.nix +++ b/nixpkgs/nixos/modules/services/networking/strongswan.nix @@ -152,7 +152,7 @@ in systemd.services.strongswan = { description = "strongSwan IPSec Service"; wantedBy = [ "multi-user.target" ]; - path = with pkgs; [ kmod iproute iptables util-linux ]; # XXX Linux + path = with pkgs; [ kmod iproute2 iptables util-linux ]; # XXX Linux after = [ "network-online.target" ]; environment = { STRONGSWAN_CONF = strongswanConf { inherit setup connections ca secretsFile managePlugins enabledPlugins; }; diff --git a/nixpkgs/nixos/modules/services/networking/tailscale.nix b/nixpkgs/nixos/modules/services/networking/tailscale.nix index 1a1474595beb..9a28a266a928 100644 --- a/nixpkgs/nixos/modules/services/networking/tailscale.nix +++ b/nixpkgs/nixos/modules/services/networking/tailscale.nix @@ -28,6 +28,7 @@ in { systemd.packages = [ cfg.package ]; systemd.services.tailscaled = { wantedBy = [ "multi-user.target" ]; + path = [ pkgs.openresolv ]; serviceConfig.Environment = "PORT=${toString cfg.port}"; }; }; diff --git a/nixpkgs/nixos/modules/services/networking/wg-quick.nix b/nixpkgs/nixos/modules/services/networking/wg-quick.nix index 02fe40a22a10..3b76de58548f 100644 --- a/nixpkgs/nixos/modules/services/networking/wg-quick.nix +++ b/nixpkgs/nixos/modules/services/networking/wg-quick.nix @@ -57,7 +57,7 @@ let preUp = mkOption { example = literalExample '' - ${pkgs.iproute}/bin/ip netns add foo + ${pkgs.iproute2}/bin/ip netns add foo ''; default = ""; type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines; @@ -68,7 +68,7 @@ let preDown = mkOption { example = literalExample '' - ${pkgs.iproute}/bin/ip netns del foo + ${pkgs.iproute2}/bin/ip netns del foo ''; default = ""; type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines; @@ -79,7 +79,7 @@ let postUp = mkOption { example = literalExample '' - ${pkgs.iproute}/bin/ip netns add foo + ${pkgs.iproute2}/bin/ip netns add foo ''; default = ""; type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines; @@ -90,7 +90,7 @@ let postDown = mkOption { example = literalExample '' - ${pkgs.iproute}/bin/ip netns del foo + ${pkgs.iproute2}/bin/ip netns del foo ''; default = ""; type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines; diff --git a/nixpkgs/nixos/modules/services/networking/wireguard.nix b/nixpkgs/nixos/modules/services/networking/wireguard.nix index e07020349cf4..34c869345357 100644 --- a/nixpkgs/nixos/modules/services/networking/wireguard.nix +++ b/nixpkgs/nixos/modules/services/networking/wireguard.nix @@ -63,7 +63,7 @@ let preSetup = mkOption { example = literalExample '' - ${pkgs.iproute}/bin/ip netns add foo + ${pkgs.iproute2}/bin/ip netns add foo ''; default = ""; type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines; @@ -238,7 +238,7 @@ let wantedBy = [ "wireguard-${name}.service" ]; requiredBy = [ "wireguard-${name}.service" ]; before = [ "wireguard-${name}.service" ]; - path = with pkgs; [ wireguard ]; + path = with pkgs; [ wireguard-tools ]; serviceConfig = { Type = "oneshot"; @@ -278,7 +278,7 @@ let wantedBy = [ "multi-user.target" "wireguard-${interfaceName}.service" ]; environment.DEVICE = interfaceName; environment.WG_ENDPOINT_RESOLUTION_RETRIES = "infinity"; - path = with pkgs; [ iproute wireguard-tools ]; + path = with pkgs; [ iproute2 wireguard-tools ]; serviceConfig = { Type = "oneshot"; @@ -333,7 +333,7 @@ let after = [ "network.target" "network-online.target" ]; wantedBy = [ "multi-user.target" ]; environment.DEVICE = name; - path = with pkgs; [ kmod iproute wireguard-tools ]; + path = with pkgs; [ kmod iproute2 wireguard-tools ]; serviceConfig = { Type = "oneshot"; diff --git a/nixpkgs/nixos/modules/services/networking/zerobin.nix b/nixpkgs/nixos/modules/services/networking/zerobin.nix index 78de246a816f..16db25d62304 100644 --- a/nixpkgs/nixos/modules/services/networking/zerobin.nix +++ b/nixpkgs/nixos/modules/services/networking/zerobin.nix @@ -88,7 +88,7 @@ in enable = true; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; - serviceConfig.ExecStart = "${pkgs.pythonPackages.zerobin}/bin/zerobin ${cfg.listenAddress} ${toString cfg.listenPort} false ${cfg.user} ${cfg.group} ${zerobin_config}"; + serviceConfig.ExecStart = "${pkgs.zerobin}/bin/zerobin ${cfg.listenAddress} ${toString cfg.listenPort} false ${cfg.user} ${cfg.group} ${zerobin_config}"; serviceConfig.PrivateTmp="yes"; serviceConfig.User = cfg.user; serviceConfig.Group = cfg.group; diff --git a/nixpkgs/nixos/modules/services/printing/cupsd.nix b/nixpkgs/nixos/modules/services/printing/cupsd.nix index e67badfcd29e..d2b36d9e7541 100644 --- a/nixpkgs/nixos/modules/services/printing/cupsd.nix +++ b/nixpkgs/nixos/modules/services/printing/cupsd.nix @@ -104,7 +104,7 @@ let ignoreCollisions = true; }; - filterGutenprint = pkgs: filter (pkg: pkg.meta.isGutenprint or false == true) pkgs; + filterGutenprint = filter (pkg: pkg.meta.isGutenprint or false == true); containsGutenprint = pkgs: length (filterGutenprint pkgs) > 0; getGutenprint = pkgs: head (filterGutenprint pkgs); @@ -270,7 +270,7 @@ in drivers = mkOption { type = types.listOf types.path; default = []; - example = literalExample "with pkgs; [ gutenprint hplip splix cups-googlecloudprint ]"; + example = literalExample "with pkgs; [ gutenprint hplip splix ]"; description = '' CUPS drivers to use. Drivers provided by CUPS, cups-filters, Ghostscript and Samba are added unconditionally. If this list contains diff --git a/nixpkgs/nixos/modules/services/security/clamav.nix b/nixpkgs/nixos/modules/services/security/clamav.nix index aaf6fb0479ba..340cbbf02fb4 100644 --- a/nixpkgs/nixos/modules/services/security/clamav.nix +++ b/nixpkgs/nixos/modules/services/security/clamav.nix @@ -8,30 +8,19 @@ let cfg = config.services.clamav; pkg = pkgs.clamav; - clamdConfigFile = pkgs.writeText "clamd.conf" '' - DatabaseDirectory ${stateDir} - LocalSocket ${runDir}/clamd.ctl - PidFile ${runDir}/clamd.pid - TemporaryDirectory /tmp - User clamav - Foreground yes - - ${cfg.daemon.extraConfig} - ''; - - freshclamConfigFile = pkgs.writeText "freshclam.conf" '' - DatabaseDirectory ${stateDir} - Foreground yes - Checks ${toString cfg.updater.frequency} - - ${cfg.updater.extraConfig} - - DatabaseMirror database.clamav.net - ''; + toKeyValue = generators.toKeyValue { + mkKeyValue = generators.mkKeyValueDefault {} " "; + listsAsDuplicateKeys = true; + }; + + clamdConfigFile = pkgs.writeText "clamd.conf" (toKeyValue cfg.daemon.settings); + freshclamConfigFile = pkgs.writeText "freshclam.conf" (toKeyValue cfg.updater.settings); in { imports = [ - (mkRenamedOptionModule [ "services" "clamav" "updater" "config" ] [ "services" "clamav" "updater" "extraConfig" ]) + (mkRemovedOptionModule [ "services" "clamav" "updater" "config" ] "Use services.clamav.updater.settings instead.") + (mkRemovedOptionModule [ "services" "clamav" "updater" "extraConfig" ] "Use services.clamav.updater.settings instead.") + (mkRemovedOptionModule [ "services" "clamav" "daemon" "extraConfig" ] "Use services.clamav.daemon.settings instead.") ]; options = { @@ -39,12 +28,12 @@ in daemon = { enable = mkEnableOption "ClamAV clamd daemon"; - extraConfig = mkOption { - type = types.lines; - default = ""; + settings = mkOption { + type = with types; attrsOf (oneOf [ bool int str (listOf str) ]); + default = {}; description = '' - Extra configuration for clamd. Contents will be added verbatim to the - configuration file. + ClamAV configuration. Refer to <link xlink:href="https://linux.die.net/man/5/clamd.conf"/>, + for details on supported values. ''; }; }; @@ -68,12 +57,12 @@ in ''; }; - extraConfig = mkOption { - type = types.lines; - default = ""; + settings = mkOption { + type = with types; attrsOf (oneOf [ bool int str (listOf str) ]); + default = {}; description = '' - Extra configuration for freshclam. Contents will be added verbatim to the - configuration file. + freshclam configuration. Refer to <link xlink:href="https://linux.die.net/man/5/freshclam.conf"/>, + for details on supported values. ''; }; }; @@ -93,6 +82,22 @@ in users.groups.${clamavGroup} = { gid = config.ids.gids.clamav; }; + services.clamav.daemon.settings = { + DatabaseDirectory = stateDir; + LocalSocket = "${runDir}/clamd.ctl"; + PidFile = "${runDir}/clamd.pid"; + TemporaryDirectory = "/tmp"; + User = "clamav"; + Foreground = true; + }; + + services.clamav.updater.settings = { + DatabaseDirectory = stateDir; + Foreground = true; + Checks = cfg.updater.frequency; + DatabaseMirror = [ "database.clamav.net" ]; + }; + environment.etc."clamav/freshclam.conf".source = freshclamConfigFile; environment.etc."clamav/clamd.conf".source = clamdConfigFile; diff --git a/nixpkgs/nixos/modules/services/security/fail2ban.nix b/nixpkgs/nixos/modules/services/security/fail2ban.nix index cf0d72d5c531..b901b19cf318 100644 --- a/nixpkgs/nixos/modules/services/security/fail2ban.nix +++ b/nixpkgs/nixos/modules/services/security/fail2ban.nix @@ -243,7 +243,7 @@ in restartTriggers = [ fail2banConf jailConf pathsConf ]; reloadIfChanged = true; - path = [ cfg.package cfg.packageFirewall pkgs.iproute ]; + path = [ cfg.package cfg.packageFirewall pkgs.iproute2 ]; unitConfig.Documentation = "man:fail2ban(1)"; diff --git a/nixpkgs/nixos/modules/services/security/privacyidea.nix b/nixpkgs/nixos/modules/services/security/privacyidea.nix index c2988858e56c..f7b40089a932 100644 --- a/nixpkgs/nixos/modules/services/security/privacyidea.nix +++ b/nixpkgs/nixos/modules/services/security/privacyidea.nix @@ -57,6 +57,26 @@ in services.privacyidea = { enable = mkEnableOption "PrivacyIDEA"; + environmentFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/root/privacyidea.env"; + description = '' + File to load as environment file. Environment variables + from this file will be interpolated into the config file + using <package>envsubst</package> which is helpful for specifying + secrets: + <programlisting> + { <xref linkend="opt-services.privacyidea.secretKey" /> = "$SECRET"; } + </programlisting> + + The environment-file can now specify the actual secret key: + <programlisting> + SECRET=veryverytopsecret + </programlisting> + ''; + }; + stateDir = mkOption { type = types.str; default = "/var/lib/privacyidea"; @@ -206,7 +226,7 @@ in wantedBy = [ "multi-user.target" ]; after = [ "postgresql.service" ]; path = with pkgs; [ openssl ]; - environment.PRIVACYIDEA_CONFIGFILE = piCfgFile; + environment.PRIVACYIDEA_CONFIGFILE = "${cfg.stateDir}/privacyidea.cfg"; preStart = let pi-manage = "${pkgs.sudo}/bin/sudo -u privacyidea -HE ${penv}/bin/pi-manage"; pgsu = config.services.postgresql.superUser; @@ -214,6 +234,10 @@ in in '' mkdir -p ${cfg.stateDir} /run/privacyidea chown ${cfg.user}:${cfg.group} -R ${cfg.stateDir} /run/privacyidea + umask 077 + ${lib.getBin pkgs.envsubst}/bin/envsubst -o ${cfg.stateDir}/privacyidea.cfg \ + -i "${piCfgFile}" + chown ${cfg.user}:${cfg.group} ${cfg.stateDir}/privacyidea.cfg if ! test -e "${cfg.stateDir}/db-created"; then ${pkgs.sudo}/bin/sudo -u ${pgsu} ${psql}/bin/createuser --no-superuser --no-createdb --no-createrole ${cfg.user} ${pkgs.sudo}/bin/sudo -u ${pgsu} ${psql}/bin/createdb --owner ${cfg.user} privacyidea @@ -231,6 +255,7 @@ in Type = "notify"; ExecStart = "${uwsgi}/bin/uwsgi --json ${piuwsgi}"; ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile; ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID"; NotifyAccess = "main"; KillSignal = "SIGQUIT"; diff --git a/nixpkgs/nixos/modules/services/security/sshguard.nix b/nixpkgs/nixos/modules/services/security/sshguard.nix index 72de11a9254c..033ff5ef4b5a 100644 --- a/nixpkgs/nixos/modules/services/security/sshguard.nix +++ b/nixpkgs/nixos/modules/services/security/sshguard.nix @@ -108,8 +108,8 @@ in { partOf = optional config.networking.firewall.enable "firewall.service"; path = with pkgs; if config.networking.nftables.enable - then [ nftables iproute systemd ] - else [ iptables ipset iproute systemd ]; + then [ nftables iproute2 systemd ] + else [ iptables ipset iproute2 systemd ]; # The sshguard ipsets must exist before we invoke # iptables. sshguard creates the ipsets after startup if diff --git a/nixpkgs/nixos/modules/services/system/cloud-init.nix b/nixpkgs/nixos/modules/services/system/cloud-init.nix index f83db30c1f02..eb82b738e492 100644 --- a/nixpkgs/nixos/modules/services/system/cloud-init.nix +++ b/nixpkgs/nixos/modules/services/system/cloud-init.nix @@ -5,7 +5,7 @@ with lib; let cfg = config.services.cloud-init; path = with pkgs; [ cloud-init - iproute + iproute2 nettools openssh shadow diff --git a/nixpkgs/nixos/modules/services/system/localtime.nix b/nixpkgs/nixos/modules/services/system/localtime.nix index 8f8e2e2e9339..bb99e5e36ff8 100644 --- a/nixpkgs/nixos/modules/services/system/localtime.nix +++ b/nixpkgs/nixos/modules/services/system/localtime.nix @@ -29,15 +29,14 @@ in { }; }; - # We use the 'out' output, since localtime has its 'bin' output - # first, so that is what we get if we use the derivation bare. # Install the polkit rules. - environment.systemPackages = [ pkgs.localtime.out ]; + environment.systemPackages = [ pkgs.localtime ]; # Install the systemd unit. - systemd.packages = [ pkgs.localtime.out ]; + systemd.packages = [ pkgs.localtime ]; users.users.localtimed = { - description = "Taskserver user"; + description = "localtime daemon"; + isSystemUser = true; }; systemd.services.localtime = { diff --git a/nixpkgs/nixos/modules/services/ttys/kmscon.nix b/nixpkgs/nixos/modules/services/ttys/kmscon.nix index dc37f9bee4b3..4fe720bf044b 100644 --- a/nixpkgs/nixos/modules/services/ttys/kmscon.nix +++ b/nixpkgs/nixos/modules/services/ttys/kmscon.nix @@ -82,11 +82,8 @@ in { X-RestartIfChanged=false ''; - systemd.units."autovt@.service".unit = pkgs.runCommand "unit" { preferLocalBuild = true; } - '' - mkdir -p $out - ln -s ${config.systemd.units."kmsconvt@.service".unit}/kmsconvt@.service $out/autovt@.service - ''; + systemd.suppressedSystemUnits = [ "autovt@.service" ]; + systemd.units."kmsconvt@.service".aliases = [ "autovt@.service" ]; systemd.services.systemd-vconsole-setup.enable = false; diff --git a/nixpkgs/nixos/modules/services/web-apps/bookstack.nix b/nixpkgs/nixos/modules/services/web-apps/bookstack.nix new file mode 100644 index 000000000000..83d05ffbad9a --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-apps/bookstack.nix @@ -0,0 +1,365 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.bookstack; + bookstack = pkgs.bookstack.override { + dataDir = cfg.dataDir; + }; + db = cfg.database; + mail = cfg.mail; + + user = cfg.user; + group = cfg.group; + + # shell script for local administration + artisan = pkgs.writeScriptBin "bookstack" '' + #! ${pkgs.runtimeShell} + cd ${bookstack} + sudo=exec + if [[ "$USER" != ${user} ]]; then + sudo='exec /run/wrappers/bin/sudo -u ${user}' + fi + $sudo ${pkgs.php}/bin/php artisan $* + ''; + + +in { + options.services.bookstack = { + + enable = mkEnableOption "BookStack"; + + user = mkOption { + default = "bookstack"; + description = "User bookstack runs as."; + type = types.str; + }; + + group = mkOption { + default = "bookstack"; + description = "Group bookstack runs as."; + type = types.str; + }; + + appKeyFile = mkOption { + description = '' + A file containing the AppKey. + Used for encryption where needed. Can be generated with <code>head -c 32 /dev/urandom| base64</code> and must be prefixed with <literal>base64:</literal>. + ''; + example = "/run/keys/bookstack-appkey"; + type = types.path; + }; + + appURL = mkOption { + description = '' + The root URL that you want to host BookStack on. All URLs in BookStack will be generated using this value. + If you change this in the future you may need to run a command to update stored URLs in the database. Command example: <code>php artisan bookstack:update-url https://old.example.com https://new.example.com</code> + ''; + example = "https://example.com"; + type = types.str; + }; + + cacheDir = mkOption { + description = "BookStack cache directory"; + default = "/var/cache/bookstack"; + type = types.path; + }; + + dataDir = mkOption { + description = "BookStack data directory"; + default = "/var/lib/bookstack"; + type = types.path; + }; + + database = { + host = mkOption { + type = types.str; + default = "localhost"; + description = "Database host address."; + }; + port = mkOption { + type = types.port; + default = 3306; + description = "Database host port."; + }; + name = mkOption { + type = types.str; + default = "bookstack"; + description = "Database name."; + }; + user = mkOption { + type = types.str; + default = user; + defaultText = "\${user}"; + description = "Database username."; + }; + passwordFile = mkOption { + type = with types; nullOr path; + default = null; + example = "/run/keys/bookstack-dbpassword"; + description = '' + A file containing the password corresponding to + <option>database.user</option>. + ''; + }; + createLocally = mkOption { + type = types.bool; + default = false; + description = "Create the database and database user locally."; + }; + }; + + mail = { + driver = mkOption { + type = types.enum [ "smtp" "sendmail" ]; + default = "smtp"; + description = "Mail driver to use."; + }; + host = mkOption { + type = types.str; + default = "localhost"; + description = "Mail host address."; + }; + port = mkOption { + type = types.port; + default = 1025; + description = "Mail host port."; + }; + fromName = mkOption { + type = types.str; + default = "BookStack"; + description = "Mail \"from\" name."; + }; + from = mkOption { + type = types.str; + default = "mail@bookstackapp.com"; + description = "Mail \"from\" email."; + }; + user = mkOption { + type = with types; nullOr str; + default = null; + example = "bookstack"; + description = "Mail username."; + }; + passwordFile = mkOption { + type = with types; nullOr path; + default = null; + example = "/run/keys/bookstack-mailpassword"; + description = '' + A file containing the password corresponding to + <option>mail.user</option>. + ''; + }; + encryption = mkOption { + type = with types; nullOr (enum [ "tls" ]); + default = null; + description = "SMTP encryption mechanism to use."; + }; + }; + + maxUploadSize = mkOption { + type = types.str; + default = "18M"; + example = "1G"; + description = "The maximum size for uploads (e.g. images)."; + }; + + poolConfig = mkOption { + type = with types; attrsOf (oneOf [ str int bool ]); + default = { + "pm" = "dynamic"; + "pm.max_children" = 32; + "pm.start_servers" = 2; + "pm.min_spare_servers" = 2; + "pm.max_spare_servers" = 4; + "pm.max_requests" = 500; + }; + description = '' + Options for the bookstack PHP pool. See the documentation on <literal>php-fpm.conf</literal> + for details on configuration directives. + ''; + }; + + nginx = mkOption { + type = types.submodule ( + recursiveUpdate + (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) {} + ); + default = {}; + example = { + serverAliases = [ + "bookstack.\${config.networking.domain}" + ]; + # To enable encryption and let let's encrypt take care of certificate + forceSSL = true; + enableACME = true; + }; + description = '' + With this option, you can customize the nginx virtualHost settings. + ''; + }; + + extraConfig = mkOption { + type = types.nullOr types.lines; + default = null; + example = '' + ALLOWED_IFRAME_HOSTS="https://example.com" + WKHTMLTOPDF=/home/user/bins/wkhtmltopdf + ''; + description = '' + Lines to be appended verbatim to the BookStack configuration. + Refer to <link xlink:href="https://www.bookstackapp.com/docs/"/> for details on supported values. + ''; + }; + + }; + + config = mkIf cfg.enable { + + assertions = [ + { assertion = db.createLocally -> db.user == user; + message = "services.bookstack.database.user must be set to ${user} if services.mediawiki.database.createLocally is set true."; + } + { assertion = db.createLocally -> db.passwordFile == null; + message = "services.bookstack.database.passwordFile cannot be specified if services.bookstack.database.createLocally is set to true."; + } + ]; + + environment.systemPackages = [ artisan ]; + + services.mysql = mkIf db.createLocally { + enable = true; + package = mkDefault pkgs.mariadb; + ensureDatabases = [ db.name ]; + ensureUsers = [ + { name = db.user; + ensurePermissions = { "${db.name}.*" = "ALL PRIVILEGES"; }; + } + ]; + }; + + services.phpfpm.pools.bookstack = { + inherit user; + inherit group; + phpOptions = '' + log_errors = on + post_max_size = ${cfg.maxUploadSize} + upload_max_filesize = ${cfg.maxUploadSize} + ''; + settings = { + "listen.mode" = "0660"; + "listen.owner" = user; + "listen.group" = group; + } // cfg.poolConfig; + }; + + services.nginx = { + enable = mkDefault true; + virtualHosts.bookstack = mkMerge [ cfg.nginx { + root = mkForce "${bookstack}/public"; + extraConfig = optionalString (cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME) "fastcgi_param HTTPS on;"; + locations = { + "/" = { + index = "index.php"; + extraConfig = ''try_files $uri $uri/ /index.php?$query_string;''; + }; + "~ \.php$" = { + extraConfig = '' + try_files $uri $uri/ /index.php?$query_string; + include ${pkgs.nginx}/conf/fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param REDIRECT_STATUS 200; + fastcgi_pass unix:${config.services.phpfpm.pools."bookstack".socket}; + ${optionalString (cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME) "fastcgi_param HTTPS on;"} + ''; + }; + "~ \.(js|css|gif|png|ico|jpg|jpeg)$" = { + extraConfig = "expires 365d;"; + }; + }; + }]; + }; + + systemd.services.bookstack-setup = { + description = "Preperation tasks for BookStack"; + before = [ "phpfpm-bookstack.service" ]; + after = optional db.createLocally "mysql.service"; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "oneshot"; + User = user; + WorkingDirectory = "${bookstack}"; + }; + script = '' + # create .env file + echo " + APP_KEY=base64:$(head -n1 ${cfg.appKeyFile}) + APP_URL=${cfg.appURL} + DB_HOST=${db.host} + DB_PORT=${toString db.port} + DB_DATABASE=${db.name} + DB_USERNAME=${db.user} + MAIL_DRIVER=${mail.driver} + MAIL_FROM_NAME=\"${mail.fromName}\" + MAIL_FROM=${mail.from} + MAIL_HOST=${mail.host} + MAIL_PORT=${toString mail.port} + ${optionalString (mail.user != null) "MAIL_USERNAME=${mail.user};"} + ${optionalString (mail.encryption != null) "MAIL_ENCRYPTION=${mail.encryption};"} + ${optionalString (db.passwordFile != null) "DB_PASSWORD=$(head -n1 ${db.passwordFile})"} + ${optionalString (mail.passwordFile != null) "MAIL_PASSWORD=$(head -n1 ${mail.passwordFile})"} + APP_SERVICES_CACHE=${cfg.cacheDir}/services.php + APP_PACKAGES_CACHE=${cfg.cacheDir}/packages.php + APP_CONFIG_CACHE=${cfg.cacheDir}/config.php + APP_ROUTES_CACHE=${cfg.cacheDir}/routes-v7.php + APP_EVENTS_CACHE=${cfg.cacheDir}/events.php + ${optionalString (cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME) "SESSION_SECURE_COOKIE=true"} + ${toString cfg.extraConfig} + " > "${cfg.dataDir}/.env" + # set permissions + chmod 700 "${cfg.dataDir}/.env" + + # migrate db + ${pkgs.php}/bin/php artisan migrate --force + + # create caches + ${pkgs.php}/bin/php artisan config:cache + ${pkgs.php}/bin/php artisan route:cache + ${pkgs.php}/bin/php artisan view:cache + ''; + }; + + systemd.tmpfiles.rules = [ + "d ${cfg.cacheDir} 0700 ${user} ${group} - -" + "d ${cfg.dataDir} 0710 ${user} ${group} - -" + "d ${cfg.dataDir}/public 0750 ${user} ${group} - -" + "d ${cfg.dataDir}/public/uploads 0750 ${user} ${group} - -" + "d ${cfg.dataDir}/storage 0700 ${user} ${group} - -" + "d ${cfg.dataDir}/storage/app 0700 ${user} ${group} - -" + "d ${cfg.dataDir}/storage/fonts 0700 ${user} ${group} - -" + "d ${cfg.dataDir}/storage/framework 0700 ${user} ${group} - -" + "d ${cfg.dataDir}/storage/framework/cache 0700 ${user} ${group} - -" + "d ${cfg.dataDir}/storage/framework/sessions 0700 ${user} ${group} - -" + "d ${cfg.dataDir}/storage/framework/views 0700 ${user} ${group} - -" + "d ${cfg.dataDir}/storage/logs 0700 ${user} ${group} - -" + "d ${cfg.dataDir}/storage/uploads 0700 ${user} ${group} - -" + ]; + + users = { + users = mkIf (user == "bookstack") { + bookstack = { + inherit group; + isSystemUser = true; + }; + "${config.services.nginx.user}".extraGroups = [ group ]; + }; + groups = mkIf (group == "bookstack") { + bookstack = {}; + }; + }; + + }; + + meta.maintainers = with maintainers; [ ymarkus ]; +} diff --git a/nixpkgs/nixos/modules/services/web-apps/calibre-web.nix b/nixpkgs/nixos/modules/services/web-apps/calibre-web.nix new file mode 100644 index 000000000000..704cd2cfa8a7 --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-apps/calibre-web.nix @@ -0,0 +1,165 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.calibre-web; + + inherit (lib) concatStringsSep mkEnableOption mkIf mkOption optional optionalString types; +in +{ + options = { + services.calibre-web = { + enable = mkEnableOption "Calibre-Web"; + + listen = { + ip = mkOption { + type = types.str; + default = "::1"; + description = '' + IP address that Calibre-Web should listen on. + ''; + }; + + port = mkOption { + type = types.port; + default = 8083; + description = '' + Listen port for Calibre-Web. + ''; + }; + }; + + dataDir = mkOption { + type = types.str; + default = "calibre-web"; + description = '' + The directory below <filename>/var/lib</filename> where Calibre-Web stores its data. + ''; + }; + + user = mkOption { + type = types.str; + default = "calibre-web"; + description = "User account under which Calibre-Web runs."; + }; + + group = mkOption { + type = types.str; + default = "calibre-web"; + description = "Group account under which Calibre-Web runs."; + }; + + openFirewall = mkOption { + type = types.bool; + default = false; + description = '' + Open ports in the firewall for the server. + ''; + }; + + options = { + calibreLibrary = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Path to Calibre library. + ''; + }; + + enableBookConversion = mkOption { + type = types.bool; + default = false; + description = '' + Configure path to the Calibre's ebook-convert in the DB. + ''; + }; + + enableBookUploading = mkOption { + type = types.bool; + default = false; + description = '' + Allow books to be uploaded via Calibre-Web UI. + ''; + }; + + reverseProxyAuth = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Enable authorization using auth proxy. + ''; + }; + + header = mkOption { + type = types.str; + default = ""; + description = '' + Auth proxy header name. + ''; + }; + }; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.services.calibre-web = let + appDb = "/var/lib/${cfg.dataDir}/app.db"; + gdriveDb = "/var/lib/${cfg.dataDir}/gdrive.db"; + calibreWebCmd = "${pkgs.calibre-web}/bin/calibre-web -p ${appDb} -g ${gdriveDb}"; + + settings = concatStringsSep ", " ( + [ + "config_port = ${toString cfg.listen.port}" + "config_uploading = ${if cfg.options.enableBookUploading then "1" else "0"}" + "config_allow_reverse_proxy_header_login = ${if cfg.options.reverseProxyAuth.enable then "1" else "0"}" + "config_reverse_proxy_login_header_name = '${cfg.options.reverseProxyAuth.header}'" + ] + ++ optional (cfg.options.calibreLibrary != null) "config_calibre_dir = '${cfg.options.calibreLibrary}'" + ++ optional cfg.options.enableBookConversion "config_converterpath = '${pkgs.calibre}/bin/ebook-convert'" + ); + in + { + description = "Web app for browsing, reading and downloading eBooks stored in a Calibre database"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + Type = "simple"; + User = cfg.user; + Group = cfg.group; + + StateDirectory = cfg.dataDir; + ExecStartPre = pkgs.writeShellScript "calibre-web-pre-start" ( + '' + __RUN_MIGRATIONS_AND_EXIT=1 ${calibreWebCmd} + + ${pkgs.sqlite}/bin/sqlite3 ${appDb} "update settings set ${settings}" + '' + optionalString (cfg.options.calibreLibrary != null) '' + test -f ${cfg.options.calibreLibrary}/metadata.db || { echo "Invalid Calibre library"; exit 1; } + '' + ); + + ExecStart = "${calibreWebCmd} -i ${cfg.listen.ip}"; + Restart = "on-failure"; + }; + }; + + networking.firewall = mkIf cfg.openFirewall { + allowedTCPPorts = [ cfg.listen.port ]; + }; + + users.users = mkIf (cfg.user == "calibre-web") { + calibre-web = { + isSystemUser = true; + group = cfg.group; + }; + }; + + users.groups = mkIf (cfg.group == "calibre-web") { + calibre-web = {}; + }; + }; + + meta.maintainers = with lib.maintainers; [ pborzenkov ]; +} diff --git a/nixpkgs/nixos/modules/services/web-apps/discourse.nix b/nixpkgs/nixos/modules/services/web-apps/discourse.nix new file mode 100644 index 000000000000..03ea002c9dee --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-apps/discourse.nix @@ -0,0 +1,1035 @@ +{ config, options, lib, pkgs, utils, ... }: + +let + json = pkgs.formats.json {}; + + cfg = config.services.discourse; + + postgresqlPackage = if config.services.postgresql.enable then + config.services.postgresql.package + else + pkgs.postgresql; + + # We only want to create a database if we're actually going to connect to it. + databaseActuallyCreateLocally = cfg.database.createLocally && cfg.database.host == null; + + tlsEnabled = (cfg.enableACME + || cfg.sslCertificate != null + || cfg.sslCertificateKey != null); +in +{ + options = { + services.discourse = { + enable = lib.mkEnableOption "Discourse, an open source discussion platform"; + + package = lib.mkOption { + type = lib.types.package; + default = pkgs.discourse; + defaultText = "pkgs.discourse"; + description = '' + The discourse package to use. + ''; + }; + + hostname = lib.mkOption { + type = lib.types.str; + default = if config.networking.domain != null then + config.networking.fqdn + else + config.networking.hostName; + defaultText = "config.networking.fqdn"; + example = "discourse.example.com"; + description = '' + The hostname to serve Discourse on. + ''; + }; + + secretKeyBaseFile = lib.mkOption { + type = with lib.types; nullOr path; + default = null; + example = "/run/keys/secret_key_base"; + description = '' + The path to a file containing the + <literal>secret_key_base</literal> secret. + + Discourse uses <literal>secret_key_base</literal> to encrypt + the cookie store, which contains session data, and to digest + user auth tokens. + + Needs to be a 64 byte long string of hexadecimal + characters. You can generate one by running + + <screen> + <prompt>$ </prompt>openssl rand -hex 64 >/path/to/secret_key_base_file + </screen> + + This should be a string, not a nix path, since nix paths are + copied into the world-readable nix store. + ''; + }; + + sslCertificate = lib.mkOption { + type = with lib.types; nullOr path; + default = null; + example = "/run/keys/ssl.cert"; + description = '' + The path to the server SSL certificate. Set this to enable + SSL. + ''; + }; + + sslCertificateKey = lib.mkOption { + type = with lib.types; nullOr path; + default = null; + example = "/run/keys/ssl.key"; + description = '' + The path to the server SSL certificate key. Set this to + enable SSL. + ''; + }; + + enableACME = lib.mkOption { + type = lib.types.bool; + default = cfg.sslCertificate == null && cfg.sslCertificateKey == null; + defaultText = "true, unless services.discourse.sslCertificate and services.discourse.sslCertificateKey are set."; + description = '' + Whether an ACME certificate should be used to secure + connections to the server. + ''; + }; + + backendSettings = lib.mkOption { + type = with lib.types; attrsOf (nullOr (oneOf [ str int bool float ])); + default = {}; + example = lib.literalExample '' + { + max_reqs_per_ip_per_minute = 300; + max_reqs_per_ip_per_10_seconds = 60; + max_asset_reqs_per_ip_per_10_seconds = 250; + max_reqs_per_ip_mode = "warn+block"; + }; + ''; + description = '' + Additional settings to put in the + <filename>discourse.conf</filename> file. + + Look in the + <link xlink:href="https://github.com/discourse/discourse/blob/master/config/discourse_defaults.conf">discourse_defaults.conf</link> + file in the upstream distribution to find available options. + + Setting an option to <literal>null</literal> means + <quote>define variable, but leave right-hand side + empty</quote>. + ''; + }; + + siteSettings = lib.mkOption { + type = json.type; + default = {}; + example = lib.literalExample '' + { + required = { + title = "My Cats"; + site_description = "Discuss My Cats (and be nice plz)"; + }; + login = { + enable_github_logins = true; + github_client_id = "a2f6dfe838cb3206ce20"; + github_client_secret._secret = /run/keys/discourse_github_client_secret; + }; + }; + ''; + description = '' + Discourse site settings. These are the settings that can be + changed from the UI. This only defines their default values: + they can still be overridden from the UI. + + Available settings can be found by looking in the + <link xlink:href="https://github.com/discourse/discourse/blob/master/config/site_settings.yml">site_settings.yml</link> + file of the upstream distribution. To find a setting's path, + you only need to care about the first two levels; i.e. its + category and name. See the example. + + Settings containing secret data should be set to an + attribute set containing the attribute + <literal>_secret</literal> - a string pointing to a file + containing the value the option should be set to. See the + example to get a better picture of this: in the resulting + <filename>config/nixos_site_settings.json</filename> file, + the <literal>login.github_client_secret</literal> key will + be set to the contents of the + <filename>/run/keys/discourse_github_client_secret</filename> + file. + ''; + }; + + admin = { + email = lib.mkOption { + type = lib.types.str; + example = "admin@example.com"; + description = '' + The admin user email address. + ''; + }; + + username = lib.mkOption { + type = lib.types.str; + example = "admin"; + description = '' + The admin user username. + ''; + }; + + fullName = lib.mkOption { + type = lib.types.str; + description = '' + The admin user's full name. + ''; + }; + + passwordFile = lib.mkOption { + type = lib.types.path; + description = '' + A path to a file containing the admin user's password. + + This should be a string, not a nix path, since nix paths are + copied into the world-readable nix store. + ''; + }; + }; + + nginx.enable = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + Whether an <literal>nginx</literal> virtual host should be + set up to serve Discourse. Only disable if you're planning + to use a different web server, which is not recommended. + ''; + }; + + database = { + pool = lib.mkOption { + type = lib.types.int; + default = 8; + description = '' + Database connection pool size. + ''; + }; + + host = lib.mkOption { + type = with lib.types; nullOr str; + default = null; + description = '' + Discourse database hostname. <literal>null</literal> means <quote>prefer + local unix socket connection</quote>. + ''; + }; + + passwordFile = lib.mkOption { + type = with lib.types; nullOr path; + default = null; + description = '' + File containing the Discourse database user password. + + This should be a string, not a nix path, since nix paths are + copied into the world-readable nix store. + ''; + }; + + createLocally = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + Whether a database should be automatically created on the + local host. Set this to <literal>false</literal> if you plan + on provisioning a local database yourself. This has no effect + if <option>services.discourse.database.host</option> is customized. + ''; + }; + + name = lib.mkOption { + type = lib.types.str; + default = "discourse"; + description = '' + Discourse database name. + ''; + }; + + username = lib.mkOption { + type = lib.types.str; + default = "discourse"; + description = '' + Discourse database user. + ''; + }; + }; + + redis = { + host = lib.mkOption { + type = lib.types.str; + default = "localhost"; + description = '' + Redis server hostname. + ''; + }; + + passwordFile = lib.mkOption { + type = with lib.types; nullOr path; + default = null; + description = '' + File containing the Redis password. + + This should be a string, not a nix path, since nix paths are + copied into the world-readable nix store. + ''; + }; + + dbNumber = lib.mkOption { + type = lib.types.int; + default = 0; + description = '' + Redis database number. + ''; + }; + + useSSL = lib.mkOption { + type = lib.types.bool; + default = cfg.redis.host != "localhost"; + description = '' + Connect to Redis with SSL. + ''; + }; + }; + + mail = { + notificationEmailAddress = lib.mkOption { + type = lib.types.str; + default = "${if cfg.mail.incoming.enable then "notifications" else "noreply"}@${cfg.hostname}"; + defaultText = '' + "notifications@`config.services.discourse.hostname`" if + config.services.discourse.mail.incoming.enable is "true", + otherwise "noreply`config.services.discourse.hostname`" + ''; + description = '' + The <literal>from:</literal> email address used when + sending all essential system emails. The domain specified + here must have SPF, DKIM and reverse PTR records set + correctly for email to arrive. + ''; + }; + + contactEmailAddress = lib.mkOption { + type = lib.types.str; + default = ""; + description = '' + Email address of key contact responsible for this + site. Used for critical notifications, as well as on the + <literal>/about</literal> contact form for urgent matters. + ''; + }; + + outgoing = { + serverAddress = lib.mkOption { + type = lib.types.str; + default = "localhost"; + description = '' + The address of the SMTP server Discourse should use to + send email. + ''; + }; + + port = lib.mkOption { + type = lib.types.int; + default = 25; + description = '' + The port of the SMTP server Discourse should use to + send email. + ''; + }; + + username = lib.mkOption { + type = with lib.types; nullOr str; + default = null; + description = '' + The username of the SMTP server. + ''; + }; + + passwordFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = '' + A file containing the password of the SMTP server account. + + This should be a string, not a nix path, since nix paths + are copied into the world-readable nix store. + ''; + }; + + domain = lib.mkOption { + type = lib.types.str; + default = cfg.hostname; + description = '' + HELO domain to use for outgoing mail. + ''; + }; + + authentication = lib.mkOption { + type = with lib.types; nullOr (enum ["plain" "login" "cram_md5"]); + default = null; + description = '' + Authentication type to use, see http://api.rubyonrails.org/classes/ActionMailer/Base.html + ''; + }; + + enableStartTLSAuto = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + Whether to try to use StartTLS. + ''; + }; + + opensslVerifyMode = lib.mkOption { + type = lib.types.str; + default = "peer"; + description = '' + How OpenSSL checks the certificate, see http://api.rubyonrails.org/classes/ActionMailer/Base.html + ''; + }; + }; + + incoming = { + enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Whether to set up Postfix to receive incoming mail. + ''; + }; + + replyEmailAddress = lib.mkOption { + type = lib.types.str; + default = "%{reply_key}@${cfg.hostname}"; + defaultText = "%{reply_key}@`config.services.discourse.hostname`"; + description = '' + Template for reply by email incoming email address, for + example: %{reply_key}@reply.example.com or + replies+%{reply_key}@example.com + ''; + }; + + mailReceiverPackage = lib.mkOption { + type = lib.types.package; + default = pkgs.discourse-mail-receiver; + defaultText = "pkgs.discourse-mail-receiver"; + description = '' + The discourse-mail-receiver package to use. + ''; + }; + + apiKeyFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = '' + A file containing the Discourse API key used to add + posts and messages from mail. If left at its default + value <literal>null</literal>, one will be automatically + generated. + + This should be a string, not a nix path, since nix paths + are copied into the world-readable nix store. + ''; + }; + }; + }; + + plugins = lib.mkOption { + type = lib.types.listOf lib.types.package; + default = []; + example = '' + [ + (pkgs.fetchFromGitHub { + owner = "discourse"; + repo = "discourse-spoiler-alert"; + rev = "e200cfa571d252cab63f3d30d619b370986e4cee"; + sha256 = "0ya69ix5g77wz4c9x9gmng6l25ghb5xxlx3icr6jam16q14dzc33"; + }) + ]; + ''; + description = '' + <productname>Discourse</productname> plugins to install as a + list of derivations. As long as a plugin supports the + standard install method, packaging it should only require + fetching its source with an appropriate fetcher. + ''; + }; + + sidekiqProcesses = lib.mkOption { + type = lib.types.int; + default = 1; + description = '' + How many Sidekiq processes should be spawned. + ''; + }; + + unicornTimeout = lib.mkOption { + type = lib.types.int; + default = 30; + description = '' + Time in seconds before a request to Unicorn times out. + + This can be raised if the system Discourse is running on is + too slow to handle many requests within 30 seconds. + ''; + }; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = (cfg.database.host != null) -> (cfg.database.passwordFile != null); + message = "When services.gitlab.database.host is customized, services.discourse.database.passwordFile must be set!"; + } + { + assertion = cfg.hostname != ""; + message = "Could not automatically determine hostname, set service.discourse.hostname manually."; + } + ]; + + + # Default config values are from `config/discourse_defaults.conf` + # upstream. + services.discourse.backendSettings = lib.mapAttrs (_: lib.mkDefault) { + db_pool = cfg.database.pool; + db_timeout = 5000; + db_connect_timeout = 5; + db_socket = null; + db_host = cfg.database.host; + db_backup_host = null; + db_port = null; + db_backup_port = 5432; + db_name = cfg.database.name; + db_username = if databaseActuallyCreateLocally then "discourse" else cfg.database.username; + db_password = cfg.database.passwordFile; + db_prepared_statements = false; + db_replica_host = null; + db_replica_port = null; + db_advisory_locks = true; + + inherit (cfg) hostname; + backup_hostname = null; + + smtp_address = cfg.mail.outgoing.serverAddress; + smtp_port = cfg.mail.outgoing.port; + smtp_domain = cfg.mail.outgoing.domain; + smtp_user_name = cfg.mail.outgoing.username; + smtp_password = cfg.mail.outgoing.passwordFile; + smtp_authentication = cfg.mail.outgoing.authentication; + smtp_enable_start_tls = cfg.mail.outgoing.enableStartTLSAuto; + smtp_openssl_verify_mode = cfg.mail.outgoing.opensslVerifyMode; + + load_mini_profiler = true; + mini_profiler_snapshots_period = 0; + mini_profiler_snapshots_transport_url = null; + mini_profiler_snapshots_transport_auth_key = null; + + cdn_url = null; + cdn_origin_hostname = null; + developer_emails = null; + + redis_host = cfg.redis.host; + redis_port = 6379; + redis_slave_host = null; + redis_slave_port = 6379; + redis_db = cfg.redis.dbNumber; + redis_password = cfg.redis.passwordFile; + redis_skip_client_commands = false; + redis_use_ssl = cfg.redis.useSSL; + + message_bus_redis_enabled = false; + message_bus_redis_host = "localhost"; + message_bus_redis_port = 6379; + message_bus_redis_slave_host = null; + message_bus_redis_slave_port = 6379; + message_bus_redis_db = 0; + message_bus_redis_password = null; + message_bus_redis_skip_client_commands = false; + + enable_cors = false; + cors_origin = ""; + serve_static_assets = false; + sidekiq_workers = 5; + rtl_css = false; + connection_reaper_age = 30; + connection_reaper_interval = 30; + relative_url_root = null; + message_bus_max_backlog_size = 100; + secret_key_base = cfg.secretKeyBaseFile; + fallback_assets_path = null; + + s3_bucket = null; + s3_region = null; + s3_access_key_id = null; + s3_secret_access_key = null; + s3_use_iam_profile = null; + s3_cdn_url = null; + s3_endpoint = null; + s3_http_continue_timeout = null; + s3_install_cors_rule = null; + + max_user_api_reqs_per_minute = 20; + max_user_api_reqs_per_day = 2880; + max_admin_api_reqs_per_key_per_minute = 60; + max_reqs_per_ip_per_minute = 200; + max_reqs_per_ip_per_10_seconds = 50; + max_asset_reqs_per_ip_per_10_seconds = 200; + max_reqs_per_ip_mode = "block"; + max_reqs_rate_limit_on_private = false; + force_anonymous_min_queue_seconds = 1; + force_anonymous_min_per_10_seconds = 3; + background_requests_max_queue_length = 0.5; + reject_message_bus_queue_seconds = 0.1; + disable_search_queue_threshold = 1; + max_old_rebakes_per_15_minutes = 300; + max_logster_logs = 1000; + refresh_maxmind_db_during_precompile_days = 2; + maxmind_backup_path = null; + maxmind_license_key = null; + enable_performance_http_headers = false; + enable_js_error_reporting = true; + mini_scheduler_workers = 5; + compress_anon_cache = false; + anon_cache_store_threshold = 2; + allowed_theme_repos = null; + enable_email_sync_demon = false; + max_digests_enqueued_per_30_mins_per_site = 10000; + }; + + services.redis.enable = lib.mkDefault (cfg.redis.host == "localhost"); + + services.postgresql = lib.mkIf databaseActuallyCreateLocally { + enable = true; + ensureUsers = [{ name = "discourse"; }]; + }; + + # The postgresql module doesn't currently support concepts like + # objects owners and extensions; for now we tack on what's needed + # here. + systemd.services.discourse-postgresql = + let + pgsql = config.services.postgresql; + in + lib.mkIf databaseActuallyCreateLocally { + after = [ "postgresql.service" ]; + bindsTo = [ "postgresql.service" ]; + wantedBy = [ "discourse.service" ]; + partOf = [ "discourse.service" ]; + path = [ + pgsql.package + ]; + script = '' + set -o errexit -o pipefail -o nounset -o errtrace + shopt -s inherit_errexit + + psql -tAc "SELECT 1 FROM pg_database WHERE datname = 'discourse'" | grep -q 1 || psql -tAc 'CREATE DATABASE "discourse" OWNER "discourse"' + psql '${cfg.database.name}' -tAc "CREATE EXTENSION IF NOT EXISTS pg_trgm" + psql '${cfg.database.name}' -tAc "CREATE EXTENSION IF NOT EXISTS hstore" + ''; + + serviceConfig = { + User = pgsql.superUser; + Type = "oneshot"; + RemainAfterExit = true; + }; + }; + + systemd.services.discourse = { + wantedBy = [ "multi-user.target" ]; + after = [ + "redis.service" + "postgresql.service" + "discourse-postgresql.service" + ]; + bindsTo = [ + "redis.service" + ] ++ lib.optionals (cfg.database.host == null) [ + "postgresql.service" + "discourse-postgresql.service" + ]; + path = cfg.package.runtimeDeps ++ [ + postgresqlPackage + pkgs.replace + cfg.package.rake + ]; + environment = cfg.package.runtimeEnv // { + UNICORN_TIMEOUT = builtins.toString cfg.unicornTimeout; + UNICORN_SIDEKIQS = builtins.toString cfg.sidekiqProcesses; + }; + + preStart = + let + discourseKeyValue = lib.generators.toKeyValue { + mkKeyValue = lib.flip lib.generators.mkKeyValueDefault " = " { + mkValueString = v: with builtins; + if isInt v then toString v + else if isString v then ''"${v}"'' + else if true == v then "true" + else if false == v then "false" + else if null == v then "" + else if isFloat v then lib.strings.floatToString v + else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}"; + }; + }; + + discourseConf = pkgs.writeText "discourse.conf" (discourseKeyValue cfg.backendSettings); + + mkSecretReplacement = file: + lib.optionalString (file != null) '' + ( + password=$(<'${file}') + replace-literal -fe '${file}' "$password" /run/discourse/config/discourse.conf + ) + ''; + in '' + set -o errexit -o pipefail -o nounset -o errtrace + shopt -s inherit_errexit + + umask u=rwx,g=rx,o= + + cp -r ${cfg.package}/share/discourse/config.dist/* /run/discourse/config/ + cp -r ${cfg.package}/share/discourse/public.dist/* /run/discourse/public/ + cp -r ${cfg.package}/share/discourse/plugins.dist/* /run/discourse/plugins/ + ${lib.concatMapStrings (p: "ln -sf ${p} /run/discourse/plugins/") cfg.plugins} + ln -sf /var/lib/discourse/uploads /run/discourse/public/uploads + ln -sf /var/lib/discourse/backups /run/discourse/public/backups + + ( + umask u=rwx,g=,o= + + ${utils.genJqSecretsReplacementSnippet + cfg.siteSettings + "/run/discourse/config/nixos_site_settings.json" + } + install -T -m 0400 -o discourse ${discourseConf} /run/discourse/config/discourse.conf + ${mkSecretReplacement cfg.database.passwordFile} + ${mkSecretReplacement cfg.mail.outgoing.passwordFile} + ${mkSecretReplacement cfg.redis.passwordFile} + ${mkSecretReplacement cfg.secretKeyBaseFile} + ) + + discourse-rake db:migrate >>/var/log/discourse/db_migration.log + chmod -R u+w /run/discourse/tmp/ + + export ADMIN_EMAIL="${cfg.admin.email}" + export ADMIN_NAME="${cfg.admin.fullName}" + export ADMIN_USERNAME="${cfg.admin.username}" + export ADMIN_PASSWORD="$(<${cfg.admin.passwordFile})" + discourse-rake admin:create_noninteractively + + discourse-rake themes:update + discourse-rake uploads:regenerate_missing_optimized + ''; + + serviceConfig = { + Type = "simple"; + User = "discourse"; + Group = "discourse"; + RuntimeDirectory = map (p: "discourse/" + p) [ + "config" + "home" + "tmp" + "assets/javascripts/plugins" + "public" + "plugins" + "sockets" + ]; + RuntimeDirectoryMode = 0750; + StateDirectory = map (p: "discourse/" + p) [ + "uploads" + "backups" + ]; + StateDirectoryMode = 0750; + LogsDirectory = "discourse"; + TimeoutSec = "infinity"; + Restart = "on-failure"; + WorkingDirectory = "${cfg.package}/share/discourse"; + + RemoveIPC = true; + PrivateTmp = true; + NoNewPrivileges = true; + RestrictSUIDSGID = true; + ProtectSystem = "strict"; + ProtectHome = "read-only"; + + ExecStart = "${cfg.package.rubyEnv}/bin/bundle exec config/unicorn_launcher -E production -c config/unicorn.conf.rb"; + }; + }; + + services.nginx = lib.mkIf cfg.nginx.enable { + enable = true; + additionalModules = [ pkgs.nginxModules.brotli ]; + + recommendedTlsSettings = true; + recommendedOptimisation = true; + recommendedGzipSettings = true; + recommendedProxySettings = true; + + upstreams.discourse.servers."unix:/run/discourse/sockets/unicorn.sock" = {}; + + appendHttpConfig = '' + # inactive means we keep stuff around for 1440m minutes regardless of last access (1 week) + # levels means it is a 2 deep heirarchy cause we can have lots of files + # max_size limits the size of the cache + proxy_cache_path /var/cache/nginx inactive=1440m levels=1:2 keys_zone=discourse:10m max_size=600m; + + # see: https://meta.discourse.org/t/x/74060 + proxy_buffer_size 8k; + ''; + + virtualHosts.${cfg.hostname} = { + inherit (cfg) sslCertificate sslCertificateKey enableACME; + forceSSL = lib.mkDefault tlsEnabled; + + root = "/run/discourse/public"; + + locations = + let + proxy = { extraConfig ? "" }: { + proxyPass = "http://discourse"; + extraConfig = extraConfig + '' + proxy_set_header X-Request-Start "t=''${msec}"; + ''; + }; + cache = time: '' + expires ${time}; + add_header Cache-Control public,immutable; + ''; + cache_1y = cache "1y"; + cache_1d = cache "1d"; + in + { + "/".tryFiles = "$uri @discourse"; + "@discourse" = proxy {}; + "^~ /backups/".extraConfig = '' + internal; + ''; + "/favicon.ico" = { + return = "204"; + extraConfig = '' + access_log off; + log_not_found off; + ''; + }; + "~ ^/uploads/short-url/" = proxy {}; + "~ ^/secure-media-uploads/" = proxy {}; + "~* (fonts|assets|plugins|uploads)/.*\.(eot|ttf|woff|woff2|ico|otf)$".extraConfig = cache_1y + '' + add_header Access-Control-Allow-Origin *; + ''; + "/srv/status" = proxy { + extraConfig = '' + access_log off; + log_not_found off; + ''; + }; + "~ ^/javascripts/".extraConfig = cache_1d; + "~ ^/assets/(?<asset_path>.+)$".extraConfig = cache_1y + '' + # asset pipeline enables this + brotli_static on; + gzip_static on; + ''; + "~ ^/plugins/".extraConfig = cache_1y; + "~ /images/emoji/".extraConfig = cache_1y; + "~ ^/uploads/" = proxy { + extraConfig = cache_1y + '' + proxy_set_header X-Sendfile-Type X-Accel-Redirect; + proxy_set_header X-Accel-Mapping /run/discourse/public/=/downloads/; + + # custom CSS + location ~ /stylesheet-cache/ { + try_files $uri =404; + } + # this allows us to bypass rails + location ~* \.(gif|png|jpg|jpeg|bmp|tif|tiff|ico|webp)$ { + try_files $uri =404; + } + # SVG needs an extra header attached + location ~* \.(svg)$ { + } + # thumbnails & optimized images + location ~ /_?optimized/ { + try_files $uri =404; + } + ''; + }; + "~ ^/admin/backups/" = proxy { + extraConfig = '' + proxy_set_header X-Sendfile-Type X-Accel-Redirect; + proxy_set_header X-Accel-Mapping /run/discourse/public/=/downloads/; + ''; + }; + "~ ^/(svg-sprite/|letter_avatar/|letter_avatar_proxy/|user_avatar|highlight-js|stylesheets|theme-javascripts|favicon/proxied|service-worker)" = proxy { + extraConfig = '' + # if Set-Cookie is in the response nothing gets cached + # this is double bad cause we are not passing last modified in + proxy_ignore_headers "Set-Cookie"; + proxy_hide_header "Set-Cookie"; + proxy_hide_header "X-Discourse-Username"; + proxy_hide_header "X-Runtime"; + + # note x-accel-redirect can not be used with proxy_cache + proxy_cache discourse; + proxy_cache_key "$scheme,$host,$request_uri"; + proxy_cache_valid 200 301 302 7d; + proxy_cache_valid any 1m; + ''; + }; + "/message-bus/" = proxy { + extraConfig = '' + proxy_http_version 1.1; + proxy_buffering off; + ''; + }; + "/downloads/".extraConfig = '' + internal; + alias /run/discourse/public/; + ''; + }; + }; + }; + + systemd.services.discourse-mail-receiver-setup = lib.mkIf cfg.mail.incoming.enable ( + let + mail-receiver-environment = { + MAIL_DOMAIN = cfg.hostname; + DISCOURSE_BASE_URL = "http${lib.optionalString tlsEnabled "s"}://${cfg.hostname}"; + DISCOURSE_API_KEY = "@api-key@"; + DISCOURSE_API_USERNAME = "system"; + }; + mail-receiver-json = json.generate "mail-receiver.json" mail-receiver-environment; + in + { + before = [ "postfix.service" ]; + after = [ "discourse.service" ]; + wantedBy = [ "discourse.service" ]; + partOf = [ "discourse.service" ]; + path = [ + cfg.package.rake + pkgs.jq + ]; + preStart = lib.optionalString (cfg.mail.incoming.apiKeyFile == null) '' + set -o errexit -o pipefail -o nounset -o errtrace + shopt -s inherit_errexit + + if [[ ! -e /var/lib/discourse-mail-receiver/api_key ]]; then + discourse-rake api_key:create_master[email-receiver] >/var/lib/discourse-mail-receiver/api_key + fi + ''; + script = + let + apiKeyPath = + if cfg.mail.incoming.apiKeyFile == null then + "/var/lib/discourse-mail-receiver/api_key" + else + cfg.mail.incoming.apiKeyFile; + in '' + set -o errexit -o pipefail -o nounset -o errtrace + shopt -s inherit_errexit + + export api_key=$(<'${apiKeyPath}') + + jq <${mail-receiver-json} \ + '.DISCOURSE_API_KEY = $ENV.api_key' \ + >'/run/discourse-mail-receiver/mail-receiver-environment.json' + ''; + + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + RuntimeDirectory = "discourse-mail-receiver"; + RuntimeDirectoryMode = "0700"; + StateDirectory = "discourse-mail-receiver"; + User = "discourse"; + Group = "discourse"; + }; + }); + + services.discourse.siteSettings = { + required = { + notification_email = cfg.mail.notificationEmailAddress; + contact_email = cfg.mail.contactEmailAddress; + }; + email = { + manual_polling_enabled = cfg.mail.incoming.enable; + reply_by_email_enabled = cfg.mail.incoming.enable; + reply_by_email_address = cfg.mail.incoming.replyEmailAddress; + }; + }; + + services.postfix = lib.mkIf cfg.mail.incoming.enable { + enable = true; + sslCert = if cfg.sslCertificate != null then cfg.sslCertificate else ""; + sslKey = if cfg.sslCertificateKey != null then cfg.sslCertificateKey else ""; + + origin = cfg.hostname; + relayDomains = [ cfg.hostname ]; + config = { + smtpd_recipient_restrictions = "check_policy_service unix:private/discourse-policy"; + append_dot_mydomain = lib.mkDefault false; + compatibility_level = "2"; + smtputf8_enable = false; + smtpd_banner = lib.mkDefault "ESMTP server"; + myhostname = lib.mkDefault cfg.hostname; + mydestination = lib.mkDefault "localhost"; + }; + transport = '' + ${cfg.hostname} discourse-mail-receiver: + ''; + masterConfig = { + "discourse-mail-receiver" = { + type = "unix"; + privileged = true; + chroot = false; + command = "pipe"; + args = [ + "user=discourse" + "argv=${cfg.mail.incoming.mailReceiverPackage}/bin/receive-mail" + "\${recipient}" + ]; + }; + "discourse-policy" = { + type = "unix"; + privileged = true; + chroot = false; + command = "spawn"; + args = [ + "user=discourse" + "argv=${cfg.mail.incoming.mailReceiverPackage}/bin/discourse-smtp-fast-rejection" + ]; + }; + }; + }; + + users.users = { + discourse = { + group = "discourse"; + isSystemUser = true; + }; + } // (lib.optionalAttrs cfg.nginx.enable { + ${config.services.nginx.user}.extraGroups = [ "discourse" ]; + }); + + users.groups = { + discourse = {}; + }; + + environment.systemPackages = [ + cfg.package.rake + ]; + }; + + meta.doc = ./discourse.xml; + meta.maintainers = [ lib.maintainers.talyz ]; +} diff --git a/nixpkgs/nixos/modules/services/web-apps/discourse.xml b/nixpkgs/nixos/modules/services/web-apps/discourse.xml new file mode 100644 index 000000000000..bae562423213 --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-apps/discourse.xml @@ -0,0 +1,323 @@ +<chapter xmlns="http://docbook.org/ns/docbook" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:xi="http://www.w3.org/2001/XInclude" + version="5.0" + xml:id="module-services-discourse"> + <title>Discourse</title> + <para> + <link xlink:href="https://www.discourse.org/">Discourse</link> is a + modern and open source discussion platform. + </para> + + <section xml:id="module-services-discourse-basic-usage"> + <title>Basic usage</title> + <para> + A minimal configuration using Let's Encrypt for TLS certificates looks like this: +<programlisting> +services.discourse = { + <link linkend="opt-services.discourse.enable">enable</link> = true; + <link linkend="opt-services.discourse.hostname">hostname</link> = "discourse.example.com"; + admin = { + <link linkend="opt-services.discourse.admin.email">email</link> = "admin@example.com"; + <link linkend="opt-services.discourse.admin.username">username</link> = "admin"; + <link linkend="opt-services.discourse.admin.fullName">fullName</link> = "Administrator"; + <link linkend="opt-services.discourse.admin.passwordFile">passwordFile</link> = "/path/to/password_file"; + }; + <link linkend="opt-services.discourse.secretKeyBaseFile">secretKeyBaseFile</link> = "/path/to/secret_key_base_file"; +}; +<link linkend="opt-security.acme.email">security.acme.email</link> = "me@example.com"; +<link linkend="opt-security.acme.acceptTerms">security.acme.acceptTerms</link> = true; +</programlisting> + </para> + + <para> + Provided a proper DNS setup, you'll be able to connect to the + instance at <literal>discourse.example.com</literal> and log in + using the credentials provided in + <literal>services.discourse.admin</literal>. + </para> + </section> + + <section xml:id="module-services-discourse-tls"> + <title>Using a regular TLS certificate</title> + <para> + To set up TLS using a regular certificate and key on file, use + the <xref linkend="opt-services.discourse.sslCertificate" /> + and <xref linkend="opt-services.discourse.sslCertificateKey" /> + options: + +<programlisting> +services.discourse = { + <link linkend="opt-services.discourse.enable">enable</link> = true; + <link linkend="opt-services.discourse.hostname">hostname</link> = "discourse.example.com"; + <link linkend="opt-services.discourse.sslCertificate">sslCertificate</link> = "/path/to/ssl_certificate"; + <link linkend="opt-services.discourse.sslCertificateKey">sslCertificateKey</link> = "/path/to/ssl_certificate_key"; + admin = { + <link linkend="opt-services.discourse.admin.email">email</link> = "admin@example.com"; + <link linkend="opt-services.discourse.admin.username">username</link> = "admin"; + <link linkend="opt-services.discourse.admin.fullName">fullName</link> = "Administrator"; + <link linkend="opt-services.discourse.admin.passwordFile">passwordFile</link> = "/path/to/password_file"; + }; + <link linkend="opt-services.discourse.secretKeyBaseFile">secretKeyBaseFile</link> = "/path/to/secret_key_base_file"; +}; +</programlisting> + + </para> + </section> + + <section xml:id="module-services-discourse-database"> + <title>Database access</title> + <para> + <productname>Discourse</productname> uses + <productname>PostgreSQL</productname> to store most of its + data. A database will automatically be enabled and a database + and role created unless <xref + linkend="opt-services.discourse.database.host" /> is changed from + its default of <literal>null</literal> or <xref + linkend="opt-services.discourse.database.createLocally" /> is set + to <literal>false</literal>. + </para> + + <para> + External database access can also be configured by setting + <xref linkend="opt-services.discourse.database.host" />, <xref + linkend="opt-services.discourse.database.username" /> and <xref + linkend="opt-services.discourse.database.passwordFile" /> as + appropriate. Note that you need to manually create a database + called <literal>discourse</literal> (or the name you chose in + <xref linkend="opt-services.discourse.database.name" />) and + allow the configured database user full access to it. + </para> + </section> + + <section xml:id="module-services-discourse-mail"> + <title>Email</title> + <para> + In addition to the basic setup, you'll want to configure an SMTP + server <productname>Discourse</productname> can use to send user + registration and password reset emails, among others. You can + also optionally let <productname>Discourse</productname> receive + email, which enables people to reply to threads and conversations + via email. + </para> + + <para> + A basic setup which assumes you want to use your configured <link + linkend="opt-services.discourse.hostname">hostname</link> as + email domain can be done like this: + +<programlisting> +services.discourse = { + <link linkend="opt-services.discourse.enable">enable</link> = true; + <link linkend="opt-services.discourse.hostname">hostname</link> = "discourse.example.com"; + <link linkend="opt-services.discourse.sslCertificate">sslCertificate</link> = "/path/to/ssl_certificate"; + <link linkend="opt-services.discourse.sslCertificateKey">sslCertificateKey</link> = "/path/to/ssl_certificate_key"; + admin = { + <link linkend="opt-services.discourse.admin.email">email</link> = "admin@example.com"; + <link linkend="opt-services.discourse.admin.username">username</link> = "admin"; + <link linkend="opt-services.discourse.admin.fullName">fullName</link> = "Administrator"; + <link linkend="opt-services.discourse.admin.passwordFile">passwordFile</link> = "/path/to/password_file"; + }; + mail.outgoing = { + <link linkend="opt-services.discourse.mail.outgoing.serverAddress">serverAddress</link> = "smtp.emailprovider.com"; + <link linkend="opt-services.discourse.mail.outgoing.port">port</link> = 587; + <link linkend="opt-services.discourse.mail.outgoing.username">username</link> = "user@emailprovider.com"; + <link linkend="opt-services.discourse.mail.outgoing.passwordFile">passwordFile</link> = "/path/to/smtp_password_file"; + }; + <link linkend="opt-services.discourse.mail.incoming.enable">mail.incoming.enable</link> = true; + <link linkend="opt-services.discourse.secretKeyBaseFile">secretKeyBaseFile</link> = "/path/to/secret_key_base_file"; +}; +</programlisting> + + This assumes you have set up an MX record for the address you've + set in <link linkend="opt-services.discourse.hostname">hostname</link> and + requires proper SPF, DKIM and DMARC configuration to be done for + the domain you're sending from, in order for email to be reliably delivered. + </para> + + <para> + If you want to use a different domain for your outgoing email + (for example <literal>example.com</literal> instead of + <literal>discourse.example.com</literal>) you should set + <xref linkend="opt-services.discourse.mail.notificationEmailAddress" /> and + <xref linkend="opt-services.discourse.mail.contactEmailAddress" /> manually. + </para> + + <note> + <para> + Setup of TLS for incoming email is currently only configured + automatically when a regular TLS certificate is used, i.e. when + <xref linkend="opt-services.discourse.sslCertificate" /> and + <xref linkend="opt-services.discourse.sslCertificateKey" /> are + set. + </para> + </note> + + </section> + + <section xml:id="module-services-discourse-settings"> + <title>Additional settings</title> + <para> + Additional site settings and backend settings, for which no + explicit <productname>NixOS</productname> options are provided, + can be set in <xref linkend="opt-services.discourse.siteSettings" /> and + <xref linkend="opt-services.discourse.backendSettings" /> respectively. + </para> + + <section xml:id="module-services-discourse-site-settings"> + <title>Site settings</title> + <para> + <quote>Site settings</quote> are the settings that can be + changed through the <productname>Discourse</productname> + UI. Their <emphasis>default</emphasis> values can be set using + <xref linkend="opt-services.discourse.siteSettings" />. + </para> + + <para> + Settings are expressed as a Nix attribute set which matches the + structure of the configuration in + <link xlink:href="https://github.com/discourse/discourse/blob/master/config/site_settings.yml">config/site_settings.yml</link>. + To find a setting's path, you only need to care about the first + two levels; i.e. its category (e.g. <literal>login</literal>) + and name (e.g. <literal>invite_only</literal>). + </para> + + <para> + Settings containing secret data should be set to an attribute + set containing the attribute <literal>_secret</literal> - a + string pointing to a file containing the value the option + should be set to. See the example. + </para> + </section> + + <section xml:id="module-services-discourse-backend-settings"> + <title>Backend settings</title> + <para> + Settings are expressed as a Nix attribute set which matches the + structure of the configuration in + <link xlink:href="https://github.com/discourse/discourse/blob/stable/config/discourse_defaults.conf">config/discourse.conf</link>. + Empty parameters can be defined by setting them to + <literal>null</literal>. + </para> + </section> + + <section xml:id="module-services-discourse-settings-example"> + <title>Example</title> + <para> + The following example sets the title and description of the + <productname>Discourse</productname> instance and enables + <productname>GitHub</productname> login in the site settings, + and changes a few request limits in the backend settings: +<programlisting> +services.discourse = { + <link linkend="opt-services.discourse.enable">enable</link> = true; + <link linkend="opt-services.discourse.hostname">hostname</link> = "discourse.example.com"; + <link linkend="opt-services.discourse.sslCertificate">sslCertificate</link> = "/path/to/ssl_certificate"; + <link linkend="opt-services.discourse.sslCertificateKey">sslCertificateKey</link> = "/path/to/ssl_certificate_key"; + admin = { + <link linkend="opt-services.discourse.admin.email">email</link> = "admin@example.com"; + <link linkend="opt-services.discourse.admin.username">username</link> = "admin"; + <link linkend="opt-services.discourse.admin.fullName">fullName</link> = "Administrator"; + <link linkend="opt-services.discourse.admin.passwordFile">passwordFile</link> = "/path/to/password_file"; + }; + mail.outgoing = { + <link linkend="opt-services.discourse.mail.outgoing.serverAddress">serverAddress</link> = "smtp.emailprovider.com"; + <link linkend="opt-services.discourse.mail.outgoing.port">port</link> = 587; + <link linkend="opt-services.discourse.mail.outgoing.username">username</link> = "user@emailprovider.com"; + <link linkend="opt-services.discourse.mail.outgoing.passwordFile">passwordFile</link> = "/path/to/smtp_password_file"; + }; + <link linkend="opt-services.discourse.mail.incoming.enable">mail.incoming.enable</link> = true; + <link linkend="opt-services.discourse.siteSettings">siteSettings</link> = { + required = { + title = "My Cats"; + site_description = "Discuss My Cats (and be nice plz)"; + }; + login = { + enable_github_logins = true; + github_client_id = "a2f6dfe838cb3206ce20"; + github_client_secret._secret = /run/keys/discourse_github_client_secret; + }; + }; + <link linkend="opt-services.discourse.backendSettings">backendSettings</link> = { + max_reqs_per_ip_per_minute = 300; + max_reqs_per_ip_per_10_seconds = 60; + max_asset_reqs_per_ip_per_10_seconds = 250; + max_reqs_per_ip_mode = "warn+block"; + }; + <link linkend="opt-services.discourse.secretKeyBaseFile">secretKeyBaseFile</link> = "/path/to/secret_key_base_file"; +}; +</programlisting> + </para> + <para> + In the resulting site settings file, the + <literal>login.github_client_secret</literal> key will be set + to the contents of the + <filename>/run/keys/discourse_github_client_secret</filename> + file. + </para> + </section> + </section> + <section xml:id="module-services-discourse-plugins"> + <title>Plugins</title> + <para> + You can install <productname>Discourse</productname> plugins + using the <xref linkend="opt-services.discourse.plugins" /> + option. As long as a plugin supports the standard install + method, packaging it should only require fetching its source + with an appropriate fetcher. + </para> + + <para> + Some plugins provide <link + linkend="module-services-discourse-site-settings">site + settings</link>. Their defaults can be configured using <xref + linkend="opt-services.discourse.siteSettings" />, just like + regular site settings. To find the names of these settings, look + in the <literal>config/settings.yml</literal> file of the plugin + repo. + </para> + + <para> + For example, to add the <link + xlink:href="https://github.com/discourse/discourse-spoiler-alert">discourse-spoiler-alert</link> + plugin and disable it by default: + +<programlisting> +services.discourse = { + <link linkend="opt-services.discourse.enable">enable</link> = true; + <link linkend="opt-services.discourse.hostname">hostname</link> = "discourse.example.com"; + <link linkend="opt-services.discourse.sslCertificate">sslCertificate</link> = "/path/to/ssl_certificate"; + <link linkend="opt-services.discourse.sslCertificateKey">sslCertificateKey</link> = "/path/to/ssl_certificate_key"; + admin = { + <link linkend="opt-services.discourse.admin.email">email</link> = "admin@example.com"; + <link linkend="opt-services.discourse.admin.username">username</link> = "admin"; + <link linkend="opt-services.discourse.admin.fullName">fullName</link> = "Administrator"; + <link linkend="opt-services.discourse.admin.passwordFile">passwordFile</link> = "/path/to/password_file"; + }; + mail.outgoing = { + <link linkend="opt-services.discourse.mail.outgoing.serverAddress">serverAddress</link> = "smtp.emailprovider.com"; + <link linkend="opt-services.discourse.mail.outgoing.port">port</link> = 587; + <link linkend="opt-services.discourse.mail.outgoing.username">username</link> = "user@emailprovider.com"; + <link linkend="opt-services.discourse.mail.outgoing.passwordFile">passwordFile</link> = "/path/to/smtp_password_file"; + }; + <link linkend="opt-services.discourse.mail.incoming.enable">mail.incoming.enable</link> = true; + <link linkend="opt-services.discourse.mail.incoming.enable">plugins</link> = [ + (pkgs.fetchFromGitHub { + owner = "discourse"; + repo = "discourse-spoiler-alert"; + rev = "e200cfa571d252cab63f3d30d619b370986e4cee"; + sha256 = "0ya69ix5g77wz4c9x9gmng6l25ghb5xxlx3icr6jam16q14dzc33"; + }) + ]; + <link linkend="opt-services.discourse.siteSettings">siteSettings</link> = { + plugins = { + spoiler_enabled = false; + }; + }; + <link linkend="opt-services.discourse.secretKeyBaseFile">secretKeyBaseFile</link> = "/path/to/secret_key_base_file"; +}; +</programlisting> + + </para> + </section> +</chapter> diff --git a/nixpkgs/nixos/modules/services/web-apps/dokuwiki.nix b/nixpkgs/nixos/modules/services/web-apps/dokuwiki.nix index 9567223ebc7b..685cb4967030 100644 --- a/nixpkgs/nixos/modules/services/web-apps/dokuwiki.nix +++ b/nixpkgs/nixos/modules/services/web-apps/dokuwiki.nix @@ -193,7 +193,7 @@ let }; sourceRoot = "."; # We need unzip to build this package - buildInputs = [ pkgs.unzip ]; + nativeBuildInputs = [ pkgs.unzip ]; # Installing simply means copying all files to the output directory installPhase = "mkdir -p $out; cp -R * $out/"; }; @@ -220,7 +220,7 @@ let sha256 = "4de5ff31d54dd61bbccaf092c9e74c1af3a4c53e07aa59f60457a8f00cfb23a6"; }; # We need unzip to build this package - buildInputs = [ pkgs.unzip ]; + nativeBuildInputs = [ pkgs.unzip ]; # Installing simply means copying all files to the output directory installPhase = "mkdir -p $out; cp -R * $out/"; }; @@ -329,7 +329,7 @@ in extraConfig = "internal;"; }; - locations."~ ^/lib.*\.(js|css|gif|png|ico|jpg|jpeg)$" = { + locations."~ ^/lib.*\\.(js|css|gif|png|ico|jpg|jpeg)$" = { extraConfig = "expires 365d;"; }; @@ -349,7 +349,7 @@ in ''; }; - locations."~ \.php$" = { + locations."~ \\.php$" = { extraConfig = '' try_files $uri $uri/ /doku.php; include ${pkgs.nginx}/conf/fastcgi_params; diff --git a/nixpkgs/nixos/modules/services/web-apps/galene.nix b/nixpkgs/nixos/modules/services/web-apps/galene.nix index 769490e915ac..dd63857a55c8 100644 --- a/nixpkgs/nixos/modules/services/web-apps/galene.nix +++ b/nixpkgs/nixos/modules/services/web-apps/galene.nix @@ -133,8 +133,10 @@ in wantedBy = [ "multi-user.target" ]; preStart = '' - install -m 700 -o '${cfg.user}' -g '${cfg.group}' ${cfg.certFile} ${cfg.dataDir}/cert.pem - install -m 700 -o '${cfg.user}' -g '${cfg.group}' ${cfg.keyFile} ${cfg.dataDir}/key.pem + ${optionalString (cfg.insecure != true) '' + install -m 700 -o '${cfg.user}' -g '${cfg.group}' ${cfg.certFile} ${cfg.dataDir}/cert.pem + install -m 700 -o '${cfg.user}' -g '${cfg.group}' ${cfg.keyFile} ${cfg.dataDir}/key.pem + ''} ''; serviceConfig = mkMerge [ diff --git a/nixpkgs/nixos/modules/services/web-apps/hledger-web.nix b/nixpkgs/nixos/modules/services/web-apps/hledger-web.nix index 43fc4daa177f..a69767194c33 100644 --- a/nixpkgs/nixos/modules/services/web-apps/hledger-web.nix +++ b/nixpkgs/nixos/modules/services/web-apps/hledger-web.nix @@ -26,19 +26,46 @@ in { ''; }; - capabilities = mkOption { - type = types.commas; - default = "view"; + capabilities = { + view = mkOption { + type = types.bool; + default = true; + description = '' + Enable the view capability. + ''; + }; + add = mkOption { + type = types.bool; + default = false; + description = '' + Enable the add capability. + ''; + }; + manage = mkOption { + type = types.bool; + default = false; + description = '' + Enable the manage capability. + ''; + }; + }; + + stateDir = mkOption { + type = types.path; + default = "/var/lib/hledger-web"; description = '' - Enable the view, add, and/or manage capabilities. E.g. view,add + Path the service has access to. If left as the default value this + directory will automatically be created before the hledger-web server + starts, otherwise the sysadmin is responsible for ensuring the + directory exists with appropriate ownership and permissions. ''; }; - journalFile = mkOption { - type = types.path; - example = "/home/hledger/.hledger.journal"; + journalFiles = mkOption { + type = types.listOf types.str; + default = [ ".hledger.journal" ]; description = '' - Input journal file. + Paths to journal files relative to <option>services.hledger-web.stateDir</option>. ''; }; @@ -50,28 +77,66 @@ in { Base URL, when sharing over a network. ''; }; + + extraOptions = mkOption { + type = types.listOf types.str; + default = []; + example = [ "--forecast" ]; + description = '' + Extra command line arguments to pass to hledger-web. + ''; + }; + }; config = mkIf cfg.enable { - systemd.services.hledger-web = { + + users.users.hledger = { + name = "hledger"; + group = "hledger"; + isSystemUser = true; + home = cfg.stateDir; + useDefaultShell = true; + }; + + users.groups.hledger = {}; + + systemd.services.hledger-web = let + capabilityString = with cfg.capabilities; concatStringsSep "," ( + (optional view "view") + ++ (optional add "add") + ++ (optional manage "manage") + ); + serverArgs = with cfg; escapeShellArgs ([ + "--serve" + "--host=${host}" + "--port=${toString port}" + "--capabilities=${capabilityString}" + (optionalString (cfg.baseUrl != null) "--base-url=${cfg.baseUrl}") + (optionalString (cfg.serveApi) "--serve-api") + ] ++ (map (f: "--file=${stateDir}/${f}") cfg.journalFiles) + ++ extraOptions); + in { description = "hledger-web - web-app for the hledger accounting tool."; documentation = [ https://hledger.org/hledger-web.html ]; wantedBy = [ "multi-user.target" ]; after = [ "networking.target" ]; - serviceConfig = { - ExecStart = '' - ${pkgs.hledger-web}/bin/hledger-web \ - --host=${cfg.host} \ - --port=${toString cfg.port} \ - --file=${cfg.journalFile} \ - "--capabilities=${cfg.capabilities}" \ - ${optionalString (cfg.baseUrl != null) "--base-url=${cfg.baseUrl}"} \ - ${optionalString (cfg.serveApi) "--serve-api"} - ''; - Restart = "always"; - }; + serviceConfig = mkMerge [ + { + ExecStart = "${pkgs.hledger-web}/bin/hledger-web ${serverArgs}"; + Restart = "always"; + WorkingDirectory = cfg.stateDir; + User = "hledger"; + Group = "hledger"; + PrivateTmp = true; + } + (mkIf (cfg.stateDir == "/var/lib/hledger-web") { + StateDirectory = "hledger-web"; + }) + ]; }; + }; - meta.maintainers = with lib.maintainers; [ marijanp ]; + meta.maintainers = with lib.maintainers; [ marijanp erictapen ]; } diff --git a/nixpkgs/nixos/modules/services/web-apps/mastodon.nix b/nixpkgs/nixos/modules/services/web-apps/mastodon.nix index 92b1be963bf7..16e8ae2ec0b2 100644 --- a/nixpkgs/nixos/modules/services/web-apps/mastodon.nix +++ b/nixpkgs/nixos/modules/services/web-apps/mastodon.nix @@ -25,10 +25,52 @@ let ES_ENABLED = if (cfg.elasticsearch.host != null) then "true" else "false"; ES_HOST = cfg.elasticsearch.host; ES_PORT = toString(cfg.elasticsearch.port); + + TRUSTED_PROXY_IP = cfg.trustedProxy; } // (if cfg.smtp.authenticate then { SMTP_LOGIN = cfg.smtp.user; } else {}) // cfg.extraConfig; + cfgService = { + # User and group + User = cfg.user; + Group = cfg.group; + # State directory and mode + StateDirectory = "mastodon"; + StateDirectoryMode = "0750"; + # Logs directory and mode + LogsDirectory = "mastodon"; + LogsDirectoryMode = "0750"; + # Access write directories + UMask = "0027"; + # Capabilities + CapabilityBoundingSet = ""; + # Security + NoNewPrivileges = true; + # Sandboxing + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + PrivateDevices = true; + PrivateUsers = true; + ProtectClock = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectControlGroups = true; + RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ]; + RestrictNamespaces = true; + LockPersonality = true; + MemoryDenyWriteExecute = false; + RestrictRealtime = true; + RestrictSUIDSGID = true; + PrivateMounts = true; + # System Call Filtering + SystemCallArchitectures = "native"; + SystemCallFilter = "~@clock @cpu-emulation @debug @keyring @module @mount @obsolete @reboot @resources @setuid @swap"; + }; + envFile = pkgs.writeText "mastodon.env" (lib.concatMapStrings (s: s + "\n") ( (lib.concatLists (lib.mapAttrsToList (name: value: if value != null then [ @@ -93,7 +135,6 @@ in { group = lib.mkOption { description = '' Group under which mastodon runs. - If it is set to "mastodon", a group will be created. ''; type = lib.types.str; default = "mastodon"; @@ -179,6 +220,26 @@ in { type = lib.types.str; }; + trustedProxy = lib.mkOption { + description = '' + You need to set it to the IP from which your reverse proxy sends requests to Mastodon's web process, + otherwise Mastodon will record the reverse proxy's own IP as the IP of all requests, which would be + bad because IP addresses are used for important rate limits and security functions. + ''; + type = lib.types.str; + default = "127.0.0.1"; + }; + + enableUnixSocket = lib.mkOption { + description = '' + Instead of binding to an IP address like 127.0.0.1, you may bind to a Unix socket. This variable + is process-specific, e.g. you need different values for every process, and it works for both web (Puma) + processes and streaming API (Node.js) processes. + ''; + type = lib.types.bool; + default = true; + }; + redis = { createLocally = lib.mkOption { description = "Configure local Redis server for Mastodon."; @@ -370,19 +431,16 @@ in { environment = env; serviceConfig = { Type = "oneshot"; - User = cfg.user; - Group = cfg.group; WorkingDirectory = cfg.package; - LogsDirectory = "mastodon"; - StateDirectory = "mastodon"; - }; + } // cfgService; + after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; }; systemd.services.mastodon-init-db = lib.mkIf cfg.automaticMigrations { script = '' - if [ `psql mastodon -c \ + if [ `psql ${cfg.database.name} -c \ "select count(*) from pg_class c \ join pg_namespace s on s.oid = c.relnamespace \ where s.nspname not in ('pg_catalog', 'pg_toast', 'information_schema') \ @@ -397,14 +455,9 @@ in { environment = env; serviceConfig = { Type = "oneshot"; - User = cfg.user; - Group = cfg.group; EnvironmentFile = "/var/lib/mastodon/.secrets_env"; - PrivateTmp = true; - LogsDirectory = "mastodon"; - StateDirectory = "mastodon"; WorkingDirectory = cfg.package; - }; + } // cfgService; after = [ "mastodon-init-dirs.service" "network.target" ] ++ (if databaseActuallyCreateLocally then [ "postgresql.service" ] else []); wantedBy = [ "multi-user.target" ]; }; @@ -415,21 +468,20 @@ in { ++ (if cfg.automaticMigrations then [ "mastodon-init-db.service" ] else [ "mastodon-init-dirs.service" ]); description = "Mastodon streaming"; wantedBy = [ "multi-user.target" ]; - environment = env // { - PORT = toString(cfg.streamingPort); - }; + environment = env // (if cfg.enableUnixSocket + then { SOCKET = "/run/mastodon-streaming/streaming.socket"; } + else { PORT = toString(cfg.streamingPort); } + ); serviceConfig = { - ExecStart = "${pkgs.nodejs-slim}/bin/node streaming"; + ExecStart = "${cfg.package}/run-streaming.sh"; Restart = "always"; RestartSec = 20; - User = cfg.user; - Group = cfg.group; - WorkingDirectory = cfg.package; EnvironmentFile = "/var/lib/mastodon/.secrets_env"; - PrivateTmp = true; - LogsDirectory = "mastodon"; - StateDirectory = "mastodon"; - }; + WorkingDirectory = cfg.package; + # Runtime directory and mode + RuntimeDirectory = "mastodon-streaming"; + RuntimeDirectoryMode = "0750"; + } // cfgService; }; systemd.services.mastodon-web = { @@ -438,21 +490,20 @@ in { ++ (if cfg.automaticMigrations then [ "mastodon-init-db.service" ] else [ "mastodon-init-dirs.service" ]); description = "Mastodon web"; wantedBy = [ "multi-user.target" ]; - environment = env // { - PORT = toString(cfg.webPort); - }; + environment = env // (if cfg.enableUnixSocket + then { SOCKET = "/run/mastodon-web/web.socket"; } + else { PORT = toString(cfg.webPort); } + ); serviceConfig = { ExecStart = "${cfg.package}/bin/puma -C config/puma.rb"; Restart = "always"; RestartSec = 20; - User = cfg.user; - Group = cfg.group; - WorkingDirectory = cfg.package; EnvironmentFile = "/var/lib/mastodon/.secrets_env"; - PrivateTmp = true; - LogsDirectory = "mastodon"; - StateDirectory = "mastodon"; - }; + WorkingDirectory = cfg.package; + # Runtime directory and mode + RuntimeDirectory = "mastodon-web"; + RuntimeDirectoryMode = "0750"; + } // cfgService; path = with pkgs; [ file imagemagick ffmpeg ]; }; @@ -469,14 +520,9 @@ in { ExecStart = "${cfg.package}/bin/sidekiq -c 25 -r ${cfg.package}"; Restart = "always"; RestartSec = 20; - User = cfg.user; - Group = cfg.group; - WorkingDirectory = cfg.package; EnvironmentFile = "/var/lib/mastodon/.secrets_env"; - PrivateTmp = true; - LogsDirectory = "mastodon"; - StateDirectory = "mastodon"; - }; + WorkingDirectory = cfg.package; + } // cfgService; path = with pkgs; [ file imagemagick ffmpeg ]; }; @@ -495,12 +541,12 @@ in { }; locations."@proxy" = { - proxyPass = "http://127.0.0.1:${toString(cfg.webPort)}"; + proxyPass = (if cfg.enableUnixSocket then "http://unix:/run/mastodon-web/web.socket" else "http://127.0.0.1:${toString(cfg.webPort)}"); proxyWebsockets = true; }; locations."/api/v1/streaming/" = { - proxyPass = "http://127.0.0.1:${toString(cfg.streamingPort)}/"; + proxyPass = (if cfg.enableUnixSocket then "http://unix:/run/mastodon-streaming/streaming.socket" else "http://127.0.0.1:${toString(cfg.streamingPort)}/"); proxyWebsockets = true; }; }; @@ -534,7 +580,7 @@ in { (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package mastodonEnv ]) ]; - users.groups.mastodon = lib.mkIf (cfg.group == "mastodon") { }; + users.groups.${cfg.group}.members = lib.optional cfg.configureNginx config.services.nginx.user; }; meta.maintainers = with lib.maintainers; [ happy-river erictapen ]; diff --git a/nixpkgs/nixos/modules/services/web-apps/miniflux.nix b/nixpkgs/nixos/modules/services/web-apps/miniflux.nix index 304712d0efc3..01710b1bd59c 100644 --- a/nixpkgs/nixos/modules/services/web-apps/miniflux.nix +++ b/nixpkgs/nixos/modules/services/web-apps/miniflux.nix @@ -14,17 +14,16 @@ let ADMIN_PASSWORD=password ''; - pgsu = "${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser}"; pgbin = "${config.services.postgresql.package}/bin"; preStart = pkgs.writeScript "miniflux-pre-start" '' #!${pkgs.runtimeShell} db_exists() { - [ "$(${pgsu} ${pgbin}/psql -Atc "select 1 from pg_database where datname='$1'")" == "1" ] + [ "$(${pgbin}/psql -Atc "select 1 from pg_database where datname='$1'")" == "1" ] } if ! db_exists "${dbName}"; then - ${pgsu} ${pgbin}/psql postgres -c "CREATE ROLE ${dbUser} WITH LOGIN NOCREATEDB NOCREATEROLE ENCRYPTED PASSWORD '${dbPassword}'" - ${pgsu} ${pgbin}/createdb --owner "${dbUser}" "${dbName}" - ${pgsu} ${pgbin}/psql "${dbName}" -c "CREATE EXTENSION IF NOT EXISTS hstore" + ${pgbin}/psql postgres -c "CREATE ROLE ${dbUser} WITH LOGIN NOCREATEDB NOCREATEROLE ENCRYPTED PASSWORD '${dbPassword}'" + ${pgbin}/createdb --owner "${dbUser}" "${dbName}" + ${pgbin}/psql "${dbName}" -c "CREATE EXTENSION IF NOT EXISTS hstore" fi ''; in @@ -44,7 +43,7 @@ in ''; description = '' Configuration for Miniflux, refer to - <link xlink:href="http://docs.miniflux.app/en/latest/configuration.html"/> + <link xlink:href="https://miniflux.app/docs/configuration.html"/> for documentation on the supported values. ''; }; @@ -73,15 +72,26 @@ in services.postgresql.enable = true; + systemd.services.miniflux-dbsetup = { + description = "Miniflux database setup"; + wantedBy = [ "multi-user.target" ]; + requires = [ "postgresql.service" ]; + after = [ "network.target" "postgresql.service" ]; + serviceConfig = { + Type = "oneshot"; + User = config.services.postgresql.superUser; + ExecStart = preStart; + }; + }; + systemd.services.miniflux = { description = "Miniflux service"; wantedBy = [ "multi-user.target" ]; requires = [ "postgresql.service" ]; - after = [ "network.target" "postgresql.service" ]; + after = [ "network.target" "postgresql.service" "miniflux-dbsetup.service" ]; serviceConfig = { ExecStart = "${pkgs.miniflux}/bin/miniflux"; - ExecStartPre = "+${preStart}"; DynamicUser = true; RuntimeDirectory = "miniflux"; RuntimeDirectoryMode = "0700"; diff --git a/nixpkgs/nixos/modules/services/web-apps/moinmoin.nix b/nixpkgs/nixos/modules/services/web-apps/moinmoin.nix index 3a876f75f4a4..7a54255a46ef 100644 --- a/nixpkgs/nixos/modules/services/web-apps/moinmoin.nix +++ b/nixpkgs/nixos/modules/services/web-apps/moinmoin.nix @@ -211,7 +211,7 @@ in environment = let penv = python.buildEnv.override { # setuptools: https://github.com/benoitc/gunicorn/issues/1716 - extraLibs = [ python.pkgs.gevent python.pkgs.setuptools pkg ]; + extraLibs = [ python.pkgs.eventlet python.pkgs.setuptools pkg ]; }; in { PYTHONPATH = "${dataDir}/${wikiIdent}/config:${penv}/${python.sitePackages}"; @@ -233,7 +233,7 @@ in ExecStart = ''${python.pkgs.gunicorn}/bin/gunicorn moin_wsgi \ --name gunicorn-${wikiIdent} \ --workers ${toString cfg.gunicorn.workers} \ - --worker-class gevent \ + --worker-class eventlet \ --bind unix:/run/moin/${wikiIdent}/gunicorn.sock ''; diff --git a/nixpkgs/nixos/modules/services/web-apps/nextcloud.nix b/nixpkgs/nixos/modules/services/web-apps/nextcloud.nix index 60d403553352..9a541aba6e43 100644 --- a/nixpkgs/nixos/modules/services/web-apps/nextcloud.nix +++ b/nixpkgs/nixos/modules/services/web-apps/nextcloud.nix @@ -10,7 +10,7 @@ let extensions = { enabled, all }: (with all; enabled - ++ [ imagick ] # Always enabled + ++ optional (!cfg.disableImagemagick) imagick # Optionally enabled depending on caching settings ++ optional cfg.caching.apcu apcu ++ optional cfg.caching.redis redis @@ -28,7 +28,10 @@ let upload_max_filesize = cfg.maxUploadSize; post_max_size = cfg.maxUploadSize; memory_limit = cfg.maxUploadSize; - } // cfg.phpOptions; + } // cfg.phpOptions + // optionalAttrs cfg.caching.apcu { + "apc.enable_cli" = "1"; + }; occ = pkgs.writeScriptBin "nextcloud-occ" '' #! ${pkgs.runtimeShell} @@ -86,7 +89,7 @@ in { package = mkOption { type = types.package; description = "Which package to use for the Nextcloud instance."; - relatedPackages = [ "nextcloud18" "nextcloud19" "nextcloud20" ]; + relatedPackages = [ "nextcloud19" "nextcloud20" "nextcloud21" ]; }; maxUploadSize = mkOption { @@ -280,6 +283,36 @@ in { may be served via HTTPS. ''; }; + + defaultPhoneRegion = mkOption { + default = null; + type = types.nullOr types.str; + example = "DE"; + description = '' + <warning> + <para>This option exists since Nextcloud 21! If older versions are used, + this will throw an eval-error!</para> + </warning> + + <link xlink:href="https://www.iso.org/iso-3166-country-codes.html">ISO 3611-1</link> + country codes for automatic phone-number detection without a country code. + + With e.g. <literal>DE</literal> set, the <literal>+49</literal> can be omitted for + phone-numbers. + ''; + }; + }; + + disableImagemagick = mkOption { + type = types.bool; + default = false; + description = '' + Whether to not load the ImageMagick module into PHP. + This is used by the theming app and for generating previews of certain images (e.g. SVG and HEIF). + You may want to disable it for increased security. In that case, previews will still be available + for some images (e.g. JPEG and PNG). + See https://github.com/nextcloud/server/issues/13099 + ''; }; caching = { @@ -345,10 +378,13 @@ in { && !(acfg.adminpass != null && acfg.adminpassFile != null)); message = "Please specify exactly one of adminpass or adminpassFile"; } + { assertion = versionOlder cfg.package.version "21" -> cfg.config.defaultPhoneRegion == null; + message = "The `defaultPhoneRegion'-setting is only supported for Nextcloud >=21!"; + } ]; warnings = let - latest = 20; + latest = 21; upgradeWarning = major: nixos: '' A legacy Nextcloud install (from before NixOS ${nixos}) may be installed. @@ -366,9 +402,9 @@ in { Using config.services.nextcloud.poolConfig is deprecated and will become unsupported in a future release. Please migrate your configuration to config.services.nextcloud.poolSettings. '') - ++ (optional (versionOlder cfg.package.version "18") (upgradeWarning 17 "20.03")) ++ (optional (versionOlder cfg.package.version "19") (upgradeWarning 18 "20.09")) - ++ (optional (versionOlder cfg.package.version "20") (upgradeWarning 19 "21.05")); + ++ (optional (versionOlder cfg.package.version "20") (upgradeWarning 19 "21.05")) + ++ (optional (versionOlder cfg.package.version "21") (upgradeWarning 20 "21.05")); services.nextcloud.package = with pkgs; mkDefault ( @@ -378,14 +414,13 @@ in { nextcloud defined in an overlay, please set `services.nextcloud.package` to `pkgs.nextcloud`. '' - else if versionOlder stateVersion "20.03" then nextcloud17 else if versionOlder stateVersion "20.09" then nextcloud18 # 21.03 will not be an official release - it was instead 21.05. # This versionOlder statement remains set to 21.03 for backwards compatibility. # See https://github.com/NixOS/nixpkgs/pull/108899 and # https://github.com/NixOS/rfcs/blob/master/rfcs/0080-nixos-release-schedule.md. else if versionOlder stateVersion "21.03" then nextcloud19 - else nextcloud20 + else nextcloud21 ); } @@ -443,6 +478,7 @@ in { 'dbtype' => '${c.dbtype}', 'trusted_domains' => ${writePhpArrary ([ cfg.hostName ] ++ c.extraTrustedDomains)}, 'trusted_proxies' => ${writePhpArrary (c.trustedProxies)}, + ${optionalString (c.defaultPhoneRegion != null) "'default_phone_region' => '${c.defaultPhoneRegion}',"} ]; ''; occInstallCmd = let @@ -591,6 +627,14 @@ in { access_log off; ''; }; + "= /" = { + priority = 100; + extraConfig = '' + if ( $http_user_agent ~ ^DavClnt ) { + return 302 /remote.php/webdav/$is_args$args; + } + ''; + }; "/" = { priority = 900; extraConfig = "rewrite ^ /index.php;"; @@ -602,11 +646,15 @@ in { "^~ /.well-known" = { priority = 210; extraConfig = '' + absolute_redirect off; location = /.well-known/carddav { - return 301 $scheme://$host/remote.php/dav; + return 301 /remote.php/dav; } location = /.well-known/caldav { - return 301 $scheme://$host/remote.php/dav; + return 301 /remote.php/dav; + } + location ~ ^/\.well-known/(?!acme-challenge|pki-validation) { + return 301 /index.php$request_uri; } try_files $uri $uri/ =404; ''; @@ -614,7 +662,7 @@ in { "~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)".extraConfig = '' return 404; ''; - "~ ^/(?:\\.|autotest|occ|issue|indie|db_|console)".extraConfig = '' + "~ ^/(?:\\.(?!well-known)|autotest|occ|issue|indie|db_|console)".extraConfig = '' return 404; ''; "~ ^\\/(?:index|remote|public|cron|core\\/ajax\\/update|status|ocs\\/v[12]|updater\\/.+|oc[ms]-provider\\/.+|.+\\/richdocumentscode\\/proxy)\\.php(?:$|\\/)" = { diff --git a/nixpkgs/nixos/modules/services/web-apps/nextcloud.xml b/nixpkgs/nixos/modules/services/web-apps/nextcloud.xml index 6cbfda118c4f..83a6f68edcbf 100644 --- a/nixpkgs/nixos/modules/services/web-apps/nextcloud.xml +++ b/nixpkgs/nixos/modules/services/web-apps/nextcloud.xml @@ -11,7 +11,7 @@ desktop client is packaged at <literal>pkgs.nextcloud-client</literal>. </para> <para> - The current default by NixOS is <package>nextcloud20</package> which is also the latest + The current default by NixOS is <package>nextcloud21</package> which is also the latest major version available. </para> <section xml:id="module-services-nextcloud-basic-usage"> diff --git a/nixpkgs/nixos/modules/services/web-apps/shiori.nix b/nixpkgs/nixos/modules/services/web-apps/shiori.nix index 9083ddfa2206..8f96dd9b5dd7 100644 --- a/nixpkgs/nixos/modules/services/web-apps/shiori.nix +++ b/nixpkgs/nixos/modules/services/web-apps/shiori.nix @@ -87,8 +87,8 @@ in { SystemCallFilter = [ "@system-service" - "~@chown" "~@cpu-emulation" "~@debug" "~@ipc" "~@keyring" "~@memlock" - "~@module" "~@obsolete" "~@privileged" "~@process" "~@raw-io" + "~@chown" "~@cpu-emulation" "~@debug" "~@keyring" "~@memlock" + "~@module" "~@obsolete" "~@privileged" "~@raw-io" "~@resources" "~@setuid" ]; }; diff --git a/nixpkgs/nixos/modules/services/web-apps/whitebophir.nix b/nixpkgs/nixos/modules/services/web-apps/whitebophir.nix index a19812547c44..b265296d5c1e 100644 --- a/nixpkgs/nixos/modules/services/web-apps/whitebophir.nix +++ b/nixpkgs/nixos/modules/services/web-apps/whitebophir.nix @@ -16,6 +16,12 @@ in { description = "Whitebophir package to use."; }; + listenAddress = mkOption { + type = types.str; + default = "0.0.0.0"; + description = "Address to listen on (use 0.0.0.0 to allow access from any address)."; + }; + port = mkOption { type = types.port; default = 5001; @@ -30,7 +36,8 @@ in { wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; environment = { - PORT = "${toString cfg.port}"; + PORT = toString cfg.port; + HOST = toString cfg.listenAddress; WBO_HISTORY_DIR = "/var/lib/whitebophir"; }; diff --git a/nixpkgs/nixos/modules/services/web-apps/wiki-js.nix b/nixpkgs/nixos/modules/services/web-apps/wiki-js.nix new file mode 100644 index 000000000000..1a6259dffeef --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-apps/wiki-js.nix @@ -0,0 +1,139 @@ +{ lib, pkgs, config, ... }: + +with lib; + +let + cfg = config.services.wiki-js; + + format = pkgs.formats.json { }; + + configFile = format.generate "wiki-js.yml" cfg.settings; +in { + options.services.wiki-js = { + enable = mkEnableOption "wiki-js"; + + environmentFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/root/wiki-js.env"; + description = '' + Environment fiel to inject e.g. secrets into the configuration. + ''; + }; + + stateDirectoryName = mkOption { + default = "wiki-js"; + type = types.str; + description = '' + Name of the directory in <filename>/var/lib</filename>. + ''; + }; + + settings = mkOption { + default = {}; + type = types.submodule { + freeformType = format.type; + options = { + port = mkOption { + type = types.port; + default = 3000; + description = '' + TCP port the process should listen to. + ''; + }; + + bindIP = mkOption { + default = "0.0.0.0"; + type = types.str; + description = '' + IPs the service should listen to. + ''; + }; + + db = { + type = mkOption { + default = "postgres"; + type = types.enum [ "postgres" "mysql" "mariadb" "mssql" ]; + description = '' + Database driver to use for persistence. Please note that <literal>sqlite</literal> + is currently not supported as the build process for it is currently not implemented + in <package>pkgs.wiki-js</package> and it's not recommended by upstream for + production use. + ''; + }; + host = mkOption { + type = types.str; + example = "/run/postgresql"; + description = '' + Hostname or socket-path to connect to. + ''; + }; + db = mkOption { + default = "wiki"; + type = types.str; + description = '' + Name of the database to use. + ''; + }; + }; + + logLevel = mkOption { + default = "info"; + type = types.enum [ "error" "warn" "info" "verbose" "debug" "silly" ]; + description = '' + Define how much detail is supposed to be logged at runtime. + ''; + }; + + offline = mkEnableOption "offline mode" // { + description = '' + Disable latest file updates and enable + <link xlink:href="https://docs.requarks.io/install/sideload">sideloading</link>. + ''; + }; + }; + }; + description = '' + Settings to configure <package>wiki-js</package>. This directly + corresponds to <link xlink:href="https://docs.requarks.io/install/config">the upstream + configuration options</link>. + + Secrets can be injected via the environment by + <itemizedlist> + <listitem><para>specifying <xref linkend="opt-services.wiki-js.environmentFile" /> + to contain secrets</para></listitem> + <listitem><para>and setting sensitive values to <literal>$(ENVIRONMENT_VAR)</literal> + with this value defined in the environment-file.</para></listitem> + </itemizedlist> + ''; + }; + }; + + config = mkIf cfg.enable { + services.wiki-js.settings.dataPath = "/var/lib/${cfg.stateDirectoryName}"; + systemd.services.wiki-js = { + description = "A modern and powerful wiki app built on Node.js"; + documentation = [ "https://docs.requarks.io/" ]; + wantedBy = [ "multi-user.target" ]; + + path = with pkgs; [ coreutils ]; + preStart = '' + ln -sf ${configFile} /var/lib/${cfg.stateDirectoryName}/config.yml + ln -sf ${pkgs.wiki-js}/server /var/lib/${cfg.stateDirectoryName} + ln -sf ${pkgs.wiki-js}/assets /var/lib/${cfg.stateDirectoryName} + ln -sf ${pkgs.wiki-js}/package.json /var/lib/${cfg.stateDirectoryName}/package.json + ''; + + serviceConfig = { + EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile; + StateDirectory = cfg.stateDirectoryName; + WorkingDirectory = "/var/lib/${cfg.stateDirectoryName}"; + DynamicUser = true; + PrivateTmp = true; + ExecStart = "${pkgs.nodejs}/bin/node ${pkgs.wiki-js}/server"; + }; + }; + }; + + meta.maintainers = with maintainers; [ ma27 ]; +} diff --git a/nixpkgs/nixos/modules/services/web-apps/wordpress.nix b/nixpkgs/nixos/modules/services/web-apps/wordpress.nix index 5fbe53221ae8..f251cfe32db6 100644 --- a/nixpkgs/nixos/modules/services/web-apps/wordpress.nix +++ b/nixpkgs/nixos/modules/services/web-apps/wordpress.nix @@ -109,7 +109,7 @@ let sha256 = "1rhba5h5fjlhy8p05zf0p14c9iagfh96y91r36ni0rmk6y891lyd"; }; # We need unzip to build this package - buildInputs = [ pkgs.unzip ]; + nativeBuildInputs = [ pkgs.unzip ]; # Installing simply means copying all files to the output directory installPhase = "mkdir -p $out; cp -R * $out/"; }; @@ -136,7 +136,7 @@ let sha256 = "0rjwm811f4aa4q43r77zxlpklyb85q08f9c8ns2akcarrvj5ydx3"; }; # We need unzip to build this package - buildInputs = [ pkgs.unzip ]; + nativeBuildInputs = [ pkgs.unzip ]; # Installing simply means copying all files to the output directory installPhase = "mkdir -p $out; cp -R * $out/"; }; diff --git a/nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix index 7f50b8fd8d44..b2bb5055cd4c 100644 --- a/nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix +++ b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix @@ -22,7 +22,9 @@ let php = cfg.phpPackage.override { apacheHttpd = pkg; }; - phpMajorVersion = lib.versions.major (lib.getVersion php); + phpModuleName = let + majorVersion = lib.versions.major (lib.getVersion php); + in (if majorVersion == "8" then "php" else "php${majorVersion}"); mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = pkg; }; @@ -63,7 +65,7 @@ let ++ optional enableSSL "ssl" ++ optional enableUserDir "userdir" ++ optional cfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; } - ++ optional cfg.enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; } + ++ optional cfg.enablePHP { name = phpModuleName; path = "${php}/modules/lib${phpModuleName}.so"; } ++ optional cfg.enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; } ++ cfg.extraModules; diff --git a/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix b/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix index fa8614e8ec17..52fcce6d17b4 100644 --- a/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix +++ b/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix @@ -154,9 +154,9 @@ let ${optionalString (cfg.recommendedProxySettings) '' proxy_redirect off; - proxy_connect_timeout 90; - proxy_send_timeout 90; - proxy_read_timeout 90; + proxy_connect_timeout 60; + proxy_send_timeout 60; + proxy_read_timeout 60; proxy_http_version 1.0; include ${recommendedProxyConfig}; ''} @@ -397,6 +397,9 @@ in default = pkgs.nginxStable; defaultText = "pkgs.nginxStable"; type = types.package; + apply = p: p.override { + modules = p.modules ++ cfg.additionalModules; + }; description = " Nginx package to use. This defaults to the stable version. Note that the nginx team recommends to use the mainline version which @@ -404,6 +407,17 @@ in "; }; + additionalModules = mkOption { + default = []; + type = types.listOf (types.attrsOf types.anything); + example = literalExample "[ pkgs.nginxModules.brotli ]"; + description = '' + Additional <link xlink:href="https://www.nginx.com/resources/wiki/modules/">third-party nginx modules</link> + to install. Packaged modules are available in + <literal>pkgs.nginxModules</literal>. + ''; + }; + logError = mkOption { default = "stderr"; type = types.str; @@ -804,7 +818,7 @@ in ProtectControlGroups = true; RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ]; LockPersonality = true; - MemoryDenyWriteExecute = !(builtins.any (mod: (mod.allowMemoryWriteExecute or false)) pkgs.nginx.modules); + MemoryDenyWriteExecute = !(builtins.any (mod: (mod.allowMemoryWriteExecute or false)) (optionals (cfg.package ? modules) cfg.package.modules)); RestrictRealtime = true; RestrictSUIDSGID = true; PrivateMounts = true; diff --git a/nixpkgs/nixos/modules/services/web-servers/pomerium.nix b/nixpkgs/nixos/modules/services/web-servers/pomerium.nix new file mode 100644 index 000000000000..2bc7d01c7c28 --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-servers/pomerium.nix @@ -0,0 +1,131 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + format = pkgs.formats.yaml {}; +in +{ + options.services.pomerium = { + enable = mkEnableOption "the Pomerium authenticating reverse proxy"; + + configFile = mkOption { + type = with types; nullOr path; + default = null; + description = "Path to Pomerium config YAML. If set, overrides services.pomerium.settings."; + }; + + useACMEHost = mkOption { + type = with types; nullOr str; + default = null; + description = '' + If set, use a NixOS-generated ACME certificate with the specified name. + + Note that this will require you to use a non-HTTP-based challenge, or + disable Pomerium's in-built HTTP redirect server by setting + http_redirect_addr to null and use a different HTTP server for serving + the challenge response. + + If you're using an HTTP-based challenge, you should use the + Pomerium-native autocert option instead. + ''; + }; + + settings = mkOption { + description = '' + The contents of Pomerium's config.yaml, in Nix expressions. + + Specifying configFile will override this in its entirety. + + See <link xlink:href="https://pomerium.io/reference/">the Pomerium + configuration reference</link> for more information about what to put + here. + ''; + default = {}; + type = format.type; + }; + + secretsFile = mkOption { + type = with types; nullOr path; + default = null; + description = '' + Path to file containing secrets for Pomerium, in systemd + EnvironmentFile format. See the systemd.exec(5) man page. + ''; + }; + }; + + config = let + cfg = config.services.pomerium; + cfgFile = if cfg.configFile != null then cfg.configFile else (format.generate "pomerium.yaml" cfg.settings); + in mkIf cfg.enable ({ + systemd.services.pomerium = { + description = "Pomerium authenticating reverse proxy"; + wants = [ "network.target" ] ++ (optional (cfg.useACMEHost != null) "acme-finished-${cfg.useACMEHost}.target"); + after = [ "network.target" ] ++ (optional (cfg.useACMEHost != null) "acme-finished-${cfg.useACMEHost}.target"); + wantedBy = [ "multi-user.target" ]; + environment = optionalAttrs (cfg.useACMEHost != null) { + CERTIFICATE_FILE = "fullchain.pem"; + CERTIFICATE_KEY_FILE = "key.pem"; + }; + startLimitIntervalSec = 60; + + serviceConfig = { + DynamicUser = true; + StateDirectory = [ "pomerium" ]; + ExecStart = "${pkgs.pomerium}/bin/pomerium -config ${cfgFile}"; + + PrivateUsers = false; # breaks CAP_NET_BIND_SERVICE + MemoryDenyWriteExecute = false; # breaks LuaJIT + + NoNewPrivileges = true; + PrivateTmp = true; + PrivateDevices = true; + DevicePolicy = "closed"; + ProtectSystem = "strict"; + ProtectHome = true; + ProtectControlGroups = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectKernelLogs = true; + RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK"; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + LockPersonality = true; + SystemCallArchitectures = "native"; + + EnvironmentFile = cfg.secretsFile; + AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; + CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; + + WorkingDirectory = mkIf (cfg.useACMEHost != null) "$CREDENTIALS_DIRECTORY"; + LoadCredential = optionals (cfg.useACMEHost != null) [ + "fullchain.pem:/var/lib/acme/${cfg.useACMEHost}/fullchain.pem" + "key.pem:/var/lib/acme/${cfg.useACMEHost}/key.pem" + ]; + }; + }; + + # postRun hooks on cert renew can't be used to restart Nginx since renewal + # runs as the unprivileged acme user. sslTargets are added to wantedBy + before + # which allows the acme-finished-$cert.target to signify the successful updating + # of certs end-to-end. + systemd.services.pomerium-config-reload = mkIf (cfg.useACMEHost != null) { + # TODO(lukegb): figure out how to make config reloading work with credentials. + + wantedBy = [ "acme-finished-${cfg.useACMEHost}.target" "multi-user.target" ]; + # Before the finished targets, after the renew services. + before = [ "acme-finished-${cfg.useACMEHost}.target" ]; + after = [ "acme-${cfg.useACMEHost}.service" ]; + # Block reloading if not all certs exist yet. + unitConfig.ConditionPathExists = [ "${config.security.acme.certs.${cfg.useACMEHost}.directory}/fullchain.pem" ]; + serviceConfig = { + Type = "oneshot"; + TimeoutSec = 60; + ExecCondition = "/run/current-system/systemd/bin/systemctl -q is-active pomerium.service"; + ExecStart = "/run/current-system/systemd/bin/systemctl restart pomerium.service"; + }; + }; + }); +} diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome3.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome3.nix index 671301246a8c..99e6edfba26e 100644 --- a/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome3.nix +++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome3.nix @@ -197,12 +197,11 @@ in config = mkMerge [ (mkIf (cfg.enable || flashbackEnabled) { # Seed our configuration into nixos-generate-config - system.nixos-generate-config.desktopConfiguration = '' + system.nixos-generate-config.desktopConfiguration = ['' # Enable the GNOME 3 Desktop Environment. - services.xserver.enable = true; services.xserver.displayManager.gdm.enable = true; services.xserver.desktopManager.gnome3.enable = true; - ''; + '']; services.gnome3.core-os-services.enable = true; services.gnome3.core-shell.enable = true; diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/kodi.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/kodi.nix index bdae9c3afdb7..af303d6fb279 100644 --- a/nixpkgs/nixos/modules/services/x11/desktop-managers/kodi.nix +++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/kodi.nix @@ -14,6 +14,16 @@ in default = false; description = "Enable the kodi multimedia center."; }; + + package = mkOption { + type = types.package; + default = pkgs.kodi; + defaultText = "pkgs.kodi"; + example = "pkgs.kodi.withPackages (p: with p; [ jellyfin pvr-iptvsimple vfs-sftp ])"; + description = '' + Package that should be used for Kodi. + ''; + }; }; }; @@ -21,11 +31,11 @@ in services.xserver.desktopManager.session = [{ name = "kodi"; start = '' - LIRC_SOCKET_PATH=/run/lirc/lircd ${pkgs.kodi}/bin/kodi --standalone & + LIRC_SOCKET_PATH=/run/lirc/lircd ${cfg.package}/bin/kodi --standalone & waitPID=$! ''; }]; - environment.systemPackages = [ pkgs.kodi ]; + environment.systemPackages = [ cfg.package ]; }; } diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/plasma5.nix index d6cf86d3a2e6..44ee079b8173 100644 --- a/nixpkgs/nixos/modules/services/x11/desktop-managers/plasma5.nix +++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/plasma5.nix @@ -184,12 +184,11 @@ in config = mkMerge [ (mkIf cfg.enable { # Seed our configuration into nixos-generate-config - system.nixos-generate-config.desktopConfiguration = '' + system.nixos-generate-config.desktopConfiguration = ['' # Enable the Plasma 5 Desktop Environment. - services.xserver.enable = true; services.xserver.displayManager.sddm.enable = true; services.xserver.desktopManager.plasma5.enable = true; - ''; + '']; services.xserver.desktopManager.session = singleton { name = "plasma5"; diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/xfce.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/xfce.nix index d39b4d64904f..fc7f7bea4e44 100644 --- a/nixpkgs/nixos/modules/services/x11/desktop-managers/xfce.nix +++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/xfce.nix @@ -58,7 +58,7 @@ in noDesktop = mkOption { type = types.bool; default = false; - description = "Don't install XFCE desktop components (xfdesktop, panel and notification daemon)."; + description = "Don't install XFCE desktop components (xfdesktop and panel)."; }; enableXfwm = mkOption { @@ -98,6 +98,7 @@ in parole ristretto xfce4-appfinder + xfce4-notifyd xfce4-screenshooter xfce4-session xfce4-settings @@ -119,7 +120,6 @@ in xfwm4 xfwm4-themes ] ++ optionals (!cfg.noDesktop) [ - xfce4-notifyd xfce4-panel xfdesktop ]; @@ -166,7 +166,8 @@ in # Systemd services systemd.packages = with pkgs.xfce; [ (thunar.override { thunarPlugins = cfg.thunarPlugins; }) - ] ++ optional (!cfg.noDesktop) xfce4-notifyd; + xfce4-notifyd + ]; }; } diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/default.nix b/nixpkgs/nixos/modules/services/x11/display-managers/default.nix index 9fdbe753dad5..e04fcdaf4145 100644 --- a/nixpkgs/nixos/modules/services/x11/display-managers/default.nix +++ b/nixpkgs/nixos/modules/services/x11/display-managers/default.nix @@ -37,6 +37,11 @@ let . /etc/profile cd "$HOME" + # Allow the user to execute commands at the beginning of the X session. + if test -f ~/.xprofile; then + source ~/.xprofile + fi + ${optionalString cfg.displayManager.job.logToJournal '' if [ -z "$_DID_SYSTEMD_CAT" ]; then export _DID_SYSTEMD_CAT=1 @@ -64,22 +69,23 @@ let # Speed up application start by 50-150ms according to # http://kdemonkey.blogspot.nl/2008/04/magic-trick.html - rm -rf "$HOME/.compose-cache" - mkdir "$HOME/.compose-cache" + compose_cache="''${XCOMPOSECACHE:-$HOME/.compose-cache}" + mkdir -p "$compose_cache" + # To avoid accidentally deleting a wrongly set up XCOMPOSECACHE directory, + # defensively try to delete cache *files* only, following the file format specified in + # https://gitlab.freedesktop.org/xorg/lib/libx11/-/blob/master/modules/im/ximcp/imLcIm.c#L353-358 + # sprintf (*res, "%s/%c%d_%03x_%08x_%08x", dir, _XimGetMyEndian(), XIM_CACHE_VERSION, (unsigned int)sizeof (DefTree), hash, hash2); + ${pkgs.findutils}/bin/find "$compose_cache" -maxdepth 1 -regextype posix-extended -regex '.*/[Bl][0-9]+_[0-9a-f]{3}_[0-9a-f]{8}_[0-9a-f]{8}' -delete + unset compose_cache # Work around KDE errors when a user first logs in and # .local/share doesn't exist yet. - mkdir -p "$HOME/.local/share" + mkdir -p "''${XDG_DATA_HOME:-$HOME/.local/share}" unset _DID_SYSTEMD_CAT ${cfg.displayManager.sessionCommands} - # Allow the user to execute commands at the beginning of the X session. - if test -f ~/.xprofile; then - source ~/.xprofile - fi - # Start systemd user services for graphical sessions /run/current-system/systemd/bin/systemctl --user start graphical-session.target diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/gdm.nix b/nixpkgs/nixos/modules/services/x11/display-managers/gdm.nix index e3c5adb9737f..f79eb64b5a6a 100644 --- a/nixpkgs/nixos/modules/services/x11/display-managers/gdm.nix +++ b/nixpkgs/nixos/modules/services/x11/display-managers/gdm.nix @@ -183,14 +183,20 @@ in "systemd-udev-settle.service" ]; systemd.services.display-manager.conflicts = [ - "getty@tty${gdm.initialVT}.service" - # TODO: Add "plymouth-quit.service" so GDM can control when plymouth quits. - # Currently this breaks switching configurations while using plymouth. + "getty@tty${gdm.initialVT}.service" + "plymouth-quit.service" ]; systemd.services.display-manager.onFailure = [ "plymouth-quit.service" ]; + # Prevent nixos-rebuild switch from bringing down the graphical + # session. (If multi-user.target wants plymouth-quit.service which + # conflicts display-manager.service, then when nixos-rebuild + # switch starts multi-user.target, display-manager.service is + # stopped so plymouth-quit.service can be started.) + systemd.services.plymouth-quit.wantedBy = lib.mkForce []; + systemd.services.display-manager.serviceConfig = { # Restart = "always"; - already defined in xserver.nix KillMode = "mixed"; diff --git a/nixpkgs/nixos/modules/services/x11/xserver.nix b/nixpkgs/nixos/modules/services/x11/xserver.nix index 8858559d8f27..35bd4dabb673 100644 --- a/nixpkgs/nixos/modules/services/x11/xserver.nix +++ b/nixpkgs/nixos/modules/services/x11/xserver.nix @@ -251,11 +251,10 @@ in videoDrivers = mkOption { type = types.listOf types.str; - # !!! We'd like "nv" here, but it segfaults the X server. - default = [ "radeon" "cirrus" "vesa" "modesetting" ]; + default = [ "amdgpu" "radeon" "nouveau" "modesetting" "fbdev" ]; example = [ - "ati_unfree" "amdgpu" "amdgpu-pro" - "nv" "nvidia" "nvidiaLegacy390" "nvidiaLegacy340" "nvidiaLegacy304" + "nvidia" "nvidiaLegacy390" "nvidiaLegacy340" "nvidiaLegacy304" + "amdgpu-pro" ]; # TODO(@oxij): think how to easily add the rest, like those nvidia things relatedPackages = concatLists |