update: project documentation and build scripts

- Add smoke-compose script for compose validation
- Update build, lint, test scripts
- Update agent roadmap and phase documentation
- Update project README and docs
This commit is contained in:
toki 2026-05-30 19:10:05 +09:00
parent 8c07405b31
commit d7e044cf5c
10 changed files with 230 additions and 29 deletions

View file

@ -64,6 +64,8 @@ docker compose up
| `flutter-test` 호스트 직접 실행 | `cd apps/flutter-test && flutter run` | Flutter SDK가 필요하다. |
| 플러그인 직접 테스트 | `cd packages/messaging_flutter && flutter test` | Flutter SDK가 필요하다. |
| Android 네이티브 단위 테스트 실행 | `cd apps/flutter-test/android && ./gradlew testDebugUnitTest` | Android SDK가 필요하다. |
| Flutter SDK integration 테스트 포함 | `NEXO_FLUTTER_INTEGRATION_TEST=1 bin/test` | `apps/flutter-test`에서 `flutter test integration_test`를 실행한다. Flutter SDK가 필요하다. |
| Android native 단위 테스트 포함 | `NEXO_ANDROID_NATIVE_TEST=1 bin/test` | `apps/flutter-test/android`에서 `./gradlew testDebugUnitTest`를 실행한다. Java/Gradle/Android SDK가 필요하다. |
`bin/*` helper는 필요한 로컬 도구가 없으면 일부 검증을 건너뛸 수 있다. 작업 보고에는 건너뛴 검증을 함께 적는다.
@ -171,6 +173,8 @@ upstream 새 기능은 다음 후보로 분류한 뒤 반영한다.
| `NEXO_CORE_GO_TEST` | `bin/test`에서 서버 Go 테스트를 실행한다. | `0` |
| `NEXO_CORE_GO_LINT` | `bin/lint`에서 서버 Go vet을 실행한다. | `0` |
| `NEXO_CORE_GO_BUILD` | `bin/build`에서 서버 Go build를 실행한다. | `0` |
| `NEXO_FLUTTER_INTEGRATION_TEST` | `bin/test`에서 `apps/flutter-test` integration 테스트를 실행한다. | `0` |
| `NEXO_ANDROID_NATIVE_TEST` | `bin/test`에서 `apps/flutter-test/android` Android 네이티브 단위 테스트를 실행한다. | `0` |
## 참고 문서

View file

@ -7,7 +7,7 @@
## 활성 Milestone
- [계획] CI/CD와 운영 업데이트 루프
- [계획] 내부 검증 파이프라인과 업데이트 대응 루프
- Phase: `agent-roadmap/phase/upstream-runtime/PHASE.md`
- 경로: `agent-roadmap/phase/upstream-runtime/milestones/cicd-operations.md`

View file

@ -7,7 +7,7 @@
## 목표
Mattermost server, webapp, push-proxy를 nexo의 upstream-followable 메시징 런타임으로 유지한다.
보안/버그/기능 업데이트를 계속 흡수할 수 있도록 baseline 기록, patch 경계, CI/CD, 배포 검증, 이슈 대응 루프를 만든다.
보안/버그/기능 업데이트를 계속 흡수할 수 있도록 baseline 기록, patch 경계, 내부 검증, 이슈 대응 루프를 만든다.
## Milestone 흐름
@ -18,9 +18,9 @@ Mattermost server, webapp, push-proxy를 nexo의 upstream-followable 메시징
- [완료] 업스트림 팔로잉 기준선
- 경로: `agent-roadmap/archive/phase/upstream-runtime/milestones/upstream-following.md`
- 요약: server/webapp/push-proxy의 upstream baseline, patch 경계, update 판단 기준을 정한다.
- [계획] CI/CD와 운영 업데이트 루프
- [계획] 내부 검증 파이프라인과 업데이트 대응 루프
- 경로: `agent-roadmap/phase/upstream-runtime/milestones/cicd-operations.md`
- 요약: upstream update, compose 배포, 검증 실패 이슈화를 반복 가능한 파이프라인으로 만든다.
- 요약: upstream update와 SDK 변경을 내부 검증, compose smoke, 실패 분류 루프로 확인한다.
## Phase 경계
@ -28,4 +28,4 @@ Mattermost server, webapp, push-proxy를 nexo의 upstream-followable 메시징
- upstream 코드의 대량 rename, deep fork, 제품 UI 재작성은 별도 결정 전 진행하지 않는다.
- `services/core/webapp`은 기본 메시지 앱 또는 reference front로 유지하되, nexo embedded SDK와 책임을 섞지 않는다.
- `packages/messaging_flutter`는 nexo-owned embedded SDK이며 Mattermost mobile code를 팔로잉하는 대상이 아니다.
- nexo 변경은 compose, docs, CI/CD, patch inventory, 얇은 branding/compatibility layer에 우선 둔다.
- nexo 변경은 compose, docs, 내부 검증, patch inventory, 얇은 branding/compatibility layer에 우선 둔다.

View file

@ -1,4 +1,4 @@
# Milestone: CI/CD와 운영 업데이트 루프
# Milestone: 내부 검증 파이프라인과 업데이트 대응 루프
## 위치
@ -7,8 +7,8 @@
## 목표
Mattermost server/webapp/push-proxy update와 nexo Flutter SDK 검증을 반복 가능한 CI/CD와 운영 runbook으로 연결한다.
upstream 업데이트에 따른 이슈를 빠르게 분류하고, 수정/보류/rollback 결정을 남길 수 있는 흐름을 만든다.
Mattermost server/webapp/push-proxy update와 nexo Flutter SDK 검증을 내부 검증 파이프라인과 운영 기록 루프로 연결한다.
upstream 업데이트 또는 SDK 변경 후 깨진 지점을 빠르게 감지하고, 수정/보류/rollback 판단을 남길 수 있는 흐름을 만든다.
## 상태
@ -20,39 +20,38 @@ upstream 업데이트에 따른 이슈를 빠르게 분류하고, 수정/보류/
## 구현 잠금
- 상태: 잠금
- 상태: 해제
- 결정 필요:
- [ ] CI/CD 실행 위치를 GitHub Actions, 자체 runner, 원격 테스트 서버 중 어디에 둘지 결정한다.
- [ ] production-like 배포 대상과 secret 보관 방식을 결정한다.
- 없음
- 결정 완료:
- [x] 2026-05-27: upstream update를 쉽게 따라가기 위한 CI/CD와 이슈 대응 루프를 프로젝트 방향성에 포함한다.
- [x] 2026-05-27: upstream update를 쉽게 따라가기 위한 검증 자동화와 이슈 대응 루프를 프로젝트 방향성에 포함한다.
- [x] 2026-05-30: 현 단계에서는 외부 Jenkins, production-like 배포, secret 정책, CD 파이프라인을 요구하지 않고 내부 검증 파이프라인을 먼저 만든다.
## 범위
- workspace `bin/test`, `bin/lint`, `bin/build` 기반 기본 검증
- optional Go test/vet/build와 webapp/npm 검증
- compose smoke, server ping, webapp 접근, push-proxy smoke
- local/internal compose smoke, server ping, webapp 접근, push-proxy smoke
- Flutter SDK Dart/integration/Android native test
- upstream update 실패 이슈화, rollback, release note 흐름
- upstream update 실패 분류, 수정/보류/rollback 판단 기록, release note 입력 흐름
## 기능
### Epic: [ci] Continuous verification
### Epic: [ci] Internal verification
upstream-followable runtime과 nexo SDK를 같은 변경 흐름에서 검증한다.
- [ ] [workspace-checks] CI에서 `bin/test`, `bin/lint`, `bin/build`를 실행하고 skip된 도구를 보고하는 기준을 만든다.
- [x] [workspace-checks] 내부 검증에서 `bin/test`, `bin/lint`, `bin/build`를 실행하고 skip된 도구를 보고하는 기준을 만든다.
- [ ] [core-checks] optional Go test/vet/build와 webapp 검증을 upstream update 또는 core 변경 시 실행하는 기준을 만든다.
- [ ] [compose-smoke] compose로 core, webapp, db, push-proxy를 올리고 최소 smoke를 확인하는 CI 또는 원격 검증 흐름을 만든다.
- [ ] [compose-smoke] compose로 core, webapp, db, push-proxy를 올리고 최소 smoke를 확인하는 local/internal 검증 흐름을 만든다.
- [ ] [sdk-checks] Flutter SDK Dart test, client integration test, Android native unit test를 환경별로 나누어 실행하는 기준을 만든다.
### Epic: [cd] Deployment loop
### Epic: [cd] Update response loop
업데이트와 배포 실패가 추적 가능한 이슈로 남도록 운영 루프를 만든다.
업데이트와 검증 실패가 추적 가능한 기록으로 남도록 대응 루프를 만든다.
- [ ] [update-branch] Mattermost server/webapp/push-proxy baseline update용 branch, diff, patch inventory 절차를 정리한다.
- [ ] [deploy-pipeline] dev/test/production-like compose 배포, secret 주입, rollback 절차를 정리한다.
- [ ] [issue-loop] upstream update 실패를 server, webapp, push-proxy, SDK contract, infra 중 하나로 분류하고 후속 이슈로 남기는 절차를 만든다.
- [ ] [issue-loop] upstream update 또는 SDK 변경 실패를 server, webapp, push-proxy, SDK contract, infra 중 하나로 분류하고 후속 기록으로 남기는 절차를 만든다.
- [ ] [release-note] server/front/push-proxy baseline과 Flutter SDK compatibility를 한 번에 볼 수 있는 release note 기준을 만든다.
## 완료 리뷰
@ -67,14 +66,14 @@ upstream-followable runtime과 nexo SDK를 같은 변경 흐름에서 검증한
## 범위 제외
- production hardening 전체 구현은 배포 대상과 secret 정책 결정 전까지 진행하지 않는다.
- 외부 Jenkins, production-like 배포, secret 관리 정책, CD 파이프라인 구현은 후속 Milestone 전까지 포함하지 않는다.
- 외부 공개용 landing page나 marketing site는 포함하지 않는다.
- billing, organization management, 별도 admin console은 포함하지 않는다.
## 작업 컨텍스트
- 관련 경로: `.github/`, `bin/`, `services/core/`, `services/core/compose/`, `packages/messaging_flutter/`, `apps/flutter-test/`, `docs/`
- 표준선(선택): 먼저 private/internal deployment loop를 만들고, runner와 secret 정책이 확정되면 production-like CD로 확장한다
- 표준선: 먼저 local/internal verification loop를 만들고, 외부 runner나 CD는 배포 필요성이 생긴 뒤 별도 Milestone으로 확장한다.
- 선행 작업: 업스트림 팔로잉 기준선
- 후속 작업: 운영 runbook, release/versioning 고도화
- 확인 필요: CI/CD 실행 위치와 production-like 배포 대상
- 후속 작업: 운영 runbook, release/versioning 고도화, production-like CD 검토
- 확인 필요: 없음

View file

@ -26,3 +26,15 @@ if [ -f "$root/services/core/server/go.mod" ]; then
echo "skip services/core/server: set NEXO_CORE_GO_BUILD=1 to run the Go build"
fi
fi
if [ -f "$root/services/core/webapp/package.json" ]; then
if [ "${NEXO_CORE_WEBAPP_BUILD:-0}" = "1" ]; then
if command -v npm >/dev/null 2>&1; then
(cd "$root/services/core/webapp" && npm run build)
else
echo "skip services/core/webapp: npm not found"
fi
else
echo "skip services/core/webapp: set NEXO_CORE_WEBAPP_BUILD=1 to run the webapp npm build"
fi
fi

View file

@ -31,3 +31,15 @@ if [ -f "$root/services/core/server/go.mod" ]; then
fi
fi
if [ -f "$root/services/core/webapp/package.json" ]; then
if [ "${NEXO_CORE_WEBAPP_LINT:-0}" = "1" ]; then
if command -v npm >/dev/null 2>&1; then
(cd "$root/services/core/webapp" && npm run check)
else
echo "skip services/core/webapp: npm not found"
fi
else
echo "skip services/core/webapp: set NEXO_CORE_WEBAPP_LINT=1 to run the webapp npm check"
fi
fi

111
bin/smoke-compose Executable file
View file

@ -0,0 +1,111 @@
#!/usr/bin/env bash
#
# nexo compose smoke helper
# Checks the status of the local Docker Compose runtime and validates key services.
#
set -euo pipefail
# Print help/usage if requested
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
echo "Usage: bin/smoke-compose [options]"
echo ""
echo "Options:"
echo " -h, --help Show this help message and exit"
echo ""
echo "Checks performed:"
echo " 1. Verify presence of required commands: docker, docker compose, curl, grep"
echo " 2. Verify that Docker Compose services are running (docker compose ps)"
echo " 3. Ping the Mattermost Core API (system/ping)"
echo " 4. Check PostgreSQL database readiness (pg_isready)"
echo " 5. Request Mattermost WebApp index"
echo " 6. Verify push-proxy container status"
echo " 7. Validate that Core points to the compose push-proxy server configuration"
echo ""
echo "Environment Variables:"
echo " NEXO_CORE_PORT Core port (default: 18065)"
exit 0
fi
# 1. Check for required commands
echo "Checking required commands..."
MISSING_TOOLS=()
if ! command -v docker &>/dev/null; then
MISSING_TOOLS+=("docker")
else
if ! docker compose version &>/dev/null; then
MISSING_TOOLS+=("docker compose")
fi
fi
if ! command -v curl &>/dev/null; then
MISSING_TOOLS+=("curl")
fi
if ! command -v grep &>/dev/null; then
MISSING_TOOLS+=("grep")
fi
if [ ${#MISSING_TOOLS[@]} -ne 0 ]; then
echo "error: The following required tools are missing: ${MISSING_TOOLS[*]}" >&2
exit 1
fi
# Locate compose directory relative to this script
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
COMPOSE_DIR="$PROJECT_ROOT/services/core/compose"
if [ ! -d "$COMPOSE_DIR" ]; then
echo "error: Compose directory not found at $COMPOSE_DIR" >&2
exit 1
fi
cd "$COMPOSE_DIR"
PORT="${NEXO_CORE_PORT:-18065}"
# 2. Check running compose status
echo "Checking Docker Compose services status..."
if ! docker compose ps; then
echo "error: docker compose services are not running or failed to query" >&2
exit 1
fi
# 3. Ping core system
echo "Pinging Mattermost Core API at http://localhost:${PORT}/api/v4/system/ping..."
if ! curl -fsS "http://localhost:${PORT}/api/v4/system/ping"; then
echo "error: Core API ping failed on port ${PORT}" >&2
exit 1
fi
# 4. Check PostgreSQL database readiness
echo "Checking PostgreSQL database readiness..."
if ! docker compose exec -T db pg_isready -U mmuser -d mattermost; then
echo "error: Database is not ready" >&2
exit 1
fi
# 5. Check WebApp HTTP access
echo "Checking Mattermost WebApp access at http://localhost:${PORT}..."
if ! curl -fsSI "http://localhost:${PORT}"; then
echo "error: WebApp is not accessible on port ${PORT}" >&2
exit 1
fi
# 6. Check Push-Proxy container running status
echo "Checking push-proxy service running status..."
if ! docker compose ps --services --status running push-proxy | grep -x "push-proxy" &>/dev/null; then
echo "error: push-proxy service is not running (stopped or missing)" >&2
exit 1
fi
# 7. Validate Push Notification Server configuration in docker compose config
echo "Verifying push notification server URL configuration..."
if ! docker compose config | grep -E "MM_EMAILSETTINGS_PUSHNOTIFICATIONSERVER[=: ]+[\"']?http://push-proxy:8066[\"']?" &>/dev/null; then
echo "error: Push notification server URL is not configured to http://push-proxy:8066" >&2
exit 1
fi
echo "All smoke checks passed successfully!"

View file

@ -31,3 +31,66 @@ if [ -f "$root/services/core/server/go.mod" ]; then
fi
fi
if [ -f "$root/services/core/webapp/package.json" ]; then
if [ "${NEXO_CORE_WEBAPP_TEST:-0}" = "1" ]; then
if command -v npm >/dev/null 2>&1; then
(cd "$root/services/core/webapp" && npm run test-ci)
else
echo "skip services/core/webapp: npm not found"
fi
else
echo "skip services/core/webapp: set NEXO_CORE_WEBAPP_TEST=1 to run the webapp npm test suite"
fi
fi
if [ -f "$root/apps/flutter-test/pubspec.yaml" ]; then
if [ "${NEXO_FLUTTER_INTEGRATION_TEST:-0}" = "1" ]; then
if command -v flutter >/dev/null 2>&1; then
(cd "$root/apps/flutter-test" && flutter test integration_test)
else
echo "skip apps/flutter-test integration_test: flutter not found"
fi
else
echo "skip apps/flutter-test integration_test: set NEXO_FLUTTER_INTEGRATION_TEST=1 to run integration tests"
fi
fi
if [ "${NEXO_ANDROID_NATIVE_TEST:-0}" = "1" ]; then
if [ -f "$root/apps/flutter-test/android/gradlew" ]; then
if command -v java >/dev/null 2>&1; then
# Check if Android SDK is available
has_android_sdk=0
if [ -n "${ANDROID_HOME:-}" ] && [ -d "$ANDROID_HOME" ]; then
has_android_sdk=1
elif [ -n "${ANDROID_SDK_ROOT:-}" ] && [ -d "$ANDROID_SDK_ROOT" ]; then
has_android_sdk=1
else
local_sdk_dir=""
if [ -f "$root/apps/flutter-test/android/local.properties" ]; then
# Extract non-commented sdk.dir value
sdk_line=$(grep -E '^[[:space:]]*sdk\.dir[[:space:]]*=' "$root/apps/flutter-test/android/local.properties" || true)
if [ -n "$sdk_line" ]; then
local_sdk_dir=$(echo "$sdk_line" | cut -d'=' -f2- | xargs || true)
fi
fi
if [ -n "$local_sdk_dir" ] && [ -d "$local_sdk_dir" ]; then
has_android_sdk=1
fi
fi
if [ "$has_android_sdk" = "1" ]; then
(cd "$root/apps/flutter-test/android" && ./gradlew testDebugUnitTest)
else
echo "skip apps/flutter-test Android native tests: Android SDK not found (ANDROID_HOME, ANDROID_SDK_ROOT, and local.properties sdk.dir are all missing or point to invalid directories)"
fi
else
echo "BLOCKED apps/flutter-test Android native tests: java not found"
fi
else
echo "skip apps/flutter-test Android native tests: gradlew not found"
fi
else
echo "skip apps/flutter-test Android native tests: set NEXO_ANDROID_NATIVE_TEST=1 to run Android native tests"
fi

View file

@ -8,10 +8,11 @@ Module-specific instructions stay with the module that owns them.
## Runtime Baseline Index
- Workspace runtime and upstream policy: `../README.md`
- Internal verification baseline: `../README.md#내부-검증-기준`
- Core compose runtime, smoke checks, remote parity, and secret boundary:
`../services/core/compose/README.md`
- Core upstream snapshot baseline: `../services/core/UPSTREAM.md`
- Mattermost mobile app upstream snapshot baseline: `../apps/mattermost/UPSTREAM.md`
- Mattermost push-proxy upstream snapshot baseline: `../services/push-proxy/UPSTREAM.md`
- Runtime Image Validation details: `runtime-image-validation.md`
- Compose smoke helper utility: `../bin/smoke-compose`

View file

@ -51,9 +51,8 @@ For the minimum push-proxy smoke, confirm the service is running and the core
service still points at the compose-network DNS name:
```sh
docker compose ps push-proxy
docker compose config | rg --fixed-strings \
"MM_EMAILSETTINGS_PUSHNOTIFICATIONSERVER=http://push-proxy:8066"
docker compose ps --services --status running push-proxy | grep -x "push-proxy"
docker compose config | grep -E "MM_EMAILSETTINGS_PUSHNOTIFICATIONSERVER[=: ]+[\"']?http://push-proxy:8066[\"']?"
```
FCM/APNS credential validation is not part of the default smoke. Run it only