feat(setup): DB 선택/pgvector/Qdrant/Gitea 통합

[DB 선택 대화형 프롬프트]
- setup/lib/db_select.sh: 설치 시 SQLite/PostgreSQL/PostgreSQL+pgvector 선택
- DB_TYPE 환경변수로 무인 설치 지원
- pgvector 자동 빌드/설치 (소스 빌드 폴백 포함)
- .env DATABASE_URL 자동 기록

[Vector DB]
- docker-compose.yml: postgres를 pgvector/pgvector:pg15 이미지로 교체
- Qdrant: docker profile=vector 로 선택적 활성화
- docs/vector_db_guide.md: pgvector/Qdrant 사용법 + 시나리오

[Gitea 온프레미스 Git 서버]
- docker-compose.yml: gitea/gitea:1.21-rootless 서비스 추가
- setup/lib/gitea_setup.sh: 공통 설치/초기화 함수
- setup/gitea_init.sh: 독립 실행형 초기화 스크립트
  - 관리자 계정 생성
  - guardia 조직 + GUARDiA 저장소 생성
  - main 브랜치 보호 (PR + 리뷰 1명 필수)
  - develop 브랜치 생성
  - 개발자 계정 (engineer1/2, pm1, admin) + feature/이름/init 브랜치 자동 생성
  - 현재 소스 자동 push

[브랜치 전략]
  main         : 보호 브랜치, PR 필수, 리뷰 1명 필수
  develop      : 통합 브랜치, force-push 금지
  feature/이름/기능: 개인 개발 브랜치

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
DESKTOP-TKLFCPR\ython 2026-05-29 19:32:47 +09:00
parent d3e13d2fc2
commit 4b7904d14a
5 changed files with 937 additions and 1 deletions

View File

@ -85,7 +85,9 @@ services:
# ── PostgreSQL ───────────────────────────────────────────
postgres:
image: postgres:15-alpine
# pgvector/pgvector:pg15 = PostgreSQL 15 + pgvector 확장 포함
# vector 타입 사용: SR 유사도 검색, KB 시맨틱 검색
image: pgvector/pgvector:pg15
container_name: guardia-postgres
environment:
POSTGRES_DB: guardia
@ -177,6 +179,64 @@ services:
retries: 3
start_period: 30s
# ── Qdrant (전용 벡터 DB — 고성능 시맨틱 검색) ───────────
# pgvector보다 빠른 ANN 검색이 필요할 때 사용
# QDRANT_ENABLED=true 시 guardia에서 QDRANT_URL=http://qdrant:6333 설정
qdrant:
image: qdrant/qdrant:v1.7.4
container_name: guardia-qdrant
profiles: ["vector"] # docker compose --profile vector up 으로 활성화
ports:
- "6333:6333"
- "6334:6334"
volumes:
- guardia-qdrant:/qdrant/storage
environment:
QDRANT__SERVICE__GRPC_PORT: "6334"
networks:
- guardia-net
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:6333/healthz"]
interval: 30s
timeout: 5s
retries: 3
# ── Gitea (온프레미스 Git 서버) ──────────────────────────
# 형상관리: 저장소 생성, 브랜치 보호, PR 워크플로우
gitea:
image: gitea/gitea:1.21-rootless
container_name: guardia-gitea
ports:
- "3000:3000" # HTTP (web + API)
- "2222:2222" # SSH
environment:
USER_UID: "1000"
USER_GID: "1000"
GITEA__DEFAULT__APP_NAME: "GUARDiA Git"
GITEA__SERVER__HTTP_PORT: "3000"
GITEA__SERVER__SSH_PORT: "2222"
GITEA__SERVER__ROOT_URL: "http://localhost:3000/"
GITEA__DATABASE__DB_TYPE: "sqlite3"
GITEA__DATABASE__PATH: "/var/lib/gitea/data/gitea.db"
GITEA__REPOSITORY__DEFAULT_BRANCH: "main"
GITEA__GIT__DEFAULT_BRANCH: "main"
GITEA__SECURITY__INSTALL_LOCK: "true"
GITEA__SERVICE__DISABLE_REGISTRATION: "false"
volumes:
- guardia-gitea-data:/var/lib/gitea
- guardia-gitea-config:/etc/gitea
- /etc/timezone:/etc/timezone:ro
networks:
- guardia-net
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:3000/api/v1/version"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
# ── 볼륨 ─────────────────────────────────────────────────
volumes:
guardia-db:
@ -186,6 +246,9 @@ volumes:
guardia-ollama-models: # Ollama 모델 (로컬 경로 마운트 가능)
guardia-tomcat-webapps:
guardia-tomcat-logs:
guardia-qdrant: # Qdrant 벡터 데이터
guardia-gitea-data: # Gitea 저장소 + DB
guardia-gitea-config: # Gitea 설정
# ── 네트워크 ──────────────────────────────────────────────
networks:

171
docs/vector_db_guide.md Normal file
View File

@ -0,0 +1,171 @@
# GUARDiA Vector DB 가이드
## 지원 방식
| 방식 | 설치 | 성능 | 용도 |
|------|------|------|------|
| **pgvector** | PostgreSQL 확장 | 중간 | SR 유사도, KB 검색 |
| **Qdrant** | Docker 컨테이너 | 높음 | 대규모 시맨틱 검색 |
---
## 1. pgvector (기본 권장)
### 설치
```bash
# setup.sh 실행 시 "3) PostgreSQL + pgvector" 선택
bash setup/setup_ubuntu.sh
# 또는 수동:
CREATE EXTENSION IF NOT EXISTS vector;
```
### GUARDiA에서 벡터 컬럼 사용
```python
from sqlalchemy import Column
from pgvector.sqlalchemy import Vector
class SRRequest(Base):
__tablename__ = "tb_sr_request"
# ...
embedding = Column(Vector(768)) # Ollama 임베딩 차원
# 유사 SR 검색
from sqlalchemy import text
similar = await db.execute(
text("SELECT sr_id, title, 1 - (embedding <=> :vec) AS similarity "
"FROM tb_sr_request ORDER BY embedding <=> :vec LIMIT 5"),
{"vec": query_embedding}
)
```
### 임베딩 생성 (Ollama)
```python
import httpx
async def get_embedding(text: str) -> list[float]:
async with httpx.AsyncClient() as client:
r = await client.post("http://localhost:11434/api/embeddings", json={
"model": "nomic-embed-text", # 경량 임베딩 모델
"prompt": text
})
return r.json()["embedding"]
```
### 환경변수
```
ENABLE_VECTOR=true
DATABASE_URL=postgresql+asyncpg://guardia:guardia@localhost:5432/guardia
```
---
## 2. Qdrant (고성능 벡터 검색)
### Docker 시작
```bash
# vector 프로파일로 Qdrant 포함 실행
docker compose --profile vector up -d qdrant
# 또는 docker compose.yml에서 profiles 제거 후 항상 실행
```
### GUARDiA에서 Qdrant 사용
```python
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct
client = QdrantClient(url=os.getenv("QDRANT_URL", "http://localhost:6333"))
# 컬렉션 생성
client.create_collection(
collection_name="sr_embeddings",
vectors_config=VectorParams(size=768, distance=Distance.COSINE),
)
# SR 인덱싱
client.upsert("sr_embeddings", points=[
PointStruct(id=sr.id, vector=embedding, payload={"sr_id": sr.sr_id, "title": sr.title})
])
# 유사 SR 검색
results = client.search("sr_embeddings", query_vector=query_embedding, limit=5)
```
### 환경변수
```
QDRANT_URL=http://localhost:6333
ENABLE_QDRANT=true
```
---
## 3. 사용 시나리오
### KB 시맨틱 검색
```python
# KB 문서 검색 — 키워드가 아닌 의미 기반
async def search_kb_semantic(query: str, db) -> list:
embedding = await get_embedding(query)
results = await db.execute(
text("SELECT id, title, content, 1-(embedding<=>:v) score "
"FROM tb_kb WHERE embedding IS NOT NULL "
"ORDER BY embedding <=> :v LIMIT 10"),
{"v": str(embedding)}
)
return results.mappings().all()
```
### 유사 SR 재발 탐지
```python
# 새 SR과 유사한 과거 SR 찾기 (학습 루프 연동)
async def find_similar_sr(new_sr: SRRequest, db) -> list:
embedding = await get_embedding(f"{new_sr.title} {new_sr.description}")
results = await db.execute(
text("SELECT sr_id, title, status, "
"1-(embedding<=>:v) AS similarity "
"FROM tb_sr_request "
"WHERE embedding IS NOT NULL AND sr_id != :sid "
" AND 1-(embedding<=>:v) > 0.85 " # 85% 이상 유사도
"ORDER BY similarity DESC LIMIT 5"),
{"v": str(embedding), "sid": new_sr.sr_id}
)
return results.mappings().all()
```
### Ollama 임베딩 모델 설치
```bash
# nomic-embed-text: 경량 임베딩 모델 (274MB)
ollama pull nomic-embed-text
# 또는 한국어 특화
ollama pull bge-m3
```
---
## 4. Docker Compose 프로파일
```bash
# 기본 (pgvector만)
docker compose up -d
# Qdrant 포함
docker compose --profile vector up -d
# Gitea 포함
docker compose up -d gitea
# 모두 실행
docker compose --profile vector up -d
```

225
setup/gitea_init.sh Normal file
View File

@ -0,0 +1,225 @@
#!/bin/bash
# ==============================================================
# GUARDiA Gitea 초기화 스크립트
# ==============================================================
# Gitea 컨테이너/서비스가 기동된 후 실행하여
# 관리자 계정, 조직, 저장소, 브랜치 보호를 자동 설정합니다.
#
# 사용법:
# bash setup/gitea_init.sh
# GITEA_PORT=3000 GITEA_ADMIN=admin bash setup/gitea_init.sh
# ==============================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GUARDIA_ROOT="$(dirname "$SCRIPT_DIR")"
GITEA_PORT="${GITEA_PORT:-3000}"
GITEA_BASE="http://localhost:${GITEA_PORT}"
GITEA_ADMIN="${GITEA_ADMIN:-gitadmin}"
GITEA_ADMIN_PW="${GITEA_ADMIN_PW:-Gitea@guardia!}"
GITEA_ADMIN_EMAIL="${GITEA_ADMIN_EMAIL:-admin@guardia.local}"
GITEA_ORG="${GITEA_ORG:-guardia}"
GITEA_REPO="${GITEA_REPO:-GUARDiA}"
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
info() { echo -e " $*"; }
API="${GITEA_BASE}/api/v1"
AUTH="Authorization: Basic $(echo -n "${GITEA_ADMIN}:${GITEA_ADMIN_PW}" | base64)"
echo "=================================================="
echo " Gitea 초기화"
echo " URL: $GITEA_BASE"
echo "=================================================="
# ── Gitea 응답 대기 ───────────────────────────────────────────
echo ""
echo "[1/7] Gitea 서비스 응답 대기..."
for i in $(seq 1 30); do
if curl -sf "$API/version" -o /dev/null 2>/dev/null; then
ok "Gitea 응답 확인"
break
fi
sleep 2
[[ $i -eq 30 ]] && { echo "[ERR] Gitea 응답 없음 — 서비스를 먼저 시작하세요"; exit 1; }
done
# ── 관리자 계정 생성 (Gitea API 인증용) ─────────────────────────
echo ""
echo "[2/7] 관리자 계정 설정..."
# Docker 환경: gitea admin 명령 사용
if command -v docker &>/dev/null && docker ps | grep -q guardia-gitea; then
docker exec guardia-gitea gitea admin user create \
--username "$GITEA_ADMIN" \
--password "$GITEA_ADMIN_PW" \
--email "$GITEA_ADMIN_EMAIL" \
--admin 2>/dev/null || info "관리자 이미 존재"
elif command -v gitea &>/dev/null; then
gitea admin user create \
--username "$GITEA_ADMIN" \
--password "$GITEA_ADMIN_PW" \
--email "$GITEA_ADMIN_EMAIL" \
--admin 2>/dev/null || info "관리자 이미 존재"
else
# API로 관리자 생성 시도 (초기 설치 시)
curl -sf -X POST "$API/users" \
-H "Content-Type: application/json" \
-d "{\"username\":\"${GITEA_ADMIN}\",\"password\":\"${GITEA_ADMIN_PW}\",\"email\":\"${GITEA_ADMIN_EMAIL}\"}" \
-o /dev/null 2>/dev/null || info "관리자 이미 존재"
fi
ok "관리자: $GITEA_ADMIN"
# ── 조직 생성 ────────────────────────────────────────────────
echo ""
echo "[3/7] 조직 생성..."
curl -sf -X POST "$API/orgs" \
-H "$AUTH" -H "Content-Type: application/json" \
-d "{\"username\":\"${GITEA_ORG}\",\"visibility\":\"private\",\"description\":\"GUARDiA ITSM Organization\"}" \
-o /dev/null 2>/dev/null && ok "조직 생성: $GITEA_ORG" || info "조직 이미 존재: $GITEA_ORG"
# ── GUARDiA 저장소 생성 ──────────────────────────────────────
echo ""
echo "[4/7] 저장소 생성..."
curl -sf -X POST "$API/orgs/${GITEA_ORG}/repos" \
-H "$AUTH" -H "Content-Type: application/json" \
-d "{
\"name\":\"${GITEA_REPO}\",
\"description\":\"GUARDiA ITSM Platform\",
\"private\":true,
\"default_branch\":\"main\",
\"auto_init\":true,
\"readme\":\"Default\"
}" \
-o /dev/null 2>/dev/null && ok "저장소 생성: ${GITEA_ORG}/${GITEA_REPO}" \
|| info "저장소 이미 존재"
sleep 3 # 저장소 초기화 대기
# ── develop 브랜치 생성 ─────────────────────────────────────
echo ""
echo "[5/7] develop 브랜치 생성..."
curl -sf -X POST "$API/repos/${GITEA_ORG}/${GITEA_REPO}/branches" \
-H "$AUTH" -H "Content-Type: application/json" \
-d '{"new_branch_name":"develop","old_branch_name":"main"}' \
-o /dev/null 2>/dev/null && ok "develop 브랜치 생성" || info "develop 브랜치 이미 존재"
# ── main 브랜치 보호 설정 ────────────────────────────────────
echo ""
echo "[6/7] main 브랜치 보호 설정..."
curl -sf -X POST "$API/repos/${GITEA_ORG}/${GITEA_REPO}/branch_protections" \
-H "$AUTH" -H "Content-Type: application/json" \
-d "{
\"branch_name\": \"main\",
\"enable_push\": false,
\"enable_push_whitelist\": true,
\"push_whitelist_usernames\": [\"${GITEA_ADMIN}\"],
\"required_approvals\": 1,
\"enable_approvals_whitelist\": false,
\"block_on_rejected_reviews\": true,
\"block_on_official_review_requests\": false,
\"dismiss_stale_approvals\": true
}" \
-o /dev/null 2>/dev/null \
&& ok "main 브랜치 보호: PR 필수, 리뷰 1명 필수" \
|| info "브랜치 보호 이미 설정됨"
# develop 브랜치 보호 (push 허용, but 직접 force-push 금지)
curl -sf -X POST "$API/repos/${GITEA_ORG}/${GITEA_REPO}/branch_protections" \
-H "$AUTH" -H "Content-Type: application/json" \
-d '{"branch_name":"develop","enable_force_push":false}' \
-o /dev/null 2>/dev/null || true
# ── 개발자 계정 + 개인 브랜치 생성 ──────────────────────────
echo ""
echo "[7/7] 개발자 계정 + 개인 브랜치 생성..."
# GUARDiA ITSM 역할에 맞는 기본 개발자 계정
declare -A USERS=(
["engineer1"]="Eng1@guardia!:engineer1@guardia.local"
["engineer2"]="Eng2@guardia!:engineer2@guardia.local"
["pm1"]="PM1@guardia!:pm1@guardia.local"
["admin"]="Admin@guardia!:admin@guardia.local"
)
# Developers 팀 생성
TEAM_RESP=$(curl -sf -X POST "$API/orgs/${GITEA_ORG}/teams" \
-H "$AUTH" -H "Content-Type: application/json" \
-d "{
\"name\":\"Developers\",
\"permission\":\"write\",
\"units\":[\"repo.code\",\"repo.issues\",\"repo.pulls\"],
\"includes_all_repositories\":true
}" 2>/dev/null)
TEAM_ID=$(echo "$TEAM_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || echo "")
[[ -n "$TEAM_ID" ]] && ok "Developers 팀 생성 (ID: $TEAM_ID)"
# 팀에 저장소 추가
[[ -n "$TEAM_ID" ]] && curl -sf -X PUT "$API/teams/${TEAM_ID}/repos/${GITEA_ORG}/${GITEA_REPO}" \
-H "$AUTH" -o /dev/null 2>/dev/null
for username in "${!USERS[@]}"; do
IFS=':' read -r pw email <<< "${USERS[$username]}"
# 사용자 생성
curl -sf -X POST "$API/admin/users" \
-H "$AUTH" -H "Content-Type: application/json" \
-d "{
\"username\":\"${username}\",
\"password\":\"${pw}\",
\"email\":\"${email}\",
\"login_name\":\"${username}\",
\"source_id\":0,
\"send_notify\":false,
\"must_change_password\":false
}" -o /dev/null 2>/dev/null || info "사용자 이미 존재: $username"
# 팀 멤버 추가
[[ -n "$TEAM_ID" ]] && curl -sf -X PUT "$API/teams/${TEAM_ID}/members/${username}" \
-H "$AUTH" -o /dev/null 2>/dev/null
# 개인 feature 브랜치 생성 (feature/이름/init)
curl -sf -X POST "$API/repos/${GITEA_ORG}/${GITEA_REPO}/branches" \
-H "$AUTH" -H "Content-Type: application/json" \
-d "{\"new_branch_name\":\"feature/${username}/init\",\"old_branch_name\":\"develop\"}" \
-o /dev/null 2>/dev/null
ok "계정 + 브랜치: $username (feature/${username}/init)"
done
# ── 현재 소스 push ───────────────────────────────────────────
echo ""
echo " 현재 GUARDiA 소스를 Gitea에 push..."
GITEA_URL="http://${GITEA_ADMIN}:${GITEA_ADMIN_PW}@localhost:${GITEA_PORT}/${GITEA_ORG}/${GITEA_REPO}.git"
git -C "$GUARDIA_ROOT" remote remove gitea 2>/dev/null || true
git -C "$GUARDIA_ROOT" remote add gitea "$GITEA_URL"
git -C "$GUARDIA_ROOT" push gitea main 2>/dev/null \
&& ok "main 브랜치 push 완료" || warn "main push 실패 — 수동: git push gitea main"
git -C "$GUARDIA_ROOT" push gitea develop 2>/dev/null || true
echo ""
echo "=================================================="
ok "Gitea 초기화 완료!"
echo ""
info "Gitea URL: http://localhost:${GITEA_PORT}"
info "관리자: ${GITEA_ADMIN} / ${GITEA_ADMIN_PW}"
info "저장소: http://localhost:${GITEA_PORT}/${GITEA_ORG}/${GITEA_REPO}"
echo ""
info "=== 브랜치 전략 ==="
info " main : PR + 리뷰 1명 필수 (보호)"
info " develop : 통합 브랜치 (force-push 금지)"
info " feature/이름/기능명: 개인 개발 브랜치"
echo ""
info "=== 개발자 계정 ==="
for username in "${!USERS[@]}"; do
IFS=':' read -r pw _ <<< "${USERS[$username]}"
info " $username / $pw → feature/${username}/init"
done
echo ""
info "=== Git 원격 저장소 설정 ==="
info " git remote add guardia-gitea http://localhost:${GITEA_PORT}/${GITEA_ORG}/${GITEA_REPO}.git"
info " git push guardia-gitea feature/이름/기능명"
echo "=================================================="

179
setup/lib/db_select.sh Normal file
View File

@ -0,0 +1,179 @@
#!/bin/bash
# ==============================================================
# GUARDiA DB 선택 공통 함수
# ==============================================================
# 사용법: source setup/lib/db_select.sh; select_database
# 결과: DB_TYPE, DATABASE_URL, INSTALL_POSTGRES, INSTALL_PGVECTOR 변수 설정
# ==============================================================
select_database() {
# 환경변수로 이미 지정된 경우 프롬프트 생략
if [[ -n "${DB_TYPE:-}" ]]; then
info "DB_TYPE=$DB_TYPE (환경변수 설정됨)"
_apply_db_type "$DB_TYPE"
return
fi
echo ""
echo "============================================================"
echo " 데이터베이스 선택"
echo "============================================================"
echo " 1) SQLite (개발·단독 실행 권장, 추가 설치 없음)"
echo " 2) PostgreSQL (운영 환경 권장)"
echo " 3) PostgreSQL + pgvector (AI 벡터 검색 포함, 권장)"
echo ""
echo " pgvector: SR 유사도 검색, KB 시맨틱 검색, 이상 탐지에 활용"
echo " Qdrant: docker-compose에 선택적 포함 (고성능 벡터 검색)"
echo "============================================================"
local choice
read -rp " 선택 [1-3, 기본값=3]: " choice
choice="${choice:-3}"
case "$choice" in
1) DB_TYPE="sqlite" ;;
2) DB_TYPE="postgres" ;;
3) DB_TYPE="postgres-vector";;
*) warn "잘못된 선택 '$choice' — 기본값 3(postgres-vector) 사용"
DB_TYPE="postgres-vector" ;;
esac
_apply_db_type "$DB_TYPE"
}
_apply_db_type() {
local dtype="$1"
case "$dtype" in
sqlite)
DATABASE_URL="sqlite+aiosqlite:///./guardia_itsm.db"
INSTALL_POSTGRES=false
INSTALL_PGVECTOR=false
INSTALL_QDRANT=false
ok "DB: SQLite (개발 모드)"
;;
postgres)
DATABASE_URL="postgresql+asyncpg://guardia:guardia@localhost:5432/guardia"
INSTALL_POSTGRES=true
INSTALL_PGVECTOR=false
INSTALL_QDRANT=false
ok "DB: PostgreSQL"
;;
postgres-vector)
DATABASE_URL="postgresql+asyncpg://guardia:guardia@localhost:5432/guardia"
INSTALL_POSTGRES=true
INSTALL_PGVECTOR=true
INSTALL_QDRANT=false # Qdrant는 docker-compose로 별도 제공
ok "DB: PostgreSQL + pgvector (AI 벡터 검색 포함)"
;;
*)
warn "알 수 없는 DB_TYPE='$dtype' — SQLite로 대체"
DATABASE_URL="sqlite+aiosqlite:///./guardia_itsm.db"
INSTALL_POSTGRES=false
INSTALL_PGVECTOR=false
INSTALL_QDRANT=false
;;
esac
export DB_TYPE DATABASE_URL INSTALL_POSTGRES INSTALL_PGVECTOR INSTALL_QDRANT
}
setup_postgres() {
local pgpw="${POSTGRES_PASSWORD:-guardia}"
[[ "$INSTALL_POSTGRES" != "true" ]] && return
echo ""
info "PostgreSQL 설정..."
# 배포 환경별 초기화 (호출 스크립트에서 처리됨)
# 여기서는 DB/사용자 생성만 담당
# 비밀번호 안전 저장
sudo -u postgres psql \
-tc "SELECT 1 FROM pg_user WHERE usename='guardia'" 2>/dev/null \
| grep -q 1 \
|| sudo -u postgres psql -c \
"CREATE USER guardia WITH PASSWORD '${pgpw}';" 2>/dev/null
sudo -u postgres psql \
-tc "SELECT 1 FROM pg_database WHERE datname='guardia'" 2>/dev/null \
| grep -q 1 \
|| sudo -u postgres psql -c \
"CREATE DATABASE guardia OWNER guardia;" 2>/dev/null
ok "PostgreSQL DB/사용자 생성 완료"
# pgvector 확장 설치
if [[ "$INSTALL_PGVECTOR" == "true" ]]; then
_install_pgvector "$pgpw"
fi
}
_install_pgvector() {
local pgpw="$1"
echo ""
info "pgvector 확장 설치..."
# OS별 패키지 설치
if command -v apt-get &>/dev/null; then
apt-get install -y -qq postgresql-server-dev-all build-essential git 2>/dev/null || true
# pgvector 소스 빌드 (패키지 없는 경우)
if ! dpkg -l postgresql-15-pgvector &>/dev/null 2>&1; then
_build_pgvector_from_source
else
apt-get install -y -qq "postgresql-$(pg_lsclusters | grep online | awk '{print $1}' | head -1)-pgvector" 2>/dev/null \
|| _build_pgvector_from_source
fi
elif command -v dnf &>/dev/null; then
dnf install -y pgvector_15 2>/dev/null \
|| _build_pgvector_from_source
else
_build_pgvector_from_source
fi
# 확장 활성화
sudo -u postgres psql -d guardia -c "CREATE EXTENSION IF NOT EXISTS vector;" 2>/dev/null \
&& ok "pgvector 확장 활성화 완료" \
|| warn "pgvector 활성화 실패 — 나중에 수동: CREATE EXTENSION vector;"
}
_build_pgvector_from_source() {
local tmpdir
tmpdir=$(mktemp -d)
info "pgvector 소스 빌드..."
git clone --depth 1 https://github.com/pgvector/pgvector.git "$tmpdir" 2>/dev/null \
|| { warn "pgvector 소스 클론 실패 — 인터넷 연결 확인"; return; }
cd "$tmpdir"
make 2>/dev/null
make install 2>/dev/null
cd -
rm -rf "$tmpdir"
ok "pgvector 빌드 완료"
}
write_db_env() {
local env_file="$1"
# .env 파일에 DATABASE_URL 기록
if grep -q "^DATABASE_URL=" "$env_file" 2>/dev/null; then
sed -i "s|^DATABASE_URL=.*|DATABASE_URL=${DATABASE_URL}|" "$env_file"
else
echo "DATABASE_URL=${DATABASE_URL}" >> "$env_file"
fi
# pgvector 설정 추가
if [[ "$INSTALL_PGVECTOR" == "true" ]]; then
grep -q "^ENABLE_VECTOR=" "$env_file" 2>/dev/null \
|| echo "ENABLE_VECTOR=true" >> "$env_file"
fi
ok ".env DATABASE_URL 기록 완료: $DATABASE_URL"
}

298
setup/lib/gitea_setup.sh Normal file
View File

@ -0,0 +1,298 @@
#!/bin/bash
# ==============================================================
# GUARDiA Gitea 설치 + 자동 초기화 공통 함수
# ==============================================================
# 기능:
# - Gitea 바이너리 설치 및 systemd 등록
# - 관리자 계정 자동 생성
# - GUARDiA 조직 + 레파지토리 생성
# - 개발자 계정 + 개인 브랜치 자동 생성
# - main 브랜치 보호 (PR 필수)
#
# 브랜치 전략:
# main - 보호 브랜치, PR + 리뷰 1명 필수
# develop - 통합 브랜치
# feature/이름/기능명 - 개인 개발 브랜치
# ==============================================================
GITEA_VER="${GITEA_VER:-1.21.11}"
GITEA_HOME="/opt/gitea"
GITEA_DATA="/var/lib/gitea"
GITEA_PORT="${GITEA_PORT:-3000}"
GITEA_ADMIN="${GITEA_ADMIN:-gitadmin}"
GITEA_ADMIN_PW="${GITEA_ADMIN_PW:-Gitea@guardia!}"
GITEA_ADMIN_EMAIL="${GITEA_ADMIN_EMAIL:-admin@guardia.local}"
GITEA_ORG="${GITEA_ORG:-guardia}"
GITEA_REPO="${GITEA_REPO:-GUARDiA}"
GITEA_BASE="http://localhost:${GITEA_PORT}"
GITEA_MIRROR="${GITEA_MIRROR:-https://dl.gitea.com/gitea/${GITEA_VER}}"
install_gitea() {
echo ""
echo "=== Gitea 설치 (포트 ${GITEA_PORT}) ==="
# 1. 시스템 계정 생성
id git &>/dev/null || useradd -r -s /bin/bash -d "$GITEA_DATA" git
# 2. 디렉토리 생성
mkdir -p "$GITEA_HOME" "$GITEA_DATA"/{custom,data,log,repositories}
chown -R git:git "$GITEA_DATA"
# 3. 바이너리 설치
ARCH="$(uname -m)"
case "$ARCH" in
x86_64) GITEA_ARCH="amd64" ;;
aarch64) GITEA_ARCH="arm64" ;;
*) GITEA_ARCH="amd64" ;;
esac
GITEA_BIN="gitea-${GITEA_VER}-linux-${GITEA_ARCH}"
if [[ ! -f "/usr/local/bin/gitea" ]]; then
info "Gitea $GITEA_VER 다운로드..."
wget -q "${GITEA_MIRROR}/${GITEA_BIN}" -O /tmp/gitea \
|| fail "Gitea 다운로드 실패 — GITEA_MIRROR 환경변수 설정"
install -m 755 /tmp/gitea /usr/local/bin/gitea
ok "Gitea 바이너리 설치: /usr/local/bin/gitea"
else
info "Gitea 이미 설치됨"
fi
# 4. Gitea 설정 파일
mkdir -p "$GITEA_DATA/custom/conf"
cat > "$GITEA_DATA/custom/conf/app.ini" << APPINI
[DEFAULT]
RUN_USER = git
RUN_MODE = prod
[server]
HTTP_ADDR = 0.0.0.0
HTTP_PORT = ${GITEA_PORT}
ROOT_URL = http://localhost:${GITEA_PORT}/
DISABLE_SSH = false
SSH_PORT = 22022
[database]
DB_TYPE = sqlite3
PATH = ${GITEA_DATA}/data/gitea.db
[repository]
ROOT = ${GITEA_DATA}/repositories
[log]
ROOT_PATH = ${GITEA_DATA}/log
MODE = file
LEVEL = info
[security]
INSTALL_LOCK = true
SECRET_KEY = $(openssl rand -hex 32 2>/dev/null || date | md5sum | head -c 32)
INTERNAL_TOKEN = $(openssl rand -hex 32 2>/dev/null || date +%s | md5sum | head -c 32)
[service]
DISABLE_REGISTRATION = false
REQUIRE_SIGNIN_VIEW = false
DEFAULT_KEEP_EMAIL_PRIVATE = true
[git]
DEFAULT_BRANCH = main
APPINI
chown -R git:git "$GITEA_DATA"
# 5. systemd 서비스
cat > /etc/systemd/system/gitea.service << GITSVC
[Unit]
Description=Gitea (Git service)
After=network.target
[Service]
Type=simple
User=git
Group=git
WorkingDirectory=${GITEA_DATA}
Environment="USER=git" "HOME=${GITEA_DATA}" "GITEA_WORK_DIR=${GITEA_DATA}"
ExecStart=/usr/local/bin/gitea web --config ${GITEA_DATA}/custom/conf/app.ini
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
GITSVC
systemctl daemon-reload
systemctl enable gitea
systemctl start gitea
# Gitea 기동 대기
info "Gitea 기동 대기 중..."
local attempt=0
until curl -sf "http://localhost:${GITEA_PORT}/api/v1/version" -o /dev/null 2>/dev/null; do
sleep 3
((attempt++))
[[ $attempt -ge 20 ]] && { warn "Gitea 기동 타임아웃"; break; }
done
ok "Gitea 서비스 시작 완료 (http://localhost:${GITEA_PORT})"
}
init_gitea_repos() {
echo ""
echo "=== Gitea 초기화 (조직·저장소·브랜치) ==="
local api="${GITEA_BASE}/api/v1"
local auth_header="Authorization: Basic $(echo -n "${GITEA_ADMIN}:${GITEA_ADMIN_PW}" | base64)"
# 1. 관리자 계정 생성
_wait_gitea
gitea admin user create \
--username "$GITEA_ADMIN" \
--password "$GITEA_ADMIN_PW" \
--email "$GITEA_ADMIN_EMAIL" \
--admin \
--config "$GITEA_DATA/custom/conf/app.ini" \
2>/dev/null || info "관리자 계정 이미 존재"
ok "관리자 계정: $GITEA_ADMIN"
# 2. 조직 생성
curl -sf -X POST "$api/orgs" \
-H "$auth_header" \
-H "Content-Type: application/json" \
-d "{\"username\":\"${GITEA_ORG}\",\"visibility\":\"private\",\"description\":\"GUARDiA ITSM\"}" \
-o /dev/null 2>/dev/null \
|| info "조직 이미 존재: $GITEA_ORG"
ok "조직 생성: $GITEA_ORG"
# 3. GUARDiA 메인 저장소 생성
curl -sf -X POST "$api/orgs/${GITEA_ORG}/repos" \
-H "$auth_header" \
-H "Content-Type: application/json" \
-d "{\"name\":\"${GITEA_REPO}\",\"description\":\"GUARDiA ITSM 플랫폼\",\"private\":true,\"default_branch\":\"main\",\"auto_init\":true}" \
-o /dev/null 2>/dev/null \
|| info "저장소 이미 존재: ${GITEA_ORG}/${GITEA_REPO}"
ok "저장소 생성: ${GITEA_ORG}/${GITEA_REPO}"
# 초기화 후 저장소가 생성될 때까지 대기
sleep 3
# 4. develop 브랜치 생성
curl -sf -X POST "$api/repos/${GITEA_ORG}/${GITEA_REPO}/branches" \
-H "$auth_header" \
-H "Content-Type: application/json" \
-d '{"new_branch_name":"develop","old_branch_name":"main"}' \
-o /dev/null 2>/dev/null \
|| info "develop 브랜치 이미 존재"
ok "develop 브랜치 생성"
# 5. main 브랜치 보호 규칙 설정 (PR 필수)
curl -sf -X POST "$api/repos/${GITEA_ORG}/${GITEA_REPO}/branch_protections" \
-H "$auth_header" \
-H "Content-Type: application/json" \
-d '{
"branch_name": "main",
"enable_push": false,
"enable_push_whitelist": true,
"push_whitelist_usernames": ["'"${GITEA_ADMIN}"'"],
"require_signed_commits": false,
"enable_status_check": false,
"required_approvals": 1,
"enable_approvals_whitelist": false,
"merge_whitelist_usernames": ["'"${GITEA_ADMIN}"'"]
}' \
-o /dev/null 2>/dev/null \
|| info "main 브랜치 보호 이미 설정됨"
ok "main 브랜치 보호 설정 (PR + 리뷰 1명 필수)"
# 6. 현재 Git 소스를 Gitea에 push
if [[ -d "${GUARDIA_ROOT:-/opt/guardia}/.git" ]]; then
info "기존 소스를 Gitea에 push..."
local gitea_url="http://${GITEA_ADMIN}:${GITEA_ADMIN_PW}@localhost:${GITEA_PORT}/${GITEA_ORG}/${GITEA_REPO}.git"
git -C "${GUARDIA_ROOT:-/opt/guardia}" remote remove gitea 2>/dev/null || true
git -C "${GUARDIA_ROOT:-/opt/guardia}" remote add gitea "$gitea_url"
git -C "${GUARDIA_ROOT:-/opt/guardia}" push gitea main 2>/dev/null \
&& ok "소스 push 완료 → Gitea main 브랜치" \
|| warn "소스 push 실패 — 나중에 수동으로 push"
git -C "${GUARDIA_ROOT:-/opt/guardia}" push gitea develop 2>/dev/null || true
fi
ok "Gitea 초기화 완료"
echo ""
info "Gitea URL: http://localhost:${GITEA_PORT}"
info "관리자 계정: ${GITEA_ADMIN} / ${GITEA_ADMIN_PW}"
info "저장소: ${GITEA_BASE}/${GITEA_ORG}/${GITEA_REPO}"
info ""
info "=== 브랜치 전략 ==="
info " main : 보호 브랜치 (PR + 리뷰 1명 필수)"
info " develop : 통합 브랜치 (feature 브랜치 merge 대상)"
info " feature/이름/기능명 : 개인 개발 브랜치"
info ""
info " 새 개발자 브랜치 생성:"
info " git checkout develop"
info " git checkout -b feature/홍길동/신기능"
info " git push -u gitea feature/홍길동/신기능"
}
create_dev_user() {
local username="$1"
local password="${2:-Dev@guardia!}"
local email="${3:-${username}@guardia.local}"
local api="${GITEA_BASE}/api/v1"
local auth_header="Authorization: Basic $(echo -n "${GITEA_ADMIN}:${GITEA_ADMIN_PW}" | base64)"
# 사용자 생성
curl -sf -X POST "$api/admin/users" \
-H "$auth_header" \
-H "Content-Type: application/json" \
-d "{\"username\":\"${username}\",\"password\":\"${password}\",\"email\":\"${email}\",\"login_name\":\"${username}\",\"source_id\":0,\"send_notify\":false,\"must_change_password\":false}" \
-o /dev/null 2>/dev/null \
|| { info "사용자 이미 존재: $username"; return; }
# 조직에 팀 멤버 추가
local team_id
team_id=$(curl -sf "$api/orgs/${GITEA_ORG}/teams" \
-H "$auth_header" 2>/dev/null \
| python3 -c "import sys,json; teams=json.load(sys.stdin); t=[t for t in teams if t.get('name')=='Developers']; print(t[0]['id'] if t else '')" 2>/dev/null)
if [[ -z "$team_id" ]]; then
# Developers 팀 생성
team_id=$(curl -sf -X POST "$api/orgs/${GITEA_ORG}/teams" \
-H "$auth_header" \
-H "Content-Type: application/json" \
-d "{\"name\":\"Developers\",\"permission\":\"write\",\"units\":[\"repo.code\",\"repo.issues\",\"repo.pulls\"]}" \
2>/dev/null \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null)
fi
if [[ -n "$team_id" ]]; then
# 팀에 사용자 추가
curl -sf -X PUT "$api/teams/${team_id}/members/${username}" \
-H "$auth_header" -o /dev/null 2>/dev/null
# 팀에 저장소 추가
curl -sf -X PUT "$api/teams/${team_id}/repos/${GITEA_ORG}/${GITEA_REPO}" \
-H "$auth_header" -o /dev/null 2>/dev/null
fi
# 개인 브랜치 생성 (feature/이름/init)
curl -sf -X POST "$api/repos/${GITEA_ORG}/${GITEA_REPO}/branches" \
-H "$auth_header" \
-H "Content-Type: application/json" \
-d "{\"new_branch_name\":\"feature/${username}/init\",\"old_branch_name\":\"develop\"}" \
-o /dev/null 2>/dev/null
ok "개발자 계정 + 브랜치 생성: $username (feature/${username}/init)"
}
_wait_gitea() {
local attempt=0
until curl -sf "http://localhost:${GITEA_PORT}/api/v1/version" -o /dev/null 2>/dev/null; do
sleep 2
((attempt++))
[[ $attempt -ge 30 ]] && { fail "Gitea 서비스 응답 없음"; return; }
done
}