SeraphDocumentation

seraph-tool.sh

Command reference for installing and managing the DirectAdmin integration. For initial setup, follow the DirectAdmin guide.

On this page

Version 12.

seraph-tool.sh prepares a DirectAdmin server with Exim and Dovecot to receive its mail through Seraph. Run it as root on that server. install does it all in one go (below); it keeps a copy in /usr/local/seraph, linked as /usr/local/sbin/seraph-tool.sh, so later commands work by the bare name. (With sudo on AlmaLinux, whose secure_path leaves out /usr/local/sbin, use the full path /usr/local/seraph/seraph-tool.sh.)

Upgrading from INBOX.spam to top-level Junk? Follow the migration runbook before installing the updated plugin: installation changes future default delivery; migrating existing mail remains a separate operation.

Exim needs DirectAdmin's hooks for its ACLs, including the one for mail sent on the server itself (/etc/exim.acl_script.pre.conf). Setup refuses without it: a forged verdict in such mail would be trusted. da build exim_conf gives an older exim.conf the hooks.

Dovecot 2.3 or 2.4 needs Pigeonhole for Sieve. This shows if CustomBuild has it on:

grep -E '^(dovecot|dovecot_conf|pigeonhole)=' /usr/local/directadmin/custombuild/options.conf

All three should say yes. If pigeonhole says no, these lines switch it on and rebuild Dovecot:

da build set pigeonhole yes
da build dovecot

The build takes a few minutes of CPU. Then Dovecot restarts, so IMAP and POP3 clients reconnect.

If dovecot or dovecot_conf says no, CustomBuild doesn't manage Dovecot on this server. Then install Pigeonhole for that Dovecot yourself. --install-deps won't do it there.

What it changes

The tool only uses files that survive da build and DirectAdmin updates. Some files are shared with DirectAdmin or with you. In those, it only changes what's between its own # BEGIN SERAPH and # END SERAPH lines.

File For
/etc/exim.variables.conf.post the host lists: Seraph, and your old filter during the switch; the domains the port-25 lock covers
/etc/exim.acl_check_recipient.pre.conf header trust and the port-25 lock
/etc/exim.acl_script.pre.conf header trust for mail sent on the server itself
/etc/virtual/seraph_hosts_ip, seraph_transition_ip Seraph's hosts and your old filter's addresses
/etc/virtual/seraph_locked_domains the domains the port-25 lock covers, as the control plane lists them
/etc/virtual/seraph_fallback_ip a delivery provider's addresses, let through the port-25 lock while a domain here uses it as fallback
/etc/virtual/seraph_relay_senders after outbound on: the senders Seraph relays
/etc/virtual/whitelist_hosts, whitelist_hosts_ip DirectAdmin's whitelist, with --whitelist yes
/etc/dovecot/conf.d/99-seraph.conf the Sieve rule that puts junk in the spam folder
/etc/dovecot/conf.d/00-seraph-learn.conf learning from the spam folder, after learn on
/etc/dovecot/conf.d/99-seraph-learn-imap.conf on Dovecot 2.3: the IMAP plugin learning needs
/etc/exim.routers.pre.conf, exim.transports.pre.conf, exim.authenticators.post.conf after outbound on: outbound mail through Seraph's relay
/etc/exim.seraph_relay after outbound on: the server's relay secret, readable by root and Exim's group only
/usr/local/seraph/relay.id after outbound on: whose relay secret /etc/exim.seraph_relay is
/etc/sudoers.d/seraph-plugin, /usr/local/libexec/seraph-plugin, seraph-plugin-events, /etc/cron.d/seraph-plugin the plugin's root helper, its sudo rule and its background run
/etc/systemd/system/seraph-learn-janitor.timer learning's check every 15 minutes: old samples out, room for new ones
/etc/systemd/system/seraph-learn-upload.timer with personal learning: corrections to the control plane every 2 minutes
/etc/systemd/system/seraph-refresh.timer after enrol: Seraph's delivery hosts from the control plane every 5 minutes
/usr/local/seraph/ the tool's own copy, Sieve scripts, seraph-learn, receiver.conf, backups, actions.log, samples and exports
/usr/local/seraph/client/ after enrol: the control plane's PHP client, client.php and cli.php
/usr/local/seraph/cp/ after enrol: this server's token and the control plane's address, root's, readable by the system user seraph alone (its cache is there too)
/usr/local/sbin/seraph-tool.sh a link to the copy in /usr/local/seraph, for the bare name; a file of yours there stays
the system user seraph after enrol: runs the plugin and the control plane's client, without a shell or a login

The scheduled jobs are systemd timers (each with its .service), and each service runs sandboxed: the system read-only but for what that job writes, no new privileges, only the capabilities and address families it needs. The janitor has no network; refresh may write in /etc/virtual and /usr/local/seraph only. Where systemd isn't running (a container), they are cron jobs in /etc/cron.d/seraph-learn and seraph-hosts instead; setup moves older cron jobs to timers.

install

The one path for a server with a Seraph account: from nothing to connected, set up, the plugin installed and outbound mail through Seraph.

Seraph sends you a one-line command and a code. Run the command as root on the server and type the code when it asks for it (not shown as you type; or put it in SERAPH_ENROL), never on the command line. It works once, within 24 hours. The command checks the code with Seraph before anything changes, then fetches this tool, checks it, and runs seraph-tool.sh install --cp <the control plane>.

install runs every step from a copy of the fetched file in a folder of root's, a line each: ok, skip with why, or FAIL with why and what to run. The first FAIL stops it; fix that and run it again.

  1. preflight: root, DirectAdmin, Exim with its hooks, Dovecot with Pigeonhole, PHP with json and curl or openssl. Everything setup and enrol would refuse, at once, before anything changes.
  2. enrol: skipped while the control plane takes this server's token. When it no longer does (revoked there), it asks for a new code.
  3. setup with the hosts and key from the control plane (setup --from-cp).
  4. plugin: plugin install, skipped when that package is installed already.
  5. outbound: outbound on, skipped until Seraph has set the server up for its relay (a FAIL when outbound mail already goes to the relay here: it waits until Seraph has, or until outbound off).
  6. report: the server's state to the control plane, then check (a refusal there is a FAIL too).
Option
--cp <url> the control plane; afterwards the one in /usr/local/seraph/cp
--without plugin|outbound leave a step out (a comma list, or the option twice)

Run it again any time (a server set up with --whitelist no keeps that): it never enrols while the token works, and setup, plugin and outbound change nothing (and reload neither Exim nor Dovecot) when nothing changed. A run stopped before it reloaded Exim or Dovecot leaves that in /usr/local/seraph/reload-pending, so the next one reloads them anyway. The plugin package brings its own copy of the tool; the one install ran keeps its place (with a warning when the versions differ). If the answer to the enrolment never arrives, the code may be spent: run install again with it, and ask Seraph for a new one if the control plane says it is used. The tool's sha256 and the plugin's version and sha256 go to actions.log. If the control plane enrolled the server but its answer can't be kept, the line says so: ask Seraph for a new code and run install again. If /usr/local/seraph/cp can't be written, nothing is sent and the code still works.

check

Shows what's set up. It changes nothing. It lists:

seraph-tool.sh check

outbound

Sends the server's outbound mail through Seraph's relay, which checks and counts it and passes it on through a delivery provider such as MailChannels, or delivers it itself. Seraph sets the server up for its relay when it enrols; the server then sends as its own domains and its own name. install turns it on.

seraph-tool.sh outbound on
seraph-tool.sh outbound status
seraph-tool.sh outbound off

on names the domains on the server that have no way out at Seraph yet: they keep their old path until Seraph routes them (a new domain once its SPF include and provider records are seen). It refuses until Seraph has set the server up for its relay (ask Seraph). It fetches the server's secret once (outbound rotate fetches a new one, and so does a new enrolment or a change of control plane or login: /usr/local/seraph/relay.id says whose it is) and routes mail from the server's own domains and name, for other servers, to the relay: over port 587, TLS with the relay's certificate checked, logged in. It goes ahead of a smarthost you had in /etc/exim.routers.pre.conf, and on refuses while a router in exim.conf itself would still take the mail first. DirectAdmin's DKIM signing and sending limits apply as before. The relay is told who sent each message: the mailbox that logged in to Exim, or the local user for scripts. A sender can't set that themselves.

While Seraph defers or can't be reached, the mail waits on the server and Exim retries it; it never goes another way. Mail the server makes itself (bounces, with an empty sender), and forwards that keep the original outside sender (DirectAdmin without SRS), go as they did before. off puts the server back as it was, and is the way back should Seraph's relay be down for long.

The port-25 lock

For a locked domain, port 25 takes mail only from Seraph, localhost and logged-in users. Mail clients keep using ports 587 and 465. Other hosts get a temporary refusal (451), never a bounce: a domain locked too early, or a Seraph node this server doesn't know yet, delays mail.

Each domain locks by itself. setup installs the lock; the control plane lists a domain 24 hours after it sees the domain's MX at Seraph alone (senders may still have the old MX until then), and refresh writes that list every 5 minutes. Nobody runs a command. A domain leaves the list at once when its MX moves away, its claim goes, or Seraph keeps it open at your request. The plugin's Restore takes the domain off the list here as it writes the old MX back. refresh keeps the list as it is when the control plane can't be reached or sends names that don't read as domains; it fetches Seraph's delivery hosts the same way. check shows the list's size and when the control plane last confirmed it.

seraph-tool.sh lock --transition "<old filter's addresses>"
seraph-tool.sh lock
seraph-tool.sh unlock
seraph-tool.sh unlock --domain <domain>

learn

Learning from the spam folder. Users already move spam into the spam folder. With learning on, your server keeps a copy of that mail for Seraph, which uses it to make the filter better. Learning is off until you switch it on, for each domain.

seraph-tool.sh learn on example.nl              # spam: mail moved into the spam folder
seraph-tool.sh learn on example.nl --ham yes    # good mail too: mail moved out of it
seraph-tool.sh learn                            # what learns, and what waits to be sent
seraph-tool.sh learn export                     # the samples, encrypted, for Seraph
seraph-tool.sh learn off example.nl

Personal learning is separate. A mailbox can have its own filter that learns from its user's corrections. Its user turns it on in the DirectAdmin plugin ("Learn from corrections"), and the plugin runs seraph-tool.sh learn personal sync <domain>, which asks Seraph who learns. Only those mailboxes' moves into and out of the spam folder are kept, with the mailbox (/usr/local/seraph/learn-personal.conf lists them). Every 2 minutes learn upload (a timer; cron in a container) asks again and sends them to Seraph; one whose mailbox turned off meanwhile is not sent. They are then deleted, unless the domain also shares that kind of mail with Seraph (learn on); then the next export includes them too, without the mailbox. learn personal on|off <domain> does the same by hand. It uses the same Dovecot rules as learn on.

Samples are personal data. Please read this first:

How learning stays out of the way of mail:

Samples must never fill a disk or a quota. They're files of the domain's owner in DirectAdmin, but they count towards that user's disk quota only where the quota covers /usr/local (one filesystem for / and /home). So learning keeps nothing for a DirectAdmin user while:

A light job checks this every 15 minutes (a timer; cron in a container), at the lowest CPU and disk priority. It also deletes samples older than 30 days (by when they were written, which a user can't change), whether or not anyone uses learning. A user's move never waits for it. learn shows for whom nothing is kept, and why.

One limit on Dovecot 2.3: when learning has to add to sieve_plugins or sieve_global_extensions, it copies your current values into its own file. If you later change them in dovecot.conf itself, learning's file keeps the old ones. Change them in a conf.d file instead, or run learn off and learn on again.

enrol, domains, settings and senders

With these, Seraph's control plane manages this server's domains. install enrols; enrol alone does only that step. Seraph gives you an enrolment code, which works once, within 24 hours. Type it at the prompt, or put it in SERAPH_ENROL; never on the command line. To enrol again after Seraph revoked the server, run seraph-tool.sh install and type the new code.

seraph-tool.sh enrol --cp <the control plane's address, from Seraph>

Before it sends the code, enrol checks that it can write the enrolment: a server that can't keep its token never uses up a code.

enrol creates a system user, seraph, without a shell or a login. Root writes this server's own token and the control plane's address in /usr/local/seraph/cp; seraph can read them, not change them. So where root sends the token (refresh, plugin install) is never that user's choice. seraph runs the PHP client: DirectAdmin's /usr/local/bin/php, else the system's, without php.ini. The client uses curl when PHP has it, else PHP's own https streams, so it needs either curl or openssl. Enrolling tells Seraph the tool's version, whether header trust is on, how the lock covers the domains (every domain, or the list, by its sha256), and the PHP version; setup, lock, unlock and a refresh that changes the list tell it again.

seraph-tool.sh domains push example.nl,shop.example.nl    # or all; each waits for Seraph to approve it
seraph-tool.sh domains alias example.com example.nl       # example.com's mail is example.nl's
seraph-tool.sh domains                                    # each domain: waiting, active, live, its MX
seraph-tool.sh domains checks example.nl                  # what still has to happen, and how
seraph-tool.sh domains check example.nl                   # the same, with the DNS looked at now
seraph-tool.sh domains dns example.nl                     # the records Seraph wants, and what DNS has
seraph-tool.sh settings example.nl                        # each setting, and where it comes from
seraph-tool.sh settings info@example.nl sensitivity=strict classes.newsletter=junk
seraph-tool.sh settings info@example.nl sensitivity=      # inherits from the domain again
seraph-tool.sh senders example.nl allow partner.example   # or block; unlist allow|block takes it off
seraph-tool.sh status

A push sends each domain's MX as it is now: domains dns lists the records Seraph wants by phase (the ownership TXT while it waits for approval, MX, the SPF include and a provider's record) with what DNS has for each (found, missing, or another value), what is left out and why, the nameservers it asked, and the records from before, if the domain leaves Seraph. domains check asks the control plane to look at the domain's DNS now (at most every 20 seconds) and prints the checks. Change the MX only once domains dns says the domain is live. If the domain has an MTA-STS policy, update the policy first.

The plugin points records only into Seraph's DNS zones. seraph-tool.sh dns-zones list shows them, and dns-zones set <zone ...> sets them, as root. Setup offers the list in its confirmation, from the delivery hosts' zones and the control plane's; refresh never writes it; a public suffix such as co.uk is never a zone; and check names what the control plane uses that the list lacks.

When the control plane can't be reached, settings, senders, domains and status show what they last saw and say so. A change then fails and changes nothing. remove deletes the token, the client, the plugin's sudo rule and root helper, and then the seraph user. Seraph still lists the server until it revokes it.

setup

Sets up the server for Seraph. Run it again to update an earlier setup; when nothing changes, it reloads nothing.

seraph-tool.sh setup --from-cp
Option
--seraph "<hosts>" Seraph's delivery hosts, as hostnames or addresses
--from-cp read them from the control plane this server is enrolled with (install does this)
--key <key> Seraph's public key, for learn export. --from-cp takes it from the control plane
--whitelist yes|no let DirectAdmin's own checks skip Seraph's mail. The default is yes, or what a server set up before has
--install-deps if Pigeonhole is missing, run the two da build lines above, after asking. Only where CustomBuild already manages Dovecot and its configuration

Without options, setup asks for the hosts. On an enrolled server, --from-cp takes the hosts and the key from the control plane, as install does.

remove

Removes everything again: header trust, junk filing, the lock, outbound through Seraph, the whitelist entries and learning. Samples you haven't sent are deleted. Run learn export first to keep them.

seraph-tool.sh remove
seraph-tool.sh remove --purge

remove keeps the backups, the log and earlier exports in /usr/local/seraph. --purge also deletes /usr/local/seraph, the link in /usr/local/sbin and the script itself, once Exim and Dovecot run without Seraph. Nothing of the tool is left.

Stop filtering every domain in the plugin before you remove it.

DirectAdmin plugin

The Mail Filter plugin adds admin, reseller and user pages for domains, filtering, sender lists, readiness checks, DNS and learning. It reuses this server's enrolment automatically. install installs it; to install or update it alone, with the package from the control plane:

seraph-tool.sh plugin install

It is skipped when that package (its version, and its sha256 kept beside it) is installed already; plugin install --force installs it again anyway. The package is unpacked under /usr/local/seraph (never /tmp, which may be noexec); a failure says what its installer printed.