From 641620923aca043fb92a1d4abebf916397143f5e Mon Sep 17 00:00:00 2001 From: Roman Lemekha Date: Thu, 25 Jun 2026 18:12:58 +0200 Subject: [PATCH 1/2] feat: add reusable workflows + actions automerge to preset Extract three shared CI workflows so the four active repos (asia-trip-bot, kurwa_bot, docker_infra, training_tracker) call them instead of copy-pasting: - zizmor-reusable.yml: Actions security scan (workflow_call) - validate-renovate-reusable.yml: renovate.json schema validation (config-file input) - docker-publish-reusable.yml: GHCR build/push + Komodo deploy for the bots (image / komodo-stack / komodo-host / context inputs) Also add a packageRules entry to the shared Renovate preset that auto-merges github-actions patch/minor updates, so every consuming repo inherits Renovate automerge for actions without a local rule (Dependabot stays as the backup). Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/docker-publish-reusable.yml | 109 ++++++++++++++++++ .../workflows/validate-renovate-reusable.yml | 44 +++++++ .github/workflows/zizmor-reusable.yml | 31 +++++ renovate-presets/default.json | 10 +- 4 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/docker-publish-reusable.yml create mode 100644 .github/workflows/validate-renovate-reusable.yml create mode 100644 .github/workflows/zizmor-reusable.yml diff --git a/.github/workflows/docker-publish-reusable.yml b/.github/workflows/docker-publish-reusable.yml new file mode 100644 index 0000000..627a8f3 --- /dev/null +++ b/.github/workflows/docker-publish-reusable.yml @@ -0,0 +1,109 @@ +# Reusable: build a Docker image, push it to GHCR, and trigger an instant +# Komodo deploy. Used by the bot repos (asia-trip-bot, kurwa_bot). +# +# Each caller keeps its own `on: push` trigger with a repo-specific `paths:` +# filter (the set of source paths that should rebuild the image), then +# delegates here: +# +# jobs: +# publish: +# uses: roleme/workflows/.github/workflows/docker-publish-reusable.yml@main +# permissions: +# contents: read +# packages: write +# with: +# image: ghcr.io// +# komodo-stack: +# secrets: inherit +# +# Deploy trigger: after the GHCR push, the final step curls Komodo's per-stack +# GitHub listener, HMAC-signing the body with KOMODO_WEBHOOK_SECRET (the +# stack's webhook_secret). Stacks have webhook_force_deploy=true so Komodo runs +# a real DeployStack (pull) — a new :latest leaves compose.yaml unchanged, so +# DeployStackIfChanged would no-op. Without this step the stack still updates +# via Komodo's poll; the webhook just makes it near-instant. +name: docker-publish (reusable) + +on: + workflow_call: + inputs: + image: + description: Fully-qualified GHCR image name (e.g. ghcr.io/owner/repo) + required: true + type: string + komodo-stack: + description: Komodo stack name for the deploy listener URL + required: true + type: string + komodo-host: + description: Komodo host for the deploy listener + required: false + type: string + default: komo.domovas.uk + context: + description: Docker build context + required: false + type: string + default: . + secrets: + KOMODO_WEBHOOK_SECRET: + description: HMAC secret for the Komodo stack webhook + required: true + +jobs: + build-and-push: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + with: + persist-credentials: false + + - name: Log in to GitHub Container Registry + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract image metadata (tags + labels) + id: meta + uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6 + with: + images: ${{ inputs.image }} + tags: | + type=raw,value=latest + + - name: Set up Buildx + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4 + + - name: Build and push + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7 + with: + context: ${{ inputs.context }} + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Trigger Komodo deploy + # All interpolated values come from workflow inputs/secrets (trusted + # caller config, not event payload). They are passed via env and quoted + # so they are never spliced into the command line. + env: + KOMODO_WEBHOOK_SECRET: ${{ secrets.KOMODO_WEBHOOK_SECRET }} + KOMODO_HOST: ${{ inputs.komodo-host }} + KOMODO_STACK: ${{ inputs.komodo-stack }} + run: | + BODY='{"ref":"refs/heads/main"}' + SIG="sha256=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$KOMODO_WEBHOOK_SECRET" | awk '{print $2}')" + curl -fsSL -X POST \ + "https://${KOMODO_HOST}/listener/github/stack/${KOMODO_STACK}/deploy" \ + -H "Content-Type: application/json" \ + -H "X-Hub-Signature-256: $SIG" \ + --data "$BODY" diff --git a/.github/workflows/validate-renovate-reusable.yml b/.github/workflows/validate-renovate-reusable.yml new file mode 100644 index 0000000..b0cc549 --- /dev/null +++ b/.github/workflows/validate-renovate-reusable.yml @@ -0,0 +1,44 @@ +# Reusable Renovate config validation. +# +# Validates renovate.json against the Renovate schema so a broken config is +# caught in a PR instead of silently failing at Renovate runtime. Callers add +# a thin wrapper triggered by pull_request touching renovate.json: +# +# jobs: +# validate-renovate: +# uses: roleme/workflows/.github/workflows/validate-renovate-reusable.yml@main +# +# Node is required for renovate-config-validator (shipped in the renovate npm +# package). +name: validate-renovate (reusable) + +on: + workflow_call: + inputs: + config-file: + description: Path to the Renovate config file to validate + required: false + type: string + default: renovate.json + +permissions: + contents: read + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + with: + persist-credentials: false + + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + with: + node-version: 24 + + - name: Validate Renovate config + # The config path comes from a workflow input (trusted caller), but pass + # it via env and quote it so it is never spliced into the command line. + env: + CONFIG_FILE: ${{ inputs.config-file }} + run: npx --yes --package renovate@43 renovate-config-validator "$CONFIG_FILE" diff --git a/.github/workflows/zizmor-reusable.yml b/.github/workflows/zizmor-reusable.yml new file mode 100644 index 0000000..abae6a8 --- /dev/null +++ b/.github/workflows/zizmor-reusable.yml @@ -0,0 +1,31 @@ +# Reusable zizmor (GitHub Actions security) scan. +# +# Callers add a thin wrapper that triggers on pull_request touching +# .github/workflows/** and delegates here: +# +# jobs: +# zizmor: +# uses: roleme/workflows/.github/workflows/zizmor-reusable.yml@main +# +# advanced-security: false surfaces results as workflow annotations (not a +# SARIF upload), so no security-events permission is needed — contents: read +# is enough. +name: zizmor (reusable) + +on: + workflow_call: {} + +permissions: + contents: read + +jobs: + zizmor: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + with: + persist-credentials: false + - name: Run zizmor + uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6 + with: + advanced-security: false diff --git a/renovate-presets/default.json b/renovate-presets/default.json index 07019f4..6b9c8aa 100644 --- a/renovate-presets/default.json +++ b/renovate-presets/default.json @@ -4,5 +4,13 @@ "extends": ["config:recommended", "helpers:pinGitHubActionDigests"], "minimumReleaseAge": "10 days", "transitiveRemediation": true, - "labels": ["dependencies", "renovate"] + "labels": ["dependencies", "renovate"], + "packageRules": [ + { + "description": "GitHub Actions patch/minor — automerge. Renovate is the primary updater for actions (Dependabot is only a backup); the 10-day minimumReleaseAge above still applies before the merge.", + "matchManagers": ["github-actions"], + "matchUpdateTypes": ["patch", "minor"], + "automerge": true + } + ] } From 106d38e4ab7bc787456d41b0c906c8ccb379cc0e Mon Sep 17 00:00:00 2001 From: Roman Lemekha Date: Thu, 25 Jun 2026 18:19:49 +0200 Subject: [PATCH 2/2] fix: pin setup-node SHA; make komodo-host a required input - dependabot-auto-merge.yml: pin actions/setup-node to the v6 SHA (zizmor unpinned-uses error; matches other repos' pin). - docker-publish-reusable.yml: komodo-host is now a required input with no default. This repo is public, so the previous default leaked an internal infra hostname; callers (private repos) supply their own host. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/dependabot-auto-merge.yml | 2 +- .github/workflows/docker-publish-reusable.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index a95ea0c..a794928 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -28,7 +28,7 @@ jobs: persist-credentials: false - name: Setup Node.js 24.x - uses: actions/setup-node@v4 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: '24' diff --git a/.github/workflows/docker-publish-reusable.yml b/.github/workflows/docker-publish-reusable.yml index 627a8f3..2307302 100644 --- a/.github/workflows/docker-publish-reusable.yml +++ b/.github/workflows/docker-publish-reusable.yml @@ -14,6 +14,7 @@ # with: # image: ghcr.io// # komodo-stack: +# komodo-host: # secrets: inherit # # Deploy trigger: after the GHCR push, the final step curls Komodo's per-stack @@ -36,10 +37,9 @@ on: required: true type: string komodo-host: - description: Komodo host for the deploy listener - required: false + description: Komodo host for the deploy listener (e.g. komodo.example.com) + required: true type: string - default: komo.domovas.uk context: description: Docker build context required: false