Files
microdao-daarion/scripts/node2/execute_cleanup.py
Apple 3de3c8cb36 feat: Add presence heartbeat for Matrix online status
- matrix-gateway: POST /internal/matrix/presence/online endpoint
- usePresenceHeartbeat hook with activity tracking
- Auto away after 5 min inactivity
- Offline on page close/visibility change
- Integrated in MatrixChatRoom component
2025-11-27 00:19:40 -08:00

285 lines
10 KiB
Python
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 python3
"""
Execute Cleanup Plan for Node-2
Safely executes cleanup actions from node2_cleanup_plan.json
"""
import json
import subprocess
from pathlib import Path
from typing import Dict, List, Any
class CleanupExecutor:
def __init__(self, cleanup_plan_path: str = "node2_cleanup_plan.json"):
self.cleanup_plan_path = Path(cleanup_plan_path)
self.cleanup_plan = {}
self.executed_actions = []
self.errors = []
def load_cleanup_plan(self):
"""Load cleanup plan JSON"""
if not self.cleanup_plan_path.exists():
raise FileNotFoundError(f"Cleanup plan not found: {self.cleanup_plan_path}")
with open(self.cleanup_plan_path, 'r', encoding='utf-8') as f:
self.cleanup_plan = json.load(f)
print(f"✅ Loaded cleanup plan from: {self.cleanup_plan_path}")
def execute_docker_action(self, action: str, container_name: str) -> bool:
"""Execute Docker action (stop, rm)"""
try:
if "stop" in action.lower():
print(f" 🛑 Stopping container: {container_name}")
result = subprocess.run(
["docker", "stop", container_name],
capture_output=True,
text=True,
timeout=30
)
if result.returncode != 0:
print(f" ⚠️ Warning: {result.stderr.strip()}")
if "rm" in action.lower():
print(f" 🗑️ Removing container: {container_name}")
result = subprocess.run(
["docker", "rm", container_name],
capture_output=True,
text=True,
timeout=30
)
if result.returncode != 0:
print(f" ⚠️ Warning: {result.stderr.strip()}")
return False
return True
except subprocess.TimeoutExpired:
print(f" ❌ Timeout removing container: {container_name}")
return False
except Exception as e:
print(f" ❌ Error: {e}")
return False
def execute_delete_actions(self, dry_run: bool = False):
"""Execute delete actions"""
print("\n🗑️ EXECUTING DELETE ACTIONS")
print("="*60)
delete_items = self.cleanup_plan.get("delete", [])
if not delete_items:
print(" No items to delete")
return
for item in delete_items:
item_type = item.get("type", "")
item_name = item.get("name", "")
action = item.get("action", "")
reason = item.get("reason", "")
print(f"\n 📦 {item_type}: {item_name}")
print(f" Reason: {reason}")
if dry_run:
print(f" [DRY RUN] Would execute: {action}")
continue
if item_type == "docker_container":
success = self.execute_docker_action(action, item_name)
if success:
self.executed_actions.append({
"type": "delete",
"item": item_name,
"status": "success"
})
else:
self.errors.append({
"type": "delete",
"item": item_name,
"error": "Failed to remove container"
})
else:
print(f" ⚠️ Unknown type: {item_type}")
def create_legacy_directory(self, dry_run: bool = False):
"""Create /node2/legacy/ directory for migrations"""
print("\n📁 CREATING LEGACY DIRECTORY")
print("="*60)
legacy_paths = [
Path.home() / "node2" / "legacy",
Path("/node2/legacy")
]
for legacy_path in legacy_paths:
if dry_run:
print(f" [DRY RUN] Would create: {legacy_path}")
else:
try:
legacy_path.mkdir(parents=True, exist_ok=True)
print(f" ✅ Created: {legacy_path}")
self.executed_actions.append({
"type": "create_directory",
"path": str(legacy_path),
"status": "success"
})
except Exception as e:
print(f" ❌ Error creating {legacy_path}: {e}")
self.errors.append({
"type": "create_directory",
"path": str(legacy_path),
"error": str(e)
})
def fix_qdrant_health(self, dry_run: bool = False):
"""Attempt to fix unhealthy Qdrant container"""
print("\n🔧 FIXING QDRANT HEALTH")
print("="*60)
# Find Qdrant container
qdrant_container = None
for container in self.cleanup_plan.get("keep", []):
if "qdrant" in container.get("name", "").lower():
qdrant_container = container
break
if not qdrant_container:
print(" Qdrant container not found in keep list")
return
container_name = qdrant_container.get("path", "").replace("Docker: ", "")
if dry_run:
print(f" [DRY RUN] Would restart: {container_name}")
print(f" [DRY RUN] Would check logs: docker logs {container_name}")
return
try:
# Check container status
result = subprocess.run(
["docker", "ps", "-a", "--filter", f"name={container_name}", "--format", "{{.Status}}"],
capture_output=True,
text=True,
timeout=10
)
if "unhealthy" in result.stdout.lower():
print(f" 🔄 Restarting container: {container_name}")
subprocess.run(
["docker", "restart", container_name],
capture_output=True,
text=True,
timeout=30
)
# Wait a bit and check again
import time
time.sleep(5)
result = subprocess.run(
["docker", "ps", "--filter", f"name={container_name}", "--format", "{{.Status}}"],
capture_output=True,
text=True,
timeout=10
)
if "healthy" in result.stdout.lower() or "up" in result.stdout.lower():
print(f" ✅ Container is now healthy")
self.executed_actions.append({
"type": "fix_health",
"container": container_name,
"status": "success"
})
else:
print(f" ⚠️ Container status: {result.stdout.strip()}")
print(f" 💡 Check logs: docker logs {container_name}")
self.errors.append({
"type": "fix_health",
"container": container_name,
"error": "Container still unhealthy after restart"
})
except Exception as e:
print(f" ❌ Error fixing Qdrant: {e}")
self.errors.append({
"type": "fix_health",
"container": container_name,
"error": str(e)
})
def execute_all(self, dry_run: bool = False):
"""Execute all cleanup actions"""
print("\n" + "="*60)
if dry_run:
print("🔍 DRY RUN MODE - No changes will be made")
else:
print("🚀 EXECUTING CLEANUP PLAN")
print("="*60)
self.create_legacy_directory(dry_run)
self.execute_delete_actions(dry_run)
self.fix_qdrant_health(dry_run)
print("\n" + "="*60)
print("📊 EXECUTION SUMMARY")
print("="*60)
print(f" ✅ Actions executed: {len(self.executed_actions)}")
print(f" ❌ Errors: {len(self.errors)}")
if self.errors:
print("\n ⚠️ ERRORS:")
for error in self.errors:
print(f" - {error.get('type')}: {error.get('error')}")
def save_execution_report(self, output_path: str = "node2_cleanup_execution_report.json"):
"""Save execution report"""
report = {
"executed_actions": self.executed_actions,
"errors": self.errors,
"timestamp": subprocess.run(
["date", "-u", "+%Y-%m-%dT%H:%M:%SZ"],
capture_output=True,
text=True
).stdout.strip()
}
output_file = Path(output_path)
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(report, f, indent=2, ensure_ascii=False)
print(f"\n💾 Execution report saved to: {output_file.absolute()}")
def main():
import sys
dry_run = "--dry-run" in sys.argv or "-d" in sys.argv
# Try to find cleanup plan
cleanup_plan_paths = [
"node2_cleanup_plan.json",
"docs/node2/node2_cleanup_plan.json",
Path.home() / "github-projects" / "microdao-daarion" / "node2_cleanup_plan.json"
]
cleanup_plan_path = None
for path in cleanup_plan_paths:
if Path(path).exists():
cleanup_plan_path = path
break
if not cleanup_plan_path:
print("❌ Cleanup plan not found. Please run cleanup_plan_generator.py first.")
return
executor = CleanupExecutor(cleanup_plan_path)
executor.load_cleanup_plan()
executor.execute_all(dry_run=dry_run)
executor.save_execution_report()
if not dry_run:
print("\n✅ Cleanup execution complete!")
print(" Next step: Proceed to PHASE 3 - Install microDAO Core")
if __name__ == "__main__":
main()