This is how to put LocalGhost on a machine you own, from a bare Debian install to a phone that unlocks it. It's the procedure I follow on my own box, taken from the scripts in the server repo, in the order they expect them and with the reason for each step. The current release is wisp 0.0.5, the fifth cut of wisp, from 5 October 2026. It carries the server and a signed Android app, and since 0.0.5 a box is set up from the release alone, with no git, Go or make. A box that builds its own code is set up from a checkout of the repository the same way, and step 4 says how.
Every step is a script you can read before you run it, and the ones that destroy anything ask you to type a word first. Setup takes an hour or two, most of it downloads and building the model engine, and the short version below is the whole of it in five moves. A large photo library then takes the box a few days to describe and index, and the phone shows how far it has got. There's one rule about order (step 2) and one cost you should know about before you start, which is that wisp has no recovery path (section 5).
# 1. Debian 13 with the TPM on in the BIOS, as root apt install sudo nginx curl gpg # 2. on the phone, install localghost-app-0.0.5.apk from the release # 3. as your login user, the release, checked against the site key V=0.0.5 R=https://github.com/LocalGhostDao/localghost/releases/download/v$V for f in localghost-server-$V-linux-amd64.tar.gz SHA256SUMS SHA256SUMS.asc; do curl -LO $R/$f done curl -s https://www.localghost.ai/.well-known/pgp-key.asc | gpg --import gpg --verify SHA256SUMS.asc SHA256SUMS sed 's# [a-z]*/# #' SHA256SUMS | sha256sum -c --ignore-missing - # 4. unpack and install: a disk or a file, the seal, your PINs, then the QR mkdir localghost tar xzf localghost-server-$V-linux-amd64.tar.gz -C localghost sudo localghost/tools/install.sh # 5. scan the QR, unlock from the app, then Wikipedia and anything newer sudo /opt/localghost/tools/update.sh
That's all of it, with nothing to compile but the model engine, which setup builds for your card. The volume can be a whole empty disk or a file on a drive you already have. The steps below show what each command asks you, and the reasons and the less common cases are folded under each one.
> 1. WHAT YOU NEED
| Machine | An x86-64 computer running Debian 13, with a terminal at least 57 columns by 35 rows for the first run, since the enrolment QR is drawn there and is never printed as text. Mine is a Debian 13 box on a home network, and the release is built for linux-amd64. |
|---|---|
| TPM | TPM 2.0, and a firmware TPM is fine (Intel PTT, AMD fTPM), switched on in the BIOS. ls /dev/tpm* should list /dev/tpmrm0. A machine without one can run the software tier, with the price in section 5. |
| Volume | Either a whole empty disk, separate from the one Debian is on, or a file on a drive that's mounted at boot, 20 GB at least, allocated in full when it's made, and never on a network share. A disk becomes one LUKS2 container with no partition table, so everything on it goes. Size it for your photo library plus about 10 GB of models and maps, and more for streets (Europe alone is 33 GB to download) or Wikipedia (a 50 GB download). |
| GPU | Optional. An NVIDIA card with its driver and the CUDA toolkit installed, which you do yourself, and setup builds the model engine for whichever card the driver reports. Mine is an RTX 4070 with 12 GB, which holds the 12B model and its vision projector. Without a card everything runs on the CPU, and the same box describes about 24 photos an hour.[6] |
| Phone | Android 15 or newer (the app's minimum SDK is 35), no Google Play services needed. |
| App | The signed APK from the release, or a computer to build it on, Windows with Android Studio or Debian with the repo's setup script. A USB cable helps either way. |
| Network | Internet during setup, for the release, the signed setup mirror (about 12 GB) and the Debian, PostgreSQL and Redis apt repositories, and the Go module proxy too when you build from a checkout. To reach the box away from home you also need a domain whose A record points at your public IP and your router forwarding TCP 443 to the box. On the home network it works without either. |
| Two PINs | A main PIN, and a wipe PIN if you want one. They must be different. The wipe PIN destroys everything on the volume and then shows the same unlock progress a real unlock would.[7] |
> 2. THE ORDER
Seven steps, from the release of the localghost repository.[1] Each step shows what to run and what it asks you, and the reasons and the less common cases are folded under it.
STEP 1. PREPARE THE MACHINE // BIOS, ROOT
Switch on the firmware TPM in the BIOS, install Debian 13, then the four things the installer expects to find.
# /dev/tpmrm0 should be there ls /dev/tpm* # as root apt install sudo nginx curl gpg
A GRAPHICS CARD, AND WHICH USER
If there's an NVIDIA card, install its driver and the CUDA toolkit yourself before step 4. LocalGhost installs neither. Setup builds the model engine for the card the driver reports, and for the CPU when it finds no nvcc under /usr/local/cuda*/bin or no card the driver can see. Run the steps as your own login user, the one with sudo. That user becomes the service user, which installs and deploys, and the daemons run as a separate user that can't log in.
STEP 2. INSTALL THE APP // PHONE
The release carries the app, built at the same commit as the server and signed. Open the release page on the phone, download localghost-app-0.0.5.apk and allow the install, or install it over USB with adb.[15]
R=https://github.com/LocalGhostDao/localghost/releases/download/v0.0.5 curl -LO $R/localghost-app-0.0.5.apk adb install localghost-app-0.0.5.apk
CHECK IT BEFORE YOU INSTALL IT
# the site key's signature over the APK curl -LO $R/localghost-app-0.0.5.apk.asc curl -s https://www.localghost.ai/.well-known/pgp-key.asc | gpg --import gpg --verify localghost-app-0.0.5.apk.asc localghost-app-0.0.5.apk # the certificate it's signed with, apksigner is in the SDK build-tools apksigner verify --print-certs localghost-app-0.0.5.apk
# the key gpg must name, and the certificate apksigner must print
DCE9 A3D1 4EB4 6197 1DD5 F393 706E 4194 F08A 09A0
3dcc5e35718036a26cf36373ee53e79f512bf02d97f7017052996d55ddf0cdf4
gpg should report a good signature from info@localghost.ai with the first, and apksigner a signer certificate whose SHA-256 digest is the second. Android installs an update over the app only when it's signed with that same certificate, so a copy signed by anyone else can't replace it quietly. In the app, VERIFY BUILD shows the release ("wisp 0.0.5 (5)"), the commit it was built from (b5ef714), the source manifest's root and the signing certificate, and APP.txt in the release states the same hash, commit and certificate. The APK isn't byte-reproducible yet (a Gradle build differs from run to run), so the signed copy in the release is the one to check, and app/android/VERIFY.md says how. 0.0.5 installs over any earlier wisp and keeps the enrolment, since the key is the same.[4] The same APK is in the mirror's app set, so a USB copy of the mirror can set up the phone as well as the box.
BUILD IT YOURSELF INSTEAD
On Windows install Android Studio (it brings JDK 21) and in its SDK Manager tick Android API 37 and 36, Build-Tools 36.0.0, NDK 28.2.13676358 and CMake 3.22.1, the versions the build pins. Put the repo in a path without spaces, turn on USB debugging on the phone, plug it in and run gradlew.bat installDebug in app\android. On Debian, one script installs the same toolchain.[3]
app/android/tools/debian_setup.sh
source ~/.localghost_android_env
adb devices # the phone should be listed
cd app/android && ./gradlew installDebug
The build fetches the llama.cpp source for the phone's own model from the setup mirror and refuses it unless its SHA-256 matches the pin in app/src/main/cpp/CMakeLists.txt. With no internet, put llamaTarball=<path> in local.properties. Open MODELS in the app to check, and the top line should say "runtime in this build (llama.cpp v0.5.0-…)". A build of your own is signed with your own key, so Android won't put it over the released app or the released app over it, and uninstalling one to install the other loses the phone's enrolment. Pick one before you scan the QR.
STEP 3. DOWNLOAD AND CHECK THE RELEASE // THE BOX, YOUR USER
The server comes as one file, checked against the site key before anything is unpacked.[15]
V=0.0.5
R=https://github.com/LocalGhostDao/localghost/releases/download/v$V
for f in localghost-server-$V-linux-amd64.tar.gz SHA256SUMS SHA256SUMS.asc; do
curl -LO $R/$f
done
# the site key, its signature over the sums, then the file against the sums
curl -s https://www.localghost.ai/.well-known/pgp-key.asc | gpg --import
gpg --verify SHA256SUMS.asc SHA256SUMS
sed 's# [a-z]*/# #' SHA256SUMS | sha256sum -c --ignore-missing -
gpg should report a good signature from info@localghost.ai, key DCE9 A3D1 4EB4 6197 1DD5 F393 706E 4194 F08A 09A0, and sha256sum an OK for the bundle. Stop if either doesn't.
WHY THE SED, AND THE MIRROR INSTEAD OF GITHUB
The sums name each file under the release's folders (server/, app/, source/), and GitHub serves them flat, so the sed drops the folder and the names match what curl saved. Without it, sha256sum finds none of the files and says so. The setup mirror's server set carries the same bundle under a manifest signed by the same key.
M=https://www.localghost.ai/mirror curl -sO $M/MANIFEST.txt curl -sO $M/MANIFEST.txt.asc gpg --verify MANIFEST.txt.asc MANIFEST.txt BUILD=$(sed -n 's/^# Build: //p' MANIFEST.txt) curl -sO $M/$BUILD/server/localghost-server-$V-linux-amd64.tar.gz grep " /$BUILD/server/localghost-server-$V-linux-amd64.tar.gz$" MANIFEST.txt | sed "s# /$BUILD/server/# #" | sha256sum -c
STEP 4. INSTALL AND SET UP // THE BOX, SUDO
Unpack the release and run its installer. It asks where the volume goes, the seal tier, a domain if you have one and your two PINs, builds the model engine for your card, and changes nothing on the volume until you've typed the word it asks for.[2]
mkdir localghost tar xzf localghost-server-$V-linux-amd64.tar.gz -C localghost sudo localghost/tools/install.sh
# what it will ask, in order
volume a whole empty disk, /dev/disk/by-id/<the empty one>, then YES
or a file on a drive mounted at boot, /mnt/data/localghost.img, and a size, 500G
seal tpm (or software on a machine without one)
domain blank for the home network only
dry run read it, then APPLY
PINs the main PIN, then the wipe PIN
WHAT IT DOES, STAGE BY STAGE
install.sh copies the release to /opt/localghost/release/0.0.5/, the operator scripts to /opt/localghost/tools/ and ghost-cli, ghost-ctl and ghost-qr to /opt/localghost/bin/, the same places a release taken from the phone puts them, then runs setup.sh from the release, which sees the binaries and builds none of them.
- Packages. Postgres 18 with pgvector and Redis 8 from their own apt repositories, then
cryptsetupandtpm2-tools. It loadsdm_crypt, lets your user reach the TPM through thetssgroup, adds a narrow sudo rule for managing theghost.secdunit, and writes/etc/ghost/ghost.env. On a machine that had no database before, the Postgres and Redis services the packages start on their own are masked, because the box runs private instances inside the encrypted volume, on their own ports. Databases the machine already runs for other things are left alone. - Checks. As your user, in a fresh login so the new group counts, it confirms that the packages, the TPM, the state folder and the sudo rule all work for you.
- The engine and the models. llama.cpp built from the mirror's pinned source for the compute capability the driver reports (
nvidia-smi, one card or several), or for the CPU, said loudly, when there's none.GHOST_CUDA_ARCHS=89names it by hand. Then Gemma 4 12B with its vision projector, the EmbeddingGemma embedder, the phone's model and the speech engine, each checked against its pin and staged for the first unlock.GHOST_LLAMA=0leaves them forupdate.sh enginelater. - Volume. It lists the disks and the mounted drives, and you give it a whole empty disk or a path for a file. Name a disk by its
/dev/disk/by-id/name, since names likenvme0n1can swap between boots.[8] It shows the model, size and serial number, or the file, its size and the drive it's on, and asks you to typeYES, plusWIPEITif the disk or file already holds a LUKS container andIUNDERSTANDif anything on a disk is mounted. - Seal and name. The seal tier,
tpmfor a real box orsoftwarefor a machine without one, and the domain, left blank for the home network only. - Provision. It creates
ghostd, the user the daemons run as (no password, no shell), runsghost-setupas a dry run that touches nothing, and asks you to typeAPPLY. The apply asks for the main PIN and the wipe PIN on the terminal, formats the volume, sets up its databases and copies their runtime onto it, makes the box its own certificate authority, writes the nginx site and the systemd unit, startsghost.secdand draws the QR.
THE VOLUME AS A FILE
The volume is one LUKS2 container, and cryptsetup opens a file as readily as a disk, attaching a loop device of its own. Give the volume step a path on a drive that's mounted at boot and a size (20 GB at least, 500G or 1.5T say). The file is allocated in full with fallocate, with copy-on-write turned off first on btrfs, and the ghost.secd unit waits for that drive to be mounted. If the drive isn't there at unlock, secd says so rather than searching the disks. The seal, the PINs and the wipe work as they do on a disk. The file is ciphertext, a copy of it is ciphertext, and there's no key outside the seal. The drive shows a file of a size and a date, the way a disk shows its size. Don't put it on a network share. By hand it's ghost-setup --image /mnt/data/localghost.img --size 500G.[15]
FROM A CHECKOUT OF THE REPOSITORY INSTEAD
A box that builds its own code is set up from a clone, and setup.sh builds first, with Go 1.27.1 from the mirror (tools/install_go.sh) when the machine's Go is older than go.mod asks for. Everything else is the same, and redeploy.sh later builds and installs from the tree.
# as root apt install git make # as your login user, the release, or main for the newest code git clone --branch v0.0.5 https://github.com/LocalGhostDao/localghost.git cd localghost/server sudo ./tools/setup.sh $USER
STEP 5. SCAN THE QR, THEN UNLOCK // PHONE
Scan the QR with the app as soon as it appears, then clear the terminal and its scrollback. Unlock with the main PIN from the app, and Box Status on the phone fills in as the box opens the volume and starts the fleet.
# the engine is up, on the GPU or the CPU sudo /opt/localghost/tools/ns.sh /opt/localghost/bin/ghost-cli ghost.oracled status # a new QR, for a second phone, a missed scan or a terminal that was too small sudo /opt/localghost/bin/ghost-qr --ca /etc/ghost/ca --host <host>
WHY THE QR IS A CREDENTIAL, AND WHICH HOST TO GIVE IT
Scanning is the enrolment, with no code to type, no confirmation and no network call. The code is 858 bytes split over twelve frames, the phone's certificate and private key, the box's address, its name and the fingerprint of its server certificate, and any eight frames rebuild it, so a clean scan takes 8 to 12 seconds.[6] The box stops showing frames after the first verified connection. Until the first unlock anyone with a recording of those frames holds a working device identity (they'd still need the PIN). At the first unlock the phone makes a key of its own in its hardware keystore, the box signs a new certificate for it and retires the one the QR carried, so a recording is worthless from then on. If the terminal is too small to draw the code, setup says so instead of printing it any other way, and ghost-qr draws it once the window is bigger.
The app trusts the box by that fingerprint, not by its name. If the box sits behind a router without NAT hairpinning, the domain won't reach the box from inside your own network, so use the LAN address as the host and keep the domain for outside. Enrolment doesn't need the domain at all, and you can leave it blank at setup and add it later.
STEP 6. BRING THE REST IN // THE BOX, UNLOCKED
Once the box is unlocked, one command brings what setup left for later, from the same mirror with the same checks. Then turn on sync in the app, and photos, videos, the location trail and Health Connect data start flowing.
# Wikipedia with 60 GB free, anything newer on the mirror sudo /opt/localghost/tools/update.sh # streets, one continent at a time sudo GHOST_GEO_ROADS=europe-latest.osm.pbf /opt/localghost/tools/update.sh maps # every daemon, the pipeline, the engine, the map layers sudo /opt/localghost/tools/health.sh
WHAT EACH PART DOES, AND WIKIPEDIA
update.sh fetches only what differs from the signed list. English Wikipedia is one file of about 50 GB, and the box imports it into its own database in the background, an hour or two, with Box Status showing how far. Once the articles are in, the file is removed, and the chat's "what is …", the coin pages and the WIKIPEDIA page in the app read them on the box. GHOST_WIKI=0 leaves it out. Streets are opt-in because they're big. Africa takes about four minutes to cut and Europe takes hours. If the model turns out to be on the CPU when you expected the GPU, sudo /opt/localghost/tools/gpu.sh says which layer failed. Box Status shows each pipeline stage as done, total and pace.
STEP 7. DAY TO DAY // THE BOX
Releases reach the box through the phone. When a newer one is on the mirror the app says so ("wisp 0.0.5 is out"), and DEPLOY under SETTINGS and SERVER installs it, with ROLL BACK beside it and the last four releases it took kept ON THE SHELF to put back on.[12][15]
# new data from the mirror sudo /opt/localghost/tools/update.sh # a hung box resets itself and comes back locked sudo /opt/localghost/tools/watchdog.sh --arm # the enrolled phones by their key, and refusing a lost one /opt/localghost/bin/ghost-cli ghost.secd devices /opt/localghost/bin/ghost-cli ghost.secd retire id=<key>
HOW A RELEASE GOES IN, AND CODE FROM GIT
Once a day on Wi-Fi the app reads the mirror's signed list. DEPLOY downloads the release and hands it to the box, which checks the signature and every hash with the same verifier setup used, keeps the previous build and switches to the new one. It goes back by itself if the first unlock onto the new build fails or a critical daemon keeps falling over in its first ten minutes. SETTINGS › SERVER names the build the box runs, its commit, when it was built and with which Go.
# a box set up from a checkout: code from git, built and installed, # secd restarts, so the box locks git pull && sudo ./tools/redeploy.sh # a shell inside the namespace, standing on the volume sudo /opt/localghost/tools/ns.sh # the box's privacy settings, encrypted swap included sudo /opt/localghost/tools/privacy_check.sh
A redeploy restarts ghost.secd, which locks the box, and you unlock again from the phone. New daemon binaries wait in a staging folder on the system disk and move onto the volume at that unlock. Arm the watchdog on any box you can't walk up to. Mine froze once while I was away and stayed frozen until someone could press the button.[6]
> 3. HOW IT FITS TOGETHER
Only two processes serve LocalGhost from the unencrypted system disk, nginx and ghost.secd, and ghost.secd is the only LocalGhost unit systemd starts. nginx holds port 443 and checks the phone's client certificate against the box's own certificate authority with ssl_verify_client optional, then passes the verdict to secd, which answers 503 to anything without a valid certificate.[9] A stranger scanning the box finds a web server that seems to be down.
phone ─ mTLS ─▶ nginx :443 ─▶ ghost.secd system disk │ PIN ─▶ TPM unseals the key ─▶ LUKS2 opens │ in secd's private namespace ├── postgres, redis on the volume └── ghost.watchd ├── framed, searchd, synthd, cued ├── noted, voiced, tallyd, shadowd └── oracled ── llama-server
When the right PIN arrives, secd unseals the volume key from the TPM, opens the LUKS2 container and mounts it inside its own private mount namespace, so from the host the mount point looks empty whether the box is locked or not.[10] It then starts Postgres and Redis from the volume and ghost.watchd, which starts each daemon from the volume's bin/, restarts any that fall over and keeps their logs. The model runs as a private llama-server child of ghost.oracled on a loopback port, and the embedder as one of ghost.searchd, with the weights read from the volume. Locking runs the same steps in reverse, and everything except secd and nginx stops existing until the next PIN. The daemons and their jobs are on the home page.
On the system disk, /etc/ghost holds the box's settings and its certificate authority, /var/lib/ghost holds the seal, the staging folders and the mount point, and /opt/localghost holds the releases, secd and the operator tools. Everything else, the archive, the databases, the models, the logs and the daemons themselves, is on the volume.
> 4. WHAT THE BOX TALKS TO
During setup, the box downloads from three kinds of place. Everything it can get from the setup mirror comes from there, signed and checked against a key pinned in the repo, and if the mirror can't be reached, setup says so and stops.[5] A box with no internet at all can read a copy of the mirror from a USB disk with GHOST_MIRROR=file:///media/usb/mirror. The packages come from Debian's, PostgreSQL's and Redis's own apt repositories, the release from GitHub or the mirror, and a box built from a checkout also fetches Go modules from proxy.golang.org, pinned by go.sum.
After setup, the phone is the only thing that talks to the box, and the box asks the internet only for public things, with nothing of yours in the request. ghost.tallyd reads prices from the exchanges and the ECB itself, every minute. News feeds and the exchanges' tickers are also fetched by the phone on Wi-Fi, so their publishers see its address, and by the box when the phone is away. A coin's page reads the coin's own website, Wikipedia's API is asked only by a box without its own copy of Wikipedia, and updates come from the mirror. Once a day ghost.tallyd also pulls the forecast for the world's three thousand larger places from Open-Meteo, the same list for every box, and the weather where you are is looked up on the box. The box never asks for a map tile and never sends a location, a photo or a note. When you ask the chat something the box can't answer from what it holds, the phone sends the question's words to DuckDuckGo, or Brave with your own key, then reads the pages and hands the box the paragraphs. In the same round a who-is question goes to Wikipedia's summary API, unless the box has its own copy of Wikipedia, which then answers and nothing is asked. Up to wisp 0.0.4 a currency question the box didn't answer also went to Frankfurter, and since 0.0.5 the box answers from the ECB table it keeps.[15] Up to wisp 0.0.3 the phone also asked Android's own geocoder which country it was in, which on phones with Google's services usually sent the position to Google. Since 0.0.4 the box reads the country from its own map data, and an app older than 0.0.4 keeps asking its geocoder until it's updated.[14] Up to wisp 0.0.2 a weather question that named no place also sent the phone's position, rounded to two decimal places (about a kilometre), to Open-Meteo, and an app older than 0.0.3 still does until it's updated.[13]
> 5. WHAT IT COSTS
There's no recovery path in wisp. On the tpm tier the volume key is sealed to this board's TPM, in the boot state it measured, and nothing else holds a copy, so a dead board or a forgotten PIN means the archive on that disk is gone. The two FIDO2 hardware keys for setup and recovery described on the About page are planned for shade (1.x) at the earliest, and until they come, keep the originals of anything you can't lose somewhere else as well.
The software tier wraps the key under Argon2id of the PIN in /var/lib/ghost/seal.env, so someone with the raw disk and that file can guess PINs offline with nothing to stop them, and a short PIN under Argon2id is still a short PIN.[11] It's for machines without a TPM, and health.sh says which tier a box is on rather than letting you assume.
The box's certificate authority key sits on the system disk, by choice, so a phone can be enrolled or refused while the vault is locked. The price is that root on the box, or someone holding that disk, could make a phone certificate, and the PIN would be the only gate left. On the default setup secd also trusts a certificate header nginx passes to it on its loopback port, so on a box that runs other services, those could reach it without a certificate. ghost-ctl edge-passthrough --domain <box name> switches secd to checking the phone's certificate itself. It isn't the default yet, a box reached by IP alone can't take it yet because nginx routes the passthrough by name, and until you've run it, run nothing on the box you don't trust. Releases are signed by the same site key as the mirror, and a separate offline release key is planned for a later wisp. Phone certificates are valid for ten years, and shorter ones the phone renews itself are planned for a later wisp too. Since wisp 0.0.2 one phone can be retired by its key (step 7), and every call it makes after that is answered as if the box were down, but a retired phone that should come back needs a fresh QR and is a new device.[12] A retire button on the phone's DEVICES screen comes in a later wisp. The QR is a credential until the first unlock retires its key, as step 5 says. Since wisp 0.0.4 the server is built with Go 1.27.1, and the earlier wisps were built with Go 1.25.4, which is out of support. CI runs the server's tests on every push and pull request, and the app's JVM tests on pull requests and on demand. After the first unlock you still run update.sh yourself, and secd queuing it is next. A first import of a large library takes days on a GPU and longer on a CPU. And this is pre-release software that changes every day on my box first, so read each script before you run it and let the dry runs do their job. What the Box Does Now goes through each part with what it costs, and what broke on the way.
If a step here doesn't match what the script does on your machine, the script is right and this page is late. Email info@localghost.ai or open an issue, and I'll fix whichever one is wrong.
> REFERENCES
server/ (the standard library plus go-tpm and three golang.org/x packages) and the Kotlin app in app/android/, and server/tools/README.md, which lists every tool and the bring-up order this page follows, including the rule about installing the app before the QR. Source for the order and the commands. github.com/LocalGhostDao/localghost and server/tools/README.mdserver/tools/setup.sh, the guided setup, which runs as root, builds as the service user with sudo -u, and asks for a typed word before the disk step and before the apply. With install_db.sh, server_setup_root.sh and server_setup_user.sh beside it, the source for the six stages in step 4. server/tools/setup.shapp/android/BUILDING.md, the app's build guide for Windows and Debian, with the pinned SDK, NDK and CMake versions, the local.properties key for an offline llama.cpp tarball and the release script's variables. Source for step 2. app/android/BUILDING.mdapp/android/VERIFY.md, how a published APK is built, signed with a detached signature by info@localghost.ai and checked by rebuilding it from the same commit with the toolchain recorded in the build environment file. Source for the release builds in step 2. app/android/VERIFY.mdserver/tools/mirror_fetch.sh, which checks the manifest against the key fingerprint pinned in the repo (DCE9 A3D1 4EB4 6197 1DD5 F393 706E 4194 F08A 09A0) and each file against its SHA-256 before giving it its real name. Source for what the box downloads and how. localghost.ai/mirrornvme0n1 are handed out in the order devices are found on each boot, and the stable handles are the IDs under /dev/disk/by-id. Source for the disk advice in step 4. wiki.archlinux.org/title/Persistent_block_device_namingngx_http_ssl_module, the ssl_verify_client directive. With optional, nginx asks for a client certificate, verifies it if one is offered and passes the result on instead of refusing the connection itself, which is what lets secd answer every unauthenticated request with the same 503. Source for section 3. nginx.org/en/docs/http/ngx_http_ssl_module.htmlv0.0.2 of 2 October 2026 (commit c9d82c0), with the server bundle, the signed Android app with its APP.txt, the source archive and a SHA256SUMS signed by the site key, and its notes, which list what changed since 0.0.1 (the HEIC repair, retiring a phone by its key) and the known gaps, and wisp 0.0.1's notes, which describe how to install it, how the phone takes a release from the mirror, how the box checks it and rolls it back, and what leaves the box. Source for step 7 and section 5. github.com/LocalGhostDao/localghost/releases/tag/v0.0.2 and releases/tag/v0.0.1v0.0.3 of 3 October 2026 (commit c3726d3), with the same assets as 0.0.2 (the server bundle, the signed app with its APP.txt, the source archive and a SHA256SUMS signed by the site key), and its notes. The box pulls the forecast for the world's three thousand larger places from Open-Meteo once a day, the same list for every box, and answers the weather where the phone is from that table, so the phone has no weather lookup of its own and sends no position. An older app keeps asking Open-Meteo itself until it is updated. Source for section 4. github.com/LocalGhostDao/localghost/releases/tag/v0.0.3v0.0.4 of 4 October 2026 (commit 1062b7f), built with Go 1.27.1, with the same assets as the earlier cuts (the server bundle, the signed app with its APP.txt, the source archive and a SHA256SUMS signed by the site key), and its notes. Go comes from the mirror at setup and before every redeploy, the phone's country is read on the box rather than from its geocoder, a "what is" the box's Wikipedia answers never reaches the web, update.sh takes Wikipedia when the volume has room, and the known gaps name the certificate header again. Source for steps 2, 4, 6 and 7 and sections 4 and 5. github.com/LocalGhostDao/localghost/releases/tag/v0.0.4v0.0.5 of 5 October 2026 (commit b5ef714), built with Go 1.27.1, and its notes. The bundle carries ghost-setup, ghost-qr and the operator scripts with tools/install.sh, so a box is set up from the release alone. The volume can be a file on a drive mounted at boot, the engine is built for the card the driver reports, Wikipedia is imported into the box's database, and the phone no longer asks a rate service. server/tools/README.md gives the same short way. Source for steps 2 to 4, 6 and 7 and sections 4 and 5. github.com/LocalGhostDao/localghost/releases/tag/v0.0.5