diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml new file mode 100644 index 0000000..a2d6dd7 --- /dev/null +++ b/.github/workflows/renovate.yml @@ -0,0 +1,61 @@ +# Reusable Renovate runner — the consumer-side updater a repo can't get from +# Dependabot. Each consumer calls this on a schedule from its own thin wrapper, +# exactly like upgrade-check.yml: it runs against the CALLING repo +# (github.repository), never a hardcoded list. +# +# Renovate reads composite action.yml (Dependabot is blind to composites — +# dependabot-core#6704), so it keeps every whuppi/ci ref uniform (workflow AND +# composite) and bumps third-party actions hidden in composites. Update rules +# live in the consumer's own renovate.json5. +# +# TOKEN: RENOVATE_TOKEN — a fine-grained PAT (Contents + Workflows + +# Pull-requests + Issues: write) on all whuppi repos, held as a whuppi ORG +# secret and passed in by the caller. GITHUB_TOKEN can't write .github/workflows/, +# which is exactly what Renovate must do — hence the PAT. +name: Renovate + +on: + workflow_call: + inputs: + dryRun: + type: boolean + required: false + default: true # safe default; callers opt into live with dryRun: false + logLevel: + type: string + required: false + default: 'info' + secrets: + RENOVATE_TOKEN: + required: true + +permissions: {} + +concurrency: + group: renovate-${{ github.repository }} + cancel-in-progress: false + +jobs: + renovate: + name: Renovate + runs-on: ubuntu-24.04 + timeout-minutes: 30 + permissions: + contents: write # push bump branches + pull-requests: write # open bump PRs + issues: write # the dependency dashboard issue + steps: + # No checkout: the Renovate action clones the target repo itself via the + # token (which is why there are no persisted git credentials to leak). + - uses: renovatebot/github-action@3064367f740a1a91cca218698a63902689cce200 # v46.1.20 + with: + token: ${{ secrets.RENOVATE_TOKEN }} + renovate-version: 43.279.0 + env: + # The calling consumer repo — generic, never hardcoded. + RENOVATE_REPOSITORIES: ${{ github.repository }} + RENOVATE_AUTODISCOVER: 'false' + # dryRun true → 'full' (detect only); false → '' (live). 'full' is + # truthy so this ternary is sound. + RENOVATE_DRY_RUN: ${{ inputs.dryRun && 'full' || '' }} + LOG_LEVEL: ${{ inputs.logLevel }} diff --git a/deploy/.deploy/secrets.json.example b/deploy/.deploy/secrets.json.example index 107b293..e06b212 100644 --- a/deploy/.deploy/secrets.json.example +++ b/deploy/.deploy/secrets.json.example @@ -1,5 +1,6 @@ { "github": { - "release_token": "FINE_GRAINED_PAT_contents+workflows_RW_on_whuppi/ci" + "release_token": "FINE_GRAINED_PAT_contents+workflows_RW_on_whuppi/ci", + "renovate_token": "FINE_GRAINED_PAT_contents+workflows+PRs+issues_RW_on_ALL_whuppi_repos" } } diff --git a/deploy/.deploy/secrets.sh b/deploy/.deploy/secrets.sh index 65ff47d..e0bb651 100755 --- a/deploy/.deploy/secrets.sh +++ b/deploy/.deploy/secrets.sh @@ -17,6 +17,7 @@ set -e REPO="whuppi/ci" +ORG="whuppi" BWS="${HOME}/bin/bws --color no" BW_PREFIX="ci" export BWS_SERVER_URL="${BWS_SERVER_URL:-https://vault.bitwarden.eu}" @@ -83,8 +84,8 @@ bws_set() { validate_env() { case "$1" in - release) return 0 ;; - *) echo "Invalid env: $1 (use release)"; exit 1 ;; + release|org) return 0 ;; + *) echo "Invalid env: $1 (use release or org)"; exit 1 ;; esac } @@ -109,8 +110,15 @@ cmd_set() { local ghn ghn="$key" - gh secret set "$ghn" --env "$env" --body "$value" --repo "$REPO" - echo "✓ GitHub: $REPO → $env → $ghn" + if [ "$env" = "org" ]; then + # Org-wide secret, visible to every whuppi repo — used by reusable + # workflows running in a consumer's context (e.g. Renovate). + gh secret set "$ghn" --org "$ORG" --visibility all --body "$value" + echo "✓ GitHub: org $ORG → $ghn (all repos)" + else + gh secret set "$ghn" --env "$env" --body "$value" --repo "$REPO" + echo "✓ GitHub: $REPO → $env → $ghn" + fi } # Deletes from both backends, scoped to THIS repo only: GitHub by --repo/--env, @@ -129,17 +137,18 @@ cmd_rm() { key=$(echo "$path" | cut -d/ -f2-) validate_env "$env" - local ghn + local ghn scope_args ghn="$key" - if gh secret list --env "$env" --repo "$REPO" 2>/dev/null | grep -qE "^${ghn}[[:space:]]"; then - if gh secret delete "$ghn" --env "$env" --repo "$REPO" 2>/dev/null; then - echo "✓ GitHub: $REPO → $env → $ghn (deleted)" + if [ "$env" = "org" ]; then scope_args=(--org "$ORG"); else scope_args=(--env "$env" --repo "$REPO"); fi + if gh secret list "${scope_args[@]}" 2>/dev/null | grep -qE "^${ghn}[[:space:]]"; then + if gh secret delete "$ghn" "${scope_args[@]}" 2>/dev/null; then + echo "✓ GitHub: $env → $ghn (deleted)" else - echo "✗ GitHub: $REPO → $env → $ghn (delete FAILED — still present)" + echo "✗ GitHub: $env → $ghn (delete FAILED — still present)" exit 1 fi else - echo "· GitHub: $REPO → $env → $ghn (not present)" + echo "· GitHub: $env → $ghn (not present)" fi local bw_key="$BW_PREFIX/$env/$key"