v1.3.8: NAS 마이그레이션 환경 파일 없을 때 보정

.env.production이 없으면 .env 또는 실행 중 DB 컨테이너 환경 변수로 migrate-production-db.sh가 동작한다.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-20 14:44:43 +09:00
parent cc34db40f2
commit a396d1d022
5 changed files with 71 additions and 16 deletions

View File

@@ -199,6 +199,7 @@ docker compose --env-file .env.production up -d --build
- 로컬 개발 Docker Compose 실행 시 `ENV_FILE=.env.development``--env-file .env.development`를 함께 사용 - 로컬 개발 Docker Compose 실행 시 `ENV_FILE=.env.development``--env-file .env.development`를 함께 사용
- 로컬 개발 DB 마이그레이션은 `npm run db:migrate:dev`로 실행 - 로컬 개발 DB 마이그레이션은 `npm run db:migrate:dev`로 실행
- NAS 운영 DB 마이그레이션은 NAS 호스트에 npm이 없어도 실행할 수 있도록 `sh scripts/migrate-production-db.sh status`로 적용 상태를 확인하고, `sh scripts/migrate-production-db.sh migrate`로 미적용 파일만 실행한다. - NAS 운영 DB 마이그레이션은 NAS 호스트에 npm이 없어도 실행할 수 있도록 `sh scripts/migrate-production-db.sh status`로 적용 상태를 확인하고, `sh scripts/migrate-production-db.sh migrate`로 미적용 파일만 실행한다.
- 운영 환경 파일은 프로젝트 루트의 `.env.production`을 우선 사용한다. 없으면 `.env`를 읽고, 둘 다 없으면 실행 중인 `sori-studio-db` 컨테이너의 `POSTGRES_DB`·`POSTGRES_USER`를 사용한다.
- `schema_migrations`가 없는 기존 운영 DB에서 `posts` 테이블이 감지되면 `migrate`는 001부터 자동 실행하지 않는다. 현재 코드 기준 최신 DB라면 최초 1회 `sh scripts/migrate-production-db.sh baseline`으로 기존 파일을 적용 완료로 기록한다. 특정 번호까지만 기록하려면 예: `sh scripts/migrate-production-db.sh baseline 031`. - `schema_migrations`가 없는 기존 운영 DB에서 `posts` 테이블이 감지되면 `migrate`는 001부터 자동 실행하지 않는다. 현재 코드 기준 최신 DB라면 최초 1회 `sh scripts/migrate-production-db.sh baseline`으로 기존 파일을 적용 완료로 기록한다. 특정 번호까지만 기록하려면 예: `sh scripts/migrate-production-db.sh baseline 031`.
- 네비게이션 계층(`parent_id`, `is_folder`)은 `017_navigation_hierarchy.sql` 적용 후 저장 API가 정상 동작한다(미적용 시 `INSERT` 컬럼 불일치). - 네비게이션 계층(`parent_id`, `is_folder`)은 `017_navigation_hierarchy.sql` 적용 후 저장 API가 정상 동작한다(미적용 시 `INSERT` 컬럼 불일치).
- 회원 마지막 로그인 표시(`previous_last_seen_at`, `previous_last_seen_ip`)는 `021_add_member_previous_login.sql` 적용 후 정상 동작한다. - 회원 마지막 로그인 표시(`previous_last_seen_at`, `previous_last_seen_ip`)는 `021_add_member_previous_login.sql` 적용 후 정상 동작한다.

View File

@@ -1,5 +1,9 @@
# 업데이트 이력 # 업데이트 이력
## v1.3.8
- NAS 마이그레이션: `.env.production`이 없을 때 `.env` 또는 실행 중 DB 컨테이너 환경 변수로 동작하도록 `migrate-production-db.sh` 보정.
## v1.3.7 ## v1.3.7
- NAS 마이그레이션: npm 없는 NAS 호스트에서도 실행 가능한 `scripts/migrate-production-db.sh` 추가. - NAS 마이그레이션: npm 없는 NAS 호스트에서도 실행 가능한 `scripts/migrate-production-db.sh` 추가.

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "sori.studio", "name": "sori.studio",
"version": "1.3.7", "version": "1.3.8",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "sori.studio", "name": "sori.studio",
"version": "1.3.7", "version": "1.3.8",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@nuxtjs/tailwindcss": "^6.14.0", "@nuxtjs/tailwindcss": "^6.14.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "sori.studio", "name": "sori.studio",
"version": "1.3.7", "version": "1.3.8",
"private": true, "private": true,
"type": "module", "type": "module",
"imports": { "imports": {

View File

@@ -2,28 +2,79 @@
set -eu set -eu
ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
ENV_FILE=${ENV_FILE:-.env.production}
DB_SERVICE=${DB_SERVICE:-sori-studio-db} DB_SERVICE=${DB_SERVICE:-sori-studio-db}
MIGRATIONS_DIR="$ROOT_DIR/db/migrations" MIGRATIONS_DIR="$ROOT_DIR/db/migrations"
CONTAINER_MIGRATIONS_DIR=${CONTAINER_MIGRATIONS_DIR:-/docker-entrypoint-initdb.d} CONTAINER_MIGRATIONS_DIR=${CONTAINER_MIGRATIONS_DIR:-/docker-entrypoint-initdb.d}
SCHEMA_MIGRATIONS_TABLE=${SCHEMA_MIGRATIONS_TABLE:-schema_migrations} SCHEMA_MIGRATIONS_TABLE=${SCHEMA_MIGRATIONS_TABLE:-schema_migrations}
MODE=${1:-migrate} MODE=${1:-migrate}
BASELINE_TARGET=${2:-} BASELINE_TARGET=${2:-}
COMPOSE_ENV_FILE=
cd "$ROOT_DIR" cd "$ROOT_DIR"
if [ -f "$ENV_FILE" ]; then
set -a
# shellcheck disable=SC1090
. "$ENV_FILE"
set +a
fi
POSTGRES_DB=${POSTGRES_DB:-sori_studio} POSTGRES_DB=${POSTGRES_DB:-sori_studio}
POSTGRES_USER=${POSTGRES_USER:-sori_studio} POSTGRES_USER=${POSTGRES_USER:-sori_studio}
# 환경 파일(.env.production → .env) 선택
pick_compose_env_file() {
if [ -n "${ENV_FILE:-}" ] && [ -r "$ENV_FILE" ]; then
COMPOSE_ENV_FILE=$ENV_FILE
return 0
fi
if [ -r .env.production ]; then
COMPOSE_ENV_FILE=.env.production
return 0
fi
if [ -r .env ]; then
COMPOSE_ENV_FILE=.env
return 0
fi
COMPOSE_ENV_FILE=
}
# 환경 파일 로드(없으면 컨테이너 환경 변수 사용)
load_env_file() {
pick_compose_env_file
if [ -z "$COMPOSE_ENV_FILE" ]; then
echo "환경 파일(.env.production 또는 .env)이 없습니다. 실행 중인 DB 컨테이너 환경 변수를 사용합니다." >&2
return 0
fi
set -a
# shellcheck disable=SC1090
. "./$COMPOSE_ENV_FILE"
set +a
}
compose() { compose() {
docker compose --env-file "$ENV_FILE" "$@" if [ -n "$COMPOSE_ENV_FILE" ]; then
docker compose --env-file "$COMPOSE_ENV_FILE" "$@"
return 0
fi
docker compose "$@"
}
# 실행 중인 DB 컨테이너에서 PostgreSQL 계정 정보 읽기
load_db_env_from_container() {
if ! compose exec -T "$DB_SERVICE" true >/dev/null 2>&1; then
return 0
fi
container_db=$(compose exec -T "$DB_SERVICE" printenv POSTGRES_DB 2>/dev/null | tr -d '\r' || true)
container_user=$(compose exec -T "$DB_SERVICE" printenv POSTGRES_USER 2>/dev/null | tr -d '\r' || true)
if [ -n "$container_db" ]; then
POSTGRES_DB=$container_db
fi
if [ -n "$container_user" ]; then
POSTGRES_USER=$container_user
fi
} }
psql_exec() { psql_exec() {
@@ -101,7 +152,6 @@ print_status() {
baseline_migrations() { baseline_migrations() {
ensure_schema_migrations_table ensure_schema_migrations_table
baseline_count=0
found_target=0 found_target=0
migration_files | while IFS= read -r file_name; do migration_files | while IFS= read -r file_name; do
@@ -111,7 +161,6 @@ baseline_migrations() {
run_sql "INSERT INTO $SCHEMA_MIGRATIONS_TABLE (file_name) VALUES ('$file_name') ON CONFLICT (file_name) DO NOTHING;" run_sql "INSERT INTO $SCHEMA_MIGRATIONS_TABLE (file_name) VALUES ('$file_name') ON CONFLICT (file_name) DO NOTHING;"
echo "baseline $file_name" echo "baseline $file_name"
baseline_count=$((baseline_count + 1))
if [ -n "$BASELINE_TARGET" ] && { [ "$file_name" = "$BASELINE_TARGET" ] || echo "$file_name" | grep -q "^${BASELINE_TARGET}_"; }; then if [ -n "$BASELINE_TARGET" ] && { [ "$file_name" = "$BASELINE_TARGET" ] || echo "$file_name" | grep -q "^${BASELINE_TARGET}_"; }; then
found_target=1 found_target=1
@@ -136,7 +185,6 @@ migrate_database() {
fi fi
ensure_schema_migrations_table ensure_schema_migrations_table
applied_count=0
migration_files | while IFS= read -r file_name; do migration_files | while IFS= read -r file_name; do
if is_applied "$file_name"; then if is_applied "$file_name"; then
@@ -146,13 +194,15 @@ migrate_database() {
echo "apply $file_name" echo "apply $file_name"
psql_exec -f "$CONTAINER_MIGRATIONS_DIR/$file_name" psql_exec -f "$CONTAINER_MIGRATIONS_DIR/$file_name"
run_sql "INSERT INTO $SCHEMA_MIGRATIONS_TABLE (file_name) VALUES ('$file_name') ON CONFLICT (file_name) DO NOTHING;" run_sql "INSERT INTO $SCHEMA_MIGRATIONS_TABLE (file_name) VALUES ('$file_name') ON CONFLICT (file_name) DO NOTHING;"
applied_count=$((applied_count + 1))
done done
echo "마이그레이션 실행 완료" echo "마이그레이션 실행 완료"
} }
load_env_file
load_db_env_from_container
ensure_database_ready ensure_database_ready
load_db_env_from_container
case "$MODE" in case "$MODE" in
status) status)