Installs into .claude/skills of the current project.
Are you the author of Build And Release?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/onelastcommit-build-and-release)
---
name: build-and-release
description: Use when building Noctra locally, cross-compiling for Raspberry Pi, validating GoReleaser, or cutting a tagged release with cross-platform archives.
---
# Build And Release Noctra
Use this playbook for local builds, Raspberry Pi binaries, and official releases.
## Local Development Build
```bash
make build
# equivalent:
go build -o noctra ./cmd/noctra
```
Run the binary:
```bash
./noctra version
./noctra doctor
./noctra
```
## Verification Before Shipping
Run the standard checks:
```bash
go test ./...
go vet ./...
```
If `golangci-lint` is available, run:
```bash
golangci-lint run
```
The repository uses `.golangci.yml` v2 with standard linters plus `gofmt`.
## Raspberry Pi Cross-Compile
For Pi 4/5 with a 64-bit OS:
```bash
GOOS=linux GOARCH=arm64 go build -o noctra ./cmd/noctra
scp noctra pi@your-pi:/srv/noctra/
```
For Pi 3 or 32-bit Raspberry Pi OS:
```bash
GOOS=linux GOARCH=arm GOARM=7 go build -o noctra ./cmd/noctra
scp noctra pi@your-pi:/srv/noctra/
```
The Makefile also has:
```bash
make build-pi
```
which produces an arm64 binary named `noctra-pi`.
## Systemd Host Upgrade
On a host running Noctra as a `systemd --user` service, `make update` pulls `main`, builds to a side file, **atomically swaps** it into place (safe while the old process is still executing) and restarts `noctra.service`. A failed build leaves the previous binary intact. `make start` / `stop` / `restart` / `status` / `logs` wrap `systemctl --user` and `journalctl --user-unit=noctra.service -f`.
On an installed host (release binary, no checkout), operate the service through the CLI: `noctra update` (add `--restart` to restart afterwards), `noctra start` / `stop` / `restart` / `status` and `noctra logs`. Recommend these over raw `systemctl --user` commands, which they wrap.
The startup banner (`pipeline.banner`) prints the resolved runtime config — repos, trigger, agent backend (`p.agent.Label()` + CLI), review gate, auto-iterate, notifications, GitHub identity — so a restart's `make logs` shows exactly what is live. Keep new operationally significant config visible there.
## Docker Image
`Dockerfile` is multi-stage: a `golang` stage compiles the static binary, and a `node:20-bookworm-slim` runtime adds `git`, `gh` and every agent CLI (`@anthropic-ai/claude-code`, `@openai/codex`, `@github/copilot`, via npm) — Noctra shells out to all of them, so the image can't be `scratch`.
- `docker-entrypoint.sh` sets a default git identity and wires `GH_TOKEN` into git/gh; a fresh container has neither (both were once silently inherited from the dev's machine).
- All mutable state goes under one `/data` volume via `REPOS_BASE` / `WORKTREE_BASE` / `LOG_DIR` / `STATE_DB` (plus legacy `STATE_FILE` as the one-time JSON migration source).
- Container auth is API-key based (no interactive login).
- `.github/workflows/docker.yml` builds on PRs and builds + pushes multi-arch (amd64/arm64) to GHCR on `main` and tags.
- Cloud templates consuming the image sit at the repo root: `fly.toml`, `render.yaml`, `railway.json`, `deploy/digitalocean-cloud-init.yaml`.
```bash
docker build -t noctra:local .
```
## GoReleaser Validation
Releases are configured in `.goreleaser.yaml` and `.github/workflows/release.yml`.
Before changing release config, validate locally:
```bash
goreleaser check
goreleaser release --snapshot --clean --skip=publish
```
GoReleaser builds:
1. Linux `amd64`, `arm64`, and `armv7`.
2. macOS `amd64` and `arm64`.
3. Archives and checksums.
The `main.version` variable is stamped through `-ldflags "-X main.version=..."`.
## Cutting A Release
### Label-driven (default)
Merging a PR to `main` runs `.github/workflows/tag-on-merge.yml`:
1. exactly one `release:major` / `release:minor` / `release:patch` label → compute the next semver from the latest `v*` tag; no label → no-op; several → fail;
2. create an annotated tag at the merge commit and push it;
3. run GoReleaser inline.
GoReleaser runs inside `tag-on-merge` because tags pushed with `GITHUB_TOKEN` don't trigger other workflows, so `release.yml` would never fire.
### Manual
```bash
git tag vX.Y.Z
git push origin vX.Y.Z
```
`.github/workflows/release.yml` publishes on any pushed `v*` tag — for hotfixes, backdates or testing.
### Changelog
GitHub Release notes are the changelog, generated by GoReleaser from Conventional Commits (Features, Bug Fixes, Performance, Refactoring; `docs:`/`test:`/`chore:`/`ci:` excluded). `CHANGELOG.md` only points at the Releases page.