server: reorganize

This commit is contained in:
chayleaf 2023-10-26 06:49:55 +07:00
parent 7535990be0
commit 598044863f
Signed by: chayleaf
GPG key ID: 78171AD46227E68E
9 changed files with 362 additions and 257 deletions

View file

@ -102,6 +102,7 @@
package = pkgs.gitAndTools.gitFull; package = pkgs.gitAndTools.gitFull;
delta.enable = true; delta.enable = true;
extraConfig = { extraConfig = {
commit.gpgsign = true;
# disable the atrocious gui password prompt # disable the atrocious gui password prompt
core.askPass = ""; core.askPass = "";
# ...and prefer getting passwords from libsecret (and storing them there) # ...and prefer getting passwords from libsecret (and storing them there)

View file

@ -9,8 +9,11 @@ let
tsja = callPackage ./tsja.nix { }; tsja = callPackage ./tsja.nix { };
}; };
gen' = postgresql: builtins.mapAttrs (k: v: v.override { inherit postgresql; }) extraPackages; gen' = postgresql: builtins.mapAttrs (k: v: v.override { inherit postgresql; }) extraPackages;
gen = ver: pkgs."postgresql${toString ver}Packages" // gen' pkgs."postgresql_${toString ver}"; gen = ver: pkgs."postgresql${toString ver}Packages" // gen' pkgs."postgresql${if ver == "" then "" else "_" + toString ver}";
in { psql = ver: let
old = pkgs."postgresql${if ver == "" then "" else "_" + toString ver}";
in old // { pkgs = old.pkgs // gen' old; };
self = {
mecab = pkgs.mecab.overrideAttrs (old: { mecab = pkgs.mecab.overrideAttrs (old: {
postInstall = '' postInstall = ''
mkdir -p $out/lib/mecab/dic mkdir -p $out/lib/mecab/dic
@ -20,10 +23,18 @@ in {
''; '';
}); });
postgresqlPackages = gen ""; postgresqlPackages = gen "";
postgresql = psql "";
postgresql11Packages = gen 11; postgresql11Packages = gen 11;
postgresql_11 = psql 11;
postgresql12Packages = gen 12; postgresql12Packages = gen 12;
postgresql_12 = psql 12;
postgresql13Packages = gen 13; postgresql13Packages = gen 13;
postgresql_13 = psql 13;
postgresql14Packages = gen 14; postgresql14Packages = gen 14;
postgresql_14 = psql 14;
postgresql15Packages = gen 15; postgresql15Packages = gen 15;
postgresql_15 = psql 15;
postgresql16Packages = gen 16; postgresql16Packages = gen 16;
} postgresql_16 = psql 16;
};
in self

View file

@ -0,0 +1,78 @@
{ config
, pkgs
, ... }:
let
cfg = config.server;
in {
# TODO: remove this in 2024
services.nginx.virtualHosts."pleroma.${cfg.domainName}" = {
quic = true;
enableACME = true;
addSSL = true;
serverAliases = [ "akkoma.${cfg.domainName}" ];
locations."/".return = "301 https://fedi.${cfg.domainName}$request_uri";
};
services.postgresql.extraPlugins = with config.services.postgresql.package.pkgs; [ tsja ];
services.akkoma = {
enable = true;
dist.extraFlags = [
"+sbwt" "none"
"+sbwtdcpu" "none"
"+sbwtdio" "none"
];
config.":pleroma"."Pleroma.Web.Endpoint" = {
url = {
scheme = "https";
host = "fedi.${cfg.domainName}";
port = 443;
};
secret_key_base._secret = "/secrets/akkoma/secret_key_base";
signing_salt._secret = "/secrets/akkoma/signing_salt";
live_view.signing_salt._secret = "/secrets/akkoma/live_view_signing_salt";
};
initDb = {
enable = false;
username = "akkoma";
password._secret = "/secrets/akkoma/postgres_password";
};
config.":pleroma".":instance" = {
name = cfg.domainName;
description = "Insert instance description here";
email = "webmaster-akkoma@${cfg.domainName}";
notify_email = "noreply@${cfg.domainName}";
limit = 5000;
registrations_open = true;
account_activation_required = true;
account_approval_required = true;
};
config.":pleroma"."Pleroma.Repo" = {
adapter = (pkgs.formats.elixirConf { }).lib.mkRaw "Ecto.Adapters.Postgres";
username = "akkoma";
password._secret = "/secrets/akkoma/postgres_password";
database = "akkoma";
hostname = "localhost";
};
config.":web_push_encryption".":vapid_details" = {
subject = "mailto:webmaster-akkoma@${cfg.domainName}";
public_key._secret = "/secrets/akkoma/push_public_key";
private_key._secret = "/secrets/akkoma/push_private_key";
};
config.":joken".":default_signer"._secret = "/secrets/akkoma/joken_signer";
nginx = {
quic = true;
enableACME = true;
forceSSL = true;
};
};
systemd.services.akkoma = {
path = [ pkgs.exiftool pkgs.gawk ];
serviceConfig.Restart = "on-failure";
unitConfig = {
StartLimitIntervalSec = 60;
StartLimitBurst = 3;
};
};
}

View file

@ -0,0 +1,44 @@
{ config
, lib
, pkgs
, ... }:
let
cfg = config.server;
in {
security.acme.certs = lib.flip builtins.mapAttrs (lib.filterAttrs (k: v: v.enableACME) config.services.nginx.virtualHosts) (k: v: {
postRun = let
python = pkgs.python3.withPackages (p: with p; [ cryptography pyasn1 pyasn1-modules ]);
tbs-hash = pkgs.writeScript "tbs-hash.py" ''
#!${python}/bin/python3
import hashlib
from pyasn1.codec.der.decoder import decode
from pyasn1.codec.der.encoder import encode
from pyasn1_modules import rfc5280
from cryptography import x509
with open('full.pem', 'rb') as f:
cert = x509.load_pem_x509_certificate(f.read())
tbs, _leftover = decode(cert.tbs_certificate_bytes, asn1Spec=rfc5280.TBSCertificate())
precert_exts = [v.dotted_string for k, v in x509.ExtensionOID.__dict__.items() if k.startswith('PRECERT_')]
exts = [ext for ext in tbs["extensions"] if str(ext["extnID"]) not in precert_exts]
tbs["extensions"].clear()
tbs["extensions"].extend(exts)
print(hashlib.sha256(encode(tbs)).hexdigest())
'';
in ''
${tbs-hash} > "/var/lib/certspotter/tbs-hashes/${k}"
'';
});
services.certspotter = {
enable = true;
extraFlags = [ ];
watchlist = [ ".pavluk.org" ];
hooks = lib.toList (pkgs.writeShellScript "certspotter-hook" ''
if [[ "$EVENT" == discovered_cert ]]; then
${pkgs.gnugrep}/bin/grep -r "$TBS_SHA256" /var/lib/certspotter/tbs-hashes/ && exit
fi
(echo "Subject: $SUMMARY" && echo && cat "$TEXT_FILENAME") | /run/wrappers/bin/sendmail -i webmaster-certspotter@${cfg.domainName}
'');
};
}

View file

@ -6,7 +6,7 @@
let let
cfg = config.server; cfg = config.server;
hosted-domains = hostedDomains =
builtins.concatLists builtins.concatLists
(builtins.attrValues (builtins.attrValues
(builtins.mapAttrs (builtins.mapAttrs
@ -15,12 +15,16 @@ let
in { in {
imports = [ imports = [
./options.nix ./options.nix
./matrix.nix ./akkoma.nix
./certspotter.nix
./fdroid.nix ./fdroid.nix
./mumble.nix ./files.nix
./mailserver.nix
./home.nix ./home.nix
./keycloak.nix ./keycloak.nix
./mailserver.nix
./matrix.nix
./mumble.nix
./searxng.nix
]; ];
system.stateVersion = "22.11"; system.stateVersion = "22.11";
@ -77,12 +81,11 @@ in {
}; };
}; };
# just in case # just in case
networking.hosts."127.0.0.1" = hosted-domains; networking.hosts."127.0.0.1" = hostedDomains;
networking.hosts."::1" = hosted-domains; networking.hosts."::1" = hostedDomains;
services.postgresql.enable = true; services.postgresql.enable = true;
services.postgresql.package = pkgs.postgresql_13; services.postgresql.package = pkgs.postgresql_13;
services.postgresql.extraPlugins = with pkgs.postgresql13Packages; [ tsja ];
# SSH # SSH
services.openssh.enable = true; services.openssh.enable = true;
@ -97,56 +100,6 @@ in {
''; '';
}; };
# SEARXNG
services.searx.enable = true;
services.searx.package = pkgs.searxng;
services.searx.runInUwsgi = true;
services.searx.uwsgiConfig = let inherit (config.services.searx) settings; in {
socket = "${lib.quoteListenAddr settings.server.bind_address}:${toString settings.server.port}";
};
services.searx.environmentFile = /var/lib/searx/searx.env;
services.searx.settings = {
use_default_settings = true;
search = {
safe_search = 0;
autocomplete = "duckduckgo"; # dbpedia, duckduckgo, google, startpage, swisscows, qwant, wikipedia - leave blank to turn off
default_lang = ""; # leave blank to detect from browser info or use codes from languages.py
};
server = {
port = 8888;
bind_address = "::1";
secret_key = "@SEARX_SECRET_KEY@";
base_url = "https://search.${cfg.domainName}/";
image_proxy = true;
default_http_headers = {
X-Content-Type-Options = "nosniff";
X-XSS-Protection = "1; mode=block";
X-Download-Options = "noopen";
X-Robots-Tag = "noindex, nofollow";
Referrer-Policy = "no-referrer";
};
};
outgoing = {
request_timeout = 5.0; # default timeout in seconds, can be override by engine
max_request_timeout = 15.0; # the maximum timeout in seconds
pool_connections = 100; # Maximum number of allowable connections, or null
pool_maxsize = 10; # Number of allowable keep-alive connections, or null
enable_http2 = true; # See https://www.python-httpx.org/http2/
};
};
services.nginx.virtualHosts."search.${cfg.domainName}" = let inherit (config.services.searx) settings; in {
quic = true;
enableACME = true;
forceSSL = true;
# locations."/".proxyPass = "http://${lib.quoteListenAddr settings.server.bind_address}:${toString settings.server.port}";
locations."/".extraConfig = ''
uwsgi_pass "${lib.quoteListenAddr settings.server.bind_address}:${toString settings.server.port}";
include ${config.services.nginx.package}/conf/uwsgi_params;
'';
};
# NGINX # NGINX
services.nginx.enable = true; services.nginx.enable = true;
services.nginx.enableReload = true; services.nginx.enableReload = true;
@ -212,188 +165,6 @@ in {
}; };
}; };
# GITEA
services.nginx.virtualHosts."git.${cfg.domainName}" = let inherit (config.services.gitea) settings; in {
quic = true;
enableACME = true;
forceSSL = true;
locations."/".proxyPass = "http://${lib.quoteListenAddr settings.server.HTTP_ADDR}:${toString settings.server.HTTP_PORT}";
};
services.gitea = {
enable = true;
package = pkgs.forgejo;
database = {
createDatabase = false;
passwordFile = "/var/lib/gitea/db_password";
type = "postgres";
};
settings = {
federation.ENABLED = true;
"git.timeout" = {
DEFAULT = 6000;
MIGRATE = 60000;
MIRROR = 60000;
GC = 120;
};
mailer = {
ENABLED = true;
FROM = "Forgejo <noreply@${cfg.domainName}>";
PROTOCOL = "smtp";
SMTP_ADDR = "mail.${cfg.domainName}";
SMTP_PORT = 587;
USER = "noreply@${cfg.domainName}";
PASSWD = cfg.unhashedNoreplyPassword;
FORCE_TRUST_SERVER_CERT = true;
};
session = {
COOKIE_SECURE = true;
};
server = {
ROOT_URL = "https://git.${cfg.domainName}";
HTTP_ADDR = "::1";
HTTP_PORT = 3310;
DOMAIN = "git.${cfg.domainName}";
# START_SSH_SERVER = true;
# SSH_PORT = 2222;
};
service = {
DISABLE_REGISTRATION = true;
REGISTER_EMAIL_CONFIRM = true;
};
};
};
# NEXTCLOUD
services.nginx.virtualHosts."cloud.${cfg.domainName}" = {
quic = true;
enableACME = true;
forceSSL = true;
};
services.nextcloud = {
enable = true;
enableBrokenCiphersForSSE = false;
package = pkgs.nextcloud27;
autoUpdateApps.enable = true;
# TODO: use socket auth and remove the next line
database.createLocally = false;
config = {
adminpassFile = "/var/lib/nextcloud/admin_password";
dbpassFile = "/var/lib/nextcloud/db_password";
dbtype = "pgsql";
dbhost = "/run/postgresql";
overwriteProtocol = "https";
};
hostName = "cloud.${cfg.domainName}";
https = true;
};
# AKKOMA
# TODO: remove this in 2024
services.nginx.virtualHosts."pleroma.${cfg.domainName}" = {
quic = true;
enableACME = true;
addSSL = true;
serverAliases = [ "akkoma.${cfg.domainName}" ];
locations."/".return = "301 https://fedi.${cfg.domainName}$request_uri";
};
services.akkoma = {
enable = true;
dist.extraFlags = [
"+sbwt" "none"
"+sbwtdcpu" "none"
"+sbwtdio" "none"
];
config.":pleroma"."Pleroma.Web.Endpoint" = {
url = {
scheme = "https";
host = "fedi.${cfg.domainName}";
port = 443;
};
secret_key_base._secret = "/secrets/akkoma/secret_key_base";
signing_salt._secret = "/secrets/akkoma/signing_salt";
live_view.signing_salt._secret = "/secrets/akkoma/live_view_signing_salt";
};
initDb = {
enable = false;
username = "akkoma";
password._secret = "/secrets/akkoma/postgres_password";
};
config.":pleroma".":instance" = {
name = cfg.domainName;
description = "Insert instance description here";
email = "webmaster-akkoma@${cfg.domainName}";
notify_email = "noreply@${cfg.domainName}";
limit = 5000;
registrations_open = true;
account_activation_required = true;
account_approval_required = true;
};
config.":pleroma"."Pleroma.Repo" = {
adapter = (pkgs.formats.elixirConf { }).lib.mkRaw "Ecto.Adapters.Postgres";
username = "akkoma";
password._secret = "/secrets/akkoma/postgres_password";
database = "akkoma";
hostname = "localhost";
};
config.":web_push_encryption".":vapid_details" = {
subject = "mailto:webmaster-akkoma@${cfg.domainName}";
public_key._secret = "/secrets/akkoma/push_public_key";
private_key._secret = "/secrets/akkoma/push_private_key";
};
config.":joken".":default_signer"._secret = "/secrets/akkoma/joken_signer";
nginx = {
quic = true;
enableACME = true;
forceSSL = true;
};
};
systemd.services.akkoma = {
path = [ pkgs.exiftool pkgs.gawk ];
serviceConfig.Restart = "on-failure";
unitConfig = {
StartLimitIntervalSec = 60;
StartLimitBurst = 3;
};
};
# TODO: calc tbs instead of pubkey hash?
security.acme.certs = lib.flip builtins.mapAttrs (lib.filterAttrs (k: v: v.enableACME) config.services.nginx.virtualHosts) (k: v: {
postRun = let
python = pkgs.python3.withPackages (p: with p; [ cryptography pyasn1 pyasn1-modules ]);
tbs-hash = pkgs.writeScript "tbs-hash.py" ''
#!${python}/bin/python3
import hashlib
from pyasn1.codec.der.decoder import decode
from pyasn1.codec.der.encoder import encode
from pyasn1_modules import rfc5280
from cryptography import x509
with open('full.pem', 'rb') as f:
cert = x509.load_pem_x509_certificate(f.read())
tbs, _leftover = decode(cert.tbs_certificate_bytes, asn1Spec=rfc5280.TBSCertificate())
precert_exts = [v.dotted_string for k, v in x509.ExtensionOID.__dict__.items() if k.startswith('PRECERT_')]
exts = [ext for ext in tbs["extensions"] if str(ext["extnID"]) not in precert_exts]
tbs["extensions"].clear()
tbs["extensions"].extend(exts)
print(hashlib.sha256(encode(tbs)).hexdigest())
'';
in ''
${tbs-hash} > "/var/lib/certspotter/tbs-hashes/${k}"
'';
});
services.certspotter = {
enable = true;
extraFlags = [ ];
watchlist = [ ".pavluk.org" ];
hooks = lib.toList (pkgs.writeShellScript "certspotter-hook" ''
if [[ "$EVENT" == discovered_cert ]]; then
${pkgs.gnugrep}/bin/grep -r "$TBS_SHA256" /var/lib/certspotter/tbs-hashes/ && exit
fi
(echo "Subject: $SUMMARY" && echo && cat "$TEXT_FILENAME") | /run/wrappers/bin/sendmail -i webmaster-certspotter@${cfg.domainName}
'');
};
/*locations."/dns-query".extraConfig = '' /*locations."/dns-query".extraConfig = ''
grpc_pass grpc://127.0.0.1:53453; grpc_pass grpc://127.0.0.1:53453;
'';*/ '';*/

View file

@ -0,0 +1,83 @@
{ config
, lib
, pkgs
, ... }:
let
cfg = config.server;
in {
services.nginx.virtualHosts."git.${cfg.domainName}" = let inherit (config.services.forgejo) settings; in {
quic = true;
enableACME = true;
forceSSL = true;
locations."/".proxyPass = "http://${lib.quoteListenAddr settings.server.HTTP_ADDR}:${toString settings.server.HTTP_PORT}";
};
services.forgejo = {
enable = true;
database = {
createDatabase = false;
type = "postgres";
user = "gitea";
name = "gitea";
passwordFile = "/secrets/forgejo_db_password";
};
lfs.enable = true;
settings = {
federation.ENABLED = true;
"git.timeout" = {
DEFAULT = 6000;
MIGRATE = 60000;
MIRROR = 60000;
GC = 120;
};
mailer = {
ENABLED = true;
FROM = "Forgejo <noreply@${cfg.domainName}>";
PROTOCOL = "smtp";
SMTP_ADDR = "mail.${cfg.domainName}";
SMTP_PORT = 587;
USER = "noreply@${cfg.domainName}";
PASSWD = cfg.unhashedNoreplyPassword;
FORCE_TRUST_SERVER_CERT = true;
};
session = {
COOKIE_SECURE = true;
};
server = {
ROOT_URL = "https://git.${cfg.domainName}";
HTTP_ADDR = "::1";
HTTP_PORT = 3310;
DOMAIN = "git.${cfg.domainName}";
# START_SSH_SERVER = true;
# SSH_PORT = 2222;
};
service = {
DISABLE_REGISTRATION = true;
REGISTER_EMAIL_CONFIRM = true;
};
};
};
services.nginx.virtualHosts."cloud.${cfg.domainName}" = {
quic = true;
enableACME = true;
forceSSL = true;
};
services.nextcloud = {
enable = true;
enableBrokenCiphersForSSE = false;
package = pkgs.nextcloud27;
autoUpdateApps.enable = true;
# TODO: use socket auth and remove the next line
database.createLocally = false;
config = {
adminpassFile = "/var/lib/nextcloud/admin_password";
dbpassFile = "/var/lib/nextcloud/db_password";
dbtype = "pgsql";
dbhost = "/run/postgresql";
overwriteProtocol = "https";
};
hostName = "cloud.${cfg.domainName}";
https = true;
};
}

View file

@ -0,0 +1,58 @@
{ config
, lib
, ... }:
let
cfg = config.server;
in {
services.nginx.virtualHosts."git.${cfg.domainName}" = let inherit (config.services.forgejo) settings; in {
quic = true;
enableACME = true;
forceSSL = true;
locations."/".proxyPass = "http://${lib.quoteListenAddr settings.server.HTTP_ADDR}:${toString settings.server.HTTP_PORT}";
};
services.forgejo = {
enable = true;
database = {
createDatabase = false;
user = "gitea";
passwordFile = "/secrets/forgejo_db_password";
type = "postgres";
};
lfs.enable = true;
settings = {
federation.ENABLED = true;
"git.timeout" = {
DEFAULT = 6000;
MIGRATE = 60000;
MIRROR = 60000;
GC = 120;
};
mailer = {
ENABLED = true;
FROM = "Forgejo <noreply@${cfg.domainName}>";
PROTOCOL = "smtp";
SMTP_ADDR = "mail.${cfg.domainName}";
SMTP_PORT = 587;
USER = "noreply@${cfg.domainName}";
PASSWD = cfg.unhashedNoreplyPassword;
FORCE_TRUST_SERVER_CERT = true;
};
session = {
COOKIE_SECURE = true;
};
server = {
ROOT_URL = "https://git.${cfg.domainName}";
HTTP_ADDR = "::1";
HTTP_PORT = 3310;
DOMAIN = "git.${cfg.domainName}";
# START_SSH_SERVER = true;
# SSH_PORT = 2222;
};
service = {
DISABLE_REGISTRATION = false;
REGISTER_MANUAL_CONFIRM = true;
};
};
};
}

View file

@ -0,0 +1,57 @@
{ config
, lib
, pkgs
, ... }:
let
cfg = config.server;
in {
services.nginx.virtualHosts."search.${cfg.domainName}" = let inherit (config.services.searx) settings; in {
quic = true;
enableACME = true;
forceSSL = true;
# locations."/".proxyPass = "http://${lib.quoteListenAddr settings.server.bind_address}:${toString settings.server.port}";
locations."/".extraConfig = ''
uwsgi_pass "${lib.quoteListenAddr settings.server.bind_address}:${toString settings.server.port}";
include ${config.services.nginx.package}/conf/uwsgi_params;
'';
};
services.searx.enable = true;
services.searx.package = pkgs.searxng;
services.searx.runInUwsgi = true;
services.searx.uwsgiConfig = let inherit (config.services.searx) settings; in {
socket = "${lib.quoteListenAddr settings.server.bind_address}:${toString settings.server.port}";
};
services.searx.environmentFile = /var/lib/searx/searx.env;
services.searx.settings = {
use_default_settings = true;
search = {
safe_search = 0;
autocomplete = "duckduckgo"; # dbpedia, duckduckgo, google, startpage, swisscows, qwant, wikipedia - leave blank to turn off
default_lang = ""; # leave blank to detect from browser info or use codes from languages.py
};
server = {
port = 8888;
bind_address = "::1";
secret_key = "@SEARX_SECRET_KEY@";
base_url = "https://search.${cfg.domainName}/";
image_proxy = true;
default_http_headers = {
X-Content-Type-Options = "nosniff";
X-XSS-Protection = "1; mode=block";
X-Download-Options = "noopen";
X-Robots-Tag = "noindex, nofollow";
Referrer-Policy = "no-referrer";
};
};
outgoing = {
request_timeout = 5.0; # default timeout in seconds, can be override by engine
max_request_timeout = 15.0; # the maximum timeout in seconds
pool_connections = 100; # Maximum number of allowable connections, or null
pool_maxsize = 10; # Number of allowable keep-alive connections, or null
enable_http2 = true; # See https://www.python-httpx.org/http2/
};
};
}

View file

@ -75,6 +75,8 @@ in {
{ directory = /var/db/dhcpcd; user = "root"; group = "root"; mode = "0755"; } { directory = /var/db/dhcpcd; user = "root"; group = "root"; mode = "0755"; }
] ++ lib.optionals config.services.gitea.enable [ ] ++ lib.optionals config.services.gitea.enable [
{ directory = /var/lib/gitea; user = "gitea"; group = "gitea"; mode = "0755"; } { directory = /var/lib/gitea; user = "gitea"; group = "gitea"; mode = "0755"; }
] ++ lib.optionals config.services.forgejo.enable [
{ directory = /var/lib/forgejo; user = "forgejo"; group = "forgejo"; mode = "0755"; }
] ++ lib.optionals config.services.matrix-synapse.enable [ ] ++ lib.optionals config.services.matrix-synapse.enable [
{ directory = /var/lib/matrix-synapse; user = "matrix-synapse"; group = "matrix-synapse"; mode = "0700"; } { directory = /var/lib/matrix-synapse; user = "matrix-synapse"; group = "matrix-synapse"; mode = "0700"; }
] ++ lib.optionals config.services.heisenbridge.enable [ ] ++ lib.optionals config.services.heisenbridge.enable [