diff options
Diffstat (limited to 'nixos/modules/services')
25 files changed, 1110 insertions, 398 deletions
diff --git a/nixos/modules/services/admin/meshcentral.nix b/nixos/modules/services/admin/meshcentral.nix new file mode 100644 index 00000000000..ae7b6edda7d --- /dev/null +++ b/nixos/modules/services/admin/meshcentral.nix @@ -0,0 +1,53 @@ +{ config, pkgs, lib, ... }: +let + cfg = config.services.meshcentral; + configFormat = pkgs.formats.json {}; + configFile = configFormat.generate "meshcentral-config.json" cfg.settings; +in with lib; { + options.services.meshcentral = with types; { + enable = mkEnableOption "MeshCentral computer management server"; + package = mkOption { + description = "MeshCentral package to use. Replacing this may be necessary to add dependencies for extra functionality."; + type = types.package; + default = pkgs.meshcentral; + defaultText = "pkgs.meshcentral"; + }; + settings = mkOption { + description = '' + Settings for MeshCentral. Refer to upstream documentation for details: + + <itemizedlist> + <listitem><para><link xlink:href="https://github.com/Ylianst/MeshCentral/blob/master/meshcentral-config-schema.json">JSON Schema definition</link></para></listitem> + <listitem><para><link xlink:href="https://github.com/Ylianst/MeshCentral/blob/master/sample-config.json">simple sample configuration</link></para></listitem> + <listitem><para><link xlink:href="https://github.com/Ylianst/MeshCentral/blob/master/sample-config-advanced.json">complex sample configuration</link></para></listitem> + <listitem><para><link xlink:href="https://www.meshcommander.com/meshcentral2">Old homepage) with documentation link</link></para></listitem> + </itemizedlist> + ''; + type = types.submodule { + freeformType = configFormat.type; + }; + example = { + settings = { + WANonly = true; + Cert = "meshcentral.example.com"; + TlsOffload = "10.0.0.2,fd42::2"; + Port = 4430; + }; + domains."".certUrl = "https://meshcentral.example.com/"; + }; + }; + }; + config = mkIf cfg.enable { + services.meshcentral.settings.settings.autoBackup.backupPath = lib.mkDefault "/var/lib/meshcentral/backups"; + systemd.services.meshcentral = { + wantedBy = ["multi-user.target"]; + serviceConfig = { + ExecStart = "${cfg.package}/bin/meshcentral --datapath /var/lib/meshcentral --configfile ${configFile}"; + DynamicUser = true; + StateDirectory = "meshcentral"; + CacheDirectory = "meshcentral"; + }; + }; + }; + meta.maintainers = [ maintainers.lheckemann ]; +} diff --git a/nixos/modules/services/cluster/kubernetes/addons/dns.nix b/nixos/modules/services/cluster/kubernetes/addons/dns.nix index 24d86628b21..8f937a13231 100644 --- a/nixos/modules/services/cluster/kubernetes/addons/dns.nix +++ b/nixos/modules/services/cluster/kubernetes/addons/dns.nix @@ -60,6 +60,45 @@ in { sha256 = "02r440xcdsgi137k5lmmvp0z5w5fmk8g9mysq5pnysq1wl8sj6mw"; }; }; + + corefile = mkOption { + description = '' + Custom coredns corefile configuration. + + See: <link xlink:href="https://coredns.io/manual/toc/#configuration"/>. + ''; + type = types.str; + default = '' + .:${toString ports.dns} { + errors + health :${toString ports.health} + kubernetes ${cfg.clusterDomain} in-addr.arpa ip6.arpa { + pods insecure + fallthrough in-addr.arpa ip6.arpa + } + prometheus :${toString ports.metrics} + forward . /etc/resolv.conf + cache 30 + loop + reload + loadbalance + }''; + defaultText = '' + .:${toString ports.dns} { + errors + health :${toString ports.health} + kubernetes ''${config.services.kubernetes.addons.dns.clusterDomain} in-addr.arpa ip6.arpa { + pods insecure + fallthrough in-addr.arpa ip6.arpa + } + prometheus :${toString ports.metrics} + forward . /etc/resolv.conf + cache 30 + loop + reload + loadbalance + }''; + }; }; config = mkIf cfg.enable { @@ -151,20 +190,7 @@ in { namespace = "kube-system"; }; data = { - Corefile = ".:${toString ports.dns} { - errors - health :${toString ports.health} - kubernetes ${cfg.clusterDomain} in-addr.arpa ip6.arpa { - pods insecure - fallthrough in-addr.arpa ip6.arpa - } - prometheus :${toString ports.metrics} - forward . /etc/resolv.conf - cache 30 - loop - reload - loadbalance - }"; + Corefile = cfg.corefile; }; }; diff --git a/nixos/modules/services/cluster/kubernetes/pki.nix b/nixos/modules/services/cluster/kubernetes/pki.nix index d9311d3e3a0..faf951d8157 100644 --- a/nixos/modules/services/cluster/kubernetes/pki.nix +++ b/nixos/modules/services/cluster/kubernetes/pki.nix @@ -189,7 +189,7 @@ in # manually paste it in place. Just symlink. # otherwise, create the target file, ready for users to insert the token - mkdir -p $(dirname ${certmgrAPITokenPath}) + mkdir -p "$(dirname "${certmgrAPITokenPath}")" if [ -f "${cfsslAPITokenPath}" ]; then ln -fs "${cfsslAPITokenPath}" "${certmgrAPITokenPath}" else diff --git a/nixos/modules/services/continuous-integration/gitlab-runner.nix b/nixos/modules/services/continuous-integration/gitlab-runner.nix index 2c6d9530a6b..15c37c2bc76 100644 --- a/nixos/modules/services/continuous-integration/gitlab-runner.nix +++ b/nixos/modules/services/continuous-integration/gitlab-runner.nix @@ -339,6 +339,9 @@ in <literal>CI_SERVER_URL=<CI server URL></literal> <literal>REGISTRATION_TOKEN=<registration secret></literal> + + WARNING: make sure to use quoted absolute path, + or it is going to be copied to Nix Store. ''; }; registrationFlags = mkOption { @@ -523,7 +526,10 @@ in }; }; config = mkIf cfg.enable { - warnings = optional (cfg.configFile != null) "services.gitlab-runner.`configFile` is deprecated, please use services.gitlab-runner.`services`."; + warnings = (mapAttrsToList + (n: v: "services.gitlab-runner.services.${n}.`registrationConfigFile` points to a file in Nix Store. You should use quoted absolute path to prevent this.") + (filterAttrs (n: v: isStorePath v.registrationConfigFile) cfg.services)) + ++ optional (cfg.configFile != null) "services.gitlab-runner.`configFile` is deprecated, please use services.gitlab-runner.`services`."; environment.systemPackages = [ cfg.package ]; systemd.services.gitlab-runner = { description = "Gitlab Runner"; diff --git a/nixos/modules/services/databases/redis.nix b/nixos/modules/services/databases/redis.nix index 9c0740f28c9..8873f6d00e0 100644 --- a/nixos/modules/services/databases/redis.nix +++ b/nixos/modules/services/databases/redis.nix @@ -272,7 +272,7 @@ in { } (mkIf (cfg.bind != null) { bind = cfg.bind; }) (mkIf (cfg.unixSocket != null) { unixsocket = cfg.unixSocket; unixsocketperm = "${toString cfg.unixSocketPerm}"; }) - (mkIf (cfg.slaveOf != null) { slaveof = "${cfg.slaveOf.ip} ${cfg.slaveOf.port}"; }) + (mkIf (cfg.slaveOf != null) { slaveof = "${cfg.slaveOf.ip} ${toString cfg.slaveOf.port}"; }) (mkIf (cfg.masterAuth != null) { masterauth = cfg.masterAuth; }) (mkIf (cfg.requirePass != null) { requirepass = cfg.requirePass; }) ]; diff --git a/nixos/modules/services/hardware/sane.nix b/nixos/modules/services/hardware/sane.nix index 8c1bde7b415..ccf726bd182 100644 --- a/nixos/modules/services/hardware/sane.nix +++ b/nixos/modules/services/hardware/sane.nix @@ -4,7 +4,10 @@ with lib; let - pkg = pkgs.sane-backends; + pkg = pkgs.sane-backends.override { + scanSnapDriversUnfree = config.hardware.sane.drivers.scanSnap.enable; + scanSnapDriversPackage = config.hardware.sane.drivers.scanSnap.package; + }; sanedConf = pkgs.writeTextFile { name = "saned.conf"; @@ -98,6 +101,28 @@ in ''; }; + hardware.sane.drivers.scanSnap.enable = mkOption { + type = types.bool; + default = false; + example = true; + description = '' + Whether to enable drivers for the Fujitsu ScanSnap scanners. + + The driver files are unfree and extracted from the Windows driver image. + ''; + }; + + hardware.sane.drivers.scanSnap.package = mkOption { + type = types.package; + default = pkgs.sane-drivers.epjitsu; + description = '' + Epjitsu driver package to use. Useful if you want to extract the driver files yourself. + + The process is described in the <literal>/etc/sane.d/epjitsu.conf</literal> file in + the <literal>sane-backends</literal> package. + ''; + }; + services.saned.enable = mkOption { type = types.bool; default = false; diff --git a/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix index b6c1ca3e61a..2f8e595cad0 100644 --- a/nixos/modules/services/misc/gitea.nix +++ b/nixos/modules/services/misc/gitea.nix @@ -523,19 +523,12 @@ in ''} # update all hooks' binary paths - HOOKS=$(find ${cfg.repositoryRoot} -mindepth 4 -maxdepth 6 -type f -wholename "*git/hooks/*") - if [ "$HOOKS" ] - then - sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gitea,${gitea}/bin/gitea,g' $HOOKS - sed -ri 's,/nix/store/[a-z0-9.-]+/bin/env,${pkgs.coreutils}/bin/env,g' $HOOKS - sed -ri 's,/nix/store/[a-z0-9.-]+/bin/bash,${pkgs.bash}/bin/bash,g' $HOOKS - sed -ri 's,/nix/store/[a-z0-9.-]+/bin/perl,${pkgs.perl}/bin/perl,g' $HOOKS - fi + ${gitea}/bin/gitea admin regenerate hooks # update command option in authorized_keys if [ -r ${cfg.stateDir}/.ssh/authorized_keys ] then - sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gitea,${gitea}/bin/gitea,g' ${cfg.stateDir}/.ssh/authorized_keys + ${gitea}/bin/gitea admin regenerate keys fi ''; diff --git a/nixos/modules/services/misc/libreddit.nix b/nixos/modules/services/misc/libreddit.nix new file mode 100644 index 00000000000..77b34a85620 --- /dev/null +++ b/nixos/modules/services/misc/libreddit.nix @@ -0,0 +1,66 @@ +{ config, lib, pkgs, ... }: + +with lib; + + let + cfg = config.services.libreddit; + + args = concatStringsSep " " ([ + "--port ${toString cfg.port}" + "--address ${cfg.address}" + ] ++ optional cfg.redirect "--redirect-https"); + +in +{ + options = { + services.libreddit = { + enable = mkEnableOption "Private front-end for Reddit"; + + address = mkOption { + default = "0.0.0.0"; + example = "127.0.0.1"; + type = types.str; + description = "The address to listen on"; + }; + + port = mkOption { + default = 8080; + example = 8000; + type = types.port; + description = "The port to listen on"; + }; + + redirect = mkOption { + type = types.bool; + default = false; + description = "Enable the redirecting to HTTPS"; + }; + + openFirewall = mkOption { + type = types.bool; + default = false; + description = "Open ports in the firewall for the libreddit web interface"; + }; + + }; + }; + + config = mkIf cfg.enable { + systemd.services.libreddit = { + description = "Private front-end for Reddit"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + serviceConfig = { + DynamicUser = true; + ExecStart = "${pkgs.libreddit}/bin/libreddit ${args}"; + AmbientCapabilities = lib.mkIf (cfg.port < 1024) [ "CAP_NET_BIND_SERVICE" ]; + Restart = "on-failure"; + RestartSec = "2s"; + }; + }; + + networking.firewall = mkIf cfg.openFirewall { + allowedTCPPorts = [ cfg.port ]; + }; + }; +} diff --git a/nixos/modules/services/misc/mx-puppet-discord.nix b/nixos/modules/services/misc/mx-puppet-discord.nix new file mode 100644 index 00000000000..11116f7c348 --- /dev/null +++ b/nixos/modules/services/misc/mx-puppet-discord.nix @@ -0,0 +1,120 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + dataDir = "/var/lib/mx-puppet-discord"; + registrationFile = "${dataDir}/discord-registration.yaml"; + cfg = config.services.mx-puppet-discord; + settingsFormat = pkgs.formats.json {}; + settingsFile = settingsFormat.generate "mx-puppet-discord-config.json" cfg.settings; + +in { + options = { + services.mx-puppet-discord = { + enable = mkEnableOption '' + mx-puppet-discord is a discord puppeting bridge for matrix. + It handles bridging private and group DMs, as well as Guilds (servers) + ''; + + settings = mkOption rec { + apply = recursiveUpdate default; + inherit (settingsFormat) type; + default = { + bridge.port = 8434; + presence = { + enabled = true; + interval = 500; + }; + provisioning.whitelist = [ ]; + relay.whitelist = [ ]; + + # variables are preceded by a colon. + namePatterns = { + user = ":name"; + userOverride = ":displayname"; + room = ":name"; + group = ":name"; + }; + + #defaults to sqlite but can be configured to use postgresql with + #connstring + database.filename = "${dataDir}/mx-puppet-discord/database.db"; + logging = { + console = "info"; + lineDateFormat = "MMM-D HH:mm:ss.SSS"; + }; + }; + example = literalExample '' + { + bridge = { + bindAddress = "localhost"; + domain = "example.com"; + homeserverUrl = "https://example.com"; + }; + + provisioning.whitelist = [ "@admin:example.com" ]; + relay.whitelist = [ "@.*:example.com" ]; + } + ''; + description = '' + <filename>config.yaml</filename> configuration as a Nix attribute set. + Configuration options should match those described in + <link xlink:href="https://github.com/matrix-discord/mx-puppet-discord/blob/master/sample.config.yaml"> + sample.config.yaml</link>. + ''; + }; + serviceDependencies = mkOption { + type = with types; listOf str; + default = optional config.services.matrix-synapse.enable "matrix-synapse.service"; + description = '' + List of Systemd services to require and wait for when starting the application service. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.services.mx-puppet-discord = { + description = '' + mx-puppet-discord is a discord puppeting bridge for matrix. + It handles bridging private and group DMs, as well as Guilds (servers). + ''; + + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ] ++ cfg.serviceDependencies; + after = [ "network-online.target" ] ++ cfg.serviceDependencies; + + preStart = '' + # generate the appservice's registration file if absent + if [ ! -f '${registrationFile}' ]; then + ${pkgs.mx-puppet-discord}/bin/mx-puppet-discord -r -c ${settingsFile} \ + -f ${registrationFile} + fi + ''; + + serviceConfig = { + Type = "simple"; + Restart = "always"; + + ProtectSystem = "strict"; + ProtectHome = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + + DynamicUser = true; + PrivateTmp = true; + WorkingDirectory = pkgs.mx-puppet-discord; + StateDirectory = baseNameOf dataDir; + UMask = 0027; + + ExecStart = '' + ${pkgs.mx-puppet-discord}/bin/mx-puppet-discord -c ${settingsFile} + ''; + }; + }; + }; + + meta.maintainers = with maintainers; [ govanify ]; +} diff --git a/nixos/modules/services/misc/nix-daemon.nix b/nixos/modules/services/misc/nix-daemon.nix index 133e96da0ec..70b27b7d3d0 100644 --- a/nixos/modules/services/misc/nix-daemon.nix +++ b/nixos/modules/services/misc/nix-daemon.nix @@ -458,7 +458,7 @@ in description = "The flake reference to which <option>from></option> is to be rewritten."; }; flake = mkOption { - type = types.unspecified; + type = types.nullOr types.attrs; default = null; example = literalExample "nixpkgs"; description = '' diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix index d648de6a414..9182c2f2ed8 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters.nix @@ -33,6 +33,7 @@ let "domain" "dovecot" "fritzbox" + "influxdb" "json" "jitsi" "kea" diff --git a/nixos/modules/services/monitoring/prometheus/exporters/influxdb.nix b/nixos/modules/services/monitoring/prometheus/exporters/influxdb.nix new file mode 100644 index 00000000000..ba45173e946 --- /dev/null +++ b/nixos/modules/services/monitoring/prometheus/exporters/influxdb.nix @@ -0,0 +1,34 @@ +{ config, lib, pkgs, options }: + +with lib; + +let + cfg = config.services.prometheus.exporters.influxdb; +in +{ + port = 9122; + extraOpts = { + sampleExpiry = mkOption { + type = types.str; + default = "5m"; + example = "10m"; + description = "How long a sample is valid for"; + }; + udpBindAddress = mkOption { + type = types.str; + default = ":9122"; + example = "192.0.2.1:9122"; + description = "Address on which to listen for udp packets"; + }; + }; + serviceOpts = { + serviceConfig = { + RuntimeDirectory = "prometheus-influxdb-exporter"; + ExecStart = '' + ${pkgs.prometheus-influxdb-exporter}/bin/influxdb_exporter \ + --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \ + --influxdb.sample-expiry ${cfg.sampleExpiry} ${concatStringsSep " " cfg.extraFlags} + ''; + }; + }; +} diff --git a/nixos/modules/services/network-filesystems/litestream/default.nix b/nixos/modules/services/network-filesystems/litestream/default.nix new file mode 100644 index 00000000000..f1806c5af0a --- /dev/null +++ b/nixos/modules/services/network-filesystems/litestream/default.nix @@ -0,0 +1,100 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.litestream; + settingsFormat = pkgs.formats.yaml {}; +in +{ + options.services.litestream = { + enable = mkEnableOption "litestream"; + + package = mkOption { + description = "Package to use."; + default = pkgs.litestream; + defaultText = "pkgs.litestream"; + type = types.package; + }; + + settings = mkOption { + description = '' + See the <link xlink:href="https://litestream.io/reference/config/">documentation</link>. + ''; + type = settingsFormat.type; + example = { + dbs = [ + { + path = "/var/lib/db1"; + replicas = [ + { + url = "s3://mybkt.litestream.io/db1"; + } + ]; + } + ]; + }; + }; + + environmentFile = mkOption { + type = types.nullOr types.path; + default = null; + example = "/run/secrets/litestream"; + 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. + + By default, Litestream will perform environment variable expansion + within the config file before reading it. Any references to ''$VAR or + ''${VAR} formatted variables will be replaced with their environment + variable values. If no value is set then it will be replaced with an + empty string. + + <programlisting> + # Content of the environment file + LITESTREAM_ACCESS_KEY_ID=AKIAxxxxxxxxxxxxxxxx + LITESTREAM_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/xxxxxxxxx + </programlisting> + + Note that this file needs to be available on the host on which + this exporter is running. + ''; + }; + }; + + config = mkIf cfg.enable { + environment.systemPackages = [ cfg.package ]; + environment.etc = { + "litestream.yml" = { + source = settingsFormat.generate "litestream-config.yaml" cfg.settings; + }; + }; + + systemd.services.litestream = { + description = "Litestream"; + wantedBy = [ "multi-user.target" ]; + after = [ "networking.target" ]; + serviceConfig = { + EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile; + ExecStart = "${cfg.package}/bin/litestream replicate"; + Restart = "always"; + User = "litestream"; + Group = "litestream"; + }; + }; + + users.users.litestream = { + description = "Litestream user"; + group = "litestream"; + isSystemUser = true; + }; + users.groups.litestream = {}; + }; + meta.doc = ./litestream.xml; +} diff --git a/nixos/modules/services/network-filesystems/litestream/litestream.xml b/nixos/modules/services/network-filesystems/litestream/litestream.xml new file mode 100644 index 00000000000..598f9be8cf6 --- /dev/null +++ b/nixos/modules/services/network-filesystems/litestream/litestream.xml @@ -0,0 +1,65 @@ +<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-litestream"> + <title>Litestream</title> + <para> + <link xlink:href="https://litestream.io/">Litestream</link> is a standalone streaming + replication tool for SQLite. + </para> + + <section xml:id="module-services-litestream-configuration"> + <title>Configuration</title> + + <para> + Litestream service is managed by a dedicated user named <literal>litestream</literal> + which needs permission to the database file. Here's an example config which gives + required permissions to access <link linkend="opt-services.grafana.database.path"> + grafana database</link>: +<programlisting> +{ pkgs, ... }: +{ + users.users.litestream.extraGroups = [ "grafana" ]; + + systemd.services.grafana.serviceConfig.ExecStartPost = "+" + pkgs.writeShellScript "grant-grafana-permissions" '' + timeout=10 + + while [ ! -f /var/lib/grafana/data/grafana.db ]; + do + if [ "$timeout" == 0 ]; then + echo "ERROR: Timeout while waiting for /var/lib/grafana/data/grafana.db." + exit 1 + fi + + sleep 1 + + ((timeout--)) + done + + find /var/lib/grafana -type d -exec chmod -v 775 {} \; + find /var/lib/grafana -type f -exec chmod -v 660 {} \; + ''; + + services.litestream = { + enable = true; + + environmentFile = "/run/secrets/litestream"; + + settings = { + dbs = [ + { + path = "/var/lib/grafana/data/grafana.db"; + replicas = [{ + url = "s3://mybkt.litestream.io/grafana"; + }]; + } + ]; + }; + }; +} +</programlisting> + </para> + </section> + +</chapter> diff --git a/nixos/modules/services/networking/bird.nix b/nixos/modules/services/networking/bird.nix index 1923afdf83f..c14adbda3c5 100644 --- a/nixos/modules/services/networking/bird.nix +++ b/nixos/modules/services/networking/bird.nix @@ -10,8 +10,8 @@ let birdBin = if variant == "bird6" then "bird6" else "bird"; birdc = if variant == "bird6" then "birdc6" else "birdc"; descr = - { bird = "1.9.x with IPv4 suport"; - bird6 = "1.9.x with IPv6 suport"; + { bird = "1.6.x with IPv4 support"; + bird6 = "1.6.x with IPv6 support"; bird2 = "2.x"; }.${variant}; in { diff --git a/nixos/modules/services/networking/firewall.nix b/nixos/modules/services/networking/firewall.nix index cdc3a172ea7..f982621e232 100644 --- a/nixos/modules/services/networking/firewall.nix +++ b/nixos/modules/services/networking/firewall.nix @@ -339,6 +339,8 @@ in description = '' Whether to log rejected or dropped incoming connections. + Note: The logs are found in the kernel logs, i.e. dmesg + or journalctl -k. ''; }; @@ -350,6 +352,8 @@ in Whether to log all rejected or dropped incoming packets. This tends to give a lot of log messages, so it's mostly useful for debugging. + Note: The logs are found in the kernel logs, i.e. dmesg + or journalctl -k. ''; }; diff --git a/nixos/modules/services/networking/kea.nix b/nixos/modules/services/networking/kea.nix index 72773b83a49..b11402204ae 100644 --- a/nixos/modules/services/networking/kea.nix +++ b/nixos/modules/services/networking/kea.nix @@ -238,6 +238,10 @@ in KEA_PIDFILE_DIR = "/run/kea"; }; + restartTriggers = [ + ctrlAgentConfig + ]; + serviceConfig = { ExecStart = "${package}/bin/kea-ctrl-agent -c /etc/kea/ctrl-agent.conf ${lib.escapeShellArgs cfg.dhcp4.extraArgs}"; KillMode = "process"; @@ -269,6 +273,10 @@ in KEA_PIDFILE_DIR = "/run/kea"; }; + restartTriggers = [ + dhcp4Config + ]; + serviceConfig = { ExecStart = "${package}/bin/kea-dhcp4 -c /etc/kea/dhcp4-server.conf ${lib.escapeShellArgs cfg.dhcp4.extraArgs}"; # Kea does not request capabilities by itself @@ -307,6 +315,10 @@ in KEA_PIDFILE_DIR = "/run/kea"; }; + restartTriggers = [ + dhcp6Config + ]; + serviceConfig = { ExecStart = "${package}/bin/kea-dhcp6 -c /etc/kea/dhcp6-server.conf ${lib.escapeShellArgs cfg.dhcp6.extraArgs}"; # Kea does not request capabilities by itself @@ -343,6 +355,10 @@ in KEA_PIDFILE_DIR = "/run/kea"; }; + restartTriggers = [ + dhcpDdnsConfig + ]; + serviceConfig = { ExecStart = "${package}/bin/kea-dhcp-ddns -c /etc/kea/dhcp-ddns.conf ${lib.escapeShellArgs cfg.dhcp-ddns.extraArgs}"; AmbientCapabilites = [ diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix index 064018057cd..c8861171dd6 100644 --- a/nixos/modules/services/networking/networkmanager.nix +++ b/nixos/modules/services/networking/networkmanager.nix @@ -6,7 +6,6 @@ let cfg = config.networking.networkmanager; basePackages = with pkgs; [ - crda modemmanager networkmanager networkmanager-fortisslvpn @@ -49,6 +48,7 @@ let rc-manager = if config.networking.resolvconf.enable then "resolvconf" else "unmanaged"; + firewall-backend = cfg.firewallBackend; }) (mkSection "keyfile" { unmanaged-devices = @@ -244,6 +244,15 @@ in { ''; }; + firewallBackend = mkOption { + type = types.enum [ "iptables" "nftables" "none" ]; + default = "iptables"; + description = '' + Which firewall backend should be used for configuring masquerading with shared mode. + If set to none, NetworkManager doesn't manage the configuration at all. + ''; + }; + logLevel = mkOption { type = types.enum [ "OFF" "ERR" "WARN" "INFO" "DEBUG" "TRACE" ]; default = "WARN"; @@ -404,6 +413,8 @@ in { } ]; + hardware.wirelessRegulatoryDatabase = true; + environment.etc = with pkgs; { "NetworkManager/NetworkManager.conf".source = configFile; diff --git a/nixos/modules/services/networking/nftables.nix b/nixos/modules/services/networking/nftables.nix index cb75142965e..72f37c32253 100644 --- a/nixos/modules/services/networking/nftables.nix +++ b/nixos/modules/services/networking/nftables.nix @@ -103,6 +103,7 @@ in }]; boot.blacklistedKernelModules = [ "ip_tables" ]; environment.systemPackages = [ pkgs.nftables ]; + networking.networkmanager.firewallBackend = mkDefault "nftables"; systemd.services.nftables = { description = "nftables firewall"; before = [ "network-pre.target" ]; diff --git a/nixos/modules/services/networking/syncthing.nix b/nixos/modules/services/networking/syncthing.nix index 28348c7893a..2e92fe51e90 100644 --- a/nixos/modules/services/networking/syncthing.nix +++ b/nixos/modules/services/networking/syncthing.nix @@ -5,15 +5,16 @@ with lib; let cfg = config.services.syncthing; defaultUser = "syncthing"; + defaultGroup = defaultUser; devices = mapAttrsToList (name: device: { deviceID = device.id; inherit (device) name addresses introducer; - }) cfg.declarative.devices; + }) cfg.devices; folders = mapAttrsToList ( _: folder: { inherit (folder) path id label type; - devices = map (device: { deviceId = cfg.declarative.devices.${device}.id; }) folder.devices; + devices = map (device: { deviceId = cfg.devices.${device}.id; }) folder.devices; rescanIntervalS = folder.rescanInterval; fsWatcherEnabled = folder.watch; fsWatcherDelayS = folder.watchDelay; @@ -23,215 +24,218 @@ let }) (filterAttrs ( _: folder: folder.enable - ) cfg.declarative.folders); - - # get the api key by parsing the config.xml - getApiKey = pkgs.writers.writeDash "getAPIKey" '' - ${pkgs.libxml2}/bin/xmllint \ - --xpath 'string(configuration/gui/apikey)'\ - ${cfg.configDir}/config.xml - ''; + ) cfg.folders); updateConfig = pkgs.writers.writeDash "merge-syncthing-config" '' set -efu - # wait for syncthing port to open - until ${pkgs.curl}/bin/curl -Ss ${cfg.guiAddress} -o /dev/null; do - sleep 1 - done - - API_KEY=$(${getApiKey}) - OLD_CFG=$(${pkgs.curl}/bin/curl -Ss \ - -H "X-API-Key: $API_KEY" \ - ${cfg.guiAddress}/rest/system/config) - - # generate the new config by merging with the nixos config options - NEW_CFG=$(echo "$OLD_CFG" | ${pkgs.jq}/bin/jq -s '.[] as $in | $in * { - "devices": (${builtins.toJSON devices}${optionalString (! cfg.declarative.overrideDevices) " + $in.devices"}), - "folders": (${builtins.toJSON folders}${optionalString (! cfg.declarative.overrideFolders) " + $in.folders"}) - }') - - # POST the new config to syncthing - echo "$NEW_CFG" | ${pkgs.curl}/bin/curl -Ss \ - -H "X-API-Key: $API_KEY" \ - ${cfg.guiAddress}/rest/system/config -d @- - - # restart syncthing after sending the new config - ${pkgs.curl}/bin/curl -Ss \ - -H "X-API-Key: $API_KEY" \ - -X POST \ - ${cfg.guiAddress}/rest/system/restart + + # get the api key by parsing the config.xml + while + ! api_key=$(${pkgs.libxml2}/bin/xmllint \ + --xpath 'string(configuration/gui/apikey)' \ + ${cfg.configDir}/config.xml) + do sleep 1; done + + curl() { + ${pkgs.curl}/bin/curl -sS -H "X-API-Key: $api_key" \ + --retry 1000 --retry-delay 1 --retry-all-errors \ + "$@" + } + + # query the old config + old_cfg=$(curl ${cfg.guiAddress}/rest/config) + + # generate the new config by merging with the NixOS config options + new_cfg=$(echo "$old_cfg" | ${pkgs.jq}/bin/jq -c '. * { + "devices": (${builtins.toJSON devices}${optionalString (! cfg.overrideDevices) " + .devices"}), + "folders": (${builtins.toJSON folders}${optionalString (! cfg.overrideFolders) " + .folders"}) + } * ${builtins.toJSON cfg.extraOptions}') + + # send the new config + curl -X PUT -d "$new_cfg" ${cfg.guiAddress}/rest/config + + # restart Syncthing if required + if curl ${cfg.guiAddress}/rest/config/restart-required | + ${pkgs.jq}/bin/jq -e .requiresRestart > /dev/null; then + curl -X POST ${cfg.guiAddress}/rest/system/restart + fi ''; in { ###### interface options = { services.syncthing = { - enable = mkEnableOption '' - Syncthing - the self-hosted open-source alternative - to Dropbox and Bittorrent Sync. Initial interface will be - available on http://127.0.0.1:8384/. - ''; - - declarative = { - cert = mkOption { - type = types.nullOr types.str; - default = null; - description = '' - Path to users cert.pem file, will be copied into the syncthing's - <literal>configDir</literal> - ''; - }; + enable = mkEnableOption + "Syncthing, a self-hosted open-source alternative to Dropbox and Bittorrent Sync"; - key = mkOption { - type = types.nullOr types.str; - default = null; - description = '' - Path to users key.pem file, will be copied into the syncthing's - <literal>configDir</literal> - ''; - }; + cert = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Path to the <literal>cert.pem</literal> file, which will be copied into Syncthing's + <link linkend="opt-services.syncthing.configDir">configDir</link>. + ''; + }; - overrideDevices = mkOption { - type = types.bool; - default = true; - description = '' - Whether to delete the devices which are not configured via the - <literal>declarative.devices</literal> option. - If set to false, devices added via the webinterface will - persist but will have to be deleted manually. - ''; + key = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Path to the <literal>key.pem</literal> file, which will be copied into Syncthing's + <link linkend="opt-services.syncthing.configDir">configDir</link>. + ''; + }; + + overrideDevices = mkOption { + type = types.bool; + default = true; + description = '' + Whether to delete the devices which are not configured via the + <link linkend="opt-services.syncthing.devices">devices</link> option. + If set to <literal>false</literal>, devices added via the web + interface will persist and will have to be deleted manually. + ''; + }; + + devices = mkOption { + default = {}; + description = '' + Peers/devices which Syncthing should communicate with. + + Note that you can still add devices manually, but those changes + will be reverted on restart if <link linkend="opt-services.syncthing.overrideDevices">overrideDevices</link> + is enabled. + ''; + example = { + bigbox = { + id = "7CFNTQM-IMTJBHJ-3UWRDIU-ZGQJFR6-VCXZ3NB-XUH3KZO-N52ITXR-LAIYUAU"; + addresses = [ "tcp://192.168.0.10:51820" ]; + }; }; + type = types.attrsOf (types.submodule ({ name, ... }: { + options = { + + name = mkOption { + type = types.str; + default = name; + description = '' + The name of the device. + ''; + }; - devices = mkOption { - default = {}; - description = '' - Peers/devices which syncthing should communicate with. - ''; - example = { - bigbox = { - id = "7CFNTQM-IMTJBHJ-3UWRDIU-ZGQJFR6-VCXZ3NB-XUH3KZO-N52ITXR-LAIYUAU"; - addresses = [ "tcp://192.168.0.10:51820" ]; + addresses = mkOption { + type = types.listOf types.str; + default = []; + description = '' + The addresses used to connect to the device. + If this is left empty, dynamic configuration is attempted. + ''; + }; + + id = mkOption { + type = types.str; + description = '' + The device ID. See <link xlink:href="https://docs.syncthing.net/dev/device-ids.html"/>. + ''; }; + + introducer = mkOption { + type = types.bool; + default = false; + description = '' + Whether the device should act as an introducer and be allowed + to add folders on this computer. + See <link xlink:href="https://docs.syncthing.net/users/introducer.html"/>. + ''; + }; + }; - type = types.attrsOf (types.submodule ({ name, ... }: { - options = { - - name = mkOption { - type = types.str; - default = name; - description = '' - Name of the device - ''; - }; - - addresses = mkOption { - type = types.listOf types.str; - default = []; - description = '' - The addresses used to connect to the device. - If this is let empty, dynamic configuration is attempted - ''; - }; - - id = mkOption { - type = types.str; - description = '' - The id of the other peer, this is mandatory. It's documented at - https://docs.syncthing.net/dev/device-ids.html - ''; - }; - - introducer = mkOption { - type = types.bool; - default = false; - description = '' - If the device should act as an introducer and be allowed - to add folders on this computer. - ''; - }; + })); + }; + + overrideFolders = mkOption { + type = types.bool; + default = true; + description = '' + Whether to delete the folders which are not configured via the + <link linkend="opt-services.syncthing.folders">folders</link> option. + If set to <literal>false</literal>, folders added via the web + interface will persist and will have to be deleted manually. + ''; + }; + folders = mkOption { + default = {}; + description = '' + Folders which should be shared by Syncthing. + + Note that you can still add devices manually, but those changes + will be reverted on restart if <link linkend="opt-services.syncthing.overrideDevices">overrideDevices</link> + is enabled. + ''; + example = literalExample '' + { + "/home/user/sync" = { + id = "syncme"; + devices = [ "bigbox" ]; + }; + } + ''; + type = types.attrsOf (types.submodule ({ name, ... }: { + options = { + + enable = mkOption { + type = types.bool; + default = true; + description = '' + Whether to share this folder. + This option is useful when you want to define all folders + in one place, but not every machine should share all folders. + ''; }; - })); - }; - overrideFolders = mkOption { - type = types.bool; - default = true; - description = '' - Whether to delete the folders which are not configured via the - <literal>declarative.folders</literal> option. - If set to false, folders added via the webinterface will persist - but will have to be deleted manually. - ''; - }; + path = mkOption { + type = types.str; + default = name; + description = '' + The path to the folder which should be shared. + ''; + }; - folders = mkOption { - default = {}; - description = '' - folders which should be shared by syncthing. - ''; - example = literalExample '' - { - "/home/user/sync" = { - id = "syncme"; - devices = [ "bigbox" ]; - }; - } - ''; - type = types.attrsOf (types.submodule ({ name, ... }: { - options = { - - enable = mkOption { - type = types.bool; - default = true; - description = '' - share this folder. - This option is useful when you want to define all folders - in one place, but not every machine should share all folders. - ''; - }; - - path = mkOption { - type = types.str; - default = name; - description = '' - The path to the folder which should be shared. - ''; - }; - - id = mkOption { - type = types.str; - default = name; - description = '' - The id of the folder. Must be the same on all devices. - ''; - }; - - label = mkOption { - type = types.str; - default = name; - description = '' - The label of the folder. - ''; - }; - - devices = mkOption { - type = types.listOf types.str; - default = []; - description = '' - The devices this folder should be shared with. Must be defined - in the <literal>declarative.devices</literal> attribute. - ''; - }; - - versioning = mkOption { - default = null; - description = '' - How to keep changed/deleted files with syncthing. - There are 4 different types of versioning with different parameters. - See https://docs.syncthing.net/users/versioning.html - ''; - example = [ + id = mkOption { + type = types.str; + default = name; + description = '' + The ID of the folder. Must be the same on all devices. + ''; + }; + + label = mkOption { + type = types.str; + default = name; + description = '' + The label of the folder. + ''; + }; + + devices = mkOption { + type = types.listOf types.str; + default = []; + description = '' + The devices this folder should be shared with. Each device must + be defined in the <link linkend="opt-services.syncthing.devices">devices</link> option. + ''; + }; + + versioning = mkOption { + default = null; + description = '' + How to keep changed/deleted files with Syncthing. + There are 4 different types of versioning with different parameters. + See <link xlink:href="https://docs.syncthing.net/users/versioning.html"/>. + ''; + example = literalExample '' + [ { versioning = { type = "simple"; @@ -257,87 +261,99 @@ in { { versioning = { type = "external"; - params.versionsPath = pkgs.writers.writeBash "backup" '' + params.versionsPath = pkgs.writers.writeBash "backup" ''' folderpath="$1" filepath="$2" rm -rf "$folderpath/$filepath" - ''; + '''; }; } - ]; - type = with types; nullOr (submodule { - options = { - type = mkOption { - type = enum [ "external" "simple" "staggered" "trashcan" ]; - description = '' - Type of versioning. - See https://docs.syncthing.net/users/versioning.html - ''; - }; - params = mkOption { - type = attrsOf (either str path); - description = '' - Parameters for versioning. Structure depends on versioning.type. - See https://docs.syncthing.net/users/versioning.html - ''; - }; + ] + ''; + type = with types; nullOr (submodule { + options = { + type = mkOption { + type = enum [ "external" "simple" "staggered" "trashcan" ]; + description = '' + The type of versioning. + See <link xlink:href="https://docs.syncthing.net/users/versioning.html"/>. + ''; + }; + params = mkOption { + type = attrsOf (either str path); + description = '' + The parameters for versioning. Structure depends on + <link linkend="opt-services.syncthing.folders._name_.versioning.type">versioning.type</link>. + See <link xlink:href="https://docs.syncthing.net/users/versioning.html"/>. + ''; }; - }); - }; - - rescanInterval = mkOption { - type = types.int; - default = 3600; - description = '' - How often the folders should be rescaned for changes. - ''; - }; - - type = mkOption { - type = types.enum [ "sendreceive" "sendonly" "receiveonly" ]; - default = "sendreceive"; - description = '' - Whether to send only changes from this folder, only receive them - or propagate both. - ''; - }; - - watch = mkOption { - type = types.bool; - default = true; - description = '' - Whether the folder should be watched for changes by inotify. - ''; - }; - - watchDelay = mkOption { - type = types.int; - default = 10; - description = '' - The delay after an inotify event is triggered. - ''; - }; - - ignorePerms = mkOption { - type = types.bool; - default = true; - description = '' - Whether to propagate permission changes. - ''; - }; - - ignoreDelete = mkOption { - type = types.bool; - default = false; - description = '' - Whether to delete files in destination. See <link - xlink:href="https://docs.syncthing.net/advanced/folder-ignoredelete.html"> - upstream's docs</link>. - ''; - }; + }; + }); + }; + + rescanInterval = mkOption { + type = types.int; + default = 3600; + description = '' + How often the folder should be rescanned for changes. + ''; + }; + + type = mkOption { + type = types.enum [ "sendreceive" "sendonly" "receiveonly" ]; + default = "sendreceive"; + description = '' + Whether to only send changes for this folder, only receive them + or both. + ''; + }; + + watch = mkOption { + type = types.bool; + default = true; + description = '' + Whether the folder should be watched for changes by inotify. + ''; + }; + + watchDelay = mkOption { + type = types.int; + default = 10; + description = '' + The delay after an inotify event is triggered. + ''; + }; + + ignorePerms = mkOption { + type = types.bool; + default = true; + description = '' + Whether to ignore permission changes. + ''; + }; + ignoreDelete = mkOption { + type = types.bool; + default = false; + description = '' + Whether to skip deleting files that are deleted by peers. + See <link xlink:href="https://docs.syncthing.net/advanced/folder-ignoredelete.html"/>. + ''; }; - })); + }; + })); + }; + + extraOptions = mkOption { + type = types.addCheck (pkgs.formats.json {}).type isAttrs; + default = {}; + description = '' + Extra configuration options for Syncthing. + See <link xlink:href="https://docs.syncthing.net/users/config.html"/>. + ''; + example = { + options.localAnnounceEnabled = false; + gui.theme = "black"; }; }; @@ -345,31 +361,35 @@ in { type = types.str; default = "127.0.0.1:8384"; description = '' - Address to serve the GUI. + The address to serve the web interface at. ''; }; systemService = mkOption { type = types.bool; default = true; - description = "Auto launch Syncthing as a system service."; + description = '' + Whether to auto-launch Syncthing as a system service. + ''; }; user = mkOption { type = types.str; default = defaultUser; + example = "yourUser"; description = '' - Syncthing will be run under this user (user will be created if it doesn't exist. - This can be your user name). + The user to run Syncthing as. + By default, a user named <literal>${defaultUser}</literal> will be created. ''; }; group = mkOption { type = types.str; - default = defaultUser; + default = defaultGroup; + example = "yourGroup"; description = '' - Syncthing will be run under this group (group will not be created if it doesn't exist. - This can be your user name). + The group to run Syncthing under. + By default, a group named <literal>${defaultGroup}</literal> will be created. ''; }; @@ -378,63 +398,67 @@ in { default = null; example = "socks5://address.com:1234"; description = '' - Overwrites all_proxy environment variable for the syncthing process to - the given value. This is normaly used to let relay client connect - through SOCKS5 proxy server. + Overwrites the all_proxy environment variable for the Syncthing process to + the given value. This is normally used to let Syncthing connect + through a SOCKS5 proxy server. + See <link xlink:href="https://docs.syncthing.net/users/proxying.html"/>. ''; }; dataDir = mkOption { type = types.path; default = "/var/lib/syncthing"; + example = "/home/yourUser"; description = '' - Path where synced directories will exist. + The path where synchronised directories will exist. ''; }; - configDir = mkOption { + configDir = let + cond = versionAtLeast config.system.stateVersion "19.03"; + in mkOption { type = types.path; description = '' - Path where the settings and keys will exist. + The path where the settings and keys will exist. ''; - default = - let - nixos = config.system.stateVersion; - cond = versionAtLeast nixos "19.03"; - in cfg.dataDir + (optionalString cond "/.config/syncthing"); + default = cfg.dataDir + (optionalString cond "/.config/syncthing"); + defaultText = literalExample "dataDir${optionalString cond " + \"/.config/syncthing\""}"; }; openDefaultPorts = mkOption { type = types.bool; default = false; - example = literalExample "true"; + example = true; description = '' - Open the default ports in the firewall: - - TCP 22000 for transfers - - UDP 21027 for discovery - If multiple users are running syncthing on this machine, you will need to manually open a set of ports for each instance and leave this disabled. - Alternatively, if are running only a single instance on this machine using the default ports, enable this. + Whether to open the default ports in the firewall: TCP 22000 for transfers + and UDP 21027 for discovery. + + If multiple users are running Syncthing on this machine, you will need + to manually open a set of ports for each instance and leave this disabled. + Alternatively, if you are running only a single instance on this machine + using the default ports, enable this. ''; }; package = mkOption { type = types.package; default = pkgs.syncthing; - defaultText = "pkgs.syncthing"; - example = literalExample "pkgs.syncthing"; + defaultText = literalExample "pkgs.syncthing"; description = '' - Syncthing package to use. + The Syncthing package to use. ''; }; }; }; imports = [ - (mkRemovedOptionModule ["services" "syncthing" "useInotify"] '' - This option was removed because syncthing now has the inotify functionality included under the name "fswatcher". - It can be enabled on a per-folder basis through the webinterface. + (mkRemovedOptionModule [ "services" "syncthing" "useInotify" ] '' + This option was removed because Syncthing now has the inotify functionality included under the name "fswatcher". + It can be enabled on a per-folder basis through the web interface. '') - ]; + ] ++ map (o: + mkRenamedOptionModule [ "services" "syncthing" "declarative" o ] [ "services" "syncthing" o ] + ) [ "cert" "key" "devices" "folders" "overrideDevices" "overrideFolders" "extraOptions"]; ###### implementation @@ -457,8 +481,8 @@ in { }; }; - users.groups = mkIf (cfg.systemService && cfg.group == defaultUser) { - ${defaultUser}.gid = + users.groups = mkIf (cfg.systemService && cfg.group == defaultGroup) { + ${defaultGroup}.gid = config.ids.gids.syncthing; }; @@ -478,14 +502,14 @@ in { RestartForceExitStatus="3 4"; User = cfg.user; Group = cfg.group; - ExecStartPre = mkIf (cfg.declarative.cert != null || cfg.declarative.key != null) + ExecStartPre = mkIf (cfg.cert != null || cfg.key != null) "+${pkgs.writers.writeBash "syncthing-copy-keys" '' install -dm700 -o ${cfg.user} -g ${cfg.group} ${cfg.configDir} - ${optionalString (cfg.declarative.cert != null) '' - install -Dm400 -o ${cfg.user} -g ${cfg.group} ${toString cfg.declarative.cert} ${cfg.configDir}/cert.pem + ${optionalString (cfg.cert != null) '' + install -Dm400 -o ${cfg.user} -g ${cfg.group} ${toString cfg.cert} ${cfg.configDir}/cert.pem ''} - ${optionalString (cfg.declarative.key != null) '' - install -Dm400 -o ${cfg.user} -g ${cfg.group} ${toString cfg.declarative.key} ${cfg.configDir}/key.pem + ${optionalString (cfg.key != null) '' + install -Dm400 -o ${cfg.user} -g ${cfg.group} ${toString cfg.key} ${cfg.configDir}/key.pem ''} ''}" ; @@ -516,8 +540,10 @@ in { }; }; syncthing-init = mkIf ( - cfg.declarative.devices != {} || cfg.declarative.folders != {} + cfg.devices != {} || cfg.folders != {} || cfg.extraOptions != {} ) { + description = "Syncthing configuration updater"; + requisite = [ "syncthing.service" ]; after = [ "syncthing.service" ]; wantedBy = [ "multi-user.target" ]; diff --git a/nixos/modules/services/networking/wpa_supplicant.nix b/nixos/modules/services/networking/wpa_supplicant.nix index c0a4ce40760..494d21cc867 100644 --- a/nixos/modules/services/networking/wpa_supplicant.nix +++ b/nixos/modules/services/networking/wpa_supplicant.nix @@ -241,7 +241,8 @@ in { environment.systemPackages = [ package ]; services.dbus.packages = [ package ]; - services.udev.packages = [ pkgs.crda ]; + + hardware.wirelessRegulatoryDatabase = true; # FIXME: start a separate wpa_supplicant instance per interface. systemd.services.wpa_supplicant = let diff --git a/nixos/modules/services/system/uptimed.nix b/nixos/modules/services/system/uptimed.nix index 1e256c51408..67a03876e19 100644 --- a/nixos/modules/services/system/uptimed.nix +++ b/nixos/modules/services/system/uptimed.nix @@ -4,7 +4,7 @@ with lib; let cfg = config.services.uptimed; - stateDir = "/var/spool/uptimed"; + stateDir = "/var/lib/uptimed"; in { options = { @@ -21,12 +21,16 @@ in }; config = mkIf cfg.enable { + + environment.systemPackages = [ pkgs.uptimed ]; + users.users.uptimed = { description = "Uptimed daemon user"; home = stateDir; - createHome = true; uid = config.ids.uids.uptimed; + group = "uptimed"; }; + users.groups.uptimed = {}; systemd.services.uptimed = { unitConfig.Documentation = "man:uptimed(8) man:uprecords(1)"; @@ -41,7 +45,7 @@ in PrivateTmp = "yes"; PrivateNetwork = "yes"; NoNewPrivileges = "yes"; - ReadWriteDirectories = stateDir; + StateDirectory = [ "uptimed" ]; InaccessibleDirectories = "/home"; ExecStart = "${pkgs.uptimed}/sbin/uptimed -f -p ${stateDir}/pid"; }; diff --git a/nixos/modules/services/web-apps/node-red.nix b/nixos/modules/services/web-apps/node-red.nix new file mode 100644 index 00000000000..16cfb29cf57 --- /dev/null +++ b/nixos/modules/services/web-apps/node-red.nix @@ -0,0 +1,148 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.node-red; + defaultUser = "node-red"; + finalPackage = if cfg.withNpmAndGcc then node-red_withNpmAndGcc else cfg.package; + node-red_withNpmAndGcc = pkgs.runCommandNoCC "node-red" { + nativeBuildInputs = [ pkgs.makeWrapper ]; + } + '' + mkdir -p $out/bin + makeWrapper ${pkgs.nodePackages.node-red}/bin/node-red $out/bin/node-red \ + --set PATH '${lib.makeBinPath [ pkgs.nodePackages.npm pkgs.gcc ]}:$PATH' \ + ''; +in +{ + options.services.node-red = { + enable = mkEnableOption "the Node-RED service"; + + package = mkOption { + default = pkgs.nodePackages.node-red; + defaultText = "pkgs.nodePackages.node-red"; + type = types.package; + description = "Node-RED package to use."; + }; + + openFirewall = mkOption { + type = types.bool; + default = false; + description = '' + Open ports in the firewall for the server. + ''; + }; + + withNpmAndGcc = mkOption { + type = types.bool; + default = false; + description = '' + Give Node-RED access to NPM and GCC at runtime, so 'Nodes' can be + downloaded and managed imperatively via the 'Palette Manager'. + ''; + }; + + configFile = mkOption { + type = types.path; + default = "${cfg.package}/lib/node_modules/node-red/settings.js"; + defaultText = "\${cfg.package}/lib/node_modules/node-red/settings.js"; + description = '' + Path to the JavaScript configuration file. + See <link + xlink:href="https://github.com/node-red/node-red/blob/master/packages/node_modules/node-red/settings.js"/> + for a configuration example. + ''; + }; + + port = mkOption { + type = types.port; + default = 1880; + description = "Listening port."; + }; + + user = mkOption { + type = types.str; + default = defaultUser; + description = '' + User under which Node-RED runs.If left as the default value this user + will automatically be created on system activation, otherwise the + sysadmin is responsible for ensuring the user exists. + ''; + }; + + group = mkOption { + type = types.str; + default = defaultUser; + description = '' + Group under which Node-RED runs.If left as the default value this group + will automatically be created on system activation, otherwise the + sysadmin is responsible for ensuring the group exists. + ''; + }; + + userDir = mkOption { + type = types.path; + default = "/var/lib/node-red"; + description = '' + The directory to store all user data, such as flow and credential files and all library data. If left + as the default value this directory will automatically be created before the node-red service starts, + otherwise the sysadmin is responsible for ensuring the directory exists with appropriate ownership + and permissions. + ''; + }; + + safe = mkOption { + type = types.bool; + default = false; + description = "Whether to launch Node-RED in --safe mode."; + }; + + define = mkOption { + type = types.attrs; + default = {}; + description = "List of settings.js overrides to pass via -D to Node-RED."; + example = literalExample '' + { + "logging.console.level" = "trace"; + } + ''; + }; + }; + + config = mkIf cfg.enable { + users.users = optionalAttrs (cfg.user == defaultUser) { + ${defaultUser} = { + isSystemUser = true; + }; + }; + + users.groups = optionalAttrs (cfg.group == defaultUser) { + ${defaultUser} = { }; + }; + + networking.firewall = mkIf cfg.openFirewall { + allowedTCPPorts = [ cfg.port ]; + }; + + systemd.services.node-red = { + description = "Node-RED Service"; + wantedBy = [ "multi-user.target" ]; + after = [ "networking.target" ]; + environment = { + HOME = cfg.userDir; + }; + serviceConfig = mkMerge [ + { + User = cfg.user; + Group = cfg.group; + ExecStart = "${finalPackage}/bin/node-red ${pkgs.lib.optionalString cfg.safe "--safe"} --settings ${cfg.configFile} --port ${toString cfg.port} --userDir ${cfg.userDir} ${concatStringsSep " " (mapAttrsToList (name: value: "-D ${name}=${value}") cfg.define)}"; + PrivateTmp = true; + Restart = "always"; + WorkingDirectory = cfg.userDir; + } + (mkIf (cfg.userDir == "/var/lib/node-red") { StateDirectory = "node-red"; }) + ]; + }; + }; +} diff --git a/nixos/modules/services/web-apps/tt-rss.nix b/nixos/modules/services/web-apps/tt-rss.nix index b78487cc928..ed13845915c 100644 --- a/nixos/modules/services/web-apps/tt-rss.nix +++ b/nixos/modules/services/web-apps/tt-rss.nix @@ -19,82 +19,84 @@ let mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql"; pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql"; - tt-rss-config = pkgs.writeText "config.php" '' + tt-rss-config = let + password = + if (cfg.database.password != null) then + "${(escape ["'" "\\"] cfg.database.password)}" + else if (cfg.database.passwordFile != null) then + "file_get_contents('${cfg.database.passwordFile}'" + else + "" + ; + in pkgs.writeText "config.php" '' <?php + putenv('TTRSS_PHP_EXECUTABLE=${pkgs.php}/bin/php'); - define('PHP_EXECUTABLE', '${pkgs.php}/bin/php'); + putenv('TTRSS_LOCK_DIRECTORY=${lockDir}'); + putenv('TTRSS_CACHE_DIR=${cacheDir}'); + putenv('TTRSS_ICONS_DIR=${feedIconsDir}'); + putenv('TTRSS_ICONS_URL=${feedIconsDir}'); + putenv('TTRSS_SELF_URL_PATH=${cfg.selfUrlPath}'); - define('LOCK_DIRECTORY', '${lockDir}'); - define('CACHE_DIR', '${cacheDir}'); - define('ICONS_DIR', '${feedIconsDir}'); - define('ICONS_URL', '${feedIconsDir}'); - define('SELF_URL_PATH', '${cfg.selfUrlPath}'); + putenv('TTRSS_MYSQL_CHARSET=UTF8'); - define('MYSQL_CHARSET', 'UTF8'); + putenv('TTRSS_DB_TYPE=${cfg.database.type}'); + putenv('TTRSS_DB_HOST=${optionalString (cfg.database.host != null) cfg.database.host}'); + putenv('TTRSS_DB_USER=${cfg.database.user}'); + putenv('TTRSS_DB_NAME=${cfg.database.name}'); + putenv('TTRSS_DB_PASS=${password}'); + putenv('TTRSS_DB_PORT=${toString dbPort}'); - define('DB_TYPE', '${cfg.database.type}'); - define('DB_HOST', '${optionalString (cfg.database.host != null) cfg.database.host}'); - define('DB_USER', '${cfg.database.user}'); - define('DB_NAME', '${cfg.database.name}'); - define('DB_PASS', ${ - if (cfg.database.password != null) then - "'${(escape ["'" "\\"] cfg.database.password)}'" - else if (cfg.database.passwordFile != null) then - "file_get_contents('${cfg.database.passwordFile}')" - else - "''" - }); - define('DB_PORT', '${toString dbPort}'); + putenv('TTRSS_AUTH_AUTO_CREATE=${boolToString cfg.auth.autoCreate}'); + putenv('TTRSS_AUTH_AUTO_LOGIN=${boolToString cfg.auth.autoLogin}'); - define('AUTH_AUTO_CREATE', ${boolToString cfg.auth.autoCreate}); - define('AUTH_AUTO_LOGIN', ${boolToString cfg.auth.autoLogin}); + putenv('TTRSS_FEED_CRYPT_KEY=${escape ["'" "\\"] cfg.feedCryptKey}'); - define('FEED_CRYPT_KEY', '${escape ["'" "\\"] cfg.feedCryptKey}'); + putenv('TTRSS_SINGLE_USER_MODE=${boolToString cfg.singleUserMode}'); - define('SINGLE_USER_MODE', ${boolToString cfg.singleUserMode}); + putenv('TTRSS_SIMPLE_UPDATE_MODE=${boolToString cfg.simpleUpdateMode}'); - define('SIMPLE_UPDATE_MODE', ${boolToString cfg.simpleUpdateMode}); + # Never check for updates - the running version of the code should + # be controlled entirely by the version of TT-RSS active in the + # current Nix profile. If TT-RSS updates itself to a version + # requiring a database schema upgrade, and then the SystemD + # tt-rss.service is restarted, the old code copied from the Nix + # store will overwrite the updated version, causing the code to + # detect the need for a schema "upgrade" (since the schema version + # in the database is different than in the code), but the update + # schema operation in TT-RSS will do nothing because the schema + # version in the database is newer than that in the code. + putenv('TTRSS_CHECK_FOR_UPDATES=false'); - // Never check for updates - the running version of the code should be - // controlled entirely by the version of TT-RSS active in the current Nix - // profile. If TT-RSS updates itself to a version requiring a database - // schema upgrade, and then the SystemD tt-rss.service is restarted, the - // old code copied from the Nix store will overwrite the updated version, - // causing the code to detect the need for a schema "upgrade" (since the - // schema version in the database is different than in the code), but the - // update schema operation in TT-RSS will do nothing because the schema - // version in the database is newer than that in the code. - define('CHECK_FOR_UPDATES', false); + putenv('TTRSS_FORCE_ARTICLE_PURGE=${toString cfg.forceArticlePurge}'); + putenv('TTRSS_SESSION_COOKIE_LIFETIME=${toString cfg.sessionCookieLifetime}'); + putenv('TTRSS_ENABLE_GZIP_OUTPUT=${boolToString cfg.enableGZipOutput}'); - define('FORCE_ARTICLE_PURGE', ${toString cfg.forceArticlePurge}); - define('SESSION_COOKIE_LIFETIME', ${toString cfg.sessionCookieLifetime}); - define('ENABLE_GZIP_OUTPUT', ${boolToString cfg.enableGZipOutput}); + putenv('TTRSS_PLUGINS=${builtins.concatStringsSep "," cfg.plugins}'); - define('PLUGINS', '${builtins.concatStringsSep "," cfg.plugins}'); + putenv('TTRSS_LOG_DESTINATION=${cfg.logDestination}'); + putenv('TTRSS_CONFIG_VERSION=${toString configVersion}'); - define('LOG_DESTINATION', '${cfg.logDestination}'); - define('CONFIG_VERSION', ${toString configVersion}); + putenv('TTRSS_PUBSUBHUBBUB_ENABLED=${boolToString cfg.pubSubHubbub.enable}'); + putenv('TTRSS_PUBSUBHUBBUB_HUB=${cfg.pubSubHubbub.hub}'); - define('PUBSUBHUBBUB_ENABLED', ${boolToString cfg.pubSubHubbub.enable}); - define('PUBSUBHUBBUB_HUB', '${cfg.pubSubHubbub.hub}'); + putenv('TTRSS_SPHINX_SERVER=${cfg.sphinx.server}'); + putenv('TTRSS_SPHINX_INDEX=${builtins.concatStringsSep "," cfg.sphinx.index}'); - define('SPHINX_SERVER', '${cfg.sphinx.server}'); - define('SPHINX_INDEX', '${builtins.concatStringsSep "," cfg.sphinx.index}'); + putenv('TTRSS_ENABLE_REGISTRATION=${boolToString cfg.registration.enable}'); + putenv('TTRSS_REG_NOTIFY_ADDRESS=${cfg.registration.notifyAddress}'); + putenv('TTRSS_REG_MAX_USERS=${toString cfg.registration.maxUsers}'); - define('ENABLE_REGISTRATION', ${boolToString cfg.registration.enable}); - define('REG_NOTIFY_ADDRESS', '${cfg.registration.notifyAddress}'); - define('REG_MAX_USERS', ${toString cfg.registration.maxUsers}); + putenv('TTRSS_SMTP_SERVER=${cfg.email.server}'); + putenv('TTRSS_SMTP_LOGIN=${cfg.email.login}'); + putenv('TTRSS_SMTP_PASSWORD=${escape ["'" "\\"] cfg.email.password}'); + putenv('TTRSS_SMTP_SECURE=${cfg.email.security}'); - define('SMTP_SERVER', '${cfg.email.server}'); - define('SMTP_LOGIN', '${cfg.email.login}'); - define('SMTP_PASSWORD', '${escape ["'" "\\"] cfg.email.password}'); - define('SMTP_SECURE', '${cfg.email.security}'); - - define('SMTP_FROM_NAME', '${escape ["'" "\\"] cfg.email.fromName}'); - define('SMTP_FROM_ADDRESS', '${escape ["'" "\\"] cfg.email.fromAddress}'); - define('DIGEST_SUBJECT', '${escape ["'" "\\"] cfg.email.digestSubject}'); + putenv('TTRSS_SMTP_FROM_NAME=${escape ["'" "\\"] cfg.email.fromName}'); + putenv('TTRSS_SMTP_FROM_ADDRESS=${escape ["'" "\\"] cfg.email.fromAddress}'); + putenv('TTRSS_DIGEST_SUBJECT=${escape ["'" "\\"] cfg.email.digestSubject}'); ${cfg.extraConfig} ''; @@ -564,9 +566,12 @@ let "Z '${cfg.root}' 0755 ${cfg.user} tt_rss - -" ]; - systemd.services.tt-rss = - { + systemd.services = { + phpfpm-tt-rss = mkIf (cfg.pool == "${poolName}") { + restartTriggers = [ tt-rss-config pkgs.tt-rss ]; + }; + tt-rss = { description = "Tiny Tiny RSS feeds update daemon"; preStart = let @@ -604,6 +609,9 @@ let ''} ln -sf "${tt-rss-config}" "${cfg.root}/config.php" chmod -R 755 "${cfg.root}" + chmod -R ug+rwX "${cfg.root}/${lockDir}" + chmod -R ug+rwX "${cfg.root}/${cacheDir}" + chmod -R ug+rwX "${cfg.root}/${feedIconsDir}" '' + (optionalString (cfg.database.type == "pgsql") '' @@ -640,6 +648,7 @@ let wantedBy = [ "multi-user.target" ]; requires = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service"; after = [ "network.target" ] ++ optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service"; + }; }; services.mysql = mkIf mysqlLocal { diff --git a/nixos/modules/services/x11/display-managers/set-session.py b/nixos/modules/services/x11/display-managers/set-session.py index 0cca80af44e..75940efe32b 100755 --- a/nixos/modules/services/x11/display-managers/set-session.py +++ b/nixos/modules/services/x11/display-managers/set-session.py @@ -72,11 +72,14 @@ def main(): f"Setting session name: {session}, as we found the existing wayland-session: {session_file}" ) user.set_session(session) + user.set_session_type("wayland") elif is_session_xsession(session_file): logging.debug( f"Setting session name: {session}, as we found the existing xsession: {session_file}" ) user.set_x_session(session) + user.set_session(session) + user.set_session_type("x11") else: logging.error(f"Couldn't figure out session type for {session_file}") sys.exit(1) |