docs: add multi-file notes and mount watchdog to README
- Document ?f= param for internal .md links in viewer.html - Add nginx location block for per-user .md files to deploy template - Document mount watchdog timer (auto-recovery every 12min) - Update nginx reference copies with new .md location block - Update repo structure and history Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ee0ab52218
commit
3fa2b7a5d7
78
README.md
78
README.md
|
|
@ -169,6 +169,59 @@ Monitoriza `/home/USER/python/` — detecta entregas de prácticas de Programaci
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Notas Multi-fichero (enlaces internos)
|
||||||
|
|
||||||
|
El viewer soporta distribuir el feedback en múltiples ficheros `.md` por alumno. Útil cuando `notas.md` crece demasiado con el feedback acumulado de muchas prácticas.
|
||||||
|
|
||||||
|
### Estructura
|
||||||
|
|
||||||
|
```
|
||||||
|
data/barrios/
|
||||||
|
├── notas.md ← TOC con tabla resumen y enlaces
|
||||||
|
├── nota-p3.1-sqlite.md ← feedback detallado P3.1
|
||||||
|
├── nota-p3.2-funciones.md ← feedback detallado P3.2
|
||||||
|
└── nota-p3.3-ficheros.md ← feedback detallado P3.3
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ejemplo de `notas.md` con enlaces
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Notas — Andrés Barrios
|
||||||
|
|
||||||
|
| **Práctica** | **Nota** | **Estado** |
|
||||||
|
|:-------------|:--------:|:----------:|
|
||||||
|
| P3.1 — [SQLite](nota-p3.1-sqlite.md) | 8 | Entregada |
|
||||||
|
| P3.2 — [Funciones](nota-p3.2-funciones.md) | 7 | Entregada |
|
||||||
|
| P3.3 — [Ficheros](nota-p3.3-ficheros.md) | — | Pendiente |
|
||||||
|
| **TOTAL** | **7.5** | |
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ejemplo de sub-nota (`nota-p3.1-sqlite.md`)
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Práctica 3.1 — SQLite
|
||||||
|
|
||||||
|
## Feedback
|
||||||
|
Buen uso de SELECT con WHERE compuestos. Normalización correcta hasta 3FN.
|
||||||
|
|
||||||
|
### A mejorar
|
||||||
|
- Revisar los JOINs entre tablas con FK compuestas
|
||||||
|
|
||||||
|
**Nota: 8/10**
|
||||||
|
|
||||||
|
[← Volver a notas](notas.md)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cómo funciona
|
||||||
|
|
||||||
|
1. `viewer.html` lee el parámetro `?f=` de la URL (por defecto `notas.md`)
|
||||||
|
2. Los enlaces internos a `.md` se reescriben automáticamente a `/?f=nombre.md`
|
||||||
|
3. Nginx sirve cualquier `.md` del directorio del alumno autenticado
|
||||||
|
4. El alumno navega entre ficheros sin salir del viewer — el botón atrás del navegador funciona
|
||||||
|
5. **Compatible hacia atrás** — si solo existe `notas.md`, todo funciona igual que antes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Requisitos del Servidor (Debian)
|
## Requisitos del Servidor (Debian)
|
||||||
|
|
||||||
- Debian 11/12/...
|
- Debian 11/12/...
|
||||||
|
|
@ -225,6 +278,14 @@ server {
|
||||||
add_header Cache-Control "no-cache";
|
add_header Cache-Control "no-cache";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Notas multi-fichero (enlaces internos desde notas.md)
|
||||||
|
location ~ ^/(.+\.md)$ {
|
||||||
|
alias /var/www/napiN/data/$remote_user/$1;
|
||||||
|
default_type text/plain;
|
||||||
|
charset utf-8;
|
||||||
|
add_header Cache-Control "no-cache";
|
||||||
|
}
|
||||||
|
|
||||||
location ~* \.(js|css)$ { expires 7d; }
|
location ~* \.(js|css)$ { expires 7d; }
|
||||||
location / { return 404; }
|
location / { return 404; }
|
||||||
}
|
}
|
||||||
|
|
@ -282,7 +343,9 @@ systemctl --user enable --now 'home-fenix-napi\x2ddataN.mount'
|
||||||
│ ├── python-upload-watcher.service ← unit systemd
|
│ ├── python-upload-watcher.service ← unit systemd
|
||||||
│ ├── nginx-user-config-watcher.service
|
│ ├── nginx-user-config-watcher.service
|
||||||
│ ├── home-fenix-napi-data.mount ← sshfs DDAW2
|
│ ├── home-fenix-napi-data.mount ← sshfs DDAW2
|
||||||
│ └── home-fenix-napi-data2.mount ← sshfs ASIR1
|
│ ├── home-fenix-napi-data2.mount ← sshfs ASIR1
|
||||||
|
│ ├── napi-mount-watchdog.service ← watchdog auto-recovery
|
||||||
|
│ └── napi-mount-watchdog.timer ← timer cada 12 min
|
||||||
└── Screenshots/
|
└── Screenshots/
|
||||||
└── zzz@librebits.info.png ← ejemplo notificaciones XMPP
|
└── zzz@librebits.info.png ← ejemplo notificaciones XMPP
|
||||||
```
|
```
|
||||||
|
|
@ -314,6 +377,15 @@ systemctl --user status home-fenix-napi\\x2ddata.mount # DDAW2
|
||||||
systemctl --user status home-fenix-napi\\x2ddata2.mount # ASIR1
|
systemctl --user status home-fenix-napi\\x2ddata2.mount # ASIR1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Watchdog de mounts (en aldebaran/anka4)
|
||||||
|
|
||||||
|
Si zzz se reinicia o la conexión SSH se corta, los mounts quedan en estado `failed` permanentemente. El watchdog los detecta y reinicia automáticamente cada 12 minutos.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl --user status napi-mount-watchdog.timer
|
||||||
|
systemctl --user list-timers napi-mount-watchdog.timer
|
||||||
|
```
|
||||||
|
|
||||||
### Logs
|
### Logs
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -331,7 +403,7 @@ sudo tail -f /var/log/nginx/napi-error.log # Nginx errors
|
||||||
| `401 Unauthorized` | Credenciales incorrectas o PAM mal configurado | Verificar `/etc/pam.d/common-auth` |
|
| `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/` |
|
| `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 |
|
| `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` |
|
| sshfs zombie | Conexión SSH caída | `fusermount -uz ~/napi-dataN && systemctl --user restart ...mount` (el watchdog lo hace automáticamente cada 12 min) |
|
||||||
| Emacs muestra datos stale tras editar | sshfs cachea lecturas del kernel | Añadir `auto_cache` a las Options del mount (ver abajo) |
|
| Emacs muestra datos stale tras editar | sshfs cachea lecturas del kernel | Añadir `auto_cache` a las Options del mount (ver abajo) |
|
||||||
| Watcher no detecta re-entregas | Versión antigua del watcher | Actualizar a v7+ y reiniciar servicio |
|
| Watcher no detecta re-entregas | Versión antigua del watcher | Actualizar a v7+ y reiniciar servicio |
|
||||||
| `__pycache__` en notificaciones | Watcher < v6 | Actualizar a v7+ |
|
| `__pycache__` en notificaciones | Watcher < v6 | Actualizar a v7+ |
|
||||||
|
|
@ -357,4 +429,6 @@ sudo tail -f /var/log/nginx/napi-error.log # Nginx errors
|
||||||
| 2026-02-25 | ASIR1 desplegado: asir1.qu3v3d0.tech para Programación (21 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 |
|
| 2026-02-25 | Watcher ASIR1 v7: batching, re-entregas, Windows-safe, __pycache__ filter, nombres completos |
|
||||||
| 2026-03-02 | sshfs mounts: añadido `auto_cache` para evitar datos stale en Emacs |
|
| 2026-03-02 | sshfs mounts: añadido `auto_cache` para evitar datos stale en Emacs |
|
||||||
|
| 2026-03-10 | Watchdog timer para auto-recovery de mounts sshfs caídos |
|
||||||
|
| 2026-03-10 | Soporte notas multi-fichero: viewer.html `?f=` + nginx `location ~ .md` |
|
||||||
| 2027-02-04 | Renovar certificado SSL wildcard (caduca ~365 días desde 2026-02-04) |
|
| 2027-02-04 | Renovar certificado SSL wildcard (caduca ~365 días desde 2026-02-04) |
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# napi — Notas API (notas.qu3v3d0.tech)
|
# napi — Notas API (notas.qu3v3d0.tech)
|
||||||
# Auth: PAM (mismas credenciales que SFTP)
|
# Auth: PAM (mismas credenciales que SFTP)
|
||||||
# Datos: /var/www/api/data/$remote_user/notas.md
|
# Datos: /var/www/napi/data/$remote_user/notas.md
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
|
|
@ -15,7 +15,7 @@ server {
|
||||||
ssl_certificate /etc/ssl/certs/qu3v3d0.tech.crt;
|
ssl_certificate /etc/ssl/certs/qu3v3d0.tech.crt;
|
||||||
ssl_certificate_key /etc/ssl/private/qu3v3d0.tech.key;
|
ssl_certificate_key /etc/ssl/private/qu3v3d0.tech.key;
|
||||||
|
|
||||||
root /var/www/api;
|
root /var/www/napi;
|
||||||
|
|
||||||
auth_pam "Notas DDAW2";
|
auth_pam "Notas DDAW2";
|
||||||
auth_pam_service_name "common-auth";
|
auth_pam_service_name "common-auth";
|
||||||
|
|
@ -25,7 +25,7 @@ server {
|
||||||
}
|
}
|
||||||
|
|
||||||
location = /notas.md {
|
location = /notas.md {
|
||||||
alias /var/www/api/data/$remote_user/notas.md;
|
alias /var/www/napi/data/$remote_user/notas.md;
|
||||||
default_type text/plain;
|
default_type text/plain;
|
||||||
charset utf-8;
|
charset utf-8;
|
||||||
add_header Cache-Control "no-cache";
|
add_header Cache-Control "no-cache";
|
||||||
|
|
@ -35,6 +35,13 @@ server {
|
||||||
expires 7d;
|
expires 7d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location ~ ^/(.+\.md)$ {
|
||||||
|
alias /var/www/napi/data/$remote_user/$1;
|
||||||
|
default_type text/plain;
|
||||||
|
charset utf-8;
|
||||||
|
add_header Cache-Control "no-cache";
|
||||||
|
}
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
return 404;
|
return 404;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,13 @@ server {
|
||||||
expires 7d;
|
expires 7d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location ~ ^/(.+\.md)$ {
|
||||||
|
alias /var/www/napi2/data/$remote_user/$1;
|
||||||
|
default_type text/plain;
|
||||||
|
charset utf-8;
|
||||||
|
add_header Cache-Control "no-cache";
|
||||||
|
}
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
return 404;
|
return 404;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
viewer.html
12
viewer.html
|
|
@ -20,9 +20,17 @@
|
||||||
<body>
|
<body>
|
||||||
<div id="out">Cargando...</div>
|
<div id="out">Cargando...</div>
|
||||||
<script>
|
<script>
|
||||||
fetch('/notas.md')
|
const file = new URLSearchParams(location.search).get('f') || 'notas.md';
|
||||||
|
fetch('/' + file)
|
||||||
.then(r => { if (!r.ok) throw new Error(r.status); return r.text(); })
|
.then(r => { if (!r.ok) throw new Error(r.status); return r.text(); })
|
||||||
.then(md => document.getElementById('out').innerHTML = marked.parse(md))
|
.then(md => {
|
||||||
|
document.getElementById('out').innerHTML = marked.parse(md);
|
||||||
|
// Rewrite internal .md links to render through viewer
|
||||||
|
document.querySelectorAll('#out a[href$=".md"]').forEach(a => {
|
||||||
|
const href = a.getAttribute('href');
|
||||||
|
if (!href.startsWith('http')) a.href = '/?f=' + href;
|
||||||
|
});
|
||||||
|
})
|
||||||
.catch(e => document.getElementById('out').textContent = 'Error cargando notas: ' + e);
|
.catch(e => document.getElementById('out').textContent = 'Error cargando notas: ' + e);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue