Files
multi-agent-mux/deploy/install.sh
T
Godopu 387b43d8e3 fix(deploy): stage installer download and copy runtime assets no-clobber (FW-D1)
deploy/install.sh extracted the repo archive in-place with
`tar --strip-components=1`, which inside an existing project could silently
overwrite the target's own README.md/FUTURE_WORKS.md/etc and litter it with
this repo's dev docs.

Rebuild the fetch path:
- stage the clone/extract into a `mktemp -d` dir, never in-place
- verify `.agents/skills/lib.sh` is present before copying anything
- copy only runtime assets (.agents/, AGENT.md, .env.example) into the target
  with per-file no-clobber guards (`[ ! -e ]`), so existing files always win
- post-fetch sanity check now tests a file, not just the directory
- fail fast when neither git nor curl is available

Use explicit `[ ! -e ]` guards + a POSIX find merge rather than `cp -n`
(non-portable; emits a deprecation warning on GNU coreutils 9.x). The earlier
`tar --exclude` denylist idea was rejected in review: non-portable and the
unanchored `--exclude="scripts"` pattern stripped the skills' own nested
scripts/ dirs, yielding a silently broken install.

Mark FW-D1 resolved and FW-D2 partially addressed in FUTURE_WORKS.md/.ko.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 10:33:05 +09:00

209 lines
7.8 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# ==============================================================================
# install.sh — Multi-Agent Mux (MAM) Orchestration Installer
# ==============================================================================
# Idempotent, robust installer to bootstrap MAM orchestration skills
# and Python backplane dependencies on any local workspace.
# ==============================================================================
set -euo pipefail
# --- Configuration & Defaults ---
TARGET_DIR="${1:-$(pwd)}"
VENV_NAME=".venv"
MIN_PYTHON_VERSION="3.9"
echo "===================================================================="
echo "⚡ Starting Multi-Agent Mux (MAM) Installation"
echo "📂 Target Workspace: $TARGET_DIR"
echo "===================================================================="
# --- 1. System Requirements Validation ---
echo "🔍 Checking system dependencies..."
check_cmd() {
local cmd="$1"
if ! command -v "$cmd" &>/dev/null; then
echo "❌ Error: '$cmd' is not installed. Please install it first." >&2
exit 1
fi
}
check_cmd tmux
check_cmd python3
# Verify Python Version
PYTHON_VERSION=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
PYTHON_MAJOR="${MIN_PYTHON_VERSION%%.*}"
PYTHON_MINOR="${MIN_PYTHON_VERSION##*.}"
if python3 -c "import sys; exit(0 if sys.version_info >= ($PYTHON_MAJOR, $PYTHON_MINOR) else 1)"; then
echo "✅ Python $PYTHON_VERSION detected."
else
echo "❌ Error: Python version must be $MIN_PYTHON_VERSION or higher. Detected: $PYTHON_VERSION" >&2
exit 1
fi
# Verify PyYAML (needed by system python3 for atomic state writes)
if ! python3 -c "import yaml" &>/dev/null; then
echo "❌ Error: 'PyYAML' is not installed in the system python3. Please install it first" >&2
echo " (e.g., 'pip3 install PyYAML' or 'sudo apt-get install python3-yaml')." >&2
exit 1
fi
echo "✅ PyYAML (system dependency) detected."
# --- 2. Workspace Setup ---
mkdir -p "$TARGET_DIR"
cd "$TARGET_DIR"
REPO_URL="https://git.godopu.com/tmpl/multi-agent-mux.git"
ARCHIVE_URL="https://git.godopu.com/tmpl/multi-agent-mux/archive/main.tar.gz"
# Fetch the orchestration assets if missing (for curl one-liner installs).
#
# Safety model (FW-D1): we NEVER extract the repo archive directly into the
# target. Running inside an existing project must not overwrite the target's
# own files (README.md, FUTURE_WORKS.md, …) or litter it with this repo's
# development docs. Instead we stage the download into a throwaway temp dir,
# verify it, then copy ONLY the runtime assets (.agents/, AGENT.md, .env.example)
# into the target with per-file no-clobber guards so a pre-existing target file
# always wins. We key off lib.sh (a file), not the .agents/skills directory, so
# an empty/partial dir can't masquerade as a valid install.
if [ ! -f ".agents/skills/lib.sh" ]; then
echo "📥 Orchestration skills not found. Fetching from Gitea repository..."
STAGE_DIR="$(mktemp -d)"
trap 'rm -rf "$STAGE_DIR"' EXIT
if command -v git &>/dev/null; then
echo "🌐 Cloning repository (shallow) into a staging area..."
git clone --depth 1 "$REPO_URL" "$STAGE_DIR"
elif command -v curl &>/dev/null; then
echo "🌐 Downloading and extracting archive into a staging area..."
curl -fsSL "$ARCHIVE_URL" | tar -xz --strip-components=1 -C "$STAGE_DIR"
else
echo "❌ Error: neither 'git' nor 'curl' is available to fetch the skills." >&2
exit 1
fi
# Verify the staged tree before we trust and copy from it.
if [ ! -f "$STAGE_DIR/.agents/skills/lib.sh" ]; then
echo "❌ Error: fetched source is missing '.agents/skills/lib.sh'. Aborting (no files copied)." >&2
exit 1
fi
# Copy ONLY runtime assets into the target, never overwriting an existing
# target file. We merge per-file (POSIX find + an explicit "[ ! -e ]" guard)
# instead of `cp -n`: `cp -n` is non-portable and now prints a deprecation
# warning on GNU coreutils 9.x, whereas the explicit guard is portable to
# BSD/macOS and makes the no-clobber intent obvious.
mkdir -p .agents
( cd "$STAGE_DIR/.agents" && find . -type f -print ) | while IFS= read -r rel; do
dest=".agents/${rel#./}"
if [ ! -e "$dest" ]; then
mkdir -p "$(dirname "$dest")"
cp "$STAGE_DIR/.agents/$rel" "$dest"
fi
done
if [ -f "$STAGE_DIR/AGENT.md" ] && [ ! -e "AGENT.md" ]; then
cp "$STAGE_DIR/AGENT.md" .
fi
if [ -f "$STAGE_DIR/.env.example" ] && [ ! -e ".env.example" ]; then
cp "$STAGE_DIR/.env.example" .
fi
rm -rf "$STAGE_DIR"
trap - EXIT
echo "✅ Skills staged into workspace (existing files preserved)."
fi
# Sanity check: verify a FILE, not just the directory — an empty .agents/skills
# would otherwise pass and yield a silently broken install.
if [ ! -f ".agents/skills/lib.sh" ]; then
echo "❌ Error: '.agents/skills/lib.sh' missing after setup. Target layout might be invalid." >&2
exit 1
fi
echo "✅ Orchestration skills present."
echo "📂 Ensuring metadata directory structure (.mam/)..."
mkdir -p .mam/jobs .mam/delegate_job_logs
# File permission lockdown on database directory (if owned by the current user to prevent multi-user system issues)
if [ -O .mam ]; then
chmod 0700 .mam
fi
# --- 3. Check Network File System (NFS) Warnings ---
echo "💾 Detecting file system mount type..."
if command -v df &>/dev/null && command -v mount &>/dev/null; then
MOUNTPOINT="$(df --output=target . 2>/dev/null | tail -1 || echo "")"
if [ -n "$MOUNTPOINT" ]; then
if mount | grep -q "$MOUNTPOINT.*nfs\|$MOUNTPOINT.*cifs\|$MOUNTPOINT.*fuse.sshfs"; then
echo "⚠️ WARNING: Target directory is on a network filesystem (NFS/CIFS/SSHFS)."
echo " SQLite WAL journaling and file locks are UNRELIABLE on network storage."
echo " The sqlite3 registry will fall back to 'DELETE' journaling instead of WAL."
else
echo "✅ File system supports WAL (Local storage detected)."
fi
fi
fi
# --- 4. Python Virtual Environment Setup ---
echo "🐍 Bootstrapping Python virtual environment (.venv)..."
if [ ! -d "$VENV_NAME" ]; then
python3 -m venv "$VENV_NAME"
echo "✅ Virtual environment created."
else
echo "️ Virtual environment (.venv) already exists. Skipping creation."
fi
# Activate virtual environment
# shellcheck disable=SC1091
source "$VENV_NAME"/bin/activate
# Upgrade pip
pip install --upgrade pip
# Install requirements
REQ_FILE=".agents/skills/multi-agent-mux-delegate-job/requirements.txt"
if [ -f "$REQ_FILE" ]; then
echo "📦 Installing backplane dependencies from $REQ_FILE..."
pip install -r "$REQ_FILE"
echo "✅ Dependencies installed successfully."
else
echo "⚠️ WARNING: Could not find requirements file: $REQ_FILE"
echo " Installing default packages (paho-mqtt, pyyaml) manually..."
pip install "paho-mqtt>=2.0.0" pyyaml
fi
# --- 5. Generate Environment Template ---
ENV_FILE=".env"
ENV_EXAMPLE=".env.example"
if [ ! -f "$ENV_FILE" ]; then
if [ -f "$ENV_EXAMPLE" ]; then
echo "📝 Creating configuration from $ENV_EXAMPLE..."
cp "$ENV_EXAMPLE" "$ENV_FILE"
else
echo "📝 Creating default $ENV_FILE..."
touch "$ENV_FILE"
fi
# Always append the active defaults to ensure they are set and not commented out
cat <<EOF >> "$ENV_FILE"
# === Installer-applied active defaults ===
MQTT_BROKER=broker.hivemq.com
MQTT_PORT=1883
MQTT_TLS=0
MQTT_CLIENT_ID_PREFIX=mam-agent
TMUX_SERVER_NAME=default
EOF
chmod 0600 "$ENV_FILE"
echo "✅ Config file .env initialized with chmod 0600."
else
echo "$ENV_FILE already exists. Skipping config override."
fi
echo "===================================================================="
echo "🎉 Installation complete!"
echo "✨ You can now run the status or monitor skills."
echo "💡 Hint: Try executing: .venv/bin/python .agents/skills/multi-agent-mux-delegate-job/scripts/registry.py list"
echo "===================================================================="