Check Upstream Versions #9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # BeeCompose Upstream Version Checker | |
| # | |
| # Automatically checks for new versions of base images and creates PRs to update .env files. | |
| # Runs weekly or can be triggered manually. | |
| # | |
| # How it works: | |
| # 1. For each service, reads the current version from .env | |
| # 2. Queries Docker Hub/registries for latest stable versions | |
| # 3. If a newer version is found, updates .env and creates a PR | |
| # | |
| # This complements Dependabot by handling: | |
| # - Custom version variable patterns (*_VERSION in .env) | |
| # - Semantic version filtering (excludes -rc, -beta, -alpha) | |
| # - Batch updates with a single PR per service | |
| name: Check Upstream Versions | |
| on: | |
| schedule: | |
| # Run every Sunday at 05:00 UTC | |
| - cron: '0 5 * * 0' | |
| workflow_dispatch: | |
| inputs: | |
| service: | |
| description: 'Service to check (empty for all)' | |
| required: false | |
| type: string | |
| dry_run: | |
| description: 'Dry run - check only, do not create PRs' | |
| required: false | |
| default: false | |
| type: boolean | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # Prevent concurrent version checking runs | |
| concurrency: | |
| group: check-versions | |
| cancel-in-progress: true | |
| # Default permissions (restrictive) - jobs override as needed | |
| permissions: | |
| contents: read | |
| jobs: | |
| # ============================================================================ | |
| # Job 1: Check Versions | |
| # ============================================================================ | |
| check-versions: | |
| name: Check Versions | |
| runs-on: ubuntu-latest | |
| outputs: | |
| updates: ${{ steps.check.outputs.updates }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Install dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y jq curl | |
| - name: Check upstream versions | |
| id: check | |
| run: | | |
| set -euo pipefail | |
| # Version checking registry for each image | |
| declare -A IMAGE_REGISTRIES=( | |
| # Format: [service]="registry/image" | |
| ["bitwarden"]="vaultwarden/server" | |
| ["cabot"]="cabotapp/cabot" | |
| ["claude-code"]="beevelop/claude" | |
| ["cloudflared"]="cloudflare/cloudflared" | |
| ["confluence"]="atlassian/confluence" | |
| ["crowd"]="atlassian/crowd" | |
| ["dependency-track"]="dependencytrack/apiserver" | |
| ["directus"]="directus/directus" | |
| ["duckling"]="rasa/duckling" | |
| ["gitlab"]="sameersbn/gitlab" | |
| ["graylog"]="graylog/graylog" | |
| ["huginn"]="huginn/huginn" | |
| ["jira"]="atlassian/jira-software" | |
| ["keycloak"]="quay.io/keycloak/keycloak" | |
| ["metabase"]="metabase/metabase" | |
| ["minio"]="minio/minio" | |
| ["monica"]="monica" | |
| ["mysql"]="library/mysql" | |
| ["n8n"]="n8nio/n8n" | |
| ["nexus"]="sonatype/nexus3" | |
| ["openvpn"]="kylemanna/openvpn" | |
| ["phpmyadmin"]="library/phpmyadmin" | |
| ["redash"]="redash/redash" | |
| ["registry"]="library/registry" | |
| ["rundeck"]="rundeck/rundeck" | |
| ["sentry"]="getsentry/sentry" | |
| ["shields"]="shieldsio/shields" | |
| ["sonarqube"]="library/sonarqube" | |
| ["statping"]="statping/statping" | |
| ["traefik"]="library/traefik" | |
| ["traefik-tunnel"]="library/traefik" | |
| ["tus"]="tusproject/tusd" | |
| ["weblate"]="weblate/weblate" | |
| ["zabbix"]="zabbix/zabbix-server-pgsql" | |
| ) | |
| # Function to get latest stable tag from Docker Hub | |
| get_latest_tag() { | |
| local image="$1" | |
| local registry_type="${2:-dockerhub}" | |
| case "$registry_type" in | |
| dockerhub) | |
| # Query Docker Hub API for tags | |
| curl -sL "https://hub.docker.com/v2/repositories/${image}/tags?page_size=100" 2>/dev/null | \ | |
| jq -r '.results[]?.name' 2>/dev/null | \ | |
| grep -E '^v?[0-9]+\.[0-9]+(\.[0-9]+)?(-[0-9]+)?$' | \ | |
| grep -vE '(alpha|beta|rc|dev|nightly|snapshot|latest|edge)' | \ | |
| sort -V | \ | |
| tail -1 || echo "" | |
| ;; | |
| quay) | |
| # Query Quay.io API | |
| local repo="${image#quay.io/}" | |
| curl -sL "https://quay.io/api/v1/repository/${repo}/tag/?limit=100" 2>/dev/null | \ | |
| jq -r '.tags[]?.name' 2>/dev/null | \ | |
| grep -E '^v?[0-9]+\.[0-9]+(\.[0-9]+)?$' | \ | |
| grep -vE '(alpha|beta|rc|dev|nightly|snapshot|latest)' | \ | |
| sort -V | \ | |
| tail -1 || echo "" | |
| ;; | |
| esac | |
| } | |
| # Function to normalize version (strip 'v' prefix for comparison) | |
| normalize_version() { | |
| echo "$1" | sed 's/^v//' | |
| } | |
| # Function to compare versions (returns 0 if $1 > $2) | |
| version_gt() { | |
| test "$(printf '%s\n' "$1" "$2" | sort -V | tail -1)" = "$1" && test "$1" != "$2" | |
| } | |
| UPDATES="[]" | |
| # Determine which services to check | |
| if [[ -n "${{ inputs.service }}" ]]; then | |
| SERVICES="${{ inputs.service }}" | |
| else | |
| SERVICES="${!IMAGE_REGISTRIES[@]}" | |
| fi | |
| echo "=== Checking upstream versions ===" | |
| for SERVICE in $SERVICES; do | |
| if [[ ! -d "services/${SERVICE}" ]]; then | |
| echo "⚠ Service not found: ${SERVICE}" | |
| continue | |
| fi | |
| ENV_FILE="services/${SERVICE}/.env" | |
| if [[ ! -f "$ENV_FILE" ]]; then | |
| echo "⚠ No .env file: ${SERVICE}" | |
| continue | |
| fi | |
| IMAGE="${IMAGE_REGISTRIES[$SERVICE]:-}" | |
| if [[ -z "$IMAGE" ]]; then | |
| echo "⚠ No registry mapping: ${SERVICE}" | |
| continue | |
| fi | |
| # Get current version from .env | |
| CURRENT=$(grep -E '_VERSION=' "$ENV_FILE" 2>/dev/null | head -1 | cut -d'=' -f2 || echo "") | |
| if [[ -z "$CURRENT" ]]; then | |
| echo "⚠ No version in .env: ${SERVICE}" | |
| continue | |
| fi | |
| # Determine registry type | |
| REGISTRY_TYPE="dockerhub" | |
| [[ "$IMAGE" == quay.io/* ]] && REGISTRY_TYPE="quay" | |
| # Get latest stable version | |
| LATEST=$(get_latest_tag "$IMAGE" "$REGISTRY_TYPE") | |
| if [[ -z "$LATEST" ]]; then | |
| echo "⚠ Could not fetch latest: ${SERVICE} (${IMAGE})" | |
| continue | |
| fi | |
| # Compare versions | |
| CURRENT_NORM=$(normalize_version "$CURRENT") | |
| LATEST_NORM=$(normalize_version "$LATEST") | |
| if version_gt "$LATEST_NORM" "$CURRENT_NORM"; then | |
| echo "✓ Update available: ${SERVICE} ${CURRENT} → ${LATEST}" | |
| UPDATES=$(echo "$UPDATES" | jq --arg s "$SERVICE" --arg c "$CURRENT" --arg l "$LATEST" \ | |
| '. + [{"service": $s, "current": $c, "latest": $l}]') | |
| else | |
| echo "· Up to date: ${SERVICE} (${CURRENT})" | |
| fi | |
| done | |
| echo "" | |
| echo "=== Summary ===" | |
| COUNT=$(echo "$UPDATES" | jq 'length') | |
| echo "Updates available: $COUNT" | |
| echo "" | |
| echo "updates=$UPDATES" >> $GITHUB_OUTPUT | |
| - name: Generate summary | |
| run: | | |
| UPDATES='${{ steps.check.outputs.updates }}' | |
| COUNT=$(echo "$UPDATES" | jq 'length') | |
| cat >> $GITHUB_STEP_SUMMARY << EOF | |
| ## Upstream Version Check | |
| **Updates available:** $COUNT | |
| | Service | Current | Latest | | |
| |---------|---------|--------| | |
| EOF | |
| echo "$UPDATES" | jq -r '.[] | "| \(.service) | \(.current) | \(.latest) |"' >> $GITHUB_STEP_SUMMARY | |
| if [[ "$COUNT" -eq 0 ]]; then | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "✅ All services are up to date!" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| # ============================================================================ | |
| # Job 2: Create Update PRs | |
| # ============================================================================ | |
| create-prs: | |
| name: Create PRs | |
| needs: check-versions | |
| if: ${{ !inputs.dry_run && needs.check-versions.outputs.updates != '[]' }} | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| max-parallel: 3 | |
| matrix: | |
| update: ${{ fromJson(needs.check-versions.outputs.updates) }} | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Update .env file | |
| run: | | |
| SERVICE="${{ matrix.update.service }}" | |
| CURRENT="${{ matrix.update.current }}" | |
| LATEST="${{ matrix.update.latest }}" | |
| ENV_FILE="services/${SERVICE}/.env" | |
| echo "Updating ${SERVICE}: ${CURRENT} → ${LATEST}" | |
| # Get the variable name | |
| VAR_NAME=$(grep -E '_VERSION=' "$ENV_FILE" | head -1 | cut -d'=' -f1) | |
| # Update the version | |
| sed -i "s/^${VAR_NAME}=.*/${VAR_NAME}=${LATEST}/" "$ENV_FILE" | |
| echo "Updated ${ENV_FILE}:" | |
| cat "$ENV_FILE" | |
| - name: Create Pull Request | |
| uses: peter-evans/create-pull-request@v8 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| commit-message: "${{ matrix.update.service }}: ${{ matrix.update.latest }}" | |
| title: "${{ matrix.update.service }}: Update to ${{ matrix.update.latest }}" | |
| body: | | |
| ## Automated Version Update | |
| This PR updates **${{ matrix.update.service }}** from `${{ matrix.update.current }}` to `${{ matrix.update.latest }}`. | |
| ### Changes | |
| - Updated `.env` version variable | |
| ### Checklist | |
| - [ ] Review upstream changelog for breaking changes | |
| - [ ] Test locally if needed | |
| - [ ] Merge when ready | |
| --- | |
| *This PR was automatically created by the upstream version checker workflow.* | |
| branch: "update/${{ matrix.update.service }}-${{ matrix.update.latest }}" | |
| base: main | |
| labels: | | |
| dependencies | |
| automated | |
| delete-branch: true |