S174-IMP-05 — Code Backup to Google Drive
S174-IMP-05 — Code Backup to Google Drive
Date: 2026-04-08
Scope: Implement production code-backup to Google Drive for web-test and agent-data-test, with cron, verify, Kuma monitors, and restore-first docs.
Rules Loaded
.claude/skills/incomex-rules.mdsearch_knowledge("operating rules SSOT")search_knowledge("hiến pháp v4.0 constitution")search_knowledge("s174-inv-04 code backup gdrive prep")search_knowledge("backup-to-gdrive.sh pg backup kuma heartbeat")search_knowledge("vps operating rules architecture health reports")
3 Câu Tuyên Ngôn
- Vĩnh viễn: code backup phải độc lập với data backup, dùng lại transport tốt nhưng không piggyback mù vào script cũ.
- Nhầm được không: 2 repo phải tách folder GDrive, tách monitor Kuma, tách filename, và verify size sau upload.
- 100% tự động: cron chạy đúng giờ VN, script tự đóng gói, upload, verify, retention, heartbeat, không cần thao tác tay sau khi triển khai xong.
1. Executive Summary
Verdict
ĐÃ TRIỂN KHAI XONG.
Production hiện có:
- script backup code riêng cho 2 repo:
/opt/incomex/scripts/code-backup-to-gdrive.sh - helper provision monitor Kuma:
/opt/incomex/scripts/ensure-code-backup-kuma-monitors.sh - cron chạy đúng
08:00, 12:00, 15:00, 20:00theoAsia/Ho_Chi_Minh - 2 push monitor Kuma riêng:
Code Backup - web-test(push_token=code-backup-web-test,id=15)Code Backup - agent-data-test(push_token=code-backup-agent-data-test,id=16)
- upload thật lên đúng 2 folder GDrive user chốt
- verify thật 2 vòng chạy tay, có archive local, file remote, size khớp, log success, heartbeat success, local retention chạy đúng
Kết luận ngắn
- Thiết kế restore-first đã hoạt động đúng mục tiêu: archive có
.git/HEADvàBACKUP-MANIFEST.txt. - Dùng
CRON_TZ=Asia/Ho_Chi_Minhthay vì mapping UTC/CEST, vì cron trên VPS xác nhận hỗ trợ cú pháp này. - Retention đang là
56 remote / 1 local per repo. - Có 1 residual risk mới phát hiện: file
/opt/incomex/.uptime-kuma-admin-passđang stale, nên helper Kuma phải dùngloginByTokendựa trênjwtSecret+ password hash trong DB thay vì password file đó. Tôi không sửa password file vì ngoài scope nhiệm vụ này.
2. Những Gì Đã Triển Khai
2.1 Script production
Đã triển khai:
/opt/incomex/scripts/code-backup-to-gdrive.sh/opt/incomex/scripts/ensure-code-backup-kuma-monitors.sh
Evidence:
$ ssh root@38.242.240.89 'sha256sum /opt/incomex/scripts/code-backup-to-gdrive.sh && ls -l /opt/incomex/scripts/code-backup-to-gdrive.sh && bash -n /opt/incomex/scripts/code-backup-to-gdrive.sh'
20762cfb3912f686b0b3bd3b56b66b23f705a5cc98731ef90752e15e803d9e5f /opt/incomex/scripts/code-backup-to-gdrive.sh
-rwxr-xr-x 1 root root 8577 Apr 8 16:15 /opt/incomex/scripts/code-backup-to-gdrive.sh
$ ssh root@38.242.240.89 'bash -n /opt/incomex/scripts/ensure-code-backup-kuma-monitors.sh && sha256sum /opt/incomex/scripts/ensure-code-backup-kuma-monitors.sh'
5aa1546ff07228f922cbf2397b21fabeb0396be052cf3a06fe39ed7eff27fc00 /opt/incomex/scripts/ensure-code-backup-kuma-monitors.sh
Script backup đã cấu hình đúng folder ID, push token, retention:
$ ssh root@38.242.240.89 'grep -nE "WEB_FOLDER_ID|AGENT_FOLDER_ID|WEB_PUSH_TOKEN|AGENT_PUSH_TOKEN|REMOTE_KEEP|LOCAL_KEEP" /opt/incomex/scripts/code-backup-to-gdrive.sh'
18:REMOTE_KEEP=56
19:LOCAL_KEEP=1
23:WEB_FOLDER_ID="1K1atlFO-bkO7KoukEKPDFbZq7wvQd1Mb"
24:WEB_PUSH_TOKEN="code-backup-web-test"
27:AGENT_FOLDER_ID="1__qHH5ndV0FqC73nJu5_kUN5sd46M-AN"
28:AGENT_PUSH_TOKEN="code-backup-agent-data-test"
269: backup_repo "web-test" "${WEB_REPO_PATH}" "${WEB_FOLDER_ID}" "${WEB_PUSH_TOKEN}" || overall_rc=1
270: backup_repo "agent-data-test" "${AGENT_REPO_PATH}" "${AGENT_FOLDER_ID}" "${AGENT_PUSH_TOKEN}" || overall_rc=1
2.2 Kuma monitors
Helper provision monitor đã chạy thật và tạo 2 monitor:
$ ssh root@38.242.240.89 '/opt/incomex/scripts/ensure-code-backup-kuma-monitors.sh'
{"ok":true,"event":"add","monitorID":15,"name":"Code Backup - web-test","pushToken":"code-backup-web-test","interval":46800}
id name type push_token interval active
-- ---------------------- ---- -------------------- -------- ------
15 Code Backup - web-test push code-backup-web-test 46800 1
monitor_id notification_id
---------- ---------------
15 2
{"ok":true,"event":"add","monitorID":16,"name":"Code Backup - agent-data-test","pushToken":"code-backup-agent-data-test","interval":46800}
id name type push_token interval active
-- ----------------------------- ---- --------------------------- -------- ------
16 Code Backup - agent-data-test push code-backup-agent-data-test 46800 1
monitor_id notification_id
---------- ---------------
16 2
Kuma notification target thực tế là notification_id=2, tức Telegram-Jack.
2.3 Cron production
Đã cài cron production bằng crontab/production.crontab.
Dry-run syntax:
$ cat crontab/production.crontab | ssh root@38.242.240.89 'crontab -n -'
The syntax of the crontab file was successfully checked.
Cron thực tế sau khi cài:
$ ssh root@38.242.240.89 'crontab -l | sed -n "50,66p"'
# VPS Full Backup to Google Drive (Mon+Thu 3AM Hanoi = Sun+Wed 20:00 UTC)
# VPS Full Backup to Google Drive (nightly 3AM Hanoi = 20:00 UTC)
0 20 * * * /opt/incomex/scripts/backup-to-gdrive.sh >> /opt/incomex/logs/backup-gdrive.log 2>&1
# S174-IMP-05 — Code Backup to Google Drive (08:00/12:00/15:00/20:00 ICT, script self-logs)
CRON_TZ=Asia/Ho_Chi_Minh
0 8,12,15,20 * * * /opt/incomex/scripts/code-backup-to-gdrive.sh
CRON_TZ=Europe/Berlin
Kết luận đã xác minh:
- không cần map giờ VN sang UTC tay
- cron runtime hỗ trợ
CRON_TZ - lịch thực chạy đúng theo
ICT
3. Verify Với Giá Trị Cụ Thể
3.1 Chạy tay vòng 1
Excerpt log thực tế:
$ ssh root@38.242.240.89 'tail -n 80 /opt/incomex/logs/code-backup-gdrive.log'
[2026-04-08 16:26:40 CEST] [START][web-test] file=web-test_20260408-2126_ICT.tar.gz repo=/opt/incomex/docker/nuxt-repo
[2026-04-08 16:26:44 CEST] [ARCHIVE][web-test] file=/opt/incomex/backups/code/web-test/web-test_20260408-2126_ICT.tar.gz bytes=10586994
2026/04/08 16:26:47 INFO : web-test_20260408-2126_ICT.tar.gz: Copied (new)
[2026-04-08 16:26:47 CEST] [VERIFY][web-test] remote= 10586994 2026-04-08 16:26:44.554000000 web-test_20260408-2126_ICT.tar.gz
[2026-04-08 16:26:49 CEST] [DONE][web-test] file=web-test_20260408-2126_ICT.tar.gz local_bytes=10586994 remote_bytes=10586994 remote_count=1
[2026-04-08 16:26:49 CEST] [START][agent-data-test] file=agent-data-test_20260408-2126_ICT.tar.gz repo=/opt/incomex/docker/agent-data-repo
[2026-04-08 16:26:54 CEST] [ARCHIVE][agent-data-test] file=/opt/incomex/backups/code/agent-data-test/agent-data-test_20260408-2126_ICT.tar.gz bytes=40411096
2026/04/08 16:26:59 INFO : agent-data-test_20260408-2126_ICT.tar.gz: Copied (new)
[2026-04-08 16:26:59 CEST] [VERIFY][agent-data-test] remote= 40411096 2026-04-08 16:26:54.500000000 agent-data-test_20260408-2126_ICT.tar.gz
[2026-04-08 16:27:01 CEST] [DONE][agent-data-test] file=agent-data-test_20260408-2126_ICT.tar.gz local_bytes=40411096 remote_bytes=40411096 remote_count=1
[2026-04-08 16:27:01 CEST] [CODE-BACKUP] completed successfully
3.2 Chạy tay vòng 2 để verify retention
$ ssh root@38.242.240.89 'tail -n 60 /opt/incomex/logs/code-backup-gdrive.log'
[2026-04-08 16:28:33 CEST] [START][web-test] file=web-test_20260408-2128_ICT.tar.gz repo=/opt/incomex/docker/nuxt-repo
[2026-04-08 16:28:35 CEST] [ARCHIVE][web-test] file=/opt/incomex/backups/code/web-test/web-test_20260408-2128_ICT.tar.gz bytes=10586997
2026/04/08 16:28:38 INFO : web-test_20260408-2128_ICT.tar.gz: Copied (new)
[2026-04-08 16:28:39 CEST] [VERIFY][web-test] remote= 10586997 2026-04-08 16:28:35.473000000 web-test_20260408-2128_ICT.tar.gz
[2026-04-08 16:28:39 CEST] [RETENTION][LOCAL][web-test] deleted=/opt/incomex/backups/code/web-test/web-test_20260408-2126_ICT.tar.gz
[2026-04-08 16:28:40 CEST] [DONE][web-test] file=web-test_20260408-2128_ICT.tar.gz local_bytes=10586997 remote_bytes=10586997 remote_count=2
[2026-04-08 16:28:40 CEST] [START][agent-data-test] file=agent-data-test_20260408-2128_ICT.tar.gz repo=/opt/incomex/docker/agent-data-repo
[2026-04-08 16:28:44 CEST] [ARCHIVE][agent-data-test] file=/opt/incomex/backups/code/agent-data-test/agent-data-test_20260408-2128_ICT.tar.gz bytes=40411058
2026/04/08 16:28:49 INFO : agent-data-test_20260408-2128_ICT.tar.gz: Copied (new)
[2026-04-08 16:28:50 CEST] [VERIFY][agent-data-test] remote= 40411058 2026-04-08 16:28:44.353000000 agent-data-test_20260408-2128_ICT.tar.gz
[2026-04-08 16:28:50 CEST] [RETENTION][LOCAL][agent-data-test] deleted=/opt/incomex/backups/code/agent-data-test/agent-data-test_20260408-2126_ICT.tar.gz
[2026-04-08 16:28:51 CEST] [DONE][agent-data-test] file=agent-data-test_20260408-2128_ICT.tar.gz local_bytes=40411058 remote_bytes=40411058 remote_count=2
[2026-04-08 16:28:51 CEST] [CODE-BACKUP] completed successfully
Kết luận đã xác minh:
- local retention
keep=1chạy đúng cho cả 2 repo - file mới KHÔNG bị xóa nhầm
- remote giữ cả 2 bản mới vì
REMOTE_KEEP=56
3.3 File thật trên GDrive
Folder web-test:
$ ssh root@38.242.240.89 'rclone lsl gdrive-backup: --drive-root-folder-id 1K1atlFO-bkO7KoukEKPDFbZq7wvQd1Mb | sort'
10586994 2026-04-08 16:26:44.554000000 web-test_20260408-2126_ICT.tar.gz
10586997 2026-04-08 16:28:35.473000000 web-test_20260408-2128_ICT.tar.gz
Folder agent-data-test:
$ ssh root@38.242.240.89 'rclone lsl gdrive-backup: --drive-root-folder-id 1__qHH5ndV0FqC73nJu5_kUN5sd46M-AN | sort'
40411058 2026-04-08 16:28:44.353000000 agent-data-test_20260408-2128_ICT.tar.gz
40411096 2026-04-08 16:26:54.500000000 agent-data-test_20260408-2126_ICT.tar.gz
3.4 Local archive và .git
Local chỉ còn bản mới nhất sau retention:
$ ssh root@38.242.240.89 'ls -lh /opt/incomex/backups/code/web-test /opt/incomex/backups/code/agent-data-test'
/opt/incomex/backups/code/agent-data-test:
total 39M
-rw------- 1 root root 39M Apr 8 16:28 agent-data-test_20260408-2128_ICT.tar.gz
/opt/incomex/backups/code/web-test:
total 11M
-rw------- 1 root root 11M Apr 8 16:28 web-test_20260408-2128_ICT.tar.gz
Archive có .git và manifest:
$ ssh root@38.242.240.89 'tar -tzf /opt/incomex/backups/code/web-test/web-test_20260408-2128_ICT.tar.gz | grep -E "^(web-test/\.git/HEAD|web-test/BACKUP-MANIFEST.txt)$"'
web-test/BACKUP-MANIFEST.txt
web-test/.git/HEAD
$ ssh root@38.242.240.89 'tar -tzf /opt/incomex/backups/code/agent-data-test/agent-data-test_20260408-2128_ICT.tar.gz | grep -E "^(agent-data-test/\.git/HEAD|agent-data-test/BACKUP-MANIFEST.txt)$"'
agent-data-test/BACKUP-MANIFEST.txt
agent-data-test/.git/HEAD
3.5 Kuma heartbeat thực tế
$ ssh root@38.242.240.89 'sqlite3 -header -column /opt/incomex/uptime-kuma/kuma.db "select m.id,m.name,h.status,h.msg,h.ping,h.time from monitor m left join heartbeat h on h.id = (select id from heartbeat where monitor_id = m.id order by time desc limit 1) where m.id in (15,16) order by m.id;"'
id name status msg ping time
-- ----------------------------- ------ -------------------------------------- -------- -----------------------
15 Code Backup - web-test 1 OK_repo=web-test_bytes=10586997 10586997 2026-04-08 14:28:39.728
16 Code Backup - agent-data-test 1 OK_repo=agent-data-test_bytes=40411058 40411058 2026-04-08 14:28:51.205
Kết luận đã xác minh:
- 2 monitor đều đã nhận heartbeat thật
status=1cho cả 2pingđúng bằng archive bytes của run gần nhất
4. Paths / Folders / Monitor / Lịch Thực Tế
Paths
- Repo nguồn:
/opt/incomex/docker/nuxt-repo/opt/incomex/docker/agent-data-repo
- Script:
/opt/incomex/scripts/code-backup-to-gdrive.sh/opt/incomex/scripts/ensure-code-backup-kuma-monitors.sh
- Local archive root:
/opt/incomex/backups/code/web-test/opt/incomex/backups/code/agent-data-test
- Log:
/opt/incomex/logs/code-backup-gdrive.log
GDrive folders
web-test-> folder id1K1atlFO-bkO7KoukEKPDFbZq7wvQd1Mbagent-data-test-> folder id1__qHH5ndV0FqC73nJu5_kUN5sd46M-AN
Filename format
web-test_YYYYMMDD-HHMM_ICT.tar.gzagent-data-test_YYYYMMDD-HHMM_ICT.tar.gz
Kuma monitors
Code Backup - web-test/push_token=code-backup-web-test/id=15Code Backup - agent-data-test/push_token=code-backup-agent-data-test/id=16
Cron
CRON_TZ=Asia/Ho_Chi_Minh0 8,12,15,20 * * * /opt/incomex/scripts/code-backup-to-gdrive.sh
5. File Mẫu Đã Upload
Đã upload thật và verify:
web-test_20260408-2126_ICT.tar.gzweb-test_20260408-2128_ICT.tar.gzagent-data-test_20260408-2126_ICT.tar.gzagent-data-test_20260408-2128_ICT.tar.gz
Latest verified sample:
web-test_20260408-2128_ICT.tar.gz->10,586,997 bytesagent-data-test_20260408-2128_ICT.tar.gz->40,411,058 bytes
6. Rủi Ro Còn Lại / Unknowns
Đã xác minh
- Script, cron, monitor, upload, log, heartbeat, local retention đều chạy thật.
- Archive chứa
.gitnên restore repo usable về mặt Git metadata.
Chưa xác minh / ngoài scope
REMOTE_KEEP=56chưa được exercise đủ 57 bản để chứng minh remote delete path trong production thật.- Chưa chạy restore thật lên VPS production, vì mission này chỉ triển khai backup + tài liệu restore, không phải DR drill.
/opt/incomex/.uptime-kuma-admin-passkhông còn auth được vào Kuma. Đây là finding mới, không block runtime backup hiện tại vì helper đã dùngloginByToken, nhưng nên ghi nhận để tránh hiểu nhầm sau này.- Data backup cũ
/opt/incomex/scripts/backup-to-gdrive.shvẫn có nhược điểm cũ (double-write log, comment timezone drift). Tôi không sửa vì ngoài scope.
7. Hướng Dùng Backup Này Để Phục Hồi VPS / Sync Ngược GitHub
Use case nhanh nhất:
- Dùng
rclonetải archive mới nhất từ đúng folder ID của repo cần phục hồi. - Giải nén archive về đúng path repo cũ trên VPS.
- Kiểm tra
git status,git rev-parse HEAD,.git/HEAD. - Sau khi môi trường ổn định, có thể set lại remote GitHub và
git pushlên branch hoặc main theo quy trình kiểm soát.
Runbook cụ thể nằm ở file riêng:
reports/s174-imp-05-code-backup-gdrive-restore.md
8. Appendix — Commands + Outputs
8.1 Cron syntax support
$ ssh root@38.242.240.89 'printf "CRON_TZ=Asia/Ho_Chi_Minh\n0 8,12,15,20 * * * /bin/true\n" | crontab -n - 2>&1'
The syntax of the crontab file was successfully checked.
8.2 Runtime timezone
$ ssh root@38.242.240.89 'date -Is; echo "---"; timedatectl 2>/dev/null | sed -n "1,12p"'
2026-04-08T16:03:26+02:00
---
Local time: Wed 2026-04-08 16:03:26 CEST
Universal time: Wed 2026-04-08 14:03:26 UTC
Time zone: Europe/Berlin (CEST, +0200)
8.3 Rclone + GDrive remote
$ ssh root@38.242.240.89 'command -v rclone; echo "---"; rclone about gdrive-backup:'
/usr/bin/rclone
---
Total: 5 TiB
Used: 7.862 GiB
Free: 4.986 TiB
Trashed: 0 B
Other: 6.129 GiB
8.4 Repo source sizes used for planning
$ ssh root@38.242.240.89 'du -sh /opt/incomex/docker/nuxt-repo /opt/incomex/docker/agent-data-repo'
23M /opt/incomex/docker/nuxt-repo
43M /opt/incomex/docker/agent-data-repo