{ config, lib, pkgs-unstable, ... }: let cfg = config.services.sillytavern; fqdn = "${cfg.subdomain}.${config.networking.domain}"; defaultUser = "sillytavern"; defaultGroup = "sillytavern"; in { options.services.sillytavern = { enable = lib.mkEnableOption "sillytavern"; subdomain = lib.mkOption { type = lib.types.str; default = "sillytavern"; }; user = lib.mkOption { type = lib.types.str; default = defaultUser; description = '' User account under which the web-application run. ''; }; group = lib.mkOption { type = lib.types.str; default = defaultGroup; description = '' Group account under which the web-application run. ''; }; package = lib.mkOption { type = lib.types.package; default = pkgs-unstable.sillytavern; description = '' SillyTavern package to use. ''; }; configFile = lib.mkOption { type = lib.types.path; default = "${pkgs-unstable.sillytavern}/lib/node_modules/sillytavern/config.yaml"; defaultText = lib.literalExpression "\${pkgs.sillytavern}/lib/node_modules/sillytavern/config.yaml"; description = '' Path to the SillyTavern configuration file. ''; }; port = lib.mkOption { type = lib.types.nullOr lib.types.port; default = null; example = 8045; description = '' Port on which SillyTavern will listen. ''; }; listenAddressIPv4 = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; example = "127.0.0.1"; description = '' Specific IPv4 address to listen to. ''; }; listenAddressIPv6 = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; example = "::1"; description = '' Specific IPv6 address to listen to. ''; }; listen = lib.mkOption { type = lib.types.nullOr lib.types.bool; default = null; example = true; description = '' Whether to listen on all network interfaces. ''; }; whitelist = lib.mkOption { type = lib.types.nullOr lib.types.bool; default = null; example = true; description = '' Enables whitelist mode. ''; }; }; config = lib.mkIf cfg.enable { systemd.services.sillytavern = { description = "Silly Tavern"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; # required by sillytavern's extension manager path = [ pkgs-unstable.git ]; environment.XDG_DATA_HOME = "%S"; serviceConfig = { Type = "simple"; ExecStart = let f = x: name: lib.optional (x != null) "--${name}=${builtins.toString x}"; in lib.concatStringsSep " " ( [ "${lib.getExe cfg.package}" ] ++ f cfg.port "port" ++ f cfg.listen "listen" ++ f cfg.listenAddressIPv4 "listenAddressIPv4" ++ f cfg.listenAddressIPv6 "listenAddressIPv6" ++ f cfg.whitelist "whitelist" ); User = cfg.user; Group = cfg.group; Restart = "always"; StateDirectory = "SillyTavern"; BindPaths = [ "%S/SillyTavern/extensions:${cfg.package}/lib/node_modules/sillytavern/public/scripts/extensions/third-party" ]; # Security hardening CapabilityBoundingSet = [ "" ]; LockPersonality = true; NoNewPrivileges = true; PrivateDevices = true; PrivateTmp = true; ProtectClock = true; ProtectControlGroups = true; ProtectHome = true; ProtectHostname = true; ProtectKernelLogs = true; ProtectKernelModules = true; ProtectKernelTunables = true; ProtectProc = "invisible"; ProtectSystem = "strict"; }; }; users.users.${cfg.user} = lib.mkIf (cfg.user == defaultUser) { description = "sillytavern service user"; isSystemUser = true; inherit (cfg) group; }; users.groups.${cfg.group} = lib.mkIf (cfg.group == defaultGroup) { }; systemd.tmpfiles.settings.sillytavern = { "/var/lib/SillyTavern/data".d = { mode = "0700"; inherit (cfg) user group; }; "/var/lib/SillyTavern/extensions".d = { mode = "0700"; inherit (cfg) user group; }; "/var/lib/SillyTavern/config.yaml"."L+" = { mode = "0600"; argument = cfg.configFile; inherit (cfg) user group; }; }; services.webserver = { enable = lib.mkDefault true; vHosts.${fqdn} = { proxyBuffering = false; locations."/" = { proxyPort = cfg.port; extraConfig = '' proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; ''; }; }; }; }; }