- A key leaked into chat, into a doc, or into the homelab-config git repo.
- A consumer (briefing helper, Prowlarr, etc.) started rejecting auth.
- Routine rotation.
The *arr API keys live inside each container’s /config/config.xml (<ApiKey>...</ApiKey>). Rotating = generate a new key, write it back, restart the container, update any consumer that hardcoded the old value.
Generate new keys and rotate one at a time (so you can verify between):
# Generate a 32-char hex key
NEW_KEY=$(openssl rand -hex 16)
echo "$NEW_KEY"
# SSH to pve, edit Sonarr config.xml inside CT100
ssh pve "pct exec 100 -- bash -c 'sed -i.bak \"s|<ApiKey>[^<]*</ApiKey>|<ApiKey>$NEW_KEY</ApiKey>|\" /var/lib/docker/volumes/sonarr_config/_data/config.xml || docker exec sonarr sed -i.bak \"s|<ApiKey>[^<]*</ApiKey>|<ApiKey>$NEW_KEY</ApiKey>|\" /config/config.xml'"
# Restart Sonarr
ssh pve "pct exec 100 -- docker restart sonarr"
# Wait for it to come back, then verify the new key works
sleep 10
ssh pve "pct exec 100 -- curl -s 'http://localhost:8989/api/v3/system/status?apikey=$NEW_KEY'" | head -c 200
Repeat for Radarr (port 7878) and Lidarr (port 8686).
The daily-briefing helper (~/scripts/arr-briefing-data.py) extracts keys at runtime from config.xml via SSH, so it doesn’t need updating after rotation โ that’s the whole point of not hardcoding.
What does need updating:
| Consumer | Where to update |
|---|---|
| Prowlarr | http://192.168.8.100:9696 โ Settings โ Apps โ Sonarr/Radarr/Lidarr โ API Key field |
| Recyclarr | /opt/recyclarr/recyclarr.yml โ set new API keys, restart container |
| Any hardcoded use | grep -rln 'd792444549|b117993eb50|3dc17d20ca664' ~/Sync/ED ~/scripts โ find leftovers from the May 2026 leak |
| Bee Hub docs | None should hardcode โ but search anyway: grep -rln '<ApiKey>' ~/Sync/ED/homelab/bee_hub/content |
Run the briefing helper and confirm it pulls real data:
python3 ~/scripts/arr-briefing-data.py --hours 168 | head -30
Should return JSON with non-empty sonarr.imports, etc. If you get {"error": "api key: ..."}, the key didn’t actually rotate or the container hasn’t restarted.
~/Sync/ED/SECRETS.md with the new keys (or note that they’re extracted at runtime, depending on the entry). If this was a leak-driven rotation, remove the rotation entry from TASKS.md once consumers are updated.