feat(deploy): install update/remove scripts into .mam_deploy/ and refine markdown staging
This commit is contained in:
+280
-93
@@ -1,22 +1,28 @@
|
||||
#!/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.
|
||||
# install.sh — Multi-Agent Mux (MAM) Orchestration Installer (Rev.2)
|
||||
# ==============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
# --- Configuration & Defaults ---
|
||||
TARGET_DIR=""
|
||||
FORCE_REFRESH="${MAM_FORCE_REFRESH:-0}"
|
||||
REFRESH="${MAM_REFRESH:-1}"
|
||||
OVERWRITE_CUSTOM="${MAM_OVERWRITE_CUSTOM:-0}"
|
||||
VENV_NAME=".venv"
|
||||
MIN_PYTHON_VERSION="3.9"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-f|--force|--refresh-skills)
|
||||
FORCE_REFRESH=1
|
||||
REFRESH=1
|
||||
shift
|
||||
;;
|
||||
--no-refresh|--offline)
|
||||
REFRESH=0
|
||||
shift
|
||||
;;
|
||||
--overwrite-custom)
|
||||
OVERWRITE_CUSTOM=1
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
@@ -24,7 +30,9 @@ while [[ $# -gt 0 ]]; do
|
||||
Usage: $0 [options] [target_dir]
|
||||
|
||||
Options:
|
||||
-f, --force, --refresh-skills Force fetch and refresh framework skills under .agents/skills/
|
||||
-f, --force, --refresh-skills Fetch and refresh skills (now default)
|
||||
--no-refresh, --offline Skip fetching latest assets if assets exist
|
||||
--overwrite-custom Force overwrite user-modified skill files (backup still created)
|
||||
-h, --help Show this help message
|
||||
EOF
|
||||
exit 0
|
||||
@@ -64,7 +72,6 @@ check_cmd() {
|
||||
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##*.}"
|
||||
@@ -75,10 +82,8 @@ else
|
||||
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
|
||||
echo "❌ Error: 'PyYAML' is not installed in system python3." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ PyYAML (system dependency) detected."
|
||||
@@ -90,8 +95,6 @@ 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=(
|
||||
@@ -109,7 +112,6 @@ check_assets_present() {
|
||||
return 0
|
||||
}
|
||||
|
||||
# Helper to classify framework-owned skill definitions vs user-owned project assets
|
||||
is_framework_owned() {
|
||||
case "$1" in
|
||||
.agents/skills/*) return 0 ;;
|
||||
@@ -117,108 +119,242 @@ is_framework_owned() {
|
||||
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
|
||||
# Fetch orchestration assets if REFRESH=1 or if core assets are missing.
|
||||
if [ "$REFRESH" -eq 1 ] || [ "${MAM_SKIP_VENV:-0}" -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
|
||||
FETCH_METHOD="archive"
|
||||
if [ -d "$REPO_URL" ]; then
|
||||
echo "🌐 Copying local working tree into a staging area..."
|
||||
cp -R "$REPO_URL/." "$STAGE_DIR/"
|
||||
FETCH_METHOD="local"
|
||||
elif command -v git &>/dev/null; then
|
||||
echo "🌐 Cloning repository (shallow) into a staging area..."
|
||||
git clone --depth 1 "$REPO_URL" "$STAGE_DIR"
|
||||
FETCH_METHOD="git"
|
||||
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"
|
||||
curl -fsSL "$ARCHIVE_URL" | tar -xz --strip-components=1 -C "$STAGE_DIR" \
|
||||
--exclude='*/.agents/reports/*' --exclude='*/.agents/references/*' \
|
||||
--exclude='*/MESSAGING.md' --exclude='*/BOOTSTRAP.md' --exclude='*/BOOTSTRAP.ko.md' 2>/dev/null || true
|
||||
FETCH_METHOD="archive"
|
||||
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
|
||||
echo "❌ Error: fetched source is missing core runtime assets. Aborting." >&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.
|
||||
# Migrate legacy root layout for remove.sh and update.sh into .mam_deploy/ if owned
|
||||
mkdir -p .mam_deploy
|
||||
for legacy_script in remove.sh update.sh; do
|
||||
if [ -f "$legacy_script" ] && grep -Fqx "$legacy_script" "$MANIFEST_FILE" 2>/dev/null; then
|
||||
mv -f "$legacy_script" ".mam_deploy/$legacy_script"
|
||||
python3 -c '
|
||||
import sys
|
||||
path = sys.argv[1]
|
||||
old_s = sys.argv[2]
|
||||
new_s = sys.argv[3]
|
||||
with open(path, "r") as f:
|
||||
lines = f.readlines()
|
||||
with open(path, "w") as f:
|
||||
for line in lines:
|
||||
if line.strip() == old_s:
|
||||
f.write(new_s + "\n")
|
||||
else:
|
||||
f.write(line)
|
||||
' "$MANIFEST_FILE" "$legacy_script" ".mam_deploy/$legacy_script" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
# Safe refresh & fingerprint checking logic
|
||||
TS=$(date -u +%Y%m%dT%H%M%SZ)
|
||||
PRESERVED_COUNT=0
|
||||
MODIFIED_FILES=()
|
||||
|
||||
mkdir -p .agents
|
||||
( cd "$STAGE_DIR/.agents" && find . -type f -print ) | while IFS= read -r rel; do
|
||||
case "$rel" in
|
||||
./reports/*|./references/*) continue ;;
|
||||
*.tmp|*.log|*.pyc|*/__pycache__/*) continue ;;
|
||||
esac
|
||||
|
||||
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
|
||||
# 3-way check using python3 inline
|
||||
STAGING_FILE="$STAGE_DIR/.agents/$rel"
|
||||
ACTION=$(python3 - "$dest" "$STAGING_FILE" ".mam/asset_hashes.txt" "$OVERWRITE_CUSTOM" <<'PY'
|
||||
import sys, hashlib, os
|
||||
|
||||
target_path, staging_path, hash_db_path, force_overwrite = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4] == "1"
|
||||
|
||||
def file_sha(path):
|
||||
if not os.path.exists(path):
|
||||
return None
|
||||
h = hashlib.sha256()
|
||||
with open(path, "rb") as f:
|
||||
while chunk := f.read(65536):
|
||||
h.update(chunk)
|
||||
return h.hexdigest()
|
||||
|
||||
target_sha = file_sha(target_path)
|
||||
staging_sha = file_sha(staging_path)
|
||||
|
||||
if target_sha is None:
|
||||
print("COPY_NEW")
|
||||
sys.exit(0)
|
||||
|
||||
if target_sha == staging_sha:
|
||||
print("NO_OP")
|
||||
sys.exit(0)
|
||||
|
||||
db_sha = None
|
||||
if os.path.exists(hash_db_path):
|
||||
with open(hash_db_path, "r") as f:
|
||||
for line in f:
|
||||
parts = line.strip().split(None, 1)
|
||||
if len(parts) == 2 and parts[1] == target_path:
|
||||
db_sha = parts[0]
|
||||
break
|
||||
|
||||
if db_sha is None:
|
||||
print("BOOTSTRAP_OVERWRITE")
|
||||
elif target_sha == db_sha:
|
||||
print("UPDATE_UNMODIFIED")
|
||||
else:
|
||||
if force_overwrite:
|
||||
print("FORCE_OVERWRITE_CUSTOM")
|
||||
else:
|
||||
print("PRESERVE_CUSTOM")
|
||||
PY
|
||||
)
|
||||
case "$ACTION" in
|
||||
COPY_NEW|UPDATE_UNMODIFIED|BOOTSTRAP_OVERWRITE|FORCE_OVERWRITE_CUSTOM)
|
||||
if [ "$ACTION" = "BOOTSTRAP_OVERWRITE" ] || [ "$ACTION" = "FORCE_OVERWRITE_CUSTOM" ]; then
|
||||
BACKUP_DIR=".mam/skill-backups/$TS/$(dirname "$dest")"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
cp "$dest" "$BACKUP_DIR/"
|
||||
fi
|
||||
cp -f "$STAGING_FILE" "$dest"
|
||||
if ! grep -Fqx "$dest" "$MANIFEST_FILE" 2>/dev/null; then
|
||||
echo "$dest" >> "$MANIFEST_FILE"
|
||||
fi
|
||||
;;
|
||||
PRESERVE_CUSTOM)
|
||||
BACKUP_DIR=".mam/skill-backups/$TS/$(dirname "$dest")"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
cp "$dest" "$BACKUP_DIR/"
|
||||
echo "PRESERVED:$dest"
|
||||
;;
|
||||
NO_OP)
|
||||
if ! grep -Fqx "$dest" "$MANIFEST_FILE" 2>/dev/null; then
|
||||
echo "$dest" >> "$MANIFEST_FILE"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
elif [ ! -e "$dest" ]; then
|
||||
cp "$STAGE_DIR/.agents/$rel" "$dest" || { echo "❌ Error: Failed to copy $rel" >&2; exit 1; }
|
||||
cp "$STAGE_DIR/.agents/$rel" "$dest"
|
||||
if ! grep -Fqx "$dest" "$MANIFEST_FILE" 2>/dev/null; then
|
||||
echo "$dest" >> "$MANIFEST_FILE"
|
||||
fi
|
||||
fi
|
||||
done | while IFS= read -r line; do
|
||||
if [[ "$line" == PRESERVED:* ]]; then
|
||||
echo "ℹ️ Local modification detected: ${line#PRESERVED:}" >&2
|
||||
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
|
||||
# Re-build asset_hashes.txt for all framework owned files
|
||||
python3 - .mam/asset_hashes.txt <<'PY'
|
||||
import os, hashlib, sys
|
||||
|
||||
hash_db_path = sys.argv[1]
|
||||
hashes = []
|
||||
|
||||
for root, _, files in os.walk(".agents/skills"):
|
||||
for file in files:
|
||||
path = os.path.join(root, file)
|
||||
h = hashlib.sha256()
|
||||
with open(path, "rb") as f:
|
||||
while chunk := f.read(65536):
|
||||
h.update(chunk)
|
||||
hashes.append(f"{h.hexdigest()} {path}\n")
|
||||
|
||||
with open(hash_db_path, "w") as f:
|
||||
f.writelines(sorted(hashes))
|
||||
PY
|
||||
|
||||
# Copy root docs (R-2 essential set)
|
||||
ROOT_DOCS="AGENTS.md"
|
||||
if [ "${MAM_INSTALL_DOCS:-minimal}" = "full" ]; then
|
||||
ROOT_DOCS="AGENTS.md MESSAGING.md BOOTSTRAP.md BOOTSTRAP.ko.md"
|
||||
fi
|
||||
for doc in $ROOT_DOCS; do
|
||||
if [ -f "$STAGE_DIR/$doc" ] && [ ! -e "$doc" ]; then
|
||||
cp "$STAGE_DIR/$doc" . || { echo "❌ Error: Failed to copy $doc" >&2; exit 1; }
|
||||
cp "$STAGE_DIR/$doc" .
|
||||
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"
|
||||
# Install remove.sh and update.sh into .mam_deploy/
|
||||
mkdir -p .mam_deploy
|
||||
if [ -f "$STAGE_DIR/deploy/remove.sh" ]; then
|
||||
cp "$STAGE_DIR/deploy/remove.sh" .mam_deploy/remove.sh
|
||||
chmod 0755 .mam_deploy/remove.sh
|
||||
if ! grep -Fqx ".mam_deploy/remove.sh" "$MANIFEST_FILE" 2>/dev/null; then
|
||||
echo ".mam_deploy/remove.sh" >> "$MANIFEST_FILE"
|
||||
fi
|
||||
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"
|
||||
if [ -f "$STAGE_DIR/deploy/update.sh" ]; then
|
||||
cp "$STAGE_DIR/deploy/update.sh" .mam_deploy/update.sh
|
||||
chmod 0755 .mam_deploy/update.sh
|
||||
if ! grep -Fqx ".mam_deploy/update.sh" "$MANIFEST_FILE" 2>/dev/null; then
|
||||
echo ".mam_deploy/update.sh" >> "$MANIFEST_FILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -f "$STAGE_DIR/.mam.env.example" ] && [ ! -e ".mam.env.example" ]; then
|
||||
cp "$STAGE_DIR/.mam.env.example" . || { echo "❌ Error: Failed to copy .mam.env.example" >&2; exit 1; }
|
||||
cp "$STAGE_DIR/.mam.env.example" .
|
||||
echo ".mam.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
|
||||
if [ -f "$STAGE_DIR/deploy/INSTALL.md" ] && [ ! -e ".agents/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
|
||||
cp "$STAGE_DIR/deploy/INSTALL.md" .agents/INSTALL.md
|
||||
echo ".agents/INSTALL.md" >> "$MANIFEST_FILE"
|
||||
fi
|
||||
|
||||
# Record version stamp
|
||||
STAGE_COMMIT="unknown"
|
||||
if [ -d "$STAGE_DIR/.git" ]; then
|
||||
STAGE_COMMIT=$(git -C "$STAGE_DIR" rev-parse HEAD 2>/dev/null || echo "unknown")
|
||||
fi
|
||||
cat <<EOF > .mam/version.txt
|
||||
source=$REPO_URL
|
||||
commit=$STAGE_COMMIT
|
||||
fetched_at=$TS
|
||||
method=$FETCH_METHOD
|
||||
EOF
|
||||
|
||||
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
|
||||
echo "❌ Error: Core runtime assets missing after setup." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Orchestration skills present."
|
||||
@@ -226,20 +362,80 @@ 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
|
||||
|
||||
# R-4: Manage .gitignore block (never put .gitignore in manifest)
|
||||
MAM_GI_START="# >>> MAM managed block (managed by install.sh — do not edit) >>>"
|
||||
MAM_GI_END="# <<< MAM managed block <<<"
|
||||
|
||||
if [ "${MAM_SKIP_GITIGNORE:-0}" != "1" ]; then
|
||||
GI_CREATED=0
|
||||
[ -e .gitignore ] || { touch .gitignore; GI_CREATED=1; }
|
||||
|
||||
if ! grep -q '^gitignore_created=' .mam/install_state 2>/dev/null; then
|
||||
echo "gitignore_created=$GI_CREATED" >> .mam/install_state
|
||||
fi
|
||||
|
||||
python3 - .gitignore "$MAM_GI_START" "$MAM_GI_END" <<'PY'
|
||||
import sys, os
|
||||
|
||||
gi_path, start_marker, end_marker = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||
|
||||
block_lines = [
|
||||
start_marker + "\n",
|
||||
"/.venv/\n",
|
||||
"/.mam/\n",
|
||||
"/.mam_deploy/\n",
|
||||
"/.mam.env\n",
|
||||
"/.mam.env.*\n",
|
||||
"!/.mam.env.example\n",
|
||||
"/.cache/multi-agent-mux-monitor/\n",
|
||||
"/.mam-skill-backup.*/\n",
|
||||
"CURRENT_JOB.md\n",
|
||||
end_marker + "\n"
|
||||
]
|
||||
|
||||
lines = []
|
||||
if os.path.exists(gi_path):
|
||||
with open(gi_path, "r") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
new_lines = []
|
||||
in_block = False
|
||||
block_inserted = False
|
||||
|
||||
for line in lines:
|
||||
if line.strip() == start_marker:
|
||||
in_block = True
|
||||
if not block_inserted:
|
||||
new_lines.extend(block_lines)
|
||||
block_inserted = True
|
||||
continue
|
||||
if line.strip() == end_marker:
|
||||
in_block = False
|
||||
continue
|
||||
if not in_block:
|
||||
new_lines.append(line)
|
||||
|
||||
if not block_inserted:
|
||||
if new_lines and not new_lines[-1].endswith("\n"):
|
||||
new_lines[-1] += "\n"
|
||||
new_lines.extend(block_lines)
|
||||
|
||||
with open(gi_path, "w") as f:
|
||||
f.writelines(new_lines)
|
||||
PY
|
||||
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."
|
||||
echo "⚠️ WARNING: Target directory is on a network filesystem."
|
||||
else
|
||||
echo "✅ File system supports WAL (Local storage detected)."
|
||||
fi
|
||||
@@ -247,38 +443,32 @@ if command -v df &>/dev/null && command -v mount &>/dev/null; then
|
||||
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
|
||||
if [ "${MAM_SKIP_VENV:-0}" != "1" ]; then
|
||||
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
|
||||
source "$VENV_NAME"/bin/activate
|
||||
pip install --upgrade pip
|
||||
|
||||
# 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
|
||||
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
|
||||
pip install "paho-mqtt>=2.0.0" pyyaml
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- 5. Generate Environment Template ---
|
||||
ENV_FILE=".mam.env"
|
||||
ENV_EXAMPLE=".mam.env.example"
|
||||
|
||||
# M-2: Evidence-based legacy env migration helper
|
||||
migrate_legacy_env() {
|
||||
local manifest=".mam/install_manifest.txt"
|
||||
[ -f ".mam.env" ] && return 0
|
||||
@@ -314,7 +504,6 @@ with open(path, "w") as f:
|
||||
|
||||
migrate_legacy_env
|
||||
|
||||
# M-1: Shadowing prevention guard — only create new default config if no legacy env or update tmp exists
|
||||
if [ ! -f "$ENV_FILE" ] && [ ! -f ".env" ] && [ ! -f ".env.update-tmp" ]; then
|
||||
if [ -f "$ENV_EXAMPLE" ]; then
|
||||
echo "📝 Creating configuration from $ENV_EXAMPLE..."
|
||||
@@ -323,8 +512,7 @@ if [ ! -f "$ENV_FILE" ] && [ ! -f ".env" ] && [ ! -f ".env.update-tmp" ]; then
|
||||
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 ===
|
||||
@@ -336,8 +524,7 @@ HERDR_SERVER_NAME=default
|
||||
EOF
|
||||
chmod 0600 "$ENV_FILE"
|
||||
echo "✅ Config file .mam.env initialized with chmod 0600."
|
||||
|
||||
# Record the newly created .mam.env in the manifest
|
||||
|
||||
mkdir -p .mam
|
||||
touch .mam/install_manifest.txt
|
||||
echo "$ENV_FILE" >> .mam/install_manifest.txt
|
||||
|
||||
+69
-27
@@ -114,15 +114,27 @@ log_info "Deploying orchestration rules & skills (.agents/)..."
|
||||
mkdir -p "$TARGET_DIR/.agents"
|
||||
|
||||
# Sync rules and skills, avoiding copying temporary or system files
|
||||
# Exclude git histories, reports, logs or internal runtime cache if any
|
||||
rsync -a --exclude='.git/' --exclude='/reports/' --exclude='*.log' --exclude='__pycache__/' --exclude='*.pyc' "$SRC_DIR/.agents/" "$TARGET_DIR/.agents/"
|
||||
# Exclude git histories, reports, references, logs or internal runtime cache
|
||||
rsync -a --exclude='.git/' --exclude='/reports/' --exclude='/references/' --exclude='*.log' --exclude='*.tmp' --exclude='__pycache__/' --exclude='*.pyc' "$SRC_DIR/.agents/" "$TARGET_DIR/.agents/"
|
||||
log_ok "Deployed Rules and Skills under target's .agents/"
|
||||
|
||||
|
||||
# Copy config templates and generate scripts (M-2)
|
||||
if [ -f "$SRC_DIR/.mam.env.example" ]; then
|
||||
cp "$SRC_DIR/.mam.env.example" "$TARGET_DIR/.mam.env.example"
|
||||
log_ok "Copied .mam.env.example configuration template"
|
||||
fi
|
||||
|
||||
# Deploy remove.sh and update.sh into .mam_deploy/ (R-3)
|
||||
mkdir -p "$TARGET_DIR/.mam_deploy"
|
||||
if [ -f "$SRC_DIR/deploy/remove.sh" ]; then
|
||||
cp "$SRC_DIR/deploy/remove.sh" "$TARGET_DIR/.mam_deploy/remove.sh"
|
||||
chmod 0755 "$TARGET_DIR/.mam_deploy/remove.sh"
|
||||
fi
|
||||
if [ -f "$SRC_DIR/deploy/update.sh" ]; then
|
||||
cp "$SRC_DIR/deploy/update.sh" "$TARGET_DIR/.mam_deploy/update.sh"
|
||||
chmod 0755 "$TARGET_DIR/.mam_deploy/update.sh"
|
||||
fi
|
||||
|
||||
if [ -f "$SRC_DIR/deploy/generate-env.sh" ]; then
|
||||
mkdir -p "$TARGET_DIR/scripts"
|
||||
cp "$SRC_DIR/deploy/generate-env.sh" "$TARGET_DIR/scripts/generate-env.sh"
|
||||
@@ -160,32 +172,62 @@ else
|
||||
log_ok "Guidelines AGENTS.md copied to project root."
|
||||
fi
|
||||
|
||||
# 4. Gitignore adjustments
|
||||
# 4. Gitignore adjustments (R-4: managed block)
|
||||
log_info "Registering runtime isolation blocks in .gitignore..."
|
||||
GITIGNORE="$TARGET_DIR/.gitignore"
|
||||
MAM_PATTERN="/.mam/"
|
||||
VENV_PATTERN="/.venv/"
|
||||
|
||||
if [ -f "$GITIGNORE" ]; then
|
||||
# Register .mam/ if absent
|
||||
if grep -Eq '^/?\.mam/?$' "$GITIGNORE"; then
|
||||
log_ok ".mam/ already registered in target's .gitignore."
|
||||
else
|
||||
echo -e "\n# Multi-Agent Mux (MAM) runtime databases and isolation cache\n$MAM_PATTERN" >> "$GITIGNORE"
|
||||
log_ok "Appended /.mam/ registration to .gitignore."
|
||||
fi
|
||||
|
||||
# Register .venv/ if absent
|
||||
if grep -Eq '^/?\.venv/?$' "$GITIGNORE"; then
|
||||
log_ok ".venv/ already registered in target's .gitignore."
|
||||
else
|
||||
echo -e "\n# Python virtual environment\n$VENV_PATTERN" >> "$GITIGNORE"
|
||||
log_ok "Appended /.venv/ registration to .gitignore."
|
||||
fi
|
||||
else
|
||||
echo -e "# Multi-Agent Mux (MAM) runtime databases and isolation cache\n$MAM_PATTERN\n\n# Python virtual environment\n$VENV_PATTERN" > "$GITIGNORE"
|
||||
log_ok "Created .gitignore with MAM and .venv exclusions."
|
||||
fi
|
||||
MAM_GI_START="# >>> MAM managed block (managed by install.sh — do not edit) >>>"
|
||||
MAM_GI_END="# <<< MAM managed block <<<"
|
||||
|
||||
python3 - "$GITIGNORE" "$MAM_GI_START" "$MAM_GI_END" <<'PY'
|
||||
import sys, os
|
||||
|
||||
gi_path, start_marker, end_marker = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||
|
||||
block_lines = [
|
||||
start_marker + "\n",
|
||||
"/.venv/\n",
|
||||
"/.mam/\n",
|
||||
"/.mam_deploy/\n",
|
||||
"/.mam.env\n",
|
||||
"/.mam.env.*\n",
|
||||
"!/.mam.env.example\n",
|
||||
"/.cache/multi-agent-mux-monitor/\n",
|
||||
"/.mam-skill-backup.*/\n",
|
||||
"CURRENT_JOB.md\n",
|
||||
end_marker + "\n"
|
||||
]
|
||||
|
||||
lines = []
|
||||
if os.path.exists(gi_path):
|
||||
with open(gi_path, "r") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
new_lines = []
|
||||
in_block = False
|
||||
block_inserted = False
|
||||
|
||||
for line in lines:
|
||||
if line.strip() == start_marker:
|
||||
in_block = True
|
||||
if not block_inserted:
|
||||
new_lines.extend(block_lines)
|
||||
block_inserted = True
|
||||
continue
|
||||
if line.strip() == end_marker:
|
||||
in_block = False
|
||||
continue
|
||||
if not in_block:
|
||||
new_lines.append(line)
|
||||
|
||||
if not block_inserted:
|
||||
if new_lines and not new_lines[-1].endswith("\n"):
|
||||
new_lines[-1] += "\n"
|
||||
new_lines.extend(block_lines)
|
||||
|
||||
with open(gi_path, "w") as f:
|
||||
f.writelines(new_lines)
|
||||
PY
|
||||
log_ok "Registered MAM managed block in .gitignore."
|
||||
|
||||
# 5. Python Virtual Environment Setup (F-1)
|
||||
log_info "Bootstrapping Python virtual environment (.venv) in target..."
|
||||
|
||||
+119
-33
@@ -11,6 +11,7 @@ set -euo pipefail
|
||||
TARGET_DIR=""
|
||||
FORCE=0
|
||||
PURGE_ENV=0
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
@@ -31,7 +32,15 @@ while [[ $# -gt 0 ]]; do
|
||||
done
|
||||
|
||||
if [ -z "$TARGET_DIR" ]; then
|
||||
TARGET_DIR="$(pwd)"
|
||||
if [ "$(basename "$SCRIPT_DIR")" = ".mam_deploy" ]; then
|
||||
TARGET_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
else
|
||||
TARGET_DIR="$(pwd)"
|
||||
fi
|
||||
else
|
||||
if [ "$(basename "$TARGET_DIR")" = ".mam_deploy" ]; then
|
||||
TARGET_DIR="$(dirname "$TARGET_DIR")"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "===================================================================="
|
||||
@@ -46,6 +55,11 @@ fi
|
||||
|
||||
cd "$TARGET_DIR"
|
||||
|
||||
GI_CREATED=0
|
||||
if [ -f ".mam/install_state" ]; then
|
||||
GI_CREATED=$(grep '^gitignore_created=' .mam/install_state 2>/dev/null | cut -d= -f2 || echo 0)
|
||||
fi
|
||||
|
||||
# 1. Non-interactive input safety guard (set -e read crash prevention)
|
||||
if [ ! -t 0 ] && [ $FORCE -eq 0 ]; then
|
||||
echo "❌ Error: Non-interactive terminal detected. Please run with -y/--yes/--force." >&2
|
||||
@@ -66,7 +80,6 @@ if [ -f "$MANIFEST_FILE" ]; then
|
||||
fi
|
||||
done < "$MANIFEST_FILE"
|
||||
else
|
||||
# Fallback to the core MAM directories to check if any exist
|
||||
fallback_assets=(
|
||||
".agents/skills/lib.sh"
|
||||
".agents/skills/multi-agent-mux-create"
|
||||
@@ -77,6 +90,7 @@ else
|
||||
".agents/skills/multi-agent-mux-stop"
|
||||
".venv"
|
||||
".mam"
|
||||
".mam_deploy"
|
||||
)
|
||||
for asset in "${fallback_assets[@]}"; do
|
||||
if [ -e "$asset" ] || [ -h "$asset" ]; then
|
||||
@@ -95,7 +109,6 @@ fi
|
||||
if [ $FORCE -eq 0 ]; then
|
||||
echo "⚠️ WARNING: This will permanently remove the MAM orchestration skills, "
|
||||
echo " virtual environment (.venv), local metadata (.mam), and docs."
|
||||
echo " (Your own custom files inside .agents/ will NOT be touched)."
|
||||
|
||||
if ! read -p "❓ Are you sure you want to proceed? [y/N]: " -r response; then
|
||||
response="n"
|
||||
@@ -114,39 +127,110 @@ delete_asset() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Check for modified skills before deleting
|
||||
TS=$(date -u +%Y%m%dT%H%M%SZ)
|
||||
python3 - ".mam/asset_hashes.txt" "$TS" <<'PY' 2>/dev/null || true
|
||||
import sys, os, hashlib, shutil
|
||||
|
||||
hash_db_path = sys.argv[1]
|
||||
ts = sys.argv[2]
|
||||
|
||||
if not os.path.exists(hash_db_path):
|
||||
sys.exit(0)
|
||||
|
||||
modified = []
|
||||
with open(hash_db_path, "r") as f:
|
||||
for line in f:
|
||||
parts = line.strip().split(None, 1)
|
||||
if len(parts) == 2:
|
||||
expected_hash, path = parts[0], parts[1]
|
||||
if os.path.exists(path):
|
||||
h = hashlib.sha256()
|
||||
with open(path, "rb") as pf:
|
||||
while chunk := pf.read(65536):
|
||||
h.update(chunk)
|
||||
if h.hexdigest() != expected_hash:
|
||||
modified.append(path)
|
||||
|
||||
if modified:
|
||||
backup_dir = f".mam-skill-backup.{ts}"
|
||||
os.makedirs(backup_dir, exist_ok=True)
|
||||
for p in modified:
|
||||
dest = os.path.join(backup_dir, p)
|
||||
os.makedirs(os.path.dirname(dest), exist_ok=True)
|
||||
shutil.copy2(p, dest)
|
||||
print(f"💾 Preserved {len(modified)} modified skill file(s) under {backup_dir}")
|
||||
PY
|
||||
|
||||
# 2. Uninstall files using the manifest if present
|
||||
if [ ${#manifest_files[@]} -gt 0 ]; then
|
||||
echo "📜 Manifest found. Reversing installer-created files..."
|
||||
for f in ${manifest_files[@]+"${manifest_files[@]}"}; do
|
||||
# Skip .env, .mam.env, and remove.sh for now, they are handled separately
|
||||
if [ "$f" = ".env" ] || [ "$f" = ".mam.env" ] || [ "$f" = "remove.sh" ]; then
|
||||
if [ "$f" = ".env" ] || [ "$f" = ".mam.env" ] || [ "$f" = "remove.sh" ] || [ "$f" = ".mam_deploy/remove.sh" ]; then
|
||||
continue
|
||||
fi
|
||||
delete_asset "$f"
|
||||
done
|
||||
else
|
||||
# Fallback: Delete MAM skills manually (only if manifest is missing)
|
||||
echo "⚠️ No manifest found. Deleting standard MAM skills..."
|
||||
delete_asset ".agents/skills/lib.sh"
|
||||
delete_asset ".agents/skills/multi-agent-mux-create"
|
||||
delete_asset ".agents/skills/multi-agent-mux-delegate-job"
|
||||
delete_asset ".agents/skills/multi-agent-mux-monitor"
|
||||
delete_asset ".agents/skills/multi-agent-mux-resume"
|
||||
delete_asset ".agents/skills/multi-agent-mux-status"
|
||||
delete_asset ".agents/skills/multi-agent-mux-stop"
|
||||
fi
|
||||
else
|
||||
echo "⚠️ No manifest found. Deleting standard MAM skills..."
|
||||
delete_asset ".agents/skills/lib.sh"
|
||||
delete_asset ".agents/skills/multi-agent-mux-create"
|
||||
delete_asset ".agents/skills/multi-agent-mux-delegate-job"
|
||||
delete_asset ".agents/skills/multi-agent-mux-monitor"
|
||||
delete_asset ".agents/skills/multi-agent-mux-resume"
|
||||
delete_asset ".agents/skills/multi-agent-mux-status"
|
||||
delete_asset ".agents/skills/multi-agent-mux-stop"
|
||||
fi
|
||||
|
||||
# 3. Clean up empty parent directories under .agents recursively to avoid littering
|
||||
if [ -d ".agents" ]; then
|
||||
find .agents -depth -type d -exec rmdir {} + 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# 4. Remove virtual environment, monitor cache, and metadata database
|
||||
# Clean up .gitignore managed block (C8)
|
||||
MAM_GI_START="# >>> MAM managed block (managed by install.sh — do not edit) >>>"
|
||||
MAM_GI_END="# <<< MAM managed block <<<"
|
||||
|
||||
if [ -f .gitignore ]; then
|
||||
python3 - .gitignore "$MAM_GI_START" "$MAM_GI_END" "$GI_CREATED" <<'PY'
|
||||
import sys, os
|
||||
|
||||
gi_path, start_marker, end_marker, gi_created = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4] == "1"
|
||||
|
||||
if not os.path.exists(gi_path):
|
||||
sys.exit(0)
|
||||
|
||||
with open(gi_path, "r") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
new_lines = []
|
||||
in_block = False
|
||||
block_found = False
|
||||
|
||||
for line in lines:
|
||||
if line.strip() == start_marker:
|
||||
in_block = True
|
||||
block_found = True
|
||||
continue
|
||||
if line.strip() == end_marker:
|
||||
in_block = False
|
||||
continue
|
||||
if not in_block:
|
||||
new_lines.append(line)
|
||||
|
||||
if block_found:
|
||||
content = "".join(new_lines).strip()
|
||||
if gi_created and not content:
|
||||
os.remove(gi_path)
|
||||
else:
|
||||
with open(gi_path, "w") as f:
|
||||
f.writelines(new_lines)
|
||||
PY
|
||||
fi
|
||||
|
||||
delete_asset ".venv"
|
||||
delete_asset ".cache/multi-agent-mux-monitor"
|
||||
delete_asset ".mam" # Deletes manifest file too
|
||||
delete_asset ".mam"
|
||||
|
||||
# 5. Clean up .env and .mam.env files (Only if created by installer, or forced with --purge-env)
|
||||
for env_name in ".mam.env" ".env"; do
|
||||
[ -f "$env_name" ] || continue
|
||||
|
||||
@@ -197,7 +281,6 @@ for env_name in ".mam.env" ".env"; do
|
||||
fi
|
||||
mv "$env_name" "$slot"
|
||||
echo "💾 Backed up $env_name -> $slot"
|
||||
echo " To remove the configuration entirely, re-run with --purge-env."
|
||||
fi
|
||||
else
|
||||
echo "ℹ️ Preserving user-owned $env_name configuration."
|
||||
@@ -205,21 +288,24 @@ for env_name in ".mam.env" ".env"; do
|
||||
fi
|
||||
done
|
||||
|
||||
# 6. Remove uninstaller file itself (if we are in the target root)
|
||||
# Simple check: only delete remove.sh if it is recorded in the manifest
|
||||
remove_in_manifest=0
|
||||
for f in ${manifest_files[@]+"${manifest_files[@]}"}; do
|
||||
if [ "$f" = "remove.sh" ]; then
|
||||
remove_in_manifest=1
|
||||
break
|
||||
# Remove uninstaller file(s)
|
||||
for self in ".mam_deploy/remove.sh" "remove.sh"; do
|
||||
[ -f "$self" ] || continue
|
||||
in_manifest=0
|
||||
for f in ${manifest_files[@]+"${manifest_files[@]}"}; do
|
||||
if [ "$f" = "$self" ]; then
|
||||
in_manifest=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ $in_manifest -eq 1 ] || [ $FORCE -eq 1 ]; then
|
||||
echo "🗑️ Removing uninstaller: $self"
|
||||
rm -f "$self"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -f "remove.sh" ] && [ $remove_in_manifest -eq 1 ]; then
|
||||
echo "🗑️ Removing uninstaller: remove.sh"
|
||||
# Self-delete is the final action
|
||||
rm -f "remove.sh"
|
||||
fi
|
||||
delete_asset ".mam_deploy/update.sh"
|
||||
rmdir .mam_deploy 2>/dev/null || true
|
||||
|
||||
echo "===================================================================="
|
||||
echo "🎉 Uninstallation complete!"
|
||||
|
||||
+48
-4
@@ -9,6 +9,7 @@ set -euo pipefail
|
||||
|
||||
TARGET_DIR=""
|
||||
FORCE=0
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
@@ -25,8 +26,15 @@ while [[ $# -gt 0 ]]; do
|
||||
done
|
||||
|
||||
if [ -z "$TARGET_DIR" ]; then
|
||||
TARGET_DIR="$(pwd)"
|
||||
if [ "$(basename "$SCRIPT_DIR")" = ".mam_deploy" ]; then
|
||||
TARGET_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
else
|
||||
TARGET_DIR="$(pwd)"
|
||||
fi
|
||||
else
|
||||
if [ "$(basename "$TARGET_DIR")" = ".mam_deploy" ]; then
|
||||
TARGET_DIR="$(dirname "$TARGET_DIR")"
|
||||
fi
|
||||
if [ ! -d "$TARGET_DIR" ]; then
|
||||
echo "❌ Error: Target directory '$TARGET_DIR' does not exist." >&2
|
||||
exit 1
|
||||
@@ -41,8 +49,16 @@ echo "===================================================================="
|
||||
|
||||
cd "$TARGET_DIR"
|
||||
|
||||
# 1. Verification of existing install
|
||||
if [ ! -f "remove.sh" ]; then
|
||||
# 1. Verification of existing install (B-2: dual resolution)
|
||||
REMOVER=""
|
||||
for cand in ".mam_deploy/remove.sh" "remove.sh"; do
|
||||
if [ -f "$cand" ]; then
|
||||
REMOVER="$cand"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$REMOVER" ]; then
|
||||
echo "❌ Error: No MAM installation (remove.sh) found in '$TARGET_DIR'." >&2
|
||||
echo " Please run install.sh first to set up the workspace." >&2
|
||||
exit 1
|
||||
@@ -117,6 +133,16 @@ if [ -d ".mam" ]; then
|
||||
if [ -f ".mam/install_manifest.txt" ]; then
|
||||
cp -f .mam/install_manifest.txt .mam.update-tmp/
|
||||
fi
|
||||
# C12: Copy MAM state files across update cycle
|
||||
for st in install_state asset_hashes.txt version.txt; do
|
||||
if [ -f ".mam/$st" ]; then
|
||||
cp -f ".mam/$st" .mam.update-tmp/
|
||||
fi
|
||||
done
|
||||
if [ -d ".mam/skill-backups" ]; then
|
||||
mkdir -p .mam.update-tmp/skill-backups
|
||||
cp -rf .mam/skill-backups/* .mam.update-tmp/skill-backups/ 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Define trap to restore backup files on failure
|
||||
@@ -139,6 +165,15 @@ restore_on_failure() {
|
||||
if [ -f ".mam.update-tmp/install_manifest.txt" ]; then
|
||||
cp -f .mam.update-tmp/install_manifest.txt .mam/ 2>/dev/null || true
|
||||
fi
|
||||
for st in install_state asset_hashes.txt version.txt; do
|
||||
if [ -f ".mam.update-tmp/$st" ]; then
|
||||
cp -f ".mam.update-tmp/$st" .mam/ 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
if [ -d ".mam.update-tmp/skill-backups" ]; then
|
||||
mkdir -p .mam/skill-backups
|
||||
cp -rf .mam.update-tmp/skill-backups/* .mam/skill-backups/ 2>/dev/null || true
|
||||
fi
|
||||
rm -rf .mam.update-tmp 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
@@ -148,7 +183,7 @@ trap restore_on_failure EXIT
|
||||
echo "🗑️ Removing existing installation..."
|
||||
# remove.sh will run in manifest mode because .mam/install_manifest.txt is still present.
|
||||
# It will delete .agents/, documents, scripts, .venv, and .mam folder.
|
||||
bash remove.sh --force
|
||||
bash "$REMOVER" --force "$TARGET_DIR"
|
||||
|
||||
# 4. Fetch and run the latest installer from Gitea
|
||||
echo "📥 Fetching and running the latest installer..."
|
||||
@@ -194,6 +229,15 @@ if [ $HAS_MAM -eq 1 ]; then
|
||||
mkdir -p .mam/delegate_job_logs
|
||||
cp -rf .mam.update-tmp/delegate_job_logs/* .mam/delegate_job_logs/
|
||||
fi
|
||||
for st in install_state asset_hashes.txt version.txt; do
|
||||
if [ -f ".mam.update-tmp/$st" ]; then
|
||||
cp -f ".mam.update-tmp/$st" .mam/
|
||||
fi
|
||||
done
|
||||
if [ -d ".mam.update-tmp/skill-backups" ]; then
|
||||
mkdir -p .mam/skill-backups
|
||||
cp -rf .mam.update-tmp/skill-backups/* .mam/skill-backups/ 2>/dev/null || true
|
||||
fi
|
||||
rm -rf ".mam.update-tmp"
|
||||
fi
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user