260 lines
8.0 KiB
Markdown
260 lines
8.0 KiB
Markdown
# 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`).
|
||
|
||
---
|
||
|