diff --git a/docs-site/src/content/docs/getting-started/install.md b/docs-site/src/content/docs/getting-started/install.md index ea4610dc1..0b55534ea 100644 --- a/docs-site/src/content/docs/getting-started/install.md +++ b/docs-site/src/content/docs/getting-started/install.md @@ -64,12 +64,14 @@ Ensure your `$GOPATH/bin` (typically `~/go/bin`) is in your system `$PATH`. If you have the repository cloned, you can use the provided `Makefile`: ```bash -make build -# This creates a 'scion' binary in the build directory. -# You can move it to a directory in your PATH: -sudo mv ./build/scion /usr/local/bin/ +make all +# This builds the web frontend and installs the 'scion' binary +# to ~/.local/bin/ with the UI assets embedded. +# You can add ~/.local/bin/ to your PATH, or `mv` the binary to another directory in your PATH ``` +Alternatively, if you don't need the web UI, you can build the CLI-only version using the no_embed_web tag: go build -tags no_embed_web -o ./build/scion ./cmd/scion (the web UI will show a placeholder page). + To verify your installation, run: ```bash diff --git a/pkg/hub/web.go b/pkg/hub/web.go index 142643b3f..9a5739535 100644 --- a/pkg/hub/web.go +++ b/pkg/hub/web.go @@ -870,9 +870,20 @@ func (ws *WebServer) prefetchPageData(r *http.Request) template.JS { } // hasWebAssets reports whether the server has web assets available to serve, -// either from an embedded FS or a filesystem directory. +// either from an embedded FS or a filesystem directory. It verifies the +// presence of the core entry point (assets/main.js) to ensure the UI is +// actually built and ready to serve. func (ws *WebServer) hasWebAssets() bool { - return ws.assets != nil || ws.assetsDisk != "" + if ws.assetsDisk != "" { + p := filepath.Join(ws.assetsDisk, "assets", "main.js") + _, err := os.Stat(p) + return err == nil + } + if ws.assets != nil { + _, err := fs.Stat(ws.assets, "assets/main.js") + return err == nil + } + return false } // spaHandler returns the SPA shell HTML for any route not matched by other handlers. diff --git a/pkg/hub/web_test.go b/pkg/hub/web_test.go index 9ce099995..c66004af2 100644 --- a/pkg/hub/web_test.go +++ b/pkg/hub/web_test.go @@ -27,6 +27,7 @@ import ( "time" "github.com/GoogleCloudPlatform/scion/pkg/store" + "github.com/GoogleCloudPlatform/scion/web" "github.com/gorilla/securecookie" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -40,6 +41,15 @@ type mockWebStore struct { func newTestWebServer(t *testing.T, cfg WebServerConfig) *WebServer { t.Helper() + // Ensure hasWebAssets() returns true for tests unless they explicitly want + // to test the "no assets" case. + if cfg.AssetsDir == "" && !web.AssetsEmbedded { + tmpDir := t.TempDir() + assetsDir := filepath.Join(tmpDir, "assets") + os.MkdirAll(assetsDir, 0755) + os.WriteFile(filepath.Join(assetsDir, "main.js"), []byte("// test"), 0644) + cfg.AssetsDir = tmpDir + } return NewWebServer(cfg) } @@ -53,7 +63,7 @@ func newDevAuthWebServer(t *testing.T, overrides ...func(*WebServerConfig)) *Web for _, fn := range overrides { fn(&cfg) } - return NewWebServer(cfg) + return newTestWebServer(t, cfg) } func TestSPAShellHandler(t *testing.T) { @@ -286,8 +296,13 @@ func TestSPAHandler_NoAssets_APIStillWorks(t *testing.T) { } func TestSPAHandler_WithAssets_ServesNormalShell(t *testing.T) { + tmpDir := t.TempDir() + assetsDir := filepath.Join(tmpDir, "assets") + os.MkdirAll(assetsDir, 0755) + os.WriteFile(filepath.Join(assetsDir, "main.js"), []byte("// test"), 0644) + ws := newDevAuthWebServer(t, func(cfg *WebServerConfig) { - cfg.AssetsDir = t.TempDir() + cfg.AssetsDir = tmpDir }) req := httptest.NewRequest("GET", "/", nil) diff --git a/web/embed.go b/web/embed.go index b3ade490b..eae414cf9 100644 --- a/web/embed.go +++ b/web/embed.go @@ -25,5 +25,15 @@ import "embed" //go:embed all:dist/client var ClientAssets embed.FS +// sentinel forces a compile-time error if the web assets have not been built. +// If you see an error here, run 'make web' or build with '-tags no_embed_web'. +// +//go:embed dist/client/assets/main.js +var sentinel []byte + // AssetsEmbedded indicates whether client assets are embedded in the binary. var AssetsEmbedded = true + +func init() { + _ = sentinel // Mark as used +}