Add Gitea workflows for CI/CD pipeline
This commit is contained in:
122
.gitea/workflows/build.yml
Normal file
122
.gitea/workflows/build.yml
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
name: Build Application Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows: ["Run Tests"]
|
||||||
|
types:
|
||||||
|
- completed
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- production
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
image_tag:
|
||||||
|
description: 'Image tag (default: latest)'
|
||||||
|
required: false
|
||||||
|
default: 'latest'
|
||||||
|
|
||||||
|
env:
|
||||||
|
IMAGE_NAME: jd-book-uploader
|
||||||
|
IMAGE_TAG: ${{ inputs.image_tag || 'latest' }}
|
||||||
|
REGISTRY: ${{ secrets.REGISTRY_URL || '' }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build with Pack
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
|
||||||
|
outputs:
|
||||||
|
image: ${{ steps.image.outputs.full }}
|
||||||
|
image-digest: ${{ steps.build.outputs.digest }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Configure Docker Socket
|
||||||
|
run: |
|
||||||
|
# Detect Docker socket location (handles rootless Docker)
|
||||||
|
if [ -S "/run/user/$(id -u)/docker.sock" ]; then
|
||||||
|
echo "PACK_DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock" >> $GITEA_ENV
|
||||||
|
elif [ -S "/var/run/docker.sock" ]; then
|
||||||
|
echo "PACK_DOCKER_HOST=unix:///var/run/docker.sock" >> $GITEA_ENV
|
||||||
|
else
|
||||||
|
echo "Error: Docker socket not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
docker info
|
||||||
|
|
||||||
|
- name: Install Pack CLI
|
||||||
|
run: |
|
||||||
|
PACK_VERSION="0.32.0"
|
||||||
|
wget -q "https://github.com/buildpacks/pack/releases/download/v${PACK_VERSION}/pack-v${PACK_VERSION}-linux.tgz"
|
||||||
|
tar -xzf "pack-v${PACK_VERSION}-linux.tgz"
|
||||||
|
sudo mv pack /usr/local/bin/
|
||||||
|
pack --version
|
||||||
|
|
||||||
|
- name: Set default builder
|
||||||
|
run: |
|
||||||
|
pack config default-builder paketobuildpacks/builder-jammy-tiny:latest
|
||||||
|
|
||||||
|
- name: Prepare build environment
|
||||||
|
working-directory: backend
|
||||||
|
run: |
|
||||||
|
# Create .env.production for build (no secrets, just structure)
|
||||||
|
cat > .env.production << EOF
|
||||||
|
PORT=8080
|
||||||
|
# Database and Firebase config loaded at runtime
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Build image
|
||||||
|
id: build
|
||||||
|
env:
|
||||||
|
PACK_DOCKER_HOST: ${{ env.PACK_DOCKER_HOST }}
|
||||||
|
run: |
|
||||||
|
PACK_ARGS=(
|
||||||
|
"${IMAGE_NAME}:${IMAGE_TAG}"
|
||||||
|
--path backend
|
||||||
|
)
|
||||||
|
|
||||||
|
if [ -n "$PACK_DOCKER_HOST" ]; then
|
||||||
|
PACK_ARGS+=(--docker-host "$PACK_DOCKER_HOST")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "backend/.env.production" ]; then
|
||||||
|
PACK_ARGS+=(--env-file backend/.env.production)
|
||||||
|
fi
|
||||||
|
|
||||||
|
pack build "${PACK_ARGS[@]}"
|
||||||
|
|
||||||
|
IMAGE_DIGEST=$(docker inspect "${IMAGE_NAME}:${IMAGE_TAG}" --format='{{.Id}}')
|
||||||
|
echo "digest=${IMAGE_DIGEST}" >> $GITEA_OUTPUT
|
||||||
|
|
||||||
|
- name: Tag image
|
||||||
|
id: image
|
||||||
|
run: |
|
||||||
|
if [ -n "${{ env.REGISTRY }}" ]; then
|
||||||
|
FULL_IMAGE="${{ env.REGISTRY }}/${IMAGE_NAME}:${IMAGE_TAG}"
|
||||||
|
docker tag "${IMAGE_NAME}:${IMAGE_TAG}" "${FULL_IMAGE}"
|
||||||
|
echo "full=${FULL_IMAGE}" >> $GITEA_OUTPUT
|
||||||
|
else
|
||||||
|
echo "full=${IMAGE_NAME}:${IMAGE_TAG}" >> $GITEA_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Push to registry
|
||||||
|
if: env.REGISTRY != ''
|
||||||
|
run: |
|
||||||
|
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin
|
||||||
|
docker push "${{ steps.image.outputs.full }}"
|
||||||
|
|
||||||
|
- name: Save image artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: docker-image
|
||||||
|
path: /tmp/image.tar
|
||||||
|
retention-days: 1
|
||||||
|
if: env.REGISTRY == ''
|
||||||
|
run: |
|
||||||
|
docker save "${IMAGE_NAME}:${IMAGE_TAG}" -o /tmp/image.tar
|
||||||
|
|
||||||
188
.gitea/workflows/deploy.yml
Normal file
188
.gitea/workflows/deploy.yml
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
name: Deploy to Production
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows: ["Build Application Image"]
|
||||||
|
types:
|
||||||
|
- completed
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- production
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
image_tag:
|
||||||
|
description: 'Image tag to deploy'
|
||||||
|
required: true
|
||||||
|
default: 'latest'
|
||||||
|
|
||||||
|
env:
|
||||||
|
IMAGE_NAME: jd-book-uploader
|
||||||
|
IMAGE_TAG: ${{ inputs.image_tag || 'latest' }}
|
||||||
|
REGISTRY: ${{ secrets.REGISTRY_URL || '' }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
name: Deploy to Production
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
|
||||||
|
environment: production
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up SSH
|
||||||
|
uses: webfactory/ssh-agent@v0.9.0
|
||||||
|
with:
|
||||||
|
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||||
|
|
||||||
|
- name: Configure SSH
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
echo "${{ secrets.SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
|
||||||
|
chmod 600 ~/.ssh/known_hosts
|
||||||
|
|
||||||
|
- name: Download image artifact
|
||||||
|
if: env.REGISTRY == ''
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: docker-image
|
||||||
|
workflow: build.yml
|
||||||
|
run-id: ${{ github.event.workflow_run.id }}
|
||||||
|
|
||||||
|
- name: Prepare deployment files
|
||||||
|
run: |
|
||||||
|
mkdir -p deployment/tmp
|
||||||
|
|
||||||
|
# Create .env.production
|
||||||
|
cat > deployment/tmp/.env.production << EOF
|
||||||
|
PORT=${{ secrets.PORT || '8080' }}
|
||||||
|
FRONTEND_URL=${{ secrets.FRONTEND_URL }}
|
||||||
|
DB_HOST=${{ secrets.DB_HOST }}
|
||||||
|
DB_PORT=${{ secrets.DB_PORT }}
|
||||||
|
DB_USER=${{ secrets.DB_USER }}
|
||||||
|
DB_PASSWORD=${{ secrets.DB_PASSWORD }}
|
||||||
|
DB_NAME=${{ secrets.DB_NAME }}
|
||||||
|
FIREBASE_PROJECT_ID=${{ secrets.FIREBASE_PROJECT_ID }}
|
||||||
|
FIREBASE_STORAGE_BUCKET=${{ secrets.FIREBASE_STORAGE_BUCKET }}
|
||||||
|
FIREBASE_CREDENTIALS_FILE=${{ secrets.FIREBASE_CREDENTIALS_FILE_PATH || './firebase-credentials.json' }}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Create deployment script
|
||||||
|
cat > deployment/tmp/deploy.sh << 'DEPLOY_SCRIPT'
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
IMAGE_NAME="${{ env.IMAGE_NAME }}"
|
||||||
|
IMAGE_TAG="${{ env.IMAGE_TAG }}"
|
||||||
|
CONTAINER_NAME="jd-book-uploader"
|
||||||
|
|
||||||
|
set -a
|
||||||
|
source .env.production
|
||||||
|
set +a
|
||||||
|
|
||||||
|
# Stop existing container
|
||||||
|
if podman ps -a --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then
|
||||||
|
podman stop "${CONTAINER_NAME}" 2>/dev/null || true
|
||||||
|
podman rm "${CONTAINER_NAME}" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Load image if artifact provided
|
||||||
|
if [ -f image.tar ]; then
|
||||||
|
podman load -i image.tar
|
||||||
|
rm -f image.tar
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Pull from registry if configured
|
||||||
|
if [ -n "${{ env.REGISTRY }}" ]; then
|
||||||
|
podman pull "${{ env.REGISTRY }}/${IMAGE_NAME}:${IMAGE_TAG}"
|
||||||
|
podman tag "${{ env.REGISTRY }}/${IMAGE_NAME}:${IMAGE_TAG}" "${IMAGE_NAME}:${IMAGE_TAG}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build run command
|
||||||
|
PODMAN_CMD=(
|
||||||
|
podman run -d
|
||||||
|
--name "${CONTAINER_NAME}"
|
||||||
|
--network=host
|
||||||
|
--user root
|
||||||
|
--restart=unless-stopped
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add environment variables
|
||||||
|
while IFS='=' read -r key value; do
|
||||||
|
[[ "$key" =~ ^#.*$ ]] && continue
|
||||||
|
[[ -z "$key" ]] && continue
|
||||||
|
value=$(echo "$value" | sed -e 's/^"//' -e 's/"$//' -e "s/^'//" -e "s/'$//")
|
||||||
|
if [ "$key" != "FIREBASE_CREDENTIALS_FILE" ]; then
|
||||||
|
PODMAN_CMD+=(-e "${key}=${value}")
|
||||||
|
fi
|
||||||
|
done < .env.production
|
||||||
|
|
||||||
|
# Mount Firebase credentials
|
||||||
|
FIREBASE_CREDS="${FIREBASE_CREDENTIALS_FILE}"
|
||||||
|
if [ -f "$FIREBASE_CREDS" ]; then
|
||||||
|
PODMAN_CMD+=(-v "${FIREBASE_CREDS}:/app/firebase-credentials.json:ro,z")
|
||||||
|
PODMAN_CMD+=(-e "FIREBASE_CREDENTIALS_FILE=/app/firebase-credentials.json")
|
||||||
|
fi
|
||||||
|
|
||||||
|
PODMAN_CMD+=("${IMAGE_NAME}:${IMAGE_TAG}")
|
||||||
|
|
||||||
|
"${PODMAN_CMD[@]}"
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
if podman ps --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then
|
||||||
|
echo "✓ Container started"
|
||||||
|
podman logs "${CONTAINER_NAME}" --tail 20
|
||||||
|
else
|
||||||
|
echo "✗ Container failed"
|
||||||
|
podman logs "${CONTAINER_NAME}" --tail 50
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
DEPLOY_SCRIPT
|
||||||
|
|
||||||
|
chmod +x deployment/tmp/deploy.sh
|
||||||
|
|
||||||
|
- name: Transfer files
|
||||||
|
run: |
|
||||||
|
scp -r deployment/tmp/* ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:${{ secrets.DEPLOY_PATH }}/deployment/
|
||||||
|
if [ -f image.tar ]; then
|
||||||
|
scp image.tar ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:${{ secrets.DEPLOY_PATH }}/image.tar
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Deploy
|
||||||
|
run: |
|
||||||
|
ssh ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} << ENDSSH
|
||||||
|
set -e
|
||||||
|
cd ${{ secrets.DEPLOY_PATH }}
|
||||||
|
|
||||||
|
if [ -f image.tar ]; then
|
||||||
|
podman load -i image.tar
|
||||||
|
rm -f image.tar
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "${{ secrets.FIREBASE_CREDENTIALS_FILE_PATH || './firebase-credentials.json' }}" ]; then
|
||||||
|
echo "Error: Firebase credentials not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd deployment
|
||||||
|
./deploy.sh
|
||||||
|
ENDSSH
|
||||||
|
|
||||||
|
- name: Verify deployment
|
||||||
|
run: |
|
||||||
|
sleep 5
|
||||||
|
HEALTH_URL="http://${{ secrets.DEPLOY_HOST }}:${{ secrets.PORT || '8080' }}/api/health"
|
||||||
|
|
||||||
|
for i in {1..10}; do
|
||||||
|
if curl -f -s "$HEALTH_URL" > /dev/null; then
|
||||||
|
echo "✓ Health check passed"
|
||||||
|
curl -s "$HEALTH_URL" | jq .
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
sleep 3
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "✗ Health check failed"
|
||||||
|
exit 1
|
||||||
|
|
||||||
Reference in New Issue
Block a user