drift-journal v1.0.0
Installation & Operations Manual

Audit-proof e-mail journaling for CommuniGate Pro

drift-journal captures every business e-mail into a write-once, content-addressed archive with encryption, retention, legal hold, tamper-evident integrity, search and audit export — self-contained, with no third-party services.

GoBDHGB §257AO §147 DSGVORFC-3161Debian 10 / AstraLinuxWindows

01 Overview

drift-journal is two small, self-contained executables that mirror all mail passing through CommuniGate Pro into a legally-compliant archive. There are no runtime dependencies: the Linux build is statically linked (runs on glibc 2.28+ — Debian 10, AstraLinux SE 1.7 and newer), and the Windows build links the CRT statically (no VC++ redistributable needed).

What it does

  • Captures every message incl. BCC, fail-closed
  • WORM store, SHA-256 content-addressed
  • Encryption at rest (XChaCha20-Poly1305)
  • Retention classes, legal hold, audited purge
  • Chained integrity manifests + RFC-3161 timestamps
  • Field + full-text search, Z3 audit export

What you get

  • journald — archival service + admin tool
  • journal-filter — the CGP capture filter
  • Hardened installer (locked ACLs, WORM, boot service)
  • Auto-generated Verfahrensdokumentation (GoBD)
  • Admin web UI on 127.0.0.1:9087
  • Legal conformance matrix

02 Architecture

The capture path is fail-closed and the two executables share nothing in-process — the only hand-off is the on-disk spool, so an archive or index hiccup can never cause a message to be lost or delivered uncaptured.

CGP
message in mail flow
→
journal-filter
capture incl. BCC
→
spool
durable hand-off
→
journald
WORM + index
→
manifest
chained SHA-256

Everything downstream of the spool — the writer, metadata, index, search, retention and maintenance — is linked into the single journald binary, which also serves the loopback control API (127.0.0.1:9086) and the admin web UI (127.0.0.1:9087).

03 Requirements

PlatformSupportedRuntime dependencies
LinuxDebian 10+, AstraLinux SE 1.7+, RHEL 8+, Ubuntu 20.04+ (glibc 2.28+)None (static musl)
WindowsWindows 10 / 11 / Server 2016+None (static CRT)

CommuniGate Pro is required for live capture. A dedicated, low-privilege service account is created by the installer (drift-journal on Linux; LOCAL SERVICE on Windows). Allow a few hundred MB plus your mail volume × retention for the archive.

04 Install — Debian 10 / Linux

Build the package (once)

# static musl binaries (no glibc dependency), then assemble the .deb
dist/build-musl.sh
dist/build-deb.sh 1.0.0        # -> dist/out/drift-journal_1.0.0_amd64.deb

Install on the server

sudo dpkg -i drift-journal_1.0.0_amd64.deb

The package installs the binaries to /usr/bin, a hardened systemd unit (drift-journald.service), example configs to /etc/drift-journal/, creates the drift-journal service account and /var/lib/drift-journal/, then runs journald init automatically to generate the key system and the Verfahrensdokumentation, and enables the service.

sudo systemctl start drift-journald
journald status            # health, counts, last integrity check
Back up the key immediately The installer prints the path to /var/lib/drift-journal/keys/archive.key. Copy it offline, separately from the archive. Losing it makes the entire archive permanently unreadable.

05 Install — Windows

Build the package (once)

powershell -ExecutionPolicy Bypass -File dist\build-win.ps1
# -> dist\out\drift-journal_1.0.0_win64.zip

Install on the server

Unzip on the target, then from an elevated PowerShell:

powershell -ExecutionPolicy Bypass -File install.ps1
# options:
#   -InstallDir "C:\Program Files\drift-journal"
#   -DataDir    "C:\ProgramData\drift-journal"
#   -TsaUrl     "http://timestamp.digicert.com"   # optional RFC-3161
#   -NoHardenWorm      # skip the delete-deny ACL (not recommended)
#   -NoService         # files + key system only, no boot service

The installer copies the binaries, writes a live config (encryption on), locks down the data store (ACL inheritance broken; only SYSTEM / Administrators full, LOCAL SERVICE modify), runs journald init, applies the WORM deny-delete ACL, and registers a least-privilege boot service — a Scheduled Task drift-journald running as LOCAL SERVICE, auto-restarted on failure.

Back up the key immediately The installer prints the tokens and the path to …\keys\archive.key. Copy the key offline. Without it the archive cannot be read.

06 The key system

journald init (run automatically by both installers, idempotent) sets up two things:

ItemFilePurpose
Encryption keykeys/archive.key32-byte XChaCha20-Poly1305 key; seals every record at rest. Generated once, never rotated in place.
API tokenskeys/api-tokens.tsvadmin (full) and auditor (read + export only) tokens for the control API and web UI.

The keys directory is locked to the service account (0700 on Linux; read-only to LOCAL SERVICE on Windows). Run journald init by hand for a manual setup; an existing key or token file is always kept.

Availability is a GoBD principle too The key is generated once and never rotated in place. Store a backup offline. If you lose it, the archive is gone.

07 WORM hardening

The archive is made append-only so that even an administrator cannot silently delete a record outside an audited purge. The installers apply a baseline automatically; re-assert it after the archive grows.

PlatformApplyTemporarily release (for an audited purge)
Linuxsudo chattr -R +a /var/lib/drift-journal/archivechattr -R -a … → purge → re-apply
Windowsharden-worm.ps1 -Apply-Release → journald purge --confirm → -Apply

On Windows the deny ACE blocks delete and rename for everyone while still allowing the service to create new record files. Content-addressing plus the chained manifest mean any out-of-band change is also detectable.

08 Connect capture to CommuniGate Pro

Fail-closed — order matters Once registered, if journald is not running CommuniGate Pro will hold mail rather than deliver it un-journaled. Install and start journald first, then wire up capture.
  1. Copy journal-filter (.exe on Windows) and its config into the CGP Helpers directory.
  2. Set cgp_settings_dir and filter_exe in journald.cfg.
  3. Preview, then apply the registration (adds the ExternalFilter + a Server-Wide Rule):
journald register              # preview the changes (no writes)
journald register --apply      # writes .bak-register-<ts> per file

Restart CommuniGate Pro afterwards. Send a test message and confirm it lands:

journald search subject:test after:2026-01-01
Both directions The outbound / server-side copy is the complete legal record (full envelope, incl. BCC). A recipient-side copy, as delivered, never contains the BCC — that is by design.

09 Licensing

Both executables are protected by the standard drift-helper key system: an RSA-signed license.key, string obfuscation, a self-integrity stamp, anti-rollback and a compiled-in beta cutoff.

  • Licensed: place a valid license.key (covering the journal helper) next to the executables — no time limit.
  • No license (eval/beta): full function until 2026-12-31, then the binaries refuse to run (fail-closed: the filter holds mail, the service refuses to start).
  • Tamper / clock-rollback: a modified binary or a clock moved backward is refused the same way.

One license covers both journald and journal-filter. Issue one with the key generator:

cd keygenerator
python gen-license-cli.py --issued-to "Acme GmbH" --helpers journal \
       --expiry 2028-12-31 --verify

10 Operating

TaskCommand
Health / countsjournald status
Searchjournald search from:acme subject:rechnung after:2026-01-01
Full-textjournald search text:invoice
Integrity checkjournald verify
Rebuild indexjournald reindex
Classify a recordjournald classify <digest> beleg
Legal holdjournald hold add CASE-1 "from:party@ext"
Retention reportjournald retention
Audited purgejournald purge → journald purge --confirm
Z3 exportjournald export /media/usb case:CASE-1
Timestamp manifestjournald timestamp
Verfahrensdokumentationjournald docs ./Verfahrensdokumentation.md

Search fields: from: to: subject: id: case: after: before: text:/body: plus bare terms. BCC is searchable in the server-side copy.

Admin web UI

With the service running, open http://127.0.0.1:9087/ and log in with a token from keys/api-tokens.tsv. Admin-only actions (classify / hold / purge) appear only for the admin token; the auditor token is read + export. The UI and API are loopback-only.

11 Retention & purge

Retention is counted from the end of the calendar year in which the document arose; the earliest permitted deletion is 1 January of (year + N + 1). Nothing is ever deleted before that date, and deletion after it is operator-initiated and audited — never automatic.

ClassPeriodApplies to
brief (default)6 yearsHandels- und Geschäftsbriefe (most business e-mail)
beleg8 yearsBuchungsbelege / invoices (post-BEG IV, 2025)
buch10 yearsHandelsbücher, Inventare, Jahresabschlüsse
One setting is enough The legal class is a property of the document, not something the mail server can detect. The safe, simple configuration is a single conservative default (keep everything for the longest relevant period). Use classify only if you want to delete the 6-year bulk earlier.

12 Verfahrensdokumentation

GoBD requires a documented procedure (Verfahrensdokumentation). The installers generate a German one automatically from the live configuration — Linux: /var/lib/drift-journal/Verfahrensdokumentation.md, Windows: <DataDir>\Verfahrensdokumentation.md. Regenerate any time:

journald docs ./Verfahrensdokumentation.md
Complete the organisational sections The generated document derives the technical parts from the running system. You must still fill in the organisational measures (operators, access roles in practice, backup procedure, and the works agreement that excludes private mail) — drift-journal supplies the technical controls and the evidence, not the organisation.

13 Integrity & verification

Three independent layers prove immutability:

  1. Content address — each record is stored under the SHA-256 of its bytes; any change changes the address.
  2. Chained manifest — journald verify builds and links an append-only manifest; a removed or altered record breaks the chain. The service also re-checks on a schedule.
  3. OS immutability — the WORM ACL / chattr +a.

Optionally, each manifest is timestamped by a trusted RFC-3161 TSA (only a hash is sent). The release binaries themselves are integrity-stamped; verify any binary:

python3 dist/stamp.py --verify /usr/bin/journald      # Linux
python  dist\stamp.py  --verify journald.exe          # Windows (stamp.py ships in the zip)

15 Uninstall

Linux: sudo apt-get remove drift-journal (or dpkg -r drift-journal) stops and removes the service and binaries. The data under /var/lib/drift-journal/ is kept.

Windows:

powershell -ExecutionPolicy Bypass -File uninstall.ps1             # keeps archive + key
powershell -ExecutionPolicy Bypass -File uninstall.ps1 -PurgeData # also deletes data (confirms)
An archive is a legal record Data is retained by default on uninstall. Do not destroy the archive or the key while records are still within their retention period.

16 Troubleshooting

SymptomCause & fix
CGP is holding / queuing mailThe filter is fail-closed and journald is not running or the spool is unwritable. Start the service and check journald status.
protection check failed (tampered)The binary was modified after stamping. Reinstall from the original package.
protection check failed (beta-expired)Eval cutoff reached. Install a valid license.key next to the executables.
encryption enabled but key unavailableThe key file is missing or unreadable by the service account. Restore the backed-up archive.key.
Search returns nothing newIndex lag or a rebuild is needed: journald reindex. Check status for index vs archived counts.
Windows service not running after rebootCheck the Scheduled Task drift-journald (Task Scheduler) and that LOCAL SERVICE can read the keys directory.
drift-journal v1.0.0 · Installation & Operations Manual. Ships with README-deploy.md, legal-conformance.md and, after install, your generated Verfahrensdokumentation.md.