/ scripts / sovereign_startup.py
sovereign_startup.py
  1  #!/usr/bin/env python3
  2  """
  3  Sovereign OS - Master Startup Script
  4  
  5  Runs on machine login to ensure everything is ready:
  6  1. Verify daemons are running
  7  2. Git pull latest code
  8  3. Run hygiene check
  9  4. Open Obsidian to LIVE-COMPRESSION.md
 10  5. Report status
 11  
 12  Can also be run manually: python3 scripts/sovereign_startup.py
 13  """
 14  
 15  import os
 16  import sys
 17  import subprocess
 18  import json
 19  from pathlib import Path
 20  from datetime import datetime
 21  
 22  # Configuration
 23  SOVEREIGN_OS_ROOT = Path(__file__).parent.parent
 24  LOGS_DIR = Path.home() / ".sovereign" / "logs"
 25  LOGS_DIR.mkdir(parents=True, exist_ok=True)
 26  
 27  # Daemons to check (name, process patterns to search for)
 28  DAEMONS = [
 29      ("mission-control", ["mission_control", "run_mission_control"]),
 30      ("first-officer", ["first_officer_local", "run_first_officer"]),
 31      ("mesh", ["sos.js", "sovereign-mesh"]),
 32  ]
 33  
 34  LAUNCH_AGENTS = [
 35      "com.sovereign.mission-control",
 36      "com.sovereign.first-officer",
 37      "com.sovereign.mesh",
 38  ]
 39  
 40  
 41  def log(msg: str, level: str = "INFO"):
 42      """Log with timestamp."""
 43      timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
 44      print(f"[{timestamp}] [{level}] {msg}")
 45  
 46  
 47  def check_daemon_running(process_patterns: list) -> bool:
 48      """Check if a daemon is running by any of the process patterns."""
 49      for pattern in process_patterns:
 50          try:
 51              result = subprocess.run(
 52                  ["pgrep", "-f", pattern],
 53                  capture_output=True, text=True
 54              )
 55              if result.returncode == 0:
 56                  return True
 57          except:
 58              pass
 59      return False
 60  
 61  
 62  def start_launch_agent(agent_name: str) -> bool:
 63      """Start a LaunchAgent."""
 64      plist_path = Path.home() / "Library" / "LaunchAgents" / f"{agent_name}.plist"
 65      if not plist_path.exists():
 66          log(f"LaunchAgent not found: {plist_path}", "WARN")
 67          return False
 68  
 69      try:
 70          # Unload first (in case it's in a bad state)
 71          subprocess.run(["launchctl", "unload", str(plist_path)],
 72                        capture_output=True, timeout=5)
 73          # Load fresh
 74          result = subprocess.run(["launchctl", "load", str(plist_path)],
 75                                 capture_output=True, timeout=5)
 76          return result.returncode == 0
 77      except:
 78          return False
 79  
 80  
 81  def verify_daemons() -> dict:
 82      """Verify all daemons are running, start if needed."""
 83      status = {}
 84  
 85      for daemon_name, process_pattern in DAEMONS:
 86          if check_daemon_running(process_pattern):
 87              status[daemon_name] = "running"
 88              log(f"✓ {daemon_name} running")
 89          else:
 90              log(f"✗ {daemon_name} not running, attempting restart...", "WARN")
 91              agent_name = f"com.sovereign.{daemon_name}"
 92              if start_launch_agent(agent_name):
 93                  # Wait and verify
 94                  import time
 95                  time.sleep(2)
 96                  if check_daemon_running(process_pattern):
 97                      status[daemon_name] = "restarted"
 98                      log(f"✓ {daemon_name} restarted successfully")
 99                  else:
100                      status[daemon_name] = "failed"
101                      log(f"✗ {daemon_name} failed to start", "ERROR")
102              else:
103                  status[daemon_name] = "failed"
104                  log(f"✗ {daemon_name} LaunchAgent not found", "ERROR")
105  
106      return status
107  
108  
109  def git_sync() -> bool:
110      """Pull latest code from git."""
111      try:
112          log("Syncing git repository...")
113          result = subprocess.run(
114              ["git", "pull", "--rebase", "--autostash"],
115              cwd=SOVEREIGN_OS_ROOT,
116              capture_output=True, text=True, timeout=30
117          )
118          if result.returncode == 0:
119              if "Already up to date" in result.stdout:
120                  log("✓ Git: Already up to date")
121              else:
122                  log(f"✓ Git: Pulled updates")
123              return True
124          else:
125              log(f"✗ Git pull failed: {result.stderr}", "WARN")
126              return False
127      except Exception as e:
128          log(f"✗ Git sync error: {e}", "WARN")
129          return False
130  
131  
132  def run_hygiene_check() -> dict:
133      """Run phoenix hygiene check."""
134      try:
135          log("Running hygiene check...")
136          result = subprocess.run(
137              [sys.executable, str(SOVEREIGN_OS_ROOT / "scripts" / "phoenix_hygiene.py"), "--json"],
138              capture_output=True, text=True, timeout=30
139          )
140  
141          if result.returncode == 0:
142              log("✓ All hygiene checks pass")
143              return {"status": "pass", "issues": []}
144          else:
145              issues = json.loads(result.stdout) if result.stdout else []
146              for issue in issues:
147                  level = "ERROR" if issue.get("status") == "critical" else "WARN"
148                  log(f"✗ [{issue.get('check')}] {issue.get('message')}", level)
149              return {"status": "issues", "issues": issues}
150      except Exception as e:
151          log(f"✗ Hygiene check error: {e}", "ERROR")
152          return {"status": "error", "error": str(e)}
153  
154  
155  def open_obsidian():
156      """Open LIVE-COMPRESSION.md in Obsidian."""
157      try:
158          log("Opening Obsidian...")
159          subprocess.run([
160              "open",
161              "obsidian://open?vault=Sovereign_OS&file=sessions/LIVE-COMPRESSION"
162          ], timeout=5)
163          log("✓ Obsidian opened")
164          return True
165      except Exception as e:
166          log(f"✗ Failed to open Obsidian: {e}", "WARN")
167          return False
168  
169  
170  def write_status_file(status: dict):
171      """Write startup status to file for other tools to read."""
172      status_file = LOGS_DIR / "startup-status.json"
173      status["timestamp"] = datetime.now().isoformat()
174      with open(status_file, "w") as f:
175          json.dump(status, f, indent=2)
176      log(f"Status written to {status_file}")
177  
178  
179  def main():
180      print()
181      print("=" * 60)
182      print("  SOVEREIGN OS - STARTUP CHECK")
183      print("=" * 60)
184      print()
185  
186      status = {
187          "daemons": {},
188          "git_sync": False,
189          "hygiene": {},
190          "obsidian": False,
191      }
192  
193      # 1. Verify daemons
194      status["daemons"] = verify_daemons()
195      print()
196  
197      # 2. Git sync
198      status["git_sync"] = git_sync()
199      print()
200  
201      # 3. Hygiene check
202      status["hygiene"] = run_hygiene_check()
203      print()
204  
205      # 4. Open Obsidian (only if running interactively)
206      if sys.stdout.isatty():
207          status["obsidian"] = open_obsidian()
208          print()
209  
210      # 5. Write status
211      write_status_file(status)
212  
213      # Summary
214      print("=" * 60)
215      all_daemons_ok = all(s in ["running", "restarted"] for s in status["daemons"].values())
216      hygiene_ok = status["hygiene"].get("status") == "pass"
217  
218      if all_daemons_ok and hygiene_ok:
219          print("  ✓ SOVEREIGN OS READY")
220      else:
221          print("  ⚠ SOVEREIGN OS - ISSUES DETECTED")
222          if not all_daemons_ok:
223              print("    - Some daemons failed to start")
224          if not hygiene_ok:
225              print("    - Hygiene check found issues")
226      print("=" * 60)
227      print()
228  
229      # Exit code
230      if all_daemons_ok and hygiene_ok:
231          sys.exit(0)
232      else:
233          sys.exit(1)
234  
235  
236  if __name__ == "__main__":
237      main()