# jera
**The shared infra runtime** — the non-UI job/infra engine lifted out of
[nornir](https://git.gunnar.rs/nordisk/nornir) so both **nornir** and **korp** depend
on one lean crate instead of pulling each other's dependency trees (no dep-hell).
jera is runtime only. The job-list *UI* stays in `facett-jobview`; jera is the
engine underneath it.
## What's in it
- **`jera::jobs`** — the durable **job ledger / engine**. One redb list
(`jobs.redb`) of every long-running operation (`running → done|failed` + timing),
orthogonal to whatever result the op produced. Fat-client (writes the local
`jobs.redb`) or thin-client (submits each record over a `JobSink`), an exclusive
serial-scheduler gate, and opportunistic retention. Public API: `JobStore`,
`JobHandle`, `JobSink`, `JobRecord`, `JobSelector`, the `kind` / `status` modules.
- **`jera::container`** — a **no-shell container runtime** over the podman/Docker
**REST API** ([bollard]). Bring a `ContainerSpec` up as a detached container,
stream its logs, and tear it down (`BollardBootRunner` / `BollardEngine`), or run
one to completion **as a tracked job** (`run_container`). There is **no
`std::process::Command`** and no CLI shell-out anywhere on this path — the
constellation's zero-shell charter. (Feature `bollard`, on by default.)
- **`jera::vm`** — a **no-shell KVM/VM boot** seam. Map a self-contained `BootSpec`
onto the hypervisor boot primitive and drive it as a job. `StubVmBootRunner` is
the honest not-wired default; `TunnrVmBootRunner` (feature `vm-tunnr`) drives
tunnr's real `tunnr_vm::boot_test` KVM primitive.
- **`jera::boot`** — the engine-agnostic boot lifecycle (`BootStatus`, `BootProc`,
`BootRunner`) shared by the container / VM / exe runners, and the job-bound
**`BootJobs`** manager: the reusable "start X as a tracked job" engine, with the
bidirectional lifecycle wired (kill the job → kill the boot; the boot dies → the
job terminates).
## Start a container as a job
```rust
use jera::jobs::JobSink;
use jera::boot::{BootJobs, BootTarget};
use jera::container::ContainerSpec;
let mut jobs = BootJobs::new(JobSink::noop(), "my-workspace");
let id = jobs.boot(BootTarget::Container(ContainerSpec {
image: "docker.io/library/nginx:alpine".into(),
ports: vec![8080],
..Default::default()
}));
jobs.poll(); // drives the lifecycle both ways
jobs.kill(&id); // stop + remove over the REST API
```
Or run one to completion and capture its outcome (feature `bollard`):
```rust
let outcome = jera::container::run_container(
JobSink::noop(),
&ContainerSpec::image("docker.io/library/alpine:3"),
"alpine", "my-workspace",
)?;
assert_eq!(outcome.exit_code, Some(0));
```
A `ContainerSpec` also carries **airgap isolation** and **bind mounts** — additive
knobs (default = unchanged, existing specs byte-identical):
```rust
use jera::container::{ContainerSpec, NetMode, BindMount};
let spec = ContainerSpec {
net: NetMode::None, // --network none: no egress (airgap)
mounts: vec![BindMount::ro("/etc/certs", "/certs")], // -v /etc/certs:/certs:ro
..ContainerSpec::image("packer:1")
};
assert_eq!(spec.network_arg().as_deref(), Some("--network none"));
assert_eq!(spec.bind_strings(), vec!["/etc/certs:/certs:ro".to_string()]);
```
Mounts are threaded into draupnir's mount-aware run (`run_to_completion_with_binds`);
`net` is the rendered contract routers gate on (the OCI engine hook is a draupnir
follow-up). See [`.nornir/container-spec.md`](.nornir/container-spec.md).
## Features
| feature | default | what it adds |
|-------------|---------|--------------|
| `bollard` | ✅ | the real podman/Docker REST engine + `run_container` |
| `vm-tunnr` | — | the real KVM backend over `tunnr_vm::boot_test` (dep pending the tunnr repo) |
A lean consumer sets `default-features = false` (nornir does, then forwards its own
`test-boot-container-bollard` feature to `jera/bollard`).
## Status
Extracted from nornir; consumed by nornir (via the `nornir-jobs` shim + the
`test-boot` runtime) and destined for korp. This standalone sibling directory will
move into a shared repo later.
[bollard]: https://docs.rs/bollard
## License
MIT OR Apache-2.0.