|
Download README.md from Snapkitty/kotlin-command-center: direct link, hf CLI and curl.
- Browser
- Download file 6.93 kB
-
https://huggingface.co/Snapkitty/kotlin-command-center/resolve/main/README.md
- Command line
-
hf download hf://Snapkitty/kotlin-command-center/README.md
-
curl -L -o README.md https://huggingface.co/Snapkitty/kotlin-command-center/resolve/main/README.md
6.93 kB
| 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 | |