Releases

Releases

Every tagged release on GitHub appears here automatically. We use semantic
date-stamped tags (`v260120` = 2026-01-20) for predictable cadence and so
you can correlate releases with the changelog at a glance.

v260609

v260609 pre-release June 11, 2026

Hermes SEG v260609

Release notes and per-release update artifacts for v260609. This file is the canonical
v260609 upgrade document and doubles as the GitHub Release body
(gh release create v260609 --notes-file updates/v260609/README.md).

What's new in v260609

v260609 is a backup, disaster-recovery, and upgrade-tooling release on top of the v260119
baseline. Highlights:

  • Docker-aware backup & restore. A rebuilt system_backup.sh and system_restore.sh

hot (zero-downtime) backups, scoped/slim storage tiers, a directory-style backup format,
streamed restore (no double-staging), disk-space pre-checks, and email notifications
(--notify-email, --notify-on-success). (#219)

  • Cross-host disaster recovery + re-host. Restore a backup onto fresh hardware:

system_restore.sh auto-remaps the storage topology when it differs from the source,
reconciles per-service DB credentials (including Nextcloud's config.php) to the target
host's own creds/, and detects a cross-host restore and offers to run system_rehost.sh
for you
. system_rehost.sh then rewires host identity — console hostname, regenerated
service configs, and the Nextcloud OIDC provider's discovery + end-session URLs. A
version-match gate guards against accidental cross-version restores. After any restore, follow
the Post-Restore Steps
checklist. (#220)

  • Smoother in-place upgrades. The system_update_docker.sh orchestrator gains a

pre-container pre-scripts/ hook and self-re-exec, so upgrades that must migrate files or
credentials before containers restart now work cleanly. (#221)

  • Authelia credential cleanup. Authelia DB credentials moved from keys/ to creds/

(alongside the other service-account credentials). This upgrade migrates them for you
automatically
— see the required procedure below.

  • Install-script reliability fixes. All bundled scripts are now marked executable (fixes a

"There was an error executing …" failure on some admin disk-usage panels), clearer install
progress output, corrected install-summary paths, and removal of a misleading console-host
prompt.

  • Documentation. README rewrite with logo + screenshots, full end-user portal docs

(11 pages), an expanded backup/restore admin guide, and a BookStack documentation-sync
pipeline. (#259)

> Note: Email Policies (Disclaimers, External Sender Banner), Organizational/Personal
> Signatures, ARC, two-factor enforcement, the device-setup wizard, and the certificate UX
> all shipped in v260119 — they are not new here.

Bugs fixed

  • #266 — repo .sh scripts

shipped non-executable (100644), so CFML admin panels that shell out failed with "There was an
error executing …"
(e.g. the dashboard disk-usage panel via disk_space_usage_archive.sh). Now
chmod +x at build time and re-applied on install/restore via ensure_scripts_executable.

  • #267hermes_smoke_test.sh

hardcoded build_no=v260119, so the post-install smoke test falsely failed on every release after
v260119. Now version-agnostic (reports build_no; optional EXPECTED_BUILD=vYYMMDD to assert).

After a cross-host restore

Restoring onto fresh hardware is now largely automatic, but a few items remain operator-driven.
The full checklist lives at
Post-Restore Steps
(also docs/install/post-restore-steps.md in the repo). In short:

  • Accept the restore script's offer to run system_rehost.sh when it detects a cross-host

restore (or run it manually with --force).

  • Re-validate the Pro license (the activation is host-bound).
  • Re-save Content Checks once, so the Postfix smtpd_milters chain is rewritten for this

host (#268 — restore does
not yet reapply it automatically).

  • Turn Nextcloud maintenance mode off if the restore left it on, then run the smoke test.

⚠️ REQUIRED — upgrading from v260119 to v260609

Do not run the normal system_update_docker.sh v260609 command for this upgrade. It
will fail when containers restart (docker compose up"bind source path does not exist"
for a new Authelia storage secret).

v260609 is the first release to add the orchestrator's pre-scripts/ hook and self-re-exec.
The v260119 orchestrator predates both, so it cannot run this release's pre-container
Authelia credential migration before restarting containers. Because the fix ships inside
v260609, it can't bootstrap itself onto a v260119 box — you must run v260609's orchestrator
directly, via the one-time bridge below.

Recommended — one command (from your install directory):

cd /opt/hermes-seg                 # your Hermes install directory
git fetch --tags origin
sudo bash <(git show v260609:updates/v260609/upgrade-to-v260609.sh)

Manual equivalent (same steps, if you prefer to run them yourself):

cd /opt/hermes-seg
git stash                          # set aside install-time config drift
git fetch --tags origin
git checkout v260609
sudo ./scripts/system_update_docker.sh --skip-git v260609

This procedure is required only for the v260119 → v260609 hop. Every later upgrade uses
the normal system_update_docker.sh <tag> command, because v260609+ self-re-execs.

Already ran the normal command and hit the error? No harm done — run the procedure above and
the upgrade completes cleanly.

What this release ships

| Path | Phase | Purpose |
|---|---|---|
| pre-scripts/01-migrate-authelia-creds.sh | 2 (pre) | Moves Authelia DB creds keys/ → creds/ before compose up (new AUTHELIA_STORAGE_USERNAME/PASSWORD secrets) |
| scripts/01-regen-authelia-config.sh | 3 | Regenerates Authelia configuration.yml for the MySQL storage backend + restarts the container |
| sql/schema_updates.sql | 3 | Schema deltas + the build_no version stamp |
| upgrade-to-v260609.sh | — | The one-time bridge above (wraps stash → fetch → checkout → --skip-git) |

Why two issues compound on this one hop

1. Bootstrap gap. v260119's orchestrator has no pre-scripts/ handling and no
self-re-exec, so it never runs pre-scripts/01-migrate-authelia-creds.sh. The Authelia
creds stay in keys/, while v260609's docker-compose.yml references creds/authelia_<em>
docker compose up fails on the missing secret. The bridge runs v260609's orchestrator
instead (git checkout v260609 + --skip-git), so the pre-script fires.
2. #256 dirty tree. A fresh v260119 install mutates tracked files (fail2ban defaults,
SpamAssassin bayes DB, slapd ldif, install-substituted templates), so the orchestrator
preflight (git diff --quiet) aborts with "uncommitted changes." The bridge's git stash
clears it. (The forward fix — gitignore + git rm --cached the runtime-mutable files — is
tracked separately and does not help this hop, since the v260119 preflight is already shipped.)

Apply order (once v260609's orchestrator is running)

Per the standard orchestrator flow (system_update_docker.sh, #221):

1. Phase 2 pre-scriptspre-scripts/</em>.sh run BEFORE docker compose pull && up -d
2. Phase 2docker compose pull + up -d
3. Phase 3sql/<em>.sql, then cfml/</em>.cfm (none this release), then scripts/*.sh
4. Phase 4 / 5 — standard finalize + post-upgrade hook

Each artifact is idempotent; re-running is a no-op.

See docs/install/release-and-update-methodology.md
for the full release/upgrade methodology.

Hermes SEG v260119 — Initial Docker Release (BETA)

v260119 pre-release May 30, 2026

Hermes SEG v260119 — Initial Docker Release (BETA)

> This release is BETA. v260119 is feature-complete and validated end-to-end on our DEV and Test environments, but it has not yet seen production deployments in the wild. Do not migrate a live email gateway to this release before standing it up in parallel and putting it through your own acceptance tests — send mail through it, receive mail through it, test your relay flows, your quarantine release path, your DKIM / SPF / DMARC alignment, your backup procedures, and your disaster-recovery plan. Treat it as the foundation you build confidence on, not as a drop-in production replacement on day one. Bug reports and field feedback from beta operators directly shape the next release.

This is the first tagged release of Hermes SEG's Docker era. Hermes has shipped for years as a bare-metal Ubuntu install — a custom installer script, host-level Postfix / Amavis / Dovecot / Lucee / OpenLDAP, host-managed systemd services. v260119 is the calendar-dated tag (2026-01-19) that marks the Dockerized rewrite as a coherent, shipping product: 18 containers orchestrated by Docker Compose, a five-tier storage topology, a five-phase single-command update orchestrator, Authelia SSO for the admin console + user portal + Nextcloud, and a brand-new release-engineering pipeline built around GitHub Releases and ghcr.io. This release is fresh-install only (#231) — legacy-to-Docker migration tooling exists in skeletal form but is not yet Docker-aware end to end; see Migrating from legacy below.

Summary

  • BETA release — feature-complete, validated on our infrastructure, but not yet production-proven in the wild. Stand it up in parallel and exercise your real mail flow before cutting over.
  • Full Docker rewrite: 18 containers, single docker compose up -d, all services run inside containers (no host-level mail stack). Replaces the legacy bare-metal Ubuntu installer.
  • Five-tier storage topology (#260): Config / Data / Archive / Vmail / Nextcloud, each independently mountable so operators put each tier on the right kind of disk.
  • Single-command update orchestrator (#221): scripts/system_update_docker.sh runs a 5-phase pipeline (git pull → image pull → per-release artifacts → finalize → post-upgrade hook). Auto-resolves the latest tag via the GitHub Releases API and auto-runs occ upgrade + rehydrates required Nextcloud apps on NCVERSION bumps.
  • GitHub-distributed: images live at ghcr.io/deeztek/hermes-<service>:<tag>; releases live on GitHub. Code remains on GitLab for dev. Legacy bare-metal history preserved on the GitHub legacy branch.
  • Authelia SSO: unified MFA (TOTP / WebAuthn / Duo Push) covers admin console, user portal, and Nextcloud (via OIDC). The legacy custom session model is gone.
  • Nextcloud integrated: webmail, file sync, calendars (CalDAV), and contacts (CardDAV) ship with the stack — pre-provisioned on first OIDC login, with vendor-driven NC version pinning (#261).

What changed vs the legacy bare-metal install

High-level comparison

| Concern | Legacy (bare-metal) | v260119 (Docker) |
|---|---|---|
| Install | ubuntu_hermes_install_legacy.sh against a bare Ubuntu host; ~60 minutes of apt installs, service config writes, certificate generation, schema imports | scripts/install_hermes_docker.sh — 10–30 minutes, mostly image pulls; idempotent, re-runnable, single session |
| Service architecture | Host-level systemd: postfix.service, amavisd.service, dovecot.service, clamav-daemon.service, etc. | 18 containers managed by docker compose |
| Database access | mysql -u root on the host | docker exec -it hermes_db_server mariadb -u root (unix-socket auth, no -p) |
| Shell commands against services | Direct: postconf -e, postfix reload, amavisd-new reload | Wrapped: docker exec hermes_postfix_dkim postconf -e, etc. — codified throughout the CFML admin app |
| Updates | system_update.sh — monolithic shell script that pulled tarballs from updates.deeztek.com and applied SQL deltas in place | scripts/system_update_docker.sh — 5-phase orchestrator backed by GitHub Releases API + ghcr.io |
| Backups | system_backup.sh — tar of host directories + mysqldump over local socket | Docker-aware backup/restore refactor is in flight (#219, #220) — see Known limitations |
| Authentication | Custom CFML session + ad-hoc admin password storage | Authelia SSO with LDAP backend, TOTP / WebAuthn / Duo Push; OIDC integration with Nextcloud |
| Storage layout | Everything under /opt/hermes/ + /var/spool/postfix/ + /var/lib/mysql/ on the OS disk | Five-tier mount topology (Config / Data / Archive / Vmail / Nextcloud) collapsible for small deployments |
| Distribution | Single tarball + install script on updates.deeztek.com | GitHub Releases + ghcr.io packages |
| Configuration regeneration | Direct file writes to /etc/postfix/main.cf etc. via CFML cfexecute | CFML renders configs into config/<service>/etc/.../ host paths; volume-mounted into containers; CFML invokes docker exec for reloads |
| Credentials | Mix of plaintext in /etc/, .env, and host filesystem | Docker secrets at config/hermes/opt/hermes/keys/ (18 secret files generated by install, mounted read-only into containers) |
| Mail-flow add-ons | None: signatures, disclaimers, ARC done outside the gateway | hermes_body_milter (Python + pymilter) container does outbound disclaimers, signatures, and inbound External Sender Banner in the Postfix milter chain; hermes_openarc for ARC; multi-instance OpenDKIM for differential sign vs verify |
| MFA enforcement | None | Per-domain enforce_mfa slider + per-mailbox override (#225); app passwords for IMAP/SMTP/DAV clients that can't do MFA |
| Mobile setup | Manual IMAP/SMTP entry | Signed .mobileconfig profiles (iOS), QR delivery via user portal, CalDAV/CardDAV autoconfig (#224) |
| Webmail / files / calendars | None (or external) | Nextcloud (Files + Mail + Calendar + Contacts) with SSO |
| User self-service | None / minimal | Per-mailbox portal: signatures, sieve rules, vacation auto-reply, app passwords, mobile device profiles, shared folder management |

Install: legacy vs Docker

LEGACY (bare-metal)                       v260119 (Docker)
-------------------                       ----------------
1. Fresh Ubuntu Server (specific version) 1. Any Docker host (Ubuntu 24.04 tested) + Compose v2
2. Clone repo                             2. Clone repo (anywhere — install script self-locates)
3. Run ubuntu_hermes_install_legacy.sh    3. Run scripts/install_hermes_docker.sh
4. ~60 minutes of apt + service config    4. ~10-30 minutes (mostly image pull)
5. Reboot                                 5. (no reboot)
6. Configure DNS                          6. Configure DNS
7. Configure first domain via web         7. Configure first domain via web
8. ...                                    8. ... (same web-side config from here on)

Updates: legacy vs Docker

LEGACY                                    v260119
------                                    -------
  • system_update.sh polled - schedule/check_for_update.cfm polls the
updates.deeztek.com daily GitHub Releases API daily
  • Update script downloaded tarball, - system_update_docker.sh runs a 5-phase
ran sql deltas, restarted services pipeline against the repo + ghcr.io
  • All-or-nothing; restart everything - Phased + idempotent: only restarts what
  • Schema deltas applied inline changed (commandbox always; others on
  • Single update path config diff in v2). Per-release artifact
dirs (sql / cfml / scripts). Auto-runs occ upgrade + NC app rehydrate on NCVERSION drift.

Releases: legacy vs Docker

LEGACY                                    v260119
------                                    -------
  • Versioning: build-YYMMDD - Versioning: vYYMMDD (calendar)
  • Tarball + sha256 on updates.deeztek.com - GitHub Release + ghcr.io images
  • One distribution channel - GitLab (dev) + GitHub (distribution),
two-remote workflow via scripts/git_release.sh
  • Release notes in RELEASE-NOTES.md - Release notes live on each GitHub
(cumulative) Release page (scoped to that tag)

New capabilities

These features exist because the Docker rewrite enabled them — most would not have been clean to retrofit onto the bare-metal install.

Mail flow

  • Outbound disclaimers (#214 — Pro): per-domain or per-address disclaimer templates, applied by the dedicated hermes_body_milter Python container in the Postfix milter chain. No reply-chain dedup — every outbound gets a fresh disclaimer, matching the Exclaimer / Crossware / M365 industry norm.
  • Personal signatures (#226 — Community): rich-HTML per-user signatures with Quill editor, template gallery (5 starter templates), tables, social-media icons (8 brand-colored 24x24 PNGs), image limit 10, milter-rendered on every outbound message.
  • Organizational signatures (#226 — Pro): admin-managed per-domain signature templates with placeholder substitution (employee name / title / phone / email / department / organization info). Rendered in the same milter, layered as body → signature → disclaimer.
  • External Sender Banner (#228 — Community): inbound mail from outside the org gets a visual banner injected by hermes_body_milter.
  • CID inline image support (#230) in body modifiers: signatures and disclaimers can embed inline images that survive multipart/related wrapping and DKIM signing — verified on Gmail + Thunderbird.
  • OpenARC integration (#229): the hermes_openarc container does ARC chain signing on outbound and verification on inbound for forwarding-trust preservation.
  • Multi-instance OpenDKIM (#232): separate signer and verifier instances so outbound-sign and inbound-verify can have different behaviors without config bleed.

Mailbox hosting

  • Email Server section (#196, #199, #200) — Domains / Mailboxes / Aliases / Mailbox Rules with full admin UI on top of Dovecot 2.4.
  • Dovecot 2.4 upgrade (#92, #182): custom Ubuntu-based hermes-dovecot image, replaces 2.3.x. Breaking changes (config syntax, plugin loading, ACL syntax) absorbed.
  • Shared mailboxes + user-managed folder sharing (#213): Dovecot ACL-based, vfile per-folder ACL, admin and user UIs, Rebuild ACL Files action.
  • Sieve rules (#202): admin global rules + per-user mail filters, dedicated dovecot_sieve volume isolated from /srv/mail.
  • Vacation auto-reply (#203): per-user date scoping with per-mailbox timezone.
  • BCC Maps UI (#201): sender BCC and recipient BCC management.
  • Mobile device setup wizard (#224): "Set Up Your Devices" walkthroughs in the user portal, signed iOS mobileconfig (IMAP/SMTP/CalDAV/CardDAV), QR-gated download.
  • App Passwords (#197): unified credential system for Dovecot IMAP/SMTP + Nextcloud DAV, designed to extend to future ActiveSync.
  • Email autoconfiguration (#185): autodiscover + autoconfig endpoints based on SNI certificates; CalDAV/CardDAV autodiscovery (#210).

Nextcloud integration

  • Webmail + Files + Calendar + Contacts out of the box, single sign-on via Authelia OIDC.
  • OIDC migration to user_oidc (#208): migrated from the legacy oidc_login app to the canonical user_oidc app, with pre-provisioning pipeline that creates NC accounts on first login.
  • Mail account auto-provisioning: NC Mail profiles created during account provisioning so users land in a working webmail.
  • External Sites integration: "User Console" link in the NC top menu points back to the Hermes user portal; stays in sync with console hostname changes.
  • Vendor-driven version management (#261): NCVERSION is Hermes-release-managed (operators don't edit it). Each release that bumps it ships only after passing the NC integration check (scripts/test_nc_integration.sh) on a Test box.
  • Maintenance Mode card (#262) in System Settings — proper NC-native admin access (local username/password + TOTP) instead of the previous Authelia-leaked path.

Authentication and access control

  • Authelia 4.39 upgrade (#177): PKCE, claims policies, log rotation, access control fix.
  • Per-domain enforce_mfa (#225): nag banner + per-domain slider + per-mailbox override.
  • LDAP RemoteAuth for relay recipients: pass-through authentication to external LDAP / Active Directory, with First/Last Name capture and welcome-email pipeline.
  • System Users refactor (#176): single-page admin UI with modals, immutable username + auth type.
  • Password Reset with multi-provider CAPTCHA (#149, #154): math CAPTCHA default + provider-config slots for reCAPTCHA / hCaptcha / Turnstile.
  • Fail2ban (brought into stack): jails for Dovecot + Authelia, DB-logged.

Security and operations

  • DNS resolver (#211): hermes_unbound recursive resolver with admin UI; defaults to recursive resolution (forwarding off) for reliable RBL lookups.
  • Multi-select filters (#193) on System Logs + Message History.
  • Mail Queue page rewrite (#194): in-memory parsing, consolidated actions, overload protection.
  • System Certificates rewrite (#175, #244#253): Server vs Mailbox cert UX split, SYSTEM badge + delete protection, Generate CSR workflow with planned-domains assist, mailbox SAN auto-derivation.
  • Scheduled Tasks UI (#178): DB-backed source of truth (ofelia_jobs table) with humanized schedule column, Run Now + enable/disable per job.
  • Malware Feeds management UI (#195): Fangfrisch integration, migrated to Ofelia.
  • Score Overrides (#183): per-rule SpamAssassin score overrides via admin UI.
  • Quarantine notifications (#180): near-real-time, one-click release.
  • Dashboard nudges (#241): topology-agnostic "Placeholder hostname" + "Self-signed cert" callouts that auto-disappear when fixed.
  • Smoke test (#242): scripts/hermes_smoke_test.sh — post-install health gauntlet (no real mail flow needed).

Admin console

  • AdminLTE 4 / Bootstrap 5 migration (#170#176, #188, #191): migrated from the legacy NetObjects Fusion templates. Every page in the sidebar is now on the modern stack.
  • Dashboard hardening (#216): graceful degradation when external services (updates check) are unreachable — no more dashboard-wide failures.
  • Console Settings + Server Setup: rename of legacy "Server Identity"; Nextcloud regen wiring, TLS hint.

Installing fresh

Detailed instructions live in README.md and docs/install/get-started-docker.md. Short version:

Requirements

| Item | Recommendation |
|---|---|
| OS | Any Linux distribution capable of running Docker Engine and Docker Compose v2. We test against Ubuntu 24.04 Server, so that's the lowest-friction path; other distributions (Debian, RHEL/Alma/Rocky, etc.) should work but are not part of our regression matrix yet. |
| Docker Engine | 24.0+ |
| Docker Compose | v2 |
| CPU | 4 cores minimum |
| RAM | 4 GB minimum, 8 GB+ recommended |
| Disk | 50 GB minimum for the install root + Data tier |
| Network | Static IP or DHCP reservation; outbound 80/443; inbound 25/80/443/465/587/993/995 as needed |

Clone + run

sudo git clone https://github.com/deeztek/Hermes-Secure-Email-Gateway.git
cd Hermes-Secure-Email-Gateway
sudo ./scripts/install_hermes_docker.sh

The installer runs in a single session, 10–30 minutes (mostly image downloads + the hermes_fail2ban container build). It will:

1. Display the Pro EULA and ask for acceptance
2. Prompt for mail server hostname (FQDN), console address, host IP, upstream DNS forwarders, and the four storage mount paths
3. Generate all secrets and per-service config files (LDAP secrets, DB passwords, Authelia session keys, OIDC keypair, self-signed bootstrap cert, ~18 Docker secret files in total)
4. Write DATA_MOUNT / ARCHIVE_MOUNT / VMAIL_MOUNT / FILES_MOUNT into .env to drive the compose volume device: paths
5. Run docker compose up -d --build to pull images and start the stack
6. Initialize all databases (Hermes, Authelia, Nextcloud, OpenDMARC, CipherMail, Syslog), populate the LDAP base structure, create the initial admin user, and pre-provision the Nextcloud admin
7. Print an installation summary with the admin console URL and the one-time admin credentials

The installer is idempotent — re-running it (or any of its sub-flags) on an already-installed system skips already-completed work via state guards. Run sudo ./scripts/install_hermes_docker.sh --help for the full flag list, including --show-config, --show-summary, --apply-schema, --init-db, --generate-secrets, --configure-storage, and (destructive) --wipe.

What you get after install

  • Admin Console: https://<console-host>/admin/
  • User Portal: https://<console-host>/users/
  • Nextcloud: https://<console-host>/nc/

The bootstrap admin lands you in a working console, but mail won't flow until you complete the minimum first-run config in docs/install/get-started-docker.md — first domain, relay networks, first recipient or mailbox, DNS records. The admin dashboard surfaces two nudges (Placeholder hostname, Self-signed cert) that auto-clear when satisfied.

Migrating from legacy

Honest status as of v260119: a skeletal migration script exists at scripts/migrate_legacy_to_docker.sh that:

  • Extracts a backup created by the legacy system_backup.sh
  • Restores the legacy databases (hermes, djigzo, opendmarc, Syslog) into the new Docker MariaDB
  • Creates the Docker-only databases (authelia, nextcloud)
  • Copies legacy config trees (/opt/hermes/keys/, /opt/hermes/creds/, /etc/postfix/, /etc/amavis/, /etc/letsencrypt/, /etc/spamassassin/) into their Docker-mounted equivalents under config/

What it does not yet do:

  • Auto-detect the legacy install's 3-tier storage topology
  • Migrate Authelia users — the script provides manual guidance; Authelia user migration to LDAP is tracked at #150
  • Cover newer features that have no legacy analogue (NC user pre-provisioning, body milter signature/disclaimer config, organizational signatures, etc.)

The Docker-aware backup and restore refactors are still in flight as #219 (system_backup.sh) and #220 (system_restore.sh). The pre-v260119 upgrade-path test is also still open as #121.

Recommendation for v260119: treat it as fresh-install only (#231). If you're running a production legacy install, hold off on automated migration until a future release brings the Docker-aware backup/restore tooling to parity, or run a parallel Docker install on a second host to evaluate before cutting over by hand.

Storage requirements

Hermes splits storage across five independent tiers so each can sit on the right kind of disk. The install script prompts for four mount paths; the fifth (Config) is implicit and chosen by where you clone the repo.

| Tier | Default path | Contents | Storage profile |
|---|---|---|---|
| 1. Config | install root (implicit) | Repo working tree, config/ subtrees, install script, secrets under config/hermes/opt/hermes/keys/, .env | Fast SSD, modest size |
| 2. Data | /mnt/data | All databases (MariaDB, Authelia, OpenLDAP), Amavis runtime state, ClamAV signatures, Fangfrisch state, Lucee server home, Sieve scripts, all service logs, OpenDMARC, Postfix queue | Fast SSD; sized for DB growth + log retention |
| 3. Archive | /mnt/archive | Amavis quarantine archive | Cheap bulk; sized for retention policy × quarantine inflow |
| 4. Vmail | /mnt/vmail | Dovecot mailboxes | Cheap bulk; sized for users × quota |
| 5. Nextcloud | /mnt/files | Nextcloud app + user files + NC Redis cache | Cheap bulk; sized for user file storage |

Smaller deployments can collapse tiers — set Archive, Vmail, and Nextcloud to the same path as Data. All four prompts are required (empty would resolve to dangerous root-relative paths in device: substitution). See docs/install/storage-topology.md for the canonical reference.

What's next

  • Per-release updates from here on are calendar-dated (vYYMMDD). There is no v260120 unless a release actually ships on 2026-01-20 — the next tag is whatever date it cuts on.
  • updates/v260119/ is empty by design — v260119 IS the baseline. The first per-release directory with deltas (sql/, cfml/, scripts/) will be created for the next release.
  • Future NC upgrades land via NCVERSION bumps in .env.template. Operators don't edit NCVERSION — Hermes manages it through release engineering (#261) and validates each bump with scripts/test_nc_integration.sh before tagging. When you run system_update_docker.sh against a release that bumped NCVERSION, the orchestrator auto-runs occ upgrade, rehydrates required NC apps (user_oidc, mail, twofactor_totp, twofactor_backupcodes, external), and verifies post-upgrade state (#264).
  • Docker-aware backup/restore (#219, #220) is the next operationally-critical piece — once it lands, four-scope backups (system / archive / vmail / nextcloud / all) become first-class.
  • CFML migrations under updates/v<DATE>/cfml/ currently warn-and-skip in the orchestrator MVP. The first release that ships one will add a host→container mount for updates/ + a Lucee mapping; the orchestrator already curls the right URL pattern.
  • Persistent post-upgrade hook at config/hermes/var/www/html/schedule/post_upgrade.cfm ships with the framework + the migrations table in place but zero blocks registered. The first cross-release CFML migration that doesn't bind to a single release lands here.
  • Beta scope is fresh-install only (#231) — the migration path from legacy will catch up over subsequent releases.

Known limitations / things you'll want to know

Update orchestrator MVP gaps

The orchestrator that shipped 2026-05-26 is deliberately scoped. These limitations are documented in docs/install/release-and-update-methodology.md and tracked for v2:

| Limitation | What happens today | v2 plan |
|---|---|---|
| Service-restart detection | Always restarts hermes_commandbox; logs a reminder that admin may need to re-save certain config pages | Diff config files between previous and new tag; only restart containers whose volume-mounted config changed |
| <em>.HERMES template re-render | Phase 4 logs a reminder; admin must re-save the corresponding admin page manually | Detect modified template files and invoke each regen endpoint via curl into commandbox |
| occ upgrade | Already MVP-complete — Phase 4 auto-detects NCVERSION drift, runs occ upgrade, rehydrates Hermes-required NC apps, verifies post-upgrade state (#264) | — |
| updates/v<DATE>/cfml/</em>.cfm artifacts | Phase 3 logs a WARN and skips them | First release shipping a CFML migration adds a host→container mount for updates/ plus a Lucee mapping |
| Mid-upgrade resume | set -e fail-fast; operator re-runs from scratch and idempotency handles re-application | Track per-phase + per-release-artifact completion; resume from last successful step |
| GitHub Releases API auth | Anonymous polling; 60 req/hr per IP rate limit | Honor a GITHUB_TOKEN env var if present |

Backup/restore tooling is deferred to a future release

Docker-aware backup/restore is not shipped in v260119. The Docker rewrite changed the storage layout entirely (5-tier topology, six databases including the new Authelia + Nextcloud DBs, container-volume coordination, NC file-cache consistency), and the legacy bare-metal system_backup.sh / system_restore.sh scripts that older Hermes documentation refers to are not safe to run on a Docker install — in particular the legacy restore does cd / && tar -xvzf <backup>, which overwrites the host filesystem in a way that worked fine in the bare-metal era but corrupts a Docker host. Do not use them.

Recommended interim strategy: hypervisor / VM snapshots of the entire Hermes host (Proxmox, VMware, KVM, AWS EBS, Azure Disk, GCE, Hyper-V), taken with the VM powered off or quiesced via guest tools. This is the only backup strategy we currently recommend for Docker installs. Take a snapshot before running scripts/system_update_docker.sh for every upgrade — that gives you a working rollback if anything goes wrong during the upgrade.

If you need file-level rather than VM-level backups, stop the stack (docker compose down), tar the five tier paths plus the repo, then docker compose up -d. This is a manual cold-backup; the automated tooling that wraps it lands later.

Tracking:

  • #219system_backup.sh Docker refactor
  • #220system_restore.sh Docker refactor

The System > Backup/Restore admin page renders a placeholder notice with the same guidance. Full procedure documented at docs/admin/01-system/backup-restore.md.

Post-upgrade browser hard-refresh

After running system_update_docker.sh on a release that bumps NCVERSION or lands changes under config/hermes/var/www/html/admin/2/, hard-refresh any open admin or Nextcloud tab (Ctrl-Shift-R on Linux/Windows, Cmd-Shift-R on macOS). The browser cache often serves the pre-upgrade CSS/JS bundle even though the server is now serving the new one — most visibly as Nextcloud's top navbar collapsing to a vertical stack of icons. Release notes that bump those will call this out explicitly.

No CLI recovery path for admin lockout (yet)

If you misconfigure the console hostname, change your host IP without updating Hermes, or otherwise lock yourself out of the admin UI, there is no in-product CLI recovery tool yet (#173 tracks this). The current recovery path is: revert the most recent hypervisor / VM snapshot (the same snapshot you took before any change per the pre-upgrade snapshot guidance). The scripts/hermes-cli.sh menu-driven recovery tool described in #173 will land in a future release.

Nextcloud Maintenance Mode for admin access

NC admin tasks that require local-NC authentication (independent of Authelia / OIDC) go through System → Settings → Nextcloud Maintenance Mode card (#262). Toggling Maintenance Mode on stops user-facing NC access, exposes the local NC login form for admin work (using local NC admin credentials + TOTP enrolled at install time), and toggling it off restores normal SSO. This replaces the pre-v260119 /nc-admin-login path which had several edge-case bypasses.

Pro Edition vs Community Edition

Community Edition is fully functional and needs no license file. Pro Edition unlocks six advanced features:

| Pro Feature | What it does |
|---|---|
| Let's Encrypt (ACME) Automation | Automated issuance and renewal of free Let's Encrypt SSL certificates for the console and per-domain. Community can still request/use LE certs manually. |
| Email Disclaimers (#214) | Per-domain outbound disclaimer templates at the milter level |
| Organizational Signatures (#226) | Centrally-managed per-domain employee signatures with placeholder substitution. Community Edition has Personal Signatures only. |
| Intrusion Prevention (IPS) | Web UI for Fail2ban jails, ban thresholds, whitelists, real-time view of active bans. The hermes_fail2ban container runs on all editions; Pro gates the UI + which jails are active. |
| Console Firewall | Web UI for host firewall protecting the admin console (port allowlisting, source IP restriction) |
| LDAP RemoteAuth | Per-domain pass-through authentication to external LDAP / Active Directory; auto-provisions mailboxes on first successful login; supports STARTTLS and LDAPS |

License validation hits validate.hermesseg.io over HTTPS; cached locally so Pro stays available during brief network outages. See CLAUDE.md for the full state machine.

Repository / distribution

  • Code: GitLab — https://gitlab.deeztek.com/dedwards/hermes-seg-docker-gl.git
  • Distribution: GitHub — https://github.com/deeztek/Hermes-Secure-Email-Gateway
  • Container images: GitHub Container Registry — ghcr.io/deeztek/hermes-<service>:<tag>
  • Issues + Discussions + Releases: all on GitHub
  • Legacy bare-metal history: preserved on the GitHub legacy branch with build-* tags reachable

Subnet pinning

The Docker bridge subnet (IPV4SUBNET=172.16.32 in .env) is hardcoded in 15+ config files across Postfix (mynetworks, master.cf), Amavis (@inet_acl), Dovecot (login_trusted_networks), CipherMail (authorizedAddresses), OpenDKIM / OpenDMARC (TrustedHosts), and CFML queries. Do not change IPV4SUBNET after install — there's no current template path to propagate the change everywhere. Dynamic subnet support is on the backlog.

Vendored CipherMail binary

#258 tracks the vendored CipherMail .deb / .tar.xz files in the repo, which exceed GitHub's 50 MB recommended size warning. Functional but flagged for cleanup in a future release.

Online documentation

In-repo admin documentation under docs/admin/ covers 100% of the admin-console sidebar (60 docs). End-user documentation under docs/users/ is being written alongside the v260119 cut. Both will be synced to BookStack at the URL announced per release — sync script tracked at #259.

---

Welcome to the Docker era of Hermes SEG.