File size: 6,928 Bytes
d9ee7c4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11814c0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
---
license: agpl-3.0
tags:
- snapkitty
- october-2026-drop
- kotlin
- android
- ollama
- agents
- server-admin
- developer-tools
---

> Mirrored from [https://github.com/SNAPKITTYWEST/kotlin-command-center](https://github.com/SNAPKITTYWEST/kotlin-command-center) at commit `6849123`. Part of the **SnapKitty October 2026 main drop**.

# Kotlin Command Center

Talk to your Ollama agents and administer your server from your phone β€”
without opening a single inbound firewall port.

```
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   Bearer APP_SECRET   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  Access svc token  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚  Kotlin  β”‚ ───────────────────▢ β”‚  Cloudflare      β”‚ ──────────────────▢ β”‚  cloudflared   │──▢ Ollama :11434
 β”‚  app     β”‚   https://api.       β”‚  Worker gateway  β”‚  https://ollama-    β”‚  (outbound     β”‚
 β”‚ (:core / β”‚   yourdomain.com     β”‚  worker/         β”‚   internal.         β”‚   only, no     β”‚
 β”‚ :android β”‚                      β”‚                  β”‚   yourdomain.com    β”‚   inbound      │──▢ sshd :22
 β”‚ :desktop)β”‚ ───────────────────▢ β”‚                  β”‚ ──────────────────▢ β”‚   ports)       β”‚
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   wss://ssh.         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  Access svc token  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                yourdomain.com        (WS handshake)       on both hostnames
                + CF-Access headers
```

How the pieces fit:

- **Server** (`server/`): `cloudflared` opens *outbound-only* tunnels for
  Ollama (`ollama-internal.yourdomain.com β†’ localhost:11434`) and SSH
  (`ssh.yourdomain.com β†’ ssh://localhost:22`). No inbound ports, ever.
- **Worker gateway** (`worker/`): the only thing the phone calls for
  inference. Verifies `Authorization: Bearer <APP_SECRET>`, rate-limits per
  IP, then forwards to the tunnel with the Cloudflare Access service token
  attached. `/health` is unauthenticated; unknown routes 404; non-POST API
  calls 405.
- **Kotlin `:core`** (`app/core/`): Ktor CIO client for the Worker
  (`AgentClient`), plus a real JSch `SocketFactory` that bridges WebSocket
  frames to stream-oriented SSH over piped streams (`WebSocketBridge.kt`),
  with `StrictHostKeyChecking=yes` against a pinned `known_hosts`
  (`SshClient`).
- **`:desktop`** (`app/desktop/`): terminal CLI (`health`, `prompt`, `chat`,
  `ssh`) for testing the whole stack from a laptop.
- **`:android`** (`app/android/`): Compose UI (agent chat + server admin
  tabs), source-complete.

## Setup order

1. **Server** β€” on the box that runs Ollama:
   `sudo DOMAIN=yourdomain.com ./server/setup.sh`
   (installs cloudflared, creates the tunnel, routes DNS, installs the
   systemd unit). Then follow `server/ACCESS_SETUP.md` to put both hostnames
   behind Cloudflare Access with a **service token** (Allow policy).
2. **Worker** β€” `cd worker && wrangler secret put APP_SECRET` (+
   `CF_CLIENT_ID`, `CF_CLIENT_SECRET`), set `TUNNEL_BASE` in
   `wrangler.toml`, then `wrangler deploy`.
3. **App** β€” fill the `CC_*` env vars (see `:desktop` usage), or the
   Android settings screen once it exists:
   `CC_WORKER_URL`, `CC_APP_SECRET`, `CC_SSH_WS_URL`,
   `CC_CF_CLIENT_ID`, `CC_CF_CLIENT_SECRET`,
   `CC_SSH_USER`, `CC_SSH_HOST`, `CC_SSH_PORT`, `CC_SSH_KEY`,
   `CC_KNOWN_HOSTS` (generate with
   `ssh-keyscan -t rsa ssh.yourdomain.com`, verify the fingerprint
   out-of-band first).
4. Build: `./gradlew :core:test :desktop:build` (needs the Android SDK only
   for `:android`; it is excluded from the build until then).

## Security model

- The phone never reaches Ollama directly; the Worker is the single choke
  point and it demands the app secret on every API call.
- Both tunnel hostnames require the Cloudflare Access service token at the
  edge β€” the open internet gets a 403 before touching your server.
- SSH host keys are **pinned**: `StrictHostKeyChecking=yes` with a
  `known_hosts` you generated. Unknown or changed keys abort the connection.
  (The sketch this replaced used `no` β€” that was fixed, not inherited.)
- Secrets live in environment / Keystore-backed storage, never in code,
  flags, shell history, or the repo. Nothing in this tree is a real secret:
  domains, UUIDs, and tokens here are placeholders.

## Rotation

- **Service token** (phone lost / token leaked): Access β†’ Service Tokens β†’
  delete `kotlin-command-center`, create a new one, `wrangler secret put`
  the new `CF_CLIENT_ID`/`CF_CLIENT_SECRET`, update the app. The old token
  dies immediately.
- **APP_SECRET**: `openssl rand -hex 32`, then `wrangler secret put
  APP_SECRET` + update the app config.
- **SSH key**: rotate like any SSH key; re-run `ssh-keyscan` only if the
  *host* key legitimately changed (and verify the new fingerprint).

## Verified here vs. needs your hardware

Verified on this build machine (2026-09-30):

- `:core` main + test sources compile clean (kotlinc 2.0.21).
- **8/8 unit tests pass**: Worker health/prompt/chat/auth-failure via
  Ktor MockEngine; 256 KiB corruption-free round trip through the
  WebSocket↔stream bridge; handshake URL/headers; a real JSch instance
  emitting its `SSH-2.0-` banner through the bridge.
- `:desktop` CLI compiles and runs (usage/help path exercised).
- `node --check worker/worker.js` passes.

Not verifiable here (no such infrastructure on this box):

- Real Cloudflare account, tunnel, DNS, and Access policies.
- Real Ollama behind the tunnel; real sshd behind the SSH hostname.
- `:android` compilation β€” no Android SDK here. The module is excluded
  from `settings.gradle.kts`; add `include(":android")` on a machine with
  the SDK. Android secrets must move to EncryptedSharedPreferences before
  shipping (see `app/android/README.md`).
- `./gradlew` itself: this sandbox intercepts JVM TCP loopback (Gradle
  daemon handshake fails with the sandbox's policy banner), so the wrapper
  could not be generated here. The Gradle scripts are standard; run
  `./gradlew :core:test :desktop:build` on your own machine.

## License

AGPLv3 β€” see `LICENSE`. Every source file carries an SPDX header; the
gate is: `grep -rL "SPDX-License-Identifier: AGPL-3.0-or-later" <sources>`.

### πŸ’Ό Commercial License

SnapKitty code is free and open under **AGPL-3.0** for open-source use. Building a commercial product or service? A **proprietary commercial license** from Snapkitty Collective LLC lets you ship this code without the AGPL's source-sharing and network-use obligations.

**[β†’ Get a commercial license](mailto:A.parr@belespritdaccord.uk?subject=Commercial%20license:%20kotlin-command-center)** Β· A.parr@belespritdaccord.uk