Files
lora.vegas/README.md
clawbot f5761b6227
All checks were successful
check / check (push) Successful in 1m9s
Run every lint-class check inside Docker (closes #38)
Add a root Dockerfile.lint that carries the checks as build steps -- a
`lint` stage running `hugo --minify --printPathWarnings` and a
`fmt-check` stage running the prettier check -- and reduce script/lint
and script/fmt-check to building their stage. A successful build is a
clean check. There is no host path and deliberately no "am I already
inside a container?" branch, which would be a host lint path in
disguise.

The two stages share a `base` whose first four instructions are
byte-identical to the main Dockerfile's, so the expensive
`RUN script/bootstrap` layer that compiles the pinned Hugo from source
is a cache hit against the main image instead of a second build of the
same thing.

Resolve the resulting recursion by splitting the checks by where they
run, not with an escape hatch. `make check` runs script/lint, so the
main Dockerfile can no longer `RUN make check`: that would be
docker-in-docker inside a bare Alpine with no docker client and no
daemon socket, and script/cibuild is what CI runs on every push. The
main Dockerfile therefore runs `make test`, the production build, and
script/cibuild builds it and then calls script/lint and
script/fmt-check. CI still covers the production build, lint and the
format check, and it runs exactly what a developer runs.

script/fmt stays on the host because it rewrites the working tree,
which a container build cannot do. That makes it the authoritative
copy of the prettier version, scope and flags that the fmt-check stage
duplicates; both sides carry a keep-in-sync note. The duplication is
forced: any `RUN script/fmt-check` inside the image is the recursion
again.

Caching is waived for the checks in the shape this repo already
settled: `ARG CHECK_EPOCH` with no default, declared and guarded
separately in each stage because ARG does not cross a FROM, with the
value expanded into the checked command as well as the guard so
invalidation does not rest on BuildKit's treatment of an unreferenced
ARG. All four image-building entrypoints now generate and pass it --
script/cibuild, script/docker, script/lint, script/fmt-check.

Verified: two consecutive script/lint runs on an unchanged tree both
executed hugo for real, with script/bootstrap CACHED; a constant-epoch
counterfactual restored the false green (exit 0, lint layer CACHED, no
hugo output); an empty epoch failed closed on the guard; a broken
template failed the lint stage and an unformatted README failed the
fmt-check stage, both reverted and re-run clean; script/cibuild and
`make check` are green with all three checks demonstrably executing.
2026-08-10 12:52:56 +00:00

5.7 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 build and the formatting check run inside Docker, so make check needs a working Docker daemon; there is no host fallback.

make fmt rewrites the repo's markdown and CSS to the project's prettier settings; run it if make check fails on formatting. It runs on the host, because it writes to your working tree.

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 with go install, which verifies it against sum.golang.org; the version is the HUGO_VERSION constant at the top of the script
  • script/setup — prepare a fresh clone: run script/bootstrap and install the git pre-commit hook
  • script/test — the correctness check: a clean hugo --minify production build
  • script/lint — a clean build that surfaces broken links and path collisions, run inside Docker: it builds the lint stage of Dockerfile.lint, where the check is a build step, so a successful build is a clean lint
  • script/fmt — format every markdown and CSS file in the repo with prettier; the exclusions live in .prettierignore with the reason for each. The one prettier entrypoint that runs on the host, because it writes to your working tree
  • script/fmt-check — check that formatting (read-only), also inside Docker: the fmt-check stage of Dockerfile.lint
  • script/check — run script/test, script/lint, then script/fmt-check; modifies no tracked files
  • script/docker — build the Docker image tagged with the project name
  • script/cibuild — the CI build: the main image (the production build), then script/lint and script/fmt-check
  • script/install-precommit — install the git pre-commit hook that runs script/check

Every lint run for this repo happens inside a container. script/lint and script/fmt-check have no host path and no "already inside a container?" branch, so what a developer runs and what CI runs are the same build.

That is also why the main Dockerfile runs make test rather than make check: make check calls script/lint, which is itself a docker build, so a make check inside an image would be docker-in-docker in a bare Alpine with no docker client and no daemon socket. The checks are split by where they run — the production build in Dockerfile, lint and the format check in Dockerfile.lint — and script/cibuild drives all three, so CI coverage is unchanged.

Build any image through script/cibuild, script/docker, script/lint or script/fmt-check only. All four 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/.

Author

@sneak