diff --git a/backend/system_api.py b/backend/system_api.py new file mode 100644 index 0000000..1294d16 --- /dev/null +++ b/backend/system_api.py @@ -0,0 +1,135 @@ +import os +from pathlib import Path + +from fastapi import APIRouter +from pydantic import BaseModel + +from backend.config_store import get_github_public_config, has_openai_key +from backend.runtime_logs import add_log +from backend.shell_runner import run_command + +router = APIRouter(prefix='/api/system', tags=['system']) + +DATA_DIR = Path(os.getenv('DEVCONSOLE_DATA_DIR', '/var/lib/devconsole')) +PROJECTS_DIR = DATA_DIR / 'projects' + + +def _runtime_env_prefix() -> str: + runtime_home = os.getenv('DEVCONSOLE_RUNTIME_HOME') or os.getenv('HOME') or '/home/devconsole' + flutter_home = os.getenv('FLUTTER_HOME') or f'{runtime_home}/flutter' + android_home = os.getenv('ANDROID_HOME') or os.getenv('ANDROID_SDK_ROOT') or f'{runtime_home}/Android' + pub_cache = os.getenv('PUB_CACHE') or f'{runtime_home}/.pub-cache' + return ( + f'export HOME="{runtime_home}" ' + f'PUB_CACHE="{pub_cache}" ' + f'ANDROID_HOME="{android_home}" ' + f'ANDROID_SDK_ROOT="{android_home}" ' + f'PATH="{flutter_home}/bin:{android_home}/cmdline-tools/latest/bin:{android_home}/platform-tools:$PATH" && ' + ) + + +def _check(command: str, timeout: int = 20) -> dict: + try: + result = run_command(command, PROJECTS_DIR.as_posix(), timeout=timeout) + return { + 'ok': result['returncode'] == 0, + 'returncode': result['returncode'], + 'stdout': (result.get('stdout') or '').strip()[-600:], + 'stderr': (result.get('stderr') or '').strip()[-600:], + } + except Exception as exc: + return {'ok': False, 'returncode': -1, 'stdout': '', 'stderr': str(exc)} + + +class SystemRestartRequest(BaseModel): + target: str + + +@router.get('/checks') +async def system_checks(): + PROJECTS_DIR.mkdir(parents=True, exist_ok=True) + env = _runtime_env_prefix() + + checks = [ + { + 'id': 'devconsole', + 'title': 'DevConsole', + 'restartable': True, + **_check('systemctl is-active devconsole || true', 10), + }, + { + 'id': 'github', + 'title': 'GitHub', + 'restartable': False, + 'ok': bool(get_github_public_config().get('token_set')), + 'returncode': 0, + 'stdout': 'token configured' if get_github_public_config().get('token_set') else 'token missing', + 'stderr': '', + }, + { + 'id': 'openai', + 'title': 'OpenAI', + 'restartable': False, + 'ok': has_openai_key(), + 'returncode': 0, + 'stdout': 'configured' if has_openai_key() else 'not configured', + 'stderr': '', + }, + { + 'id': 'flutter', + 'title': 'Flutter', + 'restartable': False, + **_check(f'{env}flutter --version | head -n 1', 30), + }, + { + 'id': 'android_sdk', + 'title': 'Android SDK', + 'restartable': False, + **_check(f'{env}sdkmanager --version', 30), + }, + { + 'id': 'adb', + 'title': 'ADB', + 'restartable': True, + **_check(f'{env}adb version | head -n 1', 20), + }, + { + 'id': 'docker', + 'title': 'Docker', + 'restartable': True, + **_check('docker --version', 20), + }, + { + 'id': 'nginx', + 'title': 'Nginx', + 'restartable': True, + **_check('systemctl is-active nginx || true', 10), + }, + ] + + for item in checks: + if item['id'] == 'devconsole': + item['ok'] = 'active' in item.get('stdout', '') + if item['id'] == 'nginx': + item['ok'] = 'active' in item.get('stdout', '') + + return {'success': True, 'systems': checks} + + +@router.post('/restart') +async def system_restart(payload: SystemRestartRequest): + target = payload.target + commands = { + 'devconsole': 'sudo systemctl restart devconsole', + 'adb': f'{_runtime_env_prefix()}adb kill-server && {_runtime_env_prefix()}adb start-server', + 'docker': 'sudo systemctl restart docker', + 'nginx': 'sudo systemctl restart nginx', + } + + command = commands.get(target) + if not command: + return {'success': False, 'message': 'Restart is not allowed for this target'} + + add_log(f'System restart requested: {target}') + result = _check(command, 60) + return {'success': result['ok'], 'target': target, 'result': result}