Skip to content

Wolverine tearing through a calendar and paper correspondence

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.gz archives 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.sh is 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.sh for 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.