mastodon-container/mastodon-container.nix

378 lines
12 KiB
Nix
Raw Normal View History

2023-07-25 18:37:43 -07:00
{ config, lib, pkgs, ... }@toplevel:
with lib;
let
cfg = config.services.mastodonContainer;
2023-07-26 16:30:53 -07:00
hostSecrets = config.fudo.secrets.host-secrets."${config.instance.hostname}";
makeEnvFile = envVars:
let
envLines =
mapAttrsToList (var: val: ''${var}="${toString val}"'') envVars;
in pkgs.writeText "envFile" (concatStringsSep "\n" envLines);
databasePasswd = readFile
(pkgs.lib.passwd.stablerandom-passwd-file "mastodon-db-passwd"
config.instance.build-seed);
2023-07-26 21:45:22 -07:00
secretKeyBase = readFile
(pkgs.lib.passwd.stablerandom-passwd-file "mastodon-secret-key-base"
config.instance.build-seed);
otpSecret = readFile (pkgs.lib.passwd.stablerandom-passwd-file "mastodon-otp"
config.instance.build-seed);
proxyConf = pkgs.writeText "mastodon-nginx.conf" ''
events {
worker_connections 1024;
}
http {
upstream backend {
2023-08-03 08:33:12 -07:00
server web:3000;
}
upstream streaming {
2023-08-03 08:33:12 -07:00
server streaming:4000;
}
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=CACHE:10m inactive=7d max_size=1g;
server {
listen 3000;
server_name localhost;
server_tokens off;
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
add_header Strict-Transport-Security "max-age=31536000" always;
location / {
2023-07-26 12:33:19 -07:00
try_files $uri @proxy;
}
2024-01-06 10:41:10 -08:00
location ~ /(emoji|packs|system/accounts/avatars|system/media_attachments/files) {
add_header Cache-Control "public, max-age=31536000, immutable";
add_header Strict-Transport-Security "max-age=31536000" always;
try_files $uri @proxy;
}
location /sw.js {
add_header Cache-Control "public, max-age=0";
add_header Strict-Transport-Security "max-age=31536000" always;
try_files $uri @proxy;
}
location @proxy {
proxy_set_header Host $host;
proxy_set_header Proxy "";
proxy_pass_header Server;
proxy_pass http://backend;
proxy_buffering on;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
2023-07-26 13:00:53 -07:00
proxy_set_header Connection "upgrade";
proxy_cache CACHE;
proxy_cache_valid 200 7d;
proxy_cache_valid 410 24h;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
add_header X-Cached $upstream_cache_status;
add_header Strict-Transport-Security "max-age=31536000" always;
tcp_nodelay on;
}
location /api/v1/streaming {
proxy_set_header Host $host;
proxy_set_header Proxy "";
proxy_pass http://streaming;
proxy_buffering off;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
2023-07-26 17:13:21 -07:00
proxy_set_header Connection "upgrade";
tcp_nodelay on;
}
}
}
'';
2023-07-25 18:37:43 -07:00
in {
options.services.mastodonContainer = with types; {
2023-07-25 18:37:43 -07:00
enable = mkEnableOption "Enable Mastodon running in an Arion container.";
2023-07-26 16:30:53 -07:00
hostname = mkOption {
type = str;
description = "Hostname of this Mastodon instance.";
};
web-domain = mkOption {
type = str;
description = "Domain name to attach to users, eg @user@web.domain";
default = toplevel.config.services.mastodonContainer.hostname;
};
2023-07-25 18:37:43 -07:00
version = mkOption {
type = str;
description = "Version of Mastodon to launch.";
};
images = {
mastodon = mkOption {
type = str;
description = "Docker image to use for Mastodon.";
default =
2023-08-03 23:08:07 -07:00
"tootsuite/mastodon:${toplevel.config.services.mastodonContainer.version}";
2023-07-25 18:37:43 -07:00
};
postgres = mkOption {
type = str;
description = "Docker image to use for PostgreSQL server.";
default = "postgres:15-alpine";
};
redis = mkOption {
type = str;
description = "Docker image to use for Redis server.";
default = "redis:7-alpine";
};
nginx = mkOption {
type = str;
description = "Docker image to use for Proxy server.";
2023-07-26 13:10:05 -07:00
default = "nginx:alpine-slim";
};
2023-07-25 18:37:43 -07:00
};
state-directory = mkOption {
type = str;
description = "Port at which to store server data.";
};
2023-07-26 09:25:12 -07:00
port = mkOption {
type = port;
description = "Port at which to serve Mastodon web requests.";
default = 3000;
2023-07-25 18:37:43 -07:00
};
2023-09-03 10:51:31 -07:00
environment = mkOption {
type = attrsOf str;
description = "Environment variables to set for the Mastodon job.";
default = { };
};
2023-07-26 16:30:53 -07:00
smtp = {
server = mkOption {
type = str;
description = "Outgoing SMTP server.";
};
port = mkOption {
type = port;
description = "Outgoing SMTP server port.";
default = 25;
};
};
2023-07-25 18:37:43 -07:00
uids = {
mastodon = mkOption {
type = int;
description = "UID as which to run Mastodon.";
default = 991;
2023-07-25 18:37:43 -07:00
};
postgres = mkOption {
type = int;
description = "UID as which to run PostgreSQL.";
default = 992;
2023-07-25 18:37:43 -07:00
};
redis = mkOption {
type = int;
description = "UID as which to run Redis.";
default = 993;
2023-07-25 18:37:43 -07:00
};
};
};
config = mkIf cfg.enable {
2024-01-06 10:41:10 -08:00
users = {
users = {
mastodon = {
isSystemUser = true;
group = "mastodon";
uid = cfg.uids.mastodon;
};
mastodon-postgres = {
isSystemUser = true;
group = "mastodon";
uid = cfg.uids.postgres;
};
mastodon-redis = {
isSystemUser = true;
group = "mastodon";
uid = cfg.uids.redis;
};
2023-07-25 18:37:43 -07:00
};
2024-01-06 10:41:10 -08:00
groups.mastodon = {
members = [ "mastodon" "mastodon-postgres" "mastodon-redis" ];
2023-07-25 18:37:43 -07:00
};
};
2023-07-26 16:30:53 -07:00
fudo.secrets.host-secrets."${config.instance.hostname}" = let
in {
2023-07-27 09:37:44 -07:00
mastodonCommonEnv = {
2023-07-26 16:30:53 -07:00
source-file = makeEnvFile {
LOCAL_DOMAIN = cfg.hostname;
WEB_DOMAIN = cfg.web-domain;
REDIS_HOST = "redis";
REDIS_PORT = 6379;
SMTP_SERVER = cfg.smtp.server;
SMTP_PORT = toString cfg.smtp.port;
SMTP_FROM_ADDRESS = "noreply@${cfg.web-domain}";
2023-07-26 21:45:22 -07:00
SECRET_KEY_BASE = secretKeyBase;
OTP_SECRET = otpSecret;
2023-07-26 16:30:53 -07:00
};
2023-07-26 16:50:22 -07:00
target-file = "/run/mastodon/common.env";
};
2023-07-27 09:37:44 -07:00
mastodonPostgresEnv = {
2023-07-27 09:07:06 -07:00
source-file = makeEnvFile {
POSTGRES_USER = "mastodon";
POSTGRES_PASSWORD = databasePasswd;
POSTGRES_DB = "mastodon";
};
2023-07-26 16:52:23 -07:00
target-file = "/run/mastodon/postgres.env";
2023-07-26 16:50:22 -07:00
};
mastodonEnv = {
2023-09-03 10:51:31 -07:00
source-file = makeEnvFile ({
2023-07-27 09:07:06 -07:00
DB_HOST = "postgres";
DB_USER = "mastodon";
DB_NAME = "mastodon";
DB_PASS = databasePasswd;
2023-09-03 12:07:03 -07:00
} // cfg.environment);
2023-07-26 16:50:22 -07:00
target-file = "/run/mastodon/mastodon.env";
2023-07-26 16:30:53 -07:00
};
};
2023-09-06 11:12:10 -07:00
systemd = {
tmpfiles.rules = [
"d ${cfg.state-directory}/mastodon-opt 0700 mastodon root - -"
"d ${cfg.state-directory}/mastodon 0700 mastodon root - -"
"d ${cfg.state-directory}/postgres 0700 mastodon-postgres root - -"
"d ${cfg.state-directory}/redis 0700 mastodon-redis root - -"
];
services.arion-mastodon = {
after = [ "network-online.target" ];
requires = [ "network-online.target" ];
};
};
2023-07-25 18:51:57 -07:00
2023-07-25 18:37:43 -07:00
virtualisation.arion.projects.mastodon.settings = let
mkUserMap = uid: "${toString uid}:${toString uid}";
image = { pkgs, ... }: {
project.name = "mastodon";
2023-09-03 11:51:29 -07:00
networks = {
2024-01-16 15:44:26 -08:00
internal_network = {
internal = true;
dns_enabled = true;
};
external_network = {
internal = false;
dns_enabled = true;
};
2023-09-03 11:51:29 -07:00
};
2023-07-25 18:37:43 -07:00
services = {
proxy.service = {
image = cfg.images.nginx;
restart = "always";
ports = [ "${toString cfg.port}:3000" ];
2023-07-26 09:32:22 -07:00
volumes = [ "${proxyConf}:/etc/nginx/nginx.conf:ro,Z" ];
depends_on = [ "web" "streaming" ];
2023-09-03 11:51:29 -07:00
networks = [ "external_network" "internal_network" ];
};
2023-07-26 16:30:53 -07:00
postgres.service = {
2023-07-25 18:37:43 -07:00
image = cfg.images.postgres;
restart = "always";
volumes =
[ "${cfg.state-directory}/postgres:/var/lib/postgresql/data" ];
2023-09-03 13:52:41 -07:00
healthcheck.test = [ "CMD" "pg_isready" "-U" "mastodon" ];
2023-07-25 18:37:43 -07:00
user = mkUserMap cfg.uids.postgres;
2023-07-26 16:30:53 -07:00
env_file = [
2023-07-27 09:37:44 -07:00
hostSecrets.mastodonCommonEnv.target-file
hostSecrets.mastodonPostgresEnv.target-file
2023-07-26 16:30:53 -07:00
];
2023-09-03 11:51:29 -07:00
networks = [ "internal_network" ];
2023-07-25 18:37:43 -07:00
};
redis.service = {
image = cfg.images.redis;
restart = "always";
volumes = [ "${cfg.state-directory}/redis:/data" ];
healthcheck.test = [ "CMD" "redis-cli" "ping" ];
user = mkUserMap cfg.uids.redis;
2023-07-27 09:37:44 -07:00
env_file = [ hostSecrets.mastodonCommonEnv.target-file ];
2023-09-03 11:51:29 -07:00
networks = [ "internal_network" ];
2023-07-25 18:37:43 -07:00
};
web.service = {
image = cfg.images.mastodon;
2023-08-03 08:33:12 -07:00
hostname = "web";
2023-07-25 18:37:43 -07:00
restart = "always";
volumes =
[ "${cfg.state-directory}/mastodon:/mastodon/public/system" ];
# command = ''bash -c "while :; do echo 'kill me'; sleep 1; done"'';
command = ''
bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"'';
healthcheck.test = [
"CMD-SHELL"
"wget -q --spider --proxy=off localhost:3000/health || exit 1"
2023-08-03 23:32:09 -07:00
];
2023-07-26 16:30:53 -07:00
depends_on = [ "postgres" "redis" ];
2023-07-25 18:37:43 -07:00
user = mkUserMap cfg.uids.mastodon;
2023-07-26 16:30:53 -07:00
env_file = [
2023-07-27 09:37:44 -07:00
hostSecrets.mastodonCommonEnv.target-file
2023-07-26 16:30:53 -07:00
hostSecrets.mastodonEnv.target-file
];
networks = [ "internal_network" "external_network" ];
2023-07-25 18:37:43 -07:00
};
streaming.service = {
image = cfg.images.mastodon;
2023-08-03 08:33:12 -07:00
hostname = "streaming";
2023-07-25 18:37:43 -07:00
restart = "always";
command = "node ./streaming";
healthcheck.test = [
"CMD-SHELL"
"wget -q --spider --proxy=off localhost:4000/api/v1/streaming/health || exit 1"
];
2023-07-26 16:30:53 -07:00
depends_on = [ "postgres" "redis" ];
env_file = [
2023-07-27 09:37:44 -07:00
hostSecrets.mastodonCommonEnv.target-file
2023-07-26 16:30:53 -07:00
hostSecrets.mastodonEnv.target-file
];
networks = [ "internal_network" "external_network" ];
2023-07-25 18:37:43 -07:00
};
sidekiq.service = {
image = cfg.images.mastodon;
restart = "always";
volumes =
[ "${cfg.state-directory}/mastodon:/mastodon/public/system" ];
command = "bundle exec sidekiq";
healthcheck.test =
[ "CMD-SHELL" "ps aux | grep '[s]idekiq 6' || false" ];
2023-07-26 16:30:53 -07:00
depends_on = [ "postgres" "redis" ];
2023-07-25 18:37:43 -07:00
user = mkUserMap cfg.uids.mastodon;
2023-07-26 16:30:53 -07:00
env_file = [
2023-07-27 09:37:44 -07:00
hostSecrets.mastodonCommonEnv.target-file
2023-07-26 16:30:53 -07:00
hostSecrets.mastodonEnv.target-file
];
networks = [ "internal_network" "external_network" ];
2023-07-25 18:37:43 -07:00
};
};
};
in { imports = [ image ]; };
};
}