킹서버(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·실키 미주입 확인.
103 lines
4.0 KiB
Bash
Executable File
103 lines
4.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# 배포 — 코드를 당기고, 지정한 서비스만 다시 빌드해 갈아끼운다.
|
|
#
|
|
# ./deploy.sh 전체
|
|
# ./deploy.sh solution-backend 그 서비스만
|
|
# ./deploy.sh solution-backend solution-worker 여럿
|
|
#
|
|
# 서비스명 대신 컨테이너명(o2o-web4ai-solution-backend)으로 불러도 받는다.
|
|
set -euo pipefail
|
|
cd "$(dirname "$0")"
|
|
|
|
PREFIX=o2o-web4ai
|
|
# api·worker·api-admin 은 이미지 한 벌(o2o-web4ai-backend)을 나눠 쓴다.
|
|
BACKEND_SVCS=(solution-backend solution-worker admin-backend)
|
|
|
|
PULL=1
|
|
ONLY=0
|
|
TARGETS=()
|
|
|
|
usage() {
|
|
cat <<'USAGE'
|
|
사용법: ./deploy.sh [옵션] [서비스...]
|
|
|
|
옵션
|
|
--no-pull git pull 을 건너뛴다(디스크에 있는 코드 그대로 빌드)
|
|
--only 백엔드 형제 서비스를 함께 갈아끼우지 않는다 (아래 ★ 참고)
|
|
-h, --help
|
|
|
|
서비스: solution-backend · solution-worker · solution-frontend · solution-site
|
|
admin-backend · admin-frontend (프로필 admin, 기본 기동에서 빠져 있다)
|
|
o2o-web4ai-solution-backend 처럼 컨테이너명으로 적어도 된다
|
|
|
|
★ solution-backend·solution-worker·admin-backend 는 이미지가 한 벌이다. 하나를 빌드하면 나머지도 새 이미지로
|
|
갈아끼워야 한다 — 안 그러면 옛 코드로 도는 컨테이너가 남는데, 셋 다 "살아 있음" 이라
|
|
화면상으로는 배포가 끝난 것처럼 보인다. --only 는 그걸 알고 건너뛸 때만 쓴다.
|
|
USAGE
|
|
}
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--no-pull) PULL=0 ;;
|
|
--only) ONLY=1 ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
-*) echo "모르는 옵션: $1" >&2; usage >&2; exit 2 ;;
|
|
*) TARGETS+=("${1#"$PREFIX"-}") ;; # 컨테이너명으로 불러도 받는다
|
|
esac
|
|
shift
|
|
done
|
|
|
|
ALL_SVCS=$(docker compose config --services)
|
|
for t in ${TARGETS+"${TARGETS[@]}"}; do
|
|
grep -qx "$t" <<<"$ALL_SVCS" || {
|
|
echo "그런 서비스가 없다: $t" >&2
|
|
echo "있는 것: $(tr '\n' ' ' <<<"$ALL_SVCS")" >&2
|
|
exit 2
|
|
}
|
|
done
|
|
|
|
# ── 코드 ────────────────────────────────────────────────────────────
|
|
if [ "$PULL" = 1 ]; then
|
|
echo "▶ git pull"
|
|
if ! git pull --ff-only 2>&1; then
|
|
echo " ! 당기지 못했다 — 디스크에 있는 코드 그대로 간다." >&2
|
|
echo " (원격 인증이 없으면 밖에서 밀어넣고 --no-pull 로 돌린다)" >&2
|
|
fi
|
|
fi
|
|
echo "▶ 지금 코드: $(git log --oneline -1)"
|
|
|
|
# ── 대상 정하기 ─────────────────────────────────────────────────────
|
|
if [ ${#TARGETS[@]} -eq 0 ]; then
|
|
SVCS=() # 빈 인자 = compose 가 전부로 해석한다
|
|
echo "▶ 대상: 전체"
|
|
else
|
|
SVCS=("${TARGETS[@]}")
|
|
# 백엔드 하나를 건드리면 같은 이미지를 쓰는 형제도 함께 갈아끼운다.
|
|
if [ "$ONLY" = 0 ]; then
|
|
for t in "${TARGETS[@]}"; do
|
|
for b in "${BACKEND_SVCS[@]}"; do [ "$t" = "$b" ] || continue
|
|
for sib in "${BACKEND_SVCS[@]}"; do
|
|
printf '%s\n' "${SVCS[@]}" | grep -qx "$sib" || {
|
|
SVCS+=("$sib")
|
|
echo " + $sib — 같은 이미지를 쓴다(옛 코드로 남지 않게 함께 간다)"
|
|
}
|
|
done
|
|
done
|
|
done
|
|
fi
|
|
echo "▶ 대상: ${SVCS[*]}"
|
|
fi
|
|
|
|
# ── 빌드 · 교체 ─────────────────────────────────────────────────────
|
|
# build 절이 없는 서비스(node:24-alpine · nginx:alpine)는 build 가 조용히 건너뛴다.
|
|
echo "▶ build"
|
|
docker compose build ${SVCS+"${SVCS[@]}"}
|
|
|
|
echo "▶ up -d"
|
|
docker compose up -d --force-recreate ${SVCS+"${SVCS[@]}"}
|
|
|
|
echo
|
|
docker compose ps
|
|
echo
|
|
echo "로그: ./log.sh"
|