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
cddirectory 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
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):
# Development (auto-reload for HTTP only; WS fine for single process)
python manage.py runserver 127.0.0.1:8000
# Production-like single-process (Channels ASGI via daphne)
python -m daphne -b 127.0.0.1 -p 8000 project.config.asgi:application
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:
ls -l ~/.ssh/id_ed25519 ~/.ssh/id_ed25519.pub 2>/dev/null || echo 'kein ed25519 key'
Falls nicht vorhanden erstellen:
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -C "$(whoami)@$(hostname)" -N ''
Sichere Rechte setzen:
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.
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):
ssh deploy@server1 'echo OK'
3. Host Key erfassen (bei Strict Checking)
Nur nötig wenn STRICT_HOST_KEY_CHECKING=true (empfohlen Production).
ssh-keyscan -T 5 -t rsa,ecdsa,ed25519 server1 >> ~/.ssh/known_hosts
Bei Key-Änderung (Rotation / Reprovisioning) vorher entfernen:
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):
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 KeyoderAgent - 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:
- Host wählen
- Connect klicken (Status-Badge -> CONNECTED)
- Ad-hoc Command
uname -aeingeben → Run - 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
- Select host, Connect.
- Choose EITHER a saved task, ad-hoc command, or batch.
- Press Run / Run Batch.
- Observe live output; stderr in red.
- For batch: status badge shows
STEP X/Y. - Cancel to terminate; status becomes Canceling then Canceled.
- On completion badge shows exit code.
- 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 pathlines 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 |
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
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:
ruff check .
black .
Tailwind is loaded via CDN (see remotectl/base.html).