|
root / VISION.md
VISION.md markdown 209 lines 12.6 KB

repoman — Vision Doc

Status: v0.1 design draft
Date: 2026-04-29
Origin: Bash prototype at ~/.local/bin/repoman (~200 LOC). This doc captures the design intent for a real productized rewrite in reef-lang.


Vision sentence

repoman is the bridge between a developer's local NVMe working copies, their per-project isolated Incus containers, and their homelab NFS/ZFS backup — one tool, opinionated conventions, both interactive and flag-driven.

Why this exists

The original problem: enable running Claude Code (or any agent / risky tool) with --dangerously-skip-permissions while limiting blast radius. Each project lives in its own Incus container; the host stays safe because the worst case is "lose a container, not the host."

That naturally turned into a workflow: per-project containers + bind-mounted repos + NFS backup + ZFS snapshots for history. Doing it with raw incus and rsync works but accretes ad-hoc shell scripts. repoman makes the workflow first-class.

Audience

Homelab developer running:

  • Incus for containerization (Linux-only for v0.1).
  • Local SSD/NVMe for active project working copies (latency matters).
  • NFS-mounted remote storage for durable backup (ideally ZFS-backed, for snapshot history).
  • One developer, multiple projects. Not multi-user, not multi-host coordination.

Realistic audience size: small. Niche tool serving its narrow audience well > generic tool nobody loves.

Differentiators (vs rolling your own scripts)

  1. Incus project for ownership. All repoman-managed containers live in an incus project create repoman namespace — clean scoping, no leakage into user's other Incus work, easy to nuke the entire estate.
  2. Single tool covering container lifecycle + project conventions + backup. Existing tools each cover one slice (incus for containers, restic/borg for backup, distrobox for dev shells); none do all three with conventions tuned for "code projects on NFS/ZFS homelab."
  3. Opinionated backup story. rsync mirror + autofs-aware mount checks + ZFS snapshots assumed on the file server. No bespoke history layer — ZFS is the time machine.
  4. Both interactive and flag-driven. Same code path serves a guided wizard and a shell-scriptable CLI.
  5. Native single-binary distribution via reef compiling to native code. No runtime install for users.

v0.1 subcommands

Every subcommand has both an interactive form (when args missing) and a flag-driven form (when args provided). Identical underlying code path.

Subcommand Purpose
repoman setup First-time host setup. Creates Incus project repoman, ensures profiles exist (claude-share or successor), validates LOCAL_REPOS and NFS_REPOS, writes initial registry. Idempotent.
repoman new <name> [--repo <dir>] [--image <img>] Create a new managed container. Bind-mounts the repo, applies profiles, restarts. Interactive form prompts for unset values.
repoman list List managed containers: name, state, IP, repo path, image, last sync. Reads from registry + reconciles against incus list --project repoman.
repoman shell <name> Drop into the container as the local user, in the repo's directory. Replaces the verbose incus shell --user 1000 --cwd ... invocation.
repoman sync [name] [--no-delete] [--dry-run] rsync mirror local → NFS, all projects or one. NFS auto-mount aware. ZFS snapshots on the file server provide history.
repoman remove <name> [--keep-repo] Destroy the container; repo dir untouched by default.
repoman status [name] Health check: container state, mounts, NFS reachability, last sync, drift between registry and Incus.

Out of scope for v0.1:

  • Other container runtimes (Docker, Podman) — Incus only.
  • Other backup destinations (S3, restic, borg) — NFS only.
  • macOS/Windows — Linux only.
  • Multi-host coordination, replication.
  • ZFS snapshot management (file-server-side; user runs sanoid themselves).
  • Cron installation (provide a doc snippet, not a subcommand).
  • IDE integration.

Configuration

Central registry: ~/.config/repoman/repoman.toml

Tracks every project repoman owns. Source of truth for "what should exist."

[defaults]
local_repos    = "~/repos"
nfs_repos      = "/nfs/repos"
incus_project  = "repoman"
default_image  = "images:ubuntu/26.04/cloud"
profiles       = ["default", "claude-share"]

[[project]]
name        = "isurus"
repo        = "isurus-project"   # relative to local_repos
image       = "images:ubuntu/26.04/cloud"
created     = "2026-04-28T15:00:00Z"
last_sync   = "2026-04-28T20:17:03Z"
backup      = true

Per-project override (optional): <repo>/.repoman.toml

Lets a repo declare its own runtime needs. Read on new, can be re-applied with repoman update <name>.

image     = "images:debian/12/cloud"
profiles  = ["default", "claude-share", "node-dev"]

[[mount]]                       # extra bind-mounts beyond the repo itself
source = "~/.npm"
path   = "/home/ctusa/.npm"

[env]
NODE_ENV = "development"

Drift handling

Source of truth: repoman registry. Reality: Incus DB.

Every command opens with a reconciliation pass against incus list --project repoman:

  • Container in registry but not in Incus → flag missing, offer to recreate or remove from registry.
  • Container in Incus (in repoman project) but not in registry → flag orphan, offer to adopt or destroy.
  • Differences in state — note in list / status output.

Reconciliation is read-only by default; modifications need explicit --repair or interactive confirmation.

Implementation: reef-lang

Reef has the right shape for this:

  • Native compilation → C → GCC/Clang → static binary. Single-file distribution.
  • Stdlib coverage we'll use:
    • encoding.toml — config files (central registry + per-project)
    • encoding.json — Incus REST API responses
    • net.http (with bundled LibreSSL) — talk to Incus over unix-socket REST API
    • net.unix — raw unix-socket primitives (unix_connect/unix_send/unix_recv), fallback if net.http doesn't accept a unix-socket transport
    • sys.args / sys.flag — CLI argument and flag parsing
    • sys.env — env var reads (LOCAL_REPOS, NFS_REPOS, etc.)
    • sys.process — argv-list process_spawn(program, [argv]) for rsync/incus shell-outs (full POSIX surface incl. wait/kill/signal/setsid)
    • sys.signal — graceful Ctrl-C handling during long syncs
    • io.console — interactive prompts for the wizard mode
    • io.file, io.dir, io.path, io.stream — local FS ops
    • fs.stat, fs.perm — file metadata, permissions, uid/gid name resolution
    • core.result, core.option — error handling primitives
    • Active Objects — natural fit for parallel operations (e.g. repoman sync syncing N projects concurrently, or healthchecks across containers)
  • Multi-platform reef = future macOS/BSD/illumos support comes for free if/when needed.

Open implementation questions

  1. Subprocess execution. (Resolved — 2026-04-29) reef-stdlib/sys/process.reef exports a comprehensive POSIX surface: process_spawn(program, argv: [string]), process_exec(program, argv), process_spawn_shell(cmd), process_fork(), plus process_wait / process_try_wait / process_wait_any[_nohang], process_kill / killpg, process_setsid / process_set/getpgid, umask, getpid / getppid, and post-wait introspection (process_exit_code, process_exited_normally, process_was_signaled, process_term_signal). Implementation rule for repoman: use process_spawn(program, argv) exclusively for rsync and incus invocations — never process_spawn_shell, so user-derived names (container/repo/path) cannot cause shell injection.
  2. Incus integration: REST vs CLI shell-out.
    • REST (preferred long-term): unix socket /var/lib/incus/unix.socket, JSON in/out, structured error codes. Cleaner. Reef has raw unix-socket I/O via net.unix; whether net.http accepts a unix-socket transport directly still needs verification — if not, build a minimal HTTP-over-unix on top of net.unix.
    • CLI shell-out (faster to bootstrap): parse incus list --format json. Brittle but works today.
    • Recommendation: shell out for v0.1 (using process_spawn, argv-list), migrate per-command to REST as we go.
  3. Interactive UX library. io.console provides line I/O. For richer prompts (default values, validation, menus), do we build a thin reef wrapper or keep it line-based? Recommendation: line-based prompts in v0.1; richer UX is v0.2.
  4. Reef stability. Reef is at 0.5.9. Pre-1.0 means breaking changes possible. Recent reef work has been GC stability fixes (BUG-033, BUG-034 through 0.5.7) — encouraging signal. Building repoman in reef serves as a real-world stress test that helps the language. Coordinate with reef releases for stability windows.
  5. Project structure inside reef. Module layout, build setup, test framework — needs a separate "scaffolding" task before feature work.

Migration from bash prototype

The current bash ~/.local/bin/repoman covers create and sync. The reef rewrite should:

  1. Match feature parity with bash version on day 1 (newcreate, plus sync).
  2. Add setup, list, shell, remove, status as the actual productization.
  3. Introduce the Incus project namespace (the bash version doesn't use it — all containers live in the default project).
  4. Keep config-by-env-var as a fallback for the central registry's [defaults] section, so existing automation isn't broken.

The bash script stays useful as a simpler tool while the rewrite matures. Plan for a clean cut-over once the reef version is stable, not a parallel maintenance burden.

Naming convention (post-fix)

In bash v1, the create command had implicit -project suffix logic that conflated container name with repo name. v0.1 reef contract:

  • Container name = <name> (positional, required).
  • Repo dirname = <name> by default, overridable with --repo <dirname>.
  • Repo absolute path = <local_repos>/<repo-dirname>.
  • No magic suffix logic. Explicit > implicit.

Differentiation reality check

Why someone would pick repoman over their own scripts:

  • They want the Incus-project-as-namespace pattern but don't want to wire it themselves.
  • They want guided setup — first-time onboarding into "this style of homelab dev workflow."
  • They want a unified registry of "what projects do I have, where are they, when did they last back up."

Why they wouldn't:

  • They already have ansible/nix/etc. covering this.
  • They use Docker, not Incus.
  • They don't separate local working copies from backup destinations.

That's fine. The audience is small and that's the intentional design.

Success criteria for v0.1

  • Builds clean from a fresh reef-lang install on Linux x86_64.
  • repoman setup runs idempotently and produces a working baseline on a fresh host.
  • repoman new creates a container that matches the bash prototype's behavior, then surpasses it (uses Incus project, persists in registry, supports per-project config).
  • repoman sync matches bash prototype's behavior with autofs check + mirror semantics.
  • All subcommands have both interactive and flag-driven invocation paths.
  • Single static binary distributable via tarball or a per-distro package.
  • README + man page + at least one end-to-end example walkthrough.
  • License chosen (MIT or Apache-2.0; user's call).
  • Public repo on a forge with a clear contribution guide.

Open product questions

  1. License. MIT? Apache-2.0? Let user decide.
  2. Forge. GitHub, Codeberg, self-hosted? Distribution implications (binaries, releases).
  3. Versioning. Semver. v0.x while pre-1.0 (parallel with reef's own version).
  4. Configuration migration. When repoman.toml schema changes between versions, how do we migrate? repoman config migrate?
  5. Logging / observability. Just stderr for now, or structured (JSON lines) output mode for automation?

Handover note

This doc is the contract. The bash prototype at ~/.local/bin/repoman is the behavioral spec — it's running in production on this host (cron-synced nightly, shipped containers in use). Read its source to understand exactly what new/sync do today before reimplementing in reef.

The next session should start by:

  1. Reading this doc and the bash prototype together.
  2. Auditing reef-lang's stdlib for the gaps called out (notably subprocess exec).
  3. Sketching the reef project skeleton (module layout, Makefile/build, first hello-world repoman --version).
  4. Porting sync first (simpler, less surface area) before new (more moving parts).