관리자 부트스트랩 복구 보강

This commit is contained in:
2026-05-14 13:36:51 +09:00
parent 6367e62ef0
commit 4862b52b3a
10 changed files with 199 additions and 19 deletions

View File

@@ -1,5 +1,13 @@
# 업데이트 요약
## v1.0.4
- owner/admin 계정이 없는 운영 DB에서도 환경 변수 관리자 계정으로 첫 owner를 생성하거나 기존 일반 회원을 승격할 수 있도록 보강.
## v1.0.3
- NAS에서 Postgres 초기 마이그레이션 디렉터리 권한 문제로 DB 컨테이너가 재시작될 때 확인할 배포 절차를 정리.
## v1.0.2
- 운영 DB 최초 상태에서 환경 변수 관리자 계정으로 첫 owner 계정을 만들고 로그인할 수 있도록 보강.

View File

@@ -129,7 +129,7 @@ cd sori.studio
# .env.production은 Git에 올리지 않는 운영 전용 파일
cp .env.example .env.production
# .env.production 파일에 운영 DB 연결 정보, 운영 전용 랜덤 비밀번호, APP_PORT=43118 입력
# 운영 DB가 비어 있으면 /admin/login에서 ADMIN_EMAIL/ADMIN_PASSWORD로 최초 owner 계정이 생성됨
# 운영 DB에 owner/admin이 없으면 /admin/login에서 ADMIN_EMAIL/ADMIN_PASSWORD로 최초 owner 계정이 생성됨
# MEMBER_SESSION_SECRET은 ADMIN_PASSWORD와 다른 긴 난수 문자열로 반드시 입력
# Docker 네트워크 대역이 NAS 기존 컨테이너와 겹치면 DOCKER_SUBNET을 다른 사설 대역으로 변경
# Docker 내부 앱에서 PostgreSQL에 접근할 때는 sori-studio-db:5432 사용
@@ -166,7 +166,7 @@ docker compose --env-file .env.production up -d --build
- NAS Docker 예시: `postgres://sori_studio:비밀번호@sori-studio-db:5432/sori_studio`
- `.env.example`에는 실제 비밀번호나 개인 이메일을 기록하지 않음
- 개발/운영 DB 비밀번호와 관리자 비밀번호는 서로 다른 랜덤 값을 사용
- `ADMIN_EMAIL`/`ADMIN_PASSWORD`는 운영 DB가 비어 있는 최초 관리자 생성에만 사용한다. 첫 owner 계정이 DB에 생성된 뒤에는 관리자 로그인도 DB의 bcrypt 비밀번호를 기준으로 검증한다.
- `ADMIN_EMAIL`/`ADMIN_PASSWORD`는 운영 DB에 owner/admin이 없는 최초 관리자 생성에만 사용한다. 같은 이메일의 일반 회원이 이미 있으면 owner로 승격하고 비밀번호를 `ADMIN_PASSWORD` 기준으로 갱신한다. 첫 owner 계정이 DB에 생성된 뒤에는 관리자 로그인도 DB의 bcrypt 비밀번호를 기준으로 검증한다.
- 회원 세션 서명용 `MEMBER_SESSION_SECRET`은 관리자 비밀번호와 분리된 긴 난수 문자열을 사용
- 개발 DB와 운영 DB는 반드시 별도 인스턴스 또는 별도 데이터베이스로 분리
- 운영 DB는 로컬 개발 서버에서 직접 연결하지 않음
@@ -263,6 +263,60 @@ git diff -- . ':!package-lock.json'
- `.env.development`, `.env.production`이 변경 목록에 포함되지 않음
- 문서와 코드 diff에 실제 DB 비밀번호, 관리자 비밀번호, 운영 접속 주소가 포함되지 않음
### 컨테이너가 `Restarting`일 때
`Error response from daemon: Container … is restarting, wait until the container is running`은 **프로세스가 곧바로 종료**되어 `restart: unless-stopped`가 반복 시도하는 상태다. 원인은 로그에 나온다.
1. **어느 서비스인지 확인** (`docker-compose.yml` 기준 이름은 `sori-studio`, `sori-studio-db`).
```bash
docker ps -a --filter "name=sori-studio"
```
2. **해당 컨테이너 로그** (가장 중요).
```bash
docker logs sori-studio --tail 150
docker logs sori-studio-db --tail 150
```
Compose로 올렸다면:
```bash
docker compose --env-file .env.production logs sori-studio --tail 200
docker compose --env-file .env.production logs sori-studio-db --tail 200
```
3. **자주 나오는 원인**
- **`sori-studio`**: `DATABASE_URL` 누락·오타, `MEMBER_SESSION_SECRET` 미설정, DB 호스트가 컨테이너 기준으로 잘못됨(예: 앱은 Docker 안인데 URL만 `127.0.0.1`로 DB를 가리킴), 애플리케이션 예외로 즉시 종료.
- **`sori-studio-db`**: 이미 초기화된 볼륨과 다른 `POSTGRES_PASSWORD`로 다시 올린 경우, `docker-entrypoint-initdb.d` 마이그레이션 SQL 오류, 디스크/권한 문제.
- **`sori-studio-db` 로그에 `ls: can't open '/docker-entrypoint-initdb.d/': Permission denied`**: 아래 **NAS·호스트에서 `db/migrations` 권한** 절차를 확인한다.
4. 로그를 고친 뒤에는 `docker compose --env-file .env.production up -d`로 다시 올리고, `docker ps`에서 `Up` 상태인지 확인한다.
### NAS·호스트에서 `db/migrations` 권한
`docker-compose.yml``./db/migrations`를 Postgres 이미지의 `/docker-entrypoint-initdb.d`**읽기 전용**으로 붙인다. 공식 엔트리포인트는 이 디렉터리를 `ls`로 읽는데, NAS(UGREEN 등)나 SSH로 복사한 트리에서 **폴더·파일이 700/600만 허용**이거나 **상위 디렉터리에 실행(x) 비트가 없으면** 컨테이너 안 `postgres` 사용자가 경로를 통과하지 못해 `Permission denied`가 반복되고 DB 컨테이너가 재시작 루프에 들어갈 수 있다.
프로젝트 루트( `docker compose` 를 실행하는 디렉터리)에서 SSH로 다음을 적용한다. **비밀번호는 바꾸지 않으며**, 읽기·디렉터리 통과만 연다.
```bash
cd /volume1/docker/projects/apps/sori.studio
# 마이그레이션 디렉터리와 그 안 SQL: 모두 읽기, 디렉터리는 검색 가능
sudo chmod -R a+rX db/migrations
# 상위 db/, 프로젝트 루트가 다른 사용자만 rwx 인 경우 통과 허용
sudo chmod a+x . db db/migrations
```
그다음 DB 컨테이너만 재시작한다.
```bash
docker compose --env-file .env.production restart sori-studio-db
```
여전히 동일하면 프로젝트가 **SMB 공유 폴더 위**에만 있지 않은지 확인한다. Docker 데몬이 네이티브 경로(ext4 등)의 디렉터리를 마운트할 때 권한이 더 예측 가능하다.
## 업로드 파일
- 관리자 글쓰기에서 업로드한 이미지는 `/uploads/posts/YYYY/MM/` URL로 제공한다.

View File

@@ -1,5 +1,17 @@
# 의사결정 이력
## 2026-05-14 v1.0.4
### 최초 관리자 기준을 owner/admin 존재 여부로 변경
운영 DB에 일반 회원이 먼저 생성되면 기존의 “사용자 0명” 기준 부트스트랩이 더 이상 동작하지 않아 관리자 계정이 없는 잠금 상태가 생길 수 있다. 최초 관리자 필요 여부를 전체 사용자 수가 아니라 `owner`/`admin` 권한 보유자 존재 여부로 판단하고, `ADMIN_EMAIL`과 같은 일반 회원이 이미 있으면 해당 회원을 `owner`로 승격하면서 `ADMIN_PASSWORD` 기준 비밀번호 해시를 갱신해 운영 복구 경로를 보장한다. 이미 `owner`/`admin`이 있으면 환경 변수 로그인은 우회 권한으로 쓰지 않는다.
## 2026-05-14 v1.0.3
### NAS에서 Postgres init 디렉터리 Permission denied
Docker가 호스트의 `db/migrations``postgres:16-alpine``/docker-entrypoint-initdb.d`에 마운트할 때, NAS 파일 시스템이나 복사 시 기본 umask 때문에 디렉터리가 `700`·파일이 `600`만 되면 컨테이너 내부 `postgres` UID로는 목록을 읽지 못한다. 엔트리포인트가 `ls /docker-entrypoint-initdb.d`에서 실패하면 DB가 즉시 종료되고 `restart: unless-stopped`로 루프에 들어간다. 배포 문서에 `chmod -R a+rX db/migrations` 및 상위 경로 `a+x` 절차를 명시하고, compose에 주석으로 동일 원인을 남겨 재발 시 빠르게 대응할 수 있게 했다.
## 2026-05-14 v1.0.1
### Docker Compose 전용 네트워크 대역 명시

View File

@@ -567,8 +567,8 @@ components/content/
- 관리자 인증은 `users.is_admin=true` 회원의 이메일/비밀번호(bcrypt 해시) 기반으로 처리한다.
- DB에 사용자가 없으면 `/signup`에서 관리자 등록 모드(관리자 이름/이메일/비밀번호/비밀번호 확인)로 첫 계정을 생성한다.
- DB에 사용자가 없는 최초 상태에서 `/admin/login``ADMIN_EMAIL`/`ADMIN_PASSWORD`와 같은 값을 입력하면 첫 owner 계정을 DB에 생성한 뒤 로그인한다. 이미 사용자가 있으면 환경 변수 계정으로 우회 로그인하지 않고 DB의 관리자 계정만 사용한다.
- 첫 계정 생성 시 `is_admin=true`, `user_role=owner`를 자동 부여한다. 최초 가입 판정은 동시 요청에서 중복 owner가 생기지 않도록 `users` 테이블 잠금 안에서 처리한다.
- DB에 owner/admin 계정이 없는 최초 상태에서 `/admin/login``ADMIN_EMAIL`/`ADMIN_PASSWORD`와 같은 값을 입력하면 첫 owner 계정을 DB에 생성한 뒤 로그인한다. 같은 이메일의 일반 회원이 이미 있으면 해당 회원을 owner로 승격하고 비밀번호를 `ADMIN_PASSWORD` 기준 bcrypt 해시로 갱신한다. 이미 owner/admin 계정이 있으면 환경 변수 계정으로 우회 로그인하지 않고 DB의 관리자 계정만 사용한다.
- 최초 owner 생성 시 `is_admin=true`, `user_role=owner`를 자동 부여한다. 최초 관리자 판정은 동시 요청에서 중복 owner가 생기지 않도록 `users` 테이블 잠금 안에서 처리한다.
- 관리자 로그인 성공 시 httpOnly 세션 쿠키를 `/admin` 경로에 설정한다.
- `/admin/login` 로그인 제출 버튼은 이메일·비밀번호가 모두 입력된 경우에만 활성화한다.
- 관리자 로그인 성공 시 관리자/회원 세션 쿠키를 함께 설정하고 `users.previous_last_seen_at`/`previous_last_seen_ip`에 직전 로그인 값을 보존한 뒤 `last_seen_at`/`last_seen_ip`를 현재 로그인으로 갱신한다.

View File

@@ -1,9 +1,22 @@
# 업데이트 이력
## v1.0.4
- 최초 관리자 부트스트랩 기준을 전체 사용자 수가 아니라 owner/admin 존재 여부로 변경.
- owner/admin이 없는 상태에서 `ADMIN_EMAIL`과 같은 일반 회원이 이미 있으면 해당 회원을 owner로 승격하고 `ADMIN_PASSWORD`로 비밀번호를 갱신하도록 수정.
- 패키지 버전 `1.0.4`로 갱신.
## v1.0.3
- NAS 등에서 `db/migrations` 바인드 마운트 권한 부족 시 `docker-entrypoint-initdb.d` Permission denied로 DB 컨테이너가 재시작하는 경우를 배포 문서에 정리.
- `docker-compose.yml`에 동일 이슈용 주석 추가.
- 패키지 버전 `1.0.3`으로 갱신.
## v1.0.2
- 운영 DB가 비어 있을 때 `/admin/login`에서 `ADMIN_EMAIL`/`ADMIN_PASSWORD`로 최초 owner 계정을 생성하도록 수정.
- 배포 문서의 `.env.production` 생성 명령과 최초 관리자 생성 기준 정리.
- 배포 문서에 Docker 컨테이너 `Restarting` 루프 시 로그 확인 절차 추가.
- 패키지 버전 `1.0.2`로 갱신.
## v1.0.1