summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/abs-podcast-autoplaylist.nix55
-rw-r--r--modules/i18n.nix156
-rw-r--r--modules/installer.nix56
-rw-r--r--modules/pgbackrest.nix2
-rw-r--r--modules/postsrsd.nix157
-rw-r--r--modules/systemd-run0.nix4
-rw-r--r--modules/uucp.nix373
7 files changed, 430 insertions, 373 deletions
diff --git a/modules/abs-podcast-autoplaylist.nix b/modules/abs-podcast-autoplaylist.nix
new file mode 100644
index 00000000..f526a434
--- /dev/null
+++ b/modules/abs-podcast-autoplaylist.nix
@@ -0,0 +1,55 @@
1{ config, pkgs, lib, utils, ... }:
2
3let
4 cfg = config.services.abs-podcast-autoplaylist;
5
6 enabledAttrs = lib.filterAttrs (_name: { enable, ... }: enable) cfg;
7in {
8 options = {
9 services.abs-podcast-autoplaylist = lib.mkOption {
10 default = {};
11 type = lib.types.attrsOf (lib.types.submodule ({ name, ... }: {
12 options = {
13 enable = lib.mkEnableOption "this instance of abs-podcast-autoplaylist" // {
14 default = true;
15 };
16 cron = lib.mkOption {
17 type = lib.types.str;
18 default = "*-*-* *:00/30:00";
19 };
20 configSecret = lib.mkOption {
21 type = lib.types.str;
22 default = "abs-podcast-autoplaylist-${name}.toml";
23 };
24 };
25 }));
26 };
27 };
28
29 config = lib.mkIf (enabledAttrs != {}) {
30 systemd.services = {
31 "abs-podcast-autoplaylist@" = {
32 serviceConfig = {
33 WorkingDirectory = "%d";
34 DynamicUser = true;
35 ProtectHome = true;
36 PrivateTmp = true;
37 PrivateDevices = true;
38 Type = "oneshot";
39 ExecStart = "${lib.getExe pkgs.abs-podcast-autoplaylist} %I.toml";
40 TimeoutSec = "5min";
41 };
42 };
43 } // lib.mapAttrs' (name: { configSecret, ... }: lib.nameValuePair "abs-podcast-autoplaylist@${utils.escapeSystemdPath name}" {
44 overrideStrategy = "asDropin";
45 serviceConfig = {
46 LoadCredential = "${name}.toml:${config.sops.secrets.${configSecret}.path}";
47 };
48 }) enabledAttrs;
49
50 systemd.timers = lib.mapAttrs' (name: { cron, ... }: lib.nameValuePair "abs-podcast-autoplaylist@${utils.escapeSystemdPath name}" {
51 wantedBy = [ "timers.target" ];
52 timerConfig.OnCalendar = cron;
53 }) enabledAttrs;
54 };
55}
diff --git a/modules/i18n.nix b/modules/i18n.nix
new file mode 100644
index 00000000..f84e8b64
--- /dev/null
+++ b/modules/i18n.nix
@@ -0,0 +1,156 @@
1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 aggregatedLocales =
9 (builtins.map
10 (l: (lib.replaceStrings [ "utf8" "utf-8" "UTF8" ] [ "UTF-8" "UTF-8" "UTF-8" ] l) + "/UTF-8")
11 (
12 [ config.i18n.defaultLocale ]
13 ++ (lib.optionals (builtins.isList config.i18n.extraLocales) config.i18n.extraLocales)
14 ++ (lib.attrValues (lib.filterAttrs (n: _v: lib.hasPrefix "LC_" n) config.i18n.extraLocaleSettings))
15 )
16 )
17 ++ (lib.optional (builtins.isString config.i18n.extraLocales) config.i18n.extraLocales);
18in
19{
20 disabledModules = [ "config/i18n.nix" ];
21
22 ###### interface
23
24 options = {
25
26 i18n = {
27 glibcLocales = lib.mkOption {
28 type = lib.types.path;
29 default = pkgs.glibcLocales.override {
30 allLocales = lib.any (x: x == "all") config.i18n.supportedLocales;
31 locales = config.i18n.supportedLocales;
32 };
33 defaultText = lib.literalExpression ''
34 pkgs.glibcLocales.override {
35 allLocales = lib.any (x: x == "all") config.i18n.supportedLocales;
36 locales = config.i18n.supportedLocales;
37 }
38 '';
39 example = lib.literalExpression "pkgs.glibcLocales";
40 description = ''
41 Customized pkg.glibcLocales package.
42
43 Changing this option can disable handling of i18n.defaultLocale
44 and supportedLocale.
45 '';
46 };
47
48 defaultLocale = lib.mkOption {
49 type = lib.types.str;
50 default = "en_US.UTF-8";
51 example = "nl_NL.UTF-8";
52 description = ''
53 The default locale. It determines the language for program
54 messages, the format for dates and times, sort order, and so on.
55 It also determines the character set, such as UTF-8.
56 '';
57 };
58
59 extraLocales = lib.mkOption {
60 type = lib.types.either (lib.types.listOf lib.types.str) (lib.types.enum [ "all" ]);
61 default = [ ];
62 example = [ "nl_NL.UTF-8" ];
63 description = ''
64 Additional locales that the system should support, besides the ones
65 configured with {option}`i18n.defaultLocale` and
66 {option}`i18n.extraLocaleSettings`.
67 Set this to `"all"` to install all available locales.
68 '';
69 };
70
71 extraLocaleSettings = lib.mkOption {
72 type = lib.types.attrsOf lib.types.str;
73 default = { };
74 example = {
75 LC_MESSAGES = "en_US.UTF-8";
76 LC_TIME = "de_DE.UTF-8";
77 };
78 description = ''
79 A set of additional system-wide locale settings other than
80 `LANG` which can be configured with
81 {option}`i18n.defaultLocale`.
82 '';
83 };
84
85 supportedLocales = lib.mkOption {
86 type = lib.types.listOf lib.types.str;
87 visible = false;
88 default = lib.unique (
89 [
90 "C.UTF-8/UTF-8"
91 "en_US.UTF-8/UTF-8"
92 ]
93 ++ aggregatedLocales
94 );
95 example = [
96 "en_US.UTF-8/UTF-8"
97 "nl_NL.UTF-8/UTF-8"
98 "nl_NL/ISO-8859-1"
99 ];
100 description = ''
101 List of locales that the system should support. The value
102 `"all"` means that all locales supported by
103 Glibc will be installed. A full list of supported locales
104 can be found at <https://sourceware.org/git/?p=glibc.git;a=blob;f=localedata/SUPPORTED>.
105 '';
106 };
107
108 };
109
110 };
111
112 ###### implementation
113
114 config = {
115 warnings =
116 lib.optional
117 (
118 !(
119 (lib.subtractLists config.i18n.supportedLocales aggregatedLocales) == [ ]
120 || lib.any (x: x == "all") config.i18n.supportedLocales
121 )
122 )
123 ''
124 `i18n.supportedLocales` is deprecated in favor of `i18n.extraLocales`,
125 and it seems you are using `i18n.supportedLocales` and forgot to
126 include some locales specified in `i18n.defaultLocale`,
127 `i18n.extraLocales` or `i18n.extraLocaleSettings`.
128
129 If you're trying to install additional locales not specified in
130 `i18n.defaultLocale` or `i18n.extraLocaleSettings`, consider adding
131 only those locales to `i18n.extraLocales`.
132 '';
133
134 environment.systemPackages =
135 # We increase the priority a little, so that plain glibc in systemPackages can't win.
136 lib.optional (config.i18n.supportedLocales != [ ]) (lib.setPrio (-1) config.i18n.glibcLocales);
137
138 environment.sessionVariables = {
139 LANG = config.i18n.defaultLocale;
140 LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive";
141 } // config.i18n.extraLocaleSettings;
142
143 systemd.globalEnvironment = lib.mkIf (config.i18n.supportedLocales != [ ]) {
144 LOCALE_ARCHIVE = "${config.i18n.glibcLocales}/lib/locale/locale-archive";
145 };
146
147 # ‘/etc/locale.conf’ is used by systemd.
148 environment.etc."locale.conf".source = pkgs.writeText "locale.conf" ''
149 LANG=${config.i18n.defaultLocale}
150 ${lib.concatStringsSep "\n" (
151 lib.mapAttrsToList (n: v: "${n}=${v}") config.i18n.extraLocaleSettings
152 )}
153 '';
154
155 };
156}
diff --git a/modules/installer.nix b/modules/installer.nix
new file mode 100644
index 00000000..3e5c6d5b
--- /dev/null
+++ b/modules/installer.nix
@@ -0,0 +1,56 @@
1{ flake, config, lib, pkgs, ... }:
2
3let
4 cfg = config.installer.links;
5
6 installerOutPath = {
7 "cd-dvd" = _: installerBuild: "${installerBuild.config.system.build.isoImage}/iso";
8 "netboot" = {system, variant}: installerBuild: pkgs.runCommandLocal "${system}-${variant}" {} ''
9 mkdir $out
10 install -m 0444 -t $out \
11 ${installerBuild.config.system.build.netbootRamdisk}/initrd \
12 ${installerBuild.config.system.build.kernel}/${config.system.boot.loader.kernelFile} \
13 ${installerBuild.config.system.build.netbootIpxeScript}/netboot.ipxe \
14 ${pkgs.ipxe.override {
15 additionalTargets = {
16 "bin-i386-efi/ipxe.efi" = "i386-ipxe.efi";
17 };
18 additionalOptions = [
19 "NSLOOKUP_CMD"
20 "PING_CMD"
21 "CONSOLE_CMD"
22 ];
23 embedScript = pkgs.writeText "netboot.ipxe" ''
24 #!ipxe
25
26 chain netboot.ipxe
27 '';
28 }}/{ipxe.efi,i386-ipxe.efi,ipxe.lkrn}
29 '';
30 };
31in {
32 options = {
33 installer.links = lib.mkOption {
34 type = lib.types.listOf (lib.types.submodule {
35 options = {
36 system = lib.mkOption {
37 type = lib.types.str;
38 };
39 variant = lib.mkOption {
40 type = lib.types.str;
41 };
42 };
43 });
44 default = [];
45 };
46 };
47
48 config = lib.mkIf (cfg != []) {
49 systemd.tmpfiles.rules = map (installer'@{system, variant}:
50 let
51 installer = "${system}-${variant}";
52 installerBuild = builtins.addErrorContext "while evaluating installer-${installer}" flake.nixosConfigurations.${"installer-${installer}"};
53 in "L+ /run/installer-${installer} - - - - ${installerOutPath.${variant} installer' installerBuild}"
54 ) cfg;
55 };
56}
diff --git a/modules/pgbackrest.nix b/modules/pgbackrest.nix
index 81c74a8e..550e970b 100644
--- a/modules/pgbackrest.nix
+++ b/modules/pgbackrest.nix
@@ -43,6 +43,8 @@ let
43 loglevelType = types.enum ["off" "error" "warn" "info" "detail" "debug" "trace"]; 43 loglevelType = types.enum ["off" "error" "warn" "info" "detail" "debug" "trace"];
44 inherit (utils.systemdUtils.unitOptions) unitOption; 44 inherit (utils.systemdUtils.unitOptions) unitOption;
45in { 45in {
46 disabledModules = ["services/backup/pgbackrest.nix"];
47
46 options = { 48 options = {
47 services.pgbackrest = { 49 services.pgbackrest = {
48 enable = mkEnableOption "pgBackRest"; 50 enable = mkEnableOption "pgBackRest";
diff --git a/modules/postsrsd.nix b/modules/postsrsd.nix
new file mode 100644
index 00000000..205e669d
--- /dev/null
+++ b/modules/postsrsd.nix
@@ -0,0 +1,157 @@
1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8
9 cfg = config.services.postsrsd;
10 runtimeDirectoryName = "postsrsd";
11 runtimeDirectory = "/run/${runtimeDirectoryName}";
12 # TODO: follow RFC 42, but we need a libconfuse format first:
13 # https://github.com/NixOS/nixpkgs/issues/401565
14 # Arrays in `libconfuse` look like this: {"Life", "Universe", "Everything"}
15 # See https://www.nongnu.org/confuse/tutorial-html/ar01s03.html.
16 #
17 # Note: We're using `builtins.toJSON` to escape strings, but JSON strings
18 # don't have exactly the same semantics as libconfuse strings. For example,
19 # "${F}" gets treated as an env var reference, see above issue for details.
20 libconfuseDomains = "{ " + lib.concatMapStringsSep ", " builtins.toJSON cfg.domains + " }";
21 configFile = pkgs.writeText "postsrsd.conf" ''
22 secrets-file = "''${CREDENTIALS_DIRECTORY}/secrets-file"
23 domains = ${libconfuseDomains}
24 separator = "${cfg.separator}"
25
26 # Disable postsrsd's jailing in favor of confinement with systemd.
27 unprivileged-user = ""
28 chroot-dir = ""
29
30 ${cfg.extraConfig}
31 '';
32
33in
34{
35 imports =
36 map
37 (
38 name:
39 lib.mkRemovedOptionModule [ "services" "postsrsd" name ] ''
40 `postsrsd` was upgraded to `>= 2.0.0`, with some different behaviors and configuration settings:
41 - NixOS Release Notes: https://nixos.org/manual/nixos/unstable/release-notes#sec-nixpkgs-release-25.05-incompatibilities
42 - NixOS Options Reference: https://nixos.org/manual/nixos/unstable/options#opt-services.postsrsd.enable
43 - Migration instructions: https://github.com/roehling/postsrsd/blob/2.0.10/README.rst#migrating-from-version-1x
44 - Postfix Setup: https://github.com/roehling/postsrsd/blob/2.0.10/README.rst#postfix-setup
45 ''
46 )
47 [
48 "domain"
49 "forwardPort"
50 "reversePort"
51 "timeout"
52 "excludeDomains"
53 ];
54
55 disabledModules = [ "services/mail/postsrsd.nix" ];
56
57 options = {
58 services.postsrsd = {
59 enable = lib.mkOption {
60 type = lib.types.bool;
61 default = false;
62 description = "Whether to enable the postsrsd SRS server for Postfix.";
63 };
64
65 secretsFile = lib.mkOption {
66 type = lib.types.path;
67 default = "/var/lib/postsrsd/postsrsd.secret";
68 description = "Secret keys used for signing and verification";
69 };
70
71 domains = lib.mkOption {
72 type = lib.types.listOf lib.types.str;
73 description = "Domain names for rewrite";
74 default = [ config.networking.hostName ];
75 defaultText = lib.literalExpression "[ config.networking.hostName ]";
76 };
77
78 separator = lib.mkOption {
79 type = lib.types.enum [
80 "-"
81 "="
82 "+"
83 ];
84 default = "=";
85 description = "First separator character in generated addresses";
86 };
87
88 user = lib.mkOption {
89 type = lib.types.str;
90 default = "postsrsd";
91 description = "User for the daemon";
92 };
93
94 group = lib.mkOption {
95 type = lib.types.str;
96 default = "postsrsd";
97 description = "Group for the daemon";
98 };
99
100 extraConfig = lib.mkOption {
101 type = lib.types.lines;
102 default = "";
103 };
104 };
105 };
106
107 config = lib.mkIf cfg.enable {
108 users.users = lib.optionalAttrs (cfg.user == "postsrsd") {
109 postsrsd = {
110 group = cfg.group;
111 uid = config.ids.uids.postsrsd;
112 };
113 };
114
115 users.groups = lib.optionalAttrs (cfg.group == "postsrsd") {
116 postsrsd.gid = config.ids.gids.postsrsd;
117 };
118
119 systemd.services.postsrsd-generate-secrets = {
120 path = [ pkgs.coreutils ];
121 script = ''
122 if [ -e "${cfg.secretsFile}" ]; then
123 echo "Secrets file exists. Nothing to do!"
124 else
125 echo "WARNING: secrets file not found, autogenerating!"
126 DIR="$(dirname "${cfg.secretsFile}")"
127 install -m 750 -o ${cfg.user} -g ${cfg.group} -d "$DIR"
128 install -m 600 -o ${cfg.user} -g ${cfg.group} <(dd if=/dev/random bs=18 count=1 | base64) "${cfg.secretsFile}"
129 fi
130 '';
131 serviceConfig = {
132 Type = "oneshot";
133 };
134 };
135
136 systemd.services.postsrsd = {
137 description = "PostSRSd SRS rewriting server";
138 after = [
139 "network.target"
140 "postsrsd-generate-secrets.service"
141 ];
142 before = [ "postfix.service" ];
143 wantedBy = [ "multi-user.target" ];
144 requires = [ "postsrsd-generate-secrets.service" ];
145 confinement.enable = true;
146
147 serviceConfig = {
148 ExecStart = "${lib.getExe pkgs.postsrsd} -C ${configFile}";
149 User = cfg.user;
150 Group = cfg.group;
151 PermissionsStartOnly = true;
152 RuntimeDirectory = runtimeDirectoryName;
153 LoadCredential = "secrets-file:${cfg.secretsFile}";
154 };
155 };
156 };
157}
diff --git a/modules/systemd-run0.nix b/modules/systemd-run0.nix
new file mode 100644
index 00000000..8575ec7c
--- /dev/null
+++ b/modules/systemd-run0.nix
@@ -0,0 +1,4 @@
1{ config, lib, ... }:
2{
3 config.security.pam.services.systemd-run0 = lib.mkIf (lib.versionAtLeast config.systemd.package.version "256") {};
4}
diff --git a/modules/uucp.nix b/modules/uucp.nix
deleted file mode 100644
index 10f7297b..00000000
--- a/modules/uucp.nix
+++ /dev/null
@@ -1,373 +0,0 @@
1{ flake, config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 portSpec = name: node: concatStringsSep "\n" (map (port: ''
7 port ${name}.${port}
8 type pipe
9 protocol ${node.protocols}
10 reliable true
11 command ${pkgs.openssh}/bin/ssh -x -o batchmode=yes ${name}.${port}
12 '') node.hostnames);
13 sysSpec = name: node: ''
14 system ${name}
15 time any
16 chat-seven-bit false
17 chat . ""
18 protocol ${node.protocols}
19 command-path ${concatStringsSep " " cfg.commandPath}
20 commands ${concatStringsSep " " node.commands}
21 ${concatStringsSep "\nalternate\n" (map (port: ''
22 port ${name}.${port}
23 '') node.hostnames)}
24 '';
25 sshConfig = name: node: concatStringsSep "\n" (map (port: ''
26 Host ${name}.${port}
27 Hostname ${port}
28 IdentitiesOnly Yes
29 IdentityFile ${cfg.sshKeyDir}/${name}
30 '') node.hostnames);
31 sshKeyGen = name: node: ''
32 if [[ ! -e ${cfg.sshKeyDir}/${name} ]]; then
33 ${pkgs.openssh}/bin/ssh-keygen ${escapeShellArgs node.generateKey} -f ${cfg.sshKeyDir}/${name}
34 fi
35 '';
36 restrictKey = key: ''
37 restrict,command="${chat}" ${key}
38 '';
39 chat = pkgs.writeScript "chat" ''
40 #!${pkgs.stdenv.shell}
41
42 echo .
43 exec ${config.security.wrapperDir}/uucico
44 '';
45
46 nodeCfg = {
47 options = {
48 commands = mkOption {
49 type = types.listOf types.str;
50 default = cfg.defaultCommands;
51 defaultText = literalExpression "config.services.uucp.defaultCommands";
52 description = "Commands to allow for this remote";
53 };
54
55 protocols = mkOption {
56 type = types.separatedString "";
57 default = cfg.defaultProtocols;
58 defaultText = literalExpression "config.services.uucp.defaultProtocols";
59 description = "UUCP protocols to use for this remote";
60 };
61
62 publicKeys = mkOption {
63 type = types.listOf types.str;
64 default = [];
65 description = "SSH client public keys for this node";
66 };
67
68 generateKey = mkOption {
69 type = types.listOf types.str;
70 default = [ "-t" "ed25519" "-N" "" ];
71 description = "Arguments to pass to `ssh-keygen` to generate a keypair for communication with this host";
72 };
73
74 hostnames = mkOption {
75 type = types.listOf types.str;
76 default = [];
77 description = "Hostnames to try in order when connecting";
78 };
79 };
80 };
81
82 cfg = config.services.uucp;
83in {
84 options = {
85 services.uucp = {
86 enable = mkOption {
87 type = types.bool;
88 default = false;
89 description = ''
90 If enabled we set up an account accesible via uucp over ssh
91 '';
92 };
93
94 nodeName = mkOption {
95 type = types.str;
96 default = "nixos";
97 description = "uucp node name";
98 };
99
100 sshUser = mkOption {
101 type = types.attrs;
102 default = {};
103 description = "Overrides for the local uucp linux-user";
104 };
105
106 extraSSHConfig = mkOption {
107 type = types.str;
108 default = "";
109 description = "Extra SSH config";
110 };
111
112 remoteNodes = mkOption {
113 type = types.attrsOf (types.submodule nodeCfg);
114 default = {};
115 description = ''
116 Ports to set up
117 Names will probably need to be configured in sshConfig
118 '';
119 };
120
121 commandPath = mkOption {
122 type = types.listOf types.path;
123 default = [ "${pkgs.rmail}/bin" ];
124 defaultText = literalExpression ''[ "''${pkgs.rmail}/bin" ]'';
125 description = ''
126 Command search path for all systems
127 '';
128 };
129
130 defaultCommands = mkOption {
131 type = types.listOf types.str;
132 default = ["rmail"];
133 description = "Commands allowed for remotes without explicit override";
134 };
135
136 defaultProtocols = mkOption {
137 type = types.separatedString "";
138 default = "te";
139 description = "UUCP protocol to use within ssh unless overriden";
140 };
141
142 incomingProtocols = mkOption {
143 type = types.separatedString "";
144 default = "te";
145 description = "UUCP protocols to use when called";
146 };
147
148 homeDir = mkOption {
149 type = types.path;
150 default = "/var/uucp";
151 description = "Home of the uucp user";
152 };
153
154 sshKeyDir = mkOption {
155 type = types.path;
156 default = "${cfg.homeDir}/.ssh/";
157 defaultText = literalExpression ''''${config.services.uucp.homeDir}/.ssh/'';
158 description = "Directory to store ssh keypairs";
159 };
160
161 spoolDir = mkOption {
162 type = types.path;
163 default = "/var/spool/uucp";
164 description = "Spool directory";
165 };
166
167 lockDir = mkOption {
168 type = types.path;
169 default = "/var/spool/uucp";
170 description = "Lock directory";
171 };
172
173 pubDir = mkOption {
174 type = types.path;
175 default = "/var/spool/uucppublic";
176 description = "Public directory";
177 };
178
179 logFile = mkOption {
180 type = types.path;
181 default = "/var/log/uucp";
182 description = "Log file";
183 };
184
185 statFile = mkOption {
186 type = types.path;
187 default = "/var/log/uucp.stat";
188 description = "Statistics file";
189 };
190
191 debugFile = mkOption {
192 type = types.path;
193 default = "/var/log/uucp.debug";
194 description = "Debug file";
195 };
196
197 interval = mkOption {
198 type = types.nullOr types.str;
199 default = "1h";
200 description = ''
201 Specification of when to run `uucico' in format used by systemd timers
202 The default is to do so every hour
203 '';
204 };
205
206 nmDispatch = mkOption {
207 type = types.bool;
208 default = config.networking.networkmanager.enable;
209 defaultText = literalExpression "config.networking.networkmanager.enable";
210 description = ''
211 Install a network-manager dispatcher script to automatically
212 call all remotes when networking is available
213 '';
214 };
215
216 extraConfig = mkOption {
217 type = types.lines;
218 default = ''
219 run-uuxqt 1
220 '';
221 description = "Extra configuration to append verbatim to `/etc/uucp/config'";
222 };
223
224 extraSys = mkOption {
225 type = types.lines;
226 default = ''
227 protocol-parameter g packet-size 4096
228 '';
229 description = "Extra configuration to prepend verbatim to `/etc/uucp/sys`";
230 };
231 };
232 };
233
234 config = mkIf cfg.enable {
235 environment.etc."uucp/config" = {
236 text = ''
237 hostname ${cfg.nodeName}
238
239 spool ${cfg.spoolDir}
240 lockdir ${cfg.lockDir}
241 pubdir ${cfg.pubDir}
242 logfile ${cfg.logFile}
243 statfile ${cfg.statFile}
244 debugfile ${cfg.debugFile}
245
246 ${cfg.extraConfig}
247 '';
248 };
249
250 users.groups."uucp" = {};
251 users.users."uucp" = {
252 name = "uucp";
253 group = "uucp";
254 isSystemUser = true;
255 isNormalUser = false;
256 createHome = true;
257 home = cfg.homeDir;
258 description = "User for uucp over ssh";
259 useDefaultShell = true;
260 openssh.authorizedKeys.keys = map restrictKey (concatLists (mapAttrsToList (name: node: node.publicKeys) cfg.remoteNodes));
261 } // cfg.sshUser;
262
263 system.activationScripts."uucp-sshconfig" = ''
264 mkdir -p ${config.users.users."uucp".home}/.ssh
265 chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${config.users.users."uucp".home}/.ssh
266 chmod 700 ${config.users.users."uucp".home}/.ssh
267 ln -fs ${builtins.toFile "ssh-config" ''
268 ${concatStringsSep "\n" (mapAttrsToList sshConfig cfg.remoteNodes)}
269
270 ${cfg.extraSSHConfig}
271 ''} ${config.users.users."uucp".home}/.ssh/config
272
273 mkdir -p ${cfg.sshKeyDir}
274 chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.sshKeyDir}
275 chmod 700 ${cfg.sshKeyDir}
276
277 ${concatStringsSep "\n" (mapAttrsToList sshKeyGen cfg.remoteNodes)}
278 '';
279
280 system.activationScripts."uucp-logs" = ''
281 touch ${cfg.logFile}
282 chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.logFile}
283 chmod 644 ${cfg.logFile}
284 touch ${cfg.statFile}
285 chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.statFile}
286 chmod 644 ${cfg.statFile}
287 touch ${cfg.debugFile}
288 chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.debugFile}
289 chmod 644 ${cfg.debugFile}
290 '';
291
292 environment.etc."uucp/port" = {
293 text = ''
294 port ssh
295 type stdin
296 protocol ${cfg.incomingProtocols}
297 '' + concatStringsSep "\n" (mapAttrsToList portSpec cfg.remoteNodes);
298 };
299 environment.etc."uucp/sys" = {
300 text = cfg.extraSys + "\n" + concatStringsSep "\n" (mapAttrsToList sysSpec cfg.remoteNodes);
301 };
302
303 security.wrappers = let
304 wrapper = p: {
305 name = p;
306 value = {
307 source = "${pkgs.uucp}/bin/${p}";
308 owner = "root";
309 group = "root";
310 setuid = true;
311 setgid = false;
312 };
313 };
314 in listToAttrs (map wrapper ["uucico" "cu" "uucp" "uuname" "uustat" "uux" "uuxqt"]);
315
316 nixpkgs.overlays = [(self: super: {
317 rmail = super.writeShellScriptBin "rmail" ''
318 # Dummy UUCP rmail command for postfix/qmail systems
319
320 IFS=" " read junk from junk junk junk junk junk junk junk relay
321
322 case "$from" in
323 *[@!]*) ;;
324 *) from="$from@$relay";;
325 esac
326
327 exec ${config.security.wrapperDir}/sendmail -G -i -f "$from" -- "$@"
328 '';
329 })];
330
331 environment.systemPackages = with pkgs; [
332 uucp
333 ];
334
335 systemd.services."uucico@" = {
336 serviceConfig = {
337 User = "uucp";
338 Type = "oneshot";
339 ExecStart = "${config.security.wrapperDir}/uucico -D -S %i";
340 };
341 };
342
343 systemd.timers."uucico@" = {
344 timerConfig.OnActiveSec = cfg.interval;
345 timerConfig.OnUnitActiveSec = cfg.interval;
346 };
347
348 systemd.targets."multi-user" = {
349 wants = mapAttrsToList (name: node: "uucico@${name}.timer") cfg.remoteNodes;
350 };
351
352 systemd.kill-user.enable = true;
353 systemd.targets."sleep" = {
354 after = [ "kill-user@uucp.service" ];
355 wants = [ "kill-user@uucp.service" ];
356 };
357
358 networking.networkmanager.dispatcherScripts = optional cfg.nmDispatch {
359 type = "basic";
360 source = pkgs.writeScript "callRemotes.sh" ''
361 #!${pkgs.stdenv.shell}
362
363 shopt -s extglob
364
365 case "''${2}" in
366 (?(vpn-)up)
367 ${concatStringsSep "\n " (mapAttrsToList (name: node: "${pkgs.systemd}/bin/systemctl start uucico@${name}.service") cfg.remoteNodes)}
368 ;;
369 esac
370 '';
371 };
372 };
373}