update_manager.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. import shutil
  2. import subprocess
  3. import logging
  4. # Configure logging
  5. logger = logging.getLogger(__name__)
  6. GIT = shutil.which("git") or "/usr/bin/git"
  7. SUDO = shutil.which("sudo") or "/usr/bin/sudo"
  8. SYSTEMCTL = shutil.which("systemctl") or "/usr/bin/systemctl"
  9. def check_git_updates():
  10. """Check for available Git updates."""
  11. try:
  12. logger.debug("Checking for Git updates")
  13. subprocess.run([GIT, "fetch", "--tags", "--force"], check=True)
  14. latest_remote_tag = subprocess.check_output(
  15. [GIT, "describe", "--tags", "--abbrev=0", "origin/main"]
  16. ).strip().decode()
  17. latest_local_tag = subprocess.check_output(
  18. [GIT, "describe", "--tags", "--abbrev=0"]
  19. ).strip().decode()
  20. tag_behind_count = 0
  21. if latest_local_tag != latest_remote_tag:
  22. tags = subprocess.check_output(
  23. [GIT, "tag", "--merged", "origin/main"], text=True
  24. ).splitlines()
  25. found_local = False
  26. for tag in tags:
  27. if tag == latest_local_tag:
  28. found_local = True
  29. elif found_local:
  30. tag_behind_count += 1
  31. if tag == latest_remote_tag:
  32. break
  33. updates_available = latest_remote_tag != latest_local_tag
  34. logger.info(f"Updates available: {updates_available}, {tag_behind_count} versions behind")
  35. return {
  36. "updates_available": updates_available,
  37. "tag_behind_count": tag_behind_count,
  38. "latest_remote_tag": latest_remote_tag,
  39. "latest_local_tag": latest_local_tag,
  40. }
  41. except subprocess.CalledProcessError as e:
  42. logger.error(f"Error checking Git updates: {e}")
  43. return {
  44. "updates_available": False,
  45. "tag_behind_count": 0,
  46. "latest_remote_tag": None,
  47. "latest_local_tag": None,
  48. }
  49. def update_software():
  50. """Update the software to the latest version.
  51. Pulls latest code, installs updated Python dependencies,
  52. and restarts the systemd service.
  53. For a full update (including frontend rebuild), run 'dw update' instead.
  54. """
  55. error_log = []
  56. logger.info("Starting software update process")
  57. def run_command(command, error_message, capture_output=False, cwd=None):
  58. try:
  59. logger.debug(f"Running command: {' '.join(command)}")
  60. result = subprocess.run(command, check=True, capture_output=capture_output, text=True, cwd=cwd)
  61. return result.stdout if capture_output else True
  62. except subprocess.CalledProcessError as e:
  63. logger.error(f"{error_message}: {e}")
  64. error_log.append(error_message)
  65. return None
  66. # Step 1: Pull latest code via git
  67. logger.info("Pulling latest code from git...")
  68. git_result = run_command(
  69. [GIT, "pull", "--ff-only"],
  70. "Failed to pull latest code from git"
  71. )
  72. if git_result:
  73. logger.info("Git pull completed successfully")
  74. # Step 2: Install updated Python dependencies
  75. logger.info("Installing updated dependencies...")
  76. run_command(
  77. [".venv/bin/pip", "install", "-r", "requirements.txt"],
  78. "Failed to install updated dependencies"
  79. )
  80. # Step 3: Restart the service
  81. logger.info("Restarting dune-weaver service...")
  82. restart_result = run_command(
  83. [SUDO, SYSTEMCTL, "restart", "dune-weaver"],
  84. "Failed to restart dune-weaver service"
  85. )
  86. if not restart_result:
  87. error_log.append("Service restart failed - please run 'dw restart' manually")
  88. if error_log:
  89. logger.error(f"Software update completed with errors: {error_log}")
  90. return False, "Update completed with errors. Run 'dw update' for a full update.", error_log
  91. logger.info("Software update completed successfully")
  92. return True, None, None