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 <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,7 @@ jobs:
|
|||||||
- name: Deploy on the server's Docker daemon (viewer published on port 8099)
|
- name: Deploy on the server's Docker daemon (viewer published on port 8099)
|
||||||
env:
|
env:
|
||||||
VIEWER_BIND: "0.0.0.0"
|
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
|
COMPOSE_PROJECT_NAME: crypto-interop-spike
|
||||||
run: |
|
run: |
|
||||||
set -eu
|
set -eu
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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.
|
- **"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.
|
- **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.
|
- 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
|
## 8. File map
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
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
|
files, builds all images, redeploys the stack on the server's Docker daemon with the web view published on
|
||||||
**port 8099** (`http://<server-ip>:8099`) and fails unless both boards pass all checks.
|
**port 8099** (`http://<server-ip>: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,
|
The public web view cannot restart anything itself (that would need the Docker socket). Its **"Run again (via Gitea)"**
|
||||||
start the workflow manually (Actions, "Run workflow"). To run the server stack with the button anyway,
|
button opens the Gitea Actions page for `.gitea/workflows/restart.yml`; run that workflow there ("Run workflow") to replay
|
||||||
add `-f docker/compose.restart.yaml` and understand it gives the viewer control of Docker on the host.
|
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
|
The compose file contains **demo secrets** (broker and service passwords, a signing seed) that protect nothing
|
||||||
real. Do not reuse them anywhere else.
|
real. Do not reuse them anywhere else.
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ services:
|
|||||||
- "${VIEWER_BIND:-127.0.0.1}:8099:8080"
|
- "${VIEWER_BIND:-127.0.0.1}:8099:8080"
|
||||||
environment:
|
environment:
|
||||||
COMPOSE_PROJECT: crypto-interop-spike
|
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:
|
volumes:
|
||||||
- logs:/logs
|
- logs:/logs
|
||||||
# The "Run again" button is NOT available here; it needs compose.restart.yaml (Docker socket).
|
# The "Run again" button is NOT available here; it needs compose.restart.yaml (Docker socket).
|
||||||
|
|||||||
@@ -239,7 +239,10 @@ for (const [id, cls] of [['onlychecks', 'only-checks'], ['hidedetail', 'hide-det
|
|||||||
// refresh shows the whole run again.
|
// refresh shows the whole run again.
|
||||||
// Run again: ask the viewer server to restart the whole demo (backend, boards and a fresh broker).
|
// 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.
|
// 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 () => {
|
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;
|
document.getElementById('restart').disabled = true;
|
||||||
try {
|
try {
|
||||||
const r = await fetch('/api/restart', { method: 'POST', headers: { 'X-Demo-Action': 'restart' } });
|
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.
|
// 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'; });
|
.catch(() => { document.getElementById('restart').style.display = 'none'; });
|
||||||
|
|
||||||
buildCards();
|
buildCards();
|
||||||
@@ -271,7 +280,7 @@ es.onmessage = e => add(JSON.parse(e.data));
|
|||||||
es.addEventListener('reset', resetView);
|
es.addEventListener('reset', resetView);
|
||||||
es.addEventListener('status', e => {
|
es.addEventListener('status', e => {
|
||||||
const s = JSON.parse(e.data);
|
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;
|
document.getElementById('status').textContent = s.busy || s.text !== 'ready' ? s.text : liveText;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ var (
|
|||||||
logDir = "/logs"
|
logDir = "/logs"
|
||||||
// The restart button needs the Docker socket; it is only enabled when the deployment says so.
|
// The restart button needs the Docker socket; it is only enabled when the deployment says so.
|
||||||
restartEnabled = os.Getenv("RESTART_ENABLED") == "1"
|
restartEnabled = os.Getenv("RESTART_ENABLED") == "1"
|
||||||
|
// 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-]+)\] (.*)$`)
|
re = regexp.MustCompile(`^(\d\d:\d\d:\d\d\.\d{3}) \[([a-z-]+)\] (.*)$`)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -294,7 +297,7 @@ func main() {
|
|||||||
json.NewEncoder(w).Encode(lines)
|
json.NewEncoder(w).Encode(lines)
|
||||||
})
|
})
|
||||||
http.HandleFunc("/api/config", func(w http.ResponseWriter, r *http.Request) {
|
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) {
|
http.HandleFunc("/api/restart", func(w http.ResponseWriter, r *http.Request) {
|
||||||
if !restartEnabled {
|
if !restartEnabled {
|
||||||
|
|||||||
Reference in New Issue
Block a user