# jprov Plan

Date: 2026-02-14

## Goal
Build `jprov`, a FreeBSD jail provisioning tool that treats jails as nearly stateless, recreating them from a template, copying a filesystem overlay, and running a provisioning command inside the jail. If provisioning fails, the run fails. If the jail already exists, it is deleted and recreated. `-y` skips confirmation.

## Current Understanding
- Primary command: `jprov [jailname]`.
- Reads a main config from `/etc/jprov.conf` (or `/usr/local/etc/jprov.conf`) which defines where per-jail configs live.
- Per-jail config is TOML and includes at least: template, provisioning command (`cmds.provision`), and nullfs mounts. `jailconf` is a map of keys to values for jail.conf entries.
- Creates a new thin jail as a ZFS clone of the template dataset.
- Deep-copies a tree into the jail filesystem (e.g. `jailname/usr/local/bin/foo` -> `/usr/local/bin/foo`).
- Runs a provisioning command inside the jail; provisioning success depends on this command's exit status.
- `up` fails if the jail already exists. `down` deletes the jail; `-y` skips all confirmations.
- Backend is plain `jail(8)` + ZFS (potentially via `jail.conf.d` emission).
- Volume mounts are `nullfs` from host ZFS datasets mounted on the host.
- Example directory layout under a configurable base (e.g., `/usr/local/jails/`):
   - `templates/RELEASE-15.0-p3/`
   - `defs/<jailname>/jprov.conf`
   - `defs/<jailname>/overlay/usr/...`
- `jprov` is intended to be run after system/template upgrades, not on every jail restart.
- Implementation language: Python 3.
- Destructive operations should be delayed as late as possible; validate configs and paths first to avoid tearing down a working jail on config errors.
- N+1 provisioning is not used; jprov stops and destroys the existing jail before creating and provisioning the new jail.
- Provide clear, informative progress output so the user can see each step at a glance.
- Capture all script output to log files in a configurable log dir (default `/var/log/jprov/`); on fatal error, print: "script logs written to <logpath>.log".

## Open Questions (Need Confirmation)
None.

## Milestones
1. Confirm requirements and config schema.
2. Define operational workflow and backend commands.
3. Implement config parsing and validation.
4. Implement jail lifecycle (delete, create, overlay copy).
5. Implement provisioning execution and failure handling.
6. Add CLI flags, confirmations, and logging.
7. Add tests and documentation.

## Architecture (Proposed)
Files and purposes:
- `bin/jprov` (entrypoint): CLI argument parsing, `-y` handling, top-level orchestration.
- `jprov/main.py`: main flow controller; calls config loading, validation, and provisioning steps.
- `jprov/config.py`: parse and validate main config and per-jail TOML; resolve paths; normalize defaults.
- `jprov/jailconf.py`: generate `jail.conf.d/<name>.conf` from config, including `extra_jail_conf`.
- `jprov/zfs.py`: ZFS operations (clone, destroy, dataset existence checks).
- `jprov/mounts.py`: nullfs mount/unmount operations and validation of host paths.
- `jprov/overlay.py`: deep copy overlay into jail root.
- `jprov/provision.py`: run provisioning command via `jexec <name> <cmd>` with env vars.
- `jprov/runner.py`: subprocess wrapper (stdout logging, error handling, and uniform exit code behavior).
- `docs/PLAN.md`: evolving plan and decisions.
- `docs/CONFIG.md`: user-facing config reference (main and per-jail TOML).
- `docs/USAGE.md`: CLI usage and examples.

Notes:
- Keep module boundaries small and testable; all shell commands go through `runner.py`.
- Provide a dry-run mode later if desired (not in v1 unless requested).

## Next Actions
1. Finalize main config schema and defaults.
2. Draft per-jail TOML schema and document in `docs/CONFIG.md`.
3. Scaffold Python package and CLI entrypoint.

## Main Config (Proposed)
```toml
base_dir = "/usr/local/jails"

[datasets]
templates_prefix = "zroot/jails/templates/"
containers_prefix = "zroot/jails/containers/"

jail_conf_dir = "/etc/jail.conf.d"
log_dir = "/var/log/jprov"
```

Notes:
- `assume_yes` is CLI-only via `-y`.
