diff --git a/backend/requirements.txt b/backend/requirements.txt index ffe86be..ec4069e 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,6 +1,7 @@ fastapi==0.115.6 uvicorn[standard]==0.34.0 python-pam==2.0.2 +six==1.17.0 PyJWT==2.10.1 psutil==6.1.1 ptyprocess==0.7.0 diff --git a/backend/routers/files.py b/backend/routers/files.py index 6e3cf0c..8b7ffeb 100644 --- a/backend/routers/files.py +++ b/backend/routers/files.py @@ -110,7 +110,13 @@ async def list_dir( entries = sorted(p.iterdir(), key=lambda x: (not x.is_dir(), x.name.lower())) except PermissionError: raise HTTPException(403, f"Permission denied: {path}") - return [_file_info(e) for e in entries] + results = [] + for e in entries: + try: + results.append(_file_info(e)) + except (OSError, PermissionError): + continue + return results @router.get("/info") diff --git a/backend/routers/plugins/asi_bridge.py b/backend/routers/plugins/asi_bridge.py index eb79263..e6be124 100644 --- a/backend/routers/plugins/asi_bridge.py +++ b/backend/routers/plugins/asi_bridge.py @@ -25,6 +25,9 @@ class MountConfig(BaseModel): async def _run(cmd: list[str]) -> tuple[int, str, str]: + import shutil + if not shutil.which(cmd[0]): + return 1, "", f"{cmd[0]}: command not found" proc = await asyncio.create_subprocess_exec( *cmd, stdout=asyncio.subprocess.PIPE, @@ -195,7 +198,7 @@ async def bridge_ws(websocket: WebSocket): seen_files = current_paths await asyncio.sleep(5) - except WebSocketDisconnect: + except (WebSocketDisconnect, RuntimeError): pass except Exception: log.exception("ASI Bridge WS error") diff --git a/backend/routers/services.py b/backend/routers/services.py index eb0ef2c..3f5a98f 100644 --- a/backend/routers/services.py +++ b/backend/routers/services.py @@ -122,7 +122,7 @@ async def service_logs(websocket: WebSocket, unit: str): "unit": unit, "line": line.decode(errors="replace").rstrip(), }) - except WebSocketDisconnect: + except (WebSocketDisconnect, RuntimeError): pass except Exception: log.exception("log stream error for %s", unit) diff --git a/backend/routers/settings.py b/backend/routers/settings.py index 491bdff..4a1d2ce 100644 --- a/backend/routers/settings.py +++ b/backend/routers/settings.py @@ -34,6 +34,9 @@ class TimezoneRequest(BaseModel): async def _run(cmd: list[str]) -> tuple[int, str, str]: + import shutil + if not shutil.which(cmd[0]): + return 1, "", f"{cmd[0]}: command not found" proc = await asyncio.create_subprocess_exec( *cmd, stdout=asyncio.subprocess.PIPE, diff --git a/frontend/js/keyboard.js b/frontend/js/keyboard.js index fd4f11a..734884e 100644 --- a/frontend/js/keyboard.js +++ b/frontend/js/keyboard.js @@ -160,12 +160,15 @@ const btn = document.createElement('button'); btn.className = 'osk-key ' + className; btn.textContent = label; + let touchFired = false; btn.addEventListener('touchstart', (e) => { e.preventDefault(); + touchFired = true; handler(); }); btn.addEventListener('click', (e) => { e.preventDefault(); + if (touchFired) { touchFired = false; return; } handler(); }); return btn; diff --git a/install.sh b/install.sh index e1d3f73..7cd2eae 100755 --- a/install.sh +++ b/install.sh @@ -1,13 +1,13 @@ #!/usr/bin/env bash # Atlus installer — targets DietPi, Armbian, Debian, Ubuntu -# Usage: curl -sSL https://raw.githubusercontent.com/YOUR_REPO/atlus/main/install.sh | bash +# Usage: curl -sSL https://git.bytestud.io/roberts/atlus/raw/branch/main/install.sh | sudo bash set -euo pipefail INSTALL_DIR="/opt/atlus" CONFIG_DIR="/etc/atlus" DATA_DIR="/var/lib/atlus" SERVICE_FILE="/etc/systemd/system/atlus.service" -REPO_URL="https://github.com/YOUR_REPO/atlus.git" +REPO_URL="https://git.bytestud.io/roberts/atlus.git" # --------------------------------------------------------------------------- # Helpers