Install on a server (bare metal)
Zappocity is one Go binary, corpsuited, plus PostgreSQL 16. It runs directly on the host under systemd; no containers are involved. This guide is for the platform administrator.
What you need
- A Linux server with systemd, a public IPv4 address (and IPv6 if you have it), and a DNS name for it such as
mail.example.com. - PostgreSQL 16 on the same host (
apt install postgresqlon Debian and Ubuntu,dnf install postgresql-serveron Fedora and RHEL). - Go, at the version in
go.mod, andmake, for the user who builds. Root never needs Go. - Ports 25, 80, 143, 443, 465, 587, 993 and 4190 (ManageSieve) open in the firewall, and UDP 443 for HTTP/3.
Install
From a checkout of the repository:
make # as your own user: compresses the web assets, builds bin/corpsuited
sudo make install
sudo make db
sudoedit /etc/corpsuite/corpsuite.env
sudo make enable
What each step does:
| Step | Effect |
|---|---|
make | Builds a static binary (CGO_ENABLED=0, -trimpath) at bin/corpsuited. |
sudo make install | Creates the corpsuite system user if missing, installs /usr/local/bin/corpsuited, writes /etc/corpsuite/corpsuite.env from the template only if it does not exist (mode 0640, group corpsuite), and installs /etc/systemd/system/corpsuited.service. |
sudo make db | Creates the Postgres role corpsuite and the database corpsuite it owns. The service connects over the local Unix socket with peer authentication, so no database password exists anywhere. |
sudoedit …/corpsuite.env | Set at least CORPSUITE_HOSTNAME, the ACME settings (or CORPSUITE_TLS_CERT and CORPSUITE_TLS_KEY) and CORPSUITE_PLATFORM_API_KEY_SHA256. The README lists every setting. |
sudo make enable | Starts the service now and at boot. Database migrations run on every start. |
Create the platform key with make platform-key: give the key to VaultDrop and put only its SHA-256 in the env file.
Once the service runs, create the first administrator for the SaaS admin panel at https://<hostname>/admin:
sudo make admin EMAIL=you@example.com
It runs corpsuited set-admin as the service user with the service's settings and asks for the password. Run it again to reset a password. See the admin panel.
PREFIX, BINDIR, CONFDIR, UNITDIR, SVC_USER and DB_NAME can be overridden on the make command line, and DESTDIR stages an install for packaging.
How the service runs
- As the unprivileged
corpsuiteuser. Its only extra capability isCAP_NET_BIND_SERVICE, to listen on ports below 1024. - Zappocity's own tables (people, organizations, subscriptions) are in a
zappocityschema of the same database, migrated at start-up. SetCORPSUITE_PLATFORM_DATABASE_URLto keep them in a database of their own. - Zappocity and Mailocity are being made able to run on separate servers (docs/plans/daemon-split.md). The first part works today: set
CORPSUITE_SERVICE_TOKEN_SHA256on the mail server, andCORPSUITE_MAILOCITY_API_URLandCORPSUITE_SERVICE_TOKENwhere Zappocity runs, and provisioning reaches Mailocity over HTTPS. Until the remaining steps are done, run both in one process. - Data lives in
/var/lib/corpsuite(blobs/for messages,acme/for certificates), created by systemd with mode 0750. Every six hours the server deletes message files nothing uses any more (mail users deleted, uploads never attached) once they are a day old, so disk use follows what users keep. Compliance archive files are kept until the archive's retention ends. - The unit turns on systemd sandboxing: read-only system directories, no access to home directories, a private
/tmp, no new privileges, a system call filter, and only Unix, IPv4 and IPv6 sockets.systemd-analyze security corpsuitedshows the result on your host. - systemd knows when the server is ready (
Type=notify: once every listener is open) and watches it: the server pings the watchdog every 45 seconds while the database answers, and systemd restarts it if the pings stop for 90 seconds (WatchdogSec).systemctl status corpsuitedshows its status line. - Socket activation is optional:
deploy/systemd/sockets/has a.socketunit for each listener (smtp, submission, submissions, imap, imaps, managesieve, http, https). Copy the ones you want to/etc/systemd/system/,systemctl enable --nowthem and restart the service: systemd then opens those ports and hands them over, so connections wait in the queue across restarts instead of being refused. Listeners without a socket unit are opened by the server as before. - HTTP/3 (QUIC) listens on UDP at the HTTPS port, with the same certificates, and HTTPS answers tell browsers it is there (
Alt-Svc). Browsers use HTTP/2 when UDP is blocked, so a closed UDP port only costs speed. SetCORPSUITE_HTTP3_ADDRto listen elsewhere, or tooff. It has no socket unit: the server opens the UDP port itself. Linux's default UDP buffers are small for QUIC; raise them withsysctl -w net.core.rmem_max=7500000 net.core.wmem_max=7500000(and in/etc/sysctl.d/) if the log asks. - Logs go to the journal:
make logs, orjournalctl -u corpsuited. - The mail log (Exim-style mainlog, rejectlog and paniclog) is in
/var/log/corpsuite; rotate it with logrotate (see Mail log).
Upgrade
git pull
make
sudo make install # keeps your env file
sudo systemctl restart corpsuited
Back up
Back up the corpsuite database (sudo -u postgres pg_dump corpsuite), /var/lib/corpsuite and /etc/corpsuite/corpsuite.env. /var/lib/corpsuite/master.key decrypts the DKIM keys in the database: keep it with the database backup, but store the two apart if you can. The env file holds only a hash of the platform key, but treat it as sensitive.
Uninstall
sudo make uninstall stops the service and removes the binary and the unit. It keeps the env file, the data directory, the database and the user, so nothing is lost by accident; remove those by hand if you mean to.
Troubleshooting
- **
connect: no such file or directoryfor the database:** your distribution keeps the Postgres socket somewhere else. Find it withsudo -u postgres psql -c 'show unix_socket_directories'and changehost=inCORPSUITE_DATABASE_URL. - **
Peer authentication failed for user "corpsuite":** the service must run as thecorpsuiteOS user, andpg_hba.confneeds the stocklocal all all peerline. - Refuses to start without a certificate source: production needs ACME settings or certificate files. For a trial without them set
CORPSUITE_ENV=dev, which allows plaintext logins; never do that on a public server. - **
bind: permission denied:** the unit grantsCAP_NET_BIND_SERVICE; if you run the binary by hand as a normal user, use ports above 1024.
Local development
make dev-db creates a corpsuite_dev role with the password from DEV_DB_PASSWORD in .env, and the corpsuite_dev and corpsuite_test databases. Then make seed, make dev and make check. See the README.