#!/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" # Helper to verify presence of all core runtime files. # Keying off a set of core files helps detect and recover from partial/interrupted installations. check_assets_present() { local dir="${1:-.}" local core_files=( ".agents/skills/lib.sh" ".agents/skills/multi-agent-mux-create/scripts/create_session.sh" ".agents/skills/multi-agent-mux-delegate-job/scripts/registry.py" ".agents/skills/multi-agent-mux-status/scripts/status.sh" ) for f in "${core_files[@]}"; do if [ ! -f "$dir/$f" ]; then return 1 fi done return 0 } # Fetch the orchestration assets if missing or incomplete (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/, documents, .env.example) # into the target with per-file no-clobber guards so a pre-existing target file # always wins. if ! check_assets_present "."; then echo "đŸ“Ĩ Orchestration skills not found or incomplete. 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 ! check_assets_present "$STAGE_DIR"; then echo "❌ Error: fetched source is missing core runtime assets. Aborting (no files copied)." >&2 exit 1 fi # Create metadata directory and initialize manifest before copying mkdir -p .mam MANIFEST_FILE=".mam/install_manifest.txt" touch "$MANIFEST_FILE" # 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" || { echo "❌ Error: Failed to copy $rel" >&2; exit 1; } echo "$dest" >> "$MANIFEST_FILE" fi done # Copy non-dev documents if they don't already exist. # We skip dev-specific docs like README.md, DONE.md, and FUTURE_WORKS.md. for doc in MESSAGING.md BOOTSTRAP.md BOOTSTRAP.ko.md INSTRUCTION.md; do if [ -f "$STAGE_DIR/$doc" ] && [ ! -e "$doc" ]; then cp "$STAGE_DIR/$doc" . || { echo "❌ Error: Failed to copy $doc" >&2; exit 1; } echo "$doc" >> "$MANIFEST_FILE" fi done if [ -f "$STAGE_DIR/deploy/remove.sh" ] && [ ! -e "remove.sh" ]; then cp "$STAGE_DIR/deploy/remove.sh" remove.sh || { echo "❌ Error: Failed to copy remove.sh" >&2; exit 1; } chmod +x remove.sh echo "remove.sh" >> "$MANIFEST_FILE" fi if [ -f "$STAGE_DIR/deploy/update.sh" ] && [ ! -e "update.sh" ]; then cp "$STAGE_DIR/deploy/update.sh" update.sh || { echo "❌ Error: Failed to copy update.sh" >&2; exit 1; } chmod +x update.sh echo "update.sh" >> "$MANIFEST_FILE" fi if [ -f "$STAGE_DIR/.env.example" ] && [ ! -e ".env.example" ]; then cp "$STAGE_DIR/.env.example" . || { echo "❌ Error: Failed to copy .env.example" >&2; exit 1; } echo ".env.example" >> "$MANIFEST_FILE" fi rm -rf "$STAGE_DIR" trap - EXIT echo "✅ Skills staged into workspace (existing files preserved)." fi # Sanity check: verify all core files, not just a single one — an empty or # incomplete layout would yield a silently broken install. if ! check_assets_present "."; then echo "❌ Error: Core runtime assets 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 <> "$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." # Record the newly created .env in the manifest mkdir -p .mam touch .mam/install_manifest.txt echo "$ENV_FILE" >> .mam/install_manifest.txt 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 "===================================================================="