Skip to content

Merge pull request #87 from openmoq/sync-moxygen/de598cd #36

Merge pull request #87 from openmoq/sync-moxygen/de598cd

Merge pull request #87 from openmoq/sync-moxygen/de598cd #36

Workflow file for this run

name: ci main
# Full pipeline on push to main: format, build/test, publish artifacts + Docker,
# release snapshot, notify. PRs use ci-pr.yml (format + build/test only).
#
# Job graph:
#
# check-format ──────────────────────────────────────────────────────────────────┐
# build (linux, asan debug) ── publish (docker+smoke) ── release ────────────────┼── notify
on:
push:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: write
checks: write
packages: write
jobs:
# ════════════════════════════════════════════════════════════════════════════
# Verify: format + build/test matrix
# ════════════════════════════════════════════════════════════════════════════
check-format:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install clang-format
run: pip install clang-format==19.1.7
- name: Check formatting
run: bash scripts/format.sh --check
build:
needs: [check-format]
strategy:
fail-fast: false
matrix:
include:
- name: linux
preset: default
build_dir: build
runner: ubuntu-22.04
- name: asan debug
preset: san
build_dir: build-san
runner: [self-hosted, linode]
name: ${{ matrix.name }}
runs-on: ${{ matrix.runner }}
steps:
- name: Generate app token
id: app-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.OMOQ_APP_ID }}
private-key: ${{ secrets.OMOQ_APP_PRIV_KEY }}
- uses: actions/checkout@v4
with:
submodules: true
- name: Install system dependencies
run: sudo deps/moxygen/standalone/install-system-deps.sh
- name: Setup dependencies
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: bash scripts/build.sh setup --from-release --no-fallback
- name: Build
run: bash scripts/build.sh --profile ${{ matrix.preset }} --build-dir ${{ matrix.build_dir }}
- name: Test
env:
ASAN_OPTIONS: ${{ matrix.name == 'asan debug' && 'detect_leaks=1:abort_on_error=1' || '' }}
run: bash scripts/build.sh test --build-dir ${{ matrix.build_dir }} -- --output-junit test-results.xml
- name: Publish test results
uses: dorny/test-reporter@v1.9.1
if: success() || failure()
with:
name: "test (${{ matrix.name }})"
path: ${{ matrix.build_dir }}/test-results.xml
reporter: java-junit
fail-on-empty: ${{ job.status == 'success' && 'true' || 'false' }}
# ════════════════════════════════════════════════════════════════════════════
# Publish: build moqx, Docker image + smoke test, push
# ════════════════════════════════════════════════════════════════════════════
publish:
needs: [check-format, build]
name: publish
runs-on: ubuntu-22.04
steps:
- name: Generate app token
id: app-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.OMOQ_APP_ID }}
private-key: ${{ secrets.OMOQ_APP_PRIV_KEY }}
- uses: actions/checkout@v4
with:
submodules: true
- name: Download bookworm moxygen tarball
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
MOQX_PLATFORM: bookworm-amd64
run: bash scripts/build.sh setup --from-release --no-fallback
- name: Stage tarball for Docker build
run: |
mkdir -p .docker-deps
cp -a .scratch/moxygen-install .docker-deps/moxygen
- name: Log in to GHCR
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Build Docker image
run: |
SHORT="${GITHUB_SHA:0:7}"
IMAGE="ghcr.io/${{ github.repository }}"
docker build -f docker/Dockerfile \
-t "${IMAGE}:${SHORT}" \
-t "${IMAGE}:latest" \
.
- name: Smoke test Docker image
run: |
IMAGE="ghcr.io/${{ github.repository }}:latest"
echo "==> ldd check"
docker run --rm --entrypoint ldd "${IMAGE}" /usr/local/bin/moqx
if docker run --rm --entrypoint ldd "${IMAGE}" /usr/local/bin/moqx 2>&1 | grep -q "not found"; then
echo "ERROR: missing shared libraries"; exit 1
fi
echo "==> Start container with test config"
docker run -d --name moqx-smoke --network host \
-v "$PWD/tests/test.config.yaml:/etc/moqx/config.yaml:ro" \
"${IMAGE}" --config=/etc/moqx/config.yaml
echo "==> Wait for admin /info"
for i in $(seq 1 50); do
if curl -sf http://[::1]:9669/info >/dev/null 2>&1; then break; fi
sleep 0.1
if [ "$i" -eq 50 ]; then
echo "ERROR: admin server did not start"; docker logs moqx-smoke; exit 1
fi
done
RESP=$(curl -sf http://[::1]:9669/info)
echo "Response: $RESP"
echo "$RESP" | grep -q '"service":"moqx"' || { echo "FAIL: bad /info response"; exit 1; }
docker stop moqx-smoke && docker rm moqx-smoke
echo "==> Smoke test passed"
- name: Push Docker image
run: |
SHORT="${GITHUB_SHA:0:7}"
IMAGE="ghcr.io/${{ github.repository }}"
docker push "${IMAGE}:${SHORT}"
docker push "${IMAGE}:latest"
- name: Package
id: package
run: |
ARTIFACT="moqx-bookworm-amd64.tar.gz"
docker cp "$(docker create --name extract ghcr.io/${{ github.repository }}:latest):/usr/local/bin/moqx" .
docker rm extract
mkdir -p install/bin && mv moqx install/bin/
tar czf "$ARTIFACT" -C install .
echo "artifact=$ARTIFACT" >> "$GITHUB_OUTPUT"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ steps.package.outputs.artifact }}
path: ${{ steps.package.outputs.artifact }}
retention-days: 90
# ════════════════════════════════════════════════════════════════════════════
# Release: create/update snapshot-latest pre-release (all green)
# ════════════════════════════════════════════════════════════════════════════
release:
needs: [build, publish]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
pattern: "*.tar.gz"
path: artifacts/
- name: Publish snapshot
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
SHORT="${GITHUB_SHA:0:7}"
TAG="snapshot-latest"
gh release delete "$TAG" --yes 2>/dev/null || true
git tag -d "$TAG" 2>/dev/null || true
git push origin ":refs/tags/$TAG" 2>/dev/null || true
gh release create "$TAG" artifacts/**/* \
--title "Latest build ($SHORT)" \
--prerelease \
--notes "$(cat <<EOF
Rolling snapshot of the latest build from \`main\`.
**Commit:** \`${GITHUB_SHA}\`
**Built:** $(date -u +%Y-%m-%dT%H:%M:%SZ)
**Docker:** \`ghcr.io/${{ github.repository }}:${SHORT}\`
This pre-release is automatically replaced on every push to \`main\`.
EOF
)"
# ════════════════════════════════════════════════════════════════════════════
# Deploy: auto-deploy to moqx-000 after successful release
# ════════════════════════════════════════════════════════════════════════════
deploy:
needs: [publish, release]
runs-on: [self-hosted, linode]
steps:
- uses: actions/checkout@v4
- name: Log in to GHCR
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Write .env
working-directory: docker
run: |
cat > .env <<EOF
DOMAIN=moqx-000.ci.openmoq.org
CERTBOT_EMAIL=gmarzot@openmoq.org
MOQX_PORT=4433
MOQX_ADMIN_PORT=8000
MOQX_LOG_LEVEL=0
MOQX_VERBOSE=0
EOF
sed -i 's/^[[:space:]]*//' .env
- name: Pull and deploy
working-directory: docker
run: |
docker compose pull
docker compose down --remove-orphans 2>/dev/null || true
# Remove any stale containers with matching names (e.g. from manual docker run)
docker rm -f moqx logmon 2>/dev/null || true
docker compose up -d
echo "==> Waiting for admin endpoint..."
for i in $(seq 1 30); do
if curl -sf http://127.0.0.1:8000/info >/dev/null 2>&1; then
break
fi
sleep 1
if [ "$i" -eq 30 ]; then
echo "::error::Admin endpoint did not respond within 30s"
docker compose logs moqx
exit 1
fi
done
RESP=$(curl -sf http://127.0.0.1:8000/info)
echo "==> Relay deployed: $RESP"
# ════════════════════════════════════════════════════════════════════════════
# Notify: aggregate status (runs after everything)
# ════════════════════════════════════════════════════════════════════════════
notify:
needs: [check-format, build, publish, release, deploy]
if: always()
runs-on: ubuntu-22.04
steps:
- name: Notify Slack
continue-on-error: true
env:
SLACK_WEBHOOK_URL: ${{ secrets.OMOQ_SLACK_WEBHOOK_URL }}
run: |
SHORT="${GITHUB_SHA:0:7}"
RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
# Map job results to status symbols
fmt_status() {
case "$1" in
success) echo "✓" ;;
failure) echo "✗" ;;
cancelled) echo "⊘" ;;
*) echo "—" ;;
esac
}
# verify = worst of check-format + build
if [ "${{ needs.check-format.result }}" = "failure" ] || [ "${{ needs.build.result }}" = "failure" ]; then
VER_RESULT="failure"
elif [ "${{ needs.check-format.result }}" = "cancelled" ] || [ "${{ needs.build.result }}" = "cancelled" ]; then
VER_RESULT="cancelled"
elif [ "${{ needs.check-format.result }}" = "success" ] && [ "${{ needs.build.result }}" = "success" ]; then
VER_RESULT="success"
else
VER_RESULT="skipped"
fi
VER=$(fmt_status "$VER_RESULT")
PUB=$(fmt_status "${{ needs.publish.result }}")
REL=$(fmt_status "${{ needs.release.result }}")
DEP=$(fmt_status "${{ needs.deploy.result }}")
STATUS="verify:${VER} publish:${PUB} release:${REL} deploy:${DEP}"
if [ "${{ needs.release.result }}" = "success" ]; then
REL_URL="${{ github.server_url }}/${{ github.repository }}/releases/tag/snapshot-latest"
TEXT=":white_check_mark: *${{ github.repository }}* \`${{ github.ref_name }}\` \`${SHORT}\` — ${STATUS} <${RUN_URL}|#${{ github.run_number }}> artifacts: <${REL_URL}|view>"
else
TEXT=":x: *${{ github.repository }}* \`${{ github.ref_name }}\` \`${SHORT}\` — ${STATUS} <${RUN_URL}|#${{ github.run_number }}>"
fi
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H "Content-Type: application/json" \
--data "{\"text\": \"${TEXT}\"}"
- name: Notify email
continue-on-error: true
env:
AWS_ACCESS_KEY_ID: ${{ secrets.OMOQ_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.OMOQ_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
run: |
SHORT="${GITHUB_SHA:0:7}"
RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
# verify = worst of check-format + build
CF="${{ needs.check-format.result }}"
BLD="${{ needs.build.result }}"
if [ "$CF" = "failure" ] || [ "$BLD" = "failure" ]; then
VER="failure"
elif [ "$CF" = "cancelled" ] || [ "$BLD" = "cancelled" ]; then
VER="cancelled"
elif [ "$CF" = "success" ] && [ "$BLD" = "success" ]; then
VER="success"
else
VER="skipped"
fi
PUB="${{ needs.publish.result }}"
REL="${{ needs.release.result }}"
DEP="${{ needs.deploy.result }}"
STATUS="verify:${VER} publish:${PUB} release:${REL} deploy:${DEP}"
if [ "${{ needs.release.result }}" = "success" ]; then
REL_URL="${{ github.server_url }}/${{ github.repository }}/releases/tag/snapshot-latest"
SUBJECT="[moqx] published ${{ github.ref_name }} ${SHORT}"
BODY="Repository: ${{ github.repository }}\nBranch: ${{ github.ref_name }}\nCommit: ${GITHUB_SHA}\nStatus: ${STATUS}\nDocker: ghcr.io/${{ github.repository }}:${SHORT}\nArtifacts: ${REL_URL}\nRun: ${RUN_URL}"
else
SUBJECT="[moqx] pipeline failed ${{ github.ref_name }} ${SHORT}"
BODY="Repository: ${{ github.repository }}\nBranch: ${{ github.ref_name }}\nCommit: ${GITHUB_SHA}\nStatus: ${STATUS}\nRun: ${RUN_URL}"
fi
aws ses send-email \
--from "noreply@ci.openmoq.org" \
--destination '{"ToAddresses":["github-notifications@openmoq.org"]}' \
--message "{
\"Subject\": {\"Data\": \"${SUBJECT}\"},
\"Body\": {\"Text\": {\"Data\": \"${BODY}\"}}
}"