# Changelog All notable changes are recorded here. Every claim links back to a measured decision record (`K-…`) in [`docs/decisions.md`](docs/decisions.md). ## v0.11.0 — 2026-11-06 A saved server list for the CLI and the desktop app (K-162) and a new desktop frame (K-163). The protocol is unchanged (3), so a server that stays on v0.10.0 keeps working with this client. The server programs are rebuilt with the new version number; `kadran-caddy` also carries a security fix (below), so upgrade your servers to get it. The desktop app is not among the release files: build it from source in `desktop/` (see the [desktop guide](docs/guides/desktop.md)). ### Security - `kadran-caddy` is built with `github.com/klauspost/compress` v1.20.2, which fixes [GO-2026-6865](https://pkg.go.dev/vuln/GO-2026-6865) (a panic on two-block frames). Kadran's proxy compresses responses with zstd and gzip through this library. Upgrade your servers to v0.11.0 to get the fixed binary (recorded under K-163). ### Added - A saved server list shared by the CLI and the desktop app (K-162). `kadran server add|list|rm|rename` manages it, and `-s ` picks a saved server on every command: `kadran status -s hetzner`, `kadran bootstrap -s gcp` (install and upgrade use the saved admin target and `-sudo`). The name must match exactly, and `-s` cannot be combined with a target. The list is `servers.json` in your config directory and holds no key, password or token. The list lives on your machine only; the server needs nothing new for it. - Desktop: servers added from the CLI appear when you switch back to the window. On first start the desktop's own list is copied into the shared one once; the old `profiles.json` is left as it was. ### Changed - Desktop: glass is now only on the window frame (sidebar, title bar and the gaps), in both themes. Content sits on opaque panels that float inside the glass. On Windows the frame uses Acrylic and stays glass while the window is in the background; without native blur, or with reduced transparency, the frame is opaque (K-163). - Desktop: two themes, light (the default) and dark. The glass theme is gone; a saved glass setting opens as light (K-163). - Desktop: switching themes repaints the whole window at once. The sidebar selection slides to the chosen row and a highlight follows the pointer; buttons no longer move on hover (K-163). The new frame was checked in a real window on Windows 11 only; macOS, Linux and Windows 10 were not run. ### Fixed - `kadran domain check -s ` without a domain no longer probes the saved server: it stops before any DNS, TCP or HTTP attempt (K-162). - Server names with invisible characters (zero-width space, right-to-left marks, soft hyphen) are rejected, so two entries can't look the same; the one-time desktop copy drops them (K-162). - Copied command hints quote server names for bash, PowerShell and cmd, and never put a name with characters those shells interpret inside quotes into a command (K-162). - Desktop: adding servers quickly no longer loses some of them, and removing a server already deleted from the CLI drops its card at once (K-162). ## v0.10.0 — 2026-11-02 A new look for the desktop app (K-161). The protocol is unchanged (3). The CLI and the server programs are rebuilt with the new version number and nothing else, so a server that stays on v0.9.3 keeps working and needs no upgrade. The desktop app is not among the release files: build it from source in `desktop/` (see the [desktop guide](docs/guides/desktop.md)). ### Changed - Desktop: the layout follows Coolify's design language. The app opens on a card for every saved server under a plain Windows-style title bar, with tabs once two or more servers are open, and a new wordmark. The cards read your saved servers at once with the existing calls, so the server does not change (K-161). - Desktop: three themes, dark, light and glass. Glass is the default and shows your desktop blurred behind the window; on Windows 11 it stays glass while the window is in the background. On systems without native blur it isn't offered and the window is dark (K-161). - Desktop: the window modes (Clear, Acrylic, Mica, Vibrancy, Solid), the tone switch and the glass sliders are gone. Settings keep language and fonts (K-161). The new look was checked in a real window on Windows 11 only; macOS, Linux and Windows 10 were not run. ## v0.9.3 — 2026-10-30 Install from a release: no checkout and no Go on your machine (K-159). The protocol is unchanged (3). The server programs are rebuilt with the new version number and nothing else, so a server that stays on v0.9.2 keeps working. Only the v0.9.3 client can install from a release: an older `kadran bootstrap` still needs local files. ### Added - `kadran bootstrap` installs from the GitHub release of the client's own version: no checkout and no Go on your machine. It refuses the server bundle unless it matches a SHA-256 built into the client, and sends no files to the server before that check. `-binaries`, `-repo`, `KADRAN_BINARY_DIR` and source builds keep installing from local files; `KADRAN_RELEASE_BASE` points it at a mirror, and the hash is still checked (K-159). - `kadran version` prints the server bundle hashes built into the client (K-159). - `scripts/get-kadran.sh` (Linux, macOS) and `scripts/get-kadran.ps1` (Windows) install the client from a release in one line, without administrator rights. They check `SHA256SUMS` and leave an existing `kadran` untouched if anything fails (K-159). ### Fixed - `kadran bootstrap` no longer rewrites a server program whose content has not changed; it only resets its owner and mode. Rewriting left a service that was not restarted, such as the reverse proxy, running from a deleted file. Ubuntu's `needrestart` then restarted it, and the proxy broker with it, at the next package upgrade: the proxy stopped for about a second at that hour (K-158). ## v0.9.2 — 2026-10-29 The authenticator entry's name now tells two setups of the same server apart (K-157). The protocol is unchanged (3). Only `kadrand` changed: the other programs, the `kadran` client included, are rebuilt with the new version number and nothing else, so a v0.9.1 client works with a v0.9.2 server. ### Changed - The authenticator app entry is named after the server and the day TOTP was set up (UTC), for example `Kadran: myhost 2026-10-29`. After TOTP is reset and set up again on the same server, the old entry stays on the phone and never works again; it used to have the same name as the new one (K-157). Only new setups get the date; existing entries keep their name. The label is made by the server, so this needs the server upgraded. ## v0.9.1 — 2026-10-29 Easier TOTP setup, found when two-step sign-in was first turned on for real (K-156). The protocol is unchanged (3). Only the `kadran` client changed: the server programs are rebuilt with the new version number and nothing else, so upgrading the server is optional and a v0.9.1 client works with a v0.9.0 server. ### Changed - `kadran totp enable` shows a QR code to scan with the authenticator app, and the setup key in one piece. When TOTP was first turned on for real, an app rejected the key grouped with spaces (K-156). On a dark terminal the QR shows as light squares on the dark background, with no white box; it leaves out the default parameters (SHA1, 6 digits, 30 seconds) to stay small. The key and the complete `otpauth://` link are still printed below it, for apps that cannot read a light-on-dark code. - The code prompt reads `6-digit code from the app`. Input that is not 6 digits (the setup key, a recovery code, a typo) is no longer sent to the server, so it does not count toward the lock: kadran says what it looks like and asks again, up to 3 times (K-156). - The `kadran` client now includes `rsc.io/qr` (BSD-3-Clause), listed in `THIRD_PARTY_LICENSES.txt`. The server programs do not. ## v0.9.0 — 2026-10-28 Two-step sign-in with TOTP (K-155) and a strict check of the reverse proxy's live config (K-154). The protocol is unchanged (3), so v0.8.x clients keep working; once TOTP is on, their admin commands are refused with a message to upgrade and sign in, while deploy-only keys are not affected. TOTP is off after the upgrade: turn it on with `kadran totp enable` once every workstation runs a v0.9.0 client. Upgrade the server with `bootstrap` as usual. Before going back to v0.8.x, read "Rolling back to v0.8.x" below. Building from source needs Go 1.26.10. ### Added - Two-step sign-in (K-155). With TOTP on, an admin key alone can no longer manage the server: `kadran login` exchanges a code from an authenticator app for an 8-hour session token, bound to the signing-in key. `kadran totp enable|disable|status` manage it, `kadran logout` deletes the local token, and the desktop uses the same saved session. TOTP is off until you turn it on, and deploy-only CI keys are never affected. See the [TOTP guide](docs/guides/totp.md). - Lockout and alarms: after 5 wrong codes a key is locked for 1 minute, doubling up to 1 hour. Each attempt is counted before the code is checked. A lock raises a warning alarm, and so does every use of a recovery code; turning TOTP off or resetting it closes them. Every sign-in, failure and change goes into the audit log, never with the code, secret or token. - 10 single-use recovery codes per enrollment. Turning TOTP off and re-enrolling need a fresh code on top of the session; `-recovery` takes a recovery code instead, so a lost phone can be replaced without root. Re-enrolling rotates the key that signs session tokens. Codes typed with spaces (`123 456`) are accepted. - `kadran totp reset [-sudo] root@server` turns TOTP off through root when the phone and the recovery codes are both lost. On the server it runs `kadrand -totp-reset` as the `kadran` user, and the daemon keeps running. - The protocol stays 3: older clients talking to a server with TOTP on are told to upgrade and sign in, and a newer client talking to an older server says the server does not support TOTP. ### Rolling back to v0.8.x An older `kadrand` does not know TOTP and does not enforce it: every admin key works again without a session. The database is left as it is, because an older `kadrand` skips the migrations it does not know, so upgrading again turns TOTP back on with the same secret and recovery codes (K-155). ### Fixed - The proxy broker's live readback now decodes Caddy's config strictly. A field Kadran does not model, a key spelled in a different case, or trailing data fails the check instead of being dropped. Before, a config with `abort`, a `transport`, a file-reading `handle_response` or a file-writing log `writer` added through the raw admin socket passed as safe. Exploiting it needs write access to that socket, so this is defence in depth; a tampered config now raises the critical proxy alarm (K-154). - The startup alarm's detail carried a Turkish fragment in the middle of its English sentence ("rotalanamayan uygulama var:"); it now reads "some apps could not be routed:" (K-153). - Backup audit records use the parameter keys `file` and `bytes`, like the other records; they were `dosya` and `bayt`. Records already stored keep their keys and still verify (K-153). ### Changed - The English-only check now also looks for Turkish words written without Turkish letters, the class both fixes above slipped through (K-153). ## v0.8.1 — 2026-10-23 Client fixes to the DNS warnings, found while moving a live site to Kadran (K-151, K-152), and English step headers in `bootstrap`. The protocol is unchanged (3). Only the `kadran` client changed: the server programs are rebuilt with the new version number and nothing else, so upgrading the server is optional and a v0.8.1 client works with a v0.8.0 server. ### Fixed - A redirect name whose record is already right at the DNS host, while this machine still has the old answer cached, was reported as "does not point at the server yet". That case is a warning, not a stop: `app create/update` now prints the same reason as `kadran domain check`, with the name in front. The exit code is unchanged (K-152). - The stale-cache warning (in `kadran domain check`, the `-domain` precheck and the redirect names) no longer suggests `kadran domain check -wait 10m`. The command had no domain or target, and `-wait` only waits while the check stops, so it returned at once (K-151). - `bootstrap` prints its "Binaries", "Vault" and "Reverse proxy" step headers in English; they were in Turkish. ## v0.8.0 — 2026-10-22 DNS guidance and www/redirect names (K-151, K-152). The protocol is unchanged (3), so v0.7.x clients keep working; a v0.8.0 client talking to an older server warns instead of claiming that redirects exist. Apps created before this release keep www off after the upgrade; turn it on with `kadran app update -www `. Upgrade the server with `bootstrap` as usual: it restarts the proxy broker before `kadrand`. Before going back to v0.7.x, read "Rolling back to v0.7.x" below. Building from source needs Go 1.26.10. ### Added - DNS guidance (K-151): when a domain does not point at the server, `app create/update -domain` and `kadran domain check` name the DNS host (~50 providers), print the exact record to add and the records to delete, the place in that provider's panel and its official guide. See the [domain setup guide](docs/guides/domains.md). - `kadran domain check` asks the DNS host's own nameservers, so a freshly changed record is recognised before caches expire; `-wait 10m` waits until the record points at the server. A negative `-wait` is rejected, and Ctrl-C while waiting stops without running the remaining checks. - `kadran domain check` shows the domain's accredited registrar (RDAP). - www and redirect names (K-152): `app create` also serves the www partner of a registrable domain (or the bare domain when the domain is `www.…`) and redirects it with a 308 to `https://`, keeping path and query. `-no-www` turns it off. `app create` and `app update` take `-redirect ` (repeatable, at most 8 per app); `app update` also has `-www`, `-no-www` and `-redirect-rm `. Subdomains get no partner. Apps that exist before the upgrade keep www off. `app update -domain` moves the names along and reports them. See [www and other names](docs/guides/domains.md#www-and-other-names). - Redirect names are checked for DNS after the server accepts the request; the check only warns and `-skip-dns-check` skips it (K-152). `app show` prints a `Redirects` line. - The proxy broker accepts one non-proxy handler: a fixed-shape 308 redirect whose target must be a routed domain (K-152). A name that collides with another app's domain or name is rejected by the API; a collision that gets through drops only that redirect, never the whole route set. `kadrand` logs every name it could not apply (startup, deploy, heal and the proxy watcher), not only `app update`. - For an app that is not routed at the moment, removing a name takes effect at once; an added name is saved and applied once the app is routed again (K-152). ### Security - Both modules and CI now require Go 1.26.10, which fixes GO-2026-6809 in `crypto/x509` (compromised intermediate certificates are distrusted); `govulncheck` reported it reachable through the HTTP health probe. Building from source needs Go 1.26.10. ### Rolling back to v0.7.x Remove redirects (`-no-www` and `-redirect-rm`; this works even for an app that is not routed) before installing a `kadrand` older than this release (K-152). The older `kadrand` rejects the proxy broker's reply while the live route set carries redirects, so proxy repair and any reconcile with an app that is not routed fail until a reconcile with every app routed loads a set without redirects. ### Changed - A record set that contains the server AND other addresses now stops the DNS precheck (it used to pass): part of the visitors and of Let's Encrypt's validation reached the other addresses. ## v0.7.0 — 2026-10-15 English server texts and a pinned Docker address pool (K-149, K-150). The protocol is unchanged (3), so v0.6.x clients keep working. Upgrading with `bootstrap` writes `/etc/docker/daemon.json` when the server has none. On a server that already runs containers it takes effect at the next dockerd restart; run `sudo systemctl restart docker` in a maintenance window, because it stops running containers briefly. If you search the journal or forward it elsewhere, read the text changes below first. Building from source needs Go 1.26.9. ### Changed - **Docker's address pool is pinned (K-150).** When `/etc/docker/daemon.json` does not exist, `kadran bootstrap` writes it with `default-address-pools` set to `172.16.0.0/12`, `/16` per network. dockerd reads it only on restart: on a server with no containers bootstrap restarts dockerd; otherwise it warns and leaves the restart to you, because running containers stop briefly. An existing `daemon.json` is never changed. The executor also checks each app network's subnet and stops the deploy when it falls outside `172.16.0.0/12` (Docker's default pool moves to `192.168.0.0/16` after about 15 networks). Before, such a deploy failed later on the health probe and the proxy. See the [operations guide](docs/guides/operations.md#docker-adres-havuzu). - **Server journal, alarm and Telegram texts are English (K-150).** This covers kadrand's and kadran-exec's log messages and field names, the systemd status line, alarm details, the alarm sender's Telegram messages and the heartbeat Worker's messages. If you search the journal for the old Turkish text, update the search: `yetki reddedildi` is now `authorization denied`, `rol=` is `role=`, `anahtar=` is `key=`, `uygulama=` is `app=`, `hata=` is `error=`. The `ALARM` line carries `state=opened|escalated|closed`, `severity=critical|warning`, `target=` and `detail=`. The sender still reads lines that were written with the old keys just before an upgrade. Watchdog loops are named `supervisor`, `proxy-watch`, `disk` and `backup`. Alarms already stored keep their Turkish detail. A heartbeat Worker you deployed earlier keeps its old texts until you run `npx wrangler deploy` again. - **CI skips the mutation job when only documentation changed (K-150).** A change that touches nothing but `*.md` files and `docs/` no longer waits ~45 minutes for mutation testing. Any other file, or a diff CI cannot compute, still runs it. - **No `NOTICE` file (K-149).** Release packages carry `LICENSE` and `THIRD_PARTY_LICENSES.txt`; the latter already reproduces the `NOTICE` files of the Go modules compiled in. The desktop app's bundled icons, font and runtime packages have their license texts in the app's `THIRD_PARTY_NOTICES.txt`. ## v0.6.0 — 2026-10-14 Desktop management and a typed proxy broker (K-142–K-148). Upgrading with `bootstrap` installs the `kadran-proxy` broker and takes the daemon off Caddy's raw admin socket. The reverse proxy binary and its units change, so the proxy restarts: plan a short maintenance window. Before upgrading, run the checks in the [upgrade guide](docs/guides/quality-upgrade.md): app domains, Docker address pools and, if you use it, the private-Git drop-in. Building from source needs Go 1.26.9. ### Added - **Typed proxy broker (K-143).** The daemon can submit validated routes through `kadran-proxy`; it can no longer send arbitrary JSON to Caddy's raw admin socket. - **Desktop resource management and original Kadran logo (K-145).** Applications, configuration, deployments, rollback, bounded logs, alarms, backups and audit details are available in the glass interface, including the 760×520 window. - **Private GitHub archives (K-146).** An opt-in executor credential file scopes access to configured repositories. Credentials never enter Docker build URLs, renderer state or deployment records. See `docs/guides/private-git.md`. - **New desktop app (K-142).** A glass window — Clear, Acrylic, Mica or Vibrancy, with the desktop showing through — and Appearance settings you tune yourself: tint, panel glass, edge highlight, hover glow, accent, theme, interface and code fonts (any installed font; JetBrains Mono ships with the app). Status gets an overview strip with live sparklines, a health pill and a server switcher; Audit gets ranges, filters, an outcome bar and "load older". English by default, Turkish in Settings. - **The desktop app asks for no permissions** except the installed-font list. Before this release Electron granted every permission request. ### Changed - **Patched build dependencies (K-147).** Both modules and CI require Go 1.26.9; the separate proxy module uses Caddy 2.11.7. Reachable dependency findings were removed; one unused module-level OpenPGP advisory remains in the module graph. - **The CLI speaks English (K-141).** Help, usage errors, tables, status labels and every message the CLI produces itself are English, including `bootstrap`'s own steps and the `kadran key` messages from the server-side script it ships. Commands and flags did not change. Two labels changed meaning on screen: a broken audit chain now shows `BROKEN` (was `GEÇERSİZ`) and an unverifiable one `UNVERIFIABLE`, so scripts can tell them apart with `grep`. Errors the server sends back are English once the server is upgraded too; an older server still answers in Turkish. - **The server-side scripts speak English (K-141).** The install, migration and rollback output of `bootstrap`, the vault rollback tool and the offsite and volume backup units print English, and `deploy/offsite/README.md` is English. The offsite summary line is now `summary: uploaded=… skipped=… failed=…` (was `özet: yüklendi=… atlandı=… başarısız=…`); adjust anything that greps the journal for it. `OFFSITE_PRUNE` still takes `evet`/`hayir`: it is a value in your configuration file, so it did not change. - **Errors the server sends back are English (K-141).** Validation errors, permission denials (`this key can only deploy`, `app is outside this key's scope`), deploy, rollback and heal failures, `app update` warnings, the chain status details of `audit verify`, and the `-h` text of `kadrand`, `kadran-exec`, `kadran-connect` and `kadran-vault`. `app prune` labels the releases it keeps `active` and `rollback target`. New audit records carry English details; records written before the upgrade keep theirs, since the chain is append-only. Still Turkish: the server's journal lines (`journalctl -u kadrand`), alarm and Telegram texts, and the database's own trigger messages. ### Fixed - **Concurrent lifecycle operations (K-144).** Deploy, rollback, configuration, deletion, pruning and automatic healing share a cancellable per-application queue. Skipped target routes and failed proxy loads cannot report successful deployment. Current validated routes for skipped existing domains are retained. Health probes reject truncated or stalled response bodies; at most 4 KiB of the body is read, and a body that reaches that limit without error is healthy (the connection is closed without draining the rest). - **Deletion fails closed (K-144).** Database errors while looking up the active deployment no longer authorize container deletion. - **Desktop protocol and stale state (K-145).** UTF-8 is decoded across chunks; management results are shape-checked; deploy needs a matching success event; connection setup counts toward the operation deadline. Failed or expired audit verification clears a previous green result, and stale health cannot remain green. - **Secret configuration edits (K-145).** An untouched secret placeholder does not overwrite the stored secret; replacement and removal require explicit input. - **Every desktop change asks first (K-148).** Create, change, deploy, rollback, delete and backup each show a native confirmation in the main process. Cancel is the default, and only one dialog can be open at a time. The dialog names the server and shows what will change. Environment variables and build arguments show names only, never values. Long values are cut in the middle so the host of `user@host` stays visible. Invisible and look-alike characters are shown as escapes. - **The desktop no longer gives up before the server (K-148).** Deploy, rollback, delete and backup wait one minute longer than the sidecar does. Before, the desktop could time out while a rollback was still running on the server and let you start it a second time. - **Desktop errors show the real reason (K-148).** Instead of only `call failed`, the desktop shows the server's reason, with tokens and credentials removed. An update sends only the fields you changed. Profiles are written atomically. - **Migration starts the proxy broker before the daemon (K-148).** Upgrading to the typed broker could restart `kadrand` before the broker's socket existed. - **The proxy broker refuses network, gateway and broadcast addresses (K-148).** A compromised daemon could point a route at the host through the Docker gateway. - **One domain rule (K-148).** The API and the proxy now validate domains with the same function. An app whose stored domain the proxy rejects is skipped instead of blocking every route. ⚠ If such an app exists from an older version, it loses its live route at the next reconcile and shows as skipped: fix its domain with `kadran app update -domain `. - **Private Git archives with a pax global header are accepted (K-148).** - **The private Git drop-in blocks cloud metadata (K-148).** The documented `kadran-exec` drop-in now denies link-local addresses, including 169.254.169.254. If you already use it, add the `IPAddressDeny=` line from `docs/guides/private-git.md`. Measure DNS first: on GCP the resolver can be the metadata address. - **Desktop "recent audit records" showed the oldest records.** It asked for the first 50 records after sequence 0; once a chain had more than 50 records, new ones never appeared. It now reads the latest sequence first. ## v0.5.1 — 2026-10-05 Hardening of the client's SSH files (K-139, K-140). Same protocol and the same reverse proxy binary as v0.5.0: upgrading with `bootstrap` does not restart the proxy (measured on the test server). If you edited `/var/lib/kadran-client/.ssh` as root, `chown` it back to `kadran-client` first (see the behaviour change below). ### Security - **`bootstrap` writes the client's `.ssh` and `authorized_keys` as `kadran-client` (K-139).** Root still reads the staged key and refuses a symlinked or non-regular target, but the write itself now runs under `setpriv` as that user. Before, a link swapped in between the check and the write made root read the link's target or change the mode of the directory it pointed to. The file is born owned by the user with mode 0600, and the install step no longer runs `chown -R` or `chmod` there as root. One path still writes that file as root: the one-time panely → kadran migration (K-136, upgrades from v0.3.x and older). - **`kadran key list/add/remove` touch `authorized_keys` as `kadran-client` (K-140).** The server-side script still starts as root (SSH or `-sudo`) and re-runs itself under `setpriv` as that user. Before, it had no link check at all: if `authorized_keys` was a link to a root-only file, root listed that file and copied it into the new `authorized_keys`. A failed read while removing a key is no longer ignored. - Behaviour change: if `authorized_keys` or `.ssh` is owned by root (for example after a manual edit as root), `bootstrap` stops without touching it and prints the `chown` command to fix it; `kadran key` refuses the same file with the same hint. Before, both went ahead as root and kept or fixed the owner silently. ## v0.5.0 — 2026-10-04 ### Secret store (K-123) Environment variable values are now sealed with the executor's key before they reach the database. The daemon writes them but cannot read them; the executor opens them only while it creates a container. - **Each value is bound to its app and name.** The sealed plaintext is `app NUL name NUL value`, and the executor refuses a value moved to another variable or app. A compromised daemon can no longer copy one app's database password into a variable another app logs. - **The store is mandatory.** The executor rejects unsealed values; there is no plaintext mode. - **Upgrading seals existing values** on first start, empty ones included, in one transaction, then runs `VACUUM` so old plaintext pages leave the database file. Measured: without it, 17 of 30 fragments of a deleted app's secret stayed in the file. - `bootstrap` creates the key with the new `kadran-vault` tool (`/var/lib/kadran-exec/vault.key`, root 0600) and prints a reminder to save it. **Save it in a password manager, not next to the offsite backup key.** Losing it makes every value unrecoverable. The public key goes to `/etc/kadran/vault.pub`. - If the database is sealed to a different key (for example a lost key replaced by a new one), the daemon refuses to start instead of failing at the next deploy. - Scrubbing is recorded only once it finishes; a crash between sealing and scrubbing makes the next start scrub again. A value an older version wrote under the seal marker is sealed on the next start. - `bootstrap` warns when the executor has no `--allow-repo` list: without it a compromised daemon could start an image of its choosing as an app and print that app's values. It also lists the plaintext pre-migration copies on every run. ### Upgrading from v0.4.x Create and save the key **before** the upgrade seals anything: 1. Copy `kadran-vault` from the v0.5.0 server package (`bin/linux-/kadran-vault` in `kadran-server-v0.5.0-linux-.tar.gz`) to the server and run it once as root: `kadran-vault -key /var/lib/kadran-exec/vault.key`. It creates the key and prints the public key; nothing is sealed yet. Running it again keeps the key and prints the same public key. 2. Save the key in a password manager: `sudo cat /var/lib/kadran-exec/vault.key`. 3. Run `kadran bootstrap` with the v0.5.0 files. It reuses the key, so the `YENİ KASA ANAHTARI` (new vault key) warning does not appear. The daemon seals the existing values on start. 4. Check the sites, then delete the plaintext pre-migration copies: `sudo rm /var/lib/kadran/kadran.db.pre-*`. A v0.4.x CLI keeps working against v0.5.0: the protocol did not change. Measured: a v0.4.1 CLI deployed to the release candidate and the new container got the value. ⚠ Never install v0.4.x over v0.5.0 without running `kadran-kasa-coz.sh` first: v0.4.x would hand the sealed text (`age:…`) to containers as the value. ### Breaking changes - `app show --json` and the API return variable names only; values come back empty. No client reads values back, so nothing is silently cleared. - The executor's privileged code limit went from 2500 to 2600 lines; the vault adds 83 (measured 2581). Key generation lives in `kadran-vault`, outside the executor. ### Rolling back to v0.4.x Stop `kadrand`, run `/usr/local/lib/kadran/kadran-kasa-coz.sh` as root (needs `sqlite3` and `age`), then install v0.4.x. The script copies the database first, writes the values back as plaintext and removes the seal marker; upgrading again reseals them. It runs `sqlite3` as the `kadran` user and keeps root for decryption, so a symlink the daemon planted in its own directory cannot redirect root's writes. If an update fails, nothing is committed and the marker stays. ### What still holds plaintext - Backups taken before the upgrade: hourly local backups for 24 hours, offsite backups (age-encrypted with the offsite key) until the bucket's lifecycle removes them, and the pre-migration copies `kadran.db.pre-*` next to the database. The upgrade itself writes `kadran.db.pre-0009_env_seal` before sealing, and these copies are only pruned when later migrations add newer ones. Delete them by hand once v0.5.0 runs. - Running containers: `docker inspect` on the host still shows the environment. Moving values out of the environment into files is a separate step (K-123 option C). ### Fixed - **Upgrades left the optional units on their old version** (K-138). `bootstrap` never touched the offsite, volume-backup and alarm scripts or their units; they were installed by hand or by the v0.4.0 migration. On the first live v0.4.1 upgrade the daemon wrote its anchor but the old offsite script did not upload it. `bootstrap` now reinstalls every optional unit it finds installed, leaves its timer on or off as it was, and skips masked units. Settings kept in drop-ins survive. ## v0.4.1 — 2026-10-04 ### Audit chain anchors (K-126) A compromised daemon could rewrite its own audit chain into one that still verifies. With offsite backups on, every daemon backup now carries an anchor, the chain's head (`seq` and hash), uploaded unencrypted under the bucket-locked `kadran-` prefix. `kadran audit verify -anchors ` recomputes the daemon chain on your machine, ignores the hashes the server sends, and exits `3` if any anchor disagrees or the chain is shorter than an anchor. `-anchors-since` sets aside anchors from before a database restore. It covers the daemon chain only, back to the lock period. - `ListAuditRecordsRequest.after_seq` is exclusive; its comment said inclusive. ### Security - **The install followed symlinks in the client user's `.ssh`** (K-137), the same class the v0.4.0 migration fixed. Writing the admin key used a fixed temporary name and appended through the path; creating `.ssh` followed a symlink and handed the target directory to the client user. Both now stop on a symlink, and the new `authorized_keys` is written to a `mktemp` copy and moved into place. ## v0.4.0 — 2026-10-03 The second half of the rename: every name on the server is now `kadran`, and `bootstrap` migrates an existing install in place. ### Upgrading from v0.3.0 Run `kadran bootstrap` with the v0.4.0 files, as `root@server` or `-sudo user@server`. It detects the old (`panely`) install and migrates it in place (K-136). - **Before you start:** if you use offsite backups, add the bucket lock and lifecycle rules for the `kadran-` prefix ([offsite guide](deploy/offsite/README.md)). Don't push to repositories that deploy through CI while the migration runs. - Users are renamed with `usermod`: uid/gid and the ownership of every file stay the same. The database, backups, volumes, deploy keys, TLS certificates, both audit chains and the optional offsite, volume-backup and alarm units move to the new names. A timer that was off stays off. - Your own systemd drop-ins follow their units. The migration stops before the new executor starts if its effective `--allow-repo` list differs from the old one. - The old proxy and containers keep serving until the new control plane has started its own containers. The site is down only while the proxy switches: about 1.3 s on the Debian 13 test server, read from the journal; 0 of 80 probe requests failed. - While they overlap, the old and new replicas of an app mount the same volume, as in a blue-green deploy but for longer. Stop apps that keep a single-writer database in a volume before migrating. - If the new containers don't come up within 5 minutes, the migration stops and the site stays on the old stack. Running `bootstrap` again continues where it stopped. - The daemon briefly cannot reach the proxy during the switch and raises "proxy not reconciled". With Telegram delivery you get that alarm and its recovery. - **Afterwards:** move CI to the v0.4.0 CLI and the `kadran-client@` target at the same time. The desktop app moves its saved profiles on first start. - **Rollback:** `/usr/local/lib/kadran/kadran-geri-donus.sh` on the server (installed before anything moves), then `bootstrap` from v0.3.0. The site is down until the old stack is back: ~75 s on the test server. ### Breaking changes - **Protocol 3.** The wire protocol is now `kadran.v1`; a v0.3.0 CLI cannot talk to a v0.4.0 server or the other way round. The new CLI recognizes an old server and says to run `bootstrap`, and answers a `panely-client@` target with the `kadran-client@` form. - Server names: `kadrand`, `kadran-exec`, `kadran-connect`, `kadran-caddy`; users `kadran`, `kadran-client`, `kadran-caddy`; `/var/lib/kadran`, `/etc/kadran`, `/usr/local/lib/kadran`; `kadran.db`; Docker labels `kadran.*`, images `kadran/`; backups `kadran-