summaryrefslogtreecommitdiff
path: root/os/common/modules
diff options
context:
space:
mode:
Diffstat (limited to 'os/common/modules')
-rw-r--r--os/common/modules/dev.nix29
-rw-r--r--os/common/modules/nix.nix6
-rw-r--r--os/common/modules/pppd.nix277
-rw-r--r--os/common/modules/stalwart-mail.nix163
-rw-r--r--os/common/modules/tmux.nix42
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
+ '';
+ };
+}