From 73f162075e4dbdf6d70b45e1d63fc497679f5ded Mon Sep 17 00:00:00 2001 From: GitHub Copilot Date: Sat, 14 Feb 2026 22:14:39 +0000 Subject: [PATCH] Embed web assets into the Go binary Serve /static from embedded assets when no static path override is configured, add static route coverage, and update Docker/docs to reflect embedded-by-default behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Dockerfile | 3 --- README.md | 2 +- docs/ARCHITECTURE.md | 1 + go/webterm/assets_embed.go | 18 ++++++++++++++++++ go/webterm/server.go | 2 ++ go/webterm/server_test.go | 9 +++++++++ 6 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 go/webterm/assets_embed.go diff --git a/Dockerfile b/Dockerfile index 72d4d65..310c715 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,9 +17,6 @@ RUN apk add --no-cache ca-certificates docker-cli WORKDIR /app COPY --from=builder /out/webterm /usr/local/bin/webterm -COPY go/webterm/static /app/static - -ENV WEBTERM_STATIC_PATH=/app/static EXPOSE 8080 diff --git a/README.md b/README.md index 7d0c6a2..a216091 100644 --- a/README.md +++ b/README.md @@ -120,5 +120,5 @@ docker build -t webterm . docker run -v /var/run/docker.sock:/var/run/docker.sock -p 8080:8080 webterm --docker-watch ``` -The image sets `WEBTERM_STATIC_PATH=/app/static` and serves assets from `go/webterm/static`. +Web assets are embedded in the Go binary by default (you can still override with `WEBTERM_STATIC_PATH`). The Dockerfile uses a minimal Alpine runtime stage and only installs `ca-certificates` plus `docker-cli`. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 5651b7c..15bb1d9 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -48,6 +48,7 @@ The server resolves static files from: 1. `WEBTERM_STATIC_PATH` (if set) 2. local repository-relative fallbacks rooted at `go/webterm/static` +3. embedded assets bundled into the Go binary ## Docker integration diff --git a/go/webterm/assets_embed.go b/go/webterm/assets_embed.go new file mode 100644 index 0000000..aa664c6 --- /dev/null +++ b/go/webterm/assets_embed.go @@ -0,0 +1,18 @@ +package webterm + +import ( + "embed" + "io/fs" + "net/http" +) + +//go:embed static static/* static/js/* static/icons/* +var embeddedStaticAssets embed.FS + +func embeddedStaticFS() (http.FileSystem, bool) { + sub, err := fs.Sub(embeddedStaticAssets, "static") + if err != nil { + return nil, false + } + return http.FS(sub), true +} diff --git a/go/webterm/server.go b/go/webterm/server.go index a5c9d5f..5a426a0 100644 --- a/go/webterm/server.go +++ b/go/webterm/server.go @@ -1466,6 +1466,8 @@ func (s *LocalServer) Handler() http.Handler { mux.HandleFunc("/", s.handleRoot) if strings.TrimSpace(s.staticPath) != "" { mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(s.staticPath)))) + } else if staticFS, ok := embeddedStaticFS(); ok { + mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(staticFS))) } return s.loggingMiddleware(s.gzipMiddleware(mux)) } diff --git a/go/webterm/server_test.go b/go/webterm/server_test.go index 99cfa1c..9d57376 100644 --- a/go/webterm/server_test.go +++ b/go/webterm/server_test.go @@ -221,6 +221,15 @@ func TestRootTerminalPageAndSparklineValidation(t *testing.T) { t.Fatalf("unexpected root page: %q", text) } + respStatic, err := http.Get(httpServer.URL + "/static/manifest.json") + if err != nil { + t.Fatalf("static request error = %v", err) + } + _ = respStatic.Body.Close() + if respStatic.StatusCode != http.StatusOK { + t.Fatalf("expected 200 for static manifest, got %d", respStatic.StatusCode) + } + resp2, err := http.Get(httpServer.URL + "/cpu-sparkline.svg") if err != nil { t.Fatalf("sparkline request error = %v", err)