From 65ec62a738781fe17ba7141d754a587be976c8d5 Mon Sep 17 00:00:00 2001 From: Reese Norris Date: Sun, 12 Jul 2026 20:54:54 -0400 Subject: [PATCH] refactor: internal/web importable; cmd/openfsd-web; Docker embed-safe Move web package; no init panic; Dockerfile_web builds cmd/openfsd-web as fsdweb. --- .gitignore | 7 ++-- Dockerfile_web | 3 +- README.md | 2 +- {web => cmd/openfsd-web}/main.go | 12 +++---- internal/postoffice/import_test.go | 2 ++ {web => internal/web}/README.md | 0 {web => internal/web}/api_tokens.go | 2 +- {web => internal/web}/api_v1_response.go | 2 +- {web => internal/web}/auth.go | 2 +- {web => internal/web}/config.go | 2 +- {web => internal/web}/data.go | 31 +++++++++--------- .../web}/data_templates/servers.txt | 0 .../web}/data_templates/status.txt | 0 {web => internal/web}/env.go | 2 +- {web => internal/web}/frontend.go | 2 +- {web => internal/web}/fsdconn.go | 2 +- internal/web/main.go | 15 +++++++++ {web => internal/web}/routes.go | 2 +- {web => internal/web}/server.go | 23 +++++++++---- .../web}/static/css/bootstrap.min.css | 0 {web => internal/web}/static/css/leaflet.css | 0 {web => internal/web}/static/images/plane.png | Bin .../web}/static/js/bootstrap.bundle.min.js | 0 .../web}/static/js/jquery-3.7.1.min.js | 0 {web => internal/web}/static/js/leaflet.js | 0 .../web}/static/js/openfsd/api.js | 0 .../web}/static/js/openfsd/configeditor.js | 0 .../web}/static/js/openfsd/dashboard.js | 0 .../js/openfsd/leaflet.rotatedmarker.js | 0 .../web}/static/js/openfsd/login.js | 0 .../web}/static/js/openfsd/usereditor.js | 0 {web => internal/web}/templates.go | 2 +- .../web}/templates/configeditor.html | 0 .../web}/templates/dashboard.html | 0 {web => internal/web}/templates/landing.html | 0 {web => internal/web}/templates/layout.html | 0 {web => internal/web}/templates/login.html | 0 .../web}/templates/usereditor.html | 0 {web => internal/web}/user.go | 2 +- {web => internal/web}/util.go | 2 +- run-windows.bat | 2 +- 41 files changed, 74 insertions(+), 45 deletions(-) rename {web => cmd/openfsd-web}/main.go (51%) rename {web => internal/web}/README.md (100%) rename {web => internal/web}/api_tokens.go (99%) rename {web => internal/web}/api_v1_response.go (99%) rename {web => internal/web}/auth.go (99%) rename {web => internal/web}/config.go (99%) rename {web => internal/web}/data.go (93%) rename {web => internal/web}/data_templates/servers.txt (100%) rename {web => internal/web}/data_templates/status.txt (100%) rename {web => internal/web}/env.go (98%) rename {web => internal/web}/frontend.go (97%) rename {web => internal/web}/fsdconn.go (99%) create mode 100644 internal/web/main.go rename {web => internal/web}/routes.go (99%) rename {web => internal/web}/server.go (72%) rename {web => internal/web}/static/css/bootstrap.min.css (100%) rename {web => internal/web}/static/css/leaflet.css (100%) rename {web => internal/web}/static/images/plane.png (100%) rename {web => internal/web}/static/js/bootstrap.bundle.min.js (100%) rename {web => internal/web}/static/js/jquery-3.7.1.min.js (100%) rename {web => internal/web}/static/js/leaflet.js (100%) rename {web => internal/web}/static/js/openfsd/api.js (100%) rename {web => internal/web}/static/js/openfsd/configeditor.js (100%) rename {web => internal/web}/static/js/openfsd/dashboard.js (100%) rename {web => internal/web}/static/js/openfsd/leaflet.rotatedmarker.js (100%) rename {web => internal/web}/static/js/openfsd/login.js (100%) rename {web => internal/web}/static/js/openfsd/usereditor.js (100%) rename {web => internal/web}/templates.go (98%) rename {web => internal/web}/templates/configeditor.html (100%) rename {web => internal/web}/templates/dashboard.html (100%) rename {web => internal/web}/templates/landing.html (100%) rename {web => internal/web}/templates/layout.html (100%) rename {web => internal/web}/templates/login.html (100%) rename {web => internal/web}/templates/usereditor.html (100%) rename {web => internal/web}/user.go (99%) rename {web => internal/web}/util.go (93%) diff --git a/.gitignore b/.gitignore index ac89f39..18bd5c0 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,8 @@ *.db **tmp** build-and-push.sh -cover.out -coverage.out + +# Local binaries +/openfsd-web +/fsdweb +/fsd diff --git a/Dockerfile_web b/Dockerfile_web index 8d8c2a3..23f4581 100644 --- a/Dockerfile_web +++ b/Dockerfile_web @@ -13,8 +13,7 @@ COPY . . # Cache builds ENV GOCACHE=/root/.cache/go-build RUN --mount=type=cache,target="/root/.cache/go-build" \ - cd web && \ - CGO_ENABLED=0 go build -o /go/bin/fsdweb + CGO_ENABLED=0 go build -o /go/bin/fsdweb ./cmd/openfsd-web FROM alpine:latest diff --git a/README.md b/README.md index 81d4cc8..5e80820 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ The preferred way to run openfsd is using **Docker** and **Docker Compose**. See ## API -The web server exposes APIs under `/api/v1` for authentication, user management, and configuration. Although a basic web interface is provided, users are encouraged to call this API from their own external applications. See the [API](https://github.com/renorris/openfsd/tree/main/web) documentation. +The web server exposes APIs under `/api/v1` for authentication, user management, and configuration. Although a basic web interface is provided, users are encouraged to call this API from their own external applications. See the [API](https://github.com/renorris/openfsd/tree/main/internal/web) documentation. ## Docs diff --git a/web/main.go b/cmd/openfsd-web/main.go similarity index 51% rename from web/main.go rename to cmd/openfsd-web/main.go index b20690f..7d46867 100644 --- a/web/main.go +++ b/cmd/openfsd-web/main.go @@ -2,17 +2,17 @@ package main import ( "context" + "log/slog" "os" "os/signal" + + "github.com/renorris/openfsd/internal/web" ) func main() { ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt) - - server, err := NewDefaultServer(ctx) - if err != nil { - panic(err) + if err := web.Main(ctx); err != nil { + slog.Error(err.Error()) + os.Exit(1) } - - server.Run(ctx) } diff --git a/internal/postoffice/import_test.go b/internal/postoffice/import_test.go index 1adb70e..c8783de 100644 --- a/internal/postoffice/import_test.go +++ b/internal/postoffice/import_test.go @@ -30,6 +30,7 @@ func TestImportGraph(t *testing.T) { postofficePkg, "github.com/renorris/openfsd/fsd", "github.com/renorris/openfsd/web", + "github.com/renorris/openfsd/internal/web", "github.com/renorris/openfsd/internal/server", }) @@ -37,6 +38,7 @@ func TestImportGraph(t *testing.T) { assertNoDeps(t, postofficePkg, []string{ "github.com/renorris/openfsd/fsd", "github.com/renorris/openfsd/web", + "github.com/renorris/openfsd/internal/web", "github.com/renorris/openfsd/internal/server", }) } diff --git a/web/README.md b/internal/web/README.md similarity index 100% rename from web/README.md rename to internal/web/README.md diff --git a/web/api_tokens.go b/internal/web/api_tokens.go similarity index 99% rename from web/api_tokens.go rename to internal/web/api_tokens.go index 03cb7f0..398134b 100644 --- a/web/api_tokens.go +++ b/internal/web/api_tokens.go @@ -1,4 +1,4 @@ -package main +package web import ( "net/http" diff --git a/web/api_v1_response.go b/internal/web/api_v1_response.go similarity index 99% rename from web/api_v1_response.go rename to internal/web/api_v1_response.go index fb65abb..d04d108 100644 --- a/web/api_v1_response.go +++ b/internal/web/api_v1_response.go @@ -1,4 +1,4 @@ -package main +package web import ( "encoding/json" diff --git a/web/auth.go b/internal/web/auth.go similarity index 99% rename from web/auth.go rename to internal/web/auth.go index ce3ab63..a326642 100644 --- a/web/auth.go +++ b/internal/web/auth.go @@ -1,4 +1,4 @@ -package main +package web import ( "database/sql" diff --git a/web/config.go b/internal/web/config.go similarity index 99% rename from web/config.go rename to internal/web/config.go index 5a46a5f..6682a17 100644 --- a/web/config.go +++ b/internal/web/config.go @@ -1,4 +1,4 @@ -package main +package web import ( "errors" diff --git a/web/data.go b/internal/web/data.go similarity index 93% rename from web/data.go rename to internal/web/data.go index c1f2c3a..a593473 100644 --- a/web/data.go +++ b/internal/web/data.go @@ -1,4 +1,4 @@ -package main +package web import ( "bytes" @@ -25,23 +25,22 @@ import ( //go:embed data_templates/status.txt var statusTxtRawTemplate string -var statusTxtTemplate *template.Template //go:embed data_templates/servers.txt var serversTxtRawTemplate string -var serversTxtTemplate *template.Template -func init() { - var err error - statusTxtTemplate = template.New("statustxt") - if statusTxtTemplate, err = statusTxtTemplate.Parse(statusTxtRawTemplate); err != nil { - panic("Unable to parse status.txt template: " + err.Error()) +// parseDataTemplates parses the embedded status/servers text templates. +// Called from NewServer so construction fails with an error instead of init panic. +func parseDataTemplates() (statusTxt, serversTxt *template.Template, err error) { + statusTxt, err = template.New("statustxt").Parse(statusTxtRawTemplate) + if err != nil { + return nil, nil, err } - - serversTxtTemplate = template.New("serverstxt") - if serversTxtTemplate, err = serversTxtTemplate.Parse(serversTxtRawTemplate); err != nil { - panic("Unable to parse servers.txt template: " + err.Error()) + serversTxt, err = template.New("serverstxt").Parse(serversTxtRawTemplate) + if err != nil { + return nil, nil, err } + return statusTxt, serversTxt, nil } func (s *Server) handleGetStatusTxt(c *gin.Context) { @@ -51,7 +50,7 @@ func (s *Server) handleGetStatusTxt(c *gin.Context) { } // Generate a new status.txt - statusTxt, err := generateStatusTxt(baseURL) + statusTxt, err := s.generateStatusTxt(baseURL) if err != nil { c.Writer.WriteHeader(http.StatusInternalServerError) c.Writer.WriteString("Error generating status.txt") @@ -63,7 +62,7 @@ func (s *Server) handleGetStatusTxt(c *gin.Context) { c.Writer.WriteString(statusTxt) } -func generateStatusTxt(baseURL string) (txt string, err error) { +func (s *Server) generateStatusTxt(baseURL string) (txt string, err error) { type TemplateData struct { ApiServerBaseURL string } @@ -72,7 +71,7 @@ func generateStatusTxt(baseURL string) (txt string, err error) { buf := bytes.Buffer{} buf.Grow(1024) - if err = statusTxtTemplate.Execute(&buf, &tmplData); err != nil { + if err = s.statusTxtTemplate.Execute(&buf, &tmplData); err != nil { return } @@ -217,7 +216,7 @@ func (s *Server) generateServersTxt() (txt string, err error) { buf := bytes.Buffer{} buf.Grow(1024) - if err = serversTxtTemplate.Execute(&buf, &tmplData); err != nil { + if err = s.serversTxtTemplate.Execute(&buf, &tmplData); err != nil { return } diff --git a/web/data_templates/servers.txt b/internal/web/data_templates/servers.txt similarity index 100% rename from web/data_templates/servers.txt rename to internal/web/data_templates/servers.txt diff --git a/web/data_templates/status.txt b/internal/web/data_templates/status.txt similarity index 100% rename from web/data_templates/status.txt rename to internal/web/data_templates/status.txt diff --git a/web/env.go b/internal/web/env.go similarity index 98% rename from web/env.go rename to internal/web/env.go index 54545c0..d72ad8f 100644 --- a/web/env.go +++ b/internal/web/env.go @@ -1,4 +1,4 @@ -package main +package web import ( "context" diff --git a/web/frontend.go b/internal/web/frontend.go similarity index 97% rename from web/frontend.go rename to internal/web/frontend.go index cdf95c9..20cc5d0 100644 --- a/web/frontend.go +++ b/internal/web/frontend.go @@ -1,4 +1,4 @@ -package main +package web import ( "github.com/gin-gonic/gin" diff --git a/web/fsdconn.go b/internal/web/fsdconn.go similarity index 99% rename from web/fsdconn.go rename to internal/web/fsdconn.go index 8dd2bca..02a61bc 100644 --- a/web/fsdconn.go +++ b/internal/web/fsdconn.go @@ -1,4 +1,4 @@ -package main +package web import ( "bytes" diff --git a/internal/web/main.go b/internal/web/main.go new file mode 100644 index 0000000..57b3ec8 --- /dev/null +++ b/internal/web/main.go @@ -0,0 +1,15 @@ +package web + +import ( + "context" +) + +// Main constructs the default web server and runs it until ctx is cancelled. +// It is the library entrypoint used by cmd/openfsd-web. +func Main(ctx context.Context) error { + server, err := NewDefaultServer(ctx) + if err != nil { + return err + } + return server.Run(ctx) +} diff --git a/web/routes.go b/internal/web/routes.go similarity index 99% rename from web/routes.go rename to internal/web/routes.go index 9aac656..6e2dec0 100644 --- a/web/routes.go +++ b/internal/web/routes.go @@ -1,4 +1,4 @@ -package main +package web import ( "embed" diff --git a/web/server.go b/internal/web/server.go similarity index 72% rename from web/server.go rename to internal/web/server.go index c77e040..edb51ce 100644 --- a/web/server.go +++ b/internal/web/server.go @@ -1,17 +1,21 @@ -package main +package web import ( "context" "database/sql" "fmt" - "github.com/renorris/openfsd/internal/db" "log/slog" "net" + "text/template" + + "github.com/renorris/openfsd/internal/db" ) type Server struct { - cfg *ServerConfig - dbRepo *db.Repositories + cfg *ServerConfig + dbRepo *db.Repositories + statusTxtTemplate *template.Template + serversTxtTemplate *template.Template } func NewDefaultServer(ctx context.Context) (server *Server, err error) { @@ -44,9 +48,16 @@ func NewDefaultServer(ctx context.Context) (server *Server, err error) { } func NewServer(cfg *ServerConfig, dbRepo *db.Repositories) (server *Server, err error) { + statusTxt, serversTxt, err := parseDataTemplates() + if err != nil { + return nil, fmt.Errorf("parse data templates: %w", err) + } + server = &Server{ - cfg: cfg, - dbRepo: dbRepo, + cfg: cfg, + dbRepo: dbRepo, + statusTxtTemplate: statusTxt, + serversTxtTemplate: serversTxt, } return diff --git a/web/static/css/bootstrap.min.css b/internal/web/static/css/bootstrap.min.css similarity index 100% rename from web/static/css/bootstrap.min.css rename to internal/web/static/css/bootstrap.min.css diff --git a/web/static/css/leaflet.css b/internal/web/static/css/leaflet.css similarity index 100% rename from web/static/css/leaflet.css rename to internal/web/static/css/leaflet.css diff --git a/web/static/images/plane.png b/internal/web/static/images/plane.png similarity index 100% rename from web/static/images/plane.png rename to internal/web/static/images/plane.png diff --git a/web/static/js/bootstrap.bundle.min.js b/internal/web/static/js/bootstrap.bundle.min.js similarity index 100% rename from web/static/js/bootstrap.bundle.min.js rename to internal/web/static/js/bootstrap.bundle.min.js diff --git a/web/static/js/jquery-3.7.1.min.js b/internal/web/static/js/jquery-3.7.1.min.js similarity index 100% rename from web/static/js/jquery-3.7.1.min.js rename to internal/web/static/js/jquery-3.7.1.min.js diff --git a/web/static/js/leaflet.js b/internal/web/static/js/leaflet.js similarity index 100% rename from web/static/js/leaflet.js rename to internal/web/static/js/leaflet.js diff --git a/web/static/js/openfsd/api.js b/internal/web/static/js/openfsd/api.js similarity index 100% rename from web/static/js/openfsd/api.js rename to internal/web/static/js/openfsd/api.js diff --git a/web/static/js/openfsd/configeditor.js b/internal/web/static/js/openfsd/configeditor.js similarity index 100% rename from web/static/js/openfsd/configeditor.js rename to internal/web/static/js/openfsd/configeditor.js diff --git a/web/static/js/openfsd/dashboard.js b/internal/web/static/js/openfsd/dashboard.js similarity index 100% rename from web/static/js/openfsd/dashboard.js rename to internal/web/static/js/openfsd/dashboard.js diff --git a/web/static/js/openfsd/leaflet.rotatedmarker.js b/internal/web/static/js/openfsd/leaflet.rotatedmarker.js similarity index 100% rename from web/static/js/openfsd/leaflet.rotatedmarker.js rename to internal/web/static/js/openfsd/leaflet.rotatedmarker.js diff --git a/web/static/js/openfsd/login.js b/internal/web/static/js/openfsd/login.js similarity index 100% rename from web/static/js/openfsd/login.js rename to internal/web/static/js/openfsd/login.js diff --git a/web/static/js/openfsd/usereditor.js b/internal/web/static/js/openfsd/usereditor.js similarity index 100% rename from web/static/js/openfsd/usereditor.js rename to internal/web/static/js/openfsd/usereditor.js diff --git a/web/templates.go b/internal/web/templates.go similarity index 98% rename from web/templates.go rename to internal/web/templates.go index 8ec5afa..dbffc26 100644 --- a/web/templates.go +++ b/internal/web/templates.go @@ -1,4 +1,4 @@ -package main +package web import ( "bytes" diff --git a/web/templates/configeditor.html b/internal/web/templates/configeditor.html similarity index 100% rename from web/templates/configeditor.html rename to internal/web/templates/configeditor.html diff --git a/web/templates/dashboard.html b/internal/web/templates/dashboard.html similarity index 100% rename from web/templates/dashboard.html rename to internal/web/templates/dashboard.html diff --git a/web/templates/landing.html b/internal/web/templates/landing.html similarity index 100% rename from web/templates/landing.html rename to internal/web/templates/landing.html diff --git a/web/templates/layout.html b/internal/web/templates/layout.html similarity index 100% rename from web/templates/layout.html rename to internal/web/templates/layout.html diff --git a/web/templates/login.html b/internal/web/templates/login.html similarity index 100% rename from web/templates/login.html rename to internal/web/templates/login.html diff --git a/web/templates/usereditor.html b/internal/web/templates/usereditor.html similarity index 100% rename from web/templates/usereditor.html rename to internal/web/templates/usereditor.html diff --git a/web/user.go b/internal/web/user.go similarity index 99% rename from web/user.go rename to internal/web/user.go index 56d30fb..951fae4 100644 --- a/web/user.go +++ b/internal/web/user.go @@ -1,4 +1,4 @@ -package main +package web import ( "database/sql" diff --git a/web/util.go b/internal/web/util.go similarity index 93% rename from web/util.go rename to internal/web/util.go index f8bf2a4..afe6d61 100644 --- a/web/util.go +++ b/internal/web/util.go @@ -1,4 +1,4 @@ -package main +package web // safeStr returns an empty string if the pointer is nil, or the underlying string value if not nil. func safeStr(str *string) string { diff --git a/run-windows.bat b/run-windows.bat index 74fe274..376933f 100644 --- a/run-windows.bat +++ b/run-windows.bat @@ -6,4 +6,4 @@ start /b cmd /c "set DATABASE_AUTO_MIGRATE=true&& set DATABASE_SOURCE_NAME=openf powershell -Command "$ProgressPreference = 'SilentlyContinue'; while (-not (Test-NetConnection -ComputerName localhost -Port 13618 -InformationLevel Quiet)) { Start-Sleep -Seconds 1 }" >nul 2>&1 -cmd /c "cd web&& set FSD_HTTP_SERVICE_ADDRESS=http://localhost:13618&& set DATABASE_SOURCE_NAME=../openfsd.db?_pragma=busy_timeout(5000)^&_pragma=journal_mode(WAL)&& go run ." +cmd /c "set FSD_HTTP_SERVICE_ADDRESS=http://localhost:13618&& set DATABASE_SOURCE_NAME=openfsd.db?_pragma=busy_timeout(5000)^&_pragma=journal_mode(WAL)&& go run ./cmd/openfsd-web"