deploy relay #48
Workflow file for this run
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 relay | |
| # Ops controls for the CI relay (moqx-main.ci.openmoq.org + moqx-000 alias). | |
| # Restart, redeploy with a specific image tag, or change the log level. | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| restart_only: | |
| description: "Restart container without redeploying" | |
| required: false | |
| type: boolean | |
| default: false | |
| image_tag: | |
| description: "Image tag (default derived from branch)" | |
| required: false | |
| default: "" | |
| type: string | |
| domain: | |
| description: "Hostname (default derived from branch)" | |
| required: false | |
| default: "" | |
| type: string | |
| logging: | |
| description: "Log level (default INFO)" | |
| required: false | |
| default: "default" | |
| type: choice | |
| options: | |
| - "default" | |
| - "WARN" | |
| - "DBG1" | |
| - "DBG2" | |
| - "DBG3" | |
| - "DBG4" | |
| enable_stats: | |
| description: "Enable stats dashboard (default enabled)" | |
| required: false | |
| type: boolean | |
| default: true | |
| permissions: | |
| contents: read | |
| packages: read | |
| env: | |
| RELAY_PORT: 4433 | |
| ADMIN_PORT: 8000 | |
| jobs: | |
| deploy: | |
| runs-on: [self-hosted, linode] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Compute deployment target | |
| id: target | |
| run: | | |
| # Branch label: main → "main"; release/<name> → "<name>" | |
| # Rolling Docker tag: <label>-latest (symmetric across branches) | |
| BRANCH="${{ github.ref_name }}" | |
| if [[ "$BRANCH" == "main" ]]; then | |
| LABEL="main" | |
| else | |
| LABEL="${BRANCH#release/}" | |
| fi | |
| DEFAULT_TAG="${LABEL}-latest" | |
| # Explicit input overrides, otherwise computed defaults | |
| IMAGE_TAG="${{ inputs.image_tag }}" | |
| IMAGE_TAG="${IMAGE_TAG:-$DEFAULT_TAG}" | |
| DOMAIN="${{ inputs.domain }}" | |
| DOMAIN="${DOMAIN:-moqx-${LABEL}.ci.openmoq.org}" | |
| # Qualify a bare label (no dot) into the CI zone, so an explicit | |
| # `domain=moqx-main` works the same as the full FQDN. | |
| if [[ "$DOMAIN" != *.* ]]; then | |
| DOMAIN="${DOMAIN}.ci.openmoq.org" | |
| fi | |
| echo "image_tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT" | |
| echo "domain=$DOMAIN" >> "$GITHUB_OUTPUT" | |
| echo "Deploying ghcr.io/${{ github.repository }}:${IMAGE_TAG} → ${DOMAIN}" | |
| - name: Ensure DNS A record | |
| if: ${{ !inputs.restart_only }} | |
| env: | |
| DOMAIN: ${{ steps.target.outputs.domain }} | |
| AWS_ACCESS_KEY_ID: ${{ secrets.OMOQ_CERTBOT_ACCESS_KEY_ID }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.OMOQ_CERTBOT_SECRET_ACCESS_KEY }} | |
| AWS_DEFAULT_REGION: us-east-1 | |
| HOSTED_ZONE_ID: Z0079758316CI99B99FLR | |
| run: | | |
| # Discover the public IP of this self-hosted runner (the deploy target) | |
| IP=$(curl -sf https://checkip.amazonaws.com | tr -d '[:space:]') | |
| if [[ -z "$IP" ]]; then | |
| echo "::error::Could not determine runner public IP" | |
| exit 1 | |
| fi | |
| echo "Runner public IP: $IP" | |
| echo "Ensuring A record: $DOMAIN → $IP" | |
| # Check current record (if any). UPSERT is idempotent — only changes | |
| # if the record is missing or pointing elsewhere. | |
| CHANGE_FILE=$(mktemp) | |
| cat > "$CHANGE_FILE" <<EOF | |
| { | |
| "Comment": "moqx deploy: $DOMAIN", | |
| "Changes": [{ | |
| "Action": "UPSERT", | |
| "ResourceRecordSet": { | |
| "Name": "$DOMAIN", | |
| "Type": "A", | |
| "TTL": 300, | |
| "ResourceRecords": [{"Value": "$IP"}] | |
| } | |
| }] | |
| } | |
| EOF | |
| CHANGE_ID=$(aws route53 change-resource-record-sets \ | |
| --hosted-zone-id "$HOSTED_ZONE_ID" \ | |
| --change-batch "file://$CHANGE_FILE" \ | |
| --query 'ChangeInfo.Id' --output text) | |
| echo "Route53 change submitted: $CHANGE_ID" | |
| # Wait for the change to propagate (usually seconds). Bounded wait | |
| # so a slow Route53 doesn't hang the deploy forever. | |
| echo "Waiting for DNS change to propagate..." | |
| aws route53 wait resource-record-sets-changed --id "$CHANGE_ID" | |
| echo "DNS change INSYNC" | |
| rm -f "$CHANGE_FILE" | |
| - name: Log in to GHCR | |
| run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin | |
| - name: Run | |
| working-directory: docker | |
| env: | |
| RESTART_ONLY: ${{ inputs.restart_only }} | |
| LOGGING: ${{ inputs.logging }} | |
| DOMAIN: ${{ steps.target.outputs.domain }} | |
| IMAGE_TAG: ${{ steps.target.outputs.image_tag }} | |
| AWS_ACCESS_KEY_ID: ${{ secrets.OMOQ_CERTBOT_ACCESS_KEY_ID }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.OMOQ_CERTBOT_SECRET_ACCESS_KEY }} | |
| ENABLE_STATS: ${{ inputs.enable_stats }} | |
| STATS_USER: ${{ secrets.STATS_USER }} | |
| STATS_PASSWORD: ${{ secrets.STATS_PASSWORD }} | |
| GRAFANA_ADMIN_PASSWORD: ${{ secrets.GRAFANA_ADMIN_PASSWORD }} | |
| run: | | |
| if [ "$RESTART_ONLY" = "true" ]; then | |
| echo "==> Restarting relay..." | |
| docker compose restart moqx | |
| for i in $(seq 1 30); do | |
| curl -sf "http://127.0.0.1:${ADMIN_PORT}/info" >/dev/null 2>&1 && break | |
| sleep 1 | |
| [ "$i" -eq 30 ] && { echo "::error::relay did not respond after restart"; docker compose logs moqx; exit 1; } | |
| done | |
| echo "==> Relay restarted: $(curl -sf http://127.0.0.1:${ADMIN_PORT}/info)" | |
| exit 0 | |
| fi | |
| # folly XLOG override ("default" from the dialog = no override). | |
| LOGGING_CFG="" | |
| [ "${LOGGING}" != "default" ] && LOGGING_CFG="${LOGGING}" | |
| # Ensure TLS cert | |
| CERT="/etc/letsencrypt/live/${DOMAIN}/fullchain.pem" | |
| if [ ! -f "$CERT" ]; then | |
| echo "::warning::No cert for ${DOMAIN} — provisioning via Route53" | |
| sudo -E certbot certonly --dns-route53 \ | |
| -d "$DOMAIN" --non-interactive --agree-tos \ | |
| --email gmarzot@openmoq.org | |
| elif ! sudo openssl x509 -in "$CERT" -noout -checkend 2592000 2>/dev/null; then | |
| echo "::warning::Cert for ${DOMAIN} expires within 30 days — renewing" | |
| sudo -E certbot renew --cert-name "$DOMAIN" | |
| else | |
| echo "Cert for ${DOMAIN} is valid" | |
| fi | |
| # Deploy via the shared core (same script ci-main uses): writes .env, | |
| # pulls IMAGE_TAG (retagged :latest for compose), brings up the relay | |
| # + stats + public dashboard when ENABLE_STATS, health check, publish. | |
| PULL_IMAGE="ghcr.io/${{ github.repository }}:${IMAGE_TAG}" \ | |
| MOQX_LOGGING="$LOGGING_CFG" \ | |
| bash relay-deploy.sh | |
| - name: Notify Slack | |
| if: always() | |
| env: | |
| SLACK_WEBHOOK_URL: ${{ secrets.OMOQ_SLACK_WEBHOOK_URL }} | |
| DOMAIN: ${{ steps.target.outputs.domain }} | |
| IMAGE_TAG: ${{ steps.target.outputs.image_tag }} | |
| run: | | |
| SHORT="${GITHUB_SHA:0:7}" | |
| ACTION=${{ inputs.restart_only && '"restarted"' || '"deployed"' }} | |
| STATUS=${{ job.status == 'success' && '":rocket:"' || '":x:"' }} | |
| TEXT="${STATUS} *${{ github.repository }}* ${ACTION} \`${IMAGE_TAG}\` on \`${DOMAIN}:${RELAY_PORT}\`" | |
| curl -sf -X POST "$SLACK_WEBHOOK_URL" \ | |
| -H "Content-Type: application/json" \ | |
| --data "{\"text\": \"${TEXT}\"}" || true |