mirror of
https://github.com/TheFunny/TelegramTwitterMediaBot.git
synced 2026-10-18 03:22:14 +00:00
fix(ci): gate docker publish on the offline test suite
docker.yml pushed the image for tags and master without waiting for the test job, so a red commit could still be published. ci.yml is now also a workflow_call target; docker depends on its result. The gate run has its own concurrency group and never cancels a superseded run, so a docs-only follow-up cannot drop the image of the code commit before it. The live job is skipped for the gate call.
This commit is contained in:
@@ -34,6 +34,15 @@ on:
|
|||||||
# Weekly probe of the live endpoints, so external API changes surface.
|
# Weekly probe of the live endpoints, so external API changes surface.
|
||||||
- cron: '0 3 * * 1'
|
- cron: '0 3 * * 1'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
# Called by docker.yml as the publish gate. `gate` marks that call: its
|
||||||
|
# concurrency group is separate from the standalone run on the same ref, and
|
||||||
|
# it never cancels a superseded run, because each gate run carries its own
|
||||||
|
# commit's image (a docs-only follow-up must not drop the code commit's).
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
gate:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -41,8 +50,8 @@ permissions:
|
|||||||
# A newer push to the same ref supersedes the older run; without this every
|
# A newer push to the same ref supersedes the older run; without this every
|
||||||
# intermediate commit of a PR branch keeps a runner busy to completion.
|
# intermediate commit of a PR branch keeps a runner busy to completion.
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ci-${{ github.ref }}
|
group: ci-${{ inputs.gate && 'gate-' || '' }}${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: ${{ !inputs.gate }}
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Panicking tests print their backtrace; free when nothing fails.
|
# Panicking tests print their backtrace; free when nothing fails.
|
||||||
@@ -134,7 +143,7 @@ jobs:
|
|||||||
|
|
||||||
live:
|
live:
|
||||||
needs: test
|
needs: test
|
||||||
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v')
|
if: ${{ !inputs.gate && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v')) }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|||||||
@@ -111,14 +111,27 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "build=true" >> "$GITHUB_OUTPUT"
|
echo "build=true" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
# The publish gate: the offline suite CI runs (fmt, clippy, tests, release
|
||||||
|
# build, audit), reused rather than copied, so no image is pushed for a
|
||||||
|
# commit whose tests are red. Pull requests skip it: their own CI run covers
|
||||||
|
# the code, and the docker job there only proves the image still builds.
|
||||||
|
ci:
|
||||||
|
needs: should-build
|
||||||
|
if: needs.should-build.outputs.build == 'true' && github.event_name != 'pull_request'
|
||||||
|
uses: ./.github/workflows/ci.yml
|
||||||
|
with:
|
||||||
|
gate: true
|
||||||
|
|
||||||
# No `actions/checkout` here on purpose: `docker/build-push-action` defaults
|
# No `actions/checkout` here on purpose: `docker/build-push-action` defaults
|
||||||
# to the Git context (`https://github.com/<owner>/<repo>.git#<ref>`), so
|
# to the Git context (`https://github.com/<owner>/<repo>.git#<ref>`), so
|
||||||
# BuildKit clones the repo itself and authenticates with the automatic
|
# BuildKit clones the repo itself and authenticates with the automatic
|
||||||
# github.token. Adding `context: .` below without a checkout step would hand
|
# github.token. Adding `context: .` below without a checkout step would hand
|
||||||
# BuildKit an empty workspace.
|
# BuildKit an empty workspace.
|
||||||
docker:
|
docker:
|
||||||
needs: should-build
|
needs: [should-build, ci]
|
||||||
if: needs.should-build.outputs.build == 'true'
|
# `always()`: a skipped `ci` (pull requests) must not skip this job; only a
|
||||||
|
# failed or cancelled gate does.
|
||||||
|
if: always() && needs.should-build.outputs.build == 'true' && (github.event_name == 'pull_request' || needs.ci.result == 'success')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ Docker: `docker build -t tgxmb .` then `docker run --rm -d --name tgxmb --env-fi
|
|||||||
| `Dockerfile` | Multi-stage: cached dep layer via stub sources + `touch *.rs` mtime bump (cargo's freshness is mtime-based and `cargo clean -p` removes 0 files — the touch is what forces the real sources to rebuild while deps stay cached), static ffmpeg from ffmpeg.martin-riedl.de (`FFMPEG_URL` arg, optional `FFMPEG_SHA256` checksum, `unzip -t` integrity check), `debian:bookworm-slim` runtime, entrypoint. Runtime ships **no libssl/libcrypto/CA bundle** — rustls webpki-roots handles all TLS, and the static ffmpeg only processes local files (downloads go through reqwest) |
|
| `Dockerfile` | Multi-stage: cached dep layer via stub sources + `touch *.rs` mtime bump (cargo's freshness is mtime-based and `cargo clean -p` removes 0 files — the touch is what forces the real sources to rebuild while deps stay cached), static ffmpeg from ffmpeg.martin-riedl.de (`FFMPEG_URL` arg, optional `FFMPEG_SHA256` checksum, `unzip -t` integrity check), `debian:bookworm-slim` runtime, entrypoint. Runtime ships **no libssl/libcrypto/CA bundle** — rustls webpki-roots handles all TLS, and the static ffmpeg only processes local files (downloads go through reqwest) |
|
||||||
|`docker-entrypoint.sh` | Privilege drop: `useradd` with `LOCAL_USER_ID` (default 9001) + `setpriv` (no gosu on bookworm-slim); rejects a non-numeric or `0` `LOCAL_USER_ID`, which would otherwise survive the drop and run the bot root |
|
|`docker-entrypoint.sh` | Privilege drop: `useradd` with `LOCAL_USER_ID` (default 9001) + `setpriv` (no gosu on bookworm-slim); rejects a non-numeric or `0` `LOCAL_USER_ID`, which would otherwise survive the drop and run the bot root |
|
||||||
| `docker-compose.yml` | The deployment composition, committed as-is: every instance value (token, admins, site credentials, domain) is a `${VAR}` substitution read from the gitignored `.env` beside it, so the file needs no per-deployment edit — and a variable not listed in a service's `environment:` never reaches that container. Ships nginx-proxy + acme-companion: webhook mode needs TLS termination in front (teloxide's axum listener is HTTP-only; `WEBHOOK_CERT` only feeds `set_webhook`), bot exposes `VIRTUAL_HOST`/`VIRTUAL_PORT` on the shared `proxy` network, no host port; container names `nginx-proxy`/`acme-companion`/`tgxmb`, start order via `depends_on` (proxy → acme → bot) |
|
| `docker-compose.yml` | The deployment composition, committed as-is: every instance value (token, admins, site credentials, domain) is a `${VAR}` substitution read from the gitignored `.env` beside it, so the file needs no per-deployment edit — and a variable not listed in a service's `environment:` never reaches that container. Ships nginx-proxy + acme-companion: webhook mode needs TLS termination in front (teloxide's axum listener is HTTP-only; `WEBHOOK_CERT` only feeds `set_webhook`), bot exposes `VIRTUAL_HOST`/`VIRTUAL_PORT` on the shared `proxy` network, no host port; container names `nginx-proxy`/`acme-companion`/`tgxmb`, start order via `depends_on` (proxy → acme → bot) |
|
||||||
| `.github/workflows/docker.yml` | CI: build+push to Docker Hub on tag `v*`/master, plus a build-only check on PRs touching the build inputs; **no test step**; verifies a release tag matches both crate versions; buildx gha cache (`cache-from` always, `cache-to` except on PRs, scope `tgxmb-build`, `mode=max`) so cargo deps + ffmpeg layers are restored across runs; `FFMPEG_URL`/`FFMPEG_SHA256` come from repo variables when set |
|
| `.github/workflows/docker.yml` | CI: build+push to Docker Hub on tag `v*`/master, plus a build-only check on PRs touching the build inputs; the push is gated on `ci.yml`'s offline suite (reused via `workflow_call` with `gate: true`, so a red suite blocks the image); verifies a release tag matches both crate versions; buildx gha cache (`cache-from` always, `cache-to` except on PRs, scope `tgxmb-build`, `mode=max`) so cargo deps + ffmpeg layers are restored across runs; `FFMPEG_URL`/`FFMPEG_SHA256` come from repo variables when set |
|
||||||
| `README.md` | Feature docs + command table (Chinese) |
|
| `README.md` | Feature docs + command table (Chinese) |
|
||||||
|
|
||||||
## Runtime/Tooling Preferences
|
## Runtime/Tooling Preferences
|
||||||
|
|||||||
Reference in New Issue
Block a user