mirror of
https://github.com/icereed/paperless-gpt.git
synced 2025-03-12 12:58:02 -05:00
feat: restructure Dockerfile to build Vite frontend and embed assets in Go application
This commit is contained in:
parent
d013bc909e
commit
fb1e512a21
5 changed files with 129 additions and 46 deletions
20
.github/workflows/docker-build-and-push.yml
vendored
20
.github/workflows/docker-build-and-push.yml
vendored
|
@ -24,14 +24,6 @@ jobs:
|
|||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22
|
||||
- name: Install mupdf
|
||||
run: sudo apt-get install -y mupdf
|
||||
- name: Set library path
|
||||
run: echo "/usr/lib" | sudo tee -a /etc/ld.so.conf.d/mupdf.conf && sudo ldconfig
|
||||
- name: Install dependencies
|
||||
run: go mod download
|
||||
- name: Run Go tests
|
||||
run: go test ./...
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
|
@ -46,10 +38,20 @@ jobs:
|
|||
- name: Install frontend dependencies
|
||||
run: npm install
|
||||
working-directory: web-app
|
||||
- name: Build frontend
|
||||
run: npm run build && cp -r dist/ ../dist/
|
||||
working-directory: web-app
|
||||
- name: Run frontend tests
|
||||
run: npm test
|
||||
working-directory: web-app
|
||||
|
||||
- name: Install mupdf
|
||||
run: sudo apt-get install -y mupdf
|
||||
- name: Set library path
|
||||
run: echo "/usr/lib" | sudo tee -a /etc/ld.so.conf.d/mupdf.conf && sudo ldconfig
|
||||
- name: Install dependencies
|
||||
run: go mod download
|
||||
- name: Run Go tests
|
||||
run: go test ./...
|
||||
build-amd64:
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
|
|
55
Dockerfile
55
Dockerfile
|
@ -3,7 +3,28 @@ ARG VERSION=docker-dev
|
|||
ARG COMMIT=unknown
|
||||
ARG BUILD_DATE=unknown
|
||||
|
||||
# Stage 1: Build the Go binary
|
||||
# Stage 1: Build Vite frontend
|
||||
FROM node:20-alpine AS frontend
|
||||
|
||||
# Set the working directory inside the container
|
||||
WORKDIR /app
|
||||
|
||||
# Install necessary packages
|
||||
RUN apk add --no-cache git
|
||||
|
||||
# Copy package.json and package-lock.json
|
||||
COPY web-app/package.json web-app/package-lock.json ./
|
||||
|
||||
# Install dependencies
|
||||
RUN npm install
|
||||
|
||||
# Copy the frontend code
|
||||
COPY web-app /app/
|
||||
|
||||
# Build the frontend
|
||||
RUN npm run build
|
||||
|
||||
# Stage 2: Build the Go binary
|
||||
FROM golang:1.23.5-alpine3.21 AS builder
|
||||
|
||||
# Set the working directory inside the container
|
||||
|
@ -28,6 +49,7 @@ RUN apk add --no-cache \
|
|||
"mupdf=${MUPDF_VERSION}" \
|
||||
"mupdf-dev=${MUPDF_DEV_VERSION}" \
|
||||
"sed=${SED_VERSION}"
|
||||
|
||||
# Copy go.mod and go.sum files
|
||||
COPY go.mod go.sum ./
|
||||
|
||||
|
@ -37,7 +59,10 @@ RUN go mod download
|
|||
# Pre-compile go-sqlite3 to avoid doing this every time
|
||||
RUN CGO_ENABLED=1 go build -tags musl -o /dev/null github.com/mattn/go-sqlite3
|
||||
|
||||
# Now copy the actual source files
|
||||
# Copy the frontend build
|
||||
COPY --from=frontend /app/dist /app/dist
|
||||
|
||||
# Copy the Go source files
|
||||
COPY *.go .
|
||||
|
||||
# Import ARGs from top level
|
||||
|
@ -55,28 +80,7 @@ RUN sed -i \
|
|||
# Build the binary using caching for both go modules and build cache
|
||||
RUN CGO_ENABLED=1 GOMAXPROCS=$(nproc) go build -tags musl -o paperless-gpt .
|
||||
|
||||
# Stage 2: Build Vite frontend
|
||||
FROM node:20-alpine AS frontend
|
||||
|
||||
# Set the working directory inside the container
|
||||
WORKDIR /app
|
||||
|
||||
# Install necessary packages
|
||||
RUN apk add --no-cache git
|
||||
|
||||
# Copy package.json and package-lock.json
|
||||
COPY web-app/package.json web-app/package-lock.json ./
|
||||
|
||||
# Install dependencies
|
||||
RUN npm install
|
||||
|
||||
# Copy the frontend code
|
||||
COPY web-app /app/
|
||||
|
||||
# Build the frontend
|
||||
RUN npm run build
|
||||
|
||||
# Stage 3: Create a lightweight image with the Go binary and frontend
|
||||
# Stage 3: Create a lightweight image with just the binary
|
||||
FROM alpine:latest
|
||||
|
||||
ENV GIN_MODE=release
|
||||
|
@ -91,9 +95,6 @@ WORKDIR /app/
|
|||
# Copy the Go binary from the builder stage
|
||||
COPY --from=builder /app/paperless-gpt .
|
||||
|
||||
# Copy the frontend build
|
||||
COPY --from=frontend /app/dist /app/web-app/dist
|
||||
|
||||
# Expose the port the app runs on
|
||||
EXPOSE 8080
|
||||
|
||||
|
|
|
@ -36,6 +36,20 @@
|
|||
4. Build and run paperless-gpt
|
||||
5. Access web interface
|
||||
|
||||
### Testing Steps (Required Before Commits)
|
||||
1. **Unit Tests**:
|
||||
```bash
|
||||
go test .
|
||||
```
|
||||
|
||||
2. **E2E Tests**:
|
||||
```bash
|
||||
docker build . -t icereed/paperless-gpt:e2e
|
||||
cd web-app && npm run test:e2e
|
||||
```
|
||||
|
||||
These tests MUST be run and pass before considering any task complete.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
|
54
embedded_assets.go
Normal file
54
embedded_assets.go
Normal file
|
@ -0,0 +1,54 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
//go:embed dist/*
|
||||
var webappContent embed.FS
|
||||
|
||||
// CreateEmbeddedFileServer creates a http.FileSystem from our embedded files
|
||||
func createEmbeddedFileServer() http.FileSystem {
|
||||
// Strip the "dist" prefix from the embedded files
|
||||
stripped, err := fs.Sub(webappContent, "dist")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return http.FS(stripped)
|
||||
}
|
||||
|
||||
// ServeEmbeddedFile serves a file from the embedded filesystem
|
||||
func serveEmbeddedFile(c *gin.Context, prefix string, filepath string) {
|
||||
// If the path is empty or ends with "/", serve index.html
|
||||
if filepath == "" || strings.HasSuffix(filepath, "/") {
|
||||
filepath = path.Join(filepath, "index.html")
|
||||
}
|
||||
|
||||
// Try to open the file from our embedded filesystem
|
||||
fullPath := path.Join("dist", prefix, filepath)
|
||||
f, err := webappContent.Open(fullPath)
|
||||
if err != nil {
|
||||
// If file not found, serve 404
|
||||
log.Warnf("File not found: %s", fullPath)
|
||||
http.Error(c.Writer, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
c.Status(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
stat, err := f.Stat()
|
||||
if err != nil {
|
||||
c.Status(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Serve the file
|
||||
http.ServeContent(c.Writer, c.Request, stat.Name(), stat.ModTime(), f.(io.ReadSeeker))
|
||||
}
|
32
main.go
32
main.go
|
@ -45,7 +45,6 @@ var (
|
|||
visionLlmModel = os.Getenv("VISION_LLM_MODEL")
|
||||
logLevel = strings.ToLower(os.Getenv("LOG_LEVEL"))
|
||||
listenInterface = os.Getenv("LISTEN_INTERFACE")
|
||||
webuiPath = os.Getenv("WEBUI_PATH")
|
||||
autoGenerateTitle = os.Getenv("AUTO_GENERATE_TITLE")
|
||||
autoGenerateTags = os.Getenv("AUTO_GENERATE_TAGS")
|
||||
autoGenerateCorrespondents = os.Getenv("AUTO_GENERATE_CORRESPONDENTS")
|
||||
|
@ -247,16 +246,29 @@ func main() {
|
|||
})
|
||||
}
|
||||
|
||||
if webuiPath == "" {
|
||||
webuiPath = "./web-app/dist"
|
||||
}
|
||||
// Serve static files for the frontend under /assets
|
||||
router.StaticFS("/assets", gin.Dir(webuiPath+"/assets", true))
|
||||
router.StaticFile("/vite.svg", webuiPath+"/vite.svg")
|
||||
// Serve embedded web-app files
|
||||
// router.GET("/*filepath", func(c *gin.Context) {
|
||||
// filepath := c.Param("filepath")
|
||||
// // Remove leading slash from filepath
|
||||
// filepath = strings.TrimPrefix(filepath, "/")
|
||||
// // Handle static assets under /assets/
|
||||
// serveEmbeddedFile(c, "", filepath)
|
||||
// })
|
||||
|
||||
// Catch-all route for serving the frontend
|
||||
router.NoRoute(func(c *gin.Context) {
|
||||
c.File(webuiPath + "/index.html")
|
||||
// Instead of wildcard, serve specific files
|
||||
router.GET("/favicon.ico", func(c *gin.Context) {
|
||||
serveEmbeddedFile(c, "", "favicon.ico")
|
||||
})
|
||||
router.GET("/vite.svg", func(c *gin.Context) {
|
||||
serveEmbeddedFile(c, "", "vite.svg")
|
||||
})
|
||||
router.GET("/assets/*filepath", func(c *gin.Context) {
|
||||
filepath := c.Param("filepath")
|
||||
fmt.Printf("Serving asset: %s\n", filepath)
|
||||
serveEmbeddedFile(c, "assets", filepath)
|
||||
})
|
||||
router.GET("/", func(c *gin.Context) {
|
||||
serveEmbeddedFile(c, "", "index.html")
|
||||
})
|
||||
|
||||
// Start OCR worker pool
|
||||
|
|
Loading…
Reference in a new issue