diff --git a/.github/actions/setup-mkdocs/action.yml b/.github/actions/setup-mkdocs/action.yml
new file mode 100644
index 00000000..69a5568f
--- /dev/null
+++ b/.github/actions/setup-mkdocs/action.yml
@@ -0,0 +1,20 @@
+name: 'Setup MkDocs'
+description: "Install MkDocs and the plugins required to build the Infix User's Guide (post-build.sh bundles it into WebUI images)."
+
+# Mirrors the dependency set in .github/workflows/docs.yml. Kept in-repo
+# (rather than an external action) so any checkout of a commit needing
+# docs-in-image can reproduce it without an out-of-tree dependency.
+runs:
+ using: composite
+ steps:
+ - name: Install MkDocs and plugins
+ shell: bash
+ run: |
+ command -v pipx >/dev/null 2>&1 || python3 -m pip install --user pipx
+ python3 -m pipx install --force mkdocs
+ python3 -m pipx inject mkdocs \
+ mkdocs-material pymdown-extensions mkdocs-callouts mike mkdocs-to-pdf mkdocs-glightbox
+ # Make the pipx-installed mkdocs visible to later steps (the build,
+ # where post-build.sh invokes it).
+ python3 -m pipx environment --value PIPX_BIN_DIR >> "$GITHUB_PATH" 2>/dev/null \
+ || echo "$HOME/.local/bin" >> "$GITHUB_PATH"
diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml
index 2e600039..059ef3e7 100644
--- a/.github/workflows/build-release.yml
+++ b/.github/workflows/build-release.yml
@@ -45,6 +45,9 @@ jobs:
target: ${{ matrix.target }}
enabled: ${{ inputs.use_cache }}
+ # WebUI images bundle the mkdocs User's Guide via post-build.sh.
+ - uses: ./.github/actions/setup-mkdocs
+
- name: Configure & Build
env:
INFIX_RELEASE: ${{ steps.vars.outputs.ver }}
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 47cc271d..afc9e145 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -124,6 +124,9 @@ jobs:
with:
target: ${{ env.TARGET }}
+ # WebUI images bundle the mkdocs User's Guide via post-build.sh.
+ - uses: ./.github/actions/setup-mkdocs
+
- name: Configure ${{ env.TARGET }}
run: |
make ${{ env.TARGET }}_defconfig
diff --git a/board/common/post-build.sh b/board/common/post-build.sh
index 2f9e665e..305f77ad 100755
--- a/board/common/post-build.sh
+++ b/board/common/post-build.sh
@@ -120,3 +120,29 @@ grep -qsE '^/bin/clish$$' "$TARGET_DIR/etc/shells" \
if [ "$BR2_PACKAGE_HOST_PYTHON_YANGDOC" = "y" ]; then
mkyangdoc "$BINARIES_DIR/yangdoc.html"
fi
+
+# Bundle the mkdocs User's Guide into the rootfs, served by the WebUI's
+# nginx at /guide/. Only when the WebUI is present (nothing serves it
+# otherwise, and it keeps minimal images small) and mkdocs is on the build
+# host. Best-effort: a failed build warns but does not abort the image
+# build, and the WebUI hides its User Guide entry when the docs are absent.
+mkuserguide()
+{
+ local cfg dst
+ cfg="$(readlink -f "$common/../..")/mkdocs.yml"
+ dst="$TARGET_DIR/var/www/guide"
+
+ if ! command -v mkdocs >/dev/null 2>&1; then
+ ixmsg "mkdocs not found, skipping User's Guide bundling"
+ return
+ fi
+ ixmsg "Building User's Guide into $dst"
+ if ! mkdocs build -f "$cfg" -d "$dst" --clean --quiet; then
+ ixmsg "WARNING: mkdocs build failed, shipping without on-device User's Guide"
+ rm -rf "$dst"
+ fi
+}
+
+if [ "$BR2_PACKAGE_WEBUI" = "y" ]; then
+ mkuserguide
+fi
diff --git a/doc/developers-guide.md b/doc/developers-guide.md
index 032fc69d..b1cdd2e4 100644
--- a/doc/developers-guide.md
+++ b/doc/developers-guide.md
@@ -122,13 +122,21 @@ recommend using `pipx` to install the necessary tooling:
```bash
$ sudo apt install pipx
$ pipx install mkdocs
-$ pipx inject mkdocs mkdocs-material pymdown-extensions mkdocs-callouts mike mkdocs-to-pdf
+$ pipx inject mkdocs mkdocs-material pymdown-extensions mkdocs-callouts mike mkdocs-to-pdf mkdocs-glightbox
```
-The last two packages, `mike` and `mkdocs-to-pdf`, are used for online
-versioning and PDF generation by GitHub Actions, but since they are in
-the `mkdocs.yml` file, everyone who wants to preview the documentation
-have to install all the tooling.
+The `mike` and `mkdocs-to-pdf` packages are used for online versioning
+and PDF generation by GitHub Actions, but since every plugin is listed
+in `mkdocs.yml`, anyone who wants to preview the documentation has to
+install all the tooling.
+
+> [!IMPORTANT]
+> MkDocs is also required to **build a WebUI image**. The build bundles
+> the User's Guide into the image (served on-device at `/guide/`), so
+> `make` runs `mkdocs build` from `post-build.sh` when the `webui`
+> package is selected. If MkDocs is missing the build still succeeds,
+> but the image ships without the on-device guide. Minimal images and
+> any build without the `webui` package skip this step entirely.
Preview with:
diff --git a/package/webui/webui.conf b/package/webui/webui.conf
index ac37f626..62c27838 100644
--- a/package/webui/webui.conf
+++ b/package/webui/webui.conf
@@ -55,6 +55,15 @@ location = /maintenance/support-bundle {
include /etc/nginx/webui-proxy.conf;
}
+# On-device User's Guide: the mkdocs site bundled into the rootfs at
+# /var/www/guide by post-build.sh. Served as static files (no upstream
+# call), public like the published guide. Present only when the build
+# host had mkdocs; the WebUI hides its User Guide entry when it's absent.
+location /guide/ {
+ alias /var/www/guide/;
+ index index.html;
+}
+
# Liveness probe — nginx-only, no upstream call. Used by the watchdog
# div in base.html and the reboot-overlay poller.
location = /device-status {
diff --git a/src/webui/internal/auth/login.go b/src/webui/internal/auth/login.go
index 8274814a..733445a4 100644
--- a/src/webui/internal/auth/login.go
+++ b/src/webui/internal/auth/login.go
@@ -80,6 +80,9 @@ func (h *LoginHandler) DoLogin(w http.ResponseWriter, r *http.Request) {
console, netbrowse := handlers.DetectWebShortcuts(ctx, h.RC)
caps.Features()["console"] = console
caps.Features()["netbrowse"] = netbrowse
+ // The User's Guide is bundled at build time (a filesystem check, not
+ // config); gate the Help entry on its presence.
+ caps.Features()["docs"] = handlers.DetectDocs()
// Trigger any post-login hooks (e.g. schema sync) with full credentials.
if h.OnLogin != nil {
diff --git a/src/webui/internal/handlers/capabilities.go b/src/webui/internal/handlers/capabilities.go
index 21cd497c..403df1cc 100644
--- a/src/webui/internal/handlers/capabilities.go
+++ b/src/webui/internal/handlers/capabilities.go
@@ -5,10 +5,22 @@ package handlers
import (
"context"
"log"
+ "os"
"infix/webui/internal/restconf"
)
+// docsIndexPath is the entry point of the on-device User's Guide bundled
+// by post-build.sh (mkdocs → /var/www/guide), served by nginx at /guide/.
+const docsIndexPath = "/var/www/guide/index.html"
+
+// DetectDocs reports whether the User's Guide was bundled into this image
+// (the build host had mkdocs), so the UI can show or hide the entry.
+func DetectDocs() bool {
+ _, err := os.Stat(docsIndexPath)
+ return err == nil
+}
+
type feature struct {
Name string // key used in Has() and session cookie
Module string // YANG module that carries the feature
diff --git a/src/webui/templates/layouts/base.html b/src/webui/templates/layouts/base.html
index a3bfa4f4..2f960a9c 100644
--- a/src/webui/templates/layouts/base.html
+++ b/src/webui/templates/layouts/base.html
@@ -24,6 +24,13 @@