Aperture

Development

Run the complete stack from source, work on the TypeScript workspace, and cut releases.

Use the default development shell for project tooling:

nix develop

The 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 .#dev

The 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 18080

The 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-62010

Each 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.env

For example:

APERTURE_LOG_LEVEL=debug
APERTURE_WEBRTC_MEDIA_PRODUCER_CODEC=vp8
APERTURE_WEBRTC_COMPOSITOR_WIDTH=1920
APERTURE_WEBRTC_COMPOSITOR_HEIGHT=1080

The 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.toml

The 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/renderD128

The 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 build

Dependency 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 dev

The 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-react and session-element) at the release version, which release-please also writes into their package.json files;
  • attaches a zip of every extension whose package.json sets "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:

LabelResult
build-binariesThe release archives, as a workflow artifact
build-dockerDocker 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.

Edit on GitHub

On this page