From 43c9825e49d25fbd2c19abcdeb8f73aee8be2a4c Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Wed, 14 May 2025 10:50:27 +0200 Subject: ... --- modules/i18n.nix | 156 +++++++++++++++++++++++++++++++++++++++++++++++++ modules/installer.nix | 56 ++++++++++++++++++ modules/postsrsd.nix | 157 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 369 insertions(+) create mode 100644 modules/i18n.nix create mode 100644 modules/installer.nix create mode 100644 modules/postsrsd.nix (limited to 'modules') 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 @@ +{ + config, + lib, + pkgs, + ... +}: +let + aggregatedLocales = + (builtins.map + (l: (lib.replaceStrings [ "utf8" "utf-8" "UTF8" ] [ "UTF-8" "UTF-8" "UTF-8" ] l) + "/UTF-8") + ( + [ config.i18n.defaultLocale ] + ++ (lib.optionals (builtins.isList config.i18n.extraLocales) config.i18n.extraLocales) + ++ (lib.attrValues (lib.filterAttrs (n: _v: lib.hasPrefix "LC_" n) config.i18n.extraLocaleSettings)) + ) + ) + ++ (lib.optional (builtins.isString config.i18n.extraLocales) config.i18n.extraLocales); +in +{ + disabledModules = [ "config/i18n.nix" ]; + + ###### interface + + options = { + + i18n = { + glibcLocales = lib.mkOption { + type = lib.types.path; + default = pkgs.glibcLocales.override { + allLocales = lib.any (x: x == "all") config.i18n.supportedLocales; + locales = config.i18n.supportedLocales; + }; + defaultText = lib.literalExpression '' + pkgs.glibcLocales.override { + allLocales = lib.any (x: x == "all") config.i18n.supportedLocales; + locales = config.i18n.supportedLocales; + } + ''; + example = lib.literalExpression "pkgs.glibcLocales"; + description = '' + Customized pkg.glibcLocales package. + + Changing this option can disable handling of i18n.defaultLocale + and supportedLocale. + ''; + }; + + defaultLocale = lib.mkOption { + type = lib.types.str; + default = "en_US.UTF-8"; + example = "nl_NL.UTF-8"; + description = '' + The default locale. It determines the language for program + messages, the format for dates and times, sort order, and so on. + It also determines the character set, such as UTF-8. + ''; + }; + + extraLocales = lib.mkOption { + type = lib.types.either (lib.types.listOf lib.types.str) (lib.types.enum [ "all" ]); + default = [ ]; + example = [ "nl_NL.UTF-8" ]; + description = '' + Additional locales that the system should support, besides the ones + configured with {option}`i18n.defaultLocale` and + {option}`i18n.extraLocaleSettings`. + Set this to `"all"` to install all available locales. + ''; + }; + + extraLocaleSettings = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = { }; + example = { + LC_MESSAGES = "en_US.UTF-8"; + LC_TIME = "de_DE.UTF-8"; + }; + description = '' + A set of additional system-wide locale settings other than + `LANG` which can be configured with + {option}`i18n.defaultLocale`. + ''; + }; + + supportedLocales = lib.mkOption { + type = lib.types.listOf lib.types.str; + visible = false; + default = lib.unique ( + [ + "C.UTF-8/UTF-8" + "en_US.UTF-8/UTF-8" + ] + ++ aggregatedLocales + ); + example = [ + "en_US.UTF-8/UTF-8" + "nl_NL.UTF-8/UTF-8" + "nl_NL/ISO-8859-1" + ]; + description = '' + List of locales that the system should support. The value + `"all"` means that all locales supported by + Glibc will be installed. A full list of supported locales + can be found at . + ''; + }; + + }; + + }; + + ###### implementation + + config = { + warnings = + lib.optional + ( + !( + (lib.subtractLists config.i18n.supportedLocales aggregatedLocales) == [ ] + || lib.any (x: x == "all") config.i18n.supportedLocales + ) + ) + '' + `i18n.supportedLocales` is deprecated in favor of `i18n.extraLocales`, + and it seems you are using `i18n.supportedLocales` and forgot to + include some locales specified in `i18n.defaultLocale`, + `i18n.extraLocales` or `i18n.extraLocaleSettings`. + + If you're trying to install additional locales not specified in + `i18n.defaultLocale` or `i18n.extraLocaleSettings`, consider adding + only those locales to `i18n.extraLocales`. + ''; + + environment.systemPackages = + # We increase the priority a little, so that plain glibc in systemPackages can't win. + lib.optional (config.i18n.supportedLocales != [ ]) (lib.setPrio (-1) config.i18n.glibcLocales); + + environment.sessionVariables = { + LANG = config.i18n.defaultLocale; + LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive"; + } // config.i18n.extraLocaleSettings; + + systemd.globalEnvironment = lib.mkIf (config.i18n.supportedLocales != [ ]) { + LOCALE_ARCHIVE = "${config.i18n.glibcLocales}/lib/locale/locale-archive"; + }; + + # ‘/etc/locale.conf’ is used by systemd. + environment.etc."locale.conf".source = pkgs.writeText "locale.conf" '' + LANG=${config.i18n.defaultLocale} + ${lib.concatStringsSep "\n" ( + lib.mapAttrsToList (n: v: "${n}=${v}") config.i18n.extraLocaleSettings + )} + ''; + + }; +} 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 @@ +{ flake, config, lib, pkgs, ... }: + +let + cfg = config.installer.links; + + installerOutPath = { + "cd-dvd" = _: installerBuild: "${installerBuild.config.system.build.isoImage}/iso"; + "netboot" = {system, variant}: installerBuild: pkgs.runCommandLocal "${system}-${variant}" {} '' + mkdir $out + install -m 0444 -t $out \ + ${installerBuild.config.system.build.netbootRamdisk}/initrd \ + ${installerBuild.config.system.build.kernel}/${config.system.boot.loader.kernelFile} \ + ${installerBuild.config.system.build.netbootIpxeScript}/netboot.ipxe \ + ${pkgs.ipxe.override { + additionalTargets = { + "bin-i386-efi/ipxe.efi" = "i386-ipxe.efi"; + }; + additionalOptions = [ + "NSLOOKUP_CMD" + "PING_CMD" + "CONSOLE_CMD" + ]; + embedScript = pkgs.writeText "netboot.ipxe" '' + #!ipxe + + chain netboot.ipxe + ''; + }}/{ipxe.efi,i386-ipxe.efi,ipxe.lkrn} + ''; + }; +in { + options = { + installer.links = lib.mkOption { + type = lib.types.listOf (lib.types.submodule { + options = { + system = lib.mkOption { + type = lib.types.str; + }; + variant = lib.mkOption { + type = lib.types.str; + }; + }; + }); + default = []; + }; + }; + + config = lib.mkIf (cfg != []) { + systemd.tmpfiles.rules = map (installer'@{system, variant}: + let + installer = "${system}-${variant}"; + installerBuild = builtins.addErrorContext "while evaluating installer-${installer}" flake.nixosConfigurations.${"installer-${installer}"}; + in "L+ /run/installer-${installer} - - - - ${installerOutPath.${variant} installer' installerBuild}" + ) cfg; + }; +} 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 @@ +{ + config, + lib, + pkgs, + ... +}: +let + + cfg = config.services.postsrsd; + runtimeDirectoryName = "postsrsd"; + runtimeDirectory = "/run/${runtimeDirectoryName}"; + # TODO: follow RFC 42, but we need a libconfuse format first: + # https://github.com/NixOS/nixpkgs/issues/401565 + # Arrays in `libconfuse` look like this: {"Life", "Universe", "Everything"} + # See https://www.nongnu.org/confuse/tutorial-html/ar01s03.html. + # + # Note: We're using `builtins.toJSON` to escape strings, but JSON strings + # don't have exactly the same semantics as libconfuse strings. For example, + # "${F}" gets treated as an env var reference, see above issue for details. + libconfuseDomains = "{ " + lib.concatMapStringsSep ", " builtins.toJSON cfg.domains + " }"; + configFile = pkgs.writeText "postsrsd.conf" '' + secrets-file = "''${CREDENTIALS_DIRECTORY}/secrets-file" + domains = ${libconfuseDomains} + separator = "${cfg.separator}" + + # Disable postsrsd's jailing in favor of confinement with systemd. + unprivileged-user = "" + chroot-dir = "" + + ${cfg.extraConfig} + ''; + +in +{ + imports = + map + ( + name: + lib.mkRemovedOptionModule [ "services" "postsrsd" name ] '' + `postsrsd` was upgraded to `>= 2.0.0`, with some different behaviors and configuration settings: + - NixOS Release Notes: https://nixos.org/manual/nixos/unstable/release-notes#sec-nixpkgs-release-25.05-incompatibilities + - NixOS Options Reference: https://nixos.org/manual/nixos/unstable/options#opt-services.postsrsd.enable + - Migration instructions: https://github.com/roehling/postsrsd/blob/2.0.10/README.rst#migrating-from-version-1x + - Postfix Setup: https://github.com/roehling/postsrsd/blob/2.0.10/README.rst#postfix-setup + '' + ) + [ + "domain" + "forwardPort" + "reversePort" + "timeout" + "excludeDomains" + ]; + + disabledModules = [ "services/mail/postsrsd.nix" ]; + + options = { + services.postsrsd = { + enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to enable the postsrsd SRS server for Postfix."; + }; + + secretsFile = lib.mkOption { + type = lib.types.path; + default = "/var/lib/postsrsd/postsrsd.secret"; + description = "Secret keys used for signing and verification"; + }; + + domains = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "Domain names for rewrite"; + default = [ config.networking.hostName ]; + defaultText = lib.literalExpression "[ config.networking.hostName ]"; + }; + + separator = lib.mkOption { + type = lib.types.enum [ + "-" + "=" + "+" + ]; + default = "="; + description = "First separator character in generated addresses"; + }; + + user = lib.mkOption { + type = lib.types.str; + default = "postsrsd"; + description = "User for the daemon"; + }; + + group = lib.mkOption { + type = lib.types.str; + default = "postsrsd"; + description = "Group for the daemon"; + }; + + extraConfig = lib.mkOption { + type = lib.types.lines; + default = ""; + }; + }; + }; + + config = lib.mkIf cfg.enable { + users.users = lib.optionalAttrs (cfg.user == "postsrsd") { + postsrsd = { + group = cfg.group; + uid = config.ids.uids.postsrsd; + }; + }; + + users.groups = lib.optionalAttrs (cfg.group == "postsrsd") { + postsrsd.gid = config.ids.gids.postsrsd; + }; + + systemd.services.postsrsd-generate-secrets = { + path = [ pkgs.coreutils ]; + script = '' + if [ -e "${cfg.secretsFile}" ]; then + echo "Secrets file exists. Nothing to do!" + else + echo "WARNING: secrets file not found, autogenerating!" + DIR="$(dirname "${cfg.secretsFile}")" + install -m 750 -o ${cfg.user} -g ${cfg.group} -d "$DIR" + install -m 600 -o ${cfg.user} -g ${cfg.group} <(dd if=/dev/random bs=18 count=1 | base64) "${cfg.secretsFile}" + fi + ''; + serviceConfig = { + Type = "oneshot"; + }; + }; + + systemd.services.postsrsd = { + description = "PostSRSd SRS rewriting server"; + after = [ + "network.target" + "postsrsd-generate-secrets.service" + ]; + before = [ "postfix.service" ]; + wantedBy = [ "multi-user.target" ]; + requires = [ "postsrsd-generate-secrets.service" ]; + confinement.enable = true; + + serviceConfig = { + ExecStart = "${lib.getExe pkgs.postsrsd} -C ${configFile}"; + User = cfg.user; + Group = cfg.group; + PermissionsStartOnly = true; + RuntimeDirectory = runtimeDirectoryName; + LoadCredential = "secrets-file:${cfg.secretsFile}"; + }; + }; + }; +} -- cgit v1.2.3