Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,116 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

---

## [0.9.30] — 2026-05-10

**Rootless track, default flip.** Thirty-first 0.9.x release.
The `rootless_per_user` master toggle now defaults to **true**.
New installs (and old installs whose `crated.conf` does not
set the field explicitly) compose paths, ZFS dataset prefix,
network sub-CIDR, and RCTL umbrella loginclass from the
connecting operator's uid. Operators who want the legacy
single-tenant shape must opt out with `rootless_per_user: false`.

### What changes

#### Default value flipped

```cpp
// daemon/config.h — was 0.9.12 .. 0.9.29
bool rootlessPerUser = false;

// daemon/config.h — 0.9.30+
bool rootlessPerUser = true;
```

The field default flips at the C++ level. The YAML key name
is unchanged (`rootless_per_user`), and the parser still
honours an explicit `false` value byte-for-byte the same way
it always did.

#### Sample config rewritten

`daemon/crated.conf.sample` now shows the toggle commented
out at its new default value (`# rootless_per_user: true`),
with prose explaining that operators wanting the legacy shape
must uncomment to `false` explicitly. The "0.9.14" forward-
reference is replaced with a "default since 0.9.30" note.

#### Migration doc updated

`docs/rootless-migration.md` rewrote two sections:

1. **Status** — bumped to "rootless model is on by default",
with explicit `rootless_per_user: false` callout for
operators who want to opt out
2. **Single-tenant migration** — split into two paths
(accept the flip vs. pin to legacy), added a dedicated
"Rolling back" subsection covering the toggle pin +
restart + jail recycle procedure

The 0.9.13–0.9.30 release-by-release changelog inside the
doc was also brought up to date (was last touched at 0.9.13
planning).

### Behaviour for upgraders

| Pre-0.9.30 `crated.conf` state | 0.9.30 effective value | Action needed |
|---------------------------------------------|------------------------|---------------------|
| `rootless_per_user: true` (explicit) | `true` | None |
| `rootless_per_user: false` (explicit) | `false` | None |
| key absent (most 0.8.x → 0.9.x upgrades) | `true` (was `false`) | Recycle jails OR pin to false |

The third row is the breaking case: a 0.8.x deployment that
upgraded through 0.9.x without ever touching `crated.conf`
gets rootless mode at the next `service crated restart`.
Operators in that boat who don't want to migrate jails should
add `rootless_per_user: false` before restarting crated.

The daemon does not auto-rearrange ZFS datasets or migrate
existing jails — operators control the cutover by stopping
and re-running their jails after the flip. See the migration
doc's "Path A — accept the flip" section.

### Wire compatibility

No wire changes. All 21 privops verbs from 0.9.0–0.9.28
unchanged. JSON + libnv schemas unchanged. Bearer token
format unchanged. The flip is purely a config-default change
in the daemon.

### Series state

Track complete except for setuid removal:

- 0.9.0–0.9.7: privops verb taxonomy (14 verbs, JSON)
- 0.9.8–0.9.13: per-user namespacing pure modules + audit
- 0.9.14: libnv listener (FreeBSD-native, getpeereid)
- 0.9.15–0.9.29: 14 CLI call sites wired through privops;
verb set grew to 21
- **0.9.30: default flip (this release)**
- 1.0.0: setuid bit removed from `Makefile install`

### Tests

No new tests — the change is a single struct member default.
Suite stays at 1303. Existing `Crated::Config::load` coverage
exercises both the absent-field path (now defaults true) and
the explicit-false path. FreeBSD CI smoke-runs the daemon
startup which validates the default end-to-end.

### Files

- `daemon/config.h` — default flipped to `true`, comment
expanded with 0.9.30 paragraph and rollback pointer
- `daemon/crated.conf.sample` — block rewritten to show
new default + opt-out instructions
- `docs/rootless-migration.md` — status, schema example,
migration path, and release-by-release log updated
- `cli/args.cpp` — version `crate 0.9.30`
- `CHANGELOG.md` — this entry

---

## [0.9.29] — 2026-05-10

**Rootless track, RCTL umbrella auto-apply.** Thirtieth 0.9.x
Expand Down
4 changes: 2 additions & 2 deletions cli/args.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -753,7 +753,7 @@ Args parseArguments(int argc, char** argv, unsigned &processed) {
args.noColor = true;
break;
} else if (strEq(argv[a], "--version")) {
std::cout << "crate 0.9.29" << std::endl;
std::cout << "crate 0.9.30" << std::endl;
exit(0);
} else if (auto argShort = isShort(argv[a])) {
switch (argShort) {
Expand All @@ -764,7 +764,7 @@ Args parseArguments(int argc, char** argv, unsigned &processed) {
args.logProgress = true;
break;
case 'V':
std::cout << "crate 0.9.29" << std::endl;
std::cout << "crate 0.9.30" << std::endl;
exit(0);
default:
err("unsupported short option '%s'", argv[a]);
Expand Down
10 changes: 7 additions & 3 deletions daemon/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,13 @@ struct Config {
// back to legacy single-tenant shape so existing 0.8.x deployments
// are byte-identical.
//
// Default: false. Operators flip after reading
// docs/rootless-migration.md. The default flips to true in 0.9.14.
bool rootlessPerUser = false;
// 0.9.30: DEFAULT FLIPPED to true. New installs (and old installs
// whose crated.conf doesn't set this field explicitly) now run
// in rootless mode. Operators wanting the legacy single-tenant
// path must add `rootless_per_user: false` to crated.conf
// explicitly. See docs/rootless-migration.md "Rolling back" for
// the procedure.
bool rootlessPerUser = true;

// Master ZFS prefix; per-user datasets land under
// `<zfsMasterPrefix>/<uid>/<jail>`. Empty string means "no
Expand Down
25 changes: 15 additions & 10 deletions daemon/crated.conf.sample
Original file line number Diff line number Diff line change
Expand Up @@ -139,17 +139,22 @@ log:
##
## --- 0.9.12: rootless per-user namespacing ---
##
## Master toggle. When false (default), runtime helpers fall back to
## legacy single-tenant shape — every operator shares /var/run/crate/,
## one ZFS prefix, one IP pool, no RCTL umbrella. Existing 0.8.x
## deployments are byte-identical at false.
##
## When true, per-user separation kicks in. See
## docs/rootless-migration.md for the full migration guide.
##
## The default flips to true in 0.9.14.
## Master toggle. When true (default since 0.9.30), per-user
## separation kicks in: runtime helpers compose paths, ZFS dataset
## prefix, network sub-CIDR, and RCTL umbrella loginclass from the
## connecting operator's uid (via getpeereid(2) on the privops
## socket).
##
## Set to false to fall back to legacy single-tenant shape — every
## operator shares /var/run/crate/, one ZFS prefix, one IP pool,
## no RCTL umbrella. Existing 0.8.x deployments that need
## byte-identical behaviour after upgrade should set this to false
## explicitly.
##
## See docs/rootless-migration.md for the full migration guide,
## including the "Rolling back" section for downgrade procedure.
#
# rootless_per_user: false
# rootless_per_user: true

##
## ZFS master prefix. Per-user datasets land under
Expand Down
129 changes: 95 additions & 34 deletions docs/rootless-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
considering the move to the rootless model that lands in
**1.0.0**.

**Current status (0.9.12):** the rootless model is opt-in. The
default install is unchanged — `crate(1)` is still installed
with `mode 04755` (setuid root), and `crated.conf` ships with
`rootless_per_user: false`. This document tells you what
changes when you flip the toggle, what infrastructure the
daemon takes over, and how to migrate existing single-tenant
deployments without downtime.
**Current status (0.9.30):** the rootless model is **on by
default**. New installs (and old installs whose `crated.conf`
doesn't set `rootless_per_user` explicitly) compose paths,
ZFS prefixes, network sub-CIDRs, and RCTL umbrellas from the
connecting operator's uid. `crate(1)` is still installed with
`mode 04755` (setuid root) until 1.0.0; the setuid bit is
removed in the 1.0.0 release. Operators wanting the legacy
single-tenant shape must opt out explicitly with
`rootless_per_user: false` — see the "Rolling back" section
below.

---

Expand Down Expand Up @@ -111,12 +114,14 @@ Stable across crated restarts; collisions at slot capacity.
Each operator gets a loginclass `crate-<uid>` whose RCTL
umbrella caps total usage across all of that operator's jails.

### 0.9.12 — config schema (this release)
### 0.9.12 — config schema

`crated.conf` gains:

```yaml
rootless_per_user: false # master toggle, default off
rootless_per_user: true # master toggle (default
# since 0.9.30; was off
# in 0.9.12–0.9.29)

zfs_master_prefix: "zroot/jails" # per-user datasets land
# under <prefix>/<uid>/
Expand All @@ -126,22 +131,47 @@ network_master_cidr_v6: "" # empty disables v6 per-user
network_sub_prefix_len_v6: 64
```

When `rootless_per_user: false` (the default), every helper
that consults the config falls back to legacy single-tenant
behaviour. **Existing deployments are byte-identical to 0.8.x**
until the operator explicitly opts in.
Setting `rootless_per_user: false` makes every helper fall
back to legacy single-tenant behaviour, byte-identical to
0.8.x. Set this if you upgraded from 0.8.x and don't yet
want the per-user split.

### 0.9.13 (planned) — wiring flip
### 0.9.13 — wiring flip

`lib/network_lease.cpp` switches to per-user lease files when
the toggle is on. RCTL handlers apply both per-jail and
loginclass umbrella rules. Audit log gets a per-user copy.

### 0.9.14 (planned) — default flip
### 0.9.14 — libnv privops listener

`crated.conf.sample` ships with `rootless_per_user: true` by
default. Operators upgrading see a one-line `pkg upgrade`
warning pointing at this doc.
`crated` opens an AF_UNIX socket and accepts nvlist-encoded
privops requests from local clients. `getpeereid(2)` extracts
the operator's uid for free, feeding the per-user audit hook.
HTTP `/api/v1/privops/<verb>` remains for remote/CI clients.

### 0.9.15–0.9.29 — call-site wiring + verb expansion

`crate(1)` call sites moved through privops one mini-PR at a
time: `set_rctl`/`clear_rctl`, `attach_zfs`, `destroy_jail`
on stop, `mount_nullfs`, `iface` atoms (`set_iface_up`,
`disable_iface_offload`, `bridge_add_member`, `bridge_del_member`,
`set_iface_inet_addr`, `create_epair`), `set_loginclass_rctl` /
`clear_loginclass_rctl`. The privops verb taxonomy grew from
14 to 21 verbs; the original 14 stayed wire-stable.

0.9.27 lazy-resolved network leases to per-user paths when
the privops socket is detected. 0.9.29 added an opt-in
`rctl_umbrella:` block that the daemon auto-applies to the
operator's `crate-<uid>` loginclass after `create_jail`.

### 0.9.30 — default flip

`bool rootlessPerUser = true;` in `daemon/config.h`. The
sample `crated.conf` shows the flag commented out at its new
default value; existing crated.conf files without the field
auto-flip on upgrade. Operators wanting the legacy single-
tenant path must add `rootless_per_user: false` explicitly —
see "Rolling back" below.

### 1.0.0 (planned) — setuid removed

Expand All @@ -154,40 +184,71 @@ patch the Makefile or pin to 0.9.x.

## Migration steps for a single-tenant deployment

Single-tenant operators don't need rootless mode but may want
to opt in to dry-run the new flow before 1.0.0 lands.
Single-tenant operators upgrading from ≤ 0.9.29 to ≥ 0.9.30
auto-flip into rootless mode unless their `crated.conf` sets
`rootless_per_user: false` explicitly. Choose one of the two
paths below.

### Path A — accept the flip

```sh
# 1. Update crated.conf — opt-in toggle plus defaults
# 1. Upgrade the package (default flips to true).
pkg upgrade crate

# 2. Restart crated.
service crated restart

# 3. Stop and restart your jails so they re-create with the
# per-user layout. (No automatic in-place migration; the
# daemon does not rearrange ZFS datasets on upgrade.)
crate stop myjail
crate run myjail
ls /var/run/crate/$(id -u)/leases/
```

### Path B — keep the legacy single-tenant shape

```sh
# 1. Pin the toggle off before restarting crated.
cat >> /usr/local/etc/crated.conf <<'EOF'

# 0.9.12 — rootless namespacing (off by default)
# Pinned to legacy single-tenant shape (was the default
# pre-0.9.30; explicit setting required from 0.9.30 onward).
rootless_per_user: false
EOF

# 2. Restart crated
# 2. Restart crated.
service crated restart

# 3. Verify legacy behaviour preserved
# 3. Verify legacy behaviour preserved.
crate run myjail # works as before
ls /var/run/crate/ # contains crated.sock + legacy files,
# no per-uid subdirs
```

### Rolling back

If you upgraded to 0.9.30+ and the per-user split misbehaves:

# 4. Opt in (dry-run)
sed -i '' 's/rootless_per_user: false/rootless_per_user: true/' \
```sh
# 1. Stop active jails.
crate stop --all

# 2. Pin the toggle off.
sed -i '' 's/^# *rootless_per_user:.*/rootless_per_user: false/' \
/usr/local/etc/crated.conf
service crated restart
grep -q '^rootless_per_user:' /usr/local/etc/crated.conf || \
echo 'rootless_per_user: false' >> /usr/local/etc/crated.conf

# 5. Stop and restart your jails so they re-create with the
# per-user layout. (No automatic in-place migration in 0.9.12;
# that lands in 0.9.13 with the wiring flip.)
crate stop myjail
# 3. Restart and recycle jails.
service crated restart
crate run myjail
ls /var/run/crate/$(id -u)/leases/
```

Rolling back is `rootless_per_user: false` + `service crated
restart` + jail recycle.
No package downgrade needed — the toggle is preserved across
0.9.30+ releases. Downgrading to ≤ 0.9.29 is also supported
(the YAML key is the same and the legacy code path is the
0.9.29 default).

---

Expand Down
Loading