From ba2475c84f474960df1b788a8887c5eec708a238 Mon Sep 17 00:00:00 2001 From: Codeman maintainer Date: Fri, 6 Nov 2026 04:31:26 +0100 Subject: [PATCH] fix: updating a Compose install takes a git pull, and the docs say what the script does (release review) The summary and README said rerunning the script updates Codeman. It never fetches, so that was a successful no-op. The summary, README, its zh-CN mirror and the wiki now say git pull, then rerun, with the in-app route first. Also corrects "printed once" (twice at a terminal, never off one), says a typed answer beats a preset, adds the first-run setup, the 180 s wait, --no-wait and the changeme refusal to Running-As-A-Service and Troubleshooting, and records in architecture-invariants why docker/.env is generated FROM .env.example. Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- README.zh-CN.md | 9 ++++++++- docker/README.md | 4 ++-- docker/Start-Codeman.sh | 2 +- docs/architecture-invariants.md | 4 ++++ docs/docker-compose.md | 2 +- docs/wiki/Installation.md | 5 +++-- docs/wiki/Running-As-A-Service.md | 10 ++++++++-- docs/wiki/Troubleshooting.md | 15 +++++++++++++++ test/docker-start-setup.test.ts | 2 ++ 10 files changed, 45 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 62c6e827..155295ae 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ git clone https://github.com/Ark0N/Codeman.git && cd Codeman bash docker/Start-Codeman.sh ``` -The first run asks three questions (data folder, port, password; Enter takes the default, including a generated password), writes `docker/.env` for you, builds the image and ends on the URL once Codeman answers. The image already includes Claude Code, Codex, Gemini CLI and OpenCode. Codeman runs in a container and spawns Docker cases as sibling containers through the host socket. To update, use **App Settings → Updates** or run the script again rather than a plain `docker compose up`, so the rebuilt image, refreshed volumes and entrypoint arrive together. See the [Docker deployment guide](docker/README.md) for Unraid, direct Compose commands, storage and networking options. +The first run asks three questions (data folder, port, password; Enter takes the default, including a generated password), writes `docker/.env` for you, builds the image and ends on the URL once Codeman answers. The image already includes Claude Code, Codex, Gemini CLI and OpenCode. Codeman runs in a container and spawns Docker cases as sibling containers through the host socket. To update, use **App Settings → Updates**, or `git pull` and run the script again rather than a plain `docker compose up`, so the rebuilt image, refreshed volumes and entrypoint arrive together. See the [Docker deployment guide](docker/README.md) for Unraid, direct Compose commands, storage and networking options.
Keep it running in the background diff --git a/README.zh-CN.md b/README.zh-CN.md index e8fd544e..b97f8823 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -87,7 +87,14 @@ codeman users add alice --admin # 创建第一个管理员账号 codeman web --multiuser # 命名登录 + 按用户隔离的案例空间 ``` -**更喜欢 Docker Compose?** `docker/` 里附带一套本地镜像的 Compose 部署:把 `docker/.env.example` 复制为 `docker/.env`,设置 `CODEMAN_PASSWORD`,然后在 Linux 上运行 `bash docker/Start-Codeman.sh`。Codeman 自己跑在容器里,并通过宿主机的 socket 把 Docker 案例作为并列容器拉起。更新之后请再跑一次这个脚本,而不是直接 `docker compose up`,这样重建的镜像、刷新的卷和新的入口脚本会一起就位。直接的 Compose 命令、存储与网络选项见 [Docker 部署指南](docker/README.md)(英文)。 +**更喜欢 Docker Compose?** 克隆仓库,运行一个脚本即可(Linux,需要 Docker 和 Compose v2 插件): + +```bash +git clone https://github.com/Ark0N/Codeman.git && cd Codeman +bash docker/Start-Codeman.sh +``` + +首次运行会问三个问题(数据目录、端口、密码;回车即采用默认值,包括自动生成的密码),替你写好 `docker/.env`,构建镜像,并在 Codeman 响应后给出访问地址。镜像已内置 Claude Code、Codex、Gemini CLI 和 OpenCode。Codeman 自己跑在容器里,并通过宿主机的 socket 把 Docker 案例作为并列容器拉起。更新时请使用 **App Settings → Updates**,或者先 `git pull` 再跑一次这个脚本,而不是直接 `docker compose up`,这样重建的镜像、刷新的卷和新的入口脚本会一起就位。Unraid、直接的 Compose 命令、存储与网络选项见 [Docker 部署指南](docker/README.md)(英文)。 详见下文[多用户模式](#多用户模式可选启用)。 diff --git a/docker/README.md b/docker/README.md index 80158230..54b5871c 100644 --- a/docker/README.md +++ b/docker/README.md @@ -17,7 +17,7 @@ The first run checks that Docker, the Compose v2 plugin (2.27.2 or newer) and th | ----------- | ------------------------------------------------------------------------------------------------------------------------------ | | Data folder | `~/codeman-docker`. It becomes the container's home: Codeman's state, CLI logins, and the `codeman-cases` folder for projects. | | Port | 3000, or the next free port when something on the machine already uses 3000. | -| Password | A generated 24-character password, printed once. | +| Password | A generated 24-character password, shown on your terminal and kept in `docker/.env`. | It writes `docker/.env` from `.env.example` (readable only by you, with this host's time zone filled in), builds the image, starts the container, waits until Codeman answers, and prints the URL to open, the address for other devices on your network, and the commands for logs and stopping. The first build takes a few minutes. @@ -29,7 +29,7 @@ Your account has to be able to use Docker without sudo. If it cannot, the script | `--setup-only` | Write `docker/.env` and stop, so you can review it (or add the optional settings below) before the build. | | `--no-wait` | Start the container without waiting for Codeman to answer. | -On a first run, `CODEMAN_APPDATA_PATH`, `CODEMAN_PORT` and `CODEMAN_PASSWORD` set in the environment replace the defaults. Every later run reads `docker/.env` as it is, asks nothing and never edits it. Change a value by editing the file and running the script again. The script refuses to start while `CODEMAN_PASSWORD` is still the example's `changeme`, because the container is reachable from your network and controls Docker on the host. +On a first run, `CODEMAN_APPDATA_PATH`, `CODEMAN_PORT` and `CODEMAN_PASSWORD` set in the environment replace the defaults, a different answer typed at the question still wins, and a preset password skips its question. A password the script generates is only ever shown on a terminal: when the output is captured (a log, a pipe), read it from `docker/.env`. Every later run reads `docker/.env` as it is, asks nothing and never edits it, except that Compose prefers a variable exported in your shell over the file. Change a value by editing the file and running the script again. The script refuses to start while `CODEMAN_PASSWORD` is still the example's `changeme`, because the container is reachable from your network and controls Docker on the host. ### Setting it up by hand diff --git a/docker/Start-Codeman.sh b/docker/Start-Codeman.sh index 3e1c4938..9f94c058 100644 --- a/docker/Start-Codeman.sh +++ b/docker/Start-Codeman.sh @@ -791,7 +791,7 @@ print_access_summary() { printf '\n' printf ' Logs cd %s && docker compose logs -f codeman\n' "$dir_q" printf ' Stop cd %s && docker compose down\n' "$dir_q" - printf ' Update App Settings > Updates in the dashboard, or rerun this script\n' + printf ' Update App Settings > Updates in the dashboard, or git pull and rerun this script\n' } report_when_ready() { diff --git a/docs/architecture-invariants.md b/docs/architecture-invariants.md index 479d279b..cd58045c 100644 --- a/docs/architecture-invariants.md +++ b/docs/architecture-invariants.md @@ -620,6 +620,10 @@ Adding the header changed the preamble, so `CODEMAN_PREAMBLE` was bumped (1.22.0 `CODEMAN_DOCKER_DISABLE_SWAP_LIMIT=1` drops `--memory-swap` (and filters only that one kernel warning) for hosts without swap accounting; `--memory` still applies. +⚠️ **`docker/.env` is generated FROM `.env.example`, at 0600, never from a list of keys.** `Start-Codeman.sh`'s first run (only when nothing exists at that path: a dangling symlink or a directory is refused) rewrites the five `KEY=` lines it asks about and copies every other line, because `diffRequiredEnvKeys` refuses an in-app update while the user's file lacks a key the target release's example sets, so a hand-picked subset would block the very next update. Compose's dotenv is not bash: a backslash escapes a single-quoted value's closing quote (measured on 2.33.0 and 5.5.0), so `accept_password` and `accept_appdata_path` refuse a trailing backslash as well as a single quote. Compose also prefers the shell environment over the file, so setup unsets what it wrote, or a preset would beat a typed answer. `test/docker-start-setup.test.ts` pins all of it, against a stub that reproduces both rules. + +⚠️ **`changeme` is refused by BOTH scripts**, before anything is built or stopped: the container publishes on every interface and holds the Docker socket, so that password is host root. `Update-Codeman.sh` needs its own check, ahead of its `down`, because a refusal found only at the handoff to `Start-Codeman.sh` would leave the stack stopped. + ⚠️ The deployment ALSO self-updates in place (the repo bind mount at `/opt/codeman` + a restart-by-exiting supervisor) — see Self-update and `docs/docker-self-update.md` before touching `server.Dockerfile`, the compose file or `.env.example`, since each is an input to the updater's environment gate. User guides: `docs/docker-compose.md` + `docker/README.md`. ### DeepSeek web UI diff --git a/docs/docker-compose.md b/docs/docker-compose.md index c3950154..ca72e8c5 100644 --- a/docs/docker-compose.md +++ b/docs/docker-compose.md @@ -26,7 +26,7 @@ git clone https://github.com/Ark0N/Codeman.git && cd Codeman bash docker/Start-Codeman.sh ``` -The first run checks Docker, Compose and the daemon, then asks for a data folder (default `~/codeman-docker`), a port (default 3000, or the next free one) and a password (Enter generates one and prints it once). It writes `docker/.env` from `docker/.env.example`, builds the image, starts the container and waits until Codeman answers, then prints the URL and how to sign in. `--yes` takes every default without asking, and `--setup-only` writes `docker/.env` and stops so it can be reviewed first. The full description, including the options, is the Start section of the [Docker deployment guide](../docker/README.md#start). +The first run checks Docker, Compose and the daemon, then asks for a data folder (default `~/codeman-docker`), a port (default 3000, or the next free one) and a password (Enter generates one, shows it on your terminal and keeps it in `docker/.env`). It writes `docker/.env` from `docker/.env.example`, builds the image, starts the container and waits until Codeman answers, then prints the URL and how to sign in. `--yes` takes every default without asking, and `--setup-only` writes `docker/.env` and stops so it can be reviewed first. The full description, including the options, is the Start section of the [Docker deployment guide](../docker/README.md#start). The data folder is mounted at `/home/${CODEMAN_RUNTIME_USER}` in the container, so Codeman state and CLI credentials stay on the host outside Docker-managed volumes. On every start the script determines `PUID` and `PGID` from the owner of that folder, and `DOCKER_SOCKET_GID` from the configured Docker socket, before invoking Compose. A root-owned data folder is rejected so the runtime account cannot become UID 0. diff --git a/docs/wiki/Installation.md b/docs/wiki/Installation.md index 65bc241d..4d204773 100644 --- a/docs/wiki/Installation.md +++ b/docs/wiki/Installation.md @@ -153,8 +153,9 @@ bash docker/Start-Codeman.sh ``` The first run asks for a data folder, a port and a password. Enter takes each default, -including a generated password that is printed once. It then writes `docker/.env`, builds the -image (a few minutes the first time) and ends on the URL once Codeman answers. The image +including a generated password, shown on your terminal and kept in `docker/.env`. It then +writes that file, builds the image (a few minutes the first time) and ends on the URL once +Codeman answers. The image already includes Claude Code, Codex, Gemini CLI and OpenCode: start a session and log the CLI in once, and the login is kept in the data folder. diff --git a/docs/wiki/Running-As-A-Service.md b/docs/wiki/Running-As-A-Service.md index 88cc5518..4e00c507 100644 --- a/docs/wiki/Running-As-A-Service.md +++ b/docs/wiki/Running-As-A-Service.md @@ -147,7 +147,7 @@ log stream --predicate 'process == "node"' # macOS, noisy | Installer | Re-run the one-liner, or **App Settings → System → Updates**. | | npm | `npm update -g aicodeman` | | git clone | `git pull && npm install && npm run build`, then restart. | -| Docker Compose | Re-run `Start-Codeman.sh`, or the in-app updater, which restarts the container in place. | +| Docker Compose | The in-app updater, which restarts the container in place, or `git pull`, then re-run `Start-Codeman.sh`. | ### The in-app updater @@ -188,7 +188,13 @@ the bind-mounted directories with the right owner, honours a `docker-compose.ove and refreshes the build volumes when the checkout moved under them. The in-app updater applies code only and restarts by letting the container exit, so it refuses a release that changes the Dockerfile, the compose file, or adds a new `.env` key, until you re-run the -script. Guide: +script. + +With no `docker/.env` yet, the script first asks for a data folder, a port and a password and +writes that file. It then waits up to 180 seconds for Codeman to answer and exits 1 if the +container crash-loops or never answers; pass `--no-wait` for scripted use. `Start-Codeman.sh` +and `Update-Codeman.sh` both refuse to run while `CODEMAN_PASSWORD` is still the example +`changeme`. Guide: [`docker/README.md`](https://github.com/Ark0N/Codeman/blob/master/docker/README.md). ## The tunnel as a service diff --git a/docs/wiki/Troubleshooting.md b/docs/wiki/Troubleshooting.md index 5a8fba2e..d9714103 100644 --- a/docs/wiki/Troubleshooting.md +++ b/docs/wiki/Troubleshooting.md @@ -329,6 +329,21 @@ Start the stack with `bash docker/Start-Codeman.sh` rather than a plain `docker and update: since 1.29.0 the entrypoint corrects a root-owned bind mount before dropping privileges. See [Running As A Service](Running-As-A-Service). +### Compose: `CODEMAN_PASSWORD is still the example value "changeme"` + +`Start-Codeman.sh` and `Update-Codeman.sh` refuse that password before they build or stop +anything, because the container publishes its port on every interface and holds the Docker +socket, so whoever signs in controls Docker on the host. Set a real password in `docker/.env` +and run the script again. + +### Compose: `Codeman did not come up`, or no answer after 180 seconds + +`Start-Codeman.sh` waits for the server after `up -d`. `did not come up` means the container +exited, restarted or went unhealthy, and the script prints its last log lines. `has not +answered after 180 seconds` means it is still running but silent, so on a slow host it may +just need longer: follow it with `docker compose logs -f codeman` from `docker/`. Either way +the script exits 1, and `--no-wait` skips the wait for scripts that check on their own. + ### A remote SSH session dropped and did not come back A bounded-backoff watcher reattaches dropped sessions, and it is on by default. Intentional diff --git a/test/docker-start-setup.test.ts b/test/docker-start-setup.test.ts index 9014625f..63634ff1 100644 --- a/test/docker-start-setup.test.ts +++ b/test/docker-start-setup.test.ts @@ -536,6 +536,8 @@ describe.skipIf(process.platform === 'win32')('Start-Codeman.sh first run, start expect(r.stdout).toMatch(/Sign in +admin, with the CODEMAN_PASSWORD from .*docker\/\.env/); expect(r.stdout).toMatch(/Logs +cd .* && docker compose logs -f codeman/); expect(r.stdout).toMatch(/Stop +cd .* && docker compose down/); + // Rerunning alone rebuilds the same checkout: nothing here fetches. + expect(r.stdout).toMatch(/Update +App Settings > Updates in the dashboard, or git pull and rerun this script/); const up = r.log.findIndex((l) => / up --build -d/.test(l)); expect(up).toBeGreaterThan(-1); expect(r.log.findIndex((l) => l.startsWith('docker inspect'))).toBeGreaterThan(up);