diff --git a/infra/outline.md b/infra/outline.md index beacfe9..a11e2b3 100644 --- a/infra/outline.md +++ b/infra/outline.md @@ -1,6 +1,6 @@ --- title: Outline Wiki -updated: 2026-03-30 +updated: 2026-04-08 tags: [k3s, wiki, outline] --- @@ -73,3 +73,77 @@ kubectl delete pod -n outline **원인**: 이 Secret은 nocodb/n8n/pgcat 변경(2026-04-08 [[postgresql-ha#2026-04-08 사고 기록]])과 함께 일괄 마이그레이션되지 않았음. **나머지 Patroni 사용자도 동일 검증 필요** — `kubectl get secret -A -o json | jq -r '.items[] | select(.data.DATABASE_URL) | "\(.metadata.namespace)/\(.metadata.name)"'`로 검수 권장. **교훈**: Patroni 사용 애플리케이션의 DB endpoint는 항상 OpenWrt HAProxy(`192.168.9.1:5432`) 또는 pgcat(`db.svc.cluster.local:6432`)을 통해야 한다. 노드 IP 직접 박지 말 것. + +## Discord 통지 파이프라인 (2026-04-08 구축) + +`agent-qna` 컬렉션에 새 문서가 만들어지면 heimdall Discord 채널(#heimdall, id `1488119168145555486`)에 자동 알림이 뜬다. 다른 컬렉션은 무시한다. + +``` +Outline (documents.create webhook) + → n8n webhook (https://n8n.inouter.com/webhook/outline-to-discord) + → Code 노드 (collectionId 필터 + Discord embed 빌드) + → HTTP Request 노드 (Discord Bot API: POST /channels/{id}/messages) +``` + +### 구성 요소 위치 + +| 항목 | 값 | +|------|-----| +| n8n workflow id | `8P714i5oBs9HkZPK` | +| n8n workflow 이름 | `outline-to-discord (heimdall)` | +| n8n webhook path | `/webhook/outline-to-discord` (POST, responseMode=onReceived) | +| n8n 노드 구성 | Webhook → Filter+Format(Code) → Discord POST(HTTP) | +| Outline webhook id | `ede9327f-b09e-4f7e-ab56-c507d3d1b3a6` (`documents.create` 이벤트) | +| Outline collection 필터 | `c3ab34ab-fae4-4642-8f4e-12728e293e1b` (agent-qna) — Code 노드 안에 하드코딩 | +| Discord channel | guild `1469999069190557799` 의 `#heimdall` 채널 (id `1488119168145555486`) | +| Discord bot | username `Heimdall` (id `1487694864232611922`) | + +### 시크릿 + +| 비밀 | Vault 경로 | +|------|-----------| +| Discord bot token | `secret/apps/discord` → `bot_token` (현재는 n8n Code 노드에 하드코딩되어 있음 — Credentials로 분리하는 게 더 깔끔, 후속 작업) | +| Discord channel/guild ID | `secret/apps/discord` → `heimdall_channel_id`, `heimdall_guild_id` | +| n8n API key | `secret/apps/n8n` → `api_key` (label: `heimdall-automation`) | +| n8n owner login | `secret/apps/n8n` → `email`(`kappa@inouter.com`), `password` | +| Outline API token | `secret/apps/outline` → `brokkr-api-key` | + +### 메시지 포맷 (Discord embed) + +- title: `📝 ` +- url: `https://outline.inouter.com` +- description: 본문 앞 200자 (개행 → 공백) +- author.name: `outline / agent-qna · ` +- color: `0x00bcd4` (cyan) +- timestamp: `payload.model.createdAt` + +### 트러블슈팅 + +증상별 점검 순서: + +1. **알림이 아예 안 옴** + - Outline UI 또는 API로 webhook subscription 활성 확인 + ```bash + curl -sS -X POST https://outline.inouter.com/api/webhookSubscriptions.list \ + -H "Authorization: Bearer $OL_TOKEN" -d '{}' | jq . + ``` + - n8n에서 workflow active 여부 확인 (`/api/v1/workflows/8P714i5oBs9HkZPK`) + - n8n executions에 webhook 도달했는지 확인 (`/api/v1/executions?workflowId=8P714i5oBs9HkZPK&limit=10`) +2. **n8n에 도달하나 Discord POST가 안 일어남** + - 거의 항상 collectionId 필터 미스매치. Filter+Format Code 노드의 `out items: 0` 이면 정상 (필터된 것) + - 필터를 통과해야 하는데 0 items면 payload.event 또는 payload.model.collectionId 구조 확인 +3. **n8n DB connection timeout** + - n8n pod 재시작: `kubectl -n n8n delete pod -l app.kubernetes.io/name=n8n` + - pgcat → 192.168.9.1:5432 (Patroni leader) 경로 확인. 2026-04-08에 stale connection으로 15h 멈춰있던 사례 있음 +4. **Discord 401/403** + - bot token 재발급 필요 (`secret/apps/discord` 갱신, n8n Code 노드의 하드코딩도 함께 갱신) + - bot이 채널에 access 권한 있는지: `GET /channels/{channel_id}` 200이면 OK +5. **Outline → n8n 호출이 BunnyCDN 508** + - 일시적. 재시도하면 풀린다. BunnyCDN edge의 `cdn-loopcount` 룰 (자세한 원인 불명, retry로 회피) + +### 후속 작업 (선택) + +- [ ] Discord bot token을 n8n Credentials로 분리 (현재 Code 노드에 하드코딩) +- [ ] Outline signing secret 지원되면 HMAC 서명 검증 추가 (현재 미적용 — 같은 인프라 내부 호출이라 위험 낮음) +- [ ] `documents.update` 이벤트도 추가 (현재 create만) +- [ ] 다른 컬렉션 (예: incident postmortem)도 동일 패턴으로 통지 추가