Playbook: MongoDB Restore Drill — Troubleshooting
Reference for diagnosing failures in scripts/mongodb-restore-drill-cron.sh and
scripts/mongodb-restore-drill.sh.
First successful drill run: 2026-07-01 (w3_db + w4_db, backup 2026-06-15). All issues below were discovered and fixed before that run.
Issue 1 — sops exec-env fails on *.env.sops extension
Symptom: Drill exits with:
Error unmarshalling input json: invalid character 'C' looking for beginning of value
Root cause: sops exec-env detects file format from extension only. The extension
*.env.sops is NOT recognized as dotenv (only *.env is recognized). SOPS falls back
to JSON parsing; the file starts with CF_API_TOKEN=C... → C is invalid JSON.
The --input-type flag is not available on the exec-env subcommand.
Fix (in mongodb-restore-drill-cron.sh): Replace sops exec-env with explicit decrypt:
_tmpenv=$(mktemp)
chmod 600 "${_tmpenv}"
trap 'rm -f "${_tmpenv}"' EXIT
sops --decrypt --input-type dotenv --output-type dotenv "${MONITORING_SOPS}" >> "${_tmpenv}"
sops --decrypt --input-type dotenv --output-type dotenv "${BMS_SERVERS_SOPS}" >> "${_tmpenv}"
# ... then inject into env (see Issue 2 and 3)Issue 2 — source fails on values with special characters
Symptom: After decrypting SOPS, env injection silently fails. Some variables not exported. The drill script exits with “SKIPPED (unconfigured)” even though SOPS decrypt succeeded.
Root cause: set -a; source "$tmpenv"; set +a calls source which parses each line
as a bash statement. Values like SUPABASE_GRAFANA_PASSWORD=.nSw^Jkn)Y?-(npgCwSW...
contain ) which bash interprets as syntax (end of subshell).
Fix: Use a safe line-by-line while loop:
while IFS= read -r _line; do
[[ -z "${_line}" || "${_line}" == \#* ]] && continue
_k="${_line%%=*}"
_v="${_line#*=}"
[[ "${_k}" =~ ^[a-zA-Z_][a-zA-Z_0-9]*$ ]] || continue # Issue 3
export "${_k}"="${_v}"
done < "${_tmpenv}"Issue 3 — Invalid bash identifiers in dotenv file
Symptom:
export: 'p24-infra-1h-check-claude-discord=...': not a valid identifier
Root cause: Some keys in monitoring.env.sops have hyphens (e.g.
p24-infra-1h-check-claude-discord) which are valid in dotenv but not as bash variable
names.
Fix: Guard with regex before export (included in Issue 2 fix):
[[ "${_k}" =~ ^[a-zA-Z_][a-zA-Z_0-9]*$ ]] || continueIssue 4 — MONGODB_ADMIN_PASSWORD not set for --compare-prod
Symptom: Drill logs “WARNING: MONGODB_ADMIN_PASSWORD not set — skipping production
comparison”. The --compare-prod step is silently skipped.
Root cause: bms-servers.env.sops uses key mongodb_rs0_admin_password (lowercase)
but the drill script expects MONGODB_ADMIN_PASSWORD. After the line-by-line injection,
only mongodb_rs0_admin_password is exported.
Fix (in mongodb-restore-drill-cron.sh): Add mapping after env injection:
export MONGODB_ADMIN_PASSWORD="${mongodb_rs0_admin_password:-}"
export MONGODB_ADMIN_USER="${mongodb_rs0_admin_user:-admin}"Issue 5 — _aws_s3 helper: redundant s3 prefix
Symptom:
aws: [ERROR]: An error occurred (ParamValidation): argument subcommand: Found invalid choice 's3'
Root cause: _aws_s3() already calls aws s3 .... All call sites were passing s3
as the first argument (e.g. _aws_s3 s3 ls), resulting in aws s3 s3 ls which fails
because s3 is not a valid subcommand of aws s3.
Fix: Change all call sites to pass only the subcommand:
_aws_s3 ls "s3://bucket/prefix/" # not: _aws_s3 s3 ls ...
_aws_s3 cp src dst # not: _aws_s3 s3 cp ...
_aws_s3 sync src dst # not: _aws_s3 s3 sync ...Issue 6 — Wrong Wasabi backup prefix
Symptom: STEP 1: Locating latest backup on Wasabi finds nothing, drill exits
immediately with “No backup found”.
Root cause: BACKUP_PREFIX="mongodb/full" in mongodb-restore-drill.sh but actual
backups are at s3://p24-infra/mongodb-backups/.
To list the actual structure:
aws s3 ls s3://p24-infra/mongodb-backups/ \
--endpoint-url https://s3.eu-central-2.wasabisys.com --region eu-central-2
# Expected: w3_db-YYYY-MM-DD.mongodump.gz, w4_db-YYYY-MM-DD.mongodump.gzFix: BACKUP_PREFIX="mongodb-backups" and update date extraction to use grep -Eo
(extracts date from filenames, not directory prefixes).
Issue 7 — Wrong restore format (directory vs archive)
Symptom: After the drill completes restore, collection counts are all 0 or the restore step fails silently.
Root cause: mongodb-restore-drill.sh was written for mongodump --out DIR --gzip
format (per-collection .bson.gz files inside a directory). But actual backups are created
with mongodump --archive=FILE --gzip format (single .mongodump.gz per database).
Fix: Restore with --archive flag:
# Inside Docker container:
mongorestore --archive="/tmp/${DB}.mongodump.gz" --gzip --drop --preserveUUID
# NOT: mongorestore --db "$DB" --dir "/dump/${DB}" --gzip ...Issue 8 — aws CLI not installed on bms-4
Symptom: bash: aws: command not found during STEP 1 or 2.
Fix:
curl -s https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip -o /tmp/awscliv2.zip
cd /tmp && unzip -q awscliv2.zip
./aws/install --install-dir /usr/local/aws-cli --bin-dir /usr/local/bin
aws --version # verifyRunning the drill manually
# On bms-4 as root:
sudo -u claude-runner /opt/p24-infra/scripts/mongodb-restore-drill-cron.sh
# Logs: /tmp/mongo-drill-$PID/drill.log
# Duration: ~90-120 min (w3_db 7.3GB + w4_db 2.9GB)Related
scripts/mongodb-restore-drill.sh— main drill logicscripts/mongodb-restore-drill-cron.sh— wrapper for SOPS injection + alertingscripts/mongodb-backup.sh— backup script on bms-3 (Wasabi, weekly → daily after #2367)docs/playbooks/mongodb-rs0-full-restore.md— full DR playbook (Scenario B)- Issue #2367 — bms-3 SSH + daily cron fix
- Issue #2148 — backup gap tracking
- PR #2371 — all drill script fixes