KB-4E2B

S174-IMP-05 — Code Backup to Google Drive

16 min read Revision 1
reports174implementationbackupgdrivecode-backupvps

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.md
  • search_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

  1. 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ũ.
  2. 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.
  3. 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:00 theo Asia/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/HEADBACKUP-MANIFEST.txt.
  • Dùng CRON_TZ=Asia/Ho_Chi_Minh thay 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ùng loginByToken dựa trên jwtSecret + 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=1 chạ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=1 cho cả 2
  • ping đú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 id 1K1atlFO-bkO7KoukEKPDFbZq7wvQd1Mb
  • agent-data-test -> folder id 1__qHH5ndV0FqC73nJu5_kUN5sd46M-AN

Filename format

  • web-test_YYYYMMDD-HHMM_ICT.tar.gz
  • agent-data-test_YYYYMMDD-HHMM_ICT.tar.gz

Kuma monitors

  • 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

Cron

  • CRON_TZ=Asia/Ho_Chi_Minh
  • 0 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.gz
  • web-test_20260408-2128_ICT.tar.gz
  • agent-data-test_20260408-2126_ICT.tar.gz
  • agent-data-test_20260408-2128_ICT.tar.gz

Latest verified sample:

  • web-test_20260408-2128_ICT.tar.gz -> 10,586,997 bytes
  • agent-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 .git nên restore repo usable về mặt Git metadata.

Chưa xác minh / ngoài scope

  1. REMOTE_KEEP=56 chưa được exercise đủ 57 bản để chứng minh remote delete path trong production thật.
  2. 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.
  3. /opt/incomex/.uptime-kuma-admin-pass khô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ùng loginByToken, nhưng nên ghi nhận để tránh hiểu nhầm sau này.
  4. Data backup cũ /opt/incomex/scripts/backup-to-gdrive.sh vẫ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:

  1. Dùng rclone tải archive mới nhất từ đúng folder ID của repo cần phục hồi.
  2. Giải nén archive về đúng path repo cũ trên VPS.
  3. Kiểm tra git status, git rev-parse HEAD, .git/HEAD.
  4. Sau khi môi trường ổn định, có thể set lại remote GitHub và git push lê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