KB-29B1

S174-INV-04 — Code Backup to GDrive Prep

24 min read Revision 1
reports174backupgdriveread-onlyvps

S174-INV-04 — Code Backup to GDrive Prep

Date: 2026-04-08 Scope: Investigation only, strict read-only. No cron created, no script edited, no upload test, no tarball created.

Rules Loaded

  • .claude/skills/incomex-rules.md
  • search_knowledge("operating rules SSOT")
  • search_knowledge("hiến pháp v4.0 constitution")
  • search_knowledge("backup-to-gdrive.sh pg backup kuma heartbeat")
  • search_knowledge("s174 prep nuxt report 2 repo web-test agent-data-test")
  • search_knowledge("operating rules v7.55 out of scope")

3 Câu Tuyên Ngôn

  1. Vĩnh viễn: nếu làm Bước 2, nên tái sử dụng transport đã chứng minh được (rclone + GDrive + Kuma), nhưng tách riêng script, folder remote, retention, monitor và log cho code backup. Không piggyback mù vào backup-to-gdrive.sh.
  2. Nhầm được không: phải chặn nhầm bằng folder riêng theo repo, filename có repo + timestamp ICT, verify size sau upload, lock chống chạy chồng, và monitor tách riêng từng repo.
  3. 100% tự động: cron chạy 4 lần/ngày, script tự đóng gói, upload, verify remote metadata, cleanup retention, push heartbeat. Không cần thao tác tay sau khi triển khai xong.

1. Executive Summary

Verdict

Khả thi. Có thể dùng cùng kênh GDrive hiện có làm secondary backup cho code của:

  • /opt/incomex/docker/nuxt-repo
  • /opt/incomex/docker/agent-data-repo

Nhưng chỉ nên tái sử dụng các phần đã chứng minh ổn định:

  • rclone remote hiện có
  • credential hiện có
  • pattern log/summary
  • pattern Kuma heartbeat

Không nên copy nguyên trạng các điểm sau:

  • remote đang ghi thẳng vào Drive root, chưa có folder riêng cho backup code
  • cron hiện tại đang chạy theo timezone host Europe/Berlin, không theo giờ VN
  • log của data-backup đang bị double-write do script tự tee vào log và cron cũng redirect cùng file
  • script hiện tại không có lock chống chạy chồng
  • heartbeat hiện tại là success-only; failure được phát hiện gián tiếp qua missed heartbeat

Kết luận ngắn

  • Kênh upload GDrive hiện tại đủ tin cậy để tái sử dụng cho code backup.
  • GDrive còn dư rất lớn: Free: 4.986 TiB.
  • Dung lượng 2 repo nhỏ so với quota hiện có.
  • Backup 4 lần/ngày hợp lý hơn mỗi lần git commit.
  • Nên dùng 2 monitor Kuma riêng, không dùng 1 monitor chung.

2. Hiện Trạng Cơ Chế Data-Backup GDrive Hiện Có

2.1 Runtime timezone và cron thực tế

Đã xác minh VPS không chạy UTC; cron đang bám timezone host Europe/Berlin (CEST, +0200).

Evidence:

$ ssh root@38.242.240.89 'date -Is; echo "---"; timedatectl 2>/dev/null | sed -n "1,12p"'
2026-04-08T15:31:50+02:00
---
               Local time: Wed 2026-04-08 15:31:51 CEST
           Universal time: Wed 2026-04-08 13:31:51 UTC
                Time zone: Europe/Berlin (CEST, +0200)
System clock synchronized: yes

Cron hiện tại gọi data-backup lúc 20:00 theo giờ host:

$ ssh root@38.242.240.89 'crontab -l 2>/dev/null | nl -ba | sed -n "52,54p"'
    52  # VPS Full Backup to Google Drive (Mon+Thu 3AM Hanoi = Sun+Wed 20:00 UTC)
    53  # VPS Full Backup to Google Drive (nightly 3AM Hanoi = 20:00 UTC)
    54  0 20 * * * /opt/incomex/scripts/backup-to-gdrive.sh >> /opt/incomex/logs/backup-gdrive.log 2>&1

Observed reality: comment nói 20:00 UTC, nhưng host thực tế là CEST, nên line 0 20 * * * đang chạy lúc 20:00 CEST, tức khoảng 01:00 ICT, không phải 03:00 ICT.

2.2 Script hiện tại dùng gì

backup-to-gdrive.sh đang dùng rclone, remote gdrive-backup:, keep 14 remote backups và 1 local backup.

Evidence:

$ ssh root@38.242.240.89 'nl -ba /opt/incomex/scripts/backup-to-gdrive.sh 2>/dev/null | sed -n "11,24p"'
    11  # --- Config ---
    12  BACKUP_DIR="/opt/incomex/backups"
    13  TIMESTAMP=$(date +%Y%m%d_%H%M%S)
    14  BACKUP_NAME="vps-backup-${TIMESTAMP}"
    17  LOG_FILE="/opt/incomex/logs/backup-gdrive.log"
    18  RCLONE_REMOTE="gdrive-backup:"
    19  MAX_REMOTE_BACKUPS=14
    21  # --- Logging (must be BEFORE any commands that might emit errors) ---
    22  mkdir -p "$(dirname "$LOG_FILE")"
    23  exec > >(tee -a "$LOG_FILE") 2>&1

Upload + retention + heartbeat pattern:

$ ssh root@38.242.240.89 'nl -ba /opt/incomex/scripts/backup-to-gdrive.sh 2>/dev/null | sed -n "104,140p"'
   104  # --- Upload to Google Drive ---
   105  echo "Uploading to Google Drive..."
   106  rclone copy "$ARCHIVE" "$RCLONE_REMOTE" \
   111  # --- Cleanup local (keep only latest) ---
   115  # --- Cleanup remote (keep MAX_REMOTE_BACKUPS) ---
   117  REMOTE_FILES=$(rclone lsf "$RCLONE_REMOTE" --files-only | grep "^vps-backup-" | sort -r)
   127  # --- Summary ---
   132  REMOTE_COUNT=$(rclone lsf "$RCLONE_REMOTE" --files-only | grep -c "^vps-backup-" || echo 0)
   136  # --- S174-FIX-01: Heartbeat → Uptime Kuma (pg-backup-gdrive) ---
   137  KUMA_PUSH_URL="http://localhost:3001/api/push/pg-backup-gdrive"
   139  curl -fsS "${KUMA_PUSH_URL}?status=up&msg=OK+archive%3D${ARCHIVE_SIZE}&ping=${ARCHIVE_BYTES}" >&2
   140  echo "HEARTBEAT sent to Kuma (pg-backup-gdrive)"

2.3 Remote GDrive, quota, folder đích, credential path

Evidence:

$ ssh root@38.242.240.89 'command -v rclone 2>/dev/null; echo "---"; rclone listremotes 2>/dev/null; echo "---"; rclone about gdrive-backup: 2>/dev/null'
/usr/bin/rclone
---
gdrive-backup:
---
Total:   5 TiB
Used:    7.862 GiB
Free:    4.986 TiB
Trashed: 0 B
Other:   6.129 GiB

Remote đang ghi vào Drive root, chưa có folder backup riêng:

$ ssh root@38.242.240.89 'rclone lsf gdrive-backup: --files-only 2>/dev/null | sed -n "1,30p"'
Bảng tính không có tiêu đề.xlsx
Ghi chú lưu ý.docx
vps-backup-20260403_103342.tar.gz
vps-backup-20260403_104307.tar.gz
vps-backup-20260403_200001.tar.gz
vps-backup-20260404_200001.tar.gz
vps-backup-20260405_200002.tar.gz
vps-backup-20260406_200001.tar.gz
vps-backup-20260407_200002.tar.gz
vps-backup-20260408_122032.tar.gz
vps-backup-20260408_122159.tar.gz

Credential path và quyền truy cập:

$ ssh root@38.242.240.89 'rclone config file 2>/dev/null; echo "---"; stat -c "%n %U:%G %a %s bytes %y" /root/.config/rclone/rclone.conf 2>/dev/null; echo "---"; ls -ld /root /root/.config /root/.config/rclone 2>/dev/null'
Configuration file is stored at:
/root/.config/rclone/rclone.conf
---
/root/.config/rclone/rclone.conf root:root 644 585 bytes 2026-04-08 12:20:49.988266943 +0200
---
drwx------ 9 root root 4096 Apr  8 12:21 /root
drwxr-xr-x 5 root root 4096 Feb 28 02:19 /root/.config
drwxr-xr-x 2 root root 4096 Apr  8 15:32 /root/.config/rclone

Kết luận đã xác minh:

  • remote là gdrive-backup:
  • storage còn rất dư
  • data-backup hiện đang đẩy vào Drive root
  • credential file ở /root/.config/rclone/rclone.conf
  • file config là 644, nhưng /root700, nên không phơi lộ cho user không có quyền traverse /root

3. Đánh Giá Độ Tin Cậy Cơ Chế Hiện Có

3.1 Điều đã xác minh

7 ngày gần nhất không thấy WARN/ERROR/timeout trong log backup chính; các lần chạy gần đây đều hoàn tất upload và gửi heartbeat.

Evidence:

$ ssh root@38.242.240.89 'grep -nE "WARN|ERROR|Failed|failed|fatal|FATAL|curl:|denied|timeout" /opt/incomex/logs/backup-gdrive.log 2>/dev/null | tail -n 120'
(no output)

Latest successful run [excerpt]:

$ ssh root@38.242.240.89 'tail -n 140 /opt/incomex/logs/backup-gdrive.log 2>/dev/null'
...
2026/04/08 12:22:40 INFO  : vps-backup-20260408_122159.tar.gz: Copied (new)
...
→ Upload complete
Cleaning local...
Cleaning remote (keep 14)...
==========================================
BACKUP DONE: 2026-04-08 12:22:41 CEST
Archive: 73M
PG: 36M | Qdrant: 63M
Remote backups: 9
==========================================
{"ok":true}HEARTBEAT sent to Kuma (pg-backup-gdrive)

Remote metadata khớp với log:

$ ssh root@38.242.240.89 'rclone lsl gdrive-backup: 2>/dev/null | sed -n "1,10p"'
 75913453 2026-04-08 12:22:17.845000000 vps-backup-20260408_122159.tar.gz
 75932107 2026-04-08 12:20:48.989000000 vps-backup-20260408_122032.tar.gz
 76667116 2026-04-07 20:00:39.563000000 vps-backup-20260407_200002.tar.gz

Local retention cũng đúng với script hiện tại:

$ ssh root@38.242.240.89 'ls -lh /opt/incomex/backups/vps-backup-*.tar.gz 2>/dev/null'
-rw-r--r-- 1 root root 73M Apr  8 12:22 /opt/incomex/backups/vps-backup-20260408_122159.tar.gz

3.2 Reliability assessment

Đánh giá: đủ tin cậy để tái sử dụng làm transport/upload channel cho code backup, nhưng chưa nên reuse 1:1.

Strengths:

  • rclone remote đang hoạt động thật.
  • quota GDrive rất lớn.
  • log 7 ngày gần nhất không có error pattern.
  • upload thật tới remote có metadata đối chiếu được.
  • heartbeat tới Kuma đã chạy thành công trong các run gần đây.

Weaknesses / caveats:

  1. Logging đang bị double-write khi chạy bằng cron. Evidence:

    • script tự tee -a "$LOG_FILE" ở line 23
    • cron lại >> /opt/incomex/logs/backup-gdrive.log 2>&1 ở line 54
    • log đêm 2026-04-03 đến 2026-04-07 có cặp BACKUP START/BACKUP DONE lặp đôi
  2. Scheduling comment không khớp runtime thật. Evidence:

    • comment nói 20:00 UTC
    • host thực tế là Europe/Berlin (CEST)
    • log cho thấy chạy 20:00:01 CEST
  3. Không có lock chống chạy chồng. Evidence:

    • trong script không có flock hoặc lockfile
    • log cho thấy đã có các lần rerun manual gần nhau (12:20:3212:21:59 ngày 2026-04-08)
  4. Heartbeat là success-only. Evidence:

    • script chỉ có push status=up; không có nhánh status=down
    • failure sẽ hiện ra chủ yếu bằng missed heartbeat, không phải lỗi chi tiết chủ động
  5. Remote path chưa tách folder. Evidence:

    • rclone lsf gdrive-backup: cho thấy backup files đang trộn với các file document không liên quan ở Drive root

3.3 Kết luận phần này

Phương án “reuse current data-backup mechanism” là khả thi nếu hiểu là:

  • reuse remote + credential + uploader pattern + summary + heartbeat pattern

Không khả thi nếu hiểu là:

  • copy nguyên cron, nguyên folder đích, nguyên log strategy, nguyên retention số 14, và nguyên heartbeat model

4. Đề Xuất Backup Code Cho 2 Repo

4.1 Số liệu 2 repo

Evidence:

$ ssh root@38.242.240.89 'du -sh /opt/incomex/docker/nuxt-repo /opt/incomex/docker/agent-data-repo 2>/dev/null; echo "---"; du -sh /opt/incomex/docker/nuxt-repo/.git /opt/incomex/docker/agent-data-repo/.git 2>/dev/null'
23M /opt/incomex/docker/nuxt-repo
43M /opt/incomex/docker/agent-data-repo
---
6.8M /opt/incomex/docker/nuxt-repo/.git
21M  /opt/incomex/docker/agent-data-repo/.git

Không tìm thấy cache/build dirs phổ biến tại thời điểm scan:

$ ssh root@38.242.240.89 'echo "NUXT EXCLUDES"; find /opt/incomex/docker/nuxt-repo -type d \( -name node_modules -o -name .nuxt -o -name .output -o -name __pycache__ -o -name .pytest_cache -o -name .mypy_cache -o -name .cache -o -name dist -o -name coverage -o -name .venv \) -print 2>/dev/null; echo "---"; echo "AGENT EXCLUDES"; find /opt/incomex/docker/agent-data-repo -type d \( -name node_modules -o -name .nuxt -o -name .output -o -name __pycache__ -o -name .pytest_cache -o -name .mypy_cache -o -name .cache -o -name dist -o -name coverage -o -name .venv \) -print 2>/dev/null'
NUXT EXCLUDES
---
AGENT EXCLUDES

Current git state:

$ ssh root@38.242.240.89 'cd /opt/incomex/docker/nuxt-repo && echo "NUXT" && git status --short --branch 2>/dev/null; echo "---"; cd /opt/incomex/docker/agent-data-repo && echo "AGENT" && git status --short --branch 2>/dev/null'
NUXT
## main...origin/main [ahead 2, behind 13]
---
AGENT
## main...origin/main [ahead 4, behind 112]

Interpretation:

  • working tree hiện đang clean ở cả 2 repo
  • có local commits chưa đồng bộ với origin ở cả 2 repo
  • secondary backup ngoài GitHub là có giá trị thực, vì repo trên VPS chứa state không chỉ là mirror sạch của remote

4.2 Large tracked files ảnh hưởng ước lượng nén

Evidence:

$ ssh root@38.242.240.89 'echo "NUXT LARGE"; find /opt/incomex/docker/nuxt-repo -type f -size +1M -printf "%s %p\n" 2>/dev/null | sort -nr | sed -n "1,30p"; echo "---"; echo "AGENT LARGE"; find /opt/incomex/docker/agent-data-repo -type f -size +1M -printf "%s %p\n" 2>/dev/null | sort -nr | sed -n "1,40p"'
NUXT LARGE
3292202 /opt/incomex/docker/nuxt-repo/.git/objects/pack/pack-ce4cc9b180a15a8c878dafa8a70b1a0c18c053e2.pack
1655258 /opt/incomex/docker/nuxt-repo/.git/objects/pack/pack-3e9182b80cfb2108502cbea202aad2b16ffe153f.pack
1396989 /opt/incomex/docker/nuxt-repo/directus/snapshot-full.json
1060780 /opt/incomex/docker/nuxt-repo/dot/snapshots/schema-2026-03-06.json
---
AGENT LARGE
19965913 /opt/incomex/docker/agent-data-repo/.git/objects/pack/pack-6a859adab85a757f240ecc06ea48d18777b95538.pack
19285985 /opt/incomex/docker/agent-data-repo/governance/backup_terraform_20250816T062059Z.tar.gz

Các file lớn này đều đang tracked:

$ ssh root@38.242.240.89 'cd /opt/incomex/docker/agent-data-repo && git ls-files --stage governance/backup_terraform_20250816T062059Z.tar.gz 2>/dev/null; echo "---"; cd /opt/incomex/docker/nuxt-repo && git ls-files --stage directus/snapshot-full.json dot/snapshots/schema-2026-03-06.json 2>/dev/null'
100644 836852769b85b02718d1fb961aa1096ffa6f9928 0 governance/backup_terraform_20250816T062059Z.tar.gz
---
100644 c2f6e62886196d1d12e4f34e58a660ca6e5596f6 0 directus/snapshot-full.json
100644 f0649fbbe8d3dd06a373a7033118dc4a5b92f666 0 dot/snapshots/schema-2026-03-06.json

4.3 Include / exclude đề xuất

Đã xác minh:

  • hiện không có node_modules, .nuxt, .output, .venv, cache dirs phổ biến trong 2 repo
  • .git là thành phần quan trọng và nên giữ

Đề xuất:

  • include toàn bộ working tree của từng repo
  • include .git
  • exclude theo pattern phòng ngừa trong script mới:
    • node_modules
    • .nuxt
    • .output
    • __pycache__
    • .pytest_cache
    • .mypy_cache
    • .cache
    • coverage
    • dist
    • .venv
  • không exclude tracked files lớn chỉ vì chúng lớn, trừ khi user xác nhận đó là historical artifact không cần cho restore

4.4 Naming đề xuất

User muốn tên file nhìn là biết repo + timestamp. Điều này đúng, nhưng format chỉ tới phút có rủi ro đụng tên khi rerun trong cùng phút.

Evidence có rerun gần nhau:

$ ssh root@38.242.240.89 'grep -nE "^BACKUP START|^BACKUP DONE|^Archive:|^PG:|^Remote backups|HEARTBEAT sent" /opt/incomex/logs/backup-gdrive.log 2>/dev/null | sed -n "70,100p"'
1331:BACKUP START: 2026-04-08 12:20:32 CEST
1426:BACKUP DONE: 2026-04-08 12:20:58 CEST
1427:Archive: 73M
1428:PG: 36M | Qdrant: 63M
1429:Remote backups: 8
1431:{"ok":true}HEARTBEAT sent to Kuma (pg-backup-gdrive)
1433:BACKUP START: 2026-04-08 12:21:59 CEST
1586:BACKUP DONE: 2026-04-08 12:22:41 CEST

Recommended naming:

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

Nếu user bắt buộc muốn format tới phút:

  • dùng web-test_YYYYMMDD-HHMM_ICT.tar.gz
  • nhưng phải thêm suffix retry khi collision (-r01, -r02)

4.5 Remote path đề xuất

Không nên đẩy code backup vào Drive root như data-backup hiện tại.

Recommended remote layout:

  • gdrive-backup:incomex-code-backups/web-test/
  • gdrive-backup:incomex-code-backups/agent-data-test/

Lý do:

  • cleanup retention theo repo an toàn hơn
  • user nhìn Drive dễ hơn
  • không trộn code backup với data backup và file document cá nhân

4.6 Ước lượng kích thước tar.gz

Không tạo tarball thật trong mission này; đây là suy luận từ số liệu đo được.

Estimated archive size:

Repo Measured size Important already-compressed content Estimated .tar.gz Confidence
web-test 23M .git packs ~4.9M; large JSON snapshots ~2.4M 10M–14M Medium
agent-data-test 43M .git pack ~20M; tracked backup_terraform_*.tar.gz ~19M 39M–42M Medium-High

Estimated combined per run:

  • khoảng 49M–56M

Estimated storage for retention:

  • 4 runs/day x 14 days56 archives/repo
  • combined retained size khoảng 2.8G–3.1G
  • so với Free: 4.986 TiB, mức này là rất nhỏ

4.7 Script flow đề xuất cho Bước 2

Proposed new script:

  • /opt/incomex/scripts/code-backup-to-gdrive.sh

Proposed flow:

  1. flock/lockfile để chặn chạy chồng
  2. xác định timestamp theo Asia/Ho_Chi_Minh để tên file đúng ICT
  3. với mỗi repo:
    • capture git rev-parse HEAD
    • capture git status --short --branch
    • copy repo vào temp workdir với exclude patterns
    • tạo archive .tar.gz
    • upload bằng rclone copy qua remote hiện có
    • verify remote metadata bằng rclone lsl và so exact size với local
    • keep only latest local archive của repo đó
    • cleanup remote theo retention riêng của repo
    • push heartbeat repo-specific
  4. ghi summary cuối cùng vào log

4.8 Heartbeat recommendation

Chọn 2 monitor riêng, không chọn 1 monitor chung.

Lý do:

  • web-testagent-data-test là 2 artifact độc lập
  • nếu 1 repo upload fail còn repo kia thành công, 1 monitor chung sẽ mơ hồ
  • 2 monitor cho phép alert rõ repo nào lỗi

Recommended monitor names/push IDs:

  • code-backup-web-test
  • code-backup-agent-data-test

4.9 Cron recommendation

Đã xác minh mapping theo runtime hiện tại (CEST):

$ ssh root@38.242.240.89 'for t in "2026-04-08 08:00" "2026-04-08 12:00" "2026-04-08 15:00" "2026-04-08 20:00"; do ts=$(TZ=Asia/Ho_Chi_Minh date -d "$t" +%s 2>/dev/null) || continue; printf "%s ICT => " "$t"; TZ=Europe/Berlin date -d @${ts} +"%Y-%m-%d %H:%M:%S %Z (%z)"; done'
2026-04-08 08:00 ICT => 2026-04-08 03:00:00 CEST (+0200)
2026-04-08 12:00 ICT => 2026-04-08 07:00:00 CEST (+0200)
2026-04-08 15:00 ICT => 2026-04-08 10:00:00 CEST (+0200)
2026-04-08 20:00 ICT => 2026-04-08 15:00:00 CEST (+0200)

Recommended final cron, nếu host cron support CRON_TZ:

CRON_TZ=Asia/Ho_Chi_Minh
0 8,12,15,20 * * * /opt/incomex/scripts/code-backup-to-gdrive.sh >> /opt/incomex/logs/code-backup-gdrive.log 2>&1

Fallback only if CRON_TZ không support và deploy khi host đang CEST:

0 3,7,10,15 * * * /opt/incomex/scripts/code-backup-to-gdrive.sh >> /opt/incomex/logs/code-backup-gdrive.log 2>&1

Lưu ý:

  • fallback này sẽ lệch khi Berlin đổi CEST -> CET
  • vì vậy CRON_TZ=Asia/Ho_Chi_Minh là hướng nên ưu tiên verify trong Bước 2

4.10 Retention recommendation

Data-backup hiện dùng 14 remote copies, nhưng với 4 runs/day thì 14 chỉ giữ được 3.5 ngày.

Recommended:

  • remote: 56 archives / repo (14 ngày)
  • local: 1 latest archive / repo

Lý do:

  • dung lượng rất nhỏ so với quota
  • restore point dày hơn
  • vẫn đơn giản để cleanup

5. Rủi Ro Và Giảm Thiểu

5.1 Dung lượng GDrive

Đã xác minh: đủ.

Evidence:

Free: 4.986 TiB

Mitigation:

  • không cần tối ưu sớm vì dung lượng dự kiến chỉ cỡ ~3 GB cho retention 14 ngày

5.2 Backup khi repo đang thay đổi

Risk:

  • tar trực tiếp trên live repo có thể chụp state lẫn trước/sau khi file đổi

Mitigation đề xuất:

  • staging copy sang temp workdir trước khi nén
  • log lại HEADgit status
  • nếu HEAD đổi trong lúc chạy, fail run hoặc retry có kiểm soát

5.3 Agent commit đúng lúc backup chạy

Risk:

  • archive có thể không tương ứng với một revision nhất quán

Mitigation đề xuất:

  • lock chống chạy chồng
  • chụp metadata HEAD before/after
  • nếu HEAD before != HEAD after, mark run failed

5.4 Credential rotate

Risk:

  • code backup và data backup cùng phụ thuộc một rclone config; rotate sai sẽ làm hỏng cả 2

Mitigation đề xuất:

  • giữ script code backup tách riêng data backup
  • sau mỗi credential rotation, manual run 1 lần trong Bước 2 để verify
  • monitor riêng cho code backup giúp thấy ngay tác động

5.5 Trùng tên file

Risk:

  • naming tới phút có thể collision khi rerun trong cùng phút

Mitigation đề xuất:

  • ưu tiên HHMMSS
  • hoặc minute + suffix retry

5.6 So sánh 4 lần/ngày vs mỗi lần git commit

Khuyến nghị: 4 lần/ngày.

Lý do:

  • reuse tốt hơn cơ chế cron + Kuma đang có
  • tải thấp, dễ dự đoán, dễ monitor
  • không phụ thuộc việc mọi thay đổi đều đi qua commit hook
  • per-commit dễ spam archive khi rewrite lịch sử hoặc commit dồn
  • per-commit cũng không bảo vệ uncommitted-but-important working tree nếu chỉ backup theo commit event

6. Kế Hoạch Bước 2

6.1 Thứ tự triển khai đề xuất

  1. Tạo script mới /opt/incomex/scripts/code-backup-to-gdrive.sh
  2. Tạo folder remote logical path cho từng repo
  3. Thêm lock, ICT timestamp, repo manifest, upload verify, cleanup retention
  4. Tạo 2 Kuma push IDs riêng
  5. Chạy tay 1 lần
  6. Verify có 2 file thật trên GDrive, đúng tên, đúng size, có log, có heartbeat
  7. Chỉ sau khi manual run pass mới thêm cron
  8. Quan sát ít nhất 1 scheduled run sau đó

6.2 Rollback plan

Rollback của Bước 2 nên rất đơn giản:

  • không đụng vào backup-to-gdrive.sh hiện có
  • nếu manual run fail: sửa script mới, chưa add cron
  • nếu scheduled run gây lỗi/noise: remove only new cron line(s), giữ lại log để postmortem
  • không ảnh hưởng data-backup hiện tại

6.3 Verify checklist cho Bước 2

  • code-backup-to-gdrive.sh chạy tay exit 0
  • trên GDrive thấy đủ 2 file thật
  • tên file đúng repo + timestamp ICT
  • rclone lsl size khớp local stat -c%s
  • log có summary từng repo
  • cả 2 heartbeat lên Kuma đúng monitor
  • cleanup local giữ đúng 1 latest / repo
  • cleanup remote giữ đúng retention đã chọn

7. Unknowns

  • Chưa verify được trực tiếp CRON_TZ support trên host; man 5 crontab | grep CRON_TZ không trả output, nên đây vẫn là mục cần xác minh trong Bước 2.
  • Chưa tạo tarball thật trong mission này, nên archive size chỉ là estimate.
  • Chưa inspect trực tiếp Kuma UI/config; mới xác minh được push pattern qua script và log.
  • File tracked governance/backup_terraform_20250816T062059Z.tar.gz trong agent-data-repo có thể là historical artifact; chưa đủ bằng chứng để đề xuất exclude.

8. Appendix: Commands + Outputs

A. Script metadata

$ ssh root@38.242.240.89 'ls -l /opt/incomex/scripts/backup-to-gdrive.sh 2>/dev/null; echo "---"; stat -c "%n %U:%G %a %s bytes %y" /opt/incomex/scripts/backup-to-gdrive.sh 2>/dev/null'
-rwxr-xr-x 1 root root 5646 Apr  8 12:19 /opt/incomex/scripts/backup-to-gdrive.sh
---
/opt/incomex/scripts/backup-to-gdrive.sh root:root 755 5646 bytes 2026-04-08 12:19:31.748304978 +0200

B. Backup log summary window [excerpt]

$ ssh root@38.242.240.89 'grep -nE "^BACKUP START|^Archive:|^PG:|^Remote backups|HEARTBEAT sent|^BACKUP DONE" /opt/incomex/logs/backup-gdrive.log 2>/dev/null | sed -n "1,120p"'
2:BACKUP START: 2026-04-03 10:33:42 CEST
58:BACKUP DONE: 2026-04-03 10:33:58 CEST
59:Archive: 32M
60:PG: 34M | Qdrant: 4.0K
61:Remote backups: 1
64:BACKUP START: 2026-04-03 10:43:07 CEST
135:BACKUP DONE: 2026-04-03 10:43:31 CEST
136:Archive: 77M
137:PG: 34M | Qdrant: 66M
138:Remote backups: 2
141:BACKUP START: 2026-04-03 20:00:01 CEST
145:BACKUP START: 2026-04-03 20:00:01 CEST
...
1586:BACKUP DONE: 2026-04-08 12:22:41 CEST
1587:Archive: 73M
1588:PG: 36M | Qdrant: 63M
1589:Remote backups: 9
1591:{"ok":true}HEARTBEAT sent to Kuma (pg-backup-gdrive)

C. Repo top-level size highlights [excerpt]

$ ssh root@38.242.240.89 'echo "NUXT TOP"; du -sh /opt/incomex/docker/nuxt-repo/* /opt/incomex/docker/nuxt-repo/.[!.]* /opt/incomex/docker/nuxt-repo/..?* 2>/dev/null | sort -h | tail -n 40; echo "---"; echo "AGENT TOP"; du -sh /opt/incomex/docker/agent-data-repo/* /opt/incomex/docker/agent-data-repo/.[!.]* /opt/incomex/docker/agent-data-repo/..?* 2>/dev/null | sort -h | tail -n 40'
NUXT TOP
...
2.2M /opt/incomex/docker/nuxt-repo/directus
2.9M /opt/incomex/docker/nuxt-repo/dot
3.1M /opt/incomex/docker/nuxt-repo/reports
5.3M /opt/incomex/docker/nuxt-repo/web
6.8M /opt/incomex/docker/nuxt-repo/.git
---
AGENT TOP
...
496K /opt/incomex/docker/agent-data-repo/tests
19M /opt/incomex/docker/agent-data-repo/governance
21M /opt/incomex/docker/agent-data-repo/.git

D. Local disk headroom

$ ssh root@38.242.240.89 'df -h /opt/incomex/backups /opt/incomex/docker 2>/dev/null'
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        96G   38G   59G  39% /
/dev/sda1        96G   38G   59G  39% /