diff --git a/docs/deploy-checklist.md b/docs/deploy-checklist.md index 1ac8994..03542c7 100644 --- a/docs/deploy-checklist.md +++ b/docs/deploy-checklist.md @@ -37,7 +37,7 @@ Phase 1 **A~C** 이후 실행 트랙. 작업 단위를 하나씩 처리한다. - 앱 코드는 클로즈드 베타 직전 수준 - **`https://msn.iykyka.com` 라이브 + N3 완료 + Gemini 실초안 OK + Track A/B 완료** -- **웹 고도화:** [`web-upgrade.md`](./web-upgrade.md) — W0~W3·W5~W7 **done** · **W4 code shipped** (live: Master `.env`에 FIREBASE_*/VAPID 후 `web` 재빌드) +- **웹 고도화:** [`web-upgrade.md`](./web-upgrade.md) — W0~W3·W5~W7 **done** · **W4 live injected** (SW populated · accept: Web `:web:` APP_ID + push-test) - 후순위: **N4 Android FCM/UI** — Master 시크릿·실기기 - UI: **iMessage-inspired light default** + soft charcoal dark 프로덕션 반영 - **Track C 콘텐츠 갭 A~F**: 프로덕션 반영 완료 (`95422bb`대) @@ -217,7 +217,7 @@ N2-A 전체 확정. 다음 구현 트랙은 **N1 스모크 → N2-B (Dockerfile/ | **N4-W1** | 웹 로컬 지속성 (prefs/idb) | **done** (2026-08-04) | `app_database_web.dart` SharedPreferences · Drift WASM 비채택 · 라이브 하드 리프레시 스모크는 배포 후 | | **N4-W2** | 데모 메신저 UX | **done** (2026-08-04) | ≥900px 분할·WS 상태·ID 복사·웹 안내 배너 | | **N4-W3** | 인앱·Notification API | **done** (2026-08-04) | 웹 Timer+Notification · 포커스 past-due · 설정 권한 | -| **N4-W4** | Web Push (FCM + VAPID) | **code** (2026-08-04) | dart-define+SW shipped · **live**는 Master `.env` FIREBASE_*/VAPID + `web` rebuild | +| **N4-W4** | Web Push (FCM + VAPID) | **live-injected** (2026-08-04) | 서버 `.env` 동기·SW populated · **accept:** APP_ID를 `:web:`로 교체 후 push-test | | **N4-W5** | PWA | **done** (2026-08-04) | manifest 테마 · nginx no-cache · OfflineBanner | | **N4-W6** | 테스터 가이드 | **done** (2026-08-04) | `tester-guide.md` 웹 절 | | **N4-W7** | 품질 | **done** (2026-08-04) | `scripts/e2e_web_smoke.sh` + 부록 QA (Playwright는 후속) | @@ -290,8 +290,8 @@ N1~N4(배포·품질에 필요한 최소분) 이후에만 착수. `roadmap.md` P 확인 필요, 그때까지는 인앱 배지·배너가 실질적 대체 경로. Track C A~F 프로덕션 재배포는 **완료** (`95422bb` / docs `f363e8e`, 2026-08-03). -**바로 다음:** 웹 **W4 live** — Master가 Firebase Web config + VAPID를 서버 `.env`에 넣고 `docker compose up -d --build web` · 또는 Android N4-1/3. -웹 W0~W7 코드 경로 **done** (W4 live accept만 시크릿 대기). +**바로 다음:** 웹 **W4 accept** — Firebase Console에서 **Web** 앱(`:web:` APP_ID) 확인·`.env` 교체·`web` 재빌드·하드 리프레시·`/admin/push-test` · 또는 Android N4-1/3. +웹 W0~W7 코드·시크릿 주입 **done** (W4 browser token accept만 남음). 완료 시 본 표의 Status를 `done`으로 바꾸고, [`roadmap.md`](./roadmap.md) §4/§5의 대응 `[~]`/`[ ]`도 같이 갱신한다. diff --git a/docs/fcm-setup.md b/docs/fcm-setup.md index d8e2d01..22471c6 100644 --- a/docs/fcm-setup.md +++ b/docs/fcm-setup.md @@ -85,9 +85,11 @@ Android N4(실기기)와 **병행 가능**. 서버 서비스 계정(HTTP v1)은 ### Master 준비물 (live accept) -1. Firebase Console → **Web** 앱 추가 (authorized domain: `msn.iykyka.com`) — SA 프로젝트와 동일 +1. Firebase Console → **Web** 앱 추가 (authorized domain: `msn.iykyka.com`) — SA 프로젝트와 동일 + - `FIREBASE_APP_ID`는 **`1:…:web:…` 형태**여야 함. Android(`:android:`) / `google-services.json`의 mobilesdk_app_id를 넣으면 웹 토큰 발급이 실패할 수 있음. 2. Cloud Messaging → Web Push certificates / **VAPID public key** -3. 배포 호스트 `~/project/ykavu/.env`에 기입 (`.env.example` 키 이름 — **값 git 금지**): +3. 배포 호스트 **`~/project/ykavu/.env`**(클라우드 에이전트 `/workspace/.env`가 아님)에 기입 + (`.env.example` 키 이름 — **값 git 금지**): ```bash FIREBASE_API_KEY= @@ -95,7 +97,7 @@ FIREBASE_AUTH_DOMAIN= # 비우면 {projectId}.firebaseapp.com FIREBASE_PROJECT_ID= FIREBASE_STORAGE_BUCKET= # 비우면 {projectId}.appspot.com FIREBASE_MESSAGING_SENDER_ID= -FIREBASE_APP_ID= +FIREBASE_APP_ID= # must be :web: app id FIREBASE_VAPID_KEY= ``` diff --git a/docs/web-upgrade.md b/docs/web-upgrade.md index 79d7bbd..29f01b8 100644 --- a/docs/web-upgrade.md +++ b/docs/web-upgrade.md @@ -255,7 +255,7 @@ flowchart LR | | | |--|--| -| **Status** | **code shipped** (2026-08-04) — **live accept**는 Master Firebase Web config + VAPID를 서버 `.env`에 넣고 `web` 재빌드 후 | +| **Status** | **live injected** (2026-08-04) — 서버 `.env`+SW populated · **accept 대기:** `FIREBASE_APP_ID`가 `:android:`로 보여 Web 앱(`:web:`) 교체 후 브라우저 토큰·push-test 확인 | | **목표** | 웹 탭이 백그라운드여도 에스컬레이션 등 서버 `notifyUser`가 **실 FCM 웹 토큰**으로 전달 | | **비범위** | 레거시 `FCM_SERVER_KEY` 신규 의존, 커스텀 푸시 프로토콜 | diff --git a/mobile/Dockerfile b/mobile/Dockerfile index ef6fbf9..a42c533 100644 --- a/mobile/Dockerfile +++ b/mobile/Dockerfile @@ -2,6 +2,9 @@ # docker build -f mobile/Dockerfile --build-arg CORE_API_BASE=https://msn.iykyka.com . # Optional Web Push (N4-W4) — pass FIREBASE_* / FIREBASE_VAPID_KEY as build-args # (never commit real values; see .env.example and docs/fcm-setup.md). +# +# Secrets are copied into ENV for the build stage only (not the final nginx image). +# The RUN line calls a script so values are not expanded into Docker build logs. ARG FLUTTER_IMAGE=ghcr.io/cirruslabs/flutter:3.32.7 FROM ${FLUTTER_IMAGE} AS build @@ -15,39 +18,20 @@ ARG FIREBASE_MESSAGING_SENDER_ID= ARG FIREBASE_APP_ID= ARG FIREBASE_VAPID_KEY= +ENV CORE_API_BASE=${CORE_API_BASE} \ + FIREBASE_API_KEY=${FIREBASE_API_KEY} \ + FIREBASE_AUTH_DOMAIN=${FIREBASE_AUTH_DOMAIN} \ + FIREBASE_PROJECT_ID=${FIREBASE_PROJECT_ID} \ + FIREBASE_STORAGE_BUCKET=${FIREBASE_STORAGE_BUCKET} \ + FIREBASE_MESSAGING_SENDER_ID=${FIREBASE_MESSAGING_SENDER_ID} \ + FIREBASE_APP_ID=${FIREBASE_APP_ID} \ + FIREBASE_VAPID_KEY=${FIREBASE_VAPID_KEY} + COPY mobile/pubspec.yaml mobile/pubspec.lock ./ RUN flutter pub get COPY mobile/ . - -# Populate firebase-messaging-sw.js when Web config is present; keep stub otherwise. -RUN set -eu; \ - if [ -n "${FIREBASE_API_KEY}" ] && [ -n "${FIREBASE_APP_ID}" ] && \ - [ -n "${FIREBASE_MESSAGING_SENDER_ID}" ] && [ -n "${FIREBASE_PROJECT_ID}" ]; then \ - AUTH_DOMAIN="${FIREBASE_AUTH_DOMAIN:-${FIREBASE_PROJECT_ID}.firebaseapp.com}"; \ - STORAGE_BUCKET="${FIREBASE_STORAGE_BUCKET:-${FIREBASE_PROJECT_ID}.appspot.com}"; \ - sed \ - -e "s|__FIREBASE_API_KEY__|${FIREBASE_API_KEY}|g" \ - -e "s|__FIREBASE_AUTH_DOMAIN__|${AUTH_DOMAIN}|g" \ - -e "s|__FIREBASE_PROJECT_ID__|${FIREBASE_PROJECT_ID}|g" \ - -e "s|__FIREBASE_STORAGE_BUCKET__|${STORAGE_BUCKET}|g" \ - -e "s|__FIREBASE_MESSAGING_SENDER_ID__|${FIREBASE_MESSAGING_SENDER_ID}|g" \ - -e "s|__FIREBASE_APP_ID__|${FIREBASE_APP_ID}|g" \ - web/firebase-messaging-sw.js.template > web/firebase-messaging-sw.js; \ - echo "firebase-messaging-sw.js: populated for project ${FIREBASE_PROJECT_ID}"; \ - else \ - echo "firebase-messaging-sw.js: stub (FIREBASE_* build-args empty)"; \ - fi - -RUN flutter build web --release \ - --dart-define=CORE_API_BASE=${CORE_API_BASE} \ - --dart-define=FIREBASE_API_KEY=${FIREBASE_API_KEY} \ - --dart-define=FIREBASE_AUTH_DOMAIN=${FIREBASE_AUTH_DOMAIN} \ - --dart-define=FIREBASE_PROJECT_ID=${FIREBASE_PROJECT_ID} \ - --dart-define=FIREBASE_STORAGE_BUCKET=${FIREBASE_STORAGE_BUCKET} \ - --dart-define=FIREBASE_MESSAGING_SENDER_ID=${FIREBASE_MESSAGING_SENDER_ID} \ - --dart-define=FIREBASE_APP_ID=${FIREBASE_APP_ID} \ - --dart-define=FIREBASE_VAPID_KEY=${FIREBASE_VAPID_KEY} +RUN chmod +x scripts/docker_build_web.sh && ./scripts/docker_build_web.sh FROM nginx:1.27-alpine COPY --from=build /app/build/web /usr/share/nginx/html diff --git a/mobile/scripts/docker_build_web.sh b/mobile/scripts/docker_build_web.sh new file mode 100755 index 0000000..a8f0a92 --- /dev/null +++ b/mobile/scripts/docker_build_web.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Runs inside the Flutter build stage. Reads FIREBASE_* from the environment +# (set via Dockerfile ENV from build-args) so secret values are not expanded +# into the Dockerfile RUN instruction / build logs. +set -euo pipefail + +CORE_API_BASE="${CORE_API_BASE:-https://msn.iykyka.com}" +FIREBASE_API_KEY="${FIREBASE_API_KEY:-}" +FIREBASE_AUTH_DOMAIN="${FIREBASE_AUTH_DOMAIN:-}" +FIREBASE_PROJECT_ID="${FIREBASE_PROJECT_ID:-}" +FIREBASE_STORAGE_BUCKET="${FIREBASE_STORAGE_BUCKET:-}" +FIREBASE_MESSAGING_SENDER_ID="${FIREBASE_MESSAGING_SENDER_ID:-}" +FIREBASE_APP_ID="${FIREBASE_APP_ID:-}" +FIREBASE_VAPID_KEY="${FIREBASE_VAPID_KEY:-}" + +if [[ -n "${FIREBASE_API_KEY}" && -n "${FIREBASE_APP_ID}" && \ + -n "${FIREBASE_MESSAGING_SENDER_ID}" && -n "${FIREBASE_PROJECT_ID}" ]]; then + AUTH_DOMAIN="${FIREBASE_AUTH_DOMAIN:-${FIREBASE_PROJECT_ID}.firebaseapp.com}" + STORAGE_BUCKET="${FIREBASE_STORAGE_BUCKET:-${FIREBASE_PROJECT_ID}.appspot.com}" + # Detect common misconfiguration: Android app id used for web build. + if [[ "${FIREBASE_APP_ID}" == *":android:"* ]]; then + echo "WARN: FIREBASE_APP_ID looks like an Android app id (:android:)." + echo "WARN: Web Push needs a Firebase Web app id (:web:). Token registration may fail." + fi + sed \ + -e "s|__FIREBASE_API_KEY__|${FIREBASE_API_KEY}|g" \ + -e "s|__FIREBASE_AUTH_DOMAIN__|${AUTH_DOMAIN}|g" \ + -e "s|__FIREBASE_PROJECT_ID__|${FIREBASE_PROJECT_ID}|g" \ + -e "s|__FIREBASE_STORAGE_BUCKET__|${STORAGE_BUCKET}|g" \ + -e "s|__FIREBASE_MESSAGING_SENDER_ID__|${FIREBASE_MESSAGING_SENDER_ID}|g" \ + -e "s|__FIREBASE_APP_ID__|${FIREBASE_APP_ID}|g" \ + web/firebase-messaging-sw.js.template > web/firebase-messaging-sw.js + echo "firebase-messaging-sw.js: populated for project ${FIREBASE_PROJECT_ID}" +else + echo "firebase-messaging-sw.js: stub (FIREBASE_* build-args empty)" +fi + +flutter build web --release \ + --dart-define="CORE_API_BASE=${CORE_API_BASE}" \ + --dart-define="FIREBASE_API_KEY=${FIREBASE_API_KEY}" \ + --dart-define="FIREBASE_AUTH_DOMAIN=${FIREBASE_AUTH_DOMAIN}" \ + --dart-define="FIREBASE_PROJECT_ID=${FIREBASE_PROJECT_ID}" \ + --dart-define="FIREBASE_STORAGE_BUCKET=${FIREBASE_STORAGE_BUCKET}" \ + --dart-define="FIREBASE_MESSAGING_SENDER_ID=${FIREBASE_MESSAGING_SENDER_ID}" \ + --dart-define="FIREBASE_APP_ID=${FIREBASE_APP_ID}" \ + --dart-define="FIREBASE_VAPID_KEY=${FIREBASE_VAPID_KEY}"