Skip to content

Можешь исправить и сделать тоже самое? #155

@skulidropek

Description

@skulidropek

› Что не так? Можешь разобраться?

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 ${attempt} failed; retrying..." &gt;&amp;2; sleep $((attem
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)))
})

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions