2 Commits
Author SHA1 Message Date
sneak db63e51c98 Send only the Referer's scheme and host to Sentry (closes #548)
check / check (push) Waiting to run
A Sentry event kept the Referer header verbatim, and a Referer can be
an entrypoint URL: a sender that follows the README's http:// to
https:// redirect sends the URL it was redirected from, and a browser
sends the 404 page's URL under /h/ when it fetches that page's
stylesheet. The Sentry hook now keeps only the Referer's scheme and
host. The access log's reduction moves, unchanged, into a new package,
internal/referer, as SchemeAndHost; the access log and the Sentry hook
both call it there. The comment above sentryKeepsHeader and the
README's Sentry passage say so, and the README's Package Layout lists
the new package.

Model: opus-5-5
2026-10-15 15:38:13 +00:00
clawbot 8c05ec4378 Build the binary without cgo (closes #545)
check / check (push) Waiting to run
make build now builds with CGO_ENABLED=0, and the image builds through
it. The binary's only cgo code was mattn/go-sqlite3, which
gorm.io/driver/sqlite imports and webhooker never opens: every SQLite
file is opened with modernc.org/sqlite and the connection handed to
gorm. Without cgo, mattn's package compiles to a stub nothing calls.

The Dockerfile builds the binary once. The static relink and its
netgo, osusergo and sqlite_omit_load_extension tags are gone, and with
them the Makefile's GO_LDFLAGS and GO_TAGS, which only the relink used.
The test phase keeps cgo for -race. No README or Dockerfile sentence
says the binary needs cgo any more.

Model: opus-5-5
2026-10-15 17:36:35 +02:00
4 changed files with 29 additions and 71 deletions
+4 -23
View File
@@ -76,8 +76,6 @@ RUN --network=none node_modules/.bin/prettier --check '**/*.md'
# depends on it.
#
# golangci/golangci-lint:v2.14.0 (Debian-based), 2026-09-24
# Using Debian-based image because mattn/go-sqlite3 (CGO) does not
# compile on Alpine musl (off64_t is a glibc type).
FROM golangci/golangci-lint:v2.14.0@sha256:ad862ba6b3798cbe0fd9fd7408d498fd74fbd2623a92406b2fd3898faf0bf98f AS lint
WORKDIR /src
@@ -167,9 +165,7 @@ RUN --mount=type=tmpfs,target=/tmp/tests,size=512m \
# Build stage
# golang:1.26.1-bookworm (Debian-based), 2026-03-17
# Using Debian-based image because gorm.io/driver/sqlite pulls in
# mattn/go-sqlite3 (CGO), which does not compile on Alpine musl. The image
# ships git and make, which the version step below uses.
# The image ships git and make, which the version step below uses.
FROM golang:1.26.1-bookworm@sha256:4465644228bc2857a954b092167e12aa59c006a3492282a6c820bf4755fd64a4 AS builder
# Nothing is wanted from the lint and test phases or from the stylesheet and
@@ -211,26 +207,11 @@ RUN version="$(make version VERSION="$VERSION")"; \
fi
# Builds through the Makefile's build target, which runs script/assets
# (Alpine.js, extracted from its tarball in 3p/) first.
# (Alpine.js, extracted from its tarball in 3p/) first. The target builds
# with cgo off, so the binary is statically linked and runs on the Alpine
# runtime image below as it is.
RUN make build VERSION="$VERSION"
# Rebuild with static linking for the Alpine runtime. The cgo binary the
# step above builds is dynamically linked against glibc, which Alpine
# (musl) does not have.
#
# A statically linked glibc still loads shared libraries at run time for
# getaddrinfo and dlopen, and Alpine has none to load, so the build tags
# keep both out of the binary: netgo and osusergo select Go's own DNS
# resolver and user lookup instead of glibc's, and
# sqlite_omit_load_extension leaves out SQLite extension loading, which
# webhooker never uses.
#
# The static flags and tags go in through GO_LDFLAGS and GO_TAGS rather
# than a -ldflags of their own: the build target composes them with the
# -X that stamps the version, so this relink cannot silently drop the
# stamp.
RUN CGO_ENABLED=1 make build VERSION="$VERSION" GO_TAGS=netgo,osusergo,sqlite_omit_load_extension GO_LDFLAGS='-extldflags "-static"'
# Runtime stage
# alpine:3.21, 2026-03-17
FROM alpine:3.21@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709
+5 -12
View File
@@ -16,17 +16,6 @@ VERSION ?= $(shell script/version)
# exists to correct.
override VERSION := $(or $(strip $(VERSION)),$(shell script/version))
# Extra linker flags and build tags for the build target. The static
# relink in the Dockerfile passes -extldflags and its build tags through
# these rather than a -ldflags of its own, so composing flags cannot drop
# the version stamp.
#
# The build target itself always passes -trimpath and -s -w, as the Go
# Dockerfile in REPO_POLICIES.md does: no build paths, symbol table or
# debug information in the binary.
GO_LDFLAGS ?=
GO_TAGS ?=
bootstrap:
@script/bootstrap
@@ -54,8 +43,12 @@ fmt-check:
check:
@script/check
# Built with cgo off, -trimpath and -s -w, as the Go Dockerfile in
# REPO_POLICIES.md does: the binary carries no build paths, symbol table or
# debug information, and on Linux, the image's included, it is statically
# linked.
build: assets
go build -trimpath -tags '$(GO_TAGS)' -ldflags '$(strip -s -w -X main.version=$(VERSION) $(GO_LDFLAGS))' -o bin/webhooker ./cmd/webhooker
CGO_ENABLED=0 go build -trimpath -ldflags '-s -w -X main.version=$(VERSION)' -o bin/webhooker ./cmd/webhooker
run: build
./bin/webhooker
+13 -22
View File
@@ -1393,10 +1393,10 @@ It uses:
management
- **[go-chi](https://github.com/go-chi/chi)** for HTTP routing
- **[GORM](https://gorm.io)** for database access with
**[modernc.org/sqlite](https://pkg.go.dev/modernc.org/sqlite)** as the runtime
SQLite driver. Note: `gorm.io/driver/sqlite` transitively depends on
`mattn/go-sqlite3`, which requires CGO at build time (see [Docker](#docker)
section)
**[modernc.org/sqlite](https://pkg.go.dev/modernc.org/sqlite)**, which is pure
Go, as the SQLite driver. `gorm.io/driver/sqlite` also imports
`mattn/go-sqlite3`, whose stub for builds without cgo is linked into the
binary but never used
- **[slog](https://pkg.go.dev/log/slog)** (stdlib) for structured logging with
TTY detection (text for dev, JSON for prod)
- **[gorilla/sessions](https://github.com/gorilla/sessions)** for encrypted
@@ -2135,9 +2135,7 @@ endpoint. That is the JSON key; the error text for a missing one reads
`webhook_url is required`, which is the message, not the key.
The database uses the
[modernc.org/sqlite](https://pkg.go.dev/modernc.org/sqlite) driver at runtime,
though CGO is required at build time due to the transitive `mattn/go-sqlite3`
dependency from `gorm.io/driver/sqlite`.
[modernc.org/sqlite](https://pkg.go.dev/modernc.org/sqlite) driver.
#### Schema
@@ -3103,6 +3101,8 @@ webhooker/
│ │ ├── ratelimit.go # Per-IP rate limiting middleware (go-chi/httprate)
│ │ ├── loginguard.go # Login failure counters and the Argon2id verification semaphore
│ │ └── middlewaretest/ # Test support: a Middleware for tests in other packages
│ ├── referer/
│ │ └── referer.go # SchemeAndHost: cuts a Referer down to its scheme and host, for the access log and the Sentry hook
│ ├── reqtls/
│ │ └── reqtls.go # IsTLS: the one TLS predicate, r.TLS or X-Forwarded-Proto
│ ├── server/
@@ -3472,16 +3472,12 @@ fixed independently of the compiler's:
a step's log, and then fails. `make test` builds this stage alone.
6. **Builder stage** (`golang:1.26.1-bookworm`) — depends on the lint and test
phases and the `css-check` and `markdown-check` stages passing (it copies a
file from each), runs `make build` (which extracts Alpine.js from `3p/`
first), and then rebuilds the binary with `CGO_ENABLED=1` and static linking
so it runs on musl, with the build tags `netgo`, `osusergo` and
`sqlite_omit_load_extension` so it needs none of glibc's shared libraries at
run time. Both builds go through `make build`, the relink adding its
`-extldflags` via `GO_LDFLAGS` and its tags via `GO_TAGS`, so neither can
drop the `-X` that stamps the version. The version is the `VERSION` build arg
if one is given, otherwise derived from the `.git` in the context, and the
stage fails if a context with `.git` would stamp an empty version, `dev` or
`unknown` (see [Version stamping](#version-stamping)).
file from each), and runs `make build` (which extracts Alpine.js from `3p/`
first). `make build` compiles with cgo off, so the binary is statically
linked and runs on the Alpine runtime image. The version is the `VERSION`
build arg if one is given, otherwise derived from the `.git` in the context,
and the stage fails if a context with `.git` would stamp an empty version,
`dev` or `unknown` (see [Version stamping](#version-stamping)).
7. **Runtime stage** (`alpine:3.21`) — copies the static binary and
`deploy/docker-entrypoint.sh`, creates the `/var/lib/webhooker` directory for
all SQLite databases, exposes port 8080, and includes a health check against
@@ -3493,11 +3489,6 @@ The lint and test phases invoke `gofmt`, `golangci-lint` and `go test` directly
rather than `make fmt-check`, `make lint` and `make test`: those targets build
docker stages, which would need a docker daemon inside this build.
The lint phase and the Go stages use Debian rather than Alpine because
`gorm.io/driver/sqlite` pulls in `mattn/go-sqlite3`, which needs CGO and does
not compile against musl. Only the final binary is statically linked, which is
what lets it run on the Alpine runtime image.
`script/cibuild` is the CI gate: it runs `script/bootstrap`, then
`script/check`, then builds the image, whose build runs the lint and test phases
and the stylesheet and Markdown checks again. A build that succeeds is a repo
+7 -14
View File
@@ -153,19 +153,15 @@ func TestVersion_IsStableAcrossInvocations(t *testing.T) {
require.Equal(t, first, second)
}
// The build target is the only place composing linker flags. If a
// future edit drops either half, the binary silently reports "dev"
// again (the defect this package exists for) or fails to link on
// Alpine. If it stops passing GO_TAGS, the image's static binary links
// glibc's getaddrinfo and dlopen again.
func TestMakefile_BuildComposesVersionAndExtraFlags(t *testing.T) {
// The build target is the only place that stamps the version. If a
// future edit drops the -X flag, the binary silently reports "dev"
// again, the defect this package exists for.
func TestMakefile_BuildStampsTheVersion(t *testing.T) {
t.Parallel()
makefile := read(t, makefilePath)
require.Contains(t, makefile, "-X main.version=$(VERSION)")
require.Contains(t, makefile, "$(GO_LDFLAGS)")
require.Contains(t, makefile, "-tags '$(GO_TAGS)'")
require.Contains(t, makefile, "VERSION ?= $(shell script/version)")
}
@@ -224,8 +220,8 @@ func requireMake(t *testing.T) {
}
}
// Every compile in the image goes through the build target, so the
// static relink cannot replace the flags that carry the stamp.
// The image compiles through the build target, so it carries the
// target's version stamp.
func TestDockerfile_BuildsThroughTheMakeTarget(t *testing.T) {
t.Parallel()
@@ -234,10 +230,7 @@ func TestDockerfile_BuildsThroughTheMakeTarget(t *testing.T) {
require.NotContains(t, dockerfile, "go build",
"a raw go build bypasses the Makefile's -X flag")
require.Contains(t, dockerfile, "ARG VERSION")
require.Contains(t, dockerfile,
`make build VERSION="$VERSION"`+
` GO_TAGS=netgo,osusergo,sqlite_omit_load_extension`+
` GO_LDFLAGS='-extldflags "-static"'`)
require.Contains(t, dockerfile, `RUN make build VERSION="$VERSION"`)
}
func read(t *testing.T, path string) string {