carlos / contra Public

Clone
git clone https://amadan.net/carlos/contra

Plain git — no account needed to clone.

Download

Download this file

1APP := contra
2
3# What `carlos ship -target` defaults to, and therefore what a release
4# must be built for. Building for your own machine and shipping that is a
5# silent architecture mismatch: the binary uploads fine and fails to exec
6# on the instance. Override for a one-off:
7# make release RELEASE_GOARCH=amd64
8RELEASE_GOOS ?= linux
9RELEASE_GOARCH ?= arm64
10RELEASE_BIN := releases/$(APP)-$(RELEASE_GOOS)-$(RELEASE_GOARCH)
11
12# What the running binary reports from GET /api/version. The platform's
13# deploy verification polls that endpoint on every instance socket and
14# compares what it finds against the release it believes it adopted; an
15# unstamped binary answers "dev" from every build you have ever made, so
16# those two facts can never disagree and a process that was never
17# recycled onto the new release verifies green. Stamping this is what
18# gives that check the ability to fail.
19#
20# git describe, because the tag is what you ship against. Outside a
21# repository, or before the first commit, name it yourself:
22# make release VERSION=v0.1.0
23VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null)
24
25.PHONY: build release version-check test test-js test-oracles test-webkit oracles vet fmt-check migration-check ci
26
27# build is the compile check: with more than one package matched, go
28# build discards the output, so this catches a broken package without
29# producing an artifact.
30build:
31 CGO_ENABLED=0 go build ./...
32
33# release is what ships. -s -w drops the symbol table and DWARF, which on
34# this family's apps is 23-31% off the binary and 45-53% off the
35# compressed artifact — and compressed size is what actually gets
36# transferred. carlos is built the same way (see platform's Makefile).
37#
38# What survives: panic traces keep their function names AND line numbers
39# (Go's pclntab is untouched by these flags), debug.ReadBuildInfo works,
40# and go version -m still reports module metadata. What you lose is
41# source-level debugging with delve or gdb — build without the flags for
42# that. rastrillo dev never strips, for the same reason.
43release: version-check
44 @mkdir -p releases
45 CGO_ENABLED=0 GOOS=$(RELEASE_GOOS) GOARCH=$(RELEASE_GOARCH) \
46 go build -ldflags="-s -w -X 'github.com/carlosframework/rastrillo.BuildVersion=$(VERSION)'" \
47 -o $(RELEASE_BIN) ./cmd/$(APP)
48 @echo
49 @echo "built $(RELEASE_BIN) reporting version $(VERSION)"
50 @echo "ship it with:"
51 @echo " carlos ship -app $(APP) -target $(RELEASE_GOOS)-$(RELEASE_GOARCH) $(RELEASE_BIN)"
52
53# version-check refuses rather than letting release ship a binary whose
54# version is untrue. Both refusals are about the same thing: /api/version
55# is only evidence if the answer identifies one artifact.
56#
57# An empty stamp is worse than the "dev" default, because "dev" is
58# saying something true about itself and "" looks like a value that
59# arrived from somewhere.
60#
61# A dirty tree is refused rather than suffixed. A -dirty stamp names a
62# commit plus changes nobody can name, so two different binaries can
63# carry the identical string — which does not fix the check, it makes
64# it look reliable while it is not, and that is the more expensive
65# failure. Refusing costs one `git stash`; a wrong stamp ships. If you
66# mean to ship uncommitted work, say so out loud:
67# make release VERSION=v0.1.0-wip
68version-check:
69 @if [ -z "$(VERSION)" ]; then \
70 echo "make release: no version to stamp."; \
71 echo " git describe found nothing here: no repository, or no commit yet."; \
72 echo " Commit, or name it: make release VERSION=v0.1.0"; \
73 exit 1; \
74 fi
75 @case "$(VERSION)" in *-dirty) \
76 echo "make release: the tree is dirty, so $(VERSION) does not name what you are about to ship."; \
77 echo " Commit or stash first, or name it: make release VERSION=v0.1.0-wip"; \
78 exit 1;; \
79 esac
80
81test:
82 go test ./...
83
84# The crypto twin. AGENTS.md: "Crypto is byte-identical in Go and JS, pinned
85# by shared golden vectors." This runs the browser modules against the same
86# vectors/contra-v1.json the Go suite uses, so a change to one side that is
87# not made to the other fails here rather than in somebody's vault.
88#
89# A HARD gate, and it does not soften when node is missing. A security product
90# must not silently stop verifying that the browser's crypto matches the
91# server's: a skip would be indistinguishable from a pass on the one check
92# that stops a Mac opening a vault the browser cannot.
93test-js:
94 @command -v node >/dev/null 2>&1 || { \
95 echo "make test-js: node is not installed."; \
96 echo " This gate is not optional -- it is what proves the browser's"; \
97 echo " crypto matches the server's. Install node, do not skip it."; \
98 exit 1; }
99 node --test test/crypto.test.mjs test/ecies.test.mjs test/items.test.mjs \
100 test/sharing.test.mjs
101
102# The third-party oracles: the QR symbols decoded by zxing-cpp and drawn
103# again by python-qrcode for a module-by-module comparison, and the two kit
104# PDFs read back by PyMuPDF and pypdf.
105#
106# These matter. Comparing matrices rather than only decoding is how the
107# reversed format-information bits were found — twelve wrong modules that a
108# decode-only check reports as "does not scan" and leaves you guessing.
109#
110# NOT part of `make ci`, and that is a deliberate, reluctant split made on
111# 2026-09-02 for the same reason test-webkit is out: the shared CI runner's
112# python3 is a bare 3.12.3 with no pip module and no ensurepip, so `make
113# oracles` cannot install anything there and the box is immutable-boot. A
114# gate that cannot run on the runner is a gate that is permanently red, and
115# permanently red CI teaches people to ignore red — which costs more than
116# this check is worth defending in the wrong place.
117#
118# It does NOT skip when the packages are missing; it fails with the command
119# that fixes it. Run it before trusting a change to qr.js, qrtables.js,
120# qrgf.js, pdf.js or kit.js:
121#
122# make oracles && make test-oracles
123#
124# Getting these back into CI is one line of python3-pip in the runner's
125# user-data. Filed rather than worked around.
126test-oracles:
127 @command -v node >/dev/null 2>&1 || { \
128 echo "make test-oracles: node is not installed."; exit 1; }
129 node --test test/qr.test.mjs test/kit.test.mjs
130
131# The third-party oracles' Python packages, for `make test-oracles`.
132#
133# Opt-in rather than a dependency of anything: a machine whose system python3
134# already has them needs nothing, and test/oracle.mjs reaches for what this
135# builds only when it exists. Pinned in test/requirements.txt, because an
136# oracle that drifts is an oracle that stops meaning what it said last week.
137#
138# Two ways in, because a Debian python3 without the python3-venv package
139# cannot create a venv at all (no ensurepip) — which is exactly what the
140# shared runner turned out to be. A venv is preferred where it works; where
141# it does not, the packages go into .oracles/ and test/oracle.mjs puts that
142# on PYTHONPATH. Both give the oracles their imports without touching the
143# box, which is the requirement: its user-data is fixed at launch.
144oracles:
145 @command -v python3 >/dev/null 2>&1 || { \
146 echo "make oracles: python3 is not installed."; exit 1; }
147 @set -e; \
148 if python3 -m venv .venv >/dev/null 2>&1; then \
149 .venv/bin/pip install --quiet --upgrade pip; \
150 .venv/bin/pip install --quiet -r test/requirements.txt; \
151 echo "oracles ready: .venv/bin/python3"; \
152 elif python3 -m pip --version >/dev/null 2>&1; then \
153 rm -rf .venv; \
154 python3 -m pip install --quiet --target .oracles \
155 -r test/requirements.txt; \
156 echo "oracles ready: python3 with PYTHONPATH=.oracles"; \
157 else \
158 rm -rf .venv; \
159 echo "make oracles: this python3 can neither create a venv nor run pip."; \
160 echo " python3: $$(python3 --version 2>&1)"; \
161 echo " venv : $$(python3 -m venv /tmp/contra-venv-probe 2>&1 | tail -1)"; \
162 echo " pip : $$(python3 -m pip --version 2>&1 | tail -1)"; \
163 rm -rf /tmp/contra-venv-probe; \
164 echo " Install python3-venv or python3-pip, or set CONTRA_PYTHON to"; \
165 echo " an interpreter that already has test/requirements.txt."; \
166 exit 1; \
167 fi
168
169# The browser crypto in WebKit, which is the engine this product can least
170# afford to be wrong about and the one the Chromium rig never touches.
171#
172# NOT part of `make ci`, on purpose. It needs Playwright, which not every
173# machine will have, and a gate that cannot run everywhere is a gate that
174# teaches people to ignore red. It skips visibly rather than silently, and it
175# is what you run before trusting a change to the crypto modules.
176#
177# npm install playwright && make test-webkit
178test-webkit:
179 node --test test/webkit.test.mjs
180
181vet:
182 go vet ./...
183
184fmt-check:
185 @out=$$(gofmt -l .); if [ -n "$$out" ]; then echo "gofmt needed:"; echo "$$out"; exit 1; fi
186
187# A step of its own, not folded into another target's recipe: ci.d/
188# steps each exec one make target, so migration-check needs its own
189# name to get its own reported step on a runner with step support.
190#
191# go run, not a bare "rastrillo": go.mod already requires rastrillo, so
192# this needs nothing on PATH but the Go toolchain, and works offline
193# after go mod download. A bare "rastrillo migration check" failed a
194# fresh clone's very first CI run with "make: rastrillo: Command not
195# found", and nothing in the scaffold said what to install.
196migration-check:
197 go run github.com/carlosframework/rastrillo/cmd/rastrillo migration check
198
199# ci is the one gate: what a runner executes and what you run before
200# pushing are the same definition. amadan's runner falls back to this
201# target when .amadan/ci is absent; ci.d/ carries the same targets,
202# reported one by one, for a runner with step support. migration-check
203# fails the build the moment models.go and migrations/ disagree,
204# instead of at boot on whatever machine notices next. If the app
205# declares manifest resources, also add: rastrillo generate --check
206ci: vet fmt-check test test-js migration-check
207