73a7f96056
Both tools are used as part of the secret-management setup in several of the repos this devbox operates on (gitleaks pre-commit hook + git-crypt for selectively-encrypted canonical config). Having them in the container means hooks fire correctly inside instead of warning 'gitleaks not installed' on every commit. git-crypt was already installed via apt in Dockerfile.base (line 58), just unasserted by smoke and unmentioned in user-facing docs. gitleaks is new: Go-compiled binary fetched from GitHub releases via the same /releases/latest redirect-resolution pattern as gosu, fzf, git-lfs, etc. Arch suffix is 'x64' (not 'x86_64' / 'amd64') on this project — flagged in the Dockerfile comment and in AGENTS.md's floated-binaries gotcha list. Adds ~21 MB to the base layer (gitleaks 8.30.1 binary). No variant threshold bumps needed (2500–3700 MB envelope, 21 MB is noise). CHANGES Dockerfile.base — new GITLEAKS_VERSION=latest ARG + install RUN right after the git-lfs block. Multi-arch (linux/amd64=x64, linux/arm64=arm64). Echoes resolved version + runs 'gitleaks version' to fail the build on any install error. scripts/smoke-test.sh — git-crypt and gitleaks added to the 'Resolved component versions' table (printed first thing in CI logs) and to the 'Core binaries' assertion list (run helper). Smoke now fails fast if either binary regresses. README.md — 'What's in the image' tree line names gitleaks alongside the existing git-crypt. AGENTS.md — gitleaks added to the 'GitHub-sourced binaries float by default' list with a new clause flagging project-specific arch-name deviations (gitleaks=x64, bat/eza/zoxide=x86_64/aarch64, gosu= amd64/arm64). Saves the next person from the 'why does this not download' debugging session. CHANGELOG.md — sub-entry under existing Unreleased, before the PI_VERSION/OMOS_VERSION cache-hit fix entry. DOWNSTREAM IMPACT This is a base-layer change — base-decide will compute a fresh base-<hash>, build-base will run (no cache hit), all four variants will rebuild. First real base rebuild since v1.14.50b. Pi-devbox's next FROM base-latest pull picks up gitleaks automatically with no Dockerfile change there. Verified end-to-end on host: gitleaks 8.30.1 21 MB binary extracts cleanly from the URL the Dockerfile constructs and 'gitleaks version' prints '8.30.1'. Holding off on tagging — opencode + pi upstreams unchanged at 1.15.10 and 0.75.5 respectively. Will ride along with the next upstream-bump release rather than burning a base rebuild on a no-upstream-change container-only roll.
356 lines
18 KiB
Docker
356 lines
18 KiB
Docker
# opencode-devbox — base image (variant-independent layers)
|
|
#
|
|
# This Dockerfile produces an image tagged base-<hash>, used as the parent
|
|
# for all four published variants (base, omos, with-pi, omos-with-pi).
|
|
# It contains everything that does not depend on variant-specific
|
|
# build-args (INSTALL_OPENCODE, INSTALL_OMOS, INSTALL_PI). The variant
|
|
# Dockerfile (Dockerfile.variant) FROMs the base and adds only those
|
|
# deltas.
|
|
#
|
|
# The base is rebuilt only when this file or anything it COPYs in
|
|
# changes (rootfs/, entrypoint*.sh). Version bumps to OPENCODE_VERSION,
|
|
# OMOS_VERSION, PI_VERSION, etc. do NOT trigger a base rebuild.
|
|
#
|
|
# To force a base rebuild for fresh apt packages without other code
|
|
# changes, bump the BASE_REBUILD_DATE comment below. The hash is
|
|
# content-addressed over this file, so any byte change invalidates the
|
|
# cache. Recommended cadence: once per release for security updates.
|
|
#
|
|
# BASE_REBUILD_DATE: 2026-05-14 (v1.14.50b — fresh apt + first promote-base-latest)
|
|
#
|
|
# See the project README's "Build pipeline" section for the rationale.
|
|
|
|
ARG DEBIAN_VERSION=trixie-slim
|
|
FROM debian:${DEBIAN_VERSION} AS base
|
|
|
|
ARG TARGETARCH
|
|
|
|
LABEL maintainer="joakimp"
|
|
LABEL description="opencode-devbox — base image (variant-independent)"
|
|
LABEL org.opencontainers.image.source="https://gitea.jordbo.se/joakimp/opencode-devbox"
|
|
|
|
# Avoid interactive prompts during build
|
|
ENV DEBIAN_FRONTEND=noninteractive
|
|
|
|
# ── Core system packages ─────────────────────────────────────────────
|
|
# apt-get upgrade picks up any security/CVE fixes published between
|
|
# debian:trixie-slim base-image rebuilds. Paired with the index update
|
|
# and the install in the same layer so we don't bloat image history.
|
|
RUN apt-get update && \
|
|
apt-get upgrade -y --no-install-recommends && \
|
|
apt-get install -y --no-install-recommends \
|
|
ca-certificates \
|
|
curl \
|
|
wget \
|
|
git \
|
|
openssh-client \
|
|
gnupg \
|
|
jq \
|
|
ripgrep \
|
|
fd-find \
|
|
tree \
|
|
less \
|
|
htop \
|
|
tmux \
|
|
make \
|
|
patch \
|
|
diffutils \
|
|
git-crypt \
|
|
age \
|
|
file \
|
|
sudo \
|
|
locales \
|
|
procps \
|
|
unzip \
|
|
gcc \
|
|
g++ \
|
|
rsync \
|
|
python3-pip \
|
|
python3-venv \
|
|
&& ln -s /usr/bin/fdfind /usr/local/bin/fd \
|
|
&& apt-get clean \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# ── Go-compiled tools (install from GitHub to avoid CVEs in Debian's old Go builds)
|
|
#
|
|
# Version policy for the binaries below:
|
|
# • Default is `latest` — resolved at build time by following the
|
|
# /releases/latest redirect on GitHub and reading the tag from the
|
|
# Location header. This means every base rebuild picks up the newest
|
|
# upstream release, with no risk of running months-old CVE-affected
|
|
# binaries.
|
|
# • Explicit pins still work: pass `--build-arg GOSU_VERSION=1.19` etc.
|
|
# • Resolved versions are printed during build and re-checked by the
|
|
# smoke test (scripts/smoke-test.sh), so drift is visible in CI logs.
|
|
|
|
# gosu — privilege de-escalation
|
|
ARG GOSU_VERSION=latest
|
|
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "amd64" ;; arm64) echo "arm64" ;; *) echo "amd64" ;; esac) && \
|
|
V="${GOSU_VERSION}" && \
|
|
if [ "$V" = "latest" ]; then \
|
|
V=$(curl -sI --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/tianon/gosu/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
|
|
fi && \
|
|
V="${V#v}" && \
|
|
[ -n "$V" ] && \
|
|
echo "Installing gosu ${V}" && \
|
|
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/tianon/gosu/releases/download/${V}/gosu-${ARCH}" -o /usr/local/bin/gosu && \
|
|
chmod +x /usr/local/bin/gosu && \
|
|
gosu --version
|
|
|
|
# fzf — fuzzy finder
|
|
ARG FZF_VERSION=latest
|
|
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "amd64" ;; arm64) echo "arm64" ;; *) echo "amd64" ;; esac) && \
|
|
V="${FZF_VERSION}" && \
|
|
if [ "$V" = "latest" ]; then \
|
|
V=$(curl -sI --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/junegunn/fzf/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
|
|
fi && \
|
|
V="${V#v}" && \
|
|
[ -n "$V" ] && \
|
|
echo "Installing fzf ${V}" && \
|
|
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/junegunn/fzf/releases/download/v${V}/fzf-${V}-linux_${ARCH}.tar.gz" | tar -xz -C /usr/local/bin fzf && \
|
|
fzf --version
|
|
|
|
# git-lfs — Git Large File Storage
|
|
ARG GIT_LFS_VERSION=latest
|
|
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "amd64" ;; arm64) echo "arm64" ;; *) echo "amd64" ;; esac) && \
|
|
V="${GIT_LFS_VERSION}" && \
|
|
if [ "$V" = "latest" ]; then \
|
|
V=$(curl -sI --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/git-lfs/git-lfs/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
|
|
fi && \
|
|
V="${V#v}" && \
|
|
[ -n "$V" ] && \
|
|
echo "Installing git-lfs ${V}" && \
|
|
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/git-lfs/git-lfs/releases/download/v${V}/git-lfs-linux-${ARCH}-v${V}.tar.gz" | tar -xz -C /tmp && \
|
|
install /tmp/git-lfs-${V}/git-lfs /usr/local/bin/git-lfs && \
|
|
rm -rf /tmp/git-lfs-${V} && \
|
|
git lfs install --system && \
|
|
git-lfs --version
|
|
|
|
# gitleaks — secret scanner (used as a pre-commit hook in several of the
|
|
# repos this devbox is meant to operate on; pairs with git-crypt below).
|
|
# Distributed as a Go-compiled tarball; arch suffix is `x64` (not `x86_64`
|
|
# or `amd64`) on this project — mind the deviation from the surrounding
|
|
# tools' naming.
|
|
ARG GITLEAKS_VERSION=latest
|
|
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x64" ;; arm64) echo "arm64" ;; *) echo "x64" ;; esac) && \
|
|
V="${GITLEAKS_VERSION}" && \
|
|
if [ "$V" = "latest" ]; then \
|
|
V=$(curl -sI --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/gitleaks/gitleaks/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
|
|
fi && \
|
|
V="${V#v}" && \
|
|
[ -n "$V" ] && \
|
|
echo "Installing gitleaks ${V}" && \
|
|
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/gitleaks/gitleaks/releases/download/v${V}/gitleaks_${V}_linux_${ARCH}.tar.gz" | tar -xz -C /usr/local/bin gitleaks && \
|
|
chmod +x /usr/local/bin/gitleaks && \
|
|
gitleaks version
|
|
|
|
# neovim — modern text editor
|
|
ARG NVIM_VERSION=latest
|
|
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "arm64" ;; *) echo "x86_64" ;; esac) && \
|
|
V="${NVIM_VERSION}" && \
|
|
if [ "$V" = "latest" ]; then \
|
|
V=$(curl -sI --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/neovim/neovim/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
|
|
fi && \
|
|
V="${V#v}" && \
|
|
[ -n "$V" ] && \
|
|
echo "Installing neovim ${V}" && \
|
|
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/neovim/neovim/releases/download/v${V}/nvim-linux-${ARCH}.tar.gz" | tar -xz -C /opt && \
|
|
ln -s /opt/nvim-linux-${ARCH}/bin/nvim /usr/local/bin/nvim && \
|
|
nvim --version | head -1
|
|
|
|
# bat — syntax-highlighted cat replacement
|
|
ARG BAT_VERSION=latest
|
|
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \
|
|
V="${BAT_VERSION}" && \
|
|
if [ "$V" = "latest" ]; then \
|
|
V=$(curl -sI --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/sharkdp/bat/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
|
|
fi && \
|
|
V="${V#v}" && \
|
|
[ -n "$V" ] && \
|
|
echo "Installing bat ${V}" && \
|
|
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/sharkdp/bat/releases/download/v${V}/bat-v${V}-${ARCH}-unknown-linux-musl.tar.gz" | tar -xz -C /tmp && \
|
|
install /tmp/bat-v${V}-${ARCH}-unknown-linux-musl/bat /usr/local/bin/bat && \
|
|
rm -rf /tmp/bat-v${V}-* && \
|
|
bat --version
|
|
|
|
# eza — modern ls replacement
|
|
ARG EZA_VERSION=latest
|
|
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \
|
|
V="${EZA_VERSION}" && \
|
|
if [ "$V" = "latest" ]; then \
|
|
V=$(curl -sI --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/eza-community/eza/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
|
|
fi && \
|
|
V="${V#v}" && \
|
|
[ -n "$V" ] && \
|
|
echo "Installing eza ${V}" && \
|
|
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/eza-community/eza/releases/download/v${V}/eza_${ARCH}-unknown-linux-gnu.tar.gz" | tar -xz -C /usr/local/bin && \
|
|
eza --version | head -1
|
|
|
|
# zoxide — smarter cd command
|
|
ARG ZOXIDE_VERSION=latest
|
|
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \
|
|
V="${ZOXIDE_VERSION}" && \
|
|
if [ "$V" = "latest" ]; then \
|
|
V=$(curl -sI --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/ajeetdsouza/zoxide/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
|
|
fi && \
|
|
V="${V#v}" && \
|
|
[ -n "$V" ] && \
|
|
echo "Installing zoxide ${V}" && \
|
|
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/ajeetdsouza/zoxide/releases/download/v${V}/zoxide-${V}-${ARCH}-unknown-linux-musl.tar.gz" | tar -xz -C /usr/local/bin zoxide && \
|
|
zoxide --version
|
|
|
|
# uv — fast Python package manager (replaces pip, venv, pyenv)
|
|
# Note: uv releases don't prefix tags with "v" (e.g. tag is "0.11.8").
|
|
ARG UV_VERSION=latest
|
|
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \
|
|
V="${UV_VERSION}" && \
|
|
if [ "$V" = "latest" ]; then \
|
|
V=$(curl -sI --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/astral-sh/uv/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
|
|
fi && \
|
|
V="${V#v}" && \
|
|
[ -n "$V" ] && \
|
|
echo "Installing uv ${V}" && \
|
|
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://github.com/astral-sh/uv/releases/download/${V}/uv-${ARCH}-unknown-linux-musl.tar.gz" | tar -xz -C /tmp && \
|
|
install /tmp/uv-${ARCH}-unknown-linux-musl/uv /usr/local/bin/uv && \
|
|
install /tmp/uv-${ARCH}-unknown-linux-musl/uvx /usr/local/bin/uvx && \
|
|
rm -rf /tmp/uv-* && \
|
|
uv --version
|
|
|
|
# ── MemPalace — local-first AI memory system ─────────────────────────
|
|
# Provides semantic search over conversation history via 29 MCP tools.
|
|
# Always installed in the base (variant-independent). Set
|
|
# INSTALL_MEMPALACE=false at base-build time to shave ~300 MB.
|
|
ARG INSTALL_MEMPALACE=true
|
|
ENV UV_TOOL_DIR=/opt/uv-tools
|
|
ENV UV_TOOL_BIN_DIR=/usr/local/bin
|
|
RUN if [ "${INSTALL_MEMPALACE}" = "true" ]; then \
|
|
mkdir -p /opt/uv-tools && \
|
|
uv tool install --no-cache mempalace && \
|
|
/opt/uv-tools/mempalace/bin/python -c "import mempalace; print('mempalace', mempalace.__version__ if hasattr(mempalace, '__version__') else 'installed')" ; \
|
|
fi
|
|
|
|
# ── mempalace-toolkit — bash wrappers for session/docs mining ────────
|
|
ARG INSTALL_MEMPALACE_TOOLKIT=true
|
|
ARG MEMPALACE_TOOLKIT_REF=main
|
|
RUN if [ "${INSTALL_MEMPALACE}" = "true" ] && [ "${INSTALL_MEMPALACE_TOOLKIT}" = "true" ]; then \
|
|
git clone --depth 1 --branch "${MEMPALACE_TOOLKIT_REF}" \
|
|
https://gitea.jordbo.se/joakimp/mempalace-toolkit.git /opt/mempalace-toolkit && \
|
|
ln -sf /opt/mempalace-toolkit/bin/mempalace-session /usr/local/bin/mempalace-session && \
|
|
ln -sf /opt/mempalace-toolkit/bin/mempalace-docs /usr/local/bin/mempalace-docs && \
|
|
chmod +x /opt/mempalace-toolkit/bin/mempalace-session /opt/mempalace-toolkit/bin/mempalace-docs && \
|
|
mempalace-session --help >/dev/null && \
|
|
mempalace-docs --help >/dev/null && \
|
|
echo "mempalace-toolkit installed at $(cd /opt/mempalace-toolkit && git rev-parse --short HEAD)" ; \
|
|
fi
|
|
|
|
# rustup — Rust toolchain manager (init binary only; toolchains installed at runtime)
|
|
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \
|
|
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://static.rust-lang.org/rustup/dist/${ARCH}-unknown-linux-gnu/rustup-init" -o /usr/local/bin/rustup-init && \
|
|
chmod +x /usr/local/bin/rustup-init
|
|
|
|
# gitea-mcp — MCP server for Gitea API
|
|
ARG GITEA_MCP_VERSION=latest
|
|
RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "arm64" ;; *) echo "x86_64" ;; esac) && \
|
|
V="${GITEA_MCP_VERSION}" && \
|
|
if [ "$V" = "latest" ]; then \
|
|
V=$(curl -sI --retry 5 --retry-delay 5 --retry-all-errors "https://gitea.com/gitea/gitea-mcp/releases/latest" | awk 'tolower($1)=="location:" { sub(/\r$/,"",$2); n=split($2,a,"/"); print a[n] }'); \
|
|
fi && \
|
|
V="${V#v}" && \
|
|
[ -n "$V" ] && \
|
|
echo "Installing gitea-mcp ${V}" && \
|
|
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://gitea.com/gitea/gitea-mcp/releases/download/v${V}/gitea-mcp_Linux_${ARCH}.tar.gz" \
|
|
| tar -xz -C /usr/local/bin/ gitea-mcp && \
|
|
chmod +x /usr/local/bin/gitea-mcp && \
|
|
gitea-mcp --version
|
|
|
|
# Set locale — generate common UTF-8 locales (override via LANG/LC_ALL env vars)
|
|
RUN sed -i -E '/(en_US|en_GB|sv_SE|da_DK|nb_NO|fi_FI|de_DE|fr_FR|es_ES|it_IT|pt_BR|nl_NL|pl_PL|ja_JP|ko_KR|zh_CN)\.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
|
|
ENV LANG=en_US.UTF-8
|
|
ENV LANGUAGE=en_US:en
|
|
ENV LC_ALL=en_US.UTF-8
|
|
ENV EDITOR=nvim
|
|
ENV PATH="/home/developer/.local/bin:/home/developer/.cargo/bin:${PATH}"
|
|
|
|
# ── Node.js (required for opencode/pi/omos at variant build + MCP servers) ──
|
|
ARG NODE_VERSION=22
|
|
RUN curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash - && \
|
|
apt-get install -y --no-install-recommends nodejs && \
|
|
rm -rf /var/lib/apt/lists/*
|
|
|
|
# ── AWS CLI v2 (for SSO/Bedrock authentication) ─────────────────────
|
|
RUN ARCH=$(case "${TARGETARCH}" in \
|
|
amd64) echo "x86_64" ;; \
|
|
arm64) echo "aarch64" ;; \
|
|
*) echo "x86_64" ;; \
|
|
esac) && \
|
|
curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors "https://awscli.amazonaws.com/awscli-exe-linux-${ARCH}.zip" -o /tmp/awscli.zip && \
|
|
unzip -q /tmp/awscli.zip -d /tmp && \
|
|
/tmp/aws/install && \
|
|
rm -rf /tmp/aws /tmp/awscli.zip && \
|
|
aws --version
|
|
|
|
# ── Non-root user ────────────────────────────────────────────────────
|
|
ARG USER_NAME=developer
|
|
ARG USER_UID=1000
|
|
ARG USER_GID=1000
|
|
|
|
RUN groupadd --gid ${USER_GID} ${USER_NAME} && \
|
|
useradd --uid ${USER_UID} --gid ${USER_GID} -m -s /bin/bash ${USER_NAME} && \
|
|
echo "${USER_NAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/${USER_NAME}
|
|
|
|
# Create standard directories
|
|
RUN mkdir -p /workspace \
|
|
/home/${USER_NAME}/.config/opencode/skills \
|
|
/home/${USER_NAME}/.pi/agent/extensions \
|
|
/home/${USER_NAME}/.agents/skills \
|
|
/home/${USER_NAME}/.local/share/opencode \
|
|
/home/${USER_NAME}/.cache/bash \
|
|
/home/${USER_NAME}/.ssh && \
|
|
chown -R ${USER_NAME}:${USER_NAME} /workspace /home/${USER_NAME}
|
|
|
|
# ── Pre-warm chromadb embedding model ──────────────────────────────
|
|
# Runs as gosu developer so Path.home() resolves correctly. Uses
|
|
# the mempalace venv's python, which is the only one that has
|
|
# chromadb importable (system python3 cannot reach the isolated venv).
|
|
RUN if [ "${INSTALL_MEMPALACE}" = "true" ]; then \
|
|
gosu ${USER_NAME} /opt/uv-tools/mempalace/bin/python -c "\
|
|
from chromadb.utils.embedding_functions import ONNXMiniLM_L6_V2; \
|
|
ef = ONNXMiniLM_L6_V2(); \
|
|
_ = ef(['warmup']); \
|
|
print('chromadb embedding model warmed: all-MiniLM-L6-v2')" && \
|
|
ls -lh /home/${USER_NAME}/.cache/chroma/onnx_models/all-MiniLM-L6-v2/ ; \
|
|
fi
|
|
|
|
# ── User-writable npm global prefix on the devbox-pi-config volume ──
|
|
# By default npm's global prefix is /usr (writable only by root) so any
|
|
# `pi install npm:<pkg>` or `npm install -g <pkg>` invoked by the
|
|
# developer user would EACCES. Pointing the prefix into ~/.pi places
|
|
# user-installed packages on the named volume, which means they survive
|
|
# container recreation AND image rebuilds.
|
|
#
|
|
# IMPORTANT: in this split-build layout the variant Dockerfile inherits
|
|
# this prefix at build time. To keep the baked binaries on /usr (so the
|
|
# ~/.pi volume mount doesn't shadow them), the variant Dockerfile MUST
|
|
# run each `npm install -g` with NPM_CONFIG_PREFIX=/usr in the per-RUN
|
|
# environment. See Dockerfile.variant.
|
|
ENV NPM_CONFIG_PREFIX=/home/${USER_NAME}/.pi/npm-global
|
|
ENV PATH="/home/${USER_NAME}/.pi/npm-global/bin:${PATH}"
|
|
|
|
# ── Shell defaults (bash history, aliases, readline) ─────────────────
|
|
RUN mkdir -p /etc/skel-devbox
|
|
COPY rootfs/home/developer/.bash_aliases /etc/skel-devbox/.bash_aliases
|
|
COPY rootfs/home/developer/.inputrc /etc/skel-devbox/.inputrc
|
|
|
|
# ── Entrypoint ────────────────────────────────────────────────────────
|
|
COPY rootfs/usr/local/lib/opencode-devbox/ /usr/local/lib/opencode-devbox/
|
|
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
|
COPY entrypoint-user.sh /usr/local/bin/entrypoint-user.sh
|
|
RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/entrypoint-user.sh \
|
|
/usr/local/lib/opencode-devbox/*.py
|
|
|
|
# Start as root — entrypoint adjusts UID/GID then drops to developer
|
|
WORKDIR /workspace
|
|
|
|
ENTRYPOINT ["entrypoint.sh"]
|
|
CMD ["bash", "-l"]
|