Files
jcoffey-dev 878aa2e682
ci / fork-checks (pull_request) Skipped
ci / build (pull_request) Skipped
github/ci (branch) GitHub Actions
ci / github (pull_request) Successful in 7m26s
Switch off upstream's web interface when a Stalwart store is first opened
A store brought over from Stalwart 0.16 lists upstream's web interface as
an Application, so inbuxa fetched it and served it at /admin. The first
time inbuxa opens a store without its version mark, it now switches that
Application off and keeps the record, logging how to switch it back on.
The mark written on the same start keeps this to once, so an operator who
wants the interface back keeps it. Other applications aren't touched.

Settles the open question in docs/spec/import-0.16.md; SPEC.md §5.3 and
the import check follow.
2026-10-29 21:55:02 -07:00

69 lines
3.4 KiB
Python
Executable File

#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2026 Coffey Labs LLC
# SPDX-License-Identifier: AGPL-3.0-only
"""What happens on the imported store after the move: delivery through the
imported Sieve filter, search over imported mail, re-saving a filter that uses
a pre-rename extension name, and the 0.16 web interface switched off."""
import json, smtplib, sys, time, os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from jmap import Jmap
BASE = sys.argv[1]
alice = Jmap(BASE, "[email protected]", "alice-fixture-pass-1")
acct = alice.account_id()
failed = []
def ok(label, cond, detail=""):
print(("PASS " if cond else "FAIL ") + label + (f": {detail}" if detail and not cond else ""))
if not cond:
failed.append(label)
# 1. Delivery: the imported filter files "kickoff" mail into Projects.
with smtplib.SMTP("127.0.0.1", 18025, timeout=300) as s:
s.sendmail("[email protected]", ["[email protected]"],
b"From: [email protected]\r\nTo: [email protected]\r\nSubject: Second kickoff\r\n"
b"Message-ID: <[email protected]>\r\n\r\nAgain.\r\n")
found = []
for _ in range(30):
r = alice.call(("Email/query", {"accountId": acct, "filter": {"subject": "Second kickoff"}}),
("Email/get", {"accountId": acct, "#ids": {"resultOf": "0", "name": "Email/query", "path": "/ids"},
"properties": ["mailboxIds"]}),
("Mailbox/get", {"accountId": acct, "ids": None, "properties": ["name"]}))
found = r[1]["list"]
if found:
break
time.sleep(1)
names = {m["id"]: m["name"] for m in r[2]["list"]}
ok("delivery after import", bool(found))
ok("imported filter ran", bool(found) and [names[b] for b in found[0]["mailboxIds"]] == ["Projects"],
found and [names[b] for b in found[0]["mailboxIds"]])
# 2. Search over imported mail, once the queued reindex has run.
hits = []
# Each account's reindex is spread over the first five minutes after start.
for _ in range(400):
hits = alice.call(("Email/query", {"accountId": acct, "filter": {"text": "quarterly"}}))[0]["ids"]
if len(hits) >= 2:
break
time.sleep(1)
ok("search finds imported mail", len(hits) >= 2, f"{len(hits)} of 2 found")
# 3. Re-saving the imported filter, which says vnd.stalwart.expressions.
s = alice.call(("SieveScript/get", {"accountId": acct, "ids": None}))[0]["list"]
script = next(x for x in s if x["name"] == "filters")
blob = alice.upload(b'require ["fileinto", "vnd.stalwart.expressions"];\r\nlet "who" "envelope.from";\r\n'
b'if header :contains "subject" "kickoff" { fileinto "Projects"; }\r\n', "application/sieve")
r = alice.call(("SieveScript/set", {"accountId": acct, "update": {script["id"]: {"blobId": blob}}}))[0]
ok("re-saving a pre-rename filter", not r.get("notUpdated"), r.get("notUpdated"))
# 4. The 0.16 web interface: switched off on first open, record kept.
A = json.load(open(os.path.join(os.environ["FIXTURE"], "admin.json")))
adm = Jmap(BASE, A["username"], A["secret"], ["urn:inbuxa:jmap:registry"])
apps = adm.call(("x:Application/get", {"ids": None}))[0]["list"]
webui = [a for a in apps if (a.get("resourceUrl") or "").startswith("https://github.com/stalwartlabs/webui/")]
ok("0.16 web interface switched off, record kept", webui and not any(a.get("enabled") for a in webui),
[(a.get("enabled"), a.get("resourceUrl")) for a in apps])
sys.exit(1 if failed else 0)