diff --git a/.github/workflows/build-base-images.yml b/.github/workflows/build-base-images.yml deleted file mode 100644 index 95c2463..0000000 --- a/.github/workflows/build-base-images.yml +++ /dev/null @@ -1,177 +0,0 @@ -name: Build Base Images - -on: - workflow_call: - inputs: - registry: - description: "Target registry" - type: string - required: true - - org: - description: "Target organization" - type: string - required: true - - push_images: - description: "Push images instead of building locally" - type: boolean - default: true - - dockerhub_namespace: - description: "Docker Hub namespace (user/org)" - type: string - default: "cloudneutral" - - workflow_dispatch: - inputs: - registry: - description: "Target registry" - type: string - default: "ghcr.io" - org: - description: "Target organization" - type: string - default: "cloud-neutral-toolkit" - push_images: - description: "Push images instead of building locally" - type: boolean - default: true - dockerhub_namespace: - description: "Docker Hub namespace (user/org)" - type: string - default: "cloudneutral" - - push: - paths: - - "deploy/base-images/**" - -permissions: - contents: read - packages: write - id-token: write - -env: - REGISTRY: ${{ inputs.registry || github.event.inputs.registry || 'ghcr.io' }} - ORG: ${{ inputs.org || github.event.inputs.org || 'cloud-neutral-toolkit' }} - - # Push control - PUSH_IMAGES: ${{ github.event_name == 'push' - || (github.event_name == 'workflow_call' && inputs.push_images) - || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true') }} - -jobs: - build: - strategy: - matrix: - image: - - { name: openresty-geoip, file: deploy/base-images/openresty-geoip.Dockerfile } - - { name: postgres-runtime, file: deploy/base-images/postgres-runtime-wth-extensions.Dockerfile } - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Generate Auto Tags - id: meta - uses: ./.github/actions/auto-tag - with: - image: ${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.image.name }} - - - uses: docker/setup-qemu-action@v3 - - - uses: docker/setup-buildx-action@v3 - - - uses: docker/build-push-action@v6 - id: build - with: - context: . - file: ${{ matrix.image.file }} - platforms: linux/amd64,linux/arm64 - push: ${{ (github.event_name == 'workflow_call' || github.event_name == 'workflow_dispatch') && inputs.push_images || github.event_name == 'push' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - - # ------------------------------------------------------------- - # Push to Docker Hub (optional) - # ------------------------------------------------------------- - - name: Login to Docker Hub - if: env.PUSH_IMAGES == 'true' - uses: docker/login-action@v3 - with: - registry: docker.io - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - # ------------------------------------------------------------- - # Re-tag & Push service image to Docker Hub - # ------------------------------------------------------------- - - name: Re-tag & Push Image (Docker Hub) - if: env.PUSH_IMAGES == 'true' - env: - TARGET_NS: ${{ inputs.dockerhub_namespace || github.event.inputs.dockerhub_namespace || 'cloudneutral' }} - run: | - set -euo pipefail - - SERVICE="${{ matrix.image.name }}" - ORIGIN_IMG="${{ env.REGISTRY }}/${{ env.ORG }}/${SERVICE}@${{ steps.build.outputs.digest }}" - TARGET_REPO="docker.io/${TARGET_NS}/${SERVICE}" - - TAG="latest" - docker pull "$ORIGIN_IMG" - docker tag "$ORIGIN_IMG" "$TARGET_REPO:$TAG" - docker push "$TARGET_REPO:$TAG" - - Security-service: - runs-on: ubuntu-latest - needs: build - - strategy: - matrix: - image: - - { name: openresty-geoip, file: deploy/base-images/openresty-geoip.Dockerfile } - - { name: postgres-runtime, file: deploy/base-images/postgres-runtime-wth-extensions.Dockerfile } - - steps: - # ------------------------------------------------------------- - # Checkout source - # ------------------------------------------------------------- - - uses: actions/checkout@v4 - - - - uses: anchore/sbom-action@v0 - with: - image: ${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.image.name }}@${{ steps.build.outputs.digest }} - output-file: sbom.spdx.json - - - uses: actions/upload-artifact@v4 - with: - name: sbom-${{ matrix.image.name }} - path: sbom.spdx.json - - # ------------------------------------------------------------- - # Trivy Vulnerability Scan - # ------------------------------------------------------------- - - uses: aquasecurity/trivy-action@0.28.0 - with: - image-ref: ${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.image.name }}@${{ steps.build.outputs.digest }} - severity: HIGH,CRITICAL - exit-code: '1' - - - uses: sigstore/cosign-installer@v3 - with: - cosign-release: 'v2.4.1' - - - name: Sign Image - env: - COSIGN_EXPERIMENTAL: "true" - run: | - COSIGN_IMAGE=${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.image.name }}@${{ steps.build.outputs.digest }} - cosign sign --yes "$COSIGN_IMAGE" diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-postgres.yml similarity index 100% rename from .github/workflows/build-image.yml rename to .github/workflows/build-postgres.yml diff --git a/.github/workflows/build-service-images.yml b/.github/workflows/build-service-images.yml deleted file mode 100644 index 1593e67..0000000 --- a/.github/workflows/build-service-images.yml +++ /dev/null @@ -1,221 +0,0 @@ -name: Build Service Images - -on: - workflow_call: - inputs: - push_images: - description: "Push service images instead of local builds" - type: boolean - default: true - - dockerhub_namespace: - description: "Docker Hub namespace (user/org)" - type: string - - # Base image references (full image URL) - node_builder_image: - type: string - default: "node:22-bookworm" - - node_runtime_image: - type: string - default: "node:22-slim" - - go_runtime_image: - type: string - default: "golang:1.25" - - workflow_dispatch: - inputs: - push_images: - type: boolean - default: true - - dockerhub_namespace: - description: "Docker Hub namespace (user/org)" - type: string - default: "cloudneutral" - - node_builder_image: - type: string - default: "node:22-bookworm" - - node_runtime_image: - type: string - default: "node:22-slim" - - go_runtime_image: - type: string - default: "golang:1.25" - - push: - branches: [ main ] - paths: - - "account/**" - - "dashboard/**" - - "rag-server/**" - - "xcontrol-init/**" - -permissions: - contents: read - packages: write - id-token: write - -env: - REGISTRY: ghcr.io - ORG: cloud-neutral-toolkit - - # Base image references (tag or digest) - GO_RUNTIME_IMAGE: ${{ inputs.go_runtime_image || github.event.inputs.go_runtime_image || 'golang:1.25' }} - NODE_BUILDER_IMAGE: ${{ inputs.node_builder_image || github.event.inputs.node_builder_image || 'node:22-bookworm' }} - NODE_RUNTIME_IMAGE: ${{ inputs.node_runtime_image || github.event.inputs.node_runtime_image || 'node:22-slim' }} - - - # Push control - PUSH_IMAGES: ${{ github.event_name == 'push' - || (github.event_name == 'workflow_call' && inputs.push_images) - || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true') }} - -jobs: - build: - runs-on: ubuntu-latest - - strategy: - matrix: - service: - - { name: account, workdir: account, dockerfile: account/Dockerfile } - - { name: dashboard, workdir: dashboard, dockerfile: dashboard/Dockerfile } - - { name: rag-server, workdir: rag-server, dockerfile: rag-server/Dockerfile } - - { name: xcontrol-init, workdir: ., dockerfile: xcontrol-init/Dockerfile } - - steps: - # ------------------------------------------------------------- - # Checkout source - # ------------------------------------------------------------- - - uses: actions/checkout@v4 - - # ------------------------------------------------------------- - # Login to GHCR - # ------------------------------------------------------------- - - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - # ------------------------------------------------------------- - # Auto Tag - # ------------------------------------------------------------- - - name: Generate Auto Tags - id: meta - uses: ./.github/actions/auto-tag - with: - image: ${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.service.name }} - - # ------------------------------------------------------------- - # Docker Buildx setup - # ------------------------------------------------------------- - - uses: docker/setup-qemu-action@v3 - - uses: docker/setup-buildx-action@v3 - - # ------------------------------------------------------------- - # Build service image - # ------------------------------------------------------------- - - name: Build & Push Service Image - id: build - uses: docker/build-push-action@v6 - with: - context: ${{ matrix.service.workdir }} - file: ${{ matrix.service.dockerfile }} - platforms: linux/amd64,linux/arm64 - push: ${{ env.PUSH_IMAGES == 'true' || env.PUSH_IMAGES == true }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - build-args: | - GO_RUNTIME_IMAGE=${{ env.GO_RUNTIME_IMAGE }} - NODE_BUILDER_IMAGE=${{ env.NODE_BUILDER_IMAGE }} - NODE_RUNTIME_IMAGE=${{ env.NODE_RUNTIME_IMAGE }} - - # ------------------------------------------------------------- - # Push to Docker Hub (optional) - # ------------------------------------------------------------- - - name: Login to Docker Hub - if: env.PUSH_IMAGES == 'true' - uses: docker/login-action@v3 - with: - registry: docker.io - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - # ------------------------------------------------------------- - # Re-tag & Push image to Docker Hub - # ------------------------------------------------------------- - - name: Re-tag & Push Service Image (Docker Hub) - if: env.PUSH_IMAGES == 'true' - env: - TARGET_NS: ${{ inputs.dockerhub_namespace || github.event.inputs.dockerhub_namespace || 'cloudneutral' }} - run: | - set -euo pipefail - - SERVICE="${{ matrix.service.name }}" - ORIGIN_IMG="${{ env.REGISTRY }}/${{ env.ORG }}/${SERVICE}@${{ steps.build.outputs.digest }}" - TARGET_REPO="docker.io/${TARGET_NS}/${SERVICE}" - - TAG="latest" - docker pull "$ORIGIN_IMG" - docker tag "$ORIGIN_IMG" "$TARGET_REPO:$TAG" - docker push "$TARGET_REPO:$TAG" - - Security: - runs-on: ubuntu-latest - needs: build - - strategy: - matrix: - service: - - { name: dashboard, workdir: dashboard, dockerfile: dashboard/Dockerfile } - - { name: account, workdir: account, dockerfile: account/Dockerfile } - - { name: rag-server, workdir: rag-server, dockerfile: rag-server/Dockerfile } - - { name: xcontrol-init, workdir: ., dockerfile: xcontrol-init/Dockerfile } - - steps: - # ------------------------------------------------------------- - # Checkout source - # ------------------------------------------------------------- - - uses: actions/checkout@v4 - - # ------------------------------------------------------------- - # SBOM Generation - # ------------------------------------------------------------- - - uses: anchore/sbom-action@v0 - with: - image: ${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.service.name }}@${{ steps.build.outputs.digest }} - output-file: sbom.spdx.json - - - uses: actions/upload-artifact@v4 - with: - name: sbom-${{ matrix.service.name }} - path: sbom.spdx.json - - # ------------------------------------------------------------- - # Trivy Vulnerability Scan - # ------------------------------------------------------------- - - uses: aquasecurity/trivy-action@0.28.0 - with: - image-ref: ${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.service.name }}@${{ steps.build.outputs.digest }} - severity: HIGH,CRITICAL - exit-code: '1' - - # ------------------------------------------------------------- - # Cosign Signing - # ------------------------------------------------------------- - - uses: sigstore/cosign-installer@v3 - with: - cosign-release: 'v2.4.1' - - - name: Cosign Sign Image - env: - COSIGN_EXPERIMENTAL: "true" - run: | - IMG=${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.service.name }}@${{ steps.build.outputs.digest }} - cosign sign --yes "$IMG" diff --git a/.github/workflows/check-xcontrol-image.yaml b/.github/workflows/check-xcontrol-image.yaml deleted file mode 100644 index cac921e..0000000 --- a/.github/workflows/check-xcontrol-image.yaml +++ /dev/null @@ -1,49 +0,0 @@ -name: Check XControl Image Ready - -on: - workflow_dispatch: - inputs: - tag: - required: false - default: latest - -permissions: - contents: read - packages: read - -jobs: - check: - runs-on: ubuntu-latest - steps: - - name: Authenticate to GHCR - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - echo "$GITHUB_TOKEN" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin - - - name: Check images exist and are pullable - env: - TAG: ${{ inputs.tag }} - run: | - set -euo pipefail - - IMAGES=( - "ghcr.io/cloud-neutral-toolkit/openresty-geoip" - "ghcr.io/cloud-neutral-toolkit/postgres-runtime" - "ghcr.io/cloud-neutral-toolkit/account" - "ghcr.io/cloud-neutral-toolkit/dashboard" - "ghcr.io/cloud-neutral-toolkit/rag-server" - "ghcr.io/cloud-neutral-toolkit/xcontrol-init" - "docker.io/cloudneutral/openresty-geoip" - "docker.io/cloudneutral/postgres-runtime" - "docker.io/cloudneutral/account" - "docker.io/cloudneutral/dashboard" - "docker.io/cloudneutral/rag-server" - "docker.io/cloudneutral/xcontrol-init" - ) - - for IMAGE in "${IMAGES[@]}"; do - echo "Checking ${IMAGE}:${TAG}" - docker manifest inspect "${IMAGE}:${TAG}" > /dev/null - docker pull "${IMAGE}:${TAG}" > /dev/null - done diff --git a/.github/workflows/deploy-k8s.yml b/.github/workflows/deploy-k8s.yml deleted file mode 100644 index 37c4577..0000000 --- a/.github/workflows/deploy-k8s.yml +++ /dev/null @@ -1,226 +0,0 @@ -name: Deploy to Kubernetes - -on: - workflow_dispatch: - inputs: - environment: - description: 'Deployment environment' - required: true - type: choice - options: - - development - - staging - - production - cluster_type: - description: 'Cluster type' - required: true - type: choice - options: - - k8s - - k3s - namespace: - description: 'Kubernetes namespace' - required: false - default: 'postgresql' - release_name: - description: 'Helm release name' - required: false - default: 'postgresql' - enable_stunnel: - description: 'Enable Stunnel sidecar' - required: false - type: boolean - default: true - enable_metrics: - description: 'Enable Prometheus metrics' - required: false - type: boolean - default: false - -env: - HELM_VERSION: v3.13.0 - -jobs: - deploy: - runs-on: ubuntu-latest - environment: ${{ github.event.inputs.environment }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Helm - uses: azure/setup-helm@v3 - with: - version: ${{ env.HELM_VERSION }} - - - name: Setup kubectl - uses: azure/setup-kubectl@v3 - with: - version: 'latest' - - - name: Configure kubeconfig - run: | - mkdir -p ~/.kube - echo "${{ secrets.KUBECONFIG }}" | base64 -d > ~/.kube/config - chmod 600 ~/.kube/config - - - name: Verify cluster connection - run: | - kubectl cluster-info - kubectl get nodes - - - name: Create namespace - run: | - kubectl create namespace ${{ github.event.inputs.namespace }} --dry-run=client -o yaml | kubectl apply -f - - - - name: Create secrets - run: | - # PostgreSQL password - kubectl create secret generic postgresql-secret \ - --from-literal=password='${{ secrets.POSTGRES_PASSWORD }}' \ - --namespace=${{ github.event.inputs.namespace }} \ - --dry-run=client -o yaml | kubectl apply -f - - - # Stunnel certificates (if enabled) - if [ "${{ github.event.inputs.enable_stunnel }}" == "true" ]; then - if [ -n "${{ secrets.STUNNEL_CERT }}" ] && [ -n "${{ secrets.STUNNEL_KEY }}" ]; then - kubectl create secret generic stunnel-certs \ - --from-literal=server-cert.pem='${{ secrets.STUNNEL_CERT }}' \ - --from-literal=server-key.pem='${{ secrets.STUNNEL_KEY }}' \ - --namespace=${{ github.event.inputs.namespace }} \ - --dry-run=client -o yaml | kubectl apply -f - - fi - fi - - - name: Create values file - run: | - cat > custom-values.yaml << EOF - image: - repository: ghcr.io/${{ github.repository }}/postgres-extensions - tag: latest - pullPolicy: Always - - auth: - existingSecret: postgresql-secret - secretKey: password - username: postgres - database: postgres - - persistence: - enabled: true - size: ${{ secrets.PVC_SIZE || '10Gi' }} - storageClass: ${{ secrets.STORAGE_CLASS || '' }} - - resources: - requests: - memory: ${{ secrets.MEMORY_REQUEST || '1Gi' }} - cpu: ${{ secrets.CPU_REQUEST || '500m' }} - limits: - memory: ${{ secrets.MEMORY_LIMIT || '2Gi' }} - cpu: ${{ secrets.CPU_LIMIT || '2000m' }} - - stunnel: - enabled: ${{ github.event.inputs.enable_stunnel }} - port: 5433 - certificatesSecret: stunnel-certs - - metrics: - enabled: ${{ github.event.inputs.enable_metrics }} - service: - annotations: - prometheus.io/scrape: "true" - prometheus.io/port: "9187" - - postgresql: - config: | - shared_buffers = 256MB - effective_cache_size = 1GB - work_mem = 16MB - maintenance_work_mem = 64MB - max_connections = 100 - wal_buffers = 16MB - checkpoint_completion_target = 0.9 - random_page_cost = 1.1 - effective_io_concurrency = 200 - log_min_duration_statement = 1000 - - initScripts: - enabled: true - scripts: - 01-init-extensions.sql: | - CREATE EXTENSION IF NOT EXISTS vector; - CREATE EXTENSION IF NOT EXISTS pg_jieba; - CREATE EXTENSION IF NOT EXISTS pgmq; - CREATE EXTENSION IF NOT EXISTS pg_trgm; - CREATE EXTENSION IF NOT EXISTS hstore; - CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; - EOF - - - name: Deploy with Helm - run: | - helm upgrade --install ${{ github.event.inputs.release_name }} \ - ./deploy/helm/postgresql \ - --namespace=${{ github.event.inputs.namespace }} \ - --values=custom-values.yaml \ - --wait \ - --timeout=10m - - - name: Verify deployment - run: | - echo "=== Pods ===" - kubectl get pods -n ${{ github.event.inputs.namespace }} -l app.kubernetes.io/name=postgresql - - echo "=== Services ===" - kubectl get svc -n ${{ github.event.inputs.namespace }} -l app.kubernetes.io/name=postgresql - - echo "=== PVC ===" - kubectl get pvc -n ${{ github.event.inputs.namespace }} - - echo "=== Waiting for PostgreSQL to be ready ===" - kubectl wait --for=condition=ready pod \ - -l app.kubernetes.io/name=postgresql \ - -n ${{ github.event.inputs.namespace }} \ - --timeout=300s - - - name: Test database connection - run: | - POD_NAME=$(kubectl get pods -n ${{ github.event.inputs.namespace }} \ - -l app.kubernetes.io/name=postgresql \ - -o jsonpath='{.items[0].metadata.name}') - - echo "=== Testing PostgreSQL ===" - kubectl exec -n ${{ github.event.inputs.namespace }} $POD_NAME -- \ - pg_isready -U postgres - - echo "=== Checking extensions ===" - kubectl exec -n ${{ github.event.inputs.namespace }} $POD_NAME -- \ - psql -U postgres -c "\dx" - - - name: Get connection info - run: | - echo "=== Connection Information ===" - echo "Namespace: ${{ github.event.inputs.namespace }}" - echo "Release: ${{ github.event.inputs.release_name }}" - echo "" - echo "Internal DNS:" - echo " postgresql.${{ github.event.inputs.namespace }}.svc.cluster.local:5432" - - if [ "${{ github.event.inputs.enable_stunnel }}" == "true" ]; then - echo "" - echo "Stunnel TLS endpoint:" - echo " postgresql.${{ github.event.inputs.namespace }}.svc.cluster.local:5433" - fi - - echo "" - echo "Port forward command:" - echo " kubectl port-forward -n ${{ github.event.inputs.namespace }} svc/postgresql 5432:5432" - - if [ "${{ github.event.inputs.enable_stunnel }}" == "true" ]; then - echo " kubectl port-forward -n ${{ github.event.inputs.namespace }} svc/postgresql 5433:5433" - fi - - - name: Cleanup kubeconfig - if: always() - run: | - rm -f ~/.kube/config diff --git a/.github/workflows/deploy-vm.yml b/.github/workflows/deploy-vm.yml deleted file mode 100644 index 7d7cff0..0000000 --- a/.github/workflows/deploy-vm.yml +++ /dev/null @@ -1,188 +0,0 @@ -name: Deploy to VM - -on: - workflow_dispatch: - inputs: - environment: - description: 'Deployment environment' - required: true - type: choice - options: - - development - - staging - - production - deploy_mode: - description: 'Deployment mode' - required: true - type: choice - options: - - basic - - nginx-certbot - - caddy - - stunnel - - full - vm_host: - description: 'VM hostname or IP' - required: true - postgres_password: - description: 'PostgreSQL password (use secrets in production!)' - required: true - -env: - DEPLOY_USER: deploy - DEPLOY_PATH: /opt/postgresql-svc-plus - -jobs: - deploy: - runs-on: ubuntu-latest - environment: ${{ github.event.inputs.environment }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup SSH - run: | - mkdir -p ~/.ssh - echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/deploy_key - chmod 600 ~/.ssh/deploy_key - ssh-keyscan -H ${{ github.event.inputs.vm_host }} >> ~/.ssh/known_hosts - - - name: Copy files to VM - run: | - ssh -i ~/.ssh/deploy_key ${{ env.DEPLOY_USER }}@${{ github.event.inputs.vm_host }} \ - "mkdir -p ${{ env.DEPLOY_PATH }}" - - scp -i ~/.ssh/deploy_key -r \ - deploy/docker/* \ - ${{ env.DEPLOY_USER }}@${{ github.event.inputs.vm_host }}:${{ env.DEPLOY_PATH }}/ - - - name: Deploy - Basic Mode - if: github.event.inputs.deploy_mode == 'basic' - run: | - ssh -i ~/.ssh/deploy_key ${{ env.DEPLOY_USER }}@${{ github.event.inputs.vm_host }} << 'EOF' - cd ${{ env.DEPLOY_PATH }} - - # Create .env file - cat > .env << ENVEOF - POSTGRES_PASSWORD=${{ github.event.inputs.postgres_password }} - POSTGRES_USER=postgres - POSTGRES_DB=postgres - ENVEOF - - # Pull latest image - docker-compose pull - - # Start services - docker-compose up -d - - # Wait for PostgreSQL - sleep 10 - docker-compose exec -T postgres pg_isready -U postgres - EOF - - - name: Deploy - Nginx + Certbot - if: github.event.inputs.deploy_mode == 'nginx-certbot' - run: | - ssh -i ~/.ssh/deploy_key ${{ env.DEPLOY_USER }}@${{ github.event.inputs.vm_host }} << 'EOF' - cd ${{ env.DEPLOY_PATH }} - - # Create .env file - cat > .env << ENVEOF - POSTGRES_PASSWORD=${{ github.event.inputs.postgres_password }} - DOMAIN=${{ secrets.DOMAIN }} - EMAIL=${{ secrets.CERTBOT_EMAIL }} - ENVEOF - - # Generate certificates - chmod +x generate-certs.sh init-letsencrypt.sh - ./generate-certs.sh - - # Initialize Let's Encrypt (if domain is configured) - if [ -n "${{ secrets.DOMAIN }}" ]; then - DOMAIN=${{ secrets.DOMAIN }} EMAIL=${{ secrets.CERTBOT_EMAIL }} ./init-letsencrypt.sh - fi - - # Start services - docker-compose -f docker-compose.yml -f docker-compose.nginx.yml up -d - EOF - - - name: Deploy - Caddy - if: github.event.inputs.deploy_mode == 'caddy' - run: | - ssh -i ~/.ssh/deploy_key ${{ env.DEPLOY_USER }}@${{ github.event.inputs.vm_host }} << 'EOF' - cd ${{ env.DEPLOY_PATH }} - - cat > .env << ENVEOF - POSTGRES_PASSWORD=${{ github.event.inputs.postgres_password }} - CADDY_DOMAIN=${{ secrets.DOMAIN }} - CADDY_EMAIL=${{ secrets.CERTBOT_EMAIL }} - ENVEOF - - docker-compose -f docker-compose.yml -f docker-compose.caddy.yml up -d - EOF - - - name: Deploy - Stunnel - if: github.event.inputs.deploy_mode == 'stunnel' - run: | - ssh -i ~/.ssh/deploy_key ${{ env.DEPLOY_USER }}@${{ github.event.inputs.vm_host }} << 'EOF' - cd ${{ env.DEPLOY_PATH }} - - cat > .env << ENVEOF - POSTGRES_PASSWORD=${{ github.event.inputs.postgres_password }} - STUNNEL_PORT=5433 - ENVEOF - - chmod +x generate-certs.sh - ./generate-certs.sh - - docker-compose -f docker-compose.yml -f docker-compose.tunnel.yml up -d - EOF - - - name: Deploy - Full Stack - if: github.event.inputs.deploy_mode == 'full' - run: | - ssh -i ~/.ssh/deploy_key ${{ env.DEPLOY_USER }}@${{ github.event.inputs.vm_host }} << 'EOF' - cd ${{ env.DEPLOY_PATH }} - - cat > .env << ENVEOF - POSTGRES_PASSWORD=${{ github.event.inputs.postgres_password }} - DOMAIN=${{ secrets.DOMAIN }} - EMAIL=${{ secrets.CERTBOT_EMAIL }} - STUNNEL_PORT=5433 - ENVEOF - - chmod +x generate-certs.sh init-letsencrypt.sh - ./generate-certs.sh - - if [ -n "${{ secrets.DOMAIN }}" ]; then - DOMAIN=${{ secrets.DOMAIN }} EMAIL=${{ secrets.CERTBOT_EMAIL }} ./init-letsencrypt.sh - fi - - docker-compose \ - -f docker-compose.yml \ - -f docker-compose.nginx.yml \ - -f docker-compose.tunnel.yml \ - --profile admin \ - up -d - EOF - - - name: Verify deployment - run: | - ssh -i ~/.ssh/deploy_key ${{ env.DEPLOY_USER }}@${{ github.event.inputs.vm_host }} << 'EOF' - cd ${{ env.DEPLOY_PATH }} - - echo "=== Container Status ===" - docker-compose ps - - echo "=== PostgreSQL Health ===" - docker-compose exec -T postgres pg_isready -U postgres || true - - echo "=== Extension Check ===" - docker-compose exec -T postgres psql -U postgres -c "\dx" || true - EOF - - - name: Cleanup - if: always() - run: | - rm -f ~/.ssh/deploy_key diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml deleted file mode 100644 index be1b85a..0000000 --- a/.github/workflows/pipeline.yml +++ /dev/null @@ -1,115 +0,0 @@ -name: XControl Unified CI/CD Pipeline - -on: - push: - branches: [main] - pull_request: - branches: [main] - workflow_dispatch: - inputs: - environment: - description: "Target environment" - type: choice - options: [dev, prod] - default: dev - -permissions: - contents: read - packages: write - id-token: write - -jobs: - - # ------------------------------------------------------------- - # CI — Code Quality → Build → Test → Security - # ------------------------------------------------------------- - ci: - name: "CI • ${{ matrix.service }} @ ${{ matrix.platform }}" - runs-on: ubuntu-latest - - env: - ENVIRONMENT: dev - - strategy: - fail-fast: false - matrix: - platform: ["linux/amd64", "linux/arm64"] - service: ["dashboard", "rag-server", "account"] - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Code Quality - uses: ./.github/actions/code-quality - with: - environment: ${{ env.ENVIRONMENT }} - service: ${{ matrix.service }} - platform: ${{ matrix.platform }} - - - name: Build - uses: ./.github/actions/build - with: - environment: ${{ env.ENVIRONMENT }} - service: ${{ matrix.service }} - platform: ${{ matrix.platform }} - - - name: "Test • ${{ matrix.service }} @ ${{ matrix.platform }}" - uses: ./.github/actions/test - with: - environment: ${{ env.ENVIRONMENT }} - service: ${{ matrix.service }} - platform: ${{ matrix.platform }} - - - name: Security Check - uses: ./.github/actions/security - with: - environment: ${{ env.ENVIRONMENT }} - service: ${{ matrix.service }} - platform: ${{ matrix.platform }} - - build-base-images: - name: Build Base Images - needs: ci - uses: ./.github/workflows/build-base-images.yml - secrets: inherit - with: - registry: ghcr.io - org: cloud-neutral-toolkit - push_images: true - - build-service-images: - name: Build Service Images - needs: build-base-images - uses: ./.github/workflows/build-service-images.yml - secrets: inherit - with: - push_images: true - - # ------------------------------------------------------------- - # CD — Deploy(只在 workflow_dispatch 时跑) - # ------------------------------------------------------------- - cd: - name: "Deploy • ${{ matrix.service }} (${{ github.event.inputs.environment }})" - runs-on: ubuntu-latest - needs: build-service-images - if: github.event_name == 'workflow_dispatch' - - strategy: - fail-fast: false - matrix: - platform: ["linux/amd64"] - service: ["dashboard", "rag-server", "account"] - - env: - ENVIRONMENT: ${{ github.event.inputs.environment }} - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Deploy Services - uses: ./.github/actions/deploy - with: - environment: ${{ env.ENVIRONMENT }} - platform: ${{ matrix.platform }} - service: ${{ matrix.service }} diff --git a/Makefile b/Makefile index 06f2dc6..11ff30b 100644 --- a/Makefile +++ b/Makefile @@ -143,6 +143,10 @@ git-purge: fi @bash scripts/clean_git_history.sh $(PATHS) +test-local-mac: + @echo "🍎 Starting macOS local integration test..." + @bash test-cases/local/macos-client/run_test.sh + clean: @echo "🧹 Cleaning up test containers..." -@docker stop postgres-test 2>/dev/null || true diff --git a/deploy/base-images/postgres-runtime-wth-extensions.Dockerfile b/deploy/base-images/postgres-runtime-wth-extensions.Dockerfile index e089c63..125c500 100644 --- a/deploy/base-images/postgres-runtime-wth-extensions.Dockerfile +++ b/deploy/base-images/postgres-runtime-wth-extensions.Dockerfile @@ -73,9 +73,12 @@ ARG PG_VERSION LABEL maintainer="Cloud-Neutral Toolkit" \ description="PostgreSQL ${PG_VERSION} + pgvector + pg_jieba + pgmq (Debian 12 Bookworm Base)" +ARG CACHEBUST=1 + # Copy .so + extension files from builder COPY --from=builder /usr/lib/postgresql/${PG_MAJOR}/lib/ /usr/lib/postgresql/${PG_MAJOR}/lib/ COPY --from=builder /usr/share/postgresql/${PG_MAJOR}/extension/ /usr/share/postgresql/${PG_MAJOR}/extension/ +COPY --from=builder /usr/share/postgresql/${PG_MAJOR}/tsearch_data/ /usr/share/postgresql/${PG_MAJOR}/tsearch_data/ # Fix collation version mismatch warning automatically on startup # We simply create a small script that the official entrypoint will run diff --git a/deploy/docker/stunnel-mac.conf b/deploy/docker/stunnel-mac.conf new file mode 100644 index 0000000..410e52c --- /dev/null +++ b/deploy/docker/stunnel-mac.conf @@ -0,0 +1,14 @@ +; Stunnel configuration for macOS local testing +pid = /tmp/stunnel-mac.pid +output = /tmp/stunnel-mac.log +foreground = yes +debug = 5 + +[postgres-mac] +client = yes +accept = 127.0.0.1:15432 +connect = postgresql.onwalk.net:443 +verify = 2 +; Using system default CA bundle on macOS +CAfile = /etc/ssl/cert.pem +checkHost = postgresql.onwalk.net diff --git a/test-cases/local/macos-client/run_test.sh b/test-cases/local/macos-client/run_test.sh new file mode 100644 index 0000000..6d416ce --- /dev/null +++ b/test-cases/local/macos-client/run_test.sh @@ -0,0 +1,72 @@ +#!/bin/bash +set -e + +# Colors +GREEN='\033[0;32m' +RED='\033[0;31m' +NC='\033[0m' + +echo -e "${GREEN}🍎 macOS Local Integration Test${NC}" +echo "==============================" + +# Check requirements +if ! command -v stunnel &> /dev/null; then + echo -e "${RED}❌ stunnel not found. Please install: brew install stunnel${NC}" + exit 1 +fi +if ! command -v psql &> /dev/null; then + echo -e "${RED}❌ psql not found. Please install: brew install libpq${NC}" + exit 1 +fi + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +STUNNEL_CONF="$DIR/stunnel.conf" +SQL_FILE="$DIR/verify_extensions.sql" + +# Check if port 15432 is free +if lsof -i :15432 >/dev/null; then + echo -e "${RED}❌ Port 15432 is already in use. Please free it first.${NC}" + exit 1 +fi + +echo "🚀 Starting Stunnel..." +stunnel "$STUNNEL_CONF" > /tmp/stunnel-test-stdout.log 2>&1 & +STUNNEL_PID=$! +echo "📝 Stunnel PID: $STUNNEL_PID" + +cleanup() { + echo "" + echo "🛑 Stopping Stunnel..." + kill $STUNNEL_PID 2>/dev/null || true + echo "🧹 Done." +} +trap cleanup EXIT + +echo "⏳ Waiting for tunnel to initialize (2s)..." +sleep 2 + +# Check if process is still running +if ! ps -p $STUNNEL_PID > /dev/null; then + echo -e "${RED}❌ Stunnel failed to start. Check logs:${NC}" + cat /tmp/stunnel-test-mac.log + exit 1 +fi + +echo "🧪 Running SQL Tests..." +echo "Target: localhost:15432 -> postgresql.onwalk.net:443" + +# Default password if not set +export PGPASSWORD=${PGPASSWORD:-otdcRLTJamszk3AE} +export PGHOST=127.0.0.1 +export PGPORT=15432 +export PGUSER=postgres +export PGDATABASE=postgres + +if psql -f "$SQL_FILE"; then + echo "" + echo -e "${GREEN}✅ ALL TESTS PASSED SUCCESSFULLY!${NC}" +else + echo "" + echo -e "${RED}❌ TESTS FAILED.${NC}" + exit 1 +fi diff --git a/test-cases/local/macos-client/stunnel.conf b/test-cases/local/macos-client/stunnel.conf new file mode 100644 index 0000000..71e2312 --- /dev/null +++ b/test-cases/local/macos-client/stunnel.conf @@ -0,0 +1,14 @@ +; Stunnel configuration for macOS local testing +pid = /tmp/stunnel-test-mac.pid +output = /tmp/stunnel-test-mac.log +foreground = yes +debug = 5 + +[postgres-mac-test] +client = yes +accept = 127.0.0.1:15432 +connect = postgresql.onwalk.net:443 +verify = 2 +; Using system default CA bundle on macOS +CAfile = /etc/ssl/cert.pem +checkHost = postgresql.onwalk.net