diff options
Diffstat (limited to 'os/common/modules')
-rw-r--r-- | os/common/modules/dev.nix | 29 | ||||
-rw-r--r-- | os/common/modules/nix.nix | 6 | ||||
-rw-r--r-- | os/common/modules/pppd.nix | 277 | ||||
-rw-r--r-- | os/common/modules/stalwart-mail.nix | 163 | ||||
-rw-r--r-- | os/common/modules/tmux.nix | 42 |
5 files changed, 517 insertions, 0 deletions
diff --git a/os/common/modules/dev.nix b/os/common/modules/dev.nix new file mode 100644 index 0000000..285dfba --- /dev/null +++ b/os/common/modules/dev.nix @@ -0,0 +1,29 @@ +{ config, pkgs, ... }: + +let + user = config.userdata.name; +in +{ + users.users.${user}.packages = with pkgs; [ + gcc + git + lua + + (python3.withPackages (p: with p; [ + pip + build + ])) + + man-pages + man-pages-posix + + ccls + lua-language-server + nil + nodePackages.bash-language-server + nodePackages.pyright + shellcheck + ]; + + documentation.dev.enable = true; +} diff --git a/os/common/modules/nix.nix b/os/common/modules/nix.nix new file mode 100644 index 0000000..d826e77 --- /dev/null +++ b/os/common/modules/nix.nix @@ -0,0 +1,6 @@ +{ ... }: { + nix.settings = { + auto-optimise-store = true; + experimental-features = [ "flakes" "nix-command" "repl-flake" ]; + }; +} diff --git a/os/common/modules/pppd.nix b/os/common/modules/pppd.nix new file mode 100644 index 0000000..772cb29 --- /dev/null +++ b/os/common/modules/pppd.nix @@ -0,0 +1,277 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.pppd; + shTypes = [ "ip-up" "ip-down" "ipv6-up" "ipv6-down" ]; +in +{ + meta = { + maintainers = with maintainers; [ danderson ]; + }; + + options.services.pppd = { + enable = mkEnableOption (lib.mdDoc "pppd"); + + package = mkOption { + default = pkgs.ppp; + defaultText = literalExpression "pkgs.ppp"; + type = types.package; + description = lib.mdDoc "pppd package to use."; + }; + + config = mkOption { + type = types.lines; + default = ""; + description = lib.mdDoc "default config for pppd"; + }; + + secret = { + chap = mkOption { + type = types.nullOr types.path; + default = null; + description = lib.mdDoc "path to chap secret for pppd"; + }; + pap = mkOption { + type = types.nullOr types.path; + default = null; + description = lib.mdDoc "path to pap secret for pppd"; + }; + srp = mkOption { + type = types.nullOr types.path; + default = null; + description = lib.mdDoc "path to srp secret for pppd"; + }; + }; + + script = mkOption { + default = {}; + description = lib.mdoc '' + script which is executed when the link is available for sending and + receiving IP packets or when the link is no longer available for sending + and receiving IP packets, see pppd(8) for more details + ''; + type = types.attrsOf (types.submodule ( + { name, ... }: + { + options = { + name = mkOption { + type = types.str; + default = name; + example = "01-ddns.sh"; + description = lib.mdDoc "Name of the script."; + }; + type = mkOption { + default = "ip-up"; + type = types.enum shTypes; + description = lib.mdDoc "Type of the script."; + }; + text = mkOption { + type = types.lines; + default = ""; + description = lib.mdDoc "Shell commands to be executed."; + }; + runtimeInputs = mkOption { + type = types.listOf types.package; + default = []; + description = lib.mdDoc "dependencies of the shell script"; + }; + }; + } + )); + }; + + peers = mkOption { + default = {}; + description = lib.mdDoc "pppd peers."; + type = types.attrsOf (types.submodule ( + { name, ... }: + { + options = { + name = mkOption { + type = types.str; + default = name; + example = "dialup"; + description = lib.mdDoc "Name of the PPP peer."; + }; + + enable = mkOption { + type = types.bool; + default = true; + example = false; + description = lib.mdDoc "Whether to enable this PPP peer."; + }; + + autostart = mkOption { + type = types.bool; + default = true; + example = false; + description = lib.mdDoc "Whether the PPP session is automatically started at boot time."; + }; + + config = mkOption { + type = types.lines; + default = ""; + description = lib.mdDoc "pppd configuration for this peer, see the pppd(8) man page."; + }; + + configFile = mkOption { + type = types.nullOr types.path; + default = null; + example = literalExpression "/run/secrets/ppp/peer/options"; + description = lib.mdDoc "pppd configuration file for this peer, see the pppd(8) man page."; + }; + }; + } + )); + }; + }; + + config = let + enabledConfigs = filter (f: f.enable) (attrValues cfg.peers); + + defaultCfg = if (cfg.config != "") then { + "ppp/options".text = cfg.config; + } else {}; + + mkPeers = peerCfg: with peerCfg; let + key = if (configFile == null) then "text" else "source"; + val = if (configFile == null) then peerCfg.config else configFile; + in + { + name = "ppp/peers/${name}"; + value.${key} = val; + }; + + enabledSh = filter (s: s.text != "") (attrValues cfg.script); + mkMsh = name : { + name = "ppp/${name}"; + value.mode = "0755"; + value.text = '' + #!/bin/sh + + # see the pppd(8) man page + for s in /etc/ppp/${name}.d/*.sh; do + [ -x "$s" ] && "$s" "$@" + done + ''; + }; + mkUsh = shCfg : { + name = "ppp/${shCfg.type}.d/${shCfg.name}.sh"; + value.mode = "0755"; + value.text = '' + #!/bin/sh + export PATH="${makeBinPath shCfg.runtimeInputs}:$PATH" + + ${shCfg.text} + ''; + }; + + enabledSec = let + l = attrNames cfg.secret; + f = (s: cfg.secret.${s} != null); + in filter f l; + mkSec = sec : { + name = "ppp/${sec}-secrets"; + value.source = cfg.secret.${sec}; + }; + + mkSystemd = peerCfg: { + name = "pppd-${peerCfg.name}"; + value = { + restartTriggers = [ config.environment.etc."ppp/peers/${peerCfg.name}".source ]; + before = [ "network.target" ]; + wants = [ "network.target" ]; + after = [ "network-pre.target" ]; + environment = { + # pppd likes to write directly into /var/run. This is rude + # on a modern system, so we use libredirect to transparently + # move those files into /run/pppd. + LD_PRELOAD = "${pkgs.libredirect}/lib/libredirect.so"; + NIX_REDIRECTS = "/var/run=/run/pppd"; + }; + serviceConfig = let + capabilities = [ + "CAP_BPF" + "CAP_SYS_TTY_CONFIG" + "CAP_NET_ADMIN" + "CAP_NET_RAW" + ]; + in + { + ExecStart = "${getBin cfg.package}/sbin/pppd call ${peerCfg.name} nodetach nolog"; + Restart = "always"; + RestartSec = 5; + + AmbientCapabilities = capabilities; + CapabilityBoundingSet = capabilities; + KeyringMode = "private"; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateMounts = true; + PrivateTmp = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelModules = true; + # pppd can be configured to tweak kernel settings. + ProtectKernelTunables = false; + ProtectSystem = "strict"; + RemoveIPC = true; + RestrictAddressFamilies = [ + "AF_ATMPVC" + "AF_ATMSVC" + "AF_INET" + "AF_INET6" + "AF_IPX" + "AF_NETLINK" + "AF_PACKET" + "AF_PPPOX" + "AF_UNIX" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SecureBits = "no-setuid-fixup-locked noroot-locked"; + SystemCallFilter = "@system-service"; + SystemCallArchitectures = "native"; + + # All pppd instances on a system must share a runtime + # directory in order for PPP multilink to work correctly. So + # we give all instances the same /run/pppd directory to store + # things in. + # + # For the same reason, we can't set PrivateUsers=true, because + # all instances need to run as the same user to access the + # multilink database. + RuntimeDirectory = "pppd"; + RuntimeDirectoryPreserve = true; + }; + wantedBy = mkIf peerCfg.autostart [ "multi-user.target" ]; + }; + }; + + etcFiles = listToAttrs (map mkPeers enabledConfigs) // + listToAttrs (map mkMsh shTypes) // + listToAttrs (map mkUsh enabledSh) // + listToAttrs (map mkSec enabledSec) // + defaultCfg; + + systemdConfigs = listToAttrs (map mkSystemd enabledConfigs); + + in mkIf cfg.enable { + assertions = map (peerCfg: { + assertion = (peerCfg.configFile == null || peerCfg.config == ""); + message = '' + Please specify either + 'services.pppd.${peerCfg.name}.config' or + 'services.pppd.${peerCfg.name}.configFile'. + ''; + }) enabledConfigs; + + environment.etc = etcFiles; + systemd.services = systemdConfigs; + }; +} diff --git a/os/common/modules/stalwart-mail.nix b/os/common/modules/stalwart-mail.nix new file mode 100644 index 0000000..68e8400 --- /dev/null +++ b/os/common/modules/stalwart-mail.nix @@ -0,0 +1,163 @@ +{ config, lib, pkgs, ... }: let + cfg = config.services.stalwart-mail; + configFormat = pkgs.formats.toml { }; + configFile = configFormat.generate "stalwart-mail.toml" cfg.settings; + dataDir = "/var/lib/stalwart-mail"; + + readTOML = + path: + builtins.fromTOML (builtins.unsafeDiscardStringContext (lib.readFile path)); + recursiveUpdateList = + attrList: + lib.lists.foldr (a1: a2: lib.attrsets.recursiveUpdate a1 a2) {} attrList; + mkOverrideRec = + priority: + content: + if lib.isAttrs content then + lib.mapAttrs (_: v: mkOverrideRec priority v) content + else + lib.mkOverride priority content; + mkOptionDefaultRec = mkOverrideRec 1500; + + cfgPkg = pkgs.callPackage ../pkgs/stalwart-mail-config.nix {}; + cfgFiles = (readTOML "${cfgPkg}/config.toml").include.files; + settingsDefault = recursiveUpdateList (map (path: readTOML path) cfgFiles); +in { + options.services.stalwart-mail = { + enable = lib.mkEnableOption "the Stalwart all-in-one email server"; + package = lib.mkPackageOption pkgs "stalwart-mail" { }; + + loadCredential = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = []; + example = [ "dkim.private:/path/to/stalwart.private" ]; + description = '' + This can be used to pass secrets to the systemd service without adding them to + the nix store. + See the LoadCredential section of systemd.exec manual for more information. + ''; + }; + + settings = lib.mkOption { + inherit (configFormat) type; + default = { }; + description = '' + Configuration options for the Stalwart email server. + See <https://stalw.art/docs/category/configuration> for available options. + + By default, the module is configured to store everything locally. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + # set the default upstream settings + # assumptions + # 1. ./config.toml exists and only containts include.files and macros + # 2. no other files containts include.files + services.stalwart-mail.settings = mkOptionDefaultRec + (lib.attrsets.recursiveUpdate settingsDefault { + macros.base_path = dataDir; + server.run-as.user = {}; + server.run-as.group = {}; + global.tracing.method = "stdout"; + # outliers as of v0.6.0 + acme."letsencrypt".cache = "${cfg.settings.macros.base_path}/acme"; + }); + + assertions = let + m = cfg.settings.macros; + + mkMacroMessage = + opt: + "config.stalwart-mail.settings.macros.${opt} can not be empty"; + in [ + { + assertion = m ? host + && m.host != "" + && m.host != null; + message = mkMacroMessage "host"; + } + { + assertion = m ? default_domain + && m.default_domain != "" + && m.default_domain != null; + message = mkMacroMessage "default_domain"; + } + { + assertion = m ? default_directory + && m.default_directory != "" + && m.default_directory != null; + message = mkMacroMessage "default_directory"; + } + { + assertion = m ? default_store && + m.default_store != "" + && m.default_store != null; + message = mkMacroMessage "default_store"; + } + ]; + + systemd.services.stalwart-mail = { + wantedBy = [ "multi-user.target" ]; + after = [ "local-fs.target" "network.target" ]; + + serviceConfig = { + ExecStart = + "${cfg.package}/bin/stalwart-mail --config=${configFile}"; + + # Base from template resources/systemd/stalwart-mail.service + Type = "simple"; + LimitNOFILE = 65536; + KillMode = "process"; + KillSignal = "SIGINT"; + Restart = "on-failure"; + RestartSec = 5; + StandardOutput = "journal"; + StandardError = "journal"; + SyslogIdentifier = "stalwart-mail"; + + DynamicUser = true; + User = "stalwart-mail"; + StateDirectory = "stalwart-mail"; + LoadCredential = cfg.loadCredential; + + # Bind standard privileged ports + AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; + CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; + + # Hardening + DeviceAllow = [ "" ]; + LockPersonality = true; + MemoryDenyWriteExecute = true; + PrivateDevices = true; + PrivateUsers = false; # incompatible with CAP_NET_BIND_SERVICE + ProcSubset = "pid"; + PrivateTmp = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ "@system-service" "~@privileged" ]; + UMask = "0077"; + }; + }; + + # Make admin commands available in the shell + environment.systemPackages = [ cfg.package cfgPkg ]; + }; + + meta = { + maintainers = with lib.maintainers; [ happysalada pacien ]; + }; +} diff --git a/os/common/modules/tmux.nix b/os/common/modules/tmux.nix new file mode 100644 index 0000000..eeaafbb --- /dev/null +++ b/os/common/modules/tmux.nix @@ -0,0 +1,42 @@ +{ pkgs, ... }: + +{ + environment = { + systemPackages = with pkgs; [ tmux ]; + + etc."tmux.conf".text = '' + # base + set-option -g prefix C-a + unbind-key C-b + bind-key C-a send-prefix + set -g base-index 1 + setw -g pane-base-index 1 + set -g history-limit 10000 + + # vim + set -g mode-keys vi + bind -T copy-mode-vi v send -X begin-selection + bind -T copy-mode-vi y send -X copy-selection + bind -r C-w last-window + + bind -r h select-pane -L + bind -r j select-pane -D + bind -r k select-pane -U + bind -r l select-pane -R + + bind -r H resize-pane -L 5 + bind -r J resize-pane -D 5 + bind -r K resize-pane -U 5 + bind -r L resize-pane -R 5 + + bind -r C-h select-window -t :- + bind -r C-l select-window -t :+ + + # not eye candy + set -g status-style "bg=default fg=7" + set -g status-left "" + set -g status-right "" + set -g status-justify right + ''; + }; +} |