#!/usr/bin/env bash set -euo pipefail # ── Shell defaults: copy baked files from /etc/skel-devbox/ if absent # Respects host bind-mounts and user customizations — existing files # are never overwritten. To restore defaults: rm ~/.bash_aliases (or # .inputrc) and recreate the container, or cp from /etc/skel-devbox/ # directly. SKEL_DIR="/etc/skel-devbox" if [ -d "$SKEL_DIR" ]; then for f in .bash_aliases .inputrc; do if [ -f "$SKEL_DIR/$f" ] && [ ! -e "$HOME/$f" ]; then cp "$SKEL_DIR/$f" "$HOME/$f" fi done fi # ── MemPalace: initialize palace for the workspace if mempalace is installed # Creates the palace directory structure on first run. Idempotent — skips # if palace already exists, so upgrades from older versions preserve # existing data. `--yes` auto-accepts detected entities so the init is # non-interactive — the container entrypoint has no usable stdin for # prompts anyway. if command -v mempalace &>/dev/null && [ -d /workspace ]; then PALACE_DIR="${HOME}/.mempalace" if [ ! -d "$PALACE_DIR/palace" ]; then echo "Initializing MemPalace for workspace (non-interactive)..." mempalace init --yes /workspace >/dev/null 2>&1 || true fi fi # ── Git config defaults ────────────────────────────────────────────── if [ -n "${GIT_USER_NAME:-}" ] && ! git config --global user.name &>/dev/null; then git config --global user.name "$GIT_USER_NAME" fi if [ -n "${GIT_USER_EMAIL:-}" ] && ! git config --global user.email &>/dev/null; then git config --global user.email "$GIT_USER_EMAIL" fi # ── Generate opencode config from env vars if no config mounted ────── # Delegated to a standalone Python script for clarity and testability. # The script is idempotent: it never overwrites an existing opencode.json # (bind-mounted from host, persisted in named volume, or previously # generated) and no-ops if OPENCODE_PROVIDER is unset. python3 /usr/local/lib/opencode-devbox/generate-config.py # ── pi: deploy toolkit + extensions + mempalace bridge ───────────── # Runs only when pi was baked into the image (INSTALL_PI=true at build). # Each install.sh is idempotent and backs up real files before linking, # so re-running across container restarts is safe. # # Order: pi-toolkit first (creates ~/.pi/agent/keybindings.json symlink # and writes the AWS env loader), then pi-extensions (symlinks our 6 # extensions), then settings.json bootstrap from the toolkit template, # then the mempalace bridge symlink (one-liner; mempalace-toolkit's # install_skill is intentionally skipped to avoid racing with skillset # auto-deploy below). if command -v pi &>/dev/null; then if [ -d /opt/pi-toolkit ]; then (cd /opt/pi-toolkit && ./install.sh --yes) || \ echo "WARN: pi-toolkit install.sh failed (continuing)" fi if [ -d /opt/pi-extensions ]; then (cd /opt/pi-extensions && ./install.sh --yes) || \ echo "WARN: pi-extensions install.sh failed (continuing)" fi # Bootstrap settings.json from template if absent (pi rewrites this # file at runtime — lastChangelogVersion, etc — so we can't symlink it). if [ ! -f "$HOME/.pi/agent/settings.json" ] && \ [ -f /opt/pi-toolkit/settings.example.json ]; then cp /opt/pi-toolkit/settings.example.json "$HOME/.pi/agent/settings.json" fi # pi↔mempalace MCP bridge — single extension symlink. if [ -f /opt/mempalace-toolkit/extensions/pi/mempalace.ts ] && \ command -v mempalace &>/dev/null && \ [ ! -L "$HOME/.pi/agent/extensions/mempalace.ts" ]; then ln -sf /opt/mempalace-toolkit/extensions/pi/mempalace.ts \ "$HOME/.pi/agent/extensions/mempalace.ts" fi fi # ── Skillset: deploy skills/instructions from mounted skillset repo ── # When the skillset repo is mounted (at $HOME/skillset or /workspace/skillset), # run the deploy script to create relative symlinks for skills and instructions. # This ensures skills resolve correctly inside the container regardless of # where the repo lives on the host. Idempotent — second run is a no-op. # # Detection order: # 1. SKILLSET_CONTAINER_PATH env var (explicit, for non-standard layouts) # 2. $HOME/skillset (dedicated volume mount via SKILLSET_PATH in compose) # 3. /workspace/skillset (skillset is directly inside workspace root) SKILLSET_DEPLOY="" if [ -n "${SKILLSET_CONTAINER_PATH:-}" ] && [ -x "${SKILLSET_CONTAINER_PATH}/deploy-skills.sh" ]; then SKILLSET_DEPLOY="${SKILLSET_CONTAINER_PATH}/deploy-skills.sh" elif [ -x "$HOME/skillset/deploy-skills.sh" ]; then SKILLSET_DEPLOY="$HOME/skillset/deploy-skills.sh" elif [ -x /workspace/skillset/deploy-skills.sh ]; then SKILLSET_DEPLOY="/workspace/skillset/deploy-skills.sh" fi if [ -n "$SKILLSET_DEPLOY" ]; then "$SKILLSET_DEPLOY" --bootstrap --prune-stale >/dev/null 2>&1 || true fi CONFIG_DIR="$HOME/.config/opencode" OMOS_CONFIG="$CONFIG_DIR/oh-my-opencode-slim.json" # ── oh-my-opencode-slim setup (multi-agent orchestration) ──────────── # Activated by ENABLE_OMOS=true. Requires the image to be built with # INSTALL_OMOS=true (which installs bun + the oh-my-opencode-slim package). OMOS_CONFIG="$CONFIG_DIR/oh-my-opencode-slim.json" if [ "${ENABLE_OMOS:-false}" = "true" ]; then if ! command -v bun &>/dev/null; then echo "WARNING: ENABLE_OMOS=true but bun is not installed." echo "Rebuild with: docker compose build --build-arg INSTALL_OMOS=true" elif [ ! -f "$OMOS_CONFIG" ]; then echo "Setting up oh-my-opencode-slim agents..." # Determine installer flags OMOS_TMUX_FLAG="no" if [ "${OMOS_TMUX:-false}" = "true" ]; then OMOS_TMUX_FLAG="yes" fi OMOS_SKILLS_FLAG="yes" if [ "${OMOS_SKILLS:-true}" = "false" ]; then OMOS_SKILLS_FLAG="no" fi bun x oh-my-opencode-slim@latest install \ --no-tui \ --tmux="${OMOS_TMUX_FLAG}" \ --skills="${OMOS_SKILLS_FLAG}" echo "oh-my-opencode-slim configured successfully." else echo "oh-my-opencode-slim config found at $OMOS_CONFIG (use OMOS_RESET=true to overwrite)." # Allow reset via env var (creates backup automatically) if [ "${OMOS_RESET:-false}" = "true" ]; then echo "OMOS_RESET=true — regenerating oh-my-opencode-slim config..." OMOS_TMUX_FLAG="no" [ "${OMOS_TMUX:-false}" = "true" ] && OMOS_TMUX_FLAG="yes" OMOS_SKILLS_FLAG="yes" [ "${OMOS_SKILLS:-true}" = "false" ] && OMOS_SKILLS_FLAG="no" bun x oh-my-opencode-slim@latest install \ --no-tui \ --tmux="${OMOS_TMUX_FLAG}" \ --skills="${OMOS_SKILLS_FLAG}" \ --reset fi fi fi # ── Execute command ────────────────────────────────────────────────── exec "$@"