# Contributing to Zoomies Thank you for helping Zoomies give self-hosted GitHub Actions runners a better run. Contributions of code, tests, documentation, bug reports, design feedback, and real-world deployment experience are all welcome. By participating, you agree to follow our [Code of Conduct](CODE_OF_CONDUCT.md). ## Before you start - Search the [issues](https://github.com/eyupio/zoomies/issues) before opening a new one. - Small, focused fixes can go straight to a pull request. - For a substantial feature, architectural change, new dependency, or breaking configuration change, open an issue first so the approach can be agreed before a lot of work is done. - To list a hosting provider, a platform or service that deploys a controller or hosts runners, open a [provider proposal](https://github.com/eyupio/zoomies/issues/new?template=provider.yml) first. Once the approach is agreed, open the pull request with the provider template by adding `template=provider.md` to the compare URL; the form says how. A provider is a third party, so a guide page is not an endorsement. - Report vulnerabilities privately as described in [SECURITY.md](SECURITY.md). Do not open a public issue for a security problem. Zoomies is early-beta software that creates runners on other people's machines. Changes to authentication, authorisation, secrets, runner isolation, container backends, networking, upgrades, or deletion paths deserve an explicit threat and failure-mode review. ## Development requirements The core binary is written in Go. The web UI is built with Svelte and embedded in that binary. - Go 1.27.1 or later (the floor in `go.mod`; CI builds and releases with 1.27.2) - A C compiler (`gcc` or `clang`), because `make test` runs under the race detector and Go refuses `-race` without cgo - Node.js 22 or later and npm, when changing or rebuilding the UI. `make lint` also runs `npm ci` in `web/`, so it needs them too - Python 3.12 (`python3`), only for building the documentation site - Git - Docker or Podman for backend and image work - A GitHub App only for tests or development flows that actually talk to GitHub Start by forking the repository, then clone your fork: ```sh git clone https://github.com/YOUR-USERNAME/zoomies.git cd zoomies git remote add -t main upstream https://github.com/eyupio/zoomies.git ``` `-t main` keeps `git fetch upstream` to the one branch you need. The repository also carries a generated `zoomies-ai-context` branch whose history is large, and a plain fetch would download all of it. Create a branch from the latest `main`: ```sh git fetch upstream git switch -c fix/short-description upstream/main ``` Use a short, descriptive branch name such as `fix/agent-reconnect`, `feat/pool-draining`, or `docs/runner-security`. ## Build and run Run `make help` for the maintained list of targets. A fresh checkout does not compile on its own. `internal/api` embeds the built UI, which is a build product that is not committed, so a plain `go vet` or `go test` fails with `pattern all:webdist: no matching files found` until it exists. Run this once first: ```sh make build-nogui # Go-only build; writes a placeholder UI, needs no Node ``` `make build` does the same with the real UI (it needs Node), and either is enough for every Go command afterwards. ```sh make build # build the Go binary with the web UI embedded make build-nogui # fast Go-only inner loop make dev # local controller with debug logging and auth disabled make ui-dev # Vite UI against a controller on localhost:8080 ``` `make ui-dev` needs the UI's dependencies, so run `npm ci` in `web/` first. The development mode disables authentication and is for a local workstation only. Do not expose it to a network. ## Make a focused change - Keep pull requests small enough to review and explain the reason for the change, not only the implementation. - Follow the surrounding code and UI patterns. Go code must be formatted with `gofmt`; web code is formatted with Prettier. `make fmt` does both. - Read [AGENTS.md](AGENTS.md) before changing anything structural. Written for coding agents, it is also the contributor's map: which package owns what, the boundaries between packages that review is meant to catch, and the house voice (British spelling, comments that explain why, no em dashes). The reasoning behind the shape is in [docs/architecture.md](docs/architecture.md). - A new documentation page goes under `docs/` and into the `nav` in `mkdocs.yml`; a page that is in neither is easy to miss and is checked by `mkdocs build --strict`. - Add or update tests for changed behaviour. - Update user documentation, configuration examples, API definitions, and screenshots when the public behaviour changes. - Preserve safe defaults. If a change weakens isolation or exposes a new capability, make the trade-off explicit in both the UI and documentation. - Never commit credentials, GitHub App keys, tokens, local databases, generated logs, or build output. Generated files have a single source of truth: - After changing `api/openapi.yaml`, run `make openapi` and commit both files it writes: the Go copy the API serves (`internal/api/openapi_spec.go`) and the TypeScript client (`web/src/lib/api/schema.d.ts`). A test fails on either one when it is stale. - After changing the runner image catalogue in `internal/naming`, run `make generate` and commit everything it updates. Do not hand-edit generated catalogue sections. ## Test your change Run the narrowest relevant tests while developing, then the standard checks before opening a pull request: ```sh make test-short # the quick Go tests, a few minutes make fmt # gofmt and Prettier; make lint fails on what this would change make lint # vet, gofmt, staticcheck and the UI lint make test # every Go test with the race detector ``` `make test` can take an hour with few CPUs (CI splits it across six runners), so `make test-short` is the loop to work in. `make lint` runs staticcheck only if it is installed and says so when it is not; CI always runs it, so installing it (`go install honnef.co/go/tools/cmd/staticcheck@latest`) saves a round trip. A change made for speed is held to a number. Run the benchmarks of the package it touches before the change and after it, and put the comparison in the commit message: ```sh make bench BENCH_PKGS=./internal/scheduler | tee before.txt # make the change, then make bench BENCH_PKGS=./internal/scheduler | tee after.txt benchstat before.txt after.txt # go install golang.org/x/perf/cmd/benchstat@latest ``` `make bench` runs every benchmark six times, so that `benchstat` can say how far apart the runs were; `BENCH=Decide/quiet` narrows it by name. Leave the machine alone while it runs, because benchmarks that share the CPU contaminate each other, and compare only runs made on the same machine. For documentation changes, install the pinned dependencies and run the same strict build as the site workflow: ```sh python3 -m pip install --require-hashes -r docs/requirements.txt mkdocs build --strict ``` The build must produce `site/sitemap.xml`, `site/llms.txt` and `site/badge.svg`, and the header must name the latest release, `hooks/source.py` asks the GitHub API for it at build time, so set `GITHUB_TOKEN` if the anonymous rate limit is in the way. These checks catch broken links and missing published assets before a PR is merged. The showcase and article layouts live in `overrides/partials/`. The existing Markdown files keep their URLs, metadata and machine-readable copies; `hooks/showcase.py` adds reading times and the field-guide cards. Add an existing page to `extra.field_guides` in `mkdocs.yml` to include it in the guide library. Its title and description remain the page's search metadata. The public site serves Fraunces and IBM Plex locally. The original Zoomies artwork stays monochrome; the display type and blue accents belong to the surrounding layout. Update `showcase.css` for that visual system and `showcase-material.css` for its integration with Material. The build gives stylesheets and scripts content-versioned filenames so returning browsers cannot combine new markup with stale styles. After the strict build, check the rendered site and its browser behaviour: ```sh cp install.sh install.ps1 connect-proxmox.sh site/ python3 hooks/tests/check_site.py npm ci --prefix web --ignore-scripts --no-audit --no-fund cd web npx --no-install playwright install chromium cd .. node hooks/tests/browser.mjs ``` `check_site.py --baseline /path/to/previous/site` also checks that an existing build's URLs, titles, descriptions, robots directives, sharing metadata and content anchors survive. Set `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` to use an installed Chromium, and `DOCS_SCREENSHOTS` to capture the responsive layouts. Mermaid stays in the Markdown; its pinned browser bundle is served locally only when a page needs it. Its source and licence are in `docs/vendor/mermaid/`. For web UI changes, also run: ```sh cd web npm ci --no-audit --no-fund npm run check npm run test:unit npx playwright install chromium # once, for make test-ui cd .. make test-ui ``` Some suites intentionally require more setup: - `make test-drill` runs a real controller and agent against fake GitHub. - `make test-e2e` exercises Docker-backed end-to-end behaviour and skips when required GitHub credentials are absent. - `make screenshots` captures documentation screenshots from the real UI. - `make vet-tags` vets the suites behind the `e2e`, `drill` and `load` build tags. A plain `go vet` never compiles them; `make lint` and CI both run this target, and it is the quick check after editing a file under one of those tags. You do not need to run an unrelated privileged or credential-dependent suite. State exactly what you ran and what you could not run in the pull request. CI runs what a change's files can affect, decided by the `changes` job at the top of `.github/workflows/ci.yml`, on a pull request and on `main` alike: | A change to | Runs | | --- | --- | | Markdown, `docs/`, the site, the workflows | the `internal/docs` and `internal/naming` checks, which hold the prose to the code, about a minute | | `install.sh` | the `install.sh` checks and the upgrade test; no binaries or images | | `web/` | the UI and Playwright jobs, and the docs checks | | `deploy/` | the image builds and the tests that read those files | | Go, or anything not listed | the whole set | | `ci.yml`, the `Makefile`, `go.mod` | everything | The Go suite is split by package across six runners (the controller and API packages are two shards each, the store is one, and everything else shares the last); `make test TEST_PKGS=./internal/store/` runs one the way CI does. Coverage is measured on `main`, when the whole Go suite ran. `main` publishes the dev channel's binaries and images only from a commit that changes what ships (Go, the UI or `deploy/`) so a documentation merge leaves `--version dev` where it was. A manual dispatch runs everything. CodeQL, govulncheck, the fuzz targets and Scorecard run only when their own inputs change, and weekly regardless. A pull request's checks stop when the pull request does. Pushing to it replaces the run in flight, and closing or merging it starts a run that takes over the pull request's concurrency group, cancels the one still going and skips every job, so a merged pull request does not keep its fleet slots while the commit on `main` queues behind them. Only a pull request is ever cancelled: runs on `main` queue instead, because they publish the dev channel. A new workflow that runs on `pull_request` needs the same three parts, and `internal/docs` fails without them: the `closed` type, the shared group, and a `github.event.action != 'closed'` guard leading every job's condition. ## Commit and open a pull request Use clear, imperative commit subjects: ```sh git add path/to/changed-file git commit -m "Add runner reconnect backoff" git push -u origin fix/agent-reconnect ``` Open a pull request against `eyupio/zoomies:main`. Include: - the problem and why it matters; - the chosen approach and important trade-offs; - the tests and manual checks performed; - screenshots or a short recording for visible UI changes; - documentation, configuration, migration, or compatibility impact; and - any follow-up work deliberately left out. Keep the branch current with `main`, respond to review comments, and avoid mixing unrelated cleanup into the same pull request. Maintainers may edit, squash, or reword commits when merging. ## Coding and design principles - Prefer straightforward code and explicit failure modes over cleverness. - Errors should tell an operator what is true, why it matters, and what to do next. - Keep the controller usable as one Go binary with SQLite; do not introduce a required external database or orchestration platform without prior agreement. - The CLI and web UI are clients of the same API. Keep their behaviour and terminology aligned. - Preserve accessibility, responsive layouts, keyboard operation, light and dark themes, and the guidance in [docs/ui-guidelines.md](docs/ui-guidelines.md). - Treat logs, metrics, audit records, and documentation as part of the feature, not an afterthought. ## Releases Maintainers cut releases. The shape a tag must have, the signing key and what to do when a release build fails are in [RELEASING.md](RELEASING.md). ## Licence By submitting a contribution, you agree that it may be distributed under the repository's [GNU Affero General Public License v3.0](LICENSE). Thank you for contributing to Zoomies.