Merging a pull request into main is now the whole release. A Gitea Actions workflow derives the CalVer tag, builds the image and pushes it with :latest, so nothing is built locally any more. That made image/push/release redundant, and with them the .release-tag state file and the main-branch guard — the workflow only runs on main, which is protected, so the guard had nothing left to catch. The digest verification went too: it guarded a `make -j` race between image and push that cannot happen in a single CI job. seed, icons and vendor ran a few times a year and are written out in the README instead. Makefile: 151 lines to 71. Docs referenced the removed targets in sixteen places, including a CONTRIBUTING note claiming the branch check "has to be local".
This commit is contained in:
@@ -0,0 +1,58 @@
|
|||||||
|
name: release
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
# ponytail: only because Traefik still serves its default self-signed cert for
|
||||||
|
# git.kessinen.com. Remove once the LE-DNS01-cloudflare runbook has been run.
|
||||||
|
env:
|
||||||
|
GIT_SSL_NO_VERIFY: "true"
|
||||||
|
REGISTRY: git.kessinen.com
|
||||||
|
IMAGE: git.kessinen.com/kessinen/foodster
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
image:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
# Full history and tags: the release number is derived by counting the
|
||||||
|
# tags already cut today.
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
# The job container is node:22-bookworm and has no docker client. The
|
||||||
|
# static binary is one file; installing docker.io would pull a daemon
|
||||||
|
# that is never used, since the build runs against the host's.
|
||||||
|
- name: Install the docker client
|
||||||
|
run: |
|
||||||
|
curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-27.3.1.tgz \
|
||||||
|
| tar xz --strip-components=1 -C /usr/local/bin docker/docker
|
||||||
|
docker version --format '{{.Client.Version}}'
|
||||||
|
|
||||||
|
- name: Work out the release tag
|
||||||
|
id: rel
|
||||||
|
run: |
|
||||||
|
day=$(date +%Y%m%d)
|
||||||
|
tag="v$day-$(( $(git tag -l "v$day-*" | wc -l) + 1 ))"
|
||||||
|
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "==> $tag"
|
||||||
|
|
||||||
|
- name: Tag the commit
|
||||||
|
run: |
|
||||||
|
git tag "${{ steps.rel.outputs.tag }}"
|
||||||
|
git push origin "${{ steps.rel.outputs.tag }}"
|
||||||
|
|
||||||
|
- name: Log in to the registry
|
||||||
|
run: |
|
||||||
|
echo "${{ secrets.GITEA_TOKEN }}" \
|
||||||
|
| docker login "$REGISTRY" -u "${{ gitea.actor }}" --password-stdin
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
run: |
|
||||||
|
tag="${{ steps.rel.outputs.tag }}"
|
||||||
|
docker build --platform linux/amd64 --build-arg VERSION="$tag" \
|
||||||
|
-f Containerfile \
|
||||||
|
-t "$IMAGE:$tag" -t "$IMAGE:latest" .
|
||||||
|
docker push "$IMAGE:$tag"
|
||||||
|
docker push "$IMAGE:latest"
|
||||||
|
echo "pushed $IMAGE:$tag and :latest - pull it in dockge when ready"
|
||||||
@@ -4,9 +4,6 @@
|
|||||||
# Build output
|
# Build output
|
||||||
/foodster
|
/foodster
|
||||||
|
|
||||||
# The tag `make image` last built, handed to `make push`.
|
|
||||||
/.release-tag
|
|
||||||
|
|
||||||
# Generated by `templ generate` during the container build.
|
# Generated by `templ generate` during the container build.
|
||||||
*_templ.go
|
*_templ.go
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -29,9 +29,9 @@ git switch dev && git reset --hard main
|
|||||||
git push --force-with-lease origin dev
|
git push --force-with-lease origin dev
|
||||||
```
|
```
|
||||||
|
|
||||||
`make image` refuses to run outside `main`. That check has to be local: the
|
The release workflow only triggers on `main`, and `main` only moves through a
|
||||||
tag and the image are made before anything reaches the remote, so branch
|
pull request, so a release can never be built from the wrong branch. Nothing
|
||||||
protection cannot catch a release built from the wrong branch.
|
needs to check for it.
|
||||||
|
|
||||||
## Commit messages
|
## Commit messages
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ release: repair the catalog 404 and stop the page jumping
|
|||||||
|
|
||||||
`main`'s log is then one line per deployment, which is what that branch is
|
`main`'s log is then one line per deployment, which is what that branch is
|
||||||
for, and the pull request body serves as the release notes. No version in the
|
for, and the pull request body serves as the release notes. No version in the
|
||||||
title — the CalVer tag is not created until `make image` runs after the merge.
|
title — CI creates the CalVer tag after the merge, so it is not known yet.
|
||||||
|
|
||||||
The types above are for `dev`, where a commit really does do one thing.
|
The types above are for `dev`, where a commit really does do one thing.
|
||||||
|
|
||||||
|
|||||||
@@ -3,18 +3,8 @@
|
|||||||
COMPOSE ?= podman compose
|
COMPOSE ?= podman compose
|
||||||
BIN := foodster
|
BIN := foodster
|
||||||
PKG := ./cmd/foodster
|
PKG := ./cmd/foodster
|
||||||
STATIC := cmd/foodster/static
|
|
||||||
|
|
||||||
# What `make image` last built. push reads it rather than re-deriving the tag:
|
# Shared password and TZ live here. Gitignored.
|
||||||
# sorting tags by date is ambiguous when two point at the same commit, and
|
|
||||||
# re-deriving is what let a parallel make push the wrong one.
|
|
||||||
TAGFILE := .release-tag
|
|
||||||
|
|
||||||
# Vendored Datastar client. Bump, run `make vendor`, commit the result.
|
|
||||||
DATASTAR_VERSION ?= v1.0.3
|
|
||||||
SEED ?= seeds/testi.json
|
|
||||||
|
|
||||||
# Registry coordinates, shared password and TZ live here. Gitignored.
|
|
||||||
ifneq (,$(wildcard .env))
|
ifneq (,$(wildcard .env))
|
||||||
include .env
|
include .env
|
||||||
export
|
export
|
||||||
@@ -24,7 +14,7 @@ endif
|
|||||||
GOFILES = $(shell find . -name '*.go' -not -name '*_templ.go' 2>/dev/null)
|
GOFILES = $(shell find . -name '*.go' -not -name '*_templ.go' 2>/dev/null)
|
||||||
|
|
||||||
.DEFAULT_GOAL := help
|
.DEFAULT_GOAL := help
|
||||||
.PHONY: help generate build run seed test smoke check lint fix icons vendor image push release up down logs clean
|
.PHONY: help generate build run test smoke check lint fix up down logs clean
|
||||||
|
|
||||||
help: ## Show this help
|
help: ## Show this help
|
||||||
@grep -hE '^[a-zA-Z_-]+:.*## ' $(MAKEFILE_LIST) \
|
@grep -hE '^[a-zA-Z_-]+:.*## ' $(MAKEFILE_LIST) \
|
||||||
@@ -40,9 +30,6 @@ build: generate ## Build ./foodster
|
|||||||
run: generate ## Run locally on :8080 (database in ./data)
|
run: generate ## Run locally on :8080 (database in ./data)
|
||||||
PASSWORD=$${PASSWORD:-dev} ENV=dev go run $(PKG)
|
PASSWORD=$${PASSWORD:-dev} ENV=dev go run $(PKG)
|
||||||
|
|
||||||
seed: ## Import a dish bundle (SEED=seeds/testi.json)
|
|
||||||
go run $(PKG) -import $(SEED)
|
|
||||||
|
|
||||||
test: generate ## Run unit tests
|
test: generate ## Run unit tests
|
||||||
go test ./...
|
go test ./...
|
||||||
|
|
||||||
@@ -57,21 +44,6 @@ check: ## Everything that must pass before a commit
|
|||||||
@$(MAKE) --no-print-directory smoke
|
@$(MAKE) --no-print-directory smoke
|
||||||
@echo "check: all passed"
|
@echo "check: all passed"
|
||||||
|
|
||||||
icons: ## Rasterise home-screen PNGs from assets/icon.svg and optimise them
|
|
||||||
rsvg-convert -w 180 -h 180 assets/icon.svg -o $(STATIC)/apple-touch-icon.png
|
|
||||||
rsvg-convert -w 192 -h 192 assets/icon.svg -o $(STATIC)/icon-192.png
|
|
||||||
rsvg-convert -w 512 -h 512 assets/icon.svg -o $(STATIC)/icon-512.png
|
|
||||||
# oxipng -o max alone loses to optipng on the 512; --zopfli wins at every
|
|
||||||
# size. Slow, but these are three tiny files built by hand.
|
|
||||||
oxipng -o max --zopfli --quiet \
|
|
||||||
$(STATIC)/apple-touch-icon.png $(STATIC)/icon-192.png $(STATIC)/icon-512.png
|
|
||||||
@ls -l $(STATIC)/*.png
|
|
||||||
|
|
||||||
vendor: ## Re-download the Datastar client (DATASTAR_VERSION=v1.0.3)
|
|
||||||
curl -sSfL -o $(STATIC)/datastar.js \
|
|
||||||
"https://cdn.jsdelivr.net/gh/starfederation/datastar@$(DATASTAR_VERSION)/bundles/datastar.js"
|
|
||||||
@head -1 $(STATIC)/datastar.js
|
|
||||||
|
|
||||||
lint: generate ## go vet, gofmt check, golangci-lint when installed
|
lint: generate ## go vet, gofmt check, golangci-lint when installed
|
||||||
go vet ./...
|
go vet ./...
|
||||||
@bad=$$(gofmt -l $(GOFILES) 2>/dev/null); \
|
@bad=$$(gofmt -l $(GOFILES) 2>/dev/null); \
|
||||||
@@ -84,57 +56,6 @@ fix: ## Format Go and templ sources, tidy go.mod
|
|||||||
go tool templ fmt .
|
go tool templ fmt .
|
||||||
go mod tidy
|
go mod tidy
|
||||||
|
|
||||||
image: ## Build and tag an image as vYYYYMMDD-N. Creates a git tag.
|
|
||||||
@test -n "$(REPO)" || { echo "set REPO in .env"; exit 1; }
|
|
||||||
@# A release tag must point into main, or the tag records a commit that
|
|
||||||
@# was never released.
|
|
||||||
@branch=$$(git symbolic-ref --short HEAD); \
|
|
||||||
if [ "$$branch" != "main" ]; then \
|
|
||||||
echo "releases are cut from main, not $$branch:"; \
|
|
||||||
echo " git switch main && git merge --ff-only dev"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
@day=$$(date +%Y%m%d); \
|
|
||||||
tag="v$$day-$$(( $$(git tag -l "v$$day-*" | wc -l) + 1 ))"; \
|
|
||||||
echo "==> $$tag"; \
|
|
||||||
git tag "$$tag"; \
|
|
||||||
podman build --platform linux/amd64 --build-arg VERSION="$$tag" \
|
|
||||||
-t "$(REPO):$$tag" -t "$(REPO):latest" . ; \
|
|
||||||
echo "$$tag" > $(TAGFILE)
|
|
||||||
|
|
||||||
# Pushing reported success while uploading the previous release once, because
|
|
||||||
# nothing compared what was built against what arrived. So afterwards, ask the
|
|
||||||
# registry what it actually serves for each tag and fail if it is not the
|
|
||||||
# image we just built.
|
|
||||||
push: ## Push the newest tag and :latest, then verify the registry
|
|
||||||
@test -n "$(REPO)" || { echo "set REPO in .env"; exit 1; }
|
|
||||||
@test -f $(TAGFILE) || { echo "nothing built - run make image"; exit 1; }; \
|
|
||||||
tag=$$(cat $(TAGFILE)); \
|
|
||||||
built=$$(podman image inspect "$(REPO):$$tag" --format '{{.Id}}' 2>/dev/null) || \
|
|
||||||
{ echo "no local image tagged $$tag - run make image"; exit 1; }; \
|
|
||||||
podman push "$(REPO):$$tag"; \
|
|
||||||
podman push "$(REPO):latest"; \
|
|
||||||
echo "==> verifying $$tag"; \
|
|
||||||
for ref in "$$tag" latest; do \
|
|
||||||
podman pull -q "$(REPO):$$ref" >/dev/null 2>&1 || \
|
|
||||||
{ echo " FAIL $$ref is not in the registry"; exit 1; }; \
|
|
||||||
served=$$(podman image inspect "$(REPO):$$ref" --format '{{.Id}}'); \
|
|
||||||
if [ "$$served" != "$$built" ]; then \
|
|
||||||
echo " FAIL $$ref serves $$served"; \
|
|
||||||
echo " expected $$built"; \
|
|
||||||
exit 1; \
|
|
||||||
fi; \
|
|
||||||
echo " ok $$ref"; \
|
|
||||||
done
|
|
||||||
|
|
||||||
# Sub-makes, not prerequisites. Under `make -j` — and -j16 is the default on
|
|
||||||
# at least one machine here — these run concurrently, so push resolves the
|
|
||||||
# newest tag and uploads :latest before image has finished building and
|
|
||||||
# tagging. That silently ships the previous release a second time.
|
|
||||||
release: ## Build, tag and push in one go
|
|
||||||
@$(MAKE) --no-print-directory image
|
|
||||||
@$(MAKE) --no-print-directory push
|
|
||||||
|
|
||||||
up: ## Start the stack
|
up: ## Start the stack
|
||||||
@mkdir -p data # or the engine creates it root-owned and the app cannot write
|
@mkdir -p data # or the engine creates it root-owned and the app cannot write
|
||||||
$(COMPOSE) up -d
|
$(COMPOSE) up -d
|
||||||
|
|||||||
@@ -372,22 +372,27 @@ Explicitly *not* React.
|
|||||||
|
|
||||||
## 10. Deployment
|
## 10. Deployment
|
||||||
|
|
||||||
Images are built locally, pushed to a private container registry, then pulled
|
Images are built by CI, pushed to a private container registry, then pulled on
|
||||||
on the server and run with Docker Compose.
|
the server and run with Docker Compose.
|
||||||
|
|
||||||
- **Branches**: `main` carries released versions only, so its history is the
|
- **Branches**: `main` carries released versions only, so its history is the
|
||||||
deployment history and every release tag points into it. Development happens
|
deployment history and every release tag points into it. Development happens
|
||||||
on `dev`, and `main` is protected on the remote: it accepts no direct
|
on `dev`, and `main` is protected on the remote: it accepts no direct
|
||||||
pushes, so a release arrives as a pull request from `dev`. `make image`
|
pushes, so a release arrives as a pull request from `dev`. The release
|
||||||
additionally refuses to run outside `main` — that one has to be local,
|
workflow runs only on `main`, so a release cannot be built from anywhere
|
||||||
because the tag and the image are made before anything reaches the remote.
|
else and nothing needs to check for it.
|
||||||
- **Versioning**: CalVer `vYYYYMMDD-N`, where `N` is the Nth build of that
|
- **Versioning**: CalVer `vYYYYMMDD-N`, where `N` is the Nth build of that
|
||||||
day. `make image` derives `N` by counting the day's existing git tags,
|
day. The release workflow derives `N` by counting the day's existing git
|
||||||
creates the new tag, and bakes the version into the binary through
|
tags, creates the new tag, and bakes the version into the binary through
|
||||||
`-ldflags -X main.version`. `make release` builds, tags and pushes.
|
`-ldflags -X main.version`.
|
||||||
- **Tooling**: a `Makefile` is the single entry point — `make` on its own
|
- **CI**: Gitea Actions, workflows in `.gitea/workflows/`. `check.yaml` runs
|
||||||
lists every target. Build, test, lint, format, image and compose commands
|
`make check` on every push to `dev`; `release.yaml` builds and pushes the
|
||||||
all live there rather than in loose scripts.
|
image when a pull request merges into `main`. Merging is the release —
|
||||||
|
there is no local build step.
|
||||||
|
- **Tooling**: a `Makefile` covers development — `make` on its own lists every
|
||||||
|
target. Build, test, lint, format and compose commands live there rather
|
||||||
|
than in loose scripts. Commands run a handful of times a year are written
|
||||||
|
out in the README instead of earning a target.
|
||||||
- **Image**: a two-stage `Containerfile`. `golang:1.27-alpine` compiles a
|
- **Image**: a two-stage `Containerfile`. `golang:1.27-alpine` compiles a
|
||||||
static binary; the runtime stage is `FROM scratch` holding only that
|
static binary; the runtime stage is `FROM scratch` holding only that
|
||||||
binary, running as UID 65534.
|
binary, running as UID 65534.
|
||||||
@@ -425,7 +430,7 @@ on the server and run with Docker Compose.
|
|||||||
- Pending migrations are applied on app start.
|
- Pending migrations are applied on app start.
|
||||||
- The Datastar client is vendored at `cmd/foodster/static/datastar.js` and
|
- The Datastar client is vendored at `cmd/foodster/static/datastar.js` and
|
||||||
served from the app's own origin — the SDK ships no browser asset, and a
|
served from the app's own origin — the SDK ships no browser asset, and a
|
||||||
CDN link would break an offline LAN. `make vendor` refreshes it; the pinned
|
CDN link would break an offline LAN. The README says how to refresh it; the pinned
|
||||||
version lives in the `Makefile` and in the file's first line.
|
version lives in the `Makefile` and in the file's first line.
|
||||||
- No internet exposure; the server binds to the LAN.
|
- No internet exposure; the server binds to the LAN.
|
||||||
|
|
||||||
|
|||||||
@@ -11,35 +11,35 @@ See [PRD.md](PRD.md) for the full specification.
|
|||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
**Stage 1 — eating history: in development.** The meal catalog and the daily
|
**Stage 1 — eating history: in use.** The meal catalog and the daily log came
|
||||||
log come first, because the suggester is worthless until there are a few
|
first, because the suggester is worthless until there are a few weeks of real
|
||||||
weeks of real history to weight against.
|
history to weight against.
|
||||||
|
|
||||||
Working:
|
Working:
|
||||||
|
|
||||||
- **Kirjaa** — log a dinner: pick a dish, tick sides, save. Dishes are ordered
|
- **Kirjaa** — log a dinner: pick a dish, tick sides, save. Dishes are grouped
|
||||||
and sized by how often they are eaten, so the likely answer is the biggest
|
by category, then ordered and sized by how often they are eaten, so the
|
||||||
target. The history sits on the same page underneath: every day back to the
|
likely answer is the biggest target. The history sits on the same page
|
||||||
first entry, unlogged days shown as explicit gaps, and every row a link that
|
underneath: every day back to the first entry, unlogged days shown as
|
||||||
loads that day into the logger above it.
|
explicit gaps, and every row opening that day's logger in place. Older days
|
||||||
|
arrive a window at a time.
|
||||||
- **Ruuat** — add, edit and delete mains and sides, or import a whole bundle
|
- **Ruuat** — add, edit and delete mains and sides, or import a whole bundle
|
||||||
by paste or file upload. Grouped by category and alphabetical inside, since
|
by paste or file upload. Grouped by category and alphabetical inside, since
|
||||||
this is a list you manage rather than one you pick from. Deletes are soft,
|
this is a list you manage rather than one you pick from. Edit and delete are
|
||||||
so old log entries keep showing the dish they used.
|
row icons, and a delete asks first. Deletes are soft, so old log entries
|
||||||
|
keep showing the dish they used.
|
||||||
|
- **Search as you type** on both tabs, debounced, patching just the list.
|
||||||
|
- **Category icons**, not colour dots: shape and colour together, so two marks
|
||||||
|
are told apart by more than hue.
|
||||||
- **Light / dark**, remembered per device, dark by default. The button shows
|
- **Light / dark**, remembered per device, dark by default. The button shows
|
||||||
the theme that is on — moon while dark, sun while light — not the one a
|
the theme that is on — moon while dark, sun while light — not the one a
|
||||||
click would bring.
|
click would bring.
|
||||||
|
|
||||||
|
Nothing navigates. Every interaction patches the page through Datastar, so
|
||||||
|
the scroll position survives; links and forms still work with JavaScript off.
|
||||||
|
|
||||||
Still to build:
|
Still to build:
|
||||||
|
|
||||||
- Live search as you type, and paging for the history and catalog lists once
|
|
||||||
years of entries make them long. Both via Datastar.
|
|
||||||
- Category icons instead of plain colour dots — colour and shape together, so
|
|
||||||
a red blob and a yellow blob are told apart by more than hue.
|
|
||||||
- Edit and delete as icons in the catalog rows, and a confirmation step before
|
|
||||||
a delete actually happens.
|
|
||||||
- A background for the header. Something subtle; the palette gets overhauled
|
|
||||||
later.
|
|
||||||
- Stage 2: the seven-meal suggester, which starts once there is history to
|
- Stage 2: the seven-meal suggester, which starts once there is history to
|
||||||
weight against.
|
weight against.
|
||||||
|
|
||||||
@@ -74,20 +74,20 @@ no direct pushes, so a release arrives through a pull request.
|
|||||||
git switch dev # where the work happens
|
git switch dev # where the work happens
|
||||||
# ... commits ...
|
# ... commits ...
|
||||||
make check # lint, unit tests, smoke
|
make check # lint, unit tests, smoke
|
||||||
git push origin dev
|
git push origin dev # CI runs make check too
|
||||||
|
|
||||||
tea pr create --base main --head dev # or open it in the forge
|
tea pr create --base main --head dev # or open it in the forge
|
||||||
# merge the pull request, then:
|
# squash-merge the pull request — that is the whole release
|
||||||
|
|
||||||
git switch main && git pull --ff-only
|
|
||||||
make release # builds, tags vYYYYMMDD-N, pushes the image
|
|
||||||
git push origin --tags
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`make image` additionally refuses to run from any branch but `main`, so a
|
Merging is the release. CI builds the image, tags it `vYYYYMMDD-N` and
|
||||||
release tag can never point at a commit that was not released. That check
|
`latest`, pushes both to the registry, and creates the matching git tag. There
|
||||||
lives locally because it has to: tags and images are built before anything
|
is nothing to run locally afterwards; pull the new image on the server when
|
||||||
reaches the remote, so protection there cannot catch it.
|
you are ready.
|
||||||
|
|
||||||
|
A release tag can therefore never point at a commit that was not released:
|
||||||
|
the workflow only runs on `main`, and `main` only moves through a pull
|
||||||
|
request.
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
@@ -102,15 +102,39 @@ make run # http://localhost:8080
|
|||||||
make fix gofmt, templ fmt, go mod tidy
|
make fix gofmt, templ fmt, go mod tidy
|
||||||
make lint go vet, gofmt check, golangci-lint when installed
|
make lint go vet, gofmt check, golangci-lint when installed
|
||||||
make test go test ./...
|
make test go test ./...
|
||||||
|
make smoke end-to-end check against a scratch server
|
||||||
|
make check lint + test + smoke — run before every commit
|
||||||
make build ./foodster
|
make build ./foodster
|
||||||
make seed import a dish bundle (SEED=seeds/testi.json)
|
|
||||||
make vendor re-download the Datastar client
|
|
||||||
make image build and tag vYYYYMMDD-N (creates a git tag)
|
|
||||||
make push push the newest tag and :latest
|
|
||||||
make release image + push
|
|
||||||
make up/down/logs compose
|
make up/down/logs compose
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Images are built by CI, not here — see [Deployment](#deployment).
|
||||||
|
|
||||||
|
### Occasional commands
|
||||||
|
|
||||||
|
Rare enough not to earn a `make` target. Both write into
|
||||||
|
`cmd/foodster/static/`, and the results are committed.
|
||||||
|
|
||||||
|
Re-download the vendored Datastar client after bumping the version:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -sSfL -o cmd/foodster/static/datastar.js \
|
||||||
|
"https://cdn.jsdelivr.net/gh/starfederation/[email protected]/bundles/datastar.js"
|
||||||
|
```
|
||||||
|
|
||||||
|
Re-rasterise the home-screen icons after editing `assets/icon.svg`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd cmd/foodster/static
|
||||||
|
rsvg-convert -w 180 -h 180 ../../../assets/icon.svg -o apple-touch-icon.png
|
||||||
|
rsvg-convert -w 192 -h 192 ../../../assets/icon.svg -o icon-192.png
|
||||||
|
rsvg-convert -w 512 -h 512 ../../../assets/icon.svg -o icon-512.png
|
||||||
|
oxipng -o max --zopfli --quiet apple-touch-icon.png icon-192.png icon-512.png
|
||||||
|
```
|
||||||
|
|
||||||
|
`oxipng -o max` on its own loses to optipng on the 512; `--zopfli` wins at
|
||||||
|
every size. Slow, but these are three tiny files built by hand.
|
||||||
|
|
||||||
## Importing dishes
|
## Importing dishes
|
||||||
|
|
||||||
The **Ruuat** tab takes a bundle of mains and sides: paste the JSON or upload
|
The **Ruuat** tab takes a bundle of mains and sides: paste the JSON or upload
|
||||||
@@ -141,7 +165,7 @@ The same importer runs from the command line when you just want to repopulate
|
|||||||
a scratch database:
|
a scratch database:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
make seed # or: SEED=seeds/other.json make seed
|
go run ./cmd/foodster -import seeds/testi.json
|
||||||
```
|
```
|
||||||
|
|
||||||
## Icons
|
## Icons
|
||||||
@@ -152,12 +176,8 @@ cannot be transparent and must not change with the theme; they are rasterised
|
|||||||
from `assets/icon.svg`, which is opaque and keeps the artwork inside the
|
from `assets/icon.svg`, which is opaque and keeps the artwork inside the
|
||||||
central 80% so Android can mask it to any shape.
|
central 80% so Android can mask it to any shape.
|
||||||
|
|
||||||
```sh
|
The PNGs are committed so the build needs no rasterizer. The commands to
|
||||||
make icons # rsvg-convert, then optipng -o7
|
regenerate them are under [Occasional commands](#occasional-commands).
|
||||||
```
|
|
||||||
|
|
||||||
The PNGs are committed so the build needs no rasterizer. Re-run `make icons`
|
|
||||||
after editing `assets/icon.svg`.
|
|
||||||
|
|
||||||
## Migrations
|
## Migrations
|
||||||
|
|
||||||
@@ -182,25 +202,24 @@ Everything is environment variables. `.env` is gitignored; start from
|
|||||||
| `ADDR` | `:8080` | Listen address. Only useful for a second local instance. |
|
| `ADDR` | `:8080` | Listen address. Only useful for a second local instance. |
|
||||||
| `PUID` / `PGID` | `1000` | Host owner of `./data`, for the bind mount. |
|
| `PUID` / `PGID` | `1000` | Host owner of `./data`, for the bind mount. |
|
||||||
| `TZ` | `Europe/Helsinki` | Used for every calendar-day calculation. |
|
| `TZ` | `Europe/Helsinki` | Used for every calendar-day calculation. |
|
||||||
|
| `REPO` | *required to run* | Image repository, no tag. Used by `compose.yaml`. |
|
||||||
|
| `TAG` | `latest` | Tag to run under compose. |
|
||||||
|
| `HOST` | *required to run* | Hostname Traefik routes to. |
|
||||||
|
|
||||||
Names carry no prefix: the container gives them their own namespace already.
|
Names carry no prefix: the container gives them their own namespace already.
|
||||||
`PUID`/`PGID` are the exception — `UID` is read-only in bash, so a value set
|
`PUID`/`PGID` are the exception — `UID` is read-only in bash, so a value set
|
||||||
in `.env` would be silently replaced by the invoking shell's own.
|
in `.env` would be silently replaced by the invoking shell's own.
|
||||||
| `REPO` | *required to build* | Image repository, no tag. |
|
|
||||||
| `TAG` | `latest` | Tag to run under compose. |
|
|
||||||
| `HOST` | *required to run* | Hostname Traefik routes to. |
|
|
||||||
|
|
||||||
Set `TZ` in development too. Under UTC the date rolls over three hours late,
|
Set `TZ` in development too. Under UTC the date rolls over three hours late,
|
||||||
which is exactly when dinner gets logged.
|
which is exactly when dinner gets logged.
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
Images are built with Podman and run under Docker Compose on a LAN server.
|
Images are built by CI when a pull request merges into `main`, and run under
|
||||||
They are OCI images, so either engine works.
|
Docker Compose on a LAN server. They are OCI images, so either engine works.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
make release # build, tag, push
|
# on the server, once CI reports the build finished:
|
||||||
# on the server:
|
|
||||||
docker compose pull && docker compose up -d
|
docker compose pull && docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -209,8 +228,15 @@ running version is served at `GET /healthz`, which is the one route outside
|
|||||||
authentication.
|
authentication.
|
||||||
|
|
||||||
There is no database container. SQLite lives in `./data`, bind-mounted into
|
There is no database container. SQLite lives in `./data`, bind-mounted into
|
||||||
the container, so a backup is `cp -r data` and you can inspect the file with
|
the container, so you can inspect the file with any sqlite client without
|
||||||
any sqlite client without going through the engine.
|
going through the engine. Back it up with
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sqlite3 data/foodster.db ".backup data/foodster-$(date +%F).db"
|
||||||
|
```
|
||||||
|
|
||||||
|
rather than copying the directory: the database runs in WAL mode, and a plain
|
||||||
|
copy of a live database can catch the `.db` and its `-wal` mid-write.
|
||||||
|
|
||||||
That directory must exist and be owned by the user compose runs as — `make up`
|
That directory must exist and be owned by the user compose runs as — `make up`
|
||||||
creates it, and `PUID`/`PGID` in `.env` tell the container who
|
creates it, and `PUID`/`PGID` in `.env` tell the container who
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512"
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512"
|
||||||
role="img" aria-label="Foodster">
|
role="img" aria-label="Foodster">
|
||||||
<!-- Source for the home-screen PNGs; `make icons` rasterises it.
|
<!-- Source for the home-screen PNGs; the README says how to rasterise it.
|
||||||
Unlike favicon.svg this one is opaque and fixed-colour: a home screen
|
Unlike favicon.svg this one is opaque and fixed-colour: a home screen
|
||||||
icon cannot be transparent and must not change with the system theme.
|
icon cannot be transparent and must not change with the system theme.
|
||||||
The bowl sits inside the central 80% so Android can mask it to any
|
The bowl sits inside the central 80% so Android can mask it to any
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 918 B After Width: | Height: | Size: 927 B |
@@ -32,7 +32,7 @@ import (
|
|||||||
//go:embed static
|
//go:embed static
|
||||||
var staticFS embed.FS
|
var staticFS embed.FS
|
||||||
|
|
||||||
// version is replaced at build time with the CalVer tag (see `make image`).
|
// version is replaced at build time with the CalVer tag by the release workflow.
|
||||||
var version = "dev"
|
var version = "dev"
|
||||||
|
|
||||||
// envTag marks the browser tab of anything that is not production, so a dev
|
// envTag marks the browser tab of anything that is not production, so a dev
|
||||||
|
|||||||
Reference in New Issue
Block a user