diff --git a/content/posts/2023/01/mastodon-nixos.md b/content/posts/2023/01/mastodon-nixos.md new file mode 100644 index 0000000..a9823f7 --- /dev/null +++ b/content/posts/2023/01/mastodon-nixos.md @@ -0,0 +1,206 @@ +--- +title: Containerised Mastodon server on NixOS +date: 2023-01-15 +tags: [nix, fediverse] +--- + +I've been on the fediverse on and off since [friendi.ca](https://friendi.ca/) started to be a thing. +I remember hosting an instance at ponk.pink that suddenly got popular and the server melted away while I was apologising to the users. +I can see someone bought the domain to host some psychedelic My Little Pony stuff, I'm all for that. + +I moved to [todon.nl](https://todon.nl/) when it got started and was quite happy there - check it out if you're looking for an instance ! +The itch was there though, what if I host my own small cozy place on the fediverse myself. +I knew I didn't have as much time to do server admin, so I decided to start small, with a single user instance an use the power of NixOS to help with maintenance. + +Here is my full config - hosting Mastodon under the apex domain of [peninsula.industries](https://peninsula.industries/), Mastodon is running inside a systemd container and the config is using nix-sops to store the secrets. +Few things that were unexpected/of note + - I needed to create the folder structure with correct permissions so that Mastodon starts normally. + - I'm decrypting secrets on the host and making them available read-only to the container. Not sure if this is better or worse than having sops inside of the container, but I was having some trouble using sops module from there, so left it as it is for now - something to look into in the future + - I needed to change the postgres port that is running inside of the Mastodon container because I had another postgres on this host already. + - If you're gonna be playing with this a lot, you might need to remove the container and its data and start from scratch - you can do so by doing: + - remove the container definition and `nixos-rebuild switch` + - rm -fr /var/lib/nixos-containers/mastodon/ * + - to use Mastodon CLI you need to be running it as a correct user and within the Nix-changed env: + ```bash + nixos-container root-login mastodon + sudo -u mastodon bash + cd + source mastodon-env + RAILS_ENV=production tootctl + ``` + - The below can use some refactoring, there is some weird repetition in some places still + - You can find the newest version of this and see how it's being used in context on my [code hosting site](https://git.cyplo.dev/cyplo/dotfiles/src/branch/main/nixos/boxes/vpsfree1/mastodon.nix). +```nix +{ + config, + pkgs, + inputs, + lib, + ... +}: let + domain = "peninsula.industries"; + streamingPort = 55000; + webPort = 55001; + postgresPort = 5433; + path = "/var/lib/mastodon/"; + mailgunSmtpSecretName = "mastodon-mailgun-smtp-password"; + mailgunSmtpPasswordPath = "/run/secrets/${mailgunSmtpSecretName}"; + mastodonDbSecretName = "mastodon-db"; + mastodonDbSecretPath = "/run/secrets/${mastodonDbSecretName}"; + uid = 2049; + gid = 3049; + systemUserName = "mastodon"; + systemGroupName = "mastodon"; + users = { + users."${systemUserName}" = { + inherit uid; + isSystemUser = true; + isNormalUser = false; + group = systemGroupName; + }; + groups."${systemGroupName}" = { + inherit gid; + members = ["${systemUserName}" "nginx"]; + }; + }; + secretSettings = { + owner = systemUserName; + group = systemGroupName; + }; + publicPath = "${path}/public-system/"; + package = + inputs.nixpkgs-nixos-unstable.legacyPackages."${pkgs.system}".mastodon; +in { + imports = [../nginx.nix]; + + services.nginx = { + virtualHosts = { + "${domain}" = { + forceSSL = true; + enableACME = true; + root = "${package}/public/"; + + locations."/" = {tryFiles = "$uri @proxy";}; + locations."/system/".alias = "${publicPath}"; + + locations."@proxy" = { + proxyPass = "http://127.0.0.1:" + toString webPort; + proxyWebsockets = true; + }; + locations."/api/v1/streaming/" = { + proxyPass = "http://127.0.0.1:" + toString streamingPort; + proxyWebsockets = true; + }; + }; + }; + }; + + sops.secrets."${mailgunSmtpSecretName}" = + { + sopsFile = ./mailgun.sops.yaml; + path = mailgunSmtpPasswordPath; + } + // secretSettings; + sops.secrets."${mastodonDbSecretName}" = + { + sopsFile = ./mastodon-db.sops.yaml; + path = mastodonDbSecretPath; + } + // secretSettings; + + inherit users; + + systemd.services.mastodon-make-path = { + script = '' + mkdir -p ${path} + chown -R ${systemUserName}:${systemGroupName} ${path} + mkdir -p ${publicPath} + chmod -R o-rwx ${publicPath} + chmod -R g-rwx ${publicPath} + chmod -R g+X ${publicPath} + chmod -R g+r ${publicPath} + chmod -R u+rwX ${publicPath} + ''; + serviceConfig = { + Type = "oneshot"; + ProtectSystem = "strict"; + ReadWritePaths = path; + }; + before = ["container@mastodon.service"]; + }; + + containers.mastodon = { + autoStart = true; + forwardPorts = [ + { + containerPort = streamingPort; + hostPort = streamingPort; + } + { + containerPort = webPort; + hostPort = webPort; + } + ]; + bindMounts = { + "${path}" = { + hostPath = "${path}"; + isReadOnly = false; + }; + "${mailgunSmtpPasswordPath}" = { + hostPath = "${mailgunSmtpPasswordPath}"; + isReadOnly = true; + }; + "${mastodonDbSecretPath}" = { + hostPath = "${mastodonDbSecretPath}"; + isReadOnly = true; + }; + }; + config = { + config, + pkgs, + lib, + ... + }: { + system.stateVersion = "22.05"; + services.postgresql.port = postgresPort; + users = + users + // { + mutableUsers = false; + allowNoPasswordLogin = true; + }; + services.mastodon = { + enable = true; + inherit package; + localDomain = "${domain}"; + user = systemUserName; + group = systemGroupName; + smtp = { + host = "smtp.eu.mailgun.org"; + port = 465; + authenticate = true; + user = "postmaster@${domain}"; + fromAddress = "Peninsula Industries Mastodon "; + createLocally = false; + passwordFile = "${mailgunSmtpPasswordPath}"; + }; + extraConfig = { + SMTP_TLS = "true"; + SMTP_ENABLE_STARTTLS_AUTO = "true"; + SINGLE_USER_MODE = "true"; + RAILS_SERVE_STATIC_FILES = "true"; + }; + inherit streamingPort; + inherit webPort; + configureNginx = false; + enableUnixSocket = false; + database = { + port = postgresPort; + passwordFile = mastodonDbSecretPath; + }; + }; + }; + }; +} +``` +