165 lines
6.3 KiB
YAML
165 lines
6.3 KiB
YAML
name: wifi-densepose sensing-server → Docker Hub + ghcr.io
|
|
|
|
# Build + publish the `wifi-densepose` sensing-server image to both Docker Hub
|
|
# (`ruvnet/wifi-densepose`) and ghcr.io (`ghcr.io/ruvnet/wifi-densepose`) on:
|
|
# - push to main affecting the Dockerfile, the server crate, the UI assets,
|
|
# or this workflow itself,
|
|
# - tag push matching v* (release builds),
|
|
# - manual workflow_dispatch.
|
|
#
|
|
# Closes #520 and #514: the stale `:latest` is rebuilt and pushed automatically
|
|
# whenever the surface that produces it changes, and the Dockerfile fails the
|
|
# build if the observatory/pose-fusion UI assets ever go missing again.
|
|
#
|
|
# Secrets:
|
|
# DOCKERHUB_USERNAME — `ruvnet` (Docker Hub login name)
|
|
# DOCKERHUB_TOKEN — Docker Hub access token with read/write/delete scope
|
|
# (ghcr.io uses the workflow's GITHUB_TOKEN — no secret needed.)
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
paths:
|
|
- 'docker/Dockerfile.rust'
|
|
- 'docker/docker-entrypoint.sh'
|
|
- 'v2/crates/wifi-densepose-sensing-server/**'
|
|
- 'v2/crates/wifi-densepose-signal/**'
|
|
- 'v2/crates/wifi-densepose-vitals/**'
|
|
- 'v2/crates/wifi-densepose-wifiscan/**'
|
|
- 'v2/Cargo.toml'
|
|
- 'v2/Cargo.lock'
|
|
- 'ui/**'
|
|
- '.github/workflows/sensing-server-docker.yml'
|
|
tags: ['v*']
|
|
workflow_dispatch: {}
|
|
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
|
|
concurrency:
|
|
group: sensing-server-docker-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
build-and-publish:
|
|
name: build · push · smoke-test
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
submodules: recursive
|
|
|
|
- uses: docker/setup-buildx-action@v3
|
|
|
|
- name: Log in to Docker Hub
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: docker.io
|
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
|
|
- name: Log in to ghcr.io
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Compute tags
|
|
id: meta
|
|
uses: docker/metadata-action@v5
|
|
with:
|
|
images: |
|
|
docker.io/ruvnet/wifi-densepose
|
|
ghcr.io/ruvnet/wifi-densepose
|
|
tags: |
|
|
type=ref,event=branch
|
|
type=ref,event=tag
|
|
type=sha,format=short
|
|
type=raw,value=latest,enable={{is_default_branch}}
|
|
|
|
- name: Build + push
|
|
id: build
|
|
uses: docker/build-push-action@v7
|
|
with:
|
|
context: .
|
|
file: docker/Dockerfile.rust
|
|
push: true
|
|
tags: ${{ steps.meta.outputs.tags }}
|
|
labels: ${{ steps.meta.outputs.labels }}
|
|
cache-from: type=gha
|
|
cache-to: type=gha,mode=max
|
|
platforms: linux/amd64
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Smoke-test the freshly-pushed image:
|
|
# 1. UI assets that closed #520 are inside `/app/ui` (the Dockerfile's
|
|
# RUN guard catches missing ones at build time, this re-checks the
|
|
# pushed artifact post-hoc as belt-and-braces).
|
|
# 2. /health is up.
|
|
# 3. /api/v1/info returns 200 with no auth (LAN-mode default).
|
|
# 4. With RUVIEW_API_TOKEN set, /api/v1/info returns 401 without a
|
|
# Bearer header, 200 with the correct one (the #443 auth middleware).
|
|
# ---------------------------------------------------------------------
|
|
- name: Smoke-test image assets + LAN-mode HTTP
|
|
run: |
|
|
set -euo pipefail
|
|
IMAGE="ghcr.io/ruvnet/wifi-densepose:sha-${GITHUB_SHA::7}"
|
|
docker pull "$IMAGE"
|
|
docker run --rm "$IMAGE" sh -c \
|
|
'ls /app/ui/observatory.html /app/ui/pose-fusion.html /app/ui/index.html /app/ui/viz.html >/dev/null'
|
|
docker run --rm "$IMAGE" sh -c 'ls -d /app/ui/observatory /app/ui/pose-fusion >/dev/null'
|
|
|
|
docker run -d --name sm -p 3000:3000 -e CSI_SOURCE=simulated "$IMAGE"
|
|
# Wait up to 30 s for /health.
|
|
for _ in $(seq 1 30); do
|
|
if curl -fsS http://127.0.0.1:3000/health >/dev/null 2>&1; then break; fi
|
|
sleep 1
|
|
done
|
|
curl -fsS http://127.0.0.1:3000/health
|
|
curl -fsS http://127.0.0.1:3000/api/v1/info >/dev/null
|
|
curl -fsS http://127.0.0.1:3000/ui/observatory.html >/dev/null
|
|
curl -fsS http://127.0.0.1:3000/ui/pose-fusion.html >/dev/null
|
|
docker stop sm
|
|
|
|
- name: Smoke-test the bearer-token auth path
|
|
run: |
|
|
set -euo pipefail
|
|
IMAGE="ghcr.io/ruvnet/wifi-densepose:sha-${GITHUB_SHA::7}"
|
|
docker run -d --name auth \
|
|
-p 3000:3000 \
|
|
-e CSI_SOURCE=simulated \
|
|
-e RUVIEW_API_TOKEN=smoke-test-token-do-not-use \
|
|
"$IMAGE"
|
|
for _ in $(seq 1 30); do
|
|
if curl -fsS http://127.0.0.1:3000/health >/dev/null 2>&1; then break; fi
|
|
sleep 1
|
|
done
|
|
# /health stays unauthenticated.
|
|
curl -fsS http://127.0.0.1:3000/health >/dev/null
|
|
# /api/v1/info without a bearer → 401.
|
|
code=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3000/api/v1/info)
|
|
test "$code" = "401" || { echo "expected 401, got $code"; exit 1; }
|
|
# Wrong bearer → 401.
|
|
code=$(curl -s -o /dev/null -w '%{http_code}' -H 'Authorization: Bearer wrong' http://127.0.0.1:3000/api/v1/info)
|
|
test "$code" = "401" || { echo "expected 401 (wrong token), got $code"; exit 1; }
|
|
# Correct bearer → 200.
|
|
curl -fsS -H 'Authorization: Bearer smoke-test-token-do-not-use' http://127.0.0.1:3000/api/v1/info >/dev/null
|
|
docker stop auth
|
|
|
|
- name: Summary
|
|
if: always()
|
|
run: |
|
|
{
|
|
echo "## sensing-server image published"
|
|
echo
|
|
echo "Tags:"
|
|
echo '```'
|
|
echo "${{ steps.meta.outputs.tags }}"
|
|
echo '```'
|
|
echo
|
|
echo "Closes #520 (missing observatory/pose-fusion UI assets) and #514 (stale `:latest` for the v0.6+ packet format)."
|
|
echo "The Dockerfile fails the build if those UI assets ever disappear again, and this workflow rebuilds + pushes automatically on every change to the surface."
|
|
} >> "$GITHUB_STEP_SUMMARY"
|