De Node-editie installeren
De Node-editie draait als een blijvende dienst: één proces, een SQLite-database op schijf, verder niets te installeren — geen externe database, geen cache, geen wachtrij. Deze handleiding gaat van een lege map naar een instantie in productie, inclusief wat de meeste handleidingen weglaten: de systeemdienst, de omgekeerde proxy, TLS, back-ups en bijwerken.
Wat u nodig hebt#
| Onderdeel | Versie | Waarom |
|---|---|---|
| Node.js | 22 of nieuwer | Het product steunt op de ingebouwde testloper en op recente API's. |
| Een C-compiler | build-essential | better-sqlite3 wordt bij het installeren vertaald, tenzij er een kant-en-klaar binair pakket voor uw platform is. |
| Een lokale schijf | — | SQLite en de geüploade bestanden. Nooit een netwerkschijf: zie verderop. |
Een machine met één kern en 512 MB geheugen volstaat voor enkele tientallen mensen. Wat telt is niet de rekenkracht maar de schijf: die moet lokaal zijn en geback-upt.
1. Ophalen en installeren#
git clone <uw-opslagplaats> /var/www/toutadmin
cd /var/www/toutadmin
npm ci --omit=dev
npm ci in plaats van npm install: het installeert precies wat het
vergrendelingsbestand beschrijft, zonder het ooit te herschrijven. Op een server is een
versie die wegglijdt een storing die niemand begrijpt.
better-sqlite3 is de enige afhankelijkheid die C vertaalt.
sudo apt install -y build-essential python3 lost vrijwel elk geval op.
2. Instellen#
cp .env.example .env
Alles wordt met omgevingsvariabelen geregeld — gelezen uit .env, of gezet door
uw dienstbeheerder. Geen enkele is verplicht: zonder .env start de instantie op
poort 3000 en stuurt ze u naar de assistent.
| Variabele | Standaard | Wat ze doet |
|---|---|---|
PORT | 3000 | De poort waarop geluisterd wordt. |
NODE_ENV | — | production in productie: strikte cookies, geen uitvoerige sporen. |
SESSION_SECRET | aangemaakt | Verzegelt de sessies. Leeg gelaten wordt het aangemaakt in data/session.key. |
INSTALL_TOKEN | — | Door de assistent gevraagd voordat hij installeert. Aanbevolen op een server aan het internet. |
TRUST_PROXY | — | 1 achter een vertrouwde proxy, en alleen daar. |
DB_PATH | data/app.sqlite | De database. |
UPLOAD_DIR, CV_DIR, VAULT_DIR, SIGN_DIR, DOCS_DIR, BACKUP_DIR | onder data/ | De bestandsmappen: foto's, cv's, kluis, tekenmap, ontvangen stukken, archieven. |
LOGIN_RATE_LIMIT | 10 | Inlogpogingen per kwartier en per adres. |
GLOBAL_RATE_LIMIT | 300 | Aanvragen per minuut en per adres. |
API_RATE_LIMIT | — | API-aanroepen per minuut en per token. |
SESSION_IDLE_MINUTES | 60 | Inactiviteit waarna de sessie vervalt. |
SESSION_MAX_HOURS | 12 | Absolute duur die geen enkele activiteit verlengt. |
ADMIN_EMAIL, ADMIN_PASSWORD | — | Installatie zonder scherm: maakt de beheerder aan bij het starten. |
SESSION_SECRET wijzigen logt iedereen in één klap uit. Zet het één keer, maak
het willekeurig aan
(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"), en
back-up het met de rest. Laat het nooit in een git-opslagplaats staan.
Een installatiesleutel zetten#
Tussen de eerste start en uw bezoek aan de assistent is de instantie van wie haar
vindt: wie het eerst komt, maakt het beheerdersaccount aan. Zet dus
INSTALL_TOKEN voordat u de poort openzet:
node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"
# daarna in .env:
INSTALL_TOKEN=c3f1…
De assistent vergelijkt hem in gelijkblijvende tijd — bij een gewone vergelijking laat de sleutel zich teken voor teken raden — en legt elke weigering vast. Zodra de instantie geïnstalleerd is, sluit de assistent zichzelf.
Zonder scherm installeren#
Voor een geautomatiseerde uitrol maken ADMIN_EMAIL en
ADMIN_PASSWORD de beheerder aan bij het starten, buiten de assistent om. Haal ze
daarna weg: een wachtwoord in de omgeving van een dienst leest wie die dienst leest.
3. Rechten en waar de gegevens staan#
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
De code mag alleen-lezen blijven; alleen data/ moet beschrijfbaar zijn. SQLite
schrijft buurbestanden (-wal, -shm): het gaat om de
map, niet alleen om de database.
NFS en SMB liegen over het vergrendelen van bestanden. SQLite vertrouwt op die vergrendeling om twee gelijktijdige schrijfacties te verhinderen: op een netwerkschijf raakt de database zonder waarschuwing beschadigd. Lokale schijf, altijd — en het is de back-up die elders heen gaat, niet de database.
4. De dienst#
Met de hand gestart stopt het product zodra u de terminal sluit. Geef het aan 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
# De dienst hoeft alleen in data/ te schrijven.
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
De vijf regels verharding zijn geen versiering: ProtectSystem=strict maakt het
hele bestandssysteem onbeschrijfbaar, en ReadWritePaths zet de ene map weer
open die het wel moet zijn. Een lek dat willekeurig schrijven toelaat reikt dan niet verder
dan data/.
5. De omgekeerde proxy#
Zet poort 3000 nooit rechtstreeks op het internet: hij doet geen TLS en heeft geen reden dat te leren.
server {
listen 443 ssl http2;
server_name intranet.voorbeeld.nl;
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 / {
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.voorbeeld.nl;
return 301 https://$host$request_uri;
}
Laat de dienst bovendien alleen op de lus luisteren, zodat poort 3000 uitsluitend door de proxy bereikbaar is.
TLS#
sudo certbot --nginx -d intranet.voorbeeld.nl
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.
TRUST_PROXY hoort bij de proxy, en alleen daarbij
Deze variabele laat de server het in een header aangekondigde herkomstadres op zijn woord
geloven. Achter nginx is dat precies wat u wilt — anders lijkt elke aanvraag van
127.0.0.1 te komen en beschermen de limieten per adres niets meer. Zonder
proxy ervoor is het omgekeerd: iedereen kondigt het adres van zijn keuze aan en loopt de
limieten voorbij.
6. De assistent#
Open uw domein. Zolang er geen account bestaat, stuurt elk adres door naar
/installation. Vijf stappen:
- De taal van de instantie, uit de 16.
- De vereisten, gecontroleerd en getoond.
- De onderneming: de naam die overal verschijnt.
- Het jaarlijks verlof dat elke nieuwe werknemer in loondienst krijgt.
- Het beheeraccount: adres en een wachtwoord van minstens twaalf tekens.
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.
7. De periodieke ronde#
Anders dan in de PHP-editie valt er niets in te plannen: de server draagt zijn eigen planner, die elk uur wakker wordt en acht dingen doet.
| Wat de ronde doet | Waarborg |
|---|---|
| Sluit de accounts waarvan het contract is afgelopen | Ook gecontroleerd bij het inloggen en bij het openen van het dashboard |
| Stuurt de facturen van vervallen abonnementen | Een unieke index voorkomt dubbel factureren |
| Maakt van vervaldata meldingen | Een ontdubbelingssleutel: één vervaldatum meldt één keer |
| 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 | Vijf pogingen, dan opgeven |
| Maakt de automatische back-up en zet hem extern weg | Zodra het interval verstreken is |
Elke bewerking is idempotent: een herhaalde ronde factureert niet twee keer en meldt niet twee keer. Daardoor kunt u de dienst op elk moment herstarten zonder erover na te denken.
8. Controleren dat alles werkt#
# de tests (ze schrijven niet in uw database)
npm test
# een voorbeeldinstantie om in rond te lopen
node scripts/seed-demo.js
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.
9. Bijwerken#
# 1. eerst een back-up, altijd — vanuit het scherm Back-ups
# 2. de code
cd /var/www/toutadmin
git pull
npm ci --omit=dev
# 3. herstarten; de database werkt zichzelf bij bij het starten
sudo systemctl restart toutadmin
journalctl -u toutadmin -n 30 --no-pager
Het schema groeit via idempotente migraties: de bijwerking opnieuw spelen breekt niets.
data/ en .env worden nooit aangeraakt.
Teruggaan#
Zet de code terug op de vorige versie en herstart. Migraties verwijderen nooit een kolom: een gemigreerde database blijft leesbaar voor de vorige versie, tenzij het wijzigingslogboek anders zegt. Bij twijfel zet u het archief van stap 1 terug.
10. Echt back-uppen#
Het archief tar.gz draagt de database en de vijf bestandsmappen. De database
wordt gekopieerd via de online back-up van SQLite, die ook tijdens een schrijfactie een
samenhangende kopie oplevert — de PHP-editie bereikt hetzelfde met VACUUM INTO.
Elk bestand draagt 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), en hang een
webhook aan sauvegarde.echec om te horen wanneer het wegzetten mislukt. Het
fijne ervan staat op de pagina Back-up en herstel.
Eerst plaatselijk proberen#
npm install
cp .env.example .env
npm run dev # herstart bij elke wijziging
Open http://localhost:3000. Laat NODE_ENV plaatselijk leeg: onder
production zijn de cookies als "veilig" gemarkeerd en worden ze over een
onversleutelde verbinding niet bewaard — u zou rondjes draaien op de inlogpagina.
Overstappen op de PHP-editie, of eruit komen#
Beide edities delen hetzelfde schema — 142 tabellen, 1311 kolommen
— en hetzelfde wachtwoordformaat. Stop de ene, kopieer app.sqlite en de
bestandsmappen, start de andere: er is geen conversie. Zie
De twee edities.
Toutadmin-documentatie — gebouwd op 2026-09-13. Zelfstandige site, onafhankelijk van de software.