Convert sign-ups stranded at the confirmation step¶
Self-enrolled directory accounts that never completed the confirmation step stay inactive. This page covers the script that converts them, so that the account's first registry sign-in sends the activation mail.
scripts/directory-convert-unactivated.py runs inside authentik's ak shell.
Its default is a read-only dry run. It prints aggregate counts, without
names, addresses, account IDs or credentials. No mail is sent by conversion;
the account's first registry sign-in sends the activation link.
The script selects inactive internal accounts at the exact path
users/self-enrolled with no prior sign-in. It skips administrators,
accounts with any authenticator (including an unconfirmed one), and accounts
already carrying agentry_email_verified. That last guard avoids turning
an operator-disabled account from the new flow back on. Only a legacy
account with an absent mark is converted: is_active=True and
agentry_email_verified=False, preserving its other attributes. A legacy
account disabled before any sign-in is indistinguishable from a stranded
sign-up by these fields; the operator must resolve any such accounts before
applying the production conversion.
An apply requires the activation switch, the active enrollment write stage,
and the registry provider's agentry-verified-email mapping to be live.
It locks and rechecks the cohort in one transaction, and refuses a count
that differs from the reviewed dry run. Repeating the script leaves the
converted accounts alone. Take a directory database backup before applying
on production. Run during a quiet enrollment window; pause enrollment and
sign-in if necessary so the reviewed count remains stable.
On a compose stack¶
From the site checkout, with the compose stack running, dry run:
docker exec -i compose-authentik-worker-1 ak shell -c \
'exec(compile(__import__("sys").stdin.read(), "conversion", "exec"), {"__name__": "__main__"})' \
< scripts/directory-convert-unactivated.py
Review eligible. Then run with its exact count (replace N):
docker exec -i -e AGENTRY_CONVERT_UNACTIVATED_APPLY=1 \
-e AGENTRY_CONVERT_UNACTIVATED_EXPECTED_COUNT=N \
compose-authentik-worker-1 ak shell -c \
'exec(compile(__import__("sys").stdin.read(), "conversion", "exec"), {"__name__": "__main__"})' \
< scripts/directory-convert-unactivated.py
Repeat the dry run; eligible must be zero. The acceptance test in
scripts/acceptance/directory-convert-unactivated.py runs against the pinned
throwaway directory in directory-social.sh. Its fixtures and conversion
roll back; it verifies the dry run, switch and count refusals, each excluded
account, preservation of attributes, and a repeat run.
On Kubernetes (production)¶
The dry run only reads. The database write is made by the operator, after the activation deploy.
Registry activation also requires a sending registry notifier: production
must render notify.kind: smtp, Postmark's transactional host on port 587
with STARTTLS, and API/worker AGENTRY_SMTP_USER and AGENTRY_SMTP_PASS
from the agentry-app Secret. The worker permits Postmark's published
SMTP endpoint ranges on that port. scripts/registry-mail-check.sh checks
these rendered manifests in CI. Directory mail working does not prove
registry mail works: the log notifier accepts a resend without delivering
anything. Verify the deployed configuration and SMTP authentication
before the real signup check; authentication alone does not prove delivery.
With the production kubeconfig loaded, select the current Ready worker:
Replace WORKER_POD with that pod's name and dry run the reviewed script:
kubectl -n agentry exec -i WORKER_POD -- ak shell -c \
'exec(compile(__import__("sys").stdin.read(), "conversion", "exec"), {"__name__": "__main__"})' \
< scripts/directory-convert-unactivated.py
After the backup and activation deploy, repeat the dry run and review its
count. The operator applies with that exact N:
kubectl -n agentry exec -i WORKER_POD -- env \
AGENTRY_CONVERT_UNACTIVATED_APPLY=1 \
AGENTRY_CONVERT_UNACTIVATED_EXPECTED_COUNT=N ak shell -c \
'exec(compile(__import__("sys").stdin.read(), "conversion", "exec"), {"__name__": "__main__"})' \
< scripts/directory-convert-unactivated.py
Repeat the dry run and record the counts and the deployment revision. Then
prove a real production sign-up reaches the Dashboard without a mail
wait, sees the activation banner, observes the five-minute resend limit,
and loses the banner after clicking the mailed activation button. Opening
the link alone must leave the account unactivated. Verify registry
emailVerified becomes true while the directory attribute stays false: the
directory claim reads confirmation from the registry and the registry token
has no change_user permission. Then erase the approved test account.