diff --git a/.github/workflows/docs-parity.yml b/.github/workflows/docs-parity.yml new file mode 100644 index 00000000..f51a316d --- /dev/null +++ b/.github/workflows/docs-parity.yml @@ -0,0 +1,31 @@ +name: Docs – Quickstart Snippet Parity + +on: + push: + paths: + - "docs/quickstart-snippet.md" + - "README.md" + - "docker/README.md" + - "tests/docs/quickstart-parity.sh" + - ".github/workflows/docs-parity.yml" + pull_request: + paths: + - "docs/quickstart-snippet.md" + - "README.md" + - "docker/README.md" + - "tests/docs/quickstart-parity.sh" + - ".github/workflows/docs-parity.yml" + +jobs: + quickstart-parity: + name: Assert quickstart snippet matches in README.md and docker/README.md + runs-on: [self-hosted, linux, x64, cliproxy] + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Run quickstart parity check + run: bash tests/docs/quickstart-parity.sh diff --git a/README.md b/README.md index 2f2aef24..cd4bce29 100644 --- a/README.md +++ b/README.md @@ -22,21 +22,23 @@ Anthropic-compatible APIs without config thrash. > **[Docker]** `ghcr.io/kaitranntt/ccs-dashboard:latest` is deprecated. Use `ghcr.io/kaitranntt/ccs:latest` instead. See [#1251](https://github.com/kaitranntt/ccs/issues/1251) and [docker/README.md](docker/README.md#choosing-an-image) for migration details. To wire a sibling container to CLIProxy, see [Connect your app to CLIProxy](docker/README.md#connect-your-app-to-cliproxy). -## Why CCS + +## Quick Start (Docker) -CCS gives you one stable command surface while letting you switch between: +With Docker installed: -- multiple runtimes such as Claude Code, Factory Droid, and Codex CLI -- multiple Claude subscriptions and isolated account contexts -- OAuth providers like Codex, Kiro, Claude, Qwen, Kimi, and more, with legacy - Copilot compatibility for existing setups -- API and local-model profiles like GLM, Kimi, OpenRouter, Ollama, llama.cpp, - Novita, and Alibaba Coding Plan +```bash +curl -fsSL https://ccs.kaitran.ca/docker-compose.yaml -o docker-compose.yaml +docker compose up -d +``` -The goal is simple: stop rewriting config files, stop breaking active sessions, -and move between providers in seconds. +Dashboard at http://localhost:3000 · CLIProxy at http://localhost:8317. -## Quick Start +Need a corporate-proxy alternative? Download directly: +`https://github.com/kaitranntt/ccs/blob/main/docker/compose.yaml` + + +## Install on Host (npm) ```bash npm install -g @kaitranntt/ccs @@ -53,6 +55,20 @@ ccs glm ccs ollama ``` +## Why CCS + +CCS gives you one stable command surface while letting you switch between: + +- multiple runtimes such as Claude Code, Factory Droid, and Codex CLI +- multiple Claude subscriptions and isolated account contexts +- OAuth providers like Codex, Kiro, Claude, Qwen, Kimi, and more, with legacy + Copilot compatibility for existing setups +- API and local-model profiles like GLM, Kimi, OpenRouter, Ollama, llama.cpp, + Novita, and Alibaba Coding Plan + +The goal is simple: stop rewriting config files, stop breaking active sessions, +and move between providers in seconds. + ## OpenAI-Compatible Routing CCS can now bridge Claude Code into OpenAI-compatible providers through a local diff --git a/docker/README.md b/docker/README.md index 5a8132df..97895ab3 100644 --- a/docker/README.md +++ b/docker/README.md @@ -11,8 +11,29 @@ Persistent config, restart on reboot. +> **[Deprecation]** `ghcr.io/kaitranntt/ccs-dashboard:latest` is deprecated. +> Migrate to `ghcr.io/kaitranntt/ccs:latest`. See [Migration](#migration-from-ccs-dashboardlatest) below. +
+ +## Quick Start (Docker) + +With Docker installed: + +```bash +curl -fsSL https://ccs.kaitran.ca/docker-compose.yaml -o docker-compose.yaml +docker compose up -d +``` + +Dashboard at http://localhost:3000 · CLIProxy at http://localhost:8317. + +Need a corporate-proxy alternative? Download directly: +`https://github.com/kaitranntt/ccs/blob/main/docker/compose.yaml` + + +--- + ## Choosing an image | Tag | Use | Approx. size | Status | @@ -23,113 +44,11 @@ Persistent config, restart on reboot. Both `ccs:latest` and `ccs:full` also publish pinned version tags (`ccs:..`, `ccs:.`, `ccs:`) for reproducible deployments. The `:full` variants carry the `full-` prefix: `ccs:full-`, `ccs:full-`, etc. -## Connect Your App to CLIProxy - -The CCS container joins a Docker network named `ccs-net`. This network name is a **stable, public contract** — it will not change without a SemVer-major release. - -### Network Contract - -| Resource | Stable name | Notes | -|---|---|---| -| Network | `ccs-net` | Attach any sibling container to this network | -| Service DNS | `ccs` | Resolves to the CCS container from inside `ccs-net` | -| CLIProxy port | `8317` | OAuth proxy — use as `OPENAI_BASE_URL` / `CLIPROXY_URL` | -| Dashboard port | `3000` | Web UI | -| Env-friendly URL | `http://ccs:8317` | Drop into your app's env without port-mapping on the host | - -### Pattern A — Same Compose File - -Declare `ccs-net` as external in your own compose file and add your service to it: - -```yaml -services: - my-app: - image: my-app:latest - environment: - CLIPROXY_URL: http://ccs:8317 - networks: - - ccs-net - -networks: - ccs-net: - external: true -``` - -Start CCS first so the network exists: - -```bash -docker compose -f docker/compose.yaml up -d # or: ccs docker up -docker compose -f my-app/compose.yaml up -d -``` - -### Pattern B — `docker run` - -Attach a container at runtime without modifying any compose file: - -```bash -docker run --rm \ - --network ccs-net \ - -e CLIPROXY_URL=http://ccs:8317 \ - my-app:latest -``` - -### Troubleshooting Network Issues - -**Service not resolvable from sibling container** - -Verify both containers are on `ccs-net`: - -```bash -docker network inspect ccs-net -``` - -The output should list both `ccs` and your app container under `Containers`. - -**Network not found** - -The `ccs-net` network is created when the CCS stack starts. Run: - -```bash -docker compose -f docker/compose.yaml up -d -# or: ccs docker up -``` - -**Conflict with an existing `ccs-net`** - -If you already have a network named `ccs-net` from unrelated tooling, either rename yours or scope -the CCS project via `COMPOSE_PROJECT_NAME`: - -```bash -COMPOSE_PROJECT_NAME=myproject docker compose -f docker/compose.yaml up -d -# Network becomes: myproject_ccs-net -``` - -Note: scoping changes the network name, so sibling compose files must use the same project name. - -**Podman / rootless containers** - -On rootless Podman, network names and DNS resolution may behave differently. Verify your Podman -version supports `--network` with named networks (`podman network ls`) and that `aardvark-dns` or -equivalent is installed for container-name resolution. - -**Low MTU on Hetzner and other cloud providers** - -Some cloud environments set a low MTU (e.g., 1450) on their overlay networks. If you see packet -fragmentation or stalled requests, add a custom MTU to the network in `compose.yaml`: - -```yaml -networks: - ccs-net: - name: ccs-net - driver_opts: - com.docker.network.driver.mtu: "1450" -``` - --- -## Preferred: `ccs docker` +## Power-user: `ccs docker` -The CLI now ships a first-class Docker command suite for the integrated CCS + CLIProxy stack: +The CLI ships a first-class Docker command suite for the integrated CCS + CLIProxy stack: ```bash ccs docker up @@ -259,6 +178,8 @@ Expected healthy output: - CLIProxy health: `cliproxy-port: ok, CLIProxy running` - Client count matches number of auth token files +--- + ## Prebuilt Image Quick Start Pull the recommended minimal image (CCS + CLIProxy, no AI CLIs): @@ -289,12 +210,7 @@ docker run -d \ Release-tag images are published as `ghcr.io/kaitranntt/ccs:` (minimal) and `ghcr.io/kaitranntt/ccs:full-` (full). -### Legacy image (deprecated) - -The `ghcr.io/kaitranntt/ccs-dashboard:latest` image continues building for 2 more releases but -emits a deprecation warning on startup. Migrate to `ccs:latest` at your earliest convenience. - -## Prebuilt Image Build Locally +### Build Locally ```bash docker build -f docker/Dockerfile -t ccs-dashboard:latest . @@ -312,6 +228,182 @@ Open `http://localhost:3000` (Dashboard). CCS also starts CLIProxy on `http://localhost:8317` (used by Dashboard features and OAuth providers). +--- + +## Connect Your App to CLIProxy + +The CCS container joins a Docker network named `ccs-net`. This network name is a **stable, public contract** — it will not change without a SemVer-major release. + +### Network Contract + +| Resource | Stable name | Notes | +|---|---|---| +| Network | `ccs-net` | Attach any sibling container to this network | +| Service DNS | `ccs` | Resolves to the CCS container from inside `ccs-net` | +| CLIProxy port | `8317` | OAuth proxy — use as `OPENAI_BASE_URL` / `CLIPROXY_URL` | +| Dashboard port | `3000` | Web UI | +| Env-friendly URL | `http://ccs:8317` | Drop into your app's env without port-mapping on the host | + +### Pattern A — Same Compose File + +Declare `ccs-net` as external in your own compose file and add your service to it: + +```yaml +services: + my-app: + image: my-app:latest + environment: + CLIPROXY_URL: http://ccs:8317 + networks: + - ccs-net + +networks: + ccs-net: + external: true +``` + +Start CCS first so the network exists: + +```bash +docker compose -f docker/compose.yaml up -d # or: ccs docker up +docker compose -f my-app/compose.yaml up -d +``` + +### Pattern B — `docker run` + +Attach a container at runtime without modifying any compose file: + +```bash +docker run --rm \ + --network ccs-net \ + -e CLIPROXY_URL=http://ccs:8317 \ + my-app:latest +``` + +### Troubleshooting Network Issues + +**Service not resolvable from sibling container** + +Verify both containers are on `ccs-net`: + +```bash +docker network inspect ccs-net +``` + +The output should list both `ccs` and your app container under `Containers`. + +**Network not found** + +The `ccs-net` network is created when the CCS stack starts. Run: + +```bash +docker compose -f docker/compose.yaml up -d +# or: ccs docker up +``` + +**Conflict with an existing `ccs-net`** + +If you already have a network named `ccs-net` from unrelated tooling, either rename yours or scope +the CCS project via `COMPOSE_PROJECT_NAME`: + +```bash +COMPOSE_PROJECT_NAME=myproject docker compose -f docker/compose.yaml up -d +# Network becomes: myproject_ccs-net +``` + +Note: scoping changes the network name, so sibling compose files must use the same project name. + +**Podman / rootless containers** + +On rootless Podman, network names and DNS resolution may behave differently. Verify your Podman +version supports `--network` with named networks (`podman network ls`) and that `aardvark-dns` or +equivalent is installed for container-name resolution. + +**Low MTU on Hetzner and other cloud providers** + +Some cloud environments set a low MTU (e.g., 1450) on their overlay networks. If you see packet +fragmentation or stalled requests, add a custom MTU to the network in `compose.yaml`: + +```yaml +networks: + ccs-net: + name: ccs-net + driver_opts: + com.docker.network.driver.mtu: "1450" +``` + +--- + +## Migration from `ccs-dashboard:latest` + +`ghcr.io/kaitranntt/ccs-dashboard:latest` is deprecated and will stop publishing after 2 more +releases. Migrate to `ghcr.io/kaitranntt/ccs:latest` now. + +### Steps + +1. **Stop the old stack.** + + ```bash + docker compose down + # or if running via docker run: + docker stop ccs-dashboard && docker rm ccs-dashboard + ``` + +2. **Preserve your data.** + + Existing `~/.ccs` data on the host is not affected by the container change. If you were using + a named volume (`ccs_home`), it persists automatically. If you were bind-mounting your host + `~/.ccs`, continue doing so — just update the compose file path below. + +3. **Get the new compose file.** + + ```bash + curl -fsSL https://ccs.kaitran.ca/docker-compose.yaml -o docker-compose.yaml + ``` + + Or download manually from: + `https://github.com/kaitranntt/ccs/blob/main/docker/compose.yaml` + +4. **If you were bind-mounting `~/.ccs`** (instead of using a named volume), edit the downloaded + `docker-compose.yaml` and replace the `ccs_home` named volume with your bind mount: + + ```yaml + volumes: + - ~/.ccs:/root/.ccs + ``` + + Otherwise the default named volume (`ccs_home`) works out of the box. Let compose create it + automatically, or create it manually first: + + ```bash + docker volume create ccs_home + ``` + +5. **Start the new stack.** + + ```bash + docker compose up -d + ``` + + Dashboard at http://localhost:3000 · CLIProxy at http://localhost:8317. + +6. **Verify.** + + ```bash + curl -fsS http://localhost:8317/ + ``` + +### What changes + +| Old | New | +|---|---| +| `ghcr.io/kaitranntt/ccs-dashboard:latest` | `ghcr.io/kaitranntt/ccs:latest` | +| > 600 MB image | < 350 MB image | +| Monolithic all-in-one | CCS + CLIProxy (AI CLIs optional via `:full`) | +| No stable network contract | `ccs-net` network, `ccs` service DNS | + +--- + ## Environment Variables Common CCS environment variables (from the docs): @@ -355,23 +447,6 @@ docker start ccs-dashboard docker rm -f ccs-dashboard ``` -## Prebuilt Image Docker Compose (Optional) - -Using the included `docker/docker-compose.yml`: - -```bash -docker-compose -f docker/docker-compose.yml up --build -d -docker-compose -f docker/docker-compose.yml logs -f -``` - -Stop: - -```bash -docker-compose -f docker/docker-compose.yml down -``` - -For the integrated CCS + CLIProxy stack managed by the CLI, use `ccs docker up` instead. - ## Persistence - CCS stores data in `/home/node/.ccs` inside the container. diff --git a/docs/quickstart-snippet.md b/docs/quickstart-snippet.md new file mode 100644 index 00000000..9400dce0 --- /dev/null +++ b/docs/quickstart-snippet.md @@ -0,0 +1,15 @@ + +## Quick Start (Docker) + +With Docker installed: + +```bash +curl -fsSL https://ccs.kaitran.ca/docker-compose.yaml -o docker-compose.yaml +docker compose up -d +``` + +Dashboard at http://localhost:3000 · CLIProxy at http://localhost:8317. + +Need a corporate-proxy alternative? Download directly: +`https://github.com/kaitranntt/ccs/blob/main/docker/compose.yaml` + diff --git a/tests/docs/quickstart-parity.sh b/tests/docs/quickstart-parity.sh new file mode 100755 index 00000000..60cc9622 --- /dev/null +++ b/tests/docs/quickstart-parity.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# quickstart-parity.sh +# Assert that README.md and docker/README.md both contain the canonical quickstart +# snippet verbatim (anchored by marker comments). +# Usage: bash tests/docs/quickstart-parity.sh (from repo root) +set -euo pipefail + +SNIPPET=$(awk '//,//' docs/quickstart-snippet.md) + +fail=0 +for f in README.md docker/README.md; do + file_block=$(awk '//,//' "$f") + if ! diff -q <(printf '%s' "$SNIPPET") <(printf '%s' "$file_block") >/dev/null 2>&1; then + echo "[X] $f quickstart snippet drift detected" >&2 + echo "--- canonical (docs/quickstart-snippet.md) ---" >&2 + printf '%s\n' "$SNIPPET" >&2 + echo "--- found in $f ---" >&2 + printf '%s\n' "$file_block" >&2 + fail=1 + else + echo "[OK] $f snippet matches canonical" + fi +done + +exit "$fail"