"""YakPanel - App Store / Software API""" from fastapi import APIRouter, Depends, HTTPException from app.core.utils import exec_shell_sync from app.api.auth import get_current_user from app.models.user import User router = APIRouter(prefix="/soft", tags=["soft"]) # Curated list of common server software (Debian/Ubuntu package names) SOFTWARE_LIST = [ {"id": "nginx", "name": "Nginx", "desc": "Web server", "pkg": "nginx"}, {"id": "mysql-server", "name": "MySQL Server", "desc": "Database server", "pkg": "mysql-server"}, {"id": "mariadb-server", "name": "MariaDB", "desc": "Database server", "pkg": "mariadb-server"}, {"id": "php", "name": "PHP", "desc": "PHP runtime", "pkg": "php"}, {"id": "php-fpm", "name": "PHP-FPM", "desc": "PHP FastCGI", "pkg": "php-fpm"}, {"id": "redis-server", "name": "Redis", "desc": "In-memory cache", "pkg": "redis-server"}, {"id": "postgresql", "name": "PostgreSQL", "desc": "Database server", "pkg": "postgresql"}, {"id": "mongodb", "name": "MongoDB", "desc": "NoSQL database", "pkg": "mongodb"}, {"id": "certbot", "name": "Certbot", "desc": "Let's Encrypt SSL", "pkg": "certbot"}, {"id": "docker", "name": "Docker", "desc": "Container runtime", "pkg": "docker.io"}, {"id": "nodejs", "name": "Node.js", "desc": "JavaScript runtime", "pkg": "nodejs"}, {"id": "npm", "name": "npm", "desc": "Node package manager", "pkg": "npm"}, {"id": "git", "name": "Git", "desc": "Version control", "pkg": "git"}, {"id": "python3", "name": "Python 3", "desc": "Python runtime", "pkg": "python3"}, ] def _check_installed(pkg: str) -> tuple[bool, str]: """Check if package is installed. Returns (installed, version_or_error).""" out, err = exec_shell_sync(f"dpkg -l {pkg} 2>/dev/null | grep ^ii", timeout=5) if out.strip(): # Parse version from dpkg output: ii pkg version ... parts = out.split() if len(parts) >= 3: return True, parts[2] return False, "" @router.get("/list") async def soft_list(current_user: User = Depends(get_current_user)): """List software with install status""" result = [] for s in SOFTWARE_LIST: installed, version = _check_installed(s["pkg"]) result.append({ **s, "installed": installed, "version": version if installed else "", }) return {"software": result} @router.post("/install/{pkg_id}") async def soft_install( pkg_id: str, current_user: User = Depends(get_current_user), ): """Install package via apt (requires root)""" pkg = next((s["pkg"] for s in SOFTWARE_LIST if s["id"] == pkg_id), None) if not pkg: raise HTTPException(status_code=404, detail="Package not found") out, err = exec_shell_sync(f"apt-get update && apt-get install -y {pkg}", timeout=300) if err and "error" in err.lower() and "E: " in err: raise HTTPException(status_code=500, detail=err.strip() or out.strip()) return {"status": True, "msg": "Installed"} @router.post("/uninstall/{pkg_id}") async def soft_uninstall( pkg_id: str, current_user: User = Depends(get_current_user), ): """Uninstall package via apt""" pkg = next((s["pkg"] for s in SOFTWARE_LIST if s["id"] == pkg_id), None) if not pkg: raise HTTPException(status_code=404, detail="Package not found") out, err = exec_shell_sync(f"apt-get remove -y {pkg}", timeout=120) if err and "error" in err.lower() and "E: " in err: raise HTTPException(status_code=500, detail=err.strip() or out.strip()) return {"status": True, "msg": "Uninstalled"}