Mirrored from 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 Β· A.parr@belespritdaccord.uk

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Space using Snapkitty/kotlin-command-center 1