#!/usr/bin/env bash # Runtime post-recreate verification for opencode-devbox. # # Verifies that after `docker compose up -d --force-recreate`: # - The new image is actually live (opencode version matches Dockerfile.variant) # - Persisted named volumes survived (mempalace palace, opencode.db, bash-history) # - OMOS runtime skill symlinks resolve (omos variant only) # - Shell defaults re-seeded from /etc/skel-devbox # - /opt toolkits intact # - Known expected-absences don't regress # # This is repo/maintainer tooling — the runtime peer of smoke-test.sh. It is # NOT baked into the published Docker Hub image; run it from a checkout of the # opencode-devbox repo (which a maintainer already has for CI builds). A plain # `docker pull` consumer is not the audience and will not have this file. # # Usage: ./scripts/recreate-sanity-check.sh [--expected-version X.Y.Z] [--variant base|omos] # # Exit codes: # 0 all checks passed # 1 one or more checks failed # 2 usage error set -euo pipefail EXPECTED_VERSION="" VARIANT="" REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # Parse arguments while [[ $# -gt 0 ]]; do case "$1" in --expected-version) EXPECTED_VERSION="$2" shift 2 ;; --variant) VARIANT="$2" shift 2 ;; *) echo "usage: $0 [--expected-version X.Y.Z] [--variant base|omos]" >&2 exit 2 ;; esac done FAILED=0 pass() { echo " ✓ $1"; } fail() { echo " ✗ $1" >&2; FAILED=$((FAILED + 1)); } warn() { echo " ⚠ $1" >&2; } # Determine expected opencode version from Dockerfile.variant if not provided if [ -z "$EXPECTED_VERSION" ]; then EXPECTED_VERSION="$(grep -oE 'OPENCODE_VERSION=[0-9.]+' "$REPO_DIR/Dockerfile.variant" | head -1 | cut -d= -f2)" if [ -z "$EXPECTED_VERSION" ]; then echo "error: could not determine OPENCODE_VERSION from $REPO_DIR/Dockerfile.variant" >&2 exit 2 fi fi # Auto-detect variant if not provided if [ -z "$VARIANT" ]; then if command -v bun >/dev/null 2>&1 || [ -d /usr/lib/node_modules/oh-my-opencode-slim ] || [ -d /usr/local/lib/node_modules/oh-my-opencode-slim ]; then VARIANT="omos" else VARIANT="base" fi fi # Print header with git context echo "=== Recreate sanity check (variant: $VARIANT) ===" if GIT_TAG=$(git -C "$REPO_DIR" describe --tags 2>/dev/null); then echo " Repo HEAD: $GIT_TAG (version-match only meaningful when image tag matches)" else echo " Repo HEAD: (not a git repo or no tags)" fi echo echo "-- opencode version --" if ACTUAL_VERSION=$(opencode --version 2>&1 | head -1); then if [ "$ACTUAL_VERSION" = "$EXPECTED_VERSION" ]; then pass "opencode version $ACTUAL_VERSION" else fail "opencode version mismatch: expected $EXPECTED_VERSION, got $ACTUAL_VERSION" fi else fail "opencode --version failed" fi echo echo "-- Persisted named volumes (must survive --force-recreate) --" # mempalace palace volume if [ -f "$HOME/.mempalace/palace/chroma.sqlite3" ]; then SIZE=$(du -h "$HOME/.mempalace/palace/chroma.sqlite3" | cut -f1) if [ -s "$HOME/.mempalace/palace/chroma.sqlite3" ]; then pass "~/.mempalace/palace/chroma.sqlite3 exists ($SIZE)" else fail "~/.mempalace/palace/chroma.sqlite3 exists but is empty" fi else fail "~/.mempalace/palace/chroma.sqlite3 missing" fi # opencode session history volume if [ -f "$HOME/.local/share/opencode/opencode.db" ]; then SIZE=$(du -h "$HOME/.local/share/opencode/opencode.db" | cut -f1) if [ -s "$HOME/.local/share/opencode/opencode.db" ]; then pass "~/.local/share/opencode/opencode.db exists ($SIZE)" else fail "~/.local/share/opencode/opencode.db exists but is empty" fi else fail "~/.local/share/opencode/opencode.db missing" fi # bash-history volume mount point (empty .bash_history right after recreate is NORMAL) if [ -d "$HOME/.cache/bash" ]; then pass "~/.cache/bash exists as directory" else fail "~/.cache/bash missing or not a directory" fi echo echo "-- omos runtime skill symlinks (omos variant only; skip on base) --" if [ "$VARIANT" = "omos" ]; then SKILLS_OK=0 SKILLS_TOTAL=5 for skill in clonedeps codemap deepwork oh-my-opencode-slim simplify; do SKILL_PATH="$HOME/.agents/skills/$skill" if [ -L "$SKILL_PATH" ]; then TARGET=$(readlink -f "$SKILL_PATH") # Check if target resolves to a real directory and contains the expected path if [ -d "$TARGET" ] && echo "$TARGET" | grep -q "node_modules/oh-my-opencode-slim/src/skills/$skill"; then SKILLS_OK=$((SKILLS_OK + 1)) else fail "~/.agents/skills/$skill symlink target invalid: $TARGET" fi else fail "~/.agents/skills/$skill missing or not a symlink" fi done if [ "$SKILLS_OK" -eq "$SKILLS_TOTAL" ]; then pass "$SKILLS_OK/$SKILLS_TOTAL omos skill symlinks resolve" fi # Migration marker if [ -f "$HOME/.config/opencode/.omos-skills-migrated" ]; then pass "~/.config/opencode/.omos-skills-migrated exists" else fail "~/.config/opencode/.omos-skills-migrated missing" fi else echo " - skipped (base variant)" fi echo echo "-- Shell defaults re-seeded from /etc/skel-devbox --" if [ -f "$HOME/.bash_aliases" ]; then pass "~/.bash_aliases exists" else fail "~/.bash_aliases missing" fi if [ -f "$HOME/.inputrc" ]; then pass "~/.inputrc exists" else fail "~/.inputrc missing" fi echo echo "-- cli_utils bind-mount --" if [ -d /workspace/cli_utils ] && [ -d /workspace/cli_utils/.git ]; then pass "/workspace/cli_utils exists with .git subdir" else fail "/workspace/cli_utils missing or .git subdir absent" fi echo echo "-- Baked /opt toolkits --" if [ -d /opt/mempalace-toolkit ]; then if MEMPALACE_SESSION_PATH=$(command -v mempalace-session 2>/dev/null); then RESOLVED=$(readlink -f "$MEMPALACE_SESSION_PATH") pass "/opt/mempalace-toolkit exists, mempalace-session resolves to $RESOLVED" else fail "/opt/mempalace-toolkit exists but mempalace-session not on PATH" fi else fail "/opt/mempalace-toolkit missing" fi echo echo "-- Known expected-absences (regressions vs by-design) --" if [ ! -d "$HOME/.local/bin" ]; then warn "~/.local/bin absent — expected; mempalace toolkit relocated to /opt (not a wrapper-loss regression)" else pass "~/.local/bin exists (toolkit may have been installed locally)" fi if ! command -v go >/dev/null 2>&1; then warn "go absent — expected unless image built with INSTALL_GO=true" else pass "go is on PATH" fi echo if [ "$FAILED" -gt 0 ]; then echo "=== FAILED: $FAILED check(s) ===" >&2 exit 1 fi echo "=== PASSED ==="