* refactor: rewrite flake.nix from scratch for clarity and modularity Rewrite the Nix flake with clean separation of concerns, organized let-bindings, and 4 purpose-specific devShells instead of a monolithic default shell with broken package outputs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add CLI tools to default devShell (gh, aws, playwright, mermaid, asciinema) Add tools needed for AI agent workflows and dev tooling: - gh (GitHub CLI) - awscli2 - asciinema (terminal recording) - playwright-driver with Nix-managed browsers - mermaid-cli (diagram generation) Playwright browsers are provided via nixpkgs' playwright-driver.browsers. Mermaid/Puppeteer reuses the headless_shell from the same browser set. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: move wm-minio scripts to default devShell MinIO (local S3) is needed for regular development, not just the full profile. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: use playwright wrapper + chromium for browser tools Replace playwright-driver (library, no CLI) with: - A `playwright` wrapper script that calls the Nix playwright-core CLI (version-matched to its own Nix-provided browsers) - pkgs.chromium for Mermaid/Puppeteer (which respects PUPPETEER_EXECUTABLE_PATH) This fixes playwright screenshot and mermaid diagram generation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: auto-load .env.local from main worktree in all devShells Gitignored files like .env.local don't exist in git worktrees. Add a shared shellHook that resolves back to the main tree via git-common-dir and sources .env.local if present. This ensures AWS credentials and other secrets are available in worktrees. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: replace deprecated pkgs.hostPlatform with stdenv.hostPlatform Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: remove AWS CLI from flake and sandbox images Pastebin is sufficient for screenshot sharing; AWS credentials add unnecessary complexity. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address PR review — ruby mismatch, quoting, shell dedup - Fix pkgs.ruby → pkgs.ruby_3_4 in extraRuntimeVars to match extraRuntimes - Replace $* with "$@" in all helper scripts (wm, wm-build, wm-caddy, wm-bench, wm-cli) to correctly preserve argument boundaries - Extract coreBuildInputs, browserVars, and playwrightWrapper as shared let-bindings to eliminate duplication between default and full shells Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: remove .env.local auto-loading from devShells Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
231 lines
10 KiB
Docker
231 lines
10 KiB
Docker
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"] |