Changelog
Notable changes per release. The release workflow refuses to publish a tag whose version has
no section here, and the section becomes the release notes - so write it before bumping
Cargo.toml, then rename Unreleased to the version you are tagging.
Format: Keep a Changelog. Versions follow Semantic Versioning. Releases before 0.159.0 predate this file - see the git history.
[0.168.1] - 2026-10-17
Added
- The docs site shows the release version in the top bar.
Fixed
rooz enterfollows terminal resizes; the session used to keep the size it started with.
[0.168.0] - 2026-10-17
Changed
- Breaking:
portsmappings are<host>:<container>, as in Docker andkubectl port-forward(was<container>:<host>). Swap the numbers in existing configs:"80:8080"that exposed container port 80 on host port 8080 is now"8080:80". - Breaking: remote
--configsources must use SSH. Non-SSH URLs (https://,http://,git://,file://, remote helpers) are refused with a clear message. - Breaking: the sidecar
userkey is removed; it was accepted but never used. Configs that set it fail to parse - useuid.
Added
- Remote
--configacceptsssh://URLs (alsogit+ssh://,ssh+git://), e.g.ssh://git@host:2222/my/configs//cfg.yamlfor a non-standard port.
Fixed
- The examples in
examples/parse again; a unit test keeps them that way. rooz doctor’s network hint counts networks correctly: one per workspace, plus one per sidecar and one per peer relation.- The secrets refusal message showed a
--config git:...syntax that does not exist.
[0.167.0] - 2026-10-17
Added
- Docs site at rooz.dev, built from
docs/with mdBook, including a generated CLI reference and config schema. curl -fsSL https://rooz.dev/install.sh | shinstalls the latest release. Each release carries aninstall.shthat pins its own version andSHA256SUMS, so the binary is checked against data served from rooz.dev, not the GitHub release it comes from. With a logged-ingh, it also checks the signed build provenance.
[0.166.0] - 2026-10-16
Changed
- Breaking: volume names use one scheme: segments joined by
_, workspace first (rooz_<ws>_work,rooz_<ws>_workspace-config,rooz_<ws>_<data>; shared ones stayrooz_sys-config,rooz_cache_<scope>_<path>). The ssh key volume isrooz_ssh-key, wasrooz-ssh-key-vol. Volumes under the old names are not found or migrated: remove them (rooz system pruneremoves everything rooz made), then runrooz system init- a new ssh key is generated and must be registered again.
[0.165.0] - 2026-10-16
Changed
rooz doctorstarts with the endpoint it connects to (the-Hhost’s socket,DOCKER_HOST, or the default socket), and-vlogs it for every command.
Fixed
- An engine that cannot be reached is reported as
Cannot reach the container engine at <where>: <why>, with what to check, instead of the HTTP client’sclient error (Connect). A missing default socket (noDOCKER_HOST, no/var/run/docker.sock) no longer panics.
[0.164.0] - 2026-10-16
Added
rooz new --dry-runandrooz update --dry-run(also with--purge) print what would be removed and created - image pull, volumes, caches, networks, sidecars, the work container and install steps- and change nothing. The configuration is resolved and gated like a real run, so a plan fails with the error a run would. Env values, scripts and secrets are not printed.
rooz exec <ws> -- cmd...runs one command in a workspace container, passes stdout and stderr through and exits with the command’s exit code.--root,--work-dir,--container.rooz list --json;rooz status <ws> [--json](containers, volumes, config origin);rooz logs <ws> [--container] [-f] [--tail N].rooz cache ls|rm|prune: size, age and mounting workspaces of the shared cache volumes.rmandpruneonly touch your own scope and refuse caches a container still mounts.rooz doctorchecks the engine,rooz system initresults (system config, ssh key, age identity) and, on docker, how much of the default network address pool is used.rooz move <ws> <new-name> [--dry-run]renames a workspace on one engine: volumes are copied under the new name, the workspace is rebuilt from its stored config and the old one is removed. A failure removes the partial new workspace and leaves the old one untouched.rooz move <ws> --to <host> [new-name] [--remove-source] [--dry-run]moves a workspace to another engine fromrooz remote: volumes stream across with owners, modes and symlinks intact, the workspace is rebuilt there from its config file, and the source is left stopped unless--remove-source. The target is checked before anything is copied (name free,system initdone, config gates) and a failure removes the partial workspace there.rooz config schemaprints a JSON Schema of the config file for YAML language servers.rooz config show <ws> --resolvedshows the merged settings (defaults, bases, config) with the layer each came from.rooz config template(it was a stub) prints a starter config; with--detect [DIR]it recognizes Rust, .NET, Node, Go and Python projects and fills in the image and cache paths.- Global
-v/--verboseturns on debug logging (RUST_LOGstill works).newandupdateprint one colored[phase]line per step: image, volumes, clone, container, install. rooz update --allow-secrets, as onnew; before, onlyROOZ_ALLOW_SECRETScould consent.
Changed
- Breaking:
rooz listshows STATUS, IMAGE and AGE instead of RUNNING, and includes workspaces whose containers are stopped. - A failed image pull, volume operation or exec returns an error saying what failed and what to check, where it used to panic. A failed pull no longer leaves a stored config behind.
rooz updatepulls the image before removing the old containers, so a failed pull leaves the workspace as it was (an image that only comes frombasesis still pulled afterwards).rooz remotelog lines color each host name consistently.
[0.163.0] - 2026-10-16
Added
installsteps can be objects:run,skip_ifandroot.skip_ifis a shell check that runs first; exit 0 skips the step, so a tool that is already installed (typically from a cache volume) is not installed again.runandskip_ifare templated.
Changed
- Breaking: install steps run as the workspace user, not root (sidecars: as their
uid). The user and its volumes are set up before the first step. Steps that need root, such asapt-getorapk add, must say so withroot: true. A configuration the operator did not author (unlisted--config git:...orbaseslayer) is refused if it asks forroot: true.
[0.162.0] - 2026-10-15
Added
file: <path>on a data value (data, inlinemounts, sidecarmounts) takes its content from a file next to the config that declares it, for scripts and dotfiles that are clumsy inline. Works for local configs, bases and configs from git (read from the checkout). The path stays below the declaring config’s directory; symlinks, files over 64 KiB and non-UTF-8 files are refused. The content is not templated, and the stored config keeps the reference.
[0.161.0] - 2026-10-15
Added
rooz remoteserves many hosts at once. Hosts are defined in~/.config/rooz/remotes.yaml(rooz remote add|rm|ls);rooz remote [names...]forwards each host’s engine socket to$XDG_RUNTIME_DIR/rooz/<host>.sock(~/.rooz/<host>.sockwithout it) and its published ports to localhost. A host that is down retries with backoff without affecting the others.- Global
-H/--host <name>(envROOZ_HOST) runs any command against a configured host.rooz list --all-hostslists every connected host.rooz codeattaches VsCode through the selected host. rooz system pruneandrooz remove --allprint the engine they target.
Changed
- Breaking:
rooz remoteno longer readsROOZ_REMOTE_SSH_URL/DOCKER_HOST, nor forwards to$DOCKER_HOST; define the host withrooz remote addinstead. - A host may set
socketto skip the$DOCKER_HOSTlookup over ssh; an unset one is now an error instead of a panic. - A local port wanted by two hosts is forwarded for the first one and skipped (with a single warning) for the other.
- Errors are printed readably: the first sentence as a headline, the rest wrapped (to
$COLUMNS, at most 100) below it - instead of one long quotedError: "..."line. This covers theROOZ_ALLOW_PRIVILEGED,ROOZ_ALLOW_SECRETS,ROOZ_ALLOW_VPNandROOZ_TRUSTED_CONFIGSrefusals, among others. The exit code is still 1. - Tunnels are dropped and reopened after an SSH reconnect, and the forwarded socket is removed on exit.
Fixed
git pull/git fetchover ssh inside a workspace no longer printhostfile_replace_entries: mkstemp: Read-only file system. The ssh key volume is mounted read-only in workspaces, and ssh kept trying to rewriteknown_hoststhere. The volume now carries an ssh client config withUpdateHostKeys no, written byrooz system init(also on an already initialized engine, so re-running it fixes existing setups) and on clone.
[0.160.0] - 2026-10-12
Added
vpn: trueon a sidecar grantsNET_ADMIN,/dev/net/tunandnet.ipv4.conf.all.src_valid_mark=1- enough forwg-quick(kernel orwireguard-go) and other in-container VPNs, withoutprivileged. Requires operator consent naming the sidecars,ROOZ_ALLOW_VPN=<names>(or=true), separate fromROOZ_ALLOW_PRIVILEGED; avpnsidecar must run as uid 0.ROOZ_TRUSTED_CONFIGSlists configuration sources that are the operator’s own: repository URLs (matched exactly as passed to--config, never overhttp:///git://) and local directories (every file, bases included, must resolve inside one). A trusted source may expand secrets (still subject toROOZ_ALLOW_SECRETS) and declare caches - including throughbases- which previously only a local--configfile without bases could.
Removed
- Breaking: rooz no longer reads a
.rooz.yamlfrom the root of a repository it clones (rooz new -g <url>). Applying a repository’s configuration is now always explicit:rooz new ws -g <url> --config <url>//.rooz.yaml. Same trust rules as before - it is a--config git:...source, authored by the repository. Existing workspaces created from an in-repo config keep updating:rooz updatere-fetches it as a--config git:...source.
Fixed
- A
rooz newrefused for a missingROOZ_ALLOW_PRIVILEGED(orROOZ_ALLOW_VPN) consent no longer leaves the workspace’s config volume behind, so re-running the same command with consent works instead of failing with “Workspace already exists”. A refusedrooz updatekeeps the previously stored config. rooz updatekeeps the workspace’s repository (-g) when the config came from anywhere other than the repository’s own.rooz.yaml. Previously it forgot it: the work container started in/workinstead of the clone, and--purgeremoved the clone without re-cloning.
[0.159.0] - 2026-09-24
Security
- Sidecar runtime images are reused only by the image id rooz recorded when it committed them
(kept on the sidecar container as
dev.rooz.runtime-image), never by the predictablelocalhost/rooz/<workspace>/<sidecar>name. An image tagged under that name by somebody else is ignored and the install stage runs again. - An age identity found in the engine’s
rooz_sys-configvolume is no longer adopted as this machine’s identity. It is taken out of the volume and kept at~/.config/rooz/age.engine.bakfor the operator to recognise and install withrooz system init --force --age-identity "$(cat ...)", because anyone with access to that engine can write a key there. - Configuration that the operator did not author can no longer abort rooz mid-creation: port
mappings,
envkeys,mountsnaming an undefineddata:entry and non-YAMLbasespaths are refused with a message instead of panicking, before any volume or container is created. envkeys must be identifier-shaped, and theROOZ_META_*namespace rooz injects itself is reserved - a config can no longer forge the metadata a workspace reads back.- Engine-supplied labels are treated as untrusted input:
rooz listskips volumes that carry rooz’s role label without a workspace label (previously a panic for every operator on that engine), and a duplicated label set reports an error instead of aborting. - Every GitHub Action is pinned to a commit SHA, with Dependabot raising upgrades as reviewable pull requests.
Changed
- Breaking: expanding
secretsnow needs explicit consent -ROOZ_ALLOW_SECRETS=<names>(comma-separated, safe to leave in a shell profile and also coversrooz update),ROOZ_ALLOW_SECRETS=true, or--allow-secrets true. A local--configpath is not proof that the operator wrote the file: repositories ship config files and README instructions to pass them. Workspaces withoutsecrets:are unaffected. FileFormat::from_path,RoozCfg::parse_ports,VolumeApi::create_volume_specsandVolumeApi::mounts_with_sourcesreturnResultinstead of panicking.
Documentation
- README states the trust model: the engine is assumed to be the operator’s own, and an engine shared with people who do not trust each other is out of scope.