Fix robustness issues across backend and frontend

- Add shutil.which guard to _run() in settings, asi_bridge routers
- Catch RuntimeError on WebSocket disconnect in services, asi_bridge
- Make file listing resilient to individual entry errors
- Fix keyboard double-fire on touch devices (touchstart + click)
- Update install.sh with correct Gitea repo URL
- Add six to requirements.txt (python-pam dependency)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
roberts 2026-03-14 17:41:43 -05:00
parent f9743bb29a
commit 342dc0f0cf
7 changed files with 21 additions and 5 deletions

View file

@ -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

View file

@ -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")

View file

@ -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")

View file

@ -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)

View file

@ -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,

View file

@ -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;

View file

@ -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