name: Laravel CI-CD on: push: branches: ["main"] env: REGISTRY: gitea.leonmorival.com IMAGE_NAME: daily_meal/bemeal-api jobs: # ========================= # TESTS # ========================= test: name: Tests Unitaires runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Run Tests uses: docker://laravelsail/php84-composer:latest env: APP_ENV: testing APP_KEY: base64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= DB_CONNECTION: sqlite DB_DATABASE: ":memory:" with: args: | bash -c "composer install --no-interaction --ignore-platform-req=ext-intl && php artisan test" # ========================= # BUILD & PUSH IMAGE # ========================= build: name: Build & Push Docker needs: test runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Login to Gitea Registry run: | echo "${{ secrets.TOKEN_GITEA }}" \ | docker login $REGISTRY \ -u "${{ github.actor }}" \ --password-stdin - name: Build Docker image run: | IMAGE=$REGISTRY/$IMAGE_NAME docker build -t "$IMAGE:${{ github.sha }}" -t "$IMAGE:latest" . docker push "$IMAGE:${{ github.sha }}" docker push "$IMAGE:latest" # ========================= # DEPLOY # ========================= deploy: name: Deploy Blue/Green needs: build runs-on: ubuntu-latest steps: - name: Checkout deploy files uses: actions/checkout@v4 - name: Upload Docker Compose file uses: appleboy/scp-action@v0.1.7 with: host: ${{ secrets.SSH_HOST }} username: ${{ secrets.SSH_USER }} key: ${{ secrets.SSH_PRIVATE_KEY }} source: docker-compose.prod.yml target: ${{ secrets.DEPLOY_PATH || '/opt/bemeal' }} - name: Deploy via SSH (Blue/Green) uses: appleboy/ssh-action@v1.0.3 env: IMAGE_TAG: ${{ github.sha }} DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }} with: host: ${{ secrets.SSH_HOST }} username: ${{ secrets.SSH_USER }} key: ${{ secrets.SSH_PRIVATE_KEY }} envs: REGISTRY,IMAGE_NAME,IMAGE_TAG,DEPLOY_PATH script: | set -eu APP_DIR="${DEPLOY_PATH:-/opt/bemeal}" COMPOSE_FILE="docker-compose.prod.yml" COMPOSE_ENV_FILE=".env.prod" UPSTREAM_FILE="/etc/nginx/conf.d/bemeal-upstream.inc" DRAIN_SECONDS="${DRAIN_SECONDS:-20}" if [ "$(id -u)" -eq 0 ]; then SUDO="" else SUDO="sudo" fi if [ ! -d "$APP_DIR" ]; then echo "Missing deploy directory: $APP_DIR" echo "Set the DEPLOY_PATH secret to the directory containing $COMPOSE_FILE." exit 1 fi cd "$APP_DIR" if [ ! -f "$COMPOSE_FILE" ]; then echo "Missing $APP_DIR/$COMPOSE_FILE" echo "Set the DEPLOY_PATH secret to the directory containing $COMPOSE_FILE, or copy the compose file there." exit 1 fi if [ -f "$COMPOSE_ENV_FILE" ]; then COMPOSE="docker compose --env-file $COMPOSE_ENV_FILE -f $COMPOSE_FILE" else COMPOSE="docker compose -f $COMPOSE_FILE" fi IMAGE="$REGISTRY/$IMAGE_NAME:$IMAGE_TAG" export IMAGE_TAG echo "Pull image $IMAGE" docker pull "$IMAGE" echo "Ensure shared services are running" $COMPOSE up -d pgsql redis meilisearch ACTIVE="" if grep -q "127.0.0.1:8081" "$UPSTREAM_FILE" 2>/dev/null; then ACTIVE="blue" elif grep -q "127.0.0.1:8082" "$UPSTREAM_FILE" 2>/dev/null; then ACTIVE="green" fi if [ "$ACTIVE" = "blue" ]; then TARGET="green" TARGET_PORT="8082" else TARGET="blue" TARGET_PORT="8081" fi TARGET_SERVICE="bemeal-api-$TARGET" echo "Start $TARGET_SERVICE" $COMPOSE up -d --force-recreate --no-deps "$TARGET_SERVICE" echo "Wait for $TARGET_SERVICE healthcheck" HEALTH_STATUS="" for attempt in $(seq 1 60); do HEALTH_STATUS="$(docker inspect --format='{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$TARGET_SERVICE" 2>/dev/null || true)" if [ "$HEALTH_STATUS" = "healthy" ]; then break fi sleep 2 done if [ "$HEALTH_STATUS" != "healthy" ]; then docker logs --tail=200 "$TARGET_SERVICE" || true exit 1 fi curl -fsS "http://127.0.0.1:$TARGET_PORT/api/health" >/dev/null if [ -f "$UPSTREAM_FILE" ]; then $SUDO cp "$UPSTREAM_FILE" "$UPSTREAM_FILE.bak" fi printf '%s\n' \ 'upstream bemeal_api {' \ " server 127.0.0.1:$TARGET_PORT max_fails=3 fail_timeout=10s;" \ ' keepalive 32;' \ '}' | $SUDO tee "$UPSTREAM_FILE" >/dev/null if ! $SUDO nginx -t; then if [ -f "$UPSTREAM_FILE.bak" ]; then $SUDO mv "$UPSTREAM_FILE.bak" "$UPSTREAM_FILE" fi exit 1 fi echo "Switch Nginx traffic to $TARGET" $SUDO nginx -s reload $SUDO rm -f "$UPSTREAM_FILE.bak" echo "Restart workers on the new image" $COMPOSE up -d --force-recreate --no-deps horizon scheduler if [ -n "$ACTIVE" ] && [ "$ACTIVE" != "$TARGET" ]; then echo "Drain old $ACTIVE service for $DRAIN_SECONDS seconds" sleep "$DRAIN_SECONDS" $COMPOSE stop "bemeal-api-$ACTIVE" || true $COMPOSE rm -f "bemeal-api-$ACTIVE" || true fi