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.
5.6 KiB
lora.vegas
lora.vegas is the website of the Las Vegas Meshtastic and LoRa community: an
MIT-licensed single-page static site, built with Hugo, by
@sneak.
It publishes what the local mesh needs in one linkable place:
- Mesh channel configurations
- Community coordination links (Discord, Signal)
- Meetup information
- Local resources
Getting Started
From a fresh clone, make setup installs every build dependency (git, make, go,
the pinned Hugo, node/npm) and the git pre-commit hook, and make serve starts
the Hugo development server:
git clone git@git.eeqj.de:sneak/lora.vegas.git
cd lora.vegas
make setup
make serve
Then open http://localhost:1313 to preview the site.
To produce the production build, which writes the rendered site to public/:
make test
Before committing, run the full check suite — the production build, the lint build, and the formatting check:
make check
The lint runs inside Docker, so make check needs a working Docker daemon;
there is no host fallback. On a machine that has never built the image, the
first run compiles the pinned Hugo from source, which takes minutes; later runs
reuse that cached layer.
make fmt rewrites the repo's markdown and CSS to the project's prettier
settings; run it if make check fails on formatting.
To contribute to this site, contact sneak@sneak.berlin for git repository access.
Entrypoints
This repository adheres to the
Scripts to Rule Them All
standard: normalized scripts in script/ are the entrypoints for the
development workflow, and the Makefile targets are thin shims that call them. We
provide:
script/bootstrap— install all build dependencies (git, make, go, hugo, node/npm) idempotently. Hugo is pinned to an exact version and installed withgo install, which verifies it againstsum.golang.org; the version is theHUGO_VERSIONconstant at the top of the scriptscript/setup— prepare a fresh clone: runscript/bootstrapand install the git pre-commit hookscript/test— the correctness check: a cleanhugo --minifyproduction buildscript/lint— a clean build that surfaces broken links and path collisions, run inside Docker: it buildsDockerfile.lint, where the lint is a build step, so a successful build is a clean lintscript/fmt— format every markdown and CSS file in the repo with prettier; the exclusions live in.prettierignorewith the reason for eachscript/fmt-check— check that formatting (read-only)script/check— runscript/test,script/lint, thenscript/fmt-check; modifies no tracked filesscript/docker— build the Docker image tagged with the project namescript/cibuild— the CI build:script/lintfirst, for fail-fast feedback, then the main image, which runs the non-lint checksscript/install-precommit— install the git pre-commit hook that runsscript/check
Every lint run for this repo happens inside a container, and only the lint does.
script/lint has no host path and no "already inside a container?" branch, so
what a developer runs and what CI runs are the same build. script/fmt and
script/fmt-check run on the host: a formatting check is not a lint.
That is also why the main Dockerfile runs script/test and script/fmt-check
rather than make check. make check calls script/lint, which is itself a
docker build, so a make check inside an image would attempt a docker build
in a bare Alpine with no docker client and no daemon socket. The lint is not
skipped — script/cibuild runs it first, in its own container, before the main
image build starts.
Build any image through script/cibuild, script/docker or script/lint only.
All three pass a per-invocation CHECK_EPOCH build argument that the
Dockerfiles require, so a check layer can never be served from cache — without
it Docker returns a green it did not earn. A bare docker build fails closed on
the CHECK_EPOCH guard rather than caching its way to a false success.
A convenience make serve target runs hugo server for local preview.
Rationale
The Las Vegas Meshtastic and LoRa community needs one durable, linkable place for its channel configurations and group links. Those details otherwise live inside a Discord or Signal thread, where they scroll away, cannot be linked to from outside, are invisible to anyone who has not already joined, and quietly go stale. A static site at a stable domain is the opposite of that: one URL to hand to a newcomer, and one place to correct when a channel changes.
Design
The site is a single page. All of its content is one Hugo content file,
content/_index.md, rendered by a minimal theme vendored in-repo at
themes/loravega/ — there is no upstream theme dependency and no submodule.
The theme's layouts/_default/baseof.html inlines
themes/loravega/static/css/style.css into a <style> block with Hugo's
readFile, so the whole site ships as a single HTML document with no external
CSS request and no second round trip.
hugo --minify builds the site into public/. Deployment is automatic: on push
to main, the Gitea Actions workflow .gitea/workflows/deploy.yml builds the
site and publishes public/ to Cloudflare Pages.
TODO
The live task list is in TODO.md.
License
MIT. See LICENSE. This covers everything in the repository — the Hugo
configuration, the script/ entrypoints, the vendored themes/loravega/
templates and CSS, and the site content in content/.