Concepts
Architecture
The services that make up Holtdroid and how a request flows through them.
Цей контент ще не доступний вашою мовою.
Holtdroid is a handful of small services built from one Go binary
(holtdroid <subcommand>), plus a heavier session image that contains the
Android emulator.
flowchart LR
user([Browser]) -->|forward-auth| ingress[Ingress]
ingress --> api
subgraph ns[holtdroid namespace]
api["api<br/>UI + REST + session proxy"]
prov["provider-emulator"]
subgraph session["Session Pod"]
agent["agent + emulator"]
vpn["vpn (sing-box)"]
end
end
api -- "list + download" --> sources[("Sources<br/>folder / S3")]
sync["sync jobs<br/>(Slack, CI, …)"] -. "*.apk + .meta.json" .-> sources
api -- Provider API --> prov
api -- "Agent API via /s/{provider}/{id}/" --> agent
prov --> k8s[(Kubernetes API)]
agent -. shared network namespace .- vpn
vpn --> internet[(Internet)]
| Service | Role |
|---|---|
| api | Serves the web UI and its REST API, lists APKs from folder/S3 sources and takes uploads, caches and parses APKs, asks providers for sessions, proxies all session traffic, tracks activity and reaps idle sessions. Stateless. |
| sync jobs | Independent of Holtdroid: anything that writes *.apk files (and optional .meta.json sidecars) into a source, e.g. a Slack channel sync. |
| provider-emulator | A device provider: owns emulator sessions as Kubernetes Pods, accounts for slots, renders the VPN sidecar. |
| agent | Runs inside each session: boots the device, installs and instruments the app, serves the screen, DevTools, clipboard and network controls. |
Design principles
Section titled “Design principles”-
Contracts between services. Providers and agents are HTTP APIs with a shared conformance suite; sources are a file layout. The api knows nothing about Slack, Kubernetes or ADB. See Contracts.
-
Stateless api. Nothing the api needs to keep sessions running lives only in its memory or on its disk:
- sessions belong to providers — the emulator provider keeps them as labels and annotations on the session Pods it creates, so listing Pods is listing sessions;
- sign-ins are signed cookies, verifiable by any replica holding the same
auth.sessionKey; - user activity is flushed to the provider every 30 seconds;
- downloaded APKs are a cache (filesystem or S3) that refills from the sources; uploads and their owner/visibility live as files in a source.
Kill the api, reschedule it to another node or run three replicas — nobody notices. The api’s database holds history, not live state: in memory by default, or a SQLite file replicated to S3, which limits the api to one replica.
-
Same origin for everything. Browser traffic to a device goes through
https://<portal>/s/<session>/…. That keeps forward-auth cookies working for WebSockets inside iframes and means users need nothing installed locally. -
No lock-in. Auth, secrets and VPN are interfaces with vendor-neutral defaults.
Launching a session
Section titled “Launching a session”sequenceDiagram
participant B as Browser
participant A as api
participant S as source
participant P as provider
participant G as agent
B->>A: POST /api/v1/sessions
A->>S: download the APK revision (if not cached)
A->>A: parse APK metadata
A->>P: POST /v1/sessions
P-->>A: session (provisioning)
A-->>B: session id
B->>A: GET /s/{provider}/{id}/v1/status (SSE)
G->>A: fetch APK (internal port)
G-->>B: booting → installing → instrumenting → ready