통합 위키 포털 아키텍처 (Wiki Portal Architecture)
Date: 2026-06-29 호스트: ubuntu (home server, 192.168.0.2) 도메인: Infra / Networking / 운영 티어: PRIVATE (basic_auth) — 사내·홈랩 내부 사양 포함, 단 자격증명 값은 비포함
1. 개요
통합 위키 포털은 여러 도메인의 markdown 노트를 단일 Caddy 프런트도어(:8080) 뒤에서 브라우저 열람용 HTML 아카이브로 묶어, 공개(open)·비공개(basic_auth)·파일허브(download)를 한 출처(same-origin)로 제공하는 시스템임. md 원본은 md-viz 파이프라인이 보존하고 같은 이름의 시각화 HTML을 생성하며, Cloudflare quick tunnel로 외부에 노출함. 목적은 ① 흩어진 작업 로그·도메인 노트를 한 곳에서 검색·열람, ② 공개해도 되는 자료(public)와 홈랩 내부 사양이 든 자료(private)를 한 코드 경로로 분리 서빙, ③ 사내 프록시 환경에서도 GET만으로 열람·다운로드가 되도록 설계하는 것임.
2. 전체 아키텍처 (ASCII 다이어그램)
+-----------+ +---------------------+ +------------------------------+
| Internet | ---> | Cloudflare | ---> | Caddy :8080 (kb-docs) |
| browsers | | quick tunnel | | single front door, host net |
+-----------+ | (kb-tunnel) | | root * /srv (ro bind) |
| url changes/restart | +------------------------------+
+---------------------+ |
| path routing
+-----------------------------------+-----------------+-----------------+
| | |
v v v
+----------------------+ +----------------------+ +----------------------+
| handle / + /public | | handle /private* | | handle /files* |
| portal + open docs | | basic_auth gate | | reverse_proxy |
| no auth | | admin / bcrypt hash | | 127.0.0.1:8081 |
| import archive | | import archive | | (dufs, ro, GET-only) |
+----------------------+ +----------------------+ +----------------------+
| | |
v v v
+----------------------+ +----------------------+ +----------------------+
| /srv/public/* | | /srv/private/* | | kb-fileshare (dufs) |
| _site.json open | | _site.json behind | | ~/fileshare -> /data |
| sanitized archives | | auth (no meta leak) | | browse/zip/search |
+----------------------+ +----------------------+ +----------------------+
LAN-only side door (NOT forwarded by the tunnel; tunnel only points at :8080):
+----------------------+ +------------------------------+
| Home LAN / mac | ---> | kb-filemgr (filebrowser) |
| 192.168.0.0/24 | | 192.168.0.2:8082 manage RW |
+----------------------+ | upload / mkdir / move / del |
+------------------------------+
- 단일 프런트도어 원칙임. 외부로 나가는 길은 quick tunnel → Caddy
:8080하나뿐이고, 그 안에서handle블록이 경로로 갈라짐. 따라서 외부에서 보이는 것은 Caddy가 허용한 경로뿐임. kb-filemgr(:8082)는 LAN IP에만 바인딩되어 터널 경로 밖에 있음 → 외부에서 도달 불가, 집/사내 LAN에서만 관리용으로 접근함.
3. 3-tier 구조 + URL 맵
| 티어 | URL 패턴 | 접근성 | 내용 |
|---|---|---|---|
| Portal / Public | /, /public/<domain>/ |
공개 (no auth) | 살균 게이트 통과한 공개 노트. 포털 랜딩 + 도메인 카탈로그 |
| Private | /private/<domain>/ |
basic_auth (admin) | 작업 로그·홈랩 내부 사양. 메타데이터(_site.json)까지 auth 뒤 |
| Files | /files/ |
공개 GET (dufs, ro) | 첨부·바이너리 다운로드 허브 (사내 다운로드용) |
핵심 메커니즘 — basic_auth는 사내 프록시도 통과함.
private 티어를 막는 것은 HTTP Basic 인증임. 브라우저는 GET /private/... 요청에 Authorization: Basic base64(admin:pass) 헤더를 실어 보내고, Caddy가 헤더의 bcrypt 해시를 검증함. 이때 사용되는 메서드는 표준 GET + 표준 헤더 하나뿐임. 사내 프록시가 흔히 차단하는 것은 PUT/POST/PROPFIND 같은 쓰기 메서드나 비표준 인증 핸드셰이크인데, basic_auth는 그 어느 것에도 해당하지 않음. 즉 private이라도 사내망에서 열람은 가능함(자격증명만 있으면). 이 점이 “private = 사내 비공개"가 아니라 “private = 인증 게이트 + 검색 메타 차단"임을 의미함. 진짜로 사내에서 가리고 싶은 자료는 티어가 아니라 별도 격리가 필요함.
4. 컨테이너 & 포트
| 컨테이너 | 이미지 | 바인드/포트 | network_mode | 역할 |
|---|---|---|---|---|
kb-docs |
caddy:latest |
:8080 (host net) |
host | 프런트도어. ~/knowledge-base -> /srv:ro, Caddyfile ro 마운트. 라우팅·basic_auth·캐시헤더 |
kb-tunnel |
cloudflare/cloudflared:latest |
outbound 7844/443 | host | tunnel --url http://localhost:8080 quick tunnel. 외부 노출. 재시작마다 URL 변경 |
kb-fileshare |
sigoden/dufs:latest |
127.0.0.1:8081 |
host | dufs read-only·no-auth. ~/fileshare -> /data. browse/zip/search, GET만. Caddy /files/가 프록시 |
kb-filemgr |
filebrowser/filebrowser:latest |
192.168.0.2:8082 |
host | filebrowser 관리 UI. ~/fileshare -> /srv. 업로드·폴더생성·이동·삭제. LAN 전용 |
- 네 컨테이너 모두
network_mode: host+restart: unless-stopped임. host net을 쓰는 이유는 dufs를127.0.0.1:8081루프백에만 묶어 터널에서 직접 못 닿게 하기 위함임(Caddy 경유만 허용). kb-fileshare와kb-filemgr는user: "1000:1000"(jinsoo)으로 돌아 호스트 파일 소유권과 일치시킴.
5. 파일허브 — 다운로드/관리 분리 (+왜)
파일허브는 다운로드 경로(dufs) 와 관리 경로(filebrowser) 를 의도적으로 두 컨테이너로 분리함.
- 다운로드(dufs,
kb-fileshare) — read-only·no-auth로/files/에 공개. 모든 동작(목록·zip·검색·다운로드)이 평범한 GET임. 로그인 버튼도 업로드 버튼도 없어 혼동 여지 자체가 없음. 사내에서 첨부 받을 때 쓰는 경로임. - 관리(filebrowser,
kb-filemgr) — 업로드·폴더 생성·이동·삭제·압축이 되는 진짜 파일 매니저. LAN IP192.168.0.2:8082에만 바인딩 → 터널로 안 나감. 집/사내 LAN의 mac에서만 접근해 쓰기 작업을 함. 쓴 파일은~/fileshare에 떨어지고, read-only dufs가 그것을 다운로드용으로 서빙함.
분리 이유(메커니즘): 사내 프록시는 보통 업로드(PUT/POST)와 비표준 인증을 차단함. 다운로드 경로를 GET-only·no-auth로 만들면 그 차단을 구조적으로 우회할 게 아니라 애초에 걸릴 게 없음. 반대로 쓰기 기능은 외부에 한 톨도 노출하지 않고 LAN에 가둠으로써, “외부=읽기 전용, 집=읽기+쓰기"라는 경계가 포트 바인딩 한 줄로 강제됨. 한 컨테이너에 인증 분기로 섞었다면 프록시 환경에서 다운로드가 같이 깨졌을 것임.
6. md-viz 파이프라인
마스터 자산은 전부 ~/.claude/skills/md-viz/에 있음 — scripts/(md2viz·gen_index·gen_tree·gen_nav·gen_portal), assets/viz.css·assets/viz.js, publish.sh, denylist.txt. 산출물은 ~/knowledge-base/<tier>/<domain>/에 떨어짐.
src.md --add--> markdown/<slug>.md --render(md2viz)--> <slug>.html
|
reindex(gen_index+gen_tree) --> index.html + files.html + catalog.json
|
sync-assets --> push viz.css/viz.js to KB + every archive
|
portal(gen_nav+gen_portal) --> _site.json x2 + /index.html
| 단계 | 명령 | 효과 |
|---|---|---|
| add | publish.sh add <tier>/<domain> <src.md> <slug> |
살균 게이트 → md 복사 → 1개 렌더 → reindex |
| render | publish.sh render <archive> |
아카이브 markdown 전체 재렌더(SVG 강화본 보존) |
| reindex | publish.sh reindex <archive> |
index.html·files.html·catalog.json 재생성 |
| sync-assets | publish.sh sync-assets |
마스터 viz.css/viz.js를 KB와 모든 아카이브 assets로 전파 |
| portal | publish.sh portal |
두 티어 _site.json + 포털 /index.html 갱신 |
- 단일 편집점. CSS/JS는 마스터 한 곳(
skills/md-viz/assets/)에서 고치고sync-assets로 전 아카이브에 복사함 → 전역 restyle 1회로 전체 반영(재렌더 불필요). 인라인 금지·CDN 의존 0(폐쇄망 대비, mermaid 미사용). - 슬러그 문법은
<topic>__<type>-<slug>임.topic은__앞,type은__뒤 첫-까지(note·src·runbook·guide·report·ref·MOC). 이 문법으로 카탈로그 토픽 그룹핑이 결정됨. - 다이어그램은 손수 인라인 SVG임.
render가 남긴needs-svgplaceholder를 사람이/에이전트가 SVG로 교체하면 이후rebuild가 그 HTML을 보존함(<svg class="dgm">감지).
7. 보안 모델
3겹 경계로 동작함.
- public 살균 게이트(denylist).
add/render/reindex가 public 아카이브를 대상으로 하면denylist.txt정규식(예:kakaopay,192\.168\.,10\.0\.0\.,1\.238\.,enp7s0,\bbr0\b,hostname ubuntu)을 servable·코드 파일에 grep하여 1건이라도 맞으면 exit 1로 차단함. private 대상이면 같은 grep을 돌리되 warn-only로 통과시킴(private은 그 값들을 정당하게 포함할 수 있으므로). 문서 내 예시 IP는 RFC5737203.0.113.x같은 문서용 대역으로 redaction함. - private basic_auth.
/private/*는 Caddybasic_auth(admin + bcrypt 해시) 뒤에 있음. - 메타유출 경계.
/private/_site.json(검색·nav용 도메인·문서 제목 메타)까지handle /private*안에 있어 auth 뒤에서만 서빙됨. 따라서 미인증 사용자는 private 도메인이 몇 개인지, 문서 제목이 무엇인지조차 못 봄. viz.js는 미인증 시/private/_site.json에서 401을 받아 제목 없는🔒 Private — log in링크 하나만 그림.
살균 게이트는 알려진 토큰의 자동 차단(floor) 일 뿐, 누출이 없음을 보장하는 천장(ceiling)이 아님. denylist는 IP·hostname·kakaopay 같은 문자열만 잡음. 반면 VM 이름(linux-lab 류), 내부 프로젝트 코드네임, 토폴로지 설명 같은 의미적(semantic) 누출은 정규식에 안 걸림. 그래서 public 승격 시에는 게이트 통과 여부와 별개로 사람이 한 번 더 읽어야 함. 판단이 애매하거나 의미적 누출이 섞인 문서는 public으로 올리지 말고 private에 유지하는 것이 기본값임.
8. 캐싱
- origin(Caddy)이 모든 아카이브 응답에
Cache-Control: no-cache를 붙임. 이는 “캐시 저장은 해도 매번 ETag로 재검증하라"는 뜻임 → 변경 없으면 304, 바뀌면 새 본문. 과거 Caddy가 Cache-Control을 안 보내던 시절 브라우저와 Cloudflare quick-tunnel 엣지가 stale한viz.css/viz.js/index.html을 물고 있어 “내 수정이 반영 안 됨” 증상이 났던 것을 이 헤더로 해결함. - 운영 함정. Caddyfile의 헤더·
basic_auth변경은caddy reload로 반영되지 않는 경우가 있음. 이때는docker restart kb-docs로 컨테이너를 재가동해야 새 설정이 먹음. 따라서 인증/캐시 관련 변경 후에는 reload가 아니라 restart로 검증할 것.
9. 헤더 / UX
- 포털 헤더 —
📁 Files(→/files/),🔒 Private(→/private/, 미인증 시 로그인 링크),🌓 테마(라이트/다크 토글, localStoragevizTheme2) 제공. - 문서 헤더 런타임 주입 — 각 문서 페이지는
fuseDocHeader()로 헤더 chrome(☰사이드바 토글, 문서 제목,📄 MD 원본,🌓 테마)을 런타임에 주입함. 손수 그린 인라인 SVG(<svg class="dgm">)는 건드리지 않고 보존함. - 좌측 글로벌 사이드바 — viz.js가 body를
#page로 감싸고#sitenav를 앞에 붙여 2열 그리드를 만듦./public/_site.json·/private/_site.json을 절대경로(same-origin)로 fetch해 도메인·토픽·문서 트리를 그리고, 검색 박스로 병합 문서 목록을 부분일치 검색함. 접기 상태는 localStoragevizNav에 영속함.
10. 경로 맵
| 경로 | 역할 | 비고 |
|---|---|---|
~/knowledge-base |
웹 루트 = 컨테이너 /srv:ro. 생성물(HTML·_site.json·catalog) |
Caddy가 read-only로 서빙 |
~/docs-publish |
docker-compose.yml·Caddyfile·.env (운영 ops 파일) |
스택 정의·자격증명 |
~/.claude/skills/md-viz |
파이프라인 마스터(scripts·assets·publish.sh·denylist) | 단일 편집점 |
~/fileshare |
파일허브 데이터 | dufs(ro 다운로드) + filebrowser(rw 관리) 공유 마운트 |
~/Documents/claude-logs |
작업 로그 markdown | SMB로 mac에서 접근 |
11. 현재 콘텐츠 인벤토리
- public (7 도메인, 살균 게이트 적용):
istio(57 md) ·etcd(6) ·k8s(4) ·devtools(3) ·networking(2) ·kernel(1) ·virsh(1)
- private (3 도메인, basic_auth):
homelab(21 md — 작업 로그·인프라 레퍼런스; 이 아키텍처 문서 + 도메인 활성화 런북 포함) ·k8s(4) ·virsh(2)
카운트는 각 아카이브
markdown/*.md기준임. public/private에 같은 도메인명(k8s·virsh)이 있으나 서로 다른 아카이브임(공개분 vs 내부분).
12. 자격증명 위치
- 모든 자격증명은
~/docs-publish/.env에 있음(값은 본 문서에 비포함). 변수명만:PRIVATE_USER/PRIVATE_PASS— private 티어 basic_auth 계정.FILEMGR_USER/FILEMGR_PASS— filebrowser(LAN 관리 UI) 로그인. filebrowser는 강도 검증이 있어 별도 값 사용.FILESHARE_PASS— dufs가 현재 no-auth라 미사용(참고용 보존).
- private의 실제 인증은 bcrypt 해시로
Caddyfile의basic_auth블록에 동기되어 있음(.env의 평문은 운영자 참조용, Caddy가 검증에 쓰는 것은 해시). 평문을 바꾸면 해시도 재생성해 Caddyfile에 반영해야 함. - filebrowser 계정은 LAN 전용 UI에만 쓰여 외부로 노출되지 않음.
13. 향후 (Phase 1)
현재는 quick tunnel이라 kb-tunnel 재시작마다 *.trycloudflare.com URL이 바뀜(북마크·고정 공유 불가). Phase 1 계획:
- 본인 소유 도메인 + Cloudflare named tunnel(토큰 기반)로 교체 → 리붓해도 주소 불변.
- private 티어 앞단에 Cloudflare Access(SSO/이메일 정책)를 선택적으로 추가.
상세 절차(도메인 구매·존 등록·named tunnel 생성·DNS CNAME·Caddy host-split·Access 정책)는 같은 아카이브의
infra__guide-domain-activation(도메인 활성화 런북)을 참조할 것.
14. 운영 절차 요약
# 1) 스택 재배포 (compose 변경 후)
cd ~/docs-publish && docker compose up -d
# 2) 인증/캐시 헤더 변경 시 — reload로 안 먹으면 restart
docker restart kb-docs
# 3) 문서 추가 (private/homelab 예)
bash ~/.claude/skills/md-viz/publish.sh add private/homelab <src.md> <topic>__<type>-<slug>
bash ~/.claude/skills/md-viz/publish.sh reindex private/homelab
bash ~/.claude/skills/md-viz/publish.sh portal # _site.json x2 + /index.html 갱신
# 4) 검증 — 미인증 401, 인증 200
curl -sI http://localhost:8080/private/homelab/<slug>.html # -> 401
curl -sI -u "$PRIVATE_USER:$PRIVATE_PASS" http://localhost:8080/private/homelab/<slug>.html # -> 200
curl -sI http://localhost:8080/public/k8s/ # -> 200 (공개 무영향)
- 새 도메인 아카이브는
publish.sh new <tier> <domain> [title]로 스캐폴드 후add함. - public 대상 작업은 살균 게이트가 누출 시 exit 1로 막으므로, 차단되면 본문을 스크럽하거나 private으로 옮길 것.
참조
- 운영 파일:
~/docs-publish/{docker-compose.yml,Caddyfile,.env} - 파이프라인 마스터:
~/.claude/skills/md-viz/{publish.sh,denylist.txt,PORTAL-CONTRACT.md,SKILL.md} - 도메인 승격 절차: 같은 아카이브
infra__guide-domain-activation(Cloudflare 도메인 활성화 런북)