@mytec: iter3.10 start, baseline rc ready
This commit is contained in:
656
docs/devlog/gpu_supp/RFCP-Dependencies-Installer.md
Normal file
656
docs/devlog/gpu_supp/RFCP-Dependencies-Installer.md
Normal file
@@ -0,0 +1,656 @@
|
||||
# RFCP Dependencies & Installer Specification
|
||||
|
||||
## Overview
|
||||
|
||||
All dependencies needed for RFCP to work out of the box, including GPU acceleration.
|
||||
The installer must handle everything — user should NOT need to run pip manually.
|
||||
|
||||
---
|
||||
|
||||
## Python Dependencies
|
||||
|
||||
### Core (MUST have)
|
||||
|
||||
```txt
|
||||
# requirements.txt
|
||||
|
||||
# Web framework
|
||||
fastapi>=0.104.0
|
||||
uvicorn[standard]>=0.24.0
|
||||
websockets>=12.0
|
||||
|
||||
# Scientific computing
|
||||
numpy>=1.24.0
|
||||
scipy>=1.11.0
|
||||
|
||||
# Geospatial
|
||||
pyproj>=3.6.0 # coordinate transformations
|
||||
shapely>=2.0.0 # geometry operations (boundary contours)
|
||||
|
||||
# Terrain data
|
||||
rasterio>=1.3.0 # GeoTIFF reading (optional, for custom terrain)
|
||||
# Note: SRTM .hgt files read with numpy directly
|
||||
|
||||
# OSM data
|
||||
requests>=2.31.0 # HTTP client for OSM Overpass API
|
||||
geopy>=2.4.0 # distance calculations
|
||||
|
||||
# Database
|
||||
# sqlite3 is built-in Python — no install needed
|
||||
|
||||
# Utilities
|
||||
orjson>=3.9.0 # fast JSON (optional, faster API responses)
|
||||
pydantic>=2.0.0 # data validation (FastAPI dependency)
|
||||
```
|
||||
|
||||
### GPU Acceleration (OPTIONAL — auto-detected)
|
||||
|
||||
```txt
|
||||
# requirements-gpu-nvidia.txt
|
||||
cupy-cuda12x>=12.0.0 # For CUDA 12.x (RTX 30xx, 40xx)
|
||||
# OR
|
||||
cupy-cuda11x>=11.0.0 # For CUDA 11.x (older cards)
|
||||
|
||||
# requirements-gpu-opencl.txt
|
||||
pyopencl>=2023.1 # For ANY GPU (Intel, AMD, NVIDIA)
|
||||
```
|
||||
|
||||
### Development / Testing
|
||||
|
||||
```txt
|
||||
# requirements-dev.txt
|
||||
pytest>=7.0.0
|
||||
pytest-asyncio>=0.21.0
|
||||
httpx>=0.25.0 # async test client
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## System Dependencies
|
||||
|
||||
### NVIDIA GPU Support
|
||||
|
||||
```
|
||||
REQUIRED: NVIDIA Driver (comes with GPU)
|
||||
REQUIRED: CUDA Toolkit 12.x (for CuPy)
|
||||
|
||||
Check if installed:
|
||||
nvidia-smi → shows driver version
|
||||
nvcc --version → shows CUDA toolkit version
|
||||
|
||||
If missing CUDA toolkit:
|
||||
Download from: https://developer.nvidia.com/cuda-downloads
|
||||
Select: Windows > x86_64 > 11/10 > exe (local)
|
||||
Size: ~3 GB
|
||||
|
||||
Alternative: cupy auto-installs CUDA runtime!
|
||||
pip install cupy-cuda12x
|
||||
This bundles CUDA runtime (~700 MB) — no separate install needed
|
||||
```
|
||||
|
||||
### Intel GPU Support (OpenCL)
|
||||
|
||||
```
|
||||
REQUIRED: Intel GPU Driver (usually pre-installed)
|
||||
REQUIRED: Intel OpenCL Runtime
|
||||
|
||||
Check if installed:
|
||||
Open Device Manager → Display Adapters → Intel UHD/Iris
|
||||
|
||||
For OpenCL:
|
||||
Download Intel GPU Computing Runtime:
|
||||
https://github.com/intel/compute-runtime/releases
|
||||
|
||||
Or: Intel oneAPI Base Toolkit (includes OpenCL)
|
||||
https://www.intel.com/content/www/us/en/developer/tools/oneapi/base-toolkit-download.html
|
||||
```
|
||||
|
||||
### AMD GPU Support (OpenCL)
|
||||
|
||||
```
|
||||
REQUIRED: AMD Adrenalin Driver (includes OpenCL)
|
||||
Download from: https://www.amd.com/en/support
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Node.js / Frontend Dependencies
|
||||
|
||||
### System Requirements
|
||||
|
||||
```
|
||||
Node.js >= 18.0.0 (LTS recommended)
|
||||
npm >= 9.0.0
|
||||
|
||||
Check:
|
||||
node --version
|
||||
npm --version
|
||||
```
|
||||
|
||||
### Frontend packages (managed by npm)
|
||||
|
||||
```json
|
||||
// package.json — key dependencies
|
||||
{
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"leaflet": "^1.9.4",
|
||||
"react-leaflet": "^4.2.0",
|
||||
"recharts": "^2.8.0",
|
||||
"zustand": "^4.4.0",
|
||||
"lucide-react": "^0.294.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^5.0.0",
|
||||
"typescript": "^5.3.0",
|
||||
"tailwindcss": "^3.4.0",
|
||||
"@types/leaflet": "^1.9.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Installer Script
|
||||
|
||||
### Windows Installer (NSIS or Electron-Builder)
|
||||
|
||||
```python
|
||||
# install_rfcp.py — Python-based installer/setup script
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import platform
|
||||
import os
|
||||
import shutil
|
||||
import json
|
||||
|
||||
def check_python():
|
||||
"""Verify Python 3.10+ is available."""
|
||||
version = sys.version_info
|
||||
if version.major < 3 or version.minor < 10:
|
||||
print(f"❌ Python 3.10+ required, found {version.major}.{version.minor}")
|
||||
return False
|
||||
print(f"✅ Python {version.major}.{version.minor}.{version.micro}")
|
||||
return True
|
||||
|
||||
def check_node():
|
||||
"""Verify Node.js 18+ is available."""
|
||||
try:
|
||||
result = subprocess.run(["node", "--version"], capture_output=True, text=True)
|
||||
version = result.stdout.strip().lstrip('v')
|
||||
major = int(version.split('.')[0])
|
||||
if major < 18:
|
||||
print(f"❌ Node.js 18+ required, found {version}")
|
||||
return False
|
||||
print(f"✅ Node.js {version}")
|
||||
return True
|
||||
except FileNotFoundError:
|
||||
print("❌ Node.js not found")
|
||||
return False
|
||||
|
||||
def detect_gpu():
|
||||
"""Detect available GPU hardware."""
|
||||
gpus = {
|
||||
"nvidia": False,
|
||||
"nvidia_name": "",
|
||||
"intel": False,
|
||||
"intel_name": "",
|
||||
"amd": False,
|
||||
"amd_name": ""
|
||||
}
|
||||
|
||||
# Check NVIDIA
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["nvidia-smi", "--query-gpu=name,driver_version,memory.total",
|
||||
"--format=csv,noheader"],
|
||||
capture_output=True, text=True, timeout=5
|
||||
)
|
||||
if result.returncode == 0:
|
||||
info = result.stdout.strip()
|
||||
gpus["nvidia"] = True
|
||||
gpus["nvidia_name"] = info.split(",")[0].strip()
|
||||
print(f"✅ NVIDIA GPU: {info}")
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired):
|
||||
print("ℹ️ No NVIDIA GPU detected")
|
||||
|
||||
# Check Intel/AMD via WMI (Windows)
|
||||
if platform.system() == "Windows":
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["wmic", "path", "win32_videocontroller", "get",
|
||||
"name,adapterram,driverversion", "/format:csv"],
|
||||
capture_output=True, text=True, timeout=5
|
||||
)
|
||||
for line in result.stdout.strip().split('\n'):
|
||||
if 'Intel' in line:
|
||||
gpus["intel"] = True
|
||||
gpus["intel_name"] = [x for x in line.split(',') if 'Intel' in x][0]
|
||||
print(f"✅ Intel GPU: {gpus['intel_name']}")
|
||||
elif 'AMD' in line or 'Radeon' in line:
|
||||
gpus["amd"] = True
|
||||
gpus["amd_name"] = [x for x in line.split(',') if 'AMD' in x or 'Radeon' in x][0]
|
||||
print(f"✅ AMD GPU: {gpus['amd_name']}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return gpus
|
||||
|
||||
def install_core_dependencies():
|
||||
"""Install core Python dependencies."""
|
||||
print("\n📦 Installing core dependencies...")
|
||||
subprocess.run([
|
||||
sys.executable, "-m", "pip", "install", "-r", "requirements.txt",
|
||||
"--quiet", "--no-warn-script-location"
|
||||
], check=True)
|
||||
print("✅ Core dependencies installed")
|
||||
|
||||
def install_gpu_dependencies(gpus: dict):
|
||||
"""Install GPU-specific dependencies based on detected hardware."""
|
||||
print("\n🎮 Setting up GPU acceleration...")
|
||||
|
||||
gpu_installed = False
|
||||
|
||||
# NVIDIA — install CuPy (includes CUDA runtime)
|
||||
if gpus["nvidia"]:
|
||||
print(f" Installing CuPy for {gpus['nvidia_name']}...")
|
||||
try:
|
||||
# Try CUDA 12 first (newer cards)
|
||||
subprocess.run([
|
||||
sys.executable, "-m", "pip", "install", "cupy-cuda12x",
|
||||
"--quiet", "--no-warn-script-location"
|
||||
], check=True, timeout=300)
|
||||
print(f" ✅ CuPy (CUDA 12) installed for {gpus['nvidia_name']}")
|
||||
gpu_installed = True
|
||||
except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
|
||||
try:
|
||||
# Fallback to CUDA 11
|
||||
subprocess.run([
|
||||
sys.executable, "-m", "pip", "install", "cupy-cuda11x",
|
||||
"--quiet", "--no-warn-script-location"
|
||||
], check=True, timeout=300)
|
||||
print(f" ✅ CuPy (CUDA 11) installed for {gpus['nvidia_name']}")
|
||||
gpu_installed = True
|
||||
except Exception as e:
|
||||
print(f" ⚠️ CuPy installation failed: {e}")
|
||||
print(f" 💡 Manual install: pip install cupy-cuda12x")
|
||||
|
||||
# Intel/AMD — install PyOpenCL
|
||||
if gpus["intel"] or gpus["amd"]:
|
||||
gpu_name = gpus["intel_name"] or gpus["amd_name"]
|
||||
print(f" Installing PyOpenCL for {gpu_name}...")
|
||||
try:
|
||||
subprocess.run([
|
||||
sys.executable, "-m", "pip", "install", "pyopencl",
|
||||
"--quiet", "--no-warn-script-location"
|
||||
], check=True, timeout=120)
|
||||
print(f" ✅ PyOpenCL installed for {gpu_name}")
|
||||
gpu_installed = True
|
||||
except Exception as e:
|
||||
print(f" ⚠️ PyOpenCL installation failed: {e}")
|
||||
print(f" 💡 Manual install: pip install pyopencl")
|
||||
|
||||
if not gpu_installed:
|
||||
print(" ℹ️ No GPU acceleration available — using CPU (NumPy)")
|
||||
print(" 💡 This is fine! GPU just makes large calculations faster.")
|
||||
|
||||
return gpu_installed
|
||||
|
||||
def install_frontend():
|
||||
"""Install frontend dependencies and build."""
|
||||
print("\n🌐 Setting up frontend...")
|
||||
frontend_dir = os.path.join(os.path.dirname(__file__), "frontend")
|
||||
|
||||
if os.path.exists(os.path.join(frontend_dir, "package.json")):
|
||||
subprocess.run(["npm", "install"], cwd=frontend_dir, check=True)
|
||||
subprocess.run(["npm", "run", "build"], cwd=frontend_dir, check=True)
|
||||
print("✅ Frontend built")
|
||||
else:
|
||||
print("⚠️ Frontend directory not found")
|
||||
|
||||
def download_terrain_data():
|
||||
"""Pre-download SRTM terrain tiles for Ukraine."""
|
||||
print("\n🏔️ Checking terrain data...")
|
||||
cache_dir = os.path.expanduser("~/.rfcp/terrain")
|
||||
os.makedirs(cache_dir, exist_ok=True)
|
||||
|
||||
# Ukraine bounding box: lat 44-53, lon 22-41
|
||||
# SRTM tiles needed for typical use
|
||||
required_tiles = [
|
||||
# Lviv oblast area (common test area)
|
||||
"N49E025", "N49E024", "N49E026",
|
||||
"N50E025", "N50E024", "N50E026",
|
||||
# Dnipro area
|
||||
"N48E034", "N48E035",
|
||||
"N49E034", "N49E035",
|
||||
]
|
||||
|
||||
existing = [f.replace(".hgt", "") for f in os.listdir(cache_dir) if f.endswith(".hgt")]
|
||||
missing = [t for t in required_tiles if t not in existing]
|
||||
|
||||
if missing:
|
||||
print(f" {len(missing)} terrain tiles needed (auto-download on first use)")
|
||||
else:
|
||||
print(f" ✅ {len(existing)} terrain tiles cached")
|
||||
|
||||
def create_launcher():
|
||||
"""Create desktop shortcut / launcher script."""
|
||||
print("\n🚀 Creating launcher...")
|
||||
|
||||
if platform.system() == "Windows":
|
||||
# Create .bat launcher
|
||||
launcher = os.path.join(os.path.dirname(__file__), "RFCP.bat")
|
||||
with open(launcher, 'w') as f:
|
||||
f.write('@echo off\n')
|
||||
f.write('title RFCP - RF Coverage Planner\n')
|
||||
f.write('echo Starting RFCP...\n')
|
||||
f.write(f'cd /d "{os.path.dirname(__file__)}"\n')
|
||||
f.write(f'"{sys.executable}" -m uvicorn backend.app.main:app --host 0.0.0.0 --port 8888\n')
|
||||
print(f" ✅ Launcher created: {launcher}")
|
||||
|
||||
return True
|
||||
|
||||
def verify_installation():
|
||||
"""Run quick verification tests."""
|
||||
print("\n🔍 Verifying installation...")
|
||||
|
||||
checks = []
|
||||
|
||||
# Check core imports
|
||||
try:
|
||||
import numpy as np
|
||||
checks.append(f"✅ NumPy {np.__version__}")
|
||||
except ImportError:
|
||||
checks.append("❌ NumPy missing")
|
||||
|
||||
try:
|
||||
import scipy
|
||||
checks.append(f"✅ SciPy {scipy.__version__}")
|
||||
except ImportError:
|
||||
checks.append("❌ SciPy missing")
|
||||
|
||||
try:
|
||||
import fastapi
|
||||
checks.append(f"✅ FastAPI {fastapi.__version__}")
|
||||
except ImportError:
|
||||
checks.append("❌ FastAPI missing")
|
||||
|
||||
try:
|
||||
import shapely
|
||||
checks.append(f"✅ Shapely {shapely.__version__}")
|
||||
except ImportError:
|
||||
checks.append("⚠️ Shapely missing (boundary features disabled)")
|
||||
|
||||
# Check GPU
|
||||
try:
|
||||
import cupy as cp
|
||||
device = cp.cuda.Device(0)
|
||||
checks.append(f"✅ CuPy → {device.name} ({device.mem_info[1]//1024//1024} MB)")
|
||||
except ImportError:
|
||||
checks.append("ℹ️ CuPy not available")
|
||||
except Exception as e:
|
||||
checks.append(f"⚠️ CuPy error: {e}")
|
||||
|
||||
try:
|
||||
import pyopencl as cl
|
||||
devices = []
|
||||
for p in cl.get_platforms():
|
||||
for d in p.get_devices():
|
||||
devices.append(d.name)
|
||||
checks.append(f"✅ PyOpenCL → {', '.join(devices)}")
|
||||
except ImportError:
|
||||
checks.append("ℹ️ PyOpenCL not available")
|
||||
except Exception as e:
|
||||
checks.append(f"⚠️ PyOpenCL error: {e}")
|
||||
|
||||
for check in checks:
|
||||
print(f" {check}")
|
||||
|
||||
return all("❌" not in c for c in checks)
|
||||
|
||||
def main():
|
||||
"""Main installer entry point."""
|
||||
print("=" * 60)
|
||||
print(" RFCP — RF Coverage Planner — Installer")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
# Step 1: Check prerequisites
|
||||
print("📋 Checking prerequisites...")
|
||||
if not check_python():
|
||||
sys.exit(1)
|
||||
check_node()
|
||||
|
||||
# Step 2: Detect GPU
|
||||
gpus = detect_gpu()
|
||||
|
||||
# Step 3: Install dependencies
|
||||
install_core_dependencies()
|
||||
install_gpu_dependencies(gpus)
|
||||
|
||||
# Step 4: Frontend
|
||||
install_frontend()
|
||||
|
||||
# Step 5: Terrain data
|
||||
download_terrain_data()
|
||||
|
||||
# Step 6: Launcher
|
||||
create_launcher()
|
||||
|
||||
# Step 7: Verify
|
||||
print()
|
||||
success = verify_installation()
|
||||
|
||||
# Summary
|
||||
print()
|
||||
print("=" * 60)
|
||||
if success:
|
||||
print(" ✅ RFCP installed successfully!")
|
||||
print()
|
||||
print(" To start RFCP:")
|
||||
print(" python -m uvicorn backend.app.main:app --port 8888")
|
||||
print(" Then open: http://localhost:8888")
|
||||
print()
|
||||
if gpus["nvidia"]:
|
||||
print(f" 🎮 GPU: {gpus['nvidia_name']} (CUDA)")
|
||||
elif gpus["intel"] or gpus["amd"]:
|
||||
gpu_name = gpus["intel_name"] or gpus["amd_name"]
|
||||
print(f" 🎮 GPU: {gpu_name} (OpenCL)")
|
||||
else:
|
||||
print(" 💻 Mode: CPU only")
|
||||
else:
|
||||
print(" ⚠️ Installation completed with warnings")
|
||||
print(" Some features may be limited")
|
||||
print("=" * 60)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Electron-Builder / NSIS Packaging
|
||||
|
||||
### For .exe Installer
|
||||
|
||||
```yaml
|
||||
# electron-builder.yml
|
||||
|
||||
appId: com.rfcp.coverage-planner
|
||||
productName: "RFCP - RF Coverage Planner"
|
||||
copyright: "RFCP 2026"
|
||||
|
||||
directories:
|
||||
output: dist
|
||||
buildResources: build
|
||||
|
||||
files:
|
||||
- "backend/**/*"
|
||||
- "frontend/dist/**/*"
|
||||
- "requirements.txt"
|
||||
- "install_rfcp.py"
|
||||
- "!**/*.pyc"
|
||||
- "!**/node_modules/**"
|
||||
- "!**/venv/**"
|
||||
|
||||
extraResources:
|
||||
- from: "python-embedded/"
|
||||
to: "python/"
|
||||
- from: "terrain-data/"
|
||||
to: "terrain/"
|
||||
|
||||
win:
|
||||
target:
|
||||
- target: nsis
|
||||
arch: [x64]
|
||||
icon: "build/icon.ico"
|
||||
|
||||
nsis:
|
||||
oneClick: false
|
||||
allowToChangeInstallationDirectory: true
|
||||
installerIcon: "build/icon.ico"
|
||||
license: "LICENSE.md"
|
||||
|
||||
# Custom NSIS script for GPU detection
|
||||
include: "build/gpu-detect.nsh"
|
||||
|
||||
# Install steps:
|
||||
# 1. Extract files
|
||||
# 2. Run install_rfcp.py (detects GPU, installs deps)
|
||||
# 3. Create Start Menu shortcuts
|
||||
# 4. Create Desktop shortcut
|
||||
```
|
||||
|
||||
### Portable Version (.zip)
|
||||
|
||||
```
|
||||
RFCP-Portable/
|
||||
├── RFCP.bat # Main launcher
|
||||
├── install.bat # First-time setup
|
||||
├── backend/
|
||||
│ ├── app/
|
||||
│ │ ├── main.py
|
||||
│ │ ├── api/
|
||||
│ │ ├── services/
|
||||
│ │ └── models/
|
||||
│ └── requirements.txt
|
||||
├── frontend/
|
||||
│ └── dist/ # Pre-built frontend
|
||||
├── python/ # Embedded Python (optional)
|
||||
│ ├── python.exe
|
||||
│ └── Lib/
|
||||
├── terrain/ # Pre-cached .hgt files
|
||||
│ ├── N49E025.hgt
|
||||
│ └── ...
|
||||
├── data/
|
||||
│ ├── osm_cache.db # SQLite cache (created on first run)
|
||||
│ └── config.json # User settings
|
||||
└── README.md
|
||||
```
|
||||
|
||||
### install.bat (First-Time Setup)
|
||||
|
||||
```batch
|
||||
@echo off
|
||||
title RFCP - First Time Setup
|
||||
echo ============================================
|
||||
echo RFCP - RF Coverage Planner - Setup
|
||||
echo ============================================
|
||||
echo.
|
||||
|
||||
REM Check if Python exists
|
||||
python --version >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo ERROR: Python not found!
|
||||
echo Please install Python 3.10+ from python.org
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Run installer
|
||||
python install_rfcp.py
|
||||
|
||||
echo.
|
||||
echo Setup complete! Run RFCP.bat to start.
|
||||
pause
|
||||
```
|
||||
|
||||
### RFCP.bat (Launcher)
|
||||
|
||||
```batch
|
||||
@echo off
|
||||
title RFCP - RF Coverage Planner
|
||||
cd /d "%~dp0"
|
||||
|
||||
REM Check if installed
|
||||
if not exist "backend\app\main.py" (
|
||||
echo ERROR: RFCP not found. Run install.bat first.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Starting RFCP...
|
||||
echo Open http://localhost:8888 in your browser
|
||||
echo Press Ctrl+C to stop
|
||||
echo.
|
||||
|
||||
python -m uvicorn backend.app.main:app --host 0.0.0.0 --port 8888
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependency Size Estimates
|
||||
|
||||
| Component | Size |
|
||||
|-----------|------|
|
||||
| Python (embedded) | ~30 MB |
|
||||
| Core pip packages | ~80 MB |
|
||||
| CuPy + CUDA runtime | ~700 MB |
|
||||
| PyOpenCL | ~15 MB |
|
||||
| Frontend (built) | ~5 MB |
|
||||
| SRTM terrain (Ukraine) | ~300 MB |
|
||||
| **Total (with CUDA)** | **~1.1 GB** |
|
||||
| **Total (CPU only)** | **~415 MB** |
|
||||
|
||||
---
|
||||
|
||||
## Runtime Requirements
|
||||
|
||||
| Resource | Minimum | Recommended |
|
||||
|----------|---------|-------------|
|
||||
| RAM | 4 GB | 8+ GB |
|
||||
| Disk | 500 MB | 2 GB (with terrain cache) |
|
||||
| CPU | 4 cores | 8+ cores |
|
||||
| GPU | - | NVIDIA GTX 1060+ / Intel UHD 630+ |
|
||||
| OS | Windows 10 | Windows 10/11 64-bit |
|
||||
| Python | 3.10 | 3.11+ |
|
||||
| Node.js | 18 | 20 LTS |
|
||||
|
||||
---
|
||||
|
||||
## Auto-Update Mechanism (Future)
|
||||
|
||||
```python
|
||||
# Check for updates on startup
|
||||
async def check_for_updates():
|
||||
try:
|
||||
response = await httpx.get(
|
||||
"https://api.github.com/repos/user/rfcp/releases/latest",
|
||||
timeout=5
|
||||
)
|
||||
latest = response.json()["tag_name"]
|
||||
current = get_current_version()
|
||||
|
||||
if latest != current:
|
||||
return {
|
||||
"update_available": True,
|
||||
"current": current,
|
||||
"latest": latest,
|
||||
"download_url": response.json()["assets"][0]["browser_download_url"]
|
||||
}
|
||||
except:
|
||||
pass
|
||||
return {"update_available": False}
|
||||
```
|
||||
Reference in New Issue
Block a user