#!/usr/bin/env bash #ddev-generated set -euo pipefail # All Claude Code state is stored under .ddev/claude-code so it survives # container rebuilds without being committed to the project repository. PERSIST_BASE="${DDEV_APPROOT}/.ddev/claude-code" PERSIST_HOME="${PERSIST_BASE}/home" # replaces $HOME inside the container PERSIST_CONFIG_HOME="${PERSIST_BASE}/config" # XDG_CONFIG_HOME (e.g. ~/.config) PERSIST_DATA_HOME="${PERSIST_BASE}/data" # XDG_DATA_HOME (e.g. ~/.local/share) PERSIST_STATE_HOME="${PERSIST_BASE}/state" # XDG_STATE_HOME (e.g. ~/.local/state) PERSIST_BIN_DIR="${PERSIST_HOME}/.local/bin" # Claude native installer target PERSIST_BIN="${PERSIST_BIN_DIR}/claude" # full path to the claude executable PERSIST_GITIGNORE_GLOBAL="${PERSIST_HOME}/.gitignore_global" # mirrors $HOME/.gitignore_global # Capture the container's original HOME before we override it below, so we can # seed the persisted gitignore from the container image on first run. CONTAINER_HOME="${HOME:?HOME must be set}" CONTAINER_GITIGNORE_GLOBAL="${CONTAINER_HOME}/.gitignore_global" # Ensure all persistence directories exist. mkdir -p \ "${PERSIST_HOME}" \ "${PERSIST_CONFIG_HOME}" \ "${PERSIST_DATA_HOME}" \ "${PERSIST_STATE_HOME}" \ "${PERSIST_BIN_DIR}" # Seed the persisted gitignore from the container image on first run only. if [ -f "${CONTAINER_GITIGNORE_GLOBAL}" ] && [ ! -f "${PERSIST_GITIGNORE_GLOBAL}" ]; then cp -f "${CONTAINER_GITIGNORE_GLOBAL}" "${PERSIST_GITIGNORE_GLOBAL}" fi # Claude Code's native installer stores its launcher, auth, settings, downloads, # and versioned package data relative to HOME/XDG paths. export HOME="${PERSIST_HOME}" export XDG_CONFIG_HOME="${PERSIST_CONFIG_HOME}" export XDG_DATA_HOME="${PERSIST_DATA_HOME}" export XDG_STATE_HOME="${PERSIST_STATE_HOME}" export PATH="${PERSIST_BIN_DIR}:${PATH}" # If a persisted binary exists but fails to run (e.g. wrong architecture after # a host OS change), remove it so the install step below fetches a fresh copy. if [ -x "${PERSIST_BIN}" ] && ! "${PERSIST_BIN}" --version >/dev/null 2>&1; then echo "Persisted Claude Code binary is not runnable, reinstalling ..." rm -f "${PERSIST_BIN}" fi # Download and install the current stable Claude Code native build only when # missing. Use "ddev claude update" to check for and install newer versions. if [ ! -x "${PERSIST_BIN}" ]; then echo "Installing Claude Code CLI ..." curl -fsSL https://claude.ai/install.sh | bash -s stable fi # Verify the binary is present and runnable after installation. if [ ! -x "${PERSIST_BIN}" ]; then echo "Claude Code installation completed but no executable was found at ${PERSIST_BIN}." >&2 exit 1 fi if ! "${PERSIST_BIN}" --version >/dev/null 2>&1; then echo "Claude Code installation completed but ${PERSIST_BIN} is not runnable." >&2 exit 1 fi # Point git's global excludes file at the persisted copy when available, # so git inside the container respects user-defined ignore patterns. if command -v git >/dev/null 2>&1 && [ -f "${PERSIST_GITIGNORE_GLOBAL}" ]; then CURRENT_EXCLUDESFILE="$(git config --global --get core.excludesfile || true)" if [ "${CURRENT_EXCLUDESFILE}" != "${PERSIST_GITIGNORE_GLOBAL}" ]; then git config --global core.excludesfile "${PERSIST_GITIGNORE_GLOBAL}" fi fi echo "Claude Code version: $("${PERSIST_BIN}" --version)"