De PHP-editie installeren
De PHP-editie zet u zoals ze is op gedeelde hosting neer: geen Composer, geen pakketbeheerder, geen dienst die moet blijven draaien. Eén map, één configuratiebestand, één geplande taak. Deze handleiding gaat van een lege map naar een instantie in productie, inclusief wat de meeste handleidingen weglaten: de rechten, TLS, de geplande taak, back-ups en bijwerken.
Wat u nodig hebt#
| Onderdeel | Versie | Waarom |
|---|---|---|
| PHP | 8.1 of nieuwer | Uniontypes, enum, readonly, never: de code gebruikt ze. |
pdo_sqlite | — | De hele database. Zonder deze draait er niets. |
mbstring | — | Correct omgaan met tekst in zestien talen. |
openssl | aanbevolen | Versleuteling van geheimen in de database (SMTP-wachtwoorden, tokens voor extern wegzetten). |
curl | aanbevolen | Uitgaande webhooks en het wegzetten van back-ups bij een derde. |
intl | aanbevolen | Datums geschreven als in de Node-editie. Zonder deze komen maandnamen uit de woordenboeken. |
De installatieassistent controleert dit alles en toont het op het scherm voordat hij u laat doorgaan: wat blokkeert houdt de installatie tegen, wat facultatief is wordt gemeld zonder te blokkeren.
Er valt niets met composer te installeren: de IMAP-lezer, het
tar.gz-archief, de PDF, de CSV, de QR-code en de HTTP-cliënt staan in het
product zelf geschreven. Juist daardoor kunt u het neerzetten op hosting waar u geen
opdrachtregel hebt.
1. De bestanden uploaden#
Haal het archief van de PHP-editie op en upload het. De map ziet er zo uit:
toutadmin/
├── app/ de code (nooit door de webserver uitgeleverd)
│ ├── Core/ kern: routering, database, sessies, beveiliging
│ ├── Modules/ bedrijfslogica: personeelszaken, financiën, projecten, kwaliteit…
│ ├── Controllers/
│ ├── views/ sjablonen
│ ├── locales/ de zestien woordenboeken
│ └── schema.sql het schema, gelijk aan dat van de Node-editie
├── public/ ← de webwortel wijst hierheen, en nergens anders
│ ├── index.php de voorliggende controller
│ ├── css/ js/
├── data/ database, kluis, tekenmap, ontvangen stukken, back-ups
├── tools/ cron.php, check-keys.php
├── tests/
├── config.sample.php
└── VERSION
public/, niet naar de map
Dit is het belangrijkste punt van deze pagina. Levert de server de hele map uit, dan is
data/app.sqlite voor iedereen te downloaden: de hele database, wachtwoordhashes
en kluis inbegrepen. De map data/ staat met opzet boven public/.
Op gedeelde hosting#
Er zijn twee gevallen. Laat uw hoster u de wortel van een domein kiezen (cPanel, Plesk, en de
meeste hosters in de stand "wortelmap"), zet de map dan buiten www/ en laat het
domein naar toutadmin/public wijzen.
Is de wortel opgelegd — vaak www/ of public_html/ — zet dan de
inhoud van public/ erin en de rest erboven, en verbeter daarna
het pad boven in public/index.php:
www/ ← opgelegde wortel
├── index.php
├── css/ js/
toutadmin/ ← de rest, buiten het bereik van het web
├── app/
├── data/
└── config.php
// www/index.php — de regel die u aanpast
require_once __DIR__ . '/../toutadmin/app/bootstrap.php';
Kunt u in het uiterste geval niets boven de wortel kwijt, dan weigert het meegeleverde
bestand .htaccess in data/ de toegang al. Dat is een riem, geen
oplossing: kies altijd de map buiten de wortel.
2. De configuratie schrijven#
cp config.sample.php config.php
Open daarna config.php:
<?php
return [
// SQLite-database en gegevensmap: buiten de webwortel.
'db_path' => __DIR__ . '/data/app.sqlite',
'data_dir' => __DIR__ . '/data',
// Een sleutel van deze instantie zelf. Eenmalig aan te maken:
// php -r "echo bin2hex(random_bytes(32));"
'session_secret' => '…',
// Installatiesleutel: zie de volgende stap.
'install_token' => '',
// Openbaar adres, voor de links die per e-mail uitgaan.
'base_url' => 'https://intranet.voorbeeld.nl',
// Alleen aanzetten achter een vertrouwde proxy.
'trust_proxy' => false,
// Plafonds: inlogpogingen per kwartier en per adres,
// aanvragen per minuut en per adres.
'login_rate_limit' => 10,
'global_rate_limit' => 300,
// Vervallen: inactiviteit, daarna een absolute duur die niets verlengt.
'session_idle_minutes' => 60,
'session_max_hours' => 12,
];
Hij verzegelt de handtekeningen van de tekenmap en versleutelt geheimen in de database.
Hem kwijtraken maakt handtekeningen ongeldig. Laat u de voorbeeldwaarde staan, dan maakt
het product er zelf een aan in data/.instance-key (rechten 0600)
— denk er dan aan dat bestand mee te back-uppen.
U kunt de configuratie ook elders bewaren: de omgevingsvariabele
TOUTADMIN_CONFIG wijst een ander bestand aan.
3. Een installatiesleutel zetten#
Tussen het uploaden van de bestanden en uw bezoek aan de assistent is de instantie van wie haar vindt: wie het eerst komt, maakt het beheerdersaccount aan. Het venster is kort, het volstaat. Zet dus een willekeurige waarde voordat u live gaat:
php -r "echo bin2hex(random_bytes(16));"
# daarna in config.php:
'install_token' => 'c3f1…',
De assistent vraagt erom, vergelijkt hem in gelijkblijvende tijd — bij een gewone vergelijking laat de sleutel zich teken voor teken raden — en legt elke weigering vast. Zonder de sleutel wordt niets aangemaakt. Zodra de instantie geïnstalleerd is, sluit de assistent zichzelf: de sleutel mag blijven staan, hij dient nergens meer toe.
4. Rechten#
| Pad | Rechten | Wie schrijft |
|---|---|---|
data/ | 0770 | De webserver, en die alleen |
data/app.sqlite | 0660 | Aangemaakt door de installatie |
config.php | 0640 | Niemand — alleen-lezen voor de server |
app/, public/ | 0755 | Niemand — alleen-lezen |
# voorbeeld op een server waar PHP als www-data draait
sudo chown -R u: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 schrijft daarnaast buurbestanden (-wal, -shm): het is de
map die beschrijfbaar moet zijn, niet alleen de database.
5. De webserver#
Nginx#
server {
listen 443 ssl http2;
server_name intranet.voorbeeld.nl;
root /var/www/toutadmin/public; # en niets anders
index index.php;
ssl_certificate /etc/letsencrypt/live/intranet.voorbeeld.nl/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/intranet.voorbeeld.nl/privkey.pem;
client_max_body_size 20M; # kluisuploads, ontvangen stukken
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;
}
# Geüploade bestanden worden nooit rechtstreeks uitgeleverd: ze lopen
# via een route die de sessie en de hash controleert.
location ~ ^/(data|app|tools|tests)/ { deny all; }
}
server {
listen 80;
server_name intranet.voorbeeld.nl;
return 301 https://$host$request_uri;
}
Apache#
<VirtualHost *:443>
ServerName intranet.voorbeeld.nl
DocumentRoot /var/www/toutadmin/public
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/intranet.voorbeeld.nl/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/intranet.voorbeeld.nl/privkey.pem
<Directory /var/www/toutadmin/public>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
Het meegeleverde bestand public/.htaccess stuurt alle adressen al naar
index.php. Op gedeelde Apache-hosting is dat alles wat nodig is.
TLS#
Het product zet de header Strict-Transport-Security zodra de verbinding
versleuteld is, en nooit onversleuteld: aangekondigd vanaf een onversleutelde pagina zou hij
niet gelezen worden, en hij zou een lokale proef zes maanden lang in https
opsluiten. Op een server die u zelf beheert:
sudo certbot --nginx -d intranet.voorbeeld.nl
Achter een proxy of loadbalancer die TLS afhandelt, zet u
'trust_proxy' => true — en alleen daar: anders zou iedereen in een header het
IP-adres en protocol van zijn keuze kunnen aankondigen.
6. De geplande taak#
Een PHP-site draait alleen tijdens een aanvraag. Wat de Node-editie in haar uurlijkse ronde doet — acht bewerkingen — gebeurt hier vanuit de cron van de hoster:
* * * * * /usr/bin/php /var/www/toutadmin/tools/cron.php >> /var/log/toutadmin-cron.log 2>&1
| Wat de taak doet | Hoe vaak |
|---|---|
| Sluit de accounts waarvan het contract is afgelopen | elke doorloop |
| Stuurt de facturen van vervallen abonnementen | elke doorloop, nooit tweemaal |
| Maakt van vervaldata meldingen | elke doorloop, ontdubbeld |
| Ruimt gelezen meldingen en het auditlogboek op | volgens de gekozen bewaartermijn |
| Haalt het boekhoudpostvak op (IMAP) | als het ophalen is ingesteld |
| Leegt de webhookwachtrij, met haar nieuwe pogingen | elke doorloop |
| Maakt de automatische back-up en zet hem extern weg | zodra het interval verstreken is |
Het script vaker aanroepen dan het ingestelde interval back-upt niet vaker: vóór de
vervaldatum gebeurt er niets. Op hosting die de cron beperkt tot één doorloop per kwartier
vervangt u * * * * * door */15 * * * * — de webhooks vertrekken dan
eenvoudig met die vertraging.
Sommige instappakketten bieden er geen. Een HTTP-aanroep vanaf een dienst van derden (een
bereikbaarheidswachter en dergelijke) volstaat niet: cron.php weigert ergens
anders te draaien dan op de opdrachtregel, juist opdat een openbaar adres geen back-up kan
uitlokken. In dat geval start u de back-up met de hand vanuit het scherm
Back-ups.
7. De assistent#
Open uw domein. Zolang er geen account bestaat, stuurt elk adres door naar
/installation. De assistent past op één pagina:
- De vereisten, gecontroleerd en getoond — wat blokkeert staat als zodanig gemerkt.
- De installatiesleutel, als er een is ingesteld.
- De onderneming: de naam die overal verschijnt, en het aantal jaarlijkse verlofdagen voor elke nieuwe werknemer in loondienst.
- De standaardtaal van de instantie, uit de 16.
- Het beheeraccount: adres en een wachtwoord van minstens twaalf tekens.
Alles wordt in één transactie weggeschreven. Zodra er een account bestaat,
stuurt /installation door naar de inlogpagina: de assistent heeft zichzelf
gesloten, er is geen bestand met de hand te verwijderen.
8. Controleren dat alles werkt#
# de tests, op de machine (ze schrijven niet in uw database)
php tests/run.php
# de vertalingen: geen scherm gebruikt een sleutel die in de woordenboeken ontbreekt
php tools/check-keys.php
Daarna in de interface: maak een lid aan, log ermee in, zet een bijlage neer, start een back-up met de hand en controleer die vanuit het scherm Back-ups. Die vier handelingen raken de database, de bestanden, de rechten en het archief — dat wil zeggen alles wat de hosting moet kunnen.
9. Bijwerken#
# 1. eerst een back-up, altijd
php tools/cron.php # of het scherm Back-ups
# 2. de code vervangen, niet de gegevens
# app/ public/ tools/ tests/ VERSION ← vervangen
# data/ config.php ← ongemoeid gelaten
# 3. de database werkt zichzelf bij bij de eerste aanvraag
Het schema groeit via idempotente migraties: de bijwerking opnieuw spelen breekt niets. Een instantie van de Node-editie en een van de PHP-editie delen hetzelfde schema — 142 tabellen, 1311 kolommen — en dezelfde wachtwoorden: een database gaat zonder conversie van de ene naar de andere.
10. Echt back-uppen#
Het archief tar.gz draagt de database (gekopieerd met VACUUM INTO,
dus samenhangend ook tijdens een schrijfactie) en de vijf bestandsmappen: profielfoto's,
ontvangen cv's, kluis, tekenmap, ontvangen stukken. Elk bestand draagt er zijn
SHA-256-waarde, die bij het terugzetten wordt gecontroleerd.
Een archief dat blijft staan op de server die het beschermt, beschermt niets: stel vanuit het scherm Back-ups een externe bestemming in (FTPS of Google Drive). Het fijne ervan staat op de pagina Back-up en herstel.
Eerst plaatselijk proberen#
cp config.sample.php config.php
php -S localhost:8000 -t public public/index.php
De ingebouwde server van PHP volstaat om het product te verkennen. Voor productie deugt hij niet: één enkel proces, geen TLS, geen begrenzing van de belasting.
Toutadmin-documentatie — gebouwd op 2026-09-13. Zelfstandige site, onafhankelijk van de software.