- JavaScript 66.3%
- HTML 31.9%
- Dockerfile 1.8%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .forgejo/workflows | ||
| src | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| docker-compose.yml | ||
| Dockerfile | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
Veezie Lista Merge
Applicazione web full-stack containerizzata per l'aggregazione, il filtraggio e l'esposizione di liste di URL in formato raw (compatibili Veezie).
- UI amministrativa (singola schermata) per inserire, modificare ed eliminare le sorgenti (es. link Pastebin raw).
- Sincronizzazione automatica oraria (cron) che scarica tutte le sorgenti, normalizza gli URL, elimina i doppioni e sovrascrive completamente i dati precedenti nel database.
- Verifica degli URL: dopo ogni sincronizzazione ogni URL viene testato (HEAD con fallback GET); quelli non funzionanti (HTTP ≥ 400, DNS, timeout, connessione rifiutata) vengono rimossi dalla lista pubblica.
- Domini mirror: URL con stesso nome a dominio ma TLD diverso (es.
pippo.it/pippo.com) e stesso path vengono raggruppati; della famiglia viene tenuto solo il primo funzionante in ordine di lista, gli altri scartati silenziosamente. - Endpoint pubblico senza autenticazione che serve la lista consolidata in formato testuale raw (una riga per URL), pronta per Veezie.
- Accesso amministrativo protetto da SSO OAuth2/OpenID Connect (Authentik), con tutti i parametri nel file
.env.
Architettura
Sorgenti (Pastebin raw, ...) Authentik (SSO)
│ fetch oraria (cron) │ OIDC/OAuth2
▼ ▼
┌─────────────────────────────────────────────────────┐
│ Node.js + Express │
│ ├── /admin UI di gestione (protetta SSO) │
│ ├── /api/admin/* API CRUD sorgenti (protette) │
│ ├── /auth/* login/logout OIDC │
│ ├── /list lista raw pubblica (Veezie) │
│ └── /healthz health check │
│ SQLite (better-sqlite3, WAL) su volume /data │
└─────────────────────────────────────────────────────┘
Un unico servizio containerizzato: il worker di sincronizzazione è uno scheduler interno (node-cron) che gira nello stesso processo. Tutta la configurazione è nel file .env.
Struttura del progetto
├── Dockerfile # immagine multi-stage ottimizzata (node:22-slim)
├── docker-compose.yml # servizio app + volume dati
├── .env.example # template di configurazione
├── .forgejo/workflows/build.yml# CI Forgejo Actions (build immagine Docker)
└── src/
├── index.js # server Express
├── config.js # caricamento/validazione configurazione da .env
├── db.js # SQLite (better-sqlite3, WAL) e schema
├── auth/
│ ├── oidc.js # client OIDC (discovery, PKCE)
│ ├── session.js # cookie di sessione firmati HMAC
│ └── middleware.js # requireAuth per le API admin
├── routes/
│ ├── auth.js # /auth/login, /auth/callback, /auth/logout
│ ├── admin.js # CRUD sorgenti, refresh, status
│ └── public.js # /list, /healthz
├── services/
│ ├── fetcher.js # fetch sorgenti, normalizzazione, dedup, overwrite
│ └── scheduler.js # cron orario + sync all'avvio
└── views/admin.html # UI amministrativa (vanilla JS)
Deploy con Docker Compose
Prerequisiti: Docker e Docker Compose.
git clone <URL-del-repo-forgejo> veezielistamerge
cd veezielistamerge
cp .env.example .env
# genera un secret di sessione
openssl rand -hex 32 # copialo in SESSION_SECRET
Modifica .env (almeno BASE_URL e la sezione Authentik, vedi sotto), poi:
docker compose up -d --build
Verifica:
curl http://localhost:3000/healthz
curl http://localhost:3000/list
Apri http://localhost:3000/admin, accedi via SSO e aggiungi la prima sorgente (es. https://pastebin.com/raw/qjvecna1). La sincronizzazione parte subito dopo l'avvio e poi ogni ora; il pulsante Aggiorna ora forza un fetch immediato.
Aggiornamento a una nuova versione:
git pull
docker compose up -d --build
Configurazione SSO con Authentik
- Nell'interfaccia di amministrazione Authentik: Applications → Providers → Create → OAuth2/OpenID Connect Provider.
- Configura il provider:
- Name:
veezielistamerge - Authorization flow: default-provider-authorization-implicit-consent (o explicit se preferisci)
- Client type: Confidential
- Redirect URI/Origins:
https://<tuo-dominio>/auth/callback(oppurehttp://localhost:3000/auth/callbackin locale) - Scopes:
openid,profile,email(inclusi di default in Authentik)
- Name:
- Salva e prendi note di Client ID e Client Secret.
- Applications → Create: nome
veezielistamerge, slugveezielistamerge, seleziona il provider. Lo slug determina l'issuer. Applica eventuali policy di accesso (es. solo gruppo admin). - Compila
.env:
BASE_URL=https://list.example.com
TRUST_PROXY=1
SESSION_SECRET=<openssl rand -hex 32>
OIDC_ISSUER_URL=https://authentik.example.com/application/o/veezielistamerge/ # slash finale!
OIDC_CLIENT_ID=<client id>
OIDC_CLIENT_SECRET=<client secret>
# facoltativo: limitare a utenti specifici (email o sub, separati da virgola)
AUTH_ALLOWED_SUBJECTS=
docker compose up -d(o restart) e aprihttps://list.example.com/admin.
Note:
- L'issuer deve corrispondere esattamente all'URL dell'applicazione Authentik con slash finale.
- Se l'app è dietro reverse proxy con TLS, imposta
BASE_URL=https://...eTRUST_PROXY=1(i cookie di sessione diventanoSecureautomaticamente). - La disconnessione è locale (cookie). Il logout completo della sessione Authentik va fatto dall'interfaccia Authentik.
Variabili di configurazione
| Variabile | Default | Descrizione |
|---|---|---|
PORT |
3000 |
Porta HTTP interna |
BASE_URL |
http://localhost:3000 |
URL pubblico (redirect OIDC, cookie sicuri) |
TRUST_PROXY |
0 |
1 se dietro reverse proxy |
DATABASE_PATH |
/data/veezie.db |
Percorso file SQLite |
CRON_SCHEDULE |
0 * * * * |
Cron della sincronizzazione |
CRON_TIMEZONE |
UTC |
Timezone dello scheduler |
FETCH_TIMEOUT_MS |
15000 |
Timeout per sorgente |
FETCH_CONCURRENCY |
4 |
Fetch paralleli |
FETCH_MAX_BYTES |
10485760 |
Dimensione massima risposta (10 MB) |
CHECK_ENABLED |
true |
Verifica gli URL dopo ogni sincronizzazione |
CHECK_REMOVE_BROKEN |
true |
Rimuove dalla lista pubblica gli URL con codice in CHECK_REMOVE_CODES |
CHECK_REMOVE_CODES |
404,410 |
Codici HTTP che causano la rimozione dell'URL |
CHECK_TIMEOUT_MS |
10000 |
Timeout per verifica di ogni URL |
CHECK_CONCURRENCY |
10 |
Verifiche URL in parallelo |
MIRROR_DEDUP |
true |
Un solo URL per famiglia di domini mirror (primo funzionante) |
PUBLIC_LIST_PATH |
list |
Path della lista raw pubblica |
SESSION_SECRET |
— | Secret HMAC dei cookie (richiesto, min 16 caratteri) |
SESSION_TTL_HOURS |
8 |
Durata sessione admin |
OIDC_ISSUER_URL |
— | Issuer Authentik (con slash finale) |
OIDC_CLIENT_ID / OIDC_CLIENT_SECRET |
— | Credenziali del provider OIDC |
OIDC_SCOPES |
openid profile email |
Scopes richiesti |
OIDC_REDIRECT_URI |
{BASE_URL}/auth/callback |
Override del redirect URI |
AUTH_ALLOWED_SUBJECTS |
vuoto | Email/sub autorizzati (vuoto = policy gestita in Authentik) |
Senza le variabili OIDC_* l'app parte comunque servendo /list e /healthz, ma la UI amministrativa resta disabilitata (HTTP 503).
Endpoint
| Metodo | Path | Autenticazione | Descrizione |
|---|---|---|---|
| GET | /list |
Nessuna | Lista consolidata raw, un URL per riga (per Veezie) |
| GET | /healthz |
Nessuna | Stato e contatori |
| GET | /admin |
SSO | UI di gestione |
| GET / POST | /api/admin/sources |
SSO | Elenco / creazione sorgenti |
| PUT / DELETE | /api/admin/sources/:id |
SSO | Modifica / eliminazione sorgente |
| POST | /api/admin/refresh |
SSO | Sincronizzazione immediata |
| POST | /api/admin/check |
SSO | Verifica URL immediata (in background) |
| GET | /api/admin/status |
SSO | Stato ultima sincronizzazione, errori, statistiche |
| GET | /auth/login, /auth/callback |
— | Flusso OIDC (PKCE) |
| POST | /auth/logout |
— | Logout locale |
Comportamento della sincronizzazione
- Ogni esecuzione scarica tutte le sorgenti in parallelo (limite
FETCH_CONCURRENCY). - Normalizzazione: spazi rimossi, commenti (
#,;,//) e righe non http/https ignorati, primo token della riga considerato, host in minuscolo, frammento (#...) rimosso. - Deduplicazione: globale su tutte le sorgenti.
- Overwrite completo: gli URL precedenti vengono cancellati e rimpiazzati in una transazione atomica.
- Se tutte le sorgenti falliscono, i dati esistenti vengono conservati (la lista pubblica non viene svuotata per un errore di rete temporaneo); gli errori sono visibili nella UI e in
/api/admin/status.
Verifica degli URL e domini mirror
Subito dopo ogni sincronizzazione (e su richiesta con il pulsante Verifica URL o POST /api/admin/check) ogni URL viene verificato con una richiesta HTTP stile curl:
- Redirect (301, 302, 307, 308 — seguiti automaticamente): l'URL viene sostituito con l'indirizzo finale ricevuto dopo il redirect. Utile quando un dominio migra (es.
eurostreaming.com→eurostreaming.nl). - 404 / 410 (configurabili con
CHECK_REMOVE_CODES): l'URL viene rimosso dalla lista pubblica. Alla sincronizzazione successiva viene comunque riscaricato dalla sorgente e riverificato, quindi un URL tornato attivo rientra automaticamente in lista. - Tutto il resto viene mantenuto: 403 (Cloudflare), 429 (rate limit), 5xx, timeout, errori DNS/rete — per evitare falsi negativi con domini che bloccano i client non-browser.
- Domini mirror: gli URL vengono raggruppati per famiglia = hostname senza porta, senza
www.e senza TLD, più lo stesso path (es.eurostreaming.it/xeeurostreaming.com/x→ famigliaeurostreaming|/x). Di ogni famiglia viene tenuto un solo dominio (il primo, in ordine di lista, che non viene rimosso); gli altri vengono scartati senza essere segnalati come errori. - La UI mostra stato e progresso della verifica (con aggiornamento automatico), il conteggio degli URL mantenuti/rimossi/aggiornati via redirect e il campione degli URL rimossi.
Reverse proxy e HTTPS
Esempio Caddy (TLS automatico):
list.example.com {
reverse_proxy 127.0.0.1:3000
}
Esempio nginx:
server {
listen 443 ssl;
server_name list.example.com;
ssl_certificate /etc/letsencrypt/live/list.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/list.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Con TLS ricordati di aggiornare BASE_URL (con https://) e TRUST_PROXY=1.
CI/CD su Forgejo
Il repository include .forgejo/workflows/build.yml (Forgejo Actions): a ogni push su main, tag v* o pull request viene costruita l'immagine Docker con cache GHA. Per pubblicare su un registry Forgejo:
- Crea i secret
REGISTRY_USEReREGISTRY_TOKENnel repository. - Decommenta gli step di login/push nel workflow e sostituisci
git.example.comcon il tuo hostname. - Sul server:
docker compose pull && docker compose up -d(oppure usa il taglatestpubblicato dal CI).
Sviluppo locale
npm install
cp .env.example .env
# per l'esecuzione senza Docker imposta nel .env:
# DATABASE_PATH=./data/veezie.db
npm start
Il file .env viene caricato automaticamente anche con npm start (dotenv); con Docker se ne occupa env_file in docker-compose.yml. Richiede Node 22+. Senza le variabili OIDC_* l'app parte comunque servendo /list e /healthz, ma la UI amministrativa resta disabilitata.
Risoluzione problemi
- Admin restituisce 503 "SSO non configurato": mancano
OIDC_ISSUER_URL,OIDC_CLIENT_ID,OIDC_CLIENT_SECREToSESSION_SECRETnel.env. - Errore "login SSO non riuscito" / discovery fallita: verifica che
OIDC_ISSUER_URLtermini con/e sia raggiungibile dal container; controlla il redirect URI nel provider Authentik. - Lista vuota dopo la sincronizzazione: controlla gli errori per sorgente nella UI; se tutte le sorgenti falliscono i dati vecchi vengono mantenuti.
- Porta già in uso: cambia il mapping in
docker-compose.yml(es.8080:3000).