2024-11-05 23:12:35 +01:00
|
|
|
|
{ self, linux-hardened, ... }: { lib, config, pkgs, ... }:
|
2024-08-18 13:47:18 +02:00
|
|
|
|
|
|
|
|
|
with lib; let
|
|
|
|
|
ports = {
|
|
|
|
|
acme = 1360;
|
|
|
|
|
nginx = 8080;
|
|
|
|
|
synapse = 8008;
|
|
|
|
|
unbound = 8484;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
security-txt = pkgs.writeText "security.txt" ''
|
2024-08-22 19:26:28 +02:00
|
|
|
|
-----BEGIN SSH SIGNED MESSAGE-----
|
2024-08-18 13:47:18 +02:00
|
|
|
|
Canonical: https://solitary.social/.well-known/security.txt
|
|
|
|
|
Contact: mailto:mvs@nya.yt
|
2024-08-22 19:26:28 +02:00
|
|
|
|
Encryption: data:application/x-age-public-key,age1dexxdduwl37hsfdxde6le0satatrfv4geva0cxt8qqw3n46vgavsanuewp
|
2024-08-18 13:47:18 +02:00
|
|
|
|
Preferred-Languages: en, de
|
2024-08-22 19:26:28 +02:00
|
|
|
|
-----END SSH SIGNED MESSAGE-----
|
|
|
|
|
-----BEGIN SSH SIGNATURE-----
|
|
|
|
|
U1NIU0lHAAAAAQAAAEoAAAAac2stc3NoLWVkMjU1MTlAb3BlbnNzaC5jb20AAAAgJzM8dH
|
|
|
|
|
Bj0wDAMaVwHRCAw4mNyksmFVTdyi+tb1EFLrYAAAAEc3NoOgAAAARmaWxlAAAAAAAAAAZz
|
|
|
|
|
aGE1MTIAAABnAAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAQNoPCgqiDsNs8+
|
|
|
|
|
PyhjKdWF3P0TkA3gXH9fRSCRJvlMTz5hlhusz6ipEnKb8q/fYIwiuPsIJQseevg1kFZTe3
|
|
|
|
|
vAoBAAADlA==
|
|
|
|
|
-----END SSH SIGNATURE-----
|
2024-08-18 13:47:18 +02:00
|
|
|
|
'';
|
|
|
|
|
in {
|
|
|
|
|
imports = with self.nixosModules; [
|
|
|
|
|
default
|
|
|
|
|
headless
|
|
|
|
|
acme-ocsp
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
boot.loader.grub = {
|
|
|
|
|
enable = true;
|
|
|
|
|
device = "/dev/vda";
|
|
|
|
|
};
|
|
|
|
|
|
2024-10-11 12:21:35 +02:00
|
|
|
|
boot.kernelParams = [
|
|
|
|
|
"hugepagesz=1G" "hugepages=1"
|
|
|
|
|
];
|
|
|
|
|
|
2024-08-18 13:47:18 +02:00
|
|
|
|
boot.kernelPackages = let
|
2024-11-05 23:12:35 +01:00
|
|
|
|
inherit (linux-hardened.packages.x86_64-linux) default;
|
|
|
|
|
in pkgs.linuxPackagesFor (default.override {
|
2024-08-18 13:47:18 +02:00
|
|
|
|
instSetArch = "x86-64-v3";
|
2024-11-05 23:12:35 +01:00
|
|
|
|
profiles = { paravirt = true; };
|
|
|
|
|
extraConfig = with linux-hardened.lib.kernel; {
|
2024-08-22 22:09:13 +02:00
|
|
|
|
NR_CPUS = 8;
|
|
|
|
|
|
|
|
|
|
BTRFS_FS = true;
|
|
|
|
|
BTRFS_FS_POSIX_ACL = true;
|
2024-11-05 23:12:35 +01:00
|
|
|
|
};
|
2024-08-18 13:47:18 +02:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
environment.etc."machine-id".text = "1c97ae368741530de77aad42b5a6ae42";
|
|
|
|
|
|
|
|
|
|
ephemeral.device = "UUID=07a91cc3-4dd4-48e6-81d7-eb5d31fcf720";
|
|
|
|
|
ephemeral.boot.device = "UUID=24c72e0c-b467-4def-a641-ae09100465f0";
|
|
|
|
|
ephemeral.boot.fsType = "ext4";
|
|
|
|
|
|
|
|
|
|
i18n.supportedLocales = [ "C.UTF-8/UTF-8" "en_EU.UTF-8/UTF-8" "en_GB.UTF-8/UTF-8" ];
|
|
|
|
|
|
|
|
|
|
networking = {
|
|
|
|
|
hostName = "solitary";
|
|
|
|
|
domain = "social";
|
|
|
|
|
firewall.allowedTCPPorts = [ 22 80 443 853 ];
|
|
|
|
|
firewall.allowedUDPPorts = [ 443 ];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
security.acme = {
|
|
|
|
|
certs.${config.networking.fqdn} = {
|
|
|
|
|
email = "mvs@nya.yt";
|
|
|
|
|
listenHTTP = "127.0.0.1:${toString ports.acme}";
|
|
|
|
|
reloadServices = [ "haproxy.service" "unbound.service" ];
|
|
|
|
|
extraDomainNames = [
|
|
|
|
|
"cache.solitary.social"
|
|
|
|
|
"matrix.solitary.social"
|
|
|
|
|
"media.solitary.social"
|
|
|
|
|
"resolve.solitary.social"
|
|
|
|
|
];
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
services.akkoma.enable = true;
|
2024-11-03 13:03:03 +01:00
|
|
|
|
services.akkoma.extraPackages = with pkgs; [ exiftool ffmpeg-headless imagemagick ];
|
2024-08-18 13:47:18 +02:00
|
|
|
|
services.akkoma.extraStatic."emoji/blobs.gg" = pkgs.akkoma-emoji.blobs_gg;
|
|
|
|
|
services.akkoma.extraStatic."static/terms-of-service.html" = pkgs.writeText "terms-of-service.html" ''
|
|
|
|
|
<h2>Commitments</h2>
|
2024-11-07 21:02:20 +01:00
|
|
|
|
<p>This was originally a single‐user instance and therefore I decided to formulate what would be <em>Terms of Service</em> for a multi‐user user instance as commitments. These are still incomplete and subject to expansion in the future.</p>
|
2024-08-18 13:47:18 +02:00
|
|
|
|
<ul>
|
|
|
|
|
<li>I shall observe and respect your boundaries.</li>
|
|
|
|
|
<li>I shall respect your right to disengage, and support you if you wish to disengage from others.</li>
|
|
|
|
|
<li>I shall accept that you may not want to be confronted with certain content and tag my posts appropriately.</li>
|
|
|
|
|
</ul>
|
|
|
|
|
'';
|
|
|
|
|
|
|
|
|
|
services.akkoma.extraStatic."favicon.png" = let
|
|
|
|
|
rev = "697a8211b0f427a921e7935a35d14bb3e32d0a2c";
|
|
|
|
|
in pkgs.stdenvNoCC.mkDerivation {
|
|
|
|
|
name = "favicon.png";
|
|
|
|
|
|
|
|
|
|
src = pkgs.fetchurl {
|
|
|
|
|
url = "https://raw.githubusercontent.com/TilCreator/NixOwO/${rev}/NixOwO_plain.svg";
|
|
|
|
|
hash = "sha256-tWhHMfJ3Od58N9H5yOKPMfM56hYWSOnr/TGCBi8bo9E=";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
nativeBuildInputs = with pkgs; [ librsvg ];
|
|
|
|
|
|
|
|
|
|
dontUnpack = true;
|
|
|
|
|
installPhase = ''
|
|
|
|
|
rsvg-convert -o $out -w 96 -h 96 $src
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
2024-10-11 13:12:18 +02:00
|
|
|
|
services.akkoma.dist.extraFlags = [
|
|
|
|
|
"-MMlp" "on"
|
|
|
|
|
"-MMsco" "true"
|
|
|
|
|
"-MMscs" "1024"
|
|
|
|
|
];
|
|
|
|
|
|
2024-08-18 13:47:18 +02:00
|
|
|
|
services.akkoma.config = let
|
|
|
|
|
elixir = pkgs.formats.elixirConf { };
|
2024-11-06 14:38:21 +01:00
|
|
|
|
attrsToTuples = lib.mapAttrsToList (name: value: elixir.lib.mkTuple [ name value ]);
|
2024-08-18 13:47:18 +02:00
|
|
|
|
in with elixir.lib; {
|
|
|
|
|
":pleroma" = {
|
|
|
|
|
":instance" = {
|
|
|
|
|
name = "solitary.social";
|
|
|
|
|
email = "mvs+solitary.social@nya.yt";
|
|
|
|
|
notify_email = "akkoma@solitary.social";
|
|
|
|
|
description = "Single‐user fediverse instance";
|
|
|
|
|
instance_thumbnail = "/instance/thumbnail.avif";
|
|
|
|
|
limit = 5120;
|
|
|
|
|
description_limit = 5120;
|
|
|
|
|
remote_limit = 131072;
|
|
|
|
|
upload_limit = 160 * 1024 * 1024;
|
|
|
|
|
avatar_upload_limit = 2097152;
|
|
|
|
|
background_upload_limit = 4194304;
|
|
|
|
|
banner_upload_limit = 4194304;
|
|
|
|
|
registrations_open = false;
|
|
|
|
|
account_approval_required = true;
|
|
|
|
|
remote_post_retention_days = 180;
|
|
|
|
|
user_bio_length = 5120;
|
|
|
|
|
user_name_length = 64;
|
|
|
|
|
max_account_fields = 8;
|
|
|
|
|
cleanup_attachments = true;
|
2024-11-01 16:56:28 +01:00
|
|
|
|
local_bubble = [
|
|
|
|
|
"florp.social"
|
|
|
|
|
];
|
2024-08-18 13:47:18 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
"Pleroma.Web.Endpoint" = {
|
|
|
|
|
secret_key_base._secret = "/var/lib/secrets/akkoma/key-base";
|
|
|
|
|
signing_salt._secret = "/var/lib/secrets/akkoma/signing-salt";
|
|
|
|
|
live_view.signing_salt._secret = "/var/lib/secrets/akkoma/liveview-salt";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
"Pleroma.Emails.Mailer" = {
|
|
|
|
|
enabled = true;
|
|
|
|
|
adapter = mkRaw "Swoosh.Adapters.SMTP";
|
|
|
|
|
relay = "localhost";
|
|
|
|
|
dkim = {
|
|
|
|
|
a = "ed25519-sha256";
|
|
|
|
|
s = "akkoma";
|
|
|
|
|
d = config.networking.fqdn;
|
|
|
|
|
private_key = mkTuple [
|
|
|
|
|
(mkAtom ":pem_plain")
|
|
|
|
|
(mkRaw ''File.read!("/var/lib/akkoma/dkim.pem")'')
|
|
|
|
|
];
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
":database".rum_enabled = true;
|
|
|
|
|
|
|
|
|
|
":media_proxy" = {
|
|
|
|
|
enabled = true;
|
|
|
|
|
base_url = "https://cache.solitary.social";
|
|
|
|
|
proxy_opts.redirect_on_failure = true;
|
|
|
|
|
proxy_opts.max_body_length = 64 * 1024 * 1024;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
":media_preview_proxy" = {
|
2024-11-03 13:03:03 +01:00
|
|
|
|
enabled = true;
|
2024-08-18 13:47:18 +02:00
|
|
|
|
thumbnail_max_width = 1920;
|
|
|
|
|
thumbnail_max_height = 1080;
|
|
|
|
|
min_content_length = 128 * 1024;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
"Pleroma.Upload".base_url = "https://media.solitary.social";
|
|
|
|
|
|
|
|
|
|
"Pleroma.Upload".filters = map mkRaw [
|
|
|
|
|
"Pleroma.Upload.Filter.Exiftool.ReadDescription"
|
|
|
|
|
"Pleroma.Upload.Filter.Exiftool.StripMetadata"
|
|
|
|
|
"Pleroma.Upload.Filter.Dedupe"
|
|
|
|
|
"Pleroma.Upload.Filter.AnonymizeFilename"
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
":mrf".policies = map mkRaw [
|
|
|
|
|
"Pleroma.Web.ActivityPub.MRF.SimplePolicy"
|
|
|
|
|
"Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy"
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
":mrf_simple" = {
|
2024-11-06 14:38:21 +01:00
|
|
|
|
reject = attrsToTuples {
|
2024-08-18 13:47:18 +02:00
|
|
|
|
"bae.st" = "harassment";
|
|
|
|
|
"brighteon.social" = "incompatible";
|
|
|
|
|
"detroitriotcity.com" = "incompatible";
|
|
|
|
|
"freeatlantis.com" = "incompatible";
|
|
|
|
|
"freespeechextremist.com" = "incompatible";
|
|
|
|
|
"gab.com" = "incompatible";
|
|
|
|
|
"gleasonator.com" = "incompatible";
|
|
|
|
|
"kitsunemimi.club" = "incompatible";
|
|
|
|
|
"poa.st" = "incompatible";
|
|
|
|
|
"seal.cafe" = "harassment";
|
|
|
|
|
"social.quodverum.com" = "incompatible";
|
|
|
|
|
"spinster.xyz" = "incompatible";
|
|
|
|
|
"truthsocial.co.in" = "incompatible";
|
|
|
|
|
"varishangout.net" = "incompatible";
|
|
|
|
|
|
|
|
|
|
"activitypub-troll.cf" = "security";
|
|
|
|
|
"misskey-forkbomb.cf" = "security";
|
|
|
|
|
"repl.co" = "security";
|
|
|
|
|
};
|
|
|
|
|
|
2024-11-06 14:38:21 +01:00
|
|
|
|
followers_only = attrsToTuples {
|
2024-08-18 13:47:18 +02:00
|
|
|
|
"bitcoinhackers.org" = "annoying";
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
":mrf_object_age".threshold = 90 * 24 * 3600;
|
|
|
|
|
|
|
|
|
|
":frontend_configurations" = {
|
|
|
|
|
pleroma_fe = mkMap {
|
|
|
|
|
collapseMessageWithSubject = true;
|
|
|
|
|
hideSiteFavicon = true;
|
|
|
|
|
streaming = true;
|
|
|
|
|
webPushNotifications = true;
|
|
|
|
|
useStreamingApi = true;
|
|
|
|
|
scopeCopy = true;
|
|
|
|
|
showFeaturesPanel = false;
|
|
|
|
|
subjectLineBehavior = "masto";
|
|
|
|
|
alwaysShowSubjectInput = true;
|
|
|
|
|
postContentType = "text/markdown";
|
|
|
|
|
modalOnRepeat = true;
|
|
|
|
|
minimalScopesMode = true;
|
|
|
|
|
redirectRootNoLogin = "/mkl";
|
|
|
|
|
translationLanguage = "EN";
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
":restrict_unauthenticated" = {
|
|
|
|
|
timelines = mkMap {
|
|
|
|
|
local = false;
|
|
|
|
|
federated = true;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
":translator" = {
|
|
|
|
|
enabled = true;
|
|
|
|
|
module = mkRaw "Pleroma.Akkoma.Translators.DeepL";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
":deepl" = {
|
|
|
|
|
tier = mkAtom ":free";
|
|
|
|
|
api_key._secret = "/var/lib/secrets/akkoma/deepl";
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
":web_push_encryption".":vapid_details" = {
|
|
|
|
|
subject = "mailto:mvs+solitary.social@nya.yt";
|
|
|
|
|
public_key = "BPwdJZjBeZw_ZkWU_RQ48RdPI2pHIhMAYaNJc6xut4nQRi2YSaKnfP_kLrXzRjETQh5VJsDI-azYCeEhtk-C33s";
|
|
|
|
|
private_key._secret = "/var/lib/secrets/akkoma/vapid";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
":joken".":default_signer"._secret = "/var/lib/secrets/akkoma/jwt-signer";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
services.haproxy.enable = true;
|
|
|
|
|
services.haproxy.config =
|
|
|
|
|
let
|
|
|
|
|
ciphers = "ECDHE+CHACHA20:ECDHE+AESGCM";
|
|
|
|
|
cipherSuites = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256";
|
|
|
|
|
options = "ssl-min-ver TLSv1.2 no-tls-tickets";
|
|
|
|
|
acmeDir = config.security.acme.certs.${config.networking.fqdn}.directory;
|
|
|
|
|
compTypes = [
|
|
|
|
|
"application/javascript"
|
|
|
|
|
"application/json"
|
|
|
|
|
"image/svg+xml"
|
|
|
|
|
"text/css"
|
|
|
|
|
"text/html"
|
|
|
|
|
"text/javascript"
|
|
|
|
|
"text/plain"
|
|
|
|
|
];
|
|
|
|
|
in ''
|
|
|
|
|
global
|
|
|
|
|
expose-experimental-directives
|
|
|
|
|
|
|
|
|
|
log stderr format short alert notice
|
|
|
|
|
|
|
|
|
|
maxconn 9216
|
|
|
|
|
nbthread 2
|
|
|
|
|
cpu-map auto:1/all 0-63
|
|
|
|
|
|
|
|
|
|
ssl-default-bind-ciphers ${ciphers}
|
|
|
|
|
ssl-default-bind-ciphersuites ${cipherSuites}
|
|
|
|
|
ssl-default-bind-options prefer-client-ciphers ${options}
|
|
|
|
|
|
|
|
|
|
ssl-default-server-ciphers ${ciphers}
|
|
|
|
|
ssl-default-server-ciphersuites ${cipherSuites}
|
|
|
|
|
ssl-default-server-options ${options}
|
|
|
|
|
|
|
|
|
|
defaults
|
|
|
|
|
log global
|
|
|
|
|
mode http
|
|
|
|
|
option abortonclose
|
|
|
|
|
option checkcache
|
|
|
|
|
option forwardfor
|
|
|
|
|
option http-keep-alive
|
|
|
|
|
option http-restrict-req-hdr-names reject
|
|
|
|
|
option httpchk
|
|
|
|
|
option splice-auto
|
|
|
|
|
option tcp-smart-connect
|
|
|
|
|
|
|
|
|
|
timeout connect 5s
|
|
|
|
|
timeout client 30s
|
|
|
|
|
timeout http-request 5s
|
|
|
|
|
timeout client-fin 5s
|
|
|
|
|
timeout tunnel 1h
|
|
|
|
|
timeout server 30s
|
|
|
|
|
timeout check 5s
|
|
|
|
|
|
|
|
|
|
cache default
|
|
|
|
|
total-max-size 16
|
|
|
|
|
max-age 240
|
|
|
|
|
|
|
|
|
|
frontend http
|
|
|
|
|
bind :::443 v4v6 defer-accept tfo ssl crt ${acmeDir}/full.pem allow-0rtt alpn h2,http/1.1
|
|
|
|
|
bind quic6@:443 v4v6 ssl crt ${acmeDir}/full.pem allow-0rtt alpn h3
|
|
|
|
|
bind :::80 v4v6 defer-accept tfo
|
|
|
|
|
|
|
|
|
|
acl replay-safe method GET HEAD OPTIONS req.body_size eq 0
|
|
|
|
|
|
|
|
|
|
acl host-solitary hdr(host),host_only solitary.social
|
|
|
|
|
acl host-cache hdr(host),host_only cache.solitary.social
|
|
|
|
|
acl host-media hdr(host),host_only media.solitary.social
|
|
|
|
|
acl host-matrix hdr(host),host_only matrix.solitary.social
|
|
|
|
|
acl host-resolve hdr(host),host_only resolve.solitary.social
|
|
|
|
|
|
2024-10-19 18:40:52 +02:00
|
|
|
|
acl path-acme path_reg ^/\.well-known/acme-challenge(/.*)?$
|
2024-10-19 19:20:43 +02:00
|
|
|
|
acl path-well-known path_beg /.well-known/
|
2024-08-18 13:47:18 +02:00
|
|
|
|
acl path-security.txt path /.well-known/security.txt
|
2024-10-19 18:40:52 +02:00
|
|
|
|
acl path-matrix-well-known path_reg ^/\.well-known/matrix(/.*)?$
|
|
|
|
|
acl path-proxy path_reg ^/proxy(/.*)?$
|
|
|
|
|
acl path-media path_reg ^/media(/.*)?$
|
2024-08-18 13:47:18 +02:00
|
|
|
|
|
|
|
|
|
#http-request normalize-uri fragment-strip
|
|
|
|
|
#http-request normalize-uri path-strip-dot
|
|
|
|
|
#http-request normalize-uri path-strip-dotdot full
|
|
|
|
|
#http-request normalize-uri path-merge-slashes
|
|
|
|
|
#http-request normalize-uri percent-decode-unreserved strict
|
|
|
|
|
#http-request normalize-uri percent-to-uppercase strict
|
|
|
|
|
#http-request normalize-uri query-sort-by-name
|
|
|
|
|
|
|
|
|
|
http-request redirect scheme https code 301 unless { ssl_fc } or path-acme
|
|
|
|
|
http-request wait-for-handshake unless replay-safe
|
|
|
|
|
|
2024-10-19 19:20:43 +02:00
|
|
|
|
http-request set-priority-class int(-1) if host-resolve
|
|
|
|
|
http-request set-priority-class int(1) if host-solitary
|
|
|
|
|
http-request set-priority-class int(2) if host-media
|
|
|
|
|
http-request set-priority-class int(3) if host-cache
|
|
|
|
|
http-request set-priority-class int(-2) if path-well-known
|
|
|
|
|
|
2024-08-18 13:47:18 +02:00
|
|
|
|
http-response set-tos 20 if host-resolve # AF22 (low‐latency, med drop)
|
|
|
|
|
http-response set-tos 10 if host-matrix # AF11 (high‐throughput, low drop)
|
|
|
|
|
http-response set-tos 12 if host-solitary # AF12 (high‐throughput, med drop)
|
|
|
|
|
http-response set-tos 14 if host-media # AF13 (high‐throughput, high drop)
|
|
|
|
|
http-response set-tos 14 if host-cache # AF13 (high‐throughput, high drop)
|
2024-10-19 19:20:43 +02:00
|
|
|
|
http-response set-tos 20 if path-well-known # AF22 (low‐latency, med drop)
|
2024-08-18 13:47:18 +02:00
|
|
|
|
|
|
|
|
|
http-request cache-use default
|
|
|
|
|
http-request set-header X-Forwarded-Proto %[ssl_fc,iif(https,http)]
|
|
|
|
|
|
2024-10-19 21:37:12 +02:00
|
|
|
|
http-response set-header Alt-Svc "h3=\":443\"; ma=7776000; persist=1, h2=\":443\"; ma=7776000; persist=1"
|
2024-10-19 18:43:37 +02:00
|
|
|
|
http-response set-header Cross-Origin-Embedder-Policy require-corp unless { res.hdr(Cross-Origin-Embedder-Policy) -m found }
|
|
|
|
|
http-response set-header Cross-Origin-Opener-Policy same-site unless { res.hdr(Cross-Origin-Opener-Policy) -m found }
|
|
|
|
|
http-response set-header Cross-Origin-Resource-Policy same-site unless { res.hdr(Cross-Origin-Resource-Policy) -m found }
|
|
|
|
|
http-response set-header Content-Security-Policy "default-src 'self'; frame-ancestors 'none'" unless { res.hdr(Content-Security-Policy) -m found }
|
|
|
|
|
http-response set-header Referrer-Policy same-origin unless { res.hdr(Referrer-Policy) -m found }
|
2024-08-18 13:47:18 +02:00
|
|
|
|
http-response set-header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"
|
|
|
|
|
http-response set-header X-Frame-Options DENY
|
|
|
|
|
http-response set-header X-Content-Type-Options nosniff
|
|
|
|
|
http-response set-header X-XSS-Protection "1; mode=block"
|
|
|
|
|
|
|
|
|
|
compression algo gzip
|
|
|
|
|
compression type ${concatStringsSep " " compTypes}
|
|
|
|
|
http-response cache-store default
|
|
|
|
|
|
|
|
|
|
http-request redirect code 308 location https://media.solitary.social%[capture.req.uri,regsub("^/media","")] if host-solitary path-media
|
|
|
|
|
http-request redirect code 308 location https://media.solitary.social%[capture.req.uri,regsub("^/media","")] if host-media path-media
|
|
|
|
|
http-request redirect code 308 location https://cache.solitary.social%[capture.req.uri] if host-solitary path-proxy
|
2024-09-24 12:29:07 +02:00
|
|
|
|
http-request set-path "/media%[path]" if host-media !path-acme !path-media
|
2024-08-18 13:47:18 +02:00
|
|
|
|
|
|
|
|
|
use_backend acme if path-acme
|
|
|
|
|
use_backend security.txt if path-security.txt
|
|
|
|
|
use_backend unbound if host-resolve
|
|
|
|
|
use_backend synapse if host-matrix
|
|
|
|
|
use_backend wellknown-matrix if host-solitary path-matrix-well-known
|
|
|
|
|
use_backend nginx if host-cache
|
|
|
|
|
use_backend akkoma if host-solitary
|
|
|
|
|
use_backend akkoma if host-media
|
|
|
|
|
default_backend notfound
|
|
|
|
|
|
|
|
|
|
backend acme
|
|
|
|
|
server acme 127.0.0.1:${toString ports.acme}
|
|
|
|
|
retry-on all-retryable-errors
|
|
|
|
|
|
|
|
|
|
backend akkoma
|
|
|
|
|
server akkoma /run/akkoma/socket
|
|
|
|
|
|
|
|
|
|
backend nginx
|
|
|
|
|
server nginx [::1]:${toString ports.nginx} tfo proto h2
|
|
|
|
|
retry-on conn-failure empty-response response-timeout
|
|
|
|
|
|
|
|
|
|
backend synapse
|
|
|
|
|
server synapse [::1]:${toString ports.synapse}
|
|
|
|
|
|
|
|
|
|
backend unbound
|
|
|
|
|
server unbound [::1]:${toString ports.unbound} tfo ssl ssl-min-ver TLSv1.3 alpn h2 allow-0rtt ca-file ${acmeDir}/chain.pem
|
|
|
|
|
retry-on conn-failure empty-response response-timeout 0rtt-rejected
|
|
|
|
|
|
|
|
|
|
backend security.txt
|
|
|
|
|
http-request return status 200 content-type text/plain file ${security-txt} if { path /.well-known/security.txt }
|
|
|
|
|
|
|
|
|
|
backend wellknown-matrix
|
|
|
|
|
http-request return status 200 content-type application/json file ${pkgs.writeText "client.json" (builtins.toJSON {
|
|
|
|
|
"m.homeserver".base_url = config.services.matrix-synapse.settings.public_baseurl;
|
|
|
|
|
"m.identity_server".base_url = "https://vector.im";
|
|
|
|
|
})} if { path /.well-known/matrix/client }
|
|
|
|
|
|
|
|
|
|
http-request return status 200 content-type application/json file ${pkgs.writeText "server.json" (builtins.toJSON {
|
|
|
|
|
"m.server" = "matrix.solitary.social:443";
|
|
|
|
|
})} if { path /.well-known/matrix/server }
|
|
|
|
|
|
|
|
|
|
backend notfound
|
|
|
|
|
http-request return status 404
|
|
|
|
|
'';
|
|
|
|
|
|
|
|
|
|
services.matrix-synapse.enable = true;
|
|
|
|
|
services.matrix-synapse.settings = {
|
|
|
|
|
database_type = "psycopg2";
|
|
|
|
|
server_name = "solitary.social";
|
|
|
|
|
public_baseurl = "https://matrix.solitary.social/";
|
|
|
|
|
default_identity_server = "https://vector.im";
|
|
|
|
|
enable_registration = false;
|
|
|
|
|
|
|
|
|
|
listeners = [ {
|
|
|
|
|
bind_addresses = [ "::1" ];
|
|
|
|
|
port = ports.synapse;
|
|
|
|
|
type = "http";
|
|
|
|
|
tls = false;
|
|
|
|
|
x_forwarded = true;
|
|
|
|
|
|
|
|
|
|
resources = [ {
|
|
|
|
|
names = [ "client" "federation" ];
|
|
|
|
|
compress = true;
|
|
|
|
|
} ];
|
|
|
|
|
} ];
|
|
|
|
|
|
|
|
|
|
log_config = ./log_config.yaml;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
services.nginx = {
|
|
|
|
|
enable = true;
|
|
|
|
|
|
|
|
|
|
package = pkgs.tengine;
|
|
|
|
|
|
|
|
|
|
recommendedGzipSettings = true;
|
|
|
|
|
recommendedOptimisation = true;
|
|
|
|
|
recommendedProxySettings = true;
|
|
|
|
|
|
|
|
|
|
commonHttpConfig = ''
|
|
|
|
|
charset utf-8;
|
|
|
|
|
proxy_cache_path /var/cache/nginx/cache/akkoma_media_cache
|
|
|
|
|
levels= keys_zone=akkoma_media_cache:16m max_size=16g
|
|
|
|
|
inactive=1y use_temp_path=off;
|
|
|
|
|
access_log off;
|
|
|
|
|
|
|
|
|
|
set_real_ip_from ::1;
|
|
|
|
|
real_ip_header X-Forwarded-For;
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
services.nginx.virtualHosts."cache.solitary.social" = {
|
|
|
|
|
listen = [ {
|
|
|
|
|
addr = "[::1]";
|
|
|
|
|
port = ports.nginx;
|
|
|
|
|
extraParameters = [ "http2" "fastopen=512" ];
|
|
|
|
|
} ];
|
|
|
|
|
locations."/" = {
|
|
|
|
|
proxyPass = "http://unix:/run/akkoma/socket";
|
|
|
|
|
extraConfig = ''
|
|
|
|
|
proxy_cache akkoma_media_cache;
|
|
|
|
|
slice 1m;
|
|
|
|
|
proxy_cache_key $host$uri$is_args$args$slice_range;
|
|
|
|
|
proxy_set_header Range $slice_range;
|
|
|
|
|
|
|
|
|
|
proxy_buffering on;
|
|
|
|
|
proxy_cache_lock on;
|
|
|
|
|
proxy_ignore_client_abort on;
|
|
|
|
|
|
|
|
|
|
proxy_cache_valid 200 1y;
|
|
|
|
|
proxy_cache_valid 206 301 304 1h;
|
|
|
|
|
|
|
|
|
|
proxy_cache_use_stale error timeout invalid_header updating;
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
services.postfix = {
|
|
|
|
|
enable = true;
|
|
|
|
|
destination = [ ];
|
|
|
|
|
localRecipients = [ ];
|
|
|
|
|
networks = [ "localhost" ];
|
|
|
|
|
hostname = config.networking.fqdn;
|
|
|
|
|
masterConfig.smtp_inet.name = mkForce "localhost:smtp";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
services.postgresql = {
|
|
|
|
|
enable = true;
|
2024-11-01 20:17:59 +01:00
|
|
|
|
package = pkgs.postgresql_16;
|
2024-08-18 13:47:18 +02:00
|
|
|
|
|
2024-11-01 20:17:59 +01:00
|
|
|
|
extraPlugins = with pkgs.postgresql_16.pkgs; [
|
2024-08-18 13:47:18 +02:00
|
|
|
|
rum
|
|
|
|
|
];
|
|
|
|
|
|
2024-11-02 16:36:10 +01:00
|
|
|
|
settings = {
|
|
|
|
|
max_connections = 128;
|
|
|
|
|
|
|
|
|
|
shared_buffers = "768MB";
|
|
|
|
|
huge_pages = "try";
|
|
|
|
|
huge_page_size = "2MB";
|
|
|
|
|
work_mem = "16MB";
|
|
|
|
|
|
|
|
|
|
effective_io_concurrency = 128;
|
|
|
|
|
|
|
|
|
|
wal_compression = "zstd";
|
|
|
|
|
};
|
|
|
|
|
|
2024-08-18 13:47:18 +02:00
|
|
|
|
initialScript = pkgs.writeText "init.psql" ''
|
|
|
|
|
CREATE ROLE "matrix-synapse";
|
|
|
|
|
CREATE DATABASE "matrix-synapse" OWNER "matrix-synapse"
|
|
|
|
|
TEMPLATE template0
|
|
|
|
|
ENCODING 'utf8'
|
|
|
|
|
LOCALE 'C';
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
services.unbound = {
|
|
|
|
|
enable = true;
|
|
|
|
|
|
|
|
|
|
package = pkgs.unbound-with-systemd.override {
|
|
|
|
|
withDoH = true;
|
|
|
|
|
withTFO = true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enableRootTrustAnchor = true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
services.unbound.settings = {
|
|
|
|
|
server = let
|
|
|
|
|
acmeDir = config.security.acme.certs.${config.networking.fqdn}.directory;
|
|
|
|
|
num-threads = 2;
|
|
|
|
|
in {
|
|
|
|
|
inherit num-threads;
|
|
|
|
|
|
|
|
|
|
interface = [
|
|
|
|
|
"::1@53"
|
|
|
|
|
"127.0.0.1@53"
|
|
|
|
|
|
|
|
|
|
"::1@${toString ports.unbound}"
|
|
|
|
|
|
|
|
|
|
"::@853"
|
|
|
|
|
"0.0.0.0@853"
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
so-reuseport = true;
|
|
|
|
|
ip-dscp = 20;
|
|
|
|
|
outgoing-range = 8192;
|
|
|
|
|
edns-buffer-size = 1472;
|
|
|
|
|
udp-upstream-without-downstream = true;
|
|
|
|
|
num-queries-per-thread = 4096;
|
|
|
|
|
incoming-num-tcp = 1024;
|
|
|
|
|
outgoing-num-tcp = 16;
|
|
|
|
|
stream-wait-size = "64m";
|
|
|
|
|
msg-cache-size = "128m";
|
|
|
|
|
msg-cache-slabs = num-threads;
|
|
|
|
|
rrset-cache-size = "256m";
|
|
|
|
|
rrset-cache-slabs = num-threads;
|
|
|
|
|
infra-cache-slabs = num-threads;
|
|
|
|
|
key-cache-slabs = num-threads;
|
|
|
|
|
cache-min-ttl = 60;
|
|
|
|
|
cache-max-negative-ttl = 360;
|
|
|
|
|
prefer-ip6 = true;
|
|
|
|
|
tls-service-pem = "${acmeDir}/fullchain.pem";
|
|
|
|
|
tls-service-key = "${acmeDir}/key.pem";
|
|
|
|
|
https-port = ports.unbound;
|
|
|
|
|
http-query-buffer-size = "64m";
|
|
|
|
|
http-response-buffer-size = "64m";
|
|
|
|
|
access-control = [ "::/0 allow" "0.0.0.0/0 allow" ];
|
|
|
|
|
harden-dnssec-stripped = true;
|
|
|
|
|
hide-identity = true;
|
|
|
|
|
hide-version = true;
|
|
|
|
|
prefetch = true;
|
|
|
|
|
prefetch-key = true;
|
|
|
|
|
serve-expired-client-timeout = 1800;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
systemd = let
|
|
|
|
|
backendServices = [
|
|
|
|
|
"akkoma.service"
|
|
|
|
|
"matrix-synapse.service"
|
|
|
|
|
"nginx.service"
|
|
|
|
|
"unbound.service"
|
|
|
|
|
];
|
|
|
|
|
in {
|
|
|
|
|
services.akkoma.confinement.enable = false;
|
|
|
|
|
services.akkoma.serviceConfig.BindReadOnlyPaths = [ "/var/lib/akkoma:/var/lib/akkoma:norbind" ];
|
|
|
|
|
|
|
|
|
|
services.haproxy = {
|
|
|
|
|
confinement.enable = false;
|
|
|
|
|
|
|
|
|
|
wants = [ "acme-finished-${config.networking.fqdn}.service" ]
|
|
|
|
|
++ backendServices;
|
|
|
|
|
after = [ "acme-selfsigned-${config.networking.fqdn}.service" ]
|
|
|
|
|
++ backendServices;
|
|
|
|
|
before = [ "acme-${config.networking.fqdn}.service" ];
|
|
|
|
|
|
|
|
|
|
reloadTriggers = [ "${config.security.acme.certs.${config.networking.fqdn}.directory}/cert.ocsp" ];
|
|
|
|
|
|
|
|
|
|
serviceConfig = {
|
|
|
|
|
BindReadOnlyPaths = [
|
|
|
|
|
"/etc/haproxy.cfg"
|
|
|
|
|
"/etc/hosts"
|
|
|
|
|
"/etc/resolv.conf"
|
|
|
|
|
"/run/akkoma"
|
|
|
|
|
config.security.acme.certs."solitary.social".directory
|
|
|
|
|
security-txt
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
|
|
|
|
|
SocketBindAllow = [ "tcp:80" "tcp:443" "udp:443" ];
|
|
|
|
|
SocketBindDeny = "any";
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
services.nginx = {
|
|
|
|
|
confinement.enable = true;
|
|
|
|
|
after = [ "akkoma.service" ];
|
|
|
|
|
serviceConfig = {
|
|
|
|
|
BindReadOnlyPaths = [
|
|
|
|
|
"/etc/hosts"
|
|
|
|
|
"/etc/resolv.conf"
|
|
|
|
|
"/run"
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
BindPaths = [
|
|
|
|
|
"/var/cache/nginx"
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
ProtectSystem = mkForce false;
|
|
|
|
|
SocketBindAllow = [ "tcp:${toString ports.nginx}" ];
|
|
|
|
|
SocketBindDeny = "any";
|
|
|
|
|
RestrictNetworkInterfaces = [ "lo" ];
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
services.unbound = {
|
|
|
|
|
wants = [ "acme-finished-${config.networking.fqdn}.service" ];
|
|
|
|
|
after = [ "acme-selfsigned-${config.networking.fqdn}.service" ];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
services.synapse-compress-state = {
|
|
|
|
|
confinement.enable = true;
|
|
|
|
|
|
|
|
|
|
after = [ "postgresql.service" ];
|
|
|
|
|
description = "Compress Synapse state tables";
|
|
|
|
|
serviceConfig = {
|
|
|
|
|
Type = "oneshot";
|
|
|
|
|
ExecStart = ''
|
|
|
|
|
${pkgs.matrix-synapse-tools.rust-synapse-compress-state}/bin/synapse_auto_compressor \
|
|
|
|
|
-p "host=/run/postgresql \
|
|
|
|
|
user=${config.services.matrix-synapse.settings.database.args.database} \
|
|
|
|
|
dbname=${config.services.matrix-synapse.settings.database.args.database}" \
|
|
|
|
|
-c 512 -n 128
|
|
|
|
|
'';
|
|
|
|
|
User = "matrix-synapse";
|
|
|
|
|
WorkingDirectory = "/tmp";
|
|
|
|
|
|
|
|
|
|
BindReadOnlyPaths = [
|
|
|
|
|
"/run/postgresql"
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
ProtectProc = "noaccess";
|
|
|
|
|
ProcSubset = "pid";
|
|
|
|
|
ProtectHome = true;
|
|
|
|
|
PrivateTmp = true;
|
|
|
|
|
PrivateDevices = true;
|
|
|
|
|
PrivateIPC = true;
|
|
|
|
|
ProtectHostname = true;
|
|
|
|
|
ProtectClock = true;
|
|
|
|
|
ProtectKernelTunables = true;
|
|
|
|
|
ProtectKernelModules = true;
|
|
|
|
|
ProtectKernelLogs = true;
|
|
|
|
|
ProtectControlGroups = true;
|
|
|
|
|
|
|
|
|
|
RestrictAddressFamilies = [ "AF_UNIX" ];
|
|
|
|
|
RestrictNamespaces = true;
|
|
|
|
|
LockPersonality = true;
|
|
|
|
|
RestrictRealtime = true;
|
|
|
|
|
RestrictSUIDSGID = true;
|
|
|
|
|
RemoveIPC = true;
|
|
|
|
|
|
|
|
|
|
CapabilityBoundingSet = null;
|
|
|
|
|
NoNewPrivileges = true;
|
|
|
|
|
SystemCallFilter = [ "@system-service" "~@resources" "~@privileged" ];
|
|
|
|
|
SystemCallArchitectures = "native";
|
|
|
|
|
|
|
|
|
|
UMask = "0077";
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
timers.synapse-compress-state = {
|
|
|
|
|
enable = true;
|
|
|
|
|
description = "Compress Synapse state tables daily";
|
|
|
|
|
timerConfig = {
|
|
|
|
|
OnCalendar = "04:00";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
wantedBy = [ "timers.target" ];
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
users.users.${config.services.haproxy.user}.extraGroups = [ config.security.acme.certs.${config.networking.fqdn}.group ];
|
|
|
|
|
users.users.${config.services.unbound.user}.extraGroups = [ config.security.acme.certs.${config.networking.fqdn}.group ];
|
|
|
|
|
}
|