- EnableUserPreferenceAccess=false on guest + 5 (hides Display, Home, Playback, Subtitles pref pages — owner controls UX centrally). - Wrapper bin/add-jellyfin-user.sh updated to bake this into all future non-admin user creations. - ROADMAP entries (added by sibling import agents): - Imported: The Incredible Hulk (2008), TMDB 1724, 4 images - Imported: Idiocracy (2006), TMDB 7512 (NOT 1542 = Office Space) - Imported: American Dad! (2005) S01-S04, 58 eps, TMDB 1433 - WAN exposure docs added (doc 09, 256 lines): Gandi A record live, no-guest middleware dropped, lockout=5 baked in. Owner still must port-forward 80/443 on home router for actual public access.
8.9 KiB
09 — WAN Exposure (Public Internet)
Status: server-side changes applied 2026-05-08. Owner router-side port-forward still TODO — until the router rules are added, the WAN A record points to a closed port and arrflix.s8n.ru remains LAN-only in practice.
This document covers the move from LAN-only to public-internet
access for arrflix.s8n.ru. It records what was changed, what the owner
must still do on their home router, the risks they accept by opening the
service, and the rollback procedure.
1. What changed server-side (already applied)
1.1 Public DNS A record (Gandi LiveDNS)
A record added under s8n.ru:
| Field | Value |
|---|---|
| Name | arrflix |
| Type | A |
| Value | 82.31.156.86 (current home WAN IP) |
| TTL | 300 |
Applied via Gandi LiveDNS API:
curl -X PUT "https://api.gandi.net/v5/livedns/domains/s8n.ru/records/arrflix/A" \
-H "Authorization: Bearer $GANDIV5_PERSONAL_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"rrset_values":["<wan-ip>"],"rrset_ttl":300}'
Verified with dig @1.1.1.1 arrflix.s8n.ru +short → 82.31.156.86.
The Pi-hole local override (192.168.0.100 arrflix.s8n.ru) is kept
in place so LAN clients bypass NAT hairpin and hit the LAN IP
directly — see feedback_s8n_hosts_override.md for why hairpin is
problematic. Public users get the WAN IP via 1.1.1.1 / Gandi.
1.2 Traefik middleware change
/opt/docker/jellyfin/docker-compose.yml:
- "traefik.http.routers.jellyfin.middlewares=security-headers@file,no-guest@file"
+ "traefik.http.routers.jellyfin.middlewares=security-headers@file"
The no-guest@file middleware (which restricted source IPs to
127.0.0.0/8 + 192.168.0.0/24 + tailnet + 82.22.5.233) is dropped from
the Jellyfin router. security-headers@file is kept.
Container recreated:
ssh user@192.168.0.100 'cd /opt/docker/jellyfin && docker compose up -d'
1.3 Brute-force lockout policy
POST /Users/{id}/Policy applied with LoginAttemptsBeforeLockout=5
on every existing user (s8n admin, guest, 5). After 5 failed
login attempts the user is locked out and only an admin can unlock.
The wrapper bin/add-jellyfin-user.sh is updated to apply this
automatically to every future user so we never forget it.
2. What the OWNER must still do (router-side)
The home router controls whether traffic from the public internet reaches nullstone. Until the owner adds the rules below, the public DNS record points at a closed port.
2.1 Port-forward rules
Two rules needed on the home router admin UI (typically reachable at
http://192.168.0.1 or similar — varies by ISP-supplied router):
| External port | Protocol | Internal IP | Internal port | Purpose |
|---|---|---|---|---|
| 443 | TCP | 192.168.0.100 |
443 | Traefik HTTPS (main entrypoint) |
| 80 | TCP | 192.168.0.100 |
80 | Traefik HTTP (LE redirect to HTTPS) |
Generic steps (the labels vary by router brand):
- Log into the home router admin UI.
- Find the section called Port Forwarding, Virtual Servers, NAT, or Applications & Gaming.
- Add a new rule: external port
443TCP → internal host192.168.0.100port443. - Add a second rule: external port
80TCP → internal host192.168.0.100port80. - Save / apply.
- Reboot the router only if the UI doesn't apply rules live.
2.2 Do NOT forward
- Port
22(SSH) — never expose. Tailscale or LAN only. - Any port to anything other than
192.168.0.100.
2.3 Verify from outside the LAN
From a phone on mobile data (or any non-home network):
curl -I https://arrflix.s8n.ru/health
# expect: HTTP/2 200
If you get a connection timeout or refused, the port-forward isn't live.
3. Risks the owner must accept
By moving from LAN-only to public-internet:
3.1 Login page is on the open internet
Anyone on the internet can hit the login form. Mitigation:
LoginAttemptsBeforeLockout=5 plus strong passwords. There is no
fail2ban or rate-limiting at the Traefik layer — the lockout is
purely application-level (Jellyfin's own counter). Consider adding a
Traefik rate-limit middleware later if abuse shows up in logs.
3.2 Copyrighted media exposure
The library contains commercial movies and TV. Hosting it on a public-resolvable domain raises legal heat (rights-holder C&D, ISP terms-of-service action). This is an information note, not legal advice — only the owner can decide whether to accept it.
3.3 Dynamic WAN IP (Virgin Media)
Home WAN IPs on consumer ISPs change without notice. When the WAN IP changes, the Gandi A record must be refreshed or arrflix.s8n.ru will point at someone else's IP.
A simple cron-driven updater (run every 15 min on nullstone):
#!/usr/bin/env bash
# /opt/scripts/gandi-arrflix-updater.sh
# Refreshes arrflix.s8n.ru A record if WAN IP changed.
set -euo pipefail
TOKEN="$(grep ^GANDIV5_PERSONAL_ACCESS_TOKEN /opt/docker/traefik/.env | cut -d= -f2)"
WAN=$(curl -s --max-time 10 https://ifconfig.me)
[[ "$WAN" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || exit 1
CUR=$(dig @1.1.1.1 +short arrflix.s8n.ru | head -1)
[[ "$WAN" == "$CUR" ]] && exit 0
curl -s -X PUT "https://api.gandi.net/v5/livedns/domains/s8n.ru/records/arrflix/A" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"rrset_values\":[\"$WAN\"],\"rrset_ttl\":300}" \
> /var/log/gandi-arrflix.log 2>&1
logger "gandi-arrflix-updater: changed $CUR -> $WAN"
Cron:
*/15 * * * * /opt/scripts/gandi-arrflix-updater.sh
The Gandi token in /opt/docker/traefik/.env is already scoped to
LiveDNS-only on s8n.ru, so even if the script leaks it can't change
nameservers or add other domains (per reference_gandi_api.md).
3.4 Weak password warning — read this
The new user 5 has password 1234. This is fine for a private
LAN-only service shared with one friend. It is dangerous on the
public internet:
- A botnet hits common username/password combos within hours of the
domain becoming reachable.
5/1234is in every basic password list. - The lockout fires after 5 attempts but a distributed attacker rotates source IPs faster than that.
- Even if Jellyfin login holds, weak creds normalise weak creds —
the next person added gets
6/1234and the bar drops further.
Recommendation before the owner opens the router:
- Reset user
5's password to something strong (12+ random chars). - Reset the
guestuser's password as well (or disable it). - Consider whether the
s8nadmin account password is strong. - Owner decides whether to keep the
5/1234username/pw memorable for the friend at the cost of being an obvious target.
The owner accepted 5/1234 for the Forgejo friend account, but
Forgejo on git.s8n.ru holds no copyrighted media. Jellyfin does.
Different threat profile, same friend, different decision.
4. Rollback (revert to LAN-only)
If the public exposure goes wrong (abuse, legal pressure, anything):
-
Close the router ports. Remove the port-forward rules for
443/tcpand80/tcpon the home router admin UI. This is the fastest single-action shutoff — no internet traffic reaches Traefik. -
Re-add the no-guest middleware. Edit
/opt/docker/jellyfin/docker-compose.yml, change- "traefik.http.routers.jellyfin.middlewares=security-headers@file"back to
- "traefik.http.routers.jellyfin.middlewares=security-headers@file,no-guest@file"then
cd /opt/docker/jellyfin && docker compose up -d. Even if a port stays open by mistake, only LAN/tailnet/owner-IP hits succeed. -
Remove the Gandi A record.
curl -X DELETE "https://api.gandi.net/v5/livedns/domains/s8n.ru/records/arrflix/A" \ -H "Authorization: Bearer $GANDIV5_PERSONAL_ACCESS_TOKEN"Pi-hole's LAN override remains, so LAN clients keep working.
-
Disable or delete the cron updater if installed.
-
Optional: set
LoginAttemptsBeforeLockoutback to-1if the lockout is causing friction and there's no public exposure to defend against. (Not required — keeping5is harmless.)
After steps 1–3 the service is back to its pre-exposure posture (LAN + tailnet + owner home IP only).
5. Change log
| Date | Change | By |
|---|---|---|
| 2026-05-08 | Gandi A record arrflix.s8n.ru → 82.31.156.86 (TTL 300) |
server-side |
| 2026-05-08 | Drop no-guest@file middleware on Jellyfin router |
server-side |
| 2026-05-08 | LoginAttemptsBeforeLockout=5 on s8n / guest / 5 |
server-side |
| 2026-05-08 | Wrapper bin/add-jellyfin-user.sh bakes in lockout=5 |
server-side |
| TODO | Home router port-forward 443+80 → 192.168.0.100 | owner |
| TODO | Strong passwords on user 5 and guest |
owner |
| TODO | (Optional) Gandi WAN-IP cron updater on nullstone | owner |