From 59dfff38bcaa55253dd074a774c8835e0a3f340f Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 6 Oct 2026 15:21:46 +0200 Subject: [PATCH] Clean up build tooling and Dockerfile - Dockerfile cross-compiles Go for the target platform (amd64/arm64 without emulation), uses templ pinned in go.mod, runs as uid 1000 on alpine and lets FRONTEND_IMAGE pin the frontend version - justfile: tools via go tool, `test` (unit) and `test-integration` (uses the active docker context), no hardcoded paths, no tailwind or frontend recipes - Removed app.yaml, init.sh, tailwind.config.js and the tracked .idea folder --- .dockerignore | 12 +- .gitignore | 5 - .idea/.gitignore | 8 -- .idea/MusicServer.iml | 16 --- .idea/codeStyles/codeStyleConfig.xml | 5 - .idea/dataSources.xml | 36 ------ .idea/modules.xml | 8 -- .idea/vcs.xml | 6 - Dockerfile | 53 ++++---- app.yaml | 8 -- init.sh | 17 --- justfile | 177 ++++----------------------- tailwind.config.js | 11 -- 13 files changed, 58 insertions(+), 304 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/MusicServer.iml delete mode 100644 .idea/codeStyles/codeStyleConfig.xml delete mode 100644 .idea/dataSources.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml delete mode 100644 app.yaml delete mode 100644 init.sh delete mode 100644 tailwind.config.js diff --git a/.dockerignore b/.dockerignore index eb9362e..5026ba4 100644 --- a/.dockerignore +++ b/.dockerignore @@ -14,18 +14,12 @@ compose.yaml *.pprof main conf.yaml -output.css -tailwindcss .env -# Node.js (frontend build artifacts) -node_modules -package.json -package-lock.json - -# Test files +# Test files and fixtures *_test.go -integration_test.go +internal/testutil +*_dump.sql # OS .DS_Store diff --git a/.gitignore b/.gitignore index f5a43fb..9be0b89 100644 --- a/.gitignore +++ b/.gitignore @@ -3,13 +3,8 @@ tmp .idea *templ.go conf.yaml -output.css compose.yaml -tailwindcss .env -node_modules -package.json -package-lock.json cpu.pprof main diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/MusicServer.iml b/.idea/MusicServer.iml deleted file mode 100644 index d840f1f..0000000 --- a/.idea/MusicServer.iml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index a55e7a1..0000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml deleted file mode 100644 index 9476a4a..0000000 --- a/.idea/dataSources.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - postgresql - true - org.postgresql.Driver - jdbc:postgresql://192.168.86.100:5432/music_prod - - - postgresql - true - org.postgresql.Driver - jdbc:postgresql://192.168.86.100:5432/music_test - - - postgresql - true - org.postgresql.Driver - jdbc:postgresql://ssh.sanplex.xyz:9432/music_test - - - postgresql - true - org.postgresql.Driver - jdbc:postgresql://ssh.sanplex.xyz:9432/music_prod - - - postgresql - true - org.postgresql.Driver - jdbc:postgresql://localhost:5432/music_test2 - $ProjectFileDir$ - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 9ce91d9..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index cc1dd39..5001ed6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,36 +1,39 @@ -# Stage 1: Build backend -FROM golang:1.25-alpine as build_go -RUN apk add --no-cache curl +# The frontend is built and published separately; override to pin a version. +ARG FRONTEND_IMAGE=gitea.sanplex.xyz/sansan/musicfrontend:latest + +# Stage 1: Build the backend on the build machine and cross-compile for the target platform, +# so multi-platform builds need no emulation. +FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS build +ARG TARGETOS TARGETARCH WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY cmd/ ./cmd/ COPY internal/ ./internal/ -RUN find . -name "*_test.go" -delete && \ - find . -name "integration_test.go" -delete -RUN go install github.com/a-h/templ/cmd/templ@latest -RUN templ generate -RUN go build -o main cmd/main.go +RUN go tool templ generate +RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH \ + go build -trimpath -ldflags="-s -w" -tags timetzdata -o main ./cmd -# Stage 2: Final image -FROM golang:1.25-alpine +# Stage 2: Frontend static files (the same for every platform) +FROM --platform=$BUILDPLATFORM ${FRONTEND_IMAGE} AS frontend +# Empty API_HOSTNAME makes the frontend use relative paths +RUN echo "window.__RUNTIME_CONFIG__ = { API_HOSTNAME: '' };" > /usr/share/nginx/html/config.js + +# Stage 3: Final image. No RUN steps here, so no emulation is needed for other architectures. +FROM alpine:3.22 +COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ +WORKDIR /app +COPY --from=build /app/main . +COPY --from=frontend /usr/share/nginx/html /frontend +COPY ./songs/ ./songs/ + +# The music volume must be readable by uid 1000, and writable for the .id file cleanup during sync. +USER 1000:1000 EXPOSE 8080 VOLUME /sorted VOLUME /characters -COPY --from=build_go /app/main . -COPY --from=gitea.sanplex.xyz/sansan/musicfrontend:latest /usr/share/nginx/html /frontend -# Generate config.js with empty API_HOSTNAME (relative paths) -RUN echo "window.__RUNTIME_CONFIG__ = { API_HOSTNAME: '' };" > /frontend/config.js -COPY ./songs/ ./songs/ +# Required: DB_HOST, DB_PORT, DB_USERNAME, DB_PASSWORD, DB_NAME, MUSIC_PATH, CHARACTERS_PATH +ENV PORT=8080 -ENV PORT 8080 -ENV DB_HOST "" -ENV DB_PORT "" -ENV DB_USERNAME "" -ENV DB_PASSWORD "" -ENV DB_NAME "" -ENV MUSIC_PATH "" -ENV CHARACTERS_PATH "" - -CMD ./main +CMD ["./main"] diff --git a/app.yaml b/app.yaml deleted file mode 100644 index 083a3d4..0000000 --- a/app.yaml +++ /dev/null @@ -1,8 +0,0 @@ -application: musicserver -version: 2.2.0 -runtime: go115 -api_version: go1 - -handlers: -- url: /.* - script: _go_app \ No newline at end of file diff --git a/init.sh b/init.sh deleted file mode 100644 index f055fe3..0000000 --- a/init.sh +++ /dev/null @@ -1,17 +0,0 @@ -#! /bin/sh - -#envsubst < ./frontend/src/arne.js - -sed -i 's^$HOSTNAME^'"$HOSTNAME"'^g' ./frontend/src/arne.js - -cat ./frontend/src/arne.js - -cd ./frontend/ - -npm install - -npm run build - -cd .. - -./MusicServer \ No newline at end of file diff --git a/justfile b/justfile index 3aa992b..4cc0a4d 100644 --- a/justfile +++ b/justfile @@ -1,104 +1,50 @@ set dotenv-load -# Build the application +# Build and run the unit tests all: build test -templ-install: - @if ! command -v templ > /dev/null; then \ - read -p "Go's 'templ' is not installed on your machine. Do you want to install it? [Y/n] " choice; \ - if [ "$$choice" != "n" ] && [ "$$choice" != "N" ]; then \ - go install github.com/a-h/templ/cmd/templ@latest; \ - if [ ! -x "$$(command -v templ)" ]; then \ - echo "templ installation failed. Exiting..."; \ - exit 1; \ - fi; \ - else \ - echo "You chose not to install templ. Exiting..."; \ - exit 1; \ - fi; \ - fi - -templ-build: templ-install +# Generate templ components (version pinned in go.mod) +templ-build: @echo "Building templ..." - @templ generate - -tailwind-macos: - @if [ ! -f tailwindcss ]; then curl -sL https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-macos-arm64 -o tailwindcss; fi - @chmod +x tailwindcss - -tailwind-linux: - @if [ ! -f tailwindcss ]; then curl -sL https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64 -o tailwindcss; fi - @chmod +x tailwindcss - -tailwind-build: - @echo "Building tailwind..." - @./tailwindcss -i cmd/web/assets/css/input.css -o cmd/web/assets/css/output.css + @go tool templ generate sqlc-generate: - @echo "Generating sqlc schema..." + @echo "Generating sqlc code..." @sqlc generate migrate-create name: @migrate create -ext sql -dir internal/db/migrations -seq {{name}} -swag-install: - @if ! command -v swag > /dev/null; then \ - read -p "Swag is not installed on your machine. Do you want to install it? [Y/n] " choice; \ - if [ "$$choice" != "n" ] && [ "$$choice" != "N" ]; then \ - go install github.com/swaggo/swag/cmd/swag@latest; \ - if [ ! -x "$$(command -v swag)" ]; then \ - echo "swag installation failed. Exiting..."; \ - exit 1; \ - fi; \ - else \ - echo "You chose not to install swag. Exiting..."; \ - exit 1; \ - fi; \ - fi - -swag-generate: swag-install +# Generate OpenAPI docs (version pinned in go.mod) +swag-generate: @echo "Generating OpenAPI docs..." - @swag init -g internal/server/routes.go -o cmd/docs + @go tool swag init -g internal/server/routes.go -o cmd/docs --parseInternal -frontend-install: - @if ! command -v npm > /dev/null; then \ - echo "npm is not installed on your machine. Please install Node.js first."; \ - exit 1; \ - fi - @cd cmd/frontend && npm install - -frontend-build: frontend-install - @echo "Building frontend..." - @cd cmd/frontend && npm run build - -[no-cd] build: sqlc-generate templ-build swag-generate - @echo "Building..." - @go build -o main cmd/main.go + @echo "Building..." + @go build -o main cmd/main.go -run: - @templ generate +run: templ-build @go run cmd/main.go build-run: build - @go run cmd/main.go + @./main -test: build - @echo "Starting test database container..." - @docker compose -f compose.test.yaml up -d - @sleep 10 - @echo "Running integration tests..." - @just test-integration - @echo "Stopping test database container..." - @just test-integration-down +# Unit tests: no database or Docker needed +test: + @go test -race ./... + +# Integration tests against PostgreSQL in Docker (testcontainers), using the active docker context. +# Pass a pattern to run a subset, e.g. `just test-integration TestSQLDumps` +test-integration pattern=".": + @DOCKER_HOST="${DOCKER_HOST:-$(docker context inspect --format '{{{{.Endpoints.docker.Host}}')}" \ + go test -tags integration -race -timeout 15m -run '{{pattern}}' ./... -# Clean the binary clean: @echo "Cleaning..." @rm -f main docker-build: - @echo "Building Docker image with docker..." @docker build -t music-server . docker-run: @@ -107,82 +53,13 @@ docker-run: docker-down: @docker compose down -# Run integration tests with docker -# Starts a test PostgreSQL container, runs tests, then cleans up -test-integration: - @echo "Cleaning old test database..." - @docker compose -f compose.test.yaml down -v - @echo "Starting fresh test database container..." - @docker compose -f compose.test.yaml up -d - @sleep 10 - @echo "Running integration tests..." - @DOCKER_HOST=unix:///Users/sebastian/.docker/run/docker.sock TESTCONTAINERS_RYUK_CONTAINER_NETWORK=testcontainers-test-net DB_HOST=localhost DB_PORT=5433 DB_USERNAME=testuser DB_PASSWORD=testpass DB_NAME=music_server_test MUSIC_PATH=/Users/sebastian/projects/MusicServer/testMusic CHARACTERS_PATH=/Users/sebastian/projects/MusicServer/testCharacters PORT=8081 LOG_LEVEL=debug LOG_JSON=false go test -v -timeout 30m -p 1 -parallel 1 ./internal/... - -# Alternative: Run integration tests using testcontainers with docker provider -test-integration-tc: - @echo "Creating test network (if it doesn't exist)..." - @docker network inspect testcontainers-test-net > /dev/null 2>&1 || docker network create testcontainers-test-net - @echo "Running integration tests with testcontainers (docker provider)..." - @DOCKER_HOST=unix:///Users/sebastian/.docker/run/docker.sock TESTCONTAINERS_PROVIDER=docker TESTCONTAINERS_RYUK_CONTAINER_NETWORK=testcontainers-test-net go test -v -timeout 30m . - -# Run SQL dump migration tests -# Tests that the application can start with each SQL dump and migrations work correctly -test-sql-dump: - @echo "Creating test network (if it doesn't exist)..." - @docker network inspect testcontainers-test-net > /dev/null 2>&1 || docker network create testcontainers-test-net - @echo "Running SQL dump migration tests..." - @DOCKER_HOST=unix:///Users/sebastian/.docker/run/docker.sock TESTCONTAINERS_PROVIDER=docker TESTCONTAINERS_RYUK_CONTAINER_NETWORK=testcontainers-test-net go test -v -timeout 15m -run "TestMusic.*Dump" ./internal/server/ - -# Run individual SQL dump tests -test-sql-dump-ref: - @echo "Creating test network (if it doesn't exist)..." - @docker network inspect testcontainers-test-net > /dev/null 2>&1 || docker network create testcontainers-test-net - @echo "Running SQL dump migration test for ref dump..." - @DOCKER_HOST=unix:///Users/sebastian/.docker/run/docker.sock TESTCONTAINERS_PROVIDER=docker TESTCONTAINERS_RYUK_CONTAINER_NETWORK=testcontainers-test-net go test -v -timeout 5m -run "TestMusicRefDump" ./internal/server/ - -test-sql-dump-test: - @echo "Creating test network (if it doesn't exist)..." - @docker network inspect testcontainers-test-net > /dev/null 2>&1 || docker network create testcontainers-test-net - @echo "Running SQL dump migration test for test dump..." - @DOCKER_HOST=unix:///Users/sebastian/.docker/run/docker.sock TESTCONTAINERS_PROVIDER=docker TESTCONTAINERS_RYUK_CONTAINER_NETWORK=testcontainers-test-net go test -v -timeout 5m -run "TestMusicTestDump" ./internal/server/ - -test-sql-dump-prod: - @echo "Creating test network (if it doesn't exist)..." - @docker network inspect testcontainers-test-net > /dev/null 2>&1 || docker network create testcontainers-test-net - @echo "Running SQL dump migration test for prod dump..." - @DOCKER_HOST=unix:///Users/sebastian/.docker/run/docker.sock TESTCONTAINERS_PROVIDER=docker TESTCONTAINERS_RYUK_CONTAINER_NETWORK=testcontainers-test-net go test -v -timeout 5m -run "TestMusicProdDump" ./internal/server/ - -# Stop and remove test database container -test-integration-down: - @echo "Stopping test database container..." - @docker compose -f compose.test.yaml down -v - -# Build and run with Docker Compose -build-run-docker: - @echo "Building and running with Docker Compose..." - @docker compose up --build - -# Shutdown containers -stop-docker: - @echo "Stopping containers..." - @docker compose down - watch-templ: - templ generate --watch --proxy="http://localhost:8080" --cmd="go run ." + @go tool templ generate --watch --proxy="http://localhost:8080" --cmd="go run ./cmd" -# Live Reload +# Live reload watch: - @if command -v air > /dev/null; then \ - air; \ - echo "Watching...";\ - else \ - read -p "Go's 'air' is not installed on your machine. Do you want to install it? [Y/n] " choice; \ - if [ "$$choice" != "n" ] && [ "$$choice" != "N" ]; then \ - go install github.com/air-verse/air@latest; \ - air; \ - echo "Watching...";\ - else \ - echo "You chose not to install air. Exiting..."; \ - exit 1; \ - fi; \ + @if ! command -v air > /dev/null; then \ + echo "air is not installed. Install it with: go install github.com/air-verse/air@latest"; \ + exit 1; \ fi + @air diff --git a/tailwind.config.js b/tailwind.config.js deleted file mode 100644 index 4ec3451..0000000 --- a/tailwind.config.js +++ /dev/null @@ -1,11 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -module.exports = { - content: [ - "./cmd/web/**/*.html", "./cmd/web/**/*.templ", - ], - theme: { - extend: {}, - }, - plugins: [], -} -