T ToutadminDocumentatie

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#

OnderdeelVersieWaarom
PHP8.1 of nieuwerUniontypes, enum, readonly, never: de code gebruikt ze.
pdo_sqliteDe hele database. Zonder deze draait er niets.
mbstringCorrect omgaan met tekst in zestien talen.
opensslaanbevolenVersleuteling van geheimen in de database (SMTP-wachtwoorden, tokens voor extern wegzetten).
curlaanbevolenUitgaande webhooks en het wegzetten van back-ups bij een derde.
intlaanbevolenDatums 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.

Geen afhankelijkheden

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
De webwortel wijst naar 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,
];
De sleutel van de instantie

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#

PadRechtenWie schrijft
data/0770De webserver, en die alleen
data/app.sqlite0660Aangemaakt door de installatie
config.php0640Niemand — alleen-lezen voor de server
app/, public/0755Niemand — 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 doetHoe vaak
Sluit de accounts waarvan het contract is afgelopenelke doorloop
Stuurt de facturen van vervallen abonnementenelke doorloop, nooit tweemaal
Maakt van vervaldata meldingenelke doorloop, ontdubbeld
Ruimt gelezen meldingen en het auditlogboek opvolgens de gekozen bewaartermijn
Haalt het boekhoudpostvak op (IMAP)als het ophalen is ingesteld
Leegt de webhookwachtrij, met haar nieuwe pogingenelke doorloop
Maakt de automatische back-up en zet hem extern wegzodra 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.

Op hosting zonder cron

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:

  1. De vereisten, gecontroleerd en getoond — wat blokkeert staat als zodanig gemerkt.
  2. De installatiesleutel, als er een is ingesteld.
  3. De onderneming: de naam die overal verschijnt, en het aantal jaarlijkse verlofdagen voor elke nieuwe werknemer in loondienst.
  4. De standaardtaal van de instantie, uit de 16.
  5. 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.