Code
Sylve is built with Go (backend) and SvelteKit in SPA mode (frontend), we’re API first so if you’re planning to work on the go side you don’t have to touch the web/ directory at all. For the best development experience, we recommend a FreeBSD -STABLE machine with pkgbase.
Install Go and Node.js On FreeBSD
Section titled “Install Go and Node.js On FreeBSD”Install required tooling with pkg:
pkg updatepkg install -y git-lite go node npmVerify your installation:
go versionnode -vnpm -vIf your local machine is not FreeBSD, use a remote FreeBSD host for development.
VS Code Remote - SSH works great for this workflow:
- Extension: https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh
- Example setup article: https://hayzam.com/blog/02-linuxulator-is-awesome/
Clone The Repository
Section titled “Clone The Repository”git clone https://github.com/AlchemillaHQ/Sylvecd SylveOn amd64, stage the boot helper before using direct Go commands or live reload:
make csmwrap-assetsmake build, backend targets, and make test stage the helper automatically when
missing. Staged inputs are reused offline; make csmwrap-download refreshes them.
ARM64 builds do not need the helper.
To build the checkout’s pinned helper instead, follow the
CSMWrap source-build instructions
and run make csmwrap.
Install Live-Reload Tooling
Section titled “Install Live-Reload Tooling”Install air:
go install github.com/air-verse/air@latestUse A Single Development Entry Point
Section titled “Use A Single Development Entry Point”In config.json, set (along with other properties):
{ "proxyToVite": true}With proxyToVite enabled, the Go backend proxies frontend requests to Vite, so you do not need to work with two separate ports directly.
Run Backend And Frontend Together
Section titled “Run Backend And Frontend Together”Use tmux, zellij, or screen and split into two panes:
- In the repository root:
air .- In the
web/directory:
npm run devCode Style
Section titled “Code Style”Prefer simple, direct code over clever abstractions. A solution should be easy to read, trace, and change without requiring the reader to decode tricks or reconstruct hidden control flow.
Code should explain itself through precise names, small focused functions, straightforward data flow, and clear boundaries. We strongly dislike comments that merely restate what the code is doing; if a block needs that kind of explanation, simplify or rename it instead.
Use a comment only when the code cannot communicate the reason on its own; for example, to record an external constraint, a specific edge case, or the rationale behind a non-obvious compatibility decision. Comments should explain why, not narrate what.
If you use an LLM or other AI-assisted tooling, read and follow Sylve’s LLM / AI Contribution Policy before submitting your work. You remain responsible for understanding and testing everything you contribute, and contribution discussions must follow the policy as well.
Commit Messages
Section titled “Commit Messages”Use a short, lowercase scope followed by a concise description:
area: summaryarea: subsystem: summaryChoose the part of the repository affected by the change as the first scope, then add a narrower scope when it makes the commit easier to scan. Write the summary in the imperative mood and omit a trailing period.
system: route jailed restarts through graceful shutdownweb: vm: make request outcomes explicitdocs: app-docs: improve the getting-started pageci/cd: add pull-request validationKeep each commit focused on one coherent change. If more context is useful, leave a blank line after the subject and explain the motivation and important trade-offs in the commit body.
Formatting, Testing, And Builds
Section titled “Formatting, Testing, And Builds”Run the checks relevant to your change before opening a pull request. The commands below mirror the repository’s automated pull-request workflows.
Repository-wide quality checks
Section titled “Repository-wide quality checks”From the repository root, run the same code-quality gate used by CI:
make qualityThis command does not intentionally modify source files. It:
- Checks every tracked or unignored Go file with
gofmtand fails with a list of files that need formatting. - Installs the exact frontend dependencies from
web/package-lock.jsonusingnpm ci. - Runs the frontend lint task, which checks Prettier formatting and ESLint rules.
- Runs
svelte-check, treating warnings as failures.
If the formatting or lint steps fail, apply the available automatic fixes with:
make quality-fixmake quality-fix rewrites tracked and unignored Go files with gofmt, then runs Prettier and ESLint’s automatic fixes in web/. Review the resulting changes before committing. It does not install frontend dependencies or run svelte-check, so follow it with make quality to verify the complete quality gate:
make quality-fixmake qualityThe fix target cannot resolve every lint or type-checking problem. If make quality still fails, address the reported issues manually and run it again.
Go formatting and unit tests
Section titled “Go formatting and unit tests”From the repository root, format Go code and run the short unit-test suite:
go fmt ./...make testmake test runs go test -short across the repository, including the ZFS test utilities. Run it on FreeBSD so packages that depend on FreeBSD interfaces are compiled and exercised in the supported environment.
Integration tests
Section titled “Integration tests”Backend changes that interact with Jails, ZFS, networking, libvirt, migration, clustering, or replication should also run the integration suite:
doas make test-integrationIntegration tests must run as root on FreeBSD with ZFS available. The target checks for leaked ZFS test resources before and after the suite and runs integration packages without the Go test cache.
iSCSI and NVMe/TCP
Section titled “iSCSI and NVMe/TCP”The same native Go tests run locally and in CI, without an opt-in flag or build tag. Use a reserved FreeBSD 15 or newer host or VM, not a jail. Load ctl, iscsi, nvmft, nvmf, and nvmf_tcp. Install the standard ctld, iscsid, iscsictl, ctladm, nvmecontrol, diskinfo, sockstat, camcontrol, and timeout tools. NVMe connections also need a valid host UUID. The fixture does not change host identity or kernel modules.
For a focused run:
./scripts/check-iscsi-test-leaks.sh./scripts/check-zfs-test-leaks.shgo test -count=1 -p=1 -v -timeout=45m -run '^TestIntegrationISCSI' ./internal/services/iscsi./scripts/check-iscsi-test-leaks.sh./scripts/check-zfs-test-leaks.shDo not overlap native suites. The fixture uses a host lock and refuses existing ctld processes, stamped CTL objects, initiator sessions, or interrupted manifests. The NVMe case also requires a host without existing NVMe controllers or associations. Setup does not stop another daemon or remove another run’s storage. Tests use recorded zvols, private file backings, loopback endpoints, and disk identities before I/O. Default-port cases check port availability first. Missing NVMe coverage is not a release pass.
After an interruption, keep /tmp/sylve-iscsi-test-*/manifest.json and the matching ZFS ownership record. The leak scripts are read-only. Compare process start times, controller NQNs and host NQNs, session IDs, CTL mappings, file device/inode pairs, and pool ownership before manual repair. Do not remove resources by prefix alone. Retry only after the matching owned sessions, daemons, CTL objects, backings, and pool are gone and both leak checks pass.
The repository also provides more specialized suites:
| Command | Use it when |
|---|---|
doas make test-acceptance |
A change affects end-to-end console workflows. |
doas make test-acceptance-full |
You need the longer full acceptance run. |
doas make test-smart-integration SMART_DEVICE=/dev/… |
A disk or SMART change must be tested against explicitly selected hardware. This target can interact with the named device, so review its options first. |
These specialized targets are not part of the standard pull-request workflow, but maintainers may request them for relevant changes.
Frontend checks
Section titled “Frontend checks”From the repository root, run:
npm run format --prefix webnpm run lint --prefix webmake frontendnpm run build:demo --prefix webnpm run format writes Prettier changes. npm run lint verifies formatting and ESLint rules. make frontend performs a clean dependency install, runs svelte-check with warnings treated as failures, builds the production frontend, and copies the generated assets into the Go embed directory. The final command verifies the separate demo build used by the project website.
If a frontend change adds, changes, or removes user-facing text, follow the translation contribution guide to extract, clean, and validate the locale catalogs before submitting it.
Full local build
Section titled “Full local build”On a FreeBSD development host, build the complete application with:
make buildThis ensures the CSMWrap embed inputs on amd64, builds the frontend assets, and
then produces the FreeBSD sylve binary in bin/.
What runs on a pull request
Section titled “What runs on a pull request”For pull requests that change application code, GitHub runs both the Test and Build workflows before merge:
| Workflow | Automated checks |
|---|---|
| Test | Runs the short Go unit suite on FreeBSD 15. If it succeeds, prepares ZFS, PF, VMM, libvirt, and local SSH before running the integration suite. Test summaries and raw reports are uploaded as workflow artifacts. |
| Build | Uses Node.js 24 to check and build the production frontend and demo frontend, then cross-builds FreeBSD 15 binaries for amd64 and arm64 and verifies each binary’s OS and architecture. |
Both workflows cancel superseded runs when a pull request is updated. Their test and build checks are expected to pass before a pull request is merged. Because the workflows ignore changes confined to docs/**, documentation-only pull requests do not run the application test/build matrix.