Install the suite, for real, in containers
The plan now happens. "inbuxa install --local --domain example.test --install-deps --yes" on a machine with nothing on it ends with a mail server, a console and a webmail running, an administrator and a first mailbox created, and the records the domain needs written out. The sequence is the one ihasmail-oneshot worked out against a running server, which is why its JMAP client and its Docker handling came across nearly whole: bring the server up in bootstrap mode with a credential that lives in an override file for that step only, complete bootstrap, bring the rest up without it -- so no recovery credential outlives the setup -- exempt the front ends from the auto-ban, restart for the settings that need it, create the first account, and write down the password nothing else holds. New here: three services rather than two. The console is static files that learn their server's address at start, and the webmail is given the first-party OAuth client secret that the server is given too. Twenty checks in the lab, from a bare Debian 13. The two worth having are the ones that catch an install that looks fine and is not: nothing in the running server carries a recovery admin any more, and the account the installer created can sign in to the webmail it installed. Two bugs the lab caught, both of which would have shipped: - the private addresses were worked out on a copy of the stack, so the server was told the webmail speaks from "", and refused it. - the console image rewrites index.html when it starts, so a read-only root filesystem left it restarting forever. The webmail keeps read_only; the console cannot have it until that rewrite moves.
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
#!/bin/bash
|
||||
# SPDX-FileCopyrightText: 2026 Coffey Labs
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
#
|
||||
# The whole thing, on a machine with nothing on it: install the suite in
|
||||
# containers, loopback only, and then prove it is a working mail system rather
|
||||
# than three containers that happen to be running.
|
||||
#
|
||||
# The checks that matter most are the last two: that the credential used to
|
||||
# bootstrap the server does not outlive the setup, and that the account the
|
||||
# installer created can actually sign in to the webmail it installed. Either
|
||||
# one failing means the install looked fine and was not.
|
||||
#
|
||||
# Run from the host with: e2e/vm/run.sh e2e/cases/install-local.sh
|
||||
set -uo pipefail
|
||||
|
||||
pass=0; fail=0
|
||||
ok() { echo " ok $*"; pass=$((pass+1)); }
|
||||
bad() { echo " FAIL $*"; fail=$((fail+1)); }
|
||||
has() { grep -q -- "$2" <<<"$1" && ok "$3" || { bad "$3"; echo "$1" | tail -20 | sed 's/^/ /'; }; }
|
||||
|
||||
DIR=/var/lib/inbuxa
|
||||
|
||||
echo "==> installing"
|
||||
OUT="$(/tmp/inbuxa install --local --domain example.test --install-deps --yes 2>&1)"; rc=$?
|
||||
echo "$OUT" | grep -v '^ |' | tail -24 | sed 's/^/ /'
|
||||
[ $rc -eq 0 ] && ok "installed (exit 0)" || bad "exit $rc"
|
||||
|
||||
echo
|
||||
echo "==> what it left on disk"
|
||||
[ -f "$DIR/compose.yaml" ] && ok "compose.yaml, which is the deployment" || bad "no compose.yaml"
|
||||
[ -f "$DIR/credentials.txt" ] && ok "credentials.txt" || bad "no credentials.txt"
|
||||
[ -f "$DIR/dns.zone" ] && ok "dns.zone" || bad "no dns.zone"
|
||||
[ "$(stat -c %a "$DIR/.env" 2>/dev/null)" = 600 ] && ok ".env is 600" || bad ".env is $(stat -c %a "$DIR/.env" 2>/dev/null), not 600"
|
||||
[ "$(stat -c %a "$DIR/credentials.txt")" = 600 ] && ok "credentials.txt is 600" || bad "credentials.txt is $(stat -c %a "$DIR/credentials.txt"), not 600"
|
||||
grep -q "MX" "$DIR/dns.zone" && ok "the zone file has the MX record" || bad "no MX in the zone file"
|
||||
grep -q "_domainkey" "$DIR/dns.zone" && ok "and the DKIM key it generated" || bad "no DKIM record"
|
||||
|
||||
echo
|
||||
echo "==> what it left running"
|
||||
STATE="$(docker compose -f $DIR/compose.yaml ps --format '{{.Service}} {{.State}}')"
|
||||
for s in server console webmail; do
|
||||
grep -q "^$s running" <<<"$STATE" && ok "$s is running" || { bad "$s is not running"; echo "$STATE" | sed 's/^/ /'; }
|
||||
done
|
||||
|
||||
echo
|
||||
echo "==> and it answers"
|
||||
curl -fsS -o /dev/null http://127.0.0.1:8081/.well-known/jmap -w '' 2>/dev/null
|
||||
[ $? -le 22 ] && ok "the mail server answers on its loopback bind" || bad "the mail server does not answer"
|
||||
curl -fsS http://127.0.0.1:8082/ 2>/dev/null | grep -q 'api-base-url' && ok "the console is served, pointed at the server" || bad "the console is not served"
|
||||
curl -fsS http://127.0.0.1:8080/api/health 2>/dev/null | grep -q '"ok":true' && ok "the webmail is healthy" || bad "the webmail is not healthy"
|
||||
|
||||
echo
|
||||
echo "==> the bootstrap credential did not outlive the setup"
|
||||
ENVOUT="$(docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' "$(docker compose -f $DIR/compose.yaml ps -q server)")"
|
||||
grep -q "RECOVERY_ADMIN" <<<"$ENVOUT" && { bad "the server still carries a recovery admin"; echo "$ENVOUT" | grep RECOVERY | sed 's/^/ /'; } || ok "no recovery admin in the running server"
|
||||
grep -q "INBUXA_WEBMAIL_CLIENT_SECRET" <<<"$ENVOUT" && ok "the webmail's client secret is where it belongs" || bad "the server has no webmail client secret"
|
||||
|
||||
echo
|
||||
echo "==> the account it created can sign in to the webmail it installed"
|
||||
USER=$(awk '/^first mailbox/ {print $3}' "$DIR/credentials.txt")
|
||||
PASS=$(awk '/^first mailbox/{getline; print $2}' "$DIR/credentials.txt")
|
||||
[ -n "$USER" ] && ok "credentials.txt names the first mailbox ($USER)" || bad "no first mailbox in credentials.txt"
|
||||
CODE=$(curl -s -o /tmp/login.json -c /tmp/jar -w '%{http_code}' -X POST http://127.0.0.1:8080/api/auth/login \
|
||||
-H 'Content-Type: application/json' -H 'X-Requested-With: ihasmail' \
|
||||
-d "{\"username\":\"$USER\",\"password\":\"$PASS\",\"remember\":true}")
|
||||
[ "$CODE" = 200 ] && ok "sign-in succeeds" || { bad "sign-in answered $CODE"; head -c 200 /tmp/login.json | sed 's/^/ /'; }
|
||||
grep -q "urn:ietf:params:jmap:mail" /tmp/login.json && ok "and the session carries the mail capability" || bad "no mail capability in the session"
|
||||
|
||||
echo
|
||||
echo "==> running it again converges rather than duplicating"
|
||||
OUT="$(/tmp/inbuxa install --local --domain example.test --yes 2>&1)"; rc=$?
|
||||
COUNT=$(docker compose -f $DIR/compose.yaml ps --format '{{.Service}}' | sort -u | wc -l)
|
||||
[ "$COUNT" = 3 ] && ok "still three services, not six" || bad "$COUNT services after a second run"
|
||||
|
||||
echo
|
||||
echo "==> $pass passed, $fail failed"
|
||||
[ "$fail" -eq 0 ]
|
||||
Reference in New Issue
Block a user