Skip to content

Repository files navigation

caddymap

로컬 개발용 통합 리버스 프록시 + 웹 관리 UI. 여러 dev 프로젝트의 로컬 포트를 *.test 같은 호스트명으로 묶고, 브라우저에서 신뢰된 HTTPS로 접속할 수 있게 해줍니다.

  • 🌐 웹 UI 로 /etc/hosts DNS 항목 + 로컬 포트 매핑을 한 곳에서 관리
  • 🔒 기존 Caddy CA Root 재사용 — 이미 시스템에 신뢰된 내부 CA를 그대로 사용하므로 브라우저 경고 없이 HTTPS 동작 (새 인증서/Root 생성 안 함)
  • ⚡ Caddy Admin API 로 동적 설정 — 사이트 추가/삭제 시 재시작 불필요
  • ♻️ 한 번 띄워두면 모든 dev 프로젝트에서 재사용

⚠️ 로컬 개발 전용 — 쿠키가 로그에 평문으로 기록됩니다

미디어 미리보기 기능을 위해 Caddy의 기본 자격증명 가림(redaction)을 끄고 (should_log_credentials) 프록시를 거치는 모든 요청의 Cookie / Authorization 헤더가 평문 그대로 data/access.log 에 기록됩니다. 즉 access 로그를 읽을 수 있는 사람은 세션 쿠키·인증 토큰을 그대로 탈취할 수 있습니다.

  • 이 도구는 반드시 로컬 개발 환경(loopback)에서만 사용하세요. 운영/스테이징/공유 서버나 신뢰할 수 없는 네트워크에 노출하지 마세요.
  • data/access.log (와 백업 파일)은 민감 정보이므로 공유·커밋·업로드 금지입니다. (이미 .gitignore 대상이지만, 외부로 보내지 않도록 주의하세요.)
  • 쿠키 로깅을 원치 않으면 lib/caddy.js 의 should_log_credentials: true 를 제거하고 컨트롤 서버를 재시작하세요. 단, 이 경우 인증이 필요한 자산의 미리보기는 동작하지 않습니다.

동작 구조

브라우저 ──HTTPS(internal CA)──▶ Caddy(:443) ──HTTP──▶ localhost:<포트> (dev 서버)
                                   ▲
            컨트롤 서버(Node, root) ┘  ← 웹 UI(:7879) / Admin API(:2019) / /etc/hosts 관리
  • 컨트롤 서버(server.js)가 root 권한으로 실행되어:

    1. 기존 Caddy 프로세스를 종료하고, 이 디렉토리에서 caddy run 으로 Caddy를 자식 프로세스로 실행/감독
    2. 사이트 목록(data/sites.json)으로부터 Caddy JSON 설정을 만들어 Admin API로 push
    3. /etc/hosts 의 관리 블록(# BEGIN/END caddymap)을 동기화
    4. 웹 UI 제공 (http://127.0.0.1:7879)
  • CA Root 재사용: Caddy 설정의 storage.root 를 기존 데이터 디렉토리 (~/Library/Application Support/Caddy)로 지정 → tls internal 이 기존 pki/authorities/local 의 root CA를 그대로 사용합니다.


사용법

시작

./start.sh

/etc/hosts 수정과 :443 바인딩을 위해 sudo 비밀번호를 한 번 묻습니다. 실행되면 브라우저에서 웹 UI를 엽니다:

http://127.0.0.1:7879

사이트 추가

웹 UI에서:

  1. 호스트명 입력 (예: myapp.test, local-foo.example.com)
  2. 대상 포트 입력 (로컬 dev 서버 포트, 예: 3000)
  3. 추가 클릭

→ /etc/hosts 에 127.0.0.1 myapp.test 자동 추가, Caddy에 라우트 등록, 내부 CA로 인증서 자동 발급. 바로 https://myapp.test 접속 가능.

원본 Host 헤더 유지 옵션: 기본 켜짐(원본 호스트를 업스트림에 전달). Vite 등 host 검사를 하는 dev 서버에서 문제가 있으면 끄세요(업스트림에 localhost:포트 로 전달).

경로 기반 라우팅 (path → 다른 포트)

한 호스트의 특정 경로를 다른 업스트림으로 보낼 수 있습니다. 폼의 경로 라우팅에서 + 경로 추가 후 경로 접두사(예 /api)와 포트(예 8080)를 입력하세요.

  • /api, /api/* 둘 다 매칭됩니다.
  • strip 체크 시 전달 전에 접두사를 제거합니다 (/api/users → /users).
  • 경로 라우팅에 매칭되지 않는 나머지는 기본 대상 포트로 갑니다.
  • 모노레포에서 app.test/api → :8080, 그 외 → :3000 같은 구성에 유용합니다.

정적 파일 서빙

dev 서버 없이 디렉토리를 바로 HTTPS로 서빙합니다. 폼 상단에서 📁 정적 파일 모드를 선택하고 디렉토리 절대 경로를 입력하세요. 디렉토리 목록 표시를 켜면 인덱스가 없는 폴더에서 파일 목록(browse)을 보여줍니다. 빌드 산출물(dist/)·문서 미리보기에 좋습니다.

CORS 허용 / 커스텀 응답 헤더

  • CORS 허용 체크 시 Access-Control-Allow-* 응답 헤더를 주입하고 OPTIONS 프리플라이트에 자동으로 204를 응답합니다 (Origin 반사 + credentials 허용). 프론트-백 분리 개발 시 CORS 우회.
  • 커스텀 응답 헤더 에서 임의의 응답 헤더(키: 값)를 추가로 주입할 수 있습니다.

Docker 컨테이너 자동 탐지 (포트 탭)

상단 포트 탭에서 Docker가 실행 중이면 🐳 Docker 컨테이너 패널이 나타나, 포트를 게시한 실행 중인 컨테이너 목록을 보여줍니다. 포트 칩(:5432 → 5432)을 클릭하면 사이트 탭으로 이동하며 폼이 자동으로 채워집니다 (호스트명 = 컨테이너명.test). ↻ 새로고침으로 갱신합니다.

리스닝 포트 자동 탐지 (포트 탭)

상단 포트 탭에서 로컬에서 LISTEN 중인 dev 서버 포트(비-Docker 프로세스 포함)를 보여줍니다. lsof 로 loopback/전체 인터페이스 리스너만 추려서 프로세스명·PID와 함께 표시하고, 칩(+ vite-5173.test)을 클릭하면 사이트 탭으로 이동하며 호스트명·포트가 폼에 자동으로 채워집니다. 도구 자신의 포트(:7879/:443/:2019)는 제외됩니다. ↻ 새로고침으로 갱신합니다.

Basic 인증으로 사이트 보호

폼의 🔒 Basic 인증 에서 사용을 켜고 사용자명/비밀번호를 입력하면 해당 사이트가 HTTP Basic 인증으로 보호됩니다. 스테이징 흉내·인증 게이트 테스트에 유용합니다.

  • 비밀번호는 저장 시 caddy hash-password(bcrypt)로 해싱되어 저장됩니다. 평문은 디스크에 남지 않습니다.
  • 수정 시 비밀번호를 비워두면 기존 비밀번호가 유지됩니다(사용자명만 변경 가능).
  • CORS 프리플라이트(OPTIONS)는 인증을 거치지 않으므로 CORS와 함께 써도 됩니다.

업스트림 다운 시 안내 페이지

프록시 대상(dev 서버)이 꺼져 있어 연결에 실패하면, 502 raw 대신 친절한 메인터넌스 안내 페이지(호스트명·오류코드 포함)를 표시합니다.

통계 탭

상단 통계 탭에서 access.log를 집계해 총 요청 / 에러율 / 평균 응답시간과 호스트별 요청 수·에러·평균/최대 응답시간·전송량·상태코드 분포 막대를 보여줍니다. ↻ 새로고침으로 최신 집계를 가져옵니다.

상단에는 시간대별 트래픽 차트가 함께 표시됩니다. access.log의 타임스탬프를 시간 범위에 맞춰 자동 버킷팅(초~시간 단위 자동 선택)하여 요청량을 막대로, 5xx 에러를 빨간색으로 겹쳐 보여줍니다. 막대에 마우스를 올리면 해당 구간의 요청 수·에러·평균 응답시간을 봅니다. 막대를 클릭하면 그 시간 구간에 해당하는 로그가 차트 아래에 펼쳐져, 트래픽 급증·에러 구간을 바로 드릴다운해 볼 수 있습니다(‘미디어 제외’ 토글 지원).

CLI (uc)

웹 UI 없이 터미널에서 사이트를 관리합니다 (컨트롤 서버가 실행 중이어야 함):

uc list                        # 사이트 + 업스트림 상태
uc add myapp.test 3000         # 프록시 사이트 추가 (--cors, --auth user:pass 옵션)
uc add docs.test --static ./dist --browse   # 정적 디렉토리 서빙
uc rm myapp.test               # 삭제
uc enable|disable myapp.test   # 활성/비활성
uc open myapp.test             # 브라우저에서 열기
uc docker                      # 포트 게시 컨테이너 목록
uc ports                       # 로컬 리스닝 포트 목록 (dev 서버 탐지)

전역에서 uc 를 쓰려면 프로젝트 루트에서 npm link 하세요. UI_HOST/UI_PORT 환경변수를 따릅니다.

MCP 서버 (AI 에이전트 연동)

mcp-server.js 는 컨트롤 서버의 REST API를 MCP(Model Context Protocol) 툴로 노출합니다. Claude Code 등 MCP 클라이언트에서 사이트를 추가/조회하고, access 로그·통계·헬스를 읽을 수 있습니다. stdio 트랜스포트(JSON-RPC over stdin/stdout)로 동작하며, 내부적으로는 Caddy admin이 아니라 컨트롤 서버(http://127.0.0.1:7879)에 붙으므로 컨트롤 서버가 실행 중이어야 합니다.

Claude Code 에 등록:

claude mcp add caddymap -- node /절대경로/caddymap/mcp-server.js
# 또는 npm link 후: claude mcp add caddymap -- caddymap-mcp

노출되는 툴:

툴 설명
get_status 전체 상태(Caddy 동작 여부·사이트 수·CA·admin) — 먼저 호출
list_sites 등록된 사이트 목록
add_site 사이트 추가 (hostname 필수; 프록시는 targetPort, 정적은 type:"static"+root) — routes·headers·auth·cors·preserveHost·https·browse·enabled 옵션
update_site 사이트 수정 (id 필수; headers/auth에 null 전달 시 제거, auth.password 전달 시 재해싱)
toggle_site / remove_site 활성 토글 / 삭제 (id 필수)
tail_access_log access 로그 tail(tail/host/method/status/errorsOnly 필터)
logs_in_range 시간 구간(from/to, epoch 초) access 로그 — 차트 스파이크 드릴다운
clear_access_log access 로그 비우기(되돌릴 수 없음)
get_stats 전체/호스트별 트래픽 통계 + 시계열(series/bucketSec)
check_health 업스트림 TCP 헬스(502 원인 진단)
list_docker / list_ports 포트 게시 컨테이너 / 로컬 리스닝 포트
export_sites / import_sites 사이트 목록 내보내기 / 가져오기(merge:true 시 병합, 기본 전체 교체)
sync Caddy 설정 + /etc/hosts 강제 재적용

UI_HOST/UI_PORT 환경변수를 따릅니다. 진단 출력은 stderr로만 나가고 stdout은 MCP 프로토콜 전용입니다.

업스트림 헬스 체크

각 사이트 카드에 대상 포트의 실시간 상태 점이 표시됩니다 (5초마다 TCP 연결로 확인):

  • 🟢 응답 — dev 서버가 떠 있음
  • 🔴 닫힘 — 포트가 닫힘(dev 서버 미실행) → 502가 떠도 프록시가 아니라 내 서버 문제임을 바로 구분
  • ⚪ 비활성 — 꺼둔 사이트

각 카드의 ↗ 열기 버튼으로 https://호스트명 을 새 탭에서 바로 엽니다.

사이트 목록 내보내기 / 가져오기

등록된 사이트 헤더의 ⬇ 내보내기 / ⬆ 가져오기 로 사이트 매핑을 JSON 파일로 주고받습니다. 새 머신·팀원과 설정을 공유할 때 사용하세요. 가져올 때 병합(같은 호스트는 덮어씀)과 전체 교체 중 선택할 수 있습니다.

로그 탭 (네트워크 트래픽 보기)

웹 UI 상단의 로그 탭에서 프록시를 거쳐 가는 모든 요청을 실시간으로 볼 수 있습니다.

  • 시간 · 메서드 · 상태코드(색상) · 호스트 · 경로 · 응답시간 · 크기 · 클라이언트 IP
  • 라이브: 1.5초마다 새 요청을 증분으로 가져와 자동 추가 (tail -f 방식)
  • 자동 스크롤: 새 로그가 오면 맨 아래로 따라감
  • 필터: host/경로/메서드/상태코드로 즉시 필터링
  • 미디어 제외: 이미지/동영상 요청을 숨겨 핵심 트래픽만 보기
  • 업스트림 에러 강조: 502/503/504(대상 서버 연결 실패)는 행 전체가 빨갛게(⚠) 표시
  • 지우기: access.log 비우기

행을 클릭하면 요약 / 요청 헤더 / 응답 헤더 / RAW 탭으로 전체 정보를 봅니다.

  • 미디어 미리보기: 이미지/동영상 요청 행을 클릭하면 해당 자산을 다이얼로그에서 바로 미리봅니다. 컨트롤 서버가 로그에 남은 원본 요청(쿠키 포함)을 Caddy로 재생해 가져오므로, 브라우저의 서드파티 쿠키 차단 없이 인증이 필요한 자산도 표시됩니다. 이 기능 때문에 access 로그에 쿠키가 평문으로 남습니다 — 위의 ⚠️ 경고를 참고하세요. (Caddy가 가린 쿠키는 재생할 수 없어 401/403이면 “↗ 새 탭”으로 직접 열라고 안내합니다.)

Caddy access 로그는 data/access.log 에 JSON 형식으로 기록되며, 5MB마다 자동 로테이션(백업 1개, 최대 3일)되어 디스크를 무한히 차지하지 않습니다.

⚠️ 자격증명 로깅: 미디어 미리보기를 위해 should_log_credentials 가 켜져 있어 Cookie/Authorization 헤더가 access 로그에 평문으로 남습니다. 로컬 개발 전용으로만 쓰고 로그 파일을 외부에 공유하지 마세요.

로그 탭을 처음 추가한 뒤에는 컨트롤 서버를 재시작해야 access 로깅이 적용됩니다 (./daemon.sh restart 또는 포그라운드면 Ctrl+C 후 ./start.sh). 이후 브라우저 새로고침.

중지 / 정리

./stop.sh        # 컨트롤 서버 + Caddy 종료 (/etc/hosts 블록은 유지)
./uninstall.sh   # 위 + /etc/hosts 관리 블록 제거

백그라운드 데몬으로 실행 (launchd)

포그라운드(./start.sh) 대신 macOS launchd LaunchDaemon으로 등록하면, 터미널을 닫아도 계속 실행되고 재부팅 시 자동 시작됩니다. root 데몬으로 돌기 때문에 /etc/hosts 수정과 :443 바인딩이 그대로 동작합니다.

⚠️ 포그라운드(./start.sh)가 켜져 있다면 먼저 Ctrl+C 로 종료하세요 (포트 충돌 방지).

./daemon.sh install      # 데몬 설치 + 시작 (부팅 시 자동 실행)
./daemon.sh status       # 상태 확인 (launchd + 웹 UI 응답)
./daemon.sh restart      # 재시작
./daemon.sh logs         # 로그 실시간 보기 (tail -f data/daemon.log)
./daemon.sh stop         # 정지 (재부팅해도 안 뜸)
./daemon.sh start        # 다시 시작
./daemon.sh uninstall    # 데몬 정지 + plist 제거

sudo 없이 실행하세요 — 권한이 필요한 부분(plist 복사, launchctl)만 자동으로 sudo 합니다. (npm run daemon:install, daemon:status, daemon:logs 등도 동일하게 동작)

동작 방식

  • plist 위치: /Library/LaunchDaemons/com.caddymap.daemon.plist (root:wheel, 644)
  • KeepAlive=true — 컨트롤 서버가 죽으면 launchd가 자동 재시작. Caddy가 비정상 종료되면 컨트롤 서버도 함께 종료시켜 전체 스택을 깨끗하게 재시작합니다.
  • plist에 CADDY_STORAGE(기존 CA Root 경로)와 node/caddy 절대 경로를 박아두어, launchd가 root로 실행해도 기존 CA를 정확히 재사용합니다.
  • 로그: data/daemon.log
  • node 경로가 plist에 고정되므로, nvm 으로 node 버전을 바꾸면 ./daemon.sh install 을 다시 실행해 경로를 갱신하세요.

설정 (환경변수)

변수 기본값 설명
UI_PORT 7879 웹 UI 포트
UI_HOST 127.0.0.1 웹 UI 바인드 주소
CADDY_ADMIN http://localhost:2019 Caddy Admin API
CADDY_STORAGE ~/Library/Application Support/Caddy Caddy 데이터 디렉토리(=기존 CA Root 위치)
CADDY_BIN caddy caddy 실행 파일 경로
UI_ALLOWED_HOSTS (없음) 관리 API 접근을 허용할 추가 host:port (쉼표 구분). LAN 노출 시에만

보안 (관리 API 보호)

컨트롤 서버(:7879)는 root 권한으로 돌고 인증이 없으므로, 사용자가 브라우저에 열어둔 악성 페이지가 이를 조작하지 못하도록 두 가지 검사를 합니다 (/api/* 한정):

  • Host allowlist — 127.0.0.1:7879 · localhost:7879 · [::1]:7879 만 허용. DNS-rebinding(악성 호스트명을 127.0.0.1로 재해석하는 공격)을 차단합니다.
  • Origin 검사 — 상태를 바꾸는 요청(POST/PUT/PATCH/DELETE)에서 Origin이 있으면 allowlist와 일치해야 합니다(브라우저발 CSRF 차단). 없으면 통과 — 그래서 uc CLI·curl· 스크립트는 영향 없이 그대로 동작합니다.

프록시 트래픽(:443 → dev 서버)은 이 검사와 무관합니다. https://*.test 접속은 그대로입니다. LAN(폰 등)에서 관리 UI를 열어야 하면 UI_ALLOWED_HOSTS=192.168.0.x:7879 처럼 추가하세요.


사전 요구사항

  • macOS, Caddy 설치 (brew install caddy)
  • Node.js 18+ (의존성 없음 — npm install 불필요)
  • 기존에 Caddy 내부 CA가 시스템에 신뢰되어 있어야 함. 안 되어 있다면 한 번:
    sudo caddy trust

파일 구조

caddymap/
├── server.js          # 컨트롤 서버 (root 실행) — HTTP API + 정적 UI + 사이트 검증 + 라이프사이클
├── cli.js             # `uc` CLI — REST API 위의 얇은 터미널 클라이언트
├── mcp-server.js      # MCP stdio 서버 — REST API를 MCP 툴로 노출 (AI 에이전트 연동)
├── config.js          # 설정/경로 (기존 CA storage 경로 계산 포함)
├── lib/
│   ├── caddy.js       # Caddy 프로세스 관리 + 설정 생성(경로라우팅/정적/CORS/인증/에러) + Admin API
│   ├── hosts.js       # /etc/hosts 관리 블록 편집
│   ├── store.js       # sites.json 읽기/쓰기
│   ├── health.js      # 업스트림 TCP 헬스 체크
│   ├── logs.js        # access.log 증분 tail
│   ├── docker.js      # docker ps 파싱 (포트 게시 컨테이너 탐지)
│   ├── ports.js       # lsof 파싱 (로컬 리스닝 포트 탐지)
│   └── stats.js       # access.log 트래픽 집계
├── public/            # 웹 UI (index.html / app.js / style.css)
├── data/sites.json    # 사이트 매핑 (영속 상태, 단일 소스 오브 트루스)
├── data/caddy.json    # buildCaddyConfig() 산출물 (생성물 — 직접 편집 금지)
├── daemon.sh          # launchd LaunchDaemon 설치/관리
├── start.sh / stop.sh / uninstall.sh
├── CLAUDE.md          # Claude Code 작업 가이드
└── INSTALL.md         # 처음부터 설치 가이드 (macOS)

About

macOS 로컬 개발용 통합 리버스 프록시 + 웹 관리 UI — 기존 Caddy 내부 CA를 재사용해 *.test 도메인을 신뢰된 HTTPS로 연결 (/etc/hosts 자동 관리, 무중단 동적 설정)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages