Преглед на файлове

Revert auto-update feature, instruct users to SSH and run dw update

The host-based update watcher added complexity and required manual
service installation. Simpler to just tell users to SSH in and run
the dw update command directly.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
tuanchris преди 2 седмици
родител
ревизия
717d25c561
променени са 6 файла, в които са добавени 75 реда и са изтрити 273 реда
  1. 1 3
      .gitignore
  2. 3 58
      frontend/src/pages/SettingsPage.tsx
  3. 71 81
      modules/update/update_manager.py
  4. 0 17
      scripts/dune-weaver-update.service
  5. 0 73
      scripts/update-watcher.sh
  6. 0 41
      setup-pi.sh

+ 1 - 3
.gitignore

@@ -28,6 +28,4 @@ node_modules/
 static/custom/*
 !static/custom/.gitkeep
 .claude/
-static/dist/
-# Update trigger file (created by container, consumed by host watcher)
-.update-trigger
+static/dist/

+ 3 - 58
frontend/src/pages/SettingsPage.tsx

@@ -175,8 +175,6 @@ export function SettingsPage() {
     latest: string
     update_available: boolean
   } | null>(null)
-  const [updateStatus, setUpdateStatus] = useState<'idle' | 'updating' | 'triggered' | 'error'>('idle')
-  const [updateError, setUpdateError] = useState<string | null>(null)
 
   // Helper to scroll to element with header offset
   const scrollToSection = (sectionId: string) => {
@@ -264,24 +262,6 @@ export function SettingsPage() {
     }
   }
 
-  const handleUpdate = async () => {
-    setUpdateStatus('updating')
-    setUpdateError(null)
-    try {
-      const response = await apiClient.post<{ success: boolean; message: string }>('/api/update')
-      if (response.success) {
-        setUpdateStatus('triggered')
-        // The system will restart, so we don't need to do anything else
-      } else {
-        setUpdateStatus('error')
-        setUpdateError(response.message || 'Update failed')
-      }
-    } catch (error) {
-      setUpdateStatus('error')
-      setUpdateError(error instanceof Error ? error.message : 'Failed to trigger update')
-    }
-  }
-
   // Handle accordion open/close and trigger data loading
   const handleAccordionChange = (values: string[]) => {
     // Find newly opened section
@@ -2166,49 +2146,14 @@ export function SettingsPage() {
               </div>
             </div>
 
-            {versionInfo?.update_available && updateStatus === 'idle' && (
-              <Button onClick={handleUpdate} className="w-full">
-                <span className="material-icons-outlined mr-2">system_update</span>
-                Update Now
-              </Button>
-            )}
-
-            {updateStatus === 'updating' && (
+            {versionInfo?.update_available && (
               <Alert className="flex items-start">
-                <span className="material-icons animate-spin text-base mr-2 shrink-0">sync</span>
+                <span className="material-icons-outlined text-base mr-2 shrink-0">info</span>
                 <AlertDescription>
-                  Triggering update... Please wait.
-                </AlertDescription>
-              </Alert>
-            )}
-
-            {updateStatus === 'triggered' && (
-              <Alert className="flex items-start border-green-500/50 bg-green-500/10">
-                <span className="material-icons text-base mr-2 shrink-0 text-green-600 dark:text-green-400">check_circle</span>
-                <AlertDescription className="text-green-700 dark:text-green-300">
-                  Update triggered! The system will restart shortly. This page will reload automatically.
+                  To update, SSH into your Raspberry Pi and run <code className="bg-muted px-1.5 py-0.5 rounded text-sm font-mono">dw update</code>
                 </AlertDescription>
               </Alert>
             )}
-
-            {updateStatus === 'error' && (
-              <Alert variant="destructive" className="flex items-start">
-                <span className="material-icons-outlined text-base mr-2 shrink-0">error</span>
-                <div className="flex-1">
-                  <AlertDescription>
-                    {updateError || 'Update failed'}
-                  </AlertDescription>
-                  <Button
-                    variant="outline"
-                    size="sm"
-                    onClick={() => setUpdateStatus('idle')}
-                    className="mt-2"
-                  >
-                    Try Again
-                  </Button>
-                </div>
-              </Alert>
-            )}
           </AccordionContent>
         </AccordionItem>
       </Accordion>

+ 71 - 81
modules/update/update_manager.py

@@ -1,17 +1,9 @@
 import subprocess
 import logging
-import os
-from pathlib import Path
-from datetime import datetime
-from typing import Optional, Tuple
 
 # Configure logging
 logger = logging.getLogger(__name__)
 
-# Trigger file location - visible to both container (/app) and host
-TRIGGER_FILE = Path("/app/.update-trigger")
-
-
 def check_git_updates():
     """Check for available Git updates."""
     try:
@@ -57,79 +49,77 @@ def check_git_updates():
             "latest_local_tag": None,
         }
 
+def update_software():
+    """Update the software to the latest version.
 
-def is_update_watcher_available() -> bool:
-    """Check if the update watcher service is running on the host.
-
-    The watcher service monitors the trigger file and runs 'dw update'
-    when it detects a trigger.
-    """
-    # The watcher is available if we can write to the trigger file location
-    # and the parent directory exists (indicating proper volume mount)
-    try:
-        return TRIGGER_FILE.parent.exists() and os.access(TRIGGER_FILE.parent, os.W_OK)
-    except Exception:
-        return False
-
-
-def trigger_host_update(message: str = None) -> Tuple[bool, Optional[str]]:
-    """Signal the host to run 'dw update' by creating a trigger file.
-
-    The update watcher service on the host monitors this file and
-    executes the full update process when triggered.
-
-    Args:
-        message: Optional message to include in the trigger file
-
-    Returns:
-        Tuple of (success, error_message)
-    """
-    try:
-        # Write trigger file with timestamp and optional message
-        trigger_content = f"triggered_at={datetime.now().isoformat()}\n"
-        if message:
-            trigger_content += f"message={message}\n"
-
-        TRIGGER_FILE.write_text(trigger_content)
-        logger.info(f"Update trigger created at {TRIGGER_FILE}")
-        return True, None
-    except Exception as e:
-        error_msg = f"Failed to create update trigger: {e}"
-        logger.error(error_msg)
-        return False, error_msg
-
+    This runs inside the Docker container, so it:
+    1. Pulls latest code via git (mounted volume at /app)
+    2. Pulls new Docker image for the backend
+    3. Restarts the container to apply updates
 
-def update_software():
-    """Trigger a software update on the host machine.
-
-    When running in Docker, this creates a trigger file that the host's
-    update-watcher service monitors. The watcher then runs 'dw update'
-    on the host, which properly handles:
-    - Git pull for latest code
-    - Docker image pulls
-    - Container recreation with new images
-    - Cleanup of old images
-
-    Returns:
-        Tuple of (success, error_message, error_log)
+    Note: For a complete update including container recreation,
+    run 'dw update' from the host machine instead.
     """
-    logger.info("Initiating software update...")
-
-    # Check if we can trigger host update
-    if not is_update_watcher_available():
-        error_msg = (
-            "Update watcher not available. The update-watcher service may not be "
-            "installed or the volume mount is not configured correctly. "
-            "Please run 'dw update' manually from the host machine."
-        )
-        logger.error(error_msg)
-        return False, error_msg, [error_msg]
-
-    # Trigger the host update
-    success, error = trigger_host_update("Triggered from web UI")
-
-    if success:
-        logger.info("Update triggered successfully - host will process shortly")
-        return True, None, None
-    else:
-        return False, error, [error]
+    error_log = []
+    logger.info("Starting software update process")
+
+    def run_command(command, error_message, capture_output=False, cwd=None):
+        try:
+            logger.debug(f"Running command: {' '.join(command)}")
+            result = subprocess.run(command, check=True, capture_output=capture_output, text=True, cwd=cwd)
+            return result.stdout if capture_output else True
+        except subprocess.CalledProcessError as e:
+            logger.error(f"{error_message}: {e}")
+            error_log.append(error_message)
+            return None
+
+    # Step 1: Pull latest code via git (works because /app is mounted from host)
+    logger.info("Pulling latest code from git...")
+    git_result = run_command(
+        ["git", "pull", "--ff-only"],
+        "Failed to pull latest code from git",
+        cwd="/app"
+    )
+    if git_result:
+        logger.info("Git pull completed successfully")
+
+    # Step 2: Pull new Docker image for the backend only
+    # Note: There is no separate frontend image - it's either bundled or built locally
+    logger.info("Pulling latest Docker image...")
+    run_command(
+        ["docker", "pull", "ghcr.io/tuanchris/dune-weaver:main"],
+        "Failed to pull backend Docker image"
+    )
+
+    # Step 3: Restart the backend container to apply updates
+    # We can't recreate ourselves from inside the container, so we just restart
+    # For full container recreation with new images, use 'dw update' from host
+    logger.info("Restarting backend container...")
+
+    # Use docker restart which works from inside the container
+    restart_result = run_command(
+        ["docker", "restart", "dune-weaver-backend"],
+        "Failed to restart backend container"
+    )
+
+    if not restart_result:
+        # If docker restart fails, try a graceful approach
+        logger.info("Attempting graceful restart via compose...")
+        try:
+            # Just restart, don't try to recreate (which would fail)
+            subprocess.run(
+                ["docker", "compose", "restart", "backend"],
+                check=True,
+                cwd="/app"
+            )
+            logger.info("Container restarted successfully via compose")
+        except (subprocess.CalledProcessError, FileNotFoundError) as e:
+            logger.warning(f"Compose restart also failed: {e}")
+            error_log.append("Container restart failed - please run 'dw update' from host")
+
+    if error_log:
+        logger.error(f"Software update completed with errors: {error_log}")
+        return False, "Update completed with errors. For best results, run 'dw update' from the host machine.", error_log
+
+    logger.info("Software update completed successfully")
+    return True, None, None

+ 0 - 17
scripts/dune-weaver-update.service

@@ -1,17 +0,0 @@
-[Unit]
-Description=Dune Weaver Update Watcher
-After=multi-user.target docker.service
-Wants=docker.service
-
-[Service]
-Type=simple
-User=root
-WorkingDirectory=/home/pi/dune-weaver
-ExecStart=/home/pi/dune-weaver/scripts/update-watcher.sh
-Restart=always
-RestartSec=10
-StartLimitInterval=200
-StartLimitBurst=5
-
-[Install]
-WantedBy=multi-user.target

+ 0 - 73
scripts/update-watcher.sh

@@ -1,73 +0,0 @@
-#!/bin/bash
-#
-# Update Watcher for Dune Weaver
-#
-# This script runs on the host machine and watches for update triggers
-# from the Docker container. When a trigger is detected, it runs 'dw update'.
-#
-# The container signals an update by creating .update-trigger file in the
-# mounted volume, which the host can see and act upon.
-#
-
-set -e
-
-# Configuration
-TRIGGER_FILE=""
-INSTALL_DIR=""
-LOG_PREFIX="[update-watcher]"
-
-# Find dune-weaver directory (same logic as dw script)
-find_install_dir() {
-    if [[ -f "$HOME/dune-weaver/main.py" ]]; then
-        echo "$HOME/dune-weaver"
-    elif [[ -f "/home/pi/dune-weaver/main.py" ]]; then
-        echo "/home/pi/dune-weaver"
-    else
-        echo ""
-    fi
-}
-
-log() {
-    echo "$LOG_PREFIX $(date '+%Y-%m-%d %H:%M:%S') $1"
-}
-
-# Initialize
-INSTALL_DIR=$(find_install_dir)
-if [[ -z "$INSTALL_DIR" ]]; then
-    log "ERROR: Dune Weaver installation not found"
-    exit 1
-fi
-
-TRIGGER_FILE="$INSTALL_DIR/.update-trigger"
-log "Watching for update triggers at: $TRIGGER_FILE"
-log "Install directory: $INSTALL_DIR"
-
-# Main watch loop
-while true; do
-    if [[ -f "$TRIGGER_FILE" ]]; then
-        log "Update trigger detected!"
-
-        # Read any message from trigger file (optional metadata)
-        if [[ -s "$TRIGGER_FILE" ]]; then
-            log "Trigger message: $(cat "$TRIGGER_FILE")"
-        fi
-
-        # Remove trigger file before update to prevent re-triggering
-        rm -f "$TRIGGER_FILE"
-
-        # Run the update
-        log "Starting update process..."
-        cd "$INSTALL_DIR"
-
-        if /usr/local/bin/dw update 2>&1 | while read -r line; do log "$line"; done; then
-            log "Update completed successfully"
-        else
-            log "Update completed with errors (exit code: $?)"
-        fi
-
-        log "Resuming watch..."
-    fi
-
-    # Poll every 2 seconds
-    sleep 2
-done

+ 0 - 41
setup-pi.sh

@@ -240,46 +240,6 @@ install_cli() {
     print_success "'dw' command installed"
 }
 
-# Install update watcher service (for Docker deployments)
-install_update_watcher() {
-    if [[ "$USE_DOCKER" != "true" ]]; then
-        # Update watcher only needed for Docker deployments
-        return
-    fi
-
-    print_step "Installing update watcher service..."
-
-    # Make watcher script executable
-    chmod +x "$INSTALL_DIR/scripts/update-watcher.sh"
-
-    # Create systemd service with correct paths
-    sudo tee /etc/systemd/system/dune-weaver-update.service > /dev/null << EOF
-[Unit]
-Description=Dune Weaver Update Watcher
-After=multi-user.target docker.service
-Wants=docker.service
-
-[Service]
-Type=simple
-User=root
-WorkingDirectory=$INSTALL_DIR
-ExecStart=$INSTALL_DIR/scripts/update-watcher.sh
-Restart=always
-RestartSec=10
-StartLimitInterval=200
-StartLimitBurst=5
-
-[Install]
-WantedBy=multi-user.target
-EOF
-
-    sudo systemctl daemon-reload
-    sudo systemctl enable dune-weaver-update
-    sudo systemctl start dune-weaver-update
-
-    print_success "Update watcher service installed"
-}
-
 # Deploy with Docker
 deploy_docker() {
     print_step "Deploying Dune Weaver with Docker Compose..."
@@ -424,7 +384,6 @@ main() {
     fi
 
     install_cli
-    install_update_watcher
     print_final_instructions
 }