From 37ca9d4da1dbfeb03c5c2c1f8c5b6ec02bd6c41c Mon Sep 17 00:00:00 2001 From: ryan Date: Sat, 24 Oct 2026 21:54:24 +0330 Subject: [PATCH] Add Gitea restart workflow and link-style "Run again" for the public view The server deployment cannot mount the Docker socket safely, so the web view's button now opens the Gitea page for a workflow_dispatch workflow that recreates the stack and replays the demo. Gitea handles who may run it. Co-Authored-By: Claude Sonnet 5.5 --- .gitea/workflows/deploy.yml | 1 + .gitea/workflows/restart.yml | 24 ++++++++++++++++++++++++ DEMO.md | 2 +- README.md | 8 +++++--- docker/compose.yaml | 2 ++ docker/viewer/index.html | 13 +++++++++++-- docker/viewer/main.go | 7 +++++-- 7 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 .gitea/workflows/restart.yml diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index a425092..39850c2 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -28,6 +28,7 @@ jobs: - name: Deploy on the server's Docker daemon (viewer published on port 8099) env: VIEWER_BIND: "0.0.0.0" + RESTART_URL: https://git.my-rm.com/Robot-Market/mqtt-security-test/actions?workflow=restart.yml COMPOSE_PROJECT_NAME: crypto-interop-spike run: | set -eu diff --git a/.gitea/workflows/restart.yml b/.gitea/workflows/restart.yml new file mode 100644 index 0000000..2c65a98 --- /dev/null +++ b/.gitea/workflows/restart.yml @@ -0,0 +1,24 @@ +name: Restart demo +# Replays the whole demo on the server without rebuilding anything. Run it from Gitea (Actions, then +# "Run workflow"); only users with access to the repository can do that, which is why the public web +# view has no restart button of its own. The web view's "Run again (via Gitea)" button opens this page. +on: + workflow_dispatch: +concurrency: + group: mqtt-security-test + cancel-in-progress: false +jobs: + restart: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Recreate the stack (fresh broker, new run); images are reused, built only if missing + env: + VIEWER_BIND: "0.0.0.0" + RESTART_URL: https://git.my-rm.com/Robot-Market/mqtt-security-test/actions?workflow=restart.yml + COMPOSE_PROJECT_NAME: crypto-interop-spike + run: docker compose -f docker/compose.yaml up -d --force-recreate --remove-orphans + - name: Wait for the run and require every board check to pass + env: + COMPOSE_PROJECT_NAME: crypto-interop-spike + run: sh scripts/ci-verify.sh diff --git a/DEMO.md b/DEMO.md index 9eba50a..afa6ffd 100644 --- a/DEMO.md +++ b/DEMO.md @@ -118,7 +118,7 @@ Visible on the web page in addition: the two **fingerprints** (backend and board - **"Now" box:** a plain-language explanation of the latest step. - **Options:** follow live, only checks and steps, hide countdown lines. Click a long hex value to expand it. - A refresh replays the whole run, because the server keeps every line. -- **Run again** button (shown only when the stack is started with `docker/compose.restart.yaml`, as `run-docker.sh` does; the server deployment does not use it): restarts the backend, both boards and the broker (clean Dynamic Security state), clears the logs and replays the whole run. It needs the Docker socket mounted into the `viewer` container (`/var/run/docker.sock`), which gives that container control of Docker on the host, so the page is bound to `127.0.0.1` and the restart endpoint requires a custom header that other web pages cannot send. Demo use only. +- **Run again** button (shown only when the stack is started with `docker/compose.restart.yaml`, as `run-docker.sh` does; the server deployment does not use it; there the button opens the Gitea "Restart demo" workflow page instead): restarts the backend, both boards and the broker (clean Dynamic Security state), clears the logs and replays the whole run. It needs the Docker socket mounted into the `viewer` container (`/var/run/docker.sock`), which gives that container control of Docker on the host, so the page is bound to `127.0.0.1` and the restart endpoint requires a custom header that other web pages cannot send. Demo use only. ## 8. File map diff --git a/README.md b/README.md index 673a07f..fc31c25 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,11 @@ cd docker && ./run-docker.sh # web view on http://localhost:8099, with the Every push to `main` runs `.gitea/workflows/deploy.yml` on the Gitea runner: it validates the compose files, builds all images, redeploys the stack on the server's Docker daemon with the web view published on **port 8099** (`http://:8099`) and fails unless both boards pass all checks. -The public deployment has no "Run again" button, because that needs the Docker socket; to replay the run, -start the workflow manually (Actions, "Run workflow"). To run the server stack with the button anyway, -add `-f docker/compose.restart.yaml` and understand it gives the viewer control of Docker on the host. +The public web view cannot restart anything itself (that would need the Docker socket). Its **"Run again (via Gitea)"** +button opens the Gitea Actions page for `.gitea/workflows/restart.yml`; run that workflow there ("Run workflow") to replay +the whole demo on the server without rebuilding. Only people with access to the repository can do that. To run a stack +locally with a self-contained button instead, use `docker/run-docker.sh` (it adds `docker/compose.restart.yaml`, which +mounts the Docker socket: local use only). The compose file contains **demo secrets** (broker and service passwords, a signing seed) that protect nothing real. Do not reuse them anywhere else. diff --git a/docker/compose.yaml b/docker/compose.yaml index 8dbd0df..7cc7b07 100644 --- a/docker/compose.yaml +++ b/docker/compose.yaml @@ -23,6 +23,8 @@ services: - "${VIEWER_BIND:-127.0.0.1}:8099:8080" environment: COMPOSE_PROJECT: crypto-interop-spike + # When set (server deployment), the page offers "Run again" as a link to the Gitea workflow page. + RESTART_URL: ${RESTART_URL:-} volumes: - logs:/logs # The "Run again" button is NOT available here; it needs compose.restart.yaml (Docker socket). diff --git a/docker/viewer/index.html b/docker/viewer/index.html index ab3b6a0..0ff0982 100644 --- a/docker/viewer/index.html +++ b/docker/viewer/index.html @@ -239,7 +239,10 @@ for (const [id, cls] of [['onlychecks', 'only-checks'], ['hidedetail', 'hide-det // refresh shows the whole run again. // Run again: ask the viewer server to restart the whole demo (backend, boards and a fresh broker). // The custom header is required by the server so other web pages cannot trigger it. +let cfg = { restart: false, restartUrl: '' }; document.getElementById('restart').addEventListener('click', async () => { + // Public deployment: no Docker access here, so send the user to Gitea, which requires a login. + if (!cfg.restart && cfg.restartUrl) { window.open(cfg.restartUrl, '_blank', 'noopener'); return; } document.getElementById('restart').disabled = true; try { const r = await fetch('/api/restart', { method: 'POST', headers: { 'X-Demo-Action': 'restart' } }); @@ -259,7 +262,13 @@ function resetView() { } // The restart button only exists when the server was started with restart support enabled. -fetch('/api/config').then(r => r.json()).then(c => { if (!c.restart) document.getElementById('restart').style.display = 'none'; }) +fetch('/api/config').then(r => r.json()).then(c => { + cfg = c; + const b = document.getElementById('restart'); + if (c.restart) return; + if (c.restartUrl) { b.innerHTML = '↻ Run again (via Gitea)'; b.title = 'Opens Gitea, where you run the "Restart demo" workflow (login required)'; } + else b.style.display = 'none'; + }) .catch(() => { document.getElementById('restart').style.display = 'none'; }); buildCards(); @@ -271,7 +280,7 @@ es.onmessage = e => add(JSON.parse(e.data)); es.addEventListener('reset', resetView); es.addEventListener('status', e => { const s = JSON.parse(e.data); - document.getElementById('restart').disabled = s.busy; + if (cfg.restart) document.getElementById('restart').disabled = s.busy; document.getElementById('status').textContent = s.busy || s.text !== 'ready' ? s.text : liveText; }); diff --git a/docker/viewer/main.go b/docker/viewer/main.go index 4482392..d34a4f8 100644 --- a/docker/viewer/main.go +++ b/docker/viewer/main.go @@ -45,7 +45,10 @@ var ( logDir = "/logs" // The restart button needs the Docker socket; it is only enabled when the deployment says so. restartEnabled = os.Getenv("RESTART_ENABLED") == "1" - re = regexp.MustCompile(`^(\d\d:\d\d:\d\d\.\d{3}) \[([a-z-]+)\] (.*)$`) + // Without the Docker socket the page can still offer "Run again" as a link to this URL (the + // Gitea page that runs the restart workflow); Gitea then does the authentication. + restartURL = os.Getenv("RESTART_URL") + re = regexp.MustCompile(`^(\d\d:\d\d:\d\d\.\d{3}) \[([a-z-]+)\] (.*)$`) ) // tail remembers how far a file has been read and any incomplete last line. @@ -294,7 +297,7 @@ func main() { json.NewEncoder(w).Encode(lines) }) http.HandleFunc("/api/config", func(w http.ResponseWriter, r *http.Request) { - json.NewEncoder(w).Encode(map[string]bool{"restart": restartEnabled}) + json.NewEncoder(w).Encode(map[string]any{"restart": restartEnabled, "restartUrl": restartURL}) }) http.HandleFunc("/api/restart", func(w http.ResponseWriter, r *http.Request) { if !restartEnabled {