homelab89 Docs Logs Legacy Files ☰ TOC 🌓
refhomelab 2026-06-29portalarchitecturemd-vizhomelab

통합 위키 포털 아키텍처 (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-filesharekb-filemgruser: "1000:1000"(jinsoo)으로 돌아 호스트 파일 소유권과 일치시킴.

5. 파일허브 — 다운로드/관리 분리 (+왜)

파일허브는 다운로드 경로(dufs)관리 경로(filebrowser) 를 의도적으로 두 컨테이너로 분리함.

  • 다운로드(dufs, kb-fileshare) — read-only·no-auth로 /files/에 공개. 모든 동작(목록·zip·검색·다운로드)이 평범한 GET임. 로그인 버튼도 업로드 버튼도 없어 혼동 여지 자체가 없음. 사내에서 첨부 받을 때 쓰는 경로임.
  • 관리(filebrowser, kb-filemgr) — 업로드·폴더 생성·이동·삭제·압축이 되는 진짜 파일 매니저. LAN IP 192.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-svg placeholder를 사람이/에이전트가 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는 RFC5737 203.0.113.x 같은 문서용 대역으로 redaction함.
  • private basic_auth. /private/*는 Caddy basic_auth(admin + bcrypt 해시) 뒤에 있음.
  • 메타유출 경계. /private/_site.json(검색·nav용 도메인·문서 제목 메타)까지 handle /private* 안에 있어 auth 뒤에서만 서빙됨. 따라서 미인증 사용자는 private 도메인이 몇 개인지, 문서 제목이 무엇인지조차 못 봄. viz.js는 미인증 시 /private/_site.json에서 401을 받아 제목 없는 🔒 Private — log in 링크 하나만 그림.
교훈 — denylist는 floor지 ceiling이 아님

살균 게이트는 알려진 토큰의 자동 차단(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/, 미인증 시 로그인 링크), 🌓 테마(라이트/다크 토글, localStorage vizTheme2) 제공.
  • 문서 헤더 런타임 주입 — 각 문서 페이지는 fuseDocHeader()로 헤더 chrome( 사이드바 토글, 문서 제목, 📄 MD 원본, 🌓 테마)을 런타임에 주입함. 손수 그린 인라인 SVG(<svg class="dgm">)는 건드리지 않고 보존함.
  • 좌측 글로벌 사이드바 — viz.js가 body를 #page로 감싸고 #sitenav를 앞에 붙여 2열 그리드를 만듦. /public/_site.json·/private/_site.json을 절대경로(same-origin)로 fetch해 도메인·토픽·문서 트리를 그리고, 검색 박스로 병합 문서 목록을 부분일치 검색함. 접기 상태는 localStorage vizNav에 영속함.

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 해시Caddyfilebasic_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 도메인 활성화 런북)

Files