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
|