Deploy Purr Balancer #45
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 Purr Balancer | |
| on: | |
| workflow_dispatch: | |
| env: | |
| APP_NAME: purrtransport | |
| APP_DOMAIN: purrservers.com | |
| APP_SECRET: ${{ secrets.PURR_APP_SECRET }} | |
| BAL_REGION: cdg | |
| jobs: | |
| # ------------------- | |
| # Balancer | |
| # ------------------- | |
| balancer: | |
| name: Build Balancer | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v3 | |
| - uses: superfly/flyctl-actions/setup-flyctl@master | |
| - name: Ensure app exists | |
| run: | | |
| if ! flyctl apps list | grep -q "^$APP_NAME"; then | |
| echo "Creating app $APP_NAME in region $BAL_REGION" | |
| flyctl apps create $APP_NAME | |
| else | |
| echo "App $APP_NAME already exists" | |
| fi | |
| env: | |
| FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} | |
| - name: Deploy Balancer | |
| working-directory: PurrBalancer | |
| run: | | |
| flyctl deploy --remote-only --strategy immediate \ | |
| --app $APP_NAME \ | |
| --primary-region $BAL_REGION \ | |
| --env SECRET="${APP_SECRET}" | |
| env: | |
| FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} | |
| - name: Ensure cert exists | |
| run: flyctl certs create "${APP_NAME}.${APP_DOMAIN}" --app $APP_NAME || true | |
| env: | |
| FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} | |
| - name: Force scale to 1 | |
| run: flyctl scale count 1 --app $APP_NAME --yes | |
| env: | |
| FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} | |
| - name: Update Cloudflare DNS (A + AAAA) | |
| run: | | |
| # IPv4 | |
| IP=$(flyctl ips list --app $APP_NAME --json | jq -r '.[] | select(.Type=="v4") | .Address') | |
| echo "Updating A record for ${APP_NAME}.${APP_DOMAIN} -> $IP" | |
| if [ -n "$IP" ]; then | |
| RECORD_ID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE_ID }}/dns_records?type=A&name=${APP_NAME}.${APP_DOMAIN}" \ | |
| -H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN }}" \ | |
| -H "Content-Type: application/json" | jq -r '.result[0].id') | |
| if [ "$RECORD_ID" = "null" ] || [ -z "$RECORD_ID" ]; then | |
| echo "Creating new A record" | |
| curl -s -X POST "https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE_ID }}/dns_records" \ | |
| -H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN }}" \ | |
| -H "Content-Type: application/json" \ | |
| --data "{\"type\":\"A\",\"name\":\"${APP_NAME}.${APP_DOMAIN}\",\"content\":\"$IP\",\"ttl\":120,\"proxied\":false}" | jq . | |
| else | |
| echo "Updating existing A record $RECORD_ID" | |
| curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE_ID }}/dns_records/$RECORD_ID" \ | |
| -H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN }}" \ | |
| -H "Content-Type: application/json" \ | |
| --data "{\"type\":\"A\",\"name\":\"${APP_NAME}.${APP_DOMAIN}\",\"content\":\"$IP\",\"ttl\":120,\"proxied\":false}" | jq . | |
| fi | |
| fi | |
| # IPv6 | |
| IPV6=$(flyctl ips list --app $APP_NAME --json | jq -r '.[] | select(.Type=="v6") | .Address') | |
| echo "Updating AAAA record for ${APP_NAME}.${APP_DOMAIN} -> $IPV6" | |
| if [ -n "$IPV6" ]; then | |
| RECORD_ID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE_ID }}/dns_records?type=AAAA&name=${APP_NAME}.${APP_DOMAIN}" \ | |
| -H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN }}" \ | |
| -H "Content-Type: application/json" | jq -r '.result[0].id') | |
| if [ "$RECORD_ID" = "null" ] || [ -z "$RECORD_ID" ]; then | |
| echo "Creating new AAAA record" | |
| curl -s -X POST "https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE_ID }}/dns_records" \ | |
| -H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN }}" \ | |
| -H "Content-Type: application/json" \ | |
| --data "{\"type\":\"AAAA\",\"name\":\"${APP_NAME}.${APP_DOMAIN}\",\"content\":\"$IPV6\",\"ttl\":120,\"proxied\":false}" | jq . | |
| else | |
| echo "Updating existing AAAA record $RECORD_ID" | |
| curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE_ID }}/dns_records/$RECORD_ID" \ | |
| -H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN }}" \ | |
| -H "Content-Type: application/json" \ | |
| --data "{\"type\":\"AAAA\",\"name\":\"${APP_NAME}.${APP_DOMAIN}\",\"content\":\"$IPV6\",\"ttl\":120,\"proxied\":false}" | jq . | |
| fi | |
| fi | |
| env: | |
| FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} | |
| # ------------------- | |
| # Relays | |
| # ------------------- | |
| deploy: | |
| name: Build Relay | |
| runs-on: ubuntu-latest | |
| needs: balancer | |
| strategy: | |
| matrix: | |
| include: | |
| - region: cdg | |
| prefix: france | |
| - region: gru | |
| prefix: brazil | |
| - region: jnb | |
| prefix: south-africa | |
| - region: ewr | |
| prefix: nj-us | |
| - region: nrt | |
| prefix: japan | |
| - region: syd | |
| prefix: australia | |
| - region: hkg | |
| prefix: china | |
| steps: | |
| - uses: actions/checkout@v3 | |
| - uses: superfly/flyctl-actions/setup-flyctl@master | |
| - name: Ensure app exists | |
| run: | | |
| RELAY_APP="${APP_NAME}-${{ matrix.prefix }}" | |
| if ! flyctl apps list | grep -q "^$RELAY_APP"; then | |
| echo "Creating relay app $RELAY_APP in region ${{ matrix.region }}" | |
| flyctl apps create $RELAY_APP | |
| else | |
| echo "Relay app $RELAY_APP already exists" | |
| fi | |
| env: | |
| FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} | |
| - name: Deploy Relay | |
| working-directory: PurrLay | |
| run: | | |
| flyctl deploy --remote-only --strategy immediate \ | |
| --app "${APP_NAME}-${{ matrix.prefix }}" \ | |
| --primary-region ${{ matrix.region }} \ | |
| --env BALANCER_URL="https://${APP_NAME}.${APP_DOMAIN}" \ | |
| --env HOST_DOMAIN="${APP_NAME}-${{ matrix.prefix }}.${APP_DOMAIN}" \ | |
| --env HOST_REGION=${{ matrix.prefix }} \ | |
| --env HOST_SSL="true" \ | |
| --env SECRET="${APP_SECRET}" | |
| env: | |
| FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} | |
| - name: Ensure cert exists | |
| run: flyctl certs create "${APP_NAME}-${{ matrix.prefix }}.${APP_DOMAIN}" --app "${APP_NAME}-${{ matrix.prefix }}" || true | |
| env: | |
| FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} | |
| - name: Ensure IPs allocated | |
| run: | | |
| RELAY_APP="${APP_NAME}-${{ matrix.prefix }}" | |
| # Ensure IPv4 | |
| if ! flyctl ips list --app $RELAY_APP --json | jq -e '.[] | select(.Type=="v4")' > /dev/null; then | |
| echo "No IPv4 found, allocating one..." | |
| flyctl ips allocate-v4 --app $RELAY_APP --yes | |
| else | |
| echo "IPv4 already exists for $RELAY_APP" | |
| fi | |
| # Ensure IPv6 | |
| if ! flyctl ips list --app $RELAY_APP --json | jq -e '.[] | select(.Type=="v6")' > /dev/null; then | |
| echo "No IPv6 found, allocating one..." | |
| flyctl ips allocate-v6 --app $RELAY_APP | |
| else | |
| echo "IPv6 already exists for $RELAY_APP" | |
| fi | |
| env: | |
| FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} | |
| - name: Force scale to 1 | |
| run: flyctl scale count 1 --app "${APP_NAME}-${{ matrix.prefix }}" --yes | |
| env: | |
| FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} | |
| - name: Update Cloudflare DNS (A + AAAA) | |
| run: | | |
| RELAY_APP="${APP_NAME}-${{ matrix.prefix }}" | |
| # IPv4 | |
| IP=$(flyctl ips list --app $RELAY_APP --json | jq -r '.[] | select(.Type=="v4") | .Address') | |
| echo "Updating A record for ${RELAY_APP}.${APP_DOMAIN} -> $IP" | |
| if [ -n "$IP" ]; then | |
| RECORD_ID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE_ID }}/dns_records?type=A&name=${RELAY_APP}.${APP_DOMAIN}" \ | |
| -H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN }}" \ | |
| -H "Content-Type: application/json" | jq -r '.result[0].id') | |
| if [ "$RECORD_ID" = "null" ] || [ -z "$RECORD_ID" ]; then | |
| echo "Creating new A record" | |
| curl -s -X POST "https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE_ID }}/dns_records" \ | |
| -H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN }}" \ | |
| -H "Content-Type: application/json" \ | |
| --data "{\"type\":\"A\",\"name\":\"${RELAY_APP}.${APP_DOMAIN}\",\"content\":\"$IP\",\"ttl\":120,\"proxied\":false}" | jq . | |
| else | |
| echo "Updating existing A record $RECORD_ID" | |
| curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE_ID }}/dns_records/$RECORD_ID" \ | |
| -H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN }}" \ | |
| -H "Content-Type: application/json" \ | |
| --data "{\"type\":\"A\",\"name\":\"${RELAY_APP}.${APP_DOMAIN}\",\"content\":\"$IP\",\"ttl\":120,\"proxied\":false}" | jq . | |
| fi | |
| fi | |
| # IPv6 | |
| IPV6=$(flyctl ips list --app $RELAY_APP --json | jq -r '.[] | select(.Type=="v6") | .Address') | |
| echo "Updating AAAA record for ${RELAY_APP}.${APP_DOMAIN} -> $IPV6" | |
| if [ -n "$IPV6" ]; then | |
| RECORD_ID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE_ID }}/dns_records?type=AAAA&name=${RELAY_APP}.${APP_DOMAIN}" \ | |
| -H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN }}" \ | |
| -H "Content-Type: application/json" | jq -r '.result[0].id') | |
| if [ "$RECORD_ID" = "null" ] || [ -z "$RECORD_ID" ]; then | |
| echo "Creating new AAAA record" | |
| curl -s -X POST "https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE_ID }}/dns_records" \ | |
| -H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN }}" \ | |
| -H "Content-Type: application/json" \ | |
| --data "{\"type\":\"AAAA\",\"name\":\"${RELAY_APP}.${APP_DOMAIN}\",\"content\":\"$IPV6\",\"ttl\":120,\"proxied\":false}" | jq . | |
| else | |
| echo "Updating existing AAAA record $RECORD_ID" | |
| curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE_ID }}/dns_records/$RECORD_ID" \ | |
| -H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN }}" \ | |
| -H "Content-Type: application/json" \ | |
| --data "{\"type\":\"AAAA\",\"name\":\"${RELAY_APP}.${APP_DOMAIN}\",\"content\":\"$IPV6\",\"ttl\":120,\"proxied\":false}" | jq . | |
| fi | |
| fi | |
| env: | |
| FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} |