From 79112e0da80d483ff18ec12589fc7f7b7e60102e Mon Sep 17 00:00:00 2001 From: M1 AIR Date: Fri, 9 Jan 2026 10:52:56 +0700 Subject: [PATCH] Penyesuaian flow repo --- .gitlab-ci.yml | 304 +++++++++++++++++++------------------------- Dockerfile | 43 ++++--- docker-compose.yaml | 39 ------ 3 files changed, 162 insertions(+), 224 deletions(-) delete mode 100644 docker-compose.yaml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e80a7e02..60e132fd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,185 +1,149 @@ stages: - build + - migrate - deploy + - seed -# ========================================================== -# ✅ Global defaults -# ========================================================== default: tags: - - server-development-biznet - interruptible: true + - self-hosted-stg -# ========================================================== -# 🏗️ Build Template -# ========================================================== -.build_template: &build_template +workflow: + rules: + - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "staging"' + when: always + - when: never + +variables: + DOCKER_BUILDKIT: "1" + IMAGE_TAG: "staging_${CI_COMMIT_SHORT_SHA}" + IMAGE_NAME: "${CI_REGISTRY_IMAGE}:${IMAGE_TAG}" + IMAGE_LATEST: "${CI_REGISTRY_IMAGE}:staging_latest" + DEPLOY_DIR: "/opt/deploy/stg-lti-api" + +# ========================= +# BUILD (AUTO) +# ========================= +build_staging: stage: build - image: node:20-alpine - cache: - key: npm-cache - paths: - - node_modules/ - variables: - NPM_CONFIG_PRODUCTION: 'false' - NODE_ENV: '' - script: - - echo "Installing dependencies..." - - npm ci --no-audit --no-fund - - echo "Build env used:" - - echo "NEXT_PUBLIC_LTI_URL=$NEXT_PUBLIC_LTI_URL" - - echo "NEXT_PUBLIC_SSO_LOGIN_URL=$NEXT_PUBLIC_SSO_LOGIN_URL" - - echo "NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL" - - echo "Building Next.js static export..." - - npx next build - - | - mkdir -p out - cat < out/build-info.json - { - "commit": "$CI_COMMIT_SHORT_SHA", - "pipeline": "$CI_PIPELINE_ID", - "built_at": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")", - "NEXT_PUBLIC_LTI_URL": "$NEXT_PUBLIC_LTI_URL", - "NEXT_PUBLIC_SSO_LOGIN_URL": "$NEXT_PUBLIC_SSO_LOGIN_URL", - "NEXT_PUBLIC_API_BASE_URL": "$NEXT_PUBLIC_API_BASE_URL" - } - EOF - artifacts: - name: 'out-$CI_COMMIT_SHORT_SHA' - paths: - - out/ - expire_in: 1 week + rules: + - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "staging"' + script: | + set -e + docker info + echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY" -# ========================================================== -# 🚀 Deploy Template -# ========================================================== -.deploy_template: &deploy_template + echo "✅ Build image: $IMAGE_NAME" + docker build -t "$IMAGE_NAME" -f Dockerfile . + + echo "✅ Push image: $IMAGE_NAME" + docker push "$IMAGE_NAME" + + echo "✅ Tag latest: $IMAGE_LATEST" + docker tag "$IMAGE_NAME" "$IMAGE_LATEST" + docker push "$IMAGE_LATEST" + +# ========================= +# MIGRATE (AUTO) - migrations diambil dari repo GitLab +# ========================= +migrate_staging: + stage: migrate + rules: + - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "staging"' + needs: + - job: build_staging + artifacts: false + script: | + set -e + + # ✅ Load env dari server (.env hanya ada di server) + cd "$DEPLOY_DIR" + test -f .env || (echo "❌ .env not found in $DEPLOY_DIR" && exit 1) + set -a + . ./.env + set +a + + # ✅ Generate DATABASE_URL dari DB_* + test -n "$DB_HOST" || (echo "❌ DB_HOST empty" && exit 1) + test -n "$DB_PORT" || (echo "❌ DB_PORT empty" && exit 1) + test -n "$DB_USER" || (echo "❌ DB_USER empty" && exit 1) + test -n "$DB_PASSWORD" || (echo "❌ DB_PASSWORD empty" && exit 1) + test -n "$DB_NAME" || (echo "❌ DB_NAME empty" && exit 1) + + export DATABASE_URL="postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=${DB_SSLMODE:-disable}" + echo "✅ DATABASE_URL ready" + + # ✅ migrations dari repo + echo "✅ Checking migrations from repo..." + ls -lah "$CI_PROJECT_DIR/internal/database/migrations" + + echo "✅ Running migrations via migrate/migrate container" + set +e + docker run --rm \ + -v "$CI_PROJECT_DIR/internal/database/migrations:/migrations:ro" \ + migrate/migrate:v4.15.2 \ + -path=/migrations -database "$DATABASE_URL" up + code=$? + set -e + + if [ $code -eq 0 ]; then + echo "✅ Migration applied successfully" + elif [ $code -eq 1 ]; then + echo "✅ No change (already up to date)" + else + echo "❌ Migration failed with exit code $code" + exit $code + fi + +# ========================= +# DEPLOY (AUTO) +# ========================= +deploy_staging: stage: deploy - image: - name: amazon/aws-cli:latest - entrypoint: ['/bin/sh', '-c'] - script: - - set -e - - aws --version - - echo "Cleaning up newline characters in AWS credentials..." - - export AWS_ACCESS_KEY_ID=$(echo $AWS_ACCESS_KEY_ID | tr -d '\r\n') - - export AWS_SECRET_ACCESS_KEY=$(echo $AWS_SECRET_ACCESS_KEY | tr -d '\r\n') - - echo "Deploying to s3://$S3_BUCKET in region $AWS_REGION" - - aws s3api head-bucket --bucket "$S3_BUCKET" --region "$AWS_REGION" || aws s3api create-bucket --bucket "$S3_BUCKET" --region "$AWS_REGION" --create-bucket-configuration LocationConstraint="$AWS_REGION" - - aws s3 sync ./out "s3://$S3_BUCKET" --delete --region "$AWS_REGION" --endpoint-url "https://s3.ap-southeast-3.amazonaws.com" - - # CloudFront invalidation - - | - STATUS="success" - if [ -n "$CLOUDFRONT_DISTRIBUTION_ID" ]; then - echo "Invalidating CloudFront cache..." - if ! aws cloudfront create-invalidation --distribution-id "$CLOUDFRONT_DISTRIBUTION_ID" --paths "/*"; then - echo "CloudFront invalidation failed." - STATUS="failed" - fi - else - echo "No CloudFront distribution specified — skipping invalidation" - fi - - # Notifikasi Discord - - | - RUN_URL="${CI_PROJECT_URL}/-/pipelines/${CI_PIPELINE_ID}" - - if [ "$CI_COMMIT_BRANCH" = "development" ]; then - ENVIRONMENT_NAME="WEB-LTI-DEV" - elif [ "$CI_COMMIT_BRANCH" = "staging" ]; then - ENVIRONMENT_NAME="WEB-LTI-STAGING" - else - ENVIRONMENT_NAME="UNKNOWN" - fi - - if [ "$STATUS" = "success" ]; then - COLOR=3066993 - TITLE="✅ Deployment ${ENVIRONMENT_NAME} Succeeded" - DESC="Deployment job on branch \${CI_COMMIT_REF_NAME}\ completed successfully." - else - COLOR=15158332 - TITLE="❌ Deployment ${ENVIRONMENT_NAME} Failed" - DESC="Deployment job on branch \${CI_COMMIT_REF_NAME}\ encountered issues." - fi - - jq -n \ - --arg title "$TITLE" \ - --arg desc "$DESC" \ - --arg color "$COLOR" \ - --arg repo "$CI_PROJECT_PATH" \ - --arg actor "$GITLAB_USER_LOGIN" \ - --arg commit "$CI_COMMIT_SHA" \ - --arg run_url "$RUN_URL" \ - '{ - username: "CI Bot - LTI WEB", - embeds: [{ - title: $title, - description: $desc, - color: ($color|tonumber), - fields: [ - {name: "Repository", value: $repo, inline: true}, - {name: "Actor", value: $actor, inline: true}, - {name: "Commit", value: $commit, inline: false}, - {name: "Pipeline", value: ("[Open run](" + $run_url + ")"), inline: false} - ] - }] - }' > payload.json - - curl -sS -H "Content-Type: application/json" -d @payload.json "$DISCORD_WEBHOOK_URL" - -# ========================================================== -# ==== DEVELOPMENT (Branch development) ====== -# ========================================================== -build:dev: - <<: *build_template rules: - - if: '$CI_COMMIT_BRANCH == "development"' - environment: - name: development - variables: - NEXT_PUBLIC_LTI_URL: 'https://dev-lti-erp.mbugroup.id' - NEXT_PUBLIC_SSO_LOGIN_URL: 'https://dev-auth-erp.mbugroup.id' - NEXT_PUBLIC_API_BASE_URL: 'https://dev-api-lti.mbugroup.id/api' - NEXT_PUBLIC_CLIENT_ID: 'Lumbung-Telur-Indonesia' + - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "staging"' + needs: + - job: migrate_staging + artifacts: false + - job: build_staging + artifacts: false + script: | + set -e -deploy:dev: - <<: *deploy_template - needs: ['build:dev'] - rules: - - if: '$CI_COMMIT_BRANCH == "development"' - variables: - S3_BUCKET: 'dev-lti-erp.mbugroup.id' - AWS_REGION: 'ap-southeast-3' - CLOUDFRONT_DISTRIBUTION_ID: 'E1Z8XTA8XF1GIV' - environment: - name: development - url: https://dev-lti-erp.mbugroup.id + docker info + echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY" -# ========================================================== -# ====== STAGING (Branch staging) ====== -# ========================================================== -build:staging: - <<: *build_template - rules: - - if: '$CI_COMMIT_BRANCH == "staging"' - environment: - name: staging - variables: - NEXT_PUBLIC_LTI_URL: 'https://stg-lti-erp.mbugroup.id' - NEXT_PUBLIC_SSO_LOGIN_URL: 'https://stg-auth-erp.mbugroup.id' - NEXT_PUBLIC_API_BASE_URL: 'https://stg-api-lti.mbugroup.id/api' - NEXT_PUBLIC_CLIENT_ID: 'Lumbung-Telur-Indonesia' + cd "$DEPLOY_DIR" + test -f docker-compose.yaml || (echo "❌ docker-compose.yaml not found in $DEPLOY_DIR" && exit 1) + test -f .env || (echo "❌ .env not found in $DEPLOY_DIR" && exit 1) -deploy:staging: - <<: *deploy_template - needs: ['build:staging'] + docker compose pull + docker compose up -d --force-recreate + docker image prune -f + +# ========================= +# SEED (MANUAL) +# ========================= +seed_staging: + stage: seed rules: - - if: '$CI_COMMIT_BRANCH == "staging"' - variables: - S3_BUCKET: 'stg-lti-erp.mbugroup.id' - AWS_REGION: 'ap-southeast-3' - CLOUDFRONT_DISTRIBUTION_ID: 'E2V6PPO1AUIU7H' - environment: - name: staging - url: https://stg-lti-erp.mbugroup.id + - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "staging"' + needs: + - job: deploy_staging + artifacts: false + when: manual + allow_failure: false + script: | + set -e + + cd "$DEPLOY_DIR" + test -f docker-compose.yaml || (echo "❌ docker-compose.yaml not found in $DEPLOY_DIR" && exit 1) + test -f .env || (echo "❌ .env not found in $DEPLOY_DIR" && exit 1) + + echo "✅ Pull latest seed image" + docker compose pull seed || true + + echo "🌱 Running seeder..." + docker compose run --rm seed + + echo "✅ Seed completed" diff --git a/Dockerfile b/Dockerfile index a3a2e197..32e0688d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,25 +1,38 @@ -FROM node:20-alpine - -RUN apk add --no-cache git bash build-base curl +# ========================= +# Builder stage +# ========================= +FROM golang:1.23-alpine AS builder +RUN apk add --no-cache git ca-certificates tzdata WORKDIR /app -COPY package*.json ./ -RUN npm ci +COPY go.mod go.sum ./ +RUN go mod download COPY . . -# Buat config agar Next tahu output: export -RUN echo "const config = { output: 'export', images: { unoptimized: true } }; export default config;" > next.config.mjs +# Build API binary +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ + go build -trimpath -ldflags="-s -w" -o lti-api ./cmd/api -# Build project (Next.js 15 otomatis static export) -RUN NEXT_DISABLE_TURBOPACK=1 npx next build +# Build SEED binary (pastikan cmd/seed ada) +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ + go build -trimpath -ldflags="-s -w" -o lti-seed ./cmd/seed -# Copy static assets dan hasil build agar bisa diakses -RUN mkdir -p .next/server/app/_next && \ - cp -r .next/static .next/server/app/_next/static && \ - cp -r public/* .next/server/app/ +# ========================= +# Runtime stage +# ========================= +FROM alpine:3.20 -EXPOSE 3000 +RUN apk add --no-cache ca-certificates tzdata curl bash postgresql-client \ + && adduser -D -H -u 10001 appuser -CMD ["npx", "serve", ".next/server/app", "-l", "3000"] \ No newline at end of file +WORKDIR /app + +COPY --from=builder /app/lti-api /app/lti-api +COPY --from=builder /app/lti-seed /app/lti-seed + +USER appuser +EXPOSE 8081 + +CMD ["/app/lti-api"] diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index b89f441b..00000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,39 +0,0 @@ -version: '3.9' - -services: - dev-web-lti: - container_name: dev-web-lti - build: - context: . - dockerfile: Dockerfile - ports: - - '3002:3000' - env_file: - - .env - environment: - NODE_ENV: production - APP_ENV: production - networks: - - dev-lti-network - restart: always - deploy: - resources: - limits: - cpus: '3.0' - memory: 3G - reservations: - cpus: '1.0' - memory: 512M - extra_hosts: - - 'host.docker.internal:host-gateway' - # Optional: aktifkan healthcheck jika punya endpoint - # healthcheck: - # test: ["CMD-SHELL", "curl -fsS http://localhost:3000/api/healthz || exit 1"] - # interval: 10s - # timeout: 3s - # retries: 10 - # start_period: 15s - -networks: - dev-lti-network: - external: true