diff --git a/extra/admin-api/Interop/Spacebar.Interop.Replication.Abstractions/ISpacebarReplication.cs b/extra/admin-api/Interop/Spacebar.Interop.Replication.Abstractions/ISpacebarReplication.cs index 95ee07f39..9398eedfd 100644 --- a/extra/admin-api/Interop/Spacebar.Interop.Replication.Abstractions/ISpacebarReplication.cs +++ b/extra/admin-api/Interop/Spacebar.Interop.Replication.Abstractions/ISpacebarReplication.cs @@ -3,4 +3,4 @@ public interface ISpacebarReplication { public Task InitializeAsync(); public Task SendAsync(ReplicationMessage message); -} \ No newline at end of file +} diff --git a/extra/admin-api/Models/Spacebar.Models.Db/Models/User.cs b/extra/admin-api/Models/Spacebar.Models.Db/Models/User.cs index 70fe581d0..f0aa08790 100644 --- a/extra/admin-api/Models/Spacebar.Models.Db/Models/User.cs +++ b/extra/admin-api/Models/Spacebar.Models.Db/Models/User.cs @@ -89,7 +89,7 @@ public partial class User [Column("email", TypeName = "character varying")] public string? Email { get; set; } - [Column("flags", TypeName = "character varying")] + [Column("flags")] public ulong Flags { get; set; } [Column("public_flags")] diff --git a/extra/admin-api/Spacebar.UApi/Services/TemplateImportService.cs b/extra/admin-api/Spacebar.UApi/Services/TemplateImportService.cs index ae7b86a2c..89c2bfbc6 100644 --- a/extra/admin-api/Spacebar.UApi/Services/TemplateImportService.cs +++ b/extra/admin-api/Spacebar.UApi/Services/TemplateImportService.cs @@ -9,5 +9,5 @@ public class TemplateImportService(SpacebarDbContext db) { return ""; } - public async Task<> + // public async Task<> } \ No newline at end of file diff --git a/nix/modules/default/cs/admin-api.nix b/nix/modules/default/cs/admin-api.nix new file mode 100644 index 000000000..a4e0a4d75 --- /dev/null +++ b/nix/modules/default/cs/admin-api.nix @@ -0,0 +1,163 @@ +self: +{ + config, + lib, + pkgs, + spacebar, + ... +}: + +let + secrets = import ../secrets.nix { inherit lib config; }; + cfg = config.services.spacebarchat-server; + jsonFormat = pkgs.formats.json { }; +in +{ + imports = [ ]; + options.services.spacebarchat-server.adminApi = lib.mkOption { + default = { }; + description = "Configuration for admin api."; + type = lib.types.submodule { + options = { + enable = lib.mkEnableOption "Enable admin api."; + extraConfiguration = lib.mkOption { + type = jsonFormat.type; + default = import ./default-appsettings-json.nix; + description = "Extra appsettings.json configuration for the gateway offload daemon."; + }; + }; + }; + }; + + config = lib.mkIf cfg.adminApi.enable ( + let + makeServerTsService = ( + conf: + lib.recursiveUpdate + (lib.recursiveUpdate { + documentation = [ "https://docs.spacebar.chat/" ]; + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; + environment = secrets.systemdEnvironment; + serviceConfig = { + LoadCredential = secrets.systemdLoadCredentials; + + User = "spacebarchat"; + Group = "spacebarchat"; + DynamicUser = false; + + LockPersonality = true; + NoNewPrivileges = true; + + ProtectClock = true; + ProtectControlGroups = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + PrivateDevices = true; + PrivateMounts = true; + PrivateUsers = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_UNIX" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "~@privileged" + "@chown" # Required for copying files with FICLONE, apparently. + ]; + CapabilityBoundingSet = [ + "~CAP_SYS_ADMIN" + "~CAP_AUDIT_*" + "~CAP_NET_(BIND_SERVICE|BROADCAST|RAW)" + "~CAP_NET_ADMIN" # No use for this as we don't currently use iptables for enforcing instance bans + "~CAP_SYS_TIME" + "~CAP_KILL" + "~CAP_(DAC_*|FOWNER|IPC_OWNER)" + "~CAP_LINUX_IMMUTABLE" + "~CAP_IPC_LOCK" + "~CAP_BPF" + "~CAP_SYS_TTY_CONFIG" + "~CAP_SYS_BOOT" + "~CAP_SYS_CHROOT" + "~CAP_BLOCK_SUSPEND" + "~CAP_LEASE" + "~CAP_(CHOWN|FSETID|FSETFCAP)" # Check if we need CAP_CHOWN for `fchown()` (FICLONE)? + "~CAP_SET(UID|GID|PCAP)" + "~CAP_MAC_*" + "~CAP_SYS_PTRACE" + "~CAP_SYS_(NICE|RESOURCE)" + "~CAP_SYS_RAWIO" + "~CAP_SYSLOG" + ]; + RestrictSUIDSGID = true; + + WorkingDirectory = "/var/lib/spacebar"; + StateDirectory = "spacebar"; + StateDirectoryMode = "0750"; + RuntimeDirectory = "spacebar"; + RuntimeDirectoryMode = "0750"; + ReadWritePaths = [ cfg.cdnPath ]; + NoExecPaths = [ cfg.cdnPath ]; + + Restart = "on-failure"; + RestartSec = 10; + StartLimitBurst = 5; + UMask = "077"; + } + // lib.optionalAttrs (cfg.databaseFile != null) { EnvironmentFile = cfg.databaseFile; }; + } conf) + { + } + ); + in + { + assertions = [ + { + assertion = + cfg.adminApi.extraConfiguration ? ConnectionStrings + && cfg.adminApi.extraConfiguration.ConnectionStrings ? Spacebar + && cfg.adminApi.extraConfiguration.ConnectionStrings.Spacebar != null; + message = '' + Admin API: Setting a database connection string in extraConfiguration (`extraConfiguration.ConnectionStrings.Spacebar`) is required when using C# services. + Example: Host=127.0.0.1; Username=Spacebar; Password=SuperSecurePassword12; Database=spacebar; Port=5432; Include Error Detail=true; Maximum Pool Size=1000; Command Timeout=6000; Timeout=600; + ''; + } + ]; + + services.spacebarchat-server.settings.admin = { + endpointPublic = "http${if cfg.adminApiEndpoint.useSsl then "s" else ""}://${cfg.adminApiEndpoint.host}:${toString cfg.adminApiEndpoint.publicPort}"; + endpointPrivate = "http://127.0.0.1:${builtins.toString cfg.adminApiEndpoint.localPort}"; + }; + + systemd.services.spacebar-admin-api = makeServerTsService { + description = "Spacebar Server - Admin API"; + environment = builtins.mapAttrs (_: val: builtins.toString val) ( + { + # things we set by default... + EVENT_TRANSMISSION = "unix"; + EVENT_SOCKET_PATH = "/run/spacebar/"; + } + // cfg.extraEnvironment + // { + # things we force... + # CONFIG_PATH = configFile; + CONFIG_READONLY = 1; + ASPNETCORE_URLS = "http://127.0.0.1:${toString cfg.adminApiEndpoint.localPort}"; + STORAGE_LOCATION = cfg.cdnPath; + APPSETTINGS_PATH = jsonFormat.generate "appsettings.spacebar-adminapi.json" (lib.recursiveUpdate (import ./default-appsettings-json.nix) cfg.adminApi.extraConfiguration); + } + ); + serviceConfig = { + ExecStart = "${self.packages.${pkgs.stdenv.hostPlatform.system}.Spacebar-AdminApi}/bin/Spacebar.AdminApi"; + }; + }; + } + ); +} diff --git a/nix/modules/default/cs/default-appsettings-json.nix b/nix/modules/default/cs/default-appsettings-json.nix index e6bb6323f..1ed1c28c0 100644 --- a/nix/modules/default/cs/default-appsettings-json.nix +++ b/nix/modules/default/cs/default-appsettings-json.nix @@ -12,5 +12,8 @@ PrivateKeyPath = "./jwt.key"; PublicKeyPath = "./jwt.key.pub"; }; + UnixSocketReplication = { + SocketDir = "/run/spacebar"; + }; }; } diff --git a/nix/modules/default/cs/gateway-offload-cs.nix b/nix/modules/default/cs/gateway-offload-cs.nix index 54e84bc37..76f97ddeb 100644 --- a/nix/modules/default/cs/gateway-offload-cs.nix +++ b/nix/modules/default/cs/gateway-offload-cs.nix @@ -133,7 +133,7 @@ in && cfg.gatewayOffload.extraConfiguration.ConnectionStrings ? Spacebar && cfg.gatewayOffload.extraConfiguration.ConnectionStrings.Spacebar != null; message = '' - Setting a database connection string in extraConfiguration (`extraConfiguration.ConnectionStrings.Spacebar`) is required when using C# services. + Gateway Offload: Setting a database connection string in extraConfiguration (`extraConfiguration.ConnectionStrings.Spacebar`) is required when using C# services. Example: Host=127.0.0.1; Username=Spacebar; Password=SuperSecurePassword12; Database=spacebar; Port=5432; Include Error Detail=true; Maximum Pool Size=1000; Command Timeout=6000; Timeout=600; ''; } @@ -162,6 +162,7 @@ in CONFIG_READONLY = 1; ASPNETCORE_URLS = "http://127.0.0.1:${toString cfg.gatewayOffload.listenPort}"; STORAGE_LOCATION = cfg.cdnPath; + APPSETTINGS_PATH = jsonFormat.generate "appsettings.spacebar-gateway-offload.json" (lib.recursiveUpdate (import ./default-appsettings-json.nix) cfg.gatewayOffload.extraConfiguration); } ); serviceConfig = { diff --git a/nix/modules/default/default.nix b/nix/modules/default/default.nix index c4f9e4cd8..ac20d3cd8 100644 --- a/nix/modules/default/default.nix +++ b/nix/modules/default/default.nix @@ -46,6 +46,7 @@ in ./integration-nginx.nix ./users.nix (import ./cs/gateway-offload-cs.nix self) + (import ./cs/admin-api.nix self) ]; options.services.spacebarchat-server = let diff --git a/nix/modules/default/integration-nginx.nix b/nix/modules/default/integration-nginx.nix index 70e4b2e13..718e57286 100644 --- a/nix/modules/default/integration-nginx.nix +++ b/nix/modules/default/integration-nginx.nix @@ -38,7 +38,13 @@ in proxyPass = "http://127.0.0.1:${toString cfg.cdnEndpoint.localPort}/"; }; }; - + "${cfg.adminApiEndpoint.host}" = lib.mkIf cfg.adminApi.enable { + enableACME = cfg.adminApiEndpoint.useSsl; + forceSSL = cfg.adminApiEndpoint.useSsl; + locations."/" = { + proxyPass = "http://127.0.0.1:${toString cfg.adminApiEndpoint.localPort}/"; + }; + }; }; }; }; diff --git a/nix/testVm/configuration.nix b/nix/testVm/configuration.nix index 2b1c19a5e..71c2451f7 100644 --- a/nix/testVm/configuration.nix +++ b/nix/testVm/configuration.nix @@ -15,9 +15,11 @@ in services.nginx.virtualHosts."api.sb.localhost" = nginxTestSigning; services.nginx.virtualHosts."gw.sb.localhost" = nginxTestSigning; services.nginx.virtualHosts."cdn.sb.localhost" = nginxTestSigning; + services.nginx.virtualHosts."admin.sb.localhost" = nginxTestSigning; services.spacebarchat-server = let + csConnectionString = "Host=127.0.0.1; Username=postgres; Password=postgres; Database=spacebar; Port=5432; Include Error Detail=true; Maximum Pool Size=1000; Command Timeout=6000; Timeout=600;"; cfg = { enable = true; apiEndpoint = { @@ -38,12 +40,22 @@ in localPort = 3003; publicPort = 8080; }; + adminApiEndpoint = { + useSsl = false; + host = "admin.sb.localhost"; + localPort = 3005; + publicPort = 8080; + }; nginx.enable = true; serverName = "sb.localhost"; gatewayOffload = { enable = true; enableGuildSync = true; - extraConfiguration.ConnectionStrings.Spacebar = "Host=127.0.0.1; Username=Spacebar; Password=postgres; Database=spacebar; Port=5432; Include Error Detail=true; Maximum Pool Size=1000; Command Timeout=6000; Timeout=600;"; + extraConfiguration.ConnectionStrings.Spacebar = csConnectionString; + }; + adminApi = { + enable = true; + extraConfiguration.ConnectionStrings.Spacebar = csConnectionString; }; extraEnvironment = { DATABASE = "postgres://postgres:postgres@127.0.0.1/spacebar";