···3030 cfg = config.myConfig;
31313232 # Build ingress routes based on enabled services.
3333- # NOTE: Nextcloud is intentionally excluded — it is gated to the Tailnet only.
3434- # Its Caddy port is not in allowedTCPPorts, and tailscale0 is a trusted
3535- # interface, so only Tailnet peers can reach it. Update the Cloudflare DNS
3636- # record for ${cfg.nextcloud.hostname} to an A record pointing to the
3737- # server's Tailscale IP (`tailscale ip -4`) so the hostname still resolves
3838- # correctly for Tailnet clients.
3933 ingressRoutes =
4034 lib.optionalAttrs cfg.services.pds.enable {
4135 ${cfg.pds.hostname} = "http://127.0.0.1:${toString cfg.pds.caddyPort}";
···4337 }
4438 // lib.optionalAttrs cfg.services.forgejo.enable {
4539 ${cfg.forgejo.hostname} = "http://127.0.0.1:${toString cfg.forgejo.caddyPort}";
4040+ }
4141+ // lib.optionalAttrs cfg.services.nextcloud.enable {
4242+ ${cfg.nextcloud.hostname} = "http://127.0.0.1:${toString cfg.nextcloud.caddyPort}";
4343+ }
4444+ // lib.optionalAttrs cfg.services.immich.enable {
4545+ ${cfg.immich.hostname} = "http://127.0.0.1:${toString cfg.immich.caddyPort}";
4646+ }
4747+ // lib.optionalAttrs cfg.services.jellyfin.enable {
4848+ ${cfg.jellyfin.hostname} = "http://127.0.0.1:${toString cfg.jellyfin.caddyPort}";
4649 };
4750in
4851lib.mkIf cfg.services.cloudflare.enable {
+6-15
modules/immich.nix
···44# Architecture:
55# Immich server (127.0.0.1:cfg.immich.port)
66# ↑ reverse proxy
77-# Caddy (http://immich.ewancroft.uk:cfg.immich.caddyPort — Tailnet-only)
88-# ✗ NOT in Cloudflare Tunnel — Tailnet access only
99-#
1010-# Access model (Tailnet-only):
1111-# Port cfg.immich.caddyPort is NOT in allowedTCPPorts, so it is blocked
1212-# on all external interfaces. The firewall marks tailscale0 as a
1313-# trustedInterface, meaning Tailnet peers bypass the firewall entirely.
1414-# Point immich.ewancroft.uk at the server's Tailscale IP in Cloudflare DNS
1515-# (A record, proxying disabled — grey cloud).
77+# Caddy (http://immich.ewancroft.uk:cfg.immich.caddyPort)
88+# ↑ Cloudflare Tunnel (outbound only, no firewall ports needed)
169#
1710# Storage — shared with Nextcloud:
1811# Media lives at cfg.immich.mediaDir, which defaults to
···3427# from Nextcloud's — no conflicts).
3528#
3629# First-run:
3737-# Navigate to http://immich.ewancroft.uk:<caddyPort> (or the raw Tailscale
3838-# IP) and complete the onboarding wizard to create your admin account.
3939-# Then configure the library path to cfg.immich.mediaDir from the UI.
3030+# Navigate to https://immich.ewancroft.uk and complete the onboarding
3131+# wizard to create your admin account. Then configure the library path
3232+# to cfg.immich.mediaDir from the UI.
4033##############################################################################
4134{
4235 config,
···9689 wants = [ "srv.mount" ];
9790 };
98919999- # ── Caddy reverse proxy (Tailnet-only) ────────────────────────────────────
100100- # Port cfg.immich.caddyPort is NOT in allowedTCPPorts. Access is restricted
101101- # to Tailnet peers via the trustedInterfaces firewall rule on tailscale0.
9292+ # ── Caddy reverse proxy ───────────────────────────────────────────────────
10293 services.caddy.virtualHosts."http://${im.hostname}:${toString im.caddyPort}" = {
10394 extraConfig = ''
10495 reverse_proxy http://127.0.0.1:${toString im.port}
+6-16
modules/jellyfin.nix
···44# Architecture:
55# Jellyfin server (127.0.0.1:cfg.jellyfin.port)
66# ↑ reverse proxy
77-# Caddy (http://jellyfin.ewancroft.uk:cfg.jellyfin.caddyPort — Tailnet-only)
88-# ✗ NOT in Cloudflare Tunnel — Tailnet access only
99-#
1010-# Access model (Tailnet-only):
1111-# Port cfg.jellyfin.caddyPort is NOT in allowedTCPPorts, so it is blocked
1212-# on all external interfaces. The firewall marks tailscale0 as a
1313-# trustedInterface, meaning Tailnet peers bypass the firewall entirely.
1414-# Point jellyfin.ewancroft.uk at the server's Tailscale IP in Cloudflare
1515-# DNS (A record, proxying disabled — grey cloud).
77+# Caddy (http://jellyfin.ewancroft.uk:cfg.jellyfin.caddyPort)
88+# ↑ Cloudflare Tunnel (outbound only, no firewall ports needed)
169#
1710# Storage — shared with Nextcloud:
1811# Media lives at cfg.jellyfin.mediaDir, which defaults to
···4134# separate from the /srv volume.
4235#
4336# First-run:
4444-# Navigate to http://jellyfin.ewancroft.uk:<caddyPort> (or the raw
4545-# Tailscale IP) and complete the setup wizard. Add media libraries pointing
4646-# at subdirectories of cfg.jellyfin.mediaDir.
3737+# Navigate to https://jellyfin.ewancroft.uk and complete the setup wizard.
3838+# Add media libraries pointing at subdirectories of cfg.jellyfin.mediaDir.
4739##############################################################################
4840{
4941 config,
···8375 dataDir = jf.dataDir;
84768577 # Do NOT open Jellyfin's default ports (8096/8920) — Caddy is the sole
8686- # entry point and only tailscale0 traffic can reach the Caddy port.
7878+ # entry point, proxied through the Cloudflare tunnel.
8779 openFirewall = false;
8880 };
8981···10193 };
10294 };
10395104104- # ── Caddy reverse proxy (Tailnet-only) ────────────────────────────────────
105105- # Port cfg.jellyfin.caddyPort is NOT in allowedTCPPorts. Access is restricted
106106- # to Tailnet peers via the trustedInterfaces firewall rule on tailscale0.
9696+ # ── Caddy reverse proxy ───────────────────────────────────────────────────
10797 services.caddy.virtualHosts."http://${jf.hostname}:${toString jf.caddyPort}" = {
10898 extraConfig = ''
10999 reverse_proxy http://127.0.0.1:${toString jf.port}