Compare commits
21 Commits
b25299a3a8
...
v1.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
| a31202f63b | |||
| 0afd6bbb60 | |||
| 2c647f0284 | |||
| d9ff6d640d | |||
| 8ab9ec7d1f | |||
| 0cc67d54ef | |||
| cbb95a38cf | |||
| 5b163d6d74 | |||
| f6e774b5c9 | |||
| 613c8b1645 | |||
| 9e1aae5d76 | |||
| f1e3ad1357 | |||
| 39b044c2c2 | |||
| c0c73315c8 | |||
| 72fbae0666 | |||
| 3ed32210ad | |||
| f2366b5a38 | |||
| dccf6ad2ce | |||
| 788c9ca7ac | |||
| 34474de163 | |||
| 12646e45e4 |
@@ -51,32 +51,61 @@ jobs:
|
||||
id: determine
|
||||
shell: bash
|
||||
run: |
|
||||
TAG="${{ github.ref_name }}"
|
||||
REF="${{ github.ref }}"
|
||||
REF_NAME="${{ github.ref_name }}"
|
||||
REF_TYPE="${{ github.ref_type }}"
|
||||
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
|
||||
DOMAIN_BASE="mb-grid-solutions.com"
|
||||
PRJ_ID="mb-grid-solutions"
|
||||
|
||||
if [[ "${{ github.ref_type }}" == "branch" && "$TAG" == "main" ]]; then
|
||||
TARGET="staging"
|
||||
IMAGE_TAG="staging-${SHORT_SHA}"
|
||||
ENV_FILE=".env.staging"
|
||||
TRAEFIK_HOST="\`staging.${DOMAIN_BASE}\`"
|
||||
NEXT_PUBLIC_BASE_URL="https://staging.${DOMAIN_BASE}"
|
||||
DIRECTUS_URL="https://cms.staging.${DOMAIN_BASE}"
|
||||
DIRECTUS_HOST="\`cms.staging.${DOMAIN_BASE}\`"
|
||||
elif [[ "${{ github.ref_type }}" == "tag" ]]; then
|
||||
TARGET="production"
|
||||
IMAGE_TAG="$TAG"
|
||||
ENV_FILE=".env.prod"
|
||||
TRAEFIK_HOST="\`${DOMAIN_BASE}\`, \`www.${DOMAIN_BASE}\`"
|
||||
NEXT_PUBLIC_BASE_URL="https://${DOMAIN_BASE}"
|
||||
DIRECTUS_URL="https://cms.${DOMAIN_BASE}"
|
||||
DIRECTUS_HOST="\`cms.${DOMAIN_BASE}\`"
|
||||
echo "Detecting environment for ref: $REF ($REF_NAME, type: $REF_TYPE)"
|
||||
|
||||
# Fallback for REF_TYPE if missing
|
||||
if [[ -z "$REF_TYPE" ]]; then
|
||||
if [[ "$REF" == refs/tags/* ]]; then
|
||||
REF_TYPE="tag"
|
||||
elif [[ "$REF" == refs/heads/* ]]; then
|
||||
REF_TYPE="branch"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$REF_TYPE" == "branch" && "$REF_NAME" == "main" ]]; then
|
||||
TARGET="testing"
|
||||
IMAGE_TAG="testing-${SHORT_SHA}"
|
||||
ENV_FILE=".env.testing"
|
||||
TRAEFIK_HOST="testing.${DOMAIN_BASE}"
|
||||
NEXT_PUBLIC_BASE_URL="https://testing.${DOMAIN_BASE}"
|
||||
DIRECTUS_URL="https://cms.testing.${DOMAIN_BASE}"
|
||||
DIRECTUS_HOST="cms.testing.${DOMAIN_BASE}"
|
||||
elif [[ "$REF_TYPE" == "tag" ]]; then
|
||||
if [[ "$REF_NAME" =~ ^v[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then
|
||||
TARGET="production"
|
||||
IMAGE_TAG="$REF_NAME"
|
||||
ENV_FILE=".env.prod"
|
||||
TRAEFIK_HOST="${DOMAIN_BASE}, www.${DOMAIN_BASE}"
|
||||
NEXT_PUBLIC_BASE_URL="https://${DOMAIN_BASE}"
|
||||
DIRECTUS_URL="https://cms.${DOMAIN_BASE}"
|
||||
DIRECTUS_HOST="cms.${DOMAIN_BASE}"
|
||||
elif [[ "$REF_NAME" =~ -rc || "$REF_NAME" =~ -beta || "$REF_NAME" =~ -alpha ]]; then
|
||||
TARGET="staging"
|
||||
IMAGE_TAG="$REF_NAME"
|
||||
ENV_FILE=".env.staging"
|
||||
TRAEFIK_HOST="staging.${DOMAIN_BASE}"
|
||||
NEXT_PUBLIC_BASE_URL="https://staging.${DOMAIN_BASE}"
|
||||
DIRECTUS_URL="https://cms.staging.${DOMAIN_BASE}"
|
||||
DIRECTUS_HOST="cms.staging.${DOMAIN_BASE}"
|
||||
else
|
||||
TARGET="skip"
|
||||
echo "Tag $REF_NAME did not match any environment pattern."
|
||||
fi
|
||||
else
|
||||
TARGET="skip"
|
||||
echo "Ref type $REF_TYPE is not handled for deployment."
|
||||
fi
|
||||
|
||||
echo "Target determined: $TARGET"
|
||||
echo "Image tag: $IMAGE_TAG"
|
||||
|
||||
echo "target=$TARGET" >> "$GITHUB_OUTPUT"
|
||||
echo "image_tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT"
|
||||
echo "env_file=$ENV_FILE" >> "$GITHUB_OUTPUT"
|
||||
@@ -144,6 +173,7 @@ jobs:
|
||||
--platform linux/arm64 \
|
||||
--build-arg NPM_TOKEN=${{ secrets.NPM_TOKEN }} \
|
||||
--build-arg NEXT_PUBLIC_BASE_URL=${{ needs.prepare.outputs.next_public_base_url }} \
|
||||
--build-arg NEXT_PUBLIC_TARGET=${{ needs.prepare.outputs.target }} \
|
||||
--build-arg DIRECTUS_URL=${{ needs.prepare.outputs.directus_url }} \
|
||||
-t registry.infra.mintel.me/mintel/mb-grid-solutions:${{ needs.prepare.outputs.image_tag }} \
|
||||
--push .
|
||||
@@ -156,42 +186,83 @@ jobs:
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
steps:
|
||||
- name: 🚀 Deploy via SSH
|
||||
uses: appleboy/ssh-action@master
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
host: ${{ secrets.SSH_HOST }}
|
||||
username: root
|
||||
key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
script: |
|
||||
fetch-depth: 1
|
||||
|
||||
- name: 🚀 Deploy via SSH
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Deploying to alpha.mintel.me"
|
||||
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.ALPHA_SSH_KEY }}" > ~/.ssh/id_ed25519
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
ssh-keyscan -H alpha.mintel.me >> ~/.ssh/known_hosts 2>/dev/null
|
||||
|
||||
# Generate Environment File
|
||||
cat > .env.deploy << 'EOF'
|
||||
ENV_FILE=${{ needs.prepare.outputs.env_file }}
|
||||
IMAGE_TAG=${{ needs.prepare.outputs.image_tag }}
|
||||
TRAEFIK_HOST=${{ needs.prepare.outputs.traefik_host }}
|
||||
PROJECT_NAME=${{ needs.prepare.outputs.project_name }}
|
||||
NEXT_PUBLIC_BASE_URL=${{ needs.prepare.outputs.next_public_base_url }}
|
||||
|
||||
# Directus
|
||||
DIRECTUS_URL=${{ needs.prepare.outputs.directus_url }}
|
||||
DIRECTUS_HOST=${{ needs.prepare.outputs.directus_host }}
|
||||
INTERNAL_DIRECTUS_URL=http://directus:8055
|
||||
DIRECTUS_API_TOKEN=${{ secrets.DIRECTUS_API_TOKEN || vars.DIRECTUS_API_TOKEN }}
|
||||
DIRECTUS_ADMIN_EMAIL=${{ secrets.DIRECTUS_ADMIN_EMAIL || vars.DIRECTUS_ADMIN_EMAIL || 'admin@mintel.me' }}
|
||||
DIRECTUS_ADMIN_PASSWORD=${{ secrets.DIRECTUS_ADMIN_PASSWORD || vars.DIRECTUS_ADMIN_PASSWORD }}
|
||||
DIRECTUS_DB_NAME=${{ secrets.DIRECTUS_DB_NAME || vars.DIRECTUS_DB_NAME || 'directus' }}
|
||||
DIRECTUS_DB_USER=${{ secrets.DIRECTUS_DB_USER || vars.DIRECTUS_DB_USER || 'directus' }}
|
||||
DIRECTUS_DB_PASSWORD=${{ secrets.DIRECTUS_DB_PASSWORD || vars.DIRECTUS_DB_PASSWORD }}
|
||||
DIRECTUS_KEY=${{ secrets.DIRECTUS_KEY || vars.DIRECTUS_KEY }}
|
||||
DIRECTUS_SECRET=${{ secrets.DIRECTUS_SECRET || vars.DIRECTUS_SECRET }}
|
||||
|
||||
# SMTP Config
|
||||
SMTP_HOST=${{ secrets.SMTP_HOST || vars.SMTP_HOST }}
|
||||
SMTP_PORT=${{ secrets.SMTP_PORT || vars.SMTP_PORT || '587' }}
|
||||
SMTP_SECURE=${{ secrets.SMTP_SECURE || vars.SMTP_SECURE || 'false' }}
|
||||
SMTP_USER=${{ secrets.SMTP_USER || vars.SMTP_USER }}
|
||||
SMTP_PASS=${{ secrets.SMTP_PASS || vars.SMTP_PASS }}
|
||||
SMTP_FROM=${{ secrets.SMTP_FROM || vars.SMTP_FROM }}
|
||||
CONTACT_RECIPIENT=${{ secrets.CONTACT_RECIPIENT || vars.CONTACT_RECIPIENT }}
|
||||
|
||||
# Authentication
|
||||
GATEKEEPER_PASSWORD=${{ secrets.GATEKEEPER_PASSWORD || vars.GATEKEEPER_PASSWORD }}
|
||||
AUTH_COOKIE_NAME=${{ secrets.AUTH_COOKIE_NAME || vars.AUTH_COOKIE_NAME || 'mintel_gatekeeper_session' }}
|
||||
COOKIE_DOMAIN=${{ secrets.COOKIE_DOMAIN || vars.COOKIE_DOMAIN || '.mb-grid-solutions.com' }}
|
||||
|
||||
# External Services
|
||||
SENTRY_DSN=${{ secrets.SENTRY_DSN || vars.SENTRY_DSN }}
|
||||
GOTIFY_URL=${{ secrets.GOTIFY_URL || vars.GOTIFY_URL }}
|
||||
GOTIFY_TOKEN=${{ secrets.GOTIFY_TOKEN || vars.GOTIFY_TOKEN }}
|
||||
NEXT_PUBLIC_UMAMI_WEBSITE_ID=${{ secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID || vars.NEXT_PUBLIC_UMAMI_WEBSITE_ID }}
|
||||
NEXT_PUBLIC_UMAMI_SCRIPT_URL=${{ secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL || vars.NEXT_PUBLIC_UMAMI_SCRIPT_URL }}
|
||||
|
||||
# Project
|
||||
PROJECT_COLOR=${{ secrets.PROJECT_COLOR || vars.PROJECT_COLOR || '#82ed20' }}
|
||||
EOF
|
||||
|
||||
APP_DIR="/home/deploy/sites/mb-grid-solutions.com"
|
||||
ssh -o StrictHostKeyChecking=accept-new root@alpha.mintel.me "mkdir -p $APP_DIR"
|
||||
|
||||
scp -o StrictHostKeyChecking=accept-new .env.deploy root@alpha.mintel.me:$APP_DIR/${{ needs.prepare.outputs.env_file }}
|
||||
scp -o StrictHostKeyChecking=accept-new docker-compose.yaml root@alpha.mintel.me:$APP_DIR/docker-compose.yaml
|
||||
|
||||
ssh -o StrictHostKeyChecking=accept-new root@alpha.mintel.me bash << 'EOF'
|
||||
set -e
|
||||
APP_DIR="/home/deploy/sites/mb-grid-solutions.com"
|
||||
mkdir -p $APP_DIR
|
||||
cd $APP_DIR
|
||||
|
||||
# Update Environment
|
||||
cat > ${{ needs.prepare.outputs.env_file }} << EOF
|
||||
IMAGE_TAG=${{ needs.prepare.outputs.image_tag }}
|
||||
TRAEFIK_HOST=${{ needs.prepare.outputs.traefik_host }}
|
||||
PROJECT_NAME=${{ needs.prepare.outputs.project_name }}
|
||||
NEXT_PUBLIC_BASE_URL=${{ needs.prepare.outputs.next_public_base_url }}
|
||||
|
||||
# Directus
|
||||
DIRECTUS_URL=${{ needs.prepare.outputs.directus_url }}
|
||||
DIRECTUS_HOST=${{ needs.prepare.outputs.directus_host }}
|
||||
DIRECTUS_API_TOKEN=${{ secrets.DIRECTUS_API_TOKEN }}
|
||||
DIRECTUS_ADMIN_EMAIL=${{ secrets.DIRECTUS_ADMIN_EMAIL || 'admin@mintel.me' }}
|
||||
DIRECTUS_ADMIN_PASSWORD=${{ secrets.DIRECTUS_ADMIN_PASSWORD }}
|
||||
DIRECTUS_DB_NAME=${{ secrets.DIRECTUS_DB_NAME || 'directus' }}
|
||||
DIRECTUS_DB_USER=${{ secrets.DIRECTUS_DB_USER || 'directus' }}
|
||||
DIRECTUS_DB_PASSWORD=${{ secrets.DIRECTUS_DB_PASSWORD }}
|
||||
DIRECTUS_KEY=${{ secrets.DIRECTUS_KEY }}
|
||||
DIRECTUS_SECRET=${{ secrets.DIRECTUS_SECRET }}
|
||||
EOF
|
||||
|
||||
# Sync docker-compose
|
||||
echo "${{ secrets.REGISTRY_PASS }}" | docker login registry.infra.mintel.me -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
||||
docker compose -p "${{ needs.prepare.outputs.project_name }}" --env-file ${{ needs.prepare.outputs.env_file }} pull
|
||||
docker compose -p "${{ needs.prepare.outputs.project_name }}" --env-file ${{ needs.prepare.outputs.env_file }} up -d --remove-orphans
|
||||
docker system prune -f --filter "until=24h"
|
||||
EOF
|
||||
|
||||
notifications:
|
||||
name: 🔔 Notifications
|
||||
|
||||
@@ -3,6 +3,9 @@ FROM registry.infra.mintel.me/mintel/nextjs:latest AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Ensure we are in a clean, standalone environment
|
||||
RUN rm -rf packages apps pnpm-workspace.yaml 2>/dev/null || true
|
||||
|
||||
# Build-time environment variables for Next.js
|
||||
ARG NEXT_PUBLIC_BASE_URL
|
||||
ARG NEXT_PUBLIC_UMAMI_WEBSITE_ID
|
||||
@@ -17,6 +20,7 @@ ENV NEXT_PUBLIC_UMAMI_SCRIPT_URL=$NEXT_PUBLIC_UMAMI_SCRIPT_URL
|
||||
ENV NEXT_PUBLIC_TARGET=$NEXT_PUBLIC_TARGET
|
||||
ENV DIRECTUS_URL=$DIRECTUS_URL
|
||||
ENV NPM_TOKEN=$NPM_TOKEN
|
||||
ENV SENTRY_SUPPRESS_TURBOPACK_WARNING=1
|
||||
|
||||
# Enable corepack
|
||||
RUN corepack enable
|
||||
@@ -25,7 +29,7 @@ RUN corepack enable
|
||||
COPY package.json pnpm-lock.yaml* .npmrc ./
|
||||
|
||||
# Install dependencies
|
||||
RUN pnpm install --frozen-lockfile
|
||||
RUN pnpm install --no-frozen-lockfile
|
||||
|
||||
# Copy local files
|
||||
COPY . .
|
||||
|
||||
@@ -52,10 +52,10 @@ const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||
className="fixed top-0 left-0 right-0 z-[100]"
|
||||
>
|
||||
<header
|
||||
className={`transition-all duration-300 flex items-center py-1 ${
|
||||
className={`transition-all duration-300 flex items-center ${
|
||||
isScrolled
|
||||
? "bg-white/90 backdrop-blur-lg border-b border-slate-200 shadow-sm"
|
||||
: "bg-gradient-to-b from-white/80 via-white/40 to-transparent"
|
||||
? "bg-white/90 backdrop-blur-lg border-b border-slate-200 shadow-sm py-2"
|
||||
: "bg-gradient-to-b from-white/80 via-white/40 to-transparent py-4"
|
||||
}`}
|
||||
>
|
||||
<div className="container-custom flex justify-between items-center w-full relative z-10">
|
||||
@@ -65,7 +65,7 @@ const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||
aria-label={`${t("nav.home")} - Zur Startseite`}
|
||||
>
|
||||
<div
|
||||
className={`relative transition-all duration-300 ${isScrolled ? "h-[50px] md:h-[80px] w-[120px] md:w-[200px] mt-0 mb-[-10px]" : "h-[80px] md:h-[140px] w-[180px] md:w-[320px] mt-2 md:mt-4 mb-[-20px] md:mb-[-40px]"}`}
|
||||
className={`relative transition-all duration-300 ${isScrolled ? "h-[50px] md:h-[60px] w-[120px] md:w-[150px]" : "h-[70px] md:h-[100px] w-[160px] md:w-[240px]"}`}
|
||||
>
|
||||
<Image
|
||||
src="/assets/logo.png"
|
||||
|
||||
@@ -8,39 +8,49 @@ services:
|
||||
- ${ENV_FILE:-.env}
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.${PROJECT_NAME:-mb-grid-solutions}.rule=Host(`${TRAEFIK_HOST:-mb-grid-solutions.localhost}`)"
|
||||
- "traefik.http.routers.${PROJECT_NAME:-mb-grid-solutions}.entrypoints=websecure"
|
||||
- "traefik.http.routers.${PROJECT_NAME:-mb-grid-solutions}.tls.certresolver=le"
|
||||
- "traefik.http.routers.${PROJECT_NAME:-mb-grid-solutions}.tls=true"
|
||||
- "traefik.http.services.${PROJECT_NAME:-mb-grid-solutions}.loadbalancer.server.port=3000"
|
||||
- "traefik.http.routers.${PROJECT_NAME:-mb-grid-solutions}.middlewares=${PROJECT_NAME:-mb-grid-solutions}-auth"
|
||||
- "traefik.http.routers.${PROJECT_NAME}.rule=Host(`${TRAEFIK_HOST:-mb-grid-solutions.localhost}`)"
|
||||
- "traefik.http.routers.${PROJECT_NAME}.entrypoints=websecure"
|
||||
- "traefik.http.routers.${PROJECT_NAME}.tls.certresolver=le"
|
||||
- "traefik.http.routers.${PROJECT_NAME}.tls=true"
|
||||
- "traefik.http.services.${PROJECT_NAME}.loadbalancer.server.port=3000"
|
||||
- "traefik.http.routers.${PROJECT_NAME}.middlewares=${PROJECT_NAME}-auth"
|
||||
- "traefik.docker.network=infra"
|
||||
|
||||
# Gatekeeper Router
|
||||
- "traefik.http.routers.${PROJECT_NAME:-mb-grid-solutions}-gatekeeper.rule=Host(`${TRAEFIK_HOST:-mb-grid-solutions.localhost}`) && PathPrefix(`/gatekeeper`)"
|
||||
- "traefik.http.routers.${PROJECT_NAME:-mb-grid-solutions}-gatekeeper.entrypoints=websecure"
|
||||
- "traefik.http.routers.${PROJECT_NAME:-mb-grid-solutions}-gatekeeper.tls.certresolver=le"
|
||||
- "traefik.http.routers.${PROJECT_NAME:-mb-grid-solutions}-gatekeeper.tls=true"
|
||||
- "traefik.http.routers.${PROJECT_NAME:-mb-grid-solutions}-gatekeeper.service=${PROJECT_NAME:-mb-grid-solutions}-gatekeeper"
|
||||
# Gatekeeper Router (Shared Host + dedicated Subdomain)
|
||||
- "traefik.http.routers.${PROJECT_NAME}-gatekeeper.rule=(Host(`${TRAEFIK_HOST:-mb-grid-solutions.localhost}`) && PathPrefix(`/gatekeeper`)) || Host(`gatekeeper.${TRAEFIK_HOST:-mb-grid-solutions.localhost}`)"
|
||||
- "traefik.http.routers.${PROJECT_NAME}-gatekeeper.entrypoints=websecure"
|
||||
- "traefik.http.routers.${PROJECT_NAME}-gatekeeper.tls.certresolver=le"
|
||||
- "traefik.http.routers.${PROJECT_NAME}-gatekeeper.tls=true"
|
||||
- "traefik.http.routers.${PROJECT_NAME}-gatekeeper.service=${PROJECT_NAME}-gatekeeper"
|
||||
|
||||
# Auth Middleware Definition
|
||||
- "traefik.http.middlewares.${PROJECT_NAME:-mb-grid-solutions}-auth.forwardauth.address=http://gatekeeper:3000/api/verify"
|
||||
- "traefik.http.middlewares.${PROJECT_NAME:-mb-grid-solutions}-auth.forwardauth.trustForwardHeader=true"
|
||||
- "traefik.http.middlewares.${PROJECT_NAME:-mb-grid-solutions}-auth.forwardauth.authResponseHeaders=X-Auth-User"
|
||||
- "traefik.http.middlewares.${PROJECT_NAME}-auth.forwardauth.address=http://${PROJECT_NAME}-gatekeeper:3000/api/verify"
|
||||
- "traefik.http.middlewares.${PROJECT_NAME}-auth.forwardauth.trustForwardHeader=true"
|
||||
- "traefik.http.middlewares.${PROJECT_NAME}-auth.forwardauth.authResponseHeaders=X-Auth-User"
|
||||
- "traefik.docker.network=infra"
|
||||
|
||||
gatekeeper:
|
||||
image: registry.infra.mintel.me/mintel/gatekeeper:latest
|
||||
container_name: ${PROJECT_NAME:-mb-grid-solutions}-gatekeeper
|
||||
restart: always
|
||||
networks:
|
||||
- infra
|
||||
infra:
|
||||
aliases:
|
||||
- ${PROJECT_NAME:-mb-grid-solutions}-gatekeeper
|
||||
env_file:
|
||||
- ${ENV_FILE:-.env}
|
||||
environment:
|
||||
PORT: 3000
|
||||
PROJECT_NAME: "MB Grid Solutions"
|
||||
PROJECT_COLOR: "#82ed20"
|
||||
PORT: ${PORT:-3000}
|
||||
PROJECT_NAME: ${PROJECT_NAME:-MB Grid Solutions}
|
||||
PROJECT_COLOR: ${PROJECT_COLOR:-#82ed20}
|
||||
COOKIE_DOMAIN: ${COOKIE_DOMAIN:-.mb-grid-solutions.com}
|
||||
AUTH_COOKIE_NAME: ${AUTH_COOKIE_NAME:-mintel_gatekeeper_session}
|
||||
GATEKEEPER_PASSWORD: ${GATEKEEPER_PASSWORD:-mintel}
|
||||
# Dedicated Base URL for Gatekeeper subdomain to prevent redirect loops
|
||||
NEXT_PUBLIC_BASE_URL: https://gatekeeper.${TRAEFIK_HOST:-mb-grid-solutions.localhost}
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.services.${PROJECT_NAME:-mb-grid-solutions}-gatekeeper.loadbalancer.server.port=3000"
|
||||
- "traefik.http.services.${PROJECT_NAME}-gatekeeper.loadbalancer.server.port=3000"
|
||||
- "traefik.docker.network=infra"
|
||||
|
||||
directus:
|
||||
image: directus/directus:11
|
||||
@@ -63,17 +73,23 @@ services:
|
||||
DB_DATABASE: ${DIRECTUS_DB_NAME:-directus}
|
||||
DB_USER: ${DIRECTUS_DB_USER:-directus}
|
||||
DB_PASSWORD: ${DIRECTUS_DB_PASSWORD:-directus}
|
||||
# Telemetry & Performance
|
||||
LOGGER_LEVEL: ${LOG_LEVEL:-info}
|
||||
SENTRY_DSN: ${SENTRY_DSN}
|
||||
SENTRY_ENVIRONMENT: ${TARGET:-development}
|
||||
volumes:
|
||||
- ./directus/uploads:/directus/uploads
|
||||
- ./directus/extensions:/directus/extensions
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.${PROJECT_NAME:-mb-grid-solutions}-directus.rule=Host(`${DIRECTUS_HOST:-cms.mb-grid-solutions.localhost}`)"
|
||||
- "traefik.http.routers.${PROJECT_NAME:-mb-grid-solutions}-directus.entrypoints=websecure"
|
||||
- "traefik.http.routers.${PROJECT_NAME:-mb-grid-solutions}-directus.tls.certresolver=le"
|
||||
- "traefik.http.routers.${PROJECT_NAME:-mb-grid-solutions}-directus.tls=true"
|
||||
- "traefik.http.routers.${PROJECT_NAME:-mb-grid-solutions}-directus.middlewares=${PROJECT_NAME:-mb-grid-solutions}-auth"
|
||||
- "traefik.http.services.${PROJECT_NAME:-mb-grid-solutions}-directus.loadbalancer.server.port=8055"
|
||||
- "traefik.http.routers.${PROJECT_NAME}-directus.rule=Host(`${DIRECTUS_HOST:-cms.mb-grid-solutions.localhost}`)"
|
||||
- "traefik.http.routers.${PROJECT_NAME}-directus.entrypoints=websecure"
|
||||
- "traefik.http.routers.${PROJECT_NAME}-directus.tls.certresolver=le"
|
||||
- "traefik.http.routers.${PROJECT_NAME}-directus.tls=true"
|
||||
- "traefik.http.routers.${PROJECT_NAME}-directus.middlewares=${PROJECT_NAME}-forward,compress"
|
||||
- "traefik.http.services.${PROJECT_NAME}-directus.loadbalancer.server.port=8055"
|
||||
- "traefik.http.middlewares.${PROJECT_NAME}-forward.headers.customrequestheaders.X-Forwarded-Proto=https"
|
||||
- "traefik.docker.network=infra"
|
||||
|
||||
directus-db:
|
||||
image: postgres:15-alpine
|
||||
|
||||
2
next-env.d.ts
vendored
2
next-env.d.ts
vendored
@@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
26
package.json
26
package.json
@@ -11,13 +11,13 @@
|
||||
"lint": "eslint app components lib scripts",
|
||||
"test": "vitest",
|
||||
"prepare": "husky",
|
||||
"directus:bootstrap": "DIRECTUS_URL=http://localhost:8055 npx tsx --env-file=.env scripts/setup-directus.ts",
|
||||
"directus:push:staging": "./scripts/sync-directus.sh push staging",
|
||||
"directus:pull:staging": "./scripts/sync-directus.sh pull staging",
|
||||
"directus:push:testing": "./scripts/sync-directus.sh push testing",
|
||||
"directus:pull:testing": "./scripts/sync-directus.sh pull testing",
|
||||
"directus:push:prod": "./scripts/sync-directus.sh push production",
|
||||
"directus:pull:prod": "./scripts/sync-directus.sh pull production",
|
||||
"cms:bootstrap": "DIRECTUS_URL=http://localhost:8055 npx tsx --env-file=.env scripts/setup-directus.ts",
|
||||
"cms:push:staging": "./scripts/sync-directus.sh push staging",
|
||||
"cms:pull:staging": "./scripts/sync-directus.sh pull staging",
|
||||
"cms:push:testing": "./scripts/sync-directus.sh push testing",
|
||||
"cms:pull:testing": "./scripts/sync-directus.sh pull testing",
|
||||
"cms:push:prod": "./scripts/sync-directus.sh push production",
|
||||
"cms:pull:prod": "./scripts/sync-directus.sh pull production",
|
||||
"pagespeed:test": "mintel pagespeed test"
|
||||
},
|
||||
"keywords": [],
|
||||
@@ -25,8 +25,8 @@
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"@mintel/next-config": "1.1.13",
|
||||
"@mintel/next-utils": "1.1.13",
|
||||
"@mintel/next-config": "^1.1.13",
|
||||
"@mintel/next-utils": "^1.1.13",
|
||||
"@sentry/nextjs": "^10.38.0",
|
||||
"framer-motion": "^12.29.2",
|
||||
"lucide-react": "^0.562.0",
|
||||
@@ -42,10 +42,10 @@
|
||||
"@commitlint/cli": "^20.4.0",
|
||||
"@commitlint/config-conventional": "^20.4.0",
|
||||
"@directus/sdk": "^21.0.0",
|
||||
"@mintel/cli": "1.1.13",
|
||||
"@mintel/eslint-config": "1.1.13",
|
||||
"@mintel/husky-config": "1.1.13",
|
||||
"@mintel/tsconfig": "1.1.13",
|
||||
"@mintel/cli": "^1.1.13",
|
||||
"@mintel/eslint-config": "^1.1.13",
|
||||
"@mintel/husky-config": "^1.1.13",
|
||||
"@mintel/tsconfig": "^1.1.13",
|
||||
"@tailwindcss/postcss": "^4.1.18",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
|
||||
21
pnpm-lock.yaml
generated
21
pnpm-lock.yaml
generated
@@ -9,10 +9,10 @@ importers:
|
||||
.:
|
||||
dependencies:
|
||||
'@mintel/next-config':
|
||||
specifier: 1.1.13
|
||||
specifier: ^1.1.13
|
||||
version: 1.1.13(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.211.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(typescript@5.9.3)(webpack@5.104.1)
|
||||
'@mintel/next-utils':
|
||||
specifier: 1.1.13
|
||||
specifier: ^1.1.13
|
||||
version: 1.1.13(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)
|
||||
'@sentry/nextjs':
|
||||
specifier: ^10.38.0
|
||||
@@ -55,16 +55,16 @@ importers:
|
||||
specifier: ^21.0.0
|
||||
version: 21.0.0
|
||||
'@mintel/cli':
|
||||
specifier: 1.1.13
|
||||
specifier: ^1.1.13
|
||||
version: 1.1.13
|
||||
'@mintel/eslint-config':
|
||||
specifier: 1.1.13
|
||||
specifier: ^1.1.13
|
||||
version: 1.1.13(eslint@8.57.1)(typescript@5.9.3)
|
||||
'@mintel/husky-config':
|
||||
specifier: 1.1.13
|
||||
specifier: ^1.1.13
|
||||
version: 1.1.13
|
||||
'@mintel/tsconfig':
|
||||
specifier: 1.1.13
|
||||
specifier: ^1.1.13
|
||||
version: 1.1.13
|
||||
'@tailwindcss/postcss':
|
||||
specifier: ^4.1.18
|
||||
@@ -3240,7 +3240,6 @@ packages:
|
||||
glob@9.3.5:
|
||||
resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||
|
||||
global-directory@4.0.1:
|
||||
resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==}
|
||||
@@ -7046,7 +7045,7 @@ snapshots:
|
||||
|
||||
'@types/pg-pool@2.0.6':
|
||||
dependencies:
|
||||
'@types/pg': 8.6.1
|
||||
'@types/pg': 8.15.6
|
||||
|
||||
'@types/pg-pool@2.0.7':
|
||||
dependencies:
|
||||
@@ -7985,7 +7984,7 @@ snapshots:
|
||||
eslint: 8.57.1
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1)
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1)
|
||||
eslint-plugin-react: 7.37.5(eslint@8.57.1)
|
||||
eslint-plugin-react-hooks: 5.2.0(eslint@8.57.1)
|
||||
@@ -8015,7 +8014,7 @@ snapshots:
|
||||
tinyglobby: 0.2.15
|
||||
unrs-resolver: 1.11.1
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -8030,7 +8029,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1):
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
array-includes: 3.1.9
|
||||
|
||||
@@ -7,26 +7,36 @@ import { createCollection, createField, updateSettings } from "@directus/sdk";
|
||||
const client = createMintelDirectusClient();
|
||||
|
||||
async function setupBranding() {
|
||||
const prjName = process.env.PROJECT_NAME || "Mintel Project";
|
||||
const prjName = process.env.PROJECT_NAME || "MB Grid Solutions";
|
||||
const prjColor = process.env.PROJECT_COLOR || "#82ed20";
|
||||
|
||||
console.log(`🎨 Setup Directus Branding for ${prjName}...`);
|
||||
console.log(`🎨 Refining Directus Branding for ${prjName}...`);
|
||||
await ensureDirectusAuthenticated(client);
|
||||
|
||||
const cssInjection = `
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
||||
body, .v-app { font-family: 'Inter', sans-serif !important; }
|
||||
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&display=swap');
|
||||
|
||||
body, .v-app { font-family: 'Outfit', sans-serif !important; }
|
||||
|
||||
.public-view .v-card {
|
||||
backdrop-filter: blur(20px);
|
||||
background: rgba(255, 255, 255, 0.9) !important;
|
||||
border-radius: 32px !important;
|
||||
box-shadow: 0 50px 100px -20px rgba(0, 0, 0, 0.4) !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
.v-navigation-drawer { background: #000c24 !important; }
|
||||
|
||||
.v-list-item--active {
|
||||
color: ${prjColor} !important;
|
||||
background: rgba(130, 237, 32, 0.1) !important;
|
||||
}
|
||||
</style>
|
||||
<div style="font-family: 'Inter', sans-serif; text-align: center; margin-top: 24px;">
|
||||
<p style="color: rgba(255,255,255,0.7); font-size: 14px; margin-bottom: 4px; font-weight: 500;">MINTEL INFRASTRUCTURE ENGINE</p>
|
||||
<h1 style="color: #ffffff; font-size: 18px; font-weight: 700; margin: 0;">${prjName.toUpperCase()} <span style="color: ${prjColor};">RELIABILITY.</span></h1>
|
||||
<div style="font-family: 'Outfit', sans-serif; text-align: center; margin-top: 24px;">
|
||||
<p style="color: rgba(255,255,255,0.6); font-size: 11px; letter-spacing: 2px; margin-bottom: 4px; font-weight: 600; text-transform: uppercase;">Mintel Infrastructure Engine</p>
|
||||
<h1 style="color: #ffffff; font-size: 20px; font-weight: 700; margin: 0; letter-spacing: -0.5px;">${prjName.toUpperCase()} <span style="color: ${prjColor};">SYNC.</span></h1>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -36,25 +46,23 @@ async function setupBranding() {
|
||||
project_name: prjName,
|
||||
project_color: prjColor,
|
||||
public_note: cssInjection,
|
||||
module_bar_background: "#00081a",
|
||||
theme_light_overrides: {
|
||||
primary: prjColor,
|
||||
borderRadius: "16px",
|
||||
borderRadius: "12px",
|
||||
navigationBackground: "#000c24",
|
||||
navigationForeground: "#ffffff",
|
||||
moduleBarBackground: "#00081a",
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} as any),
|
||||
);
|
||||
console.log("✨ Branding applied!");
|
||||
|
||||
try {
|
||||
await createCollectionAndFields();
|
||||
console.log("🏗️ Schema alignment complete!");
|
||||
} catch (error) {
|
||||
console.error("❌ Error aligning schema:", error);
|
||||
}
|
||||
await createCollectionAndFields();
|
||||
console.log("🏗️ Schema alignment complete!");
|
||||
} catch (error) {
|
||||
console.error("❌ Error setting up branding:", error);
|
||||
console.error("❌ Error during bootstrap:", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +77,9 @@ async function createCollectionAndFields() {
|
||||
meta: {
|
||||
icon: "contact_mail",
|
||||
display_template: "{{name}} <{{email}}>",
|
||||
group: null,
|
||||
sort: null,
|
||||
collapse: "open",
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -84,9 +95,7 @@ async function createCollectionAndFields() {
|
||||
);
|
||||
console.log(`✅ Collection ${collectionName} created.`);
|
||||
} catch {
|
||||
console.log(
|
||||
`ℹ️ Collection ${collectionName} already exists or error occured.`,
|
||||
);
|
||||
console.log(`ℹ️ Collection ${collectionName} exists.`);
|
||||
}
|
||||
|
||||
const safeAddField = async (
|
||||
@@ -102,14 +111,40 @@ async function createCollectionAndFields() {
|
||||
}
|
||||
};
|
||||
|
||||
await safeAddField("name", "string", { interface: "input" });
|
||||
await safeAddField("email", "string", { interface: "input" });
|
||||
await safeAddField("company", "string", { interface: "input" });
|
||||
await safeAddField("message", "text", { interface: "textarea" });
|
||||
await safeAddField("name", "string", {
|
||||
interface: "input",
|
||||
display: "raw",
|
||||
width: "half",
|
||||
});
|
||||
await safeAddField("email", "string", {
|
||||
interface: "input",
|
||||
display: "raw",
|
||||
width: "half",
|
||||
});
|
||||
await safeAddField("company", "string", {
|
||||
interface: "input",
|
||||
display: "raw",
|
||||
width: "half",
|
||||
});
|
||||
await safeAddField("message", "text", {
|
||||
interface: "textarea",
|
||||
display: "raw",
|
||||
width: "full",
|
||||
});
|
||||
await safeAddField("date_created", "timestamp", {
|
||||
interface: "datetime",
|
||||
special: ["date-created"],
|
||||
display: "datetime",
|
||||
display_options: { relative: true },
|
||||
width: "half",
|
||||
});
|
||||
}
|
||||
|
||||
setupBranding();
|
||||
setupBranding()
|
||||
.then(() => {
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("🚨 Fatal bootstrap error:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Mintel Directus Sync Engine
|
||||
# Synchronizes Directus Data (Postgres + Uploads) between Local and Remote
|
||||
|
||||
# Configuration
|
||||
REMOTE_HOST="${SSH_HOST:-root@alpha.mintel.me}"
|
||||
ACTION=$1
|
||||
ENV=$2
|
||||
|
||||
# Help
|
||||
if [ -z "$ACTION" ] || [ -z "$ENV" ]; then
|
||||
echo "Usage: mintel-sync [push|pull] [testing|staging|production]"
|
||||
echo "Usage: ./scripts/sync-directus.sh [push|pull] [testing|staging|production]"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " push Sync LOCAL data -> REMOTE"
|
||||
@@ -20,7 +18,10 @@ if [ -z "$ACTION" ] || [ -z "$ENV" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PRJ_ID=$(jq -r .name package.json | sed 's/@mintel\///')
|
||||
# Project Configuration (extracted from package.json and aligned with deploy.yml)
|
||||
PRJ_ID=$(jq -r .name package.json | sed 's/@mintel\///' | sed 's/\.com$//')
|
||||
REMOTE_DIR="/home/deploy/sites/${PRJ_ID}.com"
|
||||
|
||||
case $ENV in
|
||||
testing) PROJECT_NAME="${PRJ_ID}-testing"; ENV_FILE=".env.testing" ;;
|
||||
staging) PROJECT_NAME="${PRJ_ID}-staging"; ENV_FILE=".env.staging" ;;
|
||||
@@ -28,41 +29,90 @@ case $ENV in
|
||||
*) echo "❌ Invalid environment: $ENV"; exit 1 ;;
|
||||
esac
|
||||
|
||||
REMOTE_DIR="/home/deploy/sites/${PRJ_ID}.com"
|
||||
|
||||
# DB Details
|
||||
# DB Details (matching docker-compose defaults)
|
||||
DB_USER="directus"
|
||||
DB_NAME="directus"
|
||||
|
||||
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. Running?"
|
||||
echo "❌ Local directus-db container not found. Is it running? (npm run dev)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$ACTION" == "push" ]; then
|
||||
echo "🚀 Pushing LOCAL -> $ENV ($PROJECT_NAME)..."
|
||||
|
||||
# 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-db")
|
||||
|
||||
if [ -z "$REMOTE_DB_CONTAINER" ]; then
|
||||
echo "❌ Remote $ENV-db container not found!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
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/directus/uploads/"
|
||||
|
||||
# Clean up
|
||||
rm dump.sql
|
||||
ssh "$REMOTE_HOST" "rm $REMOTE_DIR/dump.sql"
|
||||
echo "✨ Push complete!"
|
||||
|
||||
# 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 -> LOCAL..."
|
||||
echo "📥 Pulling $ENV Data -> 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-db")
|
||||
|
||||
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
|
||||
|
||||
# 3. Restore Locally
|
||||
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/directus/uploads/" ./directus/uploads/
|
||||
|
||||
# Clean up
|
||||
rm dump.sql
|
||||
ssh "$REMOTE_HOST" "rm $REMOTE_DIR/dump.sql"
|
||||
echo "✨ Pull complete!"
|
||||
|
||||
echo "✨ Pull to Local complete!"
|
||||
fi
|
||||
|
||||
|
||||
Reference in New Issue
Block a user