Compare commits

..
1 Commits
Author SHA1 Message Date
sneak 74ba64235c The image apps build FROM, with its health check (closes #45)
check / check (push) Failing after 3s
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` 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:07:24 +00:00
5 changed files with 8 additions and 41 deletions
-3
View File
@@ -67,7 +67,6 @@ FROM golang@sha256:3b77fc618ec235a1ab412de7737f120dd507c57e8d87de4cbb7994fb94275
RUN git clone --quiet https://github.com/peterbourgon/runsvinit /src RUN git clone --quiet https://github.com/peterbourgon/runsvinit /src
WORKDIR /src WORKDIR /src
# runsvinit v2.0.0-8-gb4b2c78, 2015-10-07
RUN git checkout --quiet --detach b4b2c785308b1ce785b6155c7fe5f16879080193 \ RUN git checkout --quiet --detach b4b2c785308b1ce785b6155c7fe5f16879080193 \
&& go mod init github.com/peterbourgon/runsvinit \ && go mod init github.com/peterbourgon/runsvinit \
&& CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" \ && CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" \
@@ -110,8 +109,6 @@ 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 # 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 # as `nixpkgs`, so that an app's Dockerfile installs a package with
# `nix-env -iA nixpkgs.<name>`. curl and xz come with nix-bin. # `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 \ RUN curl -fsSL -o /tmp/nixexprs.tar.xz \
https://releases.nixos.org/nixos/26.05/nixos-26.05.11045.774debe7a0d1/nixexprs.tar.xz \ https://releases.nixos.org/nixos/26.05/nixos-26.05.11045.774debe7a0d1/nixexprs.tar.xz \
&& echo 'b2994104605601690023a5a6a3bb5a07b2bd1716b4e3b208cba1056dacd2ab08 /tmp/nixexprs.tar.xz' \ && echo 'b2994104605601690023a5a6a3bb5a07b2bd1716b4e3b208cba1056dacd2ab08 /tmp/nixexprs.tar.xz' \
+4 -6
View File
@@ -33,12 +33,10 @@ make docker
``` ```
`make docker` runs the tests and the linter, then builds the image, tagged `make docker` runs the tests and the linter, then builds the image, tagged
`smallwebwaf`, for amd64 and only on an amd64 host: the hashes the `Dockerfile` `smallwebwaf`, for amd64. Push it to a registry your hosts pull from, and build
checks Ubuntu's package lists against are those of Ubuntu's amd64 archive. Push each app's image on it, pinned by digest, as "How it works, in short" below
it to a registry your hosts pull from, and build each app's image on it, pinned shows. `make example-app` builds a small app on the image, the one in
by digest, as "How it works, in short" below shows. `make example-app` builds a `deploy/example-app`, and checks that it works.
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, 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 and `make run` builds and runs it, listening on port 8080 in front of an app at
+1 -11
View File
@@ -23,19 +23,9 @@ var errHealthEndpoint = errors.New("smallwebwaf's health endpoint answered")
// smallwebwaf answers its health endpoint on 127.0.0.1, at the port in // 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_LISTEN_ADDR, and the app accepts connections at the address in
// SWWAF_UPSTREAM_URL. Otherwise it writes why to stderr and returns 1. // 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( func HealthCheck(
ctx context.Context, args []string, lookupEnv func(string) (string, bool), ctx context.Context, lookupEnv func(string) (string, bool), stderr io.Writer,
stderr io.Writer,
) int { ) int {
if len(args) > 0 {
_, _ = fmt.Fprintf(stderr,
"smallwebwaf healthcheck: unexpected argument %q\n", args[0])
return 1
}
err := healthCheck(ctx, lookupEnv) err := healthCheck(ctx, lookupEnv)
if err != nil { if err != nil {
_, _ = fmt.Fprintln(stderr, "unhealthy:", err) _, _ = fmt.Fprintln(stderr, "unhealthy:", err)
+1 -19
View File
@@ -56,24 +56,6 @@ func TestHealthCheck(t *testing.T) {
"unhealthy: invalid setting: SWWAF_LISTEN_ADDR: ") "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 // wantHealthCheck runs the health check with the settings in env, and
// checks its exit status and the start of what it writes to stderr, // checks its exit status and the start of what it writes to stderr,
// which is nothing when message is empty. // which is nothing when message is empty.
@@ -82,7 +64,7 @@ func wantHealthCheck(t *testing.T, env map[string]string, status int, message st
var stderr bytes.Buffer var stderr bytes.Buffer
got := smallwebwaf.HealthCheck(t.Context(), nil, func(name string) (string, bool) { got := smallwebwaf.HealthCheck(t.Context(), func(name string) (string, bool) {
value, ok := env[name] value, ok := env[name]
return value, ok 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 // process's exit status. Run as `smallwebwaf healthcheck`, it is the
// container's health check instead. // container's health check instead.
func Main(version string) int { func Main(version string) int {
if len(os.Args) > 1 && os.Args[1] == "healthcheck" { if len(os.Args) == 2 && os.Args[1] == "healthcheck" {
return HealthCheck(context.Background(), os.Args[2:], os.LookupEnv, os.Stderr) return HealthCheck(context.Background(), os.LookupEnv, os.Stderr)
} }
ctx, stop := signal.NotifyContext(context.Background(), ctx, stop := signal.NotifyContext(context.Background(),