zaphyra's git: tgcNUR

fork of https://git.transgirl.cafe/zaphoid/tgc-nix-user-repository

commit df2acc258d84988ba61bf345342a0ab4953f9356
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
nixosModules/services/gotosocial/default.nix
|
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"
+          ];
+        };
+      };
+    }
+  );
+
+}