Skip to content

wisp 0.0.2

Choose a tag to compare

@vcealicu vcealicu released this 02 Oct 19:55
· 9 commits to main since this release

LocalGhost wisp 0.0.2

The second cut of wisp, the same day as the first. 2 October 2026.

Still a wisp: the name is the release line, the number is the cut. This is 0.0.1 with the day's
fixes, and the first cut whose GitHub page holds everything the notes promise: the server set,
the signed app, the source archive and the signed sums (0.0.1's upload stopped at a file-name
collision, so its page is missing three of them). Everything in 0.0.1's notes still
holds: what LocalGhost is, what is in it, how it works, how a box and a phone are installed. This
page is what changed.

What changed since 0.0.1

Photos that were filed as videos

A phone's HEIC photo with a brand the sniff did not know was archived as an MP4 video: a play
glyph on every thumbnail of a day, "84 videos" for 84 photos, and a day story quoting "video
archived" six times. The sniff now reads every brand in the file's ftyp box (a HEIF image always
carries one of the image brands among them), and the archive puts itself right at the box's next
start: every frame is re-read, a HEIC under a video's name is renamed to what it is, its record
and its journal line follow, and the previews it had stay. Stills the box cannot decode itself
(HEIC, AVIF, WebP) get their previews through ffmpeg at archive time now, rather than waiting for
a reprocess, and are never set aside as damaged for that.

A phone retired by its key

ghost-cli ghost.secd devices lists the enrolled phones by their device key, and
ghost-cli ghost.secd retire id=<key> refuses one from then on: every call it makes, the PIN
entry included, is answered as if the box were down, locked or not, because the list lives on the
OS disk. POST /v1/devices/retire does the same from another phone, never for itself. A phone
that should be back scans a fresh QR and is a new device. The button on the phone's DEVICES
screen is still to come.

The release cut

The cut stops before it tags or builds anything when the Android SDK or the app's keystore is
missing (a release carries the app; --no-apk cuts one without it, on purpose); it finds the SDK
through the usual places and the keystore at ~/localghost-release.jks without configuration;
tools/app_keystore.sh registers another keystore or makes a new one; it refuses to run under
sudo; it no longer ends without a word after the app is built (a pipe closed early under
pipefail); the sums leave the GPG signatures out, so a recut's SHA256SUMS is identical when the
build is; and the GitHub upload leaves the server set's own SHA256SUMS home, which is the collision
that cut wisp's page short. The app's version and release name come from one place
(appVersion in app/build.gradle.kts, the name from server/tools/release.names).

The repository

The README, CONTRIBUTING.md and SECURITY.md say what is built rather than "Phase 0" (what leaves
the box, the vault and the PINs as they are, how to get it, how to work on it, where it is going,
and a word for agents: the box exists, people have one, it answers nobody but its phone). CI runs
go vet, every Go test against Postgres, a macOS cross-compile and the app's JVM tests. The tree
builds on a Mac (the Linux-only calls sit behind build tags), which closes the pull request and
issue from July about Pdeathsig. The site key's fingerprint is in SECURITY.md.

Small things

Two Kotlin warnings cleared; the wipe-PIN timing test takes medians so a busy machine is not a
tell; the QR sampler's rotation tests assert what the design promises (the true grid among the
candidates the decoder tries) over a hashed synthetic fill rather than a lattice.

What the release holds

One GitHub release, v0.0.2:

  • server/: the server set, built reproducibly from the tag: the bundle
    (localghost-server-0.0.2-linux-amd64.tar.gz), RELEASE.txt, these notes as NOTES.md,
    NOTICE.txt, TERMS-MIT.txt.
  • app/: localghost-app-0.0.2.apk, built on the release machine from the tag and signed with
    the app's keystore (the same key as 0.0.1's, so it installs over it), its GPG signature by the
    site key, and APP.txt (its hash, commit, version and signing certificate).
  • source/: localghost-0.0.2-source.tar.gz, the whole tree at the tag.
  • SHA256SUMS over all of it, signed by the site key (SHA256SUMS.asc; the key is at
    https://www.localghost.ai/.well-known/pgp-key.asc, fingerprint DCE9 A3D1 4EB4 6197 1DD5 F393
    706E 4194 F08A 09A0). The signatures themselves are not in the sums.

The server set is the same bytes any machine gets from tools/cut_release.sh 0.0.2 at the tag.
The APK is not yet byte-reproducible (a gradle build differs from run to run), so the one in the
release is the one to verify: its signature, its certificate in APP.txt, and VERIFY BUILD in
the app, which shows the commit it was built from.

Installing 0.0.2

A box on 0.0.1 takes 0.0.2 from the phone: SETTINGS › SERVER shows "wisp 0.0.2 is out" once the
mirror carries it; DEPLOY downloads the set, the box checks the manifest's signature and every
hash, restarts onto the new build and opens a trial, and rolls back by itself if the first unlock
fails or a critical daemon keeps falling over. At the first start on 0.0.2, ghost.framed re-reads
every frame in the archive once (the HEIC repair above); on a box with tens of thousands of
photos that is some minutes of reading in the background, visible under ghost.framed on Box
Status, and nothing waits for it.

The phone installs localghost-app-0.0.2.apk over the 0.0.1 app (same signing key, the enrolment
kept). A fresh box or phone is installed as 0.0.1's notes say.

Known gaps at 0.0.2

  • The box CA key stays on the OS disk, by choice: a phone can be enrolled and refused while the
    vault is locked.
  • Device certificates last ten years; shorter ones renewed by the phone are next.
  • Releases are signed by the site key alone; an offline release key is planned.
  • The APK is not byte-reproducible; the server set is.
  • Mail, the Mist (peer-to-peer backup) and the decoy volume are not built.