mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
refactor(deployment): consolidate compose files into single compose.yml
- Merge docker-compose.nexus.yml into compose.yml as --profile registry - Drop docker-compose.smoke.yml, docker-compose.test.yml (deprecated), and docker-compose.stack.yml - Rename to compose.yml (Docker Compose default; no -f flag needed) - build apps / deploy now derive buildable services from compose.yml directly instead of hardcoded all_apps/service_map in commands.json — covers all 29 buildable services automatically Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
12
AGENTS.md
12
AGENTS.md
@@ -50,7 +50,7 @@ POST /pastebin/pastebin/User
|
||||
| `dbal/production/src/daemon/server_routes.cpp` | Route registration + auto-seed startup |
|
||||
| `frontends/pastebin/backend/app.py` | Flask JWT auth + Python runner |
|
||||
| `frontends/pastebin/src/` | Next.js React app |
|
||||
| `deployment/docker-compose.stack.yml` | Full stack compose |
|
||||
| `deployment/compose.yml` | Full stack compose |
|
||||
| `deployment/deployment.py` | Python CLI for all build/deploy/stack commands |
|
||||
|
||||
---
|
||||
@@ -80,15 +80,15 @@ cd deployment
|
||||
|
||||
# Full rebuild + restart
|
||||
python3 deployment.py build apps --force dbal pastebin
|
||||
docker compose -f docker-compose.stack.yml up -d
|
||||
docker compose -f compose.yml up -d
|
||||
|
||||
# Flask backend (separate from Next.js)
|
||||
docker compose -f docker-compose.stack.yml build pastebin-backend
|
||||
docker compose -f docker-compose.stack.yml up -d pastebin-backend
|
||||
docker compose -f compose.yml build pastebin-backend
|
||||
docker compose -f compose.yml up -d pastebin-backend
|
||||
|
||||
# dbal-init volume (schema volume container — rebuild when entity JSON changes)
|
||||
docker compose -f docker-compose.stack.yml build dbal-init
|
||||
docker compose -f docker-compose.stack.yml up dbal-init
|
||||
docker compose -f compose.yml build dbal-init
|
||||
docker compose -f compose.yml up dbal-init
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -230,11 +230,11 @@ npm run build --workspaces
|
||||
cd deployment && python3 deployment.py build base # Build Docker base images
|
||||
|
||||
# Deploy full stack
|
||||
cd deployment && docker compose -f docker-compose.stack.yml up -d
|
||||
cd deployment && docker compose -f compose.yml up -d
|
||||
|
||||
# Build & deploy specific apps
|
||||
python3 deployment.py build apps --force dbal pastebin # Next.js frontend only
|
||||
docker compose -f docker-compose.stack.yml build pastebin-backend # Flask backend
|
||||
docker compose -f compose.yml build pastebin-backend # Flask backend
|
||||
|
||||
# DBAL logs / seed verification
|
||||
docker logs -f metabuilder-dbal
|
||||
|
||||
@@ -32,7 +32,7 @@ http://localhost:8080 # DBAL C++ REST API (entities)
|
||||
```bash
|
||||
# Deploy full stack
|
||||
cd deployment
|
||||
docker compose -f docker-compose.stack.yml up -d
|
||||
docker compose -f compose.yml up -d
|
||||
|
||||
# Build & deploy a specific app
|
||||
python3 deployment.py build apps --force dbal pastebin
|
||||
|
||||
@@ -105,7 +105,7 @@ deployment.py artifactory init
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `docker-compose.nexus.yml` | Local registries (Nexus + Artifactory) |
|
||||
| `docker-compose.stack.yml` | Full application stack |
|
||||
| `compose.yml` | Full application stack |
|
||||
| `docker-compose.test.yml` | Integration test services |
|
||||
| `docker-compose.smoke.yml` | Smoke test environment |
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import argparse
|
||||
import time
|
||||
from cli.helpers import (
|
||||
BASE_DIR, PROJECT_ROOT, GREEN, YELLOW, NC,
|
||||
docker_compose, docker_image_exists, log_err, log_info, log_ok, log_warn,
|
||||
docker_compose, docker_image_exists, get_buildable_services, log_err, log_info, log_ok, log_warn,
|
||||
pull_with_retry, resolve_services, run as run_proc,
|
||||
)
|
||||
|
||||
@@ -37,8 +37,8 @@ def run_cmd(args: argparse.Namespace, config: dict) -> int:
|
||||
print(f" - {img}")
|
||||
print(f"{YELLOW}Build with:{NC} python3 deployment.py build base\n")
|
||||
|
||||
all_apps = defs["all_apps"]
|
||||
targets = args.apps if args.apps else list(all_apps)
|
||||
buildable = get_buildable_services()
|
||||
targets = args.apps if args.apps else buildable
|
||||
services = resolve_services(targets, config)
|
||||
if services is None:
|
||||
return 1
|
||||
|
||||
@@ -9,22 +9,9 @@
|
||||
},
|
||||
|
||||
"definitions": {
|
||||
"all_apps": ["workflowui", "codegen", "pastebin", "postgres", "emailclient", "exploded-diagrams", "storybook", "frontend-app", "dbal"],
|
||||
"base_build_order": ["apt", "conan-deps", "android-sdk", "node-deps", "pip-deps", "devcontainer"],
|
||||
"stack_commands": ["up", "start", "down", "stop", "build", "restart", "logs", "ps", "status", "clean"],
|
||||
|
||||
"service_map": {
|
||||
"workflowui": "workflowui",
|
||||
"codegen": "codegen",
|
||||
"pastebin": "pastebin",
|
||||
"postgres": "postgres-dashboard",
|
||||
"emailclient": "emailclient-app",
|
||||
"exploded-diagrams": "exploded-diagrams",
|
||||
"storybook": "storybook",
|
||||
"frontend-app": "frontend-app",
|
||||
"dbal": "dbal"
|
||||
},
|
||||
|
||||
"base_images": {
|
||||
"apt": { "dockerfile": "Dockerfile.apt", "tag": "metabuilder/base-apt:latest" },
|
||||
"conan-deps": { "dockerfile": "Dockerfile.conan-deps", "tag": "metabuilder/base-conan-deps:latest" },
|
||||
|
||||
@@ -6,16 +6,16 @@ import sys
|
||||
import time
|
||||
from cli.helpers import (
|
||||
COMPOSE_FILE, GREEN, RED, YELLOW, BLUE, NC,
|
||||
docker_compose, log_err, log_warn, resolve_services, run as run_proc,
|
||||
docker_compose, get_buildable_services, log_err, log_warn, resolve_services, run as run_proc,
|
||||
)
|
||||
|
||||
|
||||
def run_cmd(args: argparse.Namespace, config: dict) -> int:
|
||||
all_apps = config["definitions"]["all_apps"]
|
||||
targets = list(all_apps) if args.all else args.apps
|
||||
buildable = get_buildable_services()
|
||||
targets = buildable if args.all else args.apps
|
||||
if not targets:
|
||||
log_err("Specify app(s) to deploy, or use --all")
|
||||
print(f"Available: {', '.join(all_apps)}")
|
||||
print(f"Available: {', '.join(buildable)}")
|
||||
return 1
|
||||
|
||||
services = resolve_services(targets, config)
|
||||
|
||||
@@ -13,7 +13,7 @@ from pathlib import Path
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent.parent # deployment/
|
||||
PROJECT_ROOT = SCRIPT_DIR.parent
|
||||
BASE_DIR = SCRIPT_DIR / "base-images"
|
||||
COMPOSE_FILE = SCRIPT_DIR / "docker-compose.stack.yml"
|
||||
COMPOSE_FILE = SCRIPT_DIR / "compose.yml"
|
||||
|
||||
# ── Colors ───────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -121,17 +121,27 @@ def build_with_retry(tag: str, dockerfile: str, context: str, max_attempts: int
|
||||
return False
|
||||
|
||||
|
||||
def get_buildable_services() -> list[str]:
|
||||
"""Return all service names that have a build: section in the compose file."""
|
||||
import yaml
|
||||
with open(COMPOSE_FILE) as f:
|
||||
compose = yaml.safe_load(f)
|
||||
return [
|
||||
name for name, svc in compose.get("services", {}).items()
|
||||
if isinstance(svc, dict) and "build" in svc
|
||||
]
|
||||
|
||||
|
||||
def resolve_services(targets: list[str], config: dict) -> list[str] | None:
|
||||
"""Map friendly app names to compose service names. Returns None on error."""
|
||||
svc_map = config["definitions"]["service_map"]
|
||||
"""Validate compose service names against the compose file. Returns None on error."""
|
||||
buildable = get_buildable_services()
|
||||
services = []
|
||||
for t in targets:
|
||||
svc = svc_map.get(t)
|
||||
if not svc:
|
||||
log_err(f"Unknown app: {t}")
|
||||
print(f"Available: {', '.join(config['definitions']['all_apps'])}")
|
||||
if t not in buildable:
|
||||
log_err(f"Unknown or non-buildable service: {t}")
|
||||
print(f"Available: {', '.join(buildable)}")
|
||||
return None
|
||||
services.append(svc)
|
||||
services.append(t)
|
||||
return services
|
||||
|
||||
|
||||
|
||||
@@ -1,11 +1,31 @@
|
||||
"""Push all locally-built images to Nexus with :main + :latest tags."""
|
||||
|
||||
import argparse
|
||||
import pathlib
|
||||
import yaml
|
||||
|
||||
from cli.helpers import (
|
||||
BLUE, GREEN, NC,
|
||||
docker_image_exists, log_err, log_info, log_ok, log_warn, run as run_proc,
|
||||
)
|
||||
|
||||
COMPOSE_FILE = pathlib.Path(__file__).parent.parent / "compose.yml"
|
||||
|
||||
|
||||
def _load_built_images() -> list[dict]:
|
||||
"""Return [{local, name}] for every service with a build: directive."""
|
||||
with open(COMPOSE_FILE) as f:
|
||||
dc = yaml.safe_load(f)
|
||||
images = []
|
||||
for svc_name, svc in dc.get("services", {}).items():
|
||||
if "build" not in svc:
|
||||
continue
|
||||
local = svc.get("image", f"deployment-{svc_name}:latest")
|
||||
# derive a short push name from the image tag (strip prefix/tag)
|
||||
name = local.split("/")[-1].split(":")[0].removeprefix("deployment-")
|
||||
images.append({"local": local, "name": name})
|
||||
return images
|
||||
|
||||
|
||||
def run_cmd(args: argparse.Namespace, config: dict) -> int:
|
||||
nexus = "localhost:5050"
|
||||
@@ -16,23 +36,23 @@ def run_cmd(args: argparse.Namespace, config: dict) -> int:
|
||||
run_proc(["docker", "login", nexus, "-u", nexus_user, "--password-stdin"],
|
||||
input=nexus_pass.encode())
|
||||
|
||||
images_def = config["definitions"]["nexus_images"]
|
||||
images = _load_built_images()
|
||||
|
||||
pushed = skipped = failed = 0
|
||||
|
||||
def push_image(src: str, name: str, size: str) -> None:
|
||||
def push_image(local: str, name: str) -> None:
|
||||
nonlocal pushed, skipped, failed
|
||||
if not docker_image_exists(src):
|
||||
log_warn(f"SKIP {name} — {src} not found locally")
|
||||
if not docker_image_exists(local):
|
||||
log_warn(f"SKIP {name} — {local} not found locally")
|
||||
skipped += 1
|
||||
return
|
||||
|
||||
dst_main = f"{nexus}/{slug}/{name}:main"
|
||||
dst_latest = f"{nexus}/{slug}/{name}:latest"
|
||||
|
||||
log_info(f"Pushing {name} ({size})...")
|
||||
run_proc(["docker", "tag", src, dst_main])
|
||||
run_proc(["docker", "tag", src, dst_latest])
|
||||
log_info(f"Pushing {name}...")
|
||||
run_proc(["docker", "tag", local, dst_main])
|
||||
run_proc(["docker", "tag", local, dst_latest])
|
||||
|
||||
r1 = run_proc(["docker", "push", dst_main])
|
||||
r2 = run_proc(["docker", "push", dst_latest])
|
||||
@@ -43,21 +63,12 @@ def run_cmd(args: argparse.Namespace, config: dict) -> int:
|
||||
log_err(f" {name} FAILED")
|
||||
failed += 1
|
||||
|
||||
print(f"\n{BLUE}Registry : {nexus}{NC}")
|
||||
print(f"{BLUE}Slug : {slug}{NC}")
|
||||
print(f"{BLUE}Skip heavy: {args.skip_heavy}{NC}\n")
|
||||
print(f"\n{BLUE}Registry : {nexus}{NC}")
|
||||
print(f"{BLUE}Slug : {slug}{NC}")
|
||||
print(f"{BLUE}Images : {len(images)} (parsed from compose.yml){NC}\n")
|
||||
|
||||
for entry in images_def["base"] + images_def["apps"]:
|
||||
push_image(entry["local"], entry["name"], entry["size"])
|
||||
|
||||
if args.skip_heavy:
|
||||
log_warn("Skipping heavy images (--skip-heavy set):")
|
||||
for entry in images_def["heavy"] + images_def["heavy_apps"]:
|
||||
log_warn(f" {entry['name']} ({entry['size']})")
|
||||
else:
|
||||
log_info("--- Heavy images (this will take a while) ---")
|
||||
for entry in images_def["heavy_apps"] + images_def["heavy"]:
|
||||
push_image(entry["local"], entry["name"], entry["size"])
|
||||
for entry in images:
|
||||
push_image(entry["local"], entry["name"])
|
||||
|
||||
print(f"\n{GREEN}{'=' * 46}{NC}")
|
||||
print(f"{GREEN} Done. pushed={pushed} skipped={skipped} failed={failed}{NC}")
|
||||
|
||||
@@ -11,7 +11,7 @@ from cli.helpers import (
|
||||
docker_image_exists, log_info, run as run_proc,
|
||||
)
|
||||
|
||||
COMPOSE_FILE = pathlib.Path(__file__).parent.parent / "docker-compose.stack.yml"
|
||||
COMPOSE_FILE = pathlib.Path(__file__).parent.parent / "compose.yml"
|
||||
BASE_IMAGES_DIR = pathlib.Path(__file__).parent.parent / "base-images"
|
||||
|
||||
# Dockerfile.apt -> base-apt, Dockerfile.node-deps -> base-node-deps, etc.
|
||||
@@ -70,7 +70,7 @@ def run_cmd(args: argparse.Namespace, config: dict) -> int:
|
||||
print(f"{YELLOW}Registry:{NC} {local_registry}")
|
||||
print(f"{YELLOW}Slug:{NC} {slug}")
|
||||
print(f"{YELLOW}Tag:{NC} {tag}")
|
||||
print(f"{YELLOW}Images:{NC} {len(images)} (base-images/ + docker-compose.stack.yml)\n")
|
||||
print(f"{YELLOW}Images:{NC} {len(images)} (base-images/ + compose.yml)\n")
|
||||
|
||||
log_info(f"Logging in to {local_registry}...")
|
||||
run_proc(["docker", "login", local_registry, "-u", nexus_user, "--password-stdin"],
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
# --profile media Media daemon (FFmpeg/radio/retro), native HTTP streaming, HLS
|
||||
#
|
||||
# Usage:
|
||||
# docker compose -f docker-compose.stack.yml up -d
|
||||
# docker compose -f docker-compose.stack.yml --profile monitoring up -d
|
||||
# docker compose -f docker-compose.stack.yml --profile media up -d
|
||||
# docker compose -f docker-compose.stack.yml --profile monitoring --profile media up -d
|
||||
# docker compose -f compose.yml up -d
|
||||
# docker compose -f compose.yml --profile monitoring up -d
|
||||
# docker compose -f compose.yml --profile media up -d
|
||||
# docker compose -f compose.yml --profile monitoring --profile media up -d
|
||||
#
|
||||
# Access:
|
||||
# http://localhost Welcome portal
|
||||
@@ -918,6 +918,7 @@ services:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: deployment/config/prometheus/Dockerfile
|
||||
image: deployment-prometheus:latest
|
||||
container_name: metabuilder-prometheus
|
||||
restart: unless-stopped
|
||||
profiles: [monitoring]
|
||||
@@ -949,6 +950,7 @@ services:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: deployment/config/grafana/Dockerfile
|
||||
image: deployment-grafana:latest
|
||||
container_name: metabuilder-grafana
|
||||
restart: unless-stopped
|
||||
profiles: [monitoring]
|
||||
@@ -984,6 +986,7 @@ services:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: deployment/config/loki/Dockerfile
|
||||
image: deployment-loki:latest
|
||||
container_name: metabuilder-loki
|
||||
restart: unless-stopped
|
||||
profiles: [monitoring]
|
||||
@@ -1008,6 +1011,7 @@ services:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: deployment/config/promtail/Dockerfile
|
||||
image: deployment-promtail:latest
|
||||
container_name: metabuilder-promtail
|
||||
restart: unless-stopped
|
||||
profiles: [monitoring]
|
||||
@@ -1165,6 +1169,7 @@ services:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: deployment/config/alertmanager/Dockerfile
|
||||
image: deployment-alertmanager:latest
|
||||
container_name: metabuilder-alertmanager
|
||||
restart: unless-stopped
|
||||
profiles: [monitoring]
|
||||
@@ -1197,6 +1202,7 @@ services:
|
||||
build:
|
||||
context: ../services/media_daemon
|
||||
dockerfile: Dockerfile
|
||||
image: deployment-media-daemon:latest
|
||||
container_name: metabuilder-media-daemon
|
||||
restart: unless-stopped
|
||||
profiles: [media]
|
||||
@@ -1262,6 +1268,7 @@ services:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: deployment/config/nginx/Dockerfile.stream
|
||||
image: deployment-nginx-stream:latest
|
||||
container_name: metabuilder-nginx-stream
|
||||
restart: unless-stopped
|
||||
profiles: [media]
|
||||
@@ -1280,6 +1287,101 @@ services:
|
||||
networks:
|
||||
- metabuilder
|
||||
|
||||
# ============================================================================
|
||||
# Package Registries (--profile registry) — Nexus + Artifactory
|
||||
#
|
||||
# Run with:
|
||||
# docker compose -f compose.yml --profile registry up -d
|
||||
# # Wait ~2 min for init containers, then:
|
||||
# python3 deployment.py nexus push
|
||||
# python3 deployment.py npm publish-patches
|
||||
#
|
||||
# Access:
|
||||
# Nexus: http://localhost:8091 (admin / nexus)
|
||||
# Artifactory: http://localhost:8092 (admin / password)
|
||||
# ============================================================================
|
||||
|
||||
nexus:
|
||||
image: sonatype/nexus3:3.75.0
|
||||
platform: linux/amd64
|
||||
profiles: [registry]
|
||||
container_name: nexus-registry
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8091:8081"
|
||||
- "5050:5050"
|
||||
volumes:
|
||||
- nexus-data:/nexus-data
|
||||
environment:
|
||||
INSTALL4J_ADD_VM_PARAMS: "-Xms1g -Xmx1g -XX:MaxDirectMemorySize=1g"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -sf http://localhost:8081/service/rest/v1/status || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 15
|
||||
start_period: 120s
|
||||
networks:
|
||||
- metabuilder
|
||||
|
||||
nexus-init:
|
||||
image: python:3.12-alpine
|
||||
profiles: [registry]
|
||||
container_name: nexus-init
|
||||
depends_on:
|
||||
nexus:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- nexus-data:/nexus-data:ro
|
||||
- ./nexus-init.py:/nexus-init.py:ro
|
||||
entrypoint: ["/bin/sh", "-c", "pip install -q requests && python3 /nexus-init.py"]
|
||||
environment:
|
||||
NEXUS_URL: "http://nexus:8081"
|
||||
NEXUS_ADMIN_NEW_PASS: "nexus"
|
||||
DOCKER_REPO_PORT: "5050"
|
||||
restart: "no"
|
||||
networks:
|
||||
- metabuilder
|
||||
|
||||
artifactory:
|
||||
image: releases-docker.jfrog.io/jfrog/artifactory-cpp-ce:latest
|
||||
profiles: [registry]
|
||||
container_name: artifactory-ce
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8092:8081"
|
||||
- "8093:8082"
|
||||
volumes:
|
||||
- artifactory-data:/var/opt/jfrog/artifactory
|
||||
environment:
|
||||
JF_SHARED_DATABASE_TYPE: derby
|
||||
JF_SHARED_DATABASE_ALLOWNONPOSTGRESQL: "true"
|
||||
EXTRA_JAVA_OPTIONS: "-Xms512m -Xmx2g"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -sf http://localhost:8082/router/api/v1/system/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 15
|
||||
start_period: 120s
|
||||
networks:
|
||||
- metabuilder
|
||||
|
||||
artifactory-init:
|
||||
image: python:3.12-alpine
|
||||
profiles: [registry]
|
||||
container_name: artifactory-init
|
||||
depends_on:
|
||||
artifactory:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- ./artifactory-init.py:/artifactory-init.py:ro
|
||||
entrypoint: ["/bin/sh", "-c", "pip install -q requests && python3 /artifactory-init.py"]
|
||||
environment:
|
||||
ARTIFACTORY_URL: "http://artifactory:8081"
|
||||
ARTIFACTORY_ADMIN_PASS: "password"
|
||||
restart: "no"
|
||||
networks:
|
||||
- metabuilder
|
||||
|
||||
# ============================================================================
|
||||
# Volumes
|
||||
# ============================================================================
|
||||
@@ -1342,6 +1444,11 @@ volumes:
|
||||
driver: local
|
||||
packagerepo-data:
|
||||
driver: local
|
||||
# Registry
|
||||
nexus-data:
|
||||
name: nexus-data
|
||||
artifactory-data:
|
||||
name: artifactory-data
|
||||
|
||||
# ============================================================================
|
||||
# Networks
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# Primary adapter is configured here. Additional backends (cache, search,
|
||||
# secondary databases) are configured via environment variables in
|
||||
# docker-compose.stack.yml:
|
||||
# compose.yml:
|
||||
#
|
||||
# DBAL_ADAPTER Primary adapter type (postgres, mysql, sqlite, etc.)
|
||||
# DATABASE_URL Primary database connection string
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
##
|
||||
## nginx-smoke — Gateway for deployment smoke tests in CI.
|
||||
##
|
||||
## Real apps (workflowui, pastebin) are proxied to playwright's webServer
|
||||
## processes running on the host (reached via host.docker.internal).
|
||||
## Remaining apps (codegen, emailclient, etc.) return stub 200 responses
|
||||
## since they are not started as dev servers in CI.
|
||||
##
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
# ── Real apps — proxied to playwright webServer processes on host ─────
|
||||
|
||||
location /workflowui {
|
||||
proxy_pass http://host.docker.internal:3000;
|
||||
proxy_set_header Host localhost;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_read_timeout 120s;
|
||||
}
|
||||
|
||||
location /pastebin {
|
||||
proxy_pass http://host.docker.internal:3001;
|
||||
proxy_set_header Host localhost;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_read_timeout 120s;
|
||||
}
|
||||
|
||||
# ── DBAL API — proxied to real C++ daemon ─────────────────────────────
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://dbal:8080/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_read_timeout 30s;
|
||||
}
|
||||
|
||||
# ── Portal — must contain "MetaBuilder" ───────────────────────────────
|
||||
|
||||
location = / {
|
||||
add_header Content-Type text/html;
|
||||
return 200 '<html><body><h1>MetaBuilder Portal</h1></body></html>';
|
||||
}
|
||||
|
||||
# ── Postgres redirect ─────────────────────────────────────────────────
|
||||
|
||||
location = /postgres {
|
||||
return 307 /postgres/dashboard;
|
||||
}
|
||||
|
||||
# ── Remaining apps — stub (codegen, emailclient, diagrams, etc.) ──────
|
||||
|
||||
location / {
|
||||
add_header Content-Type text/html;
|
||||
return 200 '<html><body>MetaBuilder App</body></html>';
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
# Local Package Registries — Nexus (Docker + npm) + Artifactory CE (Conan2)
|
||||
#
|
||||
# Nexus (Sonatype):
|
||||
# 8091 → Nexus web UI (admin / nexus)
|
||||
# 5050 → Docker hosted repository (push + pull)
|
||||
# Repos: Docker (local), npm (hosted + proxy + group)
|
||||
#
|
||||
# Artifactory CE (JFrog):
|
||||
# 8092 → Artifactory web UI (admin / password)
|
||||
# Repos: Conan2 (local + remote), generic
|
||||
#
|
||||
# Prerequisites — Docker Desktop must trust the insecure local registry:
|
||||
# Docker Desktop → Settings → Docker Engine → add to "insecure-registries":
|
||||
# "insecure-registries": ["localhost:5050"]
|
||||
# Then restart Docker Desktop.
|
||||
#
|
||||
# Note: Nexus ships amd64-only. On Apple Silicon it runs via Rosetta (works fine).
|
||||
#
|
||||
# Usage:
|
||||
# docker compose -f docker-compose.nexus.yml up -d
|
||||
# # Wait ~2 min for init containers to finish, then:
|
||||
# python3 deployment.py nexus push # Docker images → Nexus
|
||||
# python3 deployment.py npm publish-patches # Patched npm packages → Nexus
|
||||
# conan remote add artifactory http://localhost:8092/artifactory/api/conan/conan-local
|
||||
#
|
||||
# URLs:
|
||||
# Nexus: http://localhost:8091 (admin / nexus)
|
||||
# Artifactory: http://localhost:8092 (admin / password)
|
||||
# npm group: http://localhost:8091/repository/npm-group/
|
||||
# Conan2: http://localhost:8092/artifactory/api/conan/conan-local
|
||||
|
||||
services:
|
||||
# ============================================================================
|
||||
# Sonatype Nexus — Docker registry + npm packages
|
||||
# ============================================================================
|
||||
nexus:
|
||||
image: sonatype/nexus3:3.75.0
|
||||
platform: linux/amd64 # Nexus has no ARM64 image; runs via Rosetta on Apple Silicon
|
||||
container_name: nexus-registry
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8091:8081" # Web UI + REST API
|
||||
- "5050:5050" # Docker hosted repository
|
||||
volumes:
|
||||
- nexus-data:/nexus-data
|
||||
environment:
|
||||
INSTALL4J_ADD_VM_PARAMS: "-Xms1g -Xmx1g -XX:MaxDirectMemorySize=1g"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -sf http://localhost:8081/service/rest/v1/status || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 15
|
||||
start_period: 120s
|
||||
networks:
|
||||
- registry-net
|
||||
|
||||
nexus-init:
|
||||
image: python:3.12-alpine
|
||||
container_name: nexus-init
|
||||
depends_on:
|
||||
nexus:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- nexus-data:/nexus-data:ro
|
||||
- ./nexus-init.py:/nexus-init.py:ro
|
||||
entrypoint: ["/bin/sh", "-c", "pip install -q requests && python3 /nexus-init.py"]
|
||||
environment:
|
||||
NEXUS_URL: "http://nexus:8081"
|
||||
NEXUS_ADMIN_NEW_PASS: "nexus"
|
||||
DOCKER_REPO_PORT: "5050"
|
||||
restart: "no"
|
||||
networks:
|
||||
- registry-net
|
||||
|
||||
# ============================================================================
|
||||
# JFrog Artifactory CE — Conan2 package repository
|
||||
# ============================================================================
|
||||
artifactory:
|
||||
image: releases-docker.jfrog.io/jfrog/artifactory-cpp-ce:latest
|
||||
container_name: artifactory-ce
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8092:8081" # Web UI + REST API
|
||||
- "8093:8082" # Service port (router/health)
|
||||
volumes:
|
||||
- artifactory-data:/var/opt/jfrog/artifactory
|
||||
environment:
|
||||
JF_SHARED_DATABASE_TYPE: derby
|
||||
JF_SHARED_DATABASE_ALLOWNONPOSTGRESQL: "true"
|
||||
EXTRA_JAVA_OPTIONS: "-Xms512m -Xmx2g"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -sf http://localhost:8082/router/api/v1/system/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 15
|
||||
start_period: 120s
|
||||
networks:
|
||||
- registry-net
|
||||
|
||||
artifactory-init:
|
||||
image: python:3.12-alpine
|
||||
container_name: artifactory-init
|
||||
depends_on:
|
||||
artifactory:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- ./artifactory-init.py:/artifactory-init.py:ro
|
||||
entrypoint: ["/bin/sh", "-c", "pip install -q requests && python3 /artifactory-init.py"]
|
||||
environment:
|
||||
ARTIFACTORY_URL: "http://artifactory:8081"
|
||||
ARTIFACTORY_ADMIN_PASS: "password"
|
||||
restart: "no"
|
||||
networks:
|
||||
- registry-net
|
||||
|
||||
volumes:
|
||||
nexus-data:
|
||||
name: nexus-data
|
||||
artifactory-data:
|
||||
name: artifactory-data
|
||||
|
||||
networks:
|
||||
registry-net:
|
||||
name: registry-net
|
||||
@@ -1,221 +0,0 @@
|
||||
# docker-compose.smoke.yml — Smoke test stack for CI.
|
||||
#
|
||||
# Includes a real DBAL daemon backed by PostgreSQL so E2E tests can seed
|
||||
# and query data. Admin tools (phpMyAdmin, Mongo Express, RedisInsight)
|
||||
# and an nginx gateway round out the stack.
|
||||
#
|
||||
# Usage:
|
||||
# docker compose -f deployment/docker-compose.smoke.yml up -d --wait
|
||||
# PLAYWRIGHT_BASE_URL=http://localhost/workflowui/ npx playwright test e2e/deployment-smoke.spec.ts
|
||||
# docker compose -f deployment/docker-compose.smoke.yml down -v
|
||||
|
||||
services:
|
||||
|
||||
# ── Gateway stub ──────────────────────────────────────────────────────────
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- ./config/nginx-smoke/default.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
# host.docker.internal lets nginx proxy to playwright's webServer processes
|
||||
# running on the host (workflowui :3000, pastebin :3001).
|
||||
# On Linux (GitHub Actions) this requires the host-gateway extra_host.
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
depends_on:
|
||||
dbal:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1/"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
networks:
|
||||
- smoke
|
||||
|
||||
# ── DBAL + PostgreSQL ────────────────────────────────────────────────────
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
environment:
|
||||
POSTGRES_USER: metabuilder
|
||||
POSTGRES_PASSWORD: metabuilder
|
||||
POSTGRES_DB: metabuilder
|
||||
tmpfs:
|
||||
- /var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U metabuilder"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
networks:
|
||||
- smoke
|
||||
|
||||
dbal-init:
|
||||
image: ${DBAL_INIT_IMAGE:-ghcr.io/johndoe6345789/metabuilder/dbal-init:latest}
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: deployment/config/dbal/Dockerfile.init
|
||||
volumes:
|
||||
- dbal-schemas:/target/schemas/entities
|
||||
- dbal-templates:/target/templates/sql
|
||||
networks:
|
||||
- smoke
|
||||
|
||||
dbal:
|
||||
image: ${DBAL_IMAGE:-ghcr.io/johndoe6345789/metabuilder/dbal:latest}
|
||||
build:
|
||||
context: ../dbal
|
||||
dockerfile: production/build-config/Dockerfile
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
DBAL_ADAPTER: postgres
|
||||
DATABASE_URL: "postgresql://metabuilder:metabuilder@postgres:5432/metabuilder"
|
||||
DBAL_SCHEMA_DIR: /app/schemas/entities
|
||||
DBAL_TEMPLATE_DIR: /app/templates/sql
|
||||
DBAL_SEED_DIR: /app/seeds/database
|
||||
DBAL_SEED_ON_STARTUP: "true"
|
||||
DBAL_BIND_ADDRESS: 0.0.0.0
|
||||
DBAL_PORT: 8080
|
||||
DBAL_MODE: production
|
||||
DBAL_DAEMON: "true"
|
||||
DBAL_LOG_LEVEL: info
|
||||
DBAL_AUTO_CREATE_TABLES: "true"
|
||||
DBAL_ENABLE_HEALTH_CHECK: "true"
|
||||
DBAL_ADMIN_TOKEN: "smoke-test-admin-token"
|
||||
DBAL_CORS_ORIGIN: "*"
|
||||
JWT_SECRET_KEY: "test-secret"
|
||||
volumes:
|
||||
- dbal-schemas:/app/schemas/entities:ro
|
||||
- dbal-templates:/app/templates/sql:ro
|
||||
depends_on:
|
||||
dbal-init:
|
||||
condition: service_completed_successfully
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://127.0.0.1:8080/health"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 15
|
||||
start_period: 10s
|
||||
networks:
|
||||
- smoke
|
||||
|
||||
# ── Infrastructure (stock images) ─────────────────────────────────────────
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: metabuilder
|
||||
MYSQL_USER: metabuilder
|
||||
MYSQL_PASSWORD: metabuilder
|
||||
MYSQL_DATABASE: metabuilder
|
||||
command: --default-authentication-plugin=mysql_native_password
|
||||
tmpfs:
|
||||
- /var/lib/mysql
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-u", "root", "-pmetabuilder"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 15
|
||||
start_period: 20s
|
||||
networks:
|
||||
- smoke
|
||||
|
||||
mongodb:
|
||||
image: mongo:7.0
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: metabuilder
|
||||
MONGO_INITDB_ROOT_PASSWORD: metabuilder
|
||||
tmpfs:
|
||||
- /data/db
|
||||
healthcheck:
|
||||
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')", "--quiet"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 15
|
||||
networks:
|
||||
- smoke
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
tmpfs:
|
||||
- /data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 3s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
networks:
|
||||
- smoke
|
||||
|
||||
# ── Admin tools (real containers, specific ports to match smoke tests) ────
|
||||
phpmyadmin:
|
||||
image: phpmyadmin:latest
|
||||
ports:
|
||||
- "8081:80"
|
||||
environment:
|
||||
PMA_HOST: mysql
|
||||
PMA_PORT: "3306"
|
||||
PMA_USER: metabuilder
|
||||
PMA_PASSWORD: metabuilder
|
||||
MYSQL_ROOT_PASSWORD: metabuilder
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-sf", "http://127.0.0.1/"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 15s
|
||||
networks:
|
||||
- smoke
|
||||
|
||||
mongo-express:
|
||||
image: mongo-express:latest
|
||||
ports:
|
||||
- "8082:8081"
|
||||
environment:
|
||||
ME_CONFIG_MONGODB_ADMINUSERNAME: metabuilder
|
||||
ME_CONFIG_MONGODB_ADMINPASSWORD: metabuilder
|
||||
ME_CONFIG_MONGODB_URL: mongodb://metabuilder:metabuilder@mongodb:27017/?authSource=admin
|
||||
ME_CONFIG_BASICAUTH: "false"
|
||||
depends_on:
|
||||
mongodb:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1:8081/"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 20s
|
||||
networks:
|
||||
- smoke
|
||||
|
||||
redisinsight:
|
||||
image: redis/redisinsight:latest
|
||||
ports:
|
||||
- "8083:5540"
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1:5540/api/health"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 10s
|
||||
networks:
|
||||
- smoke
|
||||
|
||||
volumes:
|
||||
dbal-schemas:
|
||||
driver: local
|
||||
dbal-templates:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
smoke:
|
||||
driver: bridge
|
||||
@@ -1,53 +0,0 @@
|
||||
# docker-compose.test.yml — Lightweight DB containers for DBAL integration tests.
|
||||
#
|
||||
# DEPRECATED: testcontainers-sidecar now starts/stops containers automatically.
|
||||
# Keep as fallback for environments without Docker daemon access (e.g. some CI configs).
|
||||
# Preferred: build with BUILD_INTEGRATION_TESTS=ON and let testcontainers manage lifecycle.
|
||||
#
|
||||
# Uses non-conflicting ports (5433, 3307) so it can run alongside the dev stack.
|
||||
# tmpfs mounts make containers fast and ephemeral (data discarded on stop).
|
||||
#
|
||||
# Usage:
|
||||
# docker compose -f deployment/docker-compose.test.yml up -d
|
||||
# export DBAL_TEST_POSTGRES_URL=postgresql://testuser:testpass@localhost:5433/dbal_test
|
||||
# export DBAL_TEST_MYSQL_URL=mysql://root:testpass@localhost:3307/dbal_test
|
||||
# cd dbal/production/_build && ctest -R dbal_integration_tests --output-on-failure
|
||||
# docker compose -f deployment/docker-compose.test.yml down
|
||||
|
||||
services:
|
||||
|
||||
postgres-test:
|
||||
image: postgres:16-alpine
|
||||
container_name: dbal-test-postgres
|
||||
environment:
|
||||
POSTGRES_PASSWORD: testpass
|
||||
POSTGRES_USER: testuser
|
||||
POSTGRES_DB: dbal_test
|
||||
ports:
|
||||
- "5433:5432" # 5433 avoids conflict with dev stack on 5432
|
||||
tmpfs:
|
||||
- /var/lib/postgresql/data # in-memory storage, instant teardown
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U testuser -d dbal_test"]
|
||||
interval: 2s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
mysql-test:
|
||||
image: mysql:8-oracle
|
||||
container_name: dbal-test-mysql
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: testpass
|
||||
MYSQL_DATABASE: dbal_test
|
||||
MYSQL_USER: testuser
|
||||
MYSQL_PASSWORD: testpass
|
||||
ports:
|
||||
- "3307:3306" # 3307 avoids conflict with dev stack on 3306
|
||||
tmpfs:
|
||||
- /var/lib/mysql # in-memory storage, instant teardown
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-ptestpass"]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 10
|
||||
start_period: 20s
|
||||
@@ -4,12 +4,12 @@
|
||||
#
|
||||
# Usage:
|
||||
# Local dev: npx verdaccio --config deployment/verdaccio.yaml &
|
||||
# Compose: docker compose -f docker-compose.stack.yml up verdaccio
|
||||
# Compose: docker compose -f compose.yml up verdaccio
|
||||
# CI: uses inline config with /tmp/verdaccio-storage
|
||||
# Then: bash deployment/publish-npm-patches.sh --verdaccio
|
||||
# .npmrc already points @esbuild-kit:registry to localhost:4873
|
||||
|
||||
# Docker container path (volume-mounted in docker-compose.stack.yml).
|
||||
# Docker container path (volume-mounted in compose.yml).
|
||||
# For local dev, use the CI composite action or npx verdaccio (default config).
|
||||
storage: /verdaccio/storage
|
||||
uplinks:
|
||||
|
||||
@@ -8,7 +8,7 @@ import { test, expect } from '@playwright/test'
|
||||
* Playwright dev servers bind to 0.0.0.0 so nginx can proxy via host.docker.internal.
|
||||
*
|
||||
* Local:
|
||||
* cd deployment && docker compose -f docker-compose.stack.yml up -d
|
||||
* cd deployment && docker compose -f compose.yml up -d
|
||||
* PLAYWRIGHT_BASE_URL=http://localhost/workflowui/ npx playwright test deployment-smoke
|
||||
*/
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ RUN conan install ../build-config/conanfile.txt \
|
||||
RUN cmake ../build-config \
|
||||
-G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_TOOLCHAIN_FILE=./build/Release/generators/conan_toolchain.cmake
|
||||
-DCMAKE_TOOLCHAIN_FILE=./conan_toolchain.cmake
|
||||
|
||||
RUN ninja
|
||||
|
||||
|
||||
@@ -9,13 +9,14 @@ find_package(nlohmann_json REQUIRED)
|
||||
find_package(yaml-cpp REQUIRED)
|
||||
find_package(spdlog REQUIRED)
|
||||
find_package(fmt REQUIRED)
|
||||
find_package(cpr REQUIRED)
|
||||
|
||||
file(GLOB_RECURSE SOURCES "../src/*.cpp")
|
||||
file(GLOB_RECURSE HEADERS "../include/*.hpp" "../include/*.h")
|
||||
|
||||
add_executable(media_daemon ${SOURCES} ${HEADERS})
|
||||
|
||||
target_include_directories(media_daemon PRIVATE ../include)
|
||||
target_include_directories(media_daemon PRIVATE ../include ../src)
|
||||
|
||||
target_link_libraries(media_daemon PRIVATE
|
||||
Drogon::Drogon
|
||||
@@ -23,6 +24,7 @@ target_link_libraries(media_daemon PRIVATE
|
||||
yaml-cpp::yaml-cpp
|
||||
spdlog::spdlog
|
||||
fmt::fmt
|
||||
cpr::cpr
|
||||
)
|
||||
|
||||
install(TARGETS media_daemon DESTINATION bin)
|
||||
|
||||
@@ -4,6 +4,7 @@ nlohmann_json/3.11.3
|
||||
yaml-cpp/0.8.0
|
||||
spdlog/1.16.0
|
||||
fmt/12.0.0
|
||||
cpr/1.10.5
|
||||
|
||||
[generators]
|
||||
CMakeDeps
|
||||
|
||||
Reference in New Issue
Block a user