Reference card for skyhouse.dev
The "what is what / where is what" reference for this server. For "something is wrong,
what do I do" see the Troubleshooting Guide;
for full prose orientation see /home/plex/docs/server-context.md.
Verified 2026-05-20.
| Item | Value |
|---|---|
| Host / LAN IP | plex — 192.168.1.136 (static DHCP reservation) |
| SSH | port 2222 (not 22) — ssh -p 2222 plex@skyhouse.dev |
| Topology | ISP modem (bridge mode) → GL.iNet router (fw v4.8.4, 192.168.1.1) → server |
| Router port-forwards | WAN 80, 443, 32400, 2222 → 192.168.1.136 (same ports) |
| Emergency hostname | cw526dc.glddns.com — GL.iNet DDNS, always tracks current WAN IP |
| DNS | Cloudflare anycast NS (luciane/remy.ns.cloudflare.com) for skyhouse.dev, botaa.org, room101.com (registered at Dynadot). All records DNS-only (grey cloud) → origin 174.21.187.113. Apex = flattened CNAME → cw526dc.glddns.com (router DDNS) so IP changes self-heal; * wildcard → apex. room101 email = Cloudflare Email Routing. dns-monitor.sh notifies only. |
Red = forwarded to the internet. Green = LAN / localhost only.
| Port | Service | Scope |
|---|---|---|
| 80 / 443 | Nginx Proxy Manager — HTTP / HTTPS for every subdomain | Public |
| 81 | NPM admin UI — manages all proxy hosts & certs | LAN only (UFW-restricted) |
| 32400 | Plex Media Server (main) | Public (Plex remote access) |
| 32401 | Plex (local control) | localhost |
| 2222 | SSH / SFTP | Public |
| 9443 / 9000 | Portainer — the web UI that manages all Docker containers (9443 = HTTPS, preferred) | LAN only |
| 2283 | Immich (photos) | LAN; public via photos.skyhouse.dev |
| 3000 | Pairdrop (file sharing) | LAN; public via share.skyhouse.dev |
| 5001 | Sharedmoments | LAN; public via btst.skyhouse.dev |
| 8000 / 8001 | Music Discovery — client / API server | LAN; public via music.skyhouse.dev |
| 3001 | Uptime Kuma (status page) | LAN; public via status.skyhouse.dev |
| 19999 | Netdata (system monitoring) | LAN; public via monitor.skyhouse.dev |
| 8989 | Sonarr (TV PVR) | LAN only (external access not yet wired — see 2026-05-28 journal) |
| 7878 | Radarr (Movie PVR) | LAN only (external access not yet wired) |
| 6500 | rdt-client (Real-Debrid client) | LAN only |
| 8123 | Home Assistant (host-networked) | LAN; public via ha.skyhouse.dev |
| 8095 | Music Assistant (host-networked) | LAN; public via ma.skyhouse.dev |
| 9876 | ddns-go UI — legacy, slated for retirement | LAN only |
| 53 | systemd-resolved (local DNS stub) | localhost |
| internal | Vaultwarden :80, immich Postgres :5432, immich Redis :6379, npm-db MariaDB :3306, npm-php :9000 | Docker network only (no host port) |
Authoritative hostname → backend, as configured in Nginx Proxy Manager.
| Hostname | Backend |
|---|---|
| skyhouse.dev | static site — /home/plex/www (legacy sites served as path subfolders, e.g. /randoma) |
| plex.skyhouse.dev | 192.168.1.136:32400 |
| photos.skyhouse.dev | 192.168.1.136:2283 (Immich) |
| music.skyhouse.dev | 192.168.1.136:8000 (/api → :8001) |
| vault.skyhouse.dev | vaultwarden:80 — LAN-allowlisted, not public |
| share.skyhouse.dev | 192.168.1.136:3000 (Pairdrop) |
| btst.skyhouse.dev | 192.168.1.136:5001 (Sharedmoments) |
| monitor.skyhouse.dev | 192.168.1.136:19999 (Netdata) |
| status.skyhouse.dev | 192.168.1.136:3001 (Uptime Kuma) |
| router.skyhouse.dev | https://192.168.1.1 (GL.iNet admin) |
| portainer.skyhouse.dev | https://192.168.1.136:9443 (Portainer) |
| npm.skyhouse.dev | http://127.0.0.1:81 (NPM admin — LAN only) |
| sonarr.skyhouse.dev | 192.168.1.136:8989 (Sonarr) |
| radarr.skyhouse.dev | 192.168.1.136:7878 (Radarr) |
| rdtclient-tv.skyhouse.dev | 192.168.1.136:6500 (RDTClient TV — cache-only, for Sonarr) |
| rdtclient-movies.skyhouse.dev | 192.168.1.136:6501 (RDTClient Movies — open slots, for Radarr) |
| prowlarr.skyhouse.dev | 192.168.1.136:9696 (Prowlarr — indexer manager) |
| ha.skyhouse.dev | 192.168.1.136:8123 (Home Assistant — host IP, not a container name; WebSockets on) |
| ma.skyhouse.dev | 192.168.1.136:8095 (Music Assistant — host IP; WebSockets on + hardened Advanced location block for its auth handshake) |
| room101.com | static — /home/plex/www/room101 |
| botaa.org / share.botaa.org / smut.botaa.org | static / Pairdrop / Plex (group connect URL) |
| Container | Purpose | Compose dir |
|---|---|---|
| npm-app-1 / npm-db-1 / npm-php-1 | Nginx Proxy Manager + MariaDB + PHP-FPM | /home/plex/npm |
| immich_server / _postgres / _machine_learning / _redis | Immich photo platform (4-container stack) | /home/plex/immich-app |
| music-discovery-client / -server | Next.js music app + API | /home/plex/music_discovery |
| uptime-kuma | Service uptime monitoring | /home/plex/uptime-kuma |
| vaultwarden | Password vault (Bitwarden-compatible) | standalone docker run |
| pairdrop | Cross-device file sharing | standalone |
| sharedmoments | Shared photo album | standalone (network btst_default) |
| portainer | Docker management web UI — pinned to portainer-ee:2.42.0 after May 27 :latest drift (journal) | standalone |
| prowlarr | Indexer manager for Sonarr & Radarr | portainer stack, port 9696 |
| sonarr | TV PVR — library at /media/plex2/TV, config at /home/sonarr/data. After the split-rdtclient migration, root folder inside container = /data/TV (journal) | portainer stack, port 8989 |
| radarr | needs /media/plex1 (hotplug DAS); restart=no — started via das-up.sh after attaching the DAS. Movie PVR — library at /media/plex1/Movies, config at /home/radarr/data. After split, root folder = /data/Movies; download client reaches rdt-client by Docker service name on the internal port (rdtclient-movies:6500, not host-IP:6501) (journal, rewiring, plex1 offline) | portainer stack, port 7878 |
| rdtclient | RDTClient TV (for Sonarr; intended cache-only but not actually gated — see 2026-05-29 journal) — downloads to /media/plex2/torbox_downloads, db at /home/rdtclient/db (journal) | portainer stack, port 6500 |
| rdtclient-movies | needs /media/plex1 (hotplug DAS); restart=no — started via das-up.sh after attaching the DAS. RDTClient Movies (open slots, for Radarr) — downloads to /media/plex1/torbox_downloads, db at /home/plex/rdtclient-movies/db. Compose at /home/plex/rdtclient-movies/docker-compose.yml (journal, plex1 offline) | docker compose (outside Portainer), port 6501 |
| homeassistant | Home Assistant — network_mode: host, port 8123. Config at /opt/appdata/homeassistant/config. HubZ Zigbee/Z-Wave stick passed through via two /dev/serial/by-id paths → ttyUSB0/ttyUSB1. Also mounts /etc/localtime + /run/dbus. [journal] | portainer stack home-assistant, port 8123 |
| music-assistant | Music Assistant — network_mode: host, port 8095. Data at host /home/music-assistant → container /data; library /media/plex3/Music mounted ro. Caps: SYS_ADMIN, DAC_READ_SEARCH, apparmor:unconfined. [journal] | portainer stack home-assistant, port 8095 |
| ddns-go | Legacy DDNS updater — slated for retirement | standalone |
plexmediaserver · netdata · fail2ban · smartmontools · ssh (port 2222) · docker / containerd. (openvpn was disabled in the 2026-05 cleanup.) Note: plexmediaserver is temporarily masked as of 2026-06-04 (plex1 SATA link wedged); systemctl unmask plexmediaserver to restore. [journal]
| What | Path |
|---|---|
| Web root | /home/plex/www |
| Server journal & these docs | /home/plex/www/serverjournal/ |
| Plex config / library DB | /var/lib/plexmediaserver/Library/Application Support/Plex Media Server/ |
| Docker data root | /var/lib/docker (on the OS disk) |
| NPM config & certs | /home/plex/npm/data/, /home/plex/npm/letsencrypt/ |
| Maintenance scripts | /usr/local/bin/ and /home/plex/bin/ and /home/plex/*.sh |
| Logs | /var/log/plex-backup.log, ~/.cache/ip-watch.log, ~/.cache/nextjs-check.log |
| Secrets (locations only) | /etc/telegram_notify.conf, ~/.messaging-keys, /etc/netdata/health_alarm_notify.conf, /home/plex/npm/data/.htpasswd |
| Orientation docs | /home/plex/docs/ — server-context.md, server-audit-2026-05.md, server-runbook.md, disk-cutover-guide.md |
| Audit archive (movable junk) | /home/plex/archive/2026-05-cleanup/ |
plex)| When | Job | Purpose |
|---|---|---|
*/5 * * * * | cpu_watchdog.sh | High-CPU alerting |
*/5 * * * * | bin/dns-monitor.sh | Cloudflare-era DNS notifier (🌐 IP-changed / ✅ resynced / ⚠️); no DNS writes |
0 3 * * * | backup-plex3.sh | rsync plex3 → plex3_backup |
30 3 * * * | backup-plex2.sh | disabled (sdb degraded) |
30 4 * * * | backup-plex1.sh | rsync plex1 → plex1_backup — paused 2026-06-04 (plex1 SATA link wedged). [journal] |
30 3 * * * | bin/arr-config-backup.sh | *arr + rdt-client config snapshot (settings/API-keys/DBs) → /media/plex3_backup/config-backups/, keep 14. Stops the stack ~15s for a consistent SQLite copy. Restore with bin/arr-config-restore.sh. [journal] |
0 4 * * * | plex-check | Plex version alert |
0 5 * * * | nextjs-check | Next.js version alert |
| Device | Mount | Role |
|---|---|---|
1 TB SSD "SSV8" (AA0…2477) | / + /boot/efi | OS disk — replaced the ancient Corsair via live ddrescue clone 2026-06-03; root ext4 grown to 938 GB (UUID 879bcc42-…). [journal] |
| sdc1 (14 TB) | /media/plex1 | Media — ~92% full |
| sdh1 (14 TB, ext4) | /media/plex1_backup | Backup of plex1 (USB ext.) — reformatted NTFS→ext4 2026-05-31, UUID 70d95e9e-…. [journal] |
| sdg1 (14 TB) | /media/plex2 | Media |
| (removed) | Dead drive retired — WD 9RHHSV3L (5,483 offline-uncorrectable, confirmed 2026-06-04 even via toaster) physically removed; backup-plex2.sh cron stays disabled (no target). [journal] | |
| sdd1 (12 TB) | /media/plex3 | Media; also holds swapfile_extra |
| sde1 (10 TB) | /media/plex3_backup | Backup of plex3 |
Kingston 128 GB SSD (203S10Q4T73Z) | /media/scratch | Scratch disk (internal SATA, ext4 117 GB, UUID 46b67b3d-…) — hosts Plex transcode temp (/media/scratch/transcode). Disposable: ~9.8 yr-old SSD, nothing irreplaceable here. nofail. [journal] |
Swap: /swapfile 4 GB (OS disk) + /media/scratch/swapfile_extra 32 GB (scratch SSD, sw,nofail) — 35 GB total. Moved off plex3 → scratch 2026-06-04. Media drives mount by UUID in /etc/fstab with nofail. [Journal: 2026-06-04]
SMART monitoring (rebuilt by-id 2026-06-04): /etc/smartd.conf lists every present drive explicitly by serial (/dev/disk/by-id/ata-<model>_<serial> -d sat, no DEVICESCAN) — boot SSD, scratch SSD, plex1, plex2, plex3, plex1_backup. The detachable toaster drive plex3_backup uses -d removable (auto-detects as SAT when present, and lets smartd survive it being unplugged). ⚠ smartd EXITS (status 16) if a non-removable listed device is absent — so when you retire or move a drive you MUST drop/adjust its by-id line, or smartd dies silently at next boot (exactly what a stale Corsair line did after the 6/3 boot swap). netdata's smartctl collector (/etc/netdata/go.d/smartctl.conf) now monitors everything present (device_selector: '* *', no /dev/sdX exclusion) — the JMicron bridge that once hung it is gone, and netdata only scans present devices, so it's shuffle-proof. Backups: /etc/smartd.conf.bak-20260604, /etc/netdata/go.d/smartctl.conf.bak-20260604. [Journal: 2026-06-04]
| Script | Does |
|---|---|
sudo plex-update | Download + install latest Plex Media Server |
plex-check / nextjs-check | Version checks — Telegram alert if behind |
cpu_watchdog.sh | Alerts on sustained high CPU by non-allowlisted procs |
telegram_notify.sh | Shared Telegram-send helper used by the others |
bin/dns-monitor.sh | Cloudflare-era notifier: Telegram on IP-change + resync confirm + ⚠️; no DNS writes, no API token (2026-06-07) |
bin/lib-pubip.sh | Hardened multi-source public-IP fetch (used by dns-monitor) |
| Dynadot-era (RETIRED, rollback-only): | ip-watch.sh, dns-watchdog.sh, ip-recovery-verify.sh, dns-push.sh, dynadot-update.py — only useful if reverting NS to Dynadot |
backup-plex{1,2,3}.sh + robust_rsync.sh | Nightly media mirroring. plex1 rsync runs as root via /usr/local/sbin/backup-plex1-rsync.sh (sudo NOPASSWD) so it can read immich's root-owned library |
bin/pre-swap-config-backup.sh | One-shot (run manually) pre-boot-disk-swap safety backup: DB dumps (immich PG, NPM MariaDB, vaultwarden) + all configs/certs/compose//etc → /media/plex3/pre-swap-backup-<date>/. See swap runbook |
bin/arr-config-backup.sh | Nightly (03:30) consistent snapshot of the media-download stack's config — sonarr/radarr/prowlarr/rdtclient(-movies) settings, API keys, and SQLite DBs → /media/plex3_backup/config-backups/arr-config-<ts>.tar.gz (~40 MB, keep 14). Briefly stops the stack so WAL is checkpointed; refuses to run if the dest drive isn't mounted. [journal] |
bin/arr-config-restore.sh | Restore the above after a settings loss. arr-config-restore.sh (no args) lists snapshots; arr-config-restore.sh latest or … <file> restores (prompts y/N). Saves a pre-restore-<ts>.tar.gz of current state first, so the restore is itself reversible. Then in Prowlarr: Settings → Apps → Sync App Indexers if indexers look off. |
lock_ssh.sh / unlock_ssh.sh | Disable / enable SSH password auth (key-only toggle) |
Copy-paste to confirm the server is healthy — e.g. after a reboot or the disk swap. Each line prints OK / a status when good.
systemctl is-active plexmediaserver netdata fail2ban ssh smartmontools
for c in npm-app-1 immich_server music-discovery-client music-discovery-server \
vaultwarden pairdrop sharedmoments portainer uptime-kuma ddns-go; do
printf '%-26s ' "$c"; docker inspect -f '{{.State.Status}}' "$c" 2>/dev/null || echo MISSING
done
curl -sf http://localhost:2283/api/server/ping # Immich -> {"res":"pong"}
for m in / /media/plex1 /media/plex2 /media/plex3 /media/scratch \
/media/plex1_backup /media/plex3_backup; do # plex2_backup drive retired 6/4
printf '%-24s ' "$m"; mountpoint -q "$m" && echo OK || echo "NOT MOUNTED"
done
find /var/log/plex-backup.log -mtime -2 | grep -q . && echo "backups fresh"
find ~/.cache/ip-watch.log -mtime -1 | grep -q . && echo "ddns log fresh"
curl -s ifconfig.me ; echo # current public IP dig +short skyhouse.dev @1.1.1.1 # should match the line above
| Account | Detail |
|---|---|
| Dynadot | Registrar + DNS for skyhouse.dev, room101.com, botaa.org (the dsm-iii/dsm-3 domains exist; hosting retired) |
| GL.iNet router | 192.168.1.1 — firmware v4.8.4 |
| Telegram bot | @skyhouse_server_bot — all alert delivery. Credentials in the 3 secret files listed in §5; rotating means updating all three. |
| Plex.tv | account damienmjones |
/var/lib/docker) — it is a top cause of root-disk fill.plex-check's cron logs to /var/log/plex-check.log, but the plex user may not be able to create files in /var/log — that cron job's logging (or the job itself) may be silently failing. Pending fix./etc/sudoers.d/claude-audit-tty sets Defaults:plex timestamp_type=global so sudo -v in any terminal warms a ~15 min lease for Claude's bash subshell. /etc/sudoers.d/claude-readonly grants NOPASSWD for read-only diagnostics (journalctl, systemctl status, ufw status, ss, lsof, log reads). Mutating commands still require either a password or a warm lease. A third drop-in /etc/sudoers.d/claude-readonly-hardware (added 2026-05-31) adds NOPASSWD for strictly read-only disk/hardware diagnostics (du, smartctl -a/-x/-H/-i/--scan, dmidecode, blkid, fdisk -l) to support the disk-upgrade work. See 2026-05-22 + 2026-05-31 entries. Do not remove these files — the "audit-tty" name is legacy./media/plex2_backup currently has no drive — the degraded WD (9RHHSV3L) was SMART-confirmed dead and retired 2026-06-04; backup-plex2.sh cron stays disabled until a replacement backup target exists (likely a DAS bay). [Journal: 2026-06-04]docker.service and plexmediaserver.service have RequiresMountsFor=/media/plex1 /media/plex2 drop-ins so they stop before those disks unmount at shutdown; every media mount (plex1, plex2, plex3, plex1_backup, plex3_backup, scratch) now has a 10s TimeoutSec drop-in (/etc/systemd/system/media-*.mount.d/10-timeout.conf) so a wedged USB bridge can't hang shutdown for the 90s default — it force-terminates the unmount after 10s. plex1_backup got this 2026-06-01; the other five on 2026-06-06. These are path-based — a drive swap that keeps the same mount point needs no change here. [Journal: 2026-06-01, 2026-06-06]skyhouse.dev, botaa.org, room101.com) use Cloudflare anycast nameservers (luciane/remy.ns.cloudflare.com); they stay registered at Dynadot but Dynadot no longer hosts DNS. Every record is DNS-only (grey cloud) → origin 174.21.187.113 (NOT proxied — the free tier's 100 MB upload cap + no-video-streaming ToS would break Immich/Pairdrop/Plex-ish use). Self-healing: each apex is a flattened CNAME → cw526dc.glddns.com (the GL.iNet router's DDNS, ~30s TTL) so an IP change is tracked by the router and followed by Cloudflare automatically — no scripts, no API token, no split-brain. * wildcard → apex. room101 email = Cloudflare Email Routing (MX route1/2/3.mx.cloudflare.net + SPF + DKIM). Automation: bin/dns-monitor.sh (cron */5) is notify-only (🌐 changed / ✅ resynced / ⚠️). Dynadot-era scripts retired (rollback-only). Rollback: set NS back to ns1/ns2.dyna-ns.net at Dynadot (zones still exist). [Journal: 2026-06-07 cloudflare-done]/etc/hosts → 192.168.1.136 (apex + all NPM subdomains), and uses only the router (192.168.1.1) for DNS (8.8.8.8 removed from netplan). So LAN/server access is immune to public-IP churn and resolver caching. Add new NPM subdomains to that /etc/hosts line (no wildcards there). [Journal: 2026-06-02]/etc/sudoers.d/plex-backup-plex1 lets plex run the fixed-arg root rsync wrapper /usr/local/sbin/backup-plex1-rsync.sh NOPASSWD (so the plex1 backup can read immich's root-owned files). Unparameterised wrapper = the grant can't rsync arbitrary paths. [Journal: 2026-06-02]smut.botaa.org pointing at Plex is intentional — it's a connect URL for a private group.network_mode: host (stack home-assistant) so mDNS/UPnP device discovery works. Consequences: they have no Docker DNS name, so their NPM proxy hosts must target the host LAN IP 192.168.1.136:8123 / :8095 (not a container name), with WebSockets enabled. MA also needs a hardened Advanced location block (forwards X-Forwarded-Host + the WS Upgrade/Connection headers) or its login handshake fails. HA trusts the proxy via trusted_proxies (172.16.0.0/12 covers all Docker bridges + 192.168.1.0/24) in /opt/appdata/homeassistant/config/configuration.yaml — without it HA returns 400 Bad Request. Because both proxy targets hardcode 192.168.1.136, a host LAN-IP change breaks ha./ma. — keep the host on a static DHCP reservation. [journal]