μ½˜ν…μΈ λ‘œ 이동

μ•„ν‚€ν…μ²˜

AI CLI (claude-code, claude-cli, ...)
β”‚
β”‚ HTTP to 127.0.0.1:3817
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ sotatek-proxy (Gin engine, loopback bind) β”‚
β”‚ β”‚
β”‚ recoveryLogger panic recovery β”‚
β”‚ requestLogger scrub Authorization, β”‚
β”‚ x-api-key, drop query β”‚
β”‚ MiddlewareGitRemote β”‚
β”‚ β”‚ β”‚
β”‚ β”œβ”€ extract peer port from RemoteAddr β”‚
β”‚ β”œβ”€ inspect.GetPidByPeerPort β†’ PID β”‚
β”‚ β”œβ”€ inspect.GetCwdByPid β†’ CWD β”‚
β”‚ β”œβ”€ walk parents (≀3) for .git β”‚
β”‚ β”œβ”€ cache.Get(pid) [verify CWD] β”‚
β”‚ β”œβ”€ git.GetRemoteRaw(cwd) (2s timeout) β”‚
β”‚ └─ inject X-Git-Remote (base64) β”‚
β”‚ β”‚
β”‚ ReverseProxy (NoRoute) β”‚
β”‚ β”œβ”€ stamp User-Agent: sotatek-proxy/X β”‚
β”‚ β”œβ”€ preserve original in β”‚
β”‚ β”‚ X-Forwarded-User-Agent β”‚
β”‚ └─ FlushInterval=-1 (SSE-safe) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ HTTP/2 (ForceAttemptHTTP2)
β–Ό
https://bifrost.sotatek.works/anthropic/v1/*

μˆœμ„œκ°€ μ€‘μš”ν•©λ‹ˆλ‹€. 각 λ ˆμ΄μ–΄λŠ” μš”μ²­ μ‹œ 이 μˆœμ„œλ‘œ μ‹€ν–‰λ˜λ©°, 응닡 μ‹œ μ—­μˆœμœΌλ‘œ μ‹€ν–‰λ©λ‹ˆλ‹€.

#미듀웨어λͺ©μ 
1recoveryLoggerνŒ¨λ‹‰ 볡ꡬ; μš”μ²­ 헀더λ₯Ό μ ˆλŒ€ 좜λ ₯ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
2requestLoggerμš”μ²­λ³„ κ°„κ²°ν•œ ꡬ쑰화 둜그: λ©”μ„œλ“œ, 경둜, μƒνƒœ, μ§€μ—° μ‹œκ°„, client_ip, has_auth/has_api_key λΆˆλ¦¬μ–Έ, has_query ν”Œλž˜κ·Έ. Authorization 값은 μ ˆλŒ€ κΈ°λ‘λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
3MiddlewareGitRemote호좜 PID와 CWDλ₯Ό ν™•μΈν•˜κ³ , git remote -vλ₯Ό μ‹€ν–‰ν•˜λ©°, κ²°κ³Όλ₯Ό base64둜 μΈμ½”λ”©ν•˜μ—¬ X-Git-Remoteλ₯Ό μ„€μ •ν•©λ‹ˆλ‹€. ν΄λΌμ΄μ–ΈνŠΈκ°€ μ œκ³΅ν•œ X-Git-RemoteλŠ” λ¨Όμ € μ œκ±°λ©λ‹ˆλ‹€.
4ReverseProxy (NoRoute)μ—…μŠ€νŠΈλ¦ΌμœΌλ‘œ 1:1 전달, User-Agent μŠ€νƒ¬ν”„, SSEλ₯Ό 버퍼링 없이 μŠ€νŠΈλ¦¬λ°ν•©λ‹ˆλ‹€.

ν”„λ‘μ‹œλŠ” ν΄λΌμ΄μ–ΈνŠΈμ˜ TCP ν”Όμ–΄ 포트 (CLIκ°€ 연결에 μ‚¬μš©ν•œ 포트)λ₯Ό PID둜, κ·Έλ‹€μŒ CWD둜 μ—­μΆ”μ ν•œ ν›„ .git을 μ°Ύμ•„ μ΅œλŒ€ 3개의 μƒμœ„ 디렉토리λ₯Ό νƒμƒ‰ν•©λ‹ˆλ‹€.

OSκ΅¬ν˜„ 방식
macOSlsof -iTCP:<port> -sTCP:ESTABLISHED -F pn (PID), lsof -p <pid> -d cwd -F n (CWD). 2초 μ‹€ν–‰ νƒ€μž„μ•„μ›ƒ.
Linux/proc/net/tcp[6]을 읽어 포트 β†’ μ•„μ΄λ…Έλ“œλ₯Ό λ§€ν•‘ν•˜κ³ , /proc/*/fd/μ—μ„œ μΌμΉ˜ν•˜λŠ” μ†ŒμΌ“μ„ μŠ€μΊ”ν•œ ν›„ /proc/<pid>/cwdλ₯Ό μ½μŠ΅λ‹ˆλ‹€. 동일 μ‚¬μš©μžμ—κ²Œλ§Œ μ μš©λ©λ‹ˆλ‹€.
WindowsIPv4/IPv6 μ—°κ²° ν…Œμ΄λΈ”μ„ μœ„ν•œ GetExtendedTcpTable; CWDλ₯Ό μœ„ν•œ NtQueryInformationProcessλ₯Ό ν†΅ν•œ PEB 탐색. 64λΉ„νŠΈ μ „μš©.

μƒμœ„ 탐색은 일반적인 λŒ€ν™”ν˜• μ…Έ (bash, zsh, fish, sh, dash, ksh, tcsh, csh)μ—μ„œ μ€‘μ§€λ˜λ―€λ‘œ ν”„λ‘μ‹œκ°€ μ…Έμ˜ CWDλ₯Ό κ°€λ‘œμ±„μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

PID β†’ {cwd, git-remote} μΊμ‹œλŠ” TTL(κΈ°λ³Έ 60초)이 μžˆλŠ” sync.Map을 μ‚¬μš©ν•©λ‹ˆλ‹€. Get 호좜 μ‹œλ§ˆλ‹€ ν˜„μž¬ CWDλ₯Ό λ‹€μ‹œ 읽으며, CWDκ°€ μΌμΉ˜ν•˜μ§€ μ•ŠμœΌλ©΄ μΊμ‹œ ν•­λͺ©μ΄ μ œκ±°λ©λ‹ˆλ‹€ β€” μ΄λŠ” PID μž¬μ‚¬μš©μ— λŒ€ν•œ λ°©μ–΄ λ©”μ»€λ‹ˆμ¦˜μž…λ‹ˆλ‹€.

λ°±κ·ΈλΌμš΄λ“œ 정리 μž‘μ—…μ΄ 5λΆ„λ§ˆλ‹€ 만료된 ν•­λͺ©κ³Ό PIDκ°€ 더 이상 μ‚΄μ•„μžˆμ§€ μ•Šμ€ ν•­λͺ©(kill -0)을 μ œκ±°ν•©λ‹ˆλ‹€.

git remote -v μ…Έ μ‹€ν–‰μ—λŠ” 2초의 ν•˜λ“œ νƒ€μž„μ•„μ›ƒμ΄ μžˆμŠ΅λ‹ˆλ‹€. νƒ€μž„μ•„μ›ƒμ΄ λ°œμƒν•˜κ±°λ‚˜ 리λͺ¨νŠΈκ°€ μ—†λŠ” 경우 μš”μ²­μ€ X-Git-Remote 헀더 없이 μ „λ‹¬λ©λ‹ˆλ‹€ β€” μ°¨λ‹¨λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

ν”„λ‘μ‹œλŠ” FlushInterval=-1을 μ‚¬μš©ν•˜λŠ” httputil.ReverseProxyλ₯Ό μ‚¬μš©ν•˜λ©°, μ΄λŠ” μ“°κΈ°λ§ˆλ‹€ κ°•μ œ ν”ŒλŸ¬μ‹œλ₯Ό μˆ˜ν–‰ν•©λ‹ˆλ‹€. SSE / 청크 응닡은 μ—…μŠ€νŠΈλ¦Όμ΄ μ „μ†‘ν•˜λŠ” μ¦‰μ‹œ ν΄λΌμ΄μ–ΈνŠΈμ— λ„λ‹¬ν•©λ‹ˆλ‹€ β€” λ³Έλ¬Έ 버퍼링 μ—†μŒ. ν΄λΌμ΄μ–ΈνŠΈ μ—°κ²° ν•΄μ œ μ‹œ μ—…μŠ€νŠΈλ¦Ό μ»¨ν…μŠ€νŠΈκ°€ μ·¨μ†Œλ©λ‹ˆλ‹€.

GET /_proxy/healthz

버전, 컀밋, μ—…νƒ€μž„, (μ—°κ²°λœ 경우) μΊμ‹œ 톡계λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

{
"status": "ok",
"version": "v0.1.0",
"commit": "abc1234",
"uptime": "1h23m",
"cache": { "entries": 12, "hits": 480, "misses": 64, "hit_rate": 0.882 }
}