Die Node-Edition installieren
Die Node-Edition läuft als dauerhafter Dienst: ein Prozess, eine SQLite-Datenbank auf der Platte, sonst nichts zu installieren — keine externe Datenbank, kein Cache, keine Warteschlange. Diese Anleitung führt vom leeren Verzeichnis zur Instanz im Produktivbetrieb, samt allem, was die meisten Anleitungen auslassen: Systemdienst, Reverse-Proxy, TLS, Sicherungen und Aktualisierung.
Was nötig ist#
| Element | Version | Warum |
|---|---|---|
| Node.js | 22 oder neuer | Das Produkt stützt sich auf den eingebauten Testläufer und auf neuere Schnittstellen. |
| Ein C-Compiler | build-essential | better-sqlite3 wird beim Installieren übersetzt, sofern für Ihre Plattform kein fertiges Binärpaket vorliegt. |
| Eine lokale Platte | — | SQLite und die hochgeladenen Dateien. Niemals eine Netzfreigabe: siehe unten. |
Eine Maschine mit einem Kern und 512 MB Speicher genügt für einige Dutzend Personen. Es zählt nicht die Rechenleistung, sondern die Platte: sie muss lokal und gesichert sein.
1. Holen und installieren#
git clone <Ihr-Repositorium> /var/www/toutadmin
cd /var/www/toutadmin
npm ci --omit=dev
npm ci statt npm install: es installiert genau das, was die
Sperrdatei beschreibt, ohne sie je neu zu schreiben. Auf einem Server ist eine verrutschte
Version eine Störung, die niemand versteht.
better-sqlite3 ist die einzige Abhängigkeit, die C übersetzt.
sudo apt install -y build-essential python3 löst nahezu alle Fälle.
2. Konfigurieren#
cp .env.example .env
Alles wird über Umgebungsvariablen eingestellt — gelesen aus .env oder gesetzt
von Ihrer Dienstverwaltung. Keine davon ist Pflicht: ohne .env startet die
Instanz auf Port 3000 und schickt Sie zum Assistenten.
| Variable | Standard | Was sie bewirkt |
|---|---|---|
PORT | 3000 | Der Port, auf dem gelauscht wird. |
NODE_ENV | — | production im Produktivbetrieb: strikte Cookies, keine ausführlichen Spuren. |
SESSION_SECRET | erzeugt | Versiegelt die Sitzungen. Leer gelassen, wird es in data/session.key erzeugt. |
INSTALL_TOKEN | — | Vom Assistenten vor der Installation verlangt. Auf einem offenen Server empfohlen. |
TRUST_PROXY | — | 1 hinter einem vertrauenswürdigen Proxy, und nur dort. |
DB_PATH | data/app.sqlite | Die Datenbank. |
UPLOAD_DIR, CV_DIR, VAULT_DIR, SIGN_DIR, DOCS_DIR, BACKUP_DIR | unter data/ | Die Dateiordner: Bilder, Lebensläufe, Tresor, Unterschriftenmappe, eingegangene Belege, Archive. |
LOGIN_RATE_LIMIT | 10 | Anmeldeversuche je Viertelstunde und Adresse. |
GLOBAL_RATE_LIMIT | 300 | Anfragen je Minute und Adresse. |
API_RATE_LIMIT | — | API-Aufrufe je Minute und Token. |
SESSION_IDLE_MINUTES | 60 | Untätigkeit, nach der die Sitzung endet. |
SESSION_MAX_HOURS | 12 | Absolute Dauer, die keine Aktivität verlängert. |
ADMIN_EMAIL, ADMIN_PASSWORD | — | Installation ohne Oberfläche: legt die Administration beim Start an. |
SESSION_SECRET zu ändern meldet alle auf einen Schlag ab. Setzen Sie es
einmal, erzeugen Sie es zufällig
(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"),
und sichern Sie es mit dem Rest. Lassen Sie es nie in einem Git-Repositorium liegen.
Ein Installationstoken setzen#
Zwischen dem ersten Start und Ihrem Besuch im Assistenten gehört die Instanz
dem, der sie findet: wer zuerst kommt, legt das Administrationskonto an.
Setzen Sie also INSTALL_TOKEN, bevor Sie den Port öffnen:
node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"
# dann in .env:
INSTALL_TOKEN=c3f1…
Der Assistent vergleicht es in gleichbleibender Zeit — ein gewöhnlicher Vergleich ließe das Token Zeichen für Zeichen erraten — und protokolliert jede Abweisung. Ist die Instanz erst installiert, schließt sich der Assistent von selbst.
Ohne Oberfläche installieren#
Für eine automatisierte Bereitstellung legen ADMIN_EMAIL und
ADMIN_PASSWORD die Administration beim Start an, ohne den Assistenten. Nehmen
Sie sie danach heraus: ein Passwort in der Umgebung eines Dienstes liest, wer diesen Dienst
liest.
3. Berechtigungen und der Ort der Daten#
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
Der Code darf schreibgeschützt bleiben; nur data/ muss beschreibbar sein.
SQLite legt Begleitdateien an (-wal, -shm): es zählt der
Ordner, nicht nur die Datenbank.
NFS und SMB lügen beim Sperren von Dateien. SQLite verlässt sich auf dieses Sperren, um zwei gleichzeitige Schreibvorgänge zu verhindern: auf einer Freigabe wird die Datenbank ohne Vorwarnung beschädigt. Lokale Platte, immer — und es ist die Sicherung, die woandershin geht, nicht die Datenbank.
4. Der Dienst#
Von Hand gestartet, endet das Produkt, sobald Sie das Terminal schließen. Übergeben Sie es an 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
# Der Dienst muss nur in data/ schreiben.
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
Die fünf Zeilen zur Härtung sind kein Schmuck: ProtectSystem=strict macht das
ganze Dateisystem unbeschreibbar, und ReadWritePaths öffnet den einen Ordner
wieder, der es sein muss. Eine Lücke, die beliebiges Schreiben erlaubt, erreicht dann nur
noch data/.
5. Der Reverse-Proxy#
Stellen Sie Port 3000 nie unmittelbar ins Internet: er kann kein TLS und hat keinen Grund, es zu lernen.
server {
listen 443 ssl http2;
server_name intranet.beispiel.de;
ssl_certificate /etc/letsencrypt/live/intranet.beispiel.de/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/intranet.beispiel.de/privkey.pem;
client_max_body_size 20M; # Ablagen im Tresor, eingegangene Belege
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.beispiel.de;
return 301 https://$host$request_uri;
}
Lassen Sie den Dienst außerdem nur auf der Rückschleife lauschen, damit Port 3000 allein vom Proxy erreichbar ist.
TLS#
sudo certbot --nginx -d intranet.beispiel.de
Das Produkt setzt den Header Strict-Transport-Security, sobald die Verbindung
verschlüsselt ist, und nie im Klartext: von einer unverschlüsselten Seite aus angekündigt
würde er nicht gelesen, und er würde einen lokalen Versuch sechs Monate lang in
https einsperren.
TRUST_PROXY gehört zum Proxy, und nur zu ihm
Diese Variable lässt den Server der in einem Header angekündigten Ursprungsadresse
glauben. Hinter nginx ist das genau richtig — sonst scheinen alle Anfragen von
127.0.0.1 zu kommen, und die Grenzen je Adresse schützen nichts mehr. Ohne
Proxy davor ist es umgekehrt: jeder kündigt die Adresse seiner Wahl an und umgeht die
Grenzen.
6. Der Assistent#
Öffnen Sie Ihre Domain. Solange kein Konto besteht, leitet jede Adresse auf
/installation. Fünf Schritte:
- Die Sprache der Instanz, unter den 16.
- Die Voraussetzungen, geprüft und angezeigt.
- Das Unternehmen: der überall angezeigte Name.
- Der Jahresurlaub, den jede neue festangestellte Person erhält.
- Das Administrationskonto: Adresse und ein Passwort von mindestens zwölf Zeichen.
Sobald ein Konto besteht, leitet /installation auf die Anmeldeseite: der
Assistent hat sich selbst geschlossen, es gibt keine Datei von Hand zu löschen.
7. Der periodische Durchlauf#
Anders als in der PHP-Edition ist nichts zu planen: der Server bringt seinen eigenen Planer mit, der stündlich aufwacht und acht Dinge tut.
| Was der Durchlauf tut | Zusicherung |
|---|---|
| Schließt Konten, deren Vertrag ausgelaufen ist | Auch bei der Anmeldung und beim Öffnen der Übersicht geprüft |
| Stellt die Rechnungen fälliger Abonnements aus | Ein eindeutiger Index verhindert doppelte Rechnungen |
| Macht aus Fristen Benachrichtigungen | Ein Entdopplungsschlüssel: eine Frist meldet sich einmal |
| Räumt gelesene Benachrichtigungen und das Prüfprotokoll auf | Gemäß der gewählten Aufbewahrung |
| Ruft das Buchhaltungspostfach ab (IMAP) | Wenn der Abruf eingerichtet ist |
| Leert die Webhook-Warteschlange samt Wiederholungen | Fünf Versuche, dann Aufgabe |
| Erstellt die automatische Sicherung und lagert sie aus | Wenn der Abstand verstrichen ist |
Jeder Vorgang ist idempotent: ein wiederholter Durchlauf stellt nichts doppelt in Rechnung und meldet nichts doppelt. Genau das erlaubt es, den Dienst jederzeit ohne Nachdenken neu zu starten.
8. Prüfen, dass alles läuft#
# die Tests (sie schreiben nicht in Ihre Datenbank)
npm test
# eine Beispielinstanz zum Umsehen
node scripts/seed-demo.js
Danach in der Oberfläche: ein Mitglied anlegen, sich damit anmelden, einen Anhang ablegen, eine Sicherung von Hand anstoßen und sie im Fenster Sicherungen prüfen. Diese vier Handgriffe berühren Datenbank, Dateien, Rechte und Archiv.
9. Aktualisieren#
# 1. zuerst eine Sicherung, immer — im Fenster Sicherungen
# 2. der Code
cd /var/www/toutadmin
git pull
npm ci --omit=dev
# 3. neu starten; die Datenbank wandert beim Start von selbst mit
sudo systemctl restart toutadmin
journalctl -u toutadmin -n 30 --no-pager
Das Schema entwickelt sich über idempotente Migrationen: die Aktualisierung erneut zu
spielen zerstört nichts. data/ und .env werden nie angefasst.
Zurückgehen#
Setzen Sie den Code auf die vorige Fassung zurück und starten Sie neu. Migrationen löschen keine Spalte: eine migrierte Datenbank bleibt für die vorige Fassung lesbar, sofern das Änderungsprotokoll nichts anderes sagt. Im Zweifel spielen Sie das in Schritt 1 erstellte Archiv zurück.
10. Wirklich sichern#
Das Archiv tar.gz trägt die Datenbank und die fünf Dateiordner. Die Datenbank
wird über SQLites Online-Sicherung kopiert, die auch während eines Schreibvorgangs eine in
sich stimmige Kopie liefert — die PHP-Edition erreicht dasselbe über
VACUUM INTO. Jede Datei trägt ihre SHA-256-Prüfsumme, beim Zurückspielen
geprüft.
Ein Archiv, das auf dem Server bleibt, den es schützt, schützt nichts: richten Sie im
Fenster Sicherungen ein entferntes Ziel ein (FTPS oder Google Drive) und hängen Sie
einen Webhook an sauvegarde.echec, um zu erfahren, wenn die Auslagerung
scheitert. Das Nähere steht auf der Seite
Sicherung und Wiederherstellung.
Erst einmal örtlich ausprobieren#
npm install
cp .env.example .env
npm run dev # startet bei jeder Änderung neu
Öffnen Sie http://localhost:3000. Lassen Sie NODE_ENV örtlich
leer: unter production sind die Cookies als „sicher“ gekennzeichnet und werden
über eine unverschlüsselte Verbindung nicht behalten — Sie drehten sich auf der Anmeldeseite
im Kreis.
Zur PHP-Edition wechseln, oder von ihr kommen#
Beide Editionen teilen dasselbe Schema — 142 Tabellen, 1311
Spalten — und dasselbe Passwortformat. Die eine anhalten, app.sqlite und die
Dateiordner kopieren, die andere starten: es gibt keine Umwandlung. Siehe
Die zwei Editionen.
Toutadmin-Dokumentation — erstellt am 2026-09-13. Eigenständige Website, unabhängig von der Software.