feat: self-hosted frontend JS (offline) + docker hardening
- Vendored epub.js/pdf.js+worker/JSZip/DOMPurify in static/vendor/ - reader.html/reader.js servono i JS da /static (niente CDN, offline-ready) - CSP: rimosse allowlist CDN, aggiunto worker-src 'self' blob: - Test CSP aggiornati; tutti 16 verdi; verificato smoke test offline in container
This commit is contained in:
1 parent
e62c50c616
commit
0f15b7958f
31 files changed
+1363
-215
No files matched your search
@@ -1,155 +1,100 @@
|
||||
# 📚 Bookish
|
||||
# Bookish (fork Docker)
|
||||
|
||||
**Convertitore e reader di ebook self-hostato** — Flask, Pandoc, WeasyPrint, SQLite.
|
||||
Convertitore e reader di ebook self-hostato — Flask, Pandoc, WeasyPrint, SQLite.
|
||||
|
||||
Carica un ebook in qualsiasi formato, convertilo in un altro, o leggilo direttamente nel browser con un reader completo. I segnalibri e la posizione di lettura sono persistenti su database SQLite, identificati univocamente dal contenuto del file (SHA256).
|
||||
Questo repository è un fork di Bookish pensato per girare in Docker. L'originale resta in `bookish/`.
|
||||
|
||||
---
|
||||
|
||||
## Funzionalità
|
||||
|
||||
### 🔄 Convertitore
|
||||
| Formato | Input | Output |
|
||||
|---------|-------|--------|
|
||||
| EPUB | ✅ | ✅ |
|
||||
| PDF | ✅ | ✅ |
|
||||
| DOCX | ✅ | ✅ |
|
||||
| Markdown | ✅ | ✅ |
|
||||
| Testo (TXT) | ✅ | ✅ |
|
||||
| HTML | ✅ | ✅ |
|
||||
| FB2 | ✅ | ✅ |
|
||||
| MOBI | ✅ | ❌ |
|
||||
| LaTeX | ✅ | ✅ |
|
||||
|
||||
- Drag & drop upload
|
||||
- Barra progresso in tempo reale (SSE)
|
||||
- Download automatico del convertito
|
||||
|
||||
### 📖 Reader
|
||||
- **EPUB** via [epub.js](https://github.com/futurepress/epub.js)
|
||||
- **PDF** via [pdf.js](https://mozilla.github.io/pdf.js/)
|
||||
- **Testo/Markdown/DOCX/FB2/HTML** renderizzati come HTML
|
||||
|
||||
Caratteristiche reader:
|
||||
- Zoom e regolazione font
|
||||
- Temi: chiaro, scuro, seppia
|
||||
- Ricerca full-text con risultati navigabili
|
||||
- Indice navigabile (TOC) per EPUB e PDF
|
||||
- **Segnalibri** persistenti su SQLite con sync server + localStorage
|
||||
- **Salvataggio posizione** lettura (server + localStorage)
|
||||
- **Slider larghezza** area di lettura (40–100%)
|
||||
- **Aiuto contestuale** con scorciatoie da tastiera
|
||||
- Pulsante 🔖 con feedback visivo (toast + pulse)
|
||||
- Navigazione tastiera (← →) e schermo intero
|
||||
- Temi: chiaro, scuro, seppia
|
||||
|
||||
### ⚙️ Motore di conversione
|
||||
- **Pandoc** come motore primario (decine di formati)
|
||||
- **WeasyPrint** per PDF con layout avanzato
|
||||
- **python-docx** per DOCX con stili
|
||||
- **BeautifulSoup** per post-processing HTML/tabelle
|
||||
|
||||
### 🗄️ Archiviazione persistente
|
||||
- **SQLite** (`bookish.db`) per segnalibri e posizioni di lettura
|
||||
- **Content Hash** (SHA256) come identificatore univoco del libro
|
||||
- **localStorage** come fallback offline
|
||||
- Stessi segnalibri anche su PC diverso (stessa LAN)
|
||||
|
||||
---
|
||||
|
||||
## Requisiti
|
||||
|
||||
- Python 3.10+
|
||||
- Pandoc (incluso via pypandoc-binary)
|
||||
- Librerie di sistema per WeasyPrint (libpango, libcairo)
|
||||
|
||||
## Installazione
|
||||
## Avvio con Docker
|
||||
|
||||
```bash
|
||||
git clone http://192.168.0.183:8085/truetype/bookish.git
|
||||
cd bookish
|
||||
cp .env.example .env
|
||||
# modifica BOOKISH_SECRET in .env
|
||||
docker compose up --build -d
|
||||
```
|
||||
|
||||
Apri `http://localhost:5000`.
|
||||
|
||||
Dati persistenti (SQLite, upload, convertiti) stanno nel volume `bookish-data` montato su `/data`.
|
||||
|
||||
```bash
|
||||
docker compose logs -f
|
||||
docker compose down
|
||||
```
|
||||
|
||||
## Variabili d'ambiente
|
||||
|
||||
| Variabile | Default | Descrizione |
|
||||
|---|---|---|---|
|
||||
| `BOOKISH_SECRET` | (obbligatorio se `DEBUG=false`) | Secret Flask |
|
||||
| `BOOKISH_DEBUG` | `false` in Docker, `true` con `run.sh` | Debugger Werkzeug |
|
||||
| `BOOKISH_DATA_DIR` | directory del progetto / `/data` in Docker | Upload, convertiti, `bookish.db` |
|
||||
| `BOOKISH_CLEANUP_HOURS` | `1` | Ore prima di cancellare i file temporanei |
|
||||
| `PORT` | `5000` | Porta host (compose) / bind gunicorn |
|
||||
| `BOOKISH_AUTH_USERNAME` | (vuoto) | Se impostato insieme a `BOOKISH_AUTH_PASSWORD`, abilita l'autenticazione Basic su tutte le route (tranne healthz/static) |
|
||||
| `BOOKISH_AUTH_PASSWORD` | (vuoto) | Password dell'autenticazione Basic |
|
||||
| `BOOKISH_RATE_LIMIT_UPLOAD` | `20` | Massimo upload per IP/ora (`0` disabilita) |
|
||||
| `FORWARDED_ALLOW_IPS` | `127.0.0.1` | IP dei proxy fidati (Caddy/nginx) per rispettare `X-Forwarded-For` |
|
||||
|
||||
## Avvio locale (senza Docker)
|
||||
|
||||
Serve Python 3.12 e Pandoc di sistema.
|
||||
|
||||
```bash
|
||||
sudo apt install pandoc
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
.venv/bin/pip install -r requirements.lock # o requirements.txt in sviluppo
|
||||
./run.sh
|
||||
```
|
||||
|
||||
## Avvio
|
||||
In locale `BOOKISH_DEBUG` è `true` e il secret di default è accettato. Non esporre questa modalità in LAN.
|
||||
|
||||
## Dipendenze e lockfile
|
||||
|
||||
- `requirements.txt` — dipendenze dirette (per lo sviluppo).
|
||||
- `requirements.lock` — lock completo (dirette + transitive) generato con
|
||||
`pip-compile`, usato dalla build Docker per build riproducibili.
|
||||
|
||||
Per aggiornare il lock (in Python 3.12, come l'immagine):
|
||||
|
||||
```bash
|
||||
./run.sh
|
||||
# oppure su porta specifica:
|
||||
./run.sh 8080
|
||||
pip install pip-tools
|
||||
pip-compile --strip-extras --no-hash --output-file requirements.lock requirements.txt
|
||||
```
|
||||
|
||||
L'app cerca automaticamente una porta libera a partire da quella specificata. Il database SQLite (`bookish.db`) viene creato automaticamente al primo avvio.
|
||||
## Note sul container
|
||||
|
||||
Apri il browser su `http://localhost:5000` (o la porta assegnata).
|
||||
- Gunicorn, 1 worker + thread: i task SSE vivono in memoria del processo.
|
||||
- Utente non-root `bookish` (uid 1000).
|
||||
- Conversione PDF può richiedere fino a 5 minuti (`timeout` gunicorn 300s).
|
||||
- Nessuna autenticazione di default: adatto a LAN fidata dietro reverse proxy.
|
||||
|
||||
---
|
||||
### Sicurezza opzionale
|
||||
|
||||
## Architettura
|
||||
Per esporre Bookish anche solo in LAN, abilita l'autenticazione Basic e
|
||||
limita gli upload:
|
||||
|
||||
```
|
||||
bookish/
|
||||
├── app.py # Entry point
|
||||
├── config.py # Configurazioni
|
||||
├── requirements.txt
|
||||
├── run.sh
|
||||
├── bookish.db # SQLite (creato al primo avvio)
|
||||
├── bookish/
|
||||
│ ├── __init__.py # Flask factory + init DB
|
||||
│ ├── routes/
|
||||
│ │ ├── convert.py # Upload, SSE progress, download
|
||||
│ │ ├── reader.py # Reader view + content_hash
|
||||
│ │ └── api.py # API REST bookmarks + positions
|
||||
│ ├── services/
|
||||
│ │ ├── converter.py # Conversione Pandoc + pipeline
|
||||
│ │ ├── sse_manager.py # SSE progress manager
|
||||
│ │ ├── formatter.py # PDF/HTML post-processing
|
||||
│ │ └── database.py # SQLite manager
|
||||
│ ├── templates/
|
||||
│ │ ├── base.html # Layout base
|
||||
│ │ ├── index.html # Convertitore
|
||||
│ │ └── reader.html # Reader
|
||||
│ └── static/
|
||||
│ ├── css/style.css
|
||||
│ ├── js/main.js # Frontend convertitore
|
||||
│ ├── js/reader.js # Frontend reader (oltre 800 righe)
|
||||
│ └── logo.svg
|
||||
├── uploads/ # File caricati (temporanei)
|
||||
├── converted/ # File convertiti (temporanei)
|
||||
└── docs/ # Documentazione
|
||||
```bash
|
||||
# .env
|
||||
BOOKISH_AUTH_USERNAME=admin
|
||||
BOOKISH_AUTH_PASSWORD=una-password-lunga
|
||||
BOOKISH_RATE_LIMIT_UPLOAD=20 # upload/ora per IP
|
||||
```
|
||||
|
||||
Vedi [docs/architecture.md](docs/architecture.md) per dettagli.
|
||||
- L'**autenticazione Basic** è la più semplice ma **non** cifra la password:
|
||||
usala solo su HTTP **dietro un reverse proxy HTTPS** (Caddy/nginx).
|
||||
- Il **rate limit** è in-memory (per processo, single-worker): adatto a uso
|
||||
personale; per difese più robuste metti un limite a livello di proxy.
|
||||
- Vengono applicati header di sicurezza su ogni risposta: `Content-Security-Policy`
|
||||
(`default-src 'self'`, nessuna allowlist CDN), `X-Content-Type-Options: nosniff`,
|
||||
`X-Frame-Options: SAMEORIGIN`, `Referrer-Policy: no-referrer`.
|
||||
- Tutti i JavaScript del reader (epub.js, pdf.js+worker, JSZip, DOMPurify) sono
|
||||
**self-hosted** in `static/vendor/` → funziona **anche offline**, senza CDN.
|
||||
- I contenuti HTML del reader vengono **sanitizzati lato client**
|
||||
(DOMPurify self-hosted in `static/vendor/`).
|
||||
|
||||
---
|
||||
## API extra
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Metodo | Path | Descrizione |
|
||||
|--------|------|-------------|
|
||||
| POST | `/upload` | Carica file e avvia conversione |
|
||||
| GET | `/progress/<task_id>` | SSE stream progresso |
|
||||
| GET | `/download/<filename>` | Scarica file convertito |
|
||||
| GET | `/formats?input=<ext>` | Formati disponibili per input |
|
||||
| GET | `/reader` | Pagina reader |
|
||||
| GET | `/reader/<filename>` | Reader per file specifico |
|
||||
| GET | `/reader/serve/<filename>` | Serve il file al reader |
|
||||
| GET | `/reader/formats` | Formati supportati dal reader |
|
||||
| GET | `/api/bookmarks/<hash>` | Segnalibri di un libro |
|
||||
| POST | `/api/bookmarks` | Aggiunge segnalibro |
|
||||
| DELETE | `/api/bookmarks/<id>` | Elimina segnalibro |
|
||||
| GET | `/api/position/<hash>` | Posizione di lettura |
|
||||
| PUT | `/api/position/<hash>` | Salva posizione di lettura |
|
||||
|
||||
---
|
||||
|
||||
## Deployment
|
||||
|
||||
Adatto per Proxmox LXC, Docker o VM leggera. Vedi [docs/deploy.md](docs/deploy.md).
|
||||
|
||||
---
|
||||
`GET /healthz` — healthcheck Docker (`{"status":"ok"}`).
|
||||
|
||||
## Licenza
|
||||
|
||||
|
||||
Reference in new issue
Block a user