From fccaee4b94ade8a946e3f32973af3598cd34166d Mon Sep 17 00:00:00 2001 From: Richard Zink Date: Sun, 15 Nov 2026 12:15:02 +0100 Subject: [PATCH] Fix ssh/scp/sftp for containers running as a uid without passwd entry Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 7 +++++++ Dockerfile | 6 +++++- docker/ssh-nss | 13 +++++++++++++ server/src/sshkeys.rs | 5 ++++- web/src/whatsnew.ts | 4 ++++ 5 files changed, 33 insertions(+), 2 deletions(-) create mode 100755 docker/ssh-nss diff --git a/CHANGELOG.md b/CHANGELOG.md index 097712b..78d69fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ All notable changes to CodeCraft. Versions follow [Semantic Versioning](https://semver.org/); until 1.0, minor versions may contain breaking changes. +## [Unreleased] + +### Fixed + +- `ssh`, `scp` and `sftp` failed with "No user exists for uid 99" because the container runs as a uid + without passwd entry; the image now wraps them with nss_wrapper (also used by the key wrappers). + ## [v202611-00033] – 2026-11-15 ### Added diff --git a/Dockerfile b/Dockerfile index 5e5b300..f4227db 100644 --- a/Dockerfile +++ b/Dockerfile @@ -85,7 +85,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential pkg-config libssl-dev cmake zlib1g-dev binutils-multiarch \ chromium fonts-dejavu-core fonts-noto-color-emoji poppler-utils openmpt123 \ openscad xvfb xauth \ - openssh-client rsync mc \ + openssh-client libnss-wrapper rsync mc \ && rm -rf /var/lib/apt/lists/* # Language servers for diagnostics after edits (rust-analyzer/gopls/clangd can be # installed by agents: rustup component add rust-analyzer, go install …). @@ -98,6 +98,10 @@ COPY --from=dockercli /docker /usr/local/bin/docker COPY server/ghidra /app/ghidra # git for agent shells: commits get the co-author trailer of the model (server.commit_trailer). COPY --chmod=755 docker/git-coauthor /usr/local/lib/codecraft/bin/git +# ssh/scp/sftp also work when the container runs as a uid without passwd entry. +COPY --chmod=755 docker/ssh-nss /usr/local/bin/ssh +COPY --chmod=755 docker/ssh-nss /usr/local/bin/scp +COPY --chmod=755 docker/ssh-nss /usr/local/bin/sftp COPY server/resources /app/resources # The agent's home lives on the data volume: tools it installs in user space # (pip --user, pipx, rustup, npm -g, downloaded binaries) survive rebuilds. diff --git a/docker/ssh-nss b/docker/ssh-nss new file mode 100755 index 0000000..d5d2731 --- /dev/null +++ b/docker/ssh-nss @@ -0,0 +1,13 @@ +#!/bin/sh +# ssh, scp and sftp for containers that run as an arbitrary uid (docker-compose `user:`): +# OpenSSH refuses to start without a passwd entry for the uid ("No user exists for uid N"). +# Without an entry, nss_wrapper provides one for this process; otherwise nothing changes. +if ! getent passwd "$(id -u)" >/dev/null 2>&1; then + d=${TMPDIR:-/tmp}/codecraft-nss-$(id -u) + mkdir -p "$d" 2>/dev/null + printf 'codecraft:x:%s:%s:CodeCraft:%s:/bin/sh\n' "$(id -u)" "$(id -g)" "${HOME:-/}" > "$d/passwd" + printf 'codecraft:x:%s:\n' "$(id -g)" > "$d/group" + export NSS_WRAPPER_PASSWD="$d/passwd" NSS_WRAPPER_GROUP="$d/group" + export LD_PRELOAD="libnss_wrapper.so${LD_PRELOAD:+:$LD_PRELOAD}" +fi +exec "/usr/bin/$(basename "$0")" "$@" diff --git a/server/src/sshkeys.rs b/server/src/sshkeys.rs index abb393c..b777177 100644 --- a/server/src/sshkeys.rs +++ b/server/src/sshkeys.rs @@ -126,7 +126,10 @@ pub fn write_files(app: &App, user: &User, s: &SshSettings) -> Result<()> { chmod(&host_cfg, 0o600)?; for (bin, config) in [(bin_dir(&home), format!("'{}'", host_cfg.display())), (home.join(SANDBOX_BIN), "\"$HOME/.ssh/config-sandbox\"".to_string())] { std::fs::create_dir_all(&bin)?; - for (name, real) in [("ssh", "/usr/bin/ssh"), ("scp", "/usr/bin/scp"), ("sftp", "/usr/bin/sftp")] { + for name in ["ssh", "scp", "sftp"] { + // The image's /usr/local/bin wrappers cope with a uid without passwd entry. + let local = format!("/usr/local/bin/{name}"); + let real = if Path::new(&local).exists() { local } else { format!("/usr/bin/{name}") }; let p = bin.join(name); std::fs::write(&p, format!("#!/bin/sh\nexec {real} -F {config} \"$@\"\n"))?; chmod(&p, 0o755)?; diff --git a/web/src/whatsnew.ts b/web/src/whatsnew.ts index de7a500..fb0c0c1 100644 --- a/web/src/whatsnew.ts +++ b/web/src/whatsnew.ts @@ -3,6 +3,10 @@ * under "unreleased"; scripts/release.sh renames the key to the release version. */ export const WHATS_NEW: Record = { + unreleased: { + en: ["ssh/scp/sftp now work in the container (fixes \"No user exists for uid\")"], + de: ["ssh/scp/sftp funktionieren jetzt im Container (behebt \"No user exists for uid\")"], + }, "202611-00033": { en: ["SSH keys in Settings → Account: agents, terminal and git use them to reach other servers"], de: ["SSH-Schlüssel unter Einstellungen → Account: Agenten, Terminal und Git nutzen sie für den Zugriff auf andere Server"],