From 96be4d09222f8188d6b23b5b275843dd0be2f360 Mon Sep 17 00:00:00 2001 From: asepharyana Date: Tue, 15 Sep 2026 22:19:53 +0700 Subject: [PATCH] fix: version commit dibaca live per-request + CI polling anti-stale 20x15s --- .github/workflows/ci.yml | 15 +++++++++++++++ backend/internal/api/server.go | 4 +--- backend/internal/api/version.go | 21 +++++++++++---------- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4c6d1dc..7ec68b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -149,13 +149,28 @@ jobs: (cd /home/code/flowsight && git fetch origin main && git reset --hard origin/main || true) && \ sudo mkdir -p /var/lib/flowsight/data && sudo chown -R code:code /var/lib/flowsight && \ (sudo cp /home/code/flowsight/deploy/flowsight.service /etc/systemd/system/flowsight.service && sudo systemctl daemon-reload || true) && \ + echo -n "$GITHUB_SHA" | sudo tee /var/lib/flowsight/version.txt >/dev/null && \ + sudo chown code:code /var/lib/flowsight/version.txt && \ sudo systemctl enable --now flowsight && \ sudo systemctl restart flowsight && \ sleep 3 && \ sudo systemctl status flowsight --no-pager --no-legend | head -5" - name: Verify service anti-stale + env: + PUSHED_SHA: ${{ github.sha }} run: | ssh "$VPS_USER@$VPS_HOST" \ "curl -s -o /dev/null -w 'local:%{http_code}\n' http://localhost:4022/api/health && \ curl -sk -o /dev/null -w 'public:%{http_code}\n' https://sectors-hackaton.asepharyana.my.id/api/health" + for i in $(seq 1 20); do + LIVE=$(curl -sk https://sectors-hackaton.asepharyana.my.id/api/version 2>/dev/null | python3 -c "import json,sys; print(json.load(sys.stdin).get('commit',''))" 2>/dev/null || true) + echo "attempt $i/20: live=${LIVE:0:7} want=${PUSHED_SHA:0:7}" + if [ -n "$LIVE" ] && [ "$LIVE" = "$PUSHED_SHA" ]; then + echo "Anti-stale OK: live commit matches pushed SHA" + exit 0 + fi + sleep 15 + done + echo "STALE: live commit differs from pushed SHA after 5 min" + exit 1 diff --git a/backend/internal/api/server.go b/backend/internal/api/server.go index 400d69e..e9399dd 100644 --- a/backend/internal/api/server.go +++ b/backend/internal/api/server.go @@ -35,8 +35,7 @@ type Server struct { LLM *llm.Client Validate *validator.Validate Hub *Hub - // Commit + StartedAt power /api/version (CI anti-stale proof). - Commit string + // StartedAt powers /api/version (CI anti-stale proof: process age). StartedAt time.Time } @@ -48,7 +47,6 @@ func New(cfg config.Config, db *store.DB, cache *store.Cache, s *sectors.Client) Cfg: cfg, DB: db, Sectors: s, Sched: sched, LLM: llmc, Validate: validator.New(), Hub: NewHub(), - Commit: readCommit(), StartedAt: time.Now(), } srv.Engine = &routines.Engine{DB: db, Notifier: sched.Notifier, UserKey: cfg.DemoUserKey, diff --git a/backend/internal/api/version.go b/backend/internal/api/version.go index 7dc3e41..30a1445 100644 --- a/backend/internal/api/version.go +++ b/backend/internal/api/version.go @@ -9,28 +9,29 @@ import ( // Version serves GET /api/version: deployed commit + process start time so CI // can prove the live process is the freshly deployed one (anti-stale). -// Commit is resolved once at startup (see readCommit); an old process keeps -// reporting its old commit even after CI writes a new version.txt. +// Commit is read LIVE per request (not cached): CI writes version.txt before +// restarting, and a stale process is caught because its started_at predates +// the deploy. Both fields together = full proof. func (s *Server) Version(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]any{ - "commit": s.Commit, + "commit": readCommitLive(), "started_at": s.StartedAt.UTC().Format(time.RFC3339), "google_configured": s.Cfg.HasGoogle(), }) } -// readCommit resolves the deployed commit once at startup: CI writes -// $GITHUB_SHA to version.txt (WorkingDirectory) before restarting. -func readCommit() string { - if v := strings.TrimSpace(os.Getenv("FLOWSIGHT_COMMIT")); v != "" { - return v - } - for _, p := range []string{"version.txt", "/var/lib/flowsight/version.txt"} { +// readCommitLive resolves the deployed commit per request: version.txt +// (written by CI before every restart) wins, then startup env. +func readCommitLive() string { + for _, p := range []string{"/var/lib/flowsight/version.txt", "version.txt"} { if b, err := os.ReadFile(p); err == nil { if v := strings.TrimSpace(string(b)); v != "" { return v } } } + if v := strings.TrimSpace(os.Getenv("FLOWSIGHT_COMMIT")); v != "" { + return v + } return "unknown" }