summary refs log tree commit diff stats
path: root/module/default.nix
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-03-20 01:03:33 +0100
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-03-20 01:03:33 +0100
commit9a20fb5c803bf4e3e72a4073a19c6c30576adda0 (patch)
tree2ed4a520afcabebba0fd48c35db22b084fad7989 /module/default.nix
downloadnix-9a20fb5c803bf4e3e72a4073a19c6c30576adda0.zip
feat: Initial commit
Diffstat (limited to 'module/default.nix')
-rw-r--r--module/default.nix140
1 files changed, 140 insertions, 0 deletions
diff --git a/module/default.nix b/module/default.nix
new file mode 100644
index 0000000..608559b
--- /dev/null
+++ b/module/default.nix
@@ -0,0 +1,140 @@
+{
+  rocie-mobile,
+  rocie-server,
+}: {
+  lib,
+  config,
+  ...
+}: let
+  cfg = config.rocie;
+in {
+  options.rocie = {
+    enable = lib.mkEnableOption "rocie";
+    domain = lib.mkOption {
+      description = "Under which domain (vhost) to deploy rocie?";
+      type = lib.types.str;
+    };
+
+    port = lib.mkOption {
+      description = "Which port to use";
+      type = lib.types.int.port;
+      default = 8543;
+    };
+    address = lib.mkOption {
+      description = "Which address to use";
+      type = lib.types.str;
+      default = "127.0.0.1";
+    };
+
+    dbPath = lib.mkOption {
+      description = "Where to store the database";
+      type = lib.types.str;
+      default = "$STATE_DIRECTORY/storage.db";
+    };
+
+    secretKeyFile = lib.mkOption {
+      description = "Which file to use as signing key for user log-ins";
+      type = lib.types.nullOr lib.types.str;
+    };
+
+    webFrontEnd = lib.mkOption {
+      description = "Which package to use for the web front end";
+      type = lib.types.package;
+      default = rocie-mobile;
+    };
+    server = lib.mkOption {
+      description = "Which package to use for the server";
+      type = lib.types.package;
+      default = rocie-server;
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.packages = [cfg.server];
+    systemd.services.rocie = {
+      wantedBy = ["default.target"];
+      serviceConfig =
+        {
+          StateDirectory = "rocie";
+
+          # Hardening
+          LockPersonality = true;
+          MemoryDenyWriteExecute = true;
+          NoNewPrivileges = true;
+          PrivateDevices = true;
+          PrivateIPC = true;
+          PrivateTmp = true;
+          ProcSubset = "pid";
+          ProtectClock = true;
+          ProtectControlGroups = true;
+          ProtectHome = true;
+          ProtectHostname = true;
+          ProtectKernelLogs = true;
+          ProtectKernelModules = true;
+          ProtectKernelTunables = true;
+          ProtectProc = "invisible";
+          ProtectSystem = "full";
+          RemoveIPC = true;
+          RestrictAddressFamilies = [
+            "AF_INET"
+            "AF_INET6"
+          ];
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          SystemCallArchitectures = "native";
+          SystemCallFilter = [
+            "~@mount"
+            "~@swap"
+            "~@resources"
+            "~@reboot"
+            "~@raw-io"
+            "~@obsolete"
+            "~@module"
+            "~@debug"
+            "~@cpu-emulation"
+            "~@clock"
+            "~@privileged"
+          ];
+          UMask = "0027";
+
+          ExecStart = [
+            ""
+            "${lib.getExe cfg.server} \
+                        serve \
+                        --port ${cfg.port} \
+                        ${lib.strings.optionalString (cfg.secretKeyFile != null) "--secret-key-file ${cfg.secretKeyFile}"} \
+                        --host ${cfg.address} \
+                        --db-path ${cfg.dbPath}"
+          ];
+        }
+        // (
+          if (cfg.port < 1024)
+          then {
+            AmbientCapabilities = ["CAP_NET_BIND_SERVICE"];
+            CapabilityBoundingSet = ["CAP_NET_BIND_SERVICE"];
+          }
+          else {
+            # A private user cannot have process capabilities on the host's user
+            # namespace and thus CAP_NET_BIND_SERVICE has no effect.
+            PrivateUsers = true;
+            CapabilityBoundingSet = false;
+          }
+        );
+    };
+
+    services.nginx.virtualHosts."${cfg.domain}" = {
+      locations."/api" = {
+        proxyPass = "http://127.0.0.1:${cfg.port}";
+
+        recommendedProxySettings = true;
+        proxyWebsockets = true;
+      };
+
+      root = "${cfg.webFrontEnd}";
+
+      enableACME = true;
+      forceSSL = true;
+    };
+  };
+}