napi/README.md

359 lines
12 KiB
Markdown

# napi — Retroalimentación Personalizada a Estudiantes
> Sistema minimalista para servir feedback personalizado a cada alumno,
> directamente desde ficheros Markdown editados con Emacs.
**Estado:** ✅ Producción — 2 grupos activos (2026-02-25)
| Grupo | URL | Alumnos |
|:------|:----|:-------:|
| **DDAW2** — Despliegue Aplicaciones Web | `https://notas.qu3v3d0.tech` | 19 |
| **ASIR1** — Programación | `https://asir1.qu3v3d0.tech` | 21 |
**Sin backend. Sin base de datos. Sin framework. Sin CDN.**
---
## Arquitectura
```
PROFESOR (aldebaran / anka4)
────────────────────────────
~/napi-data/ ← sshfs mount (DDAW2)
├── _plantilla/notas.md
├── anas/notas.md
└── ... (19 alumnos)
~/napi-data2/ ← sshfs mount (ASIR1)
├── _plantilla/notas.md
├── barja/notas.md
└── ... (21 alumnos)
│ sshfs (SSH port 22)
SERVIDOR (zzz / qu3v3d0.tech)
─────────────────────────────
/var/www/napi/ ← DDAW2
├── viewer.html
├── marked.min.js
├── twemoji.min.js
└── data/$alumno/notas.md
/var/www/napi2/ ← ASIR1
├── viewer.html
├── marked.min.js
├── twemoji.min.js
└── data/$alumno/notas.md
Nginx + libnginx-mod-http-auth-pam
→ auth con credenciales SFTP del alumno
→ $remote_user → sirve data/$remote_user/notas.md
→ viewer.html renderiza el .md con marked.js + twemoji
ALUMNO (cualquier dispositivo)
──────────────────────────────
https://notas.qu3v3d0.tech → DDAW2
https://asir1.qu3v3d0.tech → ASIR1
→ login con credenciales SFTP
→ ve sus notas en HTML
→ refresca → cambios inmediatos
```
### Stack
| Componente | Tecnología | Dónde |
|:-----------|:-----------|:------|
| **Datos** | Ficheros `notas.md` (Markdown) | zzz |
| **Transporte** | sshfs mounts persistentes (systemd) | aldebaran/anka4 → zzz |
| **Servidor web** | Nginx (1 server block por grupo) | zzz |
| **Autenticación** | `libnginx-mod-http-auth-pam` | zzz |
| **Renderer** | `marked.min.js` + `twemoji.min.js` + `viewer.html` | zzz |
| **Notificaciones DDAW2** | `nginx-user-config-watcher.sh` + XMPP | zzz |
| **Notificaciones ASIR1** | `python-upload-watcher.sh` (v7) + XMPP | zzz |
| **SSL** | Certificado wildcard `*.qu3v3d0.tech` | zzz |
| **DNS** | Wildcard `*.qu3v3d0.tech``161.22.44.104` | DNS |
---
## Grupos y Alumnos
### DDAW2 — Despliegue de Aplicaciones Web (19 alumnos)
- **URL:** `https://notas.qu3v3d0.tech`
- **Datos:** `~/napi-data/``zzz:/var/www/napi/data/`
- **Usernames:** nombre de pila en minúsculas
- **SFTP chroot:** `/home/USER/html/`
```
anas, carlos, carlosv, daniel, danieln, erick, evelin, gianfranco,
giorgio, joel, jorge, josue, juanan, juanjesus, kasandra, marius,
miguel, pablo, patrick
```
### ASIR1 — Programación (21 alumnos)
- **URL:** `https://asir1.qu3v3d0.tech`
- **Datos:** `~/napi-data2/``zzz:/var/www/napi2/data/`
- **Usernames:** apellido en minúsculas (sin tildes)
- **Contraseñas:** leet-speak del apellido (`a→4, e→3, i→1, o→0`)
- **SFTP chroot:** `/home/USER/python/`
| Username | Alumno | Username | Alumno |
|:---------|:-------|:---------|:-------|
| **barja** | Alex Barja | **martinez** | Daniel Martínez |
| **barrios** | Andrés Barrios | **munoz** | Jordy Muñoz |
| **cayo** | Jared Cayo | **olcina** | Jorge Olcina |
| **contrera** | Luciano Contrera | **ponce** | Francisco Ponce |
| **duque** | Jorge Duque | **posada** | Santiago Posada |
| **florea** | Alejandro Florea | **quiroz** | Alexander Quiroz |
| **gomes** | Gabriel Gomes | **reynoso** | Jorge Reynoso |
| **izquierdo** | Daniel Izquierdo | **sierra** | Leonel Sierra |
| **jara** | María Jara | **torrero** | Mario Torrero |
| **lillo** | Fernando Lillo | | |
| **linares** | Christopher Linares | | |
| **macedo** | Eduardo Macedo | | |
---
## Notificaciones XMPP
Ambos grupos tienen watchers en zzz que notifican al profesor via XMPP (`jla@librebits.info`) cuando un alumno sube ficheros por SFTP.
### DDAW2: `nginx-user-config-watcher.sh` (v3)
Monitoriza `/home/USER/html/*.conf` — valida, despliega y analiza CSP de configuraciones Nginx.
### ASIR1: `python-upload-watcher.sh` (v7)
Monitoriza `/home/USER/python/` — detecta entregas de prácticas de Programación.
**Características:**
- **Batching por carpeta** — Si un alumno sube `PRACTICA3.1/` con N ficheros, envía **un solo mensaje** con el listado completo (espera 10s de silencio)
- **Re-entregas** — Detecta delete+recreate de una carpeta como "re-entrega"
- **Nombre completo** — Notifica `[María Jara]` en vez de `[jara]`
- **Windows-safe** — Ignora `Thumbs.db`, `desktop.ini`, `.DS_Store`, `*.tmp`
- **Python-safe** — Ignora `__pycache__/`, `*.pyc`, `*.pyo`
- **Dev-safe** — Ignora `.git/`, `.venv/`, `node_modules/`, `.idea/`, `.vscode/`
**Ejemplo de notificación:**
```
📁 [Andrés Barrios] Entrega ASIR1: PRACTICA3.1/ (4 ficheros)
🐍 main.py (2048 bytes)
🐍 utils.py (1024 bytes)
📝 README.md (512 bytes)
📄 requisitos.txt (128 bytes)
```
```
📁 [Andrés Barrios] ♻️ Re-entrega ASIR1: PRACTICA3.1/ (3 ficheros)
🐍 main.py (2100 bytes)
📝 README.md (600 bytes)
📄 requisitos.txt (128 bytes)
```
---
## Workflow del Profesor
```
1. C-x C-f ~/napi-data2/barrios/notas.md ← abrir en Emacs
2. Editar feedback, notas, próximos pasos
3. C-x C-s ← guardar
4. Alumno refresca el navegador ← cambios visibles
```
**No hay paso 5.** El sshfs hace que guardar localmente sea equivalente a escribir en zzz.
---
## Requisitos del Servidor (Debian)
- Debian 11/12/...
- Nginx + `libnginx-mod-http-auth-pam`
- Certificado SSL (wildcard recomendado para múltiples subdominios)
- Usuarios SFTP con chroot (`grupo sftpusers`)
- `www-data` en grupo `shadow` (necesario para auth_pam)
- Python 3 + `slixmpp` (para notificaciones XMPP)
- `inotify-tools` (para los watchers)
- SSH accesible desde la máquina del profesor (para sshfs)
---
## Despliegue Rápido de un Nuevo Grupo
### 1. En zzz: crear la app web
```bash
sudo mkdir -p /var/www/napiN/data
sudo cp /var/www/napi/viewer.html /var/www/napiN/
sudo cp /var/www/napi/marked.min.js /var/www/napiN/
sudo cp /var/www/napi/twemoji.min.js /var/www/napiN/
sudo chown fenix:www-data /var/www/napiN/data
sudo chmod 775 /var/www/napiN/data
```
### 2. En zzz: crear server block Nginx
```nginx
server {
listen 80;
server_name SUBDOMINIO.qu3v3d0.tech;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name SUBDOMINIO.qu3v3d0.tech;
ssl_certificate /etc/ssl/certs/qu3v3d0.tech.crt;
ssl_certificate_key /etc/ssl/private/qu3v3d0.tech.key;
root /var/www/napiN;
auth_pam "Notas GRUPO";
auth_pam_service_name "common-auth";
location = / { try_files /viewer.html =404; }
location = /notas.md {
alias /var/www/napiN/data/$remote_user/notas.md;
default_type text/plain;
charset utf-8;
add_header Cache-Control "no-cache";
}
location ~* \.(js|css)$ { expires 7d; }
location / { return 404; }
}
```
```bash
sudo ln -s /etc/nginx/sites-available/napiN /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
```
### 3. En zzz: crear usuarios SFTP
```bash
for user in alumno1 alumno2 alumnoN; do
sudo useradd -m -d /home/$user -s /usr/sbin/nologin -G sftpusers,www-data $user
echo "$user:CONTRASEÑA" | sudo chpasswd
sudo mkdir -p /home/$user/CARPETA # html/ o python/ según grupo
sudo chown root:root /home/$user
sudo chmod 755 /home/$user
sudo chown $user:www-data /home/$user/CARPETA
sudo chmod 775 /home/$user/CARPETA
done
```
### 4. Copiar datos de alumnos y montar sshfs
```bash
# Copiar carpetas con notas.md al servidor
scp -r ~/napi-dataN/* fenix@qu3v3d0.tech:/var/www/napiN/data/
# Crear unit systemd sshfs (~/.config/systemd/user/)
systemctl --user daemon-reload
systemctl --user enable --now 'home-fenix-napi\x2ddataN.mount'
```
---
## Estructura del Repositorio
```
~/napi/
├── README.md ← este fichero
├── CLAUDE.md ← instrucciones para Claude Code
├── viewer.html ← app web (fetch + marked.js + twemoji)
├── marked.min.js ← renderer Markdown local
├── nginx/
│ ├── README.md ← documentación configs Nginx
│ ├── napi-ddaw2.conf ← server block notas.qu3v3d0.tech
│ └── napi2-asir1.conf ← server block asir1.qu3v3d0.tech
├── scripts/
│ ├── README.md ← documentación watchers y servicios
│ ├── python-upload-watcher.sh ← watcher ASIR1 (v7)
│ ├── nginx-user-config-watcher.sh ← watcher DDAW2 (v3)
│ ├── xmpp-notify.py ← bot XMPP one-shot
│ ├── python-upload-watcher.service ← unit systemd
│ ├── nginx-user-config-watcher.service
│ ├── home-fenix-napi-data.mount ← sshfs DDAW2
│ └── home-fenix-napi-data2.mount ← sshfs ASIR1
└── Screenshots/
└── zzz@librebits.info.png ← ejemplo notificaciones XMPP
```
---
## Conexión a zzz
```bash
ssh fenix@qu3v3d0.tech # clave sin -i, fenix tiene sudo
```
---
## Gestión de Servicios
### Watchers (en zzz)
```bash
sudo systemctl status python-upload-watcher.service # ASIR1
sudo systemctl status nginx-user-config-watcher.service # DDAW2
sudo systemctl restart python-upload-watcher.service
```
### Mounts sshfs (en aldebaran/anka4)
```bash
systemctl --user status home-fenix-napi\\x2ddata.mount # DDAW2
systemctl --user status home-fenix-napi\\x2ddata2.mount # ASIR1
```
### Logs
```bash
sudo tail -f /var/log/python-upload-watcher.log # ASIR1 watcher
sudo tail -f /var/log/nginx/napi-error.log # Nginx errors
```
---
## Troubleshooting
| Problema | Causa | Solución |
|:---------|:------|:---------|
| `403 Forbidden` | www-data no en grupo shadow | `sudo usermod -aG shadow www-data && sudo systemctl restart nginx` |
| `401 Unauthorized` | Credenciales incorrectas o PAM mal configurado | Verificar `/etc/pam.d/common-auth` |
| `twemoji is not defined` | Falta `twemoji.min.js` en el root del site | `sudo cp /var/www/napi/twemoji.min.js /var/www/napiN/` |
| `404` en `/notas.md` | Carpeta del alumno no existe en `data/` | Crear carpeta + copiar plantilla |
| sshfs zombie | Conexión SSH caída | `fusermount -uz ~/napi-dataN && systemctl --user restart ...mount` |
| Watcher no detecta re-entregas | Versión antigua del watcher | Actualizar a v7+ y reiniciar servicio |
| `__pycache__` en notificaciones | Watcher < v6 | Actualizar a v7+ |
---
## Seguridad
- Cada alumno solo ve **sus propias notas** Nginx resuelve el path con `$remote_user`
- Sin ejecución de código en el servidor todo es estático
- `marked.min.js` y `twemoji.min.js` locales sin dependencia de CDNs externos
- Credenciales: las mismas que usa el alumno para SFTP (FileZilla)
- SSL/TLS obligatorio (redirección 301 desde HTTP)
- SFTP con chroot alumnos no pueden ver otros directorios
---
## Historial
| Fecha | Hito |
|:------|:-----|
| 2026-02-22 | MVP desplegado: notas.qu3v3d0.tech para DDAW2 (19 alumnos) |
| 2026-02-25 | ASIR1 desplegado: asir1.qu3v3d0.tech para Programación (21 alumnos) |
| 2026-02-25 | Watcher ASIR1 v7: batching, re-entregas, Windows-safe, __pycache__ filter, nombres completos |
| 2027-02-04 | Renovar certificado SSL wildcard (caduca ~365 días desde 2026-02-04) |