scripts/banner.py builds artwork/banner.svg from the web UI's logo and wordmark, as Jarl's does; scripts/logo.py runs it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
129 lines
5.6 KiB
Markdown
129 lines
5.6 KiB
Markdown
# Developing Steward
|
|
|
|
## Layout
|
|
|
|
```
|
|
cmd/stewardd/ the daemon: serve, init, user, check, backup, restore, config
|
|
cmd/steward/ the CLI
|
|
internal/
|
|
dhcp4/ DHCPv4 messages (RFC 2131/2132/3396) and the option catalog; no I/O
|
|
model/ the configuration (State: settings, subnets, reservations), leases, users, history
|
|
validate/ whole-configuration checks with stable codes (docs/validation.md)
|
|
store/ SQLite (modernc.org/sqlite, no cgo): configuration, leases, history, users, tokens, sessions, audit
|
|
engine/ configuration changes: edit a copy, diff, validate, save with history, undo
|
|
dhcpd/ the DHCP server: sockets per interface, the protocol, address choice, leases, ping check, activity
|
|
server/ the HTTP API and the web UI, authentication, the local socket
|
|
api/, client/ JSON types and the Go client shared by server and CLI
|
|
kea/ the Kea configuration and lease file importer
|
|
auth/ passwords (argon2id), tokens, sessions, grants and roles
|
|
config/ stewardd's YAML configuration
|
|
version/ what a build reports
|
|
web/static/ the web UI: plain ES modules, no build step, embedded with go:embed
|
|
deploy/ systemd units, sysusers.d and tmpfiles.d files, Debian package scripts
|
|
scripts/e2e.sh end-to-end test with real DHCP clients in Docker
|
|
scripts/mkdeb.sh the Debian packages (make deb)
|
|
scripts/logo.py the web UI's logo from artwork/logo.svg
|
|
scripts/optionsdoc.py docs/options.md from the option catalog
|
|
scripts/banner.py the README's banner from the web UI's logo and wordmark
|
|
artwork/logo.svg the logo's master; artwork/banner.svg the README's banner
|
|
```
|
|
|
|
[design.md](design.md) describes how the pieces fit.
|
|
|
|
## Building and testing
|
|
|
|
```sh
|
|
make # bin/stewardd, bin/steward, bash completion
|
|
make test # go vet, gofmt check, go test ./...
|
|
make e2e # scripts/e2e.sh: needs Docker
|
|
make deb # dist/steward_<version>_<arch>.deb and steward-cli_...
|
|
```
|
|
|
|
**Unit tests** cover the protocol without sockets. `dhcpd.Server.Handle`
|
|
takes a parsed message and an interface name and returns the reply, with
|
|
fake interfaces, a fake pinger and a fake clock
|
|
(`internal/dhcpd/handle_test.go`). The API tests drive real DHCP exchanges
|
|
through `Handle` too.
|
|
|
|
**The e2e test** runs the real thing:
|
|
|
|
* stewardd in a `debian:trixie` container on two Docker bridge networks
|
|
(eth0 and eth1, like two VLANs);
|
|
* busybox `udhcpc` and `dhcpcd` clients in Alpine containers.
|
|
|
|
It checks:
|
|
|
|
* addresses and options per interface, and reservations;
|
|
* the ping check, against a container that squats on a pool address;
|
|
* init-reboot, renewal and release with dhcpcd;
|
|
* a client moved to another VLAN: NAK, then that VLAN's address;
|
|
* a full pool and a disabled subnet;
|
|
* history and undo, and leases across a restart;
|
|
* the API with tokens, and backups.
|
|
|
|
`KEEP=1` leaves the containers running; `DOCKER_HOST` points at another
|
|
Docker daemon. Root isn't needed on the host.
|
|
|
|
stewardd can't serve port 67 unprivileged, so a stewardd run by hand
|
|
outside a container shows `permission denied` for its interfaces. The
|
|
API and web UI still work, which is enough for UI work. The systemd
|
|
sandbox is only tested by installing the package in a container that
|
|
runs systemd (`--privileged /sbin/init`) and pointing a client container
|
|
at it.
|
|
|
|
## The web UI
|
|
|
|
Plain JavaScript modules under `internal/web/static/`, built with the
|
|
same helpers as Jarl's:
|
|
|
|
* All DOM goes through `h()` in `ui.js`, never `innerHTML`.
|
|
* The Content-Security-Policy allows only the app's own scripts and
|
|
styles, so no inline styles. Use a class in `style.css`, or set
|
|
`el.style.prop`.
|
|
* The files are embedded, so rebuild stewardd after changing them.
|
|
|
|
The logo's master is `artwork/logo.svg`; `scripts/logo.py` writes
|
|
`assets/logo.svg` (the favicon) and `assets/logo.js` (drawn inline by
|
|
`brand.js`), and then runs `scripts/banner.py`. The STEWARD wordmark is
|
|
drawn by `wordmark()` in `brand.js`, in the letters of Jarl's wordmark.
|
|
`artwork/banner.svg`, the README's banner, is the login page's logo and
|
|
wordmark, generated by `scripts/banner.py`. It draws the rule with the same
|
|
numbers as `brand.js`, so change both together.
|
|
|
|
## Conventions
|
|
|
|
* Every user-visible change goes into `CHANGELOG.md` under "Unreleased",
|
|
with the docs updated in the same change.
|
|
* Errors say what is wrong and how to fix it, naming the object.
|
|
* New validation rules get a stable code, a test in
|
|
`internal/validate/validate_test.go` and a row in
|
|
[validation.md](validation.md).
|
|
* Configuration changes go through `engine.Apply`, never straight to the
|
|
store, so they are validated, recorded and undoable.
|
|
* Schema changes append a migration to `internal/store/store.go`; never
|
|
edit a shipped one.
|
|
* `gofmt` and `go vet` clean (`make test`).
|
|
|
|
## Releasing
|
|
|
|
As for Jarl:
|
|
|
|
1. Move "Unreleased" in `CHANGELOG.md` under `## [<version>] - <date>`.
|
|
A release candidate may leave it there.
|
|
2. Put the version in `VERSION`, and update the README's status line.
|
|
3. Run `make test && make e2e`.
|
|
4. Commit as "Release X", tag it `git tag -a vX -m "Steward X"`, and push.
|
|
|
|
The tag starts `.gitea/workflows/release.yml`:
|
|
|
|
* it checks the tag against `VERSION` and the CHANGELOG;
|
|
* it runs `make test`;
|
|
* it builds the tarballs, the CLI for each OS and the `.deb`s (amd64,
|
|
arm64);
|
|
* `scripts/gitea-release.sh` publishes the release, and uploads the
|
|
`.deb`s to the registry: `stable`, or `testing` for a version with a
|
|
hyphen.
|
|
|
|
It needs the repository secret `RELEASE_TOKEN` (`write:repository`,
|
|
`write:package`).
|