lemmy-docker/lemmy-docker.nix

420 lines
12 KiB
Nix
Raw Normal View History

2023-07-08 12:01:21 -07:00
{ config, lib, pkgs, ... }@toplevel:
2023-07-07 22:11:33 -07:00
with lib;
let
2023-07-07 23:06:24 -07:00
cfg = config.services.lemmyDocker;
hostSecrets = config.fudo.secrets.host-secrets."${config.instance.hostname}";
makeEnvFile = envVars:
2023-07-18 11:27:17 -07:00
let
envLines =
mapAttrsToList (var: val: ''${var}="${toString val}"'') envVars;
in pkgs.writeText "envFile" (concatStringsSep "\n" envLines);
makeLemmyImage = { port, stateDirectory, proxyCfg, lemmyCfg, lemmyUiCfg
, pictrsCfg, postgresCfg, ... }:
2023-07-14 11:59:57 -07:00
{ pkgs, ... }: {
project.name = "lemmy";
services = {
proxy = {
service = {
image = proxyCfg.image;
2023-07-16 12:06:31 -07:00
ports = [ "${toString port}:8536" ];
volumes = [ "${proxyCfg.configFile}:/etc/nginx/nginx.conf:ro,Z" ];
2023-07-17 08:01:30 -07:00
depends_on = [ "lemmy" "lemmy-ui" "pictrs" ];
restart = "always";
2023-07-14 11:59:57 -07:00
};
};
lemmy = {
service = {
image = lemmyCfg.image;
2023-07-14 11:59:57 -07:00
hostname = "lemmy";
2023-07-16 11:50:57 -07:00
env_file = [ lemmyCfg.envFile ];
volumes = [ "${lemmyCfg.configFile}:/config/config.hjson:ro,Z" ];
2023-07-14 11:59:57 -07:00
depends_on = [ "postgres" "pictrs" ];
2023-07-17 08:01:30 -07:00
restart = "always";
2023-07-14 11:59:57 -07:00
};
};
lemmy-ui = {
service = {
image = lemmyUiCfg.image;
2023-07-14 11:59:57 -07:00
hostname = "lemmy-ui";
depends_on = [ "lemmy" ];
2023-07-17 08:01:30 -07:00
restart = "always";
env_file = [ lemmyUiCfg.envFile ];
2023-07-14 11:59:57 -07:00
};
};
pictrs = {
service = {
image = pictrsCfg.image;
2023-07-14 11:59:57 -07:00
hostname = "pictrs";
volumes = [ "${stateDirectory}/pictrs:/mnt:Z" ];
2023-07-17 08:18:34 -07:00
user = "${toString pictrsCfg.uid}:${toString pictrsCfg.uid}";
2023-07-16 11:50:57 -07:00
env_file = [ pictrsCfg.envFile ];
2023-07-17 08:01:30 -07:00
restart = "always";
2023-07-14 11:59:57 -07:00
};
};
postgres = {
service = {
image = postgresCfg.image;
2023-07-14 11:59:57 -07:00
hostname = "postgres";
volumes = [
"${stateDirectory}/postgres:/var/lib/postgresql/data:Z"
"${postgresCfg.configFile}:/etc/postgresql.conf"
2023-07-14 11:59:57 -07:00
];
2023-07-17 17:52:53 -07:00
user = "${toString postgresCfg.uid}:${toString postgresCfg.uid}";
2023-07-16 11:50:57 -07:00
env_file = [ postgresCfg.envFile ];
2023-07-17 08:01:30 -07:00
restart = "always";
2023-07-14 11:59:57 -07:00
};
};
2023-07-07 22:11:33 -07:00
};
};
2023-07-08 09:43:21 -07:00
nginxCfgFile = pkgs.writeText "lemmy-nginx.conf" ''
2023-07-07 22:11:33 -07:00
worker_processes auto;
events {
worker_connections 1024;
}
http {
error_log stderr info;
access_log stdout;
2023-07-17 18:10:31 -07:00
2023-07-07 22:11:33 -07:00
map "$request_method:$http_accept" $proxpass {
2023-07-17 16:17:33 -07:00
# If no explicit matches exists below, send traffic to lemmy-ui
2023-07-07 22:11:33 -07:00
default "http://lemmy-ui";
2023-07-17 16:17:33 -07:00
# GET/HEAD requests that accepts ActivityPub or Linked Data JSON should go to lemmy.
#
# These requests are used by Mastodon and other fediverse instances to look up profile information,
# discover site information and so on.
2023-07-07 22:11:33 -07:00
"~^(?:GET|HEAD):.*?application\/(?:activity|ld)\+json" "http://lemmy";
2023-07-17 16:17:33 -07:00
# All non-GET/HEAD requests should go to lemmy
#
# Rather than calling out POST, PUT, DELETE, PATCH, CONNECT and all the verbs manually
# we simply negate the GET|HEAD pattern from above and accept all possibly $http_accept values
2023-07-07 22:11:33 -07:00
"~^(?!(GET|HEAD)).*:" "http://lemmy";
}
upstream lemmy {
2023-07-17 16:17:33 -07:00
# Must map to lemmy image
2023-07-07 22:11:33 -07:00
server "lemmy:8536";
}
upstream lemmy-ui {
2023-07-17 16:17:33 -07:00
# Must map to lemmy-ui image
2023-07-07 22:11:33 -07:00
server "lemmy-ui:1234";
}
server {
listen 1236;
listen 8536;
server_name localhost;
server_tokens off;
gzip on;
gzip_types text/css application/javascript image/svg+xml;
gzip_vary on;
client_max_body_size 20M;
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
2023-07-17 16:17:33 -07:00
# Send actual client IP upstream
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
2023-07-07 22:11:33 -07:00
2023-07-17 16:17:33 -07:00
# frontend general requests
location / {
proxy_pass $proxpass;
rewrite ^(.+)/+$ $1 permanent;
}
2023-07-07 22:11:33 -07:00
2023-07-17 16:17:33 -07:00
# security.txt
location = /.well-known/security.txt {
proxy_pass "http://lemmy-ui";
2023-07-07 22:11:33 -07:00
}
2023-07-17 16:17:33 -07:00
# backend
2023-07-07 22:11:33 -07:00
location ~ ^/(api|pictrs|feeds|nodeinfo|.well-known) {
2023-07-17 16:17:33 -07:00
proxy_pass "http://lemmy";
2023-07-07 22:11:33 -07:00
2023-07-17 16:17:33 -07:00
# proxy common stuff
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
2023-07-07 22:11:33 -07:00
}
}
}
'';
2023-07-17 21:52:46 -07:00
makeLemmyCfg = { hostname, postgresPasswd, pictrsApiKey, smtpServer, siteName
2023-07-17 19:54:33 -07:00
, adminPasswd ? null, ... }:
2023-07-08 12:04:33 -07:00
pkgs.writeText "lemmy.hjson" (builtins.toJSON {
database = {
host = "postgres";
password = postgresPasswd;
};
hostname = hostname;
pictrs = {
url = "http://pictrs:8080/";
api_key = pictrsApiKey;
};
email = {
smtp_server = smtpServer;
tls_type = "none";
smtp_from_address = "noreply@${hostname}";
};
2023-07-17 20:13:34 -07:00
setup = {
2023-07-17 19:54:33 -07:00
admin_username = "admin";
admin_password = adminPasswd;
2023-07-17 21:52:46 -07:00
site_name = siteName;
2023-07-17 19:54:33 -07:00
};
2023-07-08 12:04:33 -07:00
});
2023-07-07 22:11:33 -07:00
postgresCfgFile = pkgs.writeText "lemmy-postgres.conf" ''
2023-07-08 12:08:50 -07:00
# DB Version: 15
# OS Type: linux
# DB Type: web
# Total Memory (RAM): 8 GB
# CPUs num: 4
# Data Storage: ssd
max_connections = 200
shared_buffers = 2GB
effective_cache_size = 6GB
maintenance_work_mem = 512MB
checkpoint_completion_target = 0.9
checkpoint_timeout = 86400
wal_buffers = 16MB
default_statistics_target = 100
random_page_cost = 1.1
effective_io_concurrency = 200
work_mem = 5242kB
min_wal_size = 1GB
max_wal_size = 30GB
max_worker_processes = 4
max_parallel_workers_per_gather = 2
max_parallel_workers = 4
max_parallel_maintenance_workers = 2
# Other custom params
temp_file_size=1GB
synchronous_commit=off
# This one shouldn't be on regularly, because DB migrations often take a long time
# statement_timeout = 10000
'';
2023-07-07 22:11:33 -07:00
in {
options.services.lemmyDocker = with types; {
enable = mkEnableOption "Enable Lemmy running in a Docker container.";
hostname = mkOption {
type = str;
description = "Hostname at which this server is accessible.";
};
2023-07-17 21:52:46 -07:00
site-name = mkOption {
type = str;
description = "Name of this server.";
};
2023-07-07 22:11:33 -07:00
port = mkOption {
type = port;
description = "Port on which to listen for Lemmy connections.";
default = 8536;
};
version = mkOption {
type = str;
description = "Lemmy version.";
};
state-directory = mkOption {
type = str;
description = "Directory at which to store application state.";
};
2023-07-08 09:43:21 -07:00
smtp-server = mkOption {
type = str;
description = "SMTP server to use for outgoing messages.";
};
2023-07-07 22:11:33 -07:00
docker-images = {
lemmy = mkOption {
type = str;
description = "Docker image to use for Lemmy.";
2023-07-07 22:33:11 -07:00
default =
"dessalines/lemmy:${toplevel.config.services.lemmyDocker.version}";
2023-07-07 22:11:33 -07:00
};
lemmy-ui = mkOption {
type = str;
description = "Docker image to use for Lemmy UI.";
2023-07-07 22:33:11 -07:00
default =
"dessalines/lemmy-ui:${toplevel.config.services.lemmyDocker.version}";
2023-07-07 22:11:33 -07:00
};
pictrs = mkOption {
type = str;
2023-07-07 22:33:11 -07:00
description = "Docker image to use for PictRS.";
2023-07-07 22:11:33 -07:00
};
postgres = mkOption {
type = str;
description = "Docker image to use for Postgres.";
};
};
};
config = mkIf cfg.enable (let
postgresPasswd =
readFile (pkgs.lib.passwd.random-passwd-file "lemmy-postgres-passwd" 30);
pictrsApiKey =
readFile (pkgs.lib.passwd.random-passwd-file "lemmy-pictrs-api-key" 30);
2023-07-17 19:54:33 -07:00
adminPasswd = readFile
(pkgs.lib.passwd.stablerandom-passwd-file "lemmy-admin-passwd"
config.instance.build-seed);
2023-07-07 22:11:33 -07:00
in {
2023-07-07 23:10:46 -07:00
fudo.secrets.host-secrets."${config.instance.hostname}" = {
lemmyPictrsEnv = {
source-file = makeEnvFile {
2023-07-07 22:11:33 -07:00
PICTRS_OPENTELEMETRY_URL = "http://otel:4137";
PICTRS__MEDIA__VIDEO_CODEC = "vp9";
PICTRS__MEDIA__GIF__MAX_WIDTH = "256";
PICTRS__MEDIA__GIF__MAX_HEIGHT = "256";
PICTRS__MEDIA__GIF__MAX_AREA = "65536";
PICTRS__MEDIA__GIF__MAX_FRAME_COUNT = "400";
RUST_LOG = "error";
2023-07-16 11:32:24 -07:00
PICTRS__API_KEY = pictrsApiKey;
};
target-file = "/run/lemmy/pictrs.env";
};
lemmyPostgresEnv = {
source-file = makeEnvFile {
2023-07-07 22:11:33 -07:00
POSTGRES_USER = "lemmy";
POSTGRES_PASSWORD = postgresPasswd;
2023-07-07 22:11:33 -07:00
POSTGRES_DB = "lemmy";
};
target-file = "/run/lemmy/postgres.env";
};
lemmyEnv = {
source-file = makeEnvFile { RUST_LOG = "error"; };
target-file = "/run/lemmy/lemmy.env";
};
lemmyUiEnv = {
source-file = makeEnvFile {
LEMMY_UI_LEMMY_INTERNAL_HOST = "lemmy:8536";
LEMMY_UI_LEMMY_EXTERNAL_HOST = cfg.hostname;
2023-07-19 17:59:23 -07:00
LEMMY_UI_HTTPS = 0;
};
target-file = "/run/lemmy/lemmy-ui.env";
};
2023-07-17 11:55:10 -07:00
lemmyCfg = {
source-file = makeLemmyCfg {
inherit (cfg) hostname;
inherit postgresPasswd pictrsApiKey;
smtpServer = cfg.smtp-server;
2023-07-17 19:54:33 -07:00
adminPasswd = adminPasswd;
2023-07-17 21:52:46 -07:00
siteName = cfg.site-name;
2023-07-17 11:55:10 -07:00
};
target-file = "/run/lemmy/lemmy.hjson";
};
2023-07-17 12:01:20 -07:00
lemmyNginxCfg = {
2023-07-17 11:55:10 -07:00
source-file = nginxCfgFile;
target-file = "/run/lemmy/nginx.conf";
};
lemmyPostgresCfg = {
source-file = postgresCfgFile;
target-file = "/var/lemmy/postgres.conf";
};
};
2023-07-17 17:52:53 -07:00
users.users = {
lemmy-pictrs = {
isSystemUser = true;
2023-07-17 20:53:32 -07:00
uid = 986;
2023-07-17 17:52:53 -07:00
group = "lemmy-pictrs";
};
lemmy-postgres = {
isSystemUser = true;
2023-07-17 20:53:32 -07:00
uid = 985;
2023-07-17 17:52:53 -07:00
group = "lemmy-postgres";
};
2023-07-17 08:16:18 -07:00
};
2023-07-17 08:01:30 -07:00
systemd = {
services.arion-lemmy = {
after = [ "network-online.target" ];
requires = [ "network-online.target" ];
};
tmpfiles.rules = [
"d ${cfg.state-directory}/pictrs 0700 lemmy-pictrs root - -"
"d ${cfg.state-directory}/postgres 0700 lemmy-postgres root - -"
];
};
2023-07-17 08:01:30 -07:00
virtualisation = {
2023-07-22 10:38:19 -07:00
arion.projects.lemmy.settings = let
lemmyImage = makeLemmyImage {
port = cfg.port;
stateDirectory = cfg.state-directory;
proxyCfg = {
image = "nginx:1-alpine";
configFile = hostSecrets.lemmyNginxCfg.target-file;
};
2023-07-22 10:38:19 -07:00
lemmyCfg = {
image = cfg.docker-images.lemmy;
configFile = hostSecrets.lemmyCfg.target-file;
envFile = hostSecrets.lemmyEnv.target-file;
};
lemmyUiCfg = {
image = cfg.docker-images.lemmy-ui;
envFile = hostSecrets.lemmyUiEnv.target-file;
};
pictrsCfg = {
image = cfg.docker-images.pictrs;
envFile = hostSecrets.lemmyPictrsEnv.target-file;
uid = config.users.users.lemmy-pictrs.uid;
};
postgresCfg = {
image = cfg.docker-images.postgres;
envFile = hostSecrets.lemmyPostgresEnv.target-file;
configFile = hostSecrets.lemmyPostgresCfg.target-file;
uid = config.users.users.lemmy-postgres.uid;
};
};
in { imports = [ lemmyImage ]; };
2023-07-07 22:11:33 -07:00
};
services.nginx = {
enable = true;
virtualHosts."${cfg.hostname}" = {
enableACME = true;
forceSSL = true;
locations = {
"/" = {
proxyPass = "http://localhost:${toString cfg.port}";
proxyWebsockets = true;
2023-07-17 19:36:14 -07:00
recommendedProxySettings = true;
# extraConfig = ''
# proxy_set_header Host $host;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection "Upgrade";
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# '';
2023-07-07 22:11:33 -07:00
};
};
};
};
});
}