Compare commits

...

8 Commits

Author SHA1 Message Date
74c28c1d71 Merge pull request '#17: Hash-pin every external reference in deploy.yml (closes #7)'
Some checks failed
check / check (push) Successful in 3s
Build and Deploy to Cloudflare Pages / build (push) Failing after 22s
Build and Deploy to Cloudflare Pages / deploy (push) Has been skipped
2026-08-09 04:28:21 +02:00
b157bfd52c Install runner prerequisites in the pinned build container (closes #7)
All checks were successful
check / check (push) Successful in 10s
Replacing klakegg/hugo:ext-alpine with the Dockerfile's pinned alpine
digest satisfied the pinning requirement but dropped the runtime the
Actions runner itself depends on, which would have broken the deploy:

- act_runner executes JavaScript actions with `node` inside the job
  container and does not inject one. Stock alpine has no node, so
  actions/checkout - the job's first step - would fail with
  "node: not found", and script/bootstrap (which installs node) is step
  2 and never runs. The build job fails, deploy is skipped for
  `needs: build`, and the site stops publishing.
- Steps default to `bash`, which stock alpine does not ship either.

Fixes, both scoped to keeping the mandated image replacement runnable:

- A pre-checkout inline `run:` step (`apk add --no-cache nodejs git tar`)
  installs what the runner needs before the first `uses:` step. An
  inline run needs only a shell, so it works on the bare image. git is
  there for checkout's `submodules: recursive`; without it checkout
  degrades to a tarball download that cannot do submodules.
- `defaults.run.shell: sh` on the build job, so the shell is stated
  rather than left to a bash-to-sh fallback.

No pinned value is touched. The 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.

Also moves each version/date comment to sit directly above the pinned
line rather than above the step's `- name:`, matching check.yml, and
dates the actions/checkout pin 2026-02-28 as check.yml already does for
the same SHA.

Verified by running the build job's step sequence inside the pinned
alpine digest: bare, `node` and `bash` are absent and the pinned
checkout bundle dies with "node: not found"; after the new apk step,
node 22.23.2, git 2.47.3 and GNU tar 1.35 are present, that same
checkout bundle runs under node and gets as far as "GITHUB_WORKSPACE not
defined", and script/bootstrap, script/test and the tar step all
complete. make check and script/cibuild (with the build cache pruned, so
nothing was CACHED) are green.
2026-08-09 02:15:44 +00:00
3f91a7c273 Hash-pin every external reference in deploy.yml (closes #7)
All checks were successful
check / check (push) Successful in 7s
deploy.yml was the last file in the repo carrying mutable external
references. Every image is now pinned by digest and every action by a
full 40-hex commit SHA, each with a version/date comment on the line
above. All values were resolved from upstream and verified to resolve.

- build container: klakegg/hugo:ext-alpine (abandoned since 2021,
  mutable tag) replaced by the exact alpine 3.21 digest the Dockerfile
  already pins, with script/bootstrap to install hugo and script/test
  to build. One pinned base and one dependency list now serve both the
  check build and the deploy build.
- deploy container: node:20 -> node@sha256:8f693eaa... (node 20.20.2,
  bookworm).
- actions/checkout: v4 -> 11bd7190... (v4.2.2), the same SHA check.yml
  pins, so the two workflows agree.
- actions/upload-artifact: v3 -> ea165f8d... (v4.6.2); v3 is deprecated.
- actions/download-artifact: v3 -> d3f86a10... (v4.3.0); v3 is
  deprecated.
- npm install -g wrangler -> wrangler@4.120.0, so the deploy no longer
  executes whatever the wrangler tag happens to point at.

Also drops the dead feat/initial-site push trigger (that branch is fully
merged into main) and reindents the file to 4-space YAML to match
check.yml and .editorconfig.

The two jobs are deliberately left separate so a deploy regression can
be attributed unambiguously.

Verified: make check and script/cibuild both green; the workflow parses
as YAML with the expected job/step structure. The Cloudflare Pages
deploy path itself cannot be exercised from a branch (it runs only on
push to main and needs CLOUDFLARE_API_TOKEN), so the deploy run on main
must be watched after merge.
2026-08-09 01:49:21 +00:00
7cad989724 Add scripts-to-rule-them-all scaffold (closes #4)
All checks were successful
check / check (push) Successful in 4s
Build and Deploy to Cloudflare Pages / build (push) Successful in 5s
Build and Deploy to Cloudflare Pages / deploy (push) Successful in 18s
Adopt the Scripts to Rule Them All standard for this Hugo site:

- script/ POSIX-sh entrypoints (bootstrap, setup, projectname, test,
  lint, fmt, fmt-check, check, docker, cibuild, precommit,
  install-precommit). The correctness check (test/lint) is a clean
  `hugo --minify` production build; fmt/fmt-check run prettier over the
  repo's own top-level markdown only, leaving content/ untouched.
- Makefile targets reduced to thin shims that call script/NAME, plus a
  convenience serve target for `hugo server`.
- Dockerfile on a sha256-pinned alpine base that installs deps via
  script/bootstrap and runs `make check`, so the image build fails on
  any formatting or Hugo build error; .dockerignore added.
- .gitea/workflows/check.yml runs script/cibuild on push.
- README Entrypoints section documenting the scripts.
2026-07-25 18:22:52 +07:00
612d15587b Add standard Workflow section to TODO.md
All checks were successful
Build and Deploy to Cloudflare Pages / build (push) Successful in 5s
Build and Deploy to Cloudflare Pages / deploy (push) Successful in 19s
2026-07-06 21:06:42 +02:00
f1cab64bd4 Merge branch 'TODO'
All checks were successful
Build and Deploy to Cloudflare Pages / build (push) Successful in 7s
Build and Deploy to Cloudflare Pages / deploy (push) Successful in 22s
2026-07-06 20:51:15 +02:00
20c133ee61 Add TODO.md 2026-07-06 20:35:49 +02:00
f993d36f0c add contact link in footer
All checks were successful
Build and Deploy to Cloudflare Pages / build (push) Successful in 5s
Build and Deploy to Cloudflare Pages / deploy (push) Successful in 21s
2026-02-14 05:51:08 +01:00
20 changed files with 462 additions and 43 deletions

4
.dockerignore Normal file
View File

@@ -0,0 +1,4 @@
.git
public
resources
.hugo_build.lock

View File

@@ -0,0 +1,9 @@
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

View File

@@ -1,52 +1,83 @@
name: Build and Deploy to Cloudflare Pages 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:
image: klakegg/hugo:ext-alpine # Same digest the Dockerfile pins: one pinned base image and the
steps: # same dependency list (script/bootstrap) for both the check build
- name: Checkout # and the deploy build. The one extra thing this job needs on top
uses: actions/checkout@v4 # of the Dockerfile is the Actions runner's own prerequisites --
with: # see the first step.
submodules: recursive # 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:
# 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: Build site - name: Checkout
run: hugo --minify # actions/checkout v4.2.2, 2026-02-28
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
submodules: recursive
- name: Archive site - name: Install build dependencies
run: tar -czf site.tar.gz public run: script/bootstrap
- name: Upload artifact - name: Build site
uses: actions/upload-artifact@v3 run: script/test
with:
name: site
path: site.tar.gz
deploy: - name: Archive site
runs-on: ubuntu-latest run: tar -czf site.tar.gz public
needs: build
container:
image: node:20
steps:
- name: Download artifact
uses: actions/download-artifact@v3
with:
name: site
- name: Extract site - name: Upload artifact
run: tar -xzf site.tar.gz # actions/upload-artifact v4.6.2, 2026-08-09
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: site
path: site.tar.gz
- name: Install Wrangler deploy:
run: npm install -g wrangler runs-on: ubuntu-latest
needs: build
container:
# node 20.20.2-bookworm, 2026-08-09
image: node@sha256:8f693eaa7e0a8e71560c9a82b55fd54c2ae920a2ba5d2cde28bac7d1c01c9ba5
steps:
- name: Download artifact
# actions/download-artifact v4.3.0, 2026-08-09
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
with:
name: site
- name: Deploy to Cloudflare Pages - name: Extract site
run: wrangler pages deploy public --project-name=lora-vegas --branch=${{ github.ref_name }} run: tar -xzf site.tar.gz
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - name: Install Wrangler
# wrangler 4.120.0, 2026-08-09
run: npm install -g wrangler@4.120.0
- name: Deploy to Cloudflare Pages
run: wrangler pages deploy public --project-name=lora-vegas --branch=${{ github.ref_name }}
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

18
Dockerfile Normal file
View File

@@ -0,0 +1,18 @@
# Hugo static-site build image. The build runs `make check` (the
# read-only prettier docs check plus a clean `hugo --minify` production
# build), so the image build fails on any formatting or Hugo build
# error. This is what CI (script/cibuild) runs on every push.
# 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, hugo, node/npm).
COPY script/ script/
RUN script/bootstrap
COPY . .
# Run all checks - build fails if any check fails.
RUN make check

31
Makefile Normal file
View File

@@ -0,0 +1,31 @@
.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

View File

@@ -4,7 +4,8 @@ Las Vegas Meshtastic and LoRa community website.
## About ## About
This site provides information about the Las Vegas mesh networking community, including: This site provides information about the Las Vegas mesh networking community,
including:
- Mesh channel configurations - Mesh channel configurations
- Community coordination (Discord, Signal) - Community coordination (Discord, Signal)
@@ -13,11 +14,13 @@ This site provides information about the Las Vegas mesh networking community, in
## Contributing ## Contributing
To contribute to this site, contact **sneak@sneak.berlin** for git repository access. To contribute to this site, contact **sneak@sneak.berlin** for git repository
access.
## Technical Details ## Technical Details
This is a static site built with Hugo. The site is deployed automatically via GitHub Actions. This is a static site built with Hugo. The site is deployed automatically via
GitHub Actions.
### Local Development ### Local Development
@@ -35,6 +38,32 @@ hugo
Output will be in the `public/` directory. Output will be in the `public/` directory.
## 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, hugo,
node/npm) idempotently
- `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
- `script/fmt` — format the repo's own top-level markdown docs with prettier
- `script/fmt-check` — check that formatting (read-only)
- `script/check` — run `script/fmt-check` then `script/test`; modifies nothing
- `script/docker` — build the Docker image tagged with the project name
- `script/cibuild` — the CI build (`docker build .`); the Dockerfile runs
`make check`
- `script/install-precommit` — install the git pre-commit hook that runs
`script/check`
A convenience `make serve` target runs `hugo server` for local preview.
## License ## License
Content is provided as-is for community use. Content is provided as-is for community use.

62
TODO.md Normal file
View File

@@ -0,0 +1,62 @@
# 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`); still missing `LICENSE` and
policy files. Every external reference in the repo is now pinned by
cryptographic hash (or, for the wrangler CLI install, an exact version).
# Next Step
Add the remaining policy scaffold: `LICENSE`, `REPO_POLICIES.md`,
`.editorconfig`, and prettier config files (`.prettierrc`, `.prettierignore`).
Update `README.md` accordingly.
# Completed Steps
- 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
(`upload`/`download-artifact` moved v3 to v4), 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`. Also dropped the dead `feat/initial-site` push trigger and
reindented the file to 4-space YAML to match `check.yml`
- 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
- Rework README.md into the standard sections: Description, Getting Started,
Rationale, Design, TODO, License, Author (currently About, Contributing,
Technical Details, License)
- Replace the "content is provided as-is" README note with the text of the
committed LICENSE
- Expand .gitignore beyond Hugo outputs (OS and editor files)
- Verify the Cloudflare Pages deploy still works after the workflow changes
- Keep mesh channel and signal group listings current

76
script/bootstrap Executable file
View File

@@ -0,0 +1,76 @@
#!/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, hugo, or node). Installs hugo (the site build) 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=""
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
if [ "$(id -u)" != "0" ]; then
SUDO="sudo"
fi
$SUDO env DEBIAN_FRONTEND=noninteractive apt-get update
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
}
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.
if missing hugo; then pkg_install hugo hugo hugo 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 "$@"

14
script/check Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/sh
# script/check: run all checks. Our own extension to
# scripts-to-rule-them-all. Must not modify any files. Runs the
# read-only formatting check first, then the clean production build.
set -eu
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
main() {
"$SCRIPT_DIR/fmt-check"
"$SCRIPT_DIR/test"
}
main "$@"

14
script/cibuild Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/sh
# script/cibuild: run the CI build. The Dockerfile runs `make check`,
# so a successful build implies all checks pass. The Gitea workflow
# runs this on push.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() {
cd "$ROOT"
docker build .
}
main "$@"

14
script/docker Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/sh
# script/docker: build the Docker image tagged with the project name.
# The tag comes from script/projectname.
set -eu
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
main() {
cd "$ROOT"
docker build -t "$("$SCRIPT_DIR/projectname")" .
}
main "$@"

18
script/fmt Executable file
View File

@@ -0,0 +1,18 @@
#!/bin/sh
# script/fmt: format the repo's own top-level markdown docs (README.md,
# TODO.md, ...) with prettier, using our standard settings. Scope is
# deliberately limited to top-level docs: site content under content/
# is left untouched so rendered output cannot change.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
PRETTIER_VERSION="3.4.2"
main() {
cd "$ROOT"
npx --yes "prettier@${PRETTIER_VERSION}" --write \
'*.md' --tab-width 4 --prose-wrap always
}
main "$@"

17
script/fmt-check Executable file
View File

@@ -0,0 +1,17 @@
#!/bin/sh
# script/fmt-check: check the formatting of the repo's own top-level
# markdown docs (read-only). Same scope as script/fmt, but fails
# instead of writing.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
PRETTIER_VERSION="3.4.2"
main() {
cd "$ROOT"
npx --yes "prettier@${PRETTIER_VERSION}" --check \
'*.md' --tab-width 4 --prose-wrap always
}
main "$@"

15
script/install-precommit Executable file
View File

@@ -0,0 +1,15 @@
#!/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 "$@"

15
script/lint Executable file
View File

@@ -0,0 +1,15 @@
#!/bin/sh
# script/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. It is a real check: `hugo` fails on build errors, and
# --printPathWarnings reports render-target collisions.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() {
cd "$ROOT"
hugo --minify --printPathWarnings
}
main "$@"

12
script/precommit Executable file
View File

@@ -0,0 +1,12 @@
#!/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 "$@"

12
script/projectname Executable file
View File

@@ -0,0 +1,12 @@
#!/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 Executable file
View File

@@ -0,0 +1,13 @@
#!/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 Executable file
View File

@@ -0,0 +1,14 @@
#!/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 "$@"

View File

@@ -12,6 +12,7 @@
<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 &mdash; Las Vegas Meshtastic community <a href="https://git.eeqj.de/sneak/lora.vegas" class="contribute-link">[Contribute]</a></p> <p>lora.vegas &mdash; Las Vegas Meshtastic community <a href="https://git.eeqj.de/sneak/lora.vegas" class="contribute-link">[Contribute]</a></p>
</footer> </footer>
</body> </body>