149 lines
5.6 KiB
Markdown
149 lines
5.6 KiB
Markdown
# Plan: Emacs Integration for napi — Remote File-Notify via TRAMP
|
|
|
|
## Context
|
|
|
|
Fénix wants **real-time awareness in Emacs** of student deliveries on zzz (qu3v3d0.tech). The GNU Emacs manual confirms that `file-notify-add-watch` works on **remote machines** via TRAMP — it runs `inotifywait` on the remote host through SSH and streams events back in real-time. No polling.
|
|
|
|
This means: **no modifications to watcher scripts on zzz**, no reverse SSH tunnels, no Porthole/JSON-RPC. Pure Emacs, using built-in capabilities + a dedicated daemon.
|
|
|
|
## Architecture
|
|
|
|
```
|
|
emacs --daemon=napi (local)
|
|
│
|
|
├── TRAMP SSH → zzz:/home/*/python/ (ASIR1 — 21 dirs)
|
|
│ └── inotifywait running on zzz (auto-started by TRAMP)
|
|
│
|
|
├── TRAMP SSH → zzz:/home/*/html/ (DDAW2 — 19 dirs)
|
|
│ └── inotifywait running on zzz (auto-started by TRAMP)
|
|
│
|
|
└── On file event → forward notification to `water` daemon
|
|
├── Desktop notification (D-Bus)
|
|
├── Sound alert (paplay)
|
|
├── *napi-log* buffer (persistent log)
|
|
└── Minibuffer message
|
|
|
|
emacsclient --socket-name=napi --eval '(napi-dashboard)' ← interact
|
|
emacsclient --socket-name=water --eval '(napi-log)' ← view in main UI
|
|
```
|
|
|
|
## Prerequisites
|
|
|
|
- `inotify-tools` installed on zzz (provides `inotifywait`) — **already present** (watchers use it)
|
|
- SSH key access from local → zzz — **already working** (`ssh fenix@qu3v3d0.tech`)
|
|
- Emacs 30.1 on local — **confirmed**
|
|
|
|
## Implementation Steps
|
|
|
|
### Step 1: Create `~/napi/emacs/napi.el`
|
|
|
|
The core elisp package. ~200 lines. Key components:
|
|
|
|
| Component | Purpose |
|
|
|:----------|:--------|
|
|
| `napi-watch-start` | Sets up TRAMP file-notify watches on all student dirs |
|
|
| `napi-watch-stop` | Removes all watches cleanly |
|
|
| `napi-notify` | Handles a file event: log + desktop notification + sound |
|
|
| `napi-log` | Interactive: open `*napi-log*` buffer |
|
|
| `napi-open-student` | Interactive: completing-read → open student dir/notas.md |
|
|
| `napi-dashboard` | Interactive: overview of all students + last delivery |
|
|
| `C-c n` prefix | Keybindings for all interactive commands |
|
|
|
|
**Student name maps** (username → full name) embedded in elisp, mirroring watcher scripts.
|
|
|
|
**TRAMP watch setup** — the core innovation:
|
|
```elisp
|
|
;; For each ASIR1 student:
|
|
(file-notify-add-watch
|
|
"/ssh:fenix@qu3v3d0.tech:/home/jara/python/"
|
|
'(change)
|
|
#'napi--handle-event)
|
|
|
|
;; 40 watches total (21 ASIR1 + 19 DDAW2)
|
|
;; Each spawns a persistent inotifywait on zzz via SSH
|
|
```
|
|
|
|
**Event handler** filters noise (Thumbs.db, __pycache__, .tmp, etc.) and forwards clean notifications to `water` daemon via:
|
|
```elisp
|
|
(shell-command
|
|
"emacsclient --socket-name=water --eval '(napi--show-notification ...)' &")
|
|
```
|
|
|
|
**Reconnection**: TRAMP sends a `stopped` event when SSH drops. The handler auto-re-establishes the watch after a delay.
|
|
|
|
### Step 2: Create systemd user unit for `napi` daemon
|
|
|
|
File: `~/.config/systemd/user/emacs-napi.service`
|
|
|
|
```ini
|
|
[Unit]
|
|
Description=Emacs daemon for napi student monitoring
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=forking
|
|
ExecStart=/usr/bin/emacs --daemon=napi -l ~/napi/emacs/napi-init.el
|
|
ExecStop=/usr/bin/emacsclient --socket-name=napi --eval "(kill-emacs)"
|
|
Restart=on-failure
|
|
RestartSec=30
|
|
|
|
[Install]
|
|
WantedBy=default.target
|
|
```
|
|
|
|
### Step 3: Create `~/napi/emacs/napi-init.el`
|
|
|
|
Minimal init file for the `napi` daemon (not the full `~/.emacs.d/init.el`):
|
|
|
|
```elisp
|
|
;; Loaded ONLY by emacs --daemon=napi
|
|
(load "~/napi/emacs/napi.el")
|
|
(napi-watch-start)
|
|
(message "napi: watching %d student directories on zzz" napi--watch-count)
|
|
```
|
|
|
|
### Step 4: Also load `napi.el` in `water` daemon
|
|
|
|
Add to `~/.emacs.d/init.el` (near line 1478, following `funciones-art.el` pattern):
|
|
|
|
```elisp
|
|
(load "~/napi/emacs/napi.el")
|
|
```
|
|
|
|
This gives the `water` daemon the `napi-log`, `napi-dashboard`, `napi-open-student` commands and the `C-c n` keybindings — but **no watches** (those run in the `napi` daemon only).
|
|
|
|
### Step 5: Verify `inotifywait` on zzz
|
|
|
|
```bash
|
|
ssh fenix@qu3v3d0.tech "which inotifywait && inotifywait --help | head -1"
|
|
```
|
|
|
|
## Files to Create/Modify
|
|
|
|
| File | Action | Location |
|
|
|:-----|:-------|:---------|
|
|
| `emacs/napi.el` | **CREATE** | `~/napi/emacs/napi.el` |
|
|
| `emacs/napi-init.el` | **CREATE** | `~/napi/emacs/napi-init.el` |
|
|
| `emacs-napi.service` | **CREATE** | `~/.config/systemd/user/emacs-napi.service` |
|
|
| `init.el` | **MODIFY** (1 line) | `~/.emacs.d/init.el` |
|
|
|
|
**No modifications to zzz** — no watcher changes, no new SSH keys, no reverse tunnels.
|
|
|
|
## Testing
|
|
|
|
1. **Unit test** — load `napi.el` in `water`, call `(napi-notify ...)` manually
|
|
2. **TRAMP watch test** — single watch on one student dir, touch a file on zzz
|
|
3. **Full integration** — start `napi` daemon, simulate SFTP upload as student
|
|
4. **Stress test** — confirm 40 concurrent TRAMP watches are stable
|
|
5. **Reconnection** — kill SSH, verify watch auto-restores
|
|
6. **UI test** — `C-c n l` (log), `C-c n s` (student), `C-c n d` (dashboard)
|
|
|
|
## Risk: 40 Concurrent SSH/inotifywait Processes
|
|
|
|
Each `file-notify-add-watch` via TRAMP spawns a separate `inotifywait` process on zzz. 40 watches = 40 persistent SSH channels + 40 inotifywait processes.
|
|
|
|
**Mitigation**: TRAMP reuses SSH connections (ControlMaster). So it's 1 SSH connection + 40 remote processes. The zzz server's `inotify` limit (`fs.inotify.max_user_watches`) is typically 65536 — 40 is nothing.
|
|
|
|
**Fallback**: If 40 watches proves unstable, consolidate to 2 watches on parent dirs (`/home/` with recursive) + filter events by path. Or switch to the emacsclient-via-SSH approach (see FINDINGS.md).
|