# Remote Admin (Local Dev) Lightweight Django + Channels app for live SSH command execution & streaming. ## Stack - Python 3.12 - Django 5.x - Channels 4 (InMemoryChannelLayer, single-process only) - asyncssh for SSH - HTMX + vanilla JS (no build toolchain) - Tailwind via CDN - SQLite ## Current Feature Set - Hosts: CRUD for SSH target metadata - Tasks: Fully editable reusable command tasks (custom only; static registry removed) - Batch Scripts: Multi-step scripts with `cd` directory persistence and per-step progress - Web Console: - Connect/disconnect via WebSocket - Run ad‑hoc command OR select saved task OR run batch - Real-time stdout/stderr streaming - Batch progress events (STEP X/Y) - Cancel running command/batch (graceful termination) - Validation & UX safeguards: - Empty commands prevented client-side - Batch normalization (strip Windows newlines, trim trailing blanks, ignore comments/blank lines) - Delete confirmations (hx-confirm) - Logs: - Status, exit code, duration, output tail - Run type (`single` / `batch`) - Failed step index (for batch failures / errors) - Structured error events JSON: `{event:"error", type:"ssh|runtime", message:"..."}` - Auth: Login required for all views - Manual: `/manual/` in-app searchable documentation - Tests: CRUD + WebSocket execution (success, failure, batch success/failure, cancel) 13 passing ## Setup ```bash python -m venv .venv source .venv/bin/activate pip install -r requirements.txt cp .env.example .env # edit values python manage.py migrate python manage.py createsuperuser ``` Run (single process ONLY): ```bash daphne -b 127.0.0.1 -p 8000 config.asgi:application # or for quick dev python manage.py runserver 0.0.0.0:8000 # fine for dev only ``` Visit: http://127.0.0.1:8000/ ## Environment Variables (.env) | Var | Purpose | |-----|---------| | DJANGO_SECRET_KEY | Django secret key | | DJANGO_DEBUG | 0/1 toggle | | ALLOWED_HOSTS | Comma list | | SSH_KEY_DIR | Optional default key base path | | KNOWN_HOSTS_PATH | Path to known_hosts file | | STRICT_HOST_KEY_CHECKING | true/false – enforce host key verification | ## SSH Setup (DE) Ausführliche Anleitung zur Einrichtung einer funktionierenden SSH-Verbindung für die Web-Konsole. ### 1. Lokalen SSH-Key prüfen oder erzeugen Prüfen ob bereits ein (modernen) Ed25519 Key existiert: ```bash ls -l ~/.ssh/id_ed25519 ~/.ssh/id_ed25519.pub 2>/dev/null || echo 'kein ed25519 key' ``` Falls nicht vorhanden erstellen: ```bash ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -C "$(whoami)@$(hostname)" -N '' ``` Sichere Rechte setzen: ```bash chmod 700 ~/.ssh chmod 600 ~/.ssh/id_ed25519 chmod 644 ~/.ssh/id_ed25519.pub ``` ### 2. Public Key auf Zielhost hinterlegen Ersetzt `deploy@server1` durch Benutzer & Host. ```bash cat ~/.ssh/id_ed25519.pub | ssh deploy@server1 '\ mkdir -p ~/.ssh && chmod 700 ~/.ssh && \ cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys' ``` Test (direkt): ```bash ssh deploy@server1 'echo OK' ``` ### 3. Host Key erfassen (bei Strict Checking) Nur nötig wenn `STRICT_HOST_KEY_CHECKING=true` (empfohlen Production). ```bash ssh-keyscan -T 5 -t rsa,ecdsa,ed25519 server1 >> ~/.ssh/known_hosts ``` Bei Key-Änderung (Rotation / Reprovisioning) vorher entfernen: ```bash ssh-keygen -R server1 ``` ### 4. Authentifizierungs-Methode wählen | Methode | Wann nutzen | Voraussetzungen | |---------|-------------|-----------------| | ssh_key | Fester Schlüsselpfad | Privater Key-Dateipfad existiert & Rechte korrekt | | agent | Temporäre Nutzung / Passphrase-Key | `ssh-agent` läuft & `ssh-add` ausgeführt | Agent starten (falls nicht aktiv): ```bash eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_ed25519 ``` ### 5. Host in der Web-App anlegen Formularfelder: - Name: frei, z.B. `prod-app-1` - Hostname: DNS / IP, z.B. `10.10.10.15` - Port: Standard 22 - Username: z.B. `deploy` - Auth Method: `SSH Key` oder `Agent` - Key Path (nur bei SSH Key): Absoluter Pfad, z.B. `/home/deploy/.ssh/id_ed25519` - Strict Host Key Checking: Aktiv lassen außer im lokalen Test-Lab ### 6. Verbindung testen In der Console: 1. Host wählen 2. Connect klicken (Status-Badge -> CONNECTED) 3. Ad-hoc Command `uname -a` eingeben → Run 4. Ausgabe sollte erscheinen; bei Fehlern → Fehlermeldung lesen (structured error event) ### 7. Typische Fehler & Lösungen | Meldung | Ursache | Lösung | |---------|---------|-------| | Host key not trusted | Host Key fehlt / weicht ab | Key mit `ssh-keyscan` sammeln oder Strict deaktivieren (nur dev) | | Permission denied | Key / User passt nicht | User/Key prüfen, Rechte kontrollieren, ggf. Agent laden | | Connection lost | Netzwerk/Firewall | Direkten SSH Versuch testen, Port offen? | | Unknown action | Falsches WebSocket Payload | Seite neu laden | ### 8. Beispiel: Zwei Hosts (Prod & Staging) ``` Name: prod-api Host: prod-api.company.internal User: deploy Auth: ssh_key Key Path: /home/deploy/.ssh/id_ed25519 Name: staging-api Host: staging.company.internal User: deploy Auth: agent Key Path: (leer, via ssh-agent) ``` ### 9. Sicherheit - Keine Passwörter im Modell gespeichert - Private Keys verbleiben ausschließlich im Dateisystem - Strict Checking schützt vor Man-in-the-Middle > Password Auth ist aktuell deaktiviert – nutze SSH Key oder Agent. ## Console Usage Overview 1. Select host, Connect. 2. Choose EITHER a saved task, ad-hoc command, or batch. 3. Press Run / Run Batch. 4. Observe live output; stderr in red. 5. For batch: status badge shows `STEP X/Y`. 6. Cancel to terminate; status becomes Canceling then Canceled. 7. On completion badge shows exit code. 8. View history under Logs. ## Tasks Custom tasks only (registry removed). Fields: - name (unique key) - label (display) - command (shell executed exactly as typed) - description (optional) Example: ``` Name: restart_app Label: Restart App Service Command: sudo systemctl restart app.service ``` ## Batch Scripts One line per step. Rules: - Blank lines & lines starting with `#` ignored. - `cd path` lines set persistent working directory for subsequent steps. - On failure (non-zero exit) batch stops; failed step recorded. Example: ``` # Deploy cd /srv/app ./stop.sh ./deploy.sh ./start.sh ``` ## Structured Events (WebSocket) | Event | Payload Fields | Notes | |-------|----------------|-------| | connected | session | Initial handshake | | started | log_id, command | Run started | | chunk | stream, data | Output chunk (stdout/stderr) | | progress | current, total, step | Batch step progress | | canceling | | Cancel requested | | completed | status, exit_code | Terminal status | | error | type, message | type = ssh | runtime | Status -> final badge mapping: - ok -> green - failed -> red (shows failed step when batch) - error -> red (darker) - canceled -> yellow ## Logs Fields now include: - run_type (single|batch) - failed_step (nullable) - duration (derived) & timestamps - output_tail (last 32K) ## Manual Accessible at `/manual/` or nav link “Manual” – contains full user guide & examples (browser searchable). ## Tests ```bash pytest -q ``` Coverage includes model CRUD and WebSocket execution scenarios (success, fail, batch fail, cancel). ## Troubleshooting (Quick Table) | Symptom | Cause | Fix | |---------|-------|-----| | Host key not trusted | Missing known_hosts entry | ssh-keyscan >> known_hosts or disable (dev) | | Permission denied | Wrong user/key / permissions | Fix key path, permissions, agent add | | Invalid task | Name not found | Refresh tasks, re-create | | Batch stops early | Non-zero exit | Inspect failed step in log, tail output | | No output | Command buffers | Use unbuffered flags (e.g. python -u) | ## Security Notes - Keys referenced by path only; never stored or uploaded. - Use strict host key checking in production. - Single-process Channels (in-memory layer) – not for multi-worker production. ## Roadmap / Future Ideas - Redis channel layer for scaling - Parameterized tasks (templated inputs) - Role-based access control - Stream-to-disk full log archiving - ANSI color pass-through ## Development Aids Formatting / lint: ```bash ruff check . black . ``` Tailwind is loaded via CDN (see `remotectl/base.html`). ---