From 14089766ea33cecc8ce9cfd34dfb9399309fa8ef Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Mon, 9 Feb 2026 23:33:45 +0100 Subject: [PATCH] feat: infra cms --- .../.env.example => .env.example | 0 apps/sample-website/package.json | 10 +- .../uploads => directus/schema}/.gitkeep | 0 directus/uploads/.gitkeep | 0 .../docker-compose.yml => docker-compose.yml | 6 +- package.json | 5 + packages/next-config/package.json | 5 +- packages/next-observability/package.json | 2 +- scripts/cms-apply.sh | 50 +++++++ scripts/cms-snapshot.sh | 15 +++ scripts/sync-directus.sh | 123 ++++++++++++++++++ 11 files changed, 203 insertions(+), 13 deletions(-) rename apps/sample-website/.env.example => .env.example (100%) rename {apps/sample-website/directus/uploads => directus/schema}/.gitkeep (100%) create mode 100644 directus/uploads/.gitkeep rename apps/sample-website/docker-compose.yml => docker-compose.yml (93%) create mode 100755 scripts/cms-apply.sh create mode 100755 scripts/cms-snapshot.sh create mode 100755 scripts/sync-directus.sh diff --git a/apps/sample-website/.env.example b/.env.example similarity index 100% rename from apps/sample-website/.env.example rename to .env.example diff --git a/apps/sample-website/package.json b/apps/sample-website/package.json index 8a059e3..fc9a0d9 100644 --- a/apps/sample-website/package.json +++ b/apps/sample-website/package.json @@ -11,12 +11,6 @@ "lint": "next lint", "typecheck": "tsc --noEmit", "test": "vitest run --passWithNoTests", - "cms:bootstrap": "mintel directus bootstrap", - "cms:push:testing": "mintel directus sync push testing", - "cms:pull:testing": "mintel directus sync pull testing", - "cms:push:staging": "mintel directus sync push staging", - "cms:pull:staging": "mintel directus sync pull staging", - "cms:push:prod": "mintel directus sync push production", "cms:pull:prod": "mintel directus sync pull production", "pagespeed:test": "mintel pagespeed" }, @@ -24,8 +18,8 @@ "@mintel/next-utils": "workspace:*", "@mintel/observability": "workspace:*", "@mintel/next-observability": "workspace:*", - "@sentry/nextjs": "^8.55.0", - "next": "15.1.6", + "@sentry/nextjs": "10.38.0", + "next": "16.1.6", "next-intl": "^4.8.2", "react": "^19.0.0", "react-dom": "^19.0.0" diff --git a/apps/sample-website/directus/uploads/.gitkeep b/directus/schema/.gitkeep similarity index 100% rename from apps/sample-website/directus/uploads/.gitkeep rename to directus/schema/.gitkeep diff --git a/directus/uploads/.gitkeep b/directus/uploads/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/apps/sample-website/docker-compose.yml b/docker-compose.yml similarity index 93% rename from apps/sample-website/docker-compose.yml rename to docker-compose.yml index c71b5c7..dcebbc3 100644 --- a/apps/sample-website/docker-compose.yml +++ b/docker-compose.yml @@ -1,17 +1,18 @@ services: app: build: - context: . + context: ./apps/sample-website dockerfile: Dockerfile args: NEXT_PUBLIC_BASE_URL: ${NEXT_PUBLIC_BASE_URL:-http://localhost:3000} NEXT_PUBLIC_UMAMI_WEBSITE_ID: ${NEXT_PUBLIC_UMAMI_WEBSITE_ID} NEXT_PUBLIC_UMAMI_SCRIPT_URL: ${NEXT_PUBLIC_UMAMI_SCRIPT_URL} NEXT_PUBLIC_TARGET: ${TARGET:-development} - DIRECTUS_URL: ${DIRECTUS_URL:-http://directus:8055} restart: always networks: - infra + environment: + - DIRECTUS_URL=${DIRECTUS_URL:-http://directus:8055} env_file: - .env ports: @@ -46,6 +47,7 @@ services: volumes: - ./directus/uploads:/directus/uploads - ./directus/extensions:/directus/extensions + - ./directus/schema:/directus/schema labels: - "traefik.enable=true" - "traefik.http.routers.sample-website-directus.rule=Host(`${DIRECTUS_HOST:-cms.sample-website.localhost}`)" diff --git a/package.json b/package.json index 10709ca..3f47b41 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,11 @@ "changeset": "changeset", "version-packages": "changeset version", "sync-versions": "tsx scripts/sync-versions.ts", + "cms:push:infra": "./scripts/sync-directus.sh push infra", + "cms:pull:infra": "./scripts/sync-directus.sh pull infra", + "cms:schema:snapshot": "./scripts/cms-snapshot.sh", + "cms:schema:apply": "./scripts/cms-apply.sh", + "dev:infra": "docker-compose up -d directus directus-db", "release": "pnpm build && changeset publish", "release:tag": "pnpm build && pnpm -r publish --no-git-checks --access public", "prepare": "husky" diff --git a/packages/next-config/package.json b/packages/next-config/package.json index 39ee389..cbb43d2 100644 --- a/packages/next-config/package.json +++ b/packages/next-config/package.json @@ -1,6 +1,6 @@ { "name": "@mintel/next-config", - "version": "1.6.1", + "version": "1.6.0", "publishConfig": { "access": "public", "registry": "https://npm.infra.mintel.me" @@ -16,6 +16,7 @@ }, "dependencies": { "next-intl": "^4.8.2", - "@sentry/nextjs": "^8.0.0" + "@sentry/nextjs": "^10.38.0", + "next": "16.1.6" } } diff --git a/packages/next-observability/package.json b/packages/next-observability/package.json index 47c2b81..cdd6f3f 100644 --- a/packages/next-observability/package.json +++ b/packages/next-observability/package.json @@ -28,7 +28,7 @@ }, "dependencies": { "@mintel/observability": "workspace:*", - "@sentry/nextjs": "^8.55.0", + "@sentry/nextjs": "^10.38.0", "next": "16.1.6" }, "peerDependencies": { diff --git a/scripts/cms-apply.sh b/scripts/cms-apply.sh new file mode 100755 index 0000000..27b28c9 --- /dev/null +++ b/scripts/cms-apply.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +ENV=$1 +REMOTE_HOST="root@infra.mintel.me" +REMOTE_DIR="/opt/infra/directus" + +if [ -z "$ENV" ]; then + echo "Usage: ./scripts/cms-apply.sh [local|infra]" + exit 1 +fi + +case $ENV in + local) + CONTAINER=$(docker compose ps -q directus) + if [ -z "$CONTAINER" ]; then + echo "โŒ Local directus container not found." + exit 1 + fi + echo "๐Ÿš€ Applying schema locally..." + docker exec "$CONTAINER" npx directus schema apply /directus/schema/snapshot.yaml --yes + ;; + infra) + PROJECT_NAME="directus" + + echo "๐Ÿ“ค Uploading snapshot to $ENV..." + # Ensure remote directory exists + ssh "$REMOTE_HOST" "mkdir -p $REMOTE_DIR/directus/schema" + scp ./directus/schema/snapshot.yaml "$REMOTE_HOST:$REMOTE_DIR/directus/schema/snapshot.yaml" + + echo "๐Ÿ” Detecting remote container..." + REMOTE_CONTAINER=$(ssh "$REMOTE_HOST" "cd $REMOTE_DIR && docker compose -p $PROJECT_NAME ps -q directus") + + if [ -z "$REMOTE_CONTAINER" ]; then + echo "โŒ Remote container for $ENV not found." + exit 1 + fi + + echo "๐Ÿš€ Applying schema to $ENV..." + ssh "$REMOTE_HOST" "docker exec $REMOTE_CONTAINER npx directus schema apply /directus/schema/snapshot.yaml --yes" + + echo "๐Ÿ”„ Restarting Directus to clear cache..." + ssh "$REMOTE_HOST" "cd $REMOTE_DIR && docker compose -p $PROJECT_NAME restart directus" + ;; + *) + echo "โŒ Invalid environment: $ENV. Only 'local' and 'infra' are supported." + exit 1 + ;; +esac + +echo "โœจ Schema apply complete!" diff --git a/scripts/cms-snapshot.sh b/scripts/cms-snapshot.sh new file mode 100755 index 0000000..e3d45ad --- /dev/null +++ b/scripts/cms-snapshot.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# Detect local container +LOCAL_CONTAINER=$(docker compose ps -q directus) + +if [ -z "$LOCAL_CONTAINER" ]; then + echo "โŒ Local directus container not found. Is it running?" + exit 1 +fi + +echo "๐Ÿ“ธ Creating schema snapshot..." +# Note: we save it to the mounted volume path inside the container +docker exec "$LOCAL_CONTAINER" npx directus schema snapshot /directus/schema/snapshot.yaml + +echo "โœ… Snapshot saved to ./directus/schema/snapshot.yaml" diff --git a/scripts/sync-directus.sh b/scripts/sync-directus.sh new file mode 100755 index 0000000..e9d5fb1 --- /dev/null +++ b/scripts/sync-directus.sh @@ -0,0 +1,123 @@ +#!/bin/bash + +# Configuration +REMOTE_HOST="root@infra.mintel.me" +REMOTE_DIR="/opt/infra/directus" + +# DB Details (matching docker-compose defaults) +DB_USER="directus" +DB_NAME="directus" + +ACTION=$1 +ENV=$2 + +# Help +if [ -z "$ACTION" ] || [ -z "$ENV" ]; then + echo "Usage: ./scripts/sync-directus.sh [push|pull] [infra|testing|staging|production]" + echo "" + echo "Commands:" + echo " push Sync LOCAL data -> REMOTE" + echo " pull Sync REMOTE data -> LOCAL" + echo "" + echo "Environments:" + echo " infra (infra.mintel.me)" + exit 1 +fi + +# Map Environment +case $ENV in + infra) + PROJECT_NAME="directus" + ;; + *) + echo "โŒ Invalid environment: $ENV. Only 'infra' is currently configured for monorepo sync." + exit 1 + ;; +esac + +# Detect local containers +echo "๐Ÿ” Detecting local database..." +LOCAL_DB_CONTAINER=$(docker compose ps -q directus-db) +if [ -z "$LOCAL_DB_CONTAINER" ]; then + echo "โŒ Local directus-db container not found. Is it running? (npm run dev)" + exit 1 +fi + +if [ "$ACTION" == "push" ]; then + echo "๐Ÿš€ Pushing Local Data to $ENV..." + + # 1. DB Dump + echo "๐Ÿ“ฆ Dumping local database..." + docker exec "$LOCAL_DB_CONTAINER" pg_dump -U "$DB_USER" --clean --if-exists --no-owner --no-privileges "$DB_NAME" > dump.sql + + # 2. Upload Dump + echo "๐Ÿ“ค Uploading dump to remote server..." + scp dump.sql "$REMOTE_HOST:$REMOTE_DIR/dump.sql" + + # 3. Restore on Remote + echo "๐Ÿ”„ Restoring dump on $ENV..." + REMOTE_DB_CONTAINER=$(ssh "$REMOTE_HOST" "cd $REMOTE_DIR && docker compose -p $PROJECT_NAME ps -q directus-postgres") + + if [ -z "$REMOTE_DB_CONTAINER" ]; then + echo "โŒ Remote $ENV-db container not found!" + exit 1 + fi + + # Wipe remote DB clean before restore to avoid constraint errors + echo "๐Ÿงน Wiping remote database schema..." + ssh "$REMOTE_HOST" "docker exec $REMOTE_DB_CONTAINER psql -U $DB_USER $DB_NAME -c 'DROP SCHEMA public CASCADE; CREATE SCHEMA public;'" + + echo "โšก Restoring database..." + ssh "$REMOTE_HOST" "docker exec -i $REMOTE_DB_CONTAINER psql -U $DB_USER $DB_NAME < $REMOTE_DIR/dump.sql" + + # 4. Sync Uploads + echo "๐Ÿ“ Syncing uploads (Local -> $ENV)..." + rsync -avz --progress ./directus/uploads/ "$REMOTE_HOST:$REMOTE_DIR/uploads/" + + # Clean up + rm dump.sql + ssh "$REMOTE_HOST" "rm $REMOTE_DIR/dump.sql" + + # 5. Restart Directus to trigger migrations and refresh schema cache + echo "๐Ÿ”„ Restarting remote Directus to apply migrations..." + ssh "$REMOTE_HOST" "cd $REMOTE_DIR && docker compose -p $PROJECT_NAME restart directus" + + echo "โœจ Push to $ENV complete!" + +elif [ "$ACTION" == "pull" ]; then + echo "๐Ÿ“ฅ Pulling $ENV Data to Local..." + + # 1. DB Dump on Remote + echo "๐Ÿ“ฆ Dumping remote database ($ENV)..." + REMOTE_DB_CONTAINER=$(ssh "$REMOTE_HOST" "cd $REMOTE_DIR && docker compose -p $PROJECT_NAME ps -q directus-postgres") + + if [ -z "$REMOTE_DB_CONTAINER" ]; then + echo "โŒ Remote $ENV-db container not found!" + exit 1 + fi + ssh "$REMOTE_HOST" "docker exec $REMOTE_DB_CONTAINER pg_dump -U $DB_USER --clean --if-exists --no-owner --no-privileges $DB_NAME > $REMOTE_DIR/dump.sql" + + # 2. Download Dump + echo "๐Ÿ“ฅ Downloading dump..." + scp "$REMOTE_HOST:$REMOTE_DIR/dump.sql" dump.sql + + # Wipe local DB clean before restore to avoid constraint errors + echo "๐Ÿงน Wiping local database schema..." + docker exec "$LOCAL_DB_CONTAINER" psql -U "$DB_USER" "$DB_NAME" -c 'DROP SCHEMA public CASCADE; CREATE SCHEMA public;' + + echo "โšก Restoring database locally..." + docker exec -i "$LOCAL_DB_CONTAINER" psql -U "$DB_USER" "$DB_NAME" < dump.sql + + # 4. Sync Uploads + echo "๐Ÿ“ Syncing uploads ($ENV -> Local)..." + rsync -avz --progress "$REMOTE_HOST:$REMOTE_DIR/uploads/" ./directus/uploads/ + + # Clean up + rm dump.sql + ssh "$REMOTE_HOST" "rm $REMOTE_DIR/dump.sql" + + echo "โœจ Pull to Local complete!" +else + echo "Invalid action: $ACTION. Use push or pull." + exit 1 +fi