Compare commits
4 Commits
f7ab09c2c3
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d6ca4b815 | |||
| 4482529f6a | |||
| 0f53e8f659 | |||
| 4d746027dc |
12
.gitea/workflows/check.yml
Normal file
12
.gitea/workflows/check.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
name: check
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- run: docker build .
|
||||
80
CLAUDE.md
Normal file
80
CLAUDE.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Repository Rules
|
||||
|
||||
Last Updated 2026-01-10
|
||||
|
||||
These rules MUST be followed at all times, it is very important.
|
||||
|
||||
* Do NOT stop working while there are still incomplete TODOs in the todo list
|
||||
or in TODO.md. Continue implementing until all tasks are complete or you are
|
||||
explicitly told to stop.
|
||||
|
||||
* Never use `git add -A` - add specific changes to a deliberate commit. A
|
||||
commit should contain one change. After each change, make a commit with a
|
||||
good one-line summary.
|
||||
|
||||
* NEVER modify the linter config without asking first.
|
||||
|
||||
* NEVER modify tests to exclude special cases or otherwise get them to pass
|
||||
without asking first. In almost all cases, the code should be changed,
|
||||
NOT the tests. If you think the test needs to be changed, make your case
|
||||
for that and ask for permission to proceed, then stop. You need explicit
|
||||
user approval to modify existing tests. (You do not need user approval
|
||||
for writing NEW tests.)
|
||||
|
||||
* When linting, assume the linter config is CORRECT, and that each item
|
||||
output by the linter is something that legitimately needs fixing in the
|
||||
code.
|
||||
|
||||
* When running tests, use `make test`.
|
||||
|
||||
* Before commits, run `make check`. This runs `make lint` and `make test`
|
||||
and `make check-fmt`. Any issues discovered MUST be resolved before
|
||||
committing unless explicitly told otherwise.
|
||||
|
||||
* When fixing a bug, write a failing test for the bug FIRST. Add
|
||||
appropriate logging to the test to ensure it is written correctly. Commit
|
||||
that. Then go about fixing the bug until the test passes (without
|
||||
modifying the test further). Then commit that.
|
||||
|
||||
* When adding a new feature, do the same - implement a test first (TDD). It
|
||||
doesn't have to be super complex. Commit the test, then commit the
|
||||
feature.
|
||||
|
||||
* When adding a new feature, use a feature branch. When the feature is
|
||||
completely finished and the code is up to standards (passes `make check`)
|
||||
then and only then can the feature branch be merged into `main` and the
|
||||
branch deleted.
|
||||
|
||||
* Write godoc documentation comments for all exported types and functions as
|
||||
you go along.
|
||||
|
||||
* ALWAYS be consistent in naming. If you name something one thing in one
|
||||
place, name it the EXACT SAME THING in another place.
|
||||
|
||||
* Be descriptive and specific in naming. `wl` is bad;
|
||||
`SourceHostWhitelist` is good. `ConnsPerHost` is bad;
|
||||
`MaxConnectionsPerHost` is good.
|
||||
|
||||
* This is not prototype or teaching code - this is designed for production.
|
||||
Any security issues (such as denial of service) or other web
|
||||
vulnerabilities are P1 bugs and must be added to TODO.md at the top.
|
||||
|
||||
* As this is production code, no stubbing of implementations unless
|
||||
specifically instructed. We need working implementations.
|
||||
|
||||
* NEVER silently fall back to a different setting when a user's parameter
|
||||
explicitly specifies a value. If a user requests format=webp and WebP
|
||||
encoding is not supported, return an error - do NOT silently output PNG
|
||||
instead. If a user specifies fit=invalid and that fit mode doesn't exist,
|
||||
return an error - do NOT silently default to "cover". Silent fallbacks
|
||||
violate the principle of least surprise and mask bugs. The only acceptable
|
||||
defaults are for OMITTED parameters, never for INVALID explicit values.
|
||||
|
||||
* Avoid vendoring deps unless specifically instructed to. NEVER commit
|
||||
the vendor directory, NEVER commit compiled binaries. If these
|
||||
directories or files exist, add them to .gitignore (and commit the
|
||||
.gitignore) if they are not already in there. Keep the entire git
|
||||
repository (with history) small - under 20MiB, unless you specifically
|
||||
must commit larger files (e.g. test fixture example media files). Only
|
||||
OUR source code and immediately supporting files (such as test examples)
|
||||
goes into the repo/history.
|
||||
@@ -114,13 +114,11 @@ import (
|
||||
var (
|
||||
Appname string = "CHANGEME"
|
||||
Version string
|
||||
Buildarch string
|
||||
)
|
||||
|
||||
func main() {
|
||||
globals.Appname = Appname
|
||||
globals.Version = Version
|
||||
globals.Buildarch = Buildarch
|
||||
|
||||
fx.New(
|
||||
fx.Provide(
|
||||
@@ -453,6 +451,11 @@ func New(lc fx.Lifecycle, params HandlersParams) (*Handlers, error) {
|
||||
|
||||
### Closure-Based Handler Pattern
|
||||
|
||||
For JSON route handlers, both the request and the response structures are
|
||||
defined in the scope of the method that returns the HandlerFunc. They can
|
||||
be called simply `Request` and `Response` or slightly more descriptive
|
||||
names.
|
||||
|
||||
All handlers return `http.HandlerFunc` using the closure pattern. This allows initialization logic to run once when the handler is created:
|
||||
|
||||
```go
|
||||
@@ -477,6 +480,11 @@ func (s *Handlers) HandleIndex() http.HandlerFunc {
|
||||
```go
|
||||
// internal/handlers/now.go
|
||||
func (s *Handlers) HandleNow() http.HandlerFunc {
|
||||
|
||||
type request struct {
|
||||
// request format
|
||||
}
|
||||
|
||||
// Response struct defined in closure scope
|
||||
type response struct {
|
||||
Now time.Time `json:"now"`
|
||||
@@ -816,7 +824,6 @@ func (l *Logger) Identify() {
|
||||
l.log.Info("starting",
|
||||
"appname", l.params.Globals.Appname,
|
||||
"version", l.params.Globals.Version,
|
||||
"buildarch", l.params.Globals.Buildarch,
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -938,20 +945,17 @@ import "go.uber.org/fx"
|
||||
var (
|
||||
Appname string
|
||||
Version string
|
||||
Buildarch string
|
||||
)
|
||||
|
||||
// Struct for DI
|
||||
type Globals struct {
|
||||
Appname string
|
||||
Version string
|
||||
Buildarch string
|
||||
}
|
||||
|
||||
func New(lc fx.Lifecycle) (*Globals, error) {
|
||||
n := &Globals{
|
||||
Appname: Appname,
|
||||
Buildarch: Buildarch,
|
||||
Version: Version,
|
||||
}
|
||||
return n, nil
|
||||
@@ -965,13 +969,11 @@ func New(lc fx.Lifecycle) (*Globals, error) {
|
||||
var (
|
||||
Appname string = "CHANGEME" // Default, overridden by build
|
||||
Version string // Set at build time
|
||||
Buildarch string // Set at build time
|
||||
)
|
||||
|
||||
func main() {
|
||||
globals.Appname = Appname
|
||||
globals.Version = Version
|
||||
globals.Buildarch = Buildarch
|
||||
// ...
|
||||
}
|
||||
```
|
||||
@@ -982,10 +984,9 @@ Use ldflags to inject version information at build time:
|
||||
|
||||
```makefile
|
||||
VERSION := $(shell git describe --tags --always)
|
||||
BUILDARCH := $(shell go env GOARCH)
|
||||
|
||||
build:
|
||||
go build -ldflags "-X main.Version=$(VERSION) -X main.Buildarch=$(BUILDARCH)" ./cmd/httpd
|
||||
go build -ldflags "-X main.Version=$(VERSION)" ./cmd/httpd
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
56
Dockerfile
56
Dockerfile
@@ -1,41 +1,35 @@
|
||||
## lint image
|
||||
FROM golangci/golangci-lint:v1.50.1
|
||||
|
||||
RUN mkdir -p /build
|
||||
WORKDIR /build
|
||||
COPY ./ ./
|
||||
RUN golangci-lint run
|
||||
|
||||
## build image:
|
||||
FROM golang:1.19.3-bullseye AS builder
|
||||
|
||||
RUN apt update && apt install -y make bzip2
|
||||
|
||||
RUN mkdir -p /build
|
||||
WORKDIR /build
|
||||
|
||||
COPY go.mod .
|
||||
COPY go.sum .
|
||||
# Lint stage — fast feedback
|
||||
# golangci/golangci-lint:v1.64.8 (2025-03-17)
|
||||
FROM golangci/golangci-lint@sha256:2987913e27f4eca9c8a39129d2c7bc1e74fbcf77f181e01cea607be437aa5cb8 AS lint
|
||||
WORKDIR /src
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
RUN make fmt-check
|
||||
RUN make lint
|
||||
|
||||
COPY ./ ./
|
||||
#RUN make lint
|
||||
RUN make httpd && mv ./httpd /httpd
|
||||
RUN go mod vendor
|
||||
RUN tar -c . | bzip2 > /src.tbz2
|
||||
# Build stage
|
||||
# golang:1.24-bookworm (Go 1.24)
|
||||
FROM golang@sha256:1a6d4452c65dea36aac2e2d606b01b4a029ec90cc1ae53890540ce6173ea77ac AS builder
|
||||
# Force BuildKit to run the lint stage
|
||||
COPY --from=lint /src/go.sum /dev/null
|
||||
WORKDIR /build
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
RUN make test
|
||||
RUN make build && cp ./httpd /httpd
|
||||
|
||||
## output image:
|
||||
FROM debian:bullseye-slim AS final
|
||||
# Runtime stage
|
||||
# debian:bookworm-slim (2025-03)
|
||||
FROM debian@sha256:74d56e3931e0d5a1dd51f8c8a2466d21de84a271cd3b5a733b803aa91abf4421 AS final
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
COPY --from=builder /httpd /app/httpd
|
||||
COPY --from=builder /src.tbz2 /usr/local/src/src.tbz2
|
||||
|
||||
|
||||
WORKDIR /app
|
||||
ENV HOME /app
|
||||
|
||||
ENV PORT 8080
|
||||
ENV DBURL none
|
||||
ENV HOME=/app
|
||||
ENV PORT=8080
|
||||
ENV DBURL=none
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
|
||||
26
Makefile
26
Makefile
@@ -4,7 +4,6 @@ VERSION := $(shell git describe --always --dirty=-dirty)
|
||||
ARCH := $(shell uname -m)
|
||||
UNAME_S := $(shell uname -s)
|
||||
GOLDFLAGS += -X main.Version=$(VERSION)
|
||||
GOLDFLAGS += -X main.Buildarch=$(ARCH)
|
||||
GOFLAGS := -ldflags "$(GOLDFLAGS)"
|
||||
|
||||
default: clean debug
|
||||
@@ -12,22 +11,30 @@ default: clean debug
|
||||
commit: fmt lint
|
||||
git commit -a
|
||||
|
||||
# get golangci-lint with:
|
||||
# go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.31.0
|
||||
# get gofumports with:
|
||||
# go get mvdan.cc/gofumpt/gofumports
|
||||
# get gofumpt with:
|
||||
# go install mvdan.cc/gofumpt@latest
|
||||
fmt:
|
||||
gofumpt -l -w .
|
||||
golangci-lint run --fix
|
||||
|
||||
fmt-check:
|
||||
@test -z "$$(gofmt -l .)" || { echo "gofmt found unformatted files:"; gofmt -l .; exit 1; }
|
||||
|
||||
lint:
|
||||
golangci-lint run
|
||||
sh -c 'test -z "$$(gofmt -l .)"'
|
||||
|
||||
test:
|
||||
go test ./...
|
||||
|
||||
check: lint test
|
||||
check: fmt-check lint test
|
||||
|
||||
build: ./$(FN)d
|
||||
|
||||
hooks:
|
||||
@mkdir -p .git/hooks
|
||||
@printf '#!/bin/sh\nmake fmt-check lint\n' > .git/hooks/pre-commit
|
||||
@chmod +x .git/hooks/pre-commit
|
||||
@echo "Pre-commit hook installed."
|
||||
|
||||
debug: ./$(FN)d
|
||||
DEBUG=1 GOTRACEBACK=all ./$(FN)d
|
||||
@@ -49,5 +56,6 @@ docker:
|
||||
go build -o ../../$(FN)d $(GOFLAGS) .
|
||||
|
||||
tools:
|
||||
go get -v github.com/golangci/golangci-lint/cmd/golangci-lint@v1.31.0
|
||||
go get -v mvdan.cc/gofumpt/gofumports
|
||||
go install mvdan.cc/gofumpt@latest
|
||||
|
||||
.PHONY: default commit fmt fmt-check lint test check build hooks debug debugger run clean docker tools
|
||||
|
||||
@@ -86,3 +86,4 @@ WTFPL (aka public domain):
|
||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -15,13 +15,11 @@ import (
|
||||
var (
|
||||
Appname string = "CHANGEME"
|
||||
Version string
|
||||
Buildarch string
|
||||
)
|
||||
|
||||
func main() {
|
||||
globals.Appname = Appname
|
||||
globals.Version = Version
|
||||
globals.Buildarch = Buildarch
|
||||
|
||||
fx.New(
|
||||
fx.Provide(
|
||||
@@ -36,5 +34,4 @@ func main() {
|
||||
),
|
||||
fx.Invoke(func(*server.Server) {}),
|
||||
).Run()
|
||||
// os.Exit(server.Run(Appname, Version, Buildarch))
|
||||
}
|
||||
|
||||
@@ -8,19 +8,16 @@ import (
|
||||
var (
|
||||
Appname string
|
||||
Version string
|
||||
Buildarch string
|
||||
)
|
||||
|
||||
type Globals struct {
|
||||
Appname string
|
||||
Version string
|
||||
Buildarch string
|
||||
}
|
||||
|
||||
func New(lc fx.Lifecycle) (*Globals, error) {
|
||||
n := &Globals{
|
||||
Appname: Appname,
|
||||
Buildarch: Buildarch,
|
||||
Version: Version,
|
||||
}
|
||||
return n, nil
|
||||
|
||||
@@ -64,6 +64,5 @@ func (l *Logger) Identify() {
|
||||
l.log.Info("starting",
|
||||
"appname", l.params.Globals.Appname,
|
||||
"version", l.params.Globals.Version,
|
||||
"buildarch", l.params.Globals.Buildarch,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user