Point at the shared Infrastructure repo instead of Musify's folder
Ping already reused Musify's Zitadel and SeaweedFS, but did so through a hardcoded relative path into Musify's own deploy folder (../../../Musify/MusifyM/deploy/zitadel/.output), which only worked because Musify happened to be the one running them. Those services now live in a separate Infrastructure repository instead, so this points there. Also drops Ping's own Postgres container in favor of Infrastructure's shared instance (its own ping_db database), with a wait-for-postgres one-shot replacing the in-file health check.
This commit is contained in:
@@ -21,8 +21,8 @@ A Discord style chat app. Servers with text and voice channels, direct messages,
|
||||
- SignalR for chat, presence and call signaling (ringing, accepting, hanging up).
|
||||
- LiveKit's server SDK issues the access tokens clients use to join a voice/video room.
|
||||
- FluentValidation on the request DTOs, wired in through a minimal API filter.
|
||||
- Storage goes through an S3 compatible bucket, reusing the SeaweedFS instance from the Musify stack instead of running a second one.
|
||||
- Zitadel for OIDC/OAuth2 login, also shared with Musify so a single account works across both apps.
|
||||
- Storage goes through an S3 compatible bucket, reusing the shared SeaweedFS instance from the Infrastructure stack instead of running a second one.
|
||||
- Zitadel for OIDC/OAuth2 login, also shared through Infrastructure so a single account works across every project.
|
||||
|
||||
**Frontend**: React 19 with TypeScript, built with Vite.
|
||||
- Zustand for the small bits of client state: current view, active call, toasts, presence.
|
||||
@@ -31,7 +31,10 @@ A Discord style chat app. Servers with text and voice channels, direct messages,
|
||||
- `@livekit/components-react` plus `livekit-client` for the actual call UI and media handling.
|
||||
- Plain CSS, no component library.
|
||||
|
||||
**Infrastructure**: Docker Compose for local development. Postgres and LiveKit run in containers; Zitadel and the S3 storage are the ones already running for Musify, so this stack only adds what's specific to Ping.
|
||||
**Infrastructure**: Docker Compose for local development. LiveKit runs in a
|
||||
container here; Postgres, Zitadel and the S3 storage are shared services
|
||||
provided by the separate `Infrastructure` git repository, used by other
|
||||
projects too, so this stack only adds what's specific to Ping.
|
||||
|
||||
## Project layout
|
||||
|
||||
@@ -43,13 +46,22 @@ backend/deploy/ Compose files and env templates for local development
|
||||
|
||||
## Running it locally
|
||||
|
||||
The backend expects Musify's Zitadel and SeaweedFS containers to already be running, since it reuses them instead of standing up its own copy. With that in place:
|
||||
The backend expects the shared `Infrastructure` stack to already be running
|
||||
(see its own README for the exact command), since it reuses its
|
||||
Postgres/Zitadel/SeaweedFS instead of standing up its own copies. With that
|
||||
in place:
|
||||
|
||||
```bash
|
||||
cp backend/deploy/.env.example backend/deploy/.env
|
||||
# edit backend/deploy/.env to point at your Musify checkout and fill in the real S3 keys
|
||||
# edit backend/deploy/.env if your Infrastructure checkout isn't where
|
||||
# ZITADEL_ADMIN_PAT_DIR assumes, or its S3 keys differ from the defaults
|
||||
|
||||
docker compose -f backend/deploy/compose.yml -f backend/deploy/compose.dev.yml up -d
|
||||
```
|
||||
|
||||
That starts Postgres, LiveKit and the one-shot setup jobs (migrations, the Zitadel project for Ping, the storage bucket). From there, run the API and the frontend from your IDE or with `dotnet run` / `npm run dev` against them. See [backend/deploy/README.md](backend/deploy/README.md) for the full details, including how to also build and run the API and frontend inside Docker.
|
||||
That starts LiveKit and the one-shot setup jobs (waiting for Postgres,
|
||||
migrations, the Zitadel project for Ping, the storage bucket). From there,
|
||||
run the API and the frontend from your IDE or with `dotnet run` / `npm run
|
||||
dev` against them. See [backend/deploy/README.md](backend/deploy/README.md)
|
||||
for the full details, including how to also build and run the API and
|
||||
frontend inside Docker.
|
||||
|
||||
+17
-17
@@ -1,29 +1,32 @@
|
||||
# Development configuration for deploy/compose.yml + compose.dev.yml.
|
||||
# Copy to deploy/.env and adjust. Not versioned.
|
||||
# Copy to deploy/.env and adjust.
|
||||
#
|
||||
# cp deploy/.env.example deploy/.env
|
||||
#
|
||||
# Requires ../../../Infrastructure running first (Postgres, Zitadel,
|
||||
# SeaweedFS). See ../../../Infrastructure/README.md. Everything marked
|
||||
# "must match Infrastructure's .env" below has to be the same value as the
|
||||
# one in that file.
|
||||
|
||||
DOTNET_ENVIRONMENT=Development
|
||||
|
||||
# --- Public URLs -------------------------------------------------------------
|
||||
PUBLIC_WEB_URL=http://localhost:3100
|
||||
PUBLIC_API_URL=http://localhost:5211
|
||||
|
||||
# Ping reuses the Zitadel and SeaweedFS instances already running for Musify instead of
|
||||
# spinning up its own, so this must match Musify's PUBLIC_AUTH_URL exactly.
|
||||
PUBLIC_AUTH_URL=http://host.docker.internal:8080
|
||||
|
||||
# --- PostgreSQL (Ping's own database — Zitadel keeps using Musify's) --------
|
||||
# --- PostgreSQL (Infrastructure's shared instance, Ping's own database) -----
|
||||
# POSTGRES_USER / POSTGRES_PASSWORD must match Infrastructure's .env.
|
||||
POSTGRES_HOST=host.docker.internal
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=postgres
|
||||
POSTGRES_DB=ping_db
|
||||
POSTGRES_HOST_PORT=59010
|
||||
|
||||
# --- Zitadel (shared with Musify) --------------------------------------------
|
||||
# zitadel-init needs Musify's admin service-account PAT to create Ping's own project/apps
|
||||
# in that same Zitadel instance. Point this at Musify's deploy/zitadel/.output folder —
|
||||
# adjust if your Musify checkout lives somewhere else relative to this one.
|
||||
ZITADEL_ADMIN_PAT_DIR=../../../Musify/MusifyM/deploy/zitadel/.output
|
||||
# --- Zitadel (OIDC, shared instance) -----------------------------------------
|
||||
# Path to the admin service-account PAT Infrastructure's Zitadel writes on
|
||||
# init. Adjust if your Infrastructure checkout lives somewhere else relative
|
||||
# to this one.
|
||||
ZITADEL_ADMIN_PAT_DIR=../../../Infrastructure/zitadel/.output
|
||||
ZITADEL_DEV_MODE=true
|
||||
|
||||
# OIDC clients: the zitadel-init one-shot service (see compose.yml) fills these in
|
||||
@@ -31,9 +34,8 @@ ZITADEL_DEV_MODE=true
|
||||
AUTH_CLIENT_ID=
|
||||
WEB_CLIENT_ID=
|
||||
|
||||
# --- SeaweedFS (S3, shared with Musify) --------------------------------------
|
||||
# Same SeaweedFS instance as Musify, so these must be the REAL values from Musify's own
|
||||
# deploy/.env (S3_ACCESS_KEY / S3_SECRET_KEY) — not the placeholders below.
|
||||
# --- SeaweedFS (S3, shared instance, Ping's own bucket) ----------------------
|
||||
# S3_ACCESS_KEY / S3_SECRET_KEY must match Infrastructure's .env.
|
||||
S3_ACCESS_KEY=admin_access_key
|
||||
S3_SECRET_KEY=admin_secret_key
|
||||
S3_BUCKET=ping-storage
|
||||
@@ -41,9 +43,7 @@ S3_PUBLIC_URL=http://host.docker.internal:8333
|
||||
S3_USE_HTTP=true
|
||||
|
||||
# --- LiveKit (voice/video SFU) ------------------------------------------------
|
||||
# Overrides --dev's built-in "devkey/secret" pair (see compose.yml's LIVEKIT_KEYS): the
|
||||
# LiveKit .NET server SDK rejects secrets shorter than 256 bits. Generate your own with:
|
||||
# $b = [byte[]]::new(32); [System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($b); [Convert]::ToBase64String($b)
|
||||
# --dev mode uses this fixed dev key pair; fine for local development only.
|
||||
LIVEKIT_API_KEY=devkey
|
||||
LIVEKIT_API_SECRET=9U1eDtZx9eQrSWern4UcWa3lYIrfvrQqExLlkUXpP+o=
|
||||
|
||||
|
||||
+15
-13
@@ -1,20 +1,21 @@
|
||||
# Ping — deploy
|
||||
# Ping deploy
|
||||
|
||||
Reuses the Zitadel and SeaweedFS containers already running for Musify instead of starting new
|
||||
ones. Musify's stack must be up first.
|
||||
Reuses the Zitadel and SeaweedFS instances already running in the separate
|
||||
`Infrastructure` repository, instead of starting new ones. That stack must be
|
||||
up first (see its own README for the exact command).
|
||||
|
||||
```bash
|
||||
cp deploy/.env.example deploy/.env
|
||||
# edit deploy/.env:
|
||||
# - ZITADEL_ADMIN_PAT_DIR must point at Musify's deploy/zitadel/.output folder
|
||||
# - S3_ACCESS_KEY / S3_SECRET_KEY must match the REAL values in Musify's own deploy/.env
|
||||
# edit deploy/.env if your Infrastructure checkout isn't where
|
||||
# ZITADEL_ADMIN_PAT_DIR assumes, or if its S3_ACCESS_KEY/SECRET_KEY differ
|
||||
# from the defaults
|
||||
|
||||
docker compose -f deploy/compose.yml -f deploy/compose.dev.yml up -d
|
||||
```
|
||||
|
||||
This starts Postgres, LiveKit, and the one-shot jobs (EF Core migrations, the Zitadel
|
||||
project/apps for Ping, the `ping-storage` S3 bucket) — the usual dev loop runs the API and
|
||||
frontend from the IDE against these.
|
||||
This starts LiveKit and the one-shot jobs (waiting for Postgres, EF Core
|
||||
migrations, the Zitadel project for Ping, the `ping-storage` S3 bucket). The
|
||||
usual dev loop runs the API and frontend from the IDE against these.
|
||||
|
||||
To also build and run the API/frontend in Docker instead:
|
||||
|
||||
@@ -22,8 +23,9 @@ To also build and run the API/frontend in Docker instead:
|
||||
docker compose -f deploy/compose.yml -f deploy/compose.dev.yml --profile apps up -d --build
|
||||
```
|
||||
|
||||
By then `zitadel-init` has already written `AUTH_CLIENT_ID` / `WEB_CLIENT_ID` into `deploy/.env`.
|
||||
By then `zitadel-init` has already written `AUTH_CLIENT_ID` / `WEB_CLIENT_ID`
|
||||
into `deploy/.env`.
|
||||
|
||||
Ports (dev): API `5211`, web `3100`, Postgres `59010`, LiveKit `7880` (signaling) / `7881` (RTC
|
||||
TCP) / `50000-50100/udp` (media). Zitadel (`8080`) and the SeaweedFS S3 API (`8333`) come from
|
||||
Musify's own stack.
|
||||
Ports (dev): API `5211`, web `3100`, LiveKit `7880` (signaling), `7881` (RTC
|
||||
TCP) and `50000-50100/udp` (media). Postgres, Zitadel and the SeaweedFS S3 API
|
||||
come from Infrastructure's own stack. See its README for that port table.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: ping-dev
|
||||
|
||||
# Development overlay: publishes the infrastructure ports to the host so the .NET apps and
|
||||
# Development overlay: publishes the ports to the host so the .NET apps and
|
||||
# the web frontend can run from the IDE.
|
||||
#
|
||||
# docker compose -f deploy/compose.yml -f deploy/compose.dev.yml up -d
|
||||
@@ -10,16 +10,15 @@ x-host-alias: &host-alias
|
||||
- "host.docker.internal:host-gateway"
|
||||
|
||||
services:
|
||||
postgres:
|
||||
ports:
|
||||
- "${POSTGRES_HOST_PORT}:5432"
|
||||
|
||||
livekit:
|
||||
ports:
|
||||
- "7880:7880" # HTTP / WebSocket signaling
|
||||
- "7881:7881" # RTC TCP fallback
|
||||
- "50000-50100:50000-50100/udp" # WebRTC media
|
||||
|
||||
wait-for-postgres:
|
||||
extra_hosts: *host-alias
|
||||
|
||||
zitadel-init:
|
||||
extra_hosts: *host-alias
|
||||
volumes:
|
||||
@@ -28,6 +27,9 @@ services:
|
||||
seaweedfs-init:
|
||||
extra_hosts: *host-alias
|
||||
|
||||
migrate:
|
||||
extra_hosts: *host-alias
|
||||
|
||||
api:
|
||||
ports:
|
||||
- "5211:5211"
|
||||
|
||||
+51
-49
@@ -1,25 +1,22 @@
|
||||
# Ping — base stack, shared by development and production.
|
||||
# Ping: base stack, shared by development and production.
|
||||
#
|
||||
# This intentionally does NOT run Zitadel or SeaweedFS: Ping reuses the same instances
|
||||
# already running for Musify (see deploy/.env.example — ZITADEL_ADMIN_PAT_DIR / S3_*).
|
||||
# Bring Musify's stack up first, then this one.
|
||||
# Zitadel and SeaweedFS are NOT run here: Ping reuses the shared instances in
|
||||
# ../../../Infrastructure (see .env.example, ZITADEL_ADMIN_PAT_DIR / S3_*).
|
||||
# Bring that stack up first.
|
||||
#
|
||||
# dev docker compose -f deploy/compose.yml -f deploy/compose.dev.yml up -d
|
||||
#
|
||||
# The `api` and `web` services sit behind the `apps` profile, so a plain `up` starts
|
||||
# Postgres, LiveKit and the one-shot bootstrap jobs (migrations, Zitadel project/apps,
|
||||
# SeaweedFS bucket) and stops there — the usual dev setup, with the apps running from the
|
||||
# IDE. Add `--profile apps up -d --build` as a SECOND command to also build and run them in
|
||||
# Docker: by then zitadel-init has already written the OIDC client ids into the env file.
|
||||
|
||||
x-app-depends: &app-depends
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
# The `api` and `web` services sit behind the `apps` profile, so a plain `up`
|
||||
# starts LiveKit and the one-shot bootstrap jobs (Postgres wait, migrations,
|
||||
# Zitadel project/apps, SeaweedFS bucket) and stops there. That's the usual
|
||||
# dev setup, with the apps running from the IDE. Add `--profile apps up -d
|
||||
# --build` as a SECOND command to also build and run them in Docker: by then
|
||||
# zitadel-init has already written the OIDC client ids into the env file.
|
||||
|
||||
x-backend-env: &backend-env
|
||||
Database__ConnectionString: "Host=postgres;Port=5432;Database=${POSTGRES_DB};Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}"
|
||||
# The public S3 URL, not the internal one: the same address is used to sign the
|
||||
# presigned upload/download URLs the browser has to be able to reach.
|
||||
Database__ConnectionString: "Host=${POSTGRES_HOST};Port=5432;Database=${POSTGRES_DB};Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}"
|
||||
# The public S3 URL, not the internal one: the same address is used to sign
|
||||
# the presigned upload/download URLs the browser has to be able to reach.
|
||||
InfrastructureStorage__Address: ${S3_PUBLIC_URL}
|
||||
InfrastructureStorage__AccessKey: ${S3_ACCESS_KEY}
|
||||
InfrastructureStorage__SecretAccessKey: ${S3_SECRET_KEY}
|
||||
@@ -30,22 +27,6 @@ x-backend-env: &backend-env
|
||||
services:
|
||||
# --- Infrastructure -------------------------------------------------------
|
||||
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
container_name: ping-postgres
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
restart: unless-stopped
|
||||
|
||||
# Voice/video SFU. --dev keeps it single-node with in-memory state (fine for local
|
||||
# development), but the key pair is overridden via LIVEKIT_KEYS: the LiveKit .NET server SDK
|
||||
# rejects secrets shorter than 256 bits, and --dev's built-in "devkey/secret" pair is too short.
|
||||
@@ -56,18 +37,36 @@ services:
|
||||
environment:
|
||||
LIVEKIT_KEYS: "${LIVEKIT_API_KEY}: ${LIVEKIT_API_SECRET}"
|
||||
# Node IP is "automatically determined" by default, which inside a container means the
|
||||
# Docker bridge network's internal address (e.g. 172.x.x.x) — unreachable from the
|
||||
# Docker bridge network's internal address (e.g. 172.x.x.x), unreachable from the
|
||||
# browser, so ICE/media never connects even though the WebSocket signaling (which goes
|
||||
# through the published port, not this address) succeeds. Every public URL in this dev
|
||||
# stack is already hardcoded to localhost (see PUBLIC_WEB_URL, VITE_LIVEKIT_URL), so the
|
||||
# browser and this container are always on the same machine — advertise that directly.
|
||||
# browser and this container are always on the same machine. Advertise that directly.
|
||||
NODE_IP: 127.0.0.1
|
||||
restart: unless-stopped
|
||||
|
||||
# One-shot: creates the Ping project and its two OIDC apps in Musify's Zitadel instance
|
||||
# (or reuses them if they already exist), then writes AUTH_CLIENT_ID / WEB_CLIENT_ID back
|
||||
# into the env file mounted at /workspace/.env — so a later `--profile apps up` picks up
|
||||
# real values instead of empty ones. Both apps are public clients (PKCE, no secret): the
|
||||
# Waits for Infrastructure's Postgres to accept connections before `migrate`
|
||||
# runs. A cross-compose-project health check isn't possible, since
|
||||
# Postgres is no longer part of this compose file.
|
||||
wait-for-postgres:
|
||||
image: postgres:17-alpine
|
||||
container_name: ping-wait-for-postgres
|
||||
entrypoint: ["/bin/sh", "-c"]
|
||||
command:
|
||||
- |
|
||||
set -e
|
||||
deadline=$$(( $$(date +%s) + 60 ))
|
||||
until pg_isready -h ${POSTGRES_HOST} -p 5432 -U ${POSTGRES_USER} >/dev/null 2>&1; do
|
||||
[ "$$(date +%s)" -lt "$$deadline" ] || { echo "postgres did not respond at ${POSTGRES_HOST}:5432. Is Infrastructure running?" >&2; exit 1; }
|
||||
sleep 2
|
||||
done
|
||||
restart: "no"
|
||||
|
||||
# One-shot: creates the Ping project and its two OIDC apps in the shared
|
||||
# Zitadel (or reuses them if they already exist), then writes
|
||||
# AUTH_CLIENT_ID / WEB_CLIENT_ID back into the env file mounted at
|
||||
# /workspace/.env, so a later `--profile apps up` picks up real values
|
||||
# instead of empty ones. Both apps are public clients (PKCE, no secret): the
|
||||
# Scalar docs UI and the React SPA.
|
||||
zitadel-init:
|
||||
image: alpine:3.20
|
||||
@@ -101,7 +100,7 @@ services:
|
||||
}
|
||||
|
||||
if [ ! -f /zitadel-output/admin-sa.pat ]; then
|
||||
echo "admin-sa.pat not found under ZITADEL_ADMIN_PAT_DIR — is Musify's Zitadel running?" >&2
|
||||
echo "admin-sa.pat not found under ZITADEL_ADMIN_PAT_DIR. Is Infrastructure's Zitadel running?" >&2
|
||||
exit 1
|
||||
fi
|
||||
PAT=$$(tr -d '\r\n' </zitadel-output/admin-sa.pat)
|
||||
@@ -160,7 +159,7 @@ services:
|
||||
echo " created (clientId $$API_CLIENT_ID)"
|
||||
fi
|
||||
|
||||
echo "==> web app (React SPA — public client, PKCE)"
|
||||
echo "==> web app (React SPA, public client, PKCE)"
|
||||
WEB_APP_NAME="Ping Web"
|
||||
WEB_APP_ID=$$(find_app "$$WEB_APP_NAME")
|
||||
if [ -n "$$WEB_APP_ID" ]; then
|
||||
@@ -190,8 +189,9 @@ services:
|
||||
echo "==> wired into $$ENV_FILE"
|
||||
restart: "no"
|
||||
|
||||
# One-shot: SeaweedFS does not auto-create buckets. Reuses Musify's SeaweedFS instance
|
||||
# (same S3 endpoint/credentials) but creates Ping's own, separate bucket.
|
||||
# One-shot: SeaweedFS does not auto-create buckets. Reuses the shared
|
||||
# SeaweedFS instance (same S3 endpoint/credentials) but creates Ping's own,
|
||||
# separate bucket. `aws s3 ls` doubles as the readiness probe.
|
||||
seaweedfs-init:
|
||||
image: amazon/aws-cli:latest
|
||||
container_name: ping-seaweedfs-init
|
||||
@@ -202,6 +202,12 @@ services:
|
||||
entrypoint: ["/bin/sh", "-c"]
|
||||
command:
|
||||
- |
|
||||
set -e
|
||||
deadline=$$(( $$(date +%s) + 60 ))
|
||||
until aws --endpoint-url ${S3_PUBLIC_URL} s3 ls >/dev/null 2>&1; do
|
||||
[ "$$(date +%s)" -lt "$$deadline" ] || { echo "seaweedfs did not respond at ${S3_PUBLIC_URL}. Is Infrastructure running?" >&2; exit 1; }
|
||||
sleep 2
|
||||
done
|
||||
aws --endpoint-url ${S3_PUBLIC_URL} s3 mb s3://${S3_BUCKET} 2>/dev/null || true
|
||||
aws --endpoint-url ${S3_PUBLIC_URL} s3 ls
|
||||
restart: "no"
|
||||
@@ -213,10 +219,10 @@ services:
|
||||
dockerfile: Ping.Migrator.Dockerfile
|
||||
container_name: ping-migrate
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
wait-for-postgres:
|
||||
condition: service_completed_successfully
|
||||
environment:
|
||||
Database__ConnectionString: "Host=postgres;Port=5432;Database=${POSTGRES_DB};Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}"
|
||||
Database__ConnectionString: "Host=${POSTGRES_HOST};Port=5432;Database=${POSTGRES_DB};Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}"
|
||||
restart: "no"
|
||||
|
||||
# --- Applications (profile: apps) -----------------------------------------
|
||||
@@ -228,7 +234,6 @@ services:
|
||||
dockerfile: Ping.Api.Dockerfile
|
||||
container_name: ping-api
|
||||
depends_on:
|
||||
<<: *app-depends
|
||||
migrate:
|
||||
condition: service_completed_successfully
|
||||
environment:
|
||||
@@ -266,6 +271,3 @@ services:
|
||||
depends_on:
|
||||
- api
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
|
||||
Reference in New Issue
Block a user