# ============================================================================= # GitLab CI/CD Pipeline — STEAM Security Dashboard # ============================================================================= # # Pipeline stages: # 1. install — install dependencies for backend and frontend # 2. lint — run linters / static checks # 3. test — run backend (Jest) and frontend (react-scripts) tests # 4. build — produce the production frontend bundle # 5. deploy — deploy to staging (local) or production (SSH to 71.85.90.6) # 6. verify — post-deploy health checks # # Environments: # staging — dashboard-dev:3100 (auto-deploy on main/master) # production — 71.85.90.6:3001 (manual trigger, requires staging verification) # # Executor: shell (runs on dashboard-dev using system Node.js) # ============================================================================= # --------------------------------------------------------------------------- # Variables # --------------------------------------------------------------------------- variables: PROD_HOST: "71.85.90.6" PROD_USER: "root" PROD_DIR: "/home/cve-dashboard" STAGING_DIR: "/home/cve-dashboard-staging" # --------------------------------------------------------------------------- # Global cache — persists node_modules between pipeline runs # --------------------------------------------------------------------------- cache: key: ${CI_COMMIT_REF_SLUG} paths: - node_modules/ - frontend/node_modules/ policy: pull # --------------------------------------------------------------------------- # Stages # --------------------------------------------------------------------------- stages: - install - lint - test - build - deploy - verify # ============================================================================= # STAGE 1: Install dependencies # ============================================================================= install-backend: stage: install script: - npm ci --prefer-offline cache: key: ${CI_COMMIT_REF_SLUG} paths: - node_modules/ policy: pull-push install-frontend: stage: install script: - cd frontend && npm ci --prefer-offline cache: key: ${CI_COMMIT_REF_SLUG} paths: - frontend/node_modules/ policy: pull-push # ============================================================================= # STAGE 2: Lint / static analysis # ============================================================================= lint-frontend: stage: lint script: - cd frontend && npx eslint src/ --max-warnings 0 needs: - install-frontend lint-backend: stage: lint script: - node -c backend/server.js - node -c backend/routes/*.js - node -c backend/helpers/*.js - node -c backend/middleware/*.js needs: - install-backend # ============================================================================= # STAGE 3: Tests # ============================================================================= test-backend: stage: test script: - npx jest --ci --forceExit backend/__tests__/ timeout: 5 minutes needs: - install-backend test-frontend: stage: test script: - cd frontend && CI=true npx react-scripts test --watchAll=false --ci --forceExit timeout: 5 minutes needs: - install-frontend # ============================================================================= # STAGE 4: Build # ============================================================================= build-frontend: stage: build script: - cd frontend && CI=false REACT_APP_API_BASE=/api REACT_APP_API_HOST="" npm run build artifacts: paths: - frontend/build/ expire_in: 7 days needs: - test-frontend - lint-frontend # ============================================================================= # STAGE 5: Deploy # ============================================================================= # --------------------------------------------------------------------------- # Staging — auto-deploys on main/master to dashboard-dev:3100 # --------------------------------------------------------------------------- deploy-staging: stage: deploy rules: - if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "master" when: on_success environment: name: staging url: http://localhost:3100 script: - echo "Deploying to staging (dashboard-dev:3100)..." # Ensure staging directory exists - mkdir -p ${STAGING_DIR} # Sync code (exclude .git, node_modules, uploads, logs) - rsync -a --delete --exclude='.git' --exclude='node_modules' --exclude='frontend/node_modules' --exclude='frontend/build' --exclude='backend/uploads' --exclude='*.log' --exclude='*.db' --exclude='.env' ${CI_PROJECT_DIR}/ ${STAGING_DIR}/ # Copy built frontend - cp -r ${CI_PROJECT_DIR}/frontend/build ${STAGING_DIR}/frontend/build # Install deps in staging - cd ${STAGING_DIR} && npm ci --prefer-offline - cd ${STAGING_DIR}/frontend && npm ci --prefer-offline # Ensure staging .env exists - | if [ ! -f "${STAGING_DIR}/backend/.env" ]; then cp ${CI_PROJECT_DIR}/backend/.env ${STAGING_DIR}/backend/.env sed -i 's/^PORT=.*/PORT=3100/' ${STAGING_DIR}/backend/.env grep -q "^PORT=" ${STAGING_DIR}/backend/.env || echo "PORT=3100" >> ${STAGING_DIR}/backend/.env fi # Restart staging service - sudo systemctl restart cve-backend-staging || sudo systemctl start cve-backend-staging || true - echo "Staging deploy complete." needs: - build-frontend - test-backend # --------------------------------------------------------------------------- # Production — manual trigger, SSH to 71.85.90.6 # --------------------------------------------------------------------------- deploy-production: stage: deploy rules: - if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "master" when: manual environment: name: production url: http://71.85.90.6:3001 script: - echo "Deploying to production (${PROD_HOST})..." # Record current commit on prod for rollback - ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR} && git rev-parse HEAD 2>/dev/null || echo none" > /tmp/prod-prev-commit - echo "Previous production commit:$(cat /tmp/prod-prev-commit)" # Sync code to production (exclude local-only files) - rsync -az --delete --exclude='.git' --exclude='node_modules' --exclude='frontend/node_modules' --exclude='frontend/build' --exclude='backend/uploads' --exclude='*.log' --exclude='*.db' --exclude='.env' --exclude='.compliance-staging' ${CI_PROJECT_DIR}/ ${PROD_USER}@${PROD_HOST}:${PROD_DIR}/ # Copy built frontend - rsync -az ${CI_PROJECT_DIR}/frontend/build/ ${PROD_USER}@${PROD_HOST}:${PROD_DIR}/frontend/build/ # Install deps on production - ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR} && npm ci --prefer-offline" - ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR}/frontend && npm ci --prefer-offline" # Restart services — install systemd unit if not present - ssh ${PROD_USER}@${PROD_HOST} "test -f /etc/systemd/system/cve-backend.service" || scp ${CI_PROJECT_DIR}/deploy/cve-backend-production.service ${PROD_USER}@${PROD_HOST}:/etc/systemd/system/cve-backend.service - ssh ${PROD_USER}@${PROD_HOST} "systemctl daemon-reload && systemctl enable cve-backend && systemctl restart cve-backend" - echo "Production deploy complete." needs: - build-frontend - test-backend - job: verify-staging optional: true # ============================================================================= # STAGE 6: Post-deploy verification # ============================================================================= # --------------------------------------------------------------------------- # Staging health check # --------------------------------------------------------------------------- verify-staging: stage: verify rules: - if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "master" when: on_success script: - echo "Verifying staging..." - sleep 3 - | for i in 1 2 3 4 5; do STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3100/api/health 2>/dev/null || echo "000") if [ "$STATUS" = "200" ]; then echo "Staging health check passed (attempt $i)" break fi echo "Staging not ready (status: $STATUS), retrying... (attempt $i/5)" sleep 3 done if [ "$STATUS" != "200" ]; then echo "FAILED: Staging health check failed after 5 attempts" exit 1 fi - echo "Staging verification passed." needs: - deploy-staging # --------------------------------------------------------------------------- # Production health check — rolls back on failure # --------------------------------------------------------------------------- verify-production: stage: verify rules: - if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "master" when: on_success script: - echo "Verifying production..." - sleep 3 - | for i in 1 2 3 4 5 6 7 8 9 10; do STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://${PROD_HOST}:3001/api/health 2>/dev/null || echo "000") if [ "$STATUS" = "200" ]; then echo "Production health check passed (attempt $i)" break fi echo "Production not ready (status: $STATUS), retrying... (attempt $i/10)" sleep 3 done if [ "$STATUS" != "200" ]; then echo "FAILED: Production health check failed — initiating rollback" PREV_COMMIT=$(cat /tmp/prod-prev-commit 2>/dev/null || echo "") if [ -n "$PREV_COMMIT" ] && [ "$PREV_COMMIT" != "none" ]; then echo "Rolling back to $PREV_COMMIT..." # Re-sync the previous version ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR} && git checkout ${PREV_COMMIT} --force 2>/dev/null" || true ssh ${PROD_USER}@${PROD_HOST} "cd ${PROD_DIR} && npm ci --prefer-offline" ssh ${PROD_USER}@${PROD_HOST} "systemctl restart cve-backend" echo "Rollback complete. Verify manually." else echo "No previous commit recorded — manual intervention required." fi exit 1 fi - echo "Production verification passed." needs: - deploy-production allow_failure: false