Redid networking

This commit is contained in:
Aner Zakobar
2026-04-26 00:09:52 +03:00
parent a7099e7d56
commit d49f0161ca
14 changed files with 114 additions and 71 deletions
+30 -13
View File
@@ -51,18 +51,34 @@ All services live under `zakobar.com`.
| Jellyfin | `jellyfin.zakobar.com` | Jellyfin-native | | Jellyfin | `jellyfin.zakobar.com` | Jellyfin-native |
| Transmission | `torrent.zakobar.com` | Authelia two_factor, admins only | | Transmission | `torrent.zakobar.com` | Authelia two_factor, admins only |
Internal ports (all bound to `127.0.0.1`): ## Networking
| Container | Port | All containers join a private podman network named **`homey`**, created by the
|-----------|------| `podman-homey-network` systemd service in `common.nix`. This provides:
| openldap | 389 |
| authelia | 9091 | - **DNS isolation** — containers reach each other by name (e.g. `openldap`,
| gitea | 3000 | `nextcloud-postgres`) without being exposed on the host network.
| nextcloud | 8080 | - **No port conflicts** — Caddy owns host ports 80/443; service containers map
| nextcloud-postgres | 5432 | only to `127.0.0.1:<port>`.
| phpldapadmin | 8081 | - **Defence in depth** — even if the firewall were misconfigured, services are
| jellyfin | 8096 | not bound to `0.0.0.0`.
| transmission | 9092 (not 9091 — avoids clash with authelia) |
Internal ports (all mapped to `127.0.0.1` on the host):
| Container | Host port | Container port |
|-----------|-----------|----------------|
| openldap | 389 | 389 |
| authelia | 9091 | 9091 |
| gitea | 3000 | 3000 |
| nextcloud | 8080 | 80 |
| nextcloud-postgres | 5432 | 5432 |
| phpldapadmin | 8081 | 80 |
| jellyfin | 8096 | 8096 |
| transmission | 9092 | 9091 |
Inter-container communication uses container names on the `homey` network
(e.g. authelia → `ldap://openldap:389`, nextcloud → `nextcloud-postgres:5432`).
Caddy (running on the host) proxies via `127.0.0.1:<host port>`.
## Storage Layout ## Storage Layout
@@ -163,8 +179,9 @@ restic password, Cloudflare tokens) can be generated fresh.
file at `/run/<service>-secrets.env` and reference it via `EnvironmentFile`. file at `/run/<service>-secrets.env` and reference it via `EnvironmentFile`.
Clean it up in `postStop`. Clean it up in `postStop`.
5. **`--network=host`** — all containers use host networking for simplicity on 5. **`--network=homey`** — all containers join the private `homey` podman
a single-node setup. Services communicate via `127.0.0.1:<port>`. network. Inter-container traffic uses container names as hostnames; host
access is via explicit `ports` mappings to `127.0.0.1:<port>`.
6. **Systemd ordering** — always express `after`/`requires` dependencies 6. **Systemd ordering** — always express `after`/`requires` dependencies
explicitly. The external HD mount unit is `mnt-data.mount`; containers that explicitly. The external HD mount unit is `mnt-data.mount`; containers that
+4 -2
View File
@@ -198,7 +198,8 @@
** DONE Configure Gitea LDAP authentication ** DONE Configure Gitea LDAP authentication
Admin → Site Administration → Authentication Sources → Add LDAP (via BindDN): Admin → Site Administration → Authentication Sources → Add LDAP (via BindDN):
- Host: =127.0.0.1=, Port: =389=, Security: Unencrypted - Host: =openldap=, Port: =389=, Security: Unencrypted
(containers talk via the =homey= podman network — use container name, not =127.0.0.1=)
- Bind DN: =cn=readonly,dc=zakobar,dc=com= - Bind DN: =cn=readonly,dc=zakobar,dc=com=
- Bind Password: see =openldap/ro_password= in sops - Bind Password: see =openldap/ro_password= in sops
- User Search Base: =ou=users,dc=zakobar,dc=com= - User Search Base: =ou=users,dc=zakobar,dc=com=
@@ -213,7 +214,8 @@
Admin → LDAP/AD Integration — confirm the LDAP Users and Contacts app is configured. Admin → LDAP/AD Integration — confirm the LDAP Users and Contacts app is configured.
If reconfiguring from scratch, use the same settings as Gitea above but with If reconfiguring from scratch, use the same settings as Gitea above but with
Nextcloud's LDAP wizard: Nextcloud's LDAP wizard:
- Server: =127.0.0.1=, Port: =389= - Server: =openldap=, Port: =389=
(container name on the =homey= network — not =127.0.0.1=)
- Bind DN: =cn=readonly,dc=zakobar,dc=com= - Bind DN: =cn=readonly,dc=zakobar,dc=com=
- Bind Password: see =openldap/ro_password= in sops - Bind Password: see =openldap/ro_password= in sops
- Base DN: =dc=zakobar,dc=com= - Base DN: =dc=zakobar,dc=com=
+4 -3
View File
@@ -87,16 +87,17 @@ in
Type = "oneshot"; Type = "oneshot";
ExecStart = pkgs.writeShellScript "backup-pre" '' ExecStart = pkgs.writeShellScript "backup-pre" ''
set -euo pipefail set -euo pipefail
podman="${pkgs.podman}/bin/podman"
# Put Nextcloud into maintenance mode (if running) # Put Nextcloud into maintenance mode (if running)
if systemctl is-active --quiet podman-nextcloud.service; then if systemctl is-active --quiet podman-nextcloud.service; then
podman exec nextcloud php occ maintenance:mode --on || true $podman exec nextcloud php occ maintenance:mode --on || true
fi fi
# Dump postgres (if running) # Dump postgres (if running)
if systemctl is-active --quiet podman-nextcloud-postgres.service; then if systemctl is-active --quiet podman-nextcloud-postgres.service; then
install -d -m 700 ${dataDir}/nextcloud/db-dump install -d -m 700 ${dataDir}/nextcloud/db-dump
podman exec nextcloud-postgres \ $podman exec nextcloud-postgres \
pg_dump -U postgres nextcloud_db \ pg_dump -U postgres nextcloud_db \
> ${dataDir}/nextcloud/db-dump/nextcloud.sql > ${dataDir}/nextcloud/db-dump/nextcloud.sql
fi fi
@@ -111,7 +112,7 @@ in
ExecStart = pkgs.writeShellScript "backup-post" '' ExecStart = pkgs.writeShellScript "backup-post" ''
set -euo pipefail set -euo pipefail
if systemctl is-active --quiet podman-nextcloud.service; then if systemctl is-active --quiet podman-nextcloud.service; then
podman exec nextcloud php occ maintenance:mode --off || true ${pkgs.podman}/bin/podman exec nextcloud php occ maintenance:mode --off || true
fi fi
''; '';
}; };
+4 -1
View File
@@ -142,7 +142,9 @@ in
max_size 5GB max_size 5GB
} }
reverse_proxy localhost:8080 reverse_proxy localhost:8080 {
header_up X-Forwarded-For {remote_host}
}
''; '';
}; };
"http://nextcloud.${domain}" = { "http://nextcloud.${domain}" = {
@@ -154,6 +156,7 @@ in
} }
reverse_proxy localhost:8080 { reverse_proxy localhost:8080 {
header_up X-Forwarded-Proto https header_up X-Forwarded-Proto https
header_up X-Forwarded-For {remote_host}
} }
''; '';
}; };
+20
View File
@@ -80,6 +80,26 @@
defaultNetwork.settings.dns_enabled = true; defaultNetwork.settings.dns_enabled = true;
}; };
# Create the shared "homey" podman network that all service containers join.
# DNS is enabled by default on netavark-backed networks, so containers can
# reach each other by container name (e.g. "openldap", "nextcloud-postgres").
systemd.services.podman-homey-network = {
description = "Create homey podman network";
wantedBy = [ "multi-user.target" ];
before = [ "podman-openldap.service" "podman-authelia.service"
"podman-gitea.service" "podman-nextcloud-postgres.service"
"podman-nextcloud.service" "podman-phpldapadmin.service"
"podman-jellyfin.service" "podman-transmission.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = pkgs.writeShellScript "create-homey-network" ''
${pkgs.podman}/bin/podman network exists homey \
|| ${pkgs.podman}/bin/podman network create homey
'';
};
};
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# Core packages available on every host # Core packages available on every host
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
+5 -5
View File
@@ -43,7 +43,7 @@ let
authentication_backend: authentication_backend:
ldap: ldap:
implementation: "custom" implementation: "custom"
url: "ldap://127.0.0.1:389" url: "ldap://openldap:389"
timeout: "5s" timeout: "5s"
start_tls: false start_tls: false
base_dn: "${ldapBaseDN}" base_dn: "${ldapBaseDN}"
@@ -162,7 +162,7 @@ in
virtualisation.oci-containers.containers.authelia = { virtualisation.oci-containers.containers.authelia = {
image = cfg.image; image = cfg.image;
# No ports mapping — --network=host shares the host network stack directly. ports = [ "127.0.0.1:${toString cfg.port}:9091" ];
environment = { environment = {
TZ = homeyConfig.timezone; TZ = homeyConfig.timezone;
@@ -184,7 +184,7 @@ in
]; ];
extraOptions = [ extraOptions = [
"--network=host" "--network=homey"
"--hostname=authelia" "--hostname=authelia"
]; ];
}; };
@@ -193,8 +193,8 @@ in
# Systemd — wait for openldap and external HD # Systemd — wait for openldap and external HD
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
systemd.services."podman-authelia" = { systemd.services."podman-authelia" = {
after = lib.mkAfter [ "mnt-data.mount" "podman-openldap.service" ]; after = lib.mkAfter [ "mnt-data.mount" "podman-openldap.service" "podman-homey-network.service" ];
requires = lib.mkAfter [ "mnt-data.mount" "podman-openldap.service" ]; requires = lib.mkAfter [ "mnt-data.mount" "podman-openldap.service" "podman-homey-network.service" ];
}; };
}; };
} }
+4 -5
View File
@@ -60,8 +60,7 @@ in
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
virtualisation.oci-containers.containers.gitea = { virtualisation.oci-containers.containers.gitea = {
image = cfg.image; image = cfg.image;
# No ports mapping — --network=host means the container shares the host ports = [ "127.0.0.1:${toString cfg.port}:3000" ];
# network stack directly. Gitea binds to 0.0.0.0:3000 on the host.
# All non-secret settings via GITEA__<SECTION>__<KEY> env vars. # All non-secret settings via GITEA__<SECTION>__<KEY> env vars.
# These are safe to store in the Nix store. # These are safe to store in the Nix store.
@@ -153,7 +152,7 @@ in
"${dataDir}/gitea/data:/data" "${dataDir}/gitea/data:/data"
]; ];
extraOptions = [ "--network=host" ]; extraOptions = [ "--network=homey" ];
}; };
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
@@ -182,8 +181,8 @@ in
'') '')
]; ];
}; };
after = lib.mkAfter [ "mnt-data.mount" "podman-openldap.service" ]; after = lib.mkAfter [ "mnt-data.mount" "podman-openldap.service" "podman-homey-network.service" ];
requires = lib.mkAfter [ "mnt-data.mount" ]; requires = lib.mkAfter [ "mnt-data.mount" "podman-homey-network.service" ];
}; };
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
+4 -4
View File
@@ -30,7 +30,7 @@ in
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
virtualisation.oci-containers.containers.jellyfin = { virtualisation.oci-containers.containers.jellyfin = {
image = cfg.image; image = cfg.image;
# No ports mapping — --network=host shares the host network stack directly. ports = [ "127.0.0.1:${toString cfg.port}:8096" ];
environment = { environment = {
JELLYFIN_PublishedServerUrl = "https://jellyfin.${domain}"; JELLYFIN_PublishedServerUrl = "https://jellyfin.${domain}";
@@ -44,12 +44,12 @@ in
"${dataDir}/media/tvshows:/data/tvshows:ro" "${dataDir}/media/tvshows:/data/tvshows:ro"
]; ];
extraOptions = [ "--network=host" ]; extraOptions = [ "--network=homey" ];
}; };
systemd.services."podman-jellyfin" = { systemd.services."podman-jellyfin" = {
after = lib.mkAfter [ "mnt-data.mount" ]; after = lib.mkAfter [ "mnt-data.mount" "podman-homey-network.service" ];
requires = lib.mkAfter [ "mnt-data.mount" ]; requires = lib.mkAfter [ "mnt-data.mount" "podman-homey-network.service" ];
}; };
}; };
} }
+17 -13
View File
@@ -58,7 +58,9 @@ in
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
virtualisation.oci-containers.containers.nextcloud-postgres = { virtualisation.oci-containers.containers.nextcloud-postgres = {
image = cfg.postgresImage; image = cfg.postgresImage;
# No ports mapping — --network=host shares the host network stack directly. # Exposed on localhost for debugging; nextcloud reaches it via the
# container name "nextcloud-postgres" on the homey network.
ports = [ "127.0.0.1:${toString cfg.postgresPort}:5432" ];
environment = { environment = {
POSTGRES_DB = "nextcloud_db"; POSTGRES_DB = "nextcloud_db";
@@ -71,7 +73,7 @@ in
]; ];
extraOptions = [ extraOptions = [
"--network=host" "--network=homey"
"--env-file=/run/nc-postgres-secrets.env" "--env-file=/run/nc-postgres-secrets.env"
]; ];
}; };
@@ -91,8 +93,8 @@ in
]; ];
}; };
postStop = "rm -f /run/nc-postgres-secrets.env"; postStop = "rm -f /run/nc-postgres-secrets.env";
after = lib.mkAfter [ "mnt-data.mount" ]; after = lib.mkAfter [ "mnt-data.mount" "podman-homey-network.service" ];
requires = lib.mkAfter [ "mnt-data.mount" ]; requires = lib.mkAfter [ "mnt-data.mount" "podman-homey-network.service" ];
}; };
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
@@ -100,20 +102,22 @@ in
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
virtualisation.oci-containers.containers.nextcloud = { virtualisation.oci-containers.containers.nextcloud = {
image = cfg.image; image = cfg.image;
# No ports mapping — --network=host shares the host network stack directly. # Apache inside the container listens on port 80; map it to cfg.port on
# the host so Caddy can reach it. Postgres is reachable by container name.
ports = [ "127.0.0.1:${toString cfg.port}:80" ];
environment = { environment = {
POSTGRES_HOST = "127.0.0.1"; POSTGRES_HOST = "nextcloud-postgres";
POSTGRES_DB = "nextcloud_db"; POSTGRES_DB = "nextcloud_db";
POSTGRES_USER = "postgres"; POSTGRES_USER = "postgres";
NEXTCLOUD_ADMIN_USER = "admin"; NEXTCLOUD_ADMIN_USER = "admin";
NEXTCLOUD_TRUSTED_DOMAINS = "nextcloud.${domain}"; NEXTCLOUD_TRUSTED_DOMAINS = "nextcloud.${domain}";
OVERWRITEPROTOCOL = "https"; OVERWRITEPROTOCOL = "https";
OVERWRITECLIURL = "https://nextcloud.${domain}"; OVERWRITECLIURL = "https://nextcloud.${domain}";
# With --network=host, port mappings are ignored and the container's OVERWRITEHOST = "nextcloud.${domain}";
# Apache binds directly on the host. Force it onto port 8080 so Caddy # Trust the reverse proxy (Caddy on the host reaches the container
# can own 80/443. # via the podman bridge; cover all RFC-1918 ranges to be robust).
APACHE_HTTP_PORT_NUMBER = toString cfg.port; TRUSTED_PROXIES = "10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 127.0.0.1 ::1";
# Passwords injected via env file # Passwords injected via env file
}; };
@@ -122,7 +126,7 @@ in
]; ];
extraOptions = [ extraOptions = [
"--network=host" "--network=homey"
"--env-file=/run/nc-secrets.env" "--env-file=/run/nc-secrets.env"
]; ];
}; };
@@ -143,8 +147,8 @@ in
]; ];
}; };
postStop = "rm -f /run/nc-secrets.env"; postStop = "rm -f /run/nc-secrets.env";
after = lib.mkAfter [ "mnt-data.mount" "podman-nextcloud-postgres.service" ]; after = lib.mkAfter [ "mnt-data.mount" "podman-nextcloud-postgres.service" "podman-homey-network.service" ];
requires = lib.mkAfter [ "mnt-data.mount" "podman-nextcloud-postgres.service" ]; requires = lib.mkAfter [ "mnt-data.mount" "podman-nextcloud-postgres.service" "podman-homey-network.service" ];
}; };
}; };
} }
+4 -7
View File
@@ -50,10 +50,7 @@ in
virtualisation.oci-containers.containers.openldap = { virtualisation.oci-containers.containers.openldap = {
image = cfg.image; image = cfg.image;
# No ports mapping — --network=host means the container shares the host ports = [ "127.0.0.1:${toString cfg.port}:389" ];
# network stack. OpenLDAP binds to 0.0.0.0:389, but the firewall
# (common.nix) only opens 22/80/443, so port 389 is unreachable from
# the LAN or internet.
environment = { environment = {
LDAP_ORGANISATION = homeyConfig.organization; LDAP_ORGANISATION = homeyConfig.organization;
@@ -78,7 +75,7 @@ in
]; ];
extraOptions = [ extraOptions = [
"--network=host" "--network=homey"
"--env-file=/run/openldap-secrets.env" "--env-file=/run/openldap-secrets.env"
]; ];
}; };
@@ -113,8 +110,8 @@ in
# Clean up the env file on stop # Clean up the env file on stop
postStop = "rm -f /run/openldap-secrets.env"; postStop = "rm -f /run/openldap-secrets.env";
# Wait for the external HD to be mounted before starting # Wait for the external HD to be mounted before starting
after = lib.mkAfter [ "mnt-data.mount" ]; after = lib.mkAfter [ "mnt-data.mount" "podman-homey-network.service" ];
requires = lib.mkAfter [ "mnt-data.mount" ]; requires = lib.mkAfter [ "mnt-data.mount" "podman-homey-network.service" ];
}; };
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
+6 -7
View File
@@ -36,19 +36,18 @@ in
environment = { environment = {
PHPLDAPADMIN_HTTPS = "false"; PHPLDAPADMIN_HTTPS = "false";
# host.containers.internal resolves to the host from inside a podman # "openldap" resolves to the OpenLDAP container via homey network DNS.
# bridge container — reaches openldap which is on --network=host at :389 PHPLDAPADMIN_LDAP_HOSTS = "openldap";
PHPLDAPADMIN_LDAP_HOSTS = "host.containers.internal";
}; };
# Bridge network (default) + port mapping: Apache binds inside the
# container on :80, podman maps it to 127.0.0.1:8081 on the host.
ports = [ "127.0.0.1:${toString cfg.port}:80" ]; ports = [ "127.0.0.1:${toString cfg.port}:80" ];
extraOptions = [ "--network=homey" ];
}; };
systemd.services."podman-phpldapadmin" = { systemd.services."podman-phpldapadmin" = {
after = lib.mkAfter [ "podman-openldap.service" ]; after = lib.mkAfter [ "podman-openldap.service" "podman-homey-network.service" ];
wants = lib.mkAfter [ "podman-openldap.service" ]; wants = lib.mkAfter [ "podman-openldap.service" "podman-homey-network.service" ];
}; };
}; };
} }
+6 -8
View File
@@ -35,16 +35,14 @@ in
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
virtualisation.oci-containers.containers.transmission = { virtualisation.oci-containers.containers.transmission = {
image = cfg.image; image = cfg.image;
# No ports mapping — --network=host shares the host network stack directly. # Map host cfg.port (9092) → container 9091 so Caddy can reach it
# without conflicting with Authelia's host port (also 9091).
ports = [ "127.0.0.1:${toString cfg.port}:9091" ];
environment = { environment = {
PUID = "1000"; PUID = "1000";
PGID = "1000"; PGID = "1000";
# With --network=host, port mappings are ignored; transmission binds
# directly on the host. Force it to cfg.port (9092) to avoid
# conflicting with Authelia on 9091.
TRANSMISSION_WEB_HOME = "/usr/share/transmission/web"; TRANSMISSION_WEB_HOME = "/usr/share/transmission/web";
WEBUI_PORT = toString cfg.port;
}; };
volumes = [ volumes = [
@@ -55,12 +53,12 @@ in
"${dataDir}/media/complete:/downloads/complete" "${dataDir}/media/complete:/downloads/complete"
]; ];
extraOptions = [ "--network=host" ]; extraOptions = [ "--network=homey" ];
}; };
systemd.services."podman-transmission" = { systemd.services."podman-transmission" = {
after = lib.mkAfter [ "mnt-data.mount" ]; after = lib.mkAfter [ "mnt-data.mount" "podman-homey-network.service" ];
requires = lib.mkAfter [ "mnt-data.mount" ]; requires = lib.mkAfter [ "mnt-data.mount" "podman-homey-network.service" ];
}; };
}; };
} }
+5 -2
View File
@@ -88,8 +88,11 @@ in
"d ${cfg.mountPoint}/gitea 0750 1000 1000 -" "d ${cfg.mountPoint}/gitea 0750 1000 1000 -"
"d ${cfg.mountPoint}/gitea/data 0750 1000 1000 -" "d ${cfg.mountPoint}/gitea/data 0750 1000 1000 -"
"d ${cfg.mountPoint}/nextcloud 0750 root root -" "d ${cfg.mountPoint}/nextcloud 0750 root root -"
"d ${cfg.mountPoint}/nextcloud/html 0750 root root -" # www-data in the Nextcloud container is UID 33; it needs rx on the
"d ${cfg.mountPoint}/nextcloud/db 0750 root root -" # directory and rw on all files it creates inside.
"d ${cfg.mountPoint}/nextcloud/html 0750 33 33 -"
# Postgres (uid 999) must own this directory — it creates files directly in it
"d ${cfg.mountPoint}/nextcloud/db 0700 999 999 -"
"d ${cfg.mountPoint}/jellyfin 0750 root root -" "d ${cfg.mountPoint}/jellyfin 0750 root root -"
"d ${cfg.mountPoint}/jellyfin/config 0750 root root -" "d ${cfg.mountPoint}/jellyfin/config 0750 root root -"
"d ${cfg.mountPoint}/media 0755 root root -" "d ${cfg.mountPoint}/media 0755 root root -"
+1 -1
View File
@@ -7,7 +7,7 @@ pkgs.mkShell {
--flake .#pi-main \ --flake .#pi-main \
--target-host admin@192.168.1.100 \ --target-host admin@192.168.1.100 \
--build-host admin@192.168.1.100 \ --build-host admin@192.168.1.100 \
--sudo --use-remote-sudo
'') '')
(pkgs.writeShellScriptBin "homey-build-rpi-main" '' (pkgs.writeShellScriptBin "homey-build-rpi-main" ''
sudo nixos-rebuild switch \ sudo nixos-rebuild switch \