forked from baron/baron-sso
Baron SSO 다중 인스턴스 배포 템플릿
This commit is contained in:
76
deploy/create-instance.sh
Normal file
76
deploy/create-instance.sh
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# =================================================================
|
||||||
|
# Baron SSO 인스턴스 자동 생성 스크립트 (Full Infrastructure)
|
||||||
|
# =================================================================
|
||||||
|
|
||||||
|
if [ "$#" -ne 2 ]; then
|
||||||
|
echo "❌ Usage: $0 [INSTANCE_NAME] [PORT_PREFIX]"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
INSTANCE_NAME=$1
|
||||||
|
PORT_PREFIX=$2
|
||||||
|
BASE_DIR=$(cd $(dirname $0); pwd)
|
||||||
|
TARGET_DIR="${BASE_DIR}/../instances/${INSTANCE_NAME}"
|
||||||
|
|
||||||
|
echo "🚀 Creating instance: ${INSTANCE_NAME} (Port Prefix: ${PORT_PREFIX}xxx)"
|
||||||
|
|
||||||
|
# 1. 폴더 구조 생성
|
||||||
|
mkdir -p "${TARGET_DIR}/gateway"
|
||||||
|
mkdir -p "${TARGET_DIR}/ory/kratos"
|
||||||
|
mkdir -p "${TARGET_DIR}/ory/oathkeeper"
|
||||||
|
mkdir -p "${TARGET_DIR}/userfront"
|
||||||
|
mkdir -p "${TARGET_DIR}/adminfront"
|
||||||
|
mkdir -p "${TARGET_DIR}/devfront"
|
||||||
|
|
||||||
|
# 2. .env 생성 및 변수 로드
|
||||||
|
sed "s/{{INSTANCE_NAME}}/${INSTANCE_NAME}/g; s/{{PORT_PREFIX}}/${PORT_PREFIX}/g" \
|
||||||
|
"${BASE_DIR}/templates/.env.template" > "${TARGET_DIR}/.env"
|
||||||
|
|
||||||
|
# 포트 계산 (단순 치환)
|
||||||
|
BACKEND_PORT="${PORT_PREFIX}000"
|
||||||
|
USERFRONT_PORT="${PORT_PREFIX}500"
|
||||||
|
DOMAIN_SUFFIX=$(grep "DOMAIN_SUFFIX=" "${TARGET_DIR}/.env" | cut -d'=' -f2 | tr -d '\r')
|
||||||
|
ADMINFRONT_DOMAIN="${INSTANCE_NAME}-admin.${DOMAIN_SUFFIX}"
|
||||||
|
DEVFRONT_DOMAIN="${INSTANCE_NAME}-dev.${DOMAIN_SUFFIX}"
|
||||||
|
|
||||||
|
# 3. Docker Compose & Config 복사 및 치환
|
||||||
|
cp "${BASE_DIR}/templates/docker-compose.yaml" "${TARGET_DIR}/"
|
||||||
|
|
||||||
|
# Gateway & UserFront Nginx
|
||||||
|
sed "s/{{BACKEND_PORT}}/${BACKEND_PORT}/g" "${BASE_DIR}/templates/gateway/nginx.conf" > "${TARGET_DIR}/gateway/nginx.conf"
|
||||||
|
sed "s/{{BACKEND_PORT}}/${BACKEND_PORT}/g" "${BASE_DIR}/templates/userfront/nginx.conf" > "${TARGET_DIR}/userfront/nginx.conf"
|
||||||
|
|
||||||
|
# Oathkeeper Rules
|
||||||
|
sed "s/{{BACKEND_PORT}}/${BACKEND_PORT}/g" "${BASE_DIR}/templates/ory/oathkeeper/rules.json" > "${TARGET_DIR}/ory/oathkeeper/rules.json"
|
||||||
|
cp "${TARGET_DIR}/ory/oathkeeper/rules.json" "${TARGET_DIR}/ory/oathkeeper/rules.active.json"
|
||||||
|
|
||||||
|
# Kratos Config
|
||||||
|
sed "s/{{BACKEND_PORT}}/${BACKEND_PORT}/g; s/{{USERFRONT_PORT}}/${USERFRONT_PORT}/g" \
|
||||||
|
"${BASE_DIR}/templates/ory/kratos/kratos.yml" > "${TARGET_DIR}/ory/kratos/kratos.yml"
|
||||||
|
|
||||||
|
# Vite Configs
|
||||||
|
sed "s/{{ADMINFRONT_DOMAIN}}/${ADMINFRONT_DOMAIN}/g; s/{{BACKEND_PORT}}/${BACKEND_PORT}/g" \
|
||||||
|
"${BASE_DIR}/templates/adminfront/vite.config.ts" > "${TARGET_DIR}/adminfront/vite.config.ts"
|
||||||
|
sed "s/{{DEVFRONT_DOMAIN}}/${DEVFRONT_DOMAIN}/g; s/{{BACKEND_PORT}}/${BACKEND_PORT}/g" \
|
||||||
|
"${BASE_DIR}/templates/devfront/vite.config.ts" > "${TARGET_DIR}/devfront/vite.config.ts"
|
||||||
|
|
||||||
|
# 4. 프론트엔드 auth.ts 주입 (하드코딩된 포트 해결)
|
||||||
|
sed "s/{{USERFRONT_PORT}}/${USERFRONT_PORT}/g; s/{{CLIENT_ID}}/adminfront/g" \
|
||||||
|
"${BASE_DIR}/templates/auth.template.ts" > "${TARGET_DIR}/adminfront/auth.ts"
|
||||||
|
sed "s/{{USERFRONT_PORT}}/${USERFRONT_PORT}/g; s/{{CLIENT_ID}}/devfront/g" \
|
||||||
|
"${BASE_DIR}/templates/auth.template.ts" > "${TARGET_DIR}/devfront/auth.ts"
|
||||||
|
|
||||||
|
# 5. Ory 정적 설정 복사
|
||||||
|
if [ -d "${BASE_DIR}/../docker/ory/kratos" ]; then cp -n "${BASE_DIR}/../docker/ory/kratos/"* "${TARGET_DIR}/ory/kratos/" 2>/dev/null || true; fi
|
||||||
|
if [ -d "${BASE_DIR}/../docker/ory/oathkeeper" ]; then cp -n "${BASE_DIR}/../docker/ory/oathkeeper/"* "${TARGET_DIR}/ory/oathkeeper/" 2>/dev/null || true; fi
|
||||||
|
|
||||||
|
# 6. 마무리
|
||||||
|
chmod +x "${TARGET_DIR}/.env"
|
||||||
|
|
||||||
|
echo "--------------------------------------------------"
|
||||||
|
echo "✅ Success! ALL files (Infra/Ory/Apps/FrontConfigs) are ready."
|
||||||
|
echo "📂 Location: ${TARGET_DIR}"
|
||||||
|
echo "🚀 Run: cd ${TARGET_DIR} && docker compose up -d"
|
||||||
|
echo "--------------------------------------------------"
|
||||||
64
deploy/deploy_guide.md
Normal file
64
deploy/deploy_guide.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# Baron SSO 다중 인스턴스 배포 가이드
|
||||||
|
|
||||||
|
이 프로젝트는 한 대의 서버(머신)에서 여러 개의 독립된 서비스 인스턴스(예: stg, test2, prod 등)를 충돌 없이 실행하기 위한 **템플릿 기반 자동 배포 시스템**을 사용합니다.
|
||||||
|
|
||||||
|
## 1. 폴더 구조
|
||||||
|
- `deploy/templates/`: 모든 설정의 원천이 되는 마스터 템플릿.
|
||||||
|
- `deploy/create-instance.sh`: 새로운 인스턴스를 생성하는 마법사 스크립트.
|
||||||
|
- `instances/`: (자동 생성) 실제 실행되는 인스턴스별 설정 파일들이 저장되는 곳.
|
||||||
|
|
||||||
|
## 2. 핵심 배포 전략
|
||||||
|
|
||||||
|
### 2.1 포트 Prefix 시스템
|
||||||
|
모든 포트는 환경 변수 `P` (Port Prefix)를 기반으로 자동 계산됩니다.
|
||||||
|
- 예: 포트 Prefix를 `23`으로 설정 시:
|
||||||
|
- Backend: `23000`
|
||||||
|
- PostgreSQL: `23432`
|
||||||
|
- UserFront: `23500`
|
||||||
|
- AdminFront: `23173`
|
||||||
|
|
||||||
|
### 2.2 네임스페이스 격리
|
||||||
|
모든 리소스 이름에 `${INSTANCE_NAME}`이 자동으로 붙어 도커 엔진 내에서 격리됩니다.
|
||||||
|
- 컨테이너: `baron-sso-test2_backend`
|
||||||
|
- 네트워크: `baron-sso-test2_net`
|
||||||
|
- 볼륨: `db_data_test2`
|
||||||
|
|
||||||
|
## 3. 새로운 인스턴스 배포 방법
|
||||||
|
|
||||||
|
### Step 1: 인스턴스 생성
|
||||||
|
`deploy/` 폴더로 이동하여 스크립트를 실행합니다.
|
||||||
|
```bash
|
||||||
|
cd deploy
|
||||||
|
./create-instance.sh [인스턴스이름] [포트앞자리]
|
||||||
|
```
|
||||||
|
> **예시**: `test3` 환경을 `24xxx` 포트 대역으로 만들고 싶을 때
|
||||||
|
> ```bash
|
||||||
|
> ./create-instance.sh test3 24
|
||||||
|
> ```
|
||||||
|
|
||||||
|
### Step 2: 서비스 실행
|
||||||
|
생성된 인스턴스 폴더로 이동하여 도커 컴포즈를 실행합니다.
|
||||||
|
```bash
|
||||||
|
cd instances/test3
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 도메인 및 URL 규칙
|
||||||
|
스크립트 실행 시 인스턴스 이름에 따라 다음 도메인이 자동으로 설정 파일에 주입됩니다. (`DOMAIN_SUFFIX`는 `.env`에서 수정 가능)
|
||||||
|
- **SSO/UserFront**: `https://[이름]-sso.hmac.kr`
|
||||||
|
- **AdminFront**: `https://[이름]-admin.hmac.kr`
|
||||||
|
- **DevFront**: `https://[이름]-dev.hmac.kr`
|
||||||
|
|
||||||
|
## 5. 주요 설정 파일 관리
|
||||||
|
- **Nginx (Gateway & UserFront)**: 각 인스턴스의 백엔드 포트를 자동으로 감지하여 리버스 프록시를 수행합니다.
|
||||||
|
- **Ory Kratos**: `allowed_origins`, `allowed_return_urls`, `webhook` 주소 등이 인스턴스 포트에 맞게 자동 치환됩니다.
|
||||||
|
- **Ory Oathkeeper (`rules.json`, `rules.active.json`)**: 인증 규칙 내의 업스트림 주소가 현재 인스턴스 포트에 맞게 자동 치환됩니다.
|
||||||
|
- **Environment (`.env`)**: 모든 시크릿과 URL 설정이 중앙에서 관리됩니다.
|
||||||
|
|
||||||
|
## 6. Vite 프론트엔드 주의사항
|
||||||
|
Vite 개발 서버나 Preview 모드를 사용하는 경우, `vite.config.ts`의 `allowedHosts` 설정이 새로운 도메인을 차단할 수 있습니다.
|
||||||
|
여러 인스턴스를 동적 도메인으로 운영할 경우, `vite.config.ts`에서 `allowedHosts: true` (Vite 5.1+) 설정을 권장합니다.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
**주의**: 새로운 인스턴스 생성 후 외부에서 접속하려면, 앞단의 메인 리버스 프록시(Nginx 등)나 DNS 설정에서 해당 인스턴스의 포트(`USERFRONT_PORT`)로 라우팅해주는 작업이 필요합니다.
|
||||||
55
deploy/templates/.env.template
Normal file
55
deploy/templates/.env.template
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# === [1] 프로젝트 식별 (중요: 인스턴스마다 다르게 설정) ===
|
||||||
|
INSTANCE_NAME={{INSTANCE_NAME}}
|
||||||
|
COMPOSE_PROJECT_NAME=baron-sso-{{INSTANCE_NAME}}
|
||||||
|
APP_ENV=production
|
||||||
|
|
||||||
|
# === [2] 포트 Prefix 설정 (예: 23 입력 시 23000, 23432 등 생성) ===
|
||||||
|
P={{PORT_PREFIX}}
|
||||||
|
|
||||||
|
# 인프라 포트
|
||||||
|
DB_PORT=${P}432
|
||||||
|
REDIS_PORT=${P}399
|
||||||
|
CLICKHOUSE_PORT_HTTP=${P}123
|
||||||
|
CLICKHOUSE_PORT_NATIVE=${P}000
|
||||||
|
|
||||||
|
# 서비스 포트
|
||||||
|
BACKEND_PORT=${P}000
|
||||||
|
USERFRONT_PORT=${P}500
|
||||||
|
ADMINFRONT_PORT=${P}173
|
||||||
|
DEVFRONT_PORT=${P}174
|
||||||
|
OATHKEEPER_PROXY_PORT=${P}467
|
||||||
|
|
||||||
|
# === [3] 도메인 설정 (별도 도메인 구조) ===
|
||||||
|
# {{INSTANCE_NAME}}이 stg면 sso-stg.hmac.kr 형식이 되도록 가이드
|
||||||
|
DOMAIN_SUFFIX=hmac.kr
|
||||||
|
USERFRONT_URL=https://{{INSTANCE_NAME}}-sso.${DOMAIN_SUFFIX}
|
||||||
|
ADMINFRONT_URL=https://{{INSTANCE_NAME}}-admin.${DOMAIN_SUFFIX}
|
||||||
|
DEVFRONT_URL=https://{{INSTANCE_NAME}}-dev.${DOMAIN_SUFFIX}
|
||||||
|
|
||||||
|
# OIDC/Auth URL
|
||||||
|
VITE_OIDC_AUTHORITY=${USERFRONT_URL}/oidc
|
||||||
|
ADMINFRONT_CALLBACK_URLS=${ADMINFRONT_URL}/auth/callback
|
||||||
|
DEVFRONT_CALLBACK_URLS=${DEVFRONT_URL}/auth/callback
|
||||||
|
|
||||||
|
# Ory URL
|
||||||
|
KRATOS_UI_URL=${USERFRONT_URL}/auth
|
||||||
|
KRATOS_BROWSER_URL=${USERFRONT_URL}/auth
|
||||||
|
HYDRA_PUBLIC_URL=${USERFRONT_URL}/oidc
|
||||||
|
OATHKEEPER_PUBLIC_URL=${USERFRONT_URL}
|
||||||
|
|
||||||
|
# === [4] IDP 및 DB Config ===
|
||||||
|
IDP_PROVIDER=ory
|
||||||
|
DB_PASSWORD=password
|
||||||
|
ORY_POSTGRES_USER=ory
|
||||||
|
ORY_POSTGRES_PASSWORD=generated_secret_here
|
||||||
|
CLICKHOUSE_PASSWORD=password
|
||||||
|
REDIS_ADDR=redis:6379
|
||||||
|
|
||||||
|
# Secrets (At least 32 chars)
|
||||||
|
COOKIE_SECRET=at_least_32_characters_long_secret_12345
|
||||||
|
JWT_SECRET=at_least_32_characters_long_secret_12345
|
||||||
|
CSRF_COOKIE_SECRET=at_least_32_characters_long_secret_12345
|
||||||
|
|
||||||
|
# Admin 초기 계정
|
||||||
|
ADMIN_EMAIL=admin@baron.co.kr
|
||||||
|
ADMIN_PASSWORD=adminPasswordIsNotSimple
|
||||||
29
deploy/templates/adminfront/vite.config.ts
Normal file
29
deploy/templates/adminfront/vite.config.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
envPrefix: ["VITE_", "USERFRONT_"],
|
||||||
|
server: {
|
||||||
|
host: "127.0.0.1",
|
||||||
|
// 인스턴스별 도메인을 자동으로 허용
|
||||||
|
allowedHosts: ["{{ADMINFRONT_DOMAIN}}", "localhost", "127.0.0.1"],
|
||||||
|
proxy: {
|
||||||
|
"/api": {
|
||||||
|
target: process.env.API_PROXY_TARGET || "http://backend:{{BACKEND_PORT}}",
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
preview: {
|
||||||
|
host: "127.0.0.1",
|
||||||
|
port: 5173,
|
||||||
|
allowedHosts: ["{{ADMINFRONT_DOMAIN}}", "localhost", "127.0.0.1"],
|
||||||
|
proxy: {
|
||||||
|
"/api": {
|
||||||
|
target: process.env.API_PROXY_TARGET || "http://backend:{{BACKEND_PORT}}",
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
21
deploy/templates/auth.template.ts
Normal file
21
deploy/templates/auth.template.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { UserManager, WebStorageStateStore } from "oidc-client-ts";
|
||||||
|
import type { AuthProviderProps } from "react-oidc-context";
|
||||||
|
|
||||||
|
export const oidcConfig: AuthProviderProps = {
|
||||||
|
authority:
|
||||||
|
import.meta.env.VITE_OIDC_AUTHORITY || `${window.location.protocol}//${window.location.hostname}:{{USERFRONT_PORT}}/oidc`,
|
||||||
|
client_id: import.meta.env.VITE_OIDC_CLIENT_ID || "{{CLIENT_ID}}",
|
||||||
|
redirect_uri: `${window.location.origin}/auth/callback`,
|
||||||
|
response_type: "code",
|
||||||
|
scope: "openid offline_access profile email",
|
||||||
|
post_logout_redirect_uri: window.location.origin,
|
||||||
|
userStore: new WebStorageStateStore({ store: window.localStorage }),
|
||||||
|
automaticSilentRenew: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const userManager = new UserManager({
|
||||||
|
...oidcConfig,
|
||||||
|
authority: oidcConfig.authority || "",
|
||||||
|
client_id: oidcConfig.client_id || "",
|
||||||
|
redirect_uri: oidcConfig.redirect_uri || "",
|
||||||
|
});
|
||||||
29
deploy/templates/devfront/vite.config.ts
Normal file
29
deploy/templates/devfront/vite.config.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
envPrefix: ["VITE_", "USERFRONT_"],
|
||||||
|
server: {
|
||||||
|
host: "127.0.0.1",
|
||||||
|
// 인스턴스별 도메인을 자동으로 허용
|
||||||
|
allowedHosts: ["{{DEVFRONT_DOMAIN}}", "localhost", "127.0.0.1"],
|
||||||
|
proxy: {
|
||||||
|
"/api": {
|
||||||
|
target: process.env.API_PROXY_TARGET || "http://backend:{{BACKEND_PORT}}",
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
preview: {
|
||||||
|
host: "127.0.0.1",
|
||||||
|
port: 5173,
|
||||||
|
allowedHosts: ["{{DEVFRONT_DOMAIN}}", "localhost", "127.0.0.1"],
|
||||||
|
proxy: {
|
||||||
|
"/api": {
|
||||||
|
target: process.env.API_PROXY_TARGET || "http://backend:{{BACKEND_PORT}}",
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
135
deploy/templates/docker-compose.yaml
Normal file
135
deploy/templates/docker-compose.yaml
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
name: ${COMPOSE_PROJECT_NAME}
|
||||||
|
|
||||||
|
services:
|
||||||
|
# --- Infrastructure ---
|
||||||
|
postgres:
|
||||||
|
image: postgres:17-alpine
|
||||||
|
container_name: ${COMPOSE_PROJECT_NAME}_db
|
||||||
|
environment:
|
||||||
|
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
||||||
|
ports:
|
||||||
|
- "${DB_PORT}:5432"
|
||||||
|
volumes:
|
||||||
|
- db_data_${INSTANCE_NAME}:/var/lib/postgresql/data
|
||||||
|
networks: [app_net]
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||||
|
interval: 5s
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
container_name: ${COMPOSE_PROJECT_NAME}_redis
|
||||||
|
ports:
|
||||||
|
- "${REDIS_PORT}:6379"
|
||||||
|
networks: [app_net]
|
||||||
|
|
||||||
|
clickhouse:
|
||||||
|
image: clickhouse/clickhouse-server:latest
|
||||||
|
container_name: ${COMPOSE_PROJECT_NAME}_clickhouse
|
||||||
|
environment:
|
||||||
|
- CLICKHOUSE_USER=baron
|
||||||
|
- CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD}
|
||||||
|
ports:
|
||||||
|
- "${CLICKHOUSE_PORT_HTTP}:8123"
|
||||||
|
- "${CLICKHOUSE_PORT_NATIVE}:9000"
|
||||||
|
volumes:
|
||||||
|
- clickhouse_data_${INSTANCE_NAME}:/var/lib/clickhouse
|
||||||
|
networks: [app_net]
|
||||||
|
|
||||||
|
# --- Ory Stack ---
|
||||||
|
postgres_ory:
|
||||||
|
image: postgres:17-alpine
|
||||||
|
container_name: ${COMPOSE_PROJECT_NAME}_ory_db
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=${ORY_POSTGRES_USER}
|
||||||
|
- POSTGRES_PASSWORD=${ORY_POSTGRES_PASSWORD}
|
||||||
|
volumes:
|
||||||
|
- ory_db_data_${INSTANCE_NAME}:/var/lib/postgresql/data
|
||||||
|
networks: [app_net]
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ${ORY_POSTGRES_USER}"]
|
||||||
|
interval: 5s
|
||||||
|
|
||||||
|
kratos:
|
||||||
|
image: oryd/kratos:v25.4.0
|
||||||
|
container_name: ${COMPOSE_PROJECT_NAME}_kratos
|
||||||
|
env_file: .env
|
||||||
|
volumes:
|
||||||
|
- ./ory/kratos:/etc/config/kratos:ro
|
||||||
|
command: serve -c /etc/config/kratos/kratos.yml --dev
|
||||||
|
networks: [app_net]
|
||||||
|
depends_on:
|
||||||
|
postgres_ory: { condition: service_healthy }
|
||||||
|
|
||||||
|
oathkeeper:
|
||||||
|
image: oryd/oathkeeper:v25.4.0
|
||||||
|
container_name: ${COMPOSE_PROJECT_NAME}_oathkeeper
|
||||||
|
env_file: .env
|
||||||
|
ports:
|
||||||
|
- "${OATHKEEPER_PROXY_PORT}:4455"
|
||||||
|
volumes:
|
||||||
|
- ./ory/oathkeeper:/etc/config/oathkeeper:ro
|
||||||
|
networks: [app_net]
|
||||||
|
|
||||||
|
# --- Application Services ---
|
||||||
|
backend:
|
||||||
|
image: baron-backend:latest
|
||||||
|
container_name: ${COMPOSE_PROJECT_NAME}_backend
|
||||||
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
- PORT=${BACKEND_PORT}
|
||||||
|
- DB_HOST=postgres
|
||||||
|
- REDIS_ADDR=redis:6379
|
||||||
|
- CLICKHOUSE_HOST=clickhouse
|
||||||
|
ports:
|
||||||
|
- "${BACKEND_PORT}:${BACKEND_PORT}"
|
||||||
|
networks: [app_net]
|
||||||
|
depends_on:
|
||||||
|
postgres: { condition: service_healthy }
|
||||||
|
redis: { condition: service_started }
|
||||||
|
|
||||||
|
gateway:
|
||||||
|
image: nginx:alpine
|
||||||
|
container_name: ${COMPOSE_PROJECT_NAME}_gateway
|
||||||
|
ports:
|
||||||
|
- "${USERFRONT_PORT}:80"
|
||||||
|
volumes:
|
||||||
|
- ./gateway/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||||
|
networks: [app_net]
|
||||||
|
|
||||||
|
adminfront:
|
||||||
|
image: node:20-alpine
|
||||||
|
container_name: ${COMPOSE_PROJECT_NAME}_adminfront
|
||||||
|
working_dir: /app
|
||||||
|
env_file: .env
|
||||||
|
ports:
|
||||||
|
- "${ADMINFRONT_PORT}:5173"
|
||||||
|
volumes:
|
||||||
|
- ../../adminfront:/app
|
||||||
|
- ./adminfront/vite.config.ts:/app/vite.config.ts:ro
|
||||||
|
- ./adminfront/auth.ts:/app/src/lib/auth.ts:ro
|
||||||
|
command: npm run dev -- --host 0.0.0.0
|
||||||
|
networks: [app_net]
|
||||||
|
|
||||||
|
devfront:
|
||||||
|
image: node:20-alpine
|
||||||
|
container_name: ${COMPOSE_PROJECT_NAME}_devfront
|
||||||
|
working_dir: /app
|
||||||
|
env_file: .env
|
||||||
|
ports:
|
||||||
|
- "${DEVFRONT_PORT}:5173"
|
||||||
|
volumes:
|
||||||
|
- ../../devfront:/app
|
||||||
|
- ./devfront/vite.config.ts:/app/vite.config.ts:ro
|
||||||
|
- ./devfront/auth.ts:/app/src/lib/auth.ts:ro
|
||||||
|
command: npm run dev -- --host 0.0.0.0
|
||||||
|
networks: [app_net]
|
||||||
|
|
||||||
|
networks:
|
||||||
|
app_net:
|
||||||
|
name: ${COMPOSE_PROJECT_NAME}_net
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
db_data_${INSTANCE_NAME}:
|
||||||
|
ory_db_data_${INSTANCE_NAME}:
|
||||||
|
clickhouse_data_${INSTANCE_NAME}:
|
||||||
43
deploy/templates/gateway/nginx.conf
Normal file
43
deploy/templates/gateway/nginx.conf
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
worker_processes auto;
|
||||||
|
events { worker_connections 1024; }
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
|
||||||
|
# 인스턴스별로 계산된 포트 주입
|
||||||
|
upstream backend_srv {
|
||||||
|
server backend:{{BACKEND_PORT}};
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream oathkeeper_srv {
|
||||||
|
server oathkeeper:4455;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
|
||||||
|
# SSO 메인 도메인 및 API 처리
|
||||||
|
location /api {
|
||||||
|
proxy_pass http://backend_srv;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /auth {
|
||||||
|
proxy_pass http://oathkeeper_srv;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /oidc {
|
||||||
|
rewrite ^/oidc/(.*)$ /$1 break;
|
||||||
|
proxy_pass http://oathkeeper_srv;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 기본 정적 파일 (UserFront)
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
114
deploy/templates/ory/kratos/kratos.yml
Normal file
114
deploy/templates/ory/kratos/kratos.yml
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
version: v25.4.0
|
||||||
|
|
||||||
|
dsn: ${DSN}
|
||||||
|
|
||||||
|
serve:
|
||||||
|
public:
|
||||||
|
base_url: http://localhost:4433/
|
||||||
|
cors:
|
||||||
|
enabled: true
|
||||||
|
allowed_origins:
|
||||||
|
- http://backend:{{BACKEND_PORT}}
|
||||||
|
admin:
|
||||||
|
base_url: http://localhost:4434/
|
||||||
|
|
||||||
|
session:
|
||||||
|
cookie:
|
||||||
|
domain: hmac.kr
|
||||||
|
same_site: Lax
|
||||||
|
path: /
|
||||||
|
|
||||||
|
selfservice:
|
||||||
|
default_browser_return_url: http://localhost:{{USERFRONT_PORT}}/
|
||||||
|
allowed_return_urls:
|
||||||
|
- http://backend:{{BACKEND_PORT}}
|
||||||
|
- http://backend:{{BACKEND_PORT}}/
|
||||||
|
- http://localhost:{{USERFRONT_PORT}}
|
||||||
|
- https://app.brsw.kr
|
||||||
|
- https://app.brsw.kr/
|
||||||
|
- https://sss.hmac.kr
|
||||||
|
- https://sss.hmac.kr/
|
||||||
|
- https://sso.hmac.kr
|
||||||
|
- https://sso.hmac.kr/
|
||||||
|
- https://ssologin.hmac.kr
|
||||||
|
- https://ssologin.hmac.kr/
|
||||||
|
- https://sso-test.hmac.kr
|
||||||
|
- https://sso-test.hmac.kr/
|
||||||
|
- https://ssob.hmac.kr
|
||||||
|
- https://ssob.hmac.kr/
|
||||||
|
- https://ssob.hmac.kr/ko
|
||||||
|
- https://ssob.hmac.kr/ko/
|
||||||
|
- https://ssob.hmac.kr/en
|
||||||
|
- https://ssob.hmac.kr/en/
|
||||||
|
- https://ssob.hmac.kr/auth/callback
|
||||||
|
- https://ssob.hmac.kr/ko/auth/callback
|
||||||
|
- https://ssob.hmac.kr/en/auth/callback
|
||||||
|
|
||||||
|
methods:
|
||||||
|
password:
|
||||||
|
enabled: true
|
||||||
|
link:
|
||||||
|
enabled: true
|
||||||
|
code:
|
||||||
|
enabled: true
|
||||||
|
passwordless_enabled: true
|
||||||
|
|
||||||
|
flows:
|
||||||
|
error:
|
||||||
|
ui_url: http://localhost:{{USERFRONT_PORT}}/error
|
||||||
|
settings:
|
||||||
|
ui_url: http://localhost:{{USERFRONT_PORT}}/error?error=settings_disabled
|
||||||
|
privileged_session_max_age: 15m
|
||||||
|
recovery:
|
||||||
|
ui_url: http://localhost:{{USERFRONT_PORT}}/recovery
|
||||||
|
use: code
|
||||||
|
verification:
|
||||||
|
ui_url: http://localhost:{{USERFRONT_PORT}}/verification
|
||||||
|
use: code
|
||||||
|
logout:
|
||||||
|
after:
|
||||||
|
default_browser_return_url: http://localhost:{{USERFRONT_PORT}}/login
|
||||||
|
login:
|
||||||
|
ui_url: http://localhost:{{USERFRONT_PORT}}/login
|
||||||
|
lifespan: 10m
|
||||||
|
registration:
|
||||||
|
ui_url: http://localhost:{{USERFRONT_PORT}}/registration
|
||||||
|
lifespan: 10m
|
||||||
|
|
||||||
|
log:
|
||||||
|
level: debug
|
||||||
|
format: text
|
||||||
|
leak_sensitive_values: true
|
||||||
|
|
||||||
|
secrets:
|
||||||
|
cookie:
|
||||||
|
- PLEASE-CHANGE-ME-I-AM-VERY-INSECURE
|
||||||
|
cipher:
|
||||||
|
- 32-LONG-SECRET-NOT-SECURE-AT-ALL
|
||||||
|
|
||||||
|
ciphers:
|
||||||
|
algorithm: xchacha20-poly1305
|
||||||
|
|
||||||
|
hashers:
|
||||||
|
algorithm: bcrypt
|
||||||
|
bcrypt:
|
||||||
|
cost: 8
|
||||||
|
|
||||||
|
identity:
|
||||||
|
default_schema_id: default
|
||||||
|
schemas:
|
||||||
|
- id: default
|
||||||
|
url: file:///etc/config/kratos/identity.schema.json
|
||||||
|
|
||||||
|
courier:
|
||||||
|
template_override_path: /etc/config/kratos/courier-templates
|
||||||
|
delivery_strategy: http
|
||||||
|
http:
|
||||||
|
request_config:
|
||||||
|
url: http://backend:{{BACKEND_PORT}}/api/v1/auth/webhooks/kratos-courier
|
||||||
|
method: POST
|
||||||
|
body: file:///etc/config/kratos/courier-http.jsonnet
|
||||||
|
headers:
|
||||||
|
Content-Type: application/json
|
||||||
|
smtp:
|
||||||
|
connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true
|
||||||
15
deploy/templates/ory/oathkeeper/rules.json
Normal file
15
deploy/templates/ory/oathkeeper/rules.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "backend-api-rule",
|
||||||
|
"match": {
|
||||||
|
"url": "<.*>://<.*>/api/v1/<.*>",
|
||||||
|
"methods": ["GET", "POST", "PUT", "DELETE", "PATCH"]
|
||||||
|
},
|
||||||
|
"upstream": {
|
||||||
|
"url": "http://backend:{{BACKEND_PORT}}"
|
||||||
|
},
|
||||||
|
"authenticators": [{ "handler": "cookie_session" }],
|
||||||
|
"authorizer": { "handler": "remote_json" },
|
||||||
|
"mutators": [{ "handler": "noop" }]
|
||||||
|
}
|
||||||
|
]
|
||||||
71
deploy/templates/userfront/nginx.conf
Normal file
71
deploy/templates/userfront/nginx.conf
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# ISO8601 시간을 "YYYY-MM-DD HH:mm:ss" 형식으로 변환
|
||||||
|
map $time_iso8601 $time_custom {
|
||||||
|
"~^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})" "$1-$2-$3 $4:$5:$6";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Go slog 포맷과 맞춘 JSON 액세스 로그
|
||||||
|
log_format json_combined escape=json
|
||||||
|
'{'
|
||||||
|
'"time":"$time_custom",'
|
||||||
|
'"level":"INFO",'
|
||||||
|
'"msg":"http_access",'
|
||||||
|
'"svc":"baron-userfront",'
|
||||||
|
'"status":$status,'
|
||||||
|
'"method":"$request_method",'
|
||||||
|
'"path":"$request_uri",'
|
||||||
|
'"latency":"${request_time}s",'
|
||||||
|
'"ip":"$remote_addr",'
|
||||||
|
'"forwarded_for":"$http_x_forwarded_for",'
|
||||||
|
'"user_agent":"$http_user_agent"'
|
||||||
|
'}';
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 5000;
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
types {
|
||||||
|
application/javascript mjs;
|
||||||
|
application/wasm wasm;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log /dev/stderr warn;
|
||||||
|
access_log /var/log/nginx/access.log json_combined;
|
||||||
|
|
||||||
|
# --- Backend API Proxy ---
|
||||||
|
location /api {
|
||||||
|
proxy_pass http://backend:{{BACKEND_PORT}};
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- UserFront Static Files ---
|
||||||
|
|
||||||
|
location ~* \.(js|css|html|json|mjs|wasm)$ {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
|
||||||
|
try_files $uri =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~* \.mjs$ {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
default_type application/javascript;
|
||||||
|
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
|
||||||
|
try_files $uri =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# dart2wasm 바이너리 MIME 명시
|
||||||
|
location ~* \.wasm$ {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
default_type application/wasm;
|
||||||
|
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
|
||||||
|
try_files $uri =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user