about summary refs log tree commit diff stats
path: root/modules/by-name
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--modules/by-name/co/constants/module.nix16
-rw-r--r--modules/by-name/ng/nginx/module.nix5
-rw-r--r--modules/by-name/sh/sharkey/module.nix297
-rw-r--r--modules/by-name/st/stalwart-mail/module.nix106
-rw-r--r--modules/by-name/st/stalwart-mail/settings.nix34
-rw-r--r--modules/by-name/ta/taskchampion-sync/module.nix10
6 files changed, 411 insertions, 57 deletions
diff --git a/modules/by-name/co/constants/module.nix b/modules/by-name/co/constants/module.nix
index d6674bb..2115a37 100644
--- a/modules/by-name/co/constants/module.nix
+++ b/modules/by-name/co/constants/module.nix
@@ -25,6 +25,7 @@
   config.vhack.constants = {
     ids.uids = {
       # Keep this sorted with `!sort --numeric-sort --key=2 --field-separator="="`
+      systemd-coredump = 151; # GROUP
       opendkim = 221;
       mautrix-whatsapp = 222;
       etebase-server = 223;
@@ -43,16 +44,22 @@
       nscd = 330;
       sshd = 331;
       systemd-oom = 332;
+      resolvconf = 333; # GROUP
       nix-sync = 334;
       nextcloud = 335;
       redis-nextcloud = 336;
       taskchampion = 337;
+      stalwart-mail-certificates = 338; # GROUP
+      sharkey = 339;
+      redis-sharkey = 340;
 
       # As per the NixOS file, the uids should not be greater or equal to 400;
     };
     ids.gids = let
       inherit (config.vhack.constants.ids) uids;
     in {
+      # Please add your groups to the users and inherit them here.
+      # This avoids having an user/group id mismatch.
       inherit
         (uids)
         acme
@@ -76,12 +83,13 @@
         sshd
         stalwart-mail
         systemd-oom
+        sharkey
+        redis-sharkey
+        systemd-coredump # matches systemd-coredump user
+        resolvconf # This group is not matched to an user?
+        stalwart-mail-certificates # This group is used to connect nginx and stalwart-mail
         ;
 
-      # Keep this sorted with `!sort --numeric-sort --key=2 --field-separator="="`
-      systemd-coredump = 151; # matches systemd-coredump user
-      resolvconf = 333; # This group is not matched to an user?
-
       # The gid should match the uid. Thus should not be >= 400;
     };
   };
diff --git a/modules/by-name/ng/nginx/module.nix b/modules/by-name/ng/nginx/module.nix
index 1cb4e46..fa3337d 100644
--- a/modules/by-name/ng/nginx/module.nix
+++ b/modules/by-name/ng/nginx/module.nix
@@ -44,7 +44,10 @@ in {
     ];
 
     users = {
-      users.acme.uid = config.vhack.constants.ids.uids.acme;
+      users.acme = {
+        uid = config.vhack.constants.ids.uids.acme;
+        group = "acme";
+      };
       groups.acme.gid = config.vhack.constants.ids.gids.acme;
     };
 
diff --git a/modules/by-name/sh/sharkey/module.nix b/modules/by-name/sh/sharkey/module.nix
new file mode 100644
index 0000000..a2f5445
--- /dev/null
+++ b/modules/by-name/sh/sharkey/module.nix
@@ -0,0 +1,297 @@
+# Source: https://github.com/sodiboo/system/blob/b63c7b27f49043e8701b3ff5e1441cd27d5a2fff/sharkey/module.nix
+{
+  config,
+  lib,
+  pkgs,
+  vhackPackages,
+  ...
+}: let
+  cfg = config.vhack.sharkey;
+
+  createDB = cfg.database.host == "127.0.0.1" && cfg.database.createLocally;
+
+  settingsFormat = pkgs.formats.yaml {};
+  configFile = settingsFormat.generate "sharkey-config.yml" cfg.settings;
+in {
+  options.vhack.sharkey = {
+    enable = lib.mkEnableOption "sharkey";
+
+    fqdn = lib.mkOption {
+      description = "The fully qualified domain name of this instance.";
+      type = lib.types.str;
+      example = "sharkey.shonk.social";
+    };
+
+    package = lib.mkOption {
+      type = lib.types.package;
+      default = vhackPackages.sharkey;
+      defaultText = lib.literalExpression "vhackPackages.sharkey";
+      description = "Sharkey package to use.";
+    };
+
+    dataDirectory = lib.mkOption {
+      type = lib.types.path;
+      default = "/var/lib/sharkey";
+      description = "The directory where sharkey stores it's data.";
+
+      # This is already set in the package.
+      readOnly = true;
+    };
+
+    database = {
+      createLocally = lib.mkOption {
+        description = "Whether to enable local db creation.";
+        type = lib.types.bool;
+        default = true;
+      };
+
+      host = lib.mkOption {
+        type = lib.types.str;
+        default = "127.0.0.1";
+        description = "The database host.";
+      };
+
+      port = lib.mkOption {
+        type = lib.types.port;
+        default = 5432;
+        description = "The database port.";
+      };
+
+      name = lib.mkOption {
+        type = lib.types.str;
+        default = "sharkey";
+        description = "The database name in postgresql.";
+      };
+    };
+
+    settings = lib.mkOption {
+      inherit (settingsFormat) type;
+      default = {};
+      description = ''
+        Configuration for Sharkey, see
+        <link xlink:href="https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/.config/example.yml"/>
+        for supported settings.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [cfg.package];
+
+    vhack = {
+      nginx.enable = true;
+
+      sharkey.settings = {
+        id = "aidx";
+
+        url = "https://${cfg.fqdn}/";
+        port = 5312;
+
+        db = {
+          inherit (cfg.database) host port;
+          db = cfg.database.name;
+          user = cfg.database.name;
+          pass = "sharkey-password";
+        };
+        redis = {
+          path = config.services.redis.servers."sharkey".unixSocket;
+        };
+      };
+
+      persist.directories = [
+        {
+          directory = "${config.services.redis.servers."sharkey".settings.dir}";
+          user = "sharkey";
+          group = "redis-sharey";
+          mode = "0770";
+        }
+        {
+          directory = "${cfg.dataDirectory}";
+          user = "sharkey";
+          group = "sharkey";
+          mode = "0770";
+        }
+      ];
+    };
+
+    services = {
+      nginx.virtualHosts."${cfg.fqdn}" = {
+        locations."/" = {
+          proxyPass = "http://127.0.0.1:${toString cfg.settings.port}";
+          proxyWebsockets = true;
+        };
+
+        # proxy_set_header Host $host;
+        # proxy_http_version 1.1;
+        # proxy_redirect off;
+        #
+        # # If it's behind another reverse proxy or CDN, remove the following.
+        # proxy_set_header X-Real-IP $remote_addr;
+        # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        # proxy_set_header X-Forwarded-Proto https;
+        #
+        # # For WebSocket
+        # proxy_set_header Upgrade $http_upgrade;
+        # proxy_set_header Connection $connection_upgrade;
+        #
+        # # Cache settings
+        # proxy_cache cache1;
+        # proxy_cache_lock on;
+        # proxy_cache_use_stale updating;
+        # proxy_force_ranges on;
+        # add_header X-Cache $upstream_cache_status;
+
+        enableACME = true;
+        forceSSL = true;
+      };
+
+      postgresql = lib.mkIf createDB {
+        enable = true;
+        settings.port = cfg.database.port;
+        ensureUsers = [
+          {
+            inherit (cfg.database) name;
+            ensureDBOwnership = true;
+          }
+        ];
+        ensureDatabases = [cfg.database.name];
+      };
+
+      redis = {
+        servers."sharkey" = {
+          enable = true;
+
+          user = "sharkey";
+
+          # Disable TCP listening. (We have a UNIX socket)
+          port = 0;
+          bind = null;
+
+          settings = {
+            protected-mode = true;
+            enable-protected-configs = false;
+            enable-debug-command = false;
+            enable-module-command = false;
+
+            supervised = "systemd";
+            stop-writes-on-bgsave-error = true;
+            sanitize-dump-payload = "clients";
+          };
+        };
+      };
+    };
+
+    systemd.services.postgresql.postStart = ''
+      $PSQL -tAc "ALTER ROLE ${cfg.database.name} WITH ENCRYPTED PASSWORD 'sharkey-password';"
+    '';
+
+    systemd.services.sharkey = {
+      requires =
+        [
+          "redis-sharkey.service"
+          "network-online.target"
+        ]
+        ++ lib.optionals createDB ["postgresql.service"];
+
+      after =
+        [
+          "redis-sharkey.service"
+          "network-online.target"
+        ]
+        ++ lib.optionals createDB ["postgresql.service"];
+
+      wantedBy = ["multi-user.target"];
+
+      environment = {
+        MISSKEY_CONFIG_YML = "${configFile}";
+        NODE_ENV = "production";
+      };
+
+      serviceConfig = {
+        Type = "simple";
+
+        StateDirectory = "sharkey";
+        StateDirectoryMode = "0700";
+        CacheDirectory = "sharkey";
+        RuntimeDirectory = "sharkey";
+        RuntimeDirectoryMode = "0700";
+        ExecStart = "${lib.getExe cfg.package} migrateandstart";
+
+        TimeoutSec = 60;
+        Restart = "no";
+
+        StandardOutput = "journal";
+        StandardError = "journal";
+        SyslogIdentifier = "sharkey";
+
+        User = "sharkey";
+        Group = "sharkey";
+
+        # Bind standard privileged ports
+        AmbientCapabilities = [];
+        CapabilityBoundingSet = [];
+
+        ReadWritePaths = [
+          "${cfg.dataDirectory}"
+        ];
+
+        # Hardening
+        DeviceAllow = [""];
+        LockPersonality = true;
+        # Probably needed for v8's JIT (crashes with it on).
+        MemoryDenyWriteExecute = false;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProcSubset = "pid";
+        PrivateTmp = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        ProtectSystem = "strict";
+        RestrictAddressFamilies = [
+          "AF_UNIX" # Local communication                        unix(7)
+          "AF_INET" # IPv4 Internet protocols                    ip(7)
+          "AF_INET6" # IPv6 Internet protocols                   ipv6(7)
+          # Needed for nodes `os.networkInterfaces()` function.
+          "AF_NETLINK" # Kernel user interface device            netlink(7)
+        ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged"
+          "~@mount"
+        ];
+        UMask = "0077";
+      };
+    };
+
+    users = {
+      groups.sharkey = {
+        gid = config.vhack.constants.ids.gids.sharkey;
+      };
+      users.sharkey = {
+        isSystemUser = true;
+        group = "sharkey";
+        uid = config.vhack.constants.ids.uids.sharkey;
+        home = cfg.package;
+        packages = [cfg.package];
+      };
+
+      groups.redis-sharkey = {
+        gid = config.vhack.constants.ids.gids.redis-sharkey;
+      };
+      users.redis-sharkey = {
+        group = "redis-sharkey";
+        uid = config.vhack.constants.ids.uids.redis-sharkey;
+      };
+    };
+  };
+}
diff --git a/modules/by-name/st/stalwart-mail/module.nix b/modules/by-name/st/stalwart-mail/module.nix
index 1e39e81..396116d 100644
--- a/modules/by-name/st/stalwart-mail/module.nix
+++ b/modules/by-name/st/stalwart-mail/module.nix
@@ -10,6 +10,33 @@
 
   configFormat = pkgs.formats.toml {};
   configFile = configFormat.generate "stalwart-mail.toml" topCfg.settings;
+
+  hashedPassword = let
+    prefix = pre: lib.types.strMatching "^${lib.strings.escapeRegex pre}.*";
+  in
+    lib.types.oneOf [
+      (prefix "$argon2")
+      (prefix "$pbkdf2")
+      (prefix "$scrypt")
+      (prefix "$2") # bcrypt
+      (prefix "$6$") # sha-512
+      (prefix "$5$") # sha-256
+      (prefix "$sha1")
+      (prefix "$1") # md5
+      (prefix "_") # BSDi crypt
+      (prefix "{SHA}") # base64 sha
+      (prefix "{SSHA}") # base64 salted sha
+
+      # unix crypt
+      (prefix "{CRYPT}")
+      (prefix "{crypt}")
+
+      # Plain text
+      (prefix "{PLAIN}")
+      (prefix "{plain}")
+      (prefix "{CLEAR}")
+      (prefix "{clear}")
+    ];
 in {
   imports = [
     ./settings.nix
@@ -24,14 +51,20 @@ in {
       description = ''
         Email address to advertise as administrator. This is the address, where dkim, spv
         etc. refusal reports are sent to.
-
-        The format should be: `mailto:<name>@<domain>`
       '';
       type = lib.types.str;
-      example = "mailto:dmarc+rua@example.com";
+      example = "dmarc+rua@example.com";
       default = "";
     };
 
+    initialAdminPassword = lib.mkOption {
+      type = hashedPassword;
+      description = ''
+        The hash of the password for the admin account, used to bootstrap account
+        creation.
+      '';
+    };
+
     fqdn = lib.mkOption {
       type = lib.types.str;
       example = "mail.foss-syndicate.org";
@@ -61,7 +94,7 @@ in {
           };
 
           secret = lib.mkOption {
-            type = lib.types.str;
+            type = hashedPassword;
             description = ''
               Sets the password for the user account.
               Passwords can be stored hashed or in plain text (not recommended).
@@ -160,25 +193,16 @@ in {
         # However, this decision could obviously be reversed in the future. <2025-02-08>
         enable = false;
         inherit (cfg) package;
-        # dataDir = cfg.dataDirectory;
       };
 
-      # FIXME(@bpeetz): This is currently needed for a successful acme http-01 challenge.
-      # We could also use the DNS challenge. <2025-03-01>
       nginx.virtualHosts."${cfg.fqdn}" = {
-        enableACME = false;
-        extraConfig =
-          # This is copied directly from the nixos nginx module.
-          # Rule for legitimate ACME Challenge requests (like /.well-known/acme-challenge/xxxxxxxxx)
-          # We use ^~ here, so that we don't check any regexes (which could
-          # otherwise easily override this intended match accidentally).
-          ''
-            location ^~ /.well-known/acme-challenge/ {
-              root ${config.security.acme.certs.${cfg.fqdn}.webroot};
-              auth_basic off;
-              auth_request off;
-            }
-          '';
+        locations."/" = {
+          proxyPass = "http://${builtins.elemAt config.services.stalwart-mail.settings.server.listener.http.bind 0}";
+          recommendedProxySettings = true;
+        };
+
+        useACMEHost = "${cfg.fqdn}";
+        forceSSL = true;
       };
 
       redis = {
@@ -209,7 +233,7 @@ in {
     security.acme.certs = {
       "${cfg.fqdn}" = {
         domain = cfg.fqdn;
-        group = "stalwart-mail";
+        group = "stalwart-mail-certificates";
       };
     };
 
@@ -249,20 +273,31 @@ in {
     # service is restarted on a potentially large number of files.
     # That would cause unnecessary and unwanted delays.
     users = {
-      groups.stalwart-mail = {
-        gid = config.vhack.constants.ids.gids.stalwart-mail;
-      };
-      users.stalwart-mail = {
-        isSystemUser = true;
-        group = "stalwart-mail";
-        uid = config.vhack.constants.ids.uids.stalwart-mail;
-      };
-      groups.redis-stalwart-mail = {
-        gid = config.vhack.constants.ids.gids.redis-stalwart-mail;
+      groups = {
+        stalwart-mail = {
+          gid = config.vhack.constants.ids.gids.stalwart-mail;
+        };
+        stalwart-mail-certificates = {
+          gid = config.vhack.constants.ids.gids.stalwart-mail-certificates;
+        };
+        redis-stalwart-mail = {
+          gid = config.vhack.constants.ids.gids.redis-stalwart-mail;
+        };
       };
-      users.redis-stalwart-mail = {
-        group = "redis-stalwart-mail";
-        uid = config.vhack.constants.ids.uids.redis-stalwart-mail;
+      users = {
+        nginx = {
+          extraGroups = ["stalwart-mail-certificates"];
+        };
+        stalwart-mail = {
+          isSystemUser = true;
+          group = "stalwart-mail";
+          uid = config.vhack.constants.ids.uids.stalwart-mail;
+          extraGroups = ["stalwart-mail-certificates"];
+        };
+        redis-stalwart-mail = {
+          group = "redis-stalwart-mail";
+          uid = config.vhack.constants.ids.uids.redis-stalwart-mail;
+        };
       };
     };
 
@@ -321,8 +356,7 @@ in {
             ${lib.getExe cfg.package} --config="$CACHE_DIRECTORY/mutable_config_file.toml"
           '';
 
-          Restart = "on-failure";
-          RestartSec = 5;
+          Restart = "no";
 
           KillMode = "process";
           KillSignal = "SIGINT";
diff --git a/modules/by-name/st/stalwart-mail/settings.nix b/modules/by-name/st/stalwart-mail/settings.nix
index 17f045d..765d8db 100644
--- a/modules/by-name/st/stalwart-mail/settings.nix
+++ b/modules/by-name/st/stalwart-mail/settings.nix
@@ -100,7 +100,7 @@ in {
         from-name = "'TLS Report'";
         from-address = "'noreply-tls@${cfg.fqdn}'";
         org-name = "'Foss Syndicate Mail Handling'";
-        contact-info = "'${cfg.admin}'";
+        contact-info = "'mailto:${cfg.admin}'";
         send = "daily";
         max-size = 26214400; # 25 MiB
         sign = lib.mkIf (cfg.security != null) "'${cfg.fqdn}'";
@@ -110,7 +110,7 @@ in {
           from-name = "'DMARC Report'";
           from-address = "'noreply-dmarc@${cfg.fqdn}'";
           org-name = "'Foss Syndicate Mail Handling'";
-          contact-info = "'${cfg.admin}'";
+          contact-info = "'mailto:${cfg.admin}'";
           send = "weekly";
           max-size = 26214400; # 25MiB
           sign = lib.mkIf (cfg.security != null) "'${cfg.fqdn}'";
@@ -344,13 +344,13 @@ in {
       hostname = cfg.fqdn;
 
       listener = {
-        # TODO(@bpeetz): Add this <2025-02-08>
-        # # HTTP (used for jmap)
-        # "http" = {
-        #   bind = ["[::]:8080"];
-        #   protocol = "http";
-        #   tls.implicit = true;
-        # };
+        # HTTP (used for jmap)
+        "http" = {
+          bind = ["127.0.0.1:8112"];
+          protocol = "http";
+          # handled by ngnix
+          tls.implicit = false;
+        };
 
         # IMAP
         "imap" = {
@@ -406,11 +406,12 @@ in {
         certificate = "default";
       };
 
-      # TODO(@bpeetz): Configure that <2025-02-07>
-      # http = {
-      #   url = "";
-      #   allowed-endpoint = ["404"];
-      # };
+      http = {
+        url = "protocol + '://' + config_get('server.hostname') + ':' + local_port";
+
+        # We are behind a nginx proxy, and can thus trust this header.
+        use-x-forwarded = true;
+      };
 
       auto-ban = {
         # Ban if the same IP fails to login 10 times in a day
@@ -540,6 +541,11 @@ in {
       };
     };
 
+    authentication.fallback-admin = {
+      user = cfg.admin;
+      secret = cfg.initialAdminPassword;
+    };
+
     certificate = {
       "default" = {
         cert = "%{file:${config.security.acme.certs.${cfg.fqdn}.directory}/fullchain.pem}%";
diff --git a/modules/by-name/ta/taskchampion-sync/module.nix b/modules/by-name/ta/taskchampion-sync/module.nix
index 2fa0a3f..1870186 100644
--- a/modules/by-name/ta/taskchampion-sync/module.nix
+++ b/modules/by-name/ta/taskchampion-sync/module.nix
@@ -4,6 +4,7 @@
   ...
 }: let
   cfg = config.vhack.taskchampion-sync;
+  dataDirectory = "/var/lib/taskchampion-sync-server";
 in {
   options.vhack.taskchampion-sync = {
     enable = lib.mkEnableOption "taskchampion-sync";
@@ -16,13 +17,18 @@ in {
     };
 
     vhack.persist.directories = [
-      "/var/lib/taskchampion-sync-server"
+      {
+        directory = dataDirectory;
+        user = "taskchampion";
+        group = "taskchampion";
+        mode = "0700";
+      }
     ];
 
     services.taskchampion-sync-server = {
       enable = true;
       openFirewall = true;
-      dataDir = "/var/lib/taskchampion-sync-server";
+      dataDir = dataDirectory;
     };
   };
 }