3,2.1..0
This commit is contained in:
parent
ec024f6d2d
commit
d270ba3ce8
|
|
@ -0,0 +1 @@
|
||||||
|
data
|
||||||
|
|
@ -0,0 +1,184 @@
|
||||||
|
# napi.qvd.tech — Retroalimentación a Estudiantes
|
||||||
|
|
||||||
|
## ✅ Estado: MVP FUNCIONAL (2026-02-22)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contexto
|
||||||
|
|
||||||
|
Proyecto minimalista para proporcionar retroalimentación personalizada a los estudiantes de DDAW2, cerrando el bucle profesor → alumno.
|
||||||
|
|
||||||
|
El profesor (Fénix) mantiene ficheros `notas.md` por alumno con:
|
||||||
|
- Tabla resumen de todas las prácticas (nota + estado)
|
||||||
|
- Feedback detallado por práctica
|
||||||
|
- Próximos pasos concretos
|
||||||
|
|
||||||
|
Los alumnos acceden desde cualquier dispositivo con su navegador, sin instalar nada.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Arquitectura Final
|
||||||
|
|
||||||
|
```
|
||||||
|
aldebaran (local) zzz (qu3v3d0.tech)
|
||||||
|
───────────────── ──────────────────
|
||||||
|
~/napi-data/ sshfs /var/www/napi/data/
|
||||||
|
├── anas/notas.md ←──────────→ ├── anas/notas.md
|
||||||
|
├── pablo/notas.md ├── pablo/notas.md
|
||||||
|
├── miguel/notas.md ├── miguel/notas.md
|
||||||
|
└── .../ (19 alumnos) └── .../ (19 alumnos)
|
||||||
|
|
||||||
|
/var/www/napi/
|
||||||
|
├── viewer.html (app completa)
|
||||||
|
└── marked.min.js (renderer local)
|
||||||
|
|
||||||
|
Nginx + libnginx-mod-http-auth-pam
|
||||||
|
→ auth con credenciales SFTP del alumno
|
||||||
|
→ $remote_user → sirve /var/www/napi/data/$remote_user/notas.md
|
||||||
|
→ viewer.html renderiza el .md en el navegador (marked.js local)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stack
|
||||||
|
|
||||||
|
| Componente | Tecnología | Dónde |
|
||||||
|
|:-----------|:-----------|:------|
|
||||||
|
| **Datos** | Ficheros `notas.md` (Markdown) | `zzz:/var/www/napi/data/` |
|
||||||
|
| **Transporte** | sshfs mount persistente | aldebaran → zzz |
|
||||||
|
| **Servidor web** | Nginx | zzz |
|
||||||
|
| **Autenticación** | `libnginx-mod-http-auth-pam` | zzz |
|
||||||
|
| **Renderer** | `marked.min.js` (local, sin CDN) + `viewer.html` | zzz |
|
||||||
|
| **Notificaciones** (watcher nginx) | Python + slixmpp → XMPP | zzz |
|
||||||
|
|
||||||
|
**Sin backend. Sin Python para servir. Sin JSON. Sin Syncthing.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## URL de Acceso
|
||||||
|
|
||||||
|
```
|
||||||
|
https://notas.qu3v3d0.tech
|
||||||
|
```
|
||||||
|
|
||||||
|
Una sola URL para todos los alumnos. Nginx usa `$remote_user` tras auth_pam para servir el `.md` correcto.
|
||||||
|
|
||||||
|
- Alumno ingresa sus credenciales SFTP (mismas que FileZilla)
|
||||||
|
- Ve únicamente sus propias notas
|
||||||
|
- Refresco del navegador = actualización inmediata
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ficheros Clave en zzz
|
||||||
|
|
||||||
|
| Ruta | Descripción |
|
||||||
|
|:-----|:------------|
|
||||||
|
| `/var/www/napi/data/$alumno/notas.md` | Fuente de datos por alumno |
|
||||||
|
| `/var/www/napi/viewer.html` | App completa (fetch + marked.js) |
|
||||||
|
| `/var/www/napi/marked.min.js` | Renderer Markdown local |
|
||||||
|
| `/etc/nginx/sites-enabled/napi` | Config Nginx del servicio |
|
||||||
|
|
||||||
|
`www-data` está en grupo `shadow` (necesario para auth_pam).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ficheros Clave en aldebaran
|
||||||
|
|
||||||
|
| Ruta | Descripción |
|
||||||
|
|:-----|:------------|
|
||||||
|
| `~/napi-data/` | Mount sshfs → `/var/www/napi/data/` en zzz |
|
||||||
|
| `~/.config/systemd/user/home-fenix-napi\x2ddata.mount` | Unit systemd persistente |
|
||||||
|
| `~/napi-data/_plantilla/notas.md` | Plantilla para alumnos nuevos |
|
||||||
|
| `~/napi-data/README.md` | Documentación del proyecto |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Workflow Emacs (edición)
|
||||||
|
|
||||||
|
```
|
||||||
|
1. C-x C-f ~/napi-data/pablo/notas.md
|
||||||
|
2. Editar → C-x C-s
|
||||||
|
3. Alumno refresca navegador → cambios visibles al instante
|
||||||
|
```
|
||||||
|
|
||||||
|
El sshfs hace que guardar en aldebaran sea equivalente a escribir directamente en zzz.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Formato notas.md
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Notas — NombreAlumno
|
||||||
|
|
||||||
|
> 🏫 Módulo: DDAW2
|
||||||
|
> 📅 Última actualización: FECHA
|
||||||
|
|
||||||
|
## 📊 Resumen
|
||||||
|
| Práctica | Título | Nota | Estado |
|
||||||
|
| P2.3 | Nginx via SFTP | 7/10 | ✅ |
|
||||||
|
...
|
||||||
|
|
||||||
|
## P2.7 — Multi-sitio Web con Nginx
|
||||||
|
**Nota: 9/10**
|
||||||
|
[feedback personalizado]
|
||||||
|
|
||||||
|
### Criterios
|
||||||
|
| Criterio | Puntos | Estado |
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Ver `_plantilla/notas.md` y cualquier alumno como referencia.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Alumnos Activos (19) — DDAW2
|
||||||
|
|
||||||
|
```
|
||||||
|
anas, carlos, carlosv, daniel, danieln, erick, evelin, gianfranco,
|
||||||
|
giorgio, joel, jorge, josue, juanan, juanjesus, kasandra, marius,
|
||||||
|
miguel, pablo, patrick
|
||||||
|
```
|
||||||
|
|
||||||
|
Credenciales SFTP en `~/EducaMadrid/DDAW2/PRACTICA2.4-despliegue-web-HTTP-y-HTTPS/README.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SSH / Conexión a zzz
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh fenix@qu3v3d0.tech # clave sin -i
|
||||||
|
```
|
||||||
|
|
||||||
|
`fenix` tiene sudo en zzz. Alumnos: grupo `sftpusers`, chroot `/home/$user`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Añadir Alumno Nuevo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. En zzz: crear usuario SFTP (ver README de P2.4 para el script completo)
|
||||||
|
sudo useradd -m -d /home/$USER -s /usr/sbin/nologin -G sftpusers,www-data $USER
|
||||||
|
|
||||||
|
# 2. En aldebaran: crear carpeta de notas
|
||||||
|
mkdir ~/napi-data/$USER
|
||||||
|
cp ~/napi-data/_plantilla/notas.md ~/napi-data/$USER/notas.md
|
||||||
|
# Editar con Emacs y personalizar
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prácticas Documentadas
|
||||||
|
|
||||||
|
| Práctica | Directorio local | Descripción |
|
||||||
|
|:---------|:-----------------|:------------|
|
||||||
|
| P2.3 | `~/EducaMadrid/DDAW2/PRACTICA2.3-despliegue-nginx-via-sftp/` | Nginx config via SFTP + watcher inotify + XMPP |
|
||||||
|
| P2.4 | `~/EducaMadrid/DDAW2/PRACTICA2.4-despliegue-web-HTTP-y-HTTPS/` | HTTP + HTTPS con cert wildcard autofirmado |
|
||||||
|
| P2.5 | `~/EducaMadrid/DDAW2/PRACTICA2.5-despliegue-web-bloquear-CDN-JS+servir-MD/` | CSP + bloqueo CDN + marked.js local |
|
||||||
|
| P2.6 | `~/EducaMadrid/DDAW2/PRACTICA2.6-Hextris-compresion-cache-y-mejora-de-rendimiento/` | Hextris + gzip + caché |
|
||||||
|
| P2.7 | `~/EducaMadrid/DDAW2/PRACTICA2.7-MULTI-SITIO-WEB-NGINX@zzz/` | Multi-sitio: Hextris + App propia + CSP + cabeceras |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pendiente
|
||||||
|
|
||||||
|
- [ ] Rellenar notas de pablo (P2.3 → P2.6 pendientes de detallar)
|
||||||
|
- [ ] Considerar CSS más elaborado para viewer.html (opcional)
|
||||||
|
- [ ] Renovar certificado SSL wildcard cuando expire (generado 2026-02-04, válido 365 días)
|
||||||
|
|
@ -0,0 +1,425 @@
|
||||||
|
# napi-data — Notas Personalizadas para Estudiantes
|
||||||
|
|
||||||
|
> Sistema minimalista para servir retroalimentación personalizada a cada alumno,
|
||||||
|
> directamente desde ficheros Markdown editados con Emacs.
|
||||||
|
|
||||||
|
**Estado:** ✅ Producción — `https://notas.qu3v3d0.tech` (2026-02-22)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ¿Qué hace esto?
|
||||||
|
|
||||||
|
Cada alumno entra en `https://notas.TU_DOMINIO` con sus credenciales SFTP y ve
|
||||||
|
**únicamente sus propias notas** en formato HTML renderizado. El profesor edita
|
||||||
|
un fichero `.md` desde Emacs y el alumno lo ve al refrescar el navegador.
|
||||||
|
|
||||||
|
**Sin backend. Sin base de datos. Sin framework. Sin CDN.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Arquitectura
|
||||||
|
|
||||||
|
```
|
||||||
|
PROFESOR (aldebaran / máquina local)
|
||||||
|
────────────────────────────────────
|
||||||
|
~/napi-data/ ← sshfs mount
|
||||||
|
├── _plantilla/notas.md
|
||||||
|
├── alumno01/notas.md ──┐
|
||||||
|
├── alumno02/notas.md ──┤ escribe con Emacs
|
||||||
|
└── alumnoN/notas.md ──┘ C-x C-s → visible al instante
|
||||||
|
|
||||||
|
│ sshfs (SSH port 22)
|
||||||
|
▼
|
||||||
|
|
||||||
|
SERVIDOR (zzz / Debian VPS)
|
||||||
|
────────────────────────────
|
||||||
|
/var/www/api/
|
||||||
|
├── viewer.html ← app completa (~40 líneas)
|
||||||
|
├── marked.min.js ← renderer Markdown local (sin CDN)
|
||||||
|
└── data/
|
||||||
|
├── alumno01/notas.md
|
||||||
|
├── alumno02/notas.md
|
||||||
|
└── alumnoN/notas.md
|
||||||
|
|
||||||
|
Nginx + libnginx-mod-http-auth-pam
|
||||||
|
↓ auth con credenciales SFTP del alumno
|
||||||
|
↓ $remote_user = "alumno01"
|
||||||
|
↓ sirve data/alumno01/notas.md
|
||||||
|
↓ viewer.html lo renderiza en el navegador
|
||||||
|
|
||||||
|
ALUMNO (cualquier dispositivo)
|
||||||
|
───────────────────────────────
|
||||||
|
https://notas.TU_DOMINIO
|
||||||
|
→ login con sus credenciales SFTP
|
||||||
|
→ ve sus notas en HTML
|
||||||
|
→ refresca → cambios inmediatos
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Gestión de registros DNS
|
||||||
|
|
||||||
|
- Se requiere un https://TU_DOMINIO ad-hoc
|
||||||
|
|
||||||
|
- Por simplicidad, necesitas apuntar TODOS los subodminios a la dirección IP del servidor (usa 'wildcard' - '*')
|
||||||
|
|
||||||
|
## Requisitos del servidor (Debian)
|
||||||
|
|
||||||
|
- Debian 11/12/...
|
||||||
|
- Nginx
|
||||||
|
- `libnginx-mod-http-auth-pam`
|
||||||
|
- `libpam-runtime` (incluido por defecto)
|
||||||
|
- Certificado SSL (Let's Encrypt o autofirmado)
|
||||||
|
- Usuarios SFTP ya configurados en el sistema (grupo `sftpusers`) usando 'chroot' , partiendo de que cada estudiante tiene su 'usuario' del sistema.
|
||||||
|
- SSH accesible desde la máquina del profesor (para sshfs)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Despliegue desde cero en un Debian nuevo
|
||||||
|
|
||||||
|
### 1. Instalar dependencias
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y nginx libnginx-mod-http-auth-pam
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Crear estructura de directorios en el servidor
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo mkdir -p /var/www/api/data
|
||||||
|
sudo chown -R www-data:www-data /var/www/api
|
||||||
|
sudo chmod 755 /var/www/api
|
||||||
|
sudo chmod 755 /var/www/api/data
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Desplegar viewer.html y marked.min.js
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copiar viewer.html al servidor (ver sección "Ficheros de la app" más abajo)
|
||||||
|
sudo cp viewer.html /var/www/api/viewer.html
|
||||||
|
sudo chown www-data:www-data /var/www/api/viewer.html
|
||||||
|
|
||||||
|
# Descargar marked.min.js (o copiar desde backup)
|
||||||
|
# https://cdn.jsdelivr.net/npm/marked/marked.min.js
|
||||||
|
sudo wget -O /var/www/api/marked.min.js \
|
||||||
|
https://cdn.jsdelivr.net/npm/marked/marked.min.js
|
||||||
|
sudo chown www-data:www-data /var/www/api/marked.min.js
|
||||||
|
```
|
||||||
|
|
||||||
|
> ⚠️ Una vez descargado `marked.min.js`, la app funciona **sin conexión a CDNs**
|
||||||
|
> — es el punto del diseño. No actualices el fichero sin probarlo primero.
|
||||||
|
|
||||||
|
### 4. Añadir www-data al grupo shadow (para auth_pam)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo usermod -aG shadow www-data
|
||||||
|
# Reiniciar nginx para que tome el cambio de grupo
|
||||||
|
sudo systemctl restart nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Configurar Nginx
|
||||||
|
|
||||||
|
Crear `/etc/nginx/sites-available/napi`:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name notas.TU_DOMINIO;
|
||||||
|
|
||||||
|
# --- SSL ---
|
||||||
|
ssl_certificate /etc/ssl/certs/TU_DOMINIO.crt;
|
||||||
|
ssl_certificate_key /etc/ssl/private/TU_DOMINIO.key;
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||||
|
|
||||||
|
root /var/www/api;
|
||||||
|
index viewer.html;
|
||||||
|
|
||||||
|
# --- Autenticación PAM ---
|
||||||
|
auth_pam "Notas DDAW2";
|
||||||
|
auth_pam_service_name "common-auth";
|
||||||
|
|
||||||
|
# --- Servir notas del alumno autenticado ---
|
||||||
|
location = /notas.md {
|
||||||
|
alias /var/www/api/data/$remote_user/notas.md;
|
||||||
|
default_type text/plain;
|
||||||
|
charset utf-8;
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Assets estáticos (viewer + marked) ---
|
||||||
|
location ~* \.(html|js)$ {
|
||||||
|
auth_pam off; # viewer.html y marked.min.js son públicos
|
||||||
|
expires 1h;
|
||||||
|
}
|
||||||
|
|
||||||
|
access_log /var/log/nginx/napi-access.log;
|
||||||
|
error_log /var/log/nginx/napi-error.log;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Redirección HTTP → HTTPS
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name notas.TU_DOMINIO;
|
||||||
|
return 301 https://$server_name$request_uri;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ln -s /etc/nginx/sites-available/napi /etc/nginx/sites-enabled/
|
||||||
|
sudo nginx -t && sudo systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Crear carpeta de datos para cada alumno
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Para un alumno:
|
||||||
|
ALUMNO="alumno01"
|
||||||
|
sudo mkdir -p /var/www/api/data/$ALUMNO
|
||||||
|
sudo cp /var/www/api/data/_plantilla/notas.md /var/www/api/data/$ALUMNO/notas.md
|
||||||
|
sudo chown -R www-data:www-data /var/www/api/data/$ALUMNO
|
||||||
|
|
||||||
|
# Para todos los alumnos de una vez (si ya existen como usuarios del sistema):
|
||||||
|
for user in $(getent group sftpusers | cut -d: -f4 | tr ',' ' '); do
|
||||||
|
sudo mkdir -p /var/www/api/data/$user
|
||||||
|
sudo cp /var/www/api/data/_plantilla/notas.md /var/www/api/data/$user/notas.md 2>/dev/null || true
|
||||||
|
sudo chown -R www-data:www-data /var/www/api/data/$user
|
||||||
|
echo "✅ $user"
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Verificar que funciona
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Probar autenticación y respuesta
|
||||||
|
curl -u alumno01:SU_CONTRASEÑA -sk https://notas.TU_DOMINIO/notas.md | head -5
|
||||||
|
|
||||||
|
# Debe devolver las primeras líneas del notas.md del alumno
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configurar sshfs en la máquina del profesor
|
||||||
|
|
||||||
|
### Instalar sshfs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Debian/Ubuntu
|
||||||
|
sudo apt install sshfs
|
||||||
|
|
||||||
|
# Fedora/RHEL
|
||||||
|
sudo dnf install fuse-sshfs
|
||||||
|
```
|
||||||
|
|
||||||
|
### Crear punto de montaje
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/napi-data
|
||||||
|
```
|
||||||
|
|
||||||
|
### Montar manualmente (prueba)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sshfs USUARIO@TU_SERVIDOR:/var/www/api/data ~/napi-data \
|
||||||
|
-o reconnect,ServerAliveInterval=15,ServerAliveCountMax=3
|
||||||
|
```
|
||||||
|
|
||||||
|
### Montaje persistente con systemd (recomendado)
|
||||||
|
|
||||||
|
Crear `~/.config/systemd/user/home-TUUSUARIO-napi\x2ddata.mount`
|
||||||
|
(sustituye `TUUSUARIO` por tu usuario real, ej: `fenix`):
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Unit]
|
||||||
|
Description=napi-data → servidor:/var/www/api/data (sshfs)
|
||||||
|
After=network-online.target
|
||||||
|
|
||||||
|
[Mount]
|
||||||
|
What=USUARIO@TU_SERVIDOR:/var/www/api/data
|
||||||
|
Where=/home/TUUSUARIO/napi-data
|
||||||
|
Type=fuse.sshfs
|
||||||
|
Options=reconnect,ServerAliveInterval=15,ServerAliveCountMax=3
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
systemctl --user enable --now 'home-TUUSUARIO-napi\x2ddata.mount'
|
||||||
|
|
||||||
|
# Verificar
|
||||||
|
systemctl --user status 'home-TUUSUARIO-napi\x2ddata.mount'
|
||||||
|
```
|
||||||
|
|
||||||
|
> 💡 El nombre de la unit debe coincidir exactamente con la ruta del `Where`
|
||||||
|
> (reemplazando `/` por `-` y los guiones por `\x2d`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Workflow del profesor
|
||||||
|
|
||||||
|
```
|
||||||
|
1. C-x C-f ~/napi-data/pablo/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.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Añadir un alumno nuevo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# En el servidor:
|
||||||
|
ALUMNO="nuevo_alumno"
|
||||||
|
sudo mkdir -p /var/www/api/data/$ALUMNO
|
||||||
|
sudo cp /var/www/api/data/_plantilla/notas.md /var/www/api/data/$ALUMNO/notas.md
|
||||||
|
sudo chown -R www-data:www-data /var/www/api/data/$ALUMNO
|
||||||
|
|
||||||
|
# En la máquina del profesor (aparece automáticamente via sshfs):
|
||||||
|
ls ~/napi-data/$ALUMNO/ # → notas.md
|
||||||
|
# Editar con Emacs y personalizar
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Formato del fichero notas.md
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Notas — NombreAlumno
|
||||||
|
|
||||||
|
> 🏫 **Módulo:** NOMBRE_MODULO
|
||||||
|
> 📅 **Última actualización:** YYYY-MM-DD
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Resumen
|
||||||
|
|
||||||
|
| Práctica | Título | Nota | Estado |
|
||||||
|
|:---------|:-------|:----:|:------:|
|
||||||
|
| P2.3 | Nginx via SFTP | 7/10 | ✅ |
|
||||||
|
| P2.4 | HTTP y HTTPS | 8/10 | ✅ |
|
||||||
|
| P2.7 | Multi-sitio | 9/10 | ✅ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## P2.7 — Multi-sitio Web con Nginx
|
||||||
|
|
||||||
|
**Nota: 9/10** · _Escaneado: 2026-02-19_
|
||||||
|
|
||||||
|
NombreAlumno, [feedback personalizado].
|
||||||
|
|
||||||
|
### Criterios
|
||||||
|
|
||||||
|
| Criterio | Puntos | Estado |
|
||||||
|
|:---------|:------:|:------:|
|
||||||
|
| Hextris desplegado + HTTPS | 1,5/1,5 | ✅ |
|
||||||
|
| Gzip | 1/1 | ✅ |
|
||||||
|
| CSP correcta | 1/1,5 | ⚠️ unsafe-inline |
|
||||||
|
|
||||||
|
### Próximos pasos
|
||||||
|
|
||||||
|
1. Corregir CSP: eliminar `unsafe-inline`
|
||||||
|
2. Subir capturas de verificación
|
||||||
|
```
|
||||||
|
|
||||||
|
Ver `_plantilla/notas.md` como punto de partida para nuevos alumnos.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ficheros de la app (en el servidor)
|
||||||
|
|
||||||
|
### `viewer.html`
|
||||||
|
|
||||||
|
App completa en ~40 líneas. Fetcha `/notas.md` (que Nginx resuelve al fichero
|
||||||
|
del alumno autenticado) y lo renderiza con `marked.min.js`:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Notas</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: sans-serif; max-width: 800px; margin: 2rem auto; padding: 0 1rem; }
|
||||||
|
table { border-collapse: collapse; width: 100%; }
|
||||||
|
th, td { border: 1px solid #ccc; padding: 0.5rem 1rem; text-align: left; }
|
||||||
|
th { background: #f4f4f4; }
|
||||||
|
code { background: #f0f0f0; padding: 0.1em 0.4em; border-radius: 3px; }
|
||||||
|
pre code { display: block; padding: 1rem; overflow-x: auto; }
|
||||||
|
blockquote { border-left: 4px solid #ccc; margin: 0; padding-left: 1rem; color: #555; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="content">Cargando...</div>
|
||||||
|
<script src="marked.min.js"></script>
|
||||||
|
<script>
|
||||||
|
fetch('/notas.md')
|
||||||
|
.then(r => r.ok ? r.text() : Promise.reject(r.status))
|
||||||
|
.then(md => { document.getElementById('content').innerHTML = marked.parse(md); })
|
||||||
|
.catch(e => { document.getElementById('content').textContent = 'Error: ' + e; });
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
### `marked.min.js`
|
||||||
|
|
||||||
|
Descargar de https://cdn.jsdelivr.net/npm/marked/marked.min.js y guardar como
|
||||||
|
fichero local en `/var/www/api/marked.min.js`. No referenciar CDN externo
|
||||||
|
(rompe la CSP y crea dependencia de terceros).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Gestión del mount sshfs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Estado
|
||||||
|
systemctl --user status 'home-TUUSUARIO-napi\x2ddata.mount'
|
||||||
|
|
||||||
|
# Montar
|
||||||
|
systemctl --user start 'home-TUUSUARIO-napi\x2ddata.mount'
|
||||||
|
|
||||||
|
# Desmontar
|
||||||
|
systemctl --user stop 'home-TUUSUARIO-napi\x2ddata.mount'
|
||||||
|
|
||||||
|
# Si se cuelga (mount zombie):
|
||||||
|
fusermount -uz ~/napi-data
|
||||||
|
systemctl --user start 'home-TUUSUARIO-napi\x2ddata.mount'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
| Síntoma | Causa probable | Solución |
|
||||||
|
|:--------|:---------------|:---------|
|
||||||
|
| `403 Forbidden` al acceder | www-data no está en grupo shadow | `sudo usermod -aG shadow www-data && sudo systemctl restart nginx` |
|
||||||
|
| `401 Unauthorized` con credenciales correctas | PAM no configurado | Verificar que `auth_pam_service_name "common-auth"` existe en `/etc/pam.d/` |
|
||||||
|
| `/notas.md` devuelve 404 | Carpeta del alumno no existe en `data/` | `sudo mkdir -p /var/www/api/data/$ALUMNO` |
|
||||||
|
| sshfs mount desaparece | Pérdida de conexión SSH | La opción `reconnect` lo recupera solo; si no, `systemctl --user restart` |
|
||||||
|
| El alumno ve las notas de otro | `$remote_user` vacío o PAM no activo | Verificar que `auth_pam` y `auth_pam_service_name` están en el bloque correcto |
|
||||||
|
| `marked is not defined` | `marked.min.js` no accesible | Verificar que el fichero existe en `/var/www/api/` y tiene permisos de lectura |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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` local — sin dependencia de CDNs externos
|
||||||
|
- Credenciales: las mismas que usa el alumno para subir archivos por SFTP
|
||||||
|
- SSL/TLS obligatorio (redirección 301 desde HTTP)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Historial
|
||||||
|
|
||||||
|
| Fecha | Hito |
|
||||||
|
|:------|:-----|
|
||||||
|
| 2026-02-22 | MVP desplegado: sshfs + Nginx auth_pam + viewer.html + marked.js |
|
||||||
|
| 2026-02-22 | notas.md generadas para los 19 alumnos de DDAW2 |
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,29 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Mis Notas</title>
|
||||||
|
<script src="/marked.min.js"></script>
|
||||||
|
<style>
|
||||||
|
body { max-width: 800px; margin: 2em auto; font-family: sans-serif; padding: 0 1.5em; line-height: 1.6; }
|
||||||
|
table { border-collapse: collapse; width: 100%; margin: 1em 0; }
|
||||||
|
th, td { border: 1px solid #ccc; padding: .4em .8em; text-align: left; }
|
||||||
|
th { background: #f5f5f5; font-weight: bold; }
|
||||||
|
code { background: #f4f4f4; padding: .1em .3em; border-radius: 3px; font-size: .9em; }
|
||||||
|
pre { background: #f4f4f4; padding: 1em; border-radius: 4px; overflow-x: auto; }
|
||||||
|
blockquote { border-left: 3px solid #ccc; margin: 0; padding-left: 1em; color: #555; }
|
||||||
|
h1 { border-bottom: 2px solid #eee; padding-bottom: .3em; }
|
||||||
|
h2 { border-bottom: 1px solid #eee; padding-bottom: .2em; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="out">Cargando...</div>
|
||||||
|
<script>
|
||||||
|
fetch('/notas.md')
|
||||||
|
.then(r => { if (!r.ok) throw new Error(r.status); return r.text(); })
|
||||||
|
.then(md => document.getElementById('out').innerHTML = marked.parse(md))
|
||||||
|
.catch(e => document.getElementById('out').textContent = 'Error cargando notas: ' + e);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Reference in New Issue