{ flake, config, pkgs, lib, ... }: let inherit (flake.config.people) user0; inherit (flake.config.services) instances; serviceCfg = flake.config.services.instances.mastodon; smtpCfg = flake.config.services.instances.smtp; hostCfg = flake.config.services.instances.web; host = serviceCfg.domains.url0; dns0 = instances.web.dns.provider0; dns0Path = "dns/${dns0}"; in { # If you need to start fresh for some reason, run these to create the new Admin account: # sudo -u mastodon mastodon-tootctl accounts create nick --email=nick@localhost --confirmed --role=Owner # sudo -u mastodon mastodon-tootctl accounts approve nick # If you fuck up and lose the password, use this: # sudo mastodon-tootctl accounts modify --reset-password nick # If you really fuck up and name yourself wrong, use this shit # sudo mastodon-tootctl accounts modify username --remove-role # nixpkgs.overlays = [ # ( # final: prev: { # mastodon = prev.mastodon.overrideAttrs (oldAttrs: { # postPatch = # (oldAttrs.postPatch or "") # + '' # patch -p1 < ${./chars.patch} # ''; # }); # } # ) # ]; microvm.vms = { ${serviceCfg.name} = { autostart = true; restartIfChanged = true; config = { system.stateVersion = "24.05"; time.timeZone = "America/Winnipeg"; users.users.root.openssh.authorizedKeys.keys = flake.config.people.users.${user0}.sshKeys; services = { ${serviceCfg.name} = { enable = true; localDomain = host; secretKeyBaseFile = "/etc/mastodon-secrets/pass"; streamingProcesses = 7; trustedProxy = hostCfg.localhost.address0; automaticMigrations = true; database = { createLocally = true; name = serviceCfg.name; host = "/run/postgresql"; user = serviceCfg.name; passwordFile = "/etc/mastodon-secrets/database"; }; extraConfig = { SINGLE_USER_MODE = "true"; SMTP_AUTH_METHOD = "plain"; SMTP_DELIVERY_METHOD = "smtp"; SMTP_ENABLE_STARTTLS_AUTO = "true"; SMTP_SSL = "false"; }; mediaAutoRemove = { enable = true; olderThanDays = 14; }; redis = { createLocally = true; enableUnixSocket = true; }; sidekiqThreads = 25; sidekiqProcesses = { all = { jobClasses = [ ]; threads = null; }; default = { jobClasses = [ "default" ]; threads = 5; }; ingress = { jobClasses = [ "ingress" ]; threads = 5; }; push-pull = { jobClasses = [ "push" "pull" ]; threads = 5; }; mailers = { jobClasses = [ "mailers" ]; threads = 5; }; }; smtp = { authenticate = true; createLocally = false; fromAddress = "upRootNutrition <${smtpCfg.email.address1}>"; host = smtpCfg.hostname; passwordFile = "/etc/mastodon-secrets/smtp"; port = smtpCfg.ports.port1; user = smtpCfg.email.address1; }; }; caddy = { enable = true; virtualHosts = { ":80" = { extraConfig = '' # Remove the outer http:// block wrapper handle_path /system/* { file_server * { root /var/lib/mastodon/public-system } } handle /api/v1/streaming/* { reverse_proxy unix//run/mastodon-streaming/streaming.socket { header_up X-Forwarded-Proto {http.request.header.X-Forwarded-Proto} header_up X-Forwarded-Host {http.request.header.X-Forwarded-Host} } } route * { file_server * { root ${pkgs.mastodon}/public pass_thru } reverse_proxy * unix//run/mastodon-web/web.socket { header_up X-Forwarded-Proto {http.request.header.X-Forwarded-Proto} header_up X-Forwarded-Host {http.request.header.X-Forwarded-Host} } } handle_errors { root * ${pkgs.mastodon}/public rewrite 500.html file_server } encode gzip header /* { Strict-Transport-Security "max-age=31536000;" } header /emoji/* Cache-Control "public, max-age=31536000, immutable" header /packs/* Cache-Control "public, max-age=31536000, immutable" header /system/accounts/avatars/* Cache-Control "public, max-age=31536000, immutable" header /system/media_attachments/files/* Cache-Control "public, max-age=31536000, immutable" ''; }; }; }; postgresql = { enable = true; }; openssh = { enable = true; settings = { PasswordAuthentication = false; PermitRootLogin = "prohibit-password"; }; }; }; users.users.${serviceCfg.name}.extraGroups = [ "postgres" ]; users.users.caddy.extraGroups = [ serviceCfg.name ]; networking.firewall.allowedTCPPorts = [ 22 # SSH 80 # Caddy 25 # SMTP 139 # SMTP 587 # SMTP 2525 # SMTP 5432 # Postgres ]; systemd = { services = { mastodon-init-dirs = { serviceConfig = { PrivateMounts = lib.mkForce false; }; }; mastodon-web = { serviceConfig = { PrivateMounts = lib.mkForce false; }; }; mastodon-streaming-1 = { serviceConfig = { PrivateMounts = lib.mkForce false; }; }; mastodon-streaming-2.serviceConfig.PrivateMounts = lib.mkForce false; mastodon-streaming-3.serviceConfig.PrivateMounts = lib.mkForce false; mastodon-streaming-4.serviceConfig.PrivateMounts = lib.mkForce false; mastodon-streaming-5.serviceConfig.PrivateMounts = lib.mkForce false; mastodon-streaming-6.serviceConfig.PrivateMounts = lib.mkForce false; mastodon-streaming-7.serviceConfig.PrivateMounts = lib.mkForce false; mastodon-sidekiq-all.serviceConfig.PrivateMounts = lib.mkForce false; mastodon-sidekiq-default.serviceConfig.PrivateMounts = lib.mkForce false; mastodon-sidekiq-ingress.serviceConfig.PrivateMounts = lib.mkForce false; mastodon-sidekiq-mailers.serviceConfig.PrivateMounts = lib.mkForce false; mastodon-sidekiq-push-pull.serviceConfig.PrivateMounts = lib.mkForce false; mastodon-copy-secrets = { description = "Copy secrets from virtiofs to local filesystem"; before = [ "mastodon-init-dirs.service" ]; requiredBy = [ "mastodon-init-dirs.service" ]; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; }; script = '' mkdir -p /etc/mastodon-secrets cp /run/secrets/pass /etc/mastodon-secrets/pass cp /run/secrets/database /etc/mastodon-secrets/database cp /run/secrets/redis /etc/mastodon-secrets/redis cp /run/secrets/smtp /etc/mastodon-secrets/smtp chmod 755 /etc/mastodon-secrets chmod 644 /etc/mastodon-secrets/* ''; }; }; network = { enable = true; networks."20-lan" = { matchConfig.Name = "enp0s6"; addresses = [ { Address = "${serviceCfg.interface.ip}/24"; } ]; routes = [ { Destination = "${hostCfg.localhost.address1}/0"; Gateway = serviceCfg.interface.gate; } ]; dns = [ "1.1.1.1" "8.8.8.8" ]; }; }; services = { mastodon-init-db = { serviceConfig = { EnvironmentFile = "/var/lib/mastodon/.secrets_env"; }; }; systemd-tmpfiles-setup.after = [ "var-lib-mastodon.mount" ]; }; tmpfiles.rules = [ "d /var/lib/mastodon 0755 mastodon mastodon -" "Z /var/lib/mastodon 0755 mastodon mastodon -" "Z /var/lib/postgresql 0755 postgres postgres -" "d /var/cache/mastodon/precompile 0755 mastodon mastodon -" "d /var/lib/mastodon/public-system 0755 mastodon mastodon -" "d /var/lib/mastodon/public-system/accounts 0755 mastodon mastodon -" "d /var/lib/mastodon/public-system/media_attachments 0755 mastodon mastodon -" "d /var/lib/mastodon/public-system/media_attachments/files 0755 mastodon mastodon -" "d /var/lib/mastodon/public-system/site_uploads 0755 mastodon mastodon -" ]; }; microvm = { vcpu = 2; mem = 1024 * 3; hypervisor = "qemu"; interfaces = [ { type = "tap"; id = serviceCfg.interface.id; mac = serviceCfg.interface.mac; } { type = "user"; id = serviceCfg.interface.idUser; mac = serviceCfg.interface.macUser; } ]; forwardPorts = [ { from = "host"; host.port = serviceCfg.interface.ssh; guest.port = 22; } ]; shares = [ { mountPoint = "/nix/.ro-store"; proto = "virtiofs"; source = "/nix/store"; tag = "read_only_nix_store"; } { mountPoint = "/var/lib/${serviceCfg.name}"; proto = "virtiofs"; source = "${serviceCfg.mntPaths.path0}/data"; tag = "${serviceCfg.name}_data"; } { mountPoint = "/var/lib/postgresql"; proto = "virtiofs"; source = "${serviceCfg.mntPaths.path0}/database"; tag = "${serviceCfg.name}_database"; } { mountPoint = "/run/secrets"; proto = "virtiofs"; source = "/run/secrets/${serviceCfg.name}"; tag = "host_secrets"; } ]; }; }; }; }; sops = { secrets = builtins.listToAttrs ( map (secret: { name = "${serviceCfg.name}/${secret}"; value = { owner = "root"; group = "root"; mode = "0644"; }; }) [ "smtp" "database" "redis" "pass" ] ); }; systemd.tmpfiles.rules = [ "d ${serviceCfg.mntPaths.path0} 0751 microvm wheel - -" "d ${serviceCfg.mntPaths.path0}/data 0751 microvm wheel - -" "d ${serviceCfg.mntPaths.path0}/database 0751 microvm wheel - -" ]; services.caddy.virtualHosts."${host}" = { extraConfig = '' reverse_proxy http://${serviceCfg.interface.ip}:80 { header_up X-Forwarded-Proto {scheme} header_up X-Real-IP {remote_host} header_up X-Forwarded-For {remote_host} } tls ${serviceCfg.ssl.cert} ${serviceCfg.ssl.key} encode zstd gzip ''; }; users.users.caddy.extraGroups = [ "acme" ]; security.acme.certs."${host}" = { dnsProvider = dns0; environmentFile = config.sops.secrets.${dns0Path}.path; group = "caddy"; }; }