commit df2acc258d84988ba61bf345342a0ab4953f9356
parent c80941752762e593649fdb132cda4ecb0e95ef93
Author: Katja Ramona Sophie Kwast (zaphyra) <git@zaphyra.eu>
Date: Thu, 21 Aug 2025 15:43:18 +0200
parent c80941752762e593649fdb132cda4ecb0e95ef93
Author: Katja Ramona Sophie Kwast (zaphyra) <git@zaphyra.eu>
Date: Thu, 21 Aug 2025 15:43:18 +0200
nixosModules/services: add `gotosocial`
1 file changed, 248 insertions(+), 0 deletions(-)
A
|
248
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
diff --git a/nixosModules/services/gotosocial/default.nix b/nixosModules/services/gotosocial/default.nix @@ -0,0 +1,248 @@ +{ 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" + ]; + }; + }; + } + ); + +}