name: Deploy CV Site to Production on: push: branches: - main workflow_dispatch: inputs: skip_tests: description: 'Skip tests' required: false default: false type: boolean env: GO_VERSION: '1.25' APP_NAME: 'cv-server' SERVICE_NAME: 'cv' DEPLOY_PATH: '/home/txeo/Git/yo/cv' HEALTH_URL: 'https://juan.andres.morenorub.io/health' jobs: test: name: Run Tests runs-on: ubuntu-latest if: ${{ !inputs.skip_tests }} steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Go uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} cache: true - name: Download dependencies run: go mod download - name: Run tests run: | go test -v -race -coverprofile=coverage.txt -covermode=atomic ./... - name: Upload coverage uses: codecov/codecov-action@v3 with: file: ./coverage.txt fail_ci_if_error: false build: name: Build Application runs-on: ubuntu-latest needs: test if: always() && (needs.test.result == 'success' || needs.test.result == 'skipped') steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Go uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} cache: true - name: Build binary run: | mkdir -p build CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ -ldflags="-w -s -X main.version=${{ github.sha }}" \ -o build/${{ env.APP_NAME }} \ . - name: Compress binary run: | cd build tar -czf ${{ env.APP_NAME }}-${{ github.sha }}.tar.gz ${{ env.APP_NAME }} - name: Upload artifact uses: actions/upload-artifact@v4 with: name: cv-server-binary path: build/${{ env.APP_NAME }}-${{ github.sha }}.tar.gz retention-days: 30 deploy: name: Deploy to Production runs-on: ubuntu-latest needs: build environment: name: production url: ${{ env.HEALTH_URL }} steps: - name: Checkout code uses: actions/checkout@v4 - name: Download artifact uses: actions/download-artifact@v4 with: name: cv-server-binary path: ./build - name: Setup SSH run: | mkdir -p ~/.ssh echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/deploy_key chmod 600 ~/.ssh/deploy_key ssh-keyscan -p ${{ secrets.SSH_PORT || 22 }} ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts - name: Deploy to Server env: SSH_KEY: ~/.ssh/deploy_key SSH_USER: ${{ secrets.SSH_USER }} SSH_HOST: ${{ secrets.SSH_HOST }} SSH_PORT: ${{ secrets.SSH_PORT || 22 }} run: | # Extract artifact cd build tar -xzf ${{ env.APP_NAME }}-${{ github.sha }}.tar.gz # Upload binary to server scp -i $SSH_KEY -P $SSH_PORT ${{ env.APP_NAME }} \ $SSH_USER@$SSH_HOST:${{ env.DEPLOY_PATH }}/${{ env.APP_NAME }}.new # Upload deployment script scp -i $SSH_KEY -P $SSH_PORT ../scripts/deploy.sh \ $SSH_USER@$SSH_HOST:${{ env.DEPLOY_PATH }}/ # Sync static assets, templates, and data echo "📦 Syncing assets..." rsync -avz -e "ssh -i $SSH_KEY -p $SSH_PORT" \ --exclude '.git' \ --exclude 'build' \ --exclude 'node_modules' \ --exclude '.env' \ ../static/ ../templates/ ../data/ \ $SSH_USER@$SSH_HOST:${{ env.DEPLOY_PATH }}/ 2>/dev/null || echo "Note: Some directories may not exist" # Execute deployment on server ssh -i $SSH_KEY -p $SSH_PORT $SSH_USER@$SSH_HOST << 'ENDSSH' cd ${{ env.DEPLOY_PATH }} # Make scripts executable chmod +x deploy.sh 2>/dev/null || true chmod +x ${{ env.APP_NAME }}.new # Export environment for deploy script export DEPLOY_PATH="${{ env.DEPLOY_PATH }}" # Run deployment ./deploy.sh ${{ env.APP_NAME }} ${{ env.SERVICE_NAME }} ENDSSH - name: Health check run: | echo "Waiting for application to start..." sleep 10 # Check service status ssh -i ~/.ssh/deploy_key -p ${{ secrets.SSH_PORT || 22 }} \ ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} \ "systemctl is-active ${{ env.SERVICE_NAME }}" # HTTP health check RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" ${{ env.HEALTH_URL }}) if [ "$RESPONSE" = "200" ]; then echo "✅ Health check passed (HTTP $RESPONSE)" else echo "❌ Health check failed (HTTP $RESPONSE)" exit 1 fi - name: Send notification if: always() run: | if [ -z "${{ secrets.SLACK_WEBHOOK }}" ]; then echo "No Slack webhook configured, skipping notification" exit 0 fi STATUS="${{ job.status }}" COLOR="good" if [ "$STATUS" != "success" ]; then COLOR="danger" fi curl -X POST "${{ secrets.SLACK_WEBHOOK }}" \ -H "Content-Type: application/json" \ -d "{ \"attachments\": [{ \"color\": \"$COLOR\", \"title\": \"CV Site Deployment $STATUS\", \"text\": \"Deployment to production completed\", \"fields\": [ {\"title\": \"Branch\", \"value\": \"${{ github.ref_name }}\", \"short\": true}, {\"title\": \"Commit\", \"value\": \"${{ github.sha }}\", \"short\": true}, {\"title\": \"Environment\", \"value\": \"production\", \"short\": true} ] }] }" || true - name: Cleanup SSH keys if: always() run: | rm -f ~/.ssh/deploy_key