Skip to content

feat(docker): guided first run for the Compose deployment - #573

Open
Ark0N wants to merge 1 commit into
masterfrom
feat/docker-easy-install
Open

Ark0N wants to merge 1 commit into
masterfrom
feat/docker-easy-install

Conversation

@Ark0N

@Ark0N Ark0N commented Oct 9, 2026

Copy link
Copy Markdown
Owner

What this is

Codeman ships a Docker Compose deployment in docker/: the server runs in a container and starts Docker cases as sibling containers through the host's Docker socket. Until now a new user had to know to clone the repo first, copy docker/.env.example to docker/.env by hand, and replace three values in it: an Unraid data path (/mnt/user/appdata/codeman), an Australia/Perth time zone and the password changeme. Then they ran docker/Start-Codeman.sh and had to guess when the server was ready. A missing Docker, an old Compose or a socket permission problem showed up as a raw Compose error.

This PR turns that into two commands from a fresh clone:

git clone https://github.com/Ark0N/Codeman.git && cd Codeman
bash docker/Start-Codeman.sh

Not touched: docker-compose.yaml and server.Dockerfile. Neither is edited and .env.example gains no new key (only values and comments change). All three feed the in-app updater's environment gate, so changing them would make every existing Docker install's next in-app update refuse until its owner reruns the script on the host. There are no server, API or frontend changes.

What Start-Codeman.sh does now

  1. Preflight. Before anything else it checks that the docker command exists, that the Compose v2 plugin exists and is at least 2.27.2, and that the daemon answers.

    • The version floor is real: the script has always read its settings with docker compose config --environment, which first shipped in Compose 2.27.2 (config --environment docker/compose#11891). On older versions it used to die on unknown flag.
    • A permission error on the socket points at sudo usermod -aG docker $USER, not at sudo.
  2. First-run setup, only when docker/.env does not exist. It asks three questions, and Enter takes each default:

    • Data folder: ~/codeman-docker.
    • Port: 3000, or the next free port.
    • Password: a generated 24-character alphanumeric one, printed once.

    It writes docker/.env by rewriting five KEY= lines of .env.example (time zone from the host, data path, cases path, port, password). Every key the example sets is therefore present, which is the check diffRequiredEnvKeys runs before an update. The file is mode 0600, and values containing characters Compose would interpolate are single-quoted.

    It refuses three locations for the data folder:

    • $HOME and ~/.codeman: the data folder becomes the container's home, so its .codeman would share state.json with a native install.
    • Anything inside the checkout: that is the image build context, so CLI logins would be copied into the image.

    Other behaviour:

    • --yes, or no terminal attached, takes the defaults.
    • CODEMAN_APPDATA_PATH, CODEMAN_PORT and CODEMAN_PASSWORD in the environment preset them.
    • --setup-only writes the file and stops.
    • It never runs as root (Unraid keeps the documented hand-written .env route) and never edits an existing .env.
  3. Wait and summary. After up -d it sends the compose healthcheck's own /api/status request through docker exec every 2 s. The healthcheck itself first runs only after its 30 s interval.

    • It fails fast on an exited, restarting or unhealthy container, or a moving restart count, and prints the last 40 log lines.
    • It gives up after 180 s.
    • On success it prints the localhost URL, the LAN URL, the username, where the password lives (plus the generated password on a first run), and the logs, stop and update commands.
    • --no-wait skips the wait.
  4. One settings read. Settings come from a single config --environment call whose failure is reported, instead of three unchecked ones.

  5. changeme is refused before anything is built or started, because the container publishes on every interface and holds the Docker socket. Update-Codeman.sh runs the same check before its build and down, so it can never leave the stack stopped.

The README, docker/README.md, docs/docker-compose.md and the wiki's Installation page now lead with the clone-and-run flow. The manual .env route stays documented for Unraid and PowerShell.

Behaviour changes for existing installs

  • Both scripts refuse to run while the password is changeme. An empty password only gets a warning.
  • Start-Codeman.sh no longer returns right after up -d. It waits until the server answers (up to 180 s) and exits 1 on a crash loop or timeout. Scripts that want the old behaviour can pass --no-wait.
  • Unknown arguments now exit 1; they used to be ignored.
  • An existing docker/.env is never prompted for or rewritten.

Reviewing the diff

Start-Codeman.sh grows from 309 to 920 lines, and most of the growth is user-facing text: printf lines go from 28 to 153.

Lines What
1-58 Header comment and argument parsing (new)
59-132 Preflight (new)
133-439 First-run setup (new; runs only without docker/.env)
440-677 The existing flow. Changed only at the single config --environment read plus the changeme check, a clearer mkdir error and a DOCKER_SOCKET hint
678-819 Wait, summary and compose_failed (new)
820-920 Existing volume refresh. The two exec ... up lines became up followed by report_when_ready

The override-file block and owner_of() are unchanged; test/docker-entrypoint.test.ts compares both byte for byte against Update-Codeman.sh. Update-Codeman.sh gains only the changeme check (16 lines).

Testing

  • New tests: test/docker-start-setup.test.ts has 39 tests that run the real scripts against a stub docker, with stdin not a terminal. They cover:

    • each preflight message;
    • a generated .env that passes diffRequiredEnvKeys and matches the example line for line except the five keys;
    • mode 0600;
    • every refusal;
    • an existing .env left byte-identical;
    • changeme refused by both scripts with nothing built or stopped.

    With a real Unix socket they also run the whole start, wait and summary path, plus a crash loop, a failed up and --no-wait.

  • Test runs: all Docker test files pass (93 tests). The full gate in the worktree had 10,373 passing and one failure, frontend-public-tooling, which needs generated spreadsheet assets that a worktree without npm install lacks. It passes in the main checkout.

  • bash 3.2 (the bash:3.2 image, the version macOS ships as /bin/bash): syntax, the setup flow, quoting and the helper functions.

  • Real Docker (Compose 5.5.0):

    • an interactive first run driven through a pty;
    • a rerun on the existing install;
    • a crash-looping container;
    • the Update-Codeman.sh handoff.

    Real Compose reads the quoted .env values ('pa$$ #w0rd!', a path with a space) literally.

Not verified

  • The real Codeman image through the new flow. On the test machine npm ci fails inside every container with ERR_SSL_CIPHER_OPERATION_FAILED, on the Node 22.22 and 22.23 images, with bridge and host networking, while native npm ci works. So the real-Docker runs used a stand-in image through a gitignored override: build: !reset null plus a tiny Node server on the same port. The script's Docker calls are identical either way; the real image's own startup was not exercised.
  • macOS and Docker Desktop: not tested, so the docs keep saying Linux.

Risk

  • The 180 s wait, then exit 1: the image is already built when the wait starts, so it only covers server startup. A very slow host could still get exit 1 for a container that comes up later. The message says it may still be starting and points at the logs.
  • Refusing changeme on existing installs is deliberate: a known password on a published port that holds the Docker socket is host root. Anyone who kept the example password now has to change it before the next start.

A new Docker install was: clone (undocumented), copy .env.example by hand,
fix an Unraid data path and a Perth time zone, replace `changeme`, run the
start script, and then guess when the server was up. Start-Codeman.sh now
does all of it from a fresh clone:

- Preflight names the fix for a missing docker CLI, a missing or too-old
  Compose plugin (config --environment needs 2.27.2, docker/compose#11891),
  and an unreachable daemon (docker group, never sudo).
- With no docker/.env, it asks three questions (data folder, port,
  password; Enter takes each default: ~/codeman-docker, 3000 or the next
  free port, a generated password) and writes docker/.env FROM the example,
  so every key the updater's diffRequiredEnvKeys expects is present. Mode
  0600, host time zone, values Compose would interpolate single-quoted.
  Refuses $HOME, ~/.codeman (a native install's state dir) and anything
  inside the checkout (the image build context). --yes / no TTY take the
  defaults, --setup-only stops after writing the file. An existing .env is
  never edited, and root never runs the setup (Unraid keeps the hand route).
- After `up`, it waits until the server answers (docker exec probe, crash
  loop caught by the restart count) and prints the URL, the LAN URL, the
  generated password and the logs/stop commands; --no-wait skips the wait.
- `changeme` is refused before anything starts (the container publishes on
  every interface and holds the Docker socket); Update-Codeman.sh checks it
  before its build and down so the stack is never left stopped.
- Compose settings are read with ONE config --environment call, with its
  error reported, instead of three silent ones.

docker-compose.yaml and server.Dockerfile are untouched (their hashes gate
the in-app updater), and .env.example changes values and comments only.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants