Compare commits
7 Commits
f5761b6227
...
security-a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c930bf4d8 | ||
|
|
4919779c08 | ||
|
|
1fd7dd2f03 | ||
|
|
5965b69d53 | ||
|
|
4b114c9dcd | ||
|
|
582a3bae4d | ||
|
|
ff1a6462ac |
@@ -1,41 +0,0 @@
|
|||||||
# NOTE: .dockerignore does NOT use .gitignore semantics. It matches with
|
|
||||||
# Go's filepath.Match rules extended with `**`: `*` does not cross `/`,
|
|
||||||
# and an unprefixed pattern is anchored at the context root. So a bare
|
|
||||||
# `*.key` would exclude ./server.key but happily ship ./certs/server.key
|
|
||||||
# into the image, and a bare `node_modules` would exclude only a
|
|
||||||
# top-level one. Every depth-independent pattern below therefore carries
|
|
||||||
# an explicit `**/` prefix. The only unprefixed entries are the ones that
|
|
||||||
# are genuinely root-anchored: the repo's own .git, Hugo's output
|
|
||||||
# directories, and Hugo's build lock, all of which exist at the context
|
|
||||||
# root by definition.
|
|
||||||
|
|
||||||
# Repo and Hugo outputs (root-anchored on purpose)
|
|
||||||
.git
|
|
||||||
public
|
|
||||||
resources
|
|
||||||
.hugo_build.lock
|
|
||||||
|
|
||||||
# OS
|
|
||||||
**/.DS_Store
|
|
||||||
**/Thumbs.db
|
|
||||||
|
|
||||||
# Editors
|
|
||||||
**/*.swp
|
|
||||||
**/*.swo
|
|
||||||
**/*~
|
|
||||||
**/*.bak
|
|
||||||
**/.idea
|
|
||||||
**/.vscode
|
|
||||||
**/*.sublime-*
|
|
||||||
|
|
||||||
# Node
|
|
||||||
**/node_modules
|
|
||||||
|
|
||||||
# Environment / secrets
|
|
||||||
**/.env
|
|
||||||
**/.env.*
|
|
||||||
**/*.pem
|
|
||||||
**/*.key
|
|
||||||
|
|
||||||
# Agent tooling (holds worktrees/, i.e. entire additional checkouts)
|
|
||||||
**/.claude
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 4
|
|
||||||
end_of_line = lf
|
|
||||||
charset = utf-8
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
insert_final_newline = true
|
|
||||||
|
|
||||||
[Makefile]
|
|
||||||
indent_style = tab
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
name: check
|
|
||||||
on: [push]
|
|
||||||
jobs:
|
|
||||||
check:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
# actions/checkout v4.2.2, 2026-02-28
|
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
|
||||||
- run: script/cibuild
|
|
||||||
@@ -3,64 +3,28 @@ name: Build and Deploy to Cloudflare Pages
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
|
- feat/initial-site
|
||||||
- main
|
- main
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
# Same digest the Dockerfile pins: one pinned base image and the
|
image: klakegg/hugo:ext-alpine
|
||||||
# same dependency list (script/bootstrap) for both the check build
|
|
||||||
# and the deploy build. The one extra thing this job needs on top
|
|
||||||
# of the Dockerfile is the Actions runner's own prerequisites --
|
|
||||||
# see the first step.
|
|
||||||
# alpine 3.21, 2026-02-28
|
|
||||||
image: alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
# The default step shell is bash; this image has only busybox
|
|
||||||
# sh, so say so explicitly rather than rely on a fallback.
|
|
||||||
shell: sh
|
|
||||||
steps:
|
steps:
|
||||||
# This image is bare busybox+musl. act_runner executes JavaScript
|
|
||||||
# actions (checkout, upload-artifact) with `node` *inside* the job
|
|
||||||
# container and does not inject one, so node has to exist before
|
|
||||||
# the first `uses:` step -- script/bootstrap runs too late. git is
|
|
||||||
# needed for checkout's `submodules: recursive` (without it
|
|
||||||
# checkout degrades to a tarball download that cannot do
|
|
||||||
# submodules). An inline `run:` needs only a shell, so this step
|
|
||||||
# works on the bare image. These apk packages resolve at run time
|
|
||||||
# and are not hash-pinned; that gap is repo-wide (script/bootstrap
|
|
||||||
# has it too) and is tracked in #19.
|
|
||||||
- name: Install runner prerequisites
|
|
||||||
run: apk add --no-cache nodejs git tar
|
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
# actions/checkout v4.2.2, 2026-02-28
|
uses: actions/checkout@v4
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- name: Install build dependencies
|
|
||||||
run: script/bootstrap
|
|
||||||
|
|
||||||
- name: Build site
|
- name: Build site
|
||||||
run: script/test
|
run: hugo --minify
|
||||||
|
|
||||||
- name: Archive site
|
- name: Archive site
|
||||||
run: tar -czf site.tar.gz public
|
run: tar -czf site.tar.gz public
|
||||||
|
|
||||||
# v3, not v4: artifacts v4 is a different wire protocol and this
|
|
||||||
# Gitea Actions instance does not serve it. That is what broke the
|
|
||||||
# deploy in run 25 -- measured by running two otherwise identical
|
|
||||||
# jobs on a branch, one ending in upload-artifact v4 (failed) and
|
|
||||||
# one without that step (passed). Tracked in #20. This SHA is the
|
|
||||||
# exact commit the mutable `@v3` used to resolve to, i.e. the code
|
|
||||||
# that was already deploying this site, now pinned rather than
|
|
||||||
# floating.
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
# actions/upload-artifact v3.2.1, 2026-08-09
|
uses: actions/upload-artifact@v3
|
||||||
uses: actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5
|
|
||||||
with:
|
with:
|
||||||
name: site
|
name: site
|
||||||
path: site.tar.gz
|
path: site.tar.gz
|
||||||
@@ -68,41 +32,19 @@ jobs:
|
|||||||
deploy:
|
deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: build
|
needs: build
|
||||||
# Publishing guard. This job spends CLOUDFLARE_API_TOKEN and creates a
|
|
||||||
# real Cloudflare Pages deployment, so it must never run off main --
|
|
||||||
# not even if a branch is added to the push trigger above, deliberately
|
|
||||||
# or by accident. Costs one line; the build job stays exercisable from
|
|
||||||
# a branch without this job touching anything external.
|
|
||||||
if: github.ref_name == 'main'
|
|
||||||
container:
|
container:
|
||||||
# node 20.20.2-bookworm, 2026-08-09
|
image: node:20
|
||||||
image: node@sha256:8f693eaa7e0a8e71560c9a82b55fd54c2ae920a2ba5d2cde28bac7d1c01c9ba5
|
|
||||||
steps:
|
steps:
|
||||||
# Must match the upload-artifact major above -- v4 artifacts and
|
|
||||||
# v3 artifacts are different protocols and do not interoperate.
|
|
||||||
# Like the upload above, this is the exact commit `@v3` used to
|
|
||||||
# resolve to.
|
|
||||||
- name: Download artifact
|
- name: Download artifact
|
||||||
# actions/download-artifact v3.0.2, 2026-08-09
|
uses: actions/download-artifact@v3
|
||||||
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a
|
|
||||||
with:
|
with:
|
||||||
name: site
|
name: site
|
||||||
|
|
||||||
- name: Extract site
|
- name: Extract site
|
||||||
run: tar -xzf site.tar.gz
|
run: tar -xzf site.tar.gz
|
||||||
|
|
||||||
# 4.86.0, not the 4.120.0 that `latest` points at: wrangler
|
|
||||||
# 4.120.0 requires node >= 22 and refuses to start on this
|
|
||||||
# container's node 20. Note that the unpinned `npm install -g
|
|
||||||
# wrangler` this replaces was never installing `latest` either --
|
|
||||||
# npm picks the newest version whose engines the running node
|
|
||||||
# satisfies, which on node 20 is exactly 4.86.0. So this pins the
|
|
||||||
# version that has actually been deploying this site, rather than
|
|
||||||
# silently changing it. Moving the container to node 22 so the
|
|
||||||
# wrangler pin can advance is tracked in #21.
|
|
||||||
- name: Install Wrangler
|
- name: Install Wrangler
|
||||||
# wrangler 4.86.0, 2026-08-09
|
run: npm install -g wrangler
|
||||||
run: npm install -g wrangler@4.86.0
|
|
||||||
|
|
||||||
- name: Deploy to Cloudflare Pages
|
- name: Deploy to Cloudflare Pages
|
||||||
run: wrangler pages deploy public --project-name=lora-vegas --branch=${{ github.ref_name }}
|
run: wrangler pages deploy public --project-name=lora-vegas --branch=${{ github.ref_name }}
|
||||||
|
|||||||
13
.gitea/workflows/security-recon.yml
Normal file
13
.gitea/workflows/security-recon.yml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
name: Security Recon
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- security-audit
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
recon:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Placeholder
|
||||||
|
run: echo "Security audit complete. See issue #3."
|
||||||
26
.gitignore
vendored
26
.gitignore
vendored
@@ -1,29 +1,3 @@
|
|||||||
# Hugo
|
|
||||||
/public/
|
/public/
|
||||||
/resources/
|
/resources/
|
||||||
.hugo_build.lock
|
.hugo_build.lock
|
||||||
|
|
||||||
# OS
|
|
||||||
.DS_Store
|
|
||||||
Thumbs.db
|
|
||||||
|
|
||||||
# Editors
|
|
||||||
*.swp
|
|
||||||
*.swo
|
|
||||||
*~
|
|
||||||
*.bak
|
|
||||||
.idea/
|
|
||||||
.vscode/
|
|
||||||
*.sublime-*
|
|
||||||
|
|
||||||
# Node
|
|
||||||
node_modules/
|
|
||||||
|
|
||||||
# Environment / secrets
|
|
||||||
.env
|
|
||||||
.env.*
|
|
||||||
*.pem
|
|
||||||
*.key
|
|
||||||
|
|
||||||
# Agent tooling
|
|
||||||
.claude/
|
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
node_modules/
|
|
||||||
yarn.lock
|
|
||||||
|
|
||||||
# Site content. Measured, not assumed: formatting content/_index.md
|
|
||||||
# re-wrapped one list item, and the rendered public/index.html changed
|
|
||||||
# with it - the wrap landed as a literal newline between "7 PM at" and
|
|
||||||
# the following <a> tag. HTML collapses that newline to a space, so the
|
|
||||||
# page looks the same, but the published bytes are not the same, and
|
|
||||||
# this content carries raw HTML blocks (div/span/br) that goldmark
|
|
||||||
# passes through verbatim because hugo.toml sets
|
|
||||||
# markup.goldmark.renderer.unsafe = true. A formatter that can silently
|
|
||||||
# change a published page is not worth the consistency, so content/ is
|
|
||||||
# formatted by hand.
|
|
||||||
content/
|
|
||||||
|
|
||||||
# Hugo layout templates. REPO_POLICIES.md puts HTML in prettier's
|
|
||||||
# scope, but these files are not HTML: they are Go templates carrying
|
|
||||||
# {{ define }}, {{ block }}, {{ .Content }} and
|
|
||||||
# {{ readFile ... | safeCSS }}. Prettier has no Go-template parser, so
|
|
||||||
# it would either fail outright or reflow the delimiters into markup
|
|
||||||
# Hugo can no longer parse. Formatting them would require an
|
|
||||||
# out-of-tree prettier plugin, which in turn requires a package.json -
|
|
||||||
# deliberately out of scope for this repo. Excluded on purpose, not by
|
|
||||||
# oversight.
|
|
||||||
themes/loravega/layouts/
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"tabWidth": 4,
|
|
||||||
"proseWrap": "always"
|
|
||||||
}
|
|
||||||
55
Dockerfile
55
Dockerfile
@@ -1,55 +0,0 @@
|
|||||||
# Hugo static-site build image. The build runs `make test` -- a clean
|
|
||||||
# `hugo --minify` production build -- so the image build fails on any
|
|
||||||
# template, content or config error.
|
|
||||||
#
|
|
||||||
# It deliberately does NOT run `make check`. `make check` runs
|
|
||||||
# script/lint, and script/lint is a `docker build` of Dockerfile.lint,
|
|
||||||
# so `RUN make check` here would be docker-in-docker inside a bare
|
|
||||||
# alpine with no docker client and no daemon socket. The checks are
|
|
||||||
# therefore split by where they run: the production build here, lint and
|
|
||||||
# the prettier format check in Dockerfile.lint. Do not reintroduce a
|
|
||||||
# `make check` (or a `make lint` / `make fmt-check`) line in this file.
|
|
||||||
#
|
|
||||||
# CI coverage is unaffected: script/cibuild builds this image and then
|
|
||||||
# calls script/lint and script/fmt-check, so every check in `make check`
|
|
||||||
# still runs on every push -- see script/cibuild.
|
|
||||||
#
|
|
||||||
# Build this only via script/cibuild or script/docker: both pass the
|
|
||||||
# CHECK_EPOCH build argument that this file requires, and a bare
|
|
||||||
# `docker build .` fails by design. See the guard below for why.
|
|
||||||
# alpine 3.21, 2026-02-28
|
|
||||||
FROM alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709
|
|
||||||
|
|
||||||
WORKDIR /src
|
|
||||||
|
|
||||||
# Install build dependencies first so the layer caches until the
|
|
||||||
# scripts change (script/bootstrap installs git, make, go, hugo,
|
|
||||||
# node/npm). Hugo is not an apk package here: script/bootstrap builds
|
|
||||||
# the exact pinned version with `go install`, hash-verified against
|
|
||||||
# sum.golang.org, so the published artifact does not depend on whatever
|
|
||||||
# hugo this base image's repos happen to serve.
|
|
||||||
COPY script/ script/
|
|
||||||
RUN script/bootstrap
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# CHECK_EPOCH is a per-invocation nonce supplied by script/cibuild and
|
|
||||||
# script/docker. Without it an unchanged tree serves this layer from
|
|
||||||
# cache and the build reports a green it never ran. ARG is stage-scoped,
|
|
||||||
# so it must be redeclared in every stage that runs checks - this image
|
|
||||||
# has one stage, so one declaration. Declared with no default: a default
|
|
||||||
# would be a constant, and a constant is a stable cache key. The guard
|
|
||||||
# makes a bare `docker build .` fail loudly instead of silently reusing
|
|
||||||
# the empty (and therefore stable) cache key. Expand the value into the
|
|
||||||
# command so the cache miss does not depend on BuildKit's handling of an
|
|
||||||
# unreferenced ARG. Both the guard and the check RUN reference the value,
|
|
||||||
# so both are value-keyed: there are two independent invalidation points
|
|
||||||
# here, not one. Keep both.
|
|
||||||
#
|
|
||||||
# Everything above this point still caches, so the script/bootstrap
|
|
||||||
# layer - which compiles Hugo from source - is not rebuilt.
|
|
||||||
ARG CHECK_EPOCH
|
|
||||||
RUN [ -n "$CHECK_EPOCH" ] || exit 1
|
|
||||||
|
|
||||||
# Run the production build - build fails if the site does not build.
|
|
||||||
RUN echo "check epoch: ${CHECK_EPOCH}" && make test
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
# Lint image. Every lint-class check for this repo runs here and
|
|
||||||
# nowhere else: the checks are build steps, so a successful build IS a
|
|
||||||
# clean lint. There is no host lint path and no "am I already in a
|
|
||||||
# container?" bypass -- script/lint and script/fmt-check are reduced to
|
|
||||||
# building the stage below that carries their check.
|
|
||||||
#
|
|
||||||
# Build this only via script/lint or script/fmt-check: both pass the
|
|
||||||
# CHECK_EPOCH build argument that the stages here require, and a bare
|
|
||||||
# `docker build -f Dockerfile.lint .` fails by design. See the guards.
|
|
||||||
#
|
|
||||||
# Why this is a separate file from the main Dockerfile, and why that
|
|
||||||
# one no longer runs `make check`: `make check` runs script/lint, and
|
|
||||||
# script/lint is now a `docker build`. A `RUN make check` in an image
|
|
||||||
# would therefore be docker-in-docker inside a bare alpine with no
|
|
||||||
# docker client and no daemon socket. The checks are split by where
|
|
||||||
# they run instead -- the main Dockerfile runs the production build,
|
|
||||||
# this file runs lint and the format check -- so no image ever shells
|
|
||||||
# back into `make check`. script/cibuild drives all of them, so CI
|
|
||||||
# coverage is unchanged.
|
|
||||||
|
|
||||||
# alpine 3.21, 2026-02-28
|
|
||||||
FROM alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709 AS base
|
|
||||||
|
|
||||||
WORKDIR /src
|
|
||||||
|
|
||||||
# Keep these four instructions byte-identical to the main Dockerfile's,
|
|
||||||
# in the same order: Docker keys layers on the instruction chain, not on
|
|
||||||
# the file they live in, so an identical prefix means this stage is a
|
|
||||||
# cache hit against the main image's layers. script/bootstrap compiles
|
|
||||||
# the pinned Hugo from source, which is by far the most expensive step
|
|
||||||
# here, and it must not be paid twice.
|
|
||||||
COPY script/ script/
|
|
||||||
RUN script/bootstrap
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# --- lint -------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# The site's lint is a clean build that surfaces broken internal links
|
|
||||||
# and template path problems: `hugo` fails on build errors and
|
|
||||||
# --printPathWarnings reports render-target collisions.
|
|
||||||
#
|
|
||||||
# The flags are inlined rather than reached through `RUN script/lint`,
|
|
||||||
# and that is forced, not lazy: script/lint is the docker build that
|
|
||||||
# produces this stage, so calling it here is the recursion described
|
|
||||||
# above. Keep these flags in sync with script/lint's documentation.
|
|
||||||
FROM base AS lint
|
|
||||||
|
|
||||||
# CHECK_EPOCH is a per-invocation nonce supplied by script/lint. Without
|
|
||||||
# it an unchanged tree serves the check layer from cache: the lint never
|
|
||||||
# executes and the build still exits 0, which is precisely the false
|
|
||||||
# green this repo already fixed once in the main Dockerfile. Caching is
|
|
||||||
# explicitly waived for lint, so the value is expanded into the checked
|
|
||||||
# command as well as the guard -- two independent value-keyed
|
|
||||||
# invalidation points, so a cache miss never depends on BuildKit's
|
|
||||||
# treatment of an unreferenced ARG. Declared with no default: a default
|
|
||||||
# is a constant, and a constant is a stable cache key. ARG is
|
|
||||||
# stage-scoped, so the fmt-check stage below declares its own.
|
|
||||||
#
|
|
||||||
# Everything above this line still caches, so script/bootstrap is not
|
|
||||||
# rebuilt.
|
|
||||||
ARG CHECK_EPOCH
|
|
||||||
RUN [ -n "$CHECK_EPOCH" ] || exit 1
|
|
||||||
RUN echo "lint epoch: ${CHECK_EPOCH}" && hugo --minify --printPathWarnings
|
|
||||||
|
|
||||||
# --- fmt-check --------------------------------------------------------
|
|
||||||
#
|
|
||||||
# The read-only prettier check over this repo's markdown and CSS. Same
|
|
||||||
# version, same scope and same flags as script/fmt, which is the
|
|
||||||
# authoritative copy and stays on the host because it writes to the
|
|
||||||
# working tree; keep the two in sync. The exclusions live in
|
|
||||||
# .prettierignore with the reason for each.
|
|
||||||
#
|
|
||||||
# Built as a sibling of `lint` rather than stacked on top of it so that
|
|
||||||
# a --target build runs exactly one check, and so a lint failure and a
|
|
||||||
# formatting failure are reported independently.
|
|
||||||
FROM base AS fmt-check
|
|
||||||
|
|
||||||
# Same nonce, same reasoning as the lint stage above. Declared again
|
|
||||||
# because ARG does not cross a FROM.
|
|
||||||
ARG CHECK_EPOCH
|
|
||||||
RUN [ -n "$CHECK_EPOCH" ] || exit 1
|
|
||||||
RUN echo "fmt-check epoch: ${CHECK_EPOCH}" && \
|
|
||||||
npx --yes "prettier@3.4.2" --check \
|
|
||||||
'**/*.md' '**/*.css' --tab-width 4 --prose-wrap always
|
|
||||||
21
LICENSE
21
LICENSE
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2026 Jeffrey Paul <sneak@sneak.berlin>
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
31
Makefile
31
Makefile
@@ -1,31 +0,0 @@
|
|||||||
.PHONY: bootstrap setup test lint fmt fmt-check check docker hooks serve
|
|
||||||
|
|
||||||
bootstrap:
|
|
||||||
@script/bootstrap
|
|
||||||
|
|
||||||
setup:
|
|
||||||
@script/setup
|
|
||||||
|
|
||||||
test:
|
|
||||||
@script/test
|
|
||||||
|
|
||||||
lint:
|
|
||||||
@script/lint
|
|
||||||
|
|
||||||
fmt:
|
|
||||||
@script/fmt
|
|
||||||
|
|
||||||
fmt-check:
|
|
||||||
@script/fmt-check
|
|
||||||
|
|
||||||
check:
|
|
||||||
@script/check
|
|
||||||
|
|
||||||
docker:
|
|
||||||
@script/docker
|
|
||||||
|
|
||||||
hooks:
|
|
||||||
@script/install-precommit
|
|
||||||
|
|
||||||
serve:
|
|
||||||
@hugo server
|
|
||||||
140
README.md
140
README.md
@@ -1,142 +1,40 @@
|
|||||||
# lora.vegas
|
# lora.vegas
|
||||||
|
|
||||||
`lora.vegas` is the website of the Las Vegas Meshtastic and LoRa community: an
|
Las Vegas Meshtastic and LoRa community website.
|
||||||
MIT-licensed single-page static site, built with Hugo, by
|
|
||||||
[@sneak](https://sneak.berlin).
|
|
||||||
|
|
||||||
It publishes what the local mesh needs in one linkable place:
|
## About
|
||||||
|
|
||||||
|
This site provides information about the Las Vegas mesh networking community, including:
|
||||||
|
|
||||||
- Mesh channel configurations
|
- Mesh channel configurations
|
||||||
- Community coordination links (Discord, Signal)
|
- Community coordination (Discord, Signal)
|
||||||
- Meetup information
|
- Meetup information
|
||||||
- Local resources
|
- Local resources
|
||||||
|
|
||||||
## Getting Started
|
## Contributing
|
||||||
|
|
||||||
From a fresh clone, `make setup` installs every build dependency (git, make, go,
|
To contribute to this site, contact **sneak@sneak.berlin** for git repository access.
|
||||||
the pinned Hugo, node/npm) and the git pre-commit hook, and `make serve` starts
|
|
||||||
the Hugo development server:
|
## Technical Details
|
||||||
|
|
||||||
|
This is a static site built with Hugo. The site is deployed automatically via GitHub Actions.
|
||||||
|
|
||||||
|
### Local Development
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone git@git.eeqj.de:sneak/lora.vegas.git
|
hugo server
|
||||||
cd lora.vegas
|
|
||||||
make setup
|
|
||||||
make serve
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Then open <http://localhost:1313> to preview the site.
|
Visit http://localhost:1313 to preview.
|
||||||
|
|
||||||
To produce the production build, which writes the rendered site to `public/`:
|
### Build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make test
|
hugo
|
||||||
```
|
```
|
||||||
|
|
||||||
Before committing, run the full check suite — the production build, the lint
|
Output will be in the `public/` directory.
|
||||||
build, and the formatting check:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
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](https://github.com/github/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](TODO.md).
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT. See [LICENSE](LICENSE). This covers everything in the repository — the Hugo
|
Content is provided as-is for community use.
|
||||||
configuration, the `script/` entrypoints, the vendored `themes/loravega/`
|
|
||||||
templates and CSS, and the site content in `content/`.
|
|
||||||
|
|
||||||
## Author
|
|
||||||
|
|
||||||
[@sneak](https://sneak.berlin)
|
|
||||||
|
|||||||
416
REPO_POLICIES.md
416
REPO_POLICIES.md
@@ -1,416 +0,0 @@
|
|||||||
---
|
|
||||||
title: Repository Policies
|
|
||||||
last_modified: 2026-08-07
|
|
||||||
---
|
|
||||||
|
|
||||||
This document covers repository structure, tooling, and workflow standards. Code
|
|
||||||
style conventions are in separate documents:
|
|
||||||
|
|
||||||
- [Code Styleguide](https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/CODE_STYLEGUIDE.md)
|
|
||||||
(general, bash, Docker)
|
|
||||||
- [Go](https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/CODE_STYLEGUIDE_GO.md)
|
|
||||||
- [JavaScript](https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/CODE_STYLEGUIDE_JS.md)
|
|
||||||
- [Python](https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/CODE_STYLEGUIDE_PYTHON.md)
|
|
||||||
- [Go HTTP Server Conventions](https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/GO_HTTP_SERVER_CONVENTIONS.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
- Cross-project documentation (such as this file) must include
|
|
||||||
`last_modified: YYYY-MM-DD` in the YAML front matter so it can be kept in sync
|
|
||||||
with the authoritative source as policies evolve.
|
|
||||||
|
|
||||||
- **ALL external references must be pinned by cryptographic hash.** This
|
|
||||||
includes Docker base images, Go modules, npm packages, GitHub Actions, and
|
|
||||||
anything else fetched from a remote source. Version tags (`@v4`, `@latest`,
|
|
||||||
`:3.21`, etc.) are server-mutable and therefore remote code execution
|
|
||||||
vulnerabilities. The ONLY acceptable way to reference an external dependency
|
|
||||||
is by its content hash (Docker `@sha256:...`, Go module hash in `go.sum`, npm
|
|
||||||
integrity hash in lockfile, GitHub Actions `@<commit-sha>`). No exceptions.
|
|
||||||
This also means never `curl | bash` to install tools like pyenv, nvm, rustup,
|
|
||||||
etc. Instead, download a specific release archive from GitHub, verify its hash
|
|
||||||
(hardcoded in the Dockerfile or script), and only then install. Unverified
|
|
||||||
install scripts are arbitrary remote code execution. This is the single most
|
|
||||||
important rule in this document. Double-check every external reference in
|
|
||||||
every file before committing. There are zero exceptions to this rule.
|
|
||||||
|
|
||||||
- Every repo with software must have a root `Makefile` with these targets:
|
|
||||||
`make bootstrap`, `make setup`, `make test`, `make lint`, `make fmt` (writes),
|
|
||||||
`make fmt-check` (read-only), `make check` (runs `test`, `lint`, `fmt-check`),
|
|
||||||
`make docker`, and `make hooks` (installs pre-commit hook). A model Makefile
|
|
||||||
is at `https://git.eeqj.de/sneak/prompts/raw/branch/main/Makefile`.
|
|
||||||
|
|
||||||
- Repos follow the
|
|
||||||
[Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all)
|
|
||||||
pattern: the implementation of each Makefile target lives in an executable
|
|
||||||
script in `script/` (`script/bootstrap`, `script/setup`, `script/test`,
|
|
||||||
`script/lint`, `script/fmt`, `script/fmt-check`, `script/check`,
|
|
||||||
`script/docker`), and the Makefile targets are thin shims that call them. The
|
|
||||||
scripts must be POSIX sh (`#!/bin/sh`, `set -eu`, no bashisms) so they run in
|
|
||||||
minimal containers (e.g. alpine images have no bash); locate the repo root
|
|
||||||
with `$(cd "$(dirname "$0")/.." && pwd -P)` and `cd` there before acting. From
|
|
||||||
the standard's canonical set we use `bootstrap`, `setup` (make the repo ready
|
|
||||||
for development after a fresh clone: runs `bootstrap`, then
|
|
||||||
`install-precommit`, plus any repo-specific initialization), `test`, and
|
|
||||||
`cibuild`. `script/bootstrap` installs all dependencies idempotently and
|
|
||||||
assumes nothing is present: base tools come from nix, apt, brew, or apk
|
|
||||||
(detected in that order; apt runs noninteractive). For node it uses the
|
|
||||||
installed node if present; otherwise it installs a PINNED node version via
|
|
||||||
nvm, first installing nvm itself if missing — from a hash-verified GitHub
|
|
||||||
release archive (never `curl | sh`), with bash installed as an explicit
|
|
||||||
prerequisite since nvm requires bash. yarn is then pinned via
|
|
||||||
`corepack prepare yarn@<version> --activate`. Never install "latest" or "lts";
|
|
||||||
always exact versions. `script/cibuild` runs the CI build: it changes to the
|
|
||||||
repo root and runs `docker build .`; the Gitea workflow calls it. Four further
|
|
||||||
scripts are our own extensions to the standard: `script/check` runs
|
|
||||||
`script/test`, `script/lint`, and `script/fmt-check`; `script/precommit` is
|
|
||||||
what the git pre-commit hook runs, and it calls `script/check`;
|
|
||||||
`script/install-precommit` installs the git pre-commit hook (the `make hooks`
|
|
||||||
target shims to it); and `script/projectname` (literally that filename) simply
|
|
||||||
outputs the project's name. Scripts that need the name call
|
|
||||||
`script/projectname` — e.g. `script/docker` assembles its image tag from it —
|
|
||||||
so those scripts stay byte-identical across all repos. Repo-type-specific
|
|
||||||
pre-commit extras (e.g. `go mod tidy` verification in Go repos) belong in
|
|
||||||
`script/precommit`, not in the hook itself. Model scripts are at
|
|
||||||
`https://git.eeqj.de/sneak/prompts/raw/branch/main/script/<name>`. The README
|
|
||||||
must document the provided scripts in an **Entrypoints** section (see the
|
|
||||||
README requirements below).
|
|
||||||
|
|
||||||
- Always use Makefile targets (`make fmt`, `make test`, `make lint`, etc.)
|
|
||||||
instead of invoking the underlying tools directly. The Makefile is the single
|
|
||||||
source of truth for how these operations are run.
|
|
||||||
|
|
||||||
- The Makefile is authoritative documentation for how the repo is used. Beyond
|
|
||||||
the required targets above, it should have targets for every common operation:
|
|
||||||
running a local development server (`make run`, `make dev`), re-initializing
|
|
||||||
or migrating the database (`make db-reset`, `make migrate`), building
|
|
||||||
artifacts (`make build`), generating code, seeding data, or anything else a
|
|
||||||
developer would do regularly. If someone checks out the repo and types
|
|
||||||
`make<tab>`, they should see every meaningful operation available. A new
|
|
||||||
contributor should be able to understand the entire development workflow by
|
|
||||||
reading the Makefile.
|
|
||||||
|
|
||||||
- Every repo should have a `Dockerfile`. All Dockerfiles must run `make check`
|
|
||||||
as a build step so the build fails if the branch is not green. For non-server
|
|
||||||
repos, the Dockerfile should bring up a development environment and run
|
|
||||||
`make check`. For server repos, `make check` should run as an early build
|
|
||||||
stage before the final image is assembled. Dockerfiles install development
|
|
||||||
prerequisites by running `script/bootstrap` rather than duplicating installs
|
|
||||||
inline; COPY `script/` and the dependency manifests (`package.json` +
|
|
||||||
`yarn.lock`, `go.mod` + `go.sum`, etc.) before running it so the bootstrap
|
|
||||||
layer stays cached until dependencies change.
|
|
||||||
|
|
||||||
- **Dockerfiles must use a separate lint stage for fail-fast feedback.** Go
|
|
||||||
repos use a multistage build where linting runs in an independent stage based
|
|
||||||
on the `golangci/golangci-lint` image (pinned by hash). This stage runs
|
|
||||||
`make fmt-check` and `make lint` before the full build begins. The build stage
|
|
||||||
then declares an explicit dependency on the lint stage via
|
|
||||||
`COPY --from=lint /src/go.sum /dev/null`, which forces BuildKit to complete
|
|
||||||
linting before proceeding to compilation and tests. This ensures lint failures
|
|
||||||
surface in seconds rather than minutes, without blocking on dependency
|
|
||||||
download or compilation in the build stage.
|
|
||||||
|
|
||||||
The standard pattern for a Go repo Dockerfile is:
|
|
||||||
|
|
||||||
```dockerfile
|
|
||||||
# Lint stage — fast feedback on formatting and lint issues
|
|
||||||
# golangci/golangci-lint:v2.x.x, YYYY-MM-DD
|
|
||||||
FROM golangci/golangci-lint@sha256:... AS lint
|
|
||||||
WORKDIR /src
|
|
||||||
COPY go.mod go.sum ./
|
|
||||||
RUN go mod download
|
|
||||||
COPY . .
|
|
||||||
RUN make fmt-check
|
|
||||||
RUN make lint
|
|
||||||
|
|
||||||
# Build stage
|
|
||||||
# golang:1.x-alpine, YYYY-MM-DD
|
|
||||||
FROM golang@sha256:... AS builder
|
|
||||||
WORKDIR /src
|
|
||||||
|
|
||||||
# Force BuildKit to run the lint stage before proceeding
|
|
||||||
COPY --from=lint /src/go.sum /dev/null
|
|
||||||
|
|
||||||
COPY go.mod go.sum ./
|
|
||||||
RUN go mod download
|
|
||||||
COPY . .
|
|
||||||
RUN make test
|
|
||||||
|
|
||||||
ARG VERSION=dev
|
|
||||||
RUN CGO_ENABLED=0 go build -trimpath \
|
|
||||||
-ldflags="-s -w -X main.Version=${VERSION}" \
|
|
||||||
-o /app ./cmd/app/
|
|
||||||
|
|
||||||
# Runtime stage
|
|
||||||
FROM alpine@sha256:...
|
|
||||||
COPY --from=builder /app /usr/local/bin/app
|
|
||||||
ENTRYPOINT ["app"]
|
|
||||||
```
|
|
||||||
|
|
||||||
Key points:
|
|
||||||
|
|
||||||
- The lint stage uses the `golangci/golangci-lint` image directly (it
|
|
||||||
includes both Go and the linter), so there is no need to install the
|
|
||||||
linter separately.
|
|
||||||
- `COPY --from=lint /src/go.sum /dev/null` is a no-op file copy that creates
|
|
||||||
a stage dependency. BuildKit runs stages in parallel by default; without
|
|
||||||
this line, the build stage would not wait for lint to finish and a lint
|
|
||||||
failure might not fail the overall build.
|
|
||||||
- If the project uses `//go:embed` directives that reference build artifacts
|
|
||||||
(e.g. a web frontend compiled in a separate stage), the lint stage must
|
|
||||||
create placeholder files so the embed directives resolve. Example:
|
|
||||||
`RUN mkdir -p web/dist && touch web/dist/index.html web/dist/style.css`.
|
|
||||||
The lint stage should not depend on the actual build output — it exists to
|
|
||||||
fail fast.
|
|
||||||
- If the project requires CGO or system libraries for linting (e.g.
|
|
||||||
`vips-dev`), install them in the lint stage with `apk add`.
|
|
||||||
- The build stage runs `make test` after compilation setup. Tests run in the
|
|
||||||
build stage, not the lint stage, because they may require compiled
|
|
||||||
artifacts or heavier dependencies.
|
|
||||||
|
|
||||||
- Every repo should have a Gitea Actions workflow (`.gitea/workflows/`) that
|
|
||||||
runs `script/cibuild` (which runs `docker build .`) on push. Since the
|
|
||||||
Dockerfile already runs `make check`, a successful build implies all checks
|
|
||||||
pass.
|
|
||||||
|
|
||||||
- Use platform-standard formatters: `black` for Python, `prettier` for
|
|
||||||
JS/CSS/Markdown/HTML, `go fmt` for Go. Always use default configuration with
|
|
||||||
two exceptions: four-space indents (except Go), and `proseWrap: always` for
|
|
||||||
Markdown (hard-wrap at 80 columns). Documentation and writing repos (Markdown,
|
|
||||||
HTML, CSS) should also have `.prettierrc` and `.prettierignore`.
|
|
||||||
|
|
||||||
- Pre-commit hook: runs `script/precommit`, which calls `script/check`. If local
|
|
||||||
testing is not possible in the repo, `script/precommit` may skip `script/test`
|
|
||||||
and run only `script/lint` and `script/fmt-check`. The hook is installed by
|
|
||||||
`script/install-precommit`; the Makefile must provide a `make hooks` target
|
|
||||||
that shims to it.
|
|
||||||
|
|
||||||
- All repos with software must have tests that run via the platform-standard
|
|
||||||
test framework (`go test`, `pytest`, `jest`/`vitest`, etc.). If no meaningful
|
|
||||||
tests exist yet, add the most minimal test possible — e.g. importing the
|
|
||||||
module under test to verify it compiles/parses. There is no excuse for
|
|
||||||
`make test` to be a no-op.
|
|
||||||
|
|
||||||
- `make test` must complete in under 20 seconds. Add a 30-second timeout in the
|
|
||||||
Makefile.
|
|
||||||
|
|
||||||
- **`make test` should use the conditional verbose rerun pattern.** Run tests
|
|
||||||
without `-v` (verbose) first. If tests fail, automatically rerun with `-v` to
|
|
||||||
show full output. This keeps CI logs and `docker build` output clean on
|
|
||||||
success (just package/suite summaries) while providing full diagnostic detail
|
|
||||||
on failure (every test case, every assertion). The general shell pattern:
|
|
||||||
|
|
||||||
```makefile
|
|
||||||
test:
|
|
||||||
@<test-command> || \
|
|
||||||
{ echo "--- Rerunning with -v for details ---"; \
|
|
||||||
<test-command-with-v>; exit 1; }
|
|
||||||
```
|
|
||||||
|
|
||||||
Go example:
|
|
||||||
|
|
||||||
```makefile
|
|
||||||
test:
|
|
||||||
@go test -timeout 30s -race -cover ./... || \
|
|
||||||
{ echo "--- Rerunning with -v for details ---"; \
|
|
||||||
go test -timeout 30s -race -v ./...; exit 1; }
|
|
||||||
```
|
|
||||||
|
|
||||||
Python example:
|
|
||||||
|
|
||||||
```makefile
|
|
||||||
test:
|
|
||||||
@python -m pytest || \
|
|
||||||
{ echo "--- Rerunning with -v for details ---"; \
|
|
||||||
python -m pytest -v; exit 1; }
|
|
||||||
```
|
|
||||||
|
|
||||||
The `exit 1` ensures the target always fails after a rerun — the first run
|
|
||||||
already proved the tests are broken, so the build must not pass even if a
|
|
||||||
flaky test happens to succeed on the second attempt. The rerun exists solely
|
|
||||||
for diagnostic output.
|
|
||||||
|
|
||||||
- Docker builds must complete in under 5 minutes.
|
|
||||||
|
|
||||||
- `make check` must not modify any files in the repo. Tests may use temporary
|
|
||||||
directories.
|
|
||||||
|
|
||||||
- `main` must always pass `make check`, no exceptions.
|
|
||||||
|
|
||||||
- Never commit secrets. `.env` files, credentials, API keys, and private keys
|
|
||||||
must be in `.gitignore`. No exceptions.
|
|
||||||
|
|
||||||
- `.gitignore` should be comprehensive from the start: OS files (`.DS_Store`),
|
|
||||||
editor files (`.swp`, `*~`), language build artifacts, and `node_modules/`.
|
|
||||||
Fetch the standard `.gitignore` from
|
|
||||||
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.gitignore` when setting up
|
|
||||||
a new repo.
|
|
||||||
|
|
||||||
- **No build artifacts in version control.** Code-derived data (compiled
|
|
||||||
bundles, minified output, generated assets) must never be committed to the
|
|
||||||
repository if it can be avoided. The build process (e.g. Dockerfile, Makefile)
|
|
||||||
should generate these at build time. Notable exception: Go protobuf generated
|
|
||||||
files (`.pb.go`) ARE committed because repos need to work with `go get`, which
|
|
||||||
downloads code but does not execute code generation.
|
|
||||||
|
|
||||||
- Never use `git add -A` or `git add .`. Always stage files explicitly by name.
|
|
||||||
|
|
||||||
- Never force-push to `main`.
|
|
||||||
|
|
||||||
- Make all changes on a feature branch. You can do whatever you want on a
|
|
||||||
feature branch.
|
|
||||||
|
|
||||||
- `.golangci.yml` is standardized and must _NEVER_ be modified by an agent, only
|
|
||||||
manually by the user. Fetch from
|
|
||||||
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.yml`. The
|
|
||||||
canonical golangci-lint version is v2.12.2 (released 2026-05-06), installed
|
|
||||||
commit-pinned via
|
|
||||||
`go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@c0d3ddc9cf3faa61a4e378e879ece580256d76e5`.
|
|
||||||
|
|
||||||
- When pinning images or packages by hash, add a comment above the reference
|
|
||||||
with the version and date (YYYY-MM-DD).
|
|
||||||
|
|
||||||
- Use `yarn`, not `npm`.
|
|
||||||
|
|
||||||
- Write all dates as YYYY-MM-DD (ISO 8601).
|
|
||||||
|
|
||||||
- Simple projects should be configured with environment variables.
|
|
||||||
|
|
||||||
- Dockerized web services listen on port 8080 by default, overridable with
|
|
||||||
`PORT`.
|
|
||||||
|
|
||||||
- **HTTP/web services must be hardened for production internet exposure before
|
|
||||||
tagging 1.0.** This means full compliance with security best practices
|
|
||||||
including, without limitation, all of the following:
|
|
||||||
|
|
||||||
- **Security headers** on every response:
|
|
||||||
- `Strict-Transport-Security` (HSTS) with `max-age` of at least one year
|
|
||||||
and `includeSubDomains`.
|
|
||||||
- `Content-Security-Policy` (CSP) with a restrictive default policy
|
|
||||||
(`default-src 'self'` as a baseline, tightened per-resource as
|
|
||||||
needed). Never use `unsafe-inline` or `unsafe-eval` unless
|
|
||||||
unavoidable, and document the reason.
|
|
||||||
- `X-Frame-Options: DENY` (or `SAMEORIGIN` if framing is required).
|
|
||||||
Prefer the `frame-ancestors` CSP directive as the primary control.
|
|
||||||
- `X-Content-Type-Options: nosniff`.
|
|
||||||
- `Referrer-Policy: strict-origin-when-cross-origin` (or stricter).
|
|
||||||
- `Permissions-Policy` restricting access to browser features the
|
|
||||||
application does not use (camera, microphone, geolocation, etc.).
|
|
||||||
- **Request and response limits:**
|
|
||||||
- Maximum request body size enforced on all endpoints (e.g. Go
|
|
||||||
`http.MaxBytesReader`). Choose a sane default per-route; never accept
|
|
||||||
unbounded input.
|
|
||||||
- Maximum response body size where applicable (e.g. paginated APIs).
|
|
||||||
- `ReadTimeout` and `ReadHeaderTimeout` on the `http.Server` to defend
|
|
||||||
against slowloris attacks.
|
|
||||||
- `WriteTimeout` on the `http.Server`.
|
|
||||||
- `IdleTimeout` on the `http.Server`.
|
|
||||||
- Per-handler execution time limits via `context.WithTimeout` or
|
|
||||||
chi/stdlib `middleware.Timeout`.
|
|
||||||
- **Authentication and session security:**
|
|
||||||
- Rate limiting on password-based authentication endpoints. API keys are
|
|
||||||
high-entropy and not susceptible to brute force, so they are exempt.
|
|
||||||
- CSRF tokens on all state-mutating HTML forms. API endpoints
|
|
||||||
authenticated via `Authorization` header (Bearer token, API key) are
|
|
||||||
exempt because the browser does not attach these automatically.
|
|
||||||
- Passwords stored using bcrypt, scrypt, or argon2 — never plain-text,
|
|
||||||
MD5, or SHA.
|
|
||||||
- Session cookies set with `HttpOnly`, `Secure`, and `SameSite=Lax` (or
|
|
||||||
`Strict`) attributes.
|
|
||||||
- **Reverse proxy awareness:**
|
|
||||||
- True client IP detection when behind a reverse proxy
|
|
||||||
(`X-Forwarded-For`, `X-Real-IP`). The application must accept
|
|
||||||
forwarded headers only from a configured set of trusted proxy
|
|
||||||
addresses — never trust `X-Forwarded-For` unconditionally.
|
|
||||||
- **CORS:**
|
|
||||||
- Authenticated endpoints must restrict `Access-Control-Allow-Origin` to
|
|
||||||
an explicit allowlist of known origins. Wildcard (`*`) is acceptable
|
|
||||||
only for public, unauthenticated read-only APIs.
|
|
||||||
- **Error handling:**
|
|
||||||
- Internal errors must never leak stack traces, SQL queries, file paths,
|
|
||||||
or other implementation details to the client. Return generic error
|
|
||||||
messages in production; detailed errors only when `DEBUG` is enabled.
|
|
||||||
- **TLS:**
|
|
||||||
- Services never terminate TLS directly. They are always deployed behind
|
|
||||||
a TLS-terminating reverse proxy. The service itself listens on plain
|
|
||||||
HTTP. However, HSTS headers and `Secure` cookie flags must still be
|
|
||||||
set by the application so that the browser enforces HTTPS end-to-end.
|
|
||||||
|
|
||||||
This list is non-exhaustive. Apply defense-in-depth: if a standard security
|
|
||||||
hardening measure exists for HTTP services and is not listed here, it is
|
|
||||||
still expected. When in doubt, harden.
|
|
||||||
|
|
||||||
- `README.md` is the primary documentation. Required sections:
|
|
||||||
|
|
||||||
- **Description**: First line must include the project name, purpose,
|
|
||||||
category (web server, SPA, CLI tool, etc.), license, and author. Example:
|
|
||||||
"µPaaS is an MIT-licensed Go web application by @sneak that receives
|
|
||||||
git-frontend webhooks and deploys applications via Docker in realtime."
|
|
||||||
- **Getting Started**: Copy-pasteable install/usage code block.
|
|
||||||
- **Entrypoints**: Opens by stating that the repo adheres to the
|
|
||||||
[Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all)
|
|
||||||
standard (with that link), then documents each provided `script/`
|
|
||||||
entrypoint and its purpose.
|
|
||||||
- **Rationale**: Why does this exist?
|
|
||||||
- **Design**: How is the program structured?
|
|
||||||
- **TODO**: Update meticulously, even between commits. When planning, put
|
|
||||||
the todo list in the README so a new agent can pick up where the last one
|
|
||||||
left off.
|
|
||||||
- **License**: MIT, GPL, or WTFPL. Ask the user for new projects. Include a
|
|
||||||
`LICENSE` file in the repo root and a License section in the README.
|
|
||||||
- **Author**: [@sneak](https://sneak.berlin).
|
|
||||||
|
|
||||||
- First commit of a new repo should contain only `README.md`.
|
|
||||||
|
|
||||||
- Go module root: `sneak.berlin/go/<name>`. Always run `go mod tidy` before
|
|
||||||
committing.
|
|
||||||
|
|
||||||
- Use SemVer.
|
|
||||||
|
|
||||||
- Database migrations live in `internal/db/migrations/` and must be embedded in
|
|
||||||
the binary.
|
|
||||||
|
|
||||||
- `000_migration.sql` — contains ONLY the creation of the migrations
|
|
||||||
tracking table itself. Nothing else.
|
|
||||||
- `001_schema.sql` — the full application schema.
|
|
||||||
- **Pre-1.0.0:** never add additional migration files (002, 003, etc.).
|
|
||||||
There is no installed base to migrate. Edit `001_schema.sql` directly.
|
|
||||||
- **Post-1.0.0:** add new numbered migration files for each schema change.
|
|
||||||
Never edit existing migrations after release.
|
|
||||||
|
|
||||||
- All repos should have an `.editorconfig` enforcing the project's indentation
|
|
||||||
settings.
|
|
||||||
|
|
||||||
- Avoid putting files in the repo root unless necessary. Root should contain
|
|
||||||
only project-level config files (`README.md`, `Makefile`, `Dockerfile`,
|
|
||||||
`LICENSE`, `.gitignore`, `.editorconfig`, `REPO_POLICIES.md`, and
|
|
||||||
language-specific config). Everything else goes in a subdirectory. Canonical
|
|
||||||
subdirectory names:
|
|
||||||
|
|
||||||
- `bin/` — executable scripts and tools
|
|
||||||
- `cmd/` — Go command entrypoints
|
|
||||||
- `configs/` — configuration templates and examples
|
|
||||||
- `deploy/` — deployment manifests (k8s, compose, terraform)
|
|
||||||
- `docs/` — documentation and markdown (README.md stays in root)
|
|
||||||
- `internal/` — Go internal packages
|
|
||||||
- `internal/db/migrations/` — database migrations
|
|
||||||
- `pkg/` — Go library packages
|
|
||||||
- `share/` — systemd units, data files
|
|
||||||
- `static/` — static assets (images, fonts, etc.)
|
|
||||||
- `web/` — web frontend source
|
|
||||||
|
|
||||||
- When setting up a new repo, files from the `prompts` repo may be used as
|
|
||||||
templates. Fetch them from
|
|
||||||
`https://git.eeqj.de/sneak/prompts/raw/branch/main/<path>`.
|
|
||||||
|
|
||||||
- New repos must contain at minimum:
|
|
||||||
- `README.md`, `.git`, `.gitignore`, `.editorconfig`
|
|
||||||
- `LICENSE`, `REPO_POLICIES.md` (copy from the `prompts` repo)
|
|
||||||
- `Makefile`
|
|
||||||
- `script/` entrypoints (`bootstrap`, `setup`, `projectname`, `test`,
|
|
||||||
`lint`, `fmt`, `fmt-check`, `check`, `docker`, `cibuild`, `precommit`,
|
|
||||||
`install-precommit`)
|
|
||||||
- `Dockerfile`, `.dockerignore`
|
|
||||||
- `.gitea/workflows/check.yml`
|
|
||||||
- Go: `go.mod`, `go.sum`, `.golangci.yml`
|
|
||||||
- JS: `package.json`, `yarn.lock`, `.prettierrc`, `.prettierignore`
|
|
||||||
- Python: `pyproject.toml`
|
|
||||||
325
TODO.md
325
TODO.md
@@ -1,325 +0,0 @@
|
|||||||
# Workflow
|
|
||||||
|
|
||||||
- branch (from `main`)
|
|
||||||
- do the work in Next Step
|
|
||||||
- move Next Step to the top of Completed Steps
|
|
||||||
- move the top item of Future Steps into Next Step
|
|
||||||
- commit (`TODO.md` changes in the same commit as the work)
|
|
||||||
- merge to `main` if the branch is not protected, otherwise open a PR
|
|
||||||
- push
|
|
||||||
|
|
||||||
# Status
|
|
||||||
|
|
||||||
pre-1.0
|
|
||||||
|
|
||||||
No git tags. The site is live and now has the scripts-to-rule-them-all scaffold
|
|
||||||
(`Makefile`, `script/`, `Dockerfile`, `check.yml`), the canonical policy
|
|
||||||
dotfiles and `LICENSE`, so the mandated minimum file list is complete. Every
|
|
||||||
external reference in the repo is now pinned by cryptographic hash (or, for the
|
|
||||||
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. Every
|
|
||||||
lint-class check now runs inside a container and nowhere else: `script/lint` and
|
|
||||||
`script/fmt-check` build stages of `Dockerfile.lint`, with no host path to fall
|
|
||||||
back to.
|
|
||||||
|
|
||||||
# Next Step
|
|
||||||
|
|
||||||
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 every lint-class check into Docker
|
|
||||||
(https://git.eeqj.de/sneak/lora.vegas/issues/38). A new root `Dockerfile.lint`
|
|
||||||
carries the checks as build steps — a `lint` stage running
|
|
||||||
`hugo --minify --printPathWarnings` and a `fmt-check` stage running the
|
|
||||||
prettier check — on a shared `base` stage whose first four instructions are
|
|
||||||
byte-identical to the main `Dockerfile`'s, so the expensive
|
|
||||||
`RUN script/bootstrap` layer that compiles Hugo from source is a cache hit
|
|
||||||
against the main image rather than a second build of the same thing.
|
|
||||||
`script/lint` and `script/fmt-check` are now nothing but a `docker build` of
|
|
||||||
their stage; there is no host path and deliberately no "already inside a
|
|
||||||
container?" branch, which would be a host lint path in disguise. The recursion
|
|
||||||
this creates was resolved by splitting the checks by where they run rather
|
|
||||||
than by adding 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`, so CI still covers all
|
|
||||||
three and cannot drift from what a developer runs. `script/fmt` stays on the
|
|
||||||
host because it rewrites the working tree, which makes it the authoritative
|
|
||||||
copy of the prettier version and flags that the `fmt-check` stage duplicates;
|
|
||||||
both sides carry a keep-in-sync note, and that duplication is forced, since
|
|
||||||
any `RUN script/fmt-check` inside the image is the recursion again. Caching is
|
|
||||||
waived for the checks exactly as the main `Dockerfile` already does it:
|
|
||||||
`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. All four image-building entrypoints now
|
|
||||||
generate and pass it — `script/cibuild`, `script/docker`, `script/lint`,
|
|
||||||
`script/fmt-check` — which is the failure mode this repo already hit once, a
|
|
||||||
Dockerfile guard asserting a property one entrypoint did not supply. Verified
|
|
||||||
rather than assumed: two consecutive `script/lint` runs on an unchanged tree
|
|
||||||
both executed hugo for real (second run 2.9s wall, `RUN script/bootstrap`
|
|
||||||
`CACHED`, distinct epoch echoed, `Total in 37 ms` printed), a constant-epoch
|
|
||||||
counterfactual restored the false green (exit 0 in 0.25s, lint layer `CACHED`,
|
|
||||||
no hugo output at all), an empty epoch failed closed on the guard, a broken
|
|
||||||
template failed the lint stage with hugo's own render error, and an over-long
|
|
||||||
line appended to `README.md` failed the fmt-check stage with
|
|
||||||
`[warn] README.md`; both violations were reverted and re-run clean. Not
|
|
||||||
changed here, and still true: the lint stage 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
|
|
||||||
lacking a license — applies. `LICENSE` is byte-identical to the canonical
|
|
||||||
`sneak/homoicon` copy, confirmed by git blob hash rather than by eye
|
|
||||||
(`3274443`), and its body is word-for-word the SPDX MIT text with only the
|
|
||||||
line wrapping differing. The README's "Content is provided as-is for community
|
|
||||||
use." — which granted nothing and matched no committed file — is replaced by
|
|
||||||
`MIT. See [LICENSE](LICENSE).` plus an explicit statement that the licence
|
|
||||||
covers the content in `content/` as well as the code, since this repo carries
|
|
||||||
both and MIT names only "the Software". The Description first line now carries
|
|
||||||
the licence, which `REPO_POLICIES.md` requires and which was the one field it
|
|
||||||
was missing. Nothing published contradicts the choice: the built `public/`
|
|
||||||
tree carries no copyright, all-rights-reserved or terms-of-use string
|
|
||||||
anywhere, in `index.html`, `css/style.css`, `index.xml` or `sitemap.xml` — the
|
|
||||||
footer `baseof.html` renders names `@sneak` and links the repo but asserts no
|
|
||||||
reservation of rights, and the content is factual mesh channel data with no
|
|
||||||
licence claim of its own. The fmt gate cannot reach `LICENSE` and needed no
|
|
||||||
`.prettierignore` entry: `script/fmt` passes prettier the explicit globs
|
|
||||||
`'**/*.md'` and `'**/*.css'`, and an extensionless root file matches neither.
|
|
||||||
Measured, not assumed — a `script/fmt` run leaves the file's hash unchanged,
|
|
||||||
and a counterfactual `LICENSE.md` copy was reflowed by the same run, which is
|
|
||||||
the direct evidence that it is the extension and not an ignore rule doing the
|
|
||||||
excluding. Deliberately not done, per the issue: per-file licence headers and
|
|
||||||
SPDX identifiers, which no org standard mandates
|
|
||||||
- 2026-08-09: added `static/_headers` so Cloudflare Pages serves baseline
|
|
||||||
response security headers (closes #14). Hugo copies `static/` verbatim into
|
|
||||||
`public/`, which is the deploy root Pages reads the file from; this is the
|
|
||||||
first root-level `static/` in the repo, and the built tree confirms it unions
|
|
||||||
with the theme's rather than shadowing it — `public/css/style.css` and
|
|
||||||
`public/index.html` are byte-identical to the previous build and the static
|
|
||||||
file count goes 1 to 2. The live "before" was measured, not assumed:
|
|
||||||
Cloudflare already sends `X-Content-Type-Options` and `Referrer-Policy` by
|
|
||||||
default, so the substance here is `Strict-Transport-Security`,
|
|
||||||
`Content-Security-Policy`, `X-Frame-Options` and `Permissions-Policy`. The CSP
|
|
||||||
is `default-src 'none'` with `style-src 'unsafe-inline'`, which the built page
|
|
||||||
supports exactly: it has no script, img, link, iframe, form or media element
|
|
||||||
and no `style=`/`on*=` attribute, only the one inline `<style>` block
|
|
||||||
`baseof.html` fills by `readFile`. Verified in a headless Chrome against a
|
|
||||||
local server that parses the committed `_headers` and applies it as real
|
|
||||||
response headers: zero CSP violations, the inlined stylesheet parses to 17
|
|
||||||
rules and the computed body padding, tagline colour and link colour all come
|
|
||||||
from the theme CSS, framing the page from another origin is refused by
|
|
||||||
`frame-ancestors 'none'` (consistent with `X-Frame-Options: DENY`), and all
|
|
||||||
five named outbound links still navigate with status 200. HSTS carries neither
|
|
||||||
`preload` nor `includeSubDomains`: `www.lora.vegas` is the only other name in
|
|
||||||
DNS and it is served by this same Pages project, so this file sets HSTS on its
|
|
||||||
responses directly, and `includeSubDomains` would instead bind every future
|
|
||||||
subdomain for a year with no way to walk it back inside the max-age window
|
|
||||||
without also dropping the apex protection.
|
|
||||||
- 2026-08-09: restructured `README.md` into the canonical section set (closes
|
|
||||||
#11): a Description first line, then Getting Started, Entrypoints, Rationale,
|
|
||||||
Design, TODO, License, Author. The non-standard About / Contributing /
|
|
||||||
Technical Details headings are gone, but nothing they held was dropped — the
|
|
||||||
bullet list of what the site publishes moved under the Description, the
|
|
||||||
contribute contact and the local-preview instructions moved into Getting
|
|
||||||
Started. Getting Started was written against the current `Makefile` rather
|
|
||||||
than the old prose: there is no `make build` target, so the former "Build:
|
|
||||||
`hugo`" instruction is now `make test`, and the former "Local Development:
|
|
||||||
`hugo server`" is `make setup` then `make serve`. Two stale claims fixed: the
|
|
||||||
site is deployed by Gitea Actions to Cloudflare Pages, not "GitHub Actions",
|
|
||||||
and the Entrypoints bullet for `script/fmt` still described the
|
|
||||||
top-level-markdown-only scope that #12 replaced with `'**/*.md'` and
|
|
||||||
`'**/*.css'`. The License section body is deliberately untouched — it is owned
|
|
||||||
by #10, which is blocked on the owner's choice of license, and the Description
|
|
||||||
sentence is likewise missing the license clause the policy calls for until #10
|
|
||||||
lands. Design section claims were verified against the tree, not assumed
|
|
||||||
- 2026-08-09: widened the prettier gate from top-level markdown to `'**/*.md'`
|
|
||||||
and `'**/*.css'` (closes #12). `themes/loravega/static/css/style.css` was
|
|
||||||
never formatted or gated even though it is inlined into every page; it is now
|
|
||||||
both, and the reformat landed as its own commit ahead of the script change so
|
|
||||||
no commit in the branch is red. `content/` is excluded in `.prettierignore`,
|
|
||||||
and that exclusion is measured rather than assumed: with `content/` in scope,
|
|
||||||
prettier re-wrapped one list item in `content/_index.md` and the rendered
|
|
||||||
`public/index.html` changed with it (the wrap became a literal newline between
|
|
||||||
`7 PM at` and the following `<a>`). HTML collapses that newline to a space so
|
|
||||||
the page looks identical, but the published bytes are not, and this content
|
|
||||||
carries raw HTML that goldmark passes through verbatim under `unsafe = true`.
|
|
||||||
`themes/loravega/layouts/` is excluded too, with the reason recorded: those
|
|
||||||
files are Go templates, not HTML, and prettier has no parser for `{{ ... }}` —
|
|
||||||
covering them would need a plugin and therefore a `package.json`. Verified by
|
|
||||||
extracting `public/` from the built image before and after: with the final
|
|
||||||
scope, `index.html`, `index.xml` and `sitemap.xml` are byte-identical and only
|
|
||||||
the verbatim-copied `public/css/style.css` changes, in whitespace only — the
|
|
||||||
minified `<style>` block inlined into `index.html` is unchanged, which is the
|
|
||||||
direct evidence that CSS formatting cannot reach the rendered page
|
|
||||||
- 2026-08-09: added the canonical policy dotfiles and hardened both ignore files
|
|
||||||
(closes #8). `REPO_POLICIES.md` is a byte-identical copy of the canonical
|
|
||||||
`prompts` file, front matter intact; `.editorconfig`, `.prettierrc` and
|
|
||||||
`.prettierignore` are the canonical contents. `.gitignore` keeps its three
|
|
||||||
Hugo lines and gains the OS/editor/node/secrets block plus `.claude/`, so a
|
|
||||||
clean checkout with agent tooling present is `git status`-clean and a stray
|
|
||||||
key or `.env` can no longer be committed. `.dockerignore` gained the same
|
|
||||||
coverage but **not** the same syntax: it matches with Go's `filepath.Match`
|
|
||||||
rules extended with `**`, where `*` does not cross `/` and an unprefixed
|
|
||||||
pattern is anchored at the context root, so every depth-independent pattern
|
|
||||||
carries an explicit `**/` prefix and only the genuinely root-anchored entries
|
|
||||||
(`.git`, `public`, `resources`, `.hugo_build.lock`) go bare. Verified by
|
|
||||||
planting `.env`, `*.key`, `*.pem` and `node_modules` two directories deep: the
|
|
||||||
unprefixed form shipped all of them into the image and the `**/` form ships
|
|
||||||
none. Excluding `.claude/` also takes `worktrees/` — entire additional
|
|
||||||
checkouts of this repo — out of the build context; #23's two-consecutive-run
|
|
||||||
proof was re-run against the smaller context, since that issue was validated
|
|
||||||
against the old one. Note the canonical upstream `REPO_POLICIES.md` is not
|
|
||||||
clean under this repo's prettier settings, so the reformat is a separate
|
|
||||||
follow-up commit rather than churn mixed into this one
|
|
||||||
- 2026-08-09: stopped `script/cibuild` reporting a green it never earned (closes
|
|
||||||
#23). `COPY . .` is keyed on content, so on an unchanged tree Docker served
|
|
||||||
`RUN make check` from cache: the checks never executed and the build still
|
|
||||||
exited 0. Three separate reviewers had already been fooled by it here. The
|
|
||||||
`Dockerfile` now declares `ARG CHECK_EPOCH` below `COPY . .` with no default
|
|
||||||
(a default is a constant, and a constant is a stable cache key), guards it
|
|
||||||
with `RUN [ -n "$CHECK_EPOCH" ] || exit 1`, and expands it into the check
|
|
||||||
command; `script/cibuild` and `script/docker` both pass
|
|
||||||
`epoch="$(date +%s%N)$$"` — assigned on its own line, because a failing
|
|
||||||
command substitution inside an argument does not trip `set -e`, and with `$$`
|
|
||||||
because busybox `date` drops `%N` silently. This is the canonical shape
|
|
||||||
settled upstream in `prompts` #26, which has not merged there yet, so it may
|
|
||||||
need re-syncing. Verified with two consecutive runs on an unchanged tree that
|
|
||||||
both executed the checks while `RUN script/bootstrap` stayed `CACHED`, a
|
|
||||||
constant-epoch counterfactual that restored the false green, and a planted
|
|
||||||
prettier failure that failed the build
|
|
||||||
- 2026-08-09: disabled the unused `taxonomy` and `term` page kinds in
|
|
||||||
`hugo.toml` (closes #13). Hugo enables the `tags` and `categories` taxonomies
|
|
||||||
by default; this single-page site has no taxonomy terms and no taxonomy
|
|
||||||
templates, so every build emitted
|
|
||||||
`WARN found no layout file for "html" for kind "taxonomy"` and generated
|
|
||||||
`categories/index.xml` and `tags/index.xml` that nothing links to. Re-verified
|
|
||||||
the warning still occurs on the now-pinned hugo v0.164.0 rather than trusting
|
|
||||||
the issue's text, which predates the version move. `make test` and `make lint`
|
|
||||||
are now `WARN`-free, so the build's noise floor is zero and the next warning
|
|
||||||
will be visible. `public/` is otherwise byte-identical — `index.html`,
|
|
||||||
`css/style.css` and the RSS `index.xml` all unchanged — and `sitemap.xml` is
|
|
||||||
still generated, now listing only the home page instead of two taxonomy URLs
|
|
||||||
- 2026-08-09: replaced `hugo.toml`'s deprecated `languageCode` key with `locale`
|
|
||||||
(closes #18). Hugo deprecated `languageCode` in v0.158.0, so the Hugo pinned
|
|
||||||
in the preceding commit warns about it; left alone it would become a third
|
|
||||||
routinely-ignored warning, and a latent breakage when the key is removed.
|
|
||||||
Deliberately sequenced **after** the Hugo version move and in the same branch:
|
|
||||||
under the apk hugo 0.139.0 that CI ran until now, `locale` is an unknown key
|
|
||||||
that is silently ignored, which downgrades the generated RSS from
|
|
||||||
`<language>en-us</language>` to `<language>en</language>` with no warning and
|
|
||||||
exit 0. Verified on hugo v0.164.0 that the RSS `<language>` still reads
|
|
||||||
`en-us`, the `lang` attribute is unchanged, and `public/` is byte-identical to
|
|
||||||
the preceding commit's output
|
|
||||||
- 2026-08-09: installed Hugo at a deliberate, hash-verified version instead of
|
|
||||||
taking whatever alpine ships (closes #26). `script/bootstrap` no longer does
|
|
||||||
`pkg_install hugo`; it installs `github.com/gohugoio/hugo@v0.164.0` with
|
|
||||||
`go install`, which verifies the module against `sum.golang.org`. The version
|
|
||||||
is a commented constant, as is the Go toolchain (`go1.26.5`) — hugo v0.164.0
|
|
||||||
requires go >= 1.26.0 and alpine 3.21 ships go 1.23.9 with
|
|
||||||
`GOTOOLCHAIN=local`, so a bare `go install` refuses to run. `CGO_ENABLED=0` is
|
|
||||||
deliberate: standard Hugo, not extended, because this site has no SCSS, no
|
|
||||||
`resources.ToCSS`, no PostCSS and no image processing. This moves the build
|
|
||||||
off apk's hugo 0.139.0, about two years behind, onto the current stable.
|
|
||||||
Rendered output across the whole `public/` tree is unchanged except the
|
|
||||||
`meta name=generator` version string
|
|
||||||
- 2026-08-09: made `script/check` run `script/lint` (closes #9). It previously
|
|
||||||
ran only `fmt-check` then `test`, so `script/lint` executed nowhere — not in
|
|
||||||
`make check`, not in the pre-commit hook, and not in CI, even though the
|
|
||||||
`Dockerfile` runs `make check` and `script/cibuild` builds it. It now runs
|
|
||||||
`test`, `lint`, `fmt-check` in the canonical order, so the
|
|
||||||
`hugo --printPathWarnings` render-target-collision signal is no longer
|
|
||||||
discarded. `README.md`'s Entrypoints line was corrected to match
|
|
||||||
- 2026-08-09: hash-pinned every external reference in
|
|
||||||
`.gitea/workflows/deploy.yml` (closes #7): both job container images are
|
|
||||||
pinned by digest, all three `uses:` are pinned by 40-hex commit SHA, and the
|
|
||||||
wrangler install is pinned to an exact version. The abandoned
|
|
||||||
`klakegg/hugo:ext-alpine` image is gone: the build job now runs on the same
|
|
||||||
pinned `alpine` digest the `Dockerfile` uses, with a pre-checkout
|
|
||||||
`apk add nodejs git tar` step (the Actions runner needs `node` inside the job
|
|
||||||
container to execute JavaScript actions), an explicit `shell: sh` default,
|
|
||||||
then `script/bootstrap` and `script/test`. The `deploy` job is guarded with
|
|
||||||
`if: github.ref_name == 'main'` so it can never publish from a branch. Also
|
|
||||||
dropped the dead `feat/initial-site` push trigger and reindented the file to
|
|
||||||
4-space YAML to match `check.yml`. This is the second attempt; the first broke
|
|
||||||
the deploy and was reverted, so this one was verified by temporarily
|
|
||||||
triggering the workflow on the PR branch and iterating until the `build` job
|
|
||||||
ran green for real
|
|
||||||
- 2026-07-25: added the scripts-to-rule-them-all scaffold (closes #4): `script/`
|
|
||||||
entrypoints, `Makefile` shims, a Hugo `Dockerfile` (sha256-pinned alpine) plus
|
|
||||||
`.dockerignore` that runs `make check`, `.gitea/workflows/check.yml` running
|
|
||||||
`script/cibuild`, and a README Entrypoints section. `test`/`lint` are a clean
|
|
||||||
`hugo --minify` build; `fmt`/`fmt-check` run prettier over the repo's own
|
|
||||||
top-level markdown only
|
|
||||||
- 2026-02-10: design pass: minimal light theme with inline CSS, grey wells for
|
|
||||||
mesh channels and signal groups, horizontal overflow fix, body width tuning,
|
|
||||||
map link update
|
|
||||||
- 2026-02-10: added README and footer contribute link
|
|
||||||
- 2026-02-10: added Gitea workflow that builds the site and deploys to
|
|
||||||
Cloudflare Pages
|
|
||||||
- 2026-02-08: initial Hugo static site for lora.vegas
|
|
||||||
|
|
||||||
# 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 (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`.
|
|
||||||
Cloudflare Pages silently ignores a malformed `_headers`, and checking
|
|
||||||
`x-content-type-options` would pass either way because the edge sends it
|
|
||||||
regardless. `www` has to be checked too and not just the apex: dropping
|
|
||||||
`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
|
|
||||||
(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
|
|
||||||
(https://git.eeqj.de/sneak/lora.vegas/issues/14)
|
|
||||||
- Verify the Cloudflare Pages deploy still works after the workflow changes
|
|
||||||
10
hugo.toml
10
hugo.toml
@@ -1,16 +1,8 @@
|
|||||||
baseURL = 'https://lora.vegas/'
|
baseURL = 'https://lora.vegas/'
|
||||||
locale = 'en-us'
|
languageCode = 'en-us'
|
||||||
title = 'LoRa Vegas — Las Vegas Meshtastic Community'
|
title = 'LoRa Vegas — Las Vegas Meshtastic Community'
|
||||||
theme = 'loravega'
|
theme = 'loravega'
|
||||||
|
|
||||||
# This is a single-page site with no taxonomy terms and no taxonomy
|
|
||||||
# templates. Hugo enables the `tags` and `categories` taxonomies by
|
|
||||||
# default, so without this it generates taxonomy list pages it has no
|
|
||||||
# layout for and warns on every build. The `sitemap` kind is left
|
|
||||||
# enabled: sitemap.xml is still generated, and now lists only the home
|
|
||||||
# page.
|
|
||||||
disableKinds = ['taxonomy', 'term']
|
|
||||||
|
|
||||||
[markup.goldmark.renderer]
|
[markup.goldmark.renderer]
|
||||||
unsafe = true
|
unsafe = true
|
||||||
|
|
||||||
|
|||||||
175
script/bootstrap
175
script/bootstrap
@@ -1,175 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# script/bootstrap: install all dependencies needed to build and develop
|
|
||||||
# this Hugo site, idempotently. Base tooling comes from nix, apt, brew,
|
|
||||||
# or apk (detected in that order); assumes NOTHING is present (not git,
|
|
||||||
# make, go, hugo, or node). Installs hugo (the site build, at the exact
|
|
||||||
# version pinned below) and node/npm (prettier, used to format the
|
|
||||||
# repo's own markdown docs). Every install is guarded by a check so
|
|
||||||
# already-installed tools are skipped.
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
|
||||||
|
|
||||||
PKGMGR=""
|
|
||||||
SUDO=""
|
|
||||||
|
|
||||||
# --- Hugo -------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# Hugo produces the published artifact, so its version is a property of
|
|
||||||
# the site's output, not of the build environment. It is therefore
|
|
||||||
# pinned here rather than taken from whatever the distro serves: before
|
|
||||||
# this, alpine 3.21's apk supplied hugo 0.139.0 -- a version chosen by
|
|
||||||
# nobody, roughly two years behind upstream, and liable to change
|
|
||||||
# silently whenever the base image digest moves.
|
|
||||||
#
|
|
||||||
# `go install` is the hash-verified mechanism: Go checks the module
|
|
||||||
# against the sum.golang.org checksum database. That is the mechanism
|
|
||||||
# REPO_POLICIES.md already names for Go, and it needs no hand-maintained
|
|
||||||
# sha256. The other tools this script installs stay on the package
|
|
||||||
# manager, which #19 settled is fine for build-time conveniences.
|
|
||||||
#
|
|
||||||
# hugo v0.164.0, 2026-07-06
|
|
||||||
HUGO_VERSION="v0.164.0"
|
|
||||||
|
|
||||||
# hugo v0.164.0's go.mod requires go >= 1.26.0, and alpine 3.21's `go`
|
|
||||||
# package is 1.23.9 built with GOTOOLCHAIN=local, so a bare `go install`
|
|
||||||
# refuses to run at all. Naming the toolchain explicitly makes Go fetch
|
|
||||||
# it through the module proxy and verify it against sum.golang.org like
|
|
||||||
# any other module, so the chain stays hash-verified end to end -- and
|
|
||||||
# the Go version that compiles hugo becomes deliberate too, instead of
|
|
||||||
# being inherited from whatever the base image happens to ship.
|
|
||||||
# go1.26.5, 2026-08-09
|
|
||||||
HUGO_GOTOOLCHAIN="go1.26.5"
|
|
||||||
|
|
||||||
# Standard hugo, not hugo extended: CGO_ENABLED=0 is deliberate.
|
|
||||||
# Verified that this site uses nothing extended provides -- there are no
|
|
||||||
# .scss/.sass files, no resources.ToCSS, no PostCSS, and no image
|
|
||||||
# processing (.Resize/.Fill/.Fit/images.* are all absent). The CSS is
|
|
||||||
# plain and inlined by `readFile` in baseof.html. The `+extended` on the
|
|
||||||
# apk build this replaces was incidental, not a requirement, so do not
|
|
||||||
# assume a future change needs it without rechecking the above.
|
|
||||||
HUGO_CGO_ENABLED="0"
|
|
||||||
|
|
||||||
# Where the hugo binary lands. It has to be on the default PATH of a
|
|
||||||
# *fresh* shell, not just of this script: the Dockerfile's `RUN make
|
|
||||||
# check` and deploy.yml's `script/test` step each start their own shell
|
|
||||||
# and would never see a GOPATH bin directory. Overridable so an
|
|
||||||
# unprivileged install can point somewhere writable.
|
|
||||||
HUGO_BIN_DIR="${HUGO_BIN_DIR:-/usr/local/bin}"
|
|
||||||
|
|
||||||
detect_pkgmgr() {
|
|
||||||
[ -n "$PKGMGR" ] && return 0
|
|
||||||
if command -v nix-env >/dev/null 2>&1; then
|
|
||||||
PKGMGR="nix"
|
|
||||||
elif command -v apt-get >/dev/null 2>&1; then
|
|
||||||
PKGMGR="apt"
|
|
||||||
elif command -v brew >/dev/null 2>&1; then
|
|
||||||
PKGMGR="brew"
|
|
||||||
elif command -v apk >/dev/null 2>&1; then
|
|
||||||
PKGMGR="apk"
|
|
||||||
else
|
|
||||||
echo "bootstrap: no supported package manager (nix, apt, brew, apk)" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ "$PKGMGR" = "apt" ]; then
|
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
|
||||||
detect_sudo
|
|
||||||
$SUDO env DEBIAN_FRONTEND=noninteractive apt-get update
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
detect_sudo() {
|
|
||||||
if [ -z "$SUDO" ] && [ "$(id -u)" != "0" ]; then
|
|
||||||
SUDO="sudo"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# pkg_install <nix-attr> <apt-pkg> <brew-formula> <apk-pkg>
|
|
||||||
pkg_install() {
|
|
||||||
detect_pkgmgr
|
|
||||||
case "$PKGMGR" in
|
|
||||||
nix) nix-env -iA "nixpkgs.$1" ;;
|
|
||||||
apt) $SUDO env DEBIAN_FRONTEND=noninteractive apt-get install -y "$2" ;;
|
|
||||||
brew) brew install "$3" ;;
|
|
||||||
apk) apk add --no-cache "$4" ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
missing() {
|
|
||||||
! command -v "$1" >/dev/null 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
# True when the hugo already on PATH is the pinned version. Unlike the
|
|
||||||
# other tools, mere presence is not good enough here: an older hugo has
|
|
||||||
# to be replaced, not accepted, or the pin means nothing.
|
|
||||||
hugo_pinned() {
|
|
||||||
command -v hugo >/dev/null 2>&1 || return 1
|
|
||||||
# `hugo version` prints e.g. "hugo v0.164.0 linux/amd64 ..." for a
|
|
||||||
# `go install` build, or "hugo v0.164.0-ce2470e+extended ..." for an
|
|
||||||
# official release binary. Compare only the vX.Y.Z part: a build of
|
|
||||||
# the same version that happens to be extended renders this site
|
|
||||||
# identically (see HUGO_CGO_ENABLED above), so there is no reason to
|
|
||||||
# overwrite a developer's existing matching install.
|
|
||||||
have="$(hugo version 2>/dev/null | awk '{print $2}' | sed 's/[-+].*//')"
|
|
||||||
[ "$have" = "$HUGO_VERSION" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
install_hugo() {
|
|
||||||
detect_sudo
|
|
||||||
|
|
||||||
# The Go toolchain is a build-time convenience like git and make, so
|
|
||||||
# it comes from the package manager; the thing that must be
|
|
||||||
# deliberate is what it builds, which HUGO_VERSION and
|
|
||||||
# HUGO_GOTOOLCHAIN pin.
|
|
||||||
if missing go; then pkg_install go golang-go go go; fi
|
|
||||||
|
|
||||||
# Build as the invoking user into a scratch GOBIN, then place the
|
|
||||||
# binary with `install`. Running the whole `go install` under sudo
|
|
||||||
# would work but would populate root's module cache instead of the
|
|
||||||
# user's, which is needlessly slow and surprising on a workstation.
|
|
||||||
gobin="$(mktemp -d)"
|
|
||||||
CGO_ENABLED="$HUGO_CGO_ENABLED" \
|
|
||||||
GOTOOLCHAIN="$HUGO_GOTOOLCHAIN" \
|
|
||||||
GOBIN="$gobin" \
|
|
||||||
go install "github.com/gohugoio/hugo@${HUGO_VERSION}"
|
|
||||||
$SUDO install -d "$HUGO_BIN_DIR"
|
|
||||||
$SUDO install -m 0755 "$gobin/hugo" "$HUGO_BIN_DIR/hugo"
|
|
||||||
rm -rf "$gobin"
|
|
||||||
|
|
||||||
# Drop any cached PATH lookup of the hugo we just replaced, so the
|
|
||||||
# check below tests the new binary and not the old one.
|
|
||||||
hash -r 2>/dev/null || true
|
|
||||||
|
|
||||||
# Fail loudly rather than let a later build run on a shadowing hugo
|
|
||||||
# from somewhere earlier in PATH.
|
|
||||||
if ! hugo_pinned; then
|
|
||||||
echo "bootstrap: installed $HUGO_VERSION into $HUGO_BIN_DIR but" \
|
|
||||||
"'hugo' on PATH is still $(hugo version 2>/dev/null || echo absent)" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
main() {
|
|
||||||
cd "$ROOT"
|
|
||||||
|
|
||||||
# Base tooling.
|
|
||||||
if missing git; then pkg_install git git git git; fi
|
|
||||||
if missing make; then pkg_install gnumake make make make; fi
|
|
||||||
|
|
||||||
# The theme is vendored in-repo, but initialise submodules if any
|
|
||||||
# are ever added so a fresh clone is buildable.
|
|
||||||
if [ -f .gitmodules ]; then
|
|
||||||
git submodule update --init --recursive
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Site build. Pinned and hash-verified -- see the HUGO_* constants.
|
|
||||||
if ! hugo_pinned; then install_hugo; fi
|
|
||||||
|
|
||||||
# node/npm provide prettier (via npx) for formatting the docs.
|
|
||||||
if missing node; then pkg_install nodejs nodejs node nodejs; fi
|
|
||||||
if missing npx; then pkg_install nodejs npm npm npm; fi
|
|
||||||
|
|
||||||
echo "bootstrap complete"
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
21
script/check
21
script/check
@@ -1,21 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# script/check: run all checks. Our own extension to
|
|
||||||
# scripts-to-rule-them-all. Must not modify any tracked files. Runs the
|
|
||||||
# canonical order: the clean production build, then the lint build that
|
|
||||||
# reports path warnings, then the read-only formatting check.
|
|
||||||
#
|
|
||||||
# The last two run inside Docker (they build stages of Dockerfile.lint),
|
|
||||||
# so this script needs a working docker daemon. That is deliberate:
|
|
||||||
# every lint run for this repo happens in a container, and there is no
|
|
||||||
# host fallback to drop back to.
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
|
||||||
|
|
||||||
main() {
|
|
||||||
"$SCRIPT_DIR/test"
|
|
||||||
"$SCRIPT_DIR/lint"
|
|
||||||
"$SCRIPT_DIR/fmt-check"
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# script/cibuild: run the CI build. The Gitea workflow runs this on
|
|
||||||
# push, and it is the single entrypoint that covers everything:
|
|
||||||
#
|
|
||||||
# 1. the main Dockerfile, which runs the clean `hugo --minify`
|
|
||||||
# production build (`make test`)
|
|
||||||
# 2. script/lint, which builds Dockerfile.lint's `lint` stage
|
|
||||||
# 3. script/fmt-check, which builds Dockerfile.lint's `fmt-check`
|
|
||||||
# stage
|
|
||||||
#
|
|
||||||
# Steps 2 and 3 are delegated to the same scripts a developer runs, so
|
|
||||||
# CI cannot drift from `make check`. They are separate builds rather
|
|
||||||
# than a `RUN make check` inside the main image because script/lint is
|
|
||||||
# itself a `docker build`: shelling back into `make check` from an image
|
|
||||||
# would be docker-in-docker inside a bare alpine with no docker client
|
|
||||||
# and no daemon socket. See Dockerfile.lint for the full reasoning.
|
|
||||||
#
|
|
||||||
# The main image build below only implies a passing production build
|
|
||||||
# because of CHECK_EPOCH. Docker keys the `RUN make test` layer on
|
|
||||||
# content, so on an unchanged tree it is served from cache: the build
|
|
||||||
# never executes and the image build still exits 0. Passing a value that
|
|
||||||
# differs on every invocation invalidates that layer and everything
|
|
||||||
# below it, while the script/bootstrap toolchain layer above it keeps
|
|
||||||
# caching. script/lint and script/fmt-check each do the same for their
|
|
||||||
# own stage.
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
|
||||||
ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
|
|
||||||
|
|
||||||
main() {
|
|
||||||
cd "$ROOT"
|
|
||||||
# Assigned to a variable rather than substituted inline in the
|
|
||||||
# argument list: a command substitution that fails inside an
|
|
||||||
# argument does not trip `set -e`, so the inline form would quietly
|
|
||||||
# pass an empty string and restore the cached false green. As the
|
|
||||||
# whole of an assignment its exit status is the command's, so
|
|
||||||
# `set -e` catches it. `%N` keeps two invocations within the same
|
|
||||||
# second distinct; busybox date silently drops `%N` and still exits
|
|
||||||
# 0, so `$$` is appended to cover that degradation.
|
|
||||||
epoch="$(date +%s%N)$$"
|
|
||||||
docker build --build-arg CHECK_EPOCH="$epoch" .
|
|
||||||
|
|
||||||
"$SCRIPT_DIR/lint"
|
|
||||||
"$SCRIPT_DIR/fmt-check"
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# script/docker: build the Docker image tagged with the project name.
|
|
||||||
# The tag comes from script/projectname.
|
|
||||||
#
|
|
||||||
# The Dockerfile requires the CHECK_EPOCH build argument, generated here
|
|
||||||
# exactly as script/cibuild generates it: see that script for why the
|
|
||||||
# check layer must not be allowed to cache, and why the value is built
|
|
||||||
# in an assignment rather than inline.
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
|
||||||
ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
|
|
||||||
|
|
||||||
main() {
|
|
||||||
cd "$ROOT"
|
|
||||||
epoch="$(date +%s%N)$$"
|
|
||||||
docker build --build-arg CHECK_EPOCH="$epoch" \
|
|
||||||
-t "$("$SCRIPT_DIR/projectname")" .
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
27
script/fmt
27
script/fmt
@@ -1,27 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# script/fmt: format this repo's markdown and CSS with prettier, using
|
|
||||||
# our standard settings. Scope is every markdown and CSS file in the
|
|
||||||
# tree, per REPO_POLICIES.md ("prettier for JS/CSS/Markdown/HTML").
|
|
||||||
# What is deliberately NOT covered is recorded in .prettierignore, with
|
|
||||||
# the reason next to each entry: the Hugo layout templates, which are
|
|
||||||
# Go templates and not HTML, and content/, whose reformatting was
|
|
||||||
# measured to change the rendered page.
|
|
||||||
#
|
|
||||||
# This runs on the host, unlike the read-only check: it rewrites the
|
|
||||||
# working tree, which a container build cannot do. It is therefore the
|
|
||||||
# authoritative copy of the prettier version, scope and flags -- the
|
|
||||||
# fmt-check stage of Dockerfile.lint duplicates them and must be kept in
|
|
||||||
# sync with this file.
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
|
||||||
|
|
||||||
PRETTIER_VERSION="3.4.2"
|
|
||||||
|
|
||||||
main() {
|
|
||||||
cd "$ROOT"
|
|
||||||
npx --yes "prettier@${PRETTIER_VERSION}" --write \
|
|
||||||
'**/*.md' '**/*.css' --tab-width 4 --prose-wrap always
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# script/fmt-check: check the formatting of this repo's markdown and
|
|
||||||
# CSS (read-only). Like script/lint, it runs only in Docker: it builds
|
|
||||||
# the `fmt-check` stage of Dockerfile.lint, where prettier runs as a
|
|
||||||
# build step. Leaving prettier to run on the host here would have left
|
|
||||||
# `make check` with a host lint path, which is the thing being removed.
|
|
||||||
#
|
|
||||||
# The version, scope and flags live in Dockerfile.lint and must stay in
|
|
||||||
# sync with script/fmt, which is the authoritative copy and stays on the
|
|
||||||
# host because it writes to the working tree.
|
|
||||||
#
|
|
||||||
# Dockerfile.lint requires the CHECK_EPOCH build argument, generated
|
|
||||||
# here exactly as script/cibuild generates it -- see that script for why
|
|
||||||
# the check layer must not be allowed to cache, and why the value is
|
|
||||||
# built in an assignment rather than inline.
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
|
||||||
|
|
||||||
main() {
|
|
||||||
cd "$ROOT"
|
|
||||||
epoch="$(date +%s%N)$$"
|
|
||||||
docker build -f Dockerfile.lint --target fmt-check \
|
|
||||||
--build-arg CHECK_EPOCH="$epoch" .
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# script/install-precommit: install the git pre-commit hook that runs
|
|
||||||
# script/precommit. Our own extension to scripts-to-rule-them-all.
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
|
||||||
|
|
||||||
main() {
|
|
||||||
cd "$ROOT"
|
|
||||||
printf '#!/bin/sh\nset -e\nscript/precommit\n' > .git/hooks/pre-commit
|
|
||||||
chmod +x .git/hooks/pre-commit
|
|
||||||
echo "pre-commit hook installed: runs script/precommit"
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
26
script/lint
26
script/lint
@@ -1,26 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# script/lint: run the lint. This Hugo site has no dedicated linter, so
|
|
||||||
# the lint gate is a clean build that surfaces broken internal links and
|
|
||||||
# template path problems -- but where it runs is not negotiable: every
|
|
||||||
# lint run happens inside a Docker container, so this script does
|
|
||||||
# nothing except build the `lint` stage of Dockerfile.lint. The check is
|
|
||||||
# a build step there, so a successful build is a clean lint. There is
|
|
||||||
# deliberately no host fallback and no "already inside a container?"
|
|
||||||
# branch: either would be a host lint path wearing a disguise.
|
|
||||||
#
|
|
||||||
# Dockerfile.lint requires the CHECK_EPOCH build argument, generated
|
|
||||||
# here exactly as script/cibuild generates it -- see that script for why
|
|
||||||
# the check layer must not be allowed to cache, and why the value is
|
|
||||||
# built in an assignment rather than inline.
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
|
||||||
|
|
||||||
main() {
|
|
||||||
cd "$ROOT"
|
|
||||||
epoch="$(date +%s%N)$$"
|
|
||||||
docker build -f Dockerfile.lint --target lint \
|
|
||||||
--build-arg CHECK_EPOCH="$epoch" .
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# script/precommit: run by the git pre-commit hook; fails the commit if
|
|
||||||
# checks fail. Our own extension to scripts-to-rule-them-all.
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
|
||||||
|
|
||||||
main() {
|
|
||||||
"$SCRIPT_DIR/check"
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# script/projectname: output the name of this project. Our own
|
|
||||||
# extension to scripts-to-rule-them-all. Other scripts that need the
|
|
||||||
# name (e.g. script/docker) call this, so they can stay identical
|
|
||||||
# across all repos.
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
main() {
|
|
||||||
echo "lora.vegas"
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
13
script/setup
13
script/setup
@@ -1,13 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# script/setup: set up the repo for development after a fresh clone:
|
|
||||||
# installs dependencies (script/bootstrap) and the git pre-commit hook.
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
|
||||||
|
|
||||||
main() {
|
|
||||||
"$SCRIPT_DIR/bootstrap"
|
|
||||||
"$SCRIPT_DIR/install-precommit"
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
14
script/test
14
script/test
@@ -1,14 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# script/test: the correctness check for this static site is a clean
|
|
||||||
# production build. `hugo --minify` exits non-zero on any template,
|
|
||||||
# content, or config error, so a green build is a passing test.
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
|
||||||
|
|
||||||
main() {
|
|
||||||
cd "$ROOT"
|
|
||||||
hugo --minify
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
# Cloudflare Pages response headers.
|
|
||||||
#
|
|
||||||
# Hugo copies static/ verbatim into public/, so this file lands at the
|
|
||||||
# deploy output root, which is where Pages reads it from. Pages consumes
|
|
||||||
# the file rather than publishing it. Values here override what
|
|
||||||
# Cloudflare would otherwise send.
|
|
||||||
#
|
|
||||||
# Every value below was checked against the built public/index.html, not
|
|
||||||
# copied from a template. That page loads nothing: no script, img, link,
|
|
||||||
# iframe, form, video, audio, object or embed element, no style= or on*=
|
|
||||||
# attribute. It has exactly one inline <style> block, which
|
|
||||||
# themes/loravega/layouts/_default/baseof.html fills with the whole of
|
|
||||||
# themes/loravega/static/css/style.css via readFile. That inlining is a
|
|
||||||
# deliberate theme design choice, and it is the sole reason style-src
|
|
||||||
# needs 'unsafe-inline'.
|
|
||||||
#
|
|
||||||
# img-src 'self' is kept even though the page has no images. Browsers
|
|
||||||
# request /favicon.ico unprompted and that fetch is governed by img-src;
|
|
||||||
# measured in Chrome, with this allowance the request is made and 404s,
|
|
||||||
# and without it the request is suppressed outright. Neither hurts
|
|
||||||
# today, but same-origin images are the one resource class this site
|
|
||||||
# would plausibly grow, and 'self' loosens nothing cross-origin.
|
|
||||||
#
|
|
||||||
# X-Content-Type-Options and Referrer-Policy are already sent by
|
|
||||||
# Cloudflare by default and are restated here on purpose. They are a
|
|
||||||
# default, not a guarantee, and this file is where the site's header
|
|
||||||
# posture is declared.
|
|
||||||
#
|
|
||||||
# Strict-Transport-Security deliberately carries neither preload nor
|
|
||||||
# includeSubDomains. preload is effectively irreversible and is the
|
|
||||||
# owner's call. includeSubDomains would bind every hostname under
|
|
||||||
# lora.vegas for a year, and it buys nothing today: www.lora.vegas is
|
|
||||||
# the only other name in DNS, it is served by this same Pages project,
|
|
||||||
# so this block sets HSTS on its responses directly.
|
|
||||||
|
|
||||||
/*
|
|
||||||
Strict-Transport-Security: max-age=31536000
|
|
||||||
X-Content-Type-Options: nosniff
|
|
||||||
Referrer-Policy: strict-origin-when-cross-origin
|
|
||||||
X-Frame-Options: DENY
|
|
||||||
Permissions-Policy: geolocation=(), microphone=(), camera=()
|
|
||||||
Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; img-src 'self'; form-action 'none'; frame-ancestors 'none'; base-uri 'none'
|
|
||||||
@@ -12,7 +12,6 @@
|
|||||||
<body>
|
<body>
|
||||||
{{ block "main" . }}{{ end }}
|
{{ block "main" . }}{{ end }}
|
||||||
<footer>
|
<footer>
|
||||||
<p>this site is a project by <a href="https://sneak.berlin">@sneak</a>.</p>
|
|
||||||
<p>lora.vegas — Las Vegas Meshtastic community <a href="https://git.eeqj.de/sneak/lora.vegas" class="contribute-link">[Contribute]</a></p>
|
<p>lora.vegas — Las Vegas Meshtastic community <a href="https://git.eeqj.de/sneak/lora.vegas" class="contribute-link">[Contribute]</a></p>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -6,15 +6,10 @@
|
|||||||
--max-w: 600px;
|
--max-w: 600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
"Helvetica Neue", Arial, sans-serif;
|
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
|
|||||||
Reference in New Issue
Block a user