Skip to content

Deploy Purr Balancer #45

Deploy Purr Balancer

Deploy Purr Balancer #45

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 }}