diff --git a/README.md b/README.md index 868eaa0..ab0fd3b 100644 --- a/README.md +++ b/README.md @@ -58,10 +58,19 @@ webhook 누락 시 `docker compose exec runtime pnpm sync:image`로 현재 이 branch의 fast-forward를 요청할 때만 사용합니다. 두 파일은 runtime에 read-only로 mount되며 원문은 환경 변수나 브라우저 bundle에 들어가지 않습니다. +`IMAGE_PUBLIC_URL`의 기본값은 `https://sam-image.hided.net`입니다. runtime은 +이를 Gateway·game frontend의 공개 이미지 origin과 사용자 아이콘 URL에 함께 +주입합니다. 정적 게임 이미지는 `/game`, 공용 아이콘은 `/icons`, Core2026 +사용자 아이콘은 서버 발급 경로 `users/core2026/<파일>`을 `/icons` base 아래에 +붙여 읽습니다. 업로드 권한값은 +URL query나 `VITE_*`가 아니라 `IMAGE_UPLOAD_CORE2026_SECRET_FILE`이 가리키는 +mode 0600 secret 파일에만 둡니다. + ## 데이터와 복구 경계 -- PostgreSQL, Redis, Core clone/worktree, PM2 상태, Caddy 인증서와 user icon은 - named volume 또는 `data/image`에 보존됩니다. +- PostgreSQL, Redis, Core clone/worktree, PM2 상태와 Caddy 인증서는 named + volume에 보존됩니다. 새 user icon은 외부 이미지 서비스가 보존하며 runtime의 + user icon volume은 기존 설치 호환용입니다. - `/image/*`는 앱 artifact가 아니며 `data/image/`의 별도 운영 자산을 제공합니다. - 일반 `docker compose down`은 volume을 보존합니다. `down --volumes`는 DB와 release 상태를 삭제하는 파괴적 명령이므로 backup 없이 실행하지 않습니다. diff --git a/compose.yaml b/compose.yaml index 8917125..1fd7133 100644 --- a/compose.yaml +++ b/compose.yaml @@ -72,7 +72,7 @@ services: KAKAO_REDIRECT_URI: ${PUBLIC_SCHEME:-https}://${DOMAIN:?set DOMAIN in .env}/gateway/oauth/callback GATEWAY_PUBLIC_URL: ${PUBLIC_SCHEME:-https}://${DOMAIN:?set DOMAIN in .env}/gateway/ GATEWAY_USER_ICON_DIR: /srv/data/user-icons - GATEWAY_USER_ICON_PUBLIC_URL: ${PUBLIC_SCHEME:-https}://${DOMAIN:?set DOMAIN in .env}/gateway/api/user-icons + GATEWAY_USER_ICON_PUBLIC_URL: ${IMAGE_PUBLIC_URL:-https://sam-image.hided.net}/icons GATEWAY_IMAGE_UPLOAD_URL: ${IMAGE_SERVICE_URL:-https://sam-image.hided.net} GATEWAY_IMAGE_UPLOAD_SECRET_FILE: /run/secrets/image_upload_core2026_secret GATEWAY_SHARED_ICON_PUBLIC_URL: ${IMAGE_PUBLIC_URL:-https://sam-image.hided.net}/icons @@ -100,7 +100,8 @@ services: VITE_GATEWAY_API_URL: /gateway/api/trpc VITE_GAME_API_URL_TEMPLATE: /{profile}/api/trpc VITE_GAME_WEB_URL_TEMPLATE: /{profile}/ - VITE_GATEWAY_USER_ICON_BASE_URL: /gateway/api/user-icons + VITE_IMAGE_PUBLIC_URL: ${IMAGE_PUBLIC_URL:-https://sam-image.hided.net} + VITE_GATEWAY_USER_ICON_BASE_URL: ${IMAGE_PUBLIC_URL:-https://sam-image.hided.net}/icons VITE_PREVIEW_ALLOWED_HOSTS: ${DOMAIN:?set DOMAIN in .env} PM2_HOME: /srv/data/pm2 TZ: ${TZ:-Asia/Seoul} diff --git a/runtime/validate-compose-model.mjs b/runtime/validate-compose-model.mjs index d02f2e7..51246ec 100644 --- a/runtime/validate-compose-model.mjs +++ b/runtime/validate-compose-model.mjs @@ -22,9 +22,12 @@ export const validateComposeModel = (model, mode) => { for (const key of [ 'GATEWAY_IMAGE_UPLOAD_URL', 'GATEWAY_SHARED_ICON_PUBLIC_URL', + 'GATEWAY_USER_ICON_PUBLIC_URL', 'GAME_IMAGE_UPLOAD_URL', 'GAME_CONTENT_IMAGE_PUBLIC_URL', 'IMAGE_SYNC_URL', + 'VITE_IMAGE_PUBLIC_URL', + 'VITE_GATEWAY_USER_ICON_BASE_URL', ]) { if (!runtime.environment?.[key]) errors.push(`${key} must be configured`); } diff --git a/test/validate-compose-model.test.mjs b/test/validate-compose-model.test.mjs index a24fe78..7a4016e 100644 --- a/test/validate-compose-model.test.mjs +++ b/test/validate-compose-model.test.mjs @@ -11,11 +11,14 @@ const safeRuntime = { GATEWAY_IMAGE_UPLOAD_URL: 'https://sam-image.hided.net', GATEWAY_IMAGE_UPLOAD_SECRET_FILE: '/run/secrets/image_upload_core2026_secret', GATEWAY_SHARED_ICON_PUBLIC_URL: 'https://sam-image.hided.net/icons', + GATEWAY_USER_ICON_PUBLIC_URL: 'https://sam-image.hided.net/icons', GAME_IMAGE_UPLOAD_URL: 'https://sam-image.hided.net', GAME_IMAGE_UPLOAD_SECRET_FILE: '/run/secrets/image_upload_core2026_secret', GAME_CONTENT_IMAGE_PUBLIC_URL: 'https://sam-image.hided.net/uploads/core2026', IMAGE_SYNC_URL: 'https://sam-image.hided.net', IMAGE_SYNC_SECRET_FILE: '/run/secrets/image_sync_core2026_secret', + VITE_IMAGE_PUBLIC_URL: 'https://sam-image.hided.net', + VITE_GATEWAY_USER_ICON_BASE_URL: 'https://sam-image.hided.net/icons', }, volumes: [ {