fix(mail): rotate-creds wrong subcommand — silently broke for 35h
`mail.droplet.rotate_creds` and `seed_mailboxes` called `maddy creds set --password X user@host`, but maddy has no `set` subcommand — the actual one is `password`. Effect: the ssh exec returned non-zero (Error: No help topic for 'set'); fall-through to `creds create` errored on the existing account; local credentials.json got the new password but the droplet kept the OLD one. The IMAP poller then looped forever on Invalid credentials. Discovered when investigating why mail-poller had been unhealthy for 35 hours straight (zero CMS emails ingested into Zotero). - Use `creds password` for existing accounts; fall through to `creds create` + `imap-acct create` only when password fails. - Save under both short cache key (back-compat) and full address. - Same fix applied to seed_mailboxes' in-sync path. Manual recovery already done out-of-band: - maddy creds password applied directly on droplet - credentials.json updated locally; mail-poller restarted - 45 items now tagged source:email in bib.sqlite Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -421,12 +421,25 @@ def _addr_to_key(addr: str) -> str:
|
||||
|
||||
def rotate_creds(user_or_addr: str) -> str:
|
||||
"""Mint new password and push to droplet. ``user_or_addr`` may be
|
||||
``user`` (assumed at apex) or full ``user@host``."""
|
||||
``user`` (assumed at apex) or full ``user@host``.
|
||||
|
||||
maddy subcommands:
|
||||
creds password → change password on existing account
|
||||
creds create → create new account (also needs imap-acct create)
|
||||
Earlier versions of this function called ``creds set``, which doesn't
|
||||
exist in maddy — it silently failed and fell through to ``creds
|
||||
create`` which then errored on the existing account. Net result:
|
||||
local credentials.json got the new password but the droplet kept
|
||||
the old one. The poller then loops forever on Invalid credentials.
|
||||
"""
|
||||
addr = user_or_addr if "@" in user_or_addr else f"{user_or_addr}@{DOMAIN}"
|
||||
meta = discover_droplet(_do_client())
|
||||
if not meta:
|
||||
raise RuntimeError("no mail droplet exists")
|
||||
new_pw = _gen_password()
|
||||
|
||||
# Try password change first (account exists). If the account
|
||||
# doesn't exist yet, create it + the matching IMAP account.
|
||||
r = _ssh(
|
||||
meta["public_ip"],
|
||||
"docker",
|
||||
@@ -434,7 +447,7 @@ def rotate_creds(user_or_addr: str) -> str:
|
||||
"mail",
|
||||
"maddy",
|
||||
"creds",
|
||||
"set",
|
||||
"password",
|
||||
"--password",
|
||||
new_pw,
|
||||
addr,
|
||||
@@ -464,7 +477,10 @@ def rotate_creds(user_or_addr: str) -> str:
|
||||
addr,
|
||||
)
|
||||
creds = _load_json(CREDS_JSON)
|
||||
# Save under both forms so lookup works whether the caller passes
|
||||
# the bare key or the full address.
|
||||
creds[_addr_to_key(addr)] = new_pw
|
||||
creds[addr] = new_pw
|
||||
_save_json(CREDS_JSON, creds)
|
||||
CREDS_JSON.chmod(0o600)
|
||||
ok(f"rotated {addr}")
|
||||
@@ -488,6 +504,8 @@ def seed_mailboxes(addrs: tuple[str, ...] = DEFAULT_MAILBOXES) -> None:
|
||||
key = _addr_to_key(addr)
|
||||
pw = creds.get(key)
|
||||
if pw:
|
||||
# `creds password` updates an existing account; falls
|
||||
# through to create+imap-acct for first-time provision.
|
||||
r = _ssh(
|
||||
ip,
|
||||
"docker",
|
||||
@@ -495,7 +513,7 @@ def seed_mailboxes(addrs: tuple[str, ...] = DEFAULT_MAILBOXES) -> None:
|
||||
"mail",
|
||||
"maddy",
|
||||
"creds",
|
||||
"set",
|
||||
"password",
|
||||
"--password",
|
||||
pw,
|
||||
addr,
|
||||
|
||||
Reference in New Issue
Block a user