Skip to content

deploy relay

deploy relay #48

Workflow file for this run

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