Home server (10.0.0.3 / 192.168.86.69) — component-by-component
Every component running on this server, and what actually backs it up. Read these cards first — the scripts behind them are explained afterward.
DigitalOcean prod MySQL
- Runs at
- Remote — DigitalOcean managed DB, reached via SSH tunnel through the VPS
- Script
do-backup.sh- Location
/home/storageserver/scripts/do-backup.sh- Destination
/home/storageserver/backups/do_dump_*.sql.gz+ Google Drive atcag-backups/(the root of the folder — not insideserver/ordev/)- Schedule
- Hourly
prodmysql
- Runs at
- Docker container
prodmysql - Script
do-backup.sh(restore step) +server-backup.sh(volume tar)- Location
- see below
- Destination
- Local restore only, not a SQL dump of its own
- Schedule
- Hourly (refresh) / Daily 03:00 (volume tar)
System MySQL
mysql.service, port 3306- Runs at
- Host process, not Docker
- Script
server-backup.sh(Step 1)- Location
/home/troubleshoot/scripts/server-backup.sh- Destination
databases/mysql-prod-all.sql.gzin the combined archive + Drive- Schedule
- Daily 03:00
devmysql
- Runs at
- Docker container
devmysql - Script
devmysql-backup.sh+ alsoserver-backup.sh(Steps 1 & 2)- Location
/home/troubleshoot/scripts/devmysql-backup.sh- Destination
/home/backups/storagebackup/dev/+ Drive; also in the combined server archive- Schedule
- Daily 02:00 (dedicated) / Daily 03:00 (combined)
servermanager-db
- Runs at
- Docker container
servermanager-db - Script
server-backup.sh(Step 1, explicitpg_dumpall)- Location
/home/troubleshoot/scripts/server-backup.sh- Destination
databases/postgres-all.sql.gzin the combined archive + Drive- Schedule
- Daily 03:00
postgres-dev-public
pg_dump — script only names servermanager-db; caught only by the generic all-volumes tar- Runs at
- Docker container
postgres-dev-public - Script
server-backup.sh(Step 2 only)- Location
/home/troubleshoot/scripts/server-backup.sh- Destination
- Raw volume tar in the combined archive + Drive
- Schedule
- Daily 03:00
postgres-prod-public
- Runs at
- Docker container
postgres-prod-public - Script
server-backup.sh(Step 2 only)- Location
/home/troubleshoot/scripts/server-backup.sh- Destination
- Raw volume tar in the combined archive + Drive
- Schedule
- Daily 03:00
MS SQL Server
mssql-server.service- Runs at
- Host process, not Docker, not under
/home - Script
- — none —
All Docker named volumes
- Runs at
- Docker
- Script
server-backup.sh(Step 2)- Location
/home/troubleshoot/scripts/server-backup.sh- Destination
docker-volumes/<name>.tar.gzin the combined archive + Drive- Schedule
- Daily 03:00
Nginx configs
/etc/nginx + container bind-mounted nginx.conf files- Runs at
- Host + Docker bind mounts
- Script
server-backup.sh(Step 3)- Location
/home/troubleshoot/scripts/server-backup.sh- Destination
nginx-configs/in the combined archive + Drive- Schedule
- Daily 03:00
System configs
- Runs at
- Host
/etc/ - Script
server-backup.sh(Step 4)- Location
/home/troubleshoot/scripts/server-backup.sh- Destination
system-configs/in the combined archive + Drive- Schedule
- Daily 03:00
/home project folders
- Runs at
/home/<user>/- Script
server-backup.sh(Step 5)- Location
/home/troubleshoot/scripts/server-backup.sh- Destination
home/<user>.tar.gzin the combined archive + Drive- Schedule
- Daily 03:00
mediaserver
/home/mediaserver/appmedia/ — actual uploaded media files, incl. cagmedia uploads- Runs at
/home/mediaserver/- Script
server-backup.sh(Step 5, catch-all loop)- Location
/home/troubleshoot/scripts/server-backup.sh- Destination
home/mediaserver.tar.gzin the combined archive + Drive- Schedule
- Daily 03:00
/home/iapp
- Runs at
/home/iapp/- Script
- — none —
CAG Monitor
cag-monitor.py, watchdog, wiki docsstorageserver folder tar (only storageserver/backups/ is excluded)- Runs at
/home/storageserver/monitoring/- Script
server-backup.sh(Step 5)- Location
/home/troubleshoot/scripts/server-backup.sh- Destination
home/storageserver.tar.gzin the combined archive + Drive- Schedule
- Daily 03:00
VPS network/firewall configs
- Runs at
- Remote —
77.37.62.240, via SSH - Script
server-backup.sh(Step 6)- Location
/home/troubleshoot/scripts/server-backup.sh- Destination
proxy/in the combined archive + Drive- Schedule
- Daily 03:00
System state snapshot
- Runs at
- Host
- Script
server-backup.sh(Step 7)- Location
/home/troubleshoot/scripts/server-backup.sh- Destination
state/in the combined archive + Drive- Schedule
- Daily 03:00
- MS SQL Server has no backup at all — it isn't Docker and isn't under
/home, so none of the three scripts touch it. postgres-dev-publicandpostgres-prod-publiconly get a raw Docker-volume tar, not a portablepg_dump— restorable, but only by attaching the exact same Postgres version/container, not by importing a SQL file.server-backup.sh'sPG_CONTAINERvariable only namesservermanager-db./home/iappis silently skipped — worth checking what's in there and whether it matters.
Home server — the three scripts behind the table above
do-backup.sh — hourly, 7-day local retention
Opens an SSH tunnel through the VPS to reach the DigitalOcean-managed MySQL, dumps all 8 prod
databases, verifies the dump, wipes the local prodmysql Docker container, restores
the fresh dump into it, then uploads the dump to gdrive:cag-backups/ and prunes
Drive copies older than 30 days.
/home/storageserver/scripts/do-backup.sh Log: /home/storageserver/logs/backup.log Cron: 0 * * * * (sysadmin crontab)
devmysql-backup.sh — daily 02:00, 7-day local retention
mysqldump --all-databases against the devmysql container, gzipped,
uploaded to gdrive:cag-backups/dev/, 30-day Drive prune.
/home/troubleshoot/scripts/devmysql-backup.sh Log: /home/backups/storagebackup/dev/backup.log Cron: 0 2 * * * (sysadmin crontab)
server-backup.sh — daily 03:00, 7-day local retention
The broadest job, in 8 explicit steps: (1) database dumps — system MySQL + devmysql +
servermanager-db Postgres only, (2) every Docker named volume as a raw tar,
(3) nginx configs, (4) system configs, (5) /home project folders (with dev-artifact
exclusions for the named folders, but a size-unaware catch-all for anything else, including
mediaserver), (6) VPS network/firewall configs pulled over SSH, (7) a system state
snapshot, (8) manifest + combine + upload to gdrive:cag-backups/server/.
/home/troubleshoot/scripts/server-backup.sh Output: /home/backups/storagebackup/server/server_backup_YYYYMMDD_HHMMSS.tar.gz Also uploaded to: gdrive:cag-backups/server/ Cron: 0 3 * * * (sysadmin crontab) Usage: ./server-backup.sh [--list|--clean|--size]
restore-mysqlprod240.sh — manual only, not scheduled
Pushes the latest complete home-server prod dump to the VPS's standalone
mysqlprod240 MySQL instance. Run by hand when the VPS copy needs refreshing.
/home/storageserver/scripts/restore-mysqlprod240.sh [dump_file] Log: /home/storageserver/logs/restore-mysqlprod240.log
Cleanup jobs — daily 04:00–04:50
| Time | Script | Scope |
|---|---|---|
| 04:00 | prod-backup-cleanup.sh | Local prod dumps |
| 04:10 | devsql-backup-cleanup.sh | Local dev dumps |
| 04:20 | server-backup-cleanup.sh | Local server backups |
| 04:30 | prod-backup-cleanup-drive.sh | Google Drive prod copies |
| 04:40 | devsql-backup-cleanup-drive.sh | Google Drive dev copies |
| 04:50 | server-backup-cleanup-drive.sh | Google Drive server copies |
log-cleanup.sh — weekly, Sunday 05:00
Trims log files to the last 30 days of entries. Not a backup job, shares the same crontab block.
VPS (77.37.62.240)
mysqlprod240
- By what
- Populated by
restore-mysqlprod240.sh, run from the home server - Schedule
- Not scheduled
postgres-dev-public / postgres-prod-public
- By what
- — none —
Everything else on this box
- By what
- — none —
The VPS runs no backup jobs of its own — every mention of it above is either things
pulled from it by the home server's server-backup.sh (network/firewall
config only), or things pushed to it manually.
prod-gurukulam (85.209.95.95)
Local MySQL — schoolmsysdb
apiuser / api1- By what
- — none —
Local MySQL — mydatabase
- By what
- — none —
App jars, deploy scripts, nginx configs
- By what
- Not versioned or backed up server-side; recoverable by redeploying from source
App logs
- By what
- Rotated (not backed up) by
rotate_logs.sh - Schedule
- Daily 23:59
schoolmsysdb database is
the most important gap — if this server is lost, that data is gone with no recovery path.
Google Drive — current status
caginfoapp@gmail.com — this project's
dedicated service account (also used as the SMTP sender for monitoring alerts in
watchdog.py and for mail in cagdevattendanceapi's config).
It owns the cag-backups folder that every Drive upload above targets.
Not a personal account — no password is recorded here; whoever manages this account's
credentials should keep them in a password manager, not in this repo.
/home/sysadmin/.config/rclone/rclone.conf (remote name gdrive) had
expired (invalid_grant), silently breaking the offsite copy for every
home-server job — every run failed in the surviving logs going back to
2026-07-10 (the oldest entry still on disk; log-cleanup.sh trims logs to
~30 days weekly, so the true start date is unknown and could be earlier). This lines up with
what Google Drive itself shows: the server folder's newest file was from
April 23 — the Drive-side cleanup scripts share the same broken token, so old files
never got pruned either, which is why that stale file was still sitting there instead of
being deleted by the 30-day retention rule. Local backups were unaffected throughout — this
only broke the offsite copy. Reconnected via rclone config reconnect gdrive:,
signed in as caginfoapp@gmail.com. Verified end-to-end by running
devmysql-backup.sh manually — it uploaded successfully and the file was
confirmed present in gdrive:cag-backups/dev/ via rclone lsl.
rclone.conf (client_id/client_secret) had a
redirect_uri_mismatch — it was never set up with
http://127.0.0.1:53682/ as an authorized redirect URI in Google Cloud Console,
so any future reconnect using that custom app would fail the same way. Worked around by
removing client_id/client_secret from rclone.conf
entirely, so it now uses rclone's own built-in OAuth app instead (functionally identical —
same Drive account, just a different app name on the consent screen). Old config backed up
at rclone.conf.bak-20260810 on the home server.
How to redo this in future (requires an interactive browser — cannot be done via a plain SSH command):
ssh -L 53682:localhost:53682 home-proxy-server rclone config reconnect gdrive: --config /home/sysadmin/.config/rclone/rclone.conf # open the printed 127.0.0.1:53682 URL in a PRIVATE/incognito browser window # on your Mac, and sign in specifically as caginfoapp@gmail.com (not your # personal account) — otherwise Google will default to whichever account # is already logged in. # verify: rclone lsd gdrive: --config /home/sysadmin/.config/rclone/rclone.conf
gdrive: remote, so a single reconnect fixes all
of them at once — no per-script changes needed.
External USB drive — third leg of 3-2-1
server_backup_*.tar.gz archives) to ext4, mounted permanently at
/mnt/cagbackup.
Commands to check the mount
# is it mounted, and how much space is used/free? df -h /mnt/cagbackup # filesystem + mount details lsblk -f mount | grep sdb # fstab entry (persists across reboots) grep cagbackup /etc/fstab
Commands to browse the synced files
# top-level folders ls -la /mnt/cagbackup/backup/ # size of each folder du -sh /mnt/cagbackup/backup/*/ # a specific folder ls -lh /mnt/cagbackup/backup/dodb/ # everything, recursively find /mnt/cagbackup/backup/ -maxdepth 2 -type f
Folder mapping
dodb
- Synced from
/home/storageserver/backups/— DigitalOcean prod dumps
homeserverdb
- Synced from
/home/backups/storagebackup/dev/— devmysql dumps (the home server's own DB)
server
- Synced from
/home/backups/storagebackup/server/— full disaster-recovery archives
scripts
- Synced from
/home/storageserver/scripts/+/home/troubleshoot/scripts/, kept in separate subfolders
media
- Synced from
/home/mediaserver/appmedia/
component-backup
- Note
- Nothing currently produces standalone per-component output to sync here — see
README.txtin that folder
sync-external-drive.sh — daily 05:00
Rsyncs the folders above from their existing local backup locations onto the USB drive.
Doesn't create any backups itself — just mirrors what do-backup.sh,
devmysql-backup.sh, and server-backup.sh already produced. Skips
entirely (exit 1) if the drive isn't mounted, so an unplugged drive won't break anything
else.
/home/troubleshoot/scripts/sync-external-drive.sh Log: /home/storageserver/logs/sync-external-drive.log Cron: 0 5 * * * (sysadmin crontab)
- I/O contention: running this at the same time as a large Google Drive upload
(or
server-backup.shitself) causes real disk I/O contention and noticeably slows the whole server — observed directly when a manualserver-backup.shtest run and this sync overlapped (load average hit 4.2, multiple processes stuck in uninterruptible I/O wait). - Schedule collision: originally scheduled for 04:00, which collided exactly
with
prod-backup-cleanup.sh(also 04:00) — the cleanup job deleted an old dump file at the same instant this script'sdodbrsync was reading the directory, causing a harmless but avoidablefile has vanishedwarning. Moved to 05:00, after all six cleanup jobs (04:00–04:50) finish.