feat: multi-agent-mux-update skill and in-place install (Issue #72)
The update is a skill (decision 4); deploy/update.sh and the installed .mam_deploy/update.sh are gone. - Phase 1 (installed code, update.sh): publish the update marker under the exclusive admission lock; refuse before any change while a running row, a live herdr agent in the workspace on any running server, an open job (unreadable JSON included, dry_run not), a live loop marker or a monitor.lock held by a monitor the skills did not start (U3) is there - the calling agent (server + pane, its row and one running job) excepted (lib_py/update.py precheck, decisions 1 and 7). Stop the monitor (TERM, then KILL and restart it if it does not end; decision 2), handle a leftover backup by its state (U4), back up .agents, .mam_deploy, root files, .venv, the installer's .mam files and the registry (SQLite backup API under the registry lock) into .mam/update-backup/, then exec a copy of install_phase.sh outside the tree (decision 5). - The copy (no lib.sh/lib_py, bash 3.2) runs the installer in place; on failure it restores the backup and restarts the monitor from the restored tree; on success it runs phase 2, post_update.sh of the new tree, which converts the registry with lib_py.registry_format and restarts the monitor if it ran. The backup and the marker are removed at the end. - install.sh updates in place (decisions 3, 4, 9): install_state keys are set, one line each; no updater is installed and an old one (.mam_deploy/update.sh, or a root update.sh the manifest lists) is removed; framework files the new version no longer ships are deleted when unmodified, kept and listed when modified, and dropped from base/, the manifest and asset_hashes.txt; a local source's .mam is not staged. remove.sh knows the new skill; CI shellchecks its scripts; docs point at the skill. - test_issue8_update_state and test_deploy_layout td6/td27 run the skill. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6359d65999
commit
47fe24e07c
19 files changed
+1820
-314
No files matched your search
+3
-3
@@ -9,7 +9,7 @@ This directory contains packaging templates and installation scripts to deploy t
|
||||
* **`install.sh`**: A self-contained, idempotent remote shell installer (via curl) that checks system requirements (`herdr`, `python3`), sets up a local python virtual environment (`.venv`), and performs 3-way refresh with key-level registry merge (Rev.2).
|
||||
* **`install_mam.sh`**: A local-clone installer that copies rules/skills (`.agents/`), `AGENTS.md`, manifests, and asset hashes into target projects.
|
||||
* **`lib_ownership.sh`**: Single source of truth for framework-owned files and key-level registry files.
|
||||
* **`update.sh`**: In-place updater script installed into target `.mam_deploy/update.sh`.
|
||||
* **Updates**: installed workspaces update with the `multi-agent-mux-update` skill (`bash .agents/skills/multi-agent-mux-update/scripts/update.sh`).
|
||||
* **`remove.sh`**: Clean uninstaller script installed into target `.mam_deploy/remove.sh`.
|
||||
* **`generate-env.sh`**: Environment configuration bootstrap helper.
|
||||
* **`INSTALL.md`**: Detailed installation and quick-start user manual.
|
||||
@@ -40,7 +40,7 @@ bash deploy/install_mam.sh --target /path/to/your/project
|
||||
Refer to **`INSTALL.md`** inside this directory for the full instructions and workflows.
|
||||
|
||||
> [!NOTE]
|
||||
> `install_mam.sh` automatically deploys `.mam_deploy/update.sh` and `.mam_deploy/remove.sh` into target workspaces, recording install manifests and asset hashes to enable clean updates and uninstalls.
|
||||
> `install_mam.sh` automatically deploys `.mam_deploy/remove.sh` into target workspaces, recording install manifests and asset hashes to enable clean updates and uninstalls.
|
||||
|
||||
### 3. Custom Fork / Private Mirror Installations
|
||||
If you run a private mirror or fork, you can override the source URLs during installation using environment variables:
|
||||
@@ -53,7 +53,7 @@ curl -fsSL https://my-mirror.example.com/.../install.sh \
|
||||
bash
|
||||
|
||||
# Updating an existing workspace against a mirror
|
||||
MAM_INSTALLER_URL=https://my-mirror.example.com/.../install.sh bash deploy/update.sh
|
||||
MAM_INSTALLER_URL=https://my-mirror.example.com/.../install.sh bash .agents/skills/multi-agent-mux-update/scripts/update.sh
|
||||
```
|
||||
|
||||
### 4. Registering as a Workspace Plugin
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ jobs:
|
||||
shellcheck deploy/install.sh
|
||||
shellcheck deploy/install_mam.sh
|
||||
shellcheck deploy/generate-env.sh
|
||||
shellcheck deploy/update.sh
|
||||
shellcheck .agents/skills/multi-agent-mux-update/scripts/*.sh
|
||||
shellcheck deploy/remove.sh
|
||||
echo "✅ ShellCheck completed successfully."
|
||||
|
||||
|
||||
+67
-18
@@ -125,7 +125,9 @@ if [ "$REFRESH" -eq 1 ] || [ "${MAM_SKIP_VENV:-0}" -eq 1 ] || ! check_assets_pre
|
||||
FETCH_METHOD="archive"
|
||||
if [ -d "$REPO_URL" ]; then
|
||||
echo "🌐 Copying local working tree into a staging area..."
|
||||
cp -R "$REPO_URL/." "$STAGE_DIR/"
|
||||
# Everything but .mam/: runtime state is never staged, and it may hold the
|
||||
# update skill's rollback copy (.mam/update-backup/, Issue #72).
|
||||
find "$REPO_URL/." -mindepth 1 -maxdepth 1 ! -name .mam -exec cp -R {} "$STAGE_DIR/" \;
|
||||
FETCH_METHOD="local"
|
||||
elif command -v git &>/dev/null; then
|
||||
echo "🌐 Cloning repository (shallow) into a staging area..."
|
||||
@@ -157,9 +159,15 @@ if [ "$REFRESH" -eq 1 ] || [ "${MAM_SKIP_VENV:-0}" -eq 1 ] || ! check_assets_pre
|
||||
MANIFEST_FILE=".mam/install_manifest.txt"
|
||||
touch "$MANIFEST_FILE"
|
||||
|
||||
# Migrate legacy root layout for remove.sh and update.sh into .mam_deploy/ if owned
|
||||
# Drop every line naming this path from the manifest
|
||||
manifest_drop() {
|
||||
grep -Fvx -- "$1" "$MANIFEST_FILE" > "$MANIFEST_FILE.tmp" || true
|
||||
mv -f "$MANIFEST_FILE.tmp" "$MANIFEST_FILE"
|
||||
}
|
||||
|
||||
# Migrate legacy root layout for remove.sh into .mam_deploy/ if owned
|
||||
mkdir -p .mam_deploy
|
||||
for legacy_script in remove.sh update.sh; do
|
||||
for legacy_script in remove.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 '
|
||||
@@ -179,6 +187,13 @@ with open(path, "w") as f:
|
||||
fi
|
||||
done
|
||||
|
||||
# update.sh is no longer shipped (Issue #72; the multi-agent-mux-update skill
|
||||
# updates). A legacy root update.sh goes only if the manifest says we put it there.
|
||||
if [ -f update.sh ] && grep -Fqx update.sh "$MANIFEST_FILE" 2>/dev/null; then
|
||||
rm -f update.sh
|
||||
manifest_drop update.sh
|
||||
fi
|
||||
|
||||
# Safe refresh & fingerprint checking logic
|
||||
TS=$(date -u +%Y%m%dT%H%M%SZ)
|
||||
FRAMEWORK_LEDGER=".mam/.framework_ledger.tmp"
|
||||
@@ -374,7 +389,7 @@ MERGEREG
|
||||
fi
|
||||
done
|
||||
|
||||
# Install remove.sh and update.sh into .mam_deploy/
|
||||
# Install remove.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
|
||||
@@ -384,13 +399,9 @@ MERGEREG
|
||||
fi
|
||||
fi
|
||||
|
||||
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
|
||||
# An older install's updater is not shipped any more (Issue #72)
|
||||
rm -f .mam_deploy/update.sh
|
||||
manifest_drop .mam_deploy/update.sh
|
||||
|
||||
if [ -f "$STAGE_DIR/.mam.env.example" ] && [ ! -e ".mam.env.example" ]; then
|
||||
cp "$STAGE_DIR/.mam.env.example" .
|
||||
@@ -407,6 +418,34 @@ MERGEREG
|
||||
fi
|
||||
fi
|
||||
|
||||
# Framework-owned files the previous install recorded (manifest and
|
||||
# asset_hashes.txt) that this version no longer ships (Issue #72). Unmodified
|
||||
# ones are deleted. Modified ones are kept and become the user's file: they
|
||||
# leave the manifest, so remove.sh leaves them alone, and asset_hashes.txt
|
||||
# (rebuilt below from the shipped files only).
|
||||
if [ -f .mam/asset_hashes.txt ]; then
|
||||
SHIPPED_LIST=".mam/.shipped_paths.tmp"
|
||||
cut -f2 "$FRAMEWORK_LEDGER" > "$SHIPPED_LIST"
|
||||
while read -r _old_sha _old_path; do
|
||||
[ -n "${_old_path:-}" ] || continue
|
||||
is_framework_owned "$_old_path" || continue
|
||||
grep -Fqx -- "$_old_path" "$MANIFEST_FILE" || continue
|
||||
if grep -Fqx -- "$_old_path" "$SHIPPED_LIST"; then continue; fi
|
||||
if [ -f "$_old_path" ] && [ "$(python3 -c 'import hashlib, sys; print(hashlib.sha256(open(sys.argv[1], "rb").read()).hexdigest())' "$_old_path")" != "$_old_sha" ]; then
|
||||
echo "ℹ️ Local modification detected (no longer shipped, kept as yours): $_old_path" >&2
|
||||
else
|
||||
rm -f "$_old_path"
|
||||
_old_dir="$(dirname "$_old_path")"
|
||||
while [ "$_old_dir" != ".agents" ] && rmdir "$_old_dir" 2>/dev/null; do
|
||||
_old_dir="$(dirname "$_old_dir")"
|
||||
done
|
||||
fi
|
||||
rm -f ".mam/base/$_old_path"
|
||||
manifest_drop "$_old_path"
|
||||
done < .mam/asset_hashes.txt
|
||||
rm -f "$SHIPPED_LIST"
|
||||
fi
|
||||
|
||||
# Re-build asset_hashes.txt from the UPSTREAM copy of every framework-owned file
|
||||
python3 - .mam/asset_hashes.txt "$FRAMEWORK_LEDGER" <<'HASHDB'
|
||||
import os, hashlib, sys
|
||||
@@ -590,15 +629,25 @@ with open(gi_path, "w") as f:
|
||||
PY
|
||||
|
||||
if [ "${MAM_SKIP_GITIGNORE:-0}" -eq 0 ]; then
|
||||
# Set the key: overwrite it in place, one line per key, other keys kept
|
||||
python3 -c '
|
||||
import os
|
||||
os.makedirs(".mam", exist_ok=True)
|
||||
lines = []
|
||||
if os.path.exists(".mam/install_state"):
|
||||
with open(".mam/install_state", "a") as f:
|
||||
f.write("gitignore_created=1\n")
|
||||
else:
|
||||
os.makedirs(".mam", exist_ok=True)
|
||||
with open(".mam/install_state", "w") as f:
|
||||
f.write("gitignore_created=1\n")
|
||||
with open(".mam/install_state") as f:
|
||||
lines = f.read().splitlines()
|
||||
out, done = [], False
|
||||
for line in lines:
|
||||
if line.startswith("gitignore_created="):
|
||||
if done:
|
||||
continue
|
||||
line, done = "gitignore_created=1", True
|
||||
out.append(line)
|
||||
if not done:
|
||||
out.append("gitignore_created=1")
|
||||
with open(".mam/install_state", "w") as f:
|
||||
f.write("\n".join(out) + "\n")
|
||||
'
|
||||
fi
|
||||
|
||||
@@ -617,6 +666,6 @@ cat <<EOF
|
||||
$ bash .agents/skills/multi-agent-mux-status/scripts/status.sh
|
||||
|
||||
3. Update installed skills in the future:
|
||||
$ bash .mam_deploy/update.sh
|
||||
$ bash .agents/skills/multi-agent-mux-update/scripts/update.sh
|
||||
--------------------------------------------------------------------------------
|
||||
EOF
|
||||
@@ -132,16 +132,12 @@ if [ -f "$SRC_DIR/.mam.env.example" ]; then
|
||||
log_ok "Copied .mam.env.example configuration template"
|
||||
fi
|
||||
|
||||
# Deploy remove.sh and update.sh into .mam_deploy/ (R-3)
|
||||
# Deploy remove.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"
|
||||
@@ -161,7 +157,7 @@ mkdir -p "$TARGET_DIR/.mam"
|
||||
MANIFEST_FILE="$TARGET_DIR/.mam/install_manifest.txt"
|
||||
: > "$MANIFEST_FILE"
|
||||
( cd "$TARGET_DIR" && find .agents -type f -print ) >> "$MANIFEST_FILE"
|
||||
for extra in ".mam.env.example" ".mam_deploy/remove.sh" ".mam_deploy/update.sh" \
|
||||
for extra in ".mam.env.example" ".mam_deploy/remove.sh" \
|
||||
"scripts/generate-env.sh"; do
|
||||
if [ -e "$TARGET_DIR/$extra" ]; then
|
||||
echo "$extra" >> "$MANIFEST_FILE"
|
||||
|
||||
@@ -92,6 +92,7 @@ else
|
||||
".agents/skills/multi-agent-mux-resume"
|
||||
".agents/skills/multi-agent-mux-status"
|
||||
".agents/skills/multi-agent-mux-stop"
|
||||
".agents/skills/multi-agent-mux-update"
|
||||
".agents/hooks"
|
||||
".agents/hooks.json"
|
||||
".agents/MULTI_AGENT_RULES.md"
|
||||
|
||||
@@ -1,269 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# update.sh — Multi-Agent Mux (MAM) Orchestration Updater
|
||||
# ==============================================================================
|
||||
# Safely updates MAM skills, virtual environment, and docs to the latest version.
|
||||
# Preserves user configuration (.env) and local metadata/jobs database (.mam).
|
||||
# ==============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
TARGET_DIR=""
|
||||
FORCE=0
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-y|--yes|--force)
|
||||
FORCE=1
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
TARGET_DIR="$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$TARGET_DIR" ]; then
|
||||
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
|
||||
fi
|
||||
TARGET_DIR="$(cd "$TARGET_DIR" && pwd)"
|
||||
fi
|
||||
|
||||
echo "===================================================================="
|
||||
echo "⚡ Starting Multi-Agent Mux (MAM) Update"
|
||||
echo "📂 Target Workspace: $TARGET_DIR"
|
||||
echo "===================================================================="
|
||||
|
||||
cd "$TARGET_DIR"
|
||||
|
||||
# 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
|
||||
fi
|
||||
|
||||
# Request confirmation if not forced
|
||||
if [ $FORCE -eq 0 ]; then
|
||||
echo "⚠️ WARNING: This will update MAM orchestration skills, virtual environment, "
|
||||
echo " and docs to the latest version."
|
||||
echo " (Your configuration, job history, and custom skills will be preserved)."
|
||||
if [ ! -t 0 ]; then
|
||||
echo "❌ Error: Non-interactive terminal detected. Please run with -y/--yes/--force." >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! read -p "❓ Proceed with update? [y/N]: " -r response; then
|
||||
response="n"
|
||||
fi
|
||||
if [[ ! "$response" =~ ^[yY](es)?$ ]]; then
|
||||
echo "❌ Update cancelled by user."
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# 1.5 Pre-capture legacy ownership before remove.sh deletes the manifest (M-4)
|
||||
MAM_LEGACY_ENV_OWNED=0
|
||||
if [ -f ".mam/install_manifest.txt" ] && grep -Fqx ".env" ".mam/install_manifest.txt" 2>/dev/null; then
|
||||
MAM_LEGACY_ENV_OWNED=1
|
||||
fi
|
||||
export MAM_LEGACY_ENV_OWNED
|
||||
|
||||
# 2. Stage backups of user configurations and metadata to prevent deletion
|
||||
echo "💾 Backing up configuration and database..."
|
||||
HAS_ENV=0
|
||||
ENV_BACKUP_SRC=""
|
||||
ENV_BACKUP_TMP=""
|
||||
|
||||
if [ -f ".mam.env" ]; then
|
||||
HAS_ENV=1
|
||||
ENV_BACKUP_SRC=".mam.env"
|
||||
ENV_BACKUP_TMP=".mam.env.update-tmp"
|
||||
mv ".mam.env" "$ENV_BACKUP_TMP"
|
||||
elif [ -f ".env" ]; then
|
||||
HAS_ENV=1
|
||||
ENV_BACKUP_SRC=".env"
|
||||
ENV_BACKUP_TMP=".env.update-tmp"
|
||||
mv ".env" "$ENV_BACKUP_TMP"
|
||||
fi
|
||||
|
||||
HAS_MAM=0
|
||||
if [ -d ".mam" ]; then
|
||||
HAS_MAM=1
|
||||
# Copy database and jobs to temporary backup outside of .mam.
|
||||
# We do NOT move the .mam folder away so that remove.sh can still read .mam/install_manifest.txt!
|
||||
mkdir -p .mam.update-tmp
|
||||
# Copy SQLite databases and session files
|
||||
for db in .mam/agent-sessions.*; do
|
||||
if [ -f "$db" ]; then
|
||||
cp -f "$db" .mam.update-tmp/
|
||||
fi
|
||||
done
|
||||
# Copy jobs history
|
||||
if [ -d ".mam/jobs" ] && [ "$(ls -A .mam/jobs 2>/dev/null)" ]; then
|
||||
mkdir -p .mam.update-tmp/jobs
|
||||
cp -rf .mam/jobs/* .mam.update-tmp/jobs/
|
||||
fi
|
||||
# Copy delegate logs
|
||||
if [ -d ".mam/delegate_job_logs" ] && [ "$(ls -A .mam/delegate_job_logs 2>/dev/null)" ]; then
|
||||
mkdir -p .mam.update-tmp/delegate_job_logs
|
||||
cp -rf .mam/delegate_job_logs/* .mam.update-tmp/delegate_job_logs/
|
||||
fi
|
||||
# Copy manifest so we have a backup
|
||||
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
|
||||
restore_on_failure() {
|
||||
echo "❌ Update failed. Reverting configuration and database to previous state..."
|
||||
if [ $HAS_ENV -eq 1 ] && [ -n "$ENV_BACKUP_TMP" ] && [ -f "$ENV_BACKUP_TMP" ]; then
|
||||
mv -f "$ENV_BACKUP_TMP" "$ENV_BACKUP_SRC" 2>/dev/null || true
|
||||
fi
|
||||
if [ $HAS_MAM -eq 1 ] && [ -d ".mam.update-tmp" ]; then
|
||||
# Revert to old database/jobs backup by restoring .mam directory
|
||||
rm -rf .mam 2>/dev/null || true
|
||||
mkdir -p .mam
|
||||
cp -f .mam.update-tmp/agent-sessions.* .mam/ 2>/dev/null || true
|
||||
if [ -d ".mam.update-tmp/jobs" ]; then
|
||||
cp -rf .mam.update-tmp/jobs .mam/ 2>/dev/null || true
|
||||
fi
|
||||
if [ -d ".mam.update-tmp/delegate_job_logs" ]; then
|
||||
cp -rf .mam.update-tmp/delegate_job_logs .mam/ 2>/dev/null || true
|
||||
fi
|
||||
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
|
||||
}
|
||||
trap restore_on_failure EXIT
|
||||
|
||||
# 3. Perform uninstallation of existing files
|
||||
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 "$REMOVER" --force "$TARGET_DIR"
|
||||
|
||||
# 4. Fetch and run the latest installer from Gitea
|
||||
echo "📥 Fetching and running the latest installer..."
|
||||
# Note: MAM_*_URL environment variables are automatically inherited by the child installer bash process.
|
||||
INSTALLER_URL="${MAM_INSTALLER_URL:-https://git.godopu.com/tmpl/multi-agent-mux/raw/branch/main/deploy/install.sh}"
|
||||
if command -v curl &>/dev/null; then
|
||||
curl -fsSL "$INSTALLER_URL" | bash -s -- "$TARGET_DIR"
|
||||
elif command -v wget &>/dev/null; then
|
||||
wget -qO- "$INSTALLER_URL" | bash -s -- "$TARGET_DIR"
|
||||
else
|
||||
echo "❌ Error: Neither 'curl' nor 'wget' is available to fetch the installer." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Disable failure trap since installation succeeded
|
||||
trap - EXIT
|
||||
|
||||
# 5. Restore backups of configuration and database
|
||||
echo "🔄 Restoring configuration and database..."
|
||||
if [ $HAS_ENV -eq 1 ] && [ -n "$ENV_BACKUP_TMP" ] && [ -f "$ENV_BACKUP_TMP" ]; then
|
||||
if [ "$MAM_LEGACY_ENV_OWNED" = "1" ] && [ "$ENV_BACKUP_SRC" = ".env" ] && [ ! -f ".mam.env" ]; then
|
||||
mv -f "$ENV_BACKUP_TMP" ".mam.env"
|
||||
chmod 0600 ".mam.env" 2>/dev/null || true
|
||||
else
|
||||
mv -f "$ENV_BACKUP_TMP" "$ENV_BACKUP_SRC"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $HAS_MAM -eq 1 ]; then
|
||||
if [ -d ".mam.update-tmp" ]; then
|
||||
# The installer created a new .mam directory with a new manifest.
|
||||
# We want to merge the old .mam database/jobs back while keeping the new manifest.
|
||||
for db in .mam.update-tmp/agent-sessions.*; do
|
||||
if [ -f "$db" ]; then
|
||||
cp -f "$db" .mam/
|
||||
fi
|
||||
done
|
||||
if [ -d ".mam.update-tmp/jobs" ] && [ "$(ls -A .mam.update-tmp/jobs 2>/dev/null)" ]; then
|
||||
mkdir -p .mam/jobs
|
||||
cp -rf .mam.update-tmp/jobs/* .mam/jobs/
|
||||
fi
|
||||
if [ -d ".mam.update-tmp/delegate_job_logs" ] && [ "$(ls -A .mam.update-tmp/delegate_job_logs 2>/dev/null)" ]; then
|
||||
mkdir -p .mam/delegate_job_logs
|
||||
cp -rf .mam.update-tmp/delegate_job_logs/* .mam/delegate_job_logs/
|
||||
fi
|
||||
# Issue #8: asset_hashes.txt and version.txt describe the tree the
|
||||
# installer just wrote, so the pre-update copies must NOT come back here -
|
||||
# restoring them made version.txt name the previous commit and remove.sh
|
||||
# treat every updated file as locally modified (spurious backups). Only
|
||||
# install_state carries history worth keeping: merge in any key the fresh
|
||||
# install did not write (e.g. gitignore_created from the original install).
|
||||
# The failure path (restore_on_failure) still puts all three back verbatim.
|
||||
if [ -f ".mam.update-tmp/install_state" ]; then
|
||||
touch .mam/install_state
|
||||
# A fresh install_state without a final newline would glue the first
|
||||
# merged key onto its last line.
|
||||
if [ -s .mam/install_state ] && [ -n "$(tail -c1 .mam/install_state)" ]; then
|
||||
printf '\n' >> .mam/install_state
|
||||
fi
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
case "$line" in
|
||||
*=*) ;;
|
||||
*) continue ;; # not a key=value line
|
||||
esac
|
||||
key="${line%%=*}"
|
||||
[ -n "$key" ] || continue
|
||||
# exact key match, no regex interpretation of the key
|
||||
if ! awk -F= -v k="$key" '$1 == k { found = 1 } END { exit !found }' .mam/install_state; then
|
||||
printf '%s\n' "$line" >> .mam/install_state
|
||||
fi
|
||||
done < .mam.update-tmp/install_state
|
||||
fi
|
||||
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
|
||||
|
||||
echo "===================================================================="
|
||||
echo "🎉 Update complete!"
|
||||
echo "===================================================================="
|
||||
Reference in new issue
Block a user