LJ Beacon · Jarvis Beacon

A chat that works
when the internet doesn't.

Phones talk to each other over Bluetooth and Wi-Fi with no server in between. When there is internet, a relay you run yourself — this one — carries the rest. Rooms are constellations drawn from the people in them; every message shows how it reached you.

openanchoredsealedrelaythe sky is the app's · it drifts as you scroll
20:24◉ ▂▄▆ ▮
Home ◉ 2 3
jadeeuxx
Made it to the ridge. No signal up here 20:21
you're still coming through — two hops, via Kai's phone 20:22
jadeeuxx
Ha. Leaving a drop for whoever comes up next ⌖ 7d · ⇢2 · 20:23
see you at the lighthouse 20:24
+
message Home🎙
what it does

Chat, first. Then the things only this can do.

  • Dead drops A message that never touches the internet. Phone to phone; every phone it reaches carries it for seven days and hands it to every new phone it meets. Leave a note for whoever will be near these phones later.
  • Event mode Prepare a room while you still have internet, then switch the relay off on purpose. Festivals, marches, trails — everything keeps working over the mesh and reconciles when you're back.
  • Hop counts Every message shows how it got to you: straight from the sender, through two other phones, or over the relay.
  • The sky Each room is a constellation drawn from its members. Your star sits at the same bearing in every room you're in. Your relay is a lighthouse on the horizon — lit when it's up, with your latency in its glow.
  • A tone per person Everyone's notification sound and vibration come from their own key. You know who it is with the phone in your pocket.
  • Everything else you'd expect Rooms with voice, direct messages, replies, reactions, edits, pins, search, a media gallery, read receipts in DMs, walkie-talkie and full-duplex voice, location pins, rhythm messages, quiet hours, a home-screen widget.

install

One file, from this beacon. Updates come the same way.

v12514.5 MB · android 8.0+ · arm64 / arm / x86
name your relay: settings, admin, relay name. plus v124: your relay as a lighthouse on the sky map
↓ download asterism-v125.apk
sha256 09b4f8f40ddd46b1a50e9ffe8b0985c6f5fcd1ad44723349bdefaf87c1c051ae
QR: download link
Tap download. Android will ask to allow installs from this browser — that's the "unknown sources" prompt; allow it once.
Open. Pick a name. Nothing else is asked; there are no accounts.
Add this relay below, or use the app's default. Turn the nearby mesh on when you want it; chat works without it.
Updates arrive in the app from this beacon — you tab → updates. The file is checked against its sha256 before install.
this relay

In the app: you → settings → network → relays → add, paste the address, then tap ★ to make it your home — it keeps the history of every room you're in.

wss://beacon.proxdon.au/mesh

identity 0Hn0RArZVgEgi1RbzqjM1CmGG9ZCmPRMOAX7Gy7/S/o= — the app pins it on first sight and warns if it ever changes.

QR: relay address

how it works

No accounts. Two transports. One honest protocol.

01Identity

On first launch the phone makes an Ed25519 keypair; the public key is you, and a 24-word phrase encodes it. Write it down — it is the only backup. Every frame is signed. Other phones pin your key the first time they see it; if it ever changes, they say so and ask you to compare safety numbers in person.

02Nearby

Google Nearby Connections over Bluetooth, BLE and Wi-Fi — a flooding mesh. Every phone forwards what it hears, dedups by id, and decrements a TTL that starts at 6. That arithmetic is where hop counts come from. Voice is IMA-ADPCM at 16 kHz.

03Beacon

A WebSocket to a relay you choose — several at once if you like; a phone on two beacons bridges them. The beacon is deliberately dumb: it fans frames out, keeps history for anchored rooms, and lists public ones. It never sees a key that could decrypt a sealed room or a DM, and it stores no DMs at all.

04Rooms

A room is one conversation: one text stream, one voice room. It has an owner, mods, an access tier, post policy, slow mode, retention. Anchor it on a beacon to outlive your phone; make it local-only and no relay will ever carry a frame for it.

05Dead drops, precisely

An ordinary message with drop and expires at the envelope level, so carriers see them without opening a sealed body. Phones store the raw signed bytes and re-offer them on every new mesh link. Never sent to a beacon. 7 days, 16 KB, 200 per phone; expired ones are refused.

06Home beacon

Mark a relay ★ home and it keeps the history of every room you're in — 20,000 messages a room, on your box. Search reaches into it; a zip export is one tap. Two homes is failover for free.

07Notifications

The app holds its own connection — the honest cost of having no push server, and the reason there's a (hideable) "connected" notification. Messages land in Android's Conversations, can be bubbled, and take an inline reply from the shade.

08Storage

Everything stays on the phone: history, media within a budget you set, keys, settings. Cloud backup is off on purpose — an app that promises sealed rooms should not be uploading room keys to Google. Export any conversation as text.

privacy, stated honestly
tierhidesdoesn't hide
opennothing
invite-onlykeeps strangers out of the roomtraffic still floods the mesh; a relay can read it
sealedmessage content — AES-256-GCM, key from a passwordthat the room exists, who's in it, when they talk
directcontent — end-to-end, X25519 keys from each phone's identitythat two identities talked, and when

honest limitations

What it can't do, so you don't find out the hard way.


run your own

A beacon is one static binary with an embedded database.

It needs nothing else. This page, the app's fonts, the QR codes, the in-app update feed, the dashboard and the doctor are all inside it.

docker

cd beacon
docker compose up -d
docker compose logs -f beacon   # prints the public key on boot

State in ./data/. Port 8787.

linux + systemd

cd beacon
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o beacon-linux-amd64 .
sudo ./deploy.sh ./beacon-linux-amd64
sudo systemctl restart beacon   # deploy.sh doesn't restart a running one

A beacon user, /opt/beacon, a hardened unit, state in /var/lib/beacon/.

tls

# Caddyfile
beacon.example.org {
    reverse_proxy localhost:8787
}

Then phones use wss://beacon.example.org/mesh. Start the beacon with -public wss://…/mesh so this page and /beacon.json advertise it.

doctor

beacon doctor -db /var/lib/beacon/beacon.db

Checks the service, config, disk, live stats, and the public path — DNS, certificate expiry, HTTP through the proxy, a real WebSocket ping through the proxy, identity match — then prints the URL and a QR. Exit 1 on any problem; put it in a cron.

flags
flagdefaultmeaning
-addr:8787listen address
-path/meshWebSocket path
-dbbeacon.dbbbolt file for anchored rooms, mirrors and history; beacon.json lives beside it
-publicthe URL phones use (wss://host/mesh); saved and advertised
-apkdirdirectory of asterism-vNNN.apk builds to offer as in-app updates
-peeranother beacon's ws URL to federate with (repeatable): live traffic and directory both ways
-mirrorkeep20000messages kept per room for people who call this beacon home
-maxpeers · -rate · -burst512 · 250 · 500concurrent clients; per-client messages/sec and burst (voice is ~50/s)
-dedup · -reannounce200000 · 15sseen-id cache; how often anchored rooms are re-broadcast
endpoints
pathwhat
/meshthe WebSocket phones connect to
/ · /getthis page
/dashboardoperator view: phones, rooms, stored bytes, drops, uptime
/healthz · /stats · /nodes · /beacon.jsonok · JSON counters · the public room directory · identity, name, MOTD, policy, public URL, peers
/app/latest.json · /app/<build>.apkthe in-app update feed
/export/<room>.zipa room's stored history: one jsonl per channel and a transcript
/j/<room>a join page for invite links
what a beacon can and cannot see

Can: which identities connect, which rooms they're in, message sizes and timing, the content of open and invite-only rooms. Cannot: the content of sealed rooms or direct messages — and it stores no DMs at all. There is no account database because there are no accounts. Every privileged frame must be signed by a key the beacon has pinned; the operator's own key is the only admin credential.