fix: service credential provisioning and install reliability
Unit Tests / test (push) Successful in 7m21s

- calendar: create_calendar_user() now writes bcrypt htpasswd entry to
  data/services/calendar/config/users (the path Radicale reads at
  /etc/radicale/users); delete_calendar_user() removes the entry

- email: create_email_user() calls `docker exec cell-mail setup email add`
  to register the account in docker-mailserver's Dovecot/Postfix store;
  delete_email_user() calls the matching `setup email del` — both are
  non-fatal if the container isn't running

- service_composer.install(): pull image separately before up so slow
  registry pulls don't race with container startup; retry up once on
  failure so a transient registry hiccup on first install doesn't
  require the user to manually retry

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 13:41:41 -04:00
parent c696ca9ef6
commit 9bdda6aaf8
3 changed files with 83 additions and 2 deletions
+28
View File
@@ -340,12 +340,39 @@ class EmailManager(BaseServiceManager):
mailbox_dir = os.path.join(self.email_data_dir, 'mailboxes', f'{username}@{domain}')
self.safe_makedirs(mailbox_dir)
# Provision account in docker-mailserver (non-fatal if container not running)
self._dms_add_account(username, domain, password)
logger.info(f"Created email user: {username}@{domain}")
return True
except Exception as e:
logger.error(f"Failed to create email user {username}@{domain}: {e}")
return False
def _dms_add_account(self, username: str, domain: str, password: str) -> None:
try:
r = subprocess.run(
['docker', 'exec', 'cell-mail', 'setup', 'email', 'add',
f'{username}@{domain}', password],
capture_output=True, text=True, timeout=30, check=False,
)
if r.returncode != 0:
logger.warning('dms add account %s@%s: %s', username, domain, r.stderr.strip())
except Exception as e:
logger.warning('dms add account %s@%s failed (non-fatal): %s', username, domain, e)
def _dms_del_account(self, username: str, domain: str) -> None:
try:
r = subprocess.run(
['docker', 'exec', 'cell-mail', 'setup', 'email', 'del',
f'{username}@{domain}'],
capture_output=True, text=True, timeout=30, check=False,
)
if r.returncode != 0:
logger.warning('dms del account %s@%s: %s', username, domain, r.stderr.strip())
except Exception as e:
logger.warning('dms del account %s@%s failed (non-fatal): %s', username, domain, e)
def delete_email_user(self, username: str, domain: str) -> bool:
"""Delete an email user"""
try:
@@ -366,6 +393,7 @@ class EmailManager(BaseServiceManager):
import shutil
shutil.rmtree(mailbox_dir)
self._dms_del_account(username, domain)
logger.info(f"Deleted email user: {username}@{domain}")
return True