체험 계정도 업그레이드 승인되도록 수정
This commit is contained in:
+73
-73
@@ -1,16 +1,16 @@
|
||||
# chocoadmin Deployment
|
||||
# chocoadmin 배포
|
||||
|
||||
## 1. Environment Files
|
||||
## 1. 환경 파일
|
||||
|
||||
Create environment files on the Synology NAS. Do not commit real `.env.*` files.
|
||||
Synology NAS에서 환경 파일을 생성합니다. 실제 `.env.*` 파일은 커밋하지 마세요.
|
||||
|
||||
Stage file path:
|
||||
스테이지 파일 경로:
|
||||
|
||||
```bash
|
||||
/volume1/docker/service/jinaju/chocoadmin/.env.stage
|
||||
```
|
||||
|
||||
Stage example:
|
||||
스테이지 예시:
|
||||
|
||||
```bash
|
||||
DATABASE_URL="mysql://USER:PASSWORD@mariadb.jisangs.com:30001/chocomae"
|
||||
@@ -20,13 +20,13 @@ AUTH_URL="https://chocoadmin-stage.jisangs.com"
|
||||
NEXTAUTH_URL="https://chocoadmin-stage.jisangs.com"
|
||||
```
|
||||
|
||||
Production file path:
|
||||
운영 파일 경로:
|
||||
|
||||
```bash
|
||||
/volume1/docker/service/jinaju/chocoadmin/.env.production
|
||||
```
|
||||
|
||||
Production example:
|
||||
운영 예시:
|
||||
|
||||
```bash
|
||||
DATABASE_URL="mysql://CHCOCO_ADMIN_USER:PASSWORD@chocomae.jinaju.com:3306/chocomae"
|
||||
@@ -36,36 +36,36 @@ AUTH_URL="https://chocoadmin.jinaju.com"
|
||||
NEXTAUTH_URL="https://chocoadmin.jinaju.com"
|
||||
```
|
||||
|
||||
`AUTH_SECRET` and `NEXTAUTH_SECRET` must be the same strong random value within each environment for Auth.js compatibility. Use different values between production and stage.
|
||||
`AUTH_SECRET`과 `NEXTAUTH_SECRET`은 Auth.js 호환을 위해 각 환경 내에서 동일한 강력한 랜덤 값이어야 합니다. 운영과 스테이지 사이에는 서로 다른 값을 사용하세요.
|
||||
|
||||
`APP_ENV` (`production` / `stage`) is set by the `docker-compose*.yml` `environment` block — do **not** put it in the env file. It drives the session cookie name (`chocoadmin-${APP_ENV}.session-token`), which keeps production and stage cookies separate even if they share a browser.
|
||||
`APP_ENV`(`production` / `stage`)는 `docker-compose*.yml`의 `environment` 블록에서 설정합니다 — env 파일에 넣지 **마세요**. 이 값이 세션 쿠키 이름(`chocoadmin-${APP_ENV}.session-token`)을 결정하며, 브라우저를 공유하더라도 운영과 스테이지 쿠키를 분리해 줍니다.
|
||||
|
||||
When the public URL uses HTTPS, both `AUTH_URL` and `NEXTAUTH_URL` must use the exact external HTTPS URL.
|
||||
공개 URL이 HTTPS를 사용하면 `AUTH_URL`과 `NEXTAUTH_URL` 모두 외부 HTTPS URL과 정확히 일치해야 합니다.
|
||||
|
||||
Generate a secret on the NAS:
|
||||
NAS에서 시크릿 생성:
|
||||
|
||||
```bash
|
||||
openssl rand -base64 32
|
||||
```
|
||||
|
||||
## 2. Synology Stage Deployment
|
||||
## 2. Synology 스테이지 배포
|
||||
|
||||
Both production and stage share the same Git checkout at `/volume1/docker/service/jinaju/chocoadmin`. Pull from there:
|
||||
운영과 스테이지 모두 `/volume1/docker/service/jinaju/chocoadmin`의 동일한 Git 체크아웃을 공유합니다. 여기에서 pull하세요:
|
||||
|
||||
```bash
|
||||
cd /volume1/docker/service/jinaju/chocoadmin-stage
|
||||
git pull --ff-only
|
||||
```
|
||||
|
||||
Stage uses `docker-compose.stage.yml`, the `chocoadmin-stage` container, and the external Docker network `proxy-network`.
|
||||
스테이지는 `docker-compose.stage.yml`, `chocoadmin-stage` 컨테이너, 외부 Docker 네트워크 `proxy-network`를 사용합니다.
|
||||
|
||||
Verify or create the network:
|
||||
네트워크 확인 또는 생성:
|
||||
|
||||
```bash
|
||||
docker network inspect proxy-network >/dev/null 2>&1 || docker network create proxy-network
|
||||
```
|
||||
|
||||
Start or update stage:
|
||||
스테이지 시작 또는 업데이트:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.stage.yml up -d --build
|
||||
@@ -73,36 +73,36 @@ docker compose -f docker-compose.stage.yml ps
|
||||
docker compose -f docker-compose.stage.yml logs -f
|
||||
```
|
||||
|
||||
If a service was renamed in the compose file, add `--remove-orphans` once to remove the stale container.
|
||||
compose 파일에서 서비스 이름을 바꾼 경우, 오래된 컨테이너를 제거하기 위해 `--remove-orphans`를 한 번 추가하세요.
|
||||
|
||||
After redeployment, reload NPM to flush its upstream DNS cache:
|
||||
재배포 후 NPM의 업스트림 DNS 캐시를 비우기 위해 재로드합니다:
|
||||
|
||||
```bash
|
||||
docker exec npm nginx -s reload
|
||||
```
|
||||
|
||||
`docker-compose.stage.yml` exposes container port `3000` to `proxy-network` instead of binding host port `3000`, because host port `3000` may already be used by another service such as Gitea.
|
||||
`docker-compose.stage.yml`은 호스트 포트 `3000`을 바인딩하는 대신 컨테이너 포트 `3000`을 `proxy-network`에 노출합니다 — 호스트 포트 `3000`이 Gitea 같은 다른 서비스에서 이미 사용 중일 수 있기 때문입니다.
|
||||
|
||||
Nginx Proxy Manager settings:
|
||||
Nginx Proxy Manager 설정:
|
||||
|
||||
- Scheme: `http`
|
||||
- Forward Hostname / IP: `chocoadmin-stage`
|
||||
- Forward Port: `3000`
|
||||
- Websockets Support: enabled
|
||||
- SSL: enabled
|
||||
- Force SSL: enabled
|
||||
- Websockets Support: 활성화
|
||||
- SSL: 활성화
|
||||
- Force SSL: 활성화
|
||||
|
||||
Stage URL:
|
||||
스테이지 URL:
|
||||
|
||||
```text
|
||||
https://chocoadmin-stage.jisangs.com
|
||||
```
|
||||
|
||||
After changing `AUTH_URL`, `NEXTAUTH_URL`, or cookie-related settings, clear browser cookies for the stage domain or test in a private window.
|
||||
`AUTH_URL`, `NEXTAUTH_URL` 또는 쿠키 관련 설정을 변경한 뒤에는 스테이지 도메인의 브라우저 쿠키를 지우거나 시크릿 창에서 테스트하세요.
|
||||
|
||||
## 3. Production Deployment
|
||||
## 3. 운영 배포
|
||||
|
||||
Production uses `docker-compose.yml` and reads `.env.production` by default:
|
||||
운영은 `docker-compose.yml`을 사용하며 기본적으로 `.env.production`을 읽습니다:
|
||||
|
||||
```bash
|
||||
cd /volume1/docker/service/jinaju/chocoadmin
|
||||
@@ -112,32 +112,32 @@ docker compose ps
|
||||
docker compose logs -f chocoadmin
|
||||
```
|
||||
|
||||
After redeployment, reload NPM:
|
||||
재배포 후 NPM 재로드:
|
||||
|
||||
```bash
|
||||
docker exec npm nginx -s reload
|
||||
```
|
||||
|
||||
Nginx Proxy Manager settings:
|
||||
Nginx Proxy Manager 설정:
|
||||
|
||||
- Scheme: `http`
|
||||
- Forward Hostname / IP: `chocoadmin`
|
||||
- Forward Port: `3000`
|
||||
- Websockets Support: enabled
|
||||
- SSL: enabled
|
||||
- Force SSL: enabled
|
||||
- Websockets Support: 활성화
|
||||
- SSL: 활성화
|
||||
- Force SSL: 활성화
|
||||
|
||||
Production URL:
|
||||
운영 URL:
|
||||
|
||||
```text
|
||||
https://chocoadmin.jinaju.com
|
||||
```
|
||||
|
||||
The Docker build uses placeholder build-time environment variables only so Next.js can compile without committing secrets. Runtime values are read from the compose `env_file`.
|
||||
Docker 빌드는 시크릿을 커밋하지 않고도 Next.js가 컴파일될 수 있도록, 빌드 시점 환경 변수를 플레이스홀더로만 사용합니다. 런타임 값은 compose의 `env_file`에서 읽습니다.
|
||||
|
||||
## 4. AWS EC2 Security Group
|
||||
## 4. AWS EC2 보안 그룹
|
||||
|
||||
Allow MariaDB only from the Synology NAS public IP.
|
||||
MariaDB는 Synology NAS 공인 IP에서만 접근하도록 허용합니다.
|
||||
|
||||
- Type: `MYSQL/Aurora`
|
||||
- Protocol: `TCP`
|
||||
@@ -145,79 +145,79 @@ Allow MariaDB only from the Synology NAS public IP.
|
||||
- Source: `NAS_PUBLIC_IP/32`
|
||||
- Description: `chocoadmin Synology NAS`
|
||||
|
||||
Do not open `3306` to `0.0.0.0/0`.
|
||||
`3306`을 `0.0.0.0/0`으로 열지 마세요.
|
||||
|
||||
## 5. Read-Only Rehearsal
|
||||
## 5. 읽기 전용 리허설
|
||||
|
||||
Before enabling approval/rejection operations against production DB:
|
||||
운영 DB에 대해 승인/거절 작업을 활성화하기 전:
|
||||
|
||||
1. Create or use a DB account with read-only permissions.
|
||||
2. Set `DATABASE_URL` in `.env.production` to that read-only account.
|
||||
3. Start the container.
|
||||
4. Verify login, maestro list, extension request list, and upgrade request list.
|
||||
5. Switch to the production write-capable chocoadmin DB account only after read screens work.
|
||||
1. 읽기 전용 권한을 가진 DB 계정을 생성하거나 사용합니다.
|
||||
2. `.env.production`의 `DATABASE_URL`을 해당 읽기 전용 계정으로 설정합니다.
|
||||
3. 컨테이너를 시작합니다.
|
||||
4. 로그인, 마에스트로 목록, 연장 신청 목록, 업그레이드 신청 목록을 확인합니다.
|
||||
5. 읽기 화면이 정상 동작한 뒤에만 쓰기 가능한 운영 chocoadmin DB 계정으로 전환합니다.
|
||||
|
||||
## 6. Smoke Checks
|
||||
## 6. 스모크 체크
|
||||
|
||||
After each deployment:
|
||||
배포 후 매번:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.stage.yml ps # stage
|
||||
docker compose ps # production
|
||||
docker compose -f docker-compose.stage.yml ps # 스테이지
|
||||
docker compose ps # 운영
|
||||
```
|
||||
|
||||
Verify these routes in the browser:
|
||||
브라우저에서 다음 경로들을 확인합니다:
|
||||
|
||||
- `/login`
|
||||
- `/maestros`
|
||||
- `/extension-requests`
|
||||
- `/upgrade-requests`
|
||||
|
||||
## 7. Troubleshooting
|
||||
## 7. 트러블슈팅
|
||||
|
||||
### ERR_TOO_MANY_REDIRECTS after login
|
||||
### 로그인 후 ERR_TOO_MANY_REDIRECTS
|
||||
|
||||
Middleware redirects to `/login`, login page redirects back to `/` — infinite loop. Work through this checklist in order:
|
||||
미들웨어가 `/login`으로 리다이렉트하고, 로그인 페이지가 다시 `/`로 리다이렉트 — 무한 루프입니다. 다음 체크리스트를 순서대로 진행하세요:
|
||||
|
||||
1. **Check `AUTH_URL` / `NEXTAUTH_URL`** — must exactly match the public HTTPS URL including scheme.
|
||||
2. **Check NPM scheme** — NPM must forward with scheme `http` (not `https`) to the container. The app detects HTTPS from `AUTH_URL`, not from the incoming request.
|
||||
3. **Check cookie name consistency** — `auth.ts` (`cookies.sessionToken.name`) and `proxy.ts` (`getToken({ cookieName })`) must both use the same `chocoadmin-${APP_ENV}.session-token` value. If `proxy.ts` uses the default name while `auth.ts` uses a custom one, middleware never finds the session.
|
||||
4. **Check for `__Secure-` prefix** — do not add it. Middleware runs in Node.js runtime and receives HTTP from Nginx. `__Secure-` cookies are silently rejected over HTTP.
|
||||
5. **Clear browser cookies** for the domain, then test in a private window.
|
||||
6. **Recreate the container** after changing `.env.*`.
|
||||
1. **`AUTH_URL` / `NEXTAUTH_URL` 확인** — 스킴을 포함해 공개 HTTPS URL과 정확히 일치해야 합니다.
|
||||
2. **NPM 스킴 확인** — NPM은 컨테이너로 `https`가 아닌 `http` 스킴으로 포워드해야 합니다. 앱은 들어오는 요청이 아니라 `AUTH_URL`로 HTTPS를 감지합니다.
|
||||
3. **쿠키 이름 일관성 확인** — `auth.ts`(`cookies.sessionToken.name`)와 `proxy.ts`(`getToken({ cookieName })`) 모두 동일한 `chocoadmin-${APP_ENV}.session-token` 값을 사용해야 합니다. `proxy.ts`가 기본 이름을 사용하는데 `auth.ts`는 커스텀 이름을 쓰면, 미들웨어는 세션을 절대 찾지 못합니다.
|
||||
4. **`__Secure-` 접두사 확인** — 붙이지 마세요. 미들웨어는 Node.js 런타임에서 실행되고 Nginx로부터 HTTP를 받습니다. `__Secure-` 쿠키는 HTTP 상에서 조용히 거부됩니다.
|
||||
5. 해당 도메인의 브라우저 **쿠키를 삭제**하고 시크릿 창에서 테스트합니다.
|
||||
6. `.env.*`를 변경한 뒤에는 컨테이너를 **재생성**합니다.
|
||||
|
||||
### Production and stage traffic mixing
|
||||
### 운영과 스테이지 트래픽이 섞임
|
||||
|
||||
Symptom: logging into `chocoadmin.jinaju.com` shows the stage UI, or requests hit both containers interchangeably.
|
||||
증상: `chocoadmin.jinaju.com`에 로그인했는데 스테이지 UI가 표시되거나, 요청이 두 컨테이너를 번갈아가며 도달함.
|
||||
|
||||
Cause: Docker registers each `services:` key as a DNS alias in `proxy-network`. If stage and production share the same service name, NPM's upstream resolves to both containers via round-robin.
|
||||
원인: Docker는 각 `services:` 키를 `proxy-network`의 DNS 별칭으로 등록합니다. 스테이지와 운영이 같은 서비스 이름을 공유하면, NPM의 업스트림이 라운드 로빈으로 두 컨테이너 모두로 매핑됩니다.
|
||||
|
||||
Check the network:
|
||||
네트워크 확인:
|
||||
|
||||
```bash
|
||||
docker network inspect proxy-network --format '{{range .Containers}}{{.Name}} {{.IPv4Address}}{{"\n"}}{{end}}'
|
||||
```
|
||||
|
||||
Expected: `chocoadmin` and `chocoadmin-stage` appear with different IPs. If `chocoadmin` appears twice, a stale container is using that alias.
|
||||
기대 결과: `chocoadmin`과 `chocoadmin-stage`가 서로 다른 IP로 나타남. `chocoadmin`이 두 번 나타나면, 오래된 컨테이너가 그 별칭을 사용 중입니다.
|
||||
|
||||
Fix: ensure `docker-compose.stage.yml` has `services: chocoadmin-stage:` (not `chocoadmin`), redeploy stage once with `--remove-orphans` to clean up the stale container, then reload NPM.
|
||||
수정: `docker-compose.stage.yml`이 `services: chocoadmin-stage:`(`chocoadmin` 아님)를 사용하는지 확인하고, 오래된 컨테이너를 정리하기 위해 `--remove-orphans`로 스테이지를 한 번 재배포한 뒤 NPM을 재로드합니다.
|
||||
|
||||
### Failed to find Server Action after redeployment
|
||||
### 재배포 후 Failed to find Server Action
|
||||
|
||||
Symptom: clicking a button (e.g. logout) returns a 404 or `Failed to find Server Action` error after deploying a new build.
|
||||
증상: 새 빌드를 배포한 뒤 버튼(예: 로그아웃)을 클릭하면 404 또는 `Failed to find Server Action` 에러가 발생함.
|
||||
|
||||
Cause: the action was defined as an inline `"use server"` closure. Closures get a new action ID on every build. The browser cached the old ID.
|
||||
원인: 액션이 인라인 `"use server"` 클로저로 정의되었습니다. 클로저는 빌드마다 새로운 액션 ID를 얻습니다. 브라우저는 오래된 ID를 캐싱합니다.
|
||||
|
||||
Fix: force-reload the page (`Cmd+Shift+R` / `Ctrl+Shift+R`) to discard the cached page with stale action IDs. If the problem recurs after every deployment, the action must be extracted to a module-level named export in a separate `actions.ts` file.
|
||||
수정: 강제 새로고침(`Cmd+Shift+R` / `Ctrl+Shift+R`)으로 오래된 액션 ID가 담긴 캐시 페이지를 폐기합니다. 배포마다 문제가 반복된다면, 액션을 별도의 `actions.ts` 파일에서 모듈 레벨 named export로 추출해야 합니다.
|
||||
|
||||
### Container starts but immediately exits
|
||||
### 컨테이너가 시작하자마자 종료됨
|
||||
|
||||
```bash
|
||||
docker compose logs chocoadmin
|
||||
```
|
||||
|
||||
Common causes:
|
||||
일반적인 원인:
|
||||
|
||||
- Missing `DATABASE_URL` or malformed connection string.
|
||||
- `AUTH_SECRET` not set — NextAuth throws on startup.
|
||||
- Port already allocated — check if another service uses host port 3000. Both compose files use `expose` (not `ports`) for port 3000, so this should not happen unless the compose file was modified.
|
||||
- 누락되었거나 형식이 잘못된 `DATABASE_URL` 연결 문자열.
|
||||
- `AUTH_SECRET`이 설정되지 않음 — NextAuth가 시작 시점에 예외를 던집니다.
|
||||
- 포트가 이미 할당됨 — 다른 서비스가 호스트 포트 3000을 사용 중인지 확인하세요. 두 compose 파일 모두 포트 3000에 `ports`가 아닌 `expose`를 사용하므로, compose 파일이 수정되지 않은 한 이런 문제는 발생하지 않아야 합니다.
|
||||
|
||||
Reference in New Issue
Block a user