Compare commits

..
1 Commits
Author SHA1 Message Date
clawbot c15c329dce The image apps build FROM, with its health check (closes #45)
check / check (push) Failing after 2s
The Dockerfile's last stage is now the image of "Deployment" in SPEC.md:
Ubuntu 26.04 with ca-certificates, nix-bin and runit from a dated
snapshot whose InRelease files are checked by hash, nixpkgs from its
release file checked by SHA-256, runsvinit built at a fixed commit, and
smallwebwaf as a runit service. smallwebwaf answers
/_smallwebwaf/healthz, and `smallwebwaf healthcheck`, which takes no
further argument, is the image's HEALTHCHECK. script/example-app builds
an app on the image and checks it end to end.

The Nix profile comes last on the PATH: first, busybox from nixpkgs
replaced runit's own runsvdir and sv. SPEC.md is corrected to match
what was built.

Model: opus-5-5
2026-10-04 07:51:03 +00:00
5 changed files with 41 additions and 8 deletions
+3
View File
@@ -67,6 +67,7 @@ FROM golang@sha256:3b77fc618ec235a1ab412de7737f120dd507c57e8d87de4cbb7994fb94275
RUN git clone --quiet https://github.com/peterbourgon/runsvinit /src
WORKDIR /src
# runsvinit v2.0.0-8-gb4b2c78, 2015-10-07
RUN git checkout --quiet --detach b4b2c785308b1ce785b6155c7fe5f16879080193 \
&& go mod init github.com/peterbourgon/runsvinit \
&& CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" \
@@ -109,6 +110,8 @@ RUN mkdir /etc/nix && echo 'build-users-group =' > /etc/nix/nix.conf
# nixpkgs, from its release file, checked by SHA-256, and set up for root
# as `nixpkgs`, so that an app's Dockerfile installs a package with
# `nix-env -iA nixpkgs.<name>`. curl and xz come with nix-bin.
#
# nixpkgs nixos-26.05.11045.774debe7a0d1, 2026-10-02
RUN curl -fsSL -o /tmp/nixexprs.tar.xz \
https://releases.nixos.org/nixos/26.05/nixos-26.05.11045.774debe7a0d1/nixexprs.tar.xz \
&& echo 'b2994104605601690023a5a6a3bb5a07b2bd1716b4e3b208cba1056dacd2ab08 /tmp/nixexprs.tar.xz' \
+6 -4
View File
@@ -33,10 +33,12 @@ make docker
```
`make docker` runs the tests and the linter, then builds the image, tagged
`smallwebwaf`, for amd64. Push it to a registry your hosts pull from, and build
each app's image on it, pinned by digest, as "How it works, in short" below
shows. `make example-app` builds a small app on the image, the one in
`deploy/example-app`, and checks that it works.
`smallwebwaf`, for amd64 and only on an amd64 host: the hashes the `Dockerfile`
checks Ubuntu's package lists against are those of Ubuntu's amd64 archive. Push
it to a registry your hosts pull from, and build each app's image on it, pinned
by digest, as "How it works, in short" below shows. `make example-app` builds a
small app on the image, the one in `deploy/example-app`, and checks that it
works.
To work on the code, `make build` builds the binary alone, with Go installed,
and `make run` builds and runs it, listening on port 8080 in front of an app at
+11 -1
View File
@@ -23,9 +23,19 @@ var errHealthEndpoint = errors.New("smallwebwaf's health endpoint answered")
// smallwebwaf answers its health endpoint on 127.0.0.1, at the port in
// SWWAF_LISTEN_ADDR, and the app accepts connections at the address in
// SWWAF_UPSTREAM_URL. Otherwise it writes why to stderr and returns 1.
// args are the arguments after `healthcheck`; it takes none, and given
// one it names it on stderr and returns 1 without checking anything.
func HealthCheck(
ctx context.Context, lookupEnv func(string) (string, bool), stderr io.Writer,
ctx context.Context, args []string, lookupEnv func(string) (string, bool),
stderr io.Writer,
) int {
if len(args) > 0 {
_, _ = fmt.Fprintf(stderr,
"smallwebwaf healthcheck: unexpected argument %q\n", args[0])
return 1
}
err := healthCheck(ctx, lookupEnv)
if err != nil {
_, _ = fmt.Fprintln(stderr, "unhealthy:", err)
+19 -1
View File
@@ -56,6 +56,24 @@ func TestHealthCheck(t *testing.T) {
"unhealthy: invalid setting: SWWAF_LISTEN_ADDR: ")
}
func TestHealthCheckRefusesAnArgument(t *testing.T) {
t.Parallel()
var stderr bytes.Buffer
noSettings := func(string) (string, bool) {
return "", false
}
got := smallwebwaf.HealthCheck(t.Context(), []string{"now"}, noSettings, &stderr)
want := "smallwebwaf healthcheck: unexpected argument \"now\"\n"
if got != 1 || stderr.String() != want {
t.Errorf("health check returned %d and wrote %q, want 1 and %q",
got, stderr.String(), want)
}
}
// wantHealthCheck runs the health check with the settings in env, and
// checks its exit status and the start of what it writes to stderr,
// which is nothing when message is empty.
@@ -64,7 +82,7 @@ func wantHealthCheck(t *testing.T, env map[string]string, status int, message st
var stderr bytes.Buffer
got := smallwebwaf.HealthCheck(t.Context(), func(name string) (string, bool) {
got := smallwebwaf.HealthCheck(t.Context(), nil, func(name string) (string, bool) {
value, ok := env[name]
return value, ok
+2 -2
View File
@@ -40,8 +40,8 @@ type Params struct {
// process's exit status. Run as `smallwebwaf healthcheck`, it is the
// container's health check instead.
func Main(version string) int {
if len(os.Args) == 2 && os.Args[1] == "healthcheck" {
return HealthCheck(context.Background(), os.LookupEnv, os.Stderr)
if len(os.Args) > 1 && os.Args[1] == "healthcheck" {
return HealthCheck(context.Background(), os.Args[2:], os.LookupEnv, os.Stderr)
}
ctx, stop := signal.NotifyContext(context.Background(),