One developer, one cloud server, one real phone. Can that be a complete mobile development loop? This post documents a self-hosted remote development architecture: build, install, on-device debugging, knowledge base, AI decision-making, and release — all scripted end to end. All addresses and paths are examples; they only express the architecture.
Pain points of solo mobile development:
- Real-device debugging is tied to a USB cable; you're stuck when you're away from the device
- Build environment and source code live on different machines; packaging, pushing, and installing by hand is error-prone
- Lessons learned during development are scattered across chat logs; you can't find them next time
- Big architecture decisions are made by gut feeling; there's no reusable discussion mechanism
The fix: make one cloud server the single hub of the whole pipeline, join your dev machine, phone, and cloud assistant into a private network, and script every step. The result is the diagram below.
graph TB
subgraph Cloud[Cloud Assistant]
A1[Build scripts]
A2[Debug wrapper scripts]
A3[Knowledge base tunnel entry]
end
subgraph VPS[Cloud Server · Hub]
B1[SSH entry]
B2[Knowledge base API]
B3[WireGuard Hub]
B4[Tunnel jump → Dev machine]
B5[AI advisory board · multi-model discussion]
B6[adb client → Phone]
B7[Debug service wrapper]
end
subgraph Dev[Dev Machine]
C1[Source code]
C2[Build system · gradle]
C3[Signing keystore]
C4[adb · LAN direct to phone]
C5[Knowledge base replica]
C6[Tunnel daemon]
end
subgraph Phone[Phone · Target App]
D1[LAN adb]
D2[WireGuard adb]
D3[On-device debug service]
end
subgraph GH[GitHub]
E1[Public repo · Release + APK]
end
A1 --> B1 --> B4 --> C1
C1 --> C2 --> C4 --> D1
B1 --> B3 --> B6 --> D2
A2 --> B1 --> B7 --> D2 --> D3
A3 --> B2
B2 --> B1 --> C5
B1 --> B5
D2 -.WG direct access.-> C1
E1 -.release.-> B1
All devices form a WireGuard mesh with the cloud server as the hub:
| Node | Example address | Role |
|---|---|---|
| Cloud server | Public entry + WG Hub (10.20.0.1/24) | Single hub; the only node exposed to the public internet |
| Dev machine | Tunnel back-connection + WG (10.20.0.2) | Source code and build host |
| Phone | LAN adb / WG adb (10.20.0.3) | Real-device debug target |
Key design decisions:
- Single entry point: all external traffic lands on the cloud server first, which forwards it; the firewall only needs a few open ports
- L3 mesh beats NAT tunneling: WireGuard is layer-3; peers reach each other directly. It feels like the device is next to you, unlike frp/reverse proxies
- Daemonized tunnel: the dev machine's reverse tunnel to the server is managed by systemd (autossh); it starts on boot and reconnects automatically. Switching WiFi/hotspot needs zero manual work
- Dual adb addresses: the same phone has a LAN adb address and a WG adb address; pick per scenario. adb keeps only one connection at a time, so disconnect the old address before switching
- Phone ↔ dev machine over WG: once the hub allows forwarding between WG subnets, the phone and dev machine talk directly inside the mesh. So the path "cloud → VPS → phone → dev machine" also works as a fallback when the tunnel jump is down
Build chain: cloud → server → dev machine → gradle → APK.
Two install paths, picked by priority:
| Priority | Path | When |
|---|---|---|
| Primary | cloud → server → dev machine → LAN adb | Dev machine and phone share a subnet (including phone hotspot; address auto-discovered by a probe script) |
| Fallback | cloud → server → WG → phone adb | When LAN is unreachable |
Three essentials of scripting (the soul of one-click scripts):
- Twin commands:
builddoes packaging + staged push + remote build + APK pull-back;installdoes install + cold start + focus check - Anti-stale transfer checks: the source archive is compared byte-by-size at every hop (local / server / dev machine); any mismatch aborts immediately — 90% of "mysterious" remote build failures are stale packages
- Atomic & re-runnable: every step is independent and re-runnable; failures resume from the checkpoint
Expose the device's debug capabilities as an HTTP service with a uniform interface:
/ping/state: health and state/ui/screen: UI tree and screenshot/logs: log streaming/sh/js: run commands / scripts on the device/touch/input: coordinate taps and text input/mem/search: in-app knowledge retrieval/chat/browser/*: conversation and browser control
Three lessons:
- One wrapper script: server and cloud each keep an identically named script, used as
ds.sh [METHOD] [PATH] [BODY]; arguments are escaped automatically, no more hand-rolled curl - Dynamic token fetch: the auth token is read live from the device config, never hardcoded — token rotation requires zero script changes
- Foreground service for keep-alive: the system freezes backgrounded apps (cached freezer) and drops the debug connection; a foreground service (dataSync type) keeps the debug API reachable when the app goes background
Note-taking apps are for humans; machines can't read them well. A self-hosted knowledge base must be machine-readable, searchable, and writable:
- Primary store lives on the server; the dev machine keeps a periodically synced replica (example: incremental sync every 10 minutes). All writes go to the primary, so local changes never get clobbered by replica sync
- Full API:
read / query / save / edit / append / rename / delete / list / recent / refresh - Full-text search: filename hits first, title weighted, ranked by score; replicas are excluded from search
- Web console: open in a browser; search highlighting, edit & save, create memories
- Index rules: every new file must be registered in the index file to stay 1:1; filenames must not be bare timestamps; appends never overwrite; replicas stay isolated from the primary
The payoff: cross-session searchability — the AI assistant can "remember" old project context in a new session instead of starting from zero every time.
Run a multi-model discussion service on the server: send the same question to several models (including third-party LLMs), let each analyze independently, then merge into constraint-shaped conclusions before implementing.
Typical outputs are not "I think", but executable constraints, e.g.:
- Performance baseline: a hard latency ceiling for an operation; exceeding it counts as failure
- Rendering discipline: purge old content before binding views, prevent reusing dirty state
- Degradation guardrail: above row/cell thresholds, degrade rendering automatically so huge tables never stutter
A solo developer's biggest risk is "assuming it works". Multi-model cross-checking is a cheap hedge.
The whole release flow (tag → push → create Release → upload APK) is scripted; one command does it all.
Credential lessons (learned the hard way):
- Tokens expire, and they don't tell you: a stored PAT can die at any time; always validate it via API first (GET /user must return 200) before releasing
- Never assume archived credentials still work: validate on every release; if stale, have the user regenerate instead of discovering a 401 halfway through the push
- Credentials never enter shell history: feed git via an askpass script + temp file, delete both immediately after use
| Stage | Pitfall | Fix |
|---|---|---|
| Build scripts | tar/grep closes the pipe early → SIGPIPE, misjudged as failure by pipefail | Buffer pipeline output to an intermediate file, then grep; avoids SIGPIPE |
| Install | Install command passed a local APK path to remote execution | Remote execution must use remote paths |
| Networking | Firewall blocks WireGuard handshake; peer shows no latest handshake forever | Open the UDP port and confirm the forward chain is ACCEPT |
| Debugging | App loses debug connection in background (process frozen by the system) | Foreground service keep-alive |
| Debugging | Phone has multiple adb addresses (LAN / WG); connection fails after switching | adb disconnect the old address before switching |
| Credentials | Stale PAT not validated before release; stuck at push 401 | Validate the token before pushing |
- One server as the pipeline hub; expose a single public entry
- WireGuard mesh turns a remote real device into "right next to you"
- Script everything + anti-stale checks; fewer manual steps
- HTTP-ify the on-device debug service; one wrapper script + dynamic tokens
- Self-hosted knowledge base (API + index + web console) for AI long-term memory
- Multi-model advisory board produces executable constraints
- Scripted releases + on-the-spot credential validation
This article shares architecture ideas; all addresses and paths are examples.