T ToutadminDokumentation

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#

ElementVersionWarum
Node.js22 oder neuerDas Produkt stützt sich auf den eingebauten Testläufer und auf neuere Schnittstellen.
Ein C-Compilerbuild-essentialbetter-sqlite3 wird beim Installieren übersetzt, sofern für Ihre Plattform kein fertiges Binärpaket vorliegt.
Eine lokale PlatteSQLite 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.

Wenn das Übersetzen fehlschlägt

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.

VariableStandardWas sie bewirkt
PORT3000Der Port, auf dem gelauscht wird.
NODE_ENVproduction im Produktivbetrieb: strikte Cookies, keine ausführlichen Spuren.
SESSION_SECRETerzeugtVersiegelt die Sitzungen. Leer gelassen, wird es in data/session.key erzeugt.
INSTALL_TOKENVom Assistenten vor der Installation verlangt. Auf einem offenen Server empfohlen.
TRUST_PROXY1 hinter einem vertrauenswürdigen Proxy, und nur dort.
DB_PATHdata/app.sqliteDie Datenbank.
UPLOAD_DIR, CV_DIR, VAULT_DIR, SIGN_DIR, DOCS_DIR, BACKUP_DIRunter data/Die Dateiordner: Bilder, Lebensläufe, Tresor, Unterschriftenmappe, eingegangene Belege, Archive.
LOGIN_RATE_LIMIT10Anmeldeversuche je Viertelstunde und Adresse.
GLOBAL_RATE_LIMIT300Anfragen je Minute und Adresse.
API_RATE_LIMITAPI-Aufrufe je Minute und Token.
SESSION_IDLE_MINUTES60Untätigkeit, nach der die Sitzung endet.
SESSION_MAX_HOURS12Absolute Dauer, die keine Aktivität verlängert.
ADMIN_EMAIL, ADMIN_PASSWORDInstallation ohne Oberfläche: legt die Administration beim Start an.
Das Sitzungsgeheimnis

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.

Niemals auf einer Netzfreigabe

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:

  1. Die Sprache der Instanz, unter den 16.
  2. Die Voraussetzungen, geprüft und angezeigt.
  3. Das Unternehmen: der überall angezeigte Name.
  4. Der Jahresurlaub, den jede neue festangestellte Person erhält.
  5. 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 tutZusicherung
Schließt Konten, deren Vertrag ausgelaufen istAuch bei der Anmeldung und beim Öffnen der Übersicht geprüft
Stellt die Rechnungen fälliger Abonnements ausEin eindeutiger Index verhindert doppelte Rechnungen
Macht aus Fristen BenachrichtigungenEin Entdopplungsschlüssel: eine Frist meldet sich einmal
Räumt gelesene Benachrichtigungen und das Prüfprotokoll aufGemäß der gewählten Aufbewahrung
Ruft das Buchhaltungspostfach ab (IMAP)Wenn der Abruf eingerichtet ist
Leert die Webhook-Warteschlange samt WiederholungenFünf Versuche, dann Aufgabe
Erstellt die automatische Sicherung und lagert sie ausWenn 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.