Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f416ed4229 |
+6
-20
@@ -13,21 +13,9 @@
|
|||||||
# `/myapp`, never `**/myapp`, which also matches `cmd/myapp/` and
|
# `/myapp`, never `**/myapp`, which also matches `cmd/myapp/` and
|
||||||
# deletes the package directory from the context.
|
# deletes the package directory from the context.
|
||||||
|
|
||||||
# .git is sent without its config. Without a VERSION build argument the
|
# Excluding .git means `git describe` cannot run in any build stage and
|
||||||
# stage that compiles runs `git describe --tags --always` on .git, which
|
# fails quietly there; pass the version in with --build-arg VERSION.
|
||||||
# does not need .git/config; that file can hold a credential, such as a
|
.git
|
||||||
# password in a remote URL or the token the CI checkout step stores there.
|
|
||||||
# Each submodule keeps a config with the same exposure in its git directory
|
|
||||||
# under .git/modules/, nested again for a submodule's own submodules, or in
|
|
||||||
# its own .git directory when it keeps one.
|
|
||||||
# KNOWN GAP: a submodule whose name has a `config` segment (`config`,
|
|
||||||
# `deploy/config`, `config/lib`) loses its whole git directory, because
|
|
||||||
# `**/.git/modules/**/config` also matches that segment's directory
|
|
||||||
# under .git/modules/. Go's version stamping then fails the build;
|
|
||||||
# nothing leaks. Name such a submodule without that segment:
|
|
||||||
# `git submodule add --name`.
|
|
||||||
**/.git/config
|
|
||||||
**/.git/modules/**/config
|
|
||||||
|
|
||||||
# Agent scratch: one full checkout of the repo per in-flight agent.
|
# Agent scratch: one full checkout of the repo per in-flight agent.
|
||||||
# Anchored because it occurs once where agents run at the repo root.
|
# Anchored because it occurs once where agents run at the repo root.
|
||||||
@@ -51,13 +39,14 @@
|
|||||||
**/[iI][dD]_[rR][sS][aA]
|
**/[iI][dD]_[rR][sS][aA]
|
||||||
**/[iI][dD]_[dD][sS][aA]
|
**/[iI][dD]_[dD][sS][aA]
|
||||||
**/[iI][dD]_[eE][cC][dD][sS][aA]
|
**/[iI][dD]_[eE][cC][dD][sS][aA]
|
||||||
**/[iI][dD]_[eE][cC][dD][sS][aA]_[sS][kK]
|
|
||||||
**/[iI][dD]_[eE][dD]25519
|
**/[iI][dD]_[eE][dD]25519
|
||||||
**/[iI][dD]_[eE][dD]25519_[sS][kK]
|
|
||||||
|
|
||||||
# Dependencies: restored inside the image, never copied in.
|
# Dependencies: restored inside the image, never copied in.
|
||||||
**/node_modules
|
**/node_modules
|
||||||
|
|
||||||
|
# The binary `make build` writes on the host; the image builds its own.
|
||||||
|
/bin
|
||||||
|
|
||||||
# OS metadata.
|
# OS metadata.
|
||||||
**/.DS_Store
|
**/.DS_Store
|
||||||
**/Thumbs.db
|
**/Thumbs.db
|
||||||
@@ -70,6 +59,3 @@
|
|||||||
**/.idea
|
**/.idea
|
||||||
**/.vscode
|
**/.vscode
|
||||||
**/*.sublime-*
|
**/*.sublime-*
|
||||||
|
|
||||||
# The binary `make build` writes on the host; the image builds its own.
|
|
||||||
/bin
|
|
||||||
|
|||||||
+5
-25
@@ -20,31 +20,11 @@ Thumbs.db
|
|||||||
# Node
|
# Node
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
# Secrets. Unanchored like every entry above, so each matches at every
|
# Environment / secrets
|
||||||
# depth. Matching is case-sensitive on Linux, so names use character
|
.env
|
||||||
# ranges rather than a lowercase form that misses `Server.Key`.
|
.env.*
|
||||||
|
*.pem
|
||||||
# Environment files. `*.env` covers bare `.env` and the `prod.env`
|
*.key
|
||||||
# convention. Only the templates `example.env` and `sample.env` are
|
|
||||||
# re-included below. A repository that commits any other template adds
|
|
||||||
# its own negation after these lines, for example `!.env.example`.
|
|
||||||
*.[eE][nN][vV]
|
|
||||||
.[eE][nN][vV].*
|
|
||||||
.[eE][nN][vV][rR][cC]
|
|
||||||
!example.env
|
|
||||||
!sample.env
|
|
||||||
|
|
||||||
# Private keys and the bundles carrying them.
|
|
||||||
*.[pP][eE][mM]
|
|
||||||
*.[kK][eE][yY]
|
|
||||||
*.[pP]12
|
|
||||||
*.[pP][fF][xX]
|
|
||||||
[iI][dD]_[rR][sS][aA]
|
|
||||||
[iI][dD]_[dD][sS][aA]
|
|
||||||
[iI][dD]_[eE][cC][dD][sS][aA]
|
|
||||||
[iI][dD]_[eE][cC][dD][sS][aA]_[sS][kK]
|
|
||||||
[iI][dD]_[eE][dD]25519
|
|
||||||
[iI][dD]_[eE][dD]25519_[sS][kK]
|
|
||||||
|
|
||||||
# Go: the binary `make build` writes, test binaries, profiles and logs
|
# Go: the binary `make build` writes, test binaries, profiles and logs
|
||||||
/bin/
|
/bin/
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ linters:
|
|||||||
disable:
|
disable:
|
||||||
# Genuinely incompatible with project patterns
|
# Genuinely incompatible with project patterns
|
||||||
- exhaustruct # Requires all struct fields
|
- exhaustruct # Requires all struct fields
|
||||||
- exhaustruct_v5 # Requires all struct fields (successor to exhaustruct)
|
|
||||||
- godot # Requires comments to end with periods
|
- godot # Requires comments to end with periods
|
||||||
- wrapcheck # Too verbose for internal packages
|
- wrapcheck # Too verbose for internal packages
|
||||||
- varnamelen # Short names like db, id are idiomatic Go
|
- varnamelen # Short names like db, id are idiomatic Go
|
||||||
|
|||||||
+8
-21
@@ -2,8 +2,8 @@
|
|||||||
# lint` or `script/lint`, which are themselves a docker build and would
|
# lint` or `script/lint`, which are themselves a docker build and would
|
||||||
# recurse into a daemon that does not exist in a build step.
|
# recurse into a daemon that does not exist in a build step.
|
||||||
#
|
#
|
||||||
# golangci/golangci-lint v2.14.0 (built with go1.27.0), 2026-09-24
|
# golangci/golangci-lint v2.12.2 (built with go1.26.2), 2026-05-06
|
||||||
FROM golangci/golangci-lint@sha256:ad862ba6b3798cbe0fd9fd7408d498fd74fbd2623a92406b2fd3898faf0bf98f AS lint
|
FROM golangci/golangci-lint@sha256:5cceeef04e53efe1470638d4b4b4f5ceefd574955ab3941b2d9a68a8c9ad5240 AS lint
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
@@ -32,9 +32,9 @@ COPY . .
|
|||||||
# Go's build cache is kept on a tmpfs, out of the image: nothing uses it
|
# Go's build cache is kept on a tmpfs, out of the image: nothing uses it
|
||||||
# after this step, and writing it into the image takes seconds.
|
# after this step, and writing it into the image takes seconds.
|
||||||
RUN --mount=type=tmpfs,target=/root/.cache/go-build \
|
RUN --mount=type=tmpfs,target=/root/.cache/go-build \
|
||||||
go test -timeout 90s -race -cover ./... || \
|
go test -count=1 -timeout 90s -race -cover ./... || \
|
||||||
{ echo "--- Rerunning with -v for details ---"; \
|
{ echo "--- Rerunning with -v for details ---"; \
|
||||||
go test -timeout 90s -race -v ./...; exit 1; }
|
go test -count=1 -timeout 90s -race -v ./...; exit 1; }
|
||||||
|
|
||||||
# Build stage. Nothing is wanted from the two phases above; the copies
|
# Build stage. Nothing is wanted from the two phases above; the copies
|
||||||
# are what make BuildKit build them first, so the image, which needs this
|
# are what make BuildKit build them first, so the image, which needs this
|
||||||
@@ -46,10 +46,6 @@ FROM golang@sha256:3b77fc618ec235a1ab412de7737f120dd507c57e8d87de4cbb7994fb94275
|
|||||||
COPY --from=lint /src/go.sum /dev/null
|
COPY --from=lint /src/go.sum /dev/null
|
||||||
COPY --from=test /src/go.sum /dev/null
|
COPY --from=test /src/go.sum /dev/null
|
||||||
|
|
||||||
# This image has git. A tar-stream context keeps the sender's file
|
|
||||||
# owners, which git refuses.
|
|
||||||
RUN git config --system --add safe.directory /src
|
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
COPY go.mod go.sum ./
|
COPY go.mod go.sum ./
|
||||||
@@ -57,19 +53,10 @@ RUN go mod download
|
|||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# The VERSION build arg when one is given, otherwise
|
# The version is computed on the host and passed in, because
|
||||||
# `git describe --tags --always` on the .git in the build context. With
|
# .dockerignore excludes .git.
|
||||||
# .git present, a version that is still empty, dev or unknown fails the
|
ARG VERSION=dev
|
||||||
# build: git is missing or could not read the checkout.
|
RUN CGO_ENABLED=0 go build -trimpath \
|
||||||
ARG VERSION
|
|
||||||
RUN VERSION="${VERSION:-$(git describe --tags --always)}"; \
|
|
||||||
if [ -e .git ]; then \
|
|
||||||
case "$VERSION" in ""|dev|unknown) \
|
|
||||||
echo "version is '$VERSION' although .git is present" >&2; \
|
|
||||||
exit 1 ;; \
|
|
||||||
esac; \
|
|
||||||
fi; \
|
|
||||||
CGO_ENABLED=0 go build -trimpath \
|
|
||||||
-ldflags="-s -w -X main.Version=${VERSION}" \
|
-ldflags="-s -w -X main.Version=${VERSION}" \
|
||||||
-o /usr/local/bin/smallwebwaf ./cmd/smallwebwaf
|
-o /usr/local/bin/smallwebwaf ./cmd/smallwebwaf
|
||||||
|
|
||||||
|
|||||||
@@ -202,11 +202,10 @@ refused ones included:
|
|||||||
|
|
||||||
- `time` is when the request arrived, in UTC. `peer_ip` is the TCP peer,
|
- `time` is when the request arrived, in UTC. `peer_ip` is the TCP peer,
|
||||||
normally traefik. `path` and `query` are as the client sent them.
|
normally traefik. `path` and `query` are as the client sent them.
|
||||||
- `country` is the client's country as GeoJS places it. It is empty with neither
|
- `country` is the client's country as GeoJS places it, and empty when it is not
|
||||||
country list set, for a client in `SWWAF_ALLOW_NETS` or `SWWAF_DENY_NETS`, for
|
known: with neither country list set, for a client in `SWWAF_ALLOW_NETS` or
|
||||||
a client on a private, loopback or link-local address, when GeoJS cannot place
|
`SWWAF_DENY_NETS`, for a client on a private, loopback or link-local address,
|
||||||
the client or has not answered in time, and for a request refused because a
|
and when GeoJS cannot place the client or has not answered in time.
|
||||||
ban covers its client, even when the client's country is known.
|
|
||||||
- `status` is what the client was sent, `0` if nothing was; `upstream_status` is
|
- `status` is what the client was sent, `0` if nothing was; `upstream_status` is
|
||||||
what the app answered, and is left out when the app did not answer.
|
what the app answered, and is left out when the app did not answer.
|
||||||
- `request_bytes` and `response_bytes` count body bytes.
|
- `request_bytes` and `response_bytes` count body bytes.
|
||||||
@@ -457,18 +456,17 @@ the metrics, failure behaviour and the build order.
|
|||||||
|
|
||||||
So far `smallwebwaf` looks up only the country, only through GeoJS, and only
|
So far `smallwebwaf` looks up only the country, only through GeoJS, and only
|
||||||
while `SWWAF_DENIED_COUNTRIES` or `SWWAF_EXCLUSIVELY_ALLOWED_COUNTRIES` is set:
|
while `SWWAF_DENIED_COUNTRIES` or `SWWAF_EXCLUSIVELY_ALLOWED_COUNTRIES` is set:
|
||||||
then the address of every new visitor is sent to GeoJS, except a visitor in
|
then the address of every new visitor outside `SWWAF_ALLOW_NETS` and
|
||||||
`SWWAF_ALLOW_NETS` or `SWWAF_DENY_NETS` and one refused because a ban covers its
|
`SWWAF_DENY_NETS` is sent to GeoJS, and with neither set, none is. An IPv6
|
||||||
netblock, and with neither set, none is. An IPv6 visitor is asked about by the
|
visitor is asked about by the first address of its /64. A new visitor waits at
|
||||||
first address of its /64. A new visitor waits at most a second for its answer,
|
most a second for its answer, and without one counts as coming from an unknown
|
||||||
and without one counts as coming from an unknown country until the answer
|
country until the answer arrives. The addresses waiting are asked about
|
||||||
arrives. The addresses waiting are asked about together, up to 200 in one
|
together, up to 200 in one request, one request at a time; at most 10,000
|
||||||
request, one request at a time; at most 10,000 visitors wait, and one more
|
visitors wait, and one more counts as coming from an unknown country until there
|
||||||
counts as coming from an unknown country until there is room. While GeoJS fails,
|
is room. While GeoJS fails, visitors with a kept answer are unaffected and new
|
||||||
visitors with a kept answer are unaffected and new ones count as coming from an
|
ones count as coming from an unknown country. GeoJS is then left alone for a
|
||||||
unknown country. GeoJS is then left alone for a second, twice as long after each
|
second, twice as long after each further failure up to five minutes, and asked
|
||||||
further failure up to five minutes, and asked again by the next request that
|
again by the next request that needs it.
|
||||||
needs it.
|
|
||||||
|
|
||||||
In the full design, `smallwebwaf` looks up the AS number and country of every
|
In the full design, `smallwebwaf` looks up the AS number and country of every
|
||||||
client, for the request log, the metrics and the ban notes, and for the country
|
client, for the request log, the metrics and the ban notes, and for the country
|
||||||
|
|||||||
+42
-118
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: Repository Policies
|
title: Repository Policies
|
||||||
last_modified: 2026-10-04
|
last_modified: 2026-09-08
|
||||||
---
|
---
|
||||||
|
|
||||||
This document covers repository structure, tooling, and workflow standards. Code
|
This document covers repository structure, tooling, and workflow standards. Code
|
||||||
@@ -104,14 +104,10 @@ style conventions are in separate documents:
|
|||||||
`lint` phase and a `test` phase, with the final stage depending on both so the
|
`lint` phase and a `test` phase, with the final stage depending on both so the
|
||||||
image cannot be built unless they pass. For non-server repos the final stage
|
image cannot be built unless they pass. For non-server repos the final stage
|
||||||
brings up a development environment; for server repos it is the runtime image.
|
brings up a development environment; for server repos it is the runtime image.
|
||||||
The gate phases and the build stage start from their pinned base images and
|
Dockerfiles install development prerequisites by running `script/bootstrap`
|
||||||
install what those images lack either inline, as the canonical Go `Dockerfile`
|
rather than duplicating installs inline; COPY `script/` and the dependency
|
||||||
below does for `git`, or by running `script/bootstrap`, as the `prompts`
|
manifests (`package.json` + `yarn.lock`, `go.mod` + `go.sum`, etc.) before
|
||||||
repo's own `Dockerfile` does for its yarn packages. The development
|
running it.
|
||||||
environment stage installs development prerequisites by running
|
|
||||||
`script/bootstrap` rather than duplicating its installs inline. A stage that
|
|
||||||
runs `script/bootstrap` COPYs `script/` and the dependency manifests
|
|
||||||
(`package.json` + `yarn.lock`, `go.mod` + `go.sum`, etc.) before running it.
|
|
||||||
|
|
||||||
- **Linting and testing run in Docker, as phases of the `Dockerfile`.** There is
|
- **Linting and testing run in Docker, as phases of the `Dockerfile`.** There is
|
||||||
no separate lint file. `script/lint` and `script/test` each build one phase
|
no separate lint file. `script/lint` and `script/test` each build one phase
|
||||||
@@ -160,14 +156,11 @@ style conventions are in separate documents:
|
|||||||
not evidence that anything ran: a sub-second build reporting success is a
|
not evidence that anything ran: a sub-second build reporting success is a
|
||||||
cache hit, not a result. Never invalidate by pruning — `docker builder prune`
|
cache hit, not a result. Never invalidate by pruning — `docker builder prune`
|
||||||
and friends destroy a build cache shared with every other build on the host.
|
and friends destroy a build cache shared with every other build on the host.
|
||||||
When a check is added or changed, prove it works by planting a defect it must
|
|
||||||
catch and watching the run fail on it, then revert the defect. A green run
|
|
||||||
alone shows neither that the check ran nor that it covers what it should.
|
|
||||||
|
|
||||||
- **The gate phases are separate stages, and the build stage depends on both.**
|
- **The gate phases are separate stages, and the build stage depends on both.**
|
||||||
The lint phase is based on the `golangci/golangci-lint` image (pinned by
|
The lint phase is based on the `golangci/golangci-lint` image (pinned by
|
||||||
hash), so lint failures surface in seconds rather than after a full compile,
|
hash), so lint failures surface in seconds rather than after a full compile,
|
||||||
and the test phase is based on the Debian Go image. The canonical Go repo
|
and the test phase is based on the Go image. The canonical Go repo
|
||||||
`Dockerfile`:
|
`Dockerfile`:
|
||||||
|
|
||||||
```dockerfile
|
```dockerfile
|
||||||
@@ -180,9 +173,8 @@ style conventions are in separate documents:
|
|||||||
COPY . .
|
COPY . .
|
||||||
RUN golangci-lint run --config .golangci.yml ./...
|
RUN golangci-lint run --config .golangci.yml ./...
|
||||||
|
|
||||||
# Test phase. -race needs cgo and so a C compiler, which the Debian Go
|
# Test phase
|
||||||
# image ships and the alpine one does not.
|
# golang:1.x-alpine, YYYY-MM-DD
|
||||||
# golang:1.x, YYYY-MM-DD
|
|
||||||
FROM golang@sha256:... AS test
|
FROM golang@sha256:... AS test
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY go.mod go.sum ./
|
COPY go.mod go.sum ./
|
||||||
@@ -199,27 +191,13 @@ style conventions are in separate documents:
|
|||||||
FROM golang@sha256:... AS builder
|
FROM golang@sha256:... AS builder
|
||||||
COPY --from=lint /src/go.sum /dev/null
|
COPY --from=lint /src/go.sum /dev/null
|
||||||
COPY --from=test /src/go.sum /dev/null
|
COPY --from=test /src/go.sum /dev/null
|
||||||
RUN apk add --no-cache git
|
|
||||||
# A tar-stream context keeps the sender's file owners, which git refuses.
|
|
||||||
RUN git config --system --add safe.directory /src
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY go.mod go.sum ./
|
COPY go.mod go.sum ./
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# The VERSION build arg when one is given, otherwise
|
ARG VERSION=dev
|
||||||
# `git describe --tags --always` on the .git in the build context. With
|
RUN CGO_ENABLED=0 go build -trimpath \
|
||||||
# .git present, a version that is still empty, dev or unknown fails the
|
|
||||||
# build: git is missing or could not read the checkout.
|
|
||||||
ARG VERSION
|
|
||||||
RUN VERSION="${VERSION:-$(git describe --tags --always)}"; \
|
|
||||||
if [ -e .git ]; then \
|
|
||||||
case "$VERSION" in ""|dev|unknown) \
|
|
||||||
echo "version is '$VERSION' although .git is present" >&2; \
|
|
||||||
exit 1 ;; \
|
|
||||||
esac; \
|
|
||||||
fi; \
|
|
||||||
CGO_ENABLED=0 go build -trimpath \
|
|
||||||
-ldflags="-s -w -X main.Version=${VERSION}" \
|
-ldflags="-s -w -X main.Version=${VERSION}" \
|
||||||
-o /app ./cmd/app/
|
-o /app ./cmd/app/
|
||||||
|
|
||||||
@@ -243,41 +221,10 @@ style conventions are in separate documents:
|
|||||||
(e.g. a web frontend compiled in a separate stage), the lint phase must
|
(e.g. a web frontend compiled in a separate stage), the lint phase must
|
||||||
create placeholder files so the embed directives resolve. Example:
|
create placeholder files so the embed directives resolve. Example:
|
||||||
`RUN mkdir -p web/dist && touch web/dist/index.html web/dist/style.css`.
|
`RUN mkdir -p web/dist && touch web/dist/index.html web/dist/style.css`.
|
||||||
- If the project requires CGO or system libraries for linting, install them
|
- If the project requires CGO or system libraries for linting (e.g.
|
||||||
in the lint phase. The `golangci/golangci-lint` image is Debian-based and
|
`vips-dev`), install them in the lint phase with `apk add`.
|
||||||
has no `apk`, so install with `apt-get` under the Debian package name
|
- `ARG VERSION=dev` is declared in the stage that compiles and supplied by
|
||||||
(`libvips-dev`, where alpine says `vips-dev`), and delete the package
|
`script/docker` and `script/cibuild`; no stage may call `git describe`.
|
||||||
lists in the same `RUN`, so the layer does not keep them:
|
|
||||||
|
|
||||||
```dockerfile
|
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get install -y --no-install-recommends libvips-dev \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
```
|
|
||||||
|
|
||||||
- `.dockerignore` lets `.git` into the build context. It keeps out every git
|
|
||||||
`config` at any depth (`**/.git/config`, `**/.git/modules/**/config`): the
|
|
||||||
repository's own, each submodule's under `.git/modules/`, and that of a
|
|
||||||
submodule keeping its own `.git` directory. `git describe` does not need
|
|
||||||
them, and each can hold a credential: a password in a remote URL, or the
|
|
||||||
token the CI checkout step stores there. A submodule whose name has a
|
|
||||||
`config` segment (`config`, `deploy/config`, `config/lib`) loses its whole
|
|
||||||
git directory to `**/.git/modules/**/config`, and Go's version stamping
|
|
||||||
then fails the build: give it a name without that segment
|
|
||||||
(`git submodule add --name`). The stage that compiles has `git` (the
|
|
||||||
Debian Go image has it; an alpine one needs `apk add --no-cache git`) and
|
|
||||||
takes the version from the `VERSION` build argument when one is given,
|
|
||||||
otherwise from `git describe --tags --always`. That gives the tag on a
|
|
||||||
tagged commit; on a later commit, the tag, the number of commits since it
|
|
||||||
and the short commit (`v1.2.3-4-gabc1234`); and the short commit when no
|
|
||||||
tag is reachable. The stage that compiles also marks its working directory
|
|
||||||
safe for git (`git config --system --add safe.directory /src`): a context
|
|
||||||
sent as a tar stream keeps the sender's file owners, and git refuses a
|
|
||||||
checkout owned by another user, so the version would come out empty.
|
|
||||||
`ARG VERSION` has no default, and the build fails if the context carries
|
|
||||||
`.git` and the version still comes out empty, `dev` or `unknown`. A plain
|
|
||||||
`docker build .` with no build arguments must succeed; a Dockerfile that
|
|
||||||
refuses an empty build argument drops that refusal and keeps the argument.
|
|
||||||
|
|
||||||
- Every repo should have a Gitea Actions workflow (`.gitea/workflows/`) that
|
- Every repo should have a Gitea Actions workflow (`.gitea/workflows/`) that
|
||||||
runs `script/cibuild` on push, and checks out the repo as its only other step.
|
runs `script/cibuild` on push, and checks out the repo as its only other step.
|
||||||
@@ -286,12 +233,7 @@ style conventions are in separate documents:
|
|||||||
carry the same guarantee, because its gate phases may come from the cache. The
|
carry the same guarantee, because its gate phases may come from the cache. The
|
||||||
image build is uncached and so runs the gate phases a second time. That is the
|
image build is uncached and so runs the gate phases a second time. That is the
|
||||||
price of the rule above, and it is worth paying: the image that ships is built
|
price of the rule above, and it is worth paying: the image that ships is built
|
||||||
from a run of its own gates rather than from a cache entry. A separate
|
from a run of its own gates rather than from a cache entry.
|
||||||
workflow limited to `main` by a `branches` list under `on: push` cannot be
|
|
||||||
checked by review: to try a change to it, add the feature branch to that list
|
|
||||||
and push, then remove the branch from the list again before merging. Keep any
|
|
||||||
job in it that publishes behind `if: github.ref_name == 'main'`, so the run
|
|
||||||
from the feature branch publishes nothing.
|
|
||||||
|
|
||||||
- Use platform-standard formatters: `black` for Python, `prettier` for
|
- Use platform-standard formatters: `black` for Python, `prettier` for
|
||||||
JS/CSS/Markdown/HTML, `go fmt` for Go. Always use default configuration with
|
JS/CSS/Markdown/HTML, `go fmt` for Go. Always use default configuration with
|
||||||
@@ -344,19 +286,17 @@ style conventions are in separate documents:
|
|||||||
```
|
```
|
||||||
|
|
||||||
`-count=1` is required on both invocations: it defeats Go's test _result_
|
`-count=1` is required on both invocations: it defeats Go's test _result_
|
||||||
cache, so neither run can report a stored pass in place of running the
|
cache, so the target cannot report a pass it did not earn, and the rerun
|
||||||
tests. It leaves the build cache alone, so it costs the runtime of the suite
|
reproduces a failure instead of replaying it. It leaves the build cache
|
||||||
and no recompilation.
|
alone, so it costs the runtime of the suite and no recompilation.
|
||||||
|
|
||||||
That cache is Go's own, separate from Docker's layer cache. Go stores a
|
Note that this is a second, independent cache, stacked below the Docker
|
||||||
passing result in its cache directory (`GOCACHE`), and when the same tests
|
layer cache that [issue #26](https://git.eeqj.de/sneak/prompts/issues/26)
|
||||||
run again on unchanged code it prints that result, marked `(cached)`,
|
addresses. `CHECK_EPOCH` guarantees the `RUN make test` _step_ re-executes;
|
||||||
without running them. That matters on a developer's machine, where this
|
it does not guarantee `go test` inside that step does any work, because the
|
||||||
target runs and the directory lasts from one run to the next. The `test`
|
`GOCACHE` baked into earlier image layers survives into the re-executed
|
||||||
phase of the `Dockerfile` needs no `-count=1`: its base image holds no
|
step. They are two separate defects requiring two separate fixes, and a fix
|
||||||
result for this repo's tests and nothing before its `go test` step runs a
|
for one must not be recorded as covering the other.
|
||||||
test, so there is nothing to replay. `--no-cache` (above) is what makes that
|
|
||||||
step run on an unchanged tree.
|
|
||||||
|
|
||||||
Python example:
|
Python example:
|
||||||
|
|
||||||
@@ -400,7 +340,7 @@ style conventions are in separate documents:
|
|||||||
— which is more dangerous than a short file with no secret patterns at all,
|
— which is more dangerous than a short file with no secret patterns at all,
|
||||||
because it reads as solved and stops anyone looking. Give every
|
because it reads as solved and stops anyone looking. Give every
|
||||||
depth-independent pattern the `**/` prefix and leave only genuinely
|
depth-independent pattern the `**/` prefix and leave only genuinely
|
||||||
root-anchored entries unprefixed: `.claude`, and the repo's own host-built
|
root-anchored entries unprefixed: `.git`, and the repo's own host-built
|
||||||
binary, written `/myapp` and never `**/myapp`, which would also match
|
binary, written `/myapp` and never `**/myapp`, which would also match
|
||||||
`cmd/myapp/` and delete the package directory from the context. Matching is
|
`cmd/myapp/` and delete the package directory from the context. Matching is
|
||||||
case-sensitive, and an ALL-CAPS twin per pattern still misses `Server.Key`, so
|
case-sensitive, and an ALL-CAPS twin per pattern still misses `Server.Key`, so
|
||||||
@@ -425,13 +365,12 @@ style conventions are in separate documents:
|
|||||||
directory, so a repo running agents in subdirectories still ships
|
directory, so a repo running agents in subdirectories still ships
|
||||||
`services/api/.claude/` and must add its own anchored entry there.
|
`services/api/.claude/` and must add its own anchored entry there.
|
||||||
|
|
||||||
- **A plain `docker build .` of a clone stamps the version that
|
- **Excluding `.git` means `git describe` cannot run inside any build stage, and
|
||||||
`git describe --tags --always` gives**, derived from the `.git` in the build
|
it fails quietly there.** In a build stage there is no repository, so
|
||||||
context as the canonical `Dockerfile` above shows. Without its failure check,
|
`git describe` writes nothing to stdout, `-X main.Version=` comes out empty,
|
||||||
a missing `git` or an unreadable checkout would leave `-X main.Version=` empty
|
the binary reports no version at all, and the build still exits 0. Compute the
|
||||||
and the build would still exit 0. `script/docker` and `script/cibuild` pass
|
version on the host and thread it in as a build arg. `script/docker` and
|
||||||
the version they compute on the host; it takes precedence. They do this
|
`script/cibuild` do this, byte-identically across repos:
|
||||||
byte-identically across repos:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Own line: a failing command substitution inside an argument does not
|
# Own line: a failing command substitution inside an argument does not
|
||||||
@@ -448,7 +387,7 @@ style conventions are in separate documents:
|
|||||||
fallback is applied — a live check that fires on a build from an export with
|
fallback is applied — a live check that fires on a build from an export with
|
||||||
no `.git` and on a repository with no commits yet. Do not fold it into the
|
no `.git` and on a repository with no commits yet. Do not fold it into the
|
||||||
substitution as `|| echo unknown`, which makes the guard unreachable. The
|
substitution as `|| echo unknown`, which makes the guard unreachable. The
|
||||||
Dockerfile's side is `ARG VERSION` in the stage that compiles, declared
|
Dockerfile's side is `ARG VERSION=dev` in the stage that compiles, declared
|
||||||
there because `ARG` is stage-scoped; passing `VERSION` to a repo whose
|
there because `ARG` is stage-scoped; passing `VERSION` to a repo whose
|
||||||
Dockerfile declares no such `ARG` is ignored and costs nothing, which is why
|
Dockerfile declares no such `ARG` is ignored and costs nothing, which is why
|
||||||
the scripts stay byte-identical. One consequence for CI: the standard
|
the scripts stay byte-identical. One consequence for CI: the standard
|
||||||
@@ -487,18 +426,12 @@ style conventions are in separate documents:
|
|||||||
`test-support` depguard rule, where a repo names its own test-support packages
|
`test-support` depguard rule, where a repo names its own test-support packages
|
||||||
by full import path. A repo adds entries there and changes nothing else, and a
|
by full import path. A repo adds entries there and changes nothing else, and a
|
||||||
re-vendor carries its entries forward. The canonical golangci-lint version is
|
re-vendor carries its entries forward. The canonical golangci-lint version is
|
||||||
v2.14.0 (released 2026-09-24), pinned as the digest of the lint phase's base
|
v2.12.2 (released 2026-05-06), pinned as the digest of the lint phase's base
|
||||||
image
|
image
|
||||||
(`golangci/golangci-lint@sha256:ad862ba6b3798cbe0fd9fd7408d498fd74fbd2623a92406b2fd3898faf0bf98f`,
|
(`golangci/golangci-lint@sha256:5cceeef04e53efe1470638d4b4b4f5ceefd574955ab3941b2d9a68a8c9ad5240`,
|
||||||
which reports `2.14.0 built with go1.27.0 from 114493f9`). A module's `go`
|
which reports `2.12.2 built with go1.26.2 from c0d3ddc9`). That digest is the
|
||||||
directive must not name a newer Go minor version than the one golangci-lint
|
only pin, since no repo installs golangci-lint on the host: bumping the
|
||||||
was built with, or golangci-lint refuses to lint it: this release lints
|
version means changing it and nothing else.
|
||||||
`go 1.27.1` but not `go 1.28`. That digest is the only pin, since no repo
|
|
||||||
installs golangci-lint on the host. A repo sets the lint phase digest to the
|
|
||||||
one named here and re-vendors `.golangci.yml` in the same commit, whichever of
|
|
||||||
the two prompted the change: the canonical copy can name linters that an older
|
|
||||||
golangci-lint rejects, and a newer golangci-lint can add linters that
|
|
||||||
`default: all` switches on until the canonical copy disables them.
|
|
||||||
|
|
||||||
- **`script/bootstrap` installs a pinned tool by comparing versions, never by
|
- **`script/bootstrap` installs a pinned tool by comparing versions, never by
|
||||||
testing presence.** An `if ! command -v <tool>; then install; fi` guard tests
|
testing presence.** An `if ! command -v <tool>; then install; fi` guard tests
|
||||||
@@ -522,11 +455,6 @@ style conventions are in separate documents:
|
|||||||
|
|
||||||
Keep it POSIX sh: no arrays, no `[[`, no `grep -P`.
|
Keep it POSIX sh: no arrays, no `[[`, no `grep -P`.
|
||||||
|
|
||||||
A Go tool a repo needs on the host is installed with `go install` pinned to
|
|
||||||
a commit hash (`go install <package>@<commit hash>`). It is never tracked as
|
|
||||||
a `go.mod` tool dependency or through a `tools.go` file, either of which
|
|
||||||
pulls the tool's own dependencies into the repo's `go.mod` and `go.sum`.
|
|
||||||
|
|
||||||
- When pinning images or packages by hash, add a comment above the reference
|
- When pinning images or packages by hash, add a comment above the reference
|
||||||
with the version and date (YYYY-MM-DD).
|
with the version and date (YYYY-MM-DD).
|
||||||
|
|
||||||
@@ -639,10 +567,10 @@ style conventions are in separate documents:
|
|||||||
settings.
|
settings.
|
||||||
|
|
||||||
- Avoid putting files in the repo root unless necessary. Root should contain
|
- Avoid putting files in the repo root unless necessary. Root should contain
|
||||||
only project-level config files (`README.md`, `AGENTS.md`, `Makefile`,
|
only project-level config files (`README.md`, `Makefile`, `Dockerfile`,
|
||||||
`Dockerfile`, `LICENSE`, `.gitignore`, `.editorconfig`, `REPO_POLICIES.md`,
|
`LICENSE`, `.gitignore`, `.editorconfig`, `REPO_POLICIES.md`, and
|
||||||
and language-specific config). Everything else goes in a subdirectory.
|
language-specific config). Everything else goes in a subdirectory. Canonical
|
||||||
Canonical subdirectory names:
|
subdirectory names:
|
||||||
- `bin/` — executable scripts and tools
|
- `bin/` — executable scripts and tools
|
||||||
- `cmd/` — Go command entrypoints; thin only: one `main.go` per binary whose
|
- `cmd/` — Go command entrypoints; thin only: one `main.go` per binary whose
|
||||||
body is a single call into `internal/` or `pkg/`, no project logic in
|
body is a single call into `internal/` or `pkg/`, no project logic in
|
||||||
@@ -673,7 +601,3 @@ style conventions are in separate documents:
|
|||||||
- Go: `go.mod`, `go.sum`, `.golangci.yml`
|
- Go: `go.mod`, `go.sum`, `.golangci.yml`
|
||||||
- JS: `package.json`, `yarn.lock`, `.prettierrc`, `.prettierignore`
|
- JS: `package.json`, `yarn.lock`, `.prettierrc`, `.prettierignore`
|
||||||
- Python: `pyproject.toml`
|
- Python: `pyproject.toml`
|
||||||
|
|
||||||
- Guidance for coding agents lives in one `AGENTS.md` at the repository root. It
|
|
||||||
is never committed under a file or directory named after one agent tool, such
|
|
||||||
as `CLAUDE.md` or `.claude/`, and never split into separate memory files.
|
|
||||||
|
|||||||
@@ -945,9 +945,9 @@ and the running `smallwebwaf` takes the edit in.
|
|||||||
- what was broken: the rule ids and target that matched, or the limit, its
|
- what was broken: the rule ids and target that matched, or the limit, its
|
||||||
window, the count reached and the client's limit percentage with what set
|
window, the count reached and the client's limit percentage with what set
|
||||||
it; and any reputation sources that listed the client;
|
it; and any reputation sources that listed the client;
|
||||||
- the request that caused the ban, the one that broke the limit or carried
|
- the requests that caused the ban, up to the last ten: time, method, host,
|
||||||
the clear sign of attack: time, method, host, path with its query string,
|
path with its query string, status and user agent, each text cut to 256
|
||||||
status and user agent, each text cut to 256 bytes;
|
bytes;
|
||||||
- how many requests counted toward the ban, and the time span over which
|
- how many requests counted toward the ban, and the time span over which
|
||||||
they came;
|
they came;
|
||||||
- the netblock's total requests since it was first seen, and the requests
|
- the netblock's total requests since it was first seen, and the requests
|
||||||
@@ -958,13 +958,13 @@ and the running `smallwebwaf` takes the edit in.
|
|||||||
the table is full, so on a public service the file grows to the default
|
the table is full, so on a public service the file grows to the default
|
||||||
`SWWAF_MAX_TRACKED_CLIENTS` of 20,000, about 20 MiB. Written every 15
|
`SWWAF_MAX_TRACKED_CLIENTS` of 20,000, about 20 MiB. Written every 15
|
||||||
minutes, that is under 2 GiB of disk writes a day.
|
minutes, that is under 2 GiB of disk writes a day.
|
||||||
- `bans.json` takes about 1.2 KiB per ban and at most about 2.5 KiB, since
|
- `bans.json` takes about 2 KiB per ban and at most about 8 KiB, since the
|
||||||
the notes hold one request and their texts are cut short. At the default
|
texts in the notes are cut short. At the default `SWWAF_MAX_BANS` of 5,000
|
||||||
`SWWAF_MAX_BANS` of 5,000 it is about 6 MiB, and never more than about 12
|
it is about 10 MiB, and never more than about 40 MiB, plus whatever bans
|
||||||
MiB, plus whatever bans an admin made. It is written when a ban is made,
|
an admin made. It is written when a ban is made, lifted or made permanent,
|
||||||
lifted or made permanent, at most once every 10 seconds, and otherwise
|
at most once every 10 seconds, and otherwise with the 15-minute write, so
|
||||||
with the 15-minute write, so its writes follow the bans made: with a full
|
its writes follow the bans made: with a full file, a hundred new bans a
|
||||||
file, a hundred new bans a day come to about 600 MiB of disk writes.
|
day come to about 1 GiB of disk writes.
|
||||||
- `lookups.json` takes about 150 bytes per answer, about 15 MiB when full.
|
- `lookups.json` takes about 150 bytes per answer, about 15 MiB when full.
|
||||||
Written every 15 minutes, that is under 1.5 GiB of disk writes a day.
|
Written every 15 minutes, that is under 1.5 GiB of disk writes a day.
|
||||||
- `reputation.json` and `alerts.json` are usually a few MiB or less.
|
- `reputation.json` and `alerts.json` are usually a few MiB or less.
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ func requestWithHeaders(
|
|||||||
ForwardedFor: r.Header.Get(forwardedFor),
|
ForwardedFor: r.Header.Get(forwardedFor),
|
||||||
ForwardedHost: r.Header.Get("X-Forwarded-Host"),
|
ForwardedHost: r.Header.Get("X-Forwarded-Host"),
|
||||||
ForwardedProto: r.Header.Get("X-Forwarded-Proto"),
|
ForwardedProto: r.Header.Get("X-Forwarded-Proto"),
|
||||||
RealIP: r.Header.Get("X-Real-IP"),
|
RealIP: r.Header.Get("X-Real-Ip"),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
addr, out := startProxy(t, app.URL, env)
|
addr, out := startProxy(t, app.URL, env)
|
||||||
|
|||||||
+3
-2
@@ -16,8 +16,9 @@ main() {
|
|||||||
"$SCRIPT_DIR/check"
|
"$SCRIPT_DIR/check"
|
||||||
# Own line: a failing command substitution inside an argument does
|
# Own line: a failing command substitution inside an argument does
|
||||||
# not trip `set -e`, so the inline form degrades silently to an
|
# not trip `set -e`, so the inline form degrades silently to an
|
||||||
# empty constant. The VERSION build argument takes precedence over
|
# empty constant. VERSION is computed here because .dockerignore
|
||||||
# the version a build stage derives from the .git in the context.
|
# excludes .git, so `git describe` in a build stage yields an empty
|
||||||
|
# version without failing.
|
||||||
version="$(git describe --tags --always --dirty 2>/dev/null || true)"
|
version="$(git describe --tags --always --dirty 2>/dev/null || true)"
|
||||||
[ -n "$version" ] || version="unknown"
|
[ -n "$version" ] || version="unknown"
|
||||||
docker build --no-cache \
|
docker build --no-cache \
|
||||||
|
|||||||
+3
-2
@@ -12,8 +12,9 @@ main() {
|
|||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
# Own line: a failing command substitution inside an argument does
|
# Own line: a failing command substitution inside an argument does
|
||||||
# not trip `set -e`, so the inline form degrades silently to an
|
# not trip `set -e`, so the inline form degrades silently to an
|
||||||
# empty constant. The VERSION build argument takes precedence over
|
# empty constant. VERSION is computed here because .dockerignore
|
||||||
# the version a build stage derives from the .git in the context.
|
# excludes .git, so `git describe` in a build stage yields an empty
|
||||||
|
# version without failing.
|
||||||
version="$(git describe --tags --always --dirty 2>/dev/null || true)"
|
version="$(git describe --tags --always --dirty 2>/dev/null || true)"
|
||||||
[ -n "$version" ] || version="unknown"
|
[ -n "$version" ] || version="unknown"
|
||||||
docker build --no-cache \
|
docker build --no-cache \
|
||||||
|
|||||||
Reference in New Issue
Block a user