Files
EOS/ai/entrypoint.sh
T

104 lines
3.9 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
# ai/entrypoint.sh
# Initializes the AI sandbox container on startup, then execs the command.
# Installed to /usr/local/bin (image layer, root-owned) so it cannot be
# shadowed or modified via the persistent /home/dev volume.
set -euo pipefail
# ---------------------------------------------------------------------------
# Hardening
# ---------------------------------------------------------------------------
# The home directory is a persistent volume, so files written in one session
# are still there in the next. The steps below remove the most direct ways to
# make such a file run automatically. They do not cover everything (agent
# settings and hooks in ~/.claude, ~/.gitconfig, the venv in ~/.cache):
# reset the volume (docker volume rm akkudoktor-ai-home) if a session may have
# been misled.
# Strip exec bits from $HOME, except from the uv cache and the virtual
# environment in ~/.cache, which must stay executable. A program planted in
# e.g. ~/.local/bin (first on PATH) then does not run in the next session.
# Runs as the non-root `dev` user, so it can only touch its own files.
strip_exec_bits() {
find /home/dev \
-path /home/dev/.cache -prune -o \
-path /home/dev/.local/share/uv -prune -o \
-type f -perm /111 -exec chmod a-x -- {} + 2>/dev/null || true
}
strip_exec_bits
# Reset the shell startup files on every start, so a startup file written in
# a previous session cannot inject commands into the next shell.
# Customizations of these files do not persist.
reset_shell_startup_files() {
local f
for f in .bashrc .profile .bash_logout; do
# Remove first: cp onto a planted symlink would write to its target.
rm -f -- "$HOME/$f"
if [[ -f "/etc/skel/$f" ]]; then
cp -- "/etc/skel/$f" "$HOME/$f"
fi
done
rm -f -- "$HOME/.bash_profile" "$HOME/.bash_login" \
"$HOME/.zshrc" "$HOME/.zprofile" "$HOME/.zshenv" "$HOME/.zlogin"
}
reset_shell_startup_files
# NOTE: /workspace is the user's own repository. The entrypoint must never
# modify it — in particular .git/hooks is left untouched (pre-commit etc.).
# .git is writable in the sandbox, so a previous session may have added hooks.
# Warn about the active hooks, so they get noticed. This is no protection: the
# review checklist in docs/develop/ai.md checks .git on the host.
warn_git_hooks() {
local hooks_dir
# Honors core.hooksPath and worktrees; falls back to the default location.
hooks_dir="$(git -C /workspace rev-parse --path-format=absolute --git-path hooks 2>/dev/null)" \
|| hooks_dir="/workspace/.git/hooks"
[[ -d "$hooks_dir" ]] || return 0
local hooks=()
while IFS= read -r -d '' f; do
hooks+=("$f")
done < <(find "$hooks_dir" -maxdepth 1 -type f ! -name '*.sample' -print0 2>/dev/null)
[[ ${#hooks[@]} -gt 0 ]] || return 0
{
echo "WARNING: active git hooks found in $hooks_dir"
echo " They run on the host too. Verify they are expected:"
local f
for f in "${hooks[@]}"; do
printf ' %s (modified %s)\n' \
"$(basename -- "$f")" \
"$(date -r "$f" '+%Y-%m-%d %H:%M' 2>/dev/null || echo '?')"
done
} >&2
}
warn_git_hooks || true
# ---------------------------------------------------------------------------
# Initialization
# ---------------------------------------------------------------------------
# Prepare a persistent uv cache inside the home volume.
export UV_CACHE_DIR="/home/dev/.cache/uv"
mkdir -p "$UV_CACHE_DIR"
# Configure git identity if provided via environment.
if [[ -n "${GIT_USER_NAME:-}" ]]; then
git config --global user.name "$GIT_USER_NAME"
fi
if [[ -n "${GIT_USER_EMAIL:-}" ]]; then
git config --global user.email "$GIT_USER_EMAIL"
fi
# If gh is already authenticated, wire git to use it as a credential helper.
if gh auth status &>/dev/null; then
gh auth setup-git || true
fi
exec "$@"