License re-activation at boot from config/env key #157

Closed
opened 2026-08-09 03:17:33 +00:00 by Cordy · 1 comment
Owner

Question 6 of #138, folded into this milestone as its own small issue (it is independent of the statestore work and can ship first).

Change: at boot, if license state is absent but a key is present in config.license.key or CAIRN_LICENSE_KEY (env already wins per config.go), perform the same activation call the UI paste triggers and persist the result. Worst case after losing state entirely: one HTTPS call to the license server on boot. With #153 the license state survives anyway; this covers the fresh-instance and disaster-recovery paths, and the air-gapped variant (a pre-signed license blob supplied as a file) should be confirmed to still work (#91 owns the air-gap policy question).

Failure handling: activation failure at boot logs loudly and continues unlicensed exactly as if no key were configured — a license-server outage must never take a customer's file server down. The monthly check-in + grace window machinery is untouched.

Mattermost/GitLab store uploaded licenses in the DB precisely so upgrades cannot eat them; this is the same guarantee without a database.

Question 6 of #138, folded into this milestone as its own small issue (it is independent of the statestore work and can ship first). **Change:** at boot, if license state is absent but a key is present in `config.license.key` or `CAIRN_LICENSE_KEY` (env already wins per config.go), perform the same activation call the UI paste triggers and persist the result. Worst case after losing state entirely: one HTTPS call to the license server on boot. With #153 the license state survives anyway; this covers the fresh-instance and disaster-recovery paths, and the air-gapped variant (a pre-signed license blob supplied as a file) should be confirmed to still work (#91 owns the air-gap policy question). Failure handling: activation failure at boot logs loudly and continues unlicensed exactly as if no key were configured — a license-server outage must never take a customer's file server down. The monthly check-in + grace window machinery is untouched. Mattermost/GitLab store uploaded licenses in the DB precisely so upgrades cannot eat them; this is the same guarantee without a database.
Cordy closed this issue 2026-08-09 03:51:45 +00:00
Author
Owner

Shipped in v0.5.1 (PR #161), live on the dogfood.

Turned out the naive version already existed — boot blind-SetKeyed cfg.License.Key — hiding a real bug: a stale config key would clobber a UI-installed or check-in-rotated renewal on every restart. The fix is Manager.EnsureKey: parse the configured key, adopt it only when it reaches further than the installed license (none/expired → adopt; later expiry → adopt; otherwise keep — every path logged distinctly). Losing the state file now self-heals from config/env exactly as this issue asked, and renewals win regardless of which side they came from. Failure semantics as specced: rejected configured key logs a warning and the server continues; check-in machinery untouched; CheckinURL:"" offline mode unaffected (#91 keeps the air-gap policy question). Comparator covered across all five orderings; garbage-key rejection tested against a real Manager instance.

Shipped in **v0.5.1** (PR #161), live on the dogfood. Turned out the naive version already existed — boot blind-`SetKey`ed `cfg.License.Key` — hiding a real bug: a stale config key would **clobber a UI-installed or check-in-rotated renewal on every restart**. The fix is `Manager.EnsureKey`: parse the configured key, adopt it only when it *reaches further* than the installed license (none/expired → adopt; later expiry → adopt; otherwise keep — every path logged distinctly). Losing the state file now self-heals from config/env exactly as this issue asked, and renewals win regardless of which side they came from. Failure semantics as specced: rejected configured key logs a warning and the server continues; check-in machinery untouched; `CheckinURL:""` offline mode unaffected (#91 keeps the air-gap policy question). Comparator covered across all five orderings; garbage-key rejection tested against a real Manager instance.
Sign in to join this conversation.
No labels
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/Cairn#157
No description provided.