mirror of
https://github.com/profullstack/agentbbs.git
synced 2026-08-13 14:27:27 +00:00
Merge pull request #17 from profullstack/feat/mailu-stack
Self-host Mailu mail stack at mail.profullstack.com
This commit is contained in:
commit
8113b6d60e
8 changed files with 464 additions and 1 deletions
3
deploy/mailu/.gitignore
vendored
Normal file
3
deploy/mailu/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
mailu.env
|
||||||
|
certs/
|
||||||
|
data/
|
||||||
53
deploy/mailu/README.md
Normal file
53
deploy/mailu/README.md
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
# deploy/mailu — self-hosted mail for `mail.profullstack.com`
|
||||||
|
|
||||||
|
Mailu (Postfix + Dovecot + Roundcube + rspamd) as a Docker Compose stack,
|
||||||
|
fronted by the host Caddy. Full setup, DNS, and architecture: [`docs/mail.md`](../../docs/mail.md).
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `docker-compose.yml` | the Mailu services (mail ports on host, HTTP on loopback) |
|
||||||
|
| `mailu.env.example` | config template → copy to `mailu.env` and fill secrets |
|
||||||
|
| `refresh-certs.sh` | copy Caddy's `mail.$DOMAIN` cert into Mailu, reload (timer) |
|
||||||
|
| `provision-mailbox.sh` | create a member mailbox / the gateway master user |
|
||||||
|
|
||||||
|
`mailu.env`, `certs/`, and `data/` are gitignored (secrets + state).
|
||||||
|
|
||||||
|
## Gateway master user
|
||||||
|
|
||||||
|
The agentbbs gateway opens any member's mailbox over IMAP with a single secret,
|
||||||
|
using Dovecot's **master user** feature (login `<name>*<master>`). Enable it with
|
||||||
|
a Dovecot override so Mailu accepts the `*` separator:
|
||||||
|
|
||||||
|
`data/overrides/dovecot/auth-master.conf`:
|
||||||
|
|
||||||
|
```
|
||||||
|
auth_master_user_separator = *
|
||||||
|
passdb {
|
||||||
|
driver = static
|
||||||
|
args = nopassword=y
|
||||||
|
master = yes
|
||||||
|
result_success = continue
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then create the master account and point agentbbs at it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./provision-mailbox.sh --master "$(openssl rand -hex 16)"
|
||||||
|
# AGENTBBS_MAIL_MASTER_USER=gateway, AGENTBBS_MAIL_MASTER_PASS=<that secret>
|
||||||
|
```
|
||||||
|
|
||||||
|
> The exact master-passdb wiring varies by Mailu version; verify against your
|
||||||
|
> pinned image before relying on it in production. SMTP submission from the
|
||||||
|
> gateway uses the trusted local relay (`127.0.0.1:25`), not the master user.
|
||||||
|
|
||||||
|
## Ops
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d # start
|
||||||
|
docker compose logs -f smtp # tail Postfix
|
||||||
|
docker compose exec admin flask mailu config-export # DKIM keys, etc.
|
||||||
|
docker compose down # stop
|
||||||
|
```
|
||||||
83
deploy/mailu/docker-compose.yml
Normal file
83
deploy/mailu/docker-compose.yml
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
# Mailu stack for mail.profullstack.com — self-hosted Postfix + Dovecot +
|
||||||
|
# Roundcube + rspamd. Coexists with the host Caddy: Mailu owns the mail ports
|
||||||
|
# (25/465/587/993/995) and serves HTTP on loopback only; Caddy fronts the
|
||||||
|
# webmail at https://mail.profullstack.com and supplies the TLS cert
|
||||||
|
# (TLS_FLAVOR=mail, certs copied by refresh-certs.sh).
|
||||||
|
#
|
||||||
|
# Pinned to a Mailu release; bump deliberately. See docs/mail.md.
|
||||||
|
x-environment: &default-environment
|
||||||
|
env_file: mailu.env
|
||||||
|
|
||||||
|
services:
|
||||||
|
redis:
|
||||||
|
image: redis:alpine
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- "./data/redis:/data"
|
||||||
|
|
||||||
|
front:
|
||||||
|
image: ghcr.io/mailu/nginx:2024.06
|
||||||
|
restart: always
|
||||||
|
env_file: mailu.env
|
||||||
|
ports:
|
||||||
|
# Mail ports bound on the host; HTTP only on loopback for Caddy.
|
||||||
|
- "25:25"
|
||||||
|
- "465:465"
|
||||||
|
- "587:587"
|
||||||
|
- "993:993"
|
||||||
|
- "995:995"
|
||||||
|
- "127.0.0.1:8080:80"
|
||||||
|
volumes:
|
||||||
|
- "./certs:/certs"
|
||||||
|
- "./data/overrides/nginx:/overrides:ro"
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
|
||||||
|
admin:
|
||||||
|
image: ghcr.io/mailu/admin:2024.06
|
||||||
|
restart: always
|
||||||
|
env_file: mailu.env
|
||||||
|
volumes:
|
||||||
|
- "./data/data:/data"
|
||||||
|
- "./data/dkim:/dkim"
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
|
||||||
|
imap:
|
||||||
|
image: ghcr.io/mailu/dovecot:2024.06
|
||||||
|
restart: always
|
||||||
|
env_file: mailu.env
|
||||||
|
volumes:
|
||||||
|
- "./data/mail:/mail"
|
||||||
|
- "./data/overrides/dovecot:/overrides:ro"
|
||||||
|
depends_on:
|
||||||
|
- front
|
||||||
|
|
||||||
|
smtp:
|
||||||
|
image: ghcr.io/mailu/postfix:2024.06
|
||||||
|
restart: always
|
||||||
|
env_file: mailu.env
|
||||||
|
volumes:
|
||||||
|
- "./data/mailqueue:/queue"
|
||||||
|
- "./data/overrides/postfix:/overrides:ro"
|
||||||
|
depends_on:
|
||||||
|
- front
|
||||||
|
|
||||||
|
antispam:
|
||||||
|
image: ghcr.io/mailu/rspamd:2024.06
|
||||||
|
restart: always
|
||||||
|
env_file: mailu.env
|
||||||
|
volumes:
|
||||||
|
- "./data/filter:/var/lib/rspamd"
|
||||||
|
- "./data/overrides/rspamd:/overrides:ro"
|
||||||
|
depends_on:
|
||||||
|
- front
|
||||||
|
|
||||||
|
webmail:
|
||||||
|
image: ghcr.io/mailu/roundcube:2024.06
|
||||||
|
restart: always
|
||||||
|
env_file: mailu.env
|
||||||
|
volumes:
|
||||||
|
- "./data/webmail:/data"
|
||||||
|
depends_on:
|
||||||
|
- front
|
||||||
44
deploy/mailu/mailu.env.example
Normal file
44
deploy/mailu/mailu.env.example
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
# Mailu configuration for mail.profullstack.com — copy to deploy/mailu/mailu.env
|
||||||
|
# and fill the secrets. See docs/mail.md for the full setup (DNS, certs, gateway).
|
||||||
|
#
|
||||||
|
# Generate secrets with: openssl rand -hex 16
|
||||||
|
|
||||||
|
# --- General -----------------------------------------------------------------
|
||||||
|
SECRET_KEY=CHANGEME_16_HEX # openssl rand -hex 16
|
||||||
|
DOMAIN=mail.profullstack.com # member addresses are <name>@mail.profullstack.com
|
||||||
|
HOSTNAMES=mail.profullstack.com,smtp.profullstack.com
|
||||||
|
POSTMASTER=postmaster
|
||||||
|
# Apex profullstack.com is reserved for corporate mail and is NOT served here.
|
||||||
|
|
||||||
|
# TLS_FLAVOR=mail: Mailu does NOT run its own ACME (Caddy owns :80/:443). We feed
|
||||||
|
# it certs copied from Caddy's mail.profullstack.com cert (deploy/mailu/refresh-certs.sh).
|
||||||
|
TLS_FLAVOR=mail
|
||||||
|
|
||||||
|
# --- Features ----------------------------------------------------------------
|
||||||
|
ADMIN=true # the admin UI (fronted at /admin via Caddy, internal only)
|
||||||
|
WEBMAIL=roundcube # the only member-facing surface (https://mail.profullstack.com)
|
||||||
|
WEBDAV=none
|
||||||
|
ANTIVIRUS=none # set to clamav on a 4GB+ host
|
||||||
|
ANTISPAM=true
|
||||||
|
|
||||||
|
# --- Networking --------------------------------------------------------------
|
||||||
|
# Mailu's front binds the mail ports on the host and HTTP on loopback only;
|
||||||
|
# Caddy reverse-proxies https://mail.profullstack.com to BIND_ADDRESS4:80.
|
||||||
|
BIND_ADDRESS4=127.0.0.1
|
||||||
|
SUBNET=192.168.203.0/24
|
||||||
|
MESSAGE_SIZE_LIMIT=52428800 # 50 MB
|
||||||
|
|
||||||
|
# --- Gateway (the BBS reads/sends on behalf of members) ----------------------
|
||||||
|
# A Dovecot master user lets the agentbbs gateway open any member's mailbox with
|
||||||
|
# one secret (login "<name>*<master>"). Created by deploy/mailu/provision-mailbox.sh.
|
||||||
|
# Mirror these into the agentbbs service env:
|
||||||
|
# AGENTBBS_MAIL_DOMAIN=mail.profullstack.com
|
||||||
|
# AGENTBBS_MAIL_IMAP_ADDR=mail.profullstack.com:993
|
||||||
|
# AGENTBBS_MAIL_SMTP_ADDR=127.0.0.1:25
|
||||||
|
# AGENTBBS_MAIL_MASTER_USER=gateway
|
||||||
|
# AGENTBBS_MAIL_MASTER_PASS=<the master password you set>
|
||||||
|
|
||||||
|
# --- Admin bootstrap ---------------------------------------------------------
|
||||||
|
INITIAL_ADMIN_ACCOUNT=admin
|
||||||
|
INITIAL_ADMIN_DOMAIN=mail.profullstack.com
|
||||||
|
INITIAL_ADMIN_PW=CHANGEME_admin_password
|
||||||
50
deploy/mailu/provision-mailbox.sh
Executable file
50
deploy/mailu/provision-mailbox.sh
Executable file
|
|
@ -0,0 +1,50 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# provision-mailbox.sh — create or update a member mailbox on the Mailu stack.
|
||||||
|
# Run on the mail host. The agentbbs gateway opens any member's mailbox via the
|
||||||
|
# Dovecot master user, so members never need an individual IMAP password — but
|
||||||
|
# the mailbox must exist, which is what this creates.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# provision-mailbox.sh <name> # create <name>@$DOMAIN (random pw)
|
||||||
|
# provision-mailbox.sh --master <pass> # (re)create the gateway master user
|
||||||
|
#
|
||||||
|
# Idempotent: re-running for an existing user is a no-op (or a password reset
|
||||||
|
# with --password). Wraps Mailu's admin CLI (flask mailu ...).
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
MAILU_DIR="${MAILU_DIR:-/opt/agentbbs/deploy/mailu}"
|
||||||
|
DOMAIN="${MAIL_DOMAIN:-mail.profullstack.com}"
|
||||||
|
MASTER_USER="${AGENTBBS_MAIL_MASTER_USER:-gateway}"
|
||||||
|
QUOTA_BYTES="${MAIL_QUOTA_BYTES:-1000000000}" # 1 GB
|
||||||
|
|
||||||
|
cli() { ( cd "$MAILU_DIR" && docker compose exec -T admin flask mailu "$@" ); }
|
||||||
|
|
||||||
|
if [ "${1:-}" = "--master" ]; then
|
||||||
|
pass="${2:?usage: provision-mailbox.sh --master <password>}"
|
||||||
|
# A Dovecot master user can authenticate as any mailbox: login "<name>*gateway".
|
||||||
|
# Implemented in Mailu as a normal user flagged for master access via an
|
||||||
|
# override (see docs/mail.md); here we ensure the account + password exist.
|
||||||
|
cli user "$MASTER_USER" "$DOMAIN" "$pass" 2>/dev/null \
|
||||||
|
|| cli password "$MASTER_USER" "$DOMAIN" "$pass"
|
||||||
|
echo "gateway master user ${MASTER_USER}@${DOMAIN} set"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
name="${1:?usage: provision-mailbox.sh <name>}"
|
||||||
|
pass="${2:-$(openssl rand -hex 16)}"
|
||||||
|
|
||||||
|
if cli user-import "$name" "$DOMAIN" "$(openssl passwd -6 "$pass")" 2>/dev/null; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
# already exists or older CLI: fall back to `user` (no-op if present)
|
||||||
|
cli user "$name" "$DOMAIN" "$pass" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
# Enforce a per-mailbox quota.
|
||||||
|
cli config-update <<EOF 2>/dev/null || true
|
||||||
|
users:
|
||||||
|
- email: ${name}@${DOMAIN}
|
||||||
|
quota_bytes: ${QUOTA_BYTES}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "mailbox ${name}@${DOMAIN} provisioned"
|
||||||
41
deploy/mailu/refresh-certs.sh
Executable file
41
deploy/mailu/refresh-certs.sh
Executable file
|
|
@ -0,0 +1,41 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# refresh-certs.sh — copy Caddy's Let's Encrypt cert for mail.$DOMAIN into the
|
||||||
|
# Mailu certs dir (TLS_FLAVOR=mail), so Postfix/Dovecot TLS on 465/587/993 track
|
||||||
|
# Caddy's auto-renewals. Mirrors deploy/news-refresh-certs.sh: Caddy is the only
|
||||||
|
# ACME client on the box (it serves the mail.$DOMAIN site block), and we reuse
|
||||||
|
# that cert rather than running a second ACME client inside Mailu.
|
||||||
|
#
|
||||||
|
# Install to /usr/local/bin/agentbbs-mailu-certs and run from a timer. Reloads
|
||||||
|
# the Mailu front/smtp/imap so the new cert is picked up. Exits non-zero
|
||||||
|
# (touching nothing) until Caddy has issued the cert.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
DOMAIN="${DOMAIN:?set DOMAIN}"
|
||||||
|
MAIL_HOST="${MAIL_HOST:-mail.${DOMAIN}}"
|
||||||
|
MAILU_DIR="${MAILU_DIR:-/opt/agentbbs/deploy/mailu}"
|
||||||
|
CERT_DIR="${CERT_DIR:-$MAILU_DIR/certs}"
|
||||||
|
CADDY_DATA="${CADDY_DATA:-/var/lib/caddy/.local/share/caddy}"
|
||||||
|
|
||||||
|
# Caddy stores certs under certificates/<acme-dir>/<host>/<host>.{crt,key};
|
||||||
|
# the ACME directory segment varies (prod vs staging), so glob for it.
|
||||||
|
crt="$(ls "$CADDY_DATA"/certificates/*/"$MAIL_HOST"/"$MAIL_HOST".crt 2>/dev/null | head -1 || true)"
|
||||||
|
key="$(ls "$CADDY_DATA"/certificates/*/"$MAIL_HOST"/"$MAIL_HOST".key 2>/dev/null | head -1 || true)"
|
||||||
|
if [ -z "$crt" ] || [ -z "$key" ]; then
|
||||||
|
echo "no Caddy cert for $MAIL_HOST yet (looked under $CADDY_DATA/certificates)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
install -d -m 0750 "$CERT_DIR"
|
||||||
|
|
||||||
|
changed=0
|
||||||
|
# Mailu (TLS_FLAVOR=mail) reads cert.pem / key.pem from its /certs mount.
|
||||||
|
if ! cmp -s "$crt" "$CERT_DIR/cert.pem"; then install -m 0644 "$crt" "$CERT_DIR/cert.pem"; changed=1; fi
|
||||||
|
if ! cmp -s "$key" "$CERT_DIR/key.pem"; then install -m 0640 "$key" "$CERT_DIR/key.pem"; changed=1; fi
|
||||||
|
|
||||||
|
if [ "$changed" = 1 ]; then
|
||||||
|
echo "updated Mailu TLS cert for $MAIL_HOST; reloading Mailu"
|
||||||
|
( cd "$MAILU_DIR" && docker compose restart front smtp imap >/dev/null 2>&1 || true )
|
||||||
|
else
|
||||||
|
echo "Mailu TLS cert for $MAIL_HOST already current"
|
||||||
|
fi
|
||||||
118
docs/mail.md
Normal file
118
docs/mail.md
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
# Mail — self-hosted Mailu at `mail.profullstack.com`
|
||||||
|
|
||||||
|
AgentBBS gives **Founding Lifetime (paid) members** a real mailbox at
|
||||||
|
`<name>@mail.profullstack.com`, reached two ways:
|
||||||
|
|
||||||
|
- **Webmail** — `https://mail.profullstack.com` (Roundcube), the only
|
||||||
|
member-facing mail surface.
|
||||||
|
- **AgentMail** — the in-BBS client (`internal/mailbox`): the `Mail` hub entry
|
||||||
|
or `ssh mail@bbs.profullstack.com` (a TUI for humans, a JSON bot mode for
|
||||||
|
agents). It connects to this stack.
|
||||||
|
|
||||||
|
The apex `profullstack.com` is **reserved for corporate mail** and is not served
|
||||||
|
here — member mail lives only on the `mail.` subdomain.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
The host already runs **Caddy** (owns `:80`/`:443`) and the **agentbbs** process.
|
||||||
|
Mailu (Postfix + Dovecot + Roundcube + rspamd) runs as a Docker Compose stack:
|
||||||
|
|
||||||
|
- Mailu owns the **mail ports** on the host: `25, 465, 587, 993, 995`.
|
||||||
|
- Mailu's HTTP front is bound to **loopback** (`127.0.0.1:8080`); **Caddy**
|
||||||
|
reverse-proxies `https://mail.profullstack.com` to it (webmail + admin).
|
||||||
|
- **TLS:** `TLS_FLAVOR=mail` — Mailu does *not* run its own ACME (Caddy is the
|
||||||
|
only ACME client). Caddy obtains the `mail.profullstack.com` cert from its site
|
||||||
|
block; [`deploy/mailu/refresh-certs.sh`](../deploy/mailu/refresh-certs.sh)
|
||||||
|
copies it into Mailu and reloads it on renewal — the same pattern as the
|
||||||
|
Ergo/IRC and NNTP cert refreshers.
|
||||||
|
- The **agentbbs gateway** reads/sends on behalf of members: IMAP via a Dovecot
|
||||||
|
**master user** (one secret opens any mailbox), SMTP via the co-located relay
|
||||||
|
on `127.0.0.1:25`. Members therefore never manage an IMAP/SMTP password.
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────── Caddy (:443) ───────────┐
|
||||||
|
webmail → │ mail.profullstack.com → 127.0.0.1:8080 (Mailu front, HTTP)
|
||||||
|
└───────────────┬─────────────────────┘
|
||||||
|
│ copies LE cert (refresh-certs.sh)
|
||||||
|
clients → Mailu front (:25 :465 :587 :993 :995) ──→ Postfix / Dovecot / rspamd
|
||||||
|
▲
|
||||||
|
agentbbs ──IMAP 993 (master user)──┘ ──SMTP 127.0.0.1:25 (local relay)──▶
|
||||||
|
```
|
||||||
|
|
||||||
|
## DNS
|
||||||
|
|
||||||
|
`mail.profullstack.com` and `smtp.profullstack.com` A records are added. Also set:
|
||||||
|
|
||||||
|
| Type | Host | Value |
|
||||||
|
|---|---|---|
|
||||||
|
| A | `mail.profullstack.com` | host IP |
|
||||||
|
| A | `smtp.profullstack.com` | host IP |
|
||||||
|
| MX | `mail.profullstack.com` | `10 mail.profullstack.com.` |
|
||||||
|
| TXT (SPF) | `mail.profullstack.com` | `v=spf1 mx -all` |
|
||||||
|
| TXT (DMARC) | `_dmarc.mail.profullstack.com` | `v=DMARC1; p=quarantine; rua=mailto:postmaster@mail.profullstack.com` |
|
||||||
|
| TXT (DKIM) | `dkim._domainkey.mail.profullstack.com` | from `flask mailu config-export` after first boot |
|
||||||
|
| PTR | host IP | `mail.profullstack.com` (set at your VPS provider) |
|
||||||
|
|
||||||
|
> **Port 25 / deliverability:** many cloud providers block outbound `:25` by
|
||||||
|
> default — request an unblock, set the PTR/rDNS, and warm the IP, or relay
|
||||||
|
> outbound through a smarthost. Inbound MX and the gateway's local submission
|
||||||
|
> work regardless.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/agentbbs/deploy/mailu
|
||||||
|
cp mailu.env.example mailu.env # fill SECRET_KEY, INITIAL_ADMIN_PW, etc.
|
||||||
|
docker compose up -d
|
||||||
|
# seed the gateway master user + (optionally) backfill member mailboxes:
|
||||||
|
AGENTBBS_MAIL_MASTER_USER=gateway ./provision-mailbox.sh --master "$(openssl rand -hex 16)"
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the Caddy site (setup.sh writes this when `MAIL=1`):
|
||||||
|
|
||||||
|
```
|
||||||
|
mail.profullstack.com {
|
||||||
|
encode zstd gzip
|
||||||
|
reverse_proxy 127.0.0.1:8080
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then install the cert refresher on a timer (setup.sh does this too):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
install -m 0755 deploy/mailu/refresh-certs.sh /usr/local/bin/agentbbs-mailu-certs
|
||||||
|
# systemd timer runs it every ~12h; first run swaps in the real cert once Caddy issues it.
|
||||||
|
```
|
||||||
|
|
||||||
|
## agentbbs gateway env
|
||||||
|
|
||||||
|
Set these on the agentbbs service so the `Mail` hub entry / `ssh mail@` work:
|
||||||
|
|
||||||
|
| Var | Value |
|
||||||
|
|---|---|
|
||||||
|
| `AGENTBBS_MAIL_DOMAIN` | `mail.profullstack.com` |
|
||||||
|
| `AGENTBBS_MAIL_IMAP_ADDR` | `mail.profullstack.com:993` |
|
||||||
|
| `AGENTBBS_MAIL_SMTP_ADDR` | `127.0.0.1:25` |
|
||||||
|
| `AGENTBBS_MAIL_MASTER_USER` | `gateway` |
|
||||||
|
| `AGENTBBS_MAIL_MASTER_PASS` | the master password set above |
|
||||||
|
|
||||||
|
## Provisioning member mailboxes
|
||||||
|
|
||||||
|
A mailbox must exist before the gateway can open it. Provision when a member
|
||||||
|
becomes paid (or backfill):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
deploy/mailu/provision-mailbox.sh alice # creates alice@mail.profullstack.com
|
||||||
|
```
|
||||||
|
|
||||||
|
The Dovecot **master user** (`gateway`) then authenticates as any member with
|
||||||
|
the login form `alice*gateway` + the master password — which is exactly what
|
||||||
|
`internal/mailbox`'s IMAP adapter sends. See
|
||||||
|
[`deploy/mailu/README.md`](../deploy/mailu/README.md) for the master-user
|
||||||
|
override and operational details.
|
||||||
|
|
||||||
|
## Webmail only for members
|
||||||
|
|
||||||
|
Members are pointed at `https://mail.profullstack.com` (Roundcube) and the BBS
|
||||||
|
`Mail` client — they are not given the Mailu admin UI or alias management. Admin
|
||||||
|
is operator-only.
|
||||||
73
setup.sh
73
setup.sh
|
|
@ -47,6 +47,8 @@ ERGO_DATA="${ERGO_DATA:-/var/lib/ergo}" # Ergo state dir (ircd.db, tls/)
|
||||||
FORGEJO="${FORGEJO:-1}" # set 0 to skip the AgentGit Forgejo backend (git.${DOMAIN#*.})
|
FORGEJO="${FORGEJO:-1}" # set 0 to skip the AgentGit Forgejo backend (git.${DOMAIN#*.})
|
||||||
GIT_DOMAIN="${GIT_DOMAIN:-git.${DOMAIN#*.}}" # AgentGit host (default: git.<root-of-DOMAIN>, e.g. git.profullstack.com)
|
GIT_DOMAIN="${GIT_DOMAIN:-git.${DOMAIN#*.}}" # AgentGit host (default: git.<root-of-DOMAIN>, e.g. git.profullstack.com)
|
||||||
FORGEJO_VERSION="${FORGEJO_VERSION:-11.0.1}" # Forgejo release to install
|
FORGEJO_VERSION="${FORGEJO_VERSION:-11.0.1}" # Forgejo release to install
|
||||||
|
MAIL="${MAIL:-1}" # set 0 to skip the co-located Mailu mail stack (mail.${DOMAIN#*.})
|
||||||
|
MAIL_DOMAIN="${MAIL_DOMAIN:-mail.${DOMAIN#*.}}" # mail host (default: mail.<root-of-DOMAIN>, e.g. mail.profullstack.com)
|
||||||
FORGEJO_HTTP_ADDR="${FORGEJO_HTTP_ADDR:-127.0.0.1:3000}" # Forgejo loopback HTTP (Caddy fronts it)
|
FORGEJO_HTTP_ADDR="${FORGEJO_HTTP_ADDR:-127.0.0.1:3000}" # Forgejo loopback HTTP (Caddy fronts it)
|
||||||
FORGEJO_DATA="${FORGEJO_DATA:-/var/lib/forgejo}" # Forgejo state dir (repos, db)
|
FORGEJO_DATA="${FORGEJO_DATA:-/var/lib/forgejo}" # Forgejo state dir (repos, db)
|
||||||
FORGEJO_ADMIN_USER="${FORGEJO_ADMIN_USER:-agentgit-admin}" # Forgejo admin used to provision members
|
FORGEJO_ADMIN_USER="${FORGEJO_ADMIN_USER:-agentgit-admin}" # Forgejo admin used to provision members
|
||||||
|
|
@ -502,6 +504,20 @@ ${IRC_DOMAIN} {
|
||||||
"
|
"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Mail site (${MAIL_DOMAIN}): Caddy serving this host obtains the LE cert that
|
||||||
|
# Mailu reuses for SMTP/IMAP TLS (deploy/mailu/refresh-certs.sh copies it). It
|
||||||
|
# also fronts the loopback Roundcube webmail. Needs A record ${MAIL_DOMAIN} ->
|
||||||
|
# this host. Webmail is the only member-facing mail surface. Omitted when MAIL=0.
|
||||||
|
MAIL_SITE=""
|
||||||
|
if [ "$MAIL" = "1" ]; then
|
||||||
|
MAIL_SITE="
|
||||||
|
${MAIL_DOMAIN} {
|
||||||
|
encode zstd gzip
|
||||||
|
reverse_proxy 127.0.0.1:8080
|
||||||
|
}
|
||||||
|
"
|
||||||
|
fi
|
||||||
|
|
||||||
cat > /etc/caddy/Caddyfile <<CADDY
|
cat > /etc/caddy/Caddyfile <<CADDY
|
||||||
{
|
{
|
||||||
email ${ACME_EMAIL}
|
email ${ACME_EMAIL}
|
||||||
|
|
@ -545,7 +561,7 @@ ${DOMAIN} {
|
||||||
file_server
|
file_server
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
${GIT_SITE}${NEWS_SITE}${IRC_SITE}
|
${GIT_SITE}${NEWS_SITE}${IRC_SITE}${MAIL_SITE}
|
||||||
# Free per-user homepages at <name>.${DOMAIN} (needs wildcard DNS
|
# Free per-user homepages at <name>.${DOMAIN} (needs wildcard DNS
|
||||||
# *.${DOMAIN} -> this host). On-demand TLS mints a cert only when agentbbs's
|
# *.${DOMAIN} -> this host). On-demand TLS mints a cert only when agentbbs's
|
||||||
# ask endpoint confirms <name> is a registered member, so random subdomains
|
# ask endpoint confirms <name> is a registered member, so random subdomains
|
||||||
|
|
@ -923,6 +939,61 @@ else
|
||||||
systemctl disable --now forgejo >/dev/null 2>&1 || true
|
systemctl disable --now forgejo >/dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ---- 9e. Mailu mail stack (co-located mail.${DOMAIN#*.}) --------------------
|
||||||
|
# Self-hosted Postfix+Dovecot+Roundcube+rspamd via Docker Compose. Mailu owns
|
||||||
|
# the mail ports; Caddy fronts the loopback webmail and supplies the TLS cert
|
||||||
|
# (TLS_FLAVOR=mail). agentbbs reads/sends on behalf of paid members. Full setup,
|
||||||
|
# DNS, and the gateway master user: docs/mail.md. Disable with MAIL=0.
|
||||||
|
MAILU_DIR="${SRC_DIR}/deploy/mailu"
|
||||||
|
if [ "$MAIL" = "1" ]; then
|
||||||
|
log "configuring Mailu mail stack (${MAIL_DOMAIN})"
|
||||||
|
# Tell agentbbs how to reach the mailbox backend (master user/pass are secrets
|
||||||
|
# the operator sets; see docs/mail.md).
|
||||||
|
upsert_env AGENTBBS_MAIL_DOMAIN "${MAIL_DOMAIN}"
|
||||||
|
upsert_env AGENTBBS_MAIL_IMAP_ADDR "${MAIL_DOMAIN}:993"
|
||||||
|
upsert_env AGENTBBS_MAIL_SMTP_ADDR "127.0.0.1:25"
|
||||||
|
|
||||||
|
# Cert refresher: copy Caddy's mail cert into Mailu on renewal (like news/IRC).
|
||||||
|
install -m 0755 "${MAILU_DIR}/refresh-certs.sh" /usr/local/bin/agentbbs-mailu-certs
|
||||||
|
cat > /etc/systemd/system/agentbbs-mailu-certs.service <<UNIT
|
||||||
|
[Unit]
|
||||||
|
Description=Refresh Mailu TLS cert from Caddy for ${MAIL_DOMAIN}
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
Environment=DOMAIN=${DOMAIN#*.}
|
||||||
|
Environment=MAIL_HOST=${MAIL_DOMAIN}
|
||||||
|
Environment=MAILU_DIR=${MAILU_DIR}
|
||||||
|
ExecStart=/usr/local/bin/agentbbs-mailu-certs
|
||||||
|
UNIT
|
||||||
|
cat > /etc/systemd/system/agentbbs-mailu-certs.timer <<UNIT
|
||||||
|
[Unit]
|
||||||
|
Description=Periodic Mailu TLS cert refresh from Caddy
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnBootSec=5min
|
||||||
|
OnUnitActiveSec=12h
|
||||||
|
Persistent=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
|
UNIT
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable --now agentbbs-mailu-certs.timer >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
# Open the mail ports; bring Mailu up only once the operator has created
|
||||||
|
# mailu.env (it carries SECRET_KEY + admin password — never auto-generated).
|
||||||
|
for p in 25 465 587 993 995; do ufw allow "${p}/tcp" >/dev/null; done
|
||||||
|
if command -v docker >/dev/null && [ -f "${MAILU_DIR}/mailu.env" ]; then
|
||||||
|
( cd "$MAILU_DIR" && docker compose up -d ) || warn "mailu: docker compose up failed — check ${MAILU_DIR}"
|
||||||
|
else
|
||||||
|
warn "Mailu not started yet: create ${MAILU_DIR}/mailu.env (cp mailu.env.example) and run 'docker compose up -d' — see docs/mail.md"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
upsert_env AGENTBBS_MAIL_DOMAIN ""
|
||||||
|
systemctl disable --now agentbbs-mailu-certs.timer >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
# ---- 10. firewall + start agentbbs on :22 ----------------------------------
|
# ---- 10. firewall + start agentbbs on :22 ----------------------------------
|
||||||
log "configuring firewall + starting agentbbs"
|
log "configuring firewall + starting agentbbs"
|
||||||
ufw allow 22/tcp >/dev/null
|
ufw allow 22/tcp >/dev/null
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue