Development
Run the complete stack from source, work on the TypeScript workspace, and cut releases.
Use the default development shell for project tooling:
nix developThe shell provides Go, pnpm, Chromium, GStreamer, Weston, and the code-quality tools. It does not start Aperture or change host configuration.
Run the complete stack
Run the Nix-built software-rendering image with rootless Podman:
nix run .#devThe runner mounts the current checkout at /workspace, so run it from the repository root.
The command builds and loads a development image, then runs it in the foreground. Later starts reuse the image when its Nix store path and image digest are unchanged. The image adds Node and pnpm to the production runtime, and s6 runs Vite beside Aperture and Traefik. Vite watches the mounted checkout, applies frontend changes live, and proxies API and session traffic to Traefik inside the container. Production images do not include the development tools or Vite service.
The runner derives a stable address from the worktree path within Linux's 127.0.0.0/8 loopback network. It publishes Vite and WebRTC on that address. Different worktrees can therefore reuse HTTP port 8080 and WebRTC UDP ports 50000-50010 without collisions. Application state persists in a Podman volume derived from the worktree path. The runner prints its name at startup.
The runner provisions a default tenant and writes the full-access system-admin token to .data/admin-token when it initializes the state volume. Later runs skip provisioning. Git ignores .data, and the runner creates the directory with mode 0700 and the token with mode 0600. The first start after upgrading from the bind-mounted state layout copies an existing .data/store into the volume and leaves the original directory untouched.
Load the token into your shell with:
export APERTURE_TOKEN=$(<.data/admin-token)Stop Vite and the container with Ctrl-C.
Custom port and settings
Choose another public port with --port:
nix run .#dev -- --port 18080The Podman container name defaults to the repository directory, which is unique across normal Worktrunk worktrees. Override the name, loopback address, or UDP range when needed:
nix run .#dev -- \
--container-name aperture-local \
--bind-address 127.80.0.1 \
--udp-port-range 62000-62010Each worktree gets its own state volume, container name, and loopback address, so parallel worktrees can keep the default ports.
Pass Aperture environment overrides with a Podman environment file:
nix run .#dev -- --env-file ./dev.envFor example:
APERTURE_LOG_LEVEL=debug
APERTURE_WEBRTC_MEDIA_PRODUCER_CODEC=vp8
APERTURE_WEBRTC_COMPOSITOR_WIDTH=1920
APERTURE_WEBRTC_COMPOSITOR_HEIGHT=1080The runner sets APERTURE_EXTERNAL_BASE_URL to the selected loopback address and port. Use the environment file for other Aperture settings.
Mount a complete configuration file when environment overrides are insufficient:
nix run .#dev -- --config ./aperture.dev.tomlThe file must be readable by the container user and must not be group or world writable. Put secrets in the environment file rather than a world-readable TOML file.
GPU runtime
Use the broad-driver image and select a DRM render node:
nix run .#dev-gpu -- --render-node /dev/dri/renderD128The render node defaults to /dev/dri/renderD128. The host user must belong to the group that owns the device. The runner preserves supplementary groups inside rootless Podman and enables hardware GPU mode with Weston's GL renderer.
Intel and AMD render nodes use the drivers bundled in the GPU image. NVIDIA uses the host driver through CDI. On NixOS, enable it before running dev-gpu:
hardware.nvidia-container-toolkit.enable = true;The runner detects an NVIDIA render node and requests its matching CDI GPU device. Codec auto falls back to VP8 because NVIDIA does not expose the VA-API encoder used by h264-va.
Use nix run .#dev -- --help or nix run .#dev-gpu -- --help for the complete option list.
TypeScript workspace
The pnpm workspace holds the web app, this documentation site and the restore worker (apps/), the browser extensions (extensions/) and the shared packages (packages/). From the repository root:
pnpm install
pnpm format:check && pnpm lint && pnpm typecheck && pnpm buildDependency versions live in the catalogs in pnpm-workspace.yaml; manifests reference them as catalog: (or catalog:peers for the peer ranges of published packages). pnpm add does this automatically, and pnpm lint fails on a manifest that names a version directly.
Documentation
This site lives in apps/docs (Fumadocs on TanStack Start). Pages are MDX files in apps/docs/content/docs; the package pages generate their type tables from the packages' sources with <auto-type-table>, and the REST API reference renders api/openapi.yaml with Scalar.
pnpm --filter @aperture-browser/docs devThe docs workflow builds the site and deploys it to Cloudflare Pages with wrangler pages deploy. Pushes to master that touch the docs, the packages or the OpenAPI spec update the production site; pull requests from this repository that touch them get a preview deployment on the pr-<number> branch alias, linked in a PR comment. Deploys need the CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID secrets. Each Aperture instance also serves its own API reference at /docs.
Releases
release-please cuts a release when its pull request merges. Besides the binaries and the Docker image, a release:
- publishes the npm packages under
packages/that are not private (@aperture-browser/api-schema,api-client,live-session,session-reactandsession-element) at the release version, which release-please also writes into theirpackage.jsonfiles; - attaches a zip of every extension whose
package.jsonsets"aperture": { "releaseZip": true }, with the extension manifest's version set to the release version.
The npm.yml workflow publishes the packages when the release is published. It uses trusted publishing: every package trusts this repository's npm.yml workflow, so publishing needs no token. npm only accepts a trusted publisher for a package that already exists, so a new package is published once by hand, then trusted with npm trust github <package> --repo tarik02/aperture --file npm.yml --allow-publish. pnpm publish --recursive --filter "./packages/**" --no-git-checks --dry-run shows what would be published.
Pull request builds
Adding one of these labels to a pull request starts a build of its head commit:
| Label | Result |
|---|---|
build-binaries | The release archives, as a workflow artifact |
build-docker | Docker images tagged pr-<number> on GHCR, linked in a PR comment |
Both workflows run on pull_request_target, so their definition comes from the default branch, their runs show on the pull request, and pull requests from forks build too. A shared gate job removes the label and stops the run unless whoever added it has write access. The build then checks out the pull request's head commit. Adding a label approves running that commit with the workflow's credentials, so review it first.
These runs share the default branch's cache scope, so they only restore caches, which the nightly workflow saves; build-docker builds without the Nix cache.