# Contributing to restic-manager

Thanks for your interest in restic-manager. This document covers how
to set up a development environment, the conventions the project
follows, and how patches make it from your machine into `main`.

## Project status and scope

restic-manager is in pre-1.0. Core functionality (Phases 0–4) is
landed; OSS-readiness polish is in progress. The top of
[`tasks.md`](./tasks.md) tracks what's next; [`spec.md`](./spec.md)
is the canonical design doc and the source of truth for any
"why is it built this way" question.

The project is **single-maintainer, hobbyist-scale, and licensed
under [PolyForm Noncommercial 1.0.0](./LICENSE)**. That has two
practical implications:

1. Big PRs without prior discussion may be declined for fit
   reasons even when they're correct — opening an issue first lets
   us check alignment cheaply.
2. Commercial use is not permitted by the license. Bug reports and
   patches from operators of personal/community deployments are
   very welcome.

## Getting started

### Prerequisites

- Go 1.25 or newer (`go.mod` is the source of truth)
- `make`
- For the front-end CSS bundle: nothing extra — `make build`
  downloads a pinned `tailwindcss` standalone binary into `bin/`.
- For the docs site: nothing extra — `make docs` does the same trick
  with `mdbook`.
- For end-to-end tests: Docker + Docker Compose, plus `npx` for
  Playwright.

### One-time setup

```sh
git clone https://gitea.dcglab.co.uk/steve/restic-manager.git
cd restic-manager
make build          # compiles bin/restic-manager-{server,agent}
make test           # full unit + integration test sweep
make lint           # gofumpt + goimports + golangci-lint
```

### Running locally

For most development, the [smoke environment](./docs/e2e-smoke.md)
is the path of least resistance:

```sh
make smoke-restart  # rebuilds, launches as a systemd --user unit
make smoke-logs     # tail of the server log
```

Then point a browser at `http://127.0.0.1:8080`. The first run
prints a one-time bootstrap token to the log; use it to create the
admin user.

## Code conventions

### Style

- `gofumpt` for formatting; `goimports` for import grouping.
  Both run via the pre-commit hook in this repo.
- `golangci-lint` with `.golangci.yml` defaults; CI rejects on lint
  errors.
- UK English in identifiers, comments, log messages, and UI strings
  (the misspell linter is configured for the UK locale — see
  P3-X5 for the original sweep).
- Comments explain **why**, not what; avoid restating the code.
  A surprising invariant or an external constraint is worth
  writing down. "Adds 1 to x" is not.
- `slog` for structured logs. Never log secrets — and especially
  never the merged-creds rest-server URL (see [`CLAUDE.md`](./CLAUDE.md)).

### File and package layout

- `cmd/server` and `cmd/agent` are the two binary entry points.
- `internal/` holds everything that's not part of the public Go
  API (which is none of it — restic-manager isn't a library).
- Per-feature packages live under `internal/server/...` for the
  control plane and `internal/agent/...` for the agent.
- `web/templates/` are HTML templates rendered with the standard
  library; embedded via `web.FS`.

### Tests

- Unit tests live alongside the code as `*_test.go`. Use the
  in-process sqlite store (`store.Open(":memory:")`) when you need
  state — there is no test mock layer to maintain.
- HTTP handlers test through `httptest.NewServer` against the real
  router; see `internal/server/http/auth_test.go` for the canonical
  fixture pattern.
- End-to-end tests live in `e2e/` and run against a Docker Compose
  stack. See [`docs/e2e.md`](./docs/e2e.md).

### Database migrations

- Migrations are hand-rolled SQL in `internal/store/migrations/`
  and embedded via `embed.FS`.
- Prefer column-level `ALTER TABLE` over rebuilds — see
  [`CLAUDE.md`](./CLAUDE.md) "Migrations" section for the FK-cascade
  trap that bit migration 0007's first draft.

## Workflow

### Before opening a PR

1. **Open an issue first** for non-trivial changes. The design is
   still moving; an issue lets us agree on direction cheaply.
2. Run `make lint test` locally — both must pass.
3. Match existing code style (see above).
4. Keep commits focused: one logical change per commit. Imperative
   subject lines, body explaining why if it isn't obvious.
5. Don't add `Co-Authored-By` trailers — repo policy. If you used
   AI assistance in writing the patch, that's fine; we just don't
   pollute every commit message with attribution boilerplate.

### Pull requests

PRs target `main`. CI runs lint + tests on Linux amd64/arm64 and
Windows amd64; all three must be green to merge. Squash-merge is
the default; the PR title becomes the merge-commit subject, so
keep it short and informative.

The PR template asks for:

- A short description of what changed and why.
- A test plan (commands run, scenarios verified).
- Anything reviewers need to know to assess the change (related
  issue, follow-up work, deferred concerns).

### Reporting bugs

Open an issue with:

- restic-manager version (`server --version`) and agent version.
- restic version on the affected host.
- Steps to reproduce.
- Server and agent logs (sanitise any tokens before pasting).

Security-sensitive bugs go through the [SECURITY.md](./SECURITY.md)
disclosure path instead — please don't open a public issue for
them.

### Suggesting features

Open an issue describing the use case (not just the proposed
solution). The roadmap in `tasks.md` shows where the project is
heading; if the suggestion fits a future phase we'll wire it in
there. If it falls outside the project's scope (multi-tenancy, SaaS,
non-restic backends — see `spec.md` §2 non-goals) we'll say so
early to save your time.

## Code of conduct

Project participation is governed by [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).
The short version: be civil; assume good faith; harassment is not
tolerated.

## License

By contributing you agree that your contributions are licensed
under the [PolyForm Noncommercial 1.0.0](./LICENSE) license.
