{ config, lib, pkgs, inputs, ... }: with lib; let cfg = config.modules.services.akkoma; poorObfuscation = y: x: "${x}@${y}"; federation-blocklist = lib.importTOML ./blocklist.toml; inherit (lib.my) wrapFile; in { options.modules.services.akkoma = { enable = mkEnableOption "Akkoma instance"; package = mkOption { type = types.package; default = pkgs.akkoma; }; domain = mkOption { type = types.str; }; realHost = mkOption { type = types.str; }; instanceName = mkOption { type = types.str; default = "Akkoma on ${cfg.domain}"; }; oauthBaseUrl = mkOption { type = types.str; default = "Base URL of the OAuth provider"; }; secrets.akkoma-envs = mkOption { type = types.str; default = "Path to the akkoma environment file"; }; }; config = mkIf cfg.enable { modules.services.postgresql.enable = true; services.akkoma = { enable = true; package = cfg.package.overrideAttrs (old: { # Akkoma doesn't include OAuth2 dependencies by default # This can be obtained by running `OAUTH_CONSUMER_STRATEGIES="..." mix deps.get`. # The server should also be launched with the same environment variable set. nativeBuildInputs = let inherit (pkgs.beamPackages) fetchHex buildMix; oldMixDeps = old.passthru.mixNixDeps; in old.nativeBuildInputs ++ builtins.attrValues rec { # TODO: This needs to be bumped from time to time oauth2 = buildMix rec { name = "oauth2"; version = "2.1.0"; src = fetchHex { pkg = name; inherit version; sha256 = "0h9bps7gq7bac5gc3q0cgpsj46qnchpqbv5hzsnd2z9hnf2pzh4a"; }; beamDeps = [ oldMixDeps.tesla ]; }; ueberauth_keycloak_strategy = buildMix rec { name = "ueberauth_keycloak_strategy"; version = "0.4.0"; src = fetchHex { pkg = name; inherit version; sha256 = "06r10w0azlpypjgggar1lf7h2yazn2dpyicy97zxkjyxgf9jfc60"; }; # There must be a way to configure this using config.exs and patchPhase, # But just applying a patch is easier since patching ueberauth and # this package didn't do the trick. patches = [ ./0001-fix-scope.patch ]; beamDeps = [ oauth2 oldMixDeps.ueberauth ]; }; }; OAUTH_CONSUMER_STRATEGIES = "keycloak:ueberauth_keycloak_strategy"; }); initDb.enable = true; extraStatic = { "static/terms-of-service.html" = wrapFile "terms-of-service.html" ./terms-of-service.html; "static/logo.svg" = wrapFile "logo.svg" ./logo.svg; "static/logo.png" = wrapFile "logo.png" ./logo.png; "static/logo-512.png" = wrapFile "logo-512.png" ./favicon-withbg.png; # Intentional, for PWA favicon. "static/icon.png" = wrapFile "icon.png" ./favicon.png; "favicon.png" = wrapFile "favicon.png" ./favicon-withbg.png; }; config = let inherit ((pkgs.formats.elixirConf { }).lib) mkRaw mkMap mkTuple; in { ":pleroma"."Pleroma.Web.Endpoint".url.host = cfg.realHost; ":pleroma"."Pleroma.Web.Endpoint".extra_cookie_attrs = [ "SameSite=Lax" ]; ":pleroma"."Pleroma.Web.WebFinger".domain = cfg.domain; ":pleroma".":media_proxy".enabled = false; ":pleroma".":instance" = { name = cfg.instanceName; description = "Private akkoma instance"; email = poorObfuscation cfg.domain "postmaster"; notify_email = poorObfuscation cfg.domain "postmaster"; registrations_open = false; account_approval_required = true; invites_enabled = true; limit = 5000; }; ":pleroma".":frontend_configurations" = { pleroma_fe = mkMap { logo = "/static/logo.png"; # FIXME: https://akkoma.dev/AkkomaGang/akkoma/pulls/668 # TODO: enable on next release loginMethod = "token"; }; }; ":pleroma".":mrf" = { policies = map mkRaw [ "Pleroma.Web.ActivityPub.MRF.SimplePolicy" ]; }; ":pleroma".":mrf_simple" = { followers_only = mkMap federation-blocklist.followers_only; media_nsfw = mkMap federation-blocklist.media_nsfw; reject = mkMap federation-blocklist.reject; }; ":pleroma"."Pleroma.Captcha" = { enabled = true; method = mkRaw "Pleroma.Captcha.Kocaptcha"; }; ":ueberauth" = { "Ueberauth.Strategy.Keycloak.OAuth" = { client_id = mkRaw ''System.get_env("AUTHENTIK_CLIENT_ID")''; client_secret = mkRaw ''System.get_env("AUTHENTIK_CLIENT_SECRET")''; site = cfg.oauthBaseUrl; authorize_url = "${cfg.oauthBaseUrl}/application/o/authorize/"; token_url = "${cfg.oauthBaseUrl}/application/o/token/"; userinfo_url = "${cfg.oauthBaseUrl}/application/o/userinfo/"; token_method = mkRaw ":post"; }; Ueberauth.providers = { keycloak = mkTuple [(mkRaw "Ueberauth.Strategy.Keycloak") { default_scope = "openid profile email"; uid_field = mkRaw ":preferred_username"; }]; }; }; }; nginx = { forceSSL = true; useACMEHost = cfg.domain; locations."~ \\.(js|css|woff|woff2?|png|jpe?g|svg)$" = { extraConfig = '' add_header Cache-Control "public, max-age=14400, must-revalidate"; ''; proxyPass = "http://unix:${config.services.akkoma.config.":pleroma"."Pleroma.Web.Endpoint".http.ip}"; proxyWebsockets = true; recommendedProxySettings = true; }; }; }; systemd.services.akkoma = { after = [ "authentik.service" ]; environment.OAUTH_CONSUMER_STRATEGIES = "keycloak:ueberauth_keycloak_strategy"; serviceConfig.EnvironmentFile = cfg.secrets.akkoma-envs; }; services.nginx.virtualHosts.${cfg.domain} = { forceSSL = true; useACMEHost = cfg.domain; locations."/.well-known/host-meta" = { extraConfig = '' return 301 https://${cfg.realHost}$request_uri; ''; }; }; modules.persistence.directories = [ "/var/lib/akkoma" ]; }; }