chore: use Nix profiles in sandbox Docker image (#8140)
* feat: use Nix profiles in sandbox Docker image Replace manual tool installs (rustup, nodesource, curl installers) in sandbox-image/Dockerfile.sandbox with a single `nix profile install .#sandbox`. All tools (Rust, Node, Bun, Deno, Go, gh, sqlx-cli, cargo-watch, Chromium, Playwright, etc.) are now managed declaratively via flake.nix. - Add `packages.sandbox` and `packages.sandbox-full` buildEnv outputs to flake.nix - Add `sandbox-env` helper script for browser tooling env vars - Update playwrightWrapper to export PLAYWRIGHT_BROWSERS_PATH - Rewrite Dockerfile.sandbox: Nix replaces ~50 lines of manual installs - Update entrypoint.sh to source Nix profile PATH - Delete deprecated root Dockerfile.sandbox Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: sandbox image runs as non-root user with wmdev - Rewrite entrypoint.sh to start PostgreSQL as current user (no chown/su needed), fixing "Operation not permitted" when wmdev runs containers with --user - Add chmod -R 777 /root and passwd entry for UID 1000 so non-root containers can access bashrc, nix-profile, and tool configs - Remove apt postgresql server (Nix profile provides it) - Fix bash history expansion errors from literal `!` in system prompt - Fix asciinema path reference (available on PATH, not hardcoded) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: wrap pkg-config in sandbox profiles to bake in Nix search path Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add openssh-client and sudo to sandbox image for full root access Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: use useradd instead of manual passwd entry for sandbox agent user Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -53,12 +53,11 @@ profiles:
|
|||||||
--endpoint-url "$(printenv R2_ENDPOINT)"
|
--endpoint-url "$(printenv R2_ENDPOINT)"
|
||||||
3) The public URL will be:
|
3) The public URL will be:
|
||||||
$(printenv R2_PUBLIC_URL)/<branch>/screenshot.png
|
$(printenv R2_PUBLIC_URL)/<branch>/screenshot.png
|
||||||
4) Include screenshots in PR descriptions as markdown images:
|
4) Include in PR descriptions using markdown image syntax.
|
||||||
/<branch>/screenshot.png)
|
|
||||||
|
|
||||||
--- Terminal Recordings (asciinema) ---
|
--- Terminal Recordings (asciinema) ---
|
||||||
You can record terminal sessions and upload them for sharing.
|
You can record terminal sessions and upload them for sharing.
|
||||||
asciinema is pre-installed at /usr/local/bin/asciinema.
|
asciinema is available on PATH.
|
||||||
|
|
||||||
1) Write a shell script with the commands to demo. Add sleep
|
1) Write a shell script with the commands to demo. Add sleep
|
||||||
delays for readable pacing:
|
delays for readable pacing:
|
||||||
@@ -98,8 +97,7 @@ profiles:
|
|||||||
4) The public URL will be:
|
4) The public URL will be:
|
||||||
$(printenv R2_PUBLIC_URL)/<branch>/diagram.svg
|
$(printenv R2_PUBLIC_URL)/<branch>/diagram.svg
|
||||||
|
|
||||||
5) Include in PR descriptions as markdown images:
|
5) Include in PR descriptions using markdown image syntax.
|
||||||
/<branch>/diagram.svg)
|
|
||||||
|
|
||||||
linkedRepos:
|
linkedRepos:
|
||||||
- repo: windmill-labs/windmill-ee-private
|
- repo: windmill-labs/windmill-ee-private
|
||||||
|
|||||||
@@ -1,231 +0,0 @@
|
|||||||
FROM debian:bookworm-slim
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
||||||
curl \
|
|
||||||
ca-certificates \
|
|
||||||
git \
|
|
||||||
iptables \
|
|
||||||
gosu \
|
|
||||||
sudo \
|
|
||||||
unzip \
|
|
||||||
# Rust native build deps (for cargo check)
|
|
||||||
pkg-config \
|
|
||||||
cmake \
|
|
||||||
clang \
|
|
||||||
mold \
|
|
||||||
libtool \
|
|
||||||
libssl-dev \
|
|
||||||
libxml2-dev \
|
|
||||||
libxmlsec1-dev \
|
|
||||||
libxslt1-dev \
|
|
||||||
libffi-dev \
|
|
||||||
zlib1g-dev \
|
|
||||||
libcurl4-openssl-dev \
|
|
||||||
libclang-dev \
|
|
||||||
libkrb5-dev \
|
|
||||||
libsasl2-dev \
|
|
||||||
# PostgreSQL (for local DB during development)
|
|
||||||
postgresql \
|
|
||||||
postgresql-client \
|
|
||||||
# Node.js 22 (for npm run check / frontend dev)
|
|
||||||
&& curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
|
|
||||||
&& apt-get install -y --no-install-recommends nodejs \
|
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
|
||||||
# Container runs as arbitrary UIDs (--user uid:gid). These three lines make
|
|
||||||
# sudo work for any UID:
|
|
||||||
# 1) NOPASSWD rule so sudo never prompts for a password
|
|
||||||
# 2) Writable passwd/group so the entrypoint can register the dynamic UID
|
|
||||||
# 3) Writable shadow so unix_chkpwd can validate the account (without this,
|
|
||||||
# sudo fails with "account validation failure, is your account locked?")
|
|
||||||
&& echo "ALL ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/sandbox \
|
|
||||||
&& chmod 0440 /etc/sudoers.d/sandbox \
|
|
||||||
&& chmod 666 /etc/passwd /etc/group /etc/shadow
|
|
||||||
|
|
||||||
# ── GitHub CLI (for PR creation) ──────────────────────────────────────────────
|
|
||||||
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
|
|
||||||
-o /usr/share/keyrings/githubcli-archive-keyring.gpg \
|
|
||||||
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
|
|
||||||
> /etc/apt/sources.list.d/github-cli.list \
|
|
||||||
&& apt-get update && apt-get install -y --no-install-recommends gh \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# ── Rust toolchain ────────────────────────────────────────────────────────────
|
|
||||||
# Install under /usr/local/lib/ so bins are world-readable with default umask.
|
|
||||||
# CARGO_HOME is overridden to /tmp/.cargo at the end for mutable runtime state.
|
|
||||||
ENV RUSTUP_HOME=/usr/local/lib/rustup CARGO_HOME=/usr/local/lib/cargo
|
|
||||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
|
|
||||||
sh -s -- -y --default-toolchain stable --profile minimal && \
|
|
||||||
ln -s /usr/local/lib/cargo/bin/* /usr/local/bin/
|
|
||||||
RUN cargo install sqlx-cli --no-default-features --features native-tls,postgres && \
|
|
||||||
cargo install cargo-watch && \
|
|
||||||
cargo install --locked --git https://github.com/asciinema/asciinema && \
|
|
||||||
ln -sf /usr/local/lib/cargo/bin/sqlx /usr/local/bin/sqlx && \
|
|
||||||
ln -sf /usr/local/lib/cargo/bin/cargo-watch /usr/local/bin/cargo-watch && \
|
|
||||||
ln -sf /usr/local/lib/cargo/bin/asciinema /usr/local/bin/asciinema
|
|
||||||
|
|
||||||
# ── Register dynamic runtime users ───────────────────────────────────────────
|
|
||||||
RUN cat <<'SCRIPT' > /usr/local/bin/register-dynamic-user.sh
|
|
||||||
#!/bin/sh
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
uid="${1:-}"
|
|
||||||
gid="${2:-}"
|
|
||||||
|
|
||||||
if [ -z "$uid" ] || [ -z "$gid" ]; then
|
|
||||||
echo "register-dynamic-user: usage: register-dynamic-user <uid> <gid>" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! getent group "$gid" >/dev/null 2>&1; then
|
|
||||||
echo "sandbox:x:${gid}:" >> /etc/group
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! getent passwd "$uid" >/dev/null 2>&1; then
|
|
||||||
echo "sandbox:x:${uid}:${gid}:sandbox:/tmp:/bin/sh" >> /etc/passwd
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Add a shadow entry ("*" = no password) so unix_chkpwd doesn't reject sudo.
|
|
||||||
if ! grep -q "^sandbox:" /etc/shadow 2>/dev/null; then
|
|
||||||
echo "sandbox:*:19000:0:99999:7:::" >> /etc/shadow
|
|
||||||
fi
|
|
||||||
SCRIPT
|
|
||||||
RUN chmod +x /usr/local/bin/register-dynamic-user.sh
|
|
||||||
|
|
||||||
# ── Network init script (iptables firewall + privilege drop) ──────────────────
|
|
||||||
RUN cat <<'SCRIPT' > /usr/local/bin/network-init.sh
|
|
||||||
#!/bin/bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
if [ -n "${WM_PROXY_HOST:-}" ] && [ -n "${WM_PROXY_PORT:-}" ]; then
|
|
||||||
# Resolve hostnames to ALL IPs (multi-A records, round-robin DNS)
|
|
||||||
PROXY_IPS=$(getent ahostsv4 "$WM_PROXY_HOST" | awk '{print $1}' | sort -u)
|
|
||||||
RPC_HOST="${WM_RPC_HOST:-$WM_PROXY_HOST}"
|
|
||||||
RPC_IPS=$(getent ahostsv4 "$RPC_HOST" | awk '{print $1}' | sort -u)
|
|
||||||
|
|
||||||
if [ -z "$PROXY_IPS" ] || [ -z "$RPC_IPS" ]; then
|
|
||||||
echo "network-init: failed to resolve proxy/RPC host" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# IPv4: default deny outbound
|
|
||||||
iptables -P OUTPUT DROP
|
|
||||||
iptables -A OUTPUT -o lo -j ACCEPT
|
|
||||||
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
|
||||||
|
|
||||||
# Allow DNS (UDP/TCP 53) to configured nameservers.
|
|
||||||
if [ -f /etc/resolv.conf ]; then
|
|
||||||
grep '^nameserver' /etc/resolv.conf | awk '{print $2}' | while read -r ns; do
|
|
||||||
iptables -A OUTPUT -d "$ns" -p udp --dport 53 -j ACCEPT
|
|
||||||
iptables -A OUTPUT -d "$ns" -p tcp --dport 53 -j ACCEPT
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Allow ALL resolved proxy IPs (handles multi-A DNS)
|
|
||||||
for ip in $PROXY_IPS; do
|
|
||||||
iptables -A OUTPUT -d "$ip" -p tcp --dport "$WM_PROXY_PORT" -j ACCEPT
|
|
||||||
done
|
|
||||||
|
|
||||||
# Allow ALL resolved RPC IPs
|
|
||||||
if [ -n "${WM_RPC_PORT:-}" ]; then
|
|
||||||
for ip in $RPC_IPS; do
|
|
||||||
iptables -A OUTPUT -d "$ip" -p tcp --dport "$WM_RPC_PORT" -j ACCEPT
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Reject (not drop) everything else to fail fast instead of hanging
|
|
||||||
iptables -A OUTPUT -j REJECT
|
|
||||||
|
|
||||||
# IPv6: block entirely to prevent leaks (fail closed)
|
|
||||||
if ip6tables -L -n >/dev/null 2>&1; then
|
|
||||||
ip6tables -P OUTPUT DROP
|
|
||||||
ip6tables -A OUTPUT -o lo -j ACCEPT
|
|
||||||
ip6tables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
|
||||||
ip6tables -A OUTPUT -j REJECT
|
|
||||||
else
|
|
||||||
if ! sysctl -w net.ipv6.conf.all.disable_ipv6=1 2>/dev/null; then
|
|
||||||
echo "network-init: failed to block IPv6 (neither ip6tables nor sysctl available)" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Add sandbox user/group so sudo works after dropping privileges.
|
|
||||||
if [ -z "${WM_TARGET_UID:-}" ] || [ -z "${WM_TARGET_GID:-}" ]; then
|
|
||||||
echo "network-init: WM_TARGET_UID and WM_TARGET_GID are required" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
/usr/local/bin/register-dynamic-user.sh "${WM_TARGET_UID}" "${WM_TARGET_GID}"
|
|
||||||
|
|
||||||
# Fix PTY ownership so the unprivileged user can read/write the terminal.
|
|
||||||
if [ -t 0 ]; then
|
|
||||||
chown "${WM_TARGET_UID}:${WM_TARGET_GID}" "$(tty)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Drop privileges and exec the user command.
|
|
||||||
exec gosu "${WM_TARGET_UID}:${WM_TARGET_GID}" env HOME=/tmp "$@"
|
|
||||||
SCRIPT
|
|
||||||
RUN chmod +x /usr/local/bin/network-init.sh
|
|
||||||
|
|
||||||
# ── workmux (sandbox RPC) ────────────────────────────────────────────────────
|
|
||||||
RUN curl -fsSL https://raw.githubusercontent.com/raine/workmux/main/scripts/install.sh | bash
|
|
||||||
|
|
||||||
# ── Claude Code ───────────────────────────────────────────────────────────────
|
|
||||||
RUN curl -fsSL https://claude.ai/install.sh | bash && \
|
|
||||||
target="$(readlink -f /root/.local/bin/claude)" && \
|
|
||||||
mv /root/.local/share/claude /usr/local/lib/claude && \
|
|
||||||
ln -s "/usr/local/lib/claude/versions/$(basename "$target")" /usr/local/bin/claude && \
|
|
||||||
mkdir -p /tmp/.local/bin && \
|
|
||||||
ln -s /usr/local/bin/claude /tmp/.local/bin/claude && \
|
|
||||||
chmod -R a+rwX /tmp/.local
|
|
||||||
|
|
||||||
# ── Codex ─────────────────────────────────────────────────────────────────────
|
|
||||||
RUN npm i -g @openai/codex
|
|
||||||
|
|
||||||
# ── Bun ───────────────────────────────────────────────────────────────────────
|
|
||||||
ENV BUN_INSTALL=/usr/local/lib/bun
|
|
||||||
RUN curl -fsSL https://bun.sh/install | bash && \
|
|
||||||
ln -s /usr/local/lib/bun/bin/bun /usr/local/bin/bun && \
|
|
||||||
ln -s /usr/local/lib/bun/bin/bunx /usr/local/bin/bunx
|
|
||||||
|
|
||||||
# ── Playwright + Chromium (for screenshots) ──────────────────────────────────
|
|
||||||
ENV PLAYWRIGHT_BROWSERS_PATH=/usr/local/lib/playwright-browsers
|
|
||||||
RUN bun add -g @playwright/test \
|
|
||||||
&& bunx playwright install chromium --with-deps \
|
|
||||||
&& chmod -R a+rwX /usr/local/lib/playwright-browsers \
|
|
||||||
&& chmod -R a+rwX /usr/local/lib/bun/install \
|
|
||||||
&& rm -rf /var/lib/apt/lists/* /tmp/bunx-*
|
|
||||||
|
|
||||||
|
|
||||||
# ── Runtime env for arbitrary UID ─────────────────────────────────────────────
|
|
||||||
# Mutable state goes to /tmp (writable by any UID). Toolchains stay read-only.
|
|
||||||
ENV CARGO_HOME=/tmp/.cargo BUN_TMPDIR=/tmp
|
|
||||||
|
|
||||||
# ── Entrypoint ────────────────────────────────────────────────────────────────
|
|
||||||
RUN cat <<'ENTRY' > /usr/local/bin/entrypoint.sh
|
|
||||||
#!/bin/sh
|
|
||||||
/usr/local/bin/register-dynamic-user.sh "$(id -u)" "$(id -g)"
|
|
||||||
|
|
||||||
# Start PostgreSQL (unix socket in /tmp, owned by postgres user)
|
|
||||||
mkdir -p /tmp/pgdata && sudo chown postgres:postgres /tmp/pgdata
|
|
||||||
if [ ! -f /tmp/pgdata/PG_VERSION ]; then
|
|
||||||
sudo -u postgres /usr/lib/postgresql/15/bin/initdb -D /tmp/pgdata --auth=trust
|
|
||||||
fi
|
|
||||||
sudo -u postgres /usr/lib/postgresql/15/bin/pg_ctl -D /tmp/pgdata -l /tmp/pg.log start -o "-k /tmp"
|
|
||||||
sudo -u postgres psql -h /tmp -c "CREATE ROLE sandbox SUPERUSER LOGIN" 2>/dev/null || true
|
|
||||||
sudo -u postgres createdb -h /tmp windmill 2>/dev/null || true
|
|
||||||
|
|
||||||
# Run database migrations so sqlx compile-time checks work
|
|
||||||
if [ -d "$PWD/backend/migrations" ]; then
|
|
||||||
DATABASE_URL="postgres://sandbox@localhost/windmill?host=/tmp" \
|
|
||||||
sqlx migrate run --source "$PWD/backend/migrations" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Install frontend dependencies and generate backend client
|
|
||||||
if [ -d "$PWD/frontend" ]; then
|
|
||||||
(cd "$PWD/frontend" && npm install && npm run generate-backend-client) 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
exec "$@"
|
|
||||||
ENTRY
|
|
||||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
|
||||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
|
||||||
49
flake.nix
49
flake.nix
@@ -342,11 +342,60 @@
|
|||||||
|
|
||||||
# Wrapper for the Nix-provided playwright CLI (version-matched to its browsers)
|
# Wrapper for the Nix-provided playwright CLI (version-matched to its browsers)
|
||||||
playwrightWrapper = pkgs.writeShellScriptBin "playwright" ''
|
playwrightWrapper = pkgs.writeShellScriptBin "playwright" ''
|
||||||
|
export PLAYWRIGHT_BROWSERS_PATH="${pkgs.playwright-driver.browsers}"
|
||||||
exec ${pkgs.nodejs}/bin/node ${pkgs.playwright-driver}/cli.js "$@"
|
exec ${pkgs.nodejs}/bin/node ${pkgs.playwright-driver}/cli.js "$@"
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# sandbox-env script — outputs env vars for browser tooling
|
||||||
|
# Usage: eval "$(sandbox-env)"
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
|
||||||
|
sandboxEnvScript = pkgs.writeShellScriptBin "sandbox-env" ''
|
||||||
|
echo "export PLAYWRIGHT_BROWSERS_PATH=${pkgs.playwright-driver.browsers}"
|
||||||
|
echo "export PUPPETEER_EXECUTABLE_PATH=${pkgs.chromium}/bin/chromium"
|
||||||
|
echo "export PUPPETEER_SKIP_DOWNLOAD=true"
|
||||||
|
'';
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# pkg-config wrapper — bakes in the Nix pkg-config search path
|
||||||
|
# so sandbox profiles (buildEnv) work without setting env vars.
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
|
||||||
|
pkgConfigWrapper = pkgs.writeShellScriptBin "pkg-config" ''
|
||||||
|
export PKG_CONFIG_PATH="${pkgConfigPath}:$PKG_CONFIG_PATH"
|
||||||
|
exec ${pkgs.pkg-config}/bin/pkg-config "$@"
|
||||||
|
'';
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# Installable sandbox profiles (nix profile install .#sandbox)
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
|
||||||
|
sandboxEnv = pkgs.buildEnv {
|
||||||
|
name = "windmill-sandbox";
|
||||||
|
paths = coreBuildInputs ++ helperScriptsBase
|
||||||
|
++ [ playwrightWrapper sandboxEnvScript pkgConfigWrapper pkgs.chromium ];
|
||||||
|
};
|
||||||
|
|
||||||
|
sandboxFullEnv = pkgs.buildEnv {
|
||||||
|
name = "windmill-sandbox-full";
|
||||||
|
paths = coreBuildInputs ++ extraRuntimes
|
||||||
|
++ helperScriptsBase ++ helperScriptsFull
|
||||||
|
++ [ playwrightWrapper sandboxEnvScript pkgConfigWrapper pkgs.chromium
|
||||||
|
pkgs.cargo-sweep pkgs.xcaddy pkgs.nsjail ];
|
||||||
|
};
|
||||||
|
|
||||||
in {
|
in {
|
||||||
|
|
||||||
|
# =============================================================
|
||||||
|
# Installable profiles — for Docker / nix profile install
|
||||||
|
# Usage: nix profile install .#sandbox
|
||||||
|
# =============================================================
|
||||||
|
|
||||||
|
packages.sandbox = sandboxEnv;
|
||||||
|
packages.sandbox-full = sandboxFullEnv;
|
||||||
|
packages.default = sandboxEnv;
|
||||||
|
|
||||||
# =============================================================
|
# =============================================================
|
||||||
# default — daily driver for backend + frontend development
|
# default — daily driver for backend + frontend development
|
||||||
# Usage: nix develop
|
# Usage: nix develop
|
||||||
|
|||||||
@@ -1,61 +1,46 @@
|
|||||||
FROM debian:bookworm-slim
|
FROM debian:bookworm-slim
|
||||||
|
|
||||||
# ── System packages ───────────────────────────────────────────────────────────
|
# ── Minimal system deps ──────────────────────────────────────────────────────
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
curl ca-certificates git unzip openssh-client \
|
ca-certificates curl git openssh-client sudo xz-utils \
|
||||||
# Rust native build deps
|
# PostgreSQL client (server provided by Nix profile)
|
||||||
pkg-config cmake clang mold libtool \
|
postgresql-client \
|
||||||
libssl-dev libxml2-dev libxmlsec1-dev libxslt1-dev \
|
|
||||||
libffi-dev zlib1g-dev libcurl4-openssl-dev libclang-dev \
|
|
||||||
libkrb5-dev libsasl2-dev \
|
|
||||||
# PostgreSQL
|
|
||||||
postgresql postgresql-client \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# ── Node.js 22 ────────────────────────────────────────────────────────────────
|
# ── Nix (single-user, Determinate installer) ────────────────────────────────
|
||||||
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
|
RUN curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | \
|
||||||
&& apt-get install -y --no-install-recommends nodejs \
|
sh -s -- install linux --no-confirm --init none
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
ENV PATH="/root/.nix-profile/bin:/nix/var/nix/profiles/default/bin:$PATH"
|
||||||
|
|
||||||
# ── GitHub CLI ────────────────────────────────────────────────────────────────
|
# ── Install default sandbox profile ─────────────────────────────────────────
|
||||||
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
|
COPY flake.nix flake.lock /tmp/flake/
|
||||||
-o /usr/share/keyrings/githubcli-archive-keyring.gpg \
|
RUN cd /tmp/flake && nix profile install .#sandbox \
|
||||||
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
|
&& rm -rf /tmp/flake
|
||||||
> /etc/apt/sources.list.d/github-cli.list \
|
|
||||||
&& apt-get update && apt-get install -y --no-install-recommends gh \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# ── Rust toolchain ────────────────────────────────────────────────────────────
|
# ── Browser env (Puppeteer / Mermaid point to Nix Chromium) ─────────────────
|
||||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
|
ENV PUPPETEER_SKIP_DOWNLOAD="true"
|
||||||
sh -s -- -y --default-toolchain stable --profile minimal
|
RUN printf '{"args":["--no-sandbox","--disable-setuid-sandbox"],"executablePath":"%s"}\n' \
|
||||||
ENV PATH="/root/.cargo/bin:$PATH"
|
"$(readlink -f "$(which chromium)")" > /root/.puppeteerrc.json
|
||||||
|
|
||||||
RUN cargo install sqlx-cli --no-default-features --features native-tls,postgres \
|
# ── Claude Code ──────────────────────────────────────────────────────────────
|
||||||
&& cargo install cargo-watch
|
|
||||||
|
|
||||||
# ── Bun ───────────────────────────────────────────────────────────────────────
|
|
||||||
RUN curl -fsSL https://bun.sh/install | bash
|
|
||||||
ENV PATH="/root/.bun/bin:$PATH"
|
|
||||||
|
|
||||||
# ── Playwright + Chromium ─────────────────────────────────────────────────────
|
|
||||||
RUN bun add -g @playwright/test \
|
|
||||||
&& bunx playwright install chromium --with-deps \
|
|
||||||
&& rm -rf /var/lib/apt/lists/* /tmp/bunx-*
|
|
||||||
|
|
||||||
# ── Claude Code ───────────────────────────────────────────────────────────────
|
|
||||||
RUN curl -fsSL https://claude.ai/install.sh | bash
|
RUN curl -fsSL https://claude.ai/install.sh | bash
|
||||||
ENV PATH="/root/.local/bin:$PATH"
|
ENV PATH="/root/.local/bin:$PATH"
|
||||||
|
|
||||||
# ── Codex ─────────────────────────────────────────────────────────────────────
|
# ── npm globals (install to /usr/local so bins land on PATH) ─────────────────
|
||||||
|
ENV NPM_CONFIG_PREFIX=/usr/local
|
||||||
RUN npm i -g @openai/codex
|
RUN npm i -g @openai/codex
|
||||||
|
RUN PUPPETEER_SKIP_DOWNLOAD=true npm i -g @mermaid-js/mermaid-cli
|
||||||
|
|
||||||
# ── Mermaid CLI ───────────────────────────────────────────────────────────────
|
# ── Runtime env ──────────────────────────────────────────────────────────────
|
||||||
# Skip puppeteer's own Chromium download; reuse the one Playwright already installed above
|
ENV CARGO_HOME=/tmp/.cargo
|
||||||
RUN PUPPETEER_SKIP_DOWNLOAD=true npm i -g @mermaid-js/mermaid-cli \
|
|
||||||
&& CHROMIUM=$(find /root/.cache/ms-playwright -name 'chrome' -executable -type f | head -1) \
|
|
||||||
&& printf '{"args":["--no-sandbox","--disable-setuid-sandbox"],"executablePath":"%s"}\n' "$CHROMIUM" \
|
|
||||||
> /root/.puppeteerrc.json
|
|
||||||
|
|
||||||
# ── Entrypoint (run explicitly via docker exec, not on container start) ──────
|
# ── Allow non-root UID (--user) to access tools installed in /root ───────────
|
||||||
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
# Give UID 1000 a proper passwd entry, passwordless sudo, and full access to /root
|
||||||
|
RUN chmod -R 777 /root \
|
||||||
|
&& useradd -u 1000 -g 100 -d /root -s /bin/bash -M agent \
|
||||||
|
&& echo "agent ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/agent \
|
||||||
|
&& chmod 0440 /etc/sudoers.d/agent
|
||||||
|
|
||||||
|
# ── Entrypoint ───────────────────────────────────────────────────────────────
|
||||||
|
COPY sandbox-image/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||||
|
|||||||
@@ -1,21 +1,31 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# ── Start PostgreSQL ──────────────────────────────────────────────────────────
|
# ── Nix profile ──────────────────────────────────────────────────────────────
|
||||||
|
export PATH="/root/.nix-profile/bin:/nix/var/nix/profiles/default/bin:$PATH"
|
||||||
|
export CARGO_HOME=/tmp/.cargo
|
||||||
|
|
||||||
|
# ── Browser env (from Nix sandbox profile) ───────────────────────────────────
|
||||||
|
if command -v sandbox-env >/dev/null 2>&1; then
|
||||||
|
eval "$(sandbox-env)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Start PostgreSQL as current user (no root/su needed) ─────────────────────
|
||||||
PGDATA=/tmp/pgdata
|
PGDATA=/tmp/pgdata
|
||||||
mkdir -p "$PGDATA"
|
mkdir -p "$PGDATA"
|
||||||
chown postgres:postgres "$PGDATA"
|
|
||||||
|
|
||||||
if [ ! -f "$PGDATA/PG_VERSION" ]; then
|
if [ ! -f "$PGDATA/PG_VERSION" ]; then
|
||||||
su - postgres -c "/usr/lib/postgresql/15/bin/initdb -D $PGDATA --auth=trust"
|
initdb -D "$PGDATA" --auth=trust
|
||||||
fi
|
fi
|
||||||
su - postgres -c "/usr/lib/postgresql/15/bin/pg_ctl -D $PGDATA -l /tmp/pg.log start -o '-k /tmp'"
|
pg_ctl -D "$PGDATA" -l /tmp/pg.log start -o "-k /tmp"
|
||||||
su - postgres -c "psql -h /tmp -c 'CREATE ROLE root SUPERUSER LOGIN'" 2>/dev/null || true
|
|
||||||
su - postgres -c "createdb -h /tmp windmill" 2>/dev/null || true
|
# Create postgres role and windmill database (idempotent)
|
||||||
|
psql -h /tmp -d postgres -c "CREATE ROLE postgres SUPERUSER LOGIN" 2>/dev/null || true
|
||||||
|
createdb -h /tmp windmill 2>/dev/null || true
|
||||||
|
|
||||||
# ── Run migrations if present ─────────────────────────────────────────────────
|
# ── Run migrations if present ─────────────────────────────────────────────────
|
||||||
if [ -d "$PWD/backend/migrations" ]; then
|
if [ -d "$PWD/backend/migrations" ]; then
|
||||||
DATABASE_URL="postgres://root@localhost/windmill?host=/tmp" \
|
DATABASE_URL="postgres://postgres@localhost/windmill?host=/tmp" \
|
||||||
sqlx migrate run --source "$PWD/backend/migrations" 2>/dev/null || true
|
sqlx migrate run --source "$PWD/backend/migrations" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user