{ tgcMaintainers, ... }: { config, pkgs, lib, ... }: let inherit (lib) types; cfg = config.tgc.services.gotosocial; settingsFormat = pkgs.formats.yaml { }; defaultSettings = { # Defaults application-name = "gotosocial"; protocol = "https"; bind-address = "127.0.0.1"; port = 8080; user = cfg.user; group = cfg.group; storage-local-base-path = cfg.stateDir; db-type = "sqlite"; db-address = "${cfg.stateDir}/database.sqlite"; }; in { meta.maintainers = [ tgcMaintainers.zaphyra ]; options.tgc.services.gotosocial = { enable = lib.mkEnableOption "ActivityPub social network server"; package = lib.mkPackageOption pkgs "gotosocial" { }; user = lib.mkOption { type = types.str; default = "gotosocial"; description = "The user gotosocial should run as"; }; group = lib.mkOption { type = types.str; default = "gotosocial"; description = "The group gotosocial should run as"; }; stateDir = lib.mkOption { type = types.str; default = "/var/lib/gotosocial"; readOnly = true; }; setupPostgresqlDB = lib.mkOption { type = lib.types.bool; default = false; description = '' Whether to setup a local postgres database and populate the `db-type` fields in `services.gotosocial.settings`. ''; }; environmentFile = lib.mkOption { type = with types; nullOr path; default = null; description = '' File path containing environment variables for configuring the GoToSocial service in the format of an EnvironmentFile as described by {manpage}`systemd.exec(5)`. This option could be used to pass sensitive configuration to the GoToSocial daemon. Please refer to the Environment Variables section in the [documentation](https://docs.gotosocial.org/en/latest/configuration/#environment-variables). ''; }; settings = lib.mkOption { default = defaultSettings; description = '' Contents of the GoToSocial YAML config. Please refer to the [documentation](https://docs.gotosocial.org/en/latest/configuration/) and [example config](https://github.com/superseriousbusiness/gotosocial/blob/main/example/config.yaml). Please note that the `host` option cannot be changed later so it is important to configure this correctly before you start GoToSocial. ''; type = types.submodule { freeformType = settingsFormat.type; options = { host = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; description = '' Hostname that this server will be reachable at. Defaults to localhost for local testing, but you should *definitely* change this when running for real, or your server won't work at all. DO NOT change this after your server has already run once, or you will break things! ''; }; port = lib.mkOption { type = lib.types.port; default = 8080; description = '' Int. Listen port for the GoToSocial webserver + API. If you're running behind a reverse proxy and/or in a docker, container, just set this to whatever you like (or leave the default), and make sure it's forwarded properly. If you are running with built-in letsencrypt enabled, and running GoToSocial directly on a host machine, you will probably want to set this to 443 (standard https port), unless you have other services already using that port. This *MUST NOT* be the same as the letsencrypt port specified below, unless letsencrypt is turned off. ''; }; }; }; }; }; config = lib.mkIf cfg.enable ( let configFile = settingsFormat.generate "gotosocial-config.yaml" cfg.settings; in { assertions = [ { assertion = cfg.settings.host or null != null; message = "You have to define a hostname for GoToSocial (`tgc.services.gotosocial.settings.host`), it cannot be changed later without starting over!"; } ]; tgc.services.gotosocial.settings = (lib.mapAttrs (name: lib.mkDefault) defaultSettings) // { web-template-base-dir = "${cfg.package}/share/gotosocial/web/template/"; web-asset-base-dir = "${cfg.package}/share/gotosocal/web/assets/"; } // (lib.optionalAttrs cfg.setupPostgresqlDB { db-type = "postgres"; db-address = "/run/postgresql"; db-database = "gotosocial"; db-user = "gotosocial"; }); users = { users."${cfg.user}" = { home = cfg.stateDir; group = cfg.group; isSystemUser = true; }; groups."${cfg.group}" = { }; }; environment.etc."gotosocial.yaml".source = configFile; environment.systemPackages = [ (pkgs.writeShellScriptBin "gotosocial-admin" '' exec systemd-run \ -u gotosocial-admin.service \ -p Group=${cfg.group} \ -p User=${cfg.user} \ -q -t -G --wait --service-type=exec \ ${lib.getExe cfg.package} --config-path ${configFile} admin "$@" '') ]; services.postgresql = lib.mkIf cfg.setupPostgresqlDB { enable = true; ensureDatabases = [ "gotosocial" ]; ensureUsers = [ { name = "gotosocial"; ensureDBOwnership = true; } ]; }; systemd.services.gotosocial = { description = "GoToSocial ActivityPub server"; restartTriggers = [ configFile ]; wantedBy = [ "multi-user.target" ]; requires = lib.optional cfg.setupPostgresqlDB "postgresql.service"; after = [ "network.target" "sops-install-secrets.service" ] ++ lib.optional cfg.setupPostgresqlDB "postgresql.service"; environment = { GTS_WAZERO_COMPILATION_CACHE = "${cfg.stateDir}/.cache"; }; serviceConfig = { User = cfg.user; Group = cfg.group; Type = "exec"; WorkingDirectory = cfg.stateDir; StateDirectory = lib.mkIf ( cfg.settings.storage-local-base-path != "/var/lib/gotosocial" ) "gotosocial"; ReadOnlyPaths = [ cfg.package ]; ReadWritePaths = [ cfg.settings.storage-local-base-path ]; StateDirectoryMode = "750"; Restart = "on-failure"; RestartSec = 5; EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile; ExecStart = "${lib.getExe cfg.package} --config-path ${configFile} server start"; # Security options: # Based on https://github.com/superseriousbusiness/gotosocial/blob/v0.8.1/example/gotosocial.service NoNewPrivileges = true; PrivateTmp = true; PrivateDevices = true; RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6"; RestrictNamespaces = true; RestrictRealtime = true; DevicePolicy = "closed"; ProtectSystem = "full"; ProtectControlGroups = true; ProtectKernelModules = true; ProtectKernelTunables = true; LockPersonality = true; SystemCallFilter = "~@clock @debug @module @mount @obsolete @reboot @setuid @swap"; AmbientCapabilities = lib.optional (cfg.settings.port < 1024) "CAP_NET_BIND_SERVICE"; CapabilityBoundingSet = [ "~CAP_RAWIO CAP_MKNOD" "~CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_AUDIT_WRITE" "~CAP_SYS_BOOT CAP_SYS_TIME CAP_SYS_MODULE CAP_SYS_PACCT" "~CAP_LEASE CAP_LINUX_IMMUTABLE CAP_IPC_LOCK" "~CAP_BLOCK_SUSPEND CAP_WAKE_ALARM" "~CAP_SYS_TTY_CONFIG" "~CAP_MAC_ADMIN CAP_MAC_OVERRIDE" "~CAP_NET_ADMIN CAP_NET_BROADCAST CAP_NET_RAW" "~CAP_SYS_ADMIN CAP_SYS_PTRACE CAP_SYSLOG" ]; }; }; } ); }