{config, pkgs, inputs, system, ...}: let staticDir = "/var/lib/akkoma/static"; inherit ((pkgs.formats.elixirConf {}).lib) mkAtom mkTuple; in { sops.secrets = { "akkoma/keyBase" = {}; "akkoma/signingSalt" = {}; "akkoma/jokenDefaultSigner" = {}; }; containers.pleroma = { autoStart = true; privateNetwork = true; hostAddress = "192.168.42.30"; localAddress = "192.168.42.31"; hostAddress6 = "fd00::42:30"; localAddress6 = "fd00::42:31"; bindMounts."/sops" = { hostPath = "/run/secrets/akkoma"; isReadOnly = true; }; config = {config, ...}: { # generating the manual will fail when mixing nixos channels, # so disable it here or this won't build at all. documentation.enable = false; nixpkgs.pkgs = pkgs; system.stateVersion = "22.11"; services.akkoma = { enable = true; frontends = { primary = { package = pkgs.akkoma-fe; name = "pleroma-fe"; ref = "stable"; }; admin = { package = pkgs.akkoma-frontends.admin-fe; name = "admin-fe"; ref = "stable"; }; }; extraStatic."static/terms-of-service.html" = pkgs.writeText "terms-of-service-html" ''

toki!

lawa kepeken pi tomo akkoma ni li lili: sina li mi la, sina ken kepeken e tomo ni.

''; config = { ":pleroma"."Pleroma.Web.Endpoint" = { "url" = { host = "pleroma.stuebinm.eu"; scheme = "https"; port = 443; }; "http" = { ip = "::"; port = 4000; }; secret_key_base._secret = "/sops/keyBase"; signing_salt._secret = "/sops/signingSalt"; }; ":joken".":default_signer"._secret = "/sops/jokenDefaultSigner"; ":pleroma" = { ":instance" = { name = "Pleroma"; limit = 5000; registrations_open = false; federating = true; healthcheck = true; allow_relay = true; description = "a test instance"; email = "dings@dings"; }; ":restrict_unauthenticated" = { timelines = true; activities = true; }; ":media_proxy" = { enabled = false; redirect_on_failure = true; }; ":rich_media".enabled = false; ":mrf_simple" = { reject = [ (mkTuple ["mastodon.social" "spam"]) (mkTuple ["mstdn.social" "scraping for language model"]) (mkTuple ["uden.ai" "lol"]) (mkTuple ["poa.st" "not a nice place"]) (mkTuple ["threads.net" "it facebook"]) ]; }; ":mrf".policies = map mkAtom [ "Pleroma.Web.ActivityPub.MRF.SimplePolicy" ]; ":http".pool_timeout = 30000; "Pleroma.Upload" = { filters = map mkAtom [ "Pleroma.Upload.Filter.Exiftool.StripMetadata" "Pleroma.Upload.Filter.AnonymizeFilename" "Pleroma.Upload.Filter.Dedupe" ]; }; # "Pleroma.Uploaders.Local".uploads = "/var/lib/akkoma/uploads"; "Pleroma.Repo" = { adapter = "Ecto.Adapters.Postgres"; username = "pleroma"; database = "pleroma"; socket_dir = "/run/postgresql"; pool_size = 10; # prepare = ":named"; show_sensitive_data_on_connection_error = true; parameters = { plan_cache_mode = "force_custom_plan"; }; }; ":database".run_enabled = false; ":configurable_from_database" = false; ":instance".static_dir = "/var/lib/akkoma/static"; }; }; }; systemd.services.akkoma = { path = [ pkgs.exiftool ]; bindsTo = [ "akkoma-static.service" ]; after = [ "akkoma-static.service" ]; }; # symlink the parts of the static dir that are inside the nix store, # so I can still have imperatively defined emojis etc. # (for some reason the module doesn't do that) systemd.services.akkoma-static = { description = "Akkoma static dir wrangling"; unitConfig.PropagatesReloadTo = [ "akkoma.service" ]; path = [ pkgs.coreutils ]; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; }; script = with pkgs.lib; '' ${concatStringsSep "\n" (mapAttrsToList (key: val: '' mkdir -p ${staticDir}/frontends/${escapeShellArg val.name}/ ln -sfT ${escapeShellArg val.package} ${staticDir}/frontends/${escapeShellArg val.name}/${escapeShellArg val.ref} '') config.services.akkoma.frontends)} ${optionalString (config.services.akkoma.extraStatic != null) (concatStringsSep "\n" (mapAttrsToList (key: val: '' mkdir -p "${staticDir}/$(dirname ${escapeShellArg key})" ln -sfT ${escapeShellArg val} ${staticDir}/${escapeShellArg key} '') config.services.akkoma.extraStatic))} ''; }; services.postgresql = { enable = true; package = pkgs.postgresql_16; ensureDatabases = [ "pleroma" ]; ensureUsers = [ { name = "pleroma"; ensureDBOwnership = true; } ]; settings = { max_connections = 20; shared_buffers = "256MB"; effective_cache_size = "768MB"; maintenance_work_mem = "64MB"; checkpoint_completion_target = 0.9; wal_buffers = "7864kB"; default_statistics_target = 100; random_page_cost = 1.1; effective_io_concurrency = 200; work_mem = "6553kB"; huge_pages = "off"; min_wal_size = "2GB"; max_wal_size = "8GB"; }; # give pleroma access. must be done with lib.mkForce, for some reason authentication = pkgs.lib.mkForce '' # Generated file; do not edit! local all all trust host pleroma akkoma ::1/128 trust ''; # this is basically legacy. even if I ever reset the database, # the initDb option of the akkoma module probably does about this. initialScript = pkgs.writeScript "postgres-pleroma-initial" '' CREATE USER pleroma; CREATE DATABASE pleroma OWNER pleroma; \c pleroma; --Extensions made by ecto.migrate that need superuser access CREATE EXTENSION IF NOT EXISTS citext; CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; ''; }; networking.firewall.allowedTCPPorts = [ 4000 ]; environment.etc."resolv.conf".text = "nameserver 1.1.1.1"; }; }; # give the container access to the external internet (necessary for # fetching content from other instances). Doesn't appear to work with # IPv6, though ... networking.nat = { enable = true; internalInterfaces = [ "ve-pleroma" ]; externalInterface = "ens3"; }; services.nginx.virtualHosts."pleroma.stuebinm.eu" = { forceSSL = true; enableACME = true; locations."/" = { proxyPass = "http://[${config.containers.pleroma.localAddress6}]:4000"; proxyWebsockets = true; # these headers are in the example config in the NixOS manual. # take some time to figure out what they all do, and if these # are necessary extraConfig = '' add_header 'Access-Control-Allow-Origin' '*' always; add_header 'Access-Control-Allow-Methods' 'POST, PUT, DELETE, GET, PATCH, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Idempotency-Key' always; add_header 'Access-Control-Expose-Headers' 'Link, X-RateLimit-Reset, X-RateLimit-Limit, X-RateLimit-Remaining, X-Request-Id' always; if ($request_method = OPTIONS) { return 204; } add_header X-XSS-Protection "1; mode=block"; add_header X-Permitted-Cross-Domain-Policies none; add_header X-Frame-Options DENY; add_header X-Content-Type-Options nosniff; add_header Referrer-Policy same-origin; add_header X-Download-Options noopen; client_max_body_size 16m; ''; }; }; }