diff --git a/.github/workflows/deploy-docker.yml b/.github/workflows/deploy-docker.yml deleted file mode 100644 index 8580a66..0000000 --- a/.github/workflows/deploy-docker.yml +++ /dev/null @@ -1,239 +0,0 @@ -name: Deploy Docker to VPS - -on: - workflow_run: - workflows: ['Build and Push Docker Images'] - types: - - completed - branches: - - main - push: - branches: - - main - paths: - - 'infra/**' - - '.github/workflows/deploy-docker.yml' - - '.github/workflows/docker-build-push.yml' - workflow_dispatch: - -# Prevent multiple deployments from running simultaneously -concurrency: - group: deploy-vps - cancel-in-progress: false - -permissions: - contents: read - packages: read - -jobs: - deploy: - runs-on: ubuntu-latest - timeout-minutes: 30 - if: github.event_name == 'workflow_dispatch' || github.event_name == 'push' || github.event.workflow_run.conclusion == 'success' - steps: - - name: Checkout repository - uses: actions/checkout@v7 - with: - fetch-depth: 1 - submodules: false - - - name: Deploy to VPS - env: - SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} - VPS_HOST: ${{ secrets.VPS_HOST }} - VPS_USER: ${{ secrets.VPS_USER }} - VPS_TARGET_DIR: ${{ secrets.VPS_TARGET_DIR }} - ENV_FILE_PRODUCTION: ${{ secrets.ENV_FILE_PRODUCTION }} - GHCR_USERNAME: ${{ github.actor }} - GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - - echo "Deploy event: ${{ github.event_name }}" - echo "Deploy ref: ${{ github.ref }}" - echo "Deploy sha: ${{ github.sha }}" - - if [ -z "${SSH_PRIVATE_KEY:-}" ] || [ -z "${VPS_HOST:-}" ] || [ -z "${VPS_USER:-}" ] || [ -z "${VPS_TARGET_DIR:-}" ]; then - echo "โŒ Deployment secrets are not fully configured. Please set SSH_PRIVATE_KEY, VPS_HOST, VPS_USER, and VPS_TARGET_DIR." - exit 1 - fi - - mkdir -p ~/.ssh - echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - ssh-keyscan -H -t ed25519,rsa "$VPS_HOST" >> ~/.ssh/known_hosts - - # Use SSH multiplexing for faster subsequent commands - SSH_OPTS=(-o ControlMaster=auto -o ControlPath=/tmp/ssh-%r@%h:%p -o ControlPersist=600 -o StrictHostKeyChecking=yes) - - ssh "${SSH_OPTS[@]}" "$VPS_USER@$VPS_HOST" "mkdir -p $VPS_TARGET_DIR && mkdir -p $VPS_TARGET_DIR/infra/compose" - echo "$ENV_FILE_PRODUCTION" > .env.prod - scp "${SSH_OPTS[@]}" .env.prod "$VPS_USER@$VPS_HOST:$VPS_TARGET_DIR/.env" - - echo "๐Ÿ” Logging in to GitHub Container Registry..." - printf '%s' "$GHCR_TOKEN" | ssh "${SSH_OPTS[@]}" "$VPS_USER@$VPS_HOST" "docker login ghcr.io -u '$GHCR_USERNAME' --password-stdin" - - ssh "${SSH_OPTS[@]}" "$VPS_USER@$VPS_HOST" "export VPS_TARGET_DIR=$VPS_TARGET_DIR; bash -s" <<'EOF' - set -euo pipefail - cd "$VPS_TARGET_DIR" - - # Ensure shared network exists - docker network inspect app-shared-net >/dev/null 2>&1 || docker network create app-shared-net - - echo "๐Ÿ”„ Synchronizing repository..." - if [ ! -d ".git" ]; then - echo "Initializing git repository..." - git init - git remote add origin https://github.com/asepharyana/asepharyana-hub.git - fi - git fetch origin main --depth=1 || true - - # Detect changed files before resetting - ALL_COMPOSE_FILES="infra/compose/traefik.yml infra/compose/shared.yml infra/compose/scraper.yml infra/compose/hub.yml infra/compose/tools.yml infra/compose/llm-api.yml infra/compose/nats.yml infra/compose/dapr.yml infra/compose/observability.yml" - TRAEFIK_DYNAMIC_DIR="infra/traefik/dynamic" - - if git rev-parse HEAD >/dev/null 2>&1; then - BEFORE_REV=$(git rev-parse HEAD) - git reset --hard FETCH_HEAD - AFTER_REV=$(git rev-parse HEAD) - - if [ "$BEFORE_REV" = "$AFTER_REV" ]; then - echo "โ„น๏ธ No new commits detected. Using full file list for safety." - TARGET_COMPOSE="" - else - CHANGED=$(git diff --name-only "$BEFORE_REV" "$AFTER_REV" || true) - echo "๐Ÿ“„ Changed files:" - echo "$CHANGED" - - # Detect compose stack changes - CHANGED_COMPOSE=$(echo "$CHANGED" | grep '^infra/compose/.*\.yml$' || true) - TARGET_COMPOSE="" - for f in $CHANGED_COMPOSE; do - case " $ALL_COMPOSE_FILES " in - *" $f "*) TARGET_COMPOSE="$TARGET_COMPOSE $f" ;; - esac - done - TARGET_COMPOSE=$(printf '%s' "$TARGET_COMPOSE" | xargs || true) - if [ -n "$TARGET_COMPOSE" ]; then - echo "๐ŸŽฏ Detected compose stack changes in: $TARGET_COMPOSE" - else - echo "โ„น๏ธ No stack compose files changed." - fi - - # Detect Traefik dynamic config changes - CHANGED_TRAEFIK=$(echo "$CHANGED" | grep "^$TRAEFIK_DYNAMIC_DIR/" || true) - if [ -n "$CHANGED_TRAEFIK" ]; then - echo "๐ŸŽฏ Detected Traefik dynamic config changes:" - echo "$CHANGED_TRAEFIK" - RELOAD_TRAEFIK="true" - else - echo "โ„น๏ธ No Traefik dynamic config changes." - fi - - # Detect infra file changes (Dockerfiles, config, traefik static) - CHANGED_INFRA=$(echo "$CHANGED" | grep '^infra/' | grep -v '^infra/compose/' || true) - if [ -n "$CHANGED_INFRA" ]; then - echo "๐Ÿ“ฆ Detected other infra file changes:" - echo "$CHANGED_INFRA" - fi - fi - else - git reset --hard FETCH_HEAD - TARGET_COMPOSE="" - fi - - if command -v "docker" >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then - COMPOSE_CMD="docker compose" - elif command -v docker-compose >/dev/null 2>&1; then - COMPOSE_CMD="docker-compose" - else - echo "โŒ docker compose is not installed on the remote host." - exit 1 - fi - - # Always include ALL compose files for dependency resolution - COMPOSE_ARGS="" - for f in $ALL_COMPOSE_FILES; do - if [ -f "$f" ]; then - COMPOSE_ARGS="$COMPOSE_ARGS -f $f" - fi - done - - if [ -n "$TARGET_COMPOSE" ]; then - # Extract service names from target compose file(s) for selective up - TARGET_SERVICES="" - for f in $TARGET_COMPOSE; do - if [ -f "$f" ]; then - svcs=$($COMPOSE_CMD -f "$f" config --services 2>/dev/null | tr '\n' ' ' | xargs) - TARGET_SERVICES="$TARGET_SERVICES $svcs" - fi - done - TARGET_SERVICES=$(echo "$TARGET_SERVICES" | xargs) # trim whitespace - echo "๐ŸŽฏ Selective update for services: $TARGET_SERVICES" - else - echo "๐Ÿš€ Performing full deployment of all services..." - TARGET_SERVICES="" - fi - - echo "๐Ÿ“ฅ Pulling images for target services..." - export DOCKER_CLI_EXPERIMENTAL=enabled - PULL_SUCCESS=false - # Retry pull up to 3 times to handle transient Docker attestation lease errors - for attempt in 1 2 3; do - echo "Pull attempt $attempt/3..." - if $COMPOSE_CMD $COMPOSE_ARGS --env-file .env pull $TARGET_SERVICES; then - echo "โœ… Pull succeeded on attempt $attempt" - PULL_SUCCESS=true - break - else - echo "โš ๏ธ Pull attempt $attempt failed. Retrying in 5s..." - sleep 5 - fi - done - if [ "$PULL_SUCCESS" != "true" ]; then - echo "โŒ Failed to pull images after 3 attempts." - exit 1 - fi - - echo "๐Ÿงน Clearing Git locks..." - rm -f .git/shallow.lock || true - - echo "๐Ÿงน Removing stale target containers by container_name..." - # Extract all explicitly defined container_names from compose files and remove them to prevent conflicts - if [ -n "$TARGET_COMPOSE" ]; then - for f in $TARGET_COMPOSE; do - if [ -f "$f" ]; then - grep "container_name:" "$f" | awk '{print $2}' | while read -r cname; do - docker rm -f "$cname" >/dev/null 2>&1 || true - done - fi - done - else - for f in $ALL_COMPOSE_FILES; do - if [ -f "$f" ]; then - grep "container_name:" "$f" | awk '{print $2}' | while read -r cname; do - docker rm -f "$cname" >/dev/null 2>&1 || true - done - fi - done - fi - - echo "๐Ÿ†™ Starting services..." - echo "๐Ÿ” Debug: Current docker containers:" - docker ps -a - if [ -n "$TARGET_SERVICES" ]; then - $COMPOSE_CMD $COMPOSE_ARGS --env-file .env up -d $TARGET_SERVICES - else - $COMPOSE_CMD $COMPOSE_ARGS --env-file .env up -d --remove-orphans - fi - - # โ”€โ”€ Traefik reload โ”€โ”€ - if [ "${RELOAD_TRAEFIK:-false}" = "true" ]; then - echo "๐Ÿ”„ Traefik dynamic config changed โ€” reloading Traefik..." - # Traefik watches the dynamic config dir (providers.file.watch=true), - # but send SIGHUP as insurance - docker kill --signal HUP traefik 2>/dev/null || docker exec traefik kill -HUP 1 2>/dev/null || true - echo "โœ… Traefik reload signal sent" - fi - EOF - diff --git a/.github/workflows/docker-build-push.yml b/.github/workflows/docker-build-push.yml deleted file mode 100644 index b37540e..0000000 --- a/.github/workflows/docker-build-push.yml +++ /dev/null @@ -1,338 +0,0 @@ -name: Build and Push Docker Images - -on: - push: - branches: - - main - paths: - - 'apps/scraper/**' - - 'apps/hub/**' - - 'apps/tools/**' - - 'apps/llm-api/**' - - '.github/workflows/docker-build-push.yml' - - 'infra/**' - - '!infra/compose/**' - repository_dispatch: - types: [submodule-updated] - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -permissions: - contents: read - -env: - REGISTRY: ghcr.io - IMAGE_NAME_PREFIX: asepharyana/asepharyana-hub - -jobs: - # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - # Phase 1: Detect which services have changed - # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - changes: - runs-on: ubuntu-latest - timeout-minutes: 10 - outputs: - matrix: ${{ steps.set-matrix.outputs.matrix }} - scraper-api: ${{ steps.filter.outputs['scraper-api'] == 'true' || steps.dispatch.outputs['scraper-api'] == 'true' || github.event_name == 'workflow_dispatch' }} - hub: ${{ steps.filter.outputs['hub'] == 'true' || steps.dispatch.outputs['hub'] == 'true' || github.event_name == 'workflow_dispatch' }} - tools: ${{ steps.filter.outputs['tools'] == 'true' || steps.dispatch.outputs['tools'] == 'true' || github.event_name == 'workflow_dispatch' }} - llm-api: ${{ steps.filter.outputs['llm-api'] == 'true' || steps.dispatch.outputs['llm-api'] == 'true' || github.event_name == 'workflow_dispatch' }} - steps: - - uses: actions/checkout@v7 - with: - submodules: false - fetch-depth: 2 - - - name: Detect changed services - id: filter - if: github.event_name == 'push' - env: - BEFORE: ${{ github.event.before }} - AFTER: ${{ github.sha }} - run: | - set -euo pipefail - if [ -z "${BEFORE:-}" ] || [[ "$BEFORE" =~ ^0+$ ]]; then - CHANGED_FILES=$(git ls-files) - else - git fetch --no-tags --depth=2 origin "$BEFORE" || true - CHANGED_FILES=$(git diff --name-only "$BEFORE" "$AFTER") - fi - - changed() { - printf '%s\n' "$CHANGED_FILES" | grep -Eq "$1" && echo true || echo false - } - - echo "scraper-api=$(changed '^(apps/scraper(/|$)|\.github/workflows/docker-build-push\.yml$|infra/docker/scraper\.Dockerfile$)')" >> "$GITHUB_OUTPUT" - echo "hub=$(changed '^(apps/hub(/|$)|\.github/workflows/docker-build-push\.yml$|infra/docker/hub\.Dockerfile$)')" >> "$GITHUB_OUTPUT" - echo "tools=$(changed '^(apps/tools(/|$)|\.github/workflows/docker-build-push\.yml$|infra/docker/tools\.Dockerfile$)')" >> "$GITHUB_OUTPUT" - echo "llm-api=$(changed '^(apps/llm-api(/|$)|\.github/workflows/docker-build-push\.yml$|infra/docker/llm-api\.Dockerfile$)')" >> "$GITHUB_OUTPUT" - - - name: Parse repository_dispatch payload - id: dispatch - if: github.event_name == 'repository_dispatch' - env: - SERVICE: ${{ github.event.client_payload.service }} - SHA: ${{ github.event.client_payload.sha }} - run: | - set -euo pipefail - - if [ -z "${SERVICE:-}" ]; then - echo "::error::repository_dispatch payload missing service" - exit 1 - fi - - if [ -z "${SHA:-}" ]; then - echo "::error::repository_dispatch payload missing sha" - exit 1 - fi - - case "$SERVICE" in - scraper-api|hub|tools|llm-api) ;; - *) - exit 1 - ;; - esac - - if ! [[ "$SHA" =~ ^[0-9a-fA-F]{40}$ ]]; then echo "::error::Invalid sha '$SHA'. Expected 40 hex characters"; fi - - SERVICES=(scraper-api hub tools llm-api) - for svc in "${SERVICES[@]}"; do - if [ "$SERVICE" = "$svc" ]; then - echo "${svc}=true" >> "$GITHUB_OUTPUT" - else - echo "${svc}=false" >> "$GITHUB_OUTPUT" - fi - done - - - name: Set matrix - id: set-matrix - run: | - SERVICES=() - add_service() { - SERVICES+=("{\"id\":\"$1\",\"target\":\"$2\",\"path\":\"$3\"}") - } - - if [ "${{ steps.filter.outputs['scraper-api'] == 'true' || steps.dispatch.outputs['scraper-api'] == 'true' || github.event_name == 'workflow_dispatch' }}" == "true" ]; then add_service "scraper-api" "docker-scraper" "apps/scraper"; fi - if [ "${{ steps.filter.outputs['hub'] == 'true' || steps.dispatch.outputs['hub'] == 'true' || github.event_name == 'workflow_dispatch' }}" == "true" ]; then add_service "hub" "docker-hub" "apps/hub"; fi - if [ "${{ steps.filter.outputs['tools'] == 'true' || steps.dispatch.outputs['tools'] == 'true' || github.event_name == 'workflow_dispatch' }}" == "true" ]; then add_service "tools" "docker-tools" "apps/tools"; fi - if [ "${{ steps.filter.outputs['llm-api'] == 'true' || steps.dispatch.outputs['llm-api'] == 'true' || github.event_name == 'workflow_dispatch' }}" == "true" ]; then add_service "llm-api" "docker-llm-api" "apps/llm-api"; fi - - JSON_ARRAY="[$(IFS=,; echo "${SERVICES[*]}")]" - echo "matrix=$JSON_ARRAY" >> $GITHUB_OUTPUT - - wait-submodule-ref: - needs: [changes] - if: github.event_name == 'repository_dispatch' - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Wait for submodule ref - env: - SERVICE: ${{ github.event.client_payload.service }} - SHA: ${{ github.event.client_payload.sha }} - run: | - set -euo pipefail - - case "$SERVICE" in - "scraper-api") REPO="https://github.com/asepharyana/asepharyana-hub-scraper.git" ;; - "hub") REPO="https://github.com/asepharyana/asepharyana-hub-hub.git" ;; - "tools") echo "tools is built from monorepo, no submodule wait needed"; exit 0 ;; - "llm-api") REPO="https://github.com/asepharyana/asepharyana-hub-llm-api.git" ;; - *) - echo "::error::Unsupported service '$SERVICE'" - exit 1 - ;; - esac - - echo "Waiting for $SERVICE commit $SHA in $REPO" - TMPDIR=$(mktemp -d) - git init "$TMPDIR/probe" >/dev/null - git -C "$TMPDIR/probe" remote add origin "$REPO" - for attempt in {1..30}; do - if git -C "$TMPDIR/probe" fetch --depth=1 origin "$SHA" >/dev/null 2>&1; then - echo "Submodule commit $SHA is fetchable for $SERVICE" - rm -rf "$TMPDIR" - exit 0 - fi - echo "Attempt $attempt/30: $SHA not fetchable yet; waiting 10s" - sleep 10 - done - rm -rf "$TMPDIR" - - echo "::error::Submodule commit $SHA for $SERVICE was not fetchable after 300s" - exit 1 - - # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - # Phase 2: Build and Push Images (Matrix) - # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - build: - needs: [changes, wait-submodule-ref] - runs-on: ubuntu-latest - timeout-minutes: 30 - strategy: - fail-fast: false - matrix: - include: ${{ fromJson(needs.changes.outputs.matrix) }} - if: | - always() && - needs.changes.result == 'success' && - (needs.wait-submodule-ref.result == 'success' || needs.wait-submodule-ref.result == 'skipped') && - needs.changes.outputs.matrix != '[]' - permissions: - contents: read - packages: write - steps: - - uses: actions/checkout@v7 - with: - submodules: false - - - name: Sync submodule locally - env: - EVENT_NAME: ${{ github.event_name }} - DISPATCH_SHA: ${{ github.event.client_payload.sha }} - SUBMODULE_PATH: ${{ matrix.path }} - run: | - set -euo pipefail - git submodule update --init --recursive "$SUBMODULE_PATH" - if [ "$EVENT_NAME" = "repository_dispatch" ] && [ -n "${DISPATCH_SHA:-}" ]; then - cd "$SUBMODULE_PATH" - git fetch origin "$DISPATCH_SHA" - git checkout "$DISPATCH_SHA" - cd "${GITHUB_WORKSPACE}" - fi - - - uses: docker/login-action@v4 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 - - - name: Docker metadata - id: meta - run: | - SVC_NAME="${{ matrix.id }}" - SHORT=${GITHUB_SHA:0:7} - echo "image=${REGISTRY}/${IMAGE_NAME_PREFIX}/${SVC_NAME}" >> $GITHUB_OUTPUT - echo "tags=${REGISTRY}/${IMAGE_NAME_PREFIX}/${SVC_NAME}:sha-${SHORT}" >> $GITHUB_OUTPUT - echo "cache-registry=${REGISTRY}/${IMAGE_NAME_PREFIX}/${SVC_NAME}:buildcache" >> $GITHUB_OUTPUT - case "$SVC_NAME" in - "scraper-api") echo "dockerfile=infra/docker/scraper.Dockerfile" >> $GITHUB_OUTPUT ;; - "hub") echo "dockerfile=infra/docker/hub.Dockerfile" >> $GITHUB_OUTPUT ;; - "tools") echo "dockerfile=infra/docker/tools.Dockerfile" >> $GITHUB_OUTPUT ;; - "llm-api") echo "dockerfile=infra/docker/llm-api.Dockerfile" >> $GITHUB_OUTPUT ;; - esac - - - name: Build and Push Docker image - uses: docker/build-push-action@v7 - with: - context: . - file: ${{ steps.meta.outputs.dockerfile }} - push: true - tags: ${{ steps.meta.outputs.tags }} - build-args: | - COMMIT_COUNT=${{ env.NR_COMMIT_COUNT || github.run_number }} - COMMIT_SHA=${{ env.NR_COMMIT_SHA || github.sha }} - cache-from: type=registry,ref=${{ steps.meta.outputs['cache-registry'] }} - cache-to: type=registry,ref=${{ steps.meta.outputs['cache-registry'] }},mode=max - - # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - # Phase 3: Update Manifests and Submodule Refs - # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - update-manifest: - needs: [changes, wait-submodule-ref, build] - if: | - always() && - needs.changes.result == 'success' && - (needs.wait-submodule-ref.result == 'success' || needs.wait-submodule-ref.result == 'skipped') && - (needs.build.result == 'success' || needs.build.result == 'skipped') - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: - contents: write - steps: - - uses: actions/checkout@v7 - with: - submodules: false - token: ${{ secrets.GITHUB_TOKEN }} - ref: main - - - name: Update tags and submodules - run: | - SHORT_SHA=${GITHUB_SHA:0:7} - TAG="sha-$SHORT_SHA" - CHANGED=false - - declare -A SERVICES - SERVICES["scraper-api"]="scraper.yml" - SERVICES["hub"]="hub.yml" - SERVICES["tools"]="tools.yml" - SERVICES["llm-api"]="llm-api.yml" - - declare -A PATHS - PATHS["scraper-api"]="apps/scraper" - PATHS["hub"]="apps/hub" - PATHS["tools"]="apps/tools" - PATHS["llm-api"]="apps/llm-api" - - # Use git config for possible commits - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - - for id in "${!SERVICES[@]}"; do - SHOULD_HAVE_RUN=false - if [ "${{ needs.changes.outputs['scraper-api'] }}" == "true" ] && [ "$id" == "scraper-api" ]; then SHOULD_HAVE_RUN=true; fi - if [ "${{ needs.changes.outputs['hub'] }}" == "true" ] && [ "$id" == "hub" ]; then SHOULD_HAVE_RUN=true; fi - if [ "${{ needs.changes.outputs['tools'] }}" == "true" ] && [ "$id" == "tools" ]; then SHOULD_HAVE_RUN=true; fi - if [ "${{ needs.changes.outputs['llm-api'] }}" == "true" ] && [ "$id" == "llm-api" ]; then SHOULD_HAVE_RUN=true; fi - - if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then SHOULD_HAVE_RUN=true; fi - - if [ "$SHOULD_HAVE_RUN" == "true" ]; then - COMPOSE_FILE="infra/compose/${SERVICES[$id]}" - if [ -f "$COMPOSE_FILE" ]; then - echo "Updating $COMPOSE_FILE to $TAG" - sed -i "s|image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_PREFIX }}/$id:.*|image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_PREFIX }}/$id:$TAG|g" "$COMPOSE_FILE" - git add "$COMPOSE_FILE" - CHANGED=true - fi - - # If it's a repository_dispatch for this specific service, update its submodule pointer - if [ "${{ github.event_name }}" == "repository_dispatch" ] && [ "${{ github.event.client_payload.service }}" == "$id" ]; then - SHA_DISPATCH="${{ github.event.client_payload.sha }}" - SUB_PATH="${PATHS[$id]}" - if [ -n "$SHA_DISPATCH" ]; then - echo "Updating submodule $SUB_PATH to $SHA_DISPATCH" - git submodule update --init "$SUB_PATH" - git -C "$SUB_PATH" fetch origin "$SHA_DISPATCH" - git -C "$SUB_PATH" checkout "$SHA_DISPATCH" - git add "$SUB_PATH" - CHANGED=true - fi - fi - fi - done - - if [ "$CHANGED" == "true" ]; then - git commit -m "chore: update manifests and submodules [skip ci]" - - for attempt in {1..3}; do - if git pull --rebase origin main && git push origin main; then - exit 0 - fi - echo "Manifest push attempt $attempt/3 failed; retrying" - git rebase --abort || true - git pull --rebase origin main || true - sleep 5 - done - - echo "::error::Failed to push manifest update after 3 attempts" - exit 1 - else - echo "No changes detected." - fi