The single-file stdlib server became the limit: no way to track what had been done about an alert, no accounts, and a UI that had to be hand-rolled in template strings. This restructures it into something deployable. Backend (FastAPI) - app/ holds config, database, auth, delivery and the routers; triagelib keeps the triage engine unchanged, so the validated screening and runbook logic is untouched. - Cases persist per alert fingerprint with a status workflow (investigating, customer contacted, escalated to Infra, waiting, remediated, resolved, won't fix, false positive), an assignee, notes and an append-only history. An alert that stops and re-fires lands back on the same case and counts as a reopen. - Suppression rules move from a JSON file into the database. Auth - Signed session cookies over PBKDF2 local accounts, plus an OIDC flow ready for Authentik: users are created on first login and admin follows a group claim. Local login can be switched off entirely once SSO is live. Zendesk and Jira - Delivery is now implemented, behind three gates: the integration must be configured, its feature flag on, and CX_FEATURE_SEND_ENABLED on. A demo instance leaves the last off and cannot mail anyone. Both search before creating, so re-diagnosing an alert updates one ticket rather than opening several, and a rolling daily cap stops a loop mailing everybody. Deployment - Multi-stage Dockerfile builds the bundle and serves it from the API origin. - docker-compose for local and single-host use; Gitea Actions runs the tests, builds the image and renders deploy/k8s with envsubst. Two fixes found while testing: assigning a case returned a null assignee, and add_event could leave an already-loaded history collection stale. Known gap: the engine reaches OpenStack via `docker exec <region>-osc`, which does not work in a pod without the CX-Tools containers alongside it. docs/DEPLOYMENT.md sets out the three ways to close that. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4.2 KiB
Enabling Zendesk and Jira
Nothing leaves this app until three separate gates are open. Until then the UI builds the full payload, shows it to you, and the Send button stays disabled with the reason written on it.
1. the integration is configured CX_ZENDESK_* / CX_JIRA_*
2. its feature flag is on CX_FEATURE_ZENDESK / CX_FEATURE_JIRA
3. sending is enabled globally CX_FEATURE_SEND_ENABLED
Gate 3 is the important one. A demo or staging instance simply leaves it off, and then no combination of clicks can email a customer.
Check where you stand at any time on Settings → Integrations, or:
curl -s localhost:8080/api/health | python3 -m json.tool
Zendesk
1. Create an API token
Zendesk Admin Center → Apps and integrations → APIs → Zendesk API, turn on Token access, then Add API token. Copy it — Zendesk shows it once.
2. Decide which agent owns the tickets
Use a dedicated agent (e.g. cx-triage@…) rather than a person's account, so
the audit trail stays clear when someone leaves.
3. Set the variables
CX_FEATURE_ZENDESK=true
CX_ZENDESK_SUBDOMAIN=nexgencloud # from https://<this>.zendesk.com
CX_ZENDESK_EMAIL=cx-triage@nexgencloud.com
CX_ZENDESK_TOKEN=<the token>
CX_ZENDESK_PUBLIC_REPLY=true # false posts an internal note instead
CX_FEATURE_SEND_ENABLED=true # the master switch
Restart. The Send button becomes live.
What happens on send
- Searches for
external_id:cx-triage-<fingerprint>. - If a ticket exists → adds a comment. If not → creates one, with the requester
set from the Infrahub owner, priority from the screening verdict, and tags
cx-triage,alert-<kind>. - Records a
zendesk_sentevent on the case and moves it to Customer contacted.
So re-diagnosing the same alert updates one ticket instead of opening five.
Jira
1. Create an API token
https://id.atlassian.com/manage-profile/security/api-tokens → Create API token.
2. Confirm the project and issue type
The defaults are INFRA / Task. If your Infrastructure project uses something
else, set it — a wrong issuetype is the usual cause of a 400 from Jira.
3. Set the variables
CX_FEATURE_JIRA=true
CX_JIRA_BASE=https://nexgencloud.atlassian.net
CX_JIRA_EMAIL=cx-triage@nexgencloud.com
CX_JIRA_TOKEN=<the token>
CX_JIRA_PROJECT=INFRA
CX_JIRA_ISSUE_TYPE=Task
CX_FEATURE_SEND_ENABLED=true
Issues are labelled cx-triage-<fingerprint> and searched for before creating,
so the same alert never opens two tickets.
Where the credentials go
Never commit them.
| Where | How |
|---|---|
| Local | .env (git-ignored) — copy from .env.example |
| Kubernetes | The cx-triage Secret, written by the pipeline from Gitea secrets |
| Gitea | Repository → Settings → Actions → Secrets |
The deploy job creates the Secret imperatively from the secret store, so no credential is ever in a manifest in git.
Safety rails that stay on
- Every send needs a click and a confirm naming the recipient.
- No auto-send: a verdict never triggers an email by itself.
CX_SEND_DAILY_CAP(default 25) refuses further sends in a rolling 24 hours, so a loop cannot mail every customer.- The body is editable before sending.
- Every attempt is written to the case history — including failures.
- Deleting, shelving and InfraInsight edits stay copy-a-command. The read-only guarantee is what makes this safe against production.
First run
Point at a Zendesk sandbox, or send the first ticket to your own address by editing the To field. Once one round trip looks right, switch it on for real.
When it fails
| Symptom | Cause |
|---|---|
| Button disabled, "sending is switched off" | CX_FEATURE_SEND_ENABLED is false |
| Button disabled, "not configured" | A CX_ZENDESK_* / CX_JIRA_* value is missing |
401 from Zendesk |
The email must be the agent address, and token access must be enabled |
400 from Jira |
Usually issuetype or project does not exist |
| "Daily send cap reached" | Raise CX_SEND_DAILY_CAP if deliberate |
| "Open the case first" | The alert has no case yet — open it in the queue once |