No description
  • JavaScript 66.3%
  • HTML 31.9%
  • Dockerfile 1.8%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
carmine 53ab7efaab
All checks were successful
build / docker (push) Successful in 1m5s
first commit
2026-10-21 16:40:52 +02:00
.forgejo/workflows first commit 2026-10-21 16:40:52 +02:00
src first commit 2026-10-21 16:40:52 +02:00
.dockerignore first commit 2026-10-21 16:40:52 +02:00
.env.example first commit 2026-10-21 16:40:52 +02:00
.gitignore first commit 2026-10-21 16:40:52 +02:00
docker-compose.yml first commit 2026-10-21 16:40:52 +02:00
Dockerfile first commit 2026-10-21 16:40:52 +02:00
package-lock.json first commit 2026-10-21 16:40:52 +02:00
package.json first commit 2026-10-21 16:40:52 +02:00
README.md first commit 2026-10-21 16:40:52 +02:00

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

  1. Nell'interfaccia di amministrazione Authentik: Applications → Providers → Create → OAuth2/OpenID Connect Provider.
  2. 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 (oppure http://localhost:3000/auth/callback in locale)
    • Scopes: openid, profile, email (inclusi di default in Authentik)
  3. Salva e prendi note di Client ID e Client Secret.
  4. Applications → Create: nome veezielistamerge, slug veezielistamerge, seleziona il provider. Lo slug determina l'issuer. Applica eventuali policy di accesso (es. solo gruppo admin).
  5. 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=
  1. docker compose up -d (o restart) e apri https://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://... e TRUST_PROXY=1 (i cookie di sessione diventano Secure automaticamente).
  • 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/x e eurostreaming.com/x → famiglia eurostreaming|/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:

  1. Crea i secret REGISTRY_USER e REGISTRY_TOKEN nel repository.
  2. Decommenta gli step di login/push nel workflow e sostituisci git.example.com con il tuo hostname.
  3. Sul server: docker compose pull && docker compose up -d (oppure usa il tag latest pubblicato 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_SECRET o SESSION_SECRET nel .env.
  • Errore "login SSO non riuscito" / discovery fallita: verifica che OIDC_ISSUER_URL termini 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).