# pyre CLI changelog

All notable changes to the `pyre` command-line client. Versions follow
semver; the tag is `cli-v<version>`.

## 1.0.30 — 2026-10-11

- **Desktop client for Linux (package `pyredata`, Debian 13 / Ubuntu 24.04).**
  One package carries the GTK 4 + libadwaita app, the per-user agent
  `pyre-agent`, the polkit-guarded `pyre-mount-helper`, the engine runtime
  (limactl) and this CLI; it provides, conflicts with and replaces `pyre`.
  The app enrols the computer with a one-time code from the Download page,
  mounts SMB, NFS and iSCSI volumes under `/mnt/pyre/<volume>`, offers time
  travel, history policy, copies, the engine and the account, and lists
  every API operation under Advanced. `docs/DESKTOP-CLIENT.md`.
- `pyre auth enroll <code|link>`: sign a computer in with a one-time
  enrolment code (or the `pyredata://enroll` link); the token is saved as
  with `auth login`. `pyre auth enroll-code [--label]` mints one (24 h,
  single use) for another computer.
- `pyre auth logout --expire`: the one deliberate way to revoke this
  computer's own token. `pyre auth token revoke` refuses the token in use
  (exit 2) and names that command; the API answers 409 unless `?self=1`.

- One appliance runtime on every OS: the Pyre VM. `pyre appliance deploy`
  for macOS and Windows now prints the Pyre VM install step (Lima from
  pyredata.io; Apple's virtualisation framework on macOS, QEMU with the
  Windows Hypervisor Platform on Windows; the VM definition for that OS;
  the launch agent or logon task) and a run block that goes through
  `limactl shell pyre docker`, exactly as Linux has since 1.0.x. Docker
  Desktop is no longer installed, required or mentioned. `remove` and
  `cleanup` print the VM steps for every OS. The Windows path is
  unverified on a Windows machine at the time of writing (the testbed has
  none); macOS is verified on Apple silicon.

## 1.0.29 — 2026-10-11

- One unit, GiB. Purchases were always GiB and the dashboard always sent
  whole GiB while every label said "GB" and the capacity refusal printed
  raw bytes. `pyre volume create --size 100GB` now rounds UP to the next
  whole GiB and says so ("creating 94GiB (100.9 GB)"); the API refuses a
  create that is not a whole GiB (422 naming the two nearest), as grow
  and shrink already did; every capacity message says GiB with one
  decimal. New 409 code `hub_capacity`: a volume (or a grow) larger than
  the serving hub's free space is refused even when the purchase allows
  it -- the hub-capacity ceiling the fleet never had.

- The first hub registers itself. `pyre admin hubs bootstrap --address
  <host> --start --name <name>` builds the machine and, when the build
  finishes, the control plane registers it as the primary under that
  name (the address's first label without `--name`); `bootstrap-status`
  says so, and `hubs add` is no longer a step of the first-hub process
  (adding a machine that is registered already is refused by address,
  not only by name). A hub that reports itself primary while the
  registry has none is adopted at the next poll instead of flagged as
  drift.

## 1.0.28 — 2026-10-10

- `pyre restore <volume> <when> --as <name>` is writable in seconds. The
  recovery reads the blocks it has not written through the volume's
  history at that moment and copies the rest in behind you; `pyre
  recovery list` shows how much is its own ("own data", 0-99% then
  "all"), and `promote` waits for "all". Needs the appliance image from
  the same release; an older appliance keeps copying first.
- `pyre admin hubs list` says "ok, replication STALLED" in the health
  column when a replica's passes are being refused (the delete guard
  after a big deletion on the primary); `hubs replication` has the
  detail and `accept-deletions` the remedy.

## 1.0.27 — 2026-09-29

- `pyre export download <link>`: fetches every part of an export from
  its temporary download link (https://api.pyredata.io/dl/...), resumes
  a broken download, keeps a part that is already there and intact,
  checks every file against its SHA-256, and prints what to run next.
  No account, token or network beyond the link.
- `pyre volume export <volume>`: asks the hub for a copy of the volume
  behind such a link (48 hours; `--status`, `--remove`, `--email`).
  Works for a deleted volume whose data is still stored. The link is
  on the hub that holds the files (https://<hub>/x/<token>/).
- `pyre volume export <volume> --medium list|<key> --region --address`:
  the same copy shipped on a BD-R XL disc, an LTO-9 tape, an SSD or a
  hard disk, priced (media, work, shipping) and ordered from the CLI.
- `pyre export show|verify|extract|open` read zip parts (what a
  download is made of) as they are, in place, next to directories on
  media; a folder holding either kind is scanned.
- `pyre export prepare --archive`: each part as one stored zip file.
  The help now names the tenant's store credentials on a converged
  hub (`/etc/pyre/tenants/<bucket>.env`), not `/etc/default/minio`.
- iSCSI volumes: `extract` refuses (the filesystem is the customer's)
  and points at `open`; `open --vhd` appends a fixed-VHD footer for
  Windows Disk Management; the README and `open` print how to attach
  the image on Windows, macOS and Linux.
- `pyre admin exports list|create|remove|ship|setup-hub`: every export
  job, one for any tenant (a preserved account, a media order), the
  shipping of a media order (tracking number emailed to the customer),
  and a hub's HTTPS download endpoint.

## 1.0.26 — 2026-09-28

- `pyre appliance deploy`: a volume whose appliance is already deployed
  is refused with one sentence -- which computer, whether it is online,
  `pyre appliance remove <volume>` to move it, `--rebuild` if that
  computer is gone -- instead of a run block that could only end in
  docker's "Conflict. The container name ... is already in use". The
  run block itself (every OS and protocol) looks for the container
  before docker run and says the same. The Linux install step warns
  when two Pyre VM host agents are running (a mount then reaches an
  appliance from an earlier setup).
- `pyre admin hubs bootstrap` / `onboard-key`: the paste block makes ssh
  keys-only (root and password login off); help text says so.

## 1.0.25 — 2026-09-28

- `pyre appliance mount` / `remove`: the commands are for the OS the
  appliance was deployed on (recorded at deploy), not the OS running
  pyre; `--os` still overrides, and the first line says which OS and
  why. From a Mac, an NFS appliance on a Linux client had printed the
  macOS block. The `mount --help` example no longer shows a literal
  `\n`.
- Installer (`install.sh`): installs into `~/.local/bin` by default, no
  sudo and no password; `pyre update` and `pyre uninstall` then need
  none either, the same per-user model as the Windows installer.
  `PYRE_INSTALL_DIR=/usr/local/bin` (or a root shell) keeps the
  system-wide install. An older copy in `/usr/local/bin` is pointed out
  at the end of the install so it cannot shadow the new one unnoticed.

## 1.0.24 — 2026-09-27

- `pyre admin hubs list`: a SERVING column -- for the primary, how many of
  its WireGuard peers shook hands in the last 3 minutes over how many it
  has (`recent/peers`); `0/N NOBODY` plus a notice naming inbound UDP
  51820 when a primary serves nobody. A primary whose firewall dropped
  every handshake had looked healthy for 14 minutes.
- `pyre admin hubs poll <hub>`: prints the health detail (role, tenants,
  tunnel identity, and for the primary whom it serves).
- Gate: run 23, every check of the release gate against the live fleet.

## 1.0.23 — 2026-09-26

- `pyre admin hubs replication`: a table (name, role, lag s, measured,
  pending, speed, eta, data, last pass). The lag is the worse of the
  measured frontier delta and the time since the last successful pass,
  as the hub's own promote guard decides; a replica whose passes the hub
  refused shows `FAILED: delete guard ...` with the count.
- `pyre admin hubs accept-deletions <hub>`: let a replica's next data
  pass mirror a primary that shrank (the delete guard at 100% for one
  pass), the answer to a refused pass without a shell on the hub.
- `pyre view open`: exits 1 with the appliance's reason when the instant
  is no longer version-backed, naming the nearest archived instants.
- `pyre volume grow/shrink`: whole GiB, and the nearest sizes named when
  not; sizes need a unit and are at least 1 GB on create too.
- `pyre view list` and `pyre recovery list`: every column filled.
- Every API call errors out after 30 s with the status and the next
  step (`build-status`, `promote-status`, `volume status`, ...).
- Behind the CLI, found by the release gate and fixed in the appliance,
  the hubs and the control plane: a resize no longer restarts the SMB
  service (a reconnected Linux client could not write afterwards) and
  closes client connections again after the remount; a promoted
  recovery attaches at the copy's real size; a released NFS view drops
  its export block; a dead iSCSI target from a killed container is
  swept before the next appliance's; a refused replication pass is
  visible on the dashboard and in the CLI; the onboarding preflight no
  longer deletes a built hub's tunnel firewall rule, answers its probe
  only to real probes, runs in a fifth of the time, and a refused build
  leaves no stale "done"; an account from before a fleet rebuild can
  be deleted; a hub removed from the control plane has its ssh master
  closed.
- Gate: run 22, 370 checks against the live fleet, every CLI call under
  30 s.

## 1.0.22 — 2026-09-26

- The release gate (`cli/e2e/release-gate.sh`): one run of the whole
  product as a customer and an operator meet it, on the real fleet;
  `cli-publish` refuses a tag without its green report. First gated
  release: 242 checks.
- `pyre appliance status` converges after a deploy; views and
  recoveries list what the API sends; recovery listings reconcile
  behind the response; a demote starts the first replication pass at
  once.

## 1.0.21 — 2026-09-25

- `pyre appliance deploy`: asks which OS and prints that OS's install
  step (the Pyre VM on Linux) before the run block.

## 1.0.20 — 2026-09-25

- `pyre admin`: hidden from a customer's help and completion; a
  customer's token gets a plain refusal naming it.

## 1.0.19 — 2026-09-25

- `pyre admin hubs build`: the primary preflights the machine first and
  refuses with the FAIL lines; the CLI prints its own next step.

## 1.0.18 — 2026-09-25

- Admin CLI sanity run (`cli/smoke/admin-hubs.sh`): the real API and CLI
  through the whole hub lifecycle against a fake fleet, in CI.

## 1.0.17 — 2026-09-25

- `pyre admin hubs onboard-key`: prints the block the new machine must
  run (sudo, the primary's onboarding key, the management key, root
  login off), not the bare key.

## 1.0.16 — 2026-09-25

- `pyre admin hubs remove`: sends only the flags that are set (an empty
  `force=` was a 422).

## 1.0.15 — 2026-09-25

- `pyre admin hubs bootstrap`: the first hub of a fleet can be built
  from the CLI (`--start`, `bootstrap-status`, `--rebuild`).

## 1.0.14 — 2026-09-25

- `pyre export`: the physical export. `prepare` (operator, on the hub)
  copies a volume's blocks off the store into per-medium directories
  with an index, README and SHA256SUMS; an encrypted volume leaves as
  the appliance's own ciphertext, a volume that was never encrypted is
  sealed to a public key from `pyre export keygen`, so nobody who
  prepares or carries the media can read it. `show` and `verify` need
  no key; `extract` copies the files out of the media on Linux, macOS
  and Windows with no mount, no image and no Docker (a built-in
  read-only ext4 reader that handles sparse files); `open` writes the
  raw disk image. A wrong key is refused by fingerprint before a block
  is read. Format and runbook: docs/PHYSICAL-EXPORT.md.

## 1.0.13 — 2026-09-24

- `pyre volume rediscover` reports volumes whose deleted row was just
  marked encrypted: a volume older than the storage's own record is
  known to be encrypted by the key-check object beside its blocks, so
  its row now says so and deploy asks for the key file.

## 1.0.12 — 2026-09-24

- `pyre volume rediscover`: your storage now carries its own record of
  every volume (written at create, resize and every appliance attach),
  and this lists any volume it describes that the account has no
  record of -- storage attached with a recovery key from before
  records were kept, a volume lost with a database -- as a deleted
  volume, ready to restore. `pyre account reclaim` runs it by itself
  and names what it found. A volume older than that record is
  described from its first block, type unknown: listed as smb, and
  `pyre volume deleted restore <id> --type nfs|iscsi` says what it was.
- `pyre volume deleted list` shows each volume's type and whether it is
  encrypted (which rows need their key file).

## 1.0.11 — 2026-09-24

- `pyre account close --mode preserve` keeps every volume as a deleted
  volume (the control plane change of the same day) and names them;
  `pyre account reclaim` then says how many came back and points at
  `pyre volume deleted list` / `deleted restore <id>` /
  `appliance deploy <volume> --rebuild`, instead of "create a volume".
  A tenant preserved before 2026-09-24 has no volume records; the
  message then explains the recreate-by-name path.

## 1.0.10 — 2026-09-24

- `pyre account reclaim --key <recovery key>`: attach the storage of an
  account closed with `account close --mode preserve` to the account
  this token belongs to. That account must have nothing of its own yet
  (no storage, volumes, stores or appliances); the key is accepted in
  any spelling (case, dashes, spaces) and is spent on success, never
  by a refusal. Until now the key could only be entered on the
  password sign-up form, which a "Sign in with Google" account never
  sees; the dashboard has the same page under "Recover it with your
  key".

## 1.0.9 — 2026-09-23

- `pyre volume deleted restore` now says what comes next: `pyre
  appliance deploy <volume> --rebuild`. Deleting a volume revokes its
  appliance's identity, so a container started again from its old
  state comes up unable to connect; the rebuild form's fresh token
  re-enrols it and the state volume and key file can stay.

## 1.0.8 — 2026-09-23

- After `pyre volume delete` of a volume that had an appliance, the CLI
  says how to clear it from the computer (`pyre appliance cleanup
  --names <volume>`), as the dashboard's redirect to /cleanup does.
- A command given the name of a DELETED volume (`appliance remove`,
  `appliance disconnect`, `volume show`, ...) now says so: when it was
  deleted, the cleanup line for its appliance, and the `deleted restore`
  line that brings it back. Before, "no volume named" left the customer
  hunting for a way to remove a stale appliance.
- `pyre appliance cleanup` with no `--names` takes every deleted volume
  of the account (logged in). Logged out it still needs `--names`.

## 1.0.7 — 2026-09-23

- `pyre volume create --encryption customer` works and is documented
  (the 1.0.0–1.0.6 example said `customer-key`, which the API refuses;
  it is now accepted as an alias of `customer`, as are `none`/`off`).
  Any other value is refused with exit 2 and the two choices, before
  anything is sent. After creating an encrypted volume the CLI says
  where the key file will be made (`pyre appliance deploy`,
  `~/.pyre/<container>.key`) and that losing it makes the volume
  unrecoverable.
- Every input is checked against the API's own rule before a request
  is built: ids in paths are numeric; volume, recovery and view names,
  labels, policies, points in time, `--since`, `--type` (now also
  `nfs+smb`), `--os`, `--cache-mb`, `--store`, store fields, emails
  and URLs (`--api`, `api_base`, `download_base`, `--endpoint`) are
  validated; a bad value is a usage error (exit 2) naming the choices.
  Validated path segments are additionally percent-escaped.
- `pyre store probe|add`: the provider list is the API's (`aws`,
  `wasabi`, `minio`, `weka`, `other`; default `other`). The old
  default `s3` and the documented `backblaze` were refused by the
  server on every call.
- Exit codes: cobra's own parse errors (`unknown shorthand flag`,
  `flag needs an argument`) are usage errors (exit 2, was 1); a stray
  word after a group command (`pyre volume nosuch`) is a usage error
  (was help with exit 0).
- A server-side validation answer (HTTP 422) is rendered as
  `field: message`, not as a JSON dump.
- Tests: the customer command matrix (`internal/cmd/permutations_test.go`,
  over 220 rows: valid, invalid and injection cases against a fake control
  plane) and new smoke checks against the real API; design and
  pass/fail criteria in `docs/CLI-TESTING.md`.

## 1.0.6 — 2026-09-23

- Encryption is shown as "customer key" or "none" from the API's
  `encryption` field (server section 10); a server that does not send
  the field shows "unknown" instead of a false "none". Also fills the
  `pyre volume list` ENCRYPTION column.

## 1.0.5 — 2026-09-23

- `pyre update` says which file it replaces ("replacing /path/pyre"),
  checks that directory is writable BEFORE downloading and names it in
  the error (with `sudo pyre update` only when that path needs it), and
  after updating lists any other `pyre` on PATH it did not touch.
  `pyre version` shows the binary's path and any others on PATH.
- Sizes match the dashboard: decimal GB with one decimal
  (26843545600 bytes → 26.8 GB); appliance measurements that arrive
  as strings are formatted too (no more raw byte counts).
- `volume show` / `volume status`: the Offline line appears only while
  the appliance is offline; buffer fill and disk-low facts sit on the
  Sync line otherwise, and an empty buffer is not mentioned.

## 1.0.4 — 2026-09-23

- `pyre volume show` is readable: one line each for Volume, Appliance,
  Sync, Offline, Storage, Errors and Measured, sizes in the dashboard's
  units, times local. Nothing in table mode prints raw JSON any more
  (nested data becomes dotted lines); `-o json` is unchanged, `-o plain`
  flattens to `key<TAB>value`.
- New `pyre volume status <volume>`: the live block alone, with
  `--watch [--every 5s]` redrawing until Ctrl-C on macOS, Linux and
  Windows (no watch(1) needed).
- `pyre appliance status` uses the same rendering.
- `pyre completion` without a shell now says
  `pyre completion <bash|zsh|fish|powershell> — e.g. pyre completion zsh`.
- `pyre update` rewrites the completion files it finds (bash, zsh,
  fish, PowerShell) with the new binary, clears stale `~/.zcompdump*`,
  and says when zsh completion is installed but `~/.zshrc` does not
  load it.

## 1.0.3 — 2026-09-23

- Homebrew: the generated formula carries a top-level `url` so current
  Homebrew accepts the tap (it validates every platform, Linux arm64
  included, and refused 1.0.2's formula with "requires at least a URL").
  No change to the binary.

## 1.0.2 — 2026-09-23

(1.0.1 was tagged on the same day but never published: its installer
still had the PATH gap below. Everything listed here is new since 1.0.0.)

- install.sh: installs into /usr/local/bin through sudo (the password
  prompt works under `curl | sh`); the ~/.local/bin fallback now adds
  itself to PATH and the zsh completion dir to fpath in your shell rc,
  and ends with "Open a new terminal (or run: source ~/.zshrc)". Found
  by the first real install on a Mac: `pyre` was not on PATH.

- Token lifecycle: `pyre auth token create --expires 30d|1y|never`
  (server default 90 days), `list` shows state and expiry, `remove`
  deletes a revoked or expired row (`--force` revokes first), `prune`
  drops every such row. Operators: `pyre admin tokens
  list|revoke|remove|prune` across tenants.
- `pyre uninstall` now removes every man page it installed
  (`pyre-<command>.1.gz`), not only `pyre.1.gz`.
- Install docs: `brew trust pyredata/pyre` before `brew install pyre`
  (current Homebrew refuses formulae from an untrusted tap).

## 1.0.0 — 2026-09-23

First packaged release, a rewrite in Go of the 0.1 Python prototype.

- One static binary per platform: macOS (Apple Silicon and Intel),
  Linux x86-64, Windows x86-64.
- Install channels: apt repository (signed), Homebrew tap, `curl | sh`,
  PowerShell one-liner, Scoop manifest, plain archives.
- `pyre update` (signed releases, in-place replace; defers to apt /
  Homebrew / Scoop when they installed it) and `pyre uninstall`.
- Shell completion for bash, zsh, fish and PowerShell; man pages.
- Scripting: `-o json` / `-o plain`, `--yes`, stable exit codes,
  `pyre batch` to run a file of commands with one login.
- Profiles for more than one control plane (`--profile`, `pyre config`),
  `--ca-cert` / `--insecure` for self-hosted managers, every host a
  config value.
- Customer commands: auth (token per user), volume (incl. deleted
  volumes), restore-points, restore, recovery, view, history, snapshot,
  appliance (deploy/mount/remove/status/disconnect/flush/cleanup),
  store (bring your own S3), account.
- Operator commands (`pyre admin`): hubs (scan/add/preflight/build/
  promote/demote/remove/bootstrap/...), users (approve/reject/delete),
  tenants.
