| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394 |
- name: "Deploy: app.torqvoice.com"
- on:
- workflow_dispatch:
- inputs:
- tag:
- description: "Image tag to deploy (e.g. latest, v1.3.0, dev-abc1234)"
- required: false
- default: "latest"
- jobs:
- deploy:
- runs-on: [self-hosted, Linux, X64, hetzner]
- steps:
- - name: Log in to GitHub Container Registry
- run: echo "${{ secrets.GHCR_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- - name: Create deployment files
- env:
- VIRTUAL_HOST: ${{ secrets.CLOUD_VIRTUAL_HOST }}
- DATABASE_URL: ${{ secrets.CLOUD_DATABASE_URL }}
- BETTER_AUTH_SECRET: ${{ secrets.CLOUD_BETTER_AUTH_SECRET }}
- APP_URL: ${{ secrets.CLOUD_APP_URL }}
- STRIPE_SECRET_KEY: ${{ secrets.CLOUD_STRIPE_SECRET_KEY }}
- STRIPE_WEBHOOK_SECRET: ${{ secrets.CLOUD_STRIPE_WEBHOOK_SECRET }}
- STRIPE_PRO_PRICE_ID: ${{ secrets.CLOUD_STRIPE_PRO_PRICE_ID }}
- STRIPE_ENTERPRISE_PRICE_ID: ${{ secrets.CLOUD_STRIPE_ENTERPRISE_PRICE_ID }}
- DATA_PATH: ${{ secrets.DATA_PATH }}
- POSTHOG_HOST: ${{ secrets.NEXT_PUBLIC_POSTHOG_HOST }}
- POSTHOG_KEY: ${{ secrets.NEXT_PUBLIC_POSTHOG_KEY }}
- run: |
- mkdir -p $HOME/torqvoice-deploy/prod
- cd $HOME/torqvoice-deploy/prod
- env | grep -E '^(VIRTUAL_HOST|DATA_PATH|DATABASE_URL|BETTER_AUTH_SECRET|APP_URL|STRIPE_|POSTHOG_)' > .env
- cat > docker-compose.yml << 'COMPOSE'
- networks:
- proxy:
- external: true
- name: proxy
- services:
- torqvoice-app:
- container_name: torqvoice-app
- image: ghcr.io/torqvoice/torqvoice:${APP_TAG:-latest}
- restart: unless-stopped
- volumes:
- - ${DATA_PATH}/torqvoice_data:/app/data
- expose:
- - "3000"
- environment:
- VIRTUAL_HOST: ${VIRTUAL_HOST}
- VIRTUAL_PORT: "3000"
- LETSENCRYPT_HOST: ${VIRTUAL_HOST}
- DATABASE_URL: ${DATABASE_URL}
- BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
- NEXT_PUBLIC_APP_URL: ${APP_URL}
- TORQVOICE_MODE: cloud
- STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY}
- STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET}
- STRIPE_PRO_PRICE_ID: ${STRIPE_PRO_PRICE_ID}
- STRIPE_ENTERPRISE_PRICE_ID: ${STRIPE_ENTERPRISE_PRICE_ID}
- POSTHOG_HOST: ${POSTHOG_HOST}
- POSTHOG_KEY: ${POSTHOG_KEY}
- networks:
- - proxy
- COMPOSE
- - name: Ensure data directory permissions
- run: |
- DATA_DIR="${{ secrets.DATA_PATH }}/torqvoice_data"
- mkdir -p "$DATA_DIR"
- docker run --rm -v "$DATA_DIR":/data alpine chown -R 1001:1001 /data
- - name: Check free disk space
- run: |
- MIN_GB=5
- DOCKER_ROOT=$(docker info --format '{{.DockerRootDir}}' 2>/dev/null || echo /var/lib/docker)
- AVAIL_KB=$(df -Pk "$DOCKER_ROOT" | awk 'NR==2 {print $4}')
- AVAIL_GB=$(awk "BEGIN {printf \"%.1f\", $AVAIL_KB/1024/1024}")
- echo "Free space on $DOCKER_ROOT: ${AVAIL_GB} GB (minimum ${MIN_GB} GB)"
- if [ "$AVAIL_KB" -lt $((MIN_GB * 1024 * 1024)) ]; then
- echo "::error::Not enough disk space to pull image: ${AVAIL_GB} GB free, need ${MIN_GB} GB. Aborting deploy."
- exit 1
- fi
- - name: Deploy
- run: |
- cd $HOME/torqvoice-deploy/prod
- APP_TAG=${{ inputs.tag }} docker compose up -d --pull always
- - name: Prune old images and build cache
- run: docker image prune -f && docker builder prune -f
|