nexo/services/core/compose
toki caafd4f320 feat(messaging): Web 알림 API 기준을 추가한다
Flutter Web foreground 알림을 준비하기 위해 초기화 옵션과 token prefix 경계를 public API로 고정한다.

포트/환경 기준 문서와 로드맵 정리, agent-task 리뷰 산출물도 함께 반영한다.
2026-06-07 10:59:12 +09:00
..
.env.example feat: remote compose 설정 업데이트 및 문서 동기화 2026-05-28 10:53:13 +09:00
docker-compose.yml feat: remote compose 설정 업데이트 및 문서 동기화 2026-05-28 10:53:13 +09:00
README.md feat(messaging): Web 알림 API 기준을 추가한다 2026-06-07 10:59:12 +09:00

Core Compose

This compose file mirrors the current Mattermost-based runtime shape inside the services/core module.

Local Runtime

cd services/core/compose
cp -n .env.example .env
docker compose pull
docker compose up -d

latest is the verified stable pointer for nexo-owned runtime images. When running or updating, you should run docker compose pull && docker compose up -d to fetch the latest stable images.

Runtime state is written to services/core/data/, which is ignored by git. Place push-proxy configuration under services/core/data/push-proxy/config/mattermost-push-proxy.json when FCM smoke testing is needed.

The default host ports are 18065 for core and 18066 for push-proxy so this test deployment can run beside the existing Mattermost service.

The Docker network subnet is fixed to 172.38.0.0/16. Treat this range as reserved for nexo and do not reuse it for other Docker networks.

Port And Environment Inventory

This compose file is the source of truth for the nexo-owned runtime port shape. It keeps public development defaults stable, while private hostnames, credentials, tokens, and concrete remote endpoints stay in ignored environment profiles or private run logs.

Nexo Runtime

Surface Host publish Container or internal target Notes
Core HTTP and webapp ${NEXO_CORE_PORT:-18065} core:8065 Compatibility baseline. Keep as-is unless a migration plan updates consumers.
Push-proxy HTTP ${NEXO_PUSH_PROXY_PORT:-18066} push-proxy:8066 Host publish is for smoke and diagnostics. Core talks to http://push-proxy:8066 on the compose network.
Postgres none db:5432 Compose-internal only. Do not publish by default.
Flutter test host none device/emulator app process Runtime URL values belong in ignored local profiles or the invoking shell.
Flutter web preview 13040 candidate future web host Reserved frontend slot for a future web preview; this repository currently does not publish a Flutter web host by default.

Upstream Dev Services

The Mattermost upstream development compose files under services/core/server/ publish several common local ports. Treat them as upstream dev auxiliaries, not nexo runtime ports.

Upstream service Typical host publish Role
Mattermost dev proxy/server 8065 Upstream server/webapp development entrypoint
PostgreSQL 5432 Upstream dev database
Redis 6379 Upstream cache/session auxiliary
MinIO / console 9000, 9002 Upstream object storage and console
Azurite 10000 Upstream Azure storage emulator
Inbucket web / SMTP / POP3 9001, 10025, 10110 Upstream mail testing
OpenLDAP 389, 636 Upstream LDAP testing
Elasticsearch / transport 9200, 9300 Upstream search testing
OpenSearch 9201 Upstream search testing
DejaVu 1358 Upstream search dashboard
Keycloak 8484 -> 8080 Upstream SAML/OIDC testing
Prometheus 9090 Upstream metrics testing
Grafana 3000 Upstream dashboard testing
Loki 3100 Upstream log aggregation testing
OTel collector 13133 Upstream telemetry collector health

If these upstream ports conflict with local services, change the upstream dev compose invocation or environment for that run. Do not move nexo runtime defaults just to satisfy an upstream dev auxiliary conflict.

Slot Plan

  • Frontend preview slot: reserve 13040 for a future Flutter/web preview.
  • Backend compatibility slots: keep core 18065 and push-proxy 18066.
  • Compose-internal auxiliaries: keep DB/cache/object storage private to the compose network unless a separate plan requires host access.
  • Future backend migration candidate: if 18065/18066 must move, use a planned 18040 range migration with compatibility notes and consumer smoke evidence instead of changing defaults in place.

Tracked Value Boundary

Tracked files may name environment variable names, example paths, sanitized profile labels, and non-secret public development defaults. Do not commit raw Firebase or APNs files, Mattermost tokens, passwords, private hostnames, private ports, or concrete remote endpoint URLs. Store those values in ignored .env files, ignored runtime data, or private test profiles, and refer to sanitized evidence locations from public reports.

Secret Boundary

Commit only .env.example. Keep .env, runtime data, logs, push-proxy credentials, certificates, and license files out of git. If a new credential or runtime path is added, first add the example variable or documented path here, then confirm the concrete value or generated file is ignored.

Smoke Checks

Run these checks after docker compose up -d.

curl -fsS "http://localhost:${NEXO_CORE_PORT:-18065}/api/v4/system/ping"
docker compose exec db pg_isready -U mmuser -d mattermost
curl -fsSI "http://localhost:${NEXO_CORE_PORT:-18065}"

The core ping response should report status as OK, Postgres should accept connections for the mattermost database, and the Mattermost app should return an HTTP response from the mapped core port.

For the minimum push-proxy smoke, confirm the service is running and the core service still points at the compose-network DNS name:

docker compose ps push-proxy
docker compose config | rg --fixed-strings \
  "MM_EMAILSETTINGS_PUSHNOTIFICATIONSERVER=http://push-proxy:8066"

FCM/APNS credential validation is not part of the default smoke. Run it only when a real push-proxy config is present outside git.

SDK Contract Anchors

The Flutter SDK side of this contract is documented in packages/messaging_flutter/README.md. This compose runtime keeps the Mattermost-compatible server and push-proxy shape that the SDK consumes.

Server source anchors:

  • Push payload model: services/core/server/public/model/push_notification.go
  • ACK endpoint route and handler: services/core/server/channels/api4/system.go
  • Push signing and push-proxy dispatch: services/core/server/channels/app/notification_push.go
  • Reference webapp client behavior: services/core/webapp/platform/client/src/client4.ts, services/core/webapp/platform/client/src/websocket.ts, and services/core/webapp/channels/src/

SDK-facing payload fields are type, ack_id, server_id, device_id, post_id, category, team_id, channel_id, root_id, channel_name, message, sender_id, sender_name, version, is_crt_enabled, is_id_loaded, and signature. The server signs push notifications with the ack_id and raw device token after splitting the stored device id into platform and token parts. The Android SDK stores device tokens with the android_rn-v2: compatibility prefix and validates the raw token portion.

ACK delivery uses POST /api/v4/notifications/ack with id, received_at, platform, type, post_id, and is_id_loaded. When id-loaded ACK is enabled for a message notification, the server may return post and routing fields that the SDK merges into the notification bundle.

The webapp remains the reference browser front. Compose and server updates must keep webapp messaging, replies, post acknowledgements, channel navigation, and thread behavior upstream-followable. Flutter embedded SDK compatibility is limited to payload fields, ACK/reply endpoints, signing-key verification, device-token format, and opened channel/thread routing events; product UI parity belongs to consuming apps or a separate roadmap item.

Remote Parity

The remote development copy lives under ~/docker/agent-service/nexo/compose. Local and remote compose inputs should match unless a work report explicitly records an intentional temporary drift.

cd services/core/compose
sha256sum docker-compose.yml .env.example
ssh <host> 'cd ~/docker/agent-service/nexo/compose && sha256sum docker-compose.yml .env.example'

The two sha256sum outputs should match for both files before remote runtime smoke is treated as equivalent to local compose smoke.

Deployment Loop

The compose deployment loop uses the same input contract for dev, test, and production-like lanes. The lane changes the target and promotion gate, not the tracked compose file.

Deployment Lanes

Lane Purpose Promotion gate
dev Disposable runtime for local feature checks and compose iteration. Compose config renders, services start, and the smoke checks above pass.
test Shared validation lane for upstream baseline or image updates before release notes. Compose inputs match the candidate branch, images pull by immutable tag or digest, smoke passes, and failures are classified.
production-like Internal release rehearsal before a stable pointer is treated as deployable. Image refs, compose input hashes, secret profile label, smoke evidence, and rollback ref are recorded together.

Use immutable tags or digests for test and production-like candidates. latest remains the stable pointer only after the candidate evidence is already recorded.

Secret Injection

Start each lane from .env.example, then keep the concrete env profile outside git. A tracked report may name the secret profile label and rotation decision, but must not include raw values, credential files, hostnames, ports, tokens, or private endpoint paths.

Push-proxy FCM/APNS credentials stay under ignored runtime data. If a new secret path is needed, add only the example variable or sanitized path contract here, then confirm the concrete file remains ignored.

Rollback Contract

Before applying a candidate, record the previous image refs, compose input hashes, secret profile label, and data snapshot reference in the private run log. The public release note or follow-up issue records only the rollback ref, classification, and sanitized evidence pointer.

A rollback restores the previous image refs and secret profile, starts the same compose services, and reruns the smoke checks above. If rollback smoke fails, classify the failure as server, webapp, push-proxy, SDK contract, or infra, then open the follow-up before another promotion attempt.