napi/FINDINGS.md

5.8 KiB

Key Findings: Emacs + napi Integration Research

Brainstorm session 2026-02-27 — distilled from TASKS.org last TODO


1. Three Candidate Packages Evaluated

Package Verdict Why
emacs-jabber (XMPP client) Skip Overkill for notifications. Full chat client overhead. Already have jabber.el configured for jla@librebits.snikket.chat but bot sends to jla@librebits.info (different JID). Would need account bridging.
json-rpc-server.el + Porthole Skip Over-engineered. Requires HTTP port exposure from zzz → local machine, firewall rules, session auth. Powerful but unnecessary when simpler options exist.
Built-in file-notify (TRAMP) Winner Real-time, zero dependencies, pure Emacs. Runs inotifywait on remote host via SSH. No new packages needed.

2. TRAMP + file-notify: The Key Discovery

The GNU Emacs manual states: "It is also possible to watch filesystems on remote machines"

How it works (confirmed via Emacs 30.1 source analysis)

  1. file-notify-add-watch on a TRAMP path (e.g., /ssh:fenix@zzz:/home/jara/python/)
  2. TRAMP's tramp-sh-handle-file-notify-add-watch kicks in
  3. It runs inotifywait -mq -e create,modify,delete <path> on the remote host via SSH
  4. Events stream back in real-time through the SSH tunnel
  5. tramp-sh-inotifywait-process-filter parses them into standard Emacs file-notify events
  6. Your callback fires with (event-type file-path) — just like local inotify

Backend priority on remote (SSH)

  1. inotifywait (preferred) — already installed on zzz
  2. gio monitor (fallback) — GLib-based, more portable
  3. Error if neither found

NOT polling

This is a persistent SSH process streaming events. Latency = network RTT only (~ms for zzz).


3. Existing Emacs Infrastructure (Already Installed)

Component Status Where
jabber.el Configured ~/.emacs.d/init.el line ~815 (jla@librebits.snikket.chat)
jsonrpc.el (v1.0.27) Installed (LSP/eglot) Built-in
file-notify Built-in Emacs 30.1 core
notifications (D-Bus) Built-in (require 'notifications)
Two Emacs daemons Running server + water (UI lives in water)
TRAMP Built-in SSH ControlMaster reuses connections

4. Current Notification Flow (What Already Works)

Student uploads via SFTP → zzz:/home/USER/python/
                               │
                               ▼
                    inotifywait (watcher script)
                               │
                               ▼
                    python-upload-watcher.sh (v7)
                    - Batches by folder (10s silence window)
                    - Maps username → full name
                    - Detects re-entregas (delete+recreate <120s)
                    - Filters noise (Thumbs.db, __pycache__, etc.)
                               │
                               ▼
                    xmpp-notify.py (slixmpp)
                    zzz@librebits.info → jla@librebits.info
                               │
                               ▼
                    Fénix's XMPP client (phone/desktop)

The Emacs integration adds a parallel path — doesn't replace XMPP.


5. Alternative Approach: emacsclient via SSH (Fallback Plan)

If TRAMP file-notify proves unstable with 40+ watches, the fallback is:

Reverse SSH tunnel (anka4 → zzz)

# On anka4 (professor's machine):
autossh -M 0 -N -R 2222:localhost:22 fenix@qu3v3d0.tech

Watcher modification on zzz

# Added to watcher scripts after existing notify_xmpp call:
notify_emacs() {
    ssh -p 2222 -o ConnectTimeout=3 -o BatchMode=yes fenix@localhost \
        emacsclient --socket-name=water --eval \
        "(napi-notify \"$group\" \"$user\" \"$files\" \"$(date +%H:%M)\")" \
        2>/dev/null &
}

Security hardening (optional)

Restrict the reverse-tunnel SSH key to emacsclient-only via authorized_keys:

command="/home/fenix/.local/bin/napi-emacs-gateway.sh",no-port-forwarding ssh-ed25519 AAAA...

This approach requires:

  • autossh on anka4
  • New SSH key pair (zzz → anka4)
  • systemd unit for the tunnel
  • Modifications to both watcher scripts on zzz

More moving parts than TRAMP, which is why TRAMP is the primary plan.


6. The emacs --daemon=napi Idea

Fénix asked: "Am I crazy saying that ~/napi could be running remotely, as long as a emacs --daemon=napi which I could hook into an emacsclient?"

Answer: Not crazy at all. A dedicated Emacs daemon for napi:

  • Isolates the 40 TRAMP watches from the main water daemon
  • Can crash/restart independently without affecting the UI
  • Light footprint (no packages loaded beyond TRAMP + napi.el)
  • Managed via systemd (emacs-napi.service)
  • Forwards notifications to water for display
  • Queryable: emacsclient --socket-name=napi --eval '(napi-status)'

7. Practical Limits

Concern Reality
40 inotifywait processes on zzz Kernel limit is ~65536 watches. 40 is trivial.
40 SSH channels TRAMP uses ControlMaster — 1 actual connection, multiplexed.
Memory on zzz Each inotifywait: ~1MB RSS. 40 = ~40MB. Fine.
file-notify on dirs (not recursive) Each student has 1 upload dir. No recursion needed.
SSH drops TRAMP sends stopped event. Elisp handler auto-restores watch.

8. Packages NOT Needed

Package Why Skip
Porthole HTTP RPC is overkill when TRAMP does it natively
emacs-jabber XMPP notifications already work outside Emacs
filenotify-recursive Student dirs are flat (no subdirs to recurse)
autossh Not needed if TRAMP approach works

9. Next Step

Implement PLAN.md — create napi.el, napi-init.el, systemd unit, and test.