
Gulo Gulo¶
Gulo Gulo is a mail-first, tenant-isolated groupware platform: secure webmail, calendar, and contacts, distributed as cPanel, Plesk, and standalone packages built from the same TypeScript core. The guiding animal is the wolverine (Gulo gulo).
This site mirrors the project's own doc/ directory — see Guide in the
navigation for the full technical documentation (configuration, mail core,
DAV, identity, observability, upgrade/migration, and every other subsystem).
Why three packages¶
Nobody deploys a mail/groupware app the same way twice: some run it on a
hosting panel they already have, some run it on a bare Linux box they fully
control. One generic artifact can't serve both well, so each package targets
the OS its ecosystem actually runs on — cPanel and Plesk get a real,
OS-native package format; a bare Linux host gets a plain tarball and
install.sh. All three currently ship as a .tar.gz plus shell scripts
while code signing is still pending; see the
README
for the full rationale.
How Gulo Gulo sits behind Plesk or cPanel¶
If a tenant already runs its domain from Plesk or cPanel, Gulo Gulo can sit behind that panel as an optional upstream tool — the panel is never a second mailbox, policy, or identity store:
Tenant / provider
│
├── Plesk or cPanel (optional upstream account and DNS tool)
│ └── explicit provider adapter or webhook/pull boundary
│
└── Gulo Gulo
├── tenant policy and RBAC
├── LDAP identity
├── PostgreSQL application state
├── mailbox and DAV data
└── audit and read-only API/MCP
Gulo Gulo stays authoritative for tenant policy, users, quotas, aliases,
delegations, mailbox content, calendars, contacts, authentication decisions,
retention, audit semantics, and application state — a panel is never a
shortcut around those rules. The panel may own the hosting account and DNS
workflow; Gulo Gulo can read that state for diagnostics and reconciliation,
but the current contract grants no DNS or domain write access, so a panel
credential can never become an unbounded deployment or mail-control
credential. There is no direct browser-to-panel call, Docker socket, SSH
command, arbitrary CLI, or unrestricted kubectl path.
The integration is disabled by default (CONTROL_PANEL_ENABLED=false) and
supports three sync modes once enabled — pull (periodic read, the
safest starting point when the panel cannot sign callbacks), webhook
(a provider-authenticated change notification that triggers a scoped read,
never a write), and hybrid (both). See
Optional Plesk and cPanel integration
for the full configuration contract, ownership rules, and capability matrix.
Install and setup¶
- Install guide — build/install/upgrade instructions and known gaps, per target
- README — full project overview and the production readiness checklist
- Releases
— packaged
.tar.gzarchives for each target - Repository — source, issues, and CI
For the tenant/provider running the install¶
A production install is not "done" once install.sh exits — it is done once
these are verified against the real deployment (the complete checklist is in
the Install guide's field verification section):
- DNS, firewall, and reverse proxy (Apache on cPanel, nginx on Plesk, your own choice on standalone), plus certificate issuance/renewal and expiry alerting.
- The secret store you selected: least-privilege access, rotation, revoke, restart, and recovery behavior.
- Identity: external LDAP (TLS, bind privilege, directory filters, timeout/
retry, outage behavior) or, on standalone with
IDENTITY_SOURCE=database, the local-users migration and row-level security instead. - PostgreSQL: TLS, role grants, row-level security, migrations, backups, restore, and connection limits.
- If you enabled the optional Plesk/cPanel upstream integration: the account binding, API version, TLS, webhook or pull policy, DNS ownership, and credential rotation.
- Tenant isolation, roles, delegation, quota ceilings, aliases, and default-deny mailbox/calendar/contact access, exercised with a real tenant and user, not just fixtures.
- The local mail server is actually reachable on
127.0.0.1— Gulo Gulo only ever connects to it as a client, on SMTP (25/587/465) and IMAP (993), and never binds these itself. This makes it independent of which software provides them: any standards-compliant SMTP and IMAP4rev1 server works, with one real condition — the IMAP server must support the IDLE extension (RFC 2177) for live inbox updates. Gulo Gulo checks this itself, once per session, and the webmail UI shows a notice and switches to a manual/timed refresh when it is missing — see IMAP IDLE availability for exactly what that check does and does not do. TCP/25 is not guaranteed just because the host is up (many cloud VPS providers block outbound 25 account-wide by default); local IMAP is not guaranteed just because a panel is installed (a domain can be MX-only/external-mail). See the Install guide for both. - Vendor Rspamd, ClamAV, CalDAV, and CardDAV versions and configuration actually installed on the host (the mail server itself — Postfix/Exim + Dovecot are the common examples, cPanel defaults to Exim — is not a Gulo Gulo dependency, only the protocol above is).
None of this is optional polish — it is the difference between "the repository's own gate is green" and "this is safe to run in production." The production readiness checklist in the README tracks exactly that boundary.
Node.js or Bun¶
Node.js is the default runtime on every target and is always required at
install time regardless of choice — npm installs dependencies and runs
database migrations either way. Bun is available as an alternative runtime
for the compiled server itself. Every available option:
- At install time, pick the runtime up front:
./install.sh --runtime=node # the default, same as omitting the flag ./install.sh --runtime=bun - On an already-installed instance,
switch-runtime.shis the one script behind every option below — it takes the runtime name as its argument:./switch-runtime.sh node ./switch-runtime.sh bun - Shortcut wrappers, if you'd rather not type the runtime name — these
call
switch-runtime.shfor you, nothing else:./switch-to-node.sh ./switch-to-bun.sh
On cPanel and Plesk, switching re-renders and restarts the managed systemd
unit; on standalone it updates the recorded choice and prints the correct
manual start command, since that target never owns a systemd unit of its
own. The choice is recorded in a .runtime marker next to .env and
survives upgrade.sh.
Status¶
Nothing here was thrown together. Every checked item in the production readiness checklist has real code behind it, a real contract or runbook, and a CI gate that actually has to pass — the full test suite, linting, security scanning, and a real install-and-boot rehearsal in CI for every packaging target. That work is done and it stays green on every push.
What a green checklist honestly does not yet cover is a real deployment run through it end to end: a live external LDAP directory, a production PostgreSQL instance, an actual cPanel or Plesk account. That's the one gap still open — not a shortcut taken in the code, but the field verification that only happens against real infrastructure, which a repository checkout can't rehearse on its own. See the Install guide for exactly what still needs that verification on each target before production use.