v1.12.26 운영 Compose 식별 통일 및 Docker 앱 배포 안내 정리

This commit is contained in:
2026-10-21 17:23:03 +09:00
parent 9e2fec6d6c
commit 345de36bd1
14 changed files with 122 additions and 40 deletions

View File

@@ -52,5 +52,6 @@ NUXT_PUBLIC_SITE_TITLE=sori.studio
# EMAIL_OTP_PEPPER= ← 선택. OTP를 DB에 해시해 저장할 때 섞는 서버 전용 비밀(긴 난문자열 권장, 예: openssl rand -hex 32). 비우면 MEMBER_SESSION_SECRET을 대신 사용.
# Server
COMPOSE_PROJECT_NAME=sori-studio
APP_PORT=43080
DOCKER_SUBNET=10.250.50.0/24

View File

@@ -1,3 +1,5 @@
name: ${COMPOSE_PROJECT_NAME:-sori-studio}
services:
sori-studio:
image: sori-studio:latest

View File

@@ -42,9 +42,9 @@ sudo docker volume ls | grep -i sori
운영 볼륨이 이미 존재하지 않는 완전 신규 설치에서는 먼저 Docker 앱에서 `soristudio_sori-studio-postgres` 이름의 빈 PostgreSQL 볼륨을 만들거나 `sudo docker volume create soristudio_sori-studio-postgres`를 실행한 뒤 배포한다.
```bash
sh scripts/migrate-production-db.sh status
sh scripts/migrate-production-db.sh migrate
sh scripts/migrate-production-db.sh status
sudo sh scripts/migrate-production-db.sh status
sudo sh scripts/migrate-production-db.sh migrate
sudo sh scripts/migrate-production-db.sh status
```
- `068_csp_violation_aggregates.sql`은 CSP 보고 집계 테이블을 만든다.
@@ -192,25 +192,52 @@ cp .env.example .env.production
# Docker 내부 앱에서 PostgreSQL에 접근할 때는 sori-studio-db:5432 사용
# Docker CLI 빌드 및 실행
docker compose --env-file .env.production up -d --build
sudo docker compose -p sori-studio -f docker-compose.yml --env-file .env.production up -d --build
# UGREEN NAS Docker 앱처럼 --env-file 옵션을 넣기 어려운 GUI 배포에서는
# docker-compose.yml과 .env.production을 같은 프로젝트 위치에 두고 Compose 빌드를 실행한다.
# 앱과 DB 컨테이너는 env_file 설정으로 .env.production을 직접 읽는다.
# 운영 DB 마이그레이션 상태 확인
sh scripts/migrate-production-db.sh status
sudo sh scripts/migrate-production-db.sh status
# schema_migrations 도입 전 운영 DB가 이미 최신이면 최초 1회 기준점 기록(실제 SQL 실행 없음)
sh scripts/migrate-production-db.sh baseline
sudo sh scripts/migrate-production-db.sh baseline
# 이후 배포에서는 아직 적용되지 않은 SQL만 순서대로 실행
sh scripts/migrate-production-db.sh migrate
sudo sh scripts/migrate-production-db.sh migrate
```
### 운영 업데이트 (코드 반영)
### Compose 프로젝트명과 네트워크 일치 (v1.12.26)
이미 한 번 올려 둔 NAS에서 **새 커밋을 받아 반영**할 때는 보통 아래 순서를 따른다. 최초 설치 절차와 달리 `git clone`은 하지 않는다.
운영 기본 프로젝트명은 `sori-studio`다. `docker-compose.yml`의 최상위 `name`은 `${COMPOSE_PROJECT_NAME:-sori-studio}`를 사용하고 `.env.production`에도 `COMPOSE_PROJECT_NAME=sori-studio`를 설정한다. CLI `-p`가 환경 변수보다 우선하므로 아래 NAS 명령은 기존 운영 프로젝트를 명시한다. DB 볼륨 이름은 프로젝트명과 독립적으로 기존 `POSTGRES_VOLUME_NAME`/외부 볼륨 설정을 유지한다.
`Pool overlaps`와 `soristudio_sori-studio-network` 생성 실패가 함께 나오고 기존 `sori-studio_sori-studio-network`가 같은 대역을 쓰고 있다면 프로젝트명 불일치다. 기존 네트워크 삭제나 다른 대역 변경 전에 프로젝트명을 맞춘다. `docker-compose.yml`과 `docker-compose.yaml`이 둘 다 있어도 `-f docker-compose.yml`로 저장소 파일을 명시한다. 운영 마이그레이션·썸네일 백필 스크립트도 같은 파일을 명시한다.
이미 빌드가 성공한 직후 네트워크 생성만 실패했다면 다음 명령으로 해당 이미지를 실행한다.
```bash
sudo docker compose -p sori-studio -f docker-compose.yml --env-file .env.production up -d --no-build --pull never
sudo docker compose -p sori-studio -f docker-compose.yml --env-file .env.production ps
```
### 운영 업데이트 (Docker 앱 우선)
기본 운영 경로는 터미널에서 소스만 `git pull`하고, UGREEN Docker 앱의 기존 `sori-studio` 프로젝트에서 재배포하는 방식이다. 같은 Docker 엔진을 쓰므로 GUI와 CLI 자체가 충돌하는 것은 아니며, 프로젝트 이름과 구성 파일이 다를 때 별도 네트워크 생성 충돌이 발생한다.
1. 프로젝트의 실제 빌드 폴더에서 `git pull`한다. GUI 프로젝트의 구성 파일 경로·빌드 context가 이 폴더를 가리켜야 한다.
2. Docker 앱 → `프로젝트` → 기존 `sori-studio` → `Compose 구성`을 연다.
3. 앱이 관리하는 구성을 저장소의 최신 `docker-compose.yml`과 맞춘다. `.yaml`과 `.yml`이 함께 있는 NAS에서는 앱의 구성 파일 경로가 어느 파일인지 확인한다. 앱이 별도 사본을 저장하면 최신 `.yml` 내용을 Compose 편집기에 반영하고 저장한다. 기존 운영 `.env.production`은 유지한다.
4. 같은 화면의 `재배포`를 실행한다. `image: sori-studio:latest`, `build.context: .`, `pull_policy: build`가 있어야 소스 기반 재빌드를 요청한다. 컨테이너 시작/중지 토글은 코드 빌드를 수행하는 배포 절차와 다르다.
5. 배포 로그에서 Dockerfile의 `npm run build` 단계가 실행됐는지와 새 컨테이너의 실행 상태를 확인한다. GUI 버전별 실제 재빌드 동작은 NAS에서 확인해야 한다. Docker 앱이 빌드 정책을 무시하면 아래 CLI 경로를 사용한다.
v1.12.24~26에는 새 DB 마이그레이션 파일이 없으므로 이 버전 범위만 업데이트할 때는 DB 마이그레이션 실행이 필요하지 않다. 이후 `db/migrations`가 변경되는 배포에서는 아래 스크립트로 미적용 SQL을 처리한다. 운영자 요청에 따라 소스 동기화 이후 앱 재배포를 우선하며 GUI에 없는 자동 Git 동기화·자동 마이그레이션을 가정하지 않는다.
근거: [UGREEN Docker/Compose 안내](https://ai.ugreen.com/blogs/knowledge/docker-docker-compose-ugreen-nas), [Compose build 정책](https://docs.docker.com/reference/compose-file/services/#pull_policy).
### 운영 업데이트 (CLI 보조 경로)
Docker 앱 대신 CLI로 배포해야 할 때는 아래처럼 기존 프로젝트명·Compose 파일을 명시한다. 최초 설치 절차와 달리 `git clone`은 하지 않는다.
```bash
# 프로젝트 루트로 이동 (경로는 NAS 환경에 맞게 조정)
@@ -220,11 +247,11 @@ cd /volume1/docker/projects/apps/sori.studio
git pull
# DB 스키마 변경이 포함된 배포면 미적용 SQL만 적용 (npm 없이 실행 가능)
sh scripts/migrate-production-db.sh status
sh scripts/migrate-production-db.sh migrate
sudo sh scripts/migrate-production-db.sh status
sudo sh scripts/migrate-production-db.sh migrate
# 앱 이미지 재빌드 후 컨테이너 재기동
docker compose --env-file .env.production up -d --build
sudo docker compose -p sori-studio -f docker-compose.yml --env-file .env.production up -d --build
```
| 단계 | 설명 |
@@ -238,12 +265,12 @@ docker compose --env-file .env.production up -d --build
- `.env.production`은 Git에 포함하지 않는다. `git pull`로 덮어쓰이지 않는다. 값을 바꿀 때만 파일을 직접 수정한다.
- `public/uploads/` 업로드 파일은 Docker 볼륨(`./public/uploads`)에 있으므로, **이미지 파일만 추가·수정한 경우** 앱 재빌드 없이도 URL로 바로 보인다.
- 로컬에서 미리 확인하려면 `npm run verify` 후 NAS에서 위 명령을 실행하면 된다.
- UGREEN Docker 앱의 재배포가 너무 빨리 끝나고 앱 빌드 로그가 보이지 않으면 기존 이미지를 재사용한 것이다. 현재 Compose는 `sori-studio:latest`와 `pull_policy: build`로 앱 이미지를 다시 빌드하도록 선언하지만, NAS Docker 앱 버전이 이 값을 무시하면 터미널에서 `docker compose --env-file .env.production up -d --build`를 실행해야 한다.
- UGREEN Docker 앱의 재배포가 너무 빨리 끝나고 앱 빌드 로그가 보이지 않으면 기존 이미지를 재사용한 것이다. 현재 Compose는 `sori-studio:latest`와 `pull_policy: build`로 앱 이미지를 다시 빌드하도록 선언하지만, NAS Docker 앱 버전이 이 값을 무시하면 터미널에서 `sudo docker compose -p sori-studio -f docker-compose.yml --env-file .env.production up -d --build`를 실행해야 한다.
컨테이너만 재시작하고 이미지는 그대로 두려면(환경 변수만 바꾼 경우 등):
```bash
docker compose --env-file .env.production up -d
sudo docker compose -p sori-studio -f docker-compose.yml --env-file .env.production up -d --no-build
```
### UGREEN NAS Docker 앱 Buildx 확인
@@ -272,7 +299,7 @@ sudo ln -sf /usr/local/lib/docker/cli-plugins/docker-buildx /usr/lib/docker/cli-
sudo docker buildx version
```
코드 변경 없이 `.env.production`만 수정했다면 `--build` 없이 `up -d`만으로 충분하다.
코드 변경 없이 `.env.production`만 수정했다면 GUI 프로젝트 재배포 또는 CLI `up -d --no-build`를 사용한다. `pull_policy: build`가 있으므로 CLI에서 재빌드를 생략하려면 `--no-build`를 명시한다.
### Google 로그인 설정
@@ -311,7 +338,7 @@ NAS에 Docker 컨테이너가 많이 실행 중이면 `could not find an availab
```bash
DOCKER_SUBNET=10.250.51.0/24
docker compose --env-file .env.production up -d --build
sudo docker compose -p sori-studio -f docker-compose.yml --env-file .env.production up -d --build
```
### 포트
@@ -327,7 +354,7 @@ docker compose --env-file .env.production up -d --build
- `docker ps`에서 `sori-studio`, `sori-studio-db`가 `Up` 상태인지 확인한다.
- 공개 사이트와 `/admin` 로그인이 정상 동작하는지 확인한다.
- 스키마 변경 배포였다면 `sh scripts/migrate-production-db.sh status`로 미적용 마이그레이션이 없는지 확인한다.
- 스키마 변경 배포였다면 `sudo sh scripts/migrate-production-db.sh status`로 미적용 마이그레이션이 없는지 확인한다.
- `/sitemap.xml`, `/robots.txt`, `/rss.xml`이 정상 응답하는지 확인한다. Google Search Console에는 `https://도메인/sitemap.xml`을 제출한다.
---
@@ -352,9 +379,9 @@ docker compose --env-file .env.production up -d --build
- 스키마 변경은 `db/migrations/`의 SQL 파일로 관리한다. 버전별 변경 내역은 `docs/update.md`를 참고한다.
- 로컬: `npm run db:migrate:dev`
- NAS 운영: `sh scripts/migrate-production-db.sh status` → `migrate`
- NAS 운영: `sudo sh scripts/migrate-production-db.sh status` → `migrate`
- 각 SQL 파일 실행과 `schema_migrations` 이력 기록은 같은 `psql --single-transaction`에서 처리되며 오류 시 함께 롤백된다.
- `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회 `sudo sh scripts/migrate-production-db.sh baseline`으로 기존 파일을 적용 완료로 기록한다. 특정 번호까지만 기록하려면 예: `sudo sh scripts/migrate-production-db.sh baseline 031`.
### 이메일 인증(Resend, 선택)
@@ -485,8 +512,8 @@ 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
sudo docker compose -p sori-studio -f docker-compose.yml --env-file .env.production logs sori-studio --tail 200
sudo docker compose -p sori-studio -f docker-compose.yml --env-file .env.production logs sori-studio-db --tail 200
```
3. **자주 나오는 원인**
@@ -495,7 +522,7 @@ docker compose --env-file .env.production logs sori-studio-db --tail 200
- **`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` 상태인지 확인한다.
4. 로그를 고친 뒤에는 `sudo docker compose -p sori-studio -f docker-compose.yml --env-file .env.production up -d`로 다시 올리고, `docker ps`에서 `Up` 상태인지 확인한다.
### NAS·호스트에서 `db/migrations` 권한
@@ -514,7 +541,7 @@ sudo chmod a+x . db db/migrations
그다음 DB 컨테이너만 재시작한다.
```bash
docker compose --env-file .env.production restart sori-studio-db
sudo docker compose -p sori-studio -f docker-compose.yml --env-file .env.production restart sori-studio-db
```
여전히 동일하면 프로젝트가 **SMB 공유 폴더 위**에만 있지 않은지 확인한다. Docker 데몬이 네이티브 경로(ext4 등)의 디렉터리를 마운트할 때 권한이 더 예측 가능하다.
@@ -530,14 +557,14 @@ docker compose --env-file .env.production restart sori-studio-db
| `npm run images:backfill-post-thumbnails` | 로컬 Node/npm 환경에서 게시물 대표 이미지용 카드 썸네일(`thumbs/*-card.webp`) 일괄 생성 |
| `npm run images:backfill-post-body-thumbnails` | 로컬 Node/npm 환경에서 게시물 본문 표시용 WebP 이미지(`thumbs/*-body-1600.webp`, `thumbs/*-body-720.webp`, `thumbs/*-body-2560.webp`) 일괄 생성 |
| `npm run images:backfill-page-cover-thumbnails` | 로컬 Node/npm 환경에서 홈 Pages 진열장 커버용 WebP 이미지(`thumbs/*-page-cover.webp`) 일괄 생성 |
| `sh scripts/backfill-post-thumbnails-production.sh` | npm 없는 NAS 호스트에서 앱 컨테이너 내부 Node로 카드 썸네일 일괄 생성 |
| `sh scripts/backfill-post-body-thumbnails-production.sh` | npm 없는 NAS 호스트에서 앱 컨테이너 내부 Node로 본문 표시용 WebP 이미지 일괄 생성 |
| `sh scripts/backfill-page-cover-thumbnails-production.sh` | npm 없는 NAS 호스트에서 앱 컨테이너 내부 Node로 Pages 커버 썸네일 일괄 생성 |
| `sudo sh scripts/backfill-post-thumbnails-production.sh` | npm 없는 NAS 호스트에서 앱 컨테이너 내부 Node로 카드 썸네일 일괄 생성 |
| `sudo sh scripts/backfill-post-body-thumbnails-production.sh` | npm 없는 NAS 호스트에서 앱 컨테이너 내부 Node로 본문 표시용 WebP 이미지 일괄 생성 |
| `sudo sh scripts/backfill-page-cover-thumbnails-production.sh` | npm 없는 NAS 호스트에서 앱 컨테이너 내부 Node로 Pages 커버 썸네일 일괄 생성 |
| `npm run navigation:refresh-recommended-favicons` | 추천 사이트 파비콘 캐시를 수동으로 전체 갱신 |
본문 표시용 WebP 이미지(`thumbs/*-body-1600.webp`, `thumbs/*-body-720.webp`, `thumbs/*-body-2560.webp`)는 게시물 저장 시 생성된다. 기존 게시물의 과거 이미지는 로컬에서는 `npm run images:backfill-post-body-thumbnails`, NAS 운영에서는 `sh scripts/backfill-post-body-thumbnails-production.sh`로 일괄 생성할 수 있다. production 래퍼는 `docker compose up -d sori-studio` 후 실행 중인 앱 컨테이너 안의 Node로 백필 스크립트를 실행하므로 NAS 호스트에 npm이 없어도 된다.
본문 표시용 WebP 이미지(`thumbs/*-body-1600.webp`, `thumbs/*-body-720.webp`, `thumbs/*-body-2560.webp`)는 게시물 저장 시 생성된다. 기존 게시물의 과거 이미지는 로컬에서는 `npm run images:backfill-post-body-thumbnails`, NAS 운영에서는 `sudo sh scripts/backfill-post-body-thumbnails-production.sh`로 일괄 생성할 수 있다. production 래퍼는 `docker compose up -d sori-studio` 후 실행 중인 앱 컨테이너 안의 Node로 백필 스크립트를 실행하므로 NAS 호스트에 npm이 없어도 된다.
Pages 진열장 커버용 WebP 이미지(`thumbs/*-page-cover.webp`, 450×600)는 페이지 저장 시 생성된다. 기존 홈 노출 페이지의 과거 이미지는 로컬에서는 `npm run images:backfill-page-cover-thumbnails`, NAS 운영에서는 `sh scripts/backfill-page-cover-thumbnails-production.sh`로 일괄 생성할 수 있다.
Pages 진열장 커버용 WebP 이미지(`thumbs/*-page-cover.webp`, 450×600)는 페이지 저장 시 생성된다. 기존 홈 노출 페이지의 과거 이미지는 로컬에서는 `npm run images:backfill-page-cover-thumbnails`, NAS 운영에서는 `sudo sh scripts/backfill-page-cover-thumbnails-production.sh`로 일괄 생성할 수 있다.
Homepage 위젯 연동은 `docs/homepage-widget.md`를 참고한다. Docker에서 Homepage가 같은 네트워크에 있으면 `http://sori-studio:3000/api/homepage-widget`을 사용한다.

View File

@@ -1,3 +1,7 @@
## 2026-10-21 v1.12.26 — 운영 Compose 프로젝트명과 파일 선택을 일치시킨다
제공된 NAS 로그에서 빌드는 성공했지만 CLI가 `soristudio` 프로젝트의 네트워크를 새로 만들려다 기존 `sori-studio` 프로젝트의 동일 대역과 충돌했다. 기존 운영 네트워크를 재사용하도록 Compose 기본 이름을 환경 변수 기반 `sori-studio`로 맞추고, 배포 안내에는 프로젝트명을 명시한다. `.yaml`과 `.yml` 중복 경고를 줄이고 운영 스크립트도 같은 구성을 사용하도록 파일 경로를 명시한다. DB 외부 볼륨의 이름과 보관 방식은 유지한다. 사용자가 Docker 앱 배포를 선호하므로 소스 git pull 후 기존 프로젝트의 Compose 구성에서 재배포하는 경로를 기본 안내로 둔다. 앱 버전의 실제 재빌드 수행 여부는 배포 로그로 확인하며, CLI는 보조 경로로 구분한다.
## 2026-10-21 v1.12.25 — 접속자 목록은 최근 페이지만 요약한다
한 방문자가 여러 탭을 열면 접속자 행 높이가 크게 늘어나므로 이름·가장 최근 확인된 페이지·나머지 고유 페이지 수·체류·IP를 한 줄로 표시한다. 상세 목록은 hover뿐 아니라 키보드와 클릭으로도 접근할 수 있게 하며, heartbeat의 마지막 확인 순서를 사용한다. 같은 경로 중복은 표시 목록에서 제거해 `외 N개`와 펼친 목록의 수를 일치시킨다. 기존 API 집계 기준은 유지하고, 자주 확인할 수입·추이·상세·댓글을 접속자 목록 위에 배치한다.

View File

@@ -637,7 +637,7 @@
| Dockerfile | NAS 운영 이미지 빌드, `.output/server` 기반 앱 런타임과 이미지 백필 스크립트 포함 |
| tests/markdown-html-block.test.js | 게시물 HTML sanitizer 공격 우회·허용 HTML 회귀 테스트 |
| tests/multipart-file-upload.test.js | 다중 multipart 디스크 스트리밍·파일 크기 제한 회귀 테스트 |
| docker-compose.yml | NAS 컨테이너 실행 초안, PostgreSQL 호스트 기본 loopback 바인딩 |
| docker-compose.yml | 운영 프로젝트명 환경 변수/기본 sori-studio 선언, NAS 컨테이너 실행, PostgreSQL 호스트 기본 loopback 바인딩 |
| components/admin/AdminCommentList.vue | 대시보드 최신 댓글 5개 작은 목록·게시글/작성자/시각/한 줄 내용·30초 갱신·조회 오류 재시도 |
| server/routes/admin/api/comments.get.js | 관리자 댓글 조회 인증·댓글 내용/게시물 제목 검색·Zod query 검증 |
| server/repositories/comment-repository.js | 공개 댓글 조회·작성·좋아요와 관리자 댓글 최신순 페이지 조회 |
@@ -650,3 +650,4 @@
| components/admin/AdminRealtimePages.vue | 접속자 최근 페이지 요약·화면 안 팝오버 위치·열린 페이지 링크·hover/클릭/키보드 접근 |
| tests/analytics-display.test.js | 천 단위 표시·최근순 중복 제거·공개 경로 검증 |
| tests/playwright/e2e/admin-dashboard.spec.js | 대시보드 쉼표·섹션 순서·한 줄 접속자·페이지 목록 접근 검증 |
| tests/production-compose.test.js | 운영 마이그레이션·썸네일 백필 스크립트의 Compose 파일·환경 파일 전달 격리 검증 |

View File

@@ -1114,3 +1114,9 @@ APP_PORT=43080
- 방문자·게시물 수·조회·스크롤·유입·디바이스·차트 수치·댓글/작성 이력 건수는 `ko-KR` 천 단위 구분(예: `10,413`)으로 표시. API 숫자 형식은 유지.
- 접속자 대표 페이지는 포커스 여부가 아닌 마지막 확인 시각 기준. 현재 API의 회원별·익명 IP별 묶음은 유지. 인원 표시는 반환된 목록 수와 전체 접속자 수를 구분.
- 열린 페이지 팝오버는 hover 시 일시 표시, 클릭/Enter로 고정 전환. 포인터를 목록으로 이동해도 링크 접근을 유지하며, 화면 범위에 맞춰 위/아래 배치하고 긴 목록은 내부 스크롤 제공.
## 운영 Compose 식별 (v1.12.26)
- 기본 프로젝트명은 `COMPOSE_PROJECT_NAME` 또는 `sori-studio`. Compose 최상위 `name`으로 선언하고 운영 명령은 `-p sori-studio -f docker-compose.yml` 사용.
- 운영 마이그레이션·썸네일 백필 스크립트는 루트의 `docker-compose.yml`을 명시적으로 선택한다. 환경 파일 선택 및 DB 외부 볼륨 설정은 유지.
- 운영 업데이트 기본 경로는 Git 소스 동기화 후 UGREEN Docker 앱의 기존 `sori-studio` 프로젝트에서 Compose 구성 확인·재배포. CLI는 보조 경로이며 GUI의 별도 Compose 사본과 원본 `.yml` 차이를 확인한다.

View File

@@ -1,5 +1,14 @@
# 업데이트 이력
## v1.12.26
- 운영 Compose 기본 프로젝트명을 기존 sori-studio로 고정해 중복 네트워크 생성 충돌 보정.
- 마이그레이션·썸네일 백필 운영 스크립트의 docker-compose.yml 파일 지정 추가.
- 환경 변수 예시와 sudo·프로젝트명·Compose 파일 지정 기반 운영 배포 명령 정리.
- git pull 후 UGREEN 기존 프로젝트의 Compose 구성·재배포를 사용하는 GUI 우선 절차 정리.
- 운영 스크립트 Compose 파일·환경 파일 전달을 실제 Docker 대신 대체 실행기로 검증 추가.
- Compose 기본/사용자 지정 프로젝트명·기존 외부 DB 볼륨·셸 문법·JavaScript 문법·단위 테스트 280건 검증 진행.
## v1.12.25
- 대시보드 요약·통계·차트·목록 건수에 천 단위 쉼표 표시 적용.

4
package-lock.json generated
View File

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

View File

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

View File

@@ -28,11 +28,11 @@ pick_compose_env_file() {
compose() {
if [ -n "$COMPOSE_ENV_FILE" ]; then
docker compose --env-file "$COMPOSE_ENV_FILE" "$@"
docker compose -f "$ROOT_DIR/docker-compose.yml" --env-file "$COMPOSE_ENV_FILE" "$@"
return 0
fi
docker compose "$@"
docker compose -f "$ROOT_DIR/docker-compose.yml" "$@"
}
pick_compose_env_file

View File

@@ -28,11 +28,11 @@ pick_compose_env_file() {
compose() {
if [ -n "$COMPOSE_ENV_FILE" ]; then
docker compose --env-file "$COMPOSE_ENV_FILE" "$@"
docker compose -f "$ROOT_DIR/docker-compose.yml" --env-file "$COMPOSE_ENV_FILE" "$@"
return 0
fi
docker compose "$@"
docker compose -f "$ROOT_DIR/docker-compose.yml" "$@"
}
pick_compose_env_file

View File

@@ -28,11 +28,11 @@ pick_compose_env_file() {
compose() {
if [ -n "$COMPOSE_ENV_FILE" ]; then
docker compose --env-file "$COMPOSE_ENV_FILE" "$@"
docker compose -f "$ROOT_DIR/docker-compose.yml" --env-file "$COMPOSE_ENV_FILE" "$@"
return 0
fi
docker compose "$@"
docker compose -f "$ROOT_DIR/docker-compose.yml" "$@"
}
pick_compose_env_file

View File

@@ -52,11 +52,11 @@ load_env_file() {
compose() {
if [ -n "$COMPOSE_ENV_FILE" ]; then
docker compose --env-file "$COMPOSE_ENV_FILE" "$@"
docker compose -f "$ROOT_DIR/docker-compose.yml" --env-file "$COMPOSE_ENV_FILE" "$@"
return 0
fi
docker compose "$@"
docker compose -f "$ROOT_DIR/docker-compose.yml" "$@"
}
# 실행 중인 DB 컨테이너에서 PostgreSQL 계정 정보 읽기

View File

@@ -0,0 +1,32 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import { mkdtempSync, writeFileSync, readFileSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join, relative, resolve } from 'node:path'
import { spawnSync } from 'node:child_process'
const root = resolve('.')
for (const script of ['migrate-production-db.sh', 'backfill-post-thumbnails-production.sh', 'backfill-post-body-thumbnails-production.sh', 'backfill-page-cover-thumbnails-production.sh']) {
test(`운영 ${script}는 명시한 Compose 파일·환경 파일만 사용한다`, () => {
const directory = mkdtempSync(join(tmpdir(), 'sori-compose-test-'))
try {
const logPath = join(directory, 'docker.log')
const envPath = join(directory, 'production.env')
writeFileSync(envPath, 'COMPOSE_PROJECT_NAME=sori-studio\n')
writeFileSync(join(directory, 'docker'), '#!/bin/sh\nprintf "<%s>" "$@" >> "$SORI_TEST_DOCKER_LOG"\nprintf "\\n" >> "$SORI_TEST_DOCKER_LOG"\n', { mode: 0o755 })
const result = spawnSync('sh', [`scripts/${script}`, 'status'], {
cwd: root, encoding: 'utf8',
env: { ...process.env, PATH: `${directory}:${process.env.PATH}`, ENV_FILE: relative(root, envPath), SORI_TEST_DOCKER_LOG: logPath }
})
assert.equal(result.status, 0, result.stderr)
const calls = readFileSync(logPath, 'utf8').trim().split('\n')
assert.ok(calls.length > 0)
for (const call of calls) {
assert.ok(call.startsWith(`<compose><-f><${join(root, 'docker-compose.yml')}><--env-file><${relative(root, envPath)}>`))
}
} finally {
rmSync(directory, { recursive: true, force: true })
}
})
}