From 4b6dddc911bafe2ffac42abeae8dee4c4a9f7a2b Mon Sep 17 00:00:00 2001 From: skulidropek <66840575+skulidropek@users.noreply.github.com> Date: Wed, 6 May 2026 20:32:32 +0000 Subject: [PATCH] fix(api): isolate docker runtime from host socket --- README.md | 2 +- docker-compose.api.yml | 13 +- docker-compose.yml | 13 +- packages/api/Dockerfile | 4 +- packages/api/README.md | 9 +- packages/api/scripts/start-controller.sh | 68 +++ .../lib/core/templates-entrypoint/agent.ts | 1 + .../src/lib/core/templates-entrypoint/base.ts | 15 +- .../src/lib/core/templates/docker-compose.ts | 7 +- .../src/core/templates-entrypoint/agent.ts | 1 + .../lib/src/core/templates-entrypoint/base.ts | 15 +- .../lib/src/core/templates/docker-compose.ts | 7 +- packages/lib/src/shell/docker-compose.ts | 135 +++++ packages/lib/src/shell/docker-network.ts | 86 +++ packages/lib/src/shell/docker-runtime.ts | 100 ++++ packages/lib/src/shell/docker.ts | 530 +----------------- packages/lib/src/usecases/projects-up.ts | 11 +- packages/lib/tests/core/templates.test.ts | 15 + .../lib/tests/shell/docker-access.test.ts | 33 ++ .../usecases/state-repo-auto-pull.test.ts | 5 +- .../tests/usecases/state-repo-init.test.ts | 5 +- scripts/e2e/_lib.sh | 252 ++++++++- scripts/e2e/clone-auto-open-ssh.sh | 34 +- scripts/e2e/clone-cache.sh | 31 +- scripts/e2e/login-context.sh | 20 +- scripts/e2e/opencode-autoconnect.sh | 35 +- scripts/e2e/runtime-volumes-ssh.sh | 79 ++- 27 files changed, 890 insertions(+), 636 deletions(-) create mode 100644 packages/api/scripts/start-controller.sh create mode 100644 packages/lib/src/shell/docker-compose.ts create mode 100644 packages/lib/src/shell/docker-network.ts create mode 100644 packages/lib/src/shell/docker-runtime.ts diff --git a/README.md b/README.md index 8fcce950..2de7b1ca 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ docker-git apply-all docker-git apply-all --active ``` -- `apply` применяет конфиг к одному проекту. `--no-up` только обновляет файлы без `docker compose up`. В текущем API-only host mode команда ещё недоступна. +- `apply` применяет конфиг к одному проекту. `--no-up` только обновляет файлы без `docker compose up`. - `apply-all` применяет конфиг ко всем проектам. `--active` только к запущенным контейнерам. diff --git a/docker-compose.api.yml b/docker-compose.api.yml index fc916e02..bee3eb1b 100644 --- a/docker-compose.api.yml +++ b/docker-compose.api.yml @@ -8,6 +8,12 @@ services: container_name: ${DOCKER_GIT_API_CONTAINER_NAME:-docker-git-api} environment: DOCKER_GIT_API_PORT: ${DOCKER_GIT_API_PORT:-3334} + DOCKER_GIT_DOCKER_RUNTIME: ${DOCKER_GIT_DOCKER_RUNTIME:-isolated} + DOCKER_HOST: ${DOCKER_GIT_CONTROLLER_DOCKER_HOST:-unix:///var/run/docker.sock} + DOCKER_GIT_DOCKERD_TCP_HOST: ${DOCKER_GIT_DOCKERD_TCP_HOST:-tcp://0.0.0.0:2375} + DOCKER_GIT_DOCKERD_DEFAULT_CGROUPNS_MODE: ${DOCKER_GIT_DOCKERD_DEFAULT_CGROUPNS_MODE:-host} + DOCKER_GIT_PROJECT_DOCKER_HOST: ${DOCKER_GIT_PROJECT_DOCKER_HOST:-tcp://host.docker.internal:2375} + DOCKER_GIT_PROJECT_SSH_BIND_HOST: ${DOCKER_GIT_PROJECT_SSH_BIND_HOST:-0.0.0.0} DOCKER_GIT_PROJECTS_ROOT: ${DOCKER_GIT_PROJECTS_ROOT:-/home/dev/.docker-git} DOCKER_GIT_PROJECTS_ROOT_VOLUME: ${DOCKER_GIT_PROJECTS_ROOT_VOLUME:-docker-git-projects} DOCKER_GIT_FEDERATION_PUBLIC_ORIGIN: ${DOCKER_GIT_FEDERATION_PUBLIC_ORIGIN:-} @@ -25,10 +31,15 @@ services: - 8.8.4.4 - 1.1.1.1 volumes: - - /var/run/docker.sock:/var/run/docker.sock - docker_git_projects:${DOCKER_GIT_PROJECTS_ROOT:-/home/dev/.docker-git} + - docker_git_docker_data:/var/lib/docker + privileged: true + cgroup: host + init: true restart: unless-stopped volumes: docker_git_projects: name: ${DOCKER_GIT_PROJECTS_ROOT_VOLUME:-docker-git-projects} + docker_git_docker_data: + name: ${DOCKER_GIT_DOCKER_DATA_VOLUME:-docker-git-docker-data} diff --git a/docker-compose.yml b/docker-compose.yml index 49ea2c02..e955aff2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,12 @@ services: environment: DOCKER_GIT_API_PORT: ${DOCKER_GIT_API_PORT:-3334} DOCKER_GIT_CONTROLLER_REV: ${DOCKER_GIT_CONTROLLER_REV:-unknown} + DOCKER_GIT_DOCKER_RUNTIME: ${DOCKER_GIT_DOCKER_RUNTIME:-isolated} + DOCKER_HOST: ${DOCKER_GIT_CONTROLLER_DOCKER_HOST:-unix:///var/run/docker.sock} + DOCKER_GIT_DOCKERD_TCP_HOST: ${DOCKER_GIT_DOCKERD_TCP_HOST:-tcp://0.0.0.0:2375} + DOCKER_GIT_DOCKERD_DEFAULT_CGROUPNS_MODE: ${DOCKER_GIT_DOCKERD_DEFAULT_CGROUPNS_MODE:-host} + DOCKER_GIT_PROJECT_DOCKER_HOST: ${DOCKER_GIT_PROJECT_DOCKER_HOST:-tcp://host.docker.internal:2375} + DOCKER_GIT_PROJECT_SSH_BIND_HOST: ${DOCKER_GIT_PROJECT_SSH_BIND_HOST:-0.0.0.0} DOCKER_GIT_PROJECTS_ROOT: ${DOCKER_GIT_PROJECTS_ROOT:-/home/dev/.docker-git} DOCKER_GIT_PROJECTS_ROOT_VOLUME: ${DOCKER_GIT_PROJECTS_ROOT_VOLUME:-docker-git-projects} DOCKER_GIT_FEDERATION_PUBLIC_ORIGIN: ${DOCKER_GIT_FEDERATION_PUBLIC_ORIGIN:-} @@ -27,10 +33,15 @@ services: - 8.8.4.4 - 1.1.1.1 volumes: - - /var/run/docker.sock:/var/run/docker.sock - docker_git_projects:${DOCKER_GIT_PROJECTS_ROOT:-/home/dev/.docker-git} + - docker_git_docker_data:/var/lib/docker + privileged: true + cgroup: host + init: true restart: unless-stopped volumes: docker_git_projects: name: ${DOCKER_GIT_PROJECTS_ROOT_VOLUME:-docker-git-projects} + docker_git_docker_data: + name: ${DOCKER_GIT_DOCKER_DATA_VOLUME:-docker-git-docker-data} diff --git a/packages/api/Dockerfile b/packages/api/Dockerfile index 5637365f..296348f3 100644 --- a/packages/api/Dockerfile +++ b/packages/api/Dockerfile @@ -59,6 +59,8 @@ RUN bun run --cwd packages/lib build RUN bun run --cwd packages/api build ENV DOCKER_GIT_API_PORT=3334 +ENV DOCKER_GIT_DOCKER_RUNTIME=isolated +ENV DOCKER_HOST=unix:///var/run/docker.sock EXPOSE 3334 -CMD ["bun", "packages/api/dist/src/main.js"] +CMD ["bash", "packages/api/scripts/start-controller.sh"] diff --git a/packages/api/README.md b/packages/api/README.md index c40f5092..08b35973 100644 --- a/packages/api/README.md +++ b/packages/api/README.md @@ -5,8 +5,9 @@ HTTP API for docker-git orchestration (projects, agents, logs/events, federation This is now the intended controller plane: - the API runs inside `docker-git-api` - `.docker-git` state lives in the Docker volume `docker-git-projects` -- the API talks to Docker through `/var/run/docker.sock` +- the API starts an isolated Docker daemon inside the controller by default - child project containers no longer depend on host bind mounts for bootstrap auth/env +- the host `/var/run/docker.sock` is not mounted into the controller or project containers ## UI wrapper @@ -41,6 +42,12 @@ Optional env: - `DOCKER_GIT_API_BIND_HOST` (default: `127.0.0.1`) - `DOCKER_GIT_API_PORT` (default: `3334`) +- `DOCKER_GIT_DOCKER_RUNTIME` (default: `isolated`; starts a managed Docker daemon in `docker-git-api`) +- `DOCKER_GIT_CONTROLLER_DOCKER_HOST` (default: `unix:///var/run/docker.sock`; socket path inside the controller) +- `DOCKER_GIT_DOCKERD_TCP_HOST` (default: `tcp://0.0.0.0:2375`; reachable only inside Docker networks unless explicitly published) +- `DOCKER_GIT_DOCKERD_DEFAULT_CGROUPNS_MODE` (default: `host`; keeps nested project containers compatible with cgroup v2 DinD) +- `DOCKER_GIT_PROJECT_DOCKER_HOST` (default: `tcp://host.docker.internal:2375`; lets project containers use the isolated daemon) +- `DOCKER_GIT_PROJECT_SSH_BIND_HOST` (default: `0.0.0.0` in controller mode; project SSH binds inside the isolated controller runtime) - `DOCKER_GIT_PROJECTS_ROOT` (container path, default: `/home/dev/.docker-git`) - `DOCKER_GIT_PROJECTS_ROOT_VOLUME` (Docker volume name for controller state, default: `docker-git-projects`) - `DOCKER_GIT_FEDERATION_PUBLIC_ORIGIN` (optional public ActivityPub origin) diff --git a/packages/api/scripts/start-controller.sh b/packages/api/scripts/start-controller.sh new file mode 100644 index 00000000..5e2b2884 --- /dev/null +++ b/packages/api/scripts/start-controller.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +set -euo pipefail + +runtime="${DOCKER_GIT_DOCKER_RUNTIME:-isolated}" +docker_host="${DOCKER_HOST:-unix:///var/run/docker.sock}" +dockerd_pid="" + +cleanup() { + if [[ -n "$dockerd_pid" ]] && kill -0 "$dockerd_pid" >/dev/null 2>&1; then + kill "$dockerd_pid" >/dev/null 2>&1 || true + wait "$dockerd_pid" >/dev/null 2>&1 || true + fi +} + +trap cleanup EXIT INT TERM + +if [[ "$runtime" == "isolated" ]]; then + if [[ "$docker_host" != unix://* ]]; then + echo "DOCKER_GIT_DOCKER_RUNTIME=isolated requires a unix:// DOCKER_HOST for the managed controller daemon." >&2 + exit 1 + fi + + export DOCKER_HOST="$docker_host" + + socket_path="${docker_host#unix://}" + data_root="${DOCKER_GIT_DOCKER_DATA_ROOT:-/var/lib/docker}" + log_path="${DOCKER_GIT_DOCKERD_LOG:-/var/log/docker-git/dockerd.log}" + tcp_host="${DOCKER_GIT_DOCKERD_TCP_HOST:-tcp://0.0.0.0:2375}" + default_cgroupns_mode="${DOCKER_GIT_DOCKERD_DEFAULT_CGROUPNS_MODE:-host}" + + mkdir -p "$(dirname "$socket_path")" "$data_root" "$(dirname "$log_path")" + + if ! docker info >/dev/null 2>&1; then + # shellcheck disable=SC2086 + dockerd \ + --host="$docker_host" \ + --host="$tcp_host" \ + --tls=false \ + --data-root="$data_root" \ + --exec-opt native.cgroupdriver=cgroupfs \ + --default-cgroupns-mode="$default_cgroupns_mode" \ + ${DOCKER_GIT_DOCKERD_ARGS:-} \ + >"$log_path" 2>&1 & + dockerd_pid="$!" + + for _ in $(seq 1 90); do + if docker info >/dev/null 2>&1; then + break + fi + if ! kill -0 "$dockerd_pid" >/dev/null 2>&1; then + echo "Managed Docker daemon exited during startup." >&2 + tail -n 200 "$log_path" >&2 || true + exit 1 + fi + sleep 1 + done + + if ! docker info >/dev/null 2>&1; then + echo "Managed Docker daemon did not become ready in time." >&2 + tail -n 200 "$log_path" >&2 || true + exit 1 + fi + fi +fi + +bun packages/api/dist/src/main.js & +api_pid="$!" +wait "$api_pid" diff --git a/packages/app/src/lib/core/templates-entrypoint/agent.ts b/packages/app/src/lib/core/templates-entrypoint/agent.ts index 6a35f4ca..d7517f3f 100644 --- a/packages/app/src/lib/core/templates-entrypoint/agent.ts +++ b/packages/app/src/lib/core/templates-entrypoint/agent.ts @@ -37,6 +37,7 @@ rm -f "$AGENT_DONE_PATH" "$AGENT_FAIL_PATH" "$AGENT_PROMPT_FILE"`, String.raw`# Collect tokens for agent environment (su - dev does not always inherit profile.d) AGENT_ENV_FILE="/run/docker-git/agent-env.sh" { + [[ -f /etc/profile.d/docker-host.sh ]] && cat /etc/profile.d/docker-host.sh [[ -f /etc/profile.d/gh-token.sh ]] && cat /etc/profile.d/gh-token.sh [[ -f /etc/profile.d/claude-config.sh ]] && cat /etc/profile.d/claude-config.sh [[ -f /etc/profile.d/gemini-config.sh ]] && cat /etc/profile.d/gemini-config.sh diff --git a/packages/app/src/lib/core/templates-entrypoint/base.ts b/packages/app/src/lib/core/templates-entrypoint/base.ts index 9709a551..3e7d6f17 100644 --- a/packages/app/src/lib/core/templates-entrypoint/base.ts +++ b/packages/app/src/lib/core/templates-entrypoint/base.ts @@ -91,8 +91,16 @@ fi chown -R 1000:1000 /home/${config.sshUser}/.ssh` export const renderEntrypointDockerSocket = (config: TemplateConfig): string => - `# Ensure docker socket access for ${config.sshUser} -if [[ -S /var/run/docker.sock ]]; then + `# Ensure Docker CLI targets only the explicitly configured daemon. +if [[ -n "\${DOCKER_GIT_PROJECT_DOCKER_HOST:-}" && -z "\${DOCKER_HOST:-}" ]]; then + DOCKER_HOST="$DOCKER_GIT_PROJECT_DOCKER_HOST" + export DOCKER_HOST +fi + +if [[ -n "\${DOCKER_HOST:-}" ]]; then + printf "export DOCKER_HOST=%q\\n" "$DOCKER_HOST" > /etc/profile.d/docker-host.sh + docker_git_upsert_ssh_env "DOCKER_HOST" "$DOCKER_HOST" +elif [[ -S /var/run/docker.sock ]]; then DOCKER_SOCK_GID="$(stat -c "%g" /var/run/docker.sock)" DOCKER_GROUP="$(getent group "$DOCKER_SOCK_GID" | cut -d: -f1 || true)" if [[ -z "$DOCKER_GROUP" ]]; then @@ -100,7 +108,8 @@ if [[ -S /var/run/docker.sock ]]; then groupadd -g "$DOCKER_SOCK_GID" "$DOCKER_GROUP" || true fi usermod -aG "$DOCKER_GROUP" ${config.sshUser} || true - printf "export DOCKER_HOST=unix:///var/run/docker.sock\n" > /etc/profile.d/docker-host.sh + printf "export DOCKER_HOST=unix:///var/run/docker.sock\\n" > /etc/profile.d/docker-host.sh + docker_git_upsert_ssh_env "DOCKER_HOST" "unix:///var/run/docker.sock" fi` export const renderEntrypointZshShell = (config: TemplateConfig): string => diff --git a/packages/app/src/lib/core/templates/docker-compose.ts b/packages/app/src/lib/core/templates/docker-compose.ts index 5714c42d..737fa909 100644 --- a/packages/app/src/lib/core/templates/docker-compose.ts +++ b/packages/app/src/lib/core/templates/docker-compose.ts @@ -164,17 +164,20 @@ ${fragments.maybeGithubAuthSkipEnv} # Optional anonymous public GitHub clon ${fragments.maybeGitTokenLabelEnv} # Optional token label selector (maps to GITHUB_TOKEN__