23 KiB
Serenity Cleanup Wave 1 Plan
Status: approved planning baseline for the first safe cleanup pass on Serenity.
Goal
Reduce obvious legacy clutter on Serenity without breaking the still-needed torrent/media-locality group or the still-needed Serenity Newt path.
This wave is intentionally conservative. It does not move qBittorrent/ARR off Serenity yet. It does not retire Serenity yet. It does not delete databases that still back live apps.
Operator decisions already resolved
- Nothing should intentionally remain on Serenity after PD owns the disks locally.
- Technitium already covers the DNS role John wants.
- Serenity Pi-hole remnants should be treated as removable.
- Serenity Newt is still needed and must be preserved.
- GameVault and RomM should migrate, not be pruned.
- Final end-state remains:
- move qbit + ARR family to PD after storage cutover
- leave no intentional production app role on Serenity
- retire Serenity entirely
Scope of cleanup wave 1
Wave 1 includes only these categories:
- Remove legacy DNS clutter that should no longer be serving production traffic.
- Remove obviously stale created/exited containers.
- Document migration order for the two database-backed apps that should move later.
Wave 1 explicitly excludes:
- qbit
- GluetunVPN
- qbit_manage
- prowlarr
- sonarr
- sonarr-anime
- radarr
- lidarr
- readarr
- readarr-epub
- bazarr
- autobrr
- unpackerr
- Notifiarr
- shelfmark
- Newt
- technitium-dns-pilot
- GameVault
- romm
- reranker
Live facts this plan is based on
From the live Serenity audit:
- ARR/torrent locality is still tied to
/mnt/user/data GameVaultpoints at local Postgres on10.5.30.5:5432RomMpoints at local MariaDB on10.5.30.5:3306- live container env inspection still shows those explicit DB_HOST/DB_PORT bindings on 2026-05-25
- quick live checks did not surface immediate DB dependencies for
Wizarr,Shelfmark, orNotifiarr Newtis still needed- legacy Pi-hole containers are still running even though Technitium is now the intended DNS path
- Serenity Newt-backed Pangolin routes still had stale health-check hostnames pointing at old
10.5.1.5even though their target IPs had already been rewritten tolocalhost/10.5.30.5 - a temporary
10.5.1.5/32alias onbr0validated the diagnosis, but it was removed because the old IP is no longer allowed on that VLAN - authoritative Pangolin cleanup was then completed by rewriting the audited Serenity target set to
10.5.30.5for both routing and health checks, removing the need for any legacy-IP workaround
Wave 1 current execution status
Live execution on 2026-05-25 established:
Huntarrandomegabrrwere low-risk stale stopped containers and were removed from Serenity- the recent non-running
Createdcontainers (calibre-web,SuggestArr,Cleanuparr,calibre,agregarr) were metadata-verified and then removed after confirming they were still onlyCreated, hadrestart=no, and were not live workloads Unraid-Cloudflared-Tunnelwas audited, stopped, and removed after verification showed it was only transport-alive and no current public traffic depended on it- the Serenity Pi-hole HA stack (
binhex-official-pihole,pihole-serenity,unbound-pihole-serenity,keepalived-pihole-serenity) was then stopped and removed after live mixed-host DNS checks confirmed the Technitium path remained healthy without it
Wave 1-A: legacy Pi-hole removal
Target containers
binhex-official-piholepihole-serenityunbound-pihole-serenitykeepalived-pihole-serenity
Why they are in scope
- They are legacy DNS/HA remnants.
- Current homelab docs describe the active internal DNS path as the Technitium trio.
- Operator confirmed Technitium covers the intended DNS role.
- Keeping old DNS stacks around increases confusion and future troubleshooting blast radius.
Preconditions
Before removal, verify only these read-only checks:
- Serenity Technitium backup node is healthy.
- DHCP-advertised resolver set is still PD/NOMAD/Serenity Technitium, not Pi-hole.
- No Pangolin route, bookmark, or admin workflow still intentionally points at a Pi-hole UI.
- No host on the LAN still relies on the old Pi-hole admin port out of habit.
Removal order
- stop
keepalived-pihole-serenity - stop
pihole-serenity - stop
unbound-pihole-serenity - stop
binhex-official-pihole - verify Technitium-only DNS behavior still looks normal
- remove the stopped containers
- archive or delete their stale appdata only after a short observation window
Verification after removal
- Serenity Technitium container remained healthy
- mixed-host DNS checks stayed good after removal:
- from NOMAD,
10.5.30.8and10.5.30.10still resolved both public and homelab names - from PD,
10.5.30.9and10.5.30.10still resolved both public and homelab names
- from NOMAD,
10.5.30.53continued answering DNS even after Serenity Pi-hole removal, confirming it is no longer tied to the removed Serenity Pi-hole containers- no immediate client-facing DNS regression was observed during the removal window
- no public regression was observed on sampled hostnames such as
panel.paccoco.comandaudiobookshelf.paccoco.com
Wave 1-B: stale container pruning
Created-only clutter to remove
calibre-webSuggestArrCleanuparrcalibreagregarr
Exited clutter to remove
Huntarromegabrr
Why they are in scope
- They are not live workloads.
- They add noise to
docker ps -aand make host intent harder to understand. - There is no current architecture reason to preserve them as active Serenity residents.
Safe pruning rules
Before deleting each one:
- confirm container status is still
CreatedorExited - confirm it is not referenced by a live reverse-proxy route
- confirm it is not the only source of some needed config/data you still care about
- if uncertain, export one final metadata snapshot first:
- image name
- mounts
- env file path if obvious
Practical order
- remove
Createdcontainers first - remove long-dead exited containers second
- leave appdata in place initially
- only delete appdata later after a short cooling-off window
Wave 1-C: cloudflared deadwood removal
Target container
Unraid-Cloudflared-Tunnel
Why it is in scope
- It is already documented in repo docs as dead/stale.
- Pangolin/Newt is the active exposure pattern now.
- Live audit on 2026-05-25 showed the container is a remote-managed Cloudflare Tunnel carrying a stale legacy config, not an active Serenity service path.
Live audit findings (2026-05-25)
Unraid-Cloudflared-Tunnelis healthy at the transport layer (/readyreported 4 ready connections), but metrics showedcloudflared_tunnel_total_requests 0andcloudflared_tunnel_request_errors 0for the current run.- Startup logs exposed the remote-managed ingress set as legacy hostnames pointed at old
192.168.1.xorigins:wazuh.paccoco.com->https://192.168.1.102remotely.paccoco.com->http://192.168.1.180:5001hp.paccoco.com->http://192.168.1.180:3000octoprint.paccoco.com->http://192.168.1.52audiobookshelf.paccoco.com->http://192.168.1.5:13378spoolman.paccoco.com->http://192.168.1.202:7912node1.paccoco.com->https://192.168.1.189:8080hp2.paccoco.com->http://192.168.1.224:3000panel.paccoco.com->https://192.168.1.189nextcloud.paccoco.com->http://192.168.1.5:11000
- The tunnel has no local config files under
/mnt/user/appdata/cloudflared; it is driven entirely by Cloudflare-side config plus a token. - Public checks for those hostnames currently resolve to
172.245.79.139and return either live responses or front-door 404s without producing any new tunnel traffic, indicating the present public path is elsewhere and not traversing this Serenity container. - Repo/docs no longer identify this tunnel as an intended live exposure path; the only repeated modern exposure requirement in Serenity docs is the local Pangolin/Newt lane.
Preconditions
- verify no current DNS/public route still expects this tunnel
- verify no local notes still treat it as the active exposure path
- verify Newt-based routes are the real live path
Status after live audit:
- (1) satisfied enough for container retirement: no observed current public traffic is reaching this tunnel
- (2) satisfied: local docs treat it as stale legacy deadwood, not the intended active path
- (3) satisfied for Serenity-hosted apps: Pangolin/Newt remains the intentionally preserved exposure path
Recommended retirement path
- stop the container and watch briefly for any unexpected complaint or new public breakage
- remove the container
- keep
/mnt/user/appdata/cloudflaredduring a cooling-off window even though it appears empty/unneeded - later, from the Cloudflare side, delete or repoint the stale tunnel config/hostnames if they still exist there
Status:
- completed on 2026-05-25:
Unraid-Cloudflared-Tunnelwas stopped and removed - sampled public checks (
panel.paccoco.com,audiobookshelf.paccoco.com) remained healthy after removal
Wave 1-D: DB-backed migration ordering
These apps should not be deleted in wave 1. They need planned migration.
Pair 1: GameVault + local Postgres
Live dependency:
GameVault->postgresql15
Recommended sequence:
- create PD-side target appdata path
- create PD-side Postgres DB/user on shared-postgres, or a deliberate dedicated PD Postgres if there is a reason not to use shared-postgres
- export GameVault DB from Serenity
- import into PD target database
- migrate GameVault appdata/config
- recreate GameVault on PD attached to the shared database network if using shared-postgres
- verify login, library visibility, and metadata path behavior
- only then retire Serenity
postgresql15
Default recommendation:
- prefer PD shared-postgres unless GameVault has a proven reason to stay isolated
- note: live
psqlinspection on 2026-05-25 showed a Postgres collation-version mismatch warning on Serenity (gamevaultcreated with glibc collation 2.36 while host now provides 2.41), so include a post-migration refresh/reindex plan rather than carrying that debt forward silently
Pair 2: RomM + local MariaDB
Live dependency:
RomM->MariaDB-Official
Recommended sequence:
- create PD-side target appdata path
- create PD-side MariaDB DB/user on shared-mariadb, or a deliberate dedicated PD MariaDB only if needed
- export RomM DB from Serenity
- import into PD target MariaDB
- migrate RomM appdata/config/assets/resources
- recreate RomM on PD attached to the shared database network if using shared-mariadb
- verify UI, library, metadata, and asset behavior
- only then retire Serenity
MariaDB-Official
Default recommendation:
- prefer PD shared-mariadb unless RomM proves awkward on the shared stack
Recommended order across all wave 1 work
- verify Technitium is the only intended active DNS path
- remove legacy Pi-hole stack
- remove dead Cloudflared tunnel
- remove stale created/exited containers
- leave GameVault/Postgres and RomM/MariaDB in place until their PD migration is prepared
- keep qbit/ARR locality untouched until PD storage cutover is real
Risks and guardrails
Do not touch yet
Do not touch in this wave:
- qbit
- ARR family
- GluetunVPN
- qbit_manage
- Newt
- technitium-dns-pilot
- GameVault
- romm
- postgresql15
- MariaDB-Official
Specific guardrails
- Do not delete any appdata directory in the same step as container removal unless the dependency is unquestionably dead.
- Do not remove
postgresql15until GameVault is verified on PD. - Do not remove
MariaDB-Officialuntil RomM is verified on PD. - Do not move qbit/ARR until PD directly owns the relevant media/torrent paths.
- Do not break Serenity Newt while cleanup is happening.
Suggested Kanban decomposition
Card A1 — verify legacy Pi-hole is truly unused
Definition of done:
- current DNS path confirmed as Technitium-only
- no intentional admin dependency on Serenity Pi-hole remains
Card A2 — remove Serenity legacy Pi-hole containers
Definition of done:
- all four legacy Pi-hole containers stopped and removed
- no DNS regression observed
Card B1 — remove stale created containers
Definition of done:
- created-only clutter removed
- appdata retained for cooling-off period
Card B2 — remove stale exited containers
Definition of done:
- exited clutter removed
- appdata retained for cooling-off period
Card C1 — remove dead Unraid Cloudflared tunnel
Definition of done:
- no public path depends on it
- container removed
Card D1 — choose PD target layout for GameVault and RomM wave
Definition of done:
- PD target appdata paths chosen for both apps
- decision recorded to use PD shared-postgres for GameVault unless blocked
- decision recorded to use PD shared-mariadb for RomM unless blocked
- required PD mount paths for libraries/assets/resources identified
Chosen target layout (2026-05-25):
- place both services in the PD
mediastack so they follow the same steady-state placement already documented for PD media/library apps - attach both services to:
media-netfor local app adjacencyix-databases_shared-databasesfor shared DB accesspangolinfor internal ingress / public exposure
- preserve the current host ports on PD because live checks showed them free there:
- GameVault:
8785:8080 - RomM:
8457:8080
- GameVault:
- preserve the public hostnames already in use during cutover:
gamevault.paccoco.comromm.paccoco.com
- use the PD internal ingress model (
Pangolin -> Traefik -> app) when the public route is re-homed, rather than introducing another direct-to-container edge pattern
Chosen storage layout:
- GameVault appdata on SSD because it is small and write-active:
/mnt/docker-ssd/docker/appdata/gamevault/media->/media/mnt/docker-ssd/docker/appdata/gamevault/logs->/logs
- GameVault canonical content mounts should be re-used, not copied:
/mnt/unraid/data/media/Games-Apps Isos->/files/mnt/unraid/data/media/Saved Games->/savefiles
- RomM split layout:
- write-active cache on SSD:
/mnt/docker-ssd/docker/appdata/romm/redis-data->/redis-data
- config/assets/resources on tank appdata:
/mnt/tank/docker/appdata/romm/config->/romm/config/mnt/tank/docker/appdata/romm/assets->/romm/assets/mnt/tank/docker/appdata/romm/resources->/romm/resources
- write-active cache on SSD:
- RomM canonical library mount should be re-used, not copied:
/mnt/unraid/data/media/RomM->/romm/library
Chosen database layout:
- GameVault -> PD
shared-postgresonix-databases_shared-databases- target DB:
gamevault - target DB user:
gamevault
- target DB:
- RomM -> PD
shared-mariadbonix-databases_shared-databases- target DB:
romm - target DB user:
romm
- target DB:
Routing/proxy notes to preserve during implementation:
- RomM upstream docs call out reverse-proxy sensitivity; keep websocket support intact when the route is moved behind PD Traefik
- existing Traefik file-provider patterns in
ingress/traefik/dynamic/routes.ymlshould be extended rather than inventing a new routing mechanism for these two apps
Card D2 — stage PD database targets for the wave
Definition of done:
- GameVault target DB/user created on PD shared-postgres or explicit exception documented
- RomM target DB/user created on PD shared-mariadb or explicit exception documented
- connection details verified from the future PD app network context
- migration rollback notes captured before any source export
Execution notes (2026-05-25):
- staged PD targets on the live shared DB services:
- Postgres DB/user:
gamevault/gamevault - MariaDB DB/user:
romm/romm
- Postgres DB/user:
- stored live credentials in the PD media stack env file and synced that env into the encrypted secrets repo; credentials were not written into the main repo
- verified future app-network connectivity with ephemeral clients on
ix-databases_shared-databases:- Postgres check returned
gamevault|gamevault - MariaDB check returned
romm/romm@%
- Postgres check returned
- rollback notes:
- pre-change backup of PD media env:
/mnt/docker-ssd/docker/compose/media/.env.pre-serenity-wave2-d2-20260525-182938 - if D3/D4 later uncover an issue, keep the staged DBs unused, restore the prior media
.envif needed, and rotate/drop the staged users before re-attempting - no Serenity source data was touched yet in D2; this card only prepared empty PD landing zones
- pre-change backup of PD media env:
Card D3 — export and validate Serenity source databases
Definition of done:
- pre-cutover exports from Serenity are taken for both apps so restore/import flow can be tested before downtime
gamevaultPostgres dump exported from Serenity and integrity-checkedrommMariaDB dump exported from Serenity and integrity-checked- source app versions and DB container versions recorded alongside dumps
- Postgres collation-version warning captured as a post-import remediation item
- final cutover note recorded: these pre-cutover dumps are not the authoritative final state; a last quiesced export must be taken after the Serenity app is stopped during cutover and imported to PD before PD goes live
Execution notes (2026-05-25):
- successful pre-cutover exports were captured and staged under PD backup storage:
- latest verified dump set:
/mnt/tank/docker/backups/db-dumps/serenity-wave2/20260525-184917/ 20260525-184917-serenity-gamevault.pgcustom— 175290 bytes, sha2561ebd0286483e7f34dddc21076a7540c966a812d8891a8ed7a24247fe972d927d20260525-184917-serenity-romm.sql— 8526126 bytes, sha256c74a2d9d715f6c0982d707ba9f41174e19ba681a1debebde865e94e63271302b
- latest verified dump set:
- source runtime facts recorded during export validation:
- Serenity
postgresql15: PostgreSQL 15.18,gamevaulthas 18 public tables and ~12 MB logical size - Serenity
MariaDB-Official: MariaDB 12.2.2,rommhas 20 tables and ~12.41 MB logical size - the existing Serenity Postgres collation-version mismatch warning is still present and should be remediated after final import on PD
- Serenity
- GameVault restore-path validation succeeded:
- the pre-cutover Postgres dump restored cleanly into PD shared-postgres
- PD
gamevaulttarget currently shows the expected 18 public tables
- RomM restore-path validation exposed a blocker on the planned PD shared-mariadb target:
- importing the Serenity RomM dump into PD shared-mariadb failed at line 167 while creating
device_save_sync - MariaDB returned
ERROR 1005 (HY000): Can't create table romm.device_save_sync (errno: 121 "Duplicate key on write or update") - PD shared-mariadb is currently MariaDB 11.4.11 while the Serenity RomM source dump was produced from MariaDB 12.2.2
- treat this as a compatibility / target-selection issue, not a dump-corruption issue; the dump itself is populated and preserved in backup storage
- importing the Serenity RomM dump into PD shared-mariadb failed at line 167 while creating
- net result:
- D3 is complete for GameVault
- D3 is only partially complete for RomM until the MariaDB target strategy is revised and the restore path is proven
Card D4 — sync appdata for GameVault and RomM to PD staging paths
Definition of done:
- GameVault config/media/log paths copied to PD staging
- RomM config/assets/resources paths copied to PD staging
- large library mounts intentionally re-used from canonical storage instead of blindly duplicating data
- ownership/permissions on PD staging paths verified
Note:
- do not advance RomM cutover assumptions until the MariaDB target mismatch found in D3 is resolved; RomM may need a different PD DB target than the original shared-mariadb assumption
Card D5 — cut over GameVault to PD
Definition of done:
- Serenity GameVault stopped only for the final cutover window
- after GameVault is stopped on Serenity, a final quiesced
gamevaultPostgres export is taken and imported to the PD target DB so PD does not come up on stale data - PD GameVault starts against the PD target Postgres DB
- login, library visibility, metadata behavior, and save/upload paths verified
- public/LAN route updated if needed and verified
- Serenity
postgresql15kept in place but clearly marked retirement-ready if cutover succeeds
Card D6 — cut over RomM to PD
Definition of done:
- Serenity RomM stopped only for the final cutover window
- after RomM is stopped on Serenity, a final quiesced
rommMariaDB export is taken and imported to the PD target DB so PD does not come up on stale data - PD RomM starts against the PD target MariaDB DB
- UI, library visibility, assets/resources, background jobs, and metadata behavior verified
- public/LAN route updated if needed and verified
- Serenity
MariaDB-Officialkept in place but clearly marked retirement-ready if cutover succeeds
Card D7 — retire Serenity DB remnants after cooldown
Definition of done:
- GameVault and RomM remain healthy on PD through an observation window
- Serenity
postgresql15andMariaDB-Officialare stopped and removed only after successful PD validation - Serenity-side DB appdata is retained for cooldown/rollback, not deleted immediately
- docs and host inventory updated to show Serenity no longer carries those DB pairs
Open item that still needs verification
rerankermounts/mnt/user/appdate/reranker- verify whether
appdateis intentional before any future reranker move or cleanup
Expected result after wave 1
After wave 1, Serenity should still be alive for the workloads that currently justify it, but with much less misleading baggage:
- torrent/media-locality group still intact
- Newt still intact
- Technitium backup node still intact
- GameVault and RomM still live until their migration is prepared
- legacy Pi-hole gone
- dead Cloudflared gone
- stale created/exited clutter gone
That leaves a cleaner host and a safer runway for the later PD storage cutover and full Serenity retirement.
Wave 1 verification results (2026-05-25)
Verified during this planning pass:
- Serenity still has these live containers relevant to wave 1:
technitium-dns-pilotbinhex-official-piholepihole-serenityunbound-pihole-serenitykeepalived-pihole-serenityNewtUnraid-Cloudflared-Tunnel
- PD still runs the primary Technitium stack plus its own Pi-hole and Newt lane.
- From NOMAD,
/etc/resolv.confcurrently lists10.5.30.8,10.5.30.9, and10.5.30.10ahead of external fallback9.9.9.9. - From NOMAD,
digto10.5.30.8and10.5.30.10succeeded for public DNS resolution; same-host checks to10.5.30.9were unreliable, matching the existing macvlan caveat in the docs. Unraid-Cloudflared-Tunnelis still running, but repo docs already classify it as dead/stale and its container hasRestart=no.- Serenity
Newtmust not be treated as deadwood: operator confirmed Pangolin tunnels Serenity resources through Serenity's local Newt instead of routing from PD or NOMAD to Serenity resources over the Serenity LAN IP. - Live Serenity
Newtlogs still show repeated Pangolin health checks against stale10.5.1.5targets (7474,8785,8787,8788,8990,8457,5690,5454).
Operational implication:
- removing Cloudflared remains low-risk after one final dependency check
- removing the legacy Pi-hole stack remains appropriate
- removing Serenity
Newtis not appropriate during wave 1 - Pangolin target drift for Serenity-hosted resources should be repaired later by rehoming those resources to the correct Serenity site/local-path model instead of stale literal pre-migration IPs