The beta channel is a zip attached to a GitHub release, loaded unpacked. That choice has sharp edges: unpacked installs never auto-update, and removing an install deletes the wallet vault, so both halves of this document matter: the maintainer procedure, and the update procedure users must follow.
Every official build carries a key in its manifest (set in
manifest.config.ts), so Chrome derives the same extension ID for every
install, on every machine, regardless of folder path:
fcdigoihpflbghifhmpecihhfjkhabji
Without the key, Chrome hashes the install path, so two testers would get two IDs, and one tester moving the folder would get a "new" extension. Anything keyed to the ID (bug reports, dApp allowlists, a future Web Store migration) would fracture.
The key value is the public half of an RSA keypair. The private half is
NOT in the repo and must never be committed (.gitignore blocks *.pem
defensively). It lives at:
~/.kivuli/release-key.pem # private key: back it up, never commit
~/.kivuli/release-key.pub.b64 # the base64 public key, as pasted into manifest.config.ts
The private key is not used by the zip flow at all: the manifest key alone
pins the ID for unpacked installs. Keep it anyway: it is what lets a future
.crx packaging or Chrome Web Store listing keep this same ID. Back it up like
a secret. If it is ever lost, unpacked releases continue unaffected as long as
manifest.config.ts keeps the same key string.
Note the ID is pinned, not authenticated: the key is public, so a matching ID proves consistency across installs, never that a build is official. Official builds come from GitHub releases only.
- Move the
unreleasednotes inCHANGELOG.mdunder the version being cut, and bumpversioninpackage.json(the manifest and the About screen both read it, nothing else to bump). npm test: must be green.npm run release: builds (typecheck + vite +finalize-dist), then packagesrelease/kivuli-extension-v<version>.zip. The packager refuses a build whose manifest is missing thekey, whose version disagrees withpackage.json, or whereweb_accessible_resourcessurvived. Any of those would ship a broken channel, not just a broken build.- Smoke the artifact itself, not
dist/: unzip into a fresh folder,chrome://extensions→ Developer mode → Load unpacked. Confirm the ID readsfcdigoihpflbghifhmpecihhfjkhabji, Settings → About shows the new version, and the service-worker console is clean. Remove this throwaway install when done. - Commit, tag
v<version>, and push the tag. The release workflow (.github/workflows/release.yml) takes it from there: it re-runs the tests, rebuilds and repackages, refuses a tag that disagrees withpackage.json, generates SHA-256 checksums, attests build provenance, and publishes the GitHub release with the zip attached. The published notes are the matchingCHANGELOG.mdentry plus the install and update procedure below, pasted in every time: users read release notes, not docs folders. - Review the published release. If the workflow is unavailable, the manual
fallback is the old procedure: create the release by hand with the zip, a
SHA256SUMS.txt, the changelog entry, and the update instructions below. The release signing key plays no part in either path and stays off CI.
- Download
kivuli-extension-v<version>.zipfrom the GitHub release. - Unzip it into a folder you will keep (e.g.
~/kivuli-extension), not Downloads. Chrome loads the extension from this folder: if it is later deleted, moved, or cleaned up, the extension stops loading. chrome://extensions→ enable Developer mode → Load unpacked → select the folder (the one containingmanifest.json).- Sanity check: the card shows Kivuli with ID
fcdigoihpflbghifhmpecihhfjkhabji. - Optional but recommended: verify the download before unzipping. Compare
sha256sum kivuli-extension-v<version>.zipagainst theSHA256SUMS.txtattached to the release, and check GitHub's build provenance withgh attestation verify kivuli-extension-v<version>.zip --repo kivuliapp/extension.
Unpacked installs never auto-update. No release, however critical, reaches you until you re-download. Watch the repo (GitHub → Watch → Custom → Releases) so security fixes are not missed.
To update:
- Download the new zip.
- Delete the contents of your existing extension folder, not the folder itself, and unzip the new version into that same folder.
chrome://extensions→ Kivuli card → Reload (circular arrow).- Settings → About should now show the new version. If it shows the old one, the reload picked up stale files. Check you unzipped into the right folder.
Never update by loading a new folder, and never click Remove. Your wallet vault lives in the extension's browser storage, which survives overwrite-in-place + Reload but is deleted the moment the extension is removed. Loading the new version from a different folder either collides with the existing install (same pinned ID) or tempts you to remove the old one, and removing it wipes the vault.
The money is not gone. Everything (the meta-address, every per-site wallet, every receive address) derives from your 12 words. If an update goes wrong (vault wiped by a remove/re-add, popup won't open, wallet shows as fresh):
- Get a clean install working in the same folder (re-download the zip, overwrite, Reload, or if the extension was removed, Load unpacked again).
- On the welcome screen choose the restore path and enter your 12 words, then set a new passcode.
- Let the first scan finish: received payments are re-discovered from the chain via the seed-derived addresses.
This is also why the backup ceremony refuses to activate a wallet until the words are verified: on this channel, the phrase is not a nice-to-have. It is the recovery path for an update gone wrong.