-
Notifications
You must be signed in to change notification settings - Fork 6
Description
› Что не так? Можешь разобраться?
meadav_david@meadav:~/docker-git$ pnpm run docker-git clone
https://github.com/TelegramGPT/openclaw_autodeployer --force --mcp-play
wright
effect-template-workspace@1.0.0 docker-git /home/meadav_david/docker-
git
pnpm --filter ./packages/app build:docker-git && node packages/app/di
st/src/docker-git/main.js clone
https://github.com/TelegramGPT/openclaw_autodeployer --force --mcp-play
wright
@prover-coder-ai/docker-git@1.0.50 prebuild:docker-git /home/
meadav_david/docker-git/packages/app
pnpm -C ../lib build
@effect-template/lib@1.0.0 build /home/meadav_david/docker-git/
packages/lib
tsc -p tsconfig.json
@prover-coder-ai/docker-git@1.0.50 build:docker-git /home/
meadav_david/docker-git/packages/app
vite build --config vite.docker-git.config.ts
vite v7.3.1 building ssr environment for production...
✓ 161 modules transformed.
dist/src/docker-git/main.js 505.83 kB │ map: 1,125.24 kB
✓ built in 844ms
[09:29:24.869] INFO (#1): Synced env file from /home/
meadav_david/.docker-git/.orch/env/global.env to /home/
meadav_david/.docker-git/telegramgpt/openclaw_autodeployer/.orch/env/
global.env
[09:29:24.872] INFO (#1): Synced GitHub auth keys from /home/
meadav_david/.docker-git/.orch/env/global.env to /home/
meadav_david/.docker-git/telegramgpt/openclaw_autodeployer/.orch/env/
global.env
[09:29:24.873] INFO (#1): Copied Codex config from /home/
meadav_david/.docker-git/.orch/auth/codex/config.toml to /home/
meadav_david/.docker-git/telegramgpt/openclaw_autodeployer/.orch/auth/
codex/config.toml
[09:29:24.874] INFO (#1): Created docker-git project in /home/
meadav_david/.docker-git/telegramgpt/openclaw_autodeployer
[09:29:24.874] INFO (#1): - /home/meadav_david/.docker-git/
telegramgpt/openclaw_autodeployer/Dockerfile
[09:29:24.874] INFO (#1): - /home/meadav_david/.docker-git/
telegramgpt/openclaw_autodeployer/entrypoint.sh
[09:29:24.874] INFO (#1): - /home/meadav_david/.docker-git/
telegramgpt/openclaw_autodeployer/docker-compose.yml
[09:29:24.875] INFO (#1): - /home/meadav_david/.docker-git/
telegramgpt/openclaw_autodeployer/.dockerignore
[09:29:24.875] INFO (#1): - /home/meadav_david/.docker-git/
telegramgpt/openclaw_autodeployer/docker-git.json
[09:29:24.875] INFO (#1): - /home/meadav_david/.docker-git/
telegramgpt/openclaw_autodeployer/.gitignore
[09:29:24.876] INFO (#1): - /home/meadav_david/.docker-git/
telegramgpt/openclaw_autodeployer/Dockerfile.browser
[09:29:24.876] INFO (#1): - /home/meadav_david/.docker-git/
telegramgpt/openclaw_autodeployer/mcp-playwright-start-extra.sh
[09:29:24.895] INFO (#1): Force enabled: wiping docker compose volumes
(docker compose down -v)...
Container dg-openclaw_autodeployer Stopping
Container dg-openclaw_autodeployer Stopped
Container dg-openclaw_autodeployer Removing
Container dg-openclaw_autodeployer Removed
Container dg-openclaw_autodeployer-browser Stopping
Container dg-openclaw_autodeployer-browser Stopped
Container dg-openclaw_autodeployer-browser Removing
Container dg-openclaw_autodeployer-browser Removed
Volume openclaw_autodeployer_dg-openclaw_autodeployer-home-browser
Removing
Volume openclaw_autodeployer_dg-openclaw_autodeployer-home Removing
Volume openclaw_autodeployer_dg-openclaw_autodeployer-home-browser
Removed
Volume openclaw_autodeployer_dg-openclaw_autodeployer-home Removed
[09:29:35.670] INFO (#1): Running: docker compose up -d --build
WARN[0000] Docker Compose is configured to build using Bake, but buildx
isn't installed
#0 building with "default" instance using docker driver
#1 [dg-openclaw_autodeployer-browser internal] load build definition
from Dockerfile.browser
#1 transferring dockerfile: 692B done
#1 DONE 0.0s
#2 [dg-openclaw_autodeployer-browser internal] load metadata for
docker.io/kechangdev/browser-vnc:latest
#2 DONE 1.0s
#3 [dg-openclaw_autodeployer-browser internal] load .dockerignore
#3 transferring context: 90B done
#3 DONE 0.0s
#4 [dg-openclaw_autodeployer-browser 1/4] FROM
docker.io/kechangdev/browser-vnc:latest@sha256:d41cc2e499b5924f743109abc3c82f5590fe2da4ff93858065f223a0770a46e2
#4 DONE 0.0s
#5 [dg-openclaw_autodeployer-browser internal] load build context
#5 transferring context: 724B done
#5 DONE 0.0s
#6 [dg-openclaw_autodeployer-browser 2/4] RUN apk add --no-cache bash
procps socat python3 net-tools
#6 CACHED
#7 [dg-openclaw_autodeployer-browser 3/4] COPY mcp-playwright-start-
extra.sh /usr/local/bin/mcp-playwright-start-extra.sh
#7 CACHED
#8 [dg-openclaw_autodeployer-browser 4/4] RUN chmod +x /usr/local/bin/
mcp-playwright-start-extra.sh
#8 CACHED
#9 [dg-openclaw_autodeployer-browser] exporting to image
#9 exporting layers done
#9 writing image
sha256:35e016aa0f193a1059efbc2b3442e3a643bb9be58cf2fc26bdb9a45be9cf7c4f
0.0s done
#9 naming to
docker.io/library/openclaw_autodeployer-dg-openclaw_autodeployer-browser
done
#9 DONE 0.0s
#10 [dg-openclaw_autodeployer-browser] resolving provenance for metadata
file
#10 DONE 0.0s
#11 [dg-openclaw_autodeployer internal] load build definition from
Dockerfile
#11 transferring dockerfile: 15.23kB done
#11 DONE 0.0s
#12 [dg-openclaw_autodeployer internal] load metadata for
docker.io/library/ubuntu:24.04
#12 DONE 0.0s
#13 [dg-openclaw_autodeployer internal] load .dockerignore
#13 transferring context: 90B done
#13 DONE 0.0s
#14 [dg-openclaw_autodeployer 1/44] FROM docker.io/library/ubuntu:24.04
#14 DONE 0.0s
#15 [dg-openclaw_autodeployer 37/44] RUN if id -u ubuntu >/dev/null
2>&1; then if getent group 1000 >/dev/null 2>&1; then
EXISTING_GROUP="$(getent group 1000 | cut -d: -f1)"; if
[ "$EXISTING_GROUP" != "dev" ]; then groupmod -n dev "$EXISTING_GROUP"
|| true; fi; fi; usermod -l dev -d /home/dev -m -s /usr/bin/
zsh ubuntu || true; fi
#15 CACHED
#16 [dg-openclaw_autodeployer 12/44] RUN printf "%s\n" "if [ -f /etc/
profile.d/zz-bash-history.sh ]; then . /etc/profile.d/zz-bash-
history.sh; fi" >> /etc/bash.bashrc
#16 CACHED
#17 [dg-openclaw_autodeployer 7/44] RUN cat <<'EOF' > /etc/profile.d/
zz-bash-completion.sh
#17 CACHED
#18 [dg-openclaw_autodeployer 17/44] RUN printf "export NVM_DIR=/usr/
local/nvm\n[ -s /usr/local/nvm/nvm.sh ] && . /usr/local/nvm/nvm.sh\n"
/etc/profile.d/nvm.sh && chmod 0644 /etc/profile.d/nvm.sh
#18 CACHED
#19 [dg-openclaw_autodeployer 11/44] RUN chmod 0644 /etc/profile.d/zz-
bash-history.sh
#19 CACHED
#20 [dg-openclaw_autodeployer 40/44] RUN mkdir -p /run/sshd
#20 CACHED
#21 [dg-openclaw_autodeployer 2/44] RUN apt-get update && apt-get
install -y --no-install-recommends openssh-server git gh ca-
certificates curl unzip bsdutils sudo make docker.io docker-compose-
v2 bash-completion zsh zsh-autosuggestions xauth ncurses-term && rm
-rf /var/lib/apt/lists/*
#21 CACHED
#22 [dg-openclaw_autodeployer 16/44] RUN mkdir -p /usr/local/nvm && c
url -fsSL
https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
#22 CACHED
#23 [dg-openclaw_autodeployer 8/44] RUN chmod 0644 /etc/profile.d/zz-
bash-completion.sh
#23 CACHED
#24 [dg-openclaw_autodeployer 24/44] RUN oh-my-opencode --version
#24 CACHED
#25 [dg-openclaw_autodeployer 14/44] RUN cat <<'EOF' > /etc/zsh/zshrc
#25 CACHED
#26 [dg-openclaw_autodeployer 39/44] RUN printf "%s\n" "dev ALL=(ALL)
NOPASSWD:ALL" > /etc/sudoers.d/dev && chmod 0440 /etc/sudoers.d/dev
#26 CACHED
#27 [dg-openclaw_autodeployer 6/44] RUN printf "%s\n" "if [ -f /etc/
profile.d/zz-prompt.sh ]; then . /etc/profile.d/zz-prompt.sh; fi" >> /
etc/bash.bashrc
#27 CACHED
#28 [dg-openclaw_autodeployer 30/44] RUN cat <<'EOF' > /usr/local/bin/
docker-git-playwright-mcp
#28 CACHED
#29 [dg-openclaw_autodeployer 27/44] RUN npm install -g @google/gemini-
cli@latest --force
#29 CACHED
#30 [dg-openclaw_autodeployer 38/44] RUN if id -u dev >/dev/null 2>&1;
then usermod -u 1000 -g 1000 -o dev; else groupadd -g
1000 dev || true; useradd -m -s /usr/bin/zsh -u 1000 -g 1000 -o
dev; fi
#30 CACHED
#31 [dg-openclaw_autodeployer 26/44] RUN claude --version
#31 CACHED
#32 [dg-openclaw_autodeployer 31/44] RUN chmod +x /usr/local/bin/docker-
git-playwright-mcp
#32 CACHED
#33 [dg-openclaw_autodeployer 5/44] RUN chmod 0644 /etc/profile.d/zz-
prompt.sh
#33 CACHED
#34 [dg-openclaw_autodeployer 21/44] RUN BUN_INSTALL=/usr/local/bun
script -q -e -c "bun add -g @openai/codex@latest" /dev/null
#34 CACHED
#35 [dg-openclaw_autodeployer 32/44] RUN printf "export PATH=/usr/local/
bun/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n"
/etc/profile.d/bun.sh && chmod 0644 /etc/profile.d/bun.sh
#35 CACHED
#36 [dg-openclaw_autodeployer 9/44] RUN printf "%s\n" "if [ -f /etc/
profile.d/zz-bash-completion.sh ]; then . /etc/profile.d/zz-bash-
completion.sh; fi" >> /etc/bash.bashrc
#36 CACHED
#37 [dg-openclaw_autodeployer 19/44] RUN set -eu; for attempt in 1 2 3
4 5; do if curl -fsSL --retry 5 --retry-all-errors --retry-delay 2
https://bun.sh/install -o /tmp/bun-install.sh && BUN_INSTALL=/usr
/local/bun bash /tmp/bun-install.sh; then rm -f /tmp/bun-install.
sh; exit 0; fi; echo "bun install attempt ${attempt} fail
ed; retrying..." >&2; rm -f /tmp/bun-install.sh; sleep $((att
empt * 2)); done; echo "bun install failed after retries" >&2; ex
it 1
#37 CACHED
#38 [dg-openclaw_autodeployer 18/44] RUN corepack enable && corepack
prepare pnpm@10.27.0 --activate
#38 CACHED
#39 [dg-openclaw_autodeployer 15/44] RUN curl -fsSL
https://deb.nodesource.com/setup_24.x | bash - && apt-get install -y -
-no-install-recommends nodejs && node -v && npm -v && corepack --
version && rm -rf /var/lib/apt/lists/*
#39 CACHED
#40 [dg-openclaw_autodeployer 22/44] RUN ln -sf /usr/local/bun/bin/
codex /usr/local/bin/codex
#40 CACHED
#41 [dg-openclaw_autodeployer 20/44] RUN ln -sf /usr/local/bun/bin/bun /
usr/local/bin/bun
#41 CACHED
#42 [dg-openclaw_autodeployer 13/44] RUN mkdir -p /etc/zsh
#42 CACHED
#43 [dg-openclaw_autodeployer 23/44] RUN set -eu; ARCH="$(uname -m)";
case "$ARCH" in x86_64|amd64) OH_MY_OPENCODE_ARCH="x64" ;;
aarch64|arm64) OH_MY_OPENCODE_ARCH="arm64" ;; *) echo "Unsupported
arch for oh-my-opencode: $ARCH" >&2; exit 1 ;; esac; npm install -g
oh-my-opencode@latest "oh-my-opencode-linux-${OH_MY_OPENCODE_ARCH}
@latest"
#43 CACHED
#44 [dg-openclaw_autodeployer 36/44] RUN ARCH="$(uname -m)" && case "
$ARCH" in x86_64|amd64) GITLEAKS_ARCH="x64" ;; aarch64|arm6
4) GITLEAKS_ARCH="arm64" ;; *) echo "Unsupported arch for gitleak
s: $ARCH" >&2; exit 1 ;; esac && curl -fsSL
"https://github.com/gitleaks/gitleaks/releases/download/v8.28.0/gitleaks_8.28.0_linux_$GITLEAKS_ARCH.tar.gz"
| tar -xz -C /usr/local/bin gitleaks && chmod +x /usr/local/bin/gitle
aks && gitleaks version
#44 CACHED
#45 [dg-openclaw_autodeployer 3/44] RUN printf "%s\n" "ALL ALL=(ALL)
NOPASSWD:ALL" > /etc/sudoers.d/zz-all && chmod 0440 /etc/sudoers.d/zz-
all
#45 CACHED
#46 [dg-openclaw_autodeployer 34/44] RUN ln -sf /usr/local/.opencode/
bin/opencode /usr/local/bin/opencode
#46 CACHED
#47 [dg-openclaw_autodeployer 25/44] RUN npm install -g @anthropic-ai/
claude-code@latest
#47 CACHED
#48 [dg-openclaw_autodeployer 33/44] RUN set -eu; for attempt in 1 2 3
4 5; do if curl -fsSL --retry 5 --retry-all-errors --retry-delay 2
https://opencode.ai/install | HOME=/usr/local bash -s -- --version
1.2.27 --no-modify-path; then exit 0; fi; echo "opencode
install attempt
pt * 2)); done; echo "opencode install failed after retries" >&2;
exit 1
#48 CACHED
#49 [dg-openclaw_autodeployer 4/44] RUN cat <<'EOF' > /etc/profile.d/
zz-prompt.sh
#49 CACHED
#50 [dg-openclaw_autodeployer 28/44] RUN gemini --version
#50 CACHED
#51 [dg-openclaw_autodeployer 29/44] RUN npm install -g @playwright/
mcp@latest
#51 CACHED
#52 [dg-openclaw_autodeployer 10/44] RUN cat <<'EOF' > /etc/profile.d/
zz-bash-history.sh
#52 CACHED
#53 [dg-openclaw_autodeployer 35/44] RUN opencode --version
#53 CACHED
#54 [dg-openclaw_autodeployer 41/44] RUN printf "%s\n"
"PasswordAuthentication no" "PermitRootLogin no"
"PubkeyAuthentication yes" "X11Forwarding yes" "X11UseLocalhost yes"
"PermitUserEnvironment yes" "AllowUsers dev" > /etc/ssh/
sshd_config.d/dev.conf
#54 CACHED
#55 [dg-openclaw_autodeployer internal] load build context
#55 transferring context: 77.65kB done
#55 DONE 0.0s
#56 [dg-openclaw_autodeployer 42/44] RUN mkdir -p /home/dev/workspaces/
telegramgpt/openclaw_autodeployer && chown -R 1000:1000 /home/dev &&
if [ "/home/dev/workspaces/telegramgpt/openclaw_autodeployer" != "/" ];
then chown -R 1000:1000 "/home/dev/workspaces/telegramgpt/
openclaw_autodeployer"; fi
#56 DONE 0.1s
#57 [dg-openclaw_autodeployer 43/44] COPY entrypoint.sh /entrypoint.sh
#57 DONE 0.0s
#58 [dg-openclaw_autodeployer 44/44] RUN sed -i 's/\r$//' /entrypoint.sh
&& chmod +x /entrypoint.sh
#58 DONE 0.2s
#59 [dg-openclaw_autodeployer] exporting to image
#59 exporting layers 0.0s done
#59 writing image
sha256:1a4bab2b3c775dde78db92bedd94da3a4e1e7e77655f08f096106273307532ea
done
#59 naming to
docker.io/library/openclaw_autodeployer-dg-openclaw_autodeployer done
#59 DONE 0.0s
#60 [dg-openclaw_autodeployer] resolving provenance for metadata file
#60 DONE 0.0s
dg-openclaw_autodeployer Built
dg-openclaw_autodeployer-browser Built
Volume "openclaw_autodeployer_dg-openclaw_autodeployer-home" Creating
Volume "openclaw_autodeployer_dg-openclaw_autodeployer-home" Created
Volume "openclaw_autodeployer_dg-openclaw_autodeployer-home-browser"
Creating
Volume "openclaw_autodeployer_dg-openclaw_autodeployer-home-browser"
Created
Container dg-openclaw_autodeployer-browser Creating
Container dg-openclaw_autodeployer-browser Created
Container dg-openclaw_autodeployer Creating
Container dg-openclaw_autodeployer Created
Container dg-openclaw_autodeployer-browser Starting
Container dg-openclaw_autodeployer-browser Started
Container dg-openclaw_autodeployer Starting
Container dg-openclaw_autodeployer Started
[09:29:37.943] INFO (#1): Streaming container logs until clone
completes...
dg-openclaw_autodeployer | cp: '/home/dev/.codex-shared/
auth.json' and '/home/dev/.docker-git/.orch/auth/codex/auth.json' are
the same file
dg-openclaw_autodeployer | usermod: no changes
dg-openclaw_autodeployer | groupadd: group 'docker' already
exists
dg-openclaw_autodeployer-browser | 2026-03-18 09:29:38,670 INFO
spawned: 'Xvfb' with pid 14
dg-openclaw_autodeployer-browser | 2026-03-18 09:29:38,671 INFO
spawned: 'chrome' with pid 15
dg-openclaw_autodeployer-browser | 2026-03-18 09:29:38,673 INFO
spawned: 'fluxbox' with pid 16
dg-openclaw_autodeployer-browser | 2026-03-18 09:29:38,674 INFO
spawned: 'noVNC' with pid 17
dg-openclaw_autodeployer-browser | 2026-03-18 09:29:38,675 INFO
spawned: 'x11vnc' with pid 18
dg-openclaw_autodeployer-browser | 2026-03-18 09:29:38,701 WARN exited:
fluxbox (exit status 1; not expected)
dg-openclaw_autodeployer-browser | 2026-03-18 09:29:39,542 WARN exited:
x11vnc (exit status 2; not expected)
dg-openclaw_autodeployer-browser | PORT=5900
dg-openclaw_autodeployer-browser | extra services started
dg-openclaw_autodeployer-browser | 2026-03-18 09:29:39,688 INFO
success: Xvfb entered RUNNING state, process has stayed up for > than 1
seconds (startsecs)
dg-openclaw_autodeployer-browser | 2026-03-18 09:29:39,688 INFO
success: chrome entered RUNNING state, process has stayed up for > than
1 seconds (startsecs)
dg-openclaw_autodeployer-browser | 2026-03-18 09:29:39,688 INFO
success: noVNC entered RUNNING state, process has stayed up for > than 1
seconds (startsecs)
dg-openclaw_autodeployer | [codex] updating via bun...
dg-openclaw_autodeployer | bun add v1.3.10 (30e609e0)
dg-openclaw_autodeployer-browser | 2026-03-18 09:29:40,693 INFO
spawned: 'fluxbox' with pid 226
dg-openclaw_autodeployer-browser | 2026-03-18 09:29:40,695 INFO
spawned: 'x11vnc' with pid 227
dg-openclaw_autodeployer-browser | 2026-03-18 09:29:40,701 WARN exited:
fluxbox (exit status 1; not expected)
dg-openclaw_autodeployer-browser | 2026-03-18 09:29:40,721 WARN exited:
x11vnc (exit status 1; not expected)
dg-openclaw_autodeployer-browser | 2026-03-18 09:29:42,726 INFO
spawned: 'fluxbox' with pid 229
dg-openclaw_autodeployer-browser | 2026-03-18 09:29:42,728 INFO
spawned: 'x11vnc' with pid 230
dg-openclaw_autodeployer-browser | 2026-03-18 09:29:42,734 WARN exited:
fluxbox (exit status 1; not expected)
dg-openclaw_autodeployer-browser | 2026-03-18 09:29:42,755 WARN exited:
x11vnc (exit status 1; not expected)
dg-openclaw_autodeployer-browser | 2026-03-18 09:29:45,759 INFO
spawned: 'fluxbox' with pid 231
dg-openclaw_autodeployer-browser | 2026-03-18 09:29:45,762 INFO
spawned: 'x11vnc' with pid 232
dg-openclaw_autodeployer-browser | 2026-03-18 09:29:45,771 WARN exited:
fluxbox (exit status 1; not expected)
dg-openclaw_autodeployer-browser | 2026-03-18 09:29:45,780 INFO gave
up: fluxbox entered FATAL state, too many start retries too quickly
dg-openclaw_autodeployer-browser | 2026-03-18 09:29:45,790 WARN exited:
x11vnc (exit status 1; not expected)
dg-openclaw_autodeployer-browser | 2026-03-18 09:29:46,791 INFO gave
up: x11vnc entered FATAL state, too many start retries too quickly
dg-openclaw_autodeploy
dg-openclaw_autodeployer | installed @openai/codex@0.115.0 with
binaries:
dg-openclaw_autodeployer | - codex
dg-openclaw_autodeployer |
dg-openclaw_autodeployer | 2 packages installed [9.35s]
dg-openclaw_autodeployer | Cloning into '/home/dev/workspaces/
telegramgpt/openclaw_autodeployer'...
dg-openclaw_autodeployer | remote: Enumerating objects: 1058,
done.
dg-openclaw_autodeployer | remote: Counting objects: 1%
(1/100)remote: Counting objects: 100% (100/100), done.
dg-openclaw_autodeployer | remote: Compressing objects: 1%
(1/8remote: Compressing objects: 100% (83/83), done.
remote: Total 1058 (delta 27), reused 18 (delta 17), pack-reused 958
(from 2)
dg-openclaw_autodeployer | Receiving objects: 99% (1048/1058),
4Receiving objects: 100% (1058/1058), 1023.30 KiB | 1.37 MiB/s, done.
Resolving deltas: 100% (529/529), done.olving deltas: 0% (0/529)
dg-openclaw_autodeployer | Cloning into bare repository '/home/
dev/.docker-git/.cache/git-
mirrors/72b5b348bf70d045e21d9c91140e67bd02c99a12de54e69578590c671aefeb77
.git.tmp-1'...
dg-openclaw_autodeployer | done.
dg-openclaw_autodeployer | [clone-cache] mirror created: /home/
dev/.docker-git/.cache/git-
mirrors/72b5b348bf70d045e21d9c91140e67bd02c99a12de54e69578590c671aefeb77
.git
dg-openclaw_autodeployer | [clone] done
[09:29:52.686] INFO (#1): Docker environment is up
[09:29:52.687] INFO (#1): SSH access: ssh -i /home/meadav_david/.ssh/
id_ed25519 -tt -Y -o LogLevel=ERROR -o StrictHostKeyChecking=no -o
UserKnownHostsFile=/dev/null -p 2222 dev@localhost
[09:29:52.707] INFO (#1): Docker DNS:
docker.telegramgpt.openclaw_autodeployer
[09:29:52.724] INFO (#1): Container IP: 172.17.0.3
[09:29:52.724] INFO (#1): Use: http://172.17.0.3:
HEAD is now at 6ed8296 chore(state): update skulidropek/
openclaw_autodeployer
[main 7c0465d] chore(state): update telegramgpt/openclaw_autodeployer
15 files changed, 2753 insertions(+), 4 deletions(-)
create mode 100644 telegramgpt/openclaw_autodeployer/.dockerignore
create mode 100644 telegramgpt/openclaw_autodeployer/.gitignore
create mode 100644 telegramgpt/openclaw_autodeployer/.orch/auth/codex/
AGENTS.md
create mode 120000 telegramgpt/openclaw_autodeployer/.orch/auth/codex/
auth.json
create mode 100644 telegramgpt/openclaw_autodeployer/.orch/auth/codex/
config.toml
create mode 100644 telegramgpt/openclaw_autodeployer/.orch/env/
global.env
create mode 100644 telegramgpt/openclaw_autodeployer/.orch/env/
project.env
create mode 100644 telegramgpt/openclaw_autodeployer/Dockerfile
create mode 100644 telegramgpt/openclaw_autodeployer/Dockerfile.browser
create mode 100644 telegramgpt/openclaw_autodeployer/docker-compose.yml
create mode 100644 telegramgpt/openclaw_autodeployer/docker-git.json
create mode 100755 telegramgpt/openclaw_autodeployer/entrypoint.sh
create mode 100755 telegramgpt/openclaw_autodeployer/mcp-playwright-
start-extra.sh
[09:29:56.217] INFO (#1): Opening SSH: ssh -i /home/meadav_david/.ssh/
id_ed25519 -tt -Y -o LogLevel=ERROR -o StrictHostKeyChecking=no -o
UserKnownHostsFile=/dev/null -p 2222 dev@localhost
dev@localhost: Permission denied (publickey).
[09:29:56.367] WARN (#1): SSH auto-open failed: ssh failed with exit
code 255
meadav_david@meadav:~/docker-git
• Разберу это как дефект в clone: сначала найду код, который поднимает
контейнер и настраивает SSH-автооткрытие, затем воспроизведу/проверю,
почему контейнер поднялся, а вход по ssh -i ~/.ssh/id_ed25519 ...
dev@localhost завершился Permission denied (publickey).
• Explored
└ List app
Search docker-git|ssh|entrypoint|compose|clone
Search SSH auto-open failed|Permission denied \(publickey\)|
authorized_keys|id_ed25519|StrictHostKeyChecking|Opening SSH|ssh
-i
in app
• Ran git -C /home/meadav_david/docker-git status --short
└ (no output)
───────────────────────────────────────────────────────────────────────────
• Нашёл участок, который именно и делает авто-SSH после clone, и шаблон
entrypoint, который копирует authorized_keys в контейнер. Сейчас сверю
эти два пути с фактическим состоянием проекта, чтобы понять, почему
контейнер поднят, а ключ внутри не принят.
• Explored
└ Read create-project.ts, base.ts, create-project-open-
ssh.test.ts, docker-
compose.ts
───────────────────────────────────────────────────────────────────────────
• Код уже показывает гипотезу: ssh использует приватный ключ из ~/.ssh/
id_ed25519, а контейнер принимает только содержимое файла,
смонтированного как authorized_keys. Если этот файл не синхронизирован с
тем же публичным ключом, получаем ровно такой Permission denied
(publickey) при полностью успешном clone. Проверяю, какой путь реально
используется и что в нём лежит.
• Explored
└ Read path-helpers.ts
List authorized_keys
• Ran printf '%s\n' '--- authorized_keys ---'; sed -n '1,5p' /home/
│ meadav_david/.docker-git/authorized_keys 2>/dev/null; printf '%s\n'
│ '--- id_ed25519.pub ---'; sed -n '1,5p' /home/meadav_david/.ssh/
│ … +1 lines
└ --- authorized_keys ---
ssh-ed25519
… +4 lines
AAAAC3NzaC1lZDI1NTE5AAAAIJ32Ttr4s6fEU4MLxdU5t5zM9ai8lQkThdQZTRo82yLO
meadav_david@meadav
• Ran docker exec dg-openclaw_autodeployer bash -lc 'id; ls -ld /home/
│ dev/.ssh; ls -l /home/dev/.ssh/authorized_keys; printf "--- container
│ authorized_keys ---\n"; sed -n "1,5p" /home/dev/.ssh/authorized_keys'
└ uid=0(root) gid=0(root) groups=0(root)
drwx------ 2 dev dev 4096 Mar 18 09:29 /home/dev/.ssh
… +4 lines
AAAAC3NzaC1lZDI1NTE5AAAAIIzSh5oCDy6mff7Jkvkei62jtGdy3buhXpu7SHhkF1YP
user@arch
───────────────────────────────────────────────────────────────────────────
• Причина уже подтверждена инструментами: в контейнер смонтирован и
установлен один публичный ключ, а ssh на хосте идёт с другим приватным
ключом. Это не сбой sshd; это нарушение инварианта authorized_keys ≡
public(used_private_key). Сейчас найду, где именно проект выбирает
источник authorized_keys, и исправлю выбор/синхронизацию так, чтобы
автологин использовал согласованную пару ключей.
• Explored
└ Read prepare-files.ts
Search findAuthorizedKeysSource|authorizedKeysPath|authorized_keys|
dev_ssh_key.pub|DOCKER_GIT_AUTHORIZED_KEYS in lib
Read prepare-files.test.ts
───────────────────────────────────────────────────────────────────────────
• Сейчас видно ещё глубже: это не только разовый сбой, а дефект приоритета
источников. findAuthorizedKeysSource предпочитает уже существующий
~/.docker-git/authorized_keys перед ~/.ssh/id_ed25519.pub, поэтому
однажды сохранённый старый ключ "залипает" и дальше ломает авто-SSH.
Проверяю безопасный минимальный фикс и его регрессионные тесты.
• Explored
└ Read path-helpers.ts, path-helpers.test.ts, command-builders.ts
───────────────────────────────────────────────────────────────────────────
• Уточнил механизм: stale-файл не выбирается как источник поиска, он уже
существует по целевому пути ~/.docker-git/authorized_keys, а
prepareProjectFiles при state === "exists" вообще не пытается его сверить
с текущим ~/.ssh/id_ed25519.pub. То есть ошибка именно в отсутствии
синхронизации, а не в sshd и не в docker compose. Исправлю это на уровне
подготовки файлов и добавлю тест на регрессию.
• Explored
└ Read paths.ts, projects-core.ts
• Ran test -f /home/meadav_david/.docker-git/telegramgpt/
│ openclaw_autodeployer/docker-git.json && sed -n '1,220p' /home/
│ meadav_david/.docker-git/telegramgpt/openclaw_autodeployer/
│ … +1 lines
└ {
"schemaVersion": 1,
… +27 lines
}
}
───────────────────────────────────────────────────────────────────────────
• Вношу точечный фикс в подготовку authorized_keys: при использовании
дефолтного managed-файла буду гарантировать, что в нём присутствует
публичный ключ от того приватного ключа, которым docker-git потом реально
делает ssh. Это закрывает именно ваш сценарий со старым ~/.docker-git/
authorized_keys.
• Патч не применился из-за несовпадения контекста. Перечитываю файл и внесу
аккуратный diff по фактическому содержимому, затем сразу прогоню тесты на
этот сценарий.
• Explored
└ Read prepare-files.ts
• Edited packages/lib/src/usecases/actions/prepare-files.ts (+48 -5)
14 } from "../auth-sync.js"
15 -import { findAuthorizedKeysSource, resolveAuthorizedKeysPath } fro
m "../path-helpers.js"
15 +import {
16 + defaultProjectsRoot,
17 + findAuthorizedKeysSource,
18 + findExistingPath,
19 + findSshPrivateKey,
20 + resolveAuthorizedKeysPath
21 +} from "../path-helpers.js"
22 import { withFsPathContext } from "../runtime.js"
⋮
55 const resolved = resolveAuthorizedKeysPath(path, baseDir, au
thorizedKeysPath)
56 + const managedDefaultAuthorizedKeys = path.join(defaultProjec
tsRoot(process.cwd()), "authorized_keys")
57 const state = yield* _(
⋮
64 )
58 - if (state === "exists") {
59 - return
60 - }
65
62 - const source = yield* _(findAuthorizedKeysSource(fs, path, p
rocess.cwd()))
66 + const sshPrivateKey = yield* _(findSshPrivateKey(fs, path, p
rocess.cwd()))
67 + const matchingPublicKey =
68 + sshPrivateKey === null ? null : yield* _(findExistingPath(
fs, `${sshPrivateKey}.pub`))
69 + const source = yield* _(
70 + matchingPublicKey === null
71 + ? findAuthorizedKeysSource(fs, path, process.cwd())
72 + : Effect.succeed(matchingPublicKey)
73 + )
74 if (source === null) {
⋮
82
83 + const desiredContents = (yield* _(fs.readFileString(source))
).trim()
84 + if (desiredContents.length === 0) {
85 + yield* _(Effect.logWarning(`Authorized keys source ${sourc
e} is empty. Skipping SSH key sync.`))
86 + return
87 + }
88 +
89 + if (state === "exists") {
90 + if (resolved !== managedDefaultAuthorizedKeys) {
91 + return
92 + }
93 +
94 + const currentContents = yield* _(fs.readFileString(resolve
d))
95 + const currentLines = currentContents
96 + .split(/\r?\n/)
97 + .map((line) => line.trim())
98 + .filter((line) => line.length > 0)
99 +
100 + if (currentLines.includes(desiredContents)) {
101 + return
102 + }
103 +
104 + const normalizedCurrent = currentContents.trimEnd()
105 + const nextContents =
106 + normalizedCurrent.length === 0
107 + ? `${desiredContents}\n`
108 + : `${normalizedCurrent}\n${desiredContents}\n`
109 +
110 + yield* _(fs.writeFileString(resolved, nextContents))
111 + yield* _(Effect.log(`Authorized keys appended from ${sourc
e} to ${resolved}`))
112 + return
113 + }
114 +
115 yield* _(fs.makeDirectory(path.dirname(resolved), { recursiv
e: true }))
• Explored
└ Read prepare-files.test.ts
• Edited packages/lib/tests/usecases/prepare-files.test.ts (+73 -0)
25
26 +const withPatchedEnv = <A, E, R>(
27 + patch: Readonly<Record<string, string | undefined>>,
28 + effect: Effect.Effect<A, E, R>
29 +): Effect.Effect<A, E, R> =>
30 + Effect.acquireUseRelease(
31 + Effect.sync(() => {
32 + const previous = new Map<string, string | undefined>()
33 + for (const [key, value] of Object.entries(patch)) {
34 + previous.set(key, process.env[key])
35 + if (value === undefined) {
36 + delete process.env[key]
37 + } else {
38 + process.env[key] = value
39 + }
40 + }
41 + return previous
42 + }),
43 + () => effect,
44 + (previous) =>
45 + Effect.sync(() => {
46 + for (const [key, value] of previous.entries()) {
47 + if (value === undefined) {
48 + delete process.env[key]
49 + } else {
50 + process.env[key] = value
51 + }
52 + }
53 + })
54 + )
55 +
56 const makeGlobalConfig = (root: string, path: Path.Path): Template
Config => ({
⋮
241 ).pipe(Effect.provide(NodeContext.layer)))
242 +
243 + it.effect("appends the active public key to the managed authoriz
ed_keys file", () =>
244 + withTempDir((root) =>
245 + Effect.gen(function*(_) {
246 + const fs = yield* _(FileSystem.FileSystem)
247 + const path = yield* _(Path.Path)
248 + const homeDir = path.join(root, "home")
249 + const projectsRoot = path.join(homeDir, ".docker-git")
250 + const outDir = path.join(projectsRoot, "org", "repo")
251 + const authorizedKeysPath = path.join(projectsRoot, "author
ized_keys")
252 + const sshPrivateKeyPath = path.join(homeDir, ".ssh", "id_e
d25519")
253 + const sshPublicKeyPath = `${sshPrivateKeyPath}.pub`
254 + const staleKey = "ssh-ed25519 AAAA-stale stale@example\n"
255 + const currentKey = "ssh-ed25519 AAAA-current current@examp
le\n"
256 + const globalConfig = makeGlobalConfig(projectsRoot, path)
257 + const projectConfig = makeProjectConfig(outDir, false, pat
h)
258 +
259 + yield* _(fs.makeDirectory(path.dirname(authorizedKeysPath)
, { recursive: true }))
260 + yield* _(fs.makeDirectory(path.dirname(sshPrivateKeyPath),
{ recursive: true }))
261 + yield* _(fs.writeFileString(authorizedKeysPath, staleKey))
262 + yield* _(fs.writeFileString(sshPrivateKeyPath, "PRIVATE\n"
))
263 + yield* _(fs.writeFileString(sshPublicKeyPath, currentKey))
264 +
265 + yield* _(
266 + withPatchedEnv(
267 + {
268 + HOME: homeDir,
269 + DOCKER_GIT_PROJECTS_ROOT: projectsRoot,
270 + DOCKER_GIT_AUTHORIZED_KEYS: undefined,
271 + DOCKER_GIT_SSH_KEY: undefined
272 + },
273 + prepareProjectFiles(outDir, projectsRoot, globalConfig
, projectConfig, {
274 + force: false,
275 + forceEnv: false
276 + })
277 + )
278 + )
279 +
280 + const synchronizedAuthorizedKeys = yield* _(fs.readFileStr
ing(authorizedKeysPath))
281 + expect(synchronizedAuthorizedKeys).toContain(staleKey.trim
())
282 + expect(synchronizedAuthorizedKeys).toContain(currentKey.tr
im())
283 + })
284 + ).pipe(Effect.provide(NodeContext.layer)))
285 })
─ Worked for 1m 13s ───────────────────────────────────────────────────────
• Правка внесена. Сейчас прогоняю целевые тесты и минимальную сборку
библиотеки/приложения, чтобы подтвердить: 1) регрессия закрыта, 2) типы и
сборка не сломаны.
• Ran pnpm --filter @effect-template/lib build
└
> @effect-template/lib@1.0.0 build /home/meadav_david/docker-git/
packages/lib
> tsc -p tsconfig.json
• Ran pnpm --filter @prover-coder-ai/docker-git build:docker-git
└
> @prover-coder-ai/docker-git@1.0.50 prebuild:docker-git /home/
… +15 lines
dist/src/docker-git/main.js 507.13 kB │ map: 1,127.64 kB
✓ built in 822ms
• Waited for background terminal · pnpm --filter @effect-template/lib test
-- --run packages/lib/tests/usecases/prepare-files.test.ts packages/lib/
tests/usecases/create-project-open-ssh.test.ts packages/lib/tests/usecases/
path-helpers.test.ts
• Ran pnpm --filter @effect-template/lib test -- --run packages/lib/tests/
│ usecases/prepare-files.test.ts packages/lib/tests/usecases/
│ create-project-open-ssh.test.ts packages/lib/tests/usecases/
│ … +1 lines
└
> @effect-template/lib@1.0.0 test /home/meadav_david/docker-git/
… +83 lines
path-helpers.test.ts`
Exit status 1
• Waited for background terminal
• Waited for background terminal
───────────────────────────────────────────────────────────────────────────
• Тест показал не сбой фикса, а ошибку в самом тестовом стенде: я дал
projectConfig.authorizedKeysPath как локальный файл проекта, тогда как
реальный clone использует managed shared path вроде ../../
authorized_keys. Исправляю тест на реальную конфигурацию и перепроверяю.
• Edited packages/lib/tests/usecases/prepare-files.test.ts (+4 -1)
256 const globalConfig = makeGlobalConfig(projectsRoot, path)
257 - const projectConfig = makeProjectConfig(outDir, false, pat
h)
257 + const projectConfig = {
258 + ...makeProjectConfig(outDir, false, path),
259 + authorizedKeysPath: "../../authorized_keys"
260 + }
261
• Ran pnpm --filter @effect-template/lib exec vitest run tests/usecases/
│ prepare-files.test.ts tests/usecases/create-project-open-ssh.test.ts
│ tests/usecases/path-helpers.test.ts
└
RUN v4.0.18 /home/meadav_david/docker-git/packages/lib
… +9 lines
329ms, environment 0ms)
• Ran pnpm --filter @effect-template/lib build
└
> @effect-template/lib@1.0.0 build /home/meadav_david/docker-git/
packages/lib
> tsc -p tsconfig.json
• Waited for background terminal
• Waited for background terminal
• Waited for background terminal · pnpm --filter @prover-coder-ai/docker-
git build:docker-git
• Ran pnpm --filter @prover-coder-ai/docker-git build:docker-git
└
> @prover-coder-ai/docker-git@1.0.50 prebuild:docker-git /home/
… +15 lines
dist/src/docker-git/main.js 507.13 kB │ map: 1,127.64 kB
✓ built in 810ms
───────────────────────────────────────────────────────────────────────────
• Фикс в коде уже проверен. Теперь довожу до конца ваш текущий broken
state: синхронизирую ~/.docker-git/authorized_keys с ~/.ssh/
id_ed25519.pub и сразу проверю реальный вход в уже поднятый контейнер по
SSH.
• Ran pub=$(cat /home/meadav_david/.ssh/id_ed25519.pub) && touch /home/
│ meadav_david/.docker-git/authorized_keys && grep -qxF "$pub" /home/
│ meadav_david/.docker-git/authorized_keys || printf '%s\n' "$pub" >> /
│ … +1 lines
└ (no output)
• Ran ssh -i /home/meadav_david/.ssh/id_ed25519 -o BatchMode=yes -o
│ ConnectTimeout=10 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/
│ dev/null -p 2222 dev@localhost 'whoami && hostname && test -d /home/
│ … +2 lines
└ Warning: Permanently added '[localhost]:2222' (ED25519) to the list of
known hosts.
dev@localhost: Permission denied (publickey).
───────────────────────────────────────────────────────────────────────────
• Ожидаемо, одного обновления host-файла оказалось недостаточно для уже
запущенного контейнера: entrypoint копирует /authorized_keys в /home/
dev/.ssh/authorized_keys только при старте. Досинхронизирую ключ внутрь
текущего контейнера и перепроверю SSH.
• Ran docker exec dg-openclaw_autodeployer bash -lc 'cp /authorized_keys /
│ home/dev/.ssh/authorized_keys && chmod 600 /home/dev/.ssh/
│ authorized_keys && chown dev:dev /home/dev/.ssh/authorized_keys &&
│ … +1 lines
└ ssh-ed25519
AAAAC3NzaC1lZDI1NTE5AAAAIIzSh5oCDy6mff7Jkvkei62jtGdy3buhXpu7SHhkF1YP
… +2 lines
AAAAC3NzaC1lZDI1NTE5AAAAIJ32Ttr4s6fEU4MLxdU5t5zM9ai8lQkThdQZTRo82yLO
meadav_david@meadav
• Ran ssh -i /home/meadav_david/.ssh/id_ed25519 -o BatchMode=yes -o
│ ConnectTimeout=10 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/
│ dev/null -p 2222 dev@localhost 'whoami && hostname && test -d /home/
│ … +2 lines
└ dev
b7cd915670d7
workspace-ok
• Ran git -C /home/meadav_david/docker-git diff -- packages/lib/src/
│ usecases/actions/prepare-files.ts packages/lib/tests/usecases/
│ prepare-files.test.ts
└ diff --git a/packages/lib/src/usecases/actions/prepare-files.ts b/
packages/lib/src/usecases/actions/prepare-files.ts
… +176 lines
+ ).pipe(Effect.provide(NodeContext.layer)))
})
• Ran git -C /home/meadav_david/docker-git status --short
└ M packages/lib/src/usecases/actions/prepare-files.ts
M packages/lib/tests/usecases/prepare-files.test.ts
───────────────────────────────────────────────────────────────────────────
• Проблема была не в clone и не в Docker. clone завершился успешно, а авто-
SSH падал из-за рассинхрона ключей: в контейнер попадал старый ключ из
~/.docker-git/authorized_keys, а ssh запускался с /home/
meadav_david/.ssh/id_ed25519. Я это подтвердил по содержимому файлов:
ключ в authorized_keys и id_ed25519.pub были разными.
Что сделал:
- Исправил логику в packages/lib/src/usecases/actions/prepare-files.ts,
чтобы для managed authorized_keys гарантированно добавлялся публичный
ключ от того приватного ключа, которым docker-git реально ходит по SSH.
- Добавил регрессионный тест в packages/lib/tests/usecases/prepare-
files.test.ts, который воспроизводит ваш сценарий со stale
authorized_keys.
Проверка:
- pnpm --filter @effect-template/lib exec vitest run tests/usecases/
prepare-files.test.ts tests/usecases/create-project-open-ssh.test.ts
tests/usecases/path-helpers.test.ts
- pnpm --filter @effect-template/lib build
- pnpm --filter @prover-coder-ai/docker-git build:docker-git
Дополнительно я починил текущее окружение вручную: синхронизировал
~/.docker-git/authorized_keys, обновил /home/dev/.ssh/authorized_keys в
уже запущенном контейнере и проверил реальный вход. Сейчас ssh -i /home/
meadav_david/.ssh/id_ed25519 -p 2222 dev@localhost проходит успешно.
Отдельно: сообщения fluxbox/x11vnc из browser-контейнера не были причиной
этой ошибки. Основной сбой был именно authorized_keys !=
public(id_ed25519).
SOURCE: n/a
› Слушай, а может оно так происходит из-за синхронизации из гитхаба? У нас
же 1 профиль в ~/.docker-git и он постоянно синхроинизируется?
• Проверю именно это допущение по коду: участвует ли authorized_keys в
state sync/коммитах и есть ли путь, по которому GitHub-синхронизация
могла перетирать ~/.docker-git/authorized_keys.
• Explored
└ Read state-repo.ts
Search authorized_keys|state repo|autoSyncState|git add|commit|sync in
state-repo*
Read state-normalize.ts
───────────────────────────────────────────────────────────────────────────
• Нашёл важное различие: GitHub sync действительно коммитит состояние из
~/.docker-git, но нужно отделить "путь к authorized_keys в конфиге" от
"сам файл authorized_keys". Проверяю, игнорируется ли сам файл или он
тоже трекается в state repo.
• Explored
└ Read gitignore.ts, templates.ts
• Ran cd /home/meadav_david/.docker-git && git ls-files authorized_keys '*/
│ authorized_keys' '.orch/**' 2>/dev/null || true
└ .orch/auth/claude/.image/Dockerfile
.orch/auth/claude/agien/.oauth-token
… +6820 lines
.orch/state/api-agents.json
authorized_keys
───────────────────────────────────────────────────────────────────────────
• Да, это может усиливать проблему, но не это было первопричиной.
Что подтвердилось по коду:
- autoSyncState(...) реально коммитит состояние из ~/.docker-git в
GitHub.
- State repo специально трекает почти всё под ~/.docker-git,
включая .orch/* и сам authorized_keys.
- У тебя authorized_keys действительно tracked в state repo: git ls-files
в /home/meadav_david/.docker-git показывает authorized_keys.
То есть модель такая:
- На одной машине или в прошлом состоянии в ~/.docker-git/authorized_keys
оказался старый публичный ключ.
- Автосинк сохранил его в state repo.
- Потом этот же файл мог приехать обратно через pull/sync на текущую
машину.
- clone брал именно этот managed authorized_keys, а ssh шёл с текущим
~/.ssh/id_ed25519.
- Если ключи не совпали, получаем Permission denied (publickey).
Но важно:
- В вашем конкретном логе сбой произошёл уже потому, что локальный
~/.docker-git/authorized_keys на этой машине содержал другой ключ.
- Даже без GitHub sync этого было бы достаточно для поломки.
- GitHub sync здесь не причина механики отказа, а механизм сохранения и
переезда stale-состояния между машинами.
Именно поэтому я правил не sync, а инвариант подготовки:
- managed authorized_keys теперь дополняется публичным ключом от реально
используемого приватного ключа;
- это ломает цепочку synced stale key -> SSH reject.
Если хочешь, следующим шагом могу добить архитектурно:
1. перестать трекать authorized_keys в state repo вообще;
2. генерировать его локально на каждой машине из ~/.ssh/id_ed25519.pub;
3. оставить в git только путь, но не содержимое ключей.
Это будет чище, чем хранить machine-specific SSH material в общем
синхронизируемом профиле.
Token usage: total=239 003 input=227 549 (+ 1 107 072 cached) output=11 454 (reasoning 3 022)
To continue this session, run codex resume 019d0046-0be7-7750-8a88-c02a2f9469a8
meadav_david@meadav:~/docker-git$ git status
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: packages/lib/src/usecases/actions/prepare-files.ts
modified: packages/lib/tests/usecases/prepare-files.test.ts
no changes added to commit (use "git add" and/or "git commit -a")
meadav_david@meadav:~/docker-git$ git diff | cat
diff --git a/packages/lib/src/usecases/actions/prepare-files.ts b/packages/lib/src/usecases/actions/prepare-files.ts
index 51514fc..c2edf0b 100644
--- a/packages/lib/src/usecases/actions/prepare-files.ts
+++ b/packages/lib/src/usecases/actions/prepare-files.ts
@@ -12,7 +12,13 @@ import {
migrateLegacyOrchLayout,
syncAuthArtifacts
} from "../auth-sync.js"
-import { findAuthorizedKeysSource, resolveAuthorizedKeysPath } from "../path-helpers.js"
+import {
+ defaultProjectsRoot,
+ findAuthorizedKeysSource,
+ findExistingPath,
+ findSshPrivateKey,
+ resolveAuthorizedKeysPath
+} from "../path-helpers.js"
import { withFsPathContext } from "../runtime.js"
import { resolvePathFromBase } from "./paths.js"
@@ -47,6 +53,7 @@ const ensureAuthorizedKeys = (
withFsPathContext(({ fs, path }) =>
Effect.gen(function*(_) {
const resolved = resolveAuthorizedKeysPath(path, baseDir, authorizedKeysPath)
+ const managedDefaultAuthorizedKeys = path.join(defaultProjectsRoot(process.cwd()), "authorized_keys")
const state = yield* _(
ensureFileReady(
fs,
@@ -55,11 +62,15 @@ const ensureAuthorizedKeys = (
`Authorized keys was a directory, moved to ${backupPath}. Creating a file at ${resolvedPath}.`
)
)
- if (state === "exists") {
- return
- }
- const source = yield* _(findAuthorizedKeysSource(fs, path, process.cwd()))
+ const sshPrivateKey = yield* _(findSshPrivateKey(fs, path, process.cwd()))
+ const matchingPublicKey =
+ sshPrivateKey === null ? null : yield* _(findExistingPath(fs, `${sshPrivateKey}.pub`))
+ const source = yield* _(
+ matchingPublicKey === null
+ ? findAuthorizedKeysSource(fs, path, process.cwd())
+ : Effect.succeed(matchingPublicKey)
+ )
if (source === null) {
yield* _(
Effect.logError(
@@ -69,6 +80,38 @@ const ensureAuthorizedKeys = (
return
}
+ const desiredContents = (yield* _(fs.readFileString(source))).trim()
+ if (desiredContents.length === 0) {
+ yield* _(Effect.logWarning(`Authorized keys source ${source} is empty. Skipping SSH key sync.`))
+ return
+ }
+
+ if (state === "exists") {
+ if (resolved !== managedDefaultAuthorizedKeys) {
+ return
+ }
+
+ const currentContents = yield* _(fs.readFileString(resolved))
+ const currentLines = currentContents
+ .split(/\r?\n/)
+ .map((line) => line.trim())
+ .filter((line) => line.length > 0)
+
+ if (currentLines.includes(desiredContents)) {
+ return
+ }
+
+ const normalizedCurrent = currentContents.trimEnd()
+ const nextContents =
+ normalizedCurrent.length === 0
+ ? `${desiredContents}\n`
+ : `${normalizedCurrent}\n${desiredContents}\n`
+
+ yield* _(fs.writeFileString(resolved, nextContents))
+ yield* _(Effect.log(`Authorized keys appended from ${source} to ${resolved}`))
+ return
+ }
+
yield* _(fs.makeDirectory(path.dirname(resolved), { recursive: true }))
yield* _(fs.copyFile(source, resolved))
yield* _(Effect.log(`Authorized keys copied from ${source} to ${resolved}`))
diff --git a/packages/lib/tests/usecases/prepare-files.test.ts b/packages/lib/tests/usecases/prepare-files.test.ts
index 1c8df2a..916beed 100644
--- a/packages/lib/tests/usecases/prepare-files.test.ts
+++ b/packages/lib/tests/usecases/prepare-files.test.ts
@@ -23,6 +23,36 @@ const withTempDir = <A, E, R>(
})
)
+const withPatchedEnv = <A, E, R>(
+ patch: Readonly<Record<string, string | undefined>>,
+ effect: Effect.Effect<A, E, R>
+): Effect.Effect<A, E, R> =>
+ Effect.acquireUseRelease(
+ Effect.sync(() => {
+ const previous = new Map<string, string | undefined>()
+ for (const [key, value] of Object.entries(patch)) {
+ previous.set(key, process.env[key])
+ if (value === undefined) {
+ delete process.env[key]
+ } else {
+ process.env[key] = value
+ }
+ }
+ return previous
+ }),
+ () => effect,
+ (previous) =>
+ Effect.sync(() => {
+ for (const [key, value] of previous.entries()) {
+ if (value === undefined) {
+ delete process.env[key]
+ } else {
+ process.env[key] = value
+ }
+ }
+ })
+ )
+
const makeGlobalConfig = (root: string, path: Path.Path): TemplateConfig => ({
containerName: "dg-test",
serviceName: "dg-test",
@@ -209,4 +239,50 @@ describe("prepareProjectFiles", () => {
expect(compose).not.toContain("external: true")
})
).pipe(Effect.provide(NodeContext.layer)))
+
+ it.effect("appends the active public key to the managed authorized_keys file", () =>
+ withTempDir((root) =>
+ Effect.gen(function*(_) {
+ const fs = yield* _(FileSystem.FileSystem)
+ const path = yield* _(Path.Path)
+ const homeDir = path.join(root, "home")
+ const projectsRoot = path.join(homeDir, ".docker-git")
+ const outDir = path.join(projectsRoot, "org", "repo")
+ const authorizedKeysPath = path.join(projectsRoot, "authorized_keys")
+ const sshPrivateKeyPath = path.join(homeDir, ".ssh", "id_ed25519")
+ const sshPublicKeyPath = `${sshPrivateKeyPath}.pub`
+ const staleKey = "ssh-ed25519 AAAA-stale stale@example\n"
+ const currentKey = "ssh-ed25519 AAAA-current current@example\n"
+ const globalConfig = makeGlobalConfig(projectsRoot, path)
+ const projectConfig = {
+ ...makeProjectConfig(outDir, false, path),
+ authorizedKeysPath: "../../authorized_keys"
+ }
+
+ yield* _(fs.makeDirectory(path.dirname(authorizedKeysPath), { recursive: true }))
+ yield* _(fs.makeDirectory(path.dirname(sshPrivateKeyPath), { recursive: true }))
+ yield* _(fs.writeFileString(authorizedKeysPath, staleKey))
+ yield* _(fs.writeFileString(sshPrivateKeyPath, "PRIVATE\n"))
+ yield* _(fs.writeFileString(sshPublicKeyPath, currentKey))
+
+ yield* _(
+ withPatchedEnv(
+ {
+ HOME: homeDir,
+ DOCKER_GIT_PROJECTS_ROOT: projectsRoot,
+ DOCKER_GIT_AUTHORIZED_KEYS: undefined,
+ DOCKER_GIT_SSH_KEY: undefined
+ },
+ prepareProjectFiles(outDir, projectsRoot, globalConfig, projectConfig, {
+ force: false,
+ forceEnv: false
+ })
+ )
+ )
+
+ const synchronizedAuthorizedKeys = yield* _(fs.readFileString(authorizedKeysPath))
+ expect(synchronizedAuthorizedKeys).toContain(staleKey.trim())
+ expect(synchronizedAuthorizedKeys).toContain(currentKey.trim())
+ })
+ ).pipe(Effect.provide(NodeContext.layer)))
})