From c3bf6149012d2f73295fda5c80c81edef91a4a89 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 13 Sep 2026 21:42:25 +0200 Subject: [PATCH] vibe --- Dockerfile | 21 +++++++++++++++++++++ backend/main.go | 4 ++++ backend/static.go | 30 ++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 Dockerfile create mode 100644 backend/static.go diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..50f1a71 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +FROM node:20-alpine AS frontend +WORKDIR /src +COPY frontend/package.json frontend/package-lock.json* ./ +RUN npm install +COPY frontend/ . +RUN npm run build + +FROM golang:1.23-alpine AS backend +WORKDIR /src +COPY backend/go.mod backend/go.sum ./ +RUN go mod download +COPY backend/ . +RUN CGO_ENABLED=0 go build -o /skeps-backend . + +FROM alpine:3.20 +WORKDIR /app +COPY --from=backend /skeps-backend ./skeps-backend +COPY --from=frontend /src/dist ./dist +ENV STATIC_DIR=/app/dist +EXPOSE 8080 +ENTRYPOINT ["/app/skeps-backend"] diff --git a/backend/main.go b/backend/main.go index b0b0ded..c4a32b3 100644 --- a/backend/main.go +++ b/backend/main.go @@ -34,6 +34,10 @@ func main() { w.WriteHeader(http.StatusOK) }) + if staticDir := os.Getenv("STATIC_DIR"); staticDir != "" { + mux.Handle("/", spaHandler(staticDir)) + } + port := os.Getenv("PORT") if port == "" { port = "8080" diff --git a/backend/static.go b/backend/static.go new file mode 100644 index 0000000..1fd06c5 --- /dev/null +++ b/backend/static.go @@ -0,0 +1,30 @@ +package main + +import ( + "net/http" + "os" + "path/filepath" + "strings" +) + +// spaHandler serves the built frontend from dir, falling back to index.html +// for any path that isn't an actual file so client-side routes (e.g. a list's +// /lion-fancy-sharpness URL) resolve correctly on a hard refresh. +func spaHandler(dir string) http.Handler { + fs := http.FileServer(http.Dir(dir)) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + cleanPath := filepath.Clean(r.URL.Path) + + if strings.HasPrefix(cleanPath, "/assets/") { + w.Header().Set("Cache-Control", "public, max-age=31536000, immutable") + } else { + w.Header().Set("Cache-Control", "no-cache") + } + + if info, err := os.Stat(filepath.Join(dir, cleanPath)); err == nil && !info.IsDir() { + fs.ServeHTTP(w, r) + return + } + http.ServeFile(w, r, filepath.Join(dir, "index.html")) + }) +}