Files
multi-agent-mux/deploy/install.sh
T

315 lines
11 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=""
FORCE_REFRESH="${MAM_FORCE_REFRESH:-0}"
VENV_NAME=".venv"
MIN_PYTHON_VERSION="3.9"
while [[ $# -gt 0 ]]; do
case "$1" in
-f|--force|--refresh-skills)
FORCE_REFRESH=1
shift
;;
-h|--help)
cat <<EOF
Usage: $0 [options] [target_dir]
Options:
-f, --force, --refresh-skills Force fetch and refresh framework skills under .agents/skills/
-h, --help Show this help message
EOF
exit 0
;;
*)
if [ -z "$TARGET_DIR" ]; then
TARGET_DIR="$1"
else
echo "❌ Error: Unknown argument: $1" >&2
exit 1
fi
shift
;;
esac
done
if [ -z "$TARGET_DIR" ]; then
TARGET_DIR="$(pwd)"
fi
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 herdr
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="${MAM_REPO_URL:-https://git.godopu.com/tmpl/multi-agent-mux.git}"
ARCHIVE_URL="${MAM_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"
".agents/skills/multi-agent-mux-loop/scripts/run_loop.sh"
)
for f in "${core_files[@]}"; do
if [ ! -f "$dir/$f" ]; then
return 1
fi
done
return 0
}
# Helper to classify framework-owned skill definitions vs user-owned project assets
is_framework_owned() {
case "$1" in
.agents/skills/*) return 0 ;;
*) return 1 ;;
esac
}
# Fetch the orchestration assets if missing or if skill refresh is requested.
#
# 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, AGENTS.md, MULTI_AGENT_RULES.md) or litter
# it with development docs. Instead we stage the download into a throwaway temp dir,
# verify it, then copy runtime assets: framework skills (.agents/skills/*) are updated,
# while user-owned documents use per-file no-clobber guards so pre-existing target files win.
if [ "$FORCE_REFRESH" -eq 1 ] || ! check_assets_present "."; then
echo "📥 Staging orchestration assets 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 runtime assets (.agents/) into the target workspace.
# Framework-owned skill files (.agents/skills/*) are updated/overwritten so that
# latest skill definitions and metadata frontmatter take effect.
# User-owned documents (.agents/MULTI_AGENT_RULES*.md, .agents/INSTALL.md, etc.) use
# explicit no-clobber guards so pre-existing user files are untouched and unmanifested.
mkdir -p .agents
( cd "$STAGE_DIR/.agents" && find . -type f -print ) | while IFS= read -r rel; do
dest=".agents/${rel#./}"
mkdir -p "$(dirname "$dest")"
if is_framework_owned "$dest"; then
cp -f "$STAGE_DIR/.agents/$rel" "$dest" || { echo "❌ Error: Failed to copy $rel" >&2; exit 1; }
if ! grep -Fqx "$dest" "$MANIFEST_FILE" 2>/dev/null; then
echo "$dest" >> "$MANIFEST_FILE"
fi
elif [ ! -e "$dest" ]; then
cp "$STAGE_DIR/.agents/$rel" "$dest" || { echo "❌ Error: Failed to copy $rel" >&2; exit 1; }
if ! grep -Fqx "$dest" "$MANIFEST_FILE" 2>/dev/null; then
echo "$dest" >> "$MANIFEST_FILE"
fi
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 AGENTS.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
# Ship the user manual into the target's .agents/ (consistent with install_mam.sh)
if [ -f "$STAGE_DIR/deploy/INSTALL.md" ]; then
mkdir -p .agents
if [ ! -e ".agents/INSTALL.md" ]; then
cp "$STAGE_DIR/deploy/INSTALL.md" .agents/INSTALL.md || { echo "❌ Error: Failed to copy INSTALL.md" >&2; exit 1; }
echo ".agents/INSTALL.md" >> "$MANIFEST_FILE"
fi
fi
rm -rf "$STAGE_DIR"
trap - EXIT
echo "✅ Skills staged into workspace (user documents and custom configs 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 <<EOF >> "$ENV_FILE"
# === Installer-applied active defaults ===
MQTT_BROKER=broker.hivemq.com
MQTT_PORT=1883
MQTT_TLS=0
MQTT_CLIENT_ID_PREFIX=mam-agent
HERDR_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 "===================================================================="