Run the lint inside Docker via Dockerfile.lint (closes #38)
All checks were successful
check / check (push) Successful in 1m23s
All checks were successful
check / check (push) Successful in 1m23s
Add a root Dockerfile.lint that runs `hugo --minify --printPathWarnings`
as a build step, so a successful build IS a clean lint, and reduce
script/lint to building that file. There is no host lint path and
deliberately no "am I already inside a container?" branch, which would
be a host lint path in disguise.
The containerisation boundary is lint only, per the owner ruling on the
issue: formatting is not a lint, so script/fmt and script/fmt-check stay
on the host, unchanged in version, scope and flags. That also removes
the forced duplication of prettier's settings between a script and a
Dockerfile, and with it the keep-in-sync notes that duplication needed.
Dockerfile.lint has exactly one stage on purpose. A whole-file
`docker build -f Dockerfile.lint .` builds only the file's last stage,
and sibling stages off a shared base carry no ordering edge, so a second
stage beside the lint would be silently skipped by exactly the
invocation the canonical org-wide script/lint uses -- a green that
linted nothing, which the per-stage CHECK_EPOCH guard cannot catch
because the stage that did run satisfies it. With one stage there is
nothing to skip and script/lint needs no --target. A comment in the file
says that any second check added here must be chained or carry an
explicit ordering edge, never left as a sibling.
Its first four instructions are byte-identical to the main Dockerfile's
and in the same order, so the expensive `RUN script/bootstrap` layer
that compiles the pinned Hugo from source is shared between the two
images rather than paid twice.
Resolve the recursion by direction, not detection. `make check` calls
script/lint, and script/lint is now a `docker build`, so `RUN make
check` in an image would attempt a docker build inside a build step
where there is no daemon. The main Dockerfile therefore runs the
individual non-lint checks -- script/test and script/fmt-check, as
separate RUN lines under the CHECK_EPOCH guard -- matching the canonical
shape, and only the lint is absent from it. script/cibuild runs
script/lint first, for fail-fast feedback: on a runner with no cached
bootstrap layer a lint failure should not wait behind a Hugo build from
source. CI coverage is therefore unchanged, and it runs the same scripts
a developer runs.
Caching is waived for the lint in the shape this repo already settled:
ARG CHECK_EPOCH with no default, guarded with
`[ -n "$CHECK_EPOCH" ] || exit 1`, and the value expanded into the
linted command as well as the guard, so invalidation never rests on
BuildKit's treatment of an unreferenced ARG. Every image-building
entrypoint generates and passes it -- script/cibuild, script/docker,
script/lint -- each as a whole assignment rather than inline, for the
`set -e` reason script/cibuild documents.
script/lint builds with `--output type=cacheonly`: the build is run for
its exit status, not for an image, and because the lint layer is
cache-busted on every invocation an exporting build leaves one dangling
image per lint run. On a host shared with other work that accumulates.
The build cache is unaffected, so script/bootstrap still hits, and
failures still propagate.
Two divergences from REPO_POLICIES.md, stated rather than buried:
- REPO_POLICIES.md:92, "all Dockerfiles must run `make check`". That
rule and "every lint run happens in Docker" cannot both hold once
`make check` contains the lint.
- REPO_POLICIES.md:102-168, which requires a separate lint stage whose
result the build stage depends on through
`COPY --from=lint /src/go.sum /dev/null`, on the stated grounds that
without the edge "the build stage would not wait for lint to finish
and a lint failure might not fail the overall build". No such edge
exists here: the lint is its own file and its own build, sequenced
by script/cibuild rather than by BuildKit. Both sections are
superseded upstream by 12e8db8 in sneak/prompts, which deletes the
Go multistage lint stage and its ordering trick for the same reason
-- that stage ran `make lint`, which is now a docker build.
Verified: two consecutive script/lint runs on an unchanged tree both
executed hugo for real, distinct epochs echoed, script/bootstrap CACHED,
second run 0.85s; a whole-file `docker build -f Dockerfile.lint .` with
the argument and no --target ran the lint for real; a bare build with no
argument failed closed on the guard; a planted template error failed the
lint with hugo's own render error and made script/cibuild exit non-zero
in 0.6s with the main image build never starting; a planted over-long
line failed the host script/fmt-check; both reverted and re-run clean;
`make check`, script/docker and script/cibuild all green with every
check layer observed executing rather than served from cache, and the
bootstrap layer CACHED in both images. The deploy path is byte-identical
to main: .gitea/, script/bootstrap, script/test and .dockerignore are
untouched.
This commit is contained in:
108
TODO.md
108
TODO.md
@@ -20,17 +20,71 @@ wrangler CLI install, an exact version), and the Hugo that builds the published
|
||||
site is a deliberate pinned version rather than whatever the base image's
|
||||
package repo serves. The site now ships a Cloudflare Pages `_headers` file, so
|
||||
its response security headers are declared in the repo instead of being whatever
|
||||
the edge defaults to — unverified in production until the next deploy.
|
||||
the edge defaults to — unverified in production until the next deploy. The lint
|
||||
now runs inside a container and nowhere else: `script/lint` is a build of
|
||||
`Dockerfile.lint`, with no host path to fall back to. Formatting is not a lint
|
||||
and stays on the host.
|
||||
|
||||
# Next Step
|
||||
|
||||
Move the artifact actions in `.gitea/workflows/deploy.yml` to v4 once this Gitea
|
||||
Actions instance serves the v4 artifact protocol; they are pinned on the
|
||||
deprecated v3 line because v4 fails here (#20). This touches the live deploy
|
||||
path, so it needs a real workflow run to verify rather than a local check.
|
||||
Add the missing `cibuild` and `precommit` shims to the `Makefile`, so that every
|
||||
documented entrypoint has a make target and the documented "always use make
|
||||
targets" rule is actually satisfiable
|
||||
(https://git.eeqj.de/sneak/lora.vegas/issues/34). Done when `make cibuild` and
|
||||
`make precommit` exist, are declared `.PHONY`, and the README Entrypoints
|
||||
section matches.
|
||||
|
||||
# Completed Steps
|
||||
|
||||
- 2026-08-10: moved the lint into Docker
|
||||
(https://git.eeqj.de/sneak/lora.vegas/issues/38). A new root `Dockerfile.lint`
|
||||
runs `hugo --minify --printPathWarnings` as a build step, so a successful
|
||||
build is a clean lint, and `script/lint` is nothing but a build of that file —
|
||||
no host path and deliberately no "already inside a container?" branch, which
|
||||
would be a host lint path in disguise. The containerisation boundary is lint
|
||||
only, per the owner ruling of the same day: formatting is not a lint, so
|
||||
`script/fmt` and `script/fmt-check` stay on the host. `Dockerfile.lint` has
|
||||
exactly one stage on purpose. A whole-file `docker build -f Dockerfile.lint .`
|
||||
builds only the file's last stage, and sibling stages off a shared base have
|
||||
no ordering edge, so any second stage beside the lint would be silently
|
||||
skipped by the invocation the canonical org-wide `script/lint` uses — a green
|
||||
that linted nothing. With one stage there is nothing to skip and `script/lint`
|
||||
needs no `--target`. Its first four instructions are byte-identical to the
|
||||
main `Dockerfile`'s, so the expensive `RUN script/bootstrap` layer that
|
||||
compiles Hugo from source is shared between the two images rather than paid
|
||||
twice. The recursion this creates was resolved by direction, not detection:
|
||||
`make check` calls `script/lint`, so the main `Dockerfile` can no longer
|
||||
`RUN make check` — that would attempt a docker build inside a build step, in a
|
||||
bare Alpine with no docker client and no daemon socket. It runs the individual
|
||||
non-lint checks instead, `script/test` and `script/fmt-check`, matching the
|
||||
canonical shape upstream, and `script/cibuild` runs `script/lint` first for
|
||||
fail-fast feedback before the main image build starts. So CI still covers all
|
||||
three checks and cannot drift from what a developer runs. Caching is waived
|
||||
for the lint exactly as the main `Dockerfile` already does it:
|
||||
`ARG CHECK_EPOCH` with no default, guarded with
|
||||
`[ -n "$CHECK_EPOCH" ] || exit 1`, and the value expanded into the linted
|
||||
command as well as the guard, so invalidation never rests on BuildKit's
|
||||
handling of an unreferenced `ARG`. Every image-building entrypoint generates
|
||||
and passes it — `script/cibuild`, `script/docker`, `script/lint` — which is
|
||||
the failure mode this repo already hit once, a Dockerfile guard asserting a
|
||||
property one entrypoint did not supply. `script/lint` builds with
|
||||
`--output type=cacheonly`: the build is run for its exit status, not for an
|
||||
image, and since the lint layer is cache-busted every run an exporting build
|
||||
would leave one dangling image per lint on a host shared with other work.
|
||||
Verified rather than assumed: two consecutive `script/lint` runs on an
|
||||
unchanged tree both executed hugo for real (second run 0.85s wall,
|
||||
`RUN script/bootstrap` `CACHED`, distinct epochs echoed, real build tables
|
||||
printed); a whole-file `docker build -f Dockerfile.lint .` with the argument
|
||||
and no `--target` ran the lint for real, which is the regression test for the
|
||||
skipped-sibling hazard; a bare build with no argument failed closed on the
|
||||
guard; a planted template error failed the lint with hugo's own render error
|
||||
and made `script/cibuild` exit in 0.6s without the main image build starting
|
||||
at all; a planted over-long line failed the host `script/fmt-check` with
|
||||
`[warn] README.md`; both violations were reverted and re-run clean. Not
|
||||
changed here, and still true: the lint fails on hugo build errors but not on
|
||||
render-target collisions, which `--printPathWarnings` only prints
|
||||
(https://git.eeqj.de/sneak/lora.vegas/issues/25) — containerising the run
|
||||
neither fixes nor worsens that
|
||||
- 2026-08-10: added the `LICENSE` file and made the README say what it says
|
||||
(closes #10). The repo is public (`private: false` on the Gitea API, verified
|
||||
rather than assumed), so the owner's standing policy — MIT on any public repo
|
||||
@@ -226,8 +280,40 @@ path, so it needs a real workflow run to verify rather than a local check.
|
||||
|
||||
# Future Steps
|
||||
|
||||
Startable work first. Everything under "Blocked" waits on somebody or something
|
||||
outside this repo, so nothing there may be picked up as the Next Step.
|
||||
|
||||
- Make the prettier scope's exclusion of dot-directories explicit instead of
|
||||
leaning on `.gitignore` (https://git.eeqj.de/sneak/lora.vegas/issues/33)
|
||||
- Fix the README's SSH-only clone URL, and add the two entrypoints the
|
||||
Entrypoints section omits, `script/precommit` and `script/projectname`
|
||||
(https://git.eeqj.de/sneak/lora.vegas/issues/36)
|
||||
- Drop the Go toolchain and module cache from the check image's final layer;
|
||||
they are needed to build hugo and dead weight afterwards
|
||||
(https://git.eeqj.de/sneak/lora.vegas/issues/28)
|
||||
- Add a timeout guard to `script/test` and `script/lint` so a wedged build fails
|
||||
instead of hanging (https://git.eeqj.de/sneak/lora.vegas/issues/16)
|
||||
- Sync the reformat of `REPO_POLICIES.md` back upstream to `prompts` so the
|
||||
canonical copy is clean under the shared prettier settings and future syncs
|
||||
are a straight byte copy
|
||||
- Keep mesh channel and signal group listings current
|
||||
|
||||
## Blocked
|
||||
|
||||
- Decide whether `script/lint` should fail on render-target collisions rather
|
||||
than only print them; `--printPathWarnings` exits 0 today, so the signal is
|
||||
reported and not enforced. Owner call, since it changes what the gate rejects
|
||||
(https://git.eeqj.de/sneak/lora.vegas/issues/25)
|
||||
- Move the artifact actions in `.gitea/workflows/deploy.yml` to v4 once this
|
||||
Gitea Actions instance serves the v4 artifact protocol; they are pinned on the
|
||||
deprecated v3 line because v4 fails here
|
||||
(https://git.eeqj.de/sneak/lora.vegas/issues/20). This touches the live deploy
|
||||
path, so it needs a real workflow run to verify rather than a local check
|
||||
- Move the deploy container to a pinned node 22 so the wrangler pin can advance
|
||||
past 4.86.0 (#21)
|
||||
past 4.86.0 (https://git.eeqj.de/sneak/lora.vegas/issues/21)
|
||||
- Delete the stale remote branches `feat/initial-site` and `security-audit`;
|
||||
only the owner can remove them
|
||||
(https://git.eeqj.de/sneak/lora.vegas/issues/15)
|
||||
- After the next deploy, confirm the `_headers` file actually took effect, on
|
||||
both `https://lora.vegas/` and `https://www.lora.vegas/`: `curl -sSI` against
|
||||
each must show `strict-transport-security` or `content-security-policy`.
|
||||
@@ -237,12 +323,10 @@ path, so it needs a real workflow run to verify rather than a local check.
|
||||
`includeSubDomains` rests on `www.lora.vegas` being served by this same Pages
|
||||
project, which was established behaviourally from identical response bodies
|
||||
rather than from the Cloudflare dashboard. If `www` turns out not to be
|
||||
covered, the `includeSubDomains` decision has to be revisited (#14)
|
||||
covered, the `includeSubDomains` decision has to be revisited
|
||||
(https://git.eeqj.de/sneak/lora.vegas/issues/14)
|
||||
- Decide the HSTS `includeSubDomains` and `preload` posture for `lora.vegas`.
|
||||
Both are owner calls: neither can be walked back inside the max-age window,
|
||||
and `includeSubDomains` binds hostnames this repo does not control (#14)
|
||||
- Sync the reformat of `REPO_POLICIES.md` back upstream to `prompts` so the
|
||||
canonical copy is clean under the shared prettier settings and future syncs
|
||||
are a straight byte copy
|
||||
and `includeSubDomains` binds hostnames this repo does not control
|
||||
(https://git.eeqj.de/sneak/lora.vegas/issues/14)
|
||||
- Verify the Cloudflare Pages deploy still works after the workflow changes
|
||||
- Keep mesh channel and signal group listings current
|
||||
|
||||
Reference in New Issue
Block a user