o2o-site-AEO/log.sh
Mina Choi 9b4fe4030b [feat] deploy,backend,site: 킹서버 배포 + 설정을 최상위 .env 하나로 통합
킹서버(o2oadmin@172.30.1.36)에 처음 올리면서, 서버에 올려야만 드러나는 결함 넷을 잡았다.
전부 "화면은 뜨는데 안 되는" 종류라 로컬에서는 끝까지 보이지 않는다.

- CORS 허용 오리진(client_url)만 env override 가 없었다. 도커가 굽던 config.local.toml 은
  플레이스홀더라 허용 목록이 localhost:3000~3005 뿐이고, 배포 주소에서는 모든 API 호출이
  프리플라이트에서 죽었다. 서버 로그에는 400 만 남아 원인이 CORS 라는 게 안 보인다
- .env 경로가 세 단계라 solution/.env(없는 파일)를 보고 있었다. 백엔드를 solution/ 아래로
  옮길 때 안 고쳐진 자리. toml 이 값을 들고 있어 로컬에서 드러나지 않았다
- admin 의 "빌더 열기" 가 VITE_SOLUTION_URL 미주입으로 localhost:3000 을 가리켰다
- PUBLIC_API_BASE_URL 은 브라우저가 부르는 주소인데 기본값이 localhost 라 서버에서 즉시 틀린다

설정 — toml 층 제거, pydantic-settings 로 전환 (FastAPI 공식 방식)
- config_loader.py · config.{local,test}.toml.example 삭제, 기본값은 config_models 로
- BaseSettings + env_file. `_apply_*_env_override` 4개 제거 — 키를 손으로 나열하는 구조라
  하나 빠뜨리면 조용히 틀렸고, 실제로 client_url 이 빠져 있었다
- 환경변수 이름은 validation_alias 로 못 박음. 필드명만 두면 `port` 가 흔한 `PORT` 를 먹는다
- 테스트 DB 분리(web4ai_test_db)는 config.test.toml 이 하던 몫이라 APP_ENV 기본값으로 이관
- lru_cache 로 .env 재읽기 방지. 새 코드는 Depends(get_*) 주입 가능
- 호출부 21개 파일 무변경 — server_configs 가 같은 이름을 계속 내보낸다

배포 — 킹서버는 :80 을 호스트 nginx 가 물고 있고 사내망에 열린 건 30xxx 뿐이다
- 컴포즈 포트를 전부 .env 변수로 추출(기본값은 기존 값 그대로, 로컬 무영향)
- 컨테이너 이름을 폴더 구조에 맞춤 — solution-backend·solution-worker·solution-frontend·
  solution-site·admin-backend·admin-frontend. api·web·nginx 는 어느 폴더 코드인지
  이름만으로 알 수 없었고, 백엔드 셋이 이미지 한 벌을 나눠 써서 특히 헷갈렸다
- worker 에 container_name 을 붙여 `-1` 접미사 제거(동시성은 WORKER_CONCURRENCY 가 맡는다)
- 어드민 앱·API 는 compose 프로필 뒤로 — 지금 안 쓴다. 켤 때 --profile admin
- deploy.sh: 서비스 하나를 지정해도 백엔드 형제를 함께 교체한다. 이미지 한 벌을 나눠 써서
  하나만 바꾸면 옛 코드로 도는 컨테이너가 남는데 `ps` 로는 셋 다 살아 있다
- log.sh: 1=전체, 2번부터 개별. compose v2.20 이 커스텀 --format 을 파싱하지 못해 상태가
  전부 "미기동" 으로 보이던 것도 --services --filter 로 교정
- docs/SERVERS.md 신설(접속·경로·포트·DB·sudo 없음), docs/DEVLOG.md 신설

정리
- 개발 전용 자동 로그인 제거 — 편의 하나에 검색 경로의 비동기 대기가 딸려 있었고,
  평문 비밀번호를 .env 에 두라고 권하는 모양새였다
- API 이름을 디렉토리에 맞춤: 사장님/내부 → 솔루션 API · 어드민 API (21곳)
- .env.example 을 읽는 폴더 기준 구역으로 재편 (solution/backend · solution/frontend ·
  solution/site · compose)
- AGENTS.md 에 negosium 브랜치·커밋 규약 명시

검증(킹서버 실측) — 컨테이너 4개 새 이름으로 기동, 솔루션 API·사장님 앱 200,
발행 사이트 404(발행물 없음, 정상), CORS 허용/차단 각 확인, toml 없이 부팅,
APP_ENV=test 시 web4ai_test_db·실키 미주입 확인.
2026-09-01 10:04:36 +09:00

88 lines
3.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# 로그 보기.
#
# ./log.sh 메뉴에서 고른다
# ./log.sh 1 1번 = 전체 로그
# ./log.sh 4 4번 컨테이너
# ./log.sh solution-backend 이름으로 바로
#
# -n <줄> 처음에 보여줄 줄 수 (기본 200)
# --no-f 따라가지 않고 지금까지만 찍고 끝낸다
set -euo pipefail
cd "$(dirname "$0")"
TAIL=200
FOLLOW=1
PICK=""
while [ $# -gt 0 ]; do
case "$1" in
-n) TAIL="$2"; shift ;;
--no-f|--no-follow) FOLLOW=0 ;;
-h|--help) awk 'NR>1 && /^#/ {sub(/^# ?/, ""); print; next} NR>1 {exit}' "$0"; exit 0 ;;
-*) echo "모르는 옵션: $1" >&2; exit 2 ;;
*) PICK="$1" ;;
esac
shift
done
# 번호가 흔들리면 손이 기억한 번호가 다른 컨테이너를 연다 — 순서를 코드로 못 박는다.
ORDER=(solution-backend solution-worker solution-frontend solution-site admin-backend admin-frontend)
AVAILABLE=$(docker compose config --services)
SVCS=()
for s in "${ORDER[@]}"; do grep -qx "$s" <<<"$AVAILABLE" && SVCS+=("$s"); done
# compose 에 서비스가 늘면 뒤에 붙는다(빠뜨리지 않으려고).
while read -r s; do
[ -n "$s" ] || continue
printf '%s\n' "${SVCS[@]}" | grep -qx "$s" || SVCS+=("$s")
done <<<"$AVAILABLE"
# 살아 있는지 한눈에 — 죽은 걸 고르고 "로그가 안 나온다" 하는 자리를 없앤다.
# ★ --format '{{.Service}}' 은 쓰지 않는다. compose v2.20 이 커스텀 템플릿을 파싱하지 못해
# 전부 "미기동" 으로 보인다(명령은 성공해서 틀린 줄 모른다).
RUNNING=$(docker compose ps --services --filter status=running 2>/dev/null || true)
status() {
grep -qx "$1" <<<"$RUNNING" && printf '실행중' || printf '멈춤'
}
menu() {
echo
echo " o2o-web4ai 로그"
echo " ─────────────────────────────"
printf " 1) %-18s %s\n" "전체" ""
local i=2
for s in "${SVCS[@]}"; do
printf " %2d) %-18s %s\n" "$i" "$s" "[$(status "$s")]"
i=$((i + 1))
done
echo " q) 나가기"
echo
}
resolve() { # 번호 또는 이름 → docker compose logs 인자
local pick="$1"
if [ "$pick" = "1" ]; then TARGET=(); return; fi
if [[ "$pick" =~ ^[0-9]+$ ]]; then
local idx=$((pick - 2))
[ "$idx" -ge 0 ] && [ "$idx" -lt "${#SVCS[@]}" ] || { echo "그런 번호가 없다: $pick" >&2; return 1; }
TARGET=("${SVCS[$idx]}"); return
fi
local name="${pick#o2o-web4ai-}"
printf '%s\n' "${SVCS[@]}" | grep -qx "$name" || { echo "그런 컨테이너가 없다: $pick" >&2; return 1; }
TARGET=("$name")
}
if [ -z "$PICK" ]; then
menu
read -rp " 번호: " PICK
[ "$PICK" = "q" ] && exit 0
fi
TARGET=()
resolve "$PICK" || exit 2
ARGS=(--tail="$TAIL")
[ "$FOLLOW" = 1 ] && ARGS+=(-f)
echo "▶ docker compose logs ${ARGS[*]} ${TARGET[*]:-(전체)}"
exec docker compose logs "${ARGS[@]}" ${TARGET+"${TARGET[@]}"}