diff --git a/.env.example b/.env.example index 1bd121a..1f6663d 100644 --- a/.env.example +++ b/.env.example @@ -24,11 +24,9 @@ ADMIN_IDS= GEMINI_API_KEY= # SOURCE_COMMIT (commit SHA) is read at startup for the deploynotify owner DM. -# Do NOT set it here. On Coolify (Docker Compose) it is a predefined variable -# that reaches the container only because compose.yml references it -# (`SOURCE_COMMIT: ${SOURCE_COMMIT:-}`) — Coolify supplies the value via -# --env-file. Local `docker compose up` has none, so deploynotify reports -# "unknown". +# Do NOT set it here. Coolify provides it at runtime. Keep "Include Source +# Commit in Build" disabled so Docker layer cache survives across commits. +# Local `docker compose up` has none, so deploynotify reports "unknown". # ====================== Leave UNSET on self-host ================== # Defaults are correct for self-host: diff --git a/Dockerfile b/Dockerfile index 9fac1ec..aa011e5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,9 +6,9 @@ RUN go mod download COPY . . -# The deploy-notify commit SHA comes from the SOURCE_COMMIT runtime env that -# Coolify injects into the container (see compose.yml), not from a build -# arg — Coolify does not pass build args here. The binary is built plain. +# The deploy-notify commit SHA comes from Coolify's SOURCE_COMMIT runtime env, +# not from a build arg. Keep it out of the build so Docker cache survives across +# commits. The binary is built plain. RUN CGO_ENABLED=0 GOOS=linux go build \ -ldflags="-s -w" \ -o /out/server \ diff --git a/cmd/server/main_test.go b/cmd/server/main_test.go index ba55f18..d10fc09 100644 --- a/cmd/server/main_test.go +++ b/cmd/server/main_test.go @@ -1,6 +1,9 @@ package main import ( + "os" + "path/filepath" + "strings" "testing" "github.com/tiennm99/miti99bot/internal/modules" @@ -29,6 +32,22 @@ func TestResolveCommitSHA(t *testing.T) { } } +func TestComposeDoesNotOverrideSourceCommit(t *testing.T) { + b, err := os.ReadFile(filepath.Join("..", "..", "compose.yml")) + if err != nil { + t.Fatalf("read compose.yml: %v", err) + } + for _, line := range strings.Split(string(b), "\n") { + trimmed := strings.TrimSpace(line) + if trimmed == "" || strings.HasPrefix(trimmed, "#") { + continue + } + if strings.HasPrefix(trimmed, "SOURCE_COMMIT:") || strings.HasPrefix(trimmed, "- SOURCE_COMMIT") { + t.Fatalf("compose.yml must not declare SOURCE_COMMIT; Coolify supplies it at runtime and an explicit Compose value can override it with empty") + } + } +} + func TestFactoriesIncludesGoldAndCoin(t *testing.T) { catalog := factories() if catalog["gold"] == nil { diff --git a/compose.yml b/compose.yml index 1ff48de..fbc8bdc 100644 --- a/compose.yml +++ b/compose.yml @@ -16,13 +16,9 @@ services: ADMIN_IDS: ${ADMIN_IDS} # CSV of admin Telegram user ids # --- Optional --- GEMINI_API_KEY: ${GEMINI_API_KEY} # only the twentyq module needs it - # SOURCE_COMMIT (commit SHA, read at startup for the deploynotify owner DM). - # For Docker Compose, Coolify passes predefined vars via --env-file for - # interpolation ONLY — the var reaches the container only if referenced - # here (per Coolify docs). The :- default keeps local `docker compose up` - # (where SOURCE_COMMIT is unset) quiet; on Coolify the real value wins, so - # deploynotify reports "unknown" only outside Coolify. - SOURCE_COMMIT: ${SOURCE_COMMIT:-} + # SOURCE_COMMIT is intentionally not declared here. Coolify provides it + # at runtime via its generated env file; declaring it here with Compose + # interpolation can override the runtime value with an empty string. # Storage auto-selects mongodb because MONGO_URL is set — no KV_PROVIDER. # The in-process cron scheduler runs by default — no CRON_MODE. # PORT defaults to 8080 (internal health server) — omit unless overriding. diff --git a/docs/deploy-coolify-selfhosted.md b/docs/deploy-coolify-selfhosted.md index b3c76c6..8e53c49 100644 --- a/docs/deploy-coolify-selfhosted.md +++ b/docs/deploy-coolify-selfhosted.md @@ -82,13 +82,13 @@ Copy [`.env.example`](../.env.example) → `.env` (gitignored) and fill in. double-fires crons. Prefer **stop-first redeploys** so two containers never overlap near a cron time. 5. **deploynotify commit SHA:** `SOURCE_COMMIT` is a Coolify predefined - variable. For Docker Compose, Coolify passes predefined vars via `--env-file` - for interpolation only, so the value reaches the container **only because - `compose.yml` references it** (`SOURCE_COMMIT: ${SOURCE_COMMIT:-}`). - The bot reads it at startup and DMs the owner on every boot; outside Coolify - (local `docker compose up`) it is unset and the DM shows `unknown`. The - "Include Source Commit in Build" Coolify setting affects build args only and - is **not** needed for this runtime path. + variable. The bot reads it at startup and DMs the owner on every boot; + outside Coolify (local `docker compose up`) it is unset and the DM shows + `unknown`. Keep "Include Source Commit in Build" disabled: that setting + affects build args only, is not needed for this runtime path, and would + invalidate Docker cache on every commit. Do not add `SOURCE_COMMIT` to + `compose.yml`; an interpolated empty value can override Coolify's runtime + env-file value. 6. **Health check:** use Coolify's HTTP monitor against `GET /` (returns `text/plain` `miti99bot ok`). Do **not** use a compose `healthcheck` — the distroless image has no shell/curl and `cmd/server` has no `-healthcheck`