diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 000000000..565892284 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,288 @@ +kind: pipeline +type: docker +name: auto-deploy + +trigger: + branch: + - dev + event: + - push + +steps: + + # ============================================================ + # DOCKER BUILD & PUSH + # + # Angular build (npm ci + ng build) Dockerfile ke andar + # multi-stage me hota hai, isliye alag build step nahi chahiye. + # ============================================================ + - name: docker-build-push + image: docker + + volumes: + - name: dockersock + path: /var/run/docker.sock + + commands: + - | + set -e + + # -------------------------------------------------------- + # Repository name comes from Drone + # -------------------------------------------------------- + REPO="$${DRONE_REPO_NAME}" + REPO="$$(echo "$$REPO" | tr '[:upper:]' '[:lower:]')" + + IMAGE="harbor.lincpay:8082/lincpay/$$REPO:$${DRONE_BUILD_NUMBER}" + + echo "==========================================" + echo "DOCKER BUILD & PUSH" + echo "==========================================" + echo "Repository : $$REPO" + echo "Build : $${DRONE_BUILD_NUMBER}" + echo "Image : $$IMAGE" + echo "==========================================" + + # -------------------------------------------------------- + # Harbor login + # -------------------------------------------------------- + echo 'QVgW5QbqlKKtBU2dYApTY28Dd6J36GK3' | \ + docker login harbor.lincpay:8082 \ + -u 'robot$lincpay+drone' \ + --password-stdin + + # -------------------------------------------------------- + # Build + # -------------------------------------------------------- + echo "Building Docker image..." + + docker build \ + -t "$$IMAGE" \ + . + + # -------------------------------------------------------- + # Push + # -------------------------------------------------------- + echo "Pushing Docker image..." + + docker push "$$IMAGE" + + echo "Docker image pushed successfully." + + + # ============================================================ + # DEPLOY + # ============================================================ + - name: deploy + image: docker + + volumes: + - name: dockersock + path: /var/run/docker.sock + + - name: envfiles + path: /platform-env + + commands: + - | + set -e + + # ======================================================== + # REPOSITORY / SERVICE / IMAGE + # ======================================================== + + REPO="$${DRONE_REPO_NAME}" + REPO="$$(echo "$$REPO" | tr '[:upper:]' '[:lower:]')" + + SERVICE_NAME="$${REPO}_app" + + IMAGE="harbor.lincpay:8082/lincpay/$$REPO:$${DRONE_BUILD_NUMBER}" + + echo "==========================================" + echo "DEPLOYMENT" + echo "==========================================" + echo "Repository : $$REPO" + echo "Service : $$SERVICE_NAME" + echo "Image : $$IMAGE" + echo "Placement : node.labels.environment == dev" + echo "==========================================" + + + # ======================================================== + # VALIDATION + # ======================================================== + + if [ -z "$$REPO" ]; then + echo "ERROR: DRONE_REPO_NAME is empty." + exit 1 + fi + + if [ -z "$$SERVICE_NAME" ]; then + echo "ERROR: Service name is empty." + exit 1 + fi + + if [ -z "$$IMAGE" ]; then + echo "ERROR: Docker image is empty." + exit 1 + fi + + echo "Repository verified : $$REPO" + echo "Service verified : $$SERVICE_NAME" + echo "Image verified : $$IMAGE" + + + # ======================================================== + # EXISTING SERVICE + # ======================================================== + + if docker service ls --format '{{.Name}}' | grep -Fxq "$$SERVICE_NAME"; then + + echo "" + echo "==========================================" + echo "EXISTING SERVICE" + echo "==========================================" + echo "Service: $$SERVICE_NAME" + + + # ------------------------------------------------------ + # Check whether dev constraint already exists + # ------------------------------------------------------ + + CONSTRAINT_EXISTS=$$(docker service inspect "$$SERVICE_NAME" \ + --format '{{range .Spec.TaskTemplate.Placement.Constraints}}{{println .}}{{end}}' \ + | grep -Fx 'node.labels.environment == dev' || true) + + + # ------------------------------------------------------ + # Add constraint if missing + # ------------------------------------------------------ + + if [ -z "$$CONSTRAINT_EXISTS" ]; then + + echo "Constraint missing." + echo "Adding: node.labels.environment == dev" + + docker service update \ + --constraint-add 'node.labels.environment == dev' \ + "$$SERVICE_NAME" + + else + + echo "Constraint already exists." + + fi + + + # ------------------------------------------------------ + # Update image + # ------------------------------------------------------ + + echo "Updating service image..." + + docker service update \ + --image "$$IMAGE" \ + --replicas 1 \ + --with-registry-auth \ + "$$SERVICE_NAME" + + + # ======================================================== + # NEW SERVICE + # ======================================================== + + else + + echo "" + echo "==========================================" + echo "NEW SERVICE" + echo "==========================================" + echo "Creating: $$SERVICE_NAME" + + + # ------------------------------------------------------ + # Traefik rule + # + # Construct it without allowing Drone to consume + # the shell backticks. + # ------------------------------------------------------ + + TRAEFIK_RULE='Host(`'$$REPO'.lincpay`)' + + echo "Traefik rule: $$TRAEFIK_RULE" + + + # ------------------------------------------------------ + # Create service + # ------------------------------------------------------ + + docker service create \ + --name "$$SERVICE_NAME" \ + --network edge-network \ + --replicas 1 \ + --constraint 'node.labels.environment == dev' \ + --with-registry-auth \ + --env-file /platform-env/global.env \ + --env-file /platform-env/dev.env \ + --label traefik.enable=true \ + --label "traefik.docker.network=edge-network" \ + --label "traefik.http.routers.$$REPO.rule=$$TRAEFIK_RULE" \ + --label "traefik.http.services.$$REPO.loadbalancer.server.port=80" \ + "$$IMAGE" + + fi + + + # ======================================================== + # FINAL STATUS + # ======================================================== + + echo "" + echo "==========================================" + echo "FINAL SERVICE STATUS" + echo "==========================================" + + docker service ls \ + --filter "name=$$SERVICE_NAME" + + + echo "" + echo "==========================================" + echo "PLACEMENT CONSTRAINT" + echo "==========================================" + + docker service inspect "$$SERVICE_NAME" \ + --format '{{json .Spec.TaskTemplate.Placement}}' + + + echo "" + echo "==========================================" + echo "SERVICE TASKS" + echo "==========================================" + + docker service ps "$$SERVICE_NAME" --no-trunc + + + echo "" + echo "==========================================" + echo "DEPLOYMENT COMPLETE" + echo "==========================================" + + +volumes: + + # ============================================================ + # DOCKER SOCKET + # ============================================================ + - name: dockersock + host: + path: /var/run/docker.sock + + + # ============================================================ + # ENVIRONMENT FILES + # ============================================================ + - name: envfiles + host: + path: /opt/platform/env + diff --git a/Dockerfile b/Dockerfile index 5e5750925..26bedcd4b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,4 @@ +# syntax=docker/dockerfile:1 ############### ### STAGE 1: Build app ############### @@ -9,10 +10,13 @@ ARG NPM_REGISTRY_URL=https://registry.npmjs.org/ ARG BUILD_ENVIRONMENT_OPTIONS="--configuration production" ARG PUPPETEER_DOWNLOAD_HOST_ARG=https://storage.googleapis.com ARG PUPPETEER_CHROMIUM_REVISION_ARG=1011831 -ARG PUPPETEER_SKIP_DOWNLOAD_ARG +# Build ko Chromium nahi chahiye — skip (warna npm ci Chromium download par atak jata hai). +ARG PUPPETEER_SKIP_DOWNLOAD_ARG=true # Set the environment variable to increase Node.js memory limit -ENV NODE_OPTIONS="--max-old-space-size=4096" +# (Mifos web-app ka production build memory-heavy hai; upstream build:prod +# 16384 use karta hai. 4096 par 'ng build' OOM ho jata hai.) +ENV NODE_OPTIONS="--max-old-space-size=8192" RUN apk add --no-cache git @@ -25,14 +29,22 @@ ENV PUPPETEER_DOWNLOAD_HOST=$PUPPETEER_DOWNLOAD_HOST_ARG ENV PUPPETEER_CHROMIUM_REVISION=$PUPPETEER_CHROMIUM_REVISION_ARG ENV PUPPETEER_SKIP_DOWNLOAD=$PUPPETEER_SKIP_DOWNLOAD_ARG +# Test/e2e tooling ke bhaari binary downloads skip karo — build inhe use nahi +# karta, aur inke bina npm ci turant complete ho jata hai. +# Cypress -> ~250MB binary (CI me sabse zyada atakta hai) +# Playwright-> browser binaries +ENV CYPRESS_INSTALL_BINARY=0 +ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 + COPY ./ /usr/src/app/ -RUN npm cache clear --force +RUN npm config set fetch-retry-maxtimeout 120000 \ + && npm config set registry $NPM_REGISTRY_URL --location=global -RUN npm config set fetch-retry-maxtimeout 120000 -RUN npm config set registry $NPM_REGISTRY_URL --location=global - -RUN npm ci +# BuildKit cache mount: npm ka download cache builds ke beech persist hota hai, +# isliye har build par saare packages dobara download nahi hote (bahut fast). +# 'npm cache clear --force' hata diya — wo har build par cache wipe kar deta tha. +RUN --mount=type=cache,target=/root/.npm npm ci --no-audit --no-fund RUN sh -c "ng build --output-path=/dist $BUILD_ENVIRONMENT_OPTIONS" @@ -41,6 +53,14 @@ RUN sh -c "ng build --output-path=/dist $BUILD_ENVIRONMENT_OPTIONS" ############### FROM $NGINX_IMAGE +# envsubst (gettext) alpine-slim me nahi hota — runtime env.js generation ke +# liye zaroori hai, isliye explicitly install kar rahe hain. +RUN apk add --no-cache gettext + +# Static-only nginx config (SPA fallback, no API proxy). Stock default.conf +# ko replace karta hai taaki deep-link refresh par 404 na aaye. +COPY nginx.spa.conf /etc/nginx/conf.d/default.conf + COPY --from=builder /dist/browser /usr/share/nginx/html EXPOSE 80 diff --git a/nginx.spa.conf b/nginx.spa.conf new file mode 100644 index 000000000..86dae6530 --- /dev/null +++ b/nginx.spa.conf @@ -0,0 +1,45 @@ +# Static-only nginx config for the CBSNexora Angular UI. +# NOTE: No API reverse-proxy here by design — the browser calls the Fineract +# backend directly via FINERACT_API_URL (env.js). nginx sirf static files +# serve karta hai + Angular SPA routing handle karta hai. + +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + # ---- Compression ---- + gzip on; + gzip_comp_level 6; + gzip_min_length 1024; + gzip_proxied any; + gzip_types text/plain text/css application/javascript application/json + application/xml image/svg+xml font/woff font/woff2; + + # ---- Runtime env: env.js aur index.html kabhi cache na ho ---- + # (env.js har container start par envsubst se regenerate hoti hai; + # index.html hashed asset filenames point karti hai) + location = /assets/env.js { + add_header Cache-Control "no-store, no-cache, must-revalidate"; + expires -1; + } + + location = /index.html { + add_header Cache-Control "no-store, no-cache, must-revalidate"; + expires -1; + } + + # ---- Hashed static assets: lambe time cache ---- + location ~* \.(?:js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 7d; + add_header Cache-Control "public"; + try_files $uri =404; + } + + # ---- Angular SPA fallback: har route index.html par ---- + location / { + try_files $uri $uri/ /index.html; + } +}