diff --git a/docker/README.md b/docker/README.md index 414d4e02..7991b994 100644 --- a/docker/README.md +++ b/docker/README.md @@ -62,6 +62,8 @@ environment: CCS_DASHBOARD_PASSWORD_HASH: "" ``` +Running `ccs config auth setup` on the outer host shell updates that machine's own `~/.ccs`, not the Docker volume mounted into `ccs-cliproxy`. For the integrated stack, configure auth inside the container or provide the auth env vars in Compose. + Generate a bcrypt hash: ```bash diff --git a/docs/dashboard-auth-cli.md b/docs/dashboard-auth-cli.md index 0745f15e..f8c83fc5 100644 --- a/docs/dashboard-auth-cli.md +++ b/docs/dashboard-auth-cli.md @@ -12,6 +12,12 @@ Authentication is **disabled by default** for backward compatibility. Use the CL CCS does **not** ship a default dashboard username or password. When someone opens the dashboard from a non-loopback/IP address before auth is enabled, the UI now shows a setup state instead of an ambiguous login form. The host owner must run `ccs config auth setup`, or the user should switch back to the localhost URL if they are on the same machine. +Docker note: the integrated `ccs docker` stack stores its config inside the running container volume, not in the outer shell's `~/.ccs`. For Docker deployments, run auth setup inside the container: + +```bash +docker exec -it ccs-cliproxy ccs config auth setup +``` + When auth stays disabled, CCS now applies a localhost-only fallback on sensitive management endpoints. Remote devices can still open the dashboard UI when you intentionally bind it beyond loopback, but write-capable routes such as AI Provider management and CLIProxy auth/status helpers reject non-loopback requests until you enable dashboard auth. ## Account Context Modes (Related Feature) @@ -193,6 +199,8 @@ dashboard_auth: Run `ccs config auth setup` to configure credentials. +If you are using the integrated Docker stack, run that command inside `ccs-cliproxy`. Running it on the outer host shell updates a different config directory and will not unlock the running dashboard. + ### Forgot password Run `ccs config auth setup` again to set a new password. diff --git a/docs/project-roadmap.md b/docs/project-roadmap.md index c6271e38..b8371475 100644 --- a/docs/project-roadmap.md +++ b/docs/project-roadmap.md @@ -41,6 +41,7 @@ All major modularization work is complete. The codebase evolved from monolithic ### Recent Fixes +- **2026-04-15**: **#1010** Remote dashboard auth guidance now explains the Docker boundary explicitly. The readonly banner, remote login/setup card, and dashboard-auth docs now tell users that integrated Docker deployments keep config inside the running `ccs-cliproxy` container volume, so `ccs config auth setup` must run there rather than in the outer host shell. - **2026-04-14**: **#991** CCS now auto-routes Claude-target settings profiles that use OpenAI-compatible endpoints through a local Anthropic-compatible proxy instead of sending raw Anthropic `/v1/messages` traffic directly to chat-completions backends. The `ccs proxy` command now supports `start`, `status`, `activate`, and `stop` with explicit host binding, shell-aware activation helpers, and a fuller local runtime env contract. The proxy surface now exposes `GET /`, `/health`, `/v1/models`, and `/v1/messages`, logs routing decisions into CCS structured logs, supports Anthropic image blocks plus request-time `profile:model` overrides, and adds config-driven scenario routing (`background`, `think`, `longContext`, `webSearch`) on top of the compatible-profile path. Coverage now includes request routing, rate-limit/timeout/empty-upstream failures, chunked tool-call streaming, and disconnect cleanup alongside the existing unit, integration, and e2e suites. - **2026-04-10**: **#765** `/providers` now includes a first-class Hugging Face preset for API Profiles. CCS exposes Hugging Face Inference Providers through the existing OpenAI-compatible profile flow with the official router endpoint `https://router.huggingface.co/v1`, a short `hf` default profile name, and `hf` preset alias support for both the dashboard chooser and `ccs api create --preset hf`. - **2026-04-10**: **#944** Image Analysis auth readiness no longer collapses to native Read when merged runtime-status dependency overrides include a missing initializer value. CCS now preserves default dependency functions when override entries are `undefined`, still reads token-backed auth status directly in the local readiness path, and includes regression coverage for the missing-initializer case that previously surfaced as `deps.initializeAccounts is not a function`. diff --git a/ui/src/lib/i18n.ts b/ui/src/lib/i18n.ts index fdb803ff..0c395c28 100644 --- a/ui/src/lib/i18n.ts +++ b/ui/src/lib/i18n.ts @@ -51,7 +51,7 @@ const resources = { remoteGuardLabel: 'Remote access guard', loading: 'Checking dashboard access…', loginDescription: - 'Use the username and password configured on the host to access the dashboard.', + 'Use the username and password configured for this CCS instance to access the dashboard.', username: 'Username', password: 'Password', usernamePlaceholder: 'Enter username', @@ -63,21 +63,22 @@ const resources = { darkMode: 'Dark', noDefaultCredentials: 'No default credentials ship with CCS.', credentialsHint: - 'Credentials are created on the host with `ccs config auth setup`, then used here.', - remoteSetupTitle: 'Remote access needs host setup', + 'Credentials are created with `ccs config auth setup` on the CCS host. Docker deployments must run that command inside the container.', + remoteSetupTitle: 'Remote access needs auth setup', remoteSetupDescription: - 'This dashboard was opened from a non-local address, but dashboard auth is not enabled on the host yet.', + 'This dashboard was opened from a non-local address, but dashboard auth is not enabled for this CCS instance yet.', incompleteSetupDescription: - 'Dashboard auth is turned on, but the host setup is incomplete. Finish the host configuration before signing in.', + 'Dashboard auth is turned on, but the setup is incomplete. Finish the configuration for this CCS instance before signing in.', safetyNoteRemote: - 'Remote management stays locked until the host owner enables dashboard auth.', + 'Remote management stays locked until the CCS host owner enables dashboard auth.', safetyNoteLocal: 'If you are on the same machine, the localhost URL remains the simplest path in.', safetyNoteSession: - 'Successful sign-ins create an HTTP-only session that stays scoped to this host.', - hostStepTitle: 'On the host machine', + 'Successful sign-ins create an HTTP-only session that stays scoped to this dashboard host.', + hostStepTitle: 'On the CCS host', hostStepDescription: 'Create or re-enable dashboard credentials, then reopen this page from the remote device.', + dockerStepDescription: 'Docker deployment? Run the setup inside the running container:', localStepTitle: 'If this is your machine', localStepDescription: 'Open the localhost URL printed by `ccs config` instead of the LAN or Tailscale address.', @@ -1543,12 +1544,13 @@ const resources = { }, localhostDisclaimer: { remoteReadonlyAuthDisabledLong: - 'Remote dashboard access is read-only because dashboard auth is currently disabled on the host. Re-enable dashboard auth on the host to unlock remote changes.', + 'Remote dashboard access is read-only because dashboard auth is currently disabled for this CCS instance. Re-enable it on the CCS host. Docker deployments must do that inside the running container.', remoteReadonlyAuthDisabledShort: - 'Remote dashboard is read-only until dashboard auth is re-enabled on the host.', + 'Remote dashboard is read-only until dashboard auth is re-enabled for this CCS instance.', remoteReadonlySetupLong: - 'Remote dashboard access is read-only until you run ccs config auth setup on the host.', - remoteReadonlySetupShort: 'Remote dashboard is read-only until host auth is configured.', + 'Remote dashboard access is read-only until you run ccs config auth setup for this CCS instance. Docker deployments must run it inside the container.', + remoteReadonlySetupShort: + 'Remote dashboard is read-only until auth is configured for this CCS instance.', localLong: 'This dashboard runs locally. All data stays on your machine.', localShort: 'Local dashboard - data stays on your device.', dismiss: 'Dismiss disclaimer', @@ -2530,16 +2532,18 @@ const resources = { lightMode: '浅色', darkMode: '深色', noDefaultCredentials: 'CCS 不提供默认用户名或密码。', - credentialsHint: '凭据需要在主机上通过 `ccs config auth setup` 创建,然后在这里使用。', - remoteSetupTitle: '远程访问需要在主机上完成设置', - remoteSetupDescription: '当前通过非本机地址打开控制台,但主机尚未启用控制台认证。', + credentialsHint: + '凭据需要在 CCS 主机上通过 `ccs config auth setup` 创建;Docker 部署必须在容器内运行该命令。', + remoteSetupTitle: '远程访问需要完成认证设置', + remoteSetupDescription: '当前通过非本机地址打开控制台,但此 CCS 实例尚未启用控制台认证。', incompleteSetupDescription: - '控制台认证已开启,但主机上的设置尚未完成。请先完成主机配置再登录。', - safetyNoteRemote: '在主机所有者启用控制台认证前,远程管理会保持锁定。', + '控制台认证已开启,但设置尚未完成。请先为此 CCS 实例完成配置再登录。', + safetyNoteRemote: '在 CCS 主机所有者启用控制台认证前,远程管理会保持锁定。', safetyNoteLocal: '如果你就在这台机器上,使用 localhost 地址始终是最直接的方式。', - safetyNoteSession: '登录成功后会创建仅限此主机的 HTTP-only 会话。', - hostStepTitle: '在主机上操作', + safetyNoteSession: '登录成功后会创建仅限此控制台主机的 HTTP-only 会话。', + hostStepTitle: '在 CCS 主机上操作', hostStepDescription: '创建或重新启用控制台凭据,然后再从远程设备重新打开此页面。', + dockerStepDescription: '如果是 Docker 部署,请在正在运行的容器内执行:', localStepTitle: '如果这就是你的机器', localStepDescription: '请使用 `ccs config` 输出的 localhost 地址,而不是局域网或 Tailscale 地址。', @@ -3930,10 +3934,11 @@ const resources = { }, localhostDisclaimer: { remoteReadonlyAuthDisabledLong: - '远程控制台当前为只读,因为主机未启用控制台认证。请在主机上重新启用控制台认证以解锁远程编辑。', - remoteReadonlyAuthDisabledShort: '远程控制台为只读,直到主机重新启用控制台认证。', - remoteReadonlySetupLong: '远程控制台为只读,直到在主机上运行 ccs config auth setup。', - remoteReadonlySetupShort: '远程控制台为只读,直到完成主机认证配置。', + '远程控制台当前为只读,因为此 CCS 实例未启用控制台认证。请在 CCS 主机上重新启用;Docker 部署需要在运行中的容器内完成。', + remoteReadonlyAuthDisabledShort: '远程控制台为只读,直到此 CCS 实例重新启用控制台认证。', + remoteReadonlySetupLong: + '远程控制台为只读,直到为此 CCS 实例运行 ccs config auth setup。Docker 部署必须在容器内运行该命令。', + remoteReadonlySetupShort: '远程控制台为只读,直到为此 CCS 实例完成认证配置。', localLong: '本控制台在本地运行,所有数据保留在本机。', localShort: '本地控制台 - 数据保留在本机。', dismiss: '关闭提示', @@ -4884,21 +4889,22 @@ const resources = { darkMode: 'Tối', noDefaultCredentials: 'CCS không có sẵn tài khoản hay mật khẩu mặc định.', credentialsHint: - 'Thông tin đăng nhập được tạo trên máy host bằng `ccs config auth setup`, rồi dùng tại đây.', - remoteSetupTitle: 'Truy cập từ xa cần được thiết lập trên máy host', + 'Thông tin đăng nhập được tạo bằng `ccs config auth setup` trên máy CCS host. Với Docker, phải chạy lệnh đó bên trong container.', + remoteSetupTitle: 'Truy cập từ xa cần thiết lập xác thực', remoteSetupDescription: - 'Bảng điều khiển này đang được mở từ một địa chỉ không phải localhost, nhưng máy host chưa bật dashboard auth.', + 'Bảng điều khiển này đang được mở từ một địa chỉ không phải localhost, nhưng phiên bản CCS này chưa bật dashboard auth.', incompleteSetupDescription: - 'Dashboard auth đã được bật nhưng cấu hình trên máy host vẫn chưa hoàn tất. Hãy hoàn thành cấu hình trước khi đăng nhập.', + 'Dashboard auth đã được bật nhưng cấu hình vẫn chưa hoàn tất. Hãy hoàn thành cấu hình cho phiên bản CCS này trước khi đăng nhập.', safetyNoteRemote: - 'Quản trị từ xa sẽ tiếp tục bị khóa cho tới khi chủ máy host bật dashboard auth.', + 'Quản trị từ xa sẽ tiếp tục bị khóa cho tới khi chủ máy CCS host bật dashboard auth.', safetyNoteLocal: 'Nếu bạn đang ngồi ngay trên máy đó, đường localhost vẫn là cách đơn giản nhất để vào.', safetyNoteSession: - 'Sau khi đăng nhập thành công, phiên HTTP-only sẽ chỉ có hiệu lực trên đúng máy host này.', - hostStepTitle: 'Trên máy host', + 'Sau khi đăng nhập thành công, phiên HTTP-only sẽ chỉ có hiệu lực trên đúng máy chủ dashboard này.', + hostStepTitle: 'Trên máy CCS host', hostStepDescription: 'Tạo hoặc bật lại thông tin đăng nhập cho dashboard, rồi mở lại trang này từ thiết bị từ xa.', + dockerStepDescription: 'Nếu dùng Docker, hãy chạy lệnh này bên trong container đang chạy:', localStepTitle: 'Nếu đây là máy của bạn', localStepDescription: 'Hãy mở URL localhost mà `ccs config` in ra, thay vì địa chỉ LAN hoặc Tailscale.', @@ -6380,13 +6386,13 @@ const resources = { }, localhostDisclaimer: { remoteReadonlyAuthDisabledLong: - 'Dashboard từ xa ở chế độ chỉ đọc vì dashboard auth đang bị tắt trên máy host. Bật lại dashboard auth trên máy host để mở khóa thay đổi từ xa.', + 'Dashboard từ xa đang ở chế độ chỉ đọc vì dashboard auth đang bị tắt cho phiên bản CCS này. Hãy bật lại trên máy CCS host; với Docker, việc đó phải được thực hiện bên trong container đang chạy.', remoteReadonlyAuthDisabledShort: - 'Dashboard từ xa chỉ đọc cho đến khi dashboard auth được bật lại trên máy host.', + 'Dashboard từ xa chỉ đọc cho đến khi dashboard auth được bật lại cho phiên bản CCS này.', remoteReadonlySetupLong: - 'Dashboard từ xa chỉ đọc cho đến khi bạn chạy ccs config auth setup trên máy host.', + 'Dashboard từ xa chỉ đọc cho đến khi bạn chạy ccs config auth setup cho phiên bản CCS này. Với Docker, hãy chạy lệnh đó bên trong container.', remoteReadonlySetupShort: - 'Dashboard từ xa chỉ đọc cho đến khi auth được cấu hình trên máy host.', + 'Dashboard từ xa chỉ đọc cho đến khi auth được cấu hình cho phiên bản CCS này.', localLong: 'Dashboard này chạy cục bộ. Toàn bộ dữ liệu nằm trên máy của bạn.', localShort: 'Dashboard cục bộ - dữ liệu nằm trên thiết bị của bạn.', dismiss: 'Bỏ qua thông báo', @@ -7346,20 +7352,21 @@ const resources = { darkMode: 'ダーク', noDefaultCredentials: 'CCS にデフォルトの認証情報はありません。', credentialsHint: - '認証情報はホスト側で `ccs config auth setup` を実行して作成し、ここで使用します。', - remoteSetupTitle: 'リモートアクセスにはホスト側の設定が必要です', + '認証情報は CCS ホスト上で `ccs config auth setup` を実行して作成します。Docker では、そのコマンドをコンテナ内で実行する必要があります。', + remoteSetupTitle: 'リモートアクセスには認証設定が必要です', remoteSetupDescription: - 'このダッシュボードはローカル以外のアドレスから開かれていますが、ホストでダッシュボード認証がまだ有効になっていません。', + 'このダッシュボードはローカル以外のアドレスから開かれていますが、この CCS インスタンスではダッシュボード認証がまだ有効になっていません。', incompleteSetupDescription: - 'ダッシュボード認証は有効ですが、ホスト側の設定が未完了です。サインイン前に設定を完了してください。', + 'ダッシュボード認証は有効ですが、設定が未完了です。この CCS インスタンスの設定を完了してからサインインしてください。', safetyNoteRemote: - 'ホスト管理者がダッシュボード認証を有効にするまで、リモート管理はロックされたままです。', + 'CCS ホスト管理者がダッシュボード認証を有効にするまで、リモート管理はロックされたままです。', safetyNoteLocal: '同じマシン上にいる場合は、localhost の URL を使うのが最も簡単です。', safetyNoteSession: - 'サインインに成功すると、このホストに限定された HTTP-only セッションが作成されます。', - hostStepTitle: 'ホスト側で行うこと', + 'サインインに成功すると、このダッシュボードホストに限定された HTTP-only セッションが作成されます。', + hostStepTitle: 'CCS ホスト側で行うこと', hostStepDescription: 'ダッシュボード認証情報を作成または再有効化してから、このページをリモート端末で開き直してください。', + dockerStepDescription: 'Docker の場合は、実行中のコンテナ内で次を実行してください:', localStepTitle: 'もしこのマシンを使っているなら', localStepDescription: '`ccs config` が表示する localhost の URL を使い、LAN や Tailscale のアドレスは避けてください。', @@ -9254,13 +9261,13 @@ const resources = { }, localhostDisclaimer: { remoteReadonlyAuthDisabledLong: - 'ホストでダッシュボード認証が無効になっているため、リモートダッシュボードは読み取り専用です。リモートでの変更を有効にするには、ホスト側でダッシュボード認証を再度有効にしてください。', + 'この CCS インスタンスでダッシュボード認証が無効になっているため、リモートダッシュボードは読み取り専用です。CCS ホスト側で再度有効にしてください。Docker では、実行中のコンテナ内で行う必要があります。', remoteReadonlyAuthDisabledShort: - 'ホストでダッシュボード認証が再有効化されるまで、リモートダッシュボードは読み取り専用です。', + 'この CCS インスタンスでダッシュボード認証が再有効化されるまで、リモートダッシュボードは読み取り専用です。', remoteReadonlySetupLong: - 'ホストで ccs config auth setup を実行するまで、リモートダッシュボードは読み取り専用です。', + 'この CCS インスタンスで ccs config auth setup を実行するまで、リモートダッシュボードは読み取り専用です。Docker では、そのコマンドをコンテナ内で実行してください。', remoteReadonlySetupShort: - 'ホストの認証が設定されるまで、リモートダッシュボードは読み取り専用です。', + 'この CCS インスタンスの認証が設定されるまで、リモートダッシュボードは読み取り専用です。', localLong: 'このダッシュボードはローカルで動作しています。データはすべてお使いのマシンに残ります。', localShort: 'ローカルダッシュボード - データはお使いのデバイスに保存されます。', diff --git a/ui/src/pages/login.tsx b/ui/src/pages/login.tsx index d9636034..adb5fde2 100644 --- a/ui/src/pages/login.tsx +++ b/ui/src/pages/login.tsx @@ -207,6 +207,12 @@ export function LoginPage() { ccs config auth setup +

+ {t('auth.dockerStepDescription')} +

+ + docker exec -it ccs-cliproxy ccs config auth setup +
diff --git a/ui/tests/unit/components/shared/localhost-disclaimer.test.tsx b/ui/tests/unit/components/shared/localhost-disclaimer.test.tsx index 8c27d933..119f48cd 100644 --- a/ui/tests/unit/components/shared/localhost-disclaimer.test.tsx +++ b/ui/tests/unit/components/shared/localhost-disclaimer.test.tsx @@ -42,7 +42,7 @@ describe('LocalhostDisclaimer', () => { expect( screen.getByText( - 'Remote dashboard access is read-only until you run ccs config auth setup on the host.' + 'Remote dashboard access is read-only until you run ccs config auth setup for this CCS instance. Docker deployments must run it inside the container.' ) ).toBeVisible(); expect(screen.queryByLabelText('Dismiss disclaimer')).toBeNull(); @@ -60,7 +60,7 @@ describe('LocalhostDisclaimer', () => { expect( screen.getByText( - 'Remote dashboard access is read-only because dashboard auth is currently disabled on the host. Re-enable dashboard auth on the host to unlock remote changes.' + 'Remote dashboard access is read-only because dashboard auth is currently disabled for this CCS instance. Re-enable it on the CCS host. Docker deployments must do that inside the running container.' ) ).toBeVisible(); expect(screen.queryByLabelText('Dismiss disclaimer')).toBeNull(); diff --git a/ui/tests/unit/pages/login-page.test.tsx b/ui/tests/unit/pages/login-page.test.tsx index 689e5992..393913c2 100644 --- a/ui/tests/unit/pages/login-page.test.tsx +++ b/ui/tests/unit/pages/login-page.test.tsx @@ -59,7 +59,7 @@ describe('LoginPage', () => { await waitFor(() => { expect(navigateMock).toHaveBeenCalledWith('/settings', { replace: true }); }); - expect(screen.queryByRole('heading', { name: 'Remote access needs host setup' })).toBeNull(); + expect(screen.queryByRole('heading', { name: 'Remote access needs auth setup' })).toBeNull(); }); it('renders the incomplete setup copy when auth is enabled without credentials', () => { @@ -80,7 +80,7 @@ describe('LoginPage', () => { expect( screen.getAllByText( - 'Dashboard auth is turned on, but the host setup is incomplete. Finish the host configuration before signing in.' + 'Dashboard auth is turned on, but the setup is incomplete. Finish the configuration for this CCS instance before signing in.' ) ).not.toHaveLength(0); expect( @@ -88,6 +88,10 @@ describe('LoginPage', () => { 'Create or re-enable dashboard credentials, then reopen this page from the remote device.' ) ).toBeVisible(); + expect( + screen.getByText('Docker deployment? Run the setup inside the running container:') + ).toBeVisible(); + expect(screen.getByText('docker exec -it ccs-cliproxy ccs config auth setup')).toBeVisible(); expect(screen.queryByLabelText('Username')).not.toBeInTheDocument(); });