Aplicación que publica Notas via API. El uso de hacks Nginx y SSHFS hacen innecesario el uso de un backend de la aplicación como tal.
Go to file
fenix 40a4178888 esto en @FINDINGS
corregir el @PLAN.md para tener en cuenta

"filenotify-recursive-Student dirs are NOT flat (YES, there are subdirs to recurse)
2026-02-28 08:29:14 +00:00
Screenshots add ASIR1 (Programación) support: nginx, watchers, scripts 2026-02-25 20:32:43 +01:00
nginx add ASIR1 (Programación) support: nginx, watchers, scripts 2026-02-25 20:32:43 +01:00
scripts docs: rewrite all documentation for multi-group architecture 2026-02-25 20:36:38 +01:00
.gitignore update TASKS.org: all tasks from session marked DONE 2026-02-25 20:41:56 +01:00
CLAUDE.md docs: rewrite all documentation for multi-group architecture 2026-02-25 20:36:38 +01:00
FINDINGS.md emacs integration PLAN 2026-02-28 09:00:22 +01:00
LICENSE Initial commit 2026-02-22 20:59:52 +00:00
README.md docs: rewrite all documentation for multi-group architecture 2026-02-25 20:36:38 +01:00
TASKS.org esto en @FINDINGS 2026-02-28 08:29:14 +00:00
marked.min.js 3,2.1..0 2026-02-22 22:26:28 +01:00
viewer.html fix emojis rendering \!/ -> CSS conversion 2026-02-25 13:59:07 +01:00

README.md

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.tech161.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

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

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; }
}
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

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

# 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

ssh fenix@qu3v3d0.tech   # clave sin -i, fenix tiene sudo

Gestión de Servicios

Watchers (en zzz)

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)

systemctl --user status home-fenix-napi\\x2ddata.mount   # DDAW2
systemctl --user status home-fenix-napi\\x2ddata2.mount  # ASIR1

Logs

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)