Compare commits

..

6 Commits

37 changed files with 749 additions and 134 deletions

View File

@@ -98,6 +98,7 @@ Each service file in `services/` follows this structure:
- **git-crypt**: `secrets/` directory and `usb-secrets/usb-secrets-key*` are encrypted (see `.gitattributes`) - **git-crypt**: `secrets/` directory and `usb-secrets/usb-secrets-key*` are encrypted (see `.gitattributes`)
- **agenix**: secrets declared in `modules/age-secrets.nix`, decrypted at runtime to `/run/agenix/` - **agenix**: secrets declared in `modules/age-secrets.nix`, decrypted at runtime to `/run/agenix/`
- **Identity**: USB drive at `/mnt/usb-secrets/usb-secrets-key` - **Identity**: USB drive at `/mnt/usb-secrets/usb-secrets-key`
- **Encrypting new secrets**: The agenix encryption key is in `usb-secrets/usb-secrets-key` (SSH private key, git-crypt encrypted). To create a new secret: derive the age public key with `ssh-keygen -y -f usb-secrets/usb-secrets-key | ssh-to-age`, then encrypt with `age -r <public-key> -o secrets/<name>.age`.
- Never read or commit plaintext secrets. Never log secret values. - Never read or commit plaintext secrets. Never log secret values.
### Important Patterns ### Important Patterns

View File

@@ -47,6 +47,7 @@
./services/ups.nix ./services/ups.nix
./services/bitwarden.nix ./services/bitwarden.nix
./services/firefox-syncserver.nix
./services/matrix.nix ./services/matrix.nix
./services/coturn.nix ./services/coturn.nix
@@ -284,7 +285,7 @@
openFirewall = true; openFirewall = true;
welcometext = "meow meow meow meow meow :3 xd"; welcometext = "meow meow meow meow meow :3 xd";
password = builtins.readFile ./secrets/murmur_password; password = builtins.readFile ./secrets/murmur_password;
port = service_configs.ports.murmur; port = service_configs.ports.public.murmur.port;
}; };
# services.botamusique = { # services.botamusique = {

View File

@@ -105,7 +105,19 @@
service_configs = import ./service-configs.nix; service_configs = import ./service-configs.nix;
pkgs = import nixpkgs { # Bootstrap pkgs used only to apply patches to nixpkgs source.
bootstrapPkgs = import nixpkgs { inherit system; };
# Patch nixpkgs to add PostgreSQL backend support for firefox-syncserver.
patchedNixpkgsSrc = bootstrapPkgs.applyPatches {
name = "nixpkgs-patched";
src = nixpkgs;
patches = [
./patches/0001-firefox-syncserver-add-postgresql-backend-support.patch
];
};
pkgs = import patchedNixpkgsSrc {
inherit system; inherit system;
targetPlatform = system; targetPlatform = system;
buildPlatform = builtins.currentSystem; buildPlatform = builtins.currentSystem;
@@ -130,19 +142,46 @@
; ;
}; };
modules = [ modules = [
# SAFETY! make sure no ports collide # SAFETY! port sanity checks
( (
{ lib, ... }: { config, lib, ... }:
let
publicPorts = lib.attrValues service_configs.ports.public;
privatePorts = lib.attrValues service_configs.ports.private;
allPortNumbers = map (p: p.port) (publicPorts ++ privatePorts);
uniquePortNumbers = lib.unique allPortNumbers;
# Which public ports must be in each firewall list
publicTcp = map (p: p.port) (lib.filter (p: p.proto == "tcp" || p.proto == "both") publicPorts);
publicUdp = map (p: p.port) (lib.filter (p: p.proto == "udp" || p.proto == "both") publicPorts);
privatePortNumbers = map (p: p.port) privatePorts;
fwTcp = config.networking.firewall.allowedTCPPorts;
fwUdp = config.networking.firewall.allowedUDPPorts;
missingTcp = lib.filter (p: !(builtins.elem p fwTcp)) publicTcp;
missingUdp = lib.filter (p: !(builtins.elem p fwUdp)) publicUdp;
leakedTcp = lib.filter (p: builtins.elem p fwTcp) privatePortNumbers;
leakedUdp = lib.filter (p: builtins.elem p fwUdp) privatePortNumbers;
in
{ {
config.assertions = [ config.assertions = [
{ {
assertion = assertion = (lib.length allPortNumbers) == (lib.length uniquePortNumbers);
let message = "Duplicate port numbers detected in ports.public / ports.private";
ports = lib.attrValues service_configs.ports; }
uniquePorts = lib.unique ports; {
in assertion = missingTcp == [ ];
(lib.length ports) == (lib.length uniquePorts); message = "Public ports missing from allowedTCPPorts: ${builtins.toString missingTcp}";
message = "Duplicate ports detected in 'ports' configuration"; }
{
assertion = missingUdp == [ ];
message = "Public ports missing from allowedUDPPorts: ${builtins.toString missingUdp}";
}
{
assertion = leakedTcp == [ ] && leakedUdp == [ ];
message = "Private ports leaked into firewall allow-lists TCP: ${builtins.toString leakedTcp}, UDP: ${builtins.toString leakedUdp}";
} }
]; ];
} }
@@ -157,10 +196,21 @@
./disk-config.nix ./disk-config.nix
./configuration.nix ./configuration.nix
# Replace upstream firefox-syncserver module + package with patched
# versions that add PostgreSQL backend support.
{ {
disabledModules = [ "services/networking/firefox-syncserver.nix" ];
imports = [
"${patchedNixpkgsSrc}/nixos/modules/services/networking/firefox-syncserver.nix"
];
nixpkgs.overlays = [ nixpkgs.overlays = [
nix-minecraft.overlay nix-minecraft.overlay
(import ./modules/overlays.nix) (import ./modules/overlays.nix)
(_final: prev: {
syncstorage-rs =
prev.callPackage "${patchedNixpkgsSrc}/pkgs/by-name/sy/syncstorage-rs/package.nix"
{ };
})
]; ];
nixpkgs.config.allowUnfreePredicate = nixpkgs.config.allowUnfreePredicate =
pkg: pkg:

View File

@@ -82,5 +82,11 @@
owner = "root"; owner = "root";
group = "root"; group = "root";
}; };
# Firefox Sync server secrets (SYNC_MASTER_SECRET)
firefox-syncserver-env = {
file = ../secrets/firefox-syncserver-env.age;
mode = "0400";
};
}; };
} }

View File

@@ -0,0 +1,379 @@
From ab57092a60123e361cf0de1c1a314a9888c45219 Mon Sep 17 00:00:00 2001
From: Simon Gardling <titaniumtown@proton.me>
Date: Sat, 21 Mar 2026 09:24:39 -0400
Subject: [PATCH] temp
---
.../services/networking/firefox-syncserver.md | 23 +++
.../networking/firefox-syncserver.nix | 140 ++++++++++++++----
pkgs/by-name/sy/syncstorage-rs/package.nix | 49 ++++--
3 files changed, 174 insertions(+), 38 deletions(-)
diff --git a/nixos/modules/services/networking/firefox-syncserver.md b/nixos/modules/services/networking/firefox-syncserver.md
index 991e97f799d6..3bc45cfa5640 100644
--- a/nixos/modules/services/networking/firefox-syncserver.md
+++ b/nixos/modules/services/networking/firefox-syncserver.md
@@ -32,6 +32,29 @@ This configuration should never be used in production. It is not encrypted and
stores its secrets in a world-readable location.
:::
+## Database backends {#module-services-firefox-syncserver-database}
+
+The sync server supports MySQL/MariaDB (the default) and PostgreSQL as database
+backends. Set `database.type` to choose the backend:
+
+```nix
+{
+ services.firefox-syncserver = {
+ enable = true;
+ database.type = "postgresql";
+ secrets = "/run/secrets/firefox-syncserver";
+ singleNode = {
+ enable = true;
+ hostname = "localhost";
+ url = "http://localhost:5000";
+ };
+ };
+}
+```
+
+When `database.createLocally` is `true` (the default), the module will
+automatically enable and configure the corresponding database service.
+
## More detailed setup {#module-services-firefox-syncserver-configuration}
The `firefox-syncserver` service provides a number of options to make setting up
diff --git a/nixos/modules/services/networking/firefox-syncserver.nix b/nixos/modules/services/networking/firefox-syncserver.nix
index 6a50e49fc096..70a56314e323 100644
--- a/nixos/modules/services/networking/firefox-syncserver.nix
+++ b/nixos/modules/services/networking/firefox-syncserver.nix
@@ -13,7 +13,21 @@ let
defaultUser = "firefox-syncserver";
dbIsLocal = cfg.database.host == "localhost";
- dbURL = "mysql://${cfg.database.user}@${cfg.database.host}/${cfg.database.name}${lib.optionalString dbIsLocal "?socket=/run/mysqld/mysqld.sock"}";
+ dbIsMySQL = cfg.database.type == "mysql";
+ dbIsPostgreSQL = cfg.database.type == "postgresql";
+
+ dbURL =
+ if dbIsMySQL then
+ "mysql://${cfg.database.user}@${cfg.database.host}/${cfg.database.name}${lib.optionalString dbIsLocal "?socket=/run/mysqld/mysqld.sock"}"
+ else
+ "postgres://${cfg.database.user}@${cfg.database.host}/${cfg.database.name}${lib.optionalString dbIsLocal "?host=/run/postgresql"}";
+
+ # postgresql.target waits for postgresql-setup.service (which runs
+ # ensureDatabases / ensureUsers) to complete, avoiding race conditions
+ # where the syncserver starts before its database and role exist.
+ dbService = if dbIsMySQL then "mysql.service" else "postgresql.target";
+
+ syncserver = cfg.package.override { dbBackend = cfg.database.type; };
format = pkgs.formats.toml { };
settings = {
@@ -22,7 +36,7 @@ let
database_url = dbURL;
};
tokenserver = {
- node_type = "mysql";
+ node_type = if dbIsMySQL then "mysql" else "postgres";
database_url = dbURL;
fxa_email_domain = "api.accounts.firefox.com";
fxa_oauth_server_url = "https://oauth.accounts.firefox.com/v1";
@@ -41,7 +55,8 @@ let
};
};
configFile = format.generate "syncstorage.toml" (lib.recursiveUpdate settings cfg.settings);
- setupScript = pkgs.writeShellScript "firefox-syncserver-setup" ''
+
+ mysqlSetupScript = pkgs.writeShellScript "firefox-syncserver-setup" ''
set -euo pipefail
shopt -s inherit_errexit
@@ -79,6 +94,47 @@ let
echo "Single-node setup failed"
exit 1
'';
+
+ postgresqlSetupScript = pkgs.writeShellScript "firefox-syncserver-setup" ''
+ set -euo pipefail
+ shopt -s inherit_errexit
+
+ schema_configured() {
+ psql -d ${cfg.database.name} -tAc "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'services')" | grep -q t
+ }
+
+ update_config() {
+ psql -d ${cfg.database.name} <<'EOF'
+ BEGIN;
+
+ INSERT INTO services (id, service, pattern)
+ VALUES (1, 'sync-1.5', '{node}/1.5/{uid}')
+ ON CONFLICT (id) DO UPDATE SET service = 'sync-1.5', pattern = '{node}/1.5/{uid}';
+ INSERT INTO nodes (id, service, node, available, current_load,
+ capacity, downed, backoff)
+ VALUES (1, 1, '${cfg.singleNode.url}', ${toString cfg.singleNode.capacity},
+ 0, ${toString cfg.singleNode.capacity}, 0, 0)
+ ON CONFLICT (id) DO UPDATE SET node = '${cfg.singleNode.url}', capacity = ${toString cfg.singleNode.capacity};
+
+ COMMIT;
+ EOF
+ }
+
+
+ for (( try = 0; try < 60; try++ )); do
+ if ! schema_configured; then
+ sleep 2
+ else
+ update_config
+ exit 0
+ fi
+ done
+
+ echo "Single-node setup failed"
+ exit 1
+ '';
+
+ setupScript = if dbIsMySQL then mysqlSetupScript else postgresqlSetupScript;
in
{
@@ -88,25 +144,26 @@ in
the Firefox Sync storage service.
Out of the box this will not be very useful unless you also configure at least
- one service and one nodes by inserting them into the mysql database manually, e.g.
- by running
-
- ```
- INSERT INTO `services` (`id`, `service`, `pattern`) VALUES ('1', 'sync-1.5', '{node}/1.5/{uid}');
- INSERT INTO `nodes` (`id`, `service`, `node`, `available`, `current_load`,
- `capacity`, `downed`, `backoff`)
- VALUES ('1', '1', 'https://mydomain.tld', '1', '0', '10', '0', '0');
- ```
+ one service and one nodes by inserting them into the database manually, e.g.
+ by running the equivalent SQL for your database backend.
{option}`${opt.singleNode.enable}` does this automatically when enabled
'';
package = lib.mkPackageOption pkgs "syncstorage-rs" { };
+ database.type = lib.mkOption {
+ type = lib.types.enum [
+ "mysql"
+ "postgresql"
+ ];
+ default = "mysql";
+ description = ''
+ Which database backend to use for storage.
+ '';
+ };
+
database.name = lib.mkOption {
- # the mysql module does not allow `-quoting without resorting to shell
- # escaping, so we restrict db names for forward compaitiblity should this
- # behavior ever change.
type = lib.types.strMatching "[a-z_][a-z0-9_]*";
default = defaultDatabase;
description = ''
@@ -117,9 +174,15 @@ in
database.user = lib.mkOption {
type = lib.types.str;
- default = defaultUser;
+ default = if dbIsPostgreSQL then defaultDatabase else defaultUser;
+ defaultText = lib.literalExpression ''
+ if database.type == "postgresql" then "${defaultDatabase}" else "${defaultUser}"
+ '';
description = ''
- Username for database connections.
+ Username for database connections. When using PostgreSQL with
+ `createLocally`, this defaults to the database name so that
+ `ensureDBOwnership` works (it requires user and database names
+ to match).
'';
};
@@ -137,7 +200,8 @@ in
default = true;
description = ''
Whether to create database and user on the local machine if they do not exist.
- This includes enabling unix domain socket authentication for the configured user.
+ This includes enabling the configured database service and setting up
+ authentication for the configured user.
'';
};
@@ -237,7 +301,7 @@ in
};
config = lib.mkIf cfg.enable {
- services.mysql = lib.mkIf cfg.database.createLocally {
+ services.mysql = lib.mkIf (cfg.database.createLocally && dbIsMySQL) {
enable = true;
ensureDatabases = [ cfg.database.name ];
ensureUsers = [
@@ -250,16 +314,27 @@ in
];
};
+ services.postgresql = lib.mkIf (cfg.database.createLocally && dbIsPostgreSQL) {
+ enable = true;
+ ensureDatabases = [ cfg.database.name ];
+ ensureUsers = [
+ {
+ name = cfg.database.user;
+ ensureDBOwnership = true;
+ }
+ ];
+ };
+
systemd.services.firefox-syncserver = {
wantedBy = [ "multi-user.target" ];
- requires = lib.mkIf dbIsLocal [ "mysql.service" ];
- after = lib.mkIf dbIsLocal [ "mysql.service" ];
+ requires = lib.mkIf dbIsLocal [ dbService ];
+ after = lib.mkIf dbIsLocal [ dbService ];
restartTriggers = lib.optional cfg.singleNode.enable setupScript;
environment.RUST_LOG = cfg.logLevel;
serviceConfig = {
- User = defaultUser;
- Group = defaultUser;
- ExecStart = "${cfg.package}/bin/syncserver --config ${configFile}";
+ User = cfg.database.user;
+ Group = cfg.database.user;
+ ExecStart = "${syncserver}/bin/syncserver --config ${configFile}";
EnvironmentFile = lib.mkIf (cfg.secrets != null) "${cfg.secrets}";
# hardening
@@ -303,10 +378,19 @@ in
systemd.services.firefox-syncserver-setup = lib.mkIf cfg.singleNode.enable {
wantedBy = [ "firefox-syncserver.service" ];
- requires = [ "firefox-syncserver.service" ] ++ lib.optional dbIsLocal "mysql.service";
- after = [ "firefox-syncserver.service" ] ++ lib.optional dbIsLocal "mysql.service";
- path = [ config.services.mysql.package ];
- serviceConfig.ExecStart = [ "${setupScript}" ];
+ requires = [ "firefox-syncserver.service" ] ++ lib.optional dbIsLocal dbService;
+ after = [ "firefox-syncserver.service" ] ++ lib.optional dbIsLocal dbService;
+ path =
+ if dbIsMySQL then [ config.services.mysql.package ] else [ config.services.postgresql.package ];
+ serviceConfig = {
+ ExecStart = [ "${setupScript}" ];
+ }
+ // lib.optionalAttrs dbIsPostgreSQL {
+ # PostgreSQL peer authentication requires the system user to match the
+ # database user. Run as the superuser so we can access all databases.
+ User = "postgres";
+ Group = "postgres";
+ };
};
services.nginx.virtualHosts = lib.mkIf cfg.singleNode.enableNginx {
diff --git a/pkgs/by-name/sy/syncstorage-rs/package.nix b/pkgs/by-name/sy/syncstorage-rs/package.nix
index 39b2b53ab03c..944ed72525af 100644
--- a/pkgs/by-name/sy/syncstorage-rs/package.nix
+++ b/pkgs/by-name/sy/syncstorage-rs/package.nix
@@ -1,14 +1,18 @@
{
fetchFromGitHub,
+ fetchurl,
rustPlatform,
pkg-config,
python3,
cmake,
libmysqlclient,
+ libpq,
+ openssl,
makeBinaryWrapper,
lib,
nix-update-script,
nixosTests,
+ dbBackend ? "mysql",
}:
let
@@ -19,17 +23,23 @@ let
p.tokenlib
p.cryptography
]);
+ # utoipa-swagger-ui downloads Swagger UI assets at build time.
+ # Prefetch the archive for sandboxed builds.
+ swaggerUi = fetchurl {
+ url = "https://github.com/swagger-api/swagger-ui/archive/refs/tags/v5.17.14.zip";
+ hash = "sha256-SBJE0IEgl7Efuu73n3HZQrFxYX+cn5UU5jrL4T5xzNw=";
+ };
in
-rustPlatform.buildRustPackage rec {
+rustPlatform.buildRustPackage (finalAttrs: {
pname = "syncstorage-rs";
- version = "0.21.1-unstable-2026-01-26";
+ version = "0.21.1-unstable-2026-02-24";
src = fetchFromGitHub {
owner = "mozilla-services";
repo = "syncstorage-rs";
- rev = "11659d98f9c69948a0aab353437ce2036c388711";
- hash = "sha256-G37QvxTNh/C3gmKG0UYHI6QBr0F+KLGRNI/Sx33uOsc=";
+ rev = "50a739b58dc9ec81995f86e71d992aa14ccc450e";
+ hash = "sha256-idq0RGdwoV6GVuq36IVVVCFbyMTe8i/EpVWE59D/dhM=";
};
nativeBuildInputs = [
@@ -39,16 +49,35 @@ rustPlatform.buildRustPackage rec {
python3
];
- buildInputs = [
- libmysqlclient
- ];
+ buildInputs =
+ lib.optional (dbBackend == "mysql") libmysqlclient
+ ++ lib.optionals (dbBackend == "postgresql") [
+ libpq
+ openssl
+ ];
+
+ buildNoDefaultFeatures = true;
+ # The syncserver "postgres" feature only enables syncstorage-db/postgres.
+ # tokenserver-db/postgres must be enabled separately so the tokenserver
+ # can also connect to PostgreSQL (it dispatches on the URL scheme at runtime).
+ buildFeatures =
+ let
+ cargoFeature = if dbBackend == "postgresql" then "postgres" else dbBackend;
+ in
+ [
+ cargoFeature
+ "tokenserver-db/${cargoFeature}"
+ "py_verifier"
+ ];
+
+ SWAGGER_UI_DOWNLOAD_URL = "file://${swaggerUi}";
preFixup = ''
wrapProgram $out/bin/syncserver \
--prefix PATH : ${lib.makeBinPath [ pyFxADeps ]}
'';
- cargoHash = "sha256-9Dcf5mDyK/XjsKTlCPXTHoBkIq+FFPDg1zfK24Y9nHQ=";
+ cargoHash = "sha256-80EztkSX+SnmqsRWIXbChUB8AeV1Tp9WXoWNbDY8rUE=";
# almost all tests need a DB to test against
doCheck = false;
@@ -60,10 +89,10 @@ rustPlatform.buildRustPackage rec {
meta = {
description = "Mozilla Sync Storage built with Rust";
homepage = "https://github.com/mozilla-services/syncstorage-rs";
- changelog = "https://github.com/mozilla-services/syncstorage-rs/releases/tag/${version}";
+ changelog = "https://github.com/mozilla-services/syncstorage-rs/releases/tag/${finalAttrs.version}";
license = lib.licenses.mpl20;
maintainers = [ ];
platforms = lib.platforms.linux;
mainProgram = "syncserver";
};
-}
+})
--
2.53.0

Binary file not shown.

View File

@@ -9,41 +9,147 @@ rec {
cpu_arch = "znver3"; cpu_arch = "znver3";
ports = { ports = {
# public # Ports exposed to the internet. The flake asserts every public port
http = 80; # TCP # appears in the corresponding firewall allow-list (TCP, UDP, or both).
https = 443; # TCP+UDP (HTTP/3 QUIC) public = {
minecraft = 25565; # TCP http = {
syncthing_protocol = 22000; # TCP+UDP (QUIC) port = 80;
syncthing_discovery = 21027; # UDP proto = "tcp";
matrix_federation = 8448; # TCP+UDP (HTTP/3 QUIC) };
coturn = 3478; # TCP+UDP https = {
coturn_tls = 5349; # TCP+UDP port = 443;
livekit = 7880; # TCP proto = "both";
soulseek_listen = 50300; # TCP }; # HTTP/3 QUIC
monero = 18080; # TCP minecraft = {
p2pool_p2p = 37889; # TCP port = 25565;
murmur = 64738; # TCP + UDP proto = "tcp";
};
syncthing_protocol = {
port = 22000;
proto = "both";
}; # QUIC
syncthing_discovery = {
port = 21027;
proto = "udp";
};
matrix_federation = {
port = 8448;
proto = "both";
}; # HTTP/3 QUIC
coturn = {
port = 3478;
proto = "both";
};
coturn_tls = {
port = 5349;
proto = "both";
};
livekit = {
port = 7880;
proto = "tcp";
};
soulseek_listen = {
port = 50300;
proto = "tcp";
};
monero = {
port = 18080;
proto = "tcp";
};
monero_rpc = {
port = 18081;
proto = "tcp";
}; # restricted public RPC
p2pool_p2p = {
port = 37889;
proto = "tcp";
};
murmur = {
port = 64738;
proto = "both";
};
};
# private # Ports bound to localhost / VPN only. The flake asserts none of
jellyfin = 8096; # TCP - no services.jellyfin option for this # these appear in the firewall allow-lists.
torrent = 6011; # TCP private = {
bitmagnet = 3333; # TCP jellyfin = {
gitea = 2283; # TCP port = 8096;
immich = 2284; # TCP proto = "tcp";
soulseek_web = 5030; # TCP };
vaultwarden = 8222; # TCP torrent = {
syncthing_gui = 8384; # TCP port = 6011;
matrix = 6167; # TCP proto = "tcp";
ntfy = 2586; # TCP };
lk_jwt = 8081; # TCP bitmagnet = {
prowlarr = 9696; # TCP port = 3333;
sonarr = 8989; # TCP proto = "tcp";
radarr = 7878; # TCP };
bazarr = 6767; # TCP gitea = {
jellyseerr = 5055; # TCP port = 2283;
monero_rpc = 18081; # TCP proto = "tcp";
monero_zmq = 18083; # TCP };
p2pool_stratum = 3334; # TCP immich = {
port = 2284;
proto = "tcp";
};
soulseek_web = {
port = 5030;
proto = "tcp";
};
vaultwarden = {
port = 8222;
proto = "tcp";
};
syncthing_gui = {
port = 8384;
proto = "tcp";
};
matrix = {
port = 6167;
proto = "tcp";
};
ntfy = {
port = 2586;
proto = "tcp";
};
lk_jwt = {
port = 8081;
proto = "tcp";
};
prowlarr = {
port = 9696;
proto = "tcp";
};
sonarr = {
port = 8989;
proto = "tcp";
};
radarr = {
port = 7878;
proto = "tcp";
};
bazarr = {
port = 6767;
proto = "tcp";
};
jellyseerr = {
port = 5055;
proto = "tcp";
};
monero_zmq = {
port = 18083;
proto = "tcp";
};
p2pool_stratum = {
port = 3334;
proto = "tcp";
};
firefox_syncserver = {
port = 5000;
proto = "tcp";
};
};
}; };
https = { https = {
@@ -70,7 +176,7 @@ rec {
minecraft = { minecraft = {
parent_dir = services_dir + "/minecraft"; parent_dir = services_dir + "/minecraft";
server_name = "main"; server_name = "main";
memory = rec { memory = {
heap_size_m = 4000; heap_size_m = 4000;
large_page_size_m = 2; large_page_size_m = 2;
}; };
@@ -147,6 +253,10 @@ rec {
dataDir = services_dir + "/recyclarr"; dataDir = services_dir + "/recyclarr";
}; };
firefox_syncserver = {
domain = "firefox-sync.${https.domain}";
};
media = { media = {
moviesDir = torrents_path + "/media/movies"; moviesDir = torrents_path + "/media/movies";
tvDir = torrents_path + "/media/tv"; tvDir = torrents_path + "/media/tv";

View File

@@ -7,8 +7,8 @@ let
radarrConfig = "${service_configs.radarr.dataDir}/config.xml"; radarrConfig = "${service_configs.radarr.dataDir}/config.xml";
sonarrConfig = "${service_configs.sonarr.dataDir}/config.xml"; sonarrConfig = "${service_configs.sonarr.dataDir}/config.xml";
radarrUrl = "http://localhost:${builtins.toString service_configs.ports.radarr}"; radarrUrl = "http://localhost:${builtins.toString service_configs.ports.private.radarr.port}";
sonarrUrl = "http://localhost:${builtins.toString service_configs.ports.sonarr}"; sonarrUrl = "http://localhost:${builtins.toString service_configs.ports.private.sonarr.port}";
curl = "${pkgs.curl}/bin/curl"; curl = "${pkgs.curl}/bin/curl";
jq = "${pkgs.jq}/bin/jq"; jq = "${pkgs.jq}/bin/jq";

View File

@@ -20,12 +20,12 @@
services.bazarr = { services.bazarr = {
enable = true; enable = true;
listenPort = service_configs.ports.bazarr; listenPort = service_configs.ports.private.bazarr.port;
}; };
services.caddy.virtualHosts."bazarr.${service_configs.https.domain}".extraConfig = '' services.caddy.virtualHosts."bazarr.${service_configs.https.domain}".extraConfig = ''
import ${config.age.secrets.caddy_auth.path} import ${config.age.secrets.caddy_auth.path}
reverse_proxy :${builtins.toString service_configs.ports.bazarr} reverse_proxy :${builtins.toString service_configs.ports.private.bazarr.port}
''; '';
users.users.${config.services.bazarr.user}.extraGroups = [ users.users.${config.services.bazarr.user}.extraGroups = [

View File

@@ -4,7 +4,7 @@
prowlarr = { prowlarr = {
enable = true; enable = true;
serviceName = "prowlarr"; serviceName = "prowlarr";
port = service_configs.ports.prowlarr; port = service_configs.ports.private.prowlarr.port;
dataDir = service_configs.prowlarr.dataDir; dataDir = service_configs.prowlarr.dataDir;
apiVersion = "v1"; apiVersion = "v1";
networkNamespacePath = "/run/netns/wg"; networkNamespacePath = "/run/netns/wg";
@@ -14,8 +14,8 @@
name = "Sonarr"; name = "Sonarr";
implementation = "Sonarr"; implementation = "Sonarr";
configContract = "SonarrSettings"; configContract = "SonarrSettings";
prowlarrUrl = "http://localhost:${builtins.toString service_configs.ports.prowlarr}"; prowlarrUrl = "http://localhost:${builtins.toString service_configs.ports.private.prowlarr.port}";
baseUrl = "http://${config.vpnNamespaces.wg.bridgeAddress}:${builtins.toString service_configs.ports.sonarr}"; baseUrl = "http://${config.vpnNamespaces.wg.bridgeAddress}:${builtins.toString service_configs.ports.private.sonarr.port}";
apiKeyFrom = "${service_configs.sonarr.dataDir}/config.xml"; apiKeyFrom = "${service_configs.sonarr.dataDir}/config.xml";
syncCategories = [ syncCategories = [
5000 5000
@@ -33,8 +33,8 @@
name = "Radarr"; name = "Radarr";
implementation = "Radarr"; implementation = "Radarr";
configContract = "RadarrSettings"; configContract = "RadarrSettings";
prowlarrUrl = "http://localhost:${builtins.toString service_configs.ports.prowlarr}"; prowlarrUrl = "http://localhost:${builtins.toString service_configs.ports.private.prowlarr.port}";
baseUrl = "http://${config.vpnNamespaces.wg.bridgeAddress}:${builtins.toString service_configs.ports.radarr}"; baseUrl = "http://${config.vpnNamespaces.wg.bridgeAddress}:${builtins.toString service_configs.ports.private.radarr.port}";
apiKeyFrom = "${service_configs.radarr.dataDir}/config.xml"; apiKeyFrom = "${service_configs.radarr.dataDir}/config.xml";
syncCategories = [ syncCategories = [
2000 2000
@@ -56,7 +56,7 @@
sonarr = { sonarr = {
enable = true; enable = true;
serviceName = "sonarr"; serviceName = "sonarr";
port = service_configs.ports.sonarr; port = service_configs.ports.private.sonarr.port;
dataDir = service_configs.sonarr.dataDir; dataDir = service_configs.sonarr.dataDir;
healthChecks = true; healthChecks = true;
rootFolders = [ service_configs.media.tvDir ]; rootFolders = [ service_configs.media.tvDir ];
@@ -68,7 +68,7 @@
serviceName = "qbittorrent"; serviceName = "qbittorrent";
fields = { fields = {
host = config.vpnNamespaces.wg.namespaceAddress; host = config.vpnNamespaces.wg.namespaceAddress;
port = service_configs.ports.torrent; port = service_configs.ports.private.torrent.port;
useSsl = false; useSsl = false;
tvCategory = "tvshows"; tvCategory = "tvshows";
}; };
@@ -79,7 +79,7 @@
radarr = { radarr = {
enable = true; enable = true;
serviceName = "radarr"; serviceName = "radarr";
port = service_configs.ports.radarr; port = service_configs.ports.private.radarr.port;
dataDir = service_configs.radarr.dataDir; dataDir = service_configs.radarr.dataDir;
healthChecks = true; healthChecks = true;
rootFolders = [ service_configs.media.moviesDir ]; rootFolders = [ service_configs.media.moviesDir ];
@@ -91,7 +91,7 @@
serviceName = "qbittorrent"; serviceName = "qbittorrent";
fields = { fields = {
host = config.vpnNamespaces.wg.namespaceAddress; host = config.vpnNamespaces.wg.namespaceAddress;
port = service_configs.ports.torrent; port = service_configs.ports.private.torrent.port;
useSsl = false; useSsl = false;
movieCategory = "movies"; movieCategory = "movies";
}; };
@@ -103,17 +103,17 @@
services.bazarrInit = { services.bazarrInit = {
enable = true; enable = true;
dataDir = "/var/lib/bazarr"; dataDir = "/var/lib/bazarr";
port = service_configs.ports.bazarr; port = service_configs.ports.private.bazarr.port;
sonarr = { sonarr = {
enable = true; enable = true;
dataDir = service_configs.sonarr.dataDir; dataDir = service_configs.sonarr.dataDir;
port = service_configs.ports.sonarr; port = service_configs.ports.private.sonarr.port;
serviceName = "sonarr"; serviceName = "sonarr";
}; };
radarr = { radarr = {
enable = true; enable = true;
dataDir = service_configs.radarr.dataDir; dataDir = service_configs.radarr.dataDir;
port = service_configs.ports.radarr; port = service_configs.ports.private.radarr.port;
serviceName = "radarr"; serviceName = "radarr";
}; };
}; };

View File

@@ -17,7 +17,7 @@
services.jellyseerr = { services.jellyseerr = {
enable = true; enable = true;
port = service_configs.ports.jellyseerr; port = service_configs.ports.private.jellyseerr.port;
configDir = service_configs.jellyseerr.configDir; configDir = service_configs.jellyseerr.configDir;
}; };
@@ -38,6 +38,6 @@
services.caddy.virtualHosts."jellyseerr.${service_configs.https.domain}".extraConfig = '' services.caddy.virtualHosts."jellyseerr.${service_configs.https.domain}".extraConfig = ''
# import ${config.age.secrets.caddy_auth.path} # import ${config.age.secrets.caddy_auth.path}
reverse_proxy :${builtins.toString service_configs.ports.jellyseerr} reverse_proxy :${builtins.toString service_configs.ports.private.jellyseerr.port}
''; '';
} }

View File

@@ -10,17 +10,17 @@
(lib.serviceMountWithZpool "prowlarr" service_configs.zpool_ssds [ (lib.serviceMountWithZpool "prowlarr" service_configs.zpool_ssds [
service_configs.prowlarr.dataDir service_configs.prowlarr.dataDir
]) ])
(lib.vpnNamespaceOpenPort service_configs.ports.prowlarr "prowlarr") (lib.vpnNamespaceOpenPort service_configs.ports.private.prowlarr.port "prowlarr")
]; ];
services.prowlarr = { services.prowlarr = {
enable = true; enable = true;
dataDir = service_configs.prowlarr.dataDir; dataDir = service_configs.prowlarr.dataDir;
settings.server.port = service_configs.ports.prowlarr; settings.server.port = service_configs.ports.private.prowlarr.port;
}; };
services.caddy.virtualHosts."prowlarr.${service_configs.https.domain}".extraConfig = '' services.caddy.virtualHosts."prowlarr.${service_configs.https.domain}".extraConfig = ''
import ${config.age.secrets.caddy_auth.path} import ${config.age.secrets.caddy_auth.path}
reverse_proxy ${config.vpnNamespaces.wg.namespaceAddress}:${builtins.toString service_configs.ports.prowlarr} reverse_proxy ${config.vpnNamespaces.wg.namespaceAddress}:${builtins.toString service_configs.ports.private.prowlarr.port}
''; '';
} }

View File

@@ -21,13 +21,13 @@
services.radarr = { services.radarr = {
enable = true; enable = true;
dataDir = service_configs.radarr.dataDir; dataDir = service_configs.radarr.dataDir;
settings.server.port = service_configs.ports.radarr; settings.server.port = service_configs.ports.private.radarr.port;
settings.update.mechanism = "external"; settings.update.mechanism = "external";
}; };
services.caddy.virtualHosts."radarr.${service_configs.https.domain}".extraConfig = '' services.caddy.virtualHosts."radarr.${service_configs.https.domain}".extraConfig = ''
import ${config.age.secrets.caddy_auth.path} import ${config.age.secrets.caddy_auth.path}
reverse_proxy :${builtins.toString service_configs.ports.radarr} reverse_proxy :${builtins.toString service_configs.ports.private.radarr.port}
''; '';
users.users.${config.services.radarr.user}.extraGroups = [ users.users.${config.services.radarr.user}.extraGroups = [

View File

@@ -44,7 +44,7 @@ in
configuration = { configuration = {
radarr.movies = { radarr.movies = {
base_url = "http://localhost:${builtins.toString service_configs.ports.radarr}"; base_url = "http://localhost:${builtins.toString service_configs.ports.private.radarr.port}";
include = [ include = [
{ template = "radarr-quality-definition-movie"; } { template = "radarr-quality-definition-movie"; }
@@ -123,7 +123,7 @@ in
}; };
sonarr.series = { sonarr.series = {
base_url = "http://localhost:${builtins.toString service_configs.ports.sonarr}"; base_url = "http://localhost:${builtins.toString service_configs.ports.private.sonarr.port}";
include = [ include = [
{ template = "sonarr-quality-definition-series"; } { template = "sonarr-quality-definition-series"; }

View File

@@ -27,13 +27,13 @@
services.sonarr = { services.sonarr = {
enable = true; enable = true;
dataDir = service_configs.sonarr.dataDir; dataDir = service_configs.sonarr.dataDir;
settings.server.port = service_configs.ports.sonarr; settings.server.port = service_configs.ports.private.sonarr.port;
settings.update.mechanism = "external"; settings.update.mechanism = "external";
}; };
services.caddy.virtualHosts."sonarr.${service_configs.https.domain}".extraConfig = '' services.caddy.virtualHosts."sonarr.${service_configs.https.domain}".extraConfig = ''
import ${config.age.secrets.caddy_auth.path} import ${config.age.secrets.caddy_auth.path}
reverse_proxy :${builtins.toString service_configs.ports.sonarr} reverse_proxy :${builtins.toString service_configs.ports.private.sonarr.port}
''; '';
users.users.${config.services.sonarr.user}.extraGroups = [ users.users.${config.services.sonarr.user}.extraGroups = [

View File

@@ -7,7 +7,7 @@
}: }:
{ {
imports = [ imports = [
(lib.vpnNamespaceOpenPort service_configs.ports.bitmagnet "bitmagnet") (lib.vpnNamespaceOpenPort service_configs.ports.private.bitmagnet.port "bitmagnet")
]; ];
services.bitmagnet = { services.bitmagnet = {
@@ -19,13 +19,13 @@
}; };
http_server = { http_server = {
# TODO! make issue about this being a string and not a `port` type # TODO! make issue about this being a string and not a `port` type
port = ":" + (builtins.toString service_configs.ports.bitmagnet); port = ":" + (builtins.toString service_configs.ports.private.bitmagnet.port);
}; };
}; };
}; };
services.caddy.virtualHosts."bitmagnet.${service_configs.https.domain}".extraConfig = '' services.caddy.virtualHosts."bitmagnet.${service_configs.https.domain}".extraConfig = ''
import ${config.age.secrets.caddy_auth.path} import ${config.age.secrets.caddy_auth.path}
reverse_proxy ${config.vpnNamespaces.wg.namespaceAddress}:${builtins.toString service_configs.ports.bitmagnet} reverse_proxy ${config.vpnNamespaces.wg.namespaceAddress}:${builtins.toString service_configs.ports.private.bitmagnet.port}
''; '';
} }

View File

@@ -9,28 +9,23 @@
imports = [ imports = [
(lib.serviceMountWithZpool "vaultwarden" service_configs.zpool_ssds [ (lib.serviceMountWithZpool "vaultwarden" service_configs.zpool_ssds [
service_configs.vaultwarden.path service_configs.vaultwarden.path
config.services.vaultwarden.backupDir
])
(lib.serviceMountWithZpool "backup-vaultwarden" service_configs.zpool_ssds [
service_configs.vaultwarden.path
config.services.vaultwarden.backupDir
]) ])
(lib.serviceFilePerms "vaultwarden" [ (lib.serviceFilePerms "vaultwarden" [
"Z ${service_configs.vaultwarden.path} 0700 vaultwarden vaultwarden" "Z ${service_configs.vaultwarden.path} 0700 vaultwarden vaultwarden"
"Z ${config.services.vaultwarden.backupDir} 0700 vaultwarden vaultwarden"
]) ])
]; ];
services.vaultwarden = { services.vaultwarden = {
enable = true; enable = true;
backupDir = "/${service_configs.zpool_ssds}/bak/vaultwarden"; dbBackend = "postgresql";
configurePostgres = true;
config = { config = {
# Refer to https://github.com/dani-garcia/vaultwarden/blob/main/.env.template # Refer to https://github.com/dani-garcia/vaultwarden/blob/main/.env.template
DOMAIN = "https://bitwarden.${service_configs.https.domain}"; DOMAIN = "https://bitwarden.${service_configs.https.domain}";
SIGNUPS_ALLOWED = false; SIGNUPS_ALLOWED = false;
ROCKET_ADDRESS = "127.0.0.1"; ROCKET_ADDRESS = "127.0.0.1";
ROCKET_PORT = service_configs.ports.vaultwarden; ROCKET_PORT = service_configs.ports.private.vaultwarden.port;
ROCKET_LOG = "critical"; ROCKET_LOG = "critical";
}; };
}; };

View File

@@ -113,14 +113,14 @@ in
systemd.packages = with pkgs; [ nssTools ]; systemd.packages = with pkgs; [ nssTools ];
networking.firewall.allowedTCPPorts = [ networking.firewall.allowedTCPPorts = [
service_configs.ports.https service_configs.ports.public.https.port
# http (but really acmeCA challenges) # http (but really acmeCA challenges)
service_configs.ports.http service_configs.ports.public.http.port
]; ];
networking.firewall.allowedUDPPorts = [ networking.firewall.allowedUDPPorts = [
service_configs.ports.https service_configs.ports.public.https.port
]; ];
# Protect Caddy basic auth endpoints from brute force attacks # Protect Caddy basic auth endpoints from brute force attacks

View File

@@ -10,8 +10,8 @@
realm = service_configs.https.domain; realm = service_configs.https.domain;
use-auth-secret = true; use-auth-secret = true;
static-auth-secret = lib.strings.trim (builtins.readFile ../secrets/coturn_static_auth_secret); static-auth-secret = lib.strings.trim (builtins.readFile ../secrets/coturn_static_auth_secret);
listening-port = service_configs.ports.coturn; listening-port = service_configs.ports.public.coturn.port;
tls-listening-port = service_configs.ports.coturn_tls; tls-listening-port = service_configs.ports.public.coturn_tls.port;
no-cli = true; no-cli = true;
# recommended security settings from Synapse's coturn docs # recommended security settings from Synapse's coturn docs
@@ -41,12 +41,12 @@
# coturn needs these ports open # coturn needs these ports open
networking.firewall = { networking.firewall = {
allowedTCPPorts = [ allowedTCPPorts = [
service_configs.ports.coturn service_configs.ports.public.coturn.port
service_configs.ports.coturn_tls service_configs.ports.public.coturn_tls.port
]; ];
allowedUDPPorts = [ allowedUDPPorts = [
service_configs.ports.coturn service_configs.ports.public.coturn.port
service_configs.ports.coturn_tls service_configs.ports.public.coturn_tls.port
]; ];
# relay port range # relay port range
allowedUDPPortRanges = [ allowedUDPPortRanges = [

View File

@@ -0,0 +1,39 @@
{
config,
lib,
pkgs,
service_configs,
...
}:
{
services.firefox-syncserver = {
enable = true;
database = {
type = "postgresql";
createLocally = false;
user = "firefox_syncserver";
};
secrets = config.age.secrets.firefox-syncserver-env.path;
settings.port = service_configs.ports.private.firefox_syncserver.port;
singleNode = {
enable = true;
hostname = service_configs.firefox_syncserver.domain;
url = "https://${service_configs.firefox_syncserver.domain}";
capacity = 1;
};
};
services.postgresql = {
ensureDatabases = [ "firefox_syncserver" ];
ensureUsers = [
{
name = "firefox_syncserver";
ensureDBOwnership = true;
}
];
};
services.caddy.virtualHosts."${service_configs.firefox_syncserver.domain}".extraConfig = ''
reverse_proxy :${builtins.toString service_configs.ports.private.firefox_syncserver.port}
'';
}

View File

@@ -27,7 +27,7 @@
SSH_USER = "gitea"; SSH_USER = "gitea";
DOMAIN = service_configs.gitea.domain; DOMAIN = service_configs.gitea.domain;
ROOT_URL = "https://" + config.services.gitea.settings.server.DOMAIN; ROOT_URL = "https://" + config.services.gitea.settings.server.DOMAIN;
HTTP_PORT = service_configs.ports.gitea; HTTP_PORT = service_configs.ports.private.gitea.port;
LANDING_PAGE = "/explore/repos"; LANDING_PAGE = "/explore/repos";
DISABLE_HTTP_GIT = true; DISABLE_HTTP_GIT = true;
}; };

View File

@@ -21,7 +21,7 @@
services.immich = { services.immich = {
enable = true; enable = true;
mediaLocation = service_configs.immich.dir; mediaLocation = service_configs.immich.dir;
port = service_configs.ports.immich; port = service_configs.ports.private.immich.port;
# openFirewall = true; # openFirewall = true;
host = "0.0.0.0"; host = "0.0.0.0";
database = { database = {

View File

@@ -43,8 +43,8 @@
}; };
environment = { environment = {
JELLYFIN_URL = "http://localhost:${builtins.toString service_configs.ports.jellyfin}"; JELLYFIN_URL = "http://localhost:${builtins.toString service_configs.ports.private.jellyfin.port}";
QBITTORRENT_URL = "http://${config.vpnNamespaces.wg.namespaceAddress}:${builtins.toString service_configs.ports.torrent}"; QBITTORRENT_URL = "http://${config.vpnNamespaces.wg.namespaceAddress}:${builtins.toString service_configs.ports.private.torrent.port}";
CHECK_INTERVAL = "30"; CHECK_INTERVAL = "30";
# Bandwidth budget configuration # Bandwidth budget configuration
TOTAL_BANDWIDTH_BUDGET = "30000000"; # 30 Mbps in bits per second TOTAL_BANDWIDTH_BUDGET = "30000000"; # 30 Mbps in bits per second

View File

@@ -25,7 +25,7 @@
}; };
services.caddy.virtualHosts."jellyfin.${service_configs.https.domain}".extraConfig = '' services.caddy.virtualHosts."jellyfin.${service_configs.https.domain}".extraConfig = ''
reverse_proxy :${builtins.toString service_configs.ports.jellyfin} { reverse_proxy :${builtins.toString service_configs.ports.private.jellyfin.port} {
header_up X-Real-IP {remote_host} header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host} header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme} header_up X-Forwarded-Proto {scheme}

View File

@@ -4,8 +4,6 @@
}: }:
let let
keyFile = ../secrets/livekit_keys; keyFile = ../secrets/livekit_keys;
ports = service_configs.ports;
in in
{ {
services.livekit = { services.livekit = {
@@ -14,7 +12,7 @@ in
openFirewall = true; openFirewall = true;
settings = { settings = {
port = ports.livekit; port = service_configs.ports.public.livekit.port;
bind_addresses = [ "127.0.0.1" ]; bind_addresses = [ "127.0.0.1" ];
rtc = { rtc = {
@@ -38,16 +36,16 @@ in
enable = true; enable = true;
inherit keyFile; inherit keyFile;
livekitUrl = "wss://${service_configs.livekit.domain}"; livekitUrl = "wss://${service_configs.livekit.domain}";
port = ports.lk_jwt; port = service_configs.ports.private.lk_jwt.port;
}; };
services.caddy.virtualHosts."${service_configs.livekit.domain}".extraConfig = '' services.caddy.virtualHosts."${service_configs.livekit.domain}".extraConfig = ''
@jwt path /sfu/get /healthz @jwt path /sfu/get /healthz
handle @jwt { handle @jwt {
reverse_proxy :${builtins.toString ports.lk_jwt} reverse_proxy :${builtins.toString service_configs.ports.private.lk_jwt.port}
} }
handle { handle {
reverse_proxy :${builtins.toString ports.livekit} reverse_proxy :${builtins.toString service_configs.ports.public.livekit.port}
} }
''; '';
} }

View File

@@ -18,7 +18,7 @@
enable = true; enable = true;
settings.global = { settings.global = {
port = [ service_configs.ports.matrix ]; port = [ service_configs.ports.private.matrix.port ];
server_name = service_configs.https.domain; server_name = service_configs.https.domain;
allow_registration = true; allow_registration = true;
registration_token = lib.strings.trim (builtins.readFile ../secrets/matrix_reg_token); registration_token = lib.strings.trim (builtins.readFile ../secrets/matrix_reg_token);
@@ -49,25 +49,25 @@
services.caddy.virtualHosts.${service_configs.https.domain}.extraConfig = lib.mkBefore '' services.caddy.virtualHosts.${service_configs.https.domain}.extraConfig = lib.mkBefore ''
header /.well-known/matrix/* Content-Type application/json header /.well-known/matrix/* Content-Type application/json
header /.well-known/matrix/* Access-Control-Allow-Origin * header /.well-known/matrix/* Access-Control-Allow-Origin *
respond /.well-known/matrix/server `{"m.server": "${service_configs.matrix.domain}:${builtins.toString service_configs.ports.https}"}` respond /.well-known/matrix/server `{"m.server": "${service_configs.matrix.domain}:${builtins.toString service_configs.ports.public.https.port}"}`
respond /.well-known/matrix/client `{"m.server":{"base_url":"https://${service_configs.matrix.domain}"},"m.homeserver":{"base_url":"https://${service_configs.matrix.domain}"},"org.matrix.msc3575.proxy":{"base_url":"https://${config.services.matrix-continuwuity.settings.global.server_name}"},"org.matrix.msc4143.rtc_foci":[{"type":"livekit","livekit_service_url":"https://${service_configs.livekit.domain}"}]}` respond /.well-known/matrix/client `{"m.server":{"base_url":"https://${service_configs.matrix.domain}"},"m.homeserver":{"base_url":"https://${service_configs.matrix.domain}"},"org.matrix.msc3575.proxy":{"base_url":"https://${config.services.matrix-continuwuity.settings.global.server_name}"},"org.matrix.msc4143.rtc_foci":[{"type":"livekit","livekit_service_url":"https://${service_configs.livekit.domain}"}]}`
''; '';
services.caddy.virtualHosts."${service_configs.matrix.domain}".extraConfig = '' services.caddy.virtualHosts."${service_configs.matrix.domain}".extraConfig = ''
reverse_proxy :${builtins.toString service_configs.ports.matrix} reverse_proxy :${builtins.toString service_configs.ports.private.matrix.port}
''; '';
# Exact duplicate for federation port # Exact duplicate for federation port
services.caddy.virtualHosts."${service_configs.matrix.domain}:${builtins.toString service_configs.ports.matrix_federation}".extraConfig = services.caddy.virtualHosts."${service_configs.matrix.domain}:${builtins.toString service_configs.ports.public.matrix_federation.port}".extraConfig =
config.services.caddy.virtualHosts."${service_configs.matrix.domain}".extraConfig; config.services.caddy.virtualHosts."${service_configs.matrix.domain}".extraConfig;
# for federation # for federation
networking.firewall.allowedTCPPorts = [ networking.firewall.allowedTCPPorts = [
service_configs.ports.matrix_federation service_configs.ports.public.matrix_federation.port
]; ];
# for federation # for federation
networking.firewall.allowedUDPPorts = [ networking.firewall.allowedUDPPorts = [
service_configs.ports.matrix_federation service_configs.ports.public.matrix_federation.port
]; ];
} }

View File

@@ -73,7 +73,7 @@
]; ];
serverProperties = { serverProperties = {
server-port = service_configs.ports.minecraft; server-port = service_configs.ports.public.minecraft.port;
enforce-whitelist = true; enforce-whitelist = true;
gamemode = "survival"; gamemode = "survival";
white-list = true; white-list = true;

View File

@@ -18,12 +18,12 @@
dataDir = service_configs.monero.dataDir; dataDir = service_configs.monero.dataDir;
rpc = { rpc = {
address = "0.0.0.0"; address = "0.0.0.0";
port = service_configs.ports.monero_rpc; port = service_configs.ports.public.monero_rpc.port;
restricted = true; restricted = true;
}; };
extraConfig = '' extraConfig = ''
p2p-bind-port=${builtins.toString service_configs.ports.monero} p2p-bind-port=${builtins.toString service_configs.ports.public.monero.port}
zmq-pub=tcp://127.0.0.1:${builtins.toString service_configs.ports.monero_zmq} zmq-pub=tcp://127.0.0.1:${builtins.toString service_configs.ports.private.monero_zmq.port}
db-sync-mode=fast:async:1000000000bytes db-sync-mode=fast:async:1000000000bytes
public-node=1 public-node=1
confirm-external-bind=1 confirm-external-bind=1
@@ -31,7 +31,7 @@
}; };
networking.firewall.allowedTCPPorts = [ networking.firewall.allowedTCPPorts = [
service_configs.ports.monero service_configs.ports.public.monero.port
service_configs.ports.monero_rpc service_configs.ports.public.monero_rpc.port
]; ];
} }

View File

@@ -19,7 +19,7 @@
settings = { settings = {
base-url = "https://${service_configs.ntfy.domain}"; base-url = "https://${service_configs.ntfy.domain}";
listen-http = "127.0.0.1:${builtins.toString service_configs.ports.ntfy}"; listen-http = "127.0.0.1:${builtins.toString service_configs.ports.private.ntfy.port}";
behind-proxy = true; behind-proxy = true;
auth-default-access = "deny-all"; auth-default-access = "deny-all";
enable-login = true; enable-login = true;
@@ -28,7 +28,7 @@
}; };
services.caddy.virtualHosts."${service_configs.ntfy.domain}".extraConfig = '' services.caddy.virtualHosts."${service_configs.ntfy.domain}".extraConfig = ''
reverse_proxy :${builtins.toString service_configs.ports.ntfy} reverse_proxy :${builtins.toString service_configs.ports.private.ntfy.port}
''; '';
} }

View File

@@ -23,10 +23,10 @@ in
walletAddress = walletAddress; walletAddress = walletAddress;
sidechain = "nano"; sidechain = "nano";
host = "127.0.0.1"; host = "127.0.0.1";
rpcPort = service_configs.ports.monero_rpc; rpcPort = service_configs.ports.public.monero_rpc.port;
zmqPort = service_configs.ports.monero_zmq; zmqPort = service_configs.ports.private.monero_zmq.port;
extraArgs = [ extraArgs = [
" --stratum 0.0.0.0:${builtins.toString service_configs.ports.p2pool_stratum}" " --stratum 0.0.0.0:${builtins.toString service_configs.ports.private.p2pool_stratum.port}"
]; ];
}; };
@@ -43,6 +43,6 @@ in
}; };
networking.firewall.allowedTCPPorts = [ networking.firewall.allowedTCPPorts = [
service_configs.ports.p2pool_p2p service_configs.ports.public.p2pool_p2p.port
]; ];
} }

View File

@@ -5,6 +5,30 @@
lib, lib,
... ...
}: }:
let
pgCheckpoint = pkgs.writeShellScript "pg-checkpoint" ''
# Flush PostgreSQL dirty buffers to disk before ZFS snapshot so the
# on-disk state is consistent and the snapshot is recoverable.
# On failure: log a warning but exit 0 so sanoid still takes the
# snapshot (an inconsistent snapshot beats no snapshot).
if ! ${pkgs.systemd}/bin/systemctl is-active --quiet postgresql.service; then
echo "postgresql is not running, skipping checkpoint" >&2
exit 0
fi
if ${pkgs.coreutils}/bin/timeout 120 \
${pkgs.util-linux}/bin/runuser -u postgres -- \
${lib.getExe' config.services.postgresql.package "psql"} \
-v ON_ERROR_STOP=1 -c "CHECKPOINT" 2>&1; then
echo "postgresql checkpoint completed"
else
echo "WARNING: postgresql checkpoint failed, snapshot may be inconsistent" >&2
fi
# Always exit 0 sanoid must run regardless
exit 0
'';
in
{ {
imports = [ imports = [
(lib.serviceMountWithZpool "postgresql" service_configs.zpool_ssds [ (lib.serviceMountWithZpool "postgresql" service_configs.zpool_ssds [
@@ -29,4 +53,10 @@
}; };
}; };
# Run a PostgreSQL CHECKPOINT before sanoid snapshots so the on-disk
# state is consistent (required since full_page_writes = false).
systemd.services.sanoid.serviceConfig = {
ExecStartPre = lib.mkAfter [ "+${pgCheckpoint}" ];
TimeoutStartSec = lib.mkForce 300; # checkpoint can be slow with large txg_timeout
};
} }

View File

@@ -26,7 +26,7 @@
services.qbittorrent = { services.qbittorrent = {
enable = true; enable = true;
webuiPort = service_configs.ports.torrent; webuiPort = service_configs.ports.private.torrent.port;
profileDir = "/var/lib/qBittorrent"; profileDir = "/var/lib/qBittorrent";
# Set the service group to 'media' so the systemd unit runs with media as # Set the service group to 'media' so the systemd unit runs with media as
# the primary GID. Linux assigns new file ownership from the process's GID # the primary GID. Linux assigns new file ownership from the process's GID

View File

@@ -30,11 +30,11 @@
settings = { settings = {
web = { web = {
port = service_configs.ports.soulseek_web; port = service_configs.ports.private.soulseek_web.port;
}; };
soulseek = { soulseek = {
# description = "smth idk"; # description = "smth idk";
listen_port = service_configs.ports.soulseek_listen; listen_port = service_configs.ports.public.soulseek_listen.port;
}; };
shares = { shares = {
@@ -64,6 +64,6 @@
''; '';
networking.firewall.allowedTCPPorts = [ networking.firewall.allowedTCPPorts = [
service_configs.ports.soulseek_listen service_configs.ports.public.soulseek_listen.port
]; ];
} }

View File

@@ -24,7 +24,7 @@
dataDir = service_configs.syncthing.dataDir; dataDir = service_configs.syncthing.dataDir;
guiAddress = "127.0.0.1:${toString service_configs.ports.syncthing_gui}"; guiAddress = "127.0.0.1:${toString service_configs.ports.private.syncthing_gui.port}";
overrideDevices = false; overrideDevices = false;
overrideFolders = false; overrideFolders = false;
@@ -42,16 +42,16 @@
# Open firewall ports for syncthing protocol # Open firewall ports for syncthing protocol
networking.firewall = { networking.firewall = {
allowedTCPPorts = [ service_configs.ports.syncthing_protocol ]; allowedTCPPorts = [ service_configs.ports.public.syncthing_protocol.port ];
allowedUDPPorts = [ allowedUDPPorts = [
service_configs.ports.syncthing_discovery service_configs.ports.public.syncthing_discovery.port
service_configs.ports.syncthing_protocol service_configs.ports.public.syncthing_protocol.port
]; ];
}; };
services.caddy.virtualHosts."syncthing.${service_configs.https.domain}".extraConfig = '' services.caddy.virtualHosts."syncthing.${service_configs.https.domain}".extraConfig = ''
import ${config.age.secrets.caddy_auth.path} import ${config.age.secrets.caddy_auth.path}
reverse_proxy :${toString service_configs.ports.syncthing_gui} reverse_proxy :${toString service_configs.ports.private.syncthing_gui.port}
''; '';
} }

View File

@@ -32,7 +32,7 @@ in
pools = [ pools = [
{ {
url = "127.0.0.1:${builtins.toString service_configs.ports.p2pool_stratum}"; url = "127.0.0.1:${builtins.toString service_configs.ports.private.p2pool_stratum.port}";
tls = false; tls = false;
} }
]; ];

View File

@@ -12,7 +12,10 @@ let
dir = "/var/lib/gitea"; dir = "/var/lib/gitea";
domain = "git.test.local"; domain = "git.test.local";
}; };
ports.gitea = 3000; ports.private.gitea = {
port = 3000;
proto = "tcp";
};
}; };
testLib = lib.extend ( testLib = lib.extend (

View File

@@ -9,7 +9,10 @@ let
testServiceConfigs = lib.recursiveUpdate baseServiceConfigs { testServiceConfigs = lib.recursiveUpdate baseServiceConfigs {
zpool_ssds = ""; zpool_ssds = "";
https.domain = "test.local"; https.domain = "test.local";
ports.immich = 2283; ports.private.immich = {
port = 2283;
proto = "tcp";
};
immich.dir = "/var/lib/immich"; immich.dir = "/var/lib/immich";
}; };