Installare l'edizione PHP
L'edizione PHP si deposita così com'è su un hosting condiviso: niente Composer, niente gestore di pacchetti, nessun servizio da tenere in funzione. Una cartella, un file di configurazione, un'attività pianificata. Questa guida va dalla cartella vuota all'istanza in produzione, compreso ciò che la maggior parte delle guide tralascia: i permessi, il TLS, l'attività pianificata, i backup e l'aggiornamento.
Che cosa serve#
| Elemento | Versione | Perché |
|---|---|---|
| PHP | 8.1 o più recente | Tipi unione, enum, readonly, never: il codice li usa. |
pdo_sqlite | — | L'intera banca dati. Senza, non gira nulla. |
mbstring | — | Trattamento corretto delle stringhe in sedici lingue. |
openssl | consigliata | Cifratura dei segreti nella banca dati (password SMTP, token di esternalizzazione). |
curl | consigliata | Webhook in uscita e invio dei backup a terzi. |
intl | consigliata | Date scritte come nell'edizione Node. Senza, i nomi dei mesi vengono dai dizionari. |
La procedura guidata d'installazione verifica tutto questo e lo mostra a schermo prima di lasciarvi proseguire: ciò che è bloccante ferma l'installazione, ciò che è facoltativo viene segnalato senza bloccare.
Non c'è nulla da installare con composer: il lettore IMAP, l'archivio
tar.gz, il PDF, il CSV, il codice QR e il client HTTP sono scritti dentro il
prodotto. È ciò che permette di depositarlo su un hosting dove non avete una riga di
comando.
1. Caricare i file#
Recuperate l'archivio dell'edizione PHP, poi caricatelo. La cartella è fatta così:
toutadmin/
├── app/ il codice (mai servito dal server web)
│ ├── Core/ nucleo: instradamento, banca dati, sessioni, sicurezza
│ ├── Modules/ dominio: risorse umane, gestione, progetti, qualità…
│ ├── Controllers/
│ ├── views/ modelli
│ ├── locales/ i sedici dizionari
│ └── schema.sql lo schema, identico a quello dell'edizione Node
├── public/ ← la radice web punta qui, e da nessun'altra parte
│ ├── index.php il controllore frontale
│ ├── css/ js/
├── data/ banca dati, cassaforte, libro firme, documenti ricevuti, backup
├── tools/ cron.php, check-keys.php
├── tests/
├── config.sample.php
└── VERSION
public/, non sulla cartella
È il punto più importante di questa pagina. Se il server serve l'intera cartella,
data/app.sqlite diventa scaricabile da chiunque: tutta la banca dati, comprese
le impronte delle password e la cassaforte. La cartella data/ sta sopra
public/ apposta.
Su un hosting condiviso#
Si presentano due casi. Se il vostro fornitore permette di scegliere la radice di un dominio
(cPanel, Plesk, e la maggior parte degli hosting in modalità «cartella radice»), depositate
la cartella fuori da www/ e fate puntare il dominio a
toutadmin/public.
Se la radice è imposta — spesso www/ o public_html/ — depositatevi
il contenuto di public/ e il resto al di sopra, poi correggete
il percorso in cima a public/index.php:
www/ ← radice imposta
├── index.php
├── css/ js/
toutadmin/ ← il resto, fuori dalla portata del web
├── app/
├── data/
└── config.php
// www/index.php — la riga da adattare
require_once __DIR__ . '/../toutadmin/app/bootstrap.php';
Come ultima risorsa, se non potete mettere nulla al di sopra della radice, il file
.htaccess consegnato dentro data/ rifiuta già l'accesso. È una
cintura, non una soluzione: preferite sempre la cartella fuori dalla radice.
2. Scrivere la configurazione#
cp config.sample.php config.php
Poi aprite config.php:
<?php
return [
// Banca dati SQLite e cartella dati: fuori dalla radice web.
'db_path' => __DIR__ . '/data/app.sqlite',
'data_dir' => __DIR__ . '/data',
// Chiave propria dell'istanza. Da generare una volta:
// php -r "echo bin2hex(random_bytes(32));"
'session_secret' => '…',
// Token d'installazione: vedi il passo successivo.
'install_token' => '',
// Indirizzo pubblico, per i collegamenti inviati per email.
'base_url' => 'https://intranet.esempio.it',
// Da attivare solo dietro un proxy fidato.
'trust_proxy' => false,
// Tetti: tentativi d'accesso per quarto d'ora e per indirizzo,
// richieste al minuto e per indirizzo.
'login_rate_limit' => 10,
'global_rate_limit' => 300,
// Scadenza: inattività, poi durata assoluta che nessuna attività prolunga.
'session_idle_minutes' => 60,
'session_max_hours' => 12,
];
Sigilla le firme del libro firme e cifra i segreti nella banca dati. Perderlo invalida
delle firme. Se lasciate il valore d'esempio, il prodotto ne genera uno da sé in
data/.instance-key (permessi 0600) — ricordatevi allora di
salvare quel file insieme al resto.
Potete anche tenere la configurazione altrove: la variabile d'ambiente
TOUTADMIN_CONFIG indica un altro file.
3. Porre un token d'installazione#
Fra il caricamento dei file e il vostro passaggio nella procedura guidata, l'istanza è di chi la trova: il primo arrivato crea l'account di amministrazione. La finestra è breve, ma basta. Ponete dunque un valore a caso prima della messa in linea:
php -r "echo bin2hex(random_bytes(16));"
# poi in config.php:
'install_token' => 'c3f1…',
La procedura guidata lo chiede, lo confronta a tempo costante — un confronto ordinario lascerebbe indovinare il token carattere per carattere — e registra ogni rifiuto. Senza il token non viene creato nulla. Una volta installata l'istanza, la procedura si chiude da sé: il token può restare, non serve più.
4. Permessi#
| Percorso | Diritti | Chi scrive |
|---|---|---|
data/ | 0770 | Il server web, e lui soltanto |
data/app.sqlite | 0660 | Creato dall'installazione |
config.php | 0640 | Nessuno — sola lettura per il server |
app/, public/ | 0755 | Nessuno — sola lettura |
# esempio su un server dove PHP gira come www-data
sudo chown -R voi:www-data /var/www/toutadmin
sudo find /var/www/toutadmin -type d -exec chmod 750 {} \;
sudo find /var/www/toutadmin -type f -exec chmod 640 {} \;
sudo chmod 770 /var/www/toutadmin/data
sudo chmod 750 /var/www/toutadmin/public
SQLite scrive anche file vicini (-wal, -shm): è la
cartella a dover essere scrivibile, non solo la banca dati.
5. Il server web#
Nginx#
server {
listen 443 ssl http2;
server_name intranet.esempio.it;
root /var/www/toutadmin/public; # e nient'altro
index index.php;
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 / {
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# I file depositati non sono mai serviti direttamente: passano
# per una rotta che verifica la sessione e l'impronta.
location ~ ^/(data|app|tools|tests)/ { deny all; }
}
server {
listen 80;
server_name intranet.esempio.it;
return 301 https://$host$request_uri;
}
Apache#
<VirtualHost *:443>
ServerName intranet.esempio.it
DocumentRoot /var/www/toutadmin/public
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/intranet.esempio.it/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/intranet.esempio.it/privkey.pem
<Directory /var/www/toutadmin/public>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
Il file public/.htaccess consegnato rimanda già tutti gli indirizzi a
index.php. Su un hosting condiviso Apache, è tutto ciò che serve.
TLS#
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. Su un server che
amministrate voi:
sudo certbot --nginx -d intranet.esempio.it
Dietro un proxy o un bilanciatore che termina il TLS, mettete
'trust_proxy' => true — e soltanto lì: altrimenti chiunque potrebbe annunciare
l'indirizzo IP e il protocollo che vuole in un'intestazione.
6. L'attività pianificata#
Un sito PHP gira solo al momento di una richiesta. Ciò che l'edizione Node fa nella sua scansione oraria — otto operazioni — qui si fa dal cron dell'hosting:
* * * * * /usr/bin/php /var/www/toutadmin/tools/cron.php >> /var/log/toutadmin-cron.log 2>&1
| Che cosa fa l'attività | Con quale ritmo |
|---|---|
| Chiude gli account il cui contratto è scaduto | a ogni passaggio |
| Emette le fatture degli abbonamenti in scadenza | a ogni passaggio, senza mai fatturare due volte |
| Trasforma le scadenze in notifiche | a ogni passaggio, senza doppioni |
| 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 | a ogni passaggio |
| Crea il backup automatico e lo porta fuori | quando l'intervallo è trascorso |
Chiamare lo script più spesso dell'intervallo configurato non fa backup più spesso: nulla si
avvia prima della scadenza. Su un hosting che limita il cron a un passaggio ogni quindici
minuti, sostituite * * * * * con */15 * * * * — i webhook
partiranno semplicemente con quel ritardo.
Certi hosting d'ingresso non ne offrono. Una chiamata HTTP da un servizio terzo (un
sorvegliante di disponibilità e simili) non basta: cron.php rifiuta di essere
eseguito altrove che in riga di comando, proprio perché un indirizzo pubblico non possa
scatenare un backup. In quel caso, il backup si lancia a mano dalla schermata
Backup.
7. La procedura guidata#
Aprite il vostro dominio. Finché non esiste alcun account, ogni indirizzo rimanda a
/installation. La procedura sta in una pagina:
- I prerequisiti, verificati e mostrati — ciò che blocca è segnato come tale.
- Il token d'installazione, se è configurato.
- L'azienda: il nome mostrato dappertutto, e il numero di giorni di ferie annuali attribuito a ogni nuovo dipendente non freelance.
- La lingua predefinita dell'istanza, fra le 16.
- L'account di amministrazione: indirizzo e password di almeno dodici caratteri.
Tutto è scritto in una sola transazione. 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.
8. Verificare che tutto funzioni#
# i test, sulla macchina (non scrivono nella vostra banca dati)
php tests/run.php
# le traduzioni: nessuna schermata impiega una chiave assente dai dizionari
php tools/check-keys.php
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 — vale a dire tutto ciò che l'hosting deve saper fare.
9. Aggiornare#
# 1. un backup prima, sempre
php tools/cron.php # o la schermata Backup
# 2. sostituire il codice, non i dati
# app/ public/ tools/ tests/ VERSION ← sostituiti
# data/ config.php ← conservati tali e quali
# 3. la banca dati si aggiorna da sé alla prima richiesta
Lo schema evolve per migrazioni idempotenti: rigiocare l'aggiornamento non rompe nulla. Un'istanza dell'edizione Node e una dell'edizione PHP condividono lo stesso schema — 142 tabelle, 1311 colonne — e le stesse password: una banca dati passa dall'una all'altra senza conversione.
10. Fare backup sul serio#
L'archivio tar.gz porta la banca dati (copiata con VACUUM INTO,
dunque coerente anche durante una scrittura) e le cinque cartelle dei file: foto di profilo,
curriculum ricevuti, cassaforte, libro firme, documenti ricevuti. Ogni file vi 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. Il dettaglio è nella pagina Backup e ripristino.
Provare prima in locale#
cp config.sample.php config.php
php -S localhost:8000 -t public public/index.php
Il server integrato di PHP basta per scoprire il prodotto. Non va bene per la produzione: un solo processo, niente TLS, nessun limite di carico.
Documentazione di Toutadmin — costruita il 2026-09-13. Sito autonomo, indipendente dal software.