23 Commits

Author SHA1 Message Date
Dorian
875da6c630 fix(fips): harden companion mesh joins 2026-07-24 18:47:57 +01:00
Dorian
7e3d01f633 fix(companion): first connect no longer stalls on unresolvable .fips dial hints — 0.5.12
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m59s
A pairing QR minted while the node UI was browsed over the mesh carried
npub….fips as fhost. Android's system DNS can't resolve .fips, so every
direct handshake dial failed and first connect crawled through public-
anchor discovery (2m35s observed) instead of one LAN hop.

- node UI: resolveServerUrl treats .fips names and IPv6 ULA literals as
  phone-unreachable and substitutes the node's LAN IP (same guard as
  tailnet/localhost)
- app: QR parser falls back to the url param's host when fhost is .fips
- app: peer store never keeps .fips dial addresses
- app: peer aliases slugged for the fips host map (spaces dropped
  "Framework PT"/"Mesh anchor" from name resolution entirely)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 14:40:44 +01:00
archipelago
ed2c14c88a fix(companion): pairing QR scannable again — cap anchors at 2, EC level L, 768px source
Three npub-bearing anchors pushed the pairing URI to ~600 chars (QR ~v23 at
EC-M) — phone cameras couldn't lock onto it off a screen. The phone only
needs the node itself (LAN p2p) plus one public rendezvous (vps2 preferred),
and screen scans don't need EC-M's damage tolerance — L drops a full
version tier.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 16:46:51 -04:00
archipelago
c58f84c6e9 feat(companion): npub-first pairing — node self-anchor + guaranteed public anchor in the QR
The pairing QR was effectively IP-first: the phone dialed the scanned LAN
host and went dark when the LAN renumbered or it left home. FIPS peers on
npubs; IPs are only dial hints. fanchors now leads with the paired node
ITSELF (fnpub @ current host, npub-keyed so LAN contact is direct p2p over
FIPS and survives DHCP), and fips.pair-info always includes the Archipelago
vps2 public anchor (pairing hint only — the node's own anchor file is not
modified) so the phone can rendezvous through the public mesh when away.
Contract doc updated with the REQUIRED app-side changes (built on the Mac).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 15:30:40 -04:00
archipelago
197e351880 fix(companion): pairing QR advertises the LAN address, never a tailnet IP
The QR mirrored the browser origin — an operator browsing over Tailscale
handed the phone a 100.x address it has no route to, so pairing silently
never connected (reported 2026-07-22; the 'random password' alongside it
was the device token working as designed). system.get-hostname now also
returns the default-route LAN IPv4; the QR substitutes it (or the mDNS
name) whenever the origin is localhost or a CGNAT/tailnet 100.64/10 IP.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 19:08:17 -04:00
Dorian
4b91765cc7 feat(companion): embedded FIPS mesh replaces WireGuard for remote access
- Android/rust/archy-fips-core: leaf-only fips node as a JNI cdylib (fips
  pinned to the fips-native fork rev with VpnService fd support), built by
  gradle via cargo-ndk (arm64), tested on host
- ArchyVpnService: split-tunnel VpnService routing only fd00::/8 (MTU 1280,
  foreground specialUse); FipsManager handles the one-time VPN consent and
  silent auto-start — no settings surface at all
- pairing QR now fully configures the mesh: fnpub/fip/fhost/fudp/ftcp plus
  fanchors (the node's seed-anchor list, npub@addr/transport) so the phone
  can rendezvous through public anchors when the LAN endpoint is unreachable
- default seed anchors gain the two dual-transport join.fips.network test
  anchors (23.182.128.74:443/tcp, 217.77.8.91:443/tcp); anchor adverts are
  Nostr kind-37195 events
- device token rides the password field end-to-end: backend accepts tokens
  wherever it accepts the password, so scan = instant login (WebSocket auth
  + WebView form injection unchanged); token logins skip TOTP
- ServerEntry.meshIp + IPv6-bracketed URLs; WebView retries the mesh address
  on main-frame errors, auto-login and origin checks honor it
- companion v0.5.0 (versionCode 20), arm64 abiFilter; FipsNative.available
  gates everything so non-arm64 still runs as a plain companion

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 22:06:32 +01:00
Dorian
b88609e0ff feat: instant companion pairing — device tokens, named QR, FIPS pair-info
- auth.createDeviceToken / listDeviceTokens / revokeDeviceToken RPCs; only
  SHA-256 hashes persist in data_dir/device-tokens.json
- auth.login accepts {token} (same rate limiter, skips TOTP like remember-me)
- pairing QR now carries name (server name, fallback "My Archipelago"),
  tok (instant login), and FIPS mesh params from new fips.pair-info RPC
  (npub, fips0 ULA, transport ports)
- companion onboarding drops the WireGuard install/tunnel screens — remote
  access moves to the FIPS mesh embedded in the companion app
- fix: fips daemon UDP bind now 2121, matching the published container port
  and fleet rosters (was upstream's 8668 — inbound UDP was dead on bridged
  installs, mesh silently rode TCP 8443)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 22:04:59 +01:00
archipelago
710f576c77 fix(ui): satisfy noUncheckedIndexedAccess in the WG retry ladder
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m43s
vue-tsc in the release build (unlike the gate's type-check) rejects
indexing WG_RETRY_DELAYS_MS with a bare counter; hoist the delay into a
local and gate on undefined instead of the length check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 02:52:43 -04:00
Dorian
0636d611e0 fix(ui): auto-retry the tunnel-QR provisioning while a first install settles
On a fresh node the wgqr step is often reached while the backend is still
settling (services starting, backend restarting during orchestration): a
single 'Failed to fetch' left a permanently blank QR. Retry network-class
failures on a 2s/4s/8s ladder while the step is still on screen, then fall
back to the friendly error + Try again button.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 06:45:34 +01:00
Dorian
aad6faa6d2 fix(ui): harden companion-overlay WireGuard provisioning and hide it in the companion app
- Never show the get-the-app pitch inside the companion WebView itself
- Don't guess peer-exists when list-peers is unreachable: try create,
  fall back to peer-config on already-exists errors
- Translate raw 'Failed to fetch' into an actionable network hint and
  add a Try again button instead of a dead-end error

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 06:07:35 +01:00
Dorian
68c25534d4 feat(ui): remote-access steps in the companion pairing flow
After 'I've installed it' the modal now walks WireGuard setup before
pairing: install WireGuard (hosted APK + QR), scan the node's tunnel
config (auto-provisioned 'companion-phone' peer via the existing
vpn.create-peer/peer-config RPCs, rendered client-side), switch it on,
then pair. Real nodes pair against http://10.44.0.1 — the tunnel only
routes AllowedIPs 10.44.0.0/16, so the LAN origin is unreachable
remotely. The demo fakes the tunnel via mock RPCs and keeps its public
pairing URL. REMOTE_ACCESS_STEPS=false restores the old two-screen flow.
Phones get the config as a downloadable .conf (can't scan own screen).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 02:32:02 +01:00
Dorian
6df9afe684 fix(android): pairing QR now actually scannable
Camera decode failed because every lever was at its minimum: the web
modal generated the QR at 112px (~2.5px per module before upscaling),
the analysis stream was CameraX's 640x480 default, and ZXing ran
without TRY_HARDER. Generate the QR at 512px (displayed 192px with a
real quiet zone), analyze at 1280x720, decode with TRY_HARDER plus an
inverted-luminance fallback. v0.4.14 (versionCode 18).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 02:03:11 +01:00
9d21dd5111 Merge pull request 'feat(android): companion QR pairing — scan, deep link, auto-login (v0.4.13)' (#92) from companion-qr-pairing into main
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m34s
2026-07-17 00:46:47 +00:00
archipelago
2e5f67a59a feat(ui): companion app banner in the App Store + APK download unblocked
Some checks failed
Demo images / Build & push demo images (push) Has been cancelled
The App Store gains a "Your Node. In Your Pocket." hero banner for the
mobile companion app, in the same format as the featured-app banner, with
the app running on a phone mockup rising out of the right edge. Install
(or clicking anywhere on it) opens the Remote Companion download/pairing
modal — now openable on demand via a shared trigger instead of only
auto-showing once.

Also fixes the companion APK download on PWA installs: /packages/ now
bypasses the service worker's SPA fallback, which was answering the
download click with index.html instead of the file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 20:46:08 -04:00
Dorian
2b3a18f189 feat(android): QR pairing — scan, deep link, one-step auto-login
Companion side of docs/companion-pairing-qr.md (v1 contract):
- Connect screen lands on Scan Node's QR / Enter Manually; NESMenu gains
  an add-server-by-QR icon button
- QrScannerOverlay: CameraX + ZXing core (pinned, on-device, no ML Kit)
- ServerQrParser: archipelago://pair?v=1&url=..[&pw=..]; unknown major v
  -> 'update the app'; upsert by origin so re-scans never duplicate
- archipelago:// deep link registered (singleTask + onNewIntent) for the
  modal's 'Open in companion app' button
- WebView auto-fills and submits Login.vue's password form when a stored
  password exists — demo QR is one step to a logged-in session
- Web modal + handoff doc copy synced to the real label: Scan Node's QR
- v0.4.13 (versionCode 17)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 01:41:35 +01:00
archipelago
3028685e6b feat(ui): companion modal pairing screen — scan a QR to auto-connect the app
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m45s
The Remote Companion intro modal gains a second screen: an "I've installed
it" button next to the download button slides over to a pairing QR the
companion app scans to load the server details automatically, with a Back
button to return. The QR is an archipelago://pair deep link carrying the
server URL — the demo embeds https://demo.archipelago-foundation.org plus
the shared demo password so a scan lands in a logged-in demo session; real
nodes send only the address (kiosk advertises the mDNS .local name since
its own origin is localhost). Phones also get a tap-to-open deep-link
button since you can't scan your own screen.

App-side contract + requirements handed off in docs/companion-pairing-qr.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 20:07:56 -04:00
archipelago
1a3170f1c3 fix(ui): companion popup waits for sustained calm; connection banner stops crying wolf
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m36s
Two "unpolished" moments, worst on the demo:

- CompanionIntroOverlay gated on a point-in-time check of the reveal
  cinematic flag. On a cold cache the entrance video can start buffering
  after the 5s base delay, so the flag was still false when sampled and
  the popup cut into the cinematic anyway. It now shows only after the
  scene has been continuously calm for the full grace window.

- ConnectionBanner's flat 2.5s debounce fired on every tab-return and on
  first dashboard paint: a dead WebSocket is the NORMAL state right then
  (browsers kill background-tab sockets; first paint races the initial
  connect), and reconnects routinely exceed 2.5s over real links. Those
  moments now get a 10s runway (15s window after load/resume); genuine
  mid-session drops keep the 2.5s response. On the demo the blip banner
  is suppressed entirely — it runs against a local mock, so "Connection
  lost" there is pure noise.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 17:42:57 -04:00
archipelago
fa91faa67c fix(intro): hold companion popup until dashboard reveal cinematic ends
Some checks failed
Demo images / Build & push demo images (push) Failing after 1m42s
The remote-companion popup fired on a blind 5s timer while the
first-visit dashboard entrance cinematic runs for 8s, so on a fresh
install it appeared mid-reveal and broke the intro. The cinematic now
publishes an introCinematicPlaying flag via the loginTransition store,
and the popup waits for it to clear (plus a 2s grace) before showing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 06:57:42 -04:00
archipelago
79564486d3 fix(demo): never leak the release-server IP in the public demo
Some checks failed
Demo images / Build & push demo images (push) Failing after 5m0s
- Companion QR overlay: demo builds encode the demo's own origin
  (/packages/archipelago-companion.apk ships in the web image) instead of
  the vps2 raw URL.
- Dockerfile.web/.backend: build-time scrub replaces every remaining
  occurrence of the release-server address with registry.demo.internal and
  fails the build if any survives.
- Mock backend update-mirror list, sideload help text, and changelog prose
  no longer name the server address.
- Copy demo-images workflow into .gitea/workflows/ — Gitea ignores
  .github/workflows when .gitea/workflows exists, so the CI never ran.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 11:38:51 -04:00
Dorian
5c43e12782 chore(android): publish companion as raw APK instead of zip
Serve the companion download as a plain .apk so a phone installs it
straight from the link/QR with no unzip step. Repoint the in-app
download URL, the ship + publish scripts, and the pre-push hook at
archipelago-companion.apk, and drop the legacy .apk.zip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 09:41:10 +01:00
archipelago
0ac67f5092 fix(ui): companion QR absolute 146 URL + Dashboard swipe type guard
- Companion app QR encoded a relative path (/packages/...apk.zip) which
  can't resolve when scanned by a phone. Point it at the absolute 146
  release-server URL so the download works from any device.
- Dashboard tab-swipe: guard tabs[next] (noUncheckedIndexedAccess) so the
  frontend type-checks/builds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 09:52:26 -04:00
archipelago
1a3d726eac frontend: polish app launch and release experience 2026-06-11 00:24:40 -04:00
Dorian
c421fdb064 feat: companion app improvements and intro overlay
Android: NES controller/keyboard enhancements, WebSocket reconnect,
portrait mode. Backend: remote input handler updates. UI: companion
intro overlay on dashboard, relay improvements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 20:01:14 +01:00