summaryrefslogtreecommitdiffstats
path: root/module
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
downloadnix-9a20fb5c803bf4e3e72a4073a19c6c30576adda0.zip
feat: Initial commit
Diffstat (limited to 'module')
-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;
+ };
+ };
+}