host mastodon assets on a separate subdomain
This commit is contained in:
parent
7be407a2b7
commit
74f7ebdcce
5 changed files with 711 additions and 4 deletions
|
@ -16,6 +16,7 @@ in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./elasticsearch.nix
|
./elasticsearch.nix
|
||||||
|
../modules/mastodon.nix
|
||||||
];
|
];
|
||||||
services.mastodon = {
|
services.mastodon = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
@ -25,11 +26,17 @@ in
|
||||||
};
|
};
|
||||||
localDomain = "chir.rs";
|
localDomain = "chir.rs";
|
||||||
extraConfig = {
|
extraConfig = {
|
||||||
WEB_DOMAIN = "mastodon.darkkirb.de";
|
WEB_DOMAIN = "mastodon.chir.rs";
|
||||||
REDIS_NAMESPACE = "mastodon";
|
REDIS_NAMESPACE = "mastodon";
|
||||||
SINGLE_USER_MODE = "true";
|
SINGLE_USER_MODE = "true";
|
||||||
REDIS_HOST = "127.0.0.1";
|
REDIS_HOST = "127.0.0.1";
|
||||||
REDIS_PORT = toString config.services.redis.servers.mastodon.port;
|
REDIS_PORT = toString config.services.redis.servers.mastodon.port;
|
||||||
|
S3_ENABLED = "true";
|
||||||
|
S3_BUCKET = "mastodon-chir-rs";
|
||||||
|
S3_REGION = "us-west-000";
|
||||||
|
S3_PROTOCOL = "https";
|
||||||
|
S3_HOSTNAME = "s3.us-west-000.backblazeb2.com";
|
||||||
|
S3_ALIAS_HOST = "mastodon-assets.chir.rs";
|
||||||
};
|
};
|
||||||
redis.createLocally = false;
|
redis.createLocally = false;
|
||||||
otpSecretFile = config.sops.secrets."services/mastodon/otpSecret".path;
|
otpSecretFile = config.sops.secrets."services/mastodon/otpSecret".path;
|
||||||
|
@ -44,12 +51,16 @@ in
|
||||||
};
|
};
|
||||||
vapidPrivateKeyFile = config.sops.secrets."services/mastodon/vapid/private".path;
|
vapidPrivateKeyFile = config.sops.secrets."services/mastodon/vapid/private".path;
|
||||||
vapidPublicKeyFile = config.sops.secrets."services/mastodon/vapid/public".path;
|
vapidPublicKeyFile = config.sops.secrets."services/mastodon/vapid/public".path;
|
||||||
|
s3AccessKeyIdFile = config.sops.secrets."services/mastodon/s3/key_id".path;
|
||||||
|
s3SecretKeyFile = config.sops.secrets."services/mastodon/s3/secret_key".path;
|
||||||
};
|
};
|
||||||
sops.secrets."services/mastodon/otpSecret" = sopsConfig;
|
sops.secrets."services/mastodon/otpSecret" = sopsConfig;
|
||||||
sops.secrets."services/mastodon/secretKeyBase" = sopsConfig;
|
sops.secrets."services/mastodon/secretKeyBase" = sopsConfig;
|
||||||
sops.secrets."services/mastodon/smtpPassword" = sopsConfig;
|
sops.secrets."services/mastodon/smtpPassword" = sopsConfig;
|
||||||
sops.secrets."services/mastodon/vapid/private" = sopsConfig;
|
sops.secrets."services/mastodon/vapid/private" = sopsConfig;
|
||||||
sops.secrets."services/mastodon/vapid/public" = sopsConfig;
|
sops.secrets."services/mastodon/vapid/public" = sopsConfig;
|
||||||
|
sops.secrets."services/mastodon/s3/key_id" = sopsConfig;
|
||||||
|
sops.secrets."services/mastodon/s3/secret_key" = sopsConfig;
|
||||||
|
|
||||||
services.nginx.virtualHosts =
|
services.nginx.virtualHosts =
|
||||||
let mastodon = {
|
let mastodon = {
|
||||||
|
|
|
@ -21,4 +21,43 @@
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
services.nginx.virtualHosts."mastodon-assets.chir.rs" = {
|
||||||
|
sslCertificate = "/var/lib/acme/chir.rs/cert.pem";
|
||||||
|
sslCertificateKey = "/var/lib/acme/chir.rs/key.pem";
|
||||||
|
locations."/" = {
|
||||||
|
tryFiles = "$uri @s3";
|
||||||
|
};
|
||||||
|
locations."@s3" = {
|
||||||
|
extraConfig = ''
|
||||||
|
limit_except GET {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
proxy_set_header Host 's3.us-west-000.backblazeb2.com';
|
||||||
|
proxy_set_header Connection \'\';
|
||||||
|
proxy_set_header Authorization \'\';
|
||||||
|
proxy_hide_header Set-Cookie;
|
||||||
|
proxy_hide_header 'Access-Control-Allow-Origin';
|
||||||
|
proxy_hide_header 'Access-Control-Allow-Methods';
|
||||||
|
proxy_hide_header 'Access-Control-Allow-Headers';
|
||||||
|
proxy_hide_header x-amz-id-2;
|
||||||
|
proxy_hide_header x-amz-request-id;
|
||||||
|
proxy_hide_header x-amz-meta-server-side-encryption;
|
||||||
|
proxy_hide_header x-amz-server-side-encryption;
|
||||||
|
proxy_hide_header x-amz-bucket-region;
|
||||||
|
proxy_hide_header x-amzn-requestid;
|
||||||
|
proxy_ignore_headers Set-Cookie;
|
||||||
|
proxy_pass http://s3.us-west-000.backblazeb2.com$uri;
|
||||||
|
proxy_intercept_errors off;
|
||||||
|
proxy_cache CACHE;
|
||||||
|
proxy_cache_valid 200 48h;
|
||||||
|
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
|
||||||
|
proxy_cache_lock on;
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control public;
|
||||||
|
add_header 'Access-Control-Allow-Origin' '*';
|
||||||
|
add_header X-Cache-Status $upstream_cache_status;
|
||||||
|
'';
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
653
modules/mastodon.nix
Normal file
653
modules/mastodon.nix
Normal file
|
@ -0,0 +1,653 @@
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.mastodon;
|
||||||
|
# We only want to create a database if we're actually going to connect to it.
|
||||||
|
databaseActuallyCreateLocally = cfg.database.createLocally && cfg.database.host == "/run/postgresql";
|
||||||
|
|
||||||
|
env = {
|
||||||
|
RAILS_ENV = "production";
|
||||||
|
NODE_ENV = "production";
|
||||||
|
|
||||||
|
LD_PRELOAD = "${pkgs.jemalloc}/lib/libjemalloc.so";
|
||||||
|
|
||||||
|
# mastodon-web concurrency.
|
||||||
|
WEB_CONCURRENCY = toString cfg.webProcesses;
|
||||||
|
MAX_THREADS = toString cfg.webThreads;
|
||||||
|
|
||||||
|
# mastodon-streaming concurrency.
|
||||||
|
STREAMING_CLUSTER_NUM = toString cfg.streamingProcesses;
|
||||||
|
|
||||||
|
DB_USER = cfg.database.user;
|
||||||
|
|
||||||
|
REDIS_HOST = cfg.redis.host;
|
||||||
|
REDIS_PORT = toString (cfg.redis.port);
|
||||||
|
DB_HOST = cfg.database.host;
|
||||||
|
DB_PORT = toString (cfg.database.port);
|
||||||
|
DB_NAME = cfg.database.name;
|
||||||
|
LOCAL_DOMAIN = cfg.localDomain;
|
||||||
|
SMTP_SERVER = cfg.smtp.host;
|
||||||
|
SMTP_PORT = toString (cfg.smtp.port);
|
||||||
|
SMTP_FROM_ADDRESS = cfg.smtp.fromAddress;
|
||||||
|
PAPERCLIP_ROOT_PATH = "/var/lib/mastodon/public-system";
|
||||||
|
PAPERCLIP_ROOT_URL = "/system";
|
||||||
|
ES_ENABLED = if (cfg.elasticsearch.host != null) then "true" else "false";
|
||||||
|
ES_HOST = cfg.elasticsearch.host;
|
||||||
|
ES_PORT = toString (cfg.elasticsearch.port);
|
||||||
|
|
||||||
|
TRUSTED_PROXY_IP = cfg.trustedProxy;
|
||||||
|
}
|
||||||
|
// (if cfg.smtp.authenticate then { SMTP_LOGIN = cfg.smtp.user; } else { })
|
||||||
|
// cfg.extraConfig;
|
||||||
|
|
||||||
|
systemCallsList = [ "@cpu-emulation" "@debug" "@keyring" "@ipc" "@mount" "@obsolete" "@privileged" "@setuid" ];
|
||||||
|
|
||||||
|
cfgService = {
|
||||||
|
# User and group
|
||||||
|
User = cfg.user;
|
||||||
|
Group = cfg.group;
|
||||||
|
# State directory and mode
|
||||||
|
StateDirectory = "mastodon";
|
||||||
|
StateDirectoryMode = "0750";
|
||||||
|
# Logs directory and mode
|
||||||
|
LogsDirectory = "mastodon";
|
||||||
|
LogsDirectoryMode = "0750";
|
||||||
|
# Proc filesystem
|
||||||
|
ProcSubset = "pid";
|
||||||
|
ProtectProc = "invisible";
|
||||||
|
# Access write directories
|
||||||
|
UMask = "0027";
|
||||||
|
# Capabilities
|
||||||
|
CapabilityBoundingSet = "";
|
||||||
|
# Security
|
||||||
|
NoNewPrivileges = true;
|
||||||
|
# Sandboxing
|
||||||
|
ProtectSystem = "strict";
|
||||||
|
ProtectHome = true;
|
||||||
|
PrivateTmp = true;
|
||||||
|
PrivateDevices = true;
|
||||||
|
PrivateUsers = true;
|
||||||
|
ProtectClock = true;
|
||||||
|
ProtectHostname = true;
|
||||||
|
ProtectKernelLogs = true;
|
||||||
|
ProtectKernelModules = true;
|
||||||
|
ProtectKernelTunables = true;
|
||||||
|
ProtectControlGroups = true;
|
||||||
|
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ];
|
||||||
|
RestrictNamespaces = true;
|
||||||
|
LockPersonality = true;
|
||||||
|
MemoryDenyWriteExecute = false;
|
||||||
|
RestrictRealtime = true;
|
||||||
|
RestrictSUIDSGID = true;
|
||||||
|
RemoveIPC = true;
|
||||||
|
PrivateMounts = true;
|
||||||
|
# System Call Filtering
|
||||||
|
SystemCallArchitectures = "native";
|
||||||
|
};
|
||||||
|
|
||||||
|
envFile = pkgs.writeText "mastodon.env" (lib.concatMapStrings (s: s + "\n") (
|
||||||
|
(lib.concatLists (lib.mapAttrsToList
|
||||||
|
(name: value:
|
||||||
|
if value != null then [
|
||||||
|
"${name}=\"${toString value}\""
|
||||||
|
] else [ ]
|
||||||
|
)
|
||||||
|
env))
|
||||||
|
));
|
||||||
|
|
||||||
|
mastodonEnv = pkgs.writeShellScriptBin "mastodon-env" ''
|
||||||
|
set -a
|
||||||
|
export RAILS_ROOT="${cfg.package}"
|
||||||
|
source "${envFile}"
|
||||||
|
source /var/lib/mastodon/.secrets_env
|
||||||
|
eval -- "\$@"
|
||||||
|
'';
|
||||||
|
|
||||||
|
in
|
||||||
|
{
|
||||||
|
|
||||||
|
options = {
|
||||||
|
services.mastodon = {
|
||||||
|
enable = lib.mkEnableOption "Mastodon, a federated social network server";
|
||||||
|
|
||||||
|
configureNginx = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Configure nginx as a reverse proxy for mastodon.
|
||||||
|
Note that this makes some assumptions on your setup, and sets settings that will
|
||||||
|
affect other virtualHosts running on your nginx instance, if any.
|
||||||
|
Alternatively you can configure a reverse-proxy of your choice to serve these paths:
|
||||||
|
|
||||||
|
<code>/ -> $(nix-instantiate --eval '<nixpkgs>' -A mastodon.outPath)/public</code>
|
||||||
|
|
||||||
|
<code>/ -> 127.0.0.1:{{ webPort }} </code>(If there was no file in the directory above.)
|
||||||
|
|
||||||
|
<code>/system/ -> /var/lib/mastodon/public-system/</code>
|
||||||
|
|
||||||
|
<code>/api/v1/streaming/ -> 127.0.0.1:{{ streamingPort }}</code>
|
||||||
|
|
||||||
|
Make sure that websockets are forwarded properly. You might want to set up caching
|
||||||
|
of some requests. Take a look at mastodon's provided nginx configuration at
|
||||||
|
<code>https://github.com/mastodon/mastodon/blob/master/dist/nginx.conf</code>.
|
||||||
|
'';
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
user = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
User under which mastodon runs. If it is set to "mastodon",
|
||||||
|
that user will be created, otherwise it should be set to the
|
||||||
|
name of a user created elsewhere. In both cases,
|
||||||
|
<package>mastodon</package> and a package containing only
|
||||||
|
the shell script <code>mastodon-env</code> will be added to
|
||||||
|
the user's package set. To run a command from
|
||||||
|
<package>mastodon</package> such as <code>tootctl</code>
|
||||||
|
with the environment configured by this module use
|
||||||
|
<code>mastodon-env</code>, as in:
|
||||||
|
|
||||||
|
<code>mastodon-env tootctl accounts create newuser --email newuser@example.com</code>
|
||||||
|
'';
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "mastodon";
|
||||||
|
};
|
||||||
|
|
||||||
|
group = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Group under which mastodon runs.
|
||||||
|
'';
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "mastodon";
|
||||||
|
};
|
||||||
|
|
||||||
|
streamingPort = lib.mkOption {
|
||||||
|
description = "TCP port used by the mastodon-streaming service.";
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 55000;
|
||||||
|
};
|
||||||
|
streamingProcesses = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Processes used by the mastodon-streaming service.
|
||||||
|
Defaults to the number of CPU cores minus one.
|
||||||
|
'';
|
||||||
|
type = lib.types.nullOr lib.types.int;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
webPort = lib.mkOption {
|
||||||
|
description = "TCP port used by the mastodon-web service.";
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 55001;
|
||||||
|
};
|
||||||
|
webProcesses = lib.mkOption {
|
||||||
|
description = "Processes used by the mastodon-web service.";
|
||||||
|
type = lib.types.int;
|
||||||
|
default = 2;
|
||||||
|
};
|
||||||
|
webThreads = lib.mkOption {
|
||||||
|
description = "Threads per process used by the mastodon-web service.";
|
||||||
|
type = lib.types.int;
|
||||||
|
default = 5;
|
||||||
|
};
|
||||||
|
|
||||||
|
sidekiqPort = lib.mkOption {
|
||||||
|
description = "TCP port used by the mastodon-sidekiq service.";
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 55002;
|
||||||
|
};
|
||||||
|
sidekiqThreads = lib.mkOption {
|
||||||
|
description = "Worker threads used by the mastodon-sidekiq service.";
|
||||||
|
type = lib.types.int;
|
||||||
|
default = 25;
|
||||||
|
};
|
||||||
|
|
||||||
|
vapidPublicKeyFile = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Path to file containing the public key used for Web Push
|
||||||
|
Voluntary Application Server Identification. A new keypair can
|
||||||
|
be generated by running:
|
||||||
|
|
||||||
|
<code>nix build -f '<nixpkgs>' mastodon; cd result; bin/rake webpush:generate_keys</code>
|
||||||
|
|
||||||
|
If <option>mastodon.vapidPrivateKeyFile</option>does not
|
||||||
|
exist, it and this file will be created with a new keypair.
|
||||||
|
'';
|
||||||
|
default = "/var/lib/mastodon/secrets/vapid-public-key";
|
||||||
|
type = lib.types.str;
|
||||||
|
};
|
||||||
|
|
||||||
|
localDomain = lib.mkOption {
|
||||||
|
description = "The domain serving your Mastodon instance.";
|
||||||
|
example = "social.example.org";
|
||||||
|
type = lib.types.str;
|
||||||
|
};
|
||||||
|
|
||||||
|
secretKeyBaseFile = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Path to file containing the secret key base.
|
||||||
|
A new secret key base can be generated by running:
|
||||||
|
|
||||||
|
<code>nix build -f '<nixpkgs>' mastodon; cd result; bin/rake secret</code>
|
||||||
|
|
||||||
|
If this file does not exist, it will be created with a new secret key base.
|
||||||
|
'';
|
||||||
|
default = "/var/lib/mastodon/secrets/secret-key-base";
|
||||||
|
type = lib.types.str;
|
||||||
|
};
|
||||||
|
|
||||||
|
otpSecretFile = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Path to file containing the OTP secret.
|
||||||
|
A new OTP secret can be generated by running:
|
||||||
|
|
||||||
|
<code>nix build -f '<nixpkgs>' mastodon; cd result; bin/rake secret</code>
|
||||||
|
|
||||||
|
If this file does not exist, it will be created with a new OTP secret.
|
||||||
|
'';
|
||||||
|
default = "/var/lib/mastodon/secrets/otp-secret";
|
||||||
|
type = lib.types.str;
|
||||||
|
};
|
||||||
|
|
||||||
|
vapidPrivateKeyFile = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Path to file containing the private key used for Web Push
|
||||||
|
Voluntary Application Server Identification. A new keypair can
|
||||||
|
be generated by running:
|
||||||
|
|
||||||
|
<code>nix build -f '<nixpkgs>' mastodon; cd result; bin/rake webpush:generate_keys</code>
|
||||||
|
|
||||||
|
If this file does not exist, it will be created with a new
|
||||||
|
private key.
|
||||||
|
'';
|
||||||
|
default = "/var/lib/mastodon/secrets/vapid-private-key";
|
||||||
|
type = lib.types.str;
|
||||||
|
};
|
||||||
|
|
||||||
|
s3AccessKeyIdFile = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
};
|
||||||
|
s3SecretAccessKeyFile = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
};
|
||||||
|
|
||||||
|
trustedProxy = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
You need to set it to the IP from which your reverse proxy sends requests to Mastodon's web process,
|
||||||
|
otherwise Mastodon will record the reverse proxy's own IP as the IP of all requests, which would be
|
||||||
|
bad because IP addresses are used for important rate limits and security functions.
|
||||||
|
'';
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "127.0.0.1";
|
||||||
|
};
|
||||||
|
|
||||||
|
enableUnixSocket = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Instead of binding to an IP address like 127.0.0.1, you may bind to a Unix socket. This variable
|
||||||
|
is process-specific, e.g. you need different values for every process, and it works for both web (Puma)
|
||||||
|
processes and streaming API (Node.js) processes.
|
||||||
|
'';
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
redis = {
|
||||||
|
createLocally = lib.mkOption {
|
||||||
|
description = "Configure local Redis server for Mastodon.";
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
host = lib.mkOption {
|
||||||
|
description = "Redis host.";
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "127.0.0.1";
|
||||||
|
};
|
||||||
|
|
||||||
|
port = lib.mkOption {
|
||||||
|
description = "Redis port.";
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 6379;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
database = {
|
||||||
|
createLocally = lib.mkOption {
|
||||||
|
description = "Configure local PostgreSQL database server for Mastodon.";
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
host = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "/run/postgresql";
|
||||||
|
example = "192.168.23.42";
|
||||||
|
description = "Database host address or unix socket.";
|
||||||
|
};
|
||||||
|
|
||||||
|
port = lib.mkOption {
|
||||||
|
type = lib.types.int;
|
||||||
|
default = 5432;
|
||||||
|
description = "Database host port.";
|
||||||
|
};
|
||||||
|
|
||||||
|
name = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "mastodon";
|
||||||
|
description = "Database name.";
|
||||||
|
};
|
||||||
|
|
||||||
|
user = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "mastodon";
|
||||||
|
description = "Database user.";
|
||||||
|
};
|
||||||
|
|
||||||
|
passwordFile = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.path;
|
||||||
|
default = "/var/lib/mastodon/secrets/db-password";
|
||||||
|
example = "/run/keys/mastodon-db-password";
|
||||||
|
description = ''
|
||||||
|
A file containing the password corresponding to
|
||||||
|
<option>database.user</option>.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
smtp = {
|
||||||
|
createLocally = lib.mkOption {
|
||||||
|
description = "Configure local Postfix SMTP server for Mastodon.";
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
authenticate = lib.mkOption {
|
||||||
|
description = "Authenticate with the SMTP server using username and password.";
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
host = lib.mkOption {
|
||||||
|
description = "SMTP host used when sending emails to users.";
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "127.0.0.1";
|
||||||
|
};
|
||||||
|
|
||||||
|
port = lib.mkOption {
|
||||||
|
description = "SMTP port used when sending emails to users.";
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 25;
|
||||||
|
};
|
||||||
|
|
||||||
|
fromAddress = lib.mkOption {
|
||||||
|
description = ''"From" address used when sending Emails to users.'';
|
||||||
|
type = lib.types.str;
|
||||||
|
};
|
||||||
|
|
||||||
|
user = lib.mkOption {
|
||||||
|
description = "SMTP login name.";
|
||||||
|
type = lib.types.str;
|
||||||
|
};
|
||||||
|
|
||||||
|
passwordFile = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Path to file containing the SMTP password.
|
||||||
|
'';
|
||||||
|
default = "/var/lib/mastodon/secrets/smtp-password";
|
||||||
|
example = "/run/keys/mastodon-smtp-password";
|
||||||
|
type = lib.types.str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
elasticsearch = {
|
||||||
|
host = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
Elasticsearch host.
|
||||||
|
If it is not null, Elasticsearch full text search will be enabled.
|
||||||
|
'';
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
port = lib.mkOption {
|
||||||
|
description = "Elasticsearch port.";
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 9200;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
package = lib.mkOption {
|
||||||
|
type = lib.types.package;
|
||||||
|
default = pkgs.mastodon;
|
||||||
|
defaultText = lib.literalExpression "pkgs.mastodon";
|
||||||
|
description = "Mastodon package to use.";
|
||||||
|
};
|
||||||
|
|
||||||
|
extraConfig = lib.mkOption {
|
||||||
|
type = lib.types.attrs;
|
||||||
|
default = { };
|
||||||
|
description = ''
|
||||||
|
Extra environment variables to pass to all mastodon services.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
automaticMigrations = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Do automatic database migrations.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
disabledModules = [ "services/web-apps/mastodon.nix" ];
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
assertions = [
|
||||||
|
{
|
||||||
|
assertion = databaseActuallyCreateLocally -> (cfg.user == cfg.database.user);
|
||||||
|
message = ''For local automatic database provisioning (services.mastodon.database.createLocally == true) with peer authentication (services.mastodon.database.host == "/run/postgresql") to work services.mastodon.user and services.mastodon.database.user must be identical.'';
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
systemd.services.mastodon-init-dirs = {
|
||||||
|
script = ''
|
||||||
|
umask 077
|
||||||
|
|
||||||
|
if ! test -f ${cfg.secretKeyBaseFile}; then
|
||||||
|
mkdir -p $(dirname ${cfg.secretKeyBaseFile})
|
||||||
|
bin/rake secret > ${cfg.secretKeyBaseFile}
|
||||||
|
fi
|
||||||
|
if ! test -f ${cfg.otpSecretFile}; then
|
||||||
|
mkdir -p $(dirname ${cfg.otpSecretFile})
|
||||||
|
bin/rake secret > ${cfg.otpSecretFile}
|
||||||
|
fi
|
||||||
|
if ! test -f ${cfg.vapidPrivateKeyFile}; then
|
||||||
|
mkdir -p $(dirname ${cfg.vapidPrivateKeyFile}) $(dirname ${cfg.vapidPublicKeyFile})
|
||||||
|
keypair=$(bin/rake webpush:generate_keys)
|
||||||
|
echo $keypair | grep --only-matching "Private -> [^ ]\+" | sed 's/^Private -> //' > ${cfg.vapidPrivateKeyFile}
|
||||||
|
echo $keypair | grep --only-matching "Public -> [^ ]\+" | sed 's/^Public -> //' > ${cfg.vapidPublicKeyFile}
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat > /var/lib/mastodon/.secrets_env <<EOF
|
||||||
|
SECRET_KEY_BASE="$(cat ${cfg.secretKeyBaseFile})"
|
||||||
|
OTP_SECRET="$(cat ${cfg.otpSecretFile})"
|
||||||
|
VAPID_PRIVATE_KEY="$(cat ${cfg.vapidPrivateKeyFile})"
|
||||||
|
VAPID_PUBLIC_KEY="$(cat ${cfg.vapidPublicKeyFile})"
|
||||||
|
DB_PASS="$(cat ${cfg.database.passwordFile})"
|
||||||
|
AWS_ACCESS_KEY_ID="$(cat ${cfg.s3AccessKeyIdFile})"
|
||||||
|
AWS_SECRET_ACCESS_KEY="$(cat ${cfg.s3SecretAccessKeyFile})"
|
||||||
|
'' + (if cfg.smtp.authenticate then ''
|
||||||
|
SMTP_PASSWORD="$(cat ${cfg.smtp.passwordFile})"
|
||||||
|
'' else "") + ''
|
||||||
|
EOF
|
||||||
|
'';
|
||||||
|
environment = env;
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
WorkingDirectory = cfg.package;
|
||||||
|
# System Call Filtering
|
||||||
|
SystemCallFilter = [ ("~" + lib.concatStringsSep " " (systemCallsList ++ [ "@resources" ])) "@chown" "pipe" "pipe2" ];
|
||||||
|
} // cfgService;
|
||||||
|
|
||||||
|
after = [ "network.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.mastodon-init-db = lib.mkIf cfg.automaticMigrations {
|
||||||
|
script = ''
|
||||||
|
if [ `psql ${cfg.database.name} -c \
|
||||||
|
"select count(*) from pg_class c \
|
||||||
|
join pg_namespace s on s.oid = c.relnamespace \
|
||||||
|
where s.nspname not in ('pg_catalog', 'pg_toast', 'information_schema') \
|
||||||
|
and s.nspname not like 'pg_temp%';" | sed -n 3p` -eq 0 ]; then
|
||||||
|
SAFETY_ASSURED=1 rails db:schema:load
|
||||||
|
rails db:seed
|
||||||
|
else
|
||||||
|
rails db:migrate
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
path = [ cfg.package pkgs.postgresql ];
|
||||||
|
environment = env;
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
EnvironmentFile = "/var/lib/mastodon/.secrets_env";
|
||||||
|
WorkingDirectory = cfg.package;
|
||||||
|
# System Call Filtering
|
||||||
|
SystemCallFilter = [ ("~" + lib.concatStringsSep " " (systemCallsList ++ [ "@resources" ])) "@chown" "pipe" "pipe2" ];
|
||||||
|
} // cfgService;
|
||||||
|
after = [ "mastodon-init-dirs.service" "network.target" ] ++ (if databaseActuallyCreateLocally then [ "postgresql.service" ] else [ ]);
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.mastodon-streaming = {
|
||||||
|
after = [ "network.target" ]
|
||||||
|
++ (if databaseActuallyCreateLocally then [ "postgresql.service" ] else [ ])
|
||||||
|
++ (if cfg.automaticMigrations then [ "mastodon-init-db.service" ] else [ "mastodon-init-dirs.service" ]);
|
||||||
|
description = "Mastodon streaming";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
environment = env // (if cfg.enableUnixSocket
|
||||||
|
then { SOCKET = "/run/mastodon-streaming/streaming.socket"; }
|
||||||
|
else { PORT = toString (cfg.streamingPort); }
|
||||||
|
);
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = "${cfg.package}/run-streaming.sh";
|
||||||
|
Restart = "always";
|
||||||
|
RestartSec = 20;
|
||||||
|
EnvironmentFile = "/var/lib/mastodon/.secrets_env";
|
||||||
|
WorkingDirectory = cfg.package;
|
||||||
|
# Runtime directory and mode
|
||||||
|
RuntimeDirectory = "mastodon-streaming";
|
||||||
|
RuntimeDirectoryMode = "0750";
|
||||||
|
# System Call Filtering
|
||||||
|
SystemCallFilter = [ ("~" + lib.concatStringsSep " " (systemCallsList ++ [ "@memlock" "@resources" ])) "pipe" "pipe2" ];
|
||||||
|
} // cfgService;
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.mastodon-web = {
|
||||||
|
after = [ "network.target" ]
|
||||||
|
++ (if databaseActuallyCreateLocally then [ "postgresql.service" ] else [ ])
|
||||||
|
++ (if cfg.automaticMigrations then [ "mastodon-init-db.service" ] else [ "mastodon-init-dirs.service" ]);
|
||||||
|
description = "Mastodon web";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
environment = env // (if cfg.enableUnixSocket
|
||||||
|
then { SOCKET = "/run/mastodon-web/web.socket"; }
|
||||||
|
else { PORT = toString (cfg.webPort); }
|
||||||
|
);
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = "${cfg.package}/bin/puma -C config/puma.rb";
|
||||||
|
Restart = "always";
|
||||||
|
RestartSec = 20;
|
||||||
|
EnvironmentFile = "/var/lib/mastodon/.secrets_env";
|
||||||
|
WorkingDirectory = cfg.package;
|
||||||
|
# Runtime directory and mode
|
||||||
|
RuntimeDirectory = "mastodon-web";
|
||||||
|
RuntimeDirectoryMode = "0750";
|
||||||
|
# System Call Filtering
|
||||||
|
SystemCallFilter = [ ("~" + lib.concatStringsSep " " systemCallsList) "@chown" "pipe" "pipe2" ];
|
||||||
|
} // cfgService;
|
||||||
|
path = with pkgs; [ file imagemagick ffmpeg ];
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.mastodon-sidekiq = {
|
||||||
|
after = [ "network.target" ]
|
||||||
|
++ (if databaseActuallyCreateLocally then [ "postgresql.service" ] else [ ])
|
||||||
|
++ (if cfg.automaticMigrations then [ "mastodon-init-db.service" ] else [ "mastodon-init-dirs.service" ]);
|
||||||
|
description = "Mastodon sidekiq";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
environment = env // {
|
||||||
|
PORT = toString (cfg.sidekiqPort);
|
||||||
|
DB_POOL = toString cfg.sidekiqThreads;
|
||||||
|
};
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = "${cfg.package}/bin/sidekiq -c ${toString cfg.sidekiqThreads} -r ${cfg.package}";
|
||||||
|
Restart = "always";
|
||||||
|
RestartSec = 20;
|
||||||
|
EnvironmentFile = "/var/lib/mastodon/.secrets_env";
|
||||||
|
WorkingDirectory = cfg.package;
|
||||||
|
# System Call Filtering
|
||||||
|
SystemCallFilter = [ ("~" + lib.concatStringsSep " " systemCallsList) "@chown" "pipe" "pipe2" ];
|
||||||
|
} // cfgService;
|
||||||
|
path = with pkgs; [ file imagemagick ffmpeg ];
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nginx = lib.mkIf cfg.configureNginx {
|
||||||
|
enable = true;
|
||||||
|
recommendedProxySettings = true; # required for redirections to work
|
||||||
|
virtualHosts."${cfg.localDomain}" = {
|
||||||
|
root = "${cfg.package}/public/";
|
||||||
|
forceSSL = true; # mastodon only supports https
|
||||||
|
enableACME = true;
|
||||||
|
|
||||||
|
locations."/system/".alias = "/var/lib/mastodon/public-system/";
|
||||||
|
|
||||||
|
locations."/" = {
|
||||||
|
tryFiles = "$uri @proxy";
|
||||||
|
};
|
||||||
|
|
||||||
|
locations."@proxy" = {
|
||||||
|
proxyPass = (if cfg.enableUnixSocket then "http://unix:/run/mastodon-web/web.socket" else "http://127.0.0.1:${toString(cfg.webPort)}");
|
||||||
|
proxyWebsockets = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
locations."/api/v1/streaming/" = {
|
||||||
|
proxyPass = (if cfg.enableUnixSocket then "http://unix:/run/mastodon-streaming/streaming.socket" else "http://127.0.0.1:${toString(cfg.streamingPort)}/");
|
||||||
|
proxyWebsockets = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.postfix = lib.mkIf (cfg.smtp.createLocally && cfg.smtp.host == "127.0.0.1") {
|
||||||
|
enable = true;
|
||||||
|
hostname = lib.mkDefault "${cfg.localDomain}";
|
||||||
|
};
|
||||||
|
services.redis = lib.mkIf (cfg.redis.createLocally && cfg.redis.host == "127.0.0.1") {
|
||||||
|
enable = true;
|
||||||
|
};
|
||||||
|
services.postgresql = lib.mkIf databaseActuallyCreateLocally {
|
||||||
|
enable = true;
|
||||||
|
ensureUsers = [
|
||||||
|
{
|
||||||
|
name = cfg.database.user;
|
||||||
|
ensurePermissions."DATABASE ${cfg.database.name}" = "ALL PRIVILEGES";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
ensureDatabases = [ cfg.database.name ];
|
||||||
|
};
|
||||||
|
|
||||||
|
users.users = lib.mkMerge [
|
||||||
|
(lib.mkIf (cfg.user == "mastodon") {
|
||||||
|
mastodon = {
|
||||||
|
isSystemUser = true;
|
||||||
|
home = cfg.package;
|
||||||
|
inherit (cfg) group;
|
||||||
|
};
|
||||||
|
})
|
||||||
|
(lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package mastodonEnv ])
|
||||||
|
];
|
||||||
|
|
||||||
|
users.groups.${cfg.group}.members = lib.optional cfg.configureNginx config.services.nginx.user;
|
||||||
|
};
|
||||||
|
|
||||||
|
meta.maintainers = with lib.maintainers; [ happy-river erictapen ];
|
||||||
|
|
||||||
|
}
|
|
@ -15,6 +15,9 @@ services:
|
||||||
vapid:
|
vapid:
|
||||||
private: ENC[AES256_GCM,data:h47YNhhrD4hCT/5Ckx5ERreV13vAurpQCgT/sgAW/4wVyOTT3LMGGgKOoVo=,iv:91q/+UzQnMUpOy57I8y0ugl6o0lojMxAGKE2jAuWYaQ=,tag:NqBbdu86G2JheQfz1N1esg==,type:str]
|
private: ENC[AES256_GCM,data:h47YNhhrD4hCT/5Ckx5ERreV13vAurpQCgT/sgAW/4wVyOTT3LMGGgKOoVo=,iv:91q/+UzQnMUpOy57I8y0ugl6o0lojMxAGKE2jAuWYaQ=,tag:NqBbdu86G2JheQfz1N1esg==,type:str]
|
||||||
public: ENC[AES256_GCM,data:/KX8PQozWSDTc/cHhepFfGEXzAKFNf7805ExjvuQjgISmT3e0dCbDwaYGgGbLv91Zu2fRiCtm6C52KvBPoLRcdYHuXHP0eJ5Pv6NSHwHk3ZBubXk1wmp8A==,iv:/kHzyM+/FG2nBZnC0ncRR3Ye/x07jAEB31hf6g0JUZw=,tag:OwvhEwFrG19lkmvwXsYNjg==,type:str]
|
public: ENC[AES256_GCM,data:/KX8PQozWSDTc/cHhepFfGEXzAKFNf7805ExjvuQjgISmT3e0dCbDwaYGgGbLv91Zu2fRiCtm6C52KvBPoLRcdYHuXHP0eJ5Pv6NSHwHk3ZBubXk1wmp8A==,iv:/kHzyM+/FG2nBZnC0ncRR3Ye/x07jAEB31hf6g0JUZw=,tag:OwvhEwFrG19lkmvwXsYNjg==,type:str]
|
||||||
|
s3:
|
||||||
|
key_id: ENC[AES256_GCM,data:zb6l+BVvjvwrFAuFvuTn89qWyb9scwSQgA==,iv:ZIqMAM2m+TLooWRKy0JDEh1Cz7dEqhc9u1fJr/YJsRo=,tag:u9cxbL+0VBrzM7w2tCXrVg==,type:str]
|
||||||
|
secret_key: ENC[AES256_GCM,data:F67XmNAgVIRpTKooQBDtk9BAKv6oD/p+Poos62ox8A==,iv:oU4KKjTjFoeNkLngiMPkqGqINKm6nHf8HyD7C4BmFXc=,tag:5zJCkejgW7preo0f80Zf3w==,type:str]
|
||||||
security:
|
security:
|
||||||
restic:
|
restic:
|
||||||
password: ENC[AES256_GCM,data:n+M6pfe0YrONaYo3HSnijHxhThg=,iv:0J2t+58tYRJD1GmnJa8w30U+RwOl67eWeHhvLk0eeks=,tag:ivuZqpGrU7ZHFZ4IiMvxBw==,type:str]
|
password: ENC[AES256_GCM,data:n+M6pfe0YrONaYo3HSnijHxhThg=,iv:0J2t+58tYRJD1GmnJa8w30U+RwOl67eWeHhvLk0eeks=,tag:ivuZqpGrU7ZHFZ4IiMvxBw==,type:str]
|
||||||
|
@ -45,8 +48,8 @@ sops:
|
||||||
WnV3QWxtalIzWFdoQmpDTmJsNGdNOW8K++rFGXy0G6Gcu2gQwSP6xfXInQ/y5nh5
|
WnV3QWxtalIzWFdoQmpDTmJsNGdNOW8K++rFGXy0G6Gcu2gQwSP6xfXInQ/y5nh5
|
||||||
2oGp8sfOLFWnNI4SWL0ChP47K3C/9ysUHwQnUYPbRafZ/4X6cN40ZQ==
|
2oGp8sfOLFWnNI4SWL0ChP47K3C/9ysUHwQnUYPbRafZ/4X6cN40ZQ==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
lastmodified: "2022-04-25T16:47:24Z"
|
lastmodified: "2022-04-26T08:29:23Z"
|
||||||
mac: ENC[AES256_GCM,data:r6mgABN7lNnndt3vV1uWGpdtuFFGbUl+SlKwpOqAVbqDIqHGAFmErxEyz9S+EZYhrhG5BNa9Bih79sGOf4dPHsfZWJmx3YYKruQyJa8Z99qvNYFXHjiVn442z5mo2DJS3ViWPUhMcxvzD7RqeHTyYyMPKGoN2gQ5AzFCT7UxHtg=,iv:i8pzZpvOtbFyYG/42nCMZL8DAA6703UF/KInT9CCH4c=,tag:G7FpFMHwW6eSU+n8Px3HLw==,type:str]
|
mac: ENC[AES256_GCM,data:4hRnDjbp5lize/pgvvDbQKxoRxntZLJT29q623MUzzbalGKozQfb9MO1pqDLVuxnlJhpBqYlxslQiv0HHOVv/15jWg770QC7EN1d8U8Qayp9PPhfgMryky4e/uAsPBUy5sy89Jk/LTCgL2mppWHRnLacxEk+qDIeAaUAAOmAaq4=,iv:DwVdlpPJhVU3zT1c406JH3iKl9Hj2HtOPUZXY132t+k=,tag:y/rsMRc0O5Thp4126A7HqA==,type:str]
|
||||||
pgp:
|
pgp:
|
||||||
- created_at: "2022-04-24T10:34:20Z"
|
- created_at: "2022-04-24T10:34:20Z"
|
||||||
enc: |
|
enc: |
|
||||||
|
|
|
@ -92,7 +92,7 @@ let
|
||||||
SOA = {
|
SOA = {
|
||||||
nameServer = "ns1.chir.rs.";
|
nameServer = "ns1.chir.rs.";
|
||||||
adminEmail = "lotte@chir.rs";
|
adminEmail = "lotte@chir.rs";
|
||||||
serial = 9;
|
serial = 10;
|
||||||
};
|
};
|
||||||
NS = [
|
NS = [
|
||||||
"ns1.chir.rs."
|
"ns1.chir.rs."
|
||||||
|
@ -171,6 +171,7 @@ let
|
||||||
ns2 = createZone { };
|
ns2 = createZone { };
|
||||||
hydra = createZone { };
|
hydra = createZone { };
|
||||||
mastodon = createZone { };
|
mastodon = createZone { };
|
||||||
|
mastodon-files = createZone { };
|
||||||
|
|
||||||
int = delegateTo [
|
int = delegateTo [
|
||||||
"ns1.chir.rs."
|
"ns1.chir.rs."
|
||||||
|
|
Loading…
Reference in a new issue