{ 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.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 = [ "" (builtins.concatStringsSep " " [ "${lib.getExe cfg.server}" "serve" "--port ${builtins.toString 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:${builtins.toString cfg.port}"; recommendedProxySettings = true; proxyWebsockets = true; }; root = "${cfg.webFrontEnd}"; enableACME = true; forceSSL = true; }; }; }