Skip to content

Renovate

Renovate #74

Workflow file for this run

name: Renovate
# Self-hosted Renovate runner. Replaces the Mend-hosted scheduler so we can tick
# more frequently than ~4h and clear the dependency backlog within the existing
# automerge windows. See PLA-48 for the throughput rationale;
# .github/renovate.json5 is still the source of truth for package rules,
# schedule, and automergeSchedule. This workflow adds the live GitHub open-PR
# cap because Renovate's config-level limits are not enough to express "run
# maintenance, but create no more PRs once 10 are open."
on:
schedule:
# Wake the runner only inside the windows where Renovate is actually
# allowed to open or maintain PRs (see `schedule` in renovate.json5),
# plus a single weekday daytime tick to keep vulnerability-alert state
# fresh in the Dependency Dashboard. Ticks outside those windows aren't
# no-ops in practice — each one is a 20-30min full extract — so the
# previous `17 * * * *` was burning ~17h/day of Actions compute for no
# merge benefit.
#
# :17-past-the-hour offset avoids top-of-hour GH Actions scheduler
# contention, which was dropping roughly every other tick on `0 * * * *`.
# Hourly cadence inside active windows roughly matches Ghost's CI
# duration, giving each merge a fresh rebase + green CI window.
#
# Times are UTC; matches renovate.json5's `schedule` block.
# Weekday early-morning window (Mon-Sat 00:00-04:59 UTC)
- cron: '17 0-4 * * 1-6'
# Weekday evening window (Mon-Fri 23:00-23:59 UTC)
- cron: '17 23 * * 1-5'
# Weekend - every 2h is plenty; no automerge urgency, just batch creation
- cron: '17 */2 * * 0,6'
# Weekday daytime CVE pickup tick (Mon-Fri 14:17 UTC)
- cron: '17 14 * * 1-5'
workflow_dispatch:
inputs:
ignoreSchedule:
description: 'Ignore Renovate schedule for this manual run'
required: false
default: false
type: boolean
concurrency:
group: renovate
cancel-in-progress: false
permissions:
contents: read
jobs:
renovate:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Get GitHub App token
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
with:
app-id: ${{ secrets.TRYGHOST_RENOVATE_APP_ID }}
private-key: ${{ secrets.TRYGHOST_RENOVATE_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: Ghost
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
# Enforce a live cap on open Renovate PRs while still letting Renovate
# maintain and automerge the PRs that already exist.
#
# Why this lives outside renovate.json5:
# - prConcurrentLimit/branchConcurrentLimit are useful guardrails, but
# Renovate computes them from the branches in the current run rather
# than by asking GitHub for every open Renovate PR.
# - vulnerability-alert PRs can bypass Renovate's normal branch, PR,
# hourly, and schedule limits.
#
# Below the cap, restrict PR creation to the number of slots left. At or
# above the cap, or when RENOVATE_MAINTENANCE_ONLY=true, force dashboard
# approval for new branches/PRs while allowing existing PR branches to
# keep updating outside the creation schedule. That gives us "rebase and
# merge the 10, but create no more."
- name: Configure Renovate PR cap
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
PR_CAP: ${{ vars.RENOVATE_OPEN_PR_CAP || '10' }}
MAINTENANCE_ONLY: ${{ vars.RENOVATE_MAINTENANCE_ONLY || 'false' }}
IGNORE_SCHEDULE: ${{ github.event_name == 'workflow_dispatch' && inputs.ignoreSchedule }}
run: |
set -euo pipefail
if ! [[ "$PR_CAP" =~ ^[0-9]+$ ]]; then
echo "::warning::RENOVATE_OPEN_PR_CAP must be a non-negative integer; falling back to 10."
PR_CAP=10
fi
open_count=$(gh pr list \
--repo "${{ github.repository }}" \
--author "app/tryghost-renovate" \
--state open \
--json number --jq 'length')
echo "Renovate has $open_count open PRs (cap: $PR_CAP)"
if [ "$MAINTENANCE_ONLY" = "true" ]; then
force='{"dependencyDashboardApproval":true,"prCreation":"approval","updateNotScheduled":true,"vulnerabilityAlerts":{"dependencyDashboardApproval":false}}'
echo "::notice::RENOVATE_MAINTENANCE_ONLY=true. Running in maintenance-only mode: existing PRs may update/automerge, new PRs require dashboard approval."
elif [ "$open_count" -ge "$PR_CAP" ]; then
force='{"dependencyDashboardApproval":true,"prCreation":"approval","updateNotScheduled":true,"vulnerabilityAlerts":{"dependencyDashboardApproval":false}}'
echo "::notice::Renovate is at or above the open PR cap. Running in maintenance-only mode: existing PRs may update/automerge, new PRs require dashboard approval."
else
remaining=$((PR_CAP - open_count))
force="{\"prHourlyLimit\":$remaining}"
if [ "$IGNORE_SCHEDULE" = "true" ]; then
force="{\"schedule\":null,\"automergeSchedule\":null,\"prHourlyLimit\":$remaining}"
fi
echo "Renovate may create up to $remaining PR(s) in this run."
fi
echo "RENOVATE_FORCE=$force" >> "$GITHUB_ENV"
- name: Self-hosted Renovate
uses: renovatebot/github-action@693b9ef15eec82123529a37c782242f091365961 # v46.1.14
with:
token: ${{ steps.app-token.outputs.token }}
env:
LOG_LEVEL: debug
RENOVATE_REPOSITORY_CACHE: enabled
RENOVATE_REPOSITORIES: TryGhost/Ghost
# Ghost is already onboarded via Mend; don't open an onboarding PR.
RENOVATE_ONBOARDING: 'false'