Installare l'edizione Node
L'edizione Node gira come un servizio permanente: un processo, una banca dati SQLite sul disco, nient'altro da installare — nessuna banca dati esterna, nessuna cache, nessuna coda. Questa guida va dalla cartella vuota all'istanza in produzione, compreso ciò che la maggior parte delle guide tralascia: il servizio di sistema, il proxy inverso, il TLS, i backup e l'aggiornamento.
Che cosa serve#
| Elemento | Versione | Perché |
|---|---|---|
| Node.js | 22 o più recente | Il prodotto si appoggia al suo esecutore di test integrato e ad API recenti. |
| Un compilatore C | build-essential | better-sqlite3 si compila all'installazione, salvo che esista un binario già pronto per la vostra piattaforma. |
| Un disco locale | — | SQLite e i file depositati. Mai una condivisione di rete: si veda più avanti. |
Una macchina a un core con 512 MB di memoria basta per qualche decina di persone. Ciò che conta non è la potenza ma il disco: dev'essere locale e sottoposto a backup.
1. Recuperare e installare#
git clone <il-vostro-deposito> /var/www/toutadmin
cd /var/www/toutadmin
npm ci --omit=dev
npm ci anziché npm install: installa esattamente ciò che descrive
il file di blocco, senza mai riscriverlo. Su un server, una versione che scivola è un guasto
che nessuno capisce.
better-sqlite3 è l'unica dipendenza che compila del C.
sudo apt install -y build-essential python3 risolve la quasi totalità dei casi.
2. Configurare#
cp .env.example .env
Tutto si regola con variabili d'ambiente — lette da .env, o poste dal vostro
gestore di servizi. Nessuna è obbligatoria: senza .env, l'istanza parte sulla
porta 3000 e vi manda alla procedura guidata.
| Variabile | Predefinito | Che cosa fa |
|---|---|---|
PORT | 3000 | La porta d'ascolto. |
NODE_ENV | — | production in produzione: cookie severi, nessuna traccia dettagliata. |
SESSION_SECRET | generato | Sigilla le sessioni. Lasciato vuoto, viene generato in data/session.key. |
INSTALL_TOKEN | — | Richiesto dalla procedura guidata prima d'installare. Consigliato su un server esposto. |
TRUST_PROXY | — | 1 dietro un proxy fidato, e soltanto lì. |
DB_PATH | data/app.sqlite | La banca dati. |
UPLOAD_DIR, CV_DIR, VAULT_DIR, SIGN_DIR, DOCS_DIR, BACKUP_DIR | sotto data/ | Le cartelle dei file: foto, curriculum, cassaforte, libro firme, documenti ricevuti, archivi. |
LOGIN_RATE_LIMIT | 10 | Tentativi d'accesso per quarto d'ora e per indirizzo. |
GLOBAL_RATE_LIMIT | 300 | Richieste al minuto e per indirizzo. |
API_RATE_LIMIT | — | Chiamate d'API al minuto e per token. |
SESSION_IDLE_MINUTES | 60 | Inattività oltre la quale la sessione cade. |
SESSION_MAX_HOURS | 12 | Durata assoluta che nessuna attività prolunga. |
ADMIN_EMAIL, ADMIN_PASSWORD | — | Installazione senza interfaccia: crea l'amministratore all'avvio. |
Cambiare SESSION_SECRET disconnette tutti in un colpo. Ponetelo una volta,
generatelo a caso
(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"), e
salvatelo insieme al resto. Non lasciatelo mai in un deposito git.
Porre un token d'installazione#
Fra il primo avvio e il vostro passaggio nella procedura guidata, l'istanza è di chi
la trova: il primo arrivato crea l'account di amministrazione. Ponete dunque
INSTALL_TOKEN prima di aprire la porta:
node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"
# poi in .env:
INSTALL_TOKEN=c3f1…
La procedura guidata lo confronta a tempo costante — un confronto ordinario lascerebbe indovinare il token carattere per carattere — e registra ogni rifiuto. Una volta installata l'istanza, la procedura si chiude da sé.
Installare senza interfaccia#
Per un rilascio automatizzato, ADMIN_EMAIL e ADMIN_PASSWORD creano
l'amministratore all'avvio, senza passare dalla procedura guidata. Toglieteli poi: una
password nell'ambiente di un servizio la legge chi legge quel servizio.
3. Permessi e dove vivono i dati#
sudo useradd --system --home /var/www/toutadmin --shell /usr/sbin/nologin toutadmin
sudo chown -R toutadmin:toutadmin /var/www/toutadmin/data
sudo chmod 750 /var/www/toutadmin/data
sudo chmod 640 /var/www/toutadmin/.env
Il codice può restare in sola lettura; solo data/ dev'essere scrivibile. SQLite
scrive file vicini (-wal, -shm): conta la cartella, non
solo la banca dati.
NFS e SMB mentono sul blocco dei file. SQLite si affida a quel blocco per impedire due scritture simultanee: su una condivisione, la banca dati finisce per corrompersi senza avvisare. Disco locale, sempre — ed è il backup che parte altrove, non la banca dati.
4. Il servizio#
Avviato a mano, il prodotto si ferma quando chiudete il terminale. Affidatelo a systemd:
# /etc/systemd/system/toutadmin.service
[Unit]
Description=Toutadmin
After=network.target
[Service]
Type=simple
User=toutadmin
WorkingDirectory=/var/www/toutadmin
EnvironmentFile=/var/www/toutadmin/.env
ExecStart=/usr/bin/node src/server.js
Restart=always
RestartSec=5
# Il servizio deve scrivere soltanto in data/.
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/www/toutadmin/data
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now toutadmin
sudo systemctl status toutadmin
journalctl -u toutadmin -f
Le cinque righe d'irrobustimento non sono decorative: ProtectSystem=strict rende
non scrivibile tutto il filesystem, e ReadWritePaths riapre la sola cartella che
deve esserlo. Una falla di scrittura arbitraria raggiunge allora soltanto
data/.
5. Il proxy inverso#
Non esponete mai la porta 3000 direttamente su internet: non fa TLS e non ha motivo di imparare a farlo.
server {
listen 443 ssl http2;
server_name intranet.esempio.it;
ssl_certificate /etc/letsencrypt/live/intranet.esempio.it/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/intranet.esempio.it/privkey.pem;
client_max_body_size 20M; # depositi in cassaforte, documenti ricevuti
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name intranet.esempio.it;
return 301 https://$host$request_uri;
}
Fate inoltre in modo che il servizio ascolti soltanto sull'anello locale, perché la porta 3000 sia raggiungibile dal solo proxy.
TLS#
sudo certbot --nginx -d intranet.esempio.it
Il prodotto pone l'intestazione Strict-Transport-Security non appena la
connessione è cifrata, e mai in chiaro: annunciata da una pagina non cifrata non verrebbe
letta, e rinchiuderebbe una prova locale in https per sei mesi.
TRUST_PROXY va con il proxy, e solo con lui
Questa variabile fa credere al server l'indirizzo d'origine annunciato in un'intestazione.
Dietro nginx è ciò che serve: altrimenti tutte le richieste sembrano venire da
127.0.0.1 e i limiti per indirizzo non proteggono più nulla. Senza proxy
davanti è il contrario: chiunque annuncia l'indirizzo che vuole e aggira i limiti.
6. La procedura guidata#
Aprite il vostro dominio. Finché non esiste alcun account, ogni indirizzo rimanda a
/installation. Cinque passi:
- La lingua dell'istanza, fra le 16.
- I prerequisiti, verificati e mostrati.
- L'azienda: il nome mostrato dappertutto.
- Le ferie annuali attribuite a ogni nuovo dipendente non freelance.
- L'account di amministrazione: indirizzo e password di almeno dodici caratteri.
Non appena esiste un account, /installation rimanda alla pagina d'accesso: la
procedura si è chiusa da sé, non c'è alcun file da cancellare a mano.
7. La scansione periodica#
Diversamente dall'edizione PHP, non c'è nulla da pianificare: il server porta il proprio pianificatore, che si sveglia ogni ora e fa otto cose.
| Che cosa fa la scansione | Garanzia |
|---|---|
| Chiude gli account il cui contratto è scaduto | Verificato anche all'accesso e all'apertura del cruscotto |
| Emette le fatture degli abbonamenti in scadenza | Un indice unico impedisce di fatturare due volte |
| Trasforma le scadenze in notifiche | Una chiave di deduplicazione: una scadenza avvisa una volta sola |
| Ripulisce le notifiche lette e il registro di audit | Secondo la conservazione scelta |
| Preleva la casella contabile (IMAP) | Se il prelievo è configurato |
| Svuota la coda dei webhook, con i suoi ritentativi | Cinque tentativi, poi abbandono |
| Crea il backup automatico e lo porta fuori | Quando l'intervallo è trascorso |
Ogni operazione è idempotente: una scansione ripetuta non fattura due volte e non notifica due volte. È ciò che permette di riavviare il servizio in qualunque momento senza pensarci.
8. Verificare che tutto funzioni#
# i test (non scrivono nella vostra banca dati)
npm test
# un'istanza dimostrativa, per farci un giro
node scripts/seed-demo.js
Poi, nell'interfaccia: create un membro, entrate con lui, depositate un allegato, lanciate un backup manuale e verificatelo dalla schermata Backup. Questi quattro gesti toccano la banca dati, i file, i diritti e l'archivio.
9. Aggiornare#
# 1. un backup prima, sempre — dalla schermata Backup
# 2. il codice
cd /var/www/toutadmin
git pull
npm ci --omit=dev
# 3. riavviare; la banca dati si aggiorna da sé all'avvio
sudo systemctl restart toutadmin
journalctl -u toutadmin -n 30 --no-pager
Lo schema evolve per migrazioni idempotenti: rigiocare l'aggiornamento non rompe nulla.
data/ e .env non vengono mai toccati.
Tornare indietro#
Riportate il codice alla versione precedente e riavviate. Le migrazioni non cancellano colonne: una banca dati migrata resta leggibile dalla versione precedente, salvo menzione esplicita nel registro delle versioni. Nel dubbio, ripristinate l'archivio preso al passo 1.
10. Fare backup sul serio#
L'archivio tar.gz porta la banca dati e le cinque cartelle dei file. La banca
dati è copiata dal backup in linea di SQLite, che produce una copia coerente anche durante
una scrittura — l'edizione PHP ottiene lo stesso risultato con VACUUM INTO.
Ogni file porta la sua impronta SHA-256, verificata al ripristino.
Un archivio rimasto sul server che protegge non protegge nulla: configurate una destinazione
remota (FTPS o Google Drive) dalla schermata Backup, e abbonate un webhook a
sauvegarde.echec per essere avvisati quando l'invio fuori fallisce. Il dettaglio
è nella pagina Backup e ripristino.
Provarlo prima in locale#
npm install
cp .env.example .env
npm run dev # riparte a ogni modifica
Aprite http://localhost:3000. In locale, lasciate NODE_ENV vuoto:
in production i cookie sono marcati «sicuri» e non verranno conservati su una
connessione in chiaro — girereste a vuoto sulla pagina d'accesso.
Passare all'edizione PHP, o venirne#
Le due edizioni condividono lo stesso schema — 142 tabelle,
1311 colonne — e lo stesso formato di password. Fermate l'una, copiate
app.sqlite e le cartelle dei file, avviate l'altra: non c'è conversione. Vedi
Le due edizioni.
Documentazione di Toutadmin — costruita il 2026-09-13. Sito autonomo, indipendente dal software.