From 41894e67c62c9b79c98145fd106e73458fc130a0 Mon Sep 17 00:00:00 2001 From: sysops Date: Fri, 10 Jul 2026 18:18:48 +0200 Subject: [PATCH] Fix 404s: serve UI templates/static from configurable ui-root, not CWD The web UI 404'd in production because templates/static were loaded via relative paths ("internal/ui/templates", "internal/ui/static"), which only resolved when running from the repo checkout. systemd sets WorkingDirectory=/var/lib/wireguard-ui-multi, so those paths never existed there. Add a -ui-root flag (default /usr/local/share/wireguard-ui-multi/ui), have install.sh copy internal/ui there, and resolve templates/static paths through it instead of hardcoded relative strings. Also add release-binary fast path to bootstrap.sh (falls back to source build with CGO_ENABLED=0/-trimpath if no release exists yet), and document real hardware/build-RAM requirements in the README. Co-Authored-By: Claude Sonnet 5 --- README.md | 15 +++++++ bootstrap.sh | 71 ++++++++++++++++++++++------------ cmd/wireguard-ui-multi/main.go | 7 ++-- internal/api/router.go | 15 ++++++- internal/api/ui_handlers.go | 8 ++-- scripts/install.sh | 9 ++++- scripts/proxmox-install.sh | 2 +- update.sh | 2 +- 8 files changed, 91 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 3943350..64fb413 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,21 @@ systemd. Betrieb als natives Go-Binary. ## Installation +### Hardware-Anforderungen + +Betrieb selbst ist sehr genügsam (kleines Go-Binary + SQLite, kein Docker/JVM): + +- **Betrieb:** 1 vCPU, 128-256 MB RAM reichen locker +- **Build aus Quellcode:** mind. **1 GB RAM** während `go build` — das + `modernc.org/sqlite`-Package (reines Go, kein cgo, aber sehr großzügiger + generierter Code) sprengt den `go`-Compiler bei 512 MB LXC-RAM + (`signal: killed`, OOM-Killer). Bei 1 GB lief der Build durch. +- Nach dem Build kann der Container/Server wieder auf 256-512 MB reduziert + werden, falls Ressourcen knapp sind. +- Alternative ohne Build-RAM-Bedarf: fertiges Release-Binary nutzen, sobald + eine Release-Pipeline existiert (`bootstrap.sh` versucht das automatisch + zuerst und fällt nur bei Fehlschlag auf den Source-Build zurück). + ### Schnellinstallation (Einzeiler) Auf einem frischen Debian/Ubuntu-Host (als root), lädt und installiert alles diff --git a/bootstrap.sh b/bootstrap.sh index fed9382..65e43f9 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -13,14 +13,18 @@ REPO_URL="https://gitea.perlbach24.de/scripte/wireguard-ui-multi.git" REF="main" SRC_DIR="/opt/wireguard-ui-multi-src" GO_VERSION="1.22.5" +RELEASE_BASE_URL="https://gitea.perlbach24.de/scripte/wireguard-ui-multi/releases/download" +SKIP_RELEASE=0 while [[ $# -gt 0 ]]; do case "$1" in --ref) REF="$2"; shift 2 ;; --src-dir) SRC_DIR="$2"; shift 2 ;; --repo-url) REPO_URL="$2"; shift 2 ;; + --release-base-url) RELEASE_BASE_URL="$2"; shift 2 ;; + --no-release) SKIP_RELEASE=1; shift ;; -h|--help) - echo "Usage: $0 [--ref ] [--src-dir ] [--repo-url ]" >&2 + echo "Usage: $0 [--ref ] [--src-dir ] [--repo-url ] [--no-release]" >&2 exit 1 ;; *) echo "Unknown option: $1" >&2; exit 1 ;; @@ -38,26 +42,7 @@ apt-get update apt-get install -y git wireguard-tools nftables curl ca-certificates export PATH="/usr/local/go/bin:/usr/local/bin:$PATH" - -if ! command -v go >/dev/null 2>&1; then - echo "Installing Go ${GO_VERSION}..." - ARCH="$(dpkg --print-architecture)" - curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-${ARCH}.tar.gz" -o /tmp/go.tar.gz - rm -rf /usr/local/go - tar -C /usr/local -xzf /tmp/go.tar.gz - ln -sf /usr/local/go/bin/go /usr/local/bin/go - rm -f /tmp/go.tar.gz -fi - -if [[ ! -f /etc/profile.d/go-path.sh ]]; then - echo 'export PATH="/usr/local/go/bin:$PATH"' > /etc/profile.d/go-path.sh - chmod 0644 /etc/profile.d/go-path.sh -fi - -if ! command -v go >/dev/null 2>&1; then - echo "go still not found on PATH after install attempt (/usr/local/go/bin)." >&2 - exit 1 -fi +ARCH="$(dpkg --print-architecture)" if [[ -d "$SRC_DIR/.git" ]]; then echo "Updating existing checkout at $SRC_DIR..." @@ -69,11 +54,47 @@ else rm -rf "$SRC_DIR" git clone --depth 1 --branch "$REF" "$REPO_URL" "$SRC_DIR" fi - -echo "Building wireguard-ui-multi..." cd "$SRC_DIR" -go mod tidy -go build -o wireguard-ui-multi ./cmd/wireguard-ui-multi + +BIN_READY=0 +if [[ "$SKIP_RELEASE" -eq 0 ]]; then + RELEASE_ASSET_URL="${RELEASE_BASE_URL}/${REF}/wireguard-ui-multi-linux-${ARCH}" + echo "Trying prebuilt release binary: $RELEASE_ASSET_URL" + if curl -fsSL "$RELEASE_ASSET_URL" -o wireguard-ui-multi.tmp; then + mv wireguard-ui-multi.tmp wireguard-ui-multi + chmod 0755 wireguard-ui-multi + BIN_READY=1 + echo "Using prebuilt release binary (skipped local Go build)." + else + rm -f wireguard-ui-multi.tmp + echo "No prebuilt release binary available, building from source instead." + fi +fi + +if [[ "$BIN_READY" -eq 0 ]]; then + if ! command -v go >/dev/null 2>&1; then + echo "Installing Go ${GO_VERSION}..." + curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-${ARCH}.tar.gz" -o /tmp/go.tar.gz + rm -rf /usr/local/go + tar -C /usr/local -xzf /tmp/go.tar.gz + ln -sf /usr/local/go/bin/go /usr/local/bin/go + rm -f /tmp/go.tar.gz + fi + + if [[ ! -f /etc/profile.d/go-path.sh ]]; then + echo 'export PATH="/usr/local/go/bin:$PATH"' > /etc/profile.d/go-path.sh + chmod 0644 /etc/profile.d/go-path.sh + fi + + if ! command -v go >/dev/null 2>&1; then + echo "go still not found on PATH after install attempt (/usr/local/go/bin)." >&2 + exit 1 + fi + + echo "Building wireguard-ui-multi from source..." + go mod tidy + CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o wireguard-ui-multi ./cmd/wireguard-ui-multi +fi echo "Running native installer..." bash scripts/install.sh diff --git a/cmd/wireguard-ui-multi/main.go b/cmd/wireguard-ui-multi/main.go index 3c7d6ab..ad2122a 100644 --- a/cmd/wireguard-ui-multi/main.go +++ b/cmd/wireguard-ui-multi/main.go @@ -31,18 +31,19 @@ func main() { lanIface = flag.String("lan-iface", "eth0", "LAN interface used for nftables forward rules") tlsCert = flag.String("tls-cert", "", "path to TLS certificate (optional; enables HTTPS together with -tls-key)") tlsKey = flag.String("tls-key", "", "path to TLS private key (optional; enables HTTPS together with -tls-cert)") + uiRoot = flag.String("ui-root", "/usr/local/share/wireguard-ui-multi/ui", "directory containing the ui templates/ and static/ subdirectories") ) flag.Parse() logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) - if err := run(logger, *listen, *dbPath, *configDir, *hooksDir, *lanIface, *tlsCert, *tlsKey); err != nil { + if err := run(logger, *listen, *dbPath, *configDir, *hooksDir, *lanIface, *tlsCert, *tlsKey, *uiRoot); err != nil { logger.Error("fatal", "error", err) os.Exit(1) } } -func run(logger *slog.Logger, listen, dbPath, configDir, hooksDir, lanIface, tlsCert, tlsKey string) error { +func run(logger *slog.Logger, listen, dbPath, configDir, hooksDir, lanIface, tlsCert, tlsKey, uiRoot string) error { // Wire package-level config before anything touches the filesystem/wg-quick. wireguard.ConfigDir = configDir firewall.HooksDir = hooksDir @@ -61,7 +62,7 @@ func run(logger *slog.Logger, listen, dbPath, configDir, hooksDir, lanIface, tls return fmt.Errorf("bootstrap admin user: %w", err) } - a := api.New(db, logger, lanIface) + a := api.New(db, logger, lanIface, uiRoot) srv := &http.Server{ Addr: listen, diff --git a/internal/api/router.go b/internal/api/router.go index 5da0f27..5639a4c 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -3,6 +3,7 @@ package api import ( "log/slog" "net/http" + "path/filepath" "gitea.perlbach24.de/scripte/wireguard-ui-multi/internal/database" "gitea.perlbach24.de/scripte/wireguard-ui-multi/internal/server" @@ -15,18 +16,28 @@ type API struct { sessions *SessionStore log *slog.Logger lanIface string + uiRoot string } -func New(db *database.DB, log *slog.Logger, lanIface string) *API { +func New(db *database.DB, log *slog.Logger, lanIface, uiRoot string) *API { return &API{ db: db, store: server.NewStore(db), sessions: NewSessionStore(), log: log, lanIface: lanIface, + uiRoot: uiRoot, } } +func (a *API) templatesDir() string { + return filepath.Join(a.uiRoot, "templates") +} + +func (a *API) staticDir() string { + return filepath.Join(a.uiRoot, "static") +} + // Routes builds the full HTTP handler tree (API + UI), using Go 1.22 mux patterns. func (a *API) Routes() http.Handler { mux := http.NewServeMux() @@ -57,7 +68,7 @@ func (a *API) Routes() http.Handler { mux.HandleFunc("GET /", a.handleDashboard) mux.HandleFunc("GET /login", a.handleLoginPage) mux.HandleFunc("GET /servers/{id}", a.handleServerPage) - mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.Dir("internal/ui/static")))) + mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.Dir(a.staticDir())))) return a.logMiddleware(mux) } diff --git a/internal/api/ui_handlers.go b/internal/api/ui_handlers.go index d2769aa..93ed18c 100644 --- a/internal/api/ui_handlers.go +++ b/internal/api/ui_handlers.go @@ -4,8 +4,6 @@ import ( "net/http" ) -const templatesDir = "internal/ui/templates" - // hasSession reports whether the request carries a valid, non-expired session cookie. func (a *API) hasSession(r *http.Request) bool { c, err := r.Cookie(sessionCookieName) @@ -21,7 +19,7 @@ func (a *API) handleDashboard(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/login", http.StatusFound) return } - http.ServeFile(w, r, templatesDir+"/dashboard.html") + http.ServeFile(w, r, a.templatesDir()+"/dashboard.html") } func (a *API) handleLoginPage(w http.ResponseWriter, r *http.Request) { @@ -29,7 +27,7 @@ func (a *API) handleLoginPage(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/", http.StatusFound) return } - http.ServeFile(w, r, templatesDir+"/login.html") + http.ServeFile(w, r, a.templatesDir()+"/login.html") } func (a *API) handleServerPage(w http.ResponseWriter, r *http.Request) { @@ -37,5 +35,5 @@ func (a *API) handleServerPage(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/login", http.StatusFound) return } - http.ServeFile(w, r, templatesDir+"/server.html") + http.ServeFile(w, r, a.templatesDir()+"/server.html") } diff --git a/scripts/install.sh b/scripts/install.sh index 84df0f8..16bcb54 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -10,6 +10,8 @@ BIN_DST="/usr/local/bin/wireguard-ui-multi" CONFIG_DIR="/etc/wireguard-ui-multi" DATA_DIR="/var/lib/wireguard-ui-multi" HOOKS_DIR="/etc/wireguard-manager/hooks" +UI_SRC="internal/ui" +UI_DST="/usr/local/share/wireguard-ui-multi/ui" SERVICE_SRC="systemd/wireguard-ui-multi.service" SERVICE_DST="/etc/systemd/system/wireguard-ui-multi.service" @@ -22,7 +24,7 @@ if [[ ! -f "$BIN_SRC" ]]; then if [[ -d "./cmd/wireguard-ui-multi" ]] && command -v go >/dev/null 2>&1; then echo "Binary not found, building from source with 'go build'..." go mod tidy - go build -o "$BIN_SRC" ./cmd/wireguard-ui-multi + CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o "$BIN_SRC" ./cmd/wireguard-ui-multi else echo "Binary not found at $BIN_SRC and cannot build (need Go toolchain + source). Build it first, e.g.:" >&2 echo " go build -o wireguard-ui-multi ./cmd/wireguard-ui-multi" >&2 @@ -46,6 +48,11 @@ echo "Installing binary to $BIN_DST..." cp "$BIN_SRC" "$BIN_DST" chmod 0755 "$BIN_DST" +echo "Installing UI assets to $UI_DST..." +mkdir -p "$(dirname "$UI_DST")" +rm -rf "$UI_DST" +cp -r "$UI_SRC" "$UI_DST" + echo "Installing systemd unit to $SERVICE_DST..." cp "$SERVICE_SRC" "$SERVICE_DST" systemctl daemon-reload diff --git a/scripts/proxmox-install.sh b/scripts/proxmox-install.sh index f3a1641..902e09b 100755 --- a/scripts/proxmox-install.sh +++ b/scripts/proxmox-install.sh @@ -162,7 +162,7 @@ pct exec "$VMID" -- bash -c " set -e cd /opt/wireguard-ui-multi-src go mod tidy - go build -o wireguard-ui-multi ./cmd/wireguard-ui-multi + CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o wireguard-ui-multi ./cmd/wireguard-ui-multi bash scripts/install.sh " diff --git a/update.sh b/update.sh index d1bfdfe..1bb820f 100755 --- a/update.sh +++ b/update.sh @@ -53,7 +53,7 @@ fi echo "Rebuilding..." cd "$SRC_DIR" go mod tidy -go build -o wireguard-ui-multi ./cmd/wireguard-ui-multi +CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o wireguard-ui-multi ./cmd/wireguard-ui-multi echo "Reinstalling..." bash scripts/install.sh