Deploy to On-Premise Server #16
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
| name: Deploy to On-Premise Server | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| branch: | |
| description: 'Branch to deploy' | |
| required: true | |
| type: choice | |
| options: | |
| - main | |
| - dev | |
| - staging | |
| environment: | |
| description: 'Deployment environment' | |
| required: true | |
| default: 'production' | |
| type: string | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_NAME: purrowallet/purro-core | |
| jobs: | |
| build-and-push: | |
| name: Build and Push Docker Image | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ inputs.branch }} | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract metadata | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | |
| tags: | | |
| type=ref,event=branch | |
| type=sha,prefix={{branch}}- | |
| type=raw,value=${{ inputs.branch }}-latest | |
| type=raw,value=${{ inputs.branch }}-${{ github.run_number }} | |
| - name: Build and push Docker image | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: . | |
| push: true | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache | |
| cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max | |
| build-args: | | |
| BUILD_DATE=${{ github.event.head_commit.timestamp }} | |
| VCS_REF=${{ github.sha }} | |
| VERSION=${{ inputs.branch }}-${{ github.run_number }} | |
| deploy: | |
| name: Deploy to Server | |
| runs-on: ubuntu-latest | |
| needs: build-and-push | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ inputs.branch }} | |
| - name: Connect to Tailscale | |
| uses: tailscale/github-action@v2 | |
| with: | |
| oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }} | |
| oauth-secret: ${{ secrets.TS_OAUTH_SECRET }} | |
| tags: tag:ci | |
| - name: Setup SSH | |
| run: | | |
| mkdir -p ~/.ssh | |
| chmod 700 ~/.ssh | |
| echo "=== Debug: SSH Key Info ===" | |
| echo "Key length: $(echo '${{ secrets.SSH_PRIVATE_KEY }}' | wc -c) characters" | |
| echo "Key lines: $(echo '${{ secrets.SSH_PRIVATE_KEY }}' | wc -l) lines" | |
| echo "First 50 chars: $(echo '${{ secrets.SSH_PRIVATE_KEY }}' | head -c 50)" | |
| # Use printf to preserve newlines properly | |
| printf '%s\n' "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa | |
| # Verify file was written | |
| if [ ! -s ~/.ssh/id_rsa ]; then | |
| echo "ERROR: SSH key file is empty!" | |
| exit 1 | |
| fi | |
| echo "=== SSH key file created ===" | |
| echo "File size: $(wc -c < ~/.ssh/id_rsa) bytes" | |
| echo "File lines: $(wc -l < ~/.ssh/id_rsa) lines" | |
| chmod 600 ~/.ssh/id_rsa | |
| # Validate SSH key format | |
| echo "=== Validating SSH key format ===" | |
| if ssh-keygen -y -f ~/.ssh/id_rsa > /dev/null 2>&1; then | |
| echo "✅ SSH key format is VALID" | |
| else | |
| echo "❌ SSH key format is INVALID!" | |
| echo "Key content (first 2 lines):" | |
| head -2 ~/.ssh/id_rsa | |
| exit 1 | |
| fi | |
| # Add server to known_hosts | |
| echo "=== Adding server to known_hosts ===" | |
| echo "Server host: ${{ secrets.SERVER_HOST }}" | |
| if ssh-keyscan -H -T 10 ${{ secrets.SERVER_HOST }} >> ~/.ssh/known_hosts 2>&1; then | |
| echo "✅ ssh-keyscan successful" | |
| else | |
| echo "⚠️ ssh-keyscan failed, but continuing..." | |
| echo "This might be ok if server uses non-standard SSH config" | |
| fi | |
| echo "✅ SSH setup completed successfully" | |
| - name: Copy docker-compose.yml to server | |
| run: | | |
| echo "=== Copying docker-compose.yml to server ===" | |
| scp -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ | |
| docker-compose.yml ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }}:${{ secrets.RUNTIME_PATH }}/ | |
| echo "✅ File copied successfully" | |
| - name: Deploy on server | |
| run: | | |
| echo "=== Deploying to server ===" | |
| # Pass environment variables to SSH session | |
| ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ | |
| ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }} \ | |
| "GH_TOKEN='${{ secrets.GHCR_PAT }}' \ | |
| REGISTRY='${{ env.REGISTRY }}' \ | |
| IMAGE='${{ env.IMAGE_NAME }}' \ | |
| BRANCH='${{ inputs.branch }}' \ | |
| GITHUB_USER='${{ github.actor }}' \ | |
| CONFIG_PATH='${{ secrets.CONFIG_PATH }}' \ | |
| RUNTIME_PATH='${{ secrets.RUNTIME_PATH }}' \ | |
| bash -s" << 'ENDSSH' | |
| # Navigate to runtime directory | |
| cd ${RUNTIME_PATH} | |
| # Login to GitHub Container Registry with PAT | |
| echo "${GH_TOKEN}" | docker login ${REGISTRY} -u ${GITHUB_USER} --password-stdin | |
| # Set image name and env file path for docker-compose | |
| export DOCKER_IMAGE="${REGISTRY}/${IMAGE}:${BRANCH}-latest" | |
| export ENV_FILE="${CONFIG_PATH}/.env" | |
| # Pull latest image | |
| docker pull ${DOCKER_IMAGE} | |
| # Stop and remove old container | |
| docker-compose down | |
| # Start new container with pulled image and custom env file | |
| docker-compose up -d | |
| # Clean up old images | |
| docker image prune -af --filter "until=72h" | |
| ENDSSH | |
| - name: Cleanup | |
| if: always() | |
| run: | | |
| rm -f ~/.ssh/id_rsa |