feat: Set up CI/CD pipeline with embedded webapp and Woodpecker configuration

This commit is contained in:
garionion (aider) 2025-03-02 11:37:44 +01:00
parent ebbb2bac41
commit 6400eb9513
5 changed files with 163 additions and 3 deletions

39
.gitignore vendored
View file

@ -1 +1,40 @@
.aider* .aider*
# Go build artifacts
/inventory
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool
*.out
# Dependency directories
vendor/
# SQLite database files
*.sqlite
*.db
# Node.js dependencies
node_modules/
web/node_modules/
web/dist/
# IDE files
.idea/
.vscode/
*.swp
*.swo
# OS specific files
.DS_Store
Thumbs.db
# Build artifacts
/artifacts/

58
.woodpecker.yml Normal file
View file

@ -0,0 +1,58 @@
pipeline:
backend-lint:
image: golang:1.21
group: lint
commands:
- go vet ./...
- go fmt ./...
frontend-lint:
image: node:20
group: lint
commands:
- cd web
- npm ci
- npm run lint
frontend-build:
image: node:20
commands:
- cd web
- npm ci
- npm run build
when:
status: [success]
backend-build:
image: golang:1.21
commands:
- go mod download
- go generate ./...
# Build with embedded frontend
- go build -o inventory -ldflags="-s -w" .
when:
status: [success]
# Create artifacts
artifacts:
image: alpine:latest
commands:
- mkdir -p artifacts
- cp inventory artifacts/
- tar -czvf artifacts/inventory.tar.gz inventory
when:
status: [success]
# Optional step for creating a Docker image
docker-build:
image: plugins/docker
settings:
repo: ${DRONE_REPO_OWNER}/inventory
tags:
- latest
- ${DRONE_TAG##v}
dockerfile: Dockerfile
when:
branch: main
event: [push, tag]
status: [success]

32
Dockerfile Normal file
View file

@ -0,0 +1,32 @@
FROM golang:1.21-alpine as builder
WORKDIR /app
COPY . .
# Install build dependencies
RUN apk add --no-cache git build-base
# Build the frontend
RUN apk add --no-cache nodejs npm
RUN cd web && npm ci && npm run build
# Build the backend with embedded frontend
RUN go mod download
RUN go generate ./...
RUN go build -o inventory -ldflags="-s -w" .
# Create minimal runtime image
FROM alpine:latest
RUN apk --no-cache add ca-certificates tzdata
WORKDIR /app
COPY --from=builder /app/inventory .
COPY --from=builder /app/config.yaml ./config.yaml
# Create a non-root user to run the application
RUN adduser -D -H -h /app appuser
RUN chown -R appuser:appuser /app
USER appuser
EXPOSE 8088
CMD ["./inventory"]

34
main.go
View file

@ -26,7 +26,7 @@ import (
db "git.entr0py.de/garionion/inventory/database/sqlite/generated" db "git.entr0py.de/garionion/inventory/database/sqlite/generated"
) )
//go:embed static/* //go:embed static/* web/dist
var staticFiles embed.FS var staticFiles embed.FS
//go:embed database/sqlite/migration/*.sql //go:embed database/sqlite/migration/*.sql
@ -239,7 +239,37 @@ func main() {
apiHandler.RegisterRoutes(e, oauthMiddleware) apiHandler.RegisterRoutes(e, oauthMiddleware)
// Serve static files - must be after API routes // Serve static files - must be after API routes
e.GET("/*", echo.WrapHandler(http.FileServer(http.FS(staticFiles)))) // First try to serve from web/dist (for the Vue app)
e.GET("/*", func(c echo.Context) error {
path := c.Request().URL.Path
// Try to serve from web/dist first
content, err := staticFiles.ReadFile("web/dist" + path)
if err == nil {
// Determine content type
contentType := http.DetectContentType(content)
if strings.HasSuffix(path, ".css") {
contentType = "text/css"
} else if strings.HasSuffix(path, ".js") {
contentType = "application/javascript"
} else if strings.HasSuffix(path, ".html") {
contentType = "text/html"
}
return c.Blob(http.StatusOK, contentType, content)
}
// If the path is a directory or doesn't exist, try index.html for SPA routing
if path == "/" || err != nil {
indexContent, err := staticFiles.ReadFile("web/dist/index.html")
if err == nil {
return c.HTMLBlob(http.StatusOK, indexContent)
}
}
// Fall back to the static directory
return echo.WrapHandler(http.FileServer(http.FS(staticFiles)))(c)
})
// Start server // Start server
addr := fmt.Sprintf(":%d", cfg.Server.Port) addr := fmt.Sprintf(":%d", cfg.Server.Port)

View file

@ -4,7 +4,8 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Welcome to Vuetify 3</title> <title>Inventory Management System</title>
<base href="/">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>