feat: 확인 완료 버튼 추가

This commit is contained in:
root
2026-09-04 16:40:31 +09:00
parent 090e9fdf25
commit 4a79555d16
14 changed files with 2114 additions and 694 deletions
+114 -177
View File
@@ -1,210 +1,147 @@
# Gitea 스테이징 등록 변수
# Gitea Actions 변수·Secret 등록 가이드
BARON User Feedback와 secretary-api를 `172.16.10.175:3030`으로 배포할 때 Gitea에 등록할 환경변수와 Secret 정리입니다.
이 문서는 현재 [`deploy-staging.yml`](./.gitea/workflows/deploy-staging.yml)이 사용하는 피드백 작성 전용 배포 설정입니다. 이 workflow는 `web` 컨테이너만 `10.13.10.4:8864`에 배포합니다. ABC API·Secretary API·MySQL을 배포하던 이전 전체 플랫폼용 변수 목록과 섞어 등록하지 않습니다.
## 1. 스테이징 기본값
## 1. 등록 위치와 구분
### Variables
| 이름 | 등록값 | 용도 |
| ---------------------------- | ----------------------------------------------- | ----------------------------------------------- |
| `APP_ENV` | `staging` | 실행 환경 |
| `WEB_PORT` | `3030` | 외부 웹 포트 |
| `API_PORT` | `4000` | Docker 내부 API 포트 (host에 공개하지 않음) |
| `SECRETARY_API_PORT` | `8010` | Docker 내부 FastAPI 포트 (host에 공개하지 않음) |
| `MYSQL_PORT` | 등록 불필요 | prod Compose는 DB host port를 공개하지 않음 |
| `MYSQL_SECRETARY_PORT` | 등록 불필요 | prod Compose는 DB host port를 공개하지 않음 |
| `NEXT_PUBLIC_API_BASE_URL` | `http://172.16.10.175:3030` | Web reverse proxy를 통한 브라우저 API 주소 |
| `ADMIN_WEB_URL` | `http://172.16.10.175:3030` | 관리자 웹 주소 및 OAuth 기준 주소 |
| `BASE_URL` | `http://172.16.10.175:3030` | Web reverse proxy를 통한 API 기준 주소 |
| `SMTP_ENABLED` | `false` (현재 SSO 전용) 또는 `true` (SMTP 사용) | 이메일 기능 활성화 여부 |
| `SMTP_HOST` | 사내 SMTP 호스트 | 메일 서버 |
| `SMTP_PORT` | `25` 또는 사내 SMTP 포트 | 메일 서버 포트 |
| `SMTP_SENDER` | 사내 발신 이메일 | 메일 발신자 |
| `SMTP_TLS` | `false` 또는 `true` | SMTP TLS 사용 여부 |
| `SMTP_CIPHER_SPEC` | 사내 SMTP 정책값 | TLS cipher 설정 |
| `SMTP_OPPORTUNISTIC_TLS` | `false` 또는 `true` | SMTP opportunistic TLS |
| `ACCESS_TOKEN_EXPIRED_TIME` | `10m` | Access Token 만료시간 |
| `REFRESH_TOKEN_EXPIRED_TIME` | `1h` | Refresh Token 만료시간 |
| `AUTO_MIGRATION` | `true` | API 시작 시 마이그레이션 |
| `OPENSEARCH_USE` | `false` | OpenSearch 사용 여부 |
| `OPENSEARCH_NODE` | 빈 값 | OpenSearch 미사용 |
| `OPENSEARCH_USERNAME` | 빈 값 | OpenSearch 미사용 |
| `OPENSEARCH_PASSWORD` | 빈 값 | OpenSearch 미사용 |
> 스테이징 DB는 외부 host port를 사용하지 않습니다. 컨테이너 내부에서는 `mysql:3306`, `mysql-secretary:3306`으로 접근합니다. 외부 DB 접속이 필요하면 SSH 터널을 사용합니다.
> | `SSO_ISSUER` | `https://sso.hmac.kr/oidc` | secretary-api용 SSO issuer |
> | `SSO_CLIENT_ID` | `838cd69d-e722-41da-9f79-b3c42a509ef2` | BARON-SSO Client ID |
`INTERNAL_API_BASE_URL`, `SUPPORT_API_BASE_URL`, `ABC_API_BASE_URL`는 Docker 내부 주소이므로 Gitea에 별도 등록하지 않습니다.
Gitea 저장소 `b24014/egbim_qa_platform`에서 다음 메뉴를 엽니다.
```text
INTERNAL_API_BASE_URL=http://api:4000
SUPPORT_API_BASE_URL=http://secretary-api:8010
ABC_API_BASE_URL=http://api:4000
Repository Settings → Actions → Variables / Secrets
```
다음 값도 Compose에 고정되어 있으므로 Gitea에 별도 등록하지 않습니다.
Gitea 버전에 따라 메뉴명이 `Actions secrets and variables`로 보일 수 있습니다. workflow의 표현식 기준은 다음과 같습니다.
| 이름 | 고정값 |
| ------------------- | -------------------------------------------------------------------------------- |
| `APP_NAME` | `secretary-api` |
| `APP_HOST` | `0.0.0.0` |
| `APP_PORT` | `8010` |
| `UPLOAD_ROOT_DIR` | `/app/uploads` |
| `MYSQL_PRIMARY_URL` | `mysql://userfeedback:userfeedback@mysql:3306/userfeedback` |
| `DATABASE_URL` | `mysql+pymysql://baron_support:baron_support@mysql-secretary:3306/baron_support` |
```yaml
${{ vars.NAME }} # 일반 Variables
${{ secrets.NAME }} # 마스킹되는 Secrets
```
다음은 선택 기능을 사용할 때만 추가합니다.
등록할 때 변수 이름은 대문자·underscore까지 아래 표와 똑같이 입력합니다. 값에 따옴표를 붙이지 않습니다. Secret은 채팅, 이슈, commit, workflow 로그에 기록하지 않습니다.
| 이름 | 등록값 |
| ------------------------------------ | --------------------------- |
| `MYSQL_SECONDARY_URLS` | 보조 MySQL URL JSON 배열 |
| `AUTO_FEEDBACK_DELETION_ENABLED` | `false` |
| `AUTO_FEEDBACK_DELETION_PERIOD_DAYS` | 자동 삭제 사용 시 보존 일수 |
## 2. Variables
## 2. Gitea Secrets
다음은 공개되어도 되는 주소·포트·동작 설정입니다. 표의 `기본값`은 Gitea에 생략해도 workflow가 사용하는 값입니다.
### API 및 관리자 인증
| 이름 | 필수 | 권장값/기본값 | 용도 |
| --- | --- | --- | --- |
| `STAGING_HOST` | 선택 | `10.13.10.4` | SSH 대상. 현재 workflow가 이 값만 허용 |
| `STAGING_PORT` | 선택 | `22` | SSH 포트 |
| `STAGING_APP_DIR` | 선택 | `/home/user/egbim_qa_platform` | 원격 배포 디렉터리 |
| `WEB_PORT` | 선택 | `8864` | 외부 Web 포트. 현재 workflow가 이 값만 허용 |
| `SUPPORT_CONSOLE_API_BASE_URL` | 선택 | `https://feedback.hmac.kr/api/support` | 관리 콘솔 Support API |
| `SSO_ISSUER` | 선택 | `https://sso.hmac.kr/oidc` | BARON-SSO issuer |
| `SSO_AUTHORIZATION_ENDPOINT` | 선택 | `https://sso.hmac.kr/oidc/oauth2/auth` | OAuth authorization endpoint |
| `SSO_TOKEN_ENDPOINT` | 선택 | `https://sso.hmac.kr/oidc/oauth2/token` | 서버 간 code 교환 endpoint |
| `SSO_USERINFO_ENDPOINT` | 선택 | `https://sso.hmac.kr/oidc/userinfo` | 로그인 사용자 정보 endpoint |
| `SSO_SCOPE` | 선택 | `openid profile email` | BARON-SSO scope |
| `SSO_CLIENT_ID` | 권장 | BARON-SSO 작성 서버 Client ID | 공개 식별자. Variable 등록 권장 |
| `SUPPORT_TENANT_ID` | 선택 | 빈 값 | userinfo에 `tenant_id`가 없을 때 사용할 tenant |
| 이름 | 등록값 |
| ---------------------------------- | -------------------------------------------------------- |
| `JWT_SECRET` | 긴 무작위 문자열 |
| `MASTER_API_KEY` | ABC 전체 API 관리용 무작위 키 |
| `INITIAL_SUPER_ADMIN_PHONE_NUMBER` | 선택값: 초기 SUPER 관리자 자동 지정용 BARON-SSO 전화번호 |
| `ADMIN_CANDIDATE_EMAILS` | 관리자 후보 이메일 목록을 쉼표로 연결 |
`STAGING_HOST``WEB_PORT`는 선택으로 표시했지만 다른 값으로 바꾸면 현재 workflow의 사전 검사를 통과하지 못합니다. 스테이징 대상이나 포트를 변경하려면 workflow의 고정 검사를 코드와 함께 변경하고 BARON-SSO redirect URI도 다시 등록합니다.
예시:
### `SSO_CLIENT_ID`를 Secret에 넣은 경우
현재 workflow는 호환성을 위해 `secrets.SSO_CLIENT_ID``vars.SSO_CLIENT_ID`보다 우선합니다. 새로 등록할 때는 Client ID를 Variable에만 등록합니다. 두 위치에 동시에 넣으면 어느 값이 적용되는지 혼동할 수 있습니다.
## 3. Secrets
다음 5개는 workflow가 필수로 검사합니다.
| 이름 | 필수 | 등록 내용 | 주의 |
| --- | --- | --- | --- |
| `STAGING_USER` | 필수 | 스테이징 서버 SSH 사용자 | Docker 명령 실행 권한 필요 |
| `STAGING_SSH_PRIVATE_KEY` | 필수 | 배포용 Ed25519 private key 전체 | passphrase가 없는 키를 사용해야 함 |
| `STAGING_SSH_KNOWN_HOSTS` | 필수 | 스테이징 서버의 검증된 known_hosts 한 줄 이상 | 줄바꿈 보존, 임의 값 금지 |
| `SSO_CLIENT_SECRET` | 필수 | 작성 서버 전용 BARON-SSO confidential client secret | 관리 콘솔 client secret과 분리 |
| `JWT_SECRET` | 필수 | 작성 서버와 관리 콘솔 Support API가 공유하는 HS256 secret | 두 서버 값이 반드시 같아야 함 |
### SSH key 등록
private key는 로컬 파일 내용을 그대로 복사합니다. 앞뒤 공백이나 줄바꿈을 임의로 제거하지 않습니다. passphrase가 있는 키는 현재 workflow의 `ssh-keygen -y` 검사와 비대화형 SSH에서 실패할 수 있습니다.
공개키는 스테이징 서버의 해당 사용자의 `~/.ssh/authorized_keys`에 등록합니다. private key는 Gitea Secret에만 둡니다.
`known_hosts`는 다음처럼 수집할 수 있지만, 결과 fingerprint를 서버 관리자나 별도 신뢰 채널로 확인한 뒤 등록합니다.
```bash
ssh-keyscan -p 22 10.13.10.4
```
현재 workflow는 `StrictHostKeyChecking=yes`를 사용하므로 `STAGING_SSH_KNOWN_HOSTS`가 틀리거나 누락되면 배포하지 않습니다. 이 검사를 끄거나 `accept-new`로 완화하지 않습니다.
### JWT secret 등록
작성 서버는 BARON-SSO token을 그대로 브라우저에 노출하지 않고, userinfo를 기반으로 자체 HS256 JWT를 만듭니다. 관리 콘솔의 Support API가 검증하는 secret과 같은 값을 사용해야 합니다.
```text
ADMIN_CANDIDATE_EMAILS=admin1@example.com,admin2@example.com
작성 서버 JWT_SECRET ─┐
├─ 같은 값
관리 콘솔 Support JWT 검증 ─┘
```
현재 로컬에 등록된 후보 이메일은 다음과 같습니다. 스테이징에서도 동일하게 사용할 때만 등록합니다.
값이 다르면 로그인 callback은 끝나도 `/api/support/access``401 Unauthorized`를 반환합니다. 이 secret은 새로 발급하거나 변경할 때 양쪽을 같은 변경 창에 갱신하고 기존 세션 만료를 고려합니다.
## 4. 현재 등록하지 않는 변수
피드백 작성 전용 `docker/docker-compose.prod.yml`에는 `web`만 있으므로 다음은 이 workflow에 등록할 필요가 없습니다.
```text
cyhan@samaneng.com,hsmoon@hanmaceng.co.kr,hikim2@samaneng.com,thlee3@samaneng.com
ABC_API_KEY
SECRETARY_ABC_API_KEY
MASTER_API_KEY
GITEA_API_URL
GITEA_API_TOKEN
GITHUB_API_TOKEN
JIRA_API_TOKEN
SMTP_USERNAME
SMTP_PASSWORD
NAVER_WORKS_ACCESS_TOKEN
MYSQL_* / DATABASE_URL
OPENSEARCH_*
```
### SMTP
이 값들은 과거 전체 플랫폼 배포 또는 관리 콘솔 서버의 책임입니다. 작성 서버에 추가하면 보안 경계와 배포 목적이 흐려집니다. 특히 API key를 `NEXT_PUBLIC_*` 변수나 Web 이미지 build arg로 만들지 않습니다.
| 이름 | 등록값 |
| --------------- | ------------- |
| `SMTP_USERNAME` | SMTP 계정 |
| `SMTP_PASSWORD` | SMTP 비밀번호 |
또한 현재 workflow는 `SSO_REDIRECT_URI`를 전달하지 않습니다. callback은 요청의 `Host``X-Forwarded-Proto`로 동적으로 계산됩니다. 고정 URI가 필요하면 코드·Compose·workflow를 먼저 일관되게 변경해야 합니다.
SMTP를 인증 없이 사용하면 두 값은 빈 값으로 둡니다.
## 5. 등록 후 확인
### BARON-SSO
### 등록 체크리스트
| 이름 | 등록값 |
| ------------------- | ----------------------- |
| `SSO_CLIENT_SECRET` | BARON-SSO Client Secret |
- [ ] `STAGING_HOST=10.13.10.4`
- [ ] `WEB_PORT=8864`
- [ ] `SUPPORT_CONSOLE_API_BASE_URL=https://feedback.hmac.kr/api/support`
- [ ] SSO endpoint 4개가 `https://sso.hmac.kr/oidc` 계열인지 확인
- [ ] `SSO_CLIENT_ID`는 Variable에 등록하고 Secret에는 중복 등록하지 않음
- [ ] `STAGING_USER`가 올바른 SSH 사용자임
- [ ] private key에 대응하는 public key가 원격 `authorized_keys`에 있음
- [ ] 검증된 host key가 `STAGING_SSH_KNOWN_HOSTS`에 있음
- [ ] 작성 서버 전용 `SSO_CLIENT_SECRET`이 맞음
- [ ] `JWT_SECRET`이 관리 콘솔 Support API 설정과 같음
- [ ] BARON-SSO에 실제 접속 주소의 callback URI가 등록됨
BARON-SSO에 다음 Redirect URI도 등록해야 합니다.
Gitea Actions에서 `Deploy feedback demo`를 수동 실행합니다. 첫 단계의 `Validate deployment settings`가 secret 값을 출력하지 않고 설정 존재 여부만 통과해야 합니다.
```text
http://172.16.10.175:3030/api/auth/baron-sso/callback
성공 후 스테이징 서버에서 확인합니다.
```bash
docker compose -f docker/docker-compose.prod.yml ps
curl -fsS http://127.0.0.1:8864/api/health
```
## 3. ABC API Key와 workspace 매핑
그 다음 브라우저에서 `/support/EGBIM_DEMO/new`에 로그인해 양식 조회·피드백 등록·첨부파일 등록을 확인하고, 최종 데이터가 `https://feedback.hmac.kr/`에 보이는지 검증합니다.
Gitea에는 ABC API Key와 내부 매핑 조회용 `MASTER_API_KEY`를 Secret으로 등록합니다. 프로젝트·채널 ID는 Gitea 변수나 수동 SQL로 관리하지 않고, 로그인 시 API가 ABC DB의 프로젝트·채널을 workspace code 기준으로 조회하여 `baron_support.workspace_channel_mappings`에 자동 등록·갱신합니다.
## 6. 자주 발생한 실패와 대응
```text
SECRETARY_ABC_API_KEY=<스테이징 ABC API Key>
MASTER_API_KEY=<API와 secretary-api가 공유하는 내부 인증 키>
```
| 증상 | 원인 후보 | 확인 |
| --- | --- | --- |
| workflow 사전 검사에서 `Missing Gitea secret` | 이름 오타, Variables/Secrets 위치 오류, 빈 값 | workflow의 `${{ vars.* }}`/`${{ secrets.* }}`와 표 대조 |
| SSH host key 오류 | known_hosts 불일치 또는 서버 재설치 | fingerprint를 확인한 뒤 Secret 갱신 |
| callback 이후 `401` | `JWT_SECRET` 불일치, userinfo tenant 누락 | 두 서버 설정과 `SUPPORT_TENANT_ID` 확인 |
| 로그인 URL 또는 API가 localhost로 감 | 공개 API base URL을 build에 주입 | `NEXT_PUBLIC_API_BASE_URL`을 빈 값으로 빌드했는지 확인 |
| 양식 조회 `404` | 잘못된 Support base URL 또는 workspace code | `/api/support` suffix와 mapping 확인 |
| 첨부파일 `413` | 파일당/전체 30MB 또는 10개 초과 | 파일 수와 용량 확인 |
| 배포 script shell syntax 오류 | 원격 login shell이 zsh 등으로 실행 | workflow의 `bash -s` 경계를 유지 |
자동 매핑 대상은 다음 조건을 만족해야 합니다.
- workspace code와 동일한 이름의 ABC 프로젝트가 정확히 1개일 것
- 해당 프로젝트의 채널이 정확히 1개이거나, workspace code/프로젝트 이름과 일치하는 채널이 정확히 1개일 것
- 조건을 만족하면 기존 매핑은 보완하고, 매핑 행이 없으면 새로 INSERT할 것
현재 매핑 확인:
```sql
SELECT
w.id,
w.workspace_code,
w.workspace_name,
m.id AS mapping_id,
m.abc_project_id,
m.abc_channel_id,
m.is_active
FROM workspaces w
LEFT JOIN workspace_channel_mappings m
ON m.workspace_id = w.id
WHERE w.is_active = 1
ORDER BY w.id, m.id;
```
배포 후 사용자가 로그아웃/로그인하거나 `/api/access/me`를 호출하면 EGBIM, TOVA 등의 누락 매핑이 자동으로 채워집니다. 프로젝트·채널 이름이 여러 개로 모호하면 잘못된 연결을 막기 위해 자동 등록하지 않으므로, 이 경우 ABC DB의 이름을 확인해야 합니다.
## 4. DB 접속값
Docker 내부 서비스 간 접속값은 다음과 같습니다.
```text
# ABC 내장 DB
MYSQL_PRIMARY_URL=mysql://userfeedback:userfeedback@mysql:3306/userfeedback
# secretary-api 구축 DB
DATABASE_URL=mysql+pymysql://baron_support:baron_support@mysql-secretary:3306/baron_support
```
현재 `docker-compose.prod.yml`에는 DB 계정과 비밀번호가 직접 작성되어 있습니다.
```text
ABC DB: userfeedback / userfeedback
구축 DB: baron_support / baron_support
```
운영 배포 전에는 다음 값을 Gitea Secret으로 분리하고 Compose에서 참조하도록 변경하는 것을 권장합니다.
```text
MYSQL_ROOT_PASSWORD
MYSQL_PASSWORD
SECRETARY_MYSQL_ROOT_PASSWORD
SECRETARY_MYSQL_PASSWORD
```
## 5. CI/CD 배포용 변수
Gitea Actions에서 SSH 배포 방식을 사용할 경우 다음 값을 등록합니다.
### Variables
| 이름 | 등록값 |
| ---------------------- | ----------------------------------------------------------------- |
| `STAGING_HOST` | `172.16.10.175` (미등록 시 workflow 기본값 사용) |
| `STAGING_PORT` | `22` (미등록 시 workflow가 기본값으로 사용) |
| `STAGING_APP_DIR` | `/home/user/baron_qa` (미등록 시 workflow 기본값 사용) |
| `STAGING_COMPOSE_FILE` | `docker/docker-compose.prod.yml` (미등록 시 workflow 기본값 사용) |
| `STAGING_WEB_PORT` | `3030` |
### Secrets
| 이름 | 등록값 |
| ------------------------- | -------------------------------- |
| `STAGING_USER` | 스테이징 서버 SSH 사용자 |
| `STAGING_SSH_PRIVATE_KEY` | 배포용 SSH private key |
| `STAGING_SSH_KNOWN_HOSTS` | 스테이징 서버의 `known_hosts` 값 |
Docker Registry를 사용하는 경우 추가로 등록합니다.
```text
REGISTRY_URL
REGISTRY_USERNAME
REGISTRY_PASSWORD
```
## 6. 등록 전 확인사항
- 현재 스테이징은 `SMTP_ENABLED=false`로 등록하면 SMTP 없이 BARON-SSO 로그인과 권한 분기만 검증할 수 있습니다. 추후 SMTP 정보를 확보하면 `true`로 변경합니다.
- Gitea Secret에는 API Key, `MASTER_API_KEY`, JWT Secret, SMTP 비밀번호, SSO Client Secret을 등록합니다.
- ABC 프로젝트·채널 매핑은 로그인 시 ABC DB와 자동 동기화되며, 이름이 모호한 경우에만 ABC DB의 프로젝트·채널 이름을 확인합니다.
- `INITIAL_SUPER_ADMIN_PHONE_NUMBER`는 선택값입니다. 등록하면 해당 BARON-SSO 전화번호 사용자를 초기 SUPER 관리자로 자동 승격하며, 미등록 시에도 API는 정상 기동합니다.
- 외부 공개 포트는 Web `3030` 하나이며, Web이 Docker 내부 `api:4000``secretary-api:8010`으로 reverse proxy합니다.
- 로컬 Compose에 존재하는 API Key와 DB 비밀번호를 스테이징에 재사용하지 말고 스테이징용 Secret을 별도로 발급합니다.
문제 해결 중 secret을 `echo`, `set -x`, `docker compose config` 출력으로 노출하지 않습니다. 필요한 로그는 값이 아닌 변수 이름과 HTTP 상태만 남깁니다.