mirror of
https://github.com/Grace-Solutions/Depl0y-Custom.git
synced 2026-08-28 11:06:58 +00:00
Initial Commit
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
"""System update API endpoints"""
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi.responses import FileResponse
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
from app.api.auth import get_current_user
|
||||
from app.core.config import settings
|
||||
import logging
|
||||
import subprocess
|
||||
import os
|
||||
import requests
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
# Update server configuration
|
||||
UPDATE_SERVER = "http://deploy.agit8or.net"
|
||||
UPDATE_ENDPOINT = f"{UPDATE_SERVER}/api/v1/system-updates/version"
|
||||
|
||||
|
||||
class UpdateCheckResponse(BaseModel):
|
||||
current_version: str
|
||||
latest_version: str
|
||||
update_available: bool
|
||||
download_url: Optional[str] = None
|
||||
release_notes: Optional[str] = None
|
||||
|
||||
|
||||
@router.get("/check")
|
||||
def check_for_updates(current_user=Depends(get_current_user)):
|
||||
"""Check if updates are available from the main server"""
|
||||
try:
|
||||
# Get current version
|
||||
current_version = settings.APP_VERSION
|
||||
|
||||
# Query update server for latest version
|
||||
try:
|
||||
response = requests.get(UPDATE_ENDPOINT, timeout=10)
|
||||
if response.status_code == 200:
|
||||
update_info = response.json()
|
||||
latest_version = update_info.get("version", current_version)
|
||||
|
||||
# Simple version comparison (assumes semantic versioning)
|
||||
update_available = latest_version != current_version
|
||||
|
||||
return UpdateCheckResponse(
|
||||
current_version=current_version,
|
||||
latest_version=latest_version,
|
||||
update_available=update_available,
|
||||
download_url=update_info.get("download_url") if update_available else None,
|
||||
release_notes=update_info.get("release_notes")
|
||||
)
|
||||
else:
|
||||
raise Exception(f"Update server returned {response.status_code}")
|
||||
except requests.RequestException as e:
|
||||
logger.warning(f"Could not reach update server: {e}")
|
||||
return UpdateCheckResponse(
|
||||
current_version=current_version,
|
||||
latest_version=current_version,
|
||||
update_available=False,
|
||||
release_notes="Could not reach update server"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to check for updates: {e}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Failed to check for updates: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.get("/version")
|
||||
def get_version_info():
|
||||
"""
|
||||
Serve version information for update clients
|
||||
This endpoint is called BY OTHER INSTANCES to check for updates
|
||||
"""
|
||||
return {
|
||||
"version": settings.APP_VERSION,
|
||||
"download_url": f"{UPDATE_SERVER}/api/v1/system-updates/download",
|
||||
"install_url": f"{UPDATE_SERVER}/install.sh",
|
||||
"release_notes": f"""
|
||||
Depl0y {settings.APP_VERSION} Release Notes:
|
||||
|
||||
✨ New in v1.1.9:
|
||||
- Automatic backend restart after upgrades
|
||||
- Updates now apply immediately without manual intervention
|
||||
- Backend automatically loads new code after update completes
|
||||
|
||||
✨ New in v1.1.8:
|
||||
- Fixed JavaScript error preventing Settings page from loading
|
||||
- Fixed installer version mismatch
|
||||
- Updates work correctly in one step
|
||||
|
||||
✨ Previous versions:
|
||||
- One-click automatic updates from Settings (v1.1.5)
|
||||
- Fast 30-second upgrades with pre-built frontend (v1.1.5)
|
||||
- System Updates section with loading/error states (v1.1.7)
|
||||
- Automated cloud image deployment
|
||||
- Inter-node SSH setup for clusters
|
||||
""".strip()
|
||||
}
|
||||
|
||||
|
||||
@router.get("/download")
|
||||
def download_update():
|
||||
"""Download the latest update package (public endpoint for automated updates)"""
|
||||
try:
|
||||
# Use pre-packaged file
|
||||
package_path = "/opt/depl0y/depl0y-v1.1.5.tar.gz"
|
||||
|
||||
if not os.path.exists(package_path):
|
||||
# Fallback: create package on-the-fly
|
||||
temp_package = "/tmp/depl0y-update.tar.gz"
|
||||
create_package_cmd = f"""
|
||||
cd /home/administrator/depl0y && \
|
||||
tar -czf {temp_package} \
|
||||
--exclude='node_modules' \
|
||||
--exclude='dist' \
|
||||
--exclude='.git' \
|
||||
--exclude='__pycache__' \
|
||||
--exclude='*.pyc' \
|
||||
--exclude='venv' \
|
||||
backend/ frontend/ *.md *.sh 2>/dev/null || true
|
||||
"""
|
||||
subprocess.run(create_package_cmd, shell=True, check=True)
|
||||
package_path = temp_package
|
||||
|
||||
if not os.path.exists(package_path):
|
||||
raise HTTPException(status_code=500, detail="Failed to find or create update package")
|
||||
|
||||
return FileResponse(
|
||||
package_path,
|
||||
media_type="application/gzip",
|
||||
filename="depl0y-latest.tar.gz",
|
||||
headers={
|
||||
"Cache-Control": "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0",
|
||||
"Pragma": "no-cache",
|
||||
"Expires": "0"
|
||||
}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to serve update package: {e}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Failed to serve update package: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.post("/apply")
|
||||
def apply_update(current_user=Depends(get_current_user)):
|
||||
"""Apply update by downloading and running the installer script"""
|
||||
try:
|
||||
logger.info("Starting update process via installer...")
|
||||
|
||||
# Download installer script
|
||||
installer_url = f"{UPDATE_SERVER}/downloads/install.sh"
|
||||
installer_path = "/tmp/depl0y-update-install.sh"
|
||||
|
||||
logger.info(f"Downloading installer from {installer_url}")
|
||||
|
||||
response = requests.get(installer_url, timeout=30)
|
||||
if response.status_code != 200:
|
||||
raise Exception(f"Failed to download installer: HTTP {response.status_code}")
|
||||
|
||||
# Save installer
|
||||
with open(installer_path, 'wb') as f:
|
||||
f.write(response.content)
|
||||
|
||||
# Make executable
|
||||
os.chmod(installer_path, 0o755)
|
||||
|
||||
logger.info("Installer downloaded, starting update in background...")
|
||||
|
||||
# Run installer in background (it will detect existing installation and upgrade)
|
||||
update_script = f"/usr/bin/sudo /opt/depl0y/scripts/update-wrapper.sh {installer_path}"
|
||||
subprocess.Popen(update_script, shell=True)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Update is being applied. The installer will upgrade your installation while preserving your data. The service will restart automatically.",
|
||||
"log_file": "/tmp/depl0y-update.log"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to start update: {e}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Failed to start update: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user