From b6a60d50093313292b8b7199077f7118ed1b232d Mon Sep 17 00:00:00 2001 From: GitLab Deploy Bot Date: Mon, 15 Dec 2025 09:25:50 +0700 Subject: [PATCH 01/11] remove --- .air.toml | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 .air.toml diff --git a/.air.toml b/.air.toml deleted file mode 100644 index 0c534172..00000000 --- a/.air.toml +++ /dev/null @@ -1,13 +0,0 @@ -# .air.toml -root = "." -tmp_dir = "tmp" - -[build] -cmd = "go build -o ./tmp/main ./cmd/api" -bin = "tmp/main" -full_bin = "APP_ENV=dev ./tmp/main" -include_ext = ["go", "tpl", "tmpl", "html"] -exclude_dir = ["vendor", "tmp"] - -[log] -time = true From 1e9fdd2b0da1d1541baca8174668a7512d54b9bc Mon Sep 17 00:00:00 2001 From: kris Date: Thu, 18 Dec 2025 06:41:04 +0000 Subject: [PATCH 02/11] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 127 +++++++++++++++++++++---------------------------- 1 file changed, 54 insertions(+), 73 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 53f28b3e..62acf585 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,90 +1,71 @@ stages: + - build - deploy -deploy-dev: +variables: + DOCKER_BUILDKIT: "1" + DOCKER_DRIVER: overlay2 + DOCKER_HOST: tcp://docker:2375 + DOCKER_TLS_CERTDIR: "" + + IMAGE_TAG: "staging_${CI_COMMIT_SHORT_SHA}" + IMAGE_NAME: "${CI_REGISTRY_IMAGE}:${IMAGE_TAG}" + IMAGE_LATEST_STG_EC2: "${CI_REGISTRY_IMAGE}:staging_latest" + +build:staging: + stage: build + image: docker:27.0.3 + services: + - name: docker:27.0.3-dind + command: ["--mtu=1460"] + rules: + - if: '$CI_COMMIT_BRANCH == "staging"' + before_script: + - docker info + - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY" + script: + - docker build -t "$IMAGE_NAME" -f Dockerfile . + - docker push "$IMAGE_NAME" + - docker tag "$IMAGE_NAME" "$IMAGE_LATEST_STG_EC2" + - docker push "$IMAGE_LATEST_STG_EC2" + +deploy:staging: stage: deploy image: alpine:3.20 - variables: - DEPLOY_APP: "LTI-MBUGROUP" - # Opsional: kalau pakai submodule, ini bikin clone submodule pakai SSH juga - GIT_SUBMODULE_STRATEGY: recursive - GIT_DEPTH: "1" + rules: + - if: '$CI_COMMIT_BRANCH == "staging"' + needs: + - job: build:staging before_script: - - echo "🧰 Installing dependencies..." - - apk update && apk add --no-cache openssh git curl bash - - # Setup SSH di runner + - apk add --no-cache openssh-client bash ca-certificates - mkdir -p ~/.ssh - - echo "$SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_rsa + - chmod 700 ~/.ssh + + # SSH_PRIVATE_KEY = multiline private key (bukan File) + - printf "%s\n" "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa + - sed -i 's/\r$//' ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa + + - head -n 1 ~/.ssh/id_rsa + - tail -n 1 ~/.ssh/id_rsa + - eval "$(ssh-agent -s)" - ssh-add ~/.ssh/id_rsa - - # Trust host keys (server + gitlab) biar SSH gak nanya interaktif - ssh-keyscan -H "$SERVER_IP" >> ~/.ssh/known_hosts - - ssh-keyscan -H gitlab.com >> ~/.ssh/known_hosts script: - - echo "🚀 Deploying latest code to $SERVER_USER@$SERVER_IP" - - > - if ssh -o StrictHostKeyChecking=no "$SERVER_USER@$SERVER_IP" " - set -e - - cd /home/devops/docker/deployment/development/lti-api - - # Pastikan remote origin SSH (antisipasi kalau pernah ke-set HTTPS) - git remote set-url origin git@gitlab.com:mbugroup/lti-api.git - - # Pastikan server percaya gitlab.com juga (untuk git fetch via SSH) - mkdir -p ~/.ssh - ssh-keyscan -H gitlab.com >> ~/.ssh/known_hosts - - # Fetch/reset pakai SSH - GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no' git fetch origin development - git reset --hard origin/development - - docker compose restart dev-api-lti || docker compose up -d dev-api-lti - "; then - STATUS='success'; - else - STATUS='failed'; - fi; - - RUN_URL="${CI_PROJECT_URL}/-/pipelines/${CI_PIPELINE_ID}"; - - if [ "$STATUS" = "success" ]; then - COLOR=3066993; - TITLE="✅ Deployment API Succeeded"; - DESC="Deployment job on branch \`${CI_COMMIT_REF_NAME}\` completed successfully."; - else - COLOR=15158332; - TITLE="❌ Deployment API Failed Gaes"; - DESC="Deployment job on branch \`${CI_COMMIT_REF_NAME}\` failed."; - fi; - - echo "{ - \"username\": \"CI Bot\", - \"embeds\": [{ - \"title\": \"$TITLE\", - \"description\": \"$DESC\", - \"color\": $COLOR, - \"fields\": [ - {\"name\": \"Repository\", \"value\": \"${CI_PROJECT_PATH}\", \"inline\": true}, - {\"name\": \"Actor\", \"value\": \"${GITLAB_USER_LOGIN}\", \"inline\": true}, - {\"name\": \"Commit\", \"value\": \"${CI_COMMIT_SHA}\", \"inline\": false}, - {\"name\": \"Pipeline\", \"value\": \"[Open run](${RUN_URL})\", \"inline\": false} - ] - }] - }" > payload.json; - - echo "📡 Sending notification to Discord..."; - curl -sS -H "Content-Type: application/json" \ - -d @payload.json "$DISCORD_WEBHOOK_URL"; - - only: - - development + ssh "$SERVER_USER@$SERVER_IP" + "export CI_REGISTRY_USER='$CI_REGISTRY_USER'; + export CI_REGISTRY_PASSWORD='$CI_REGISTRY_PASSWORD'; + export CI_REGISTRY='$CI_REGISTRY'; + set -e; + cd /home/ubuntu/docker/deployment/staging/stg-lti-api; + echo \"\$CI_REGISTRY_PASSWORD\" | docker login -u \"\$CI_REGISTRY_USER\" --password-stdin \"\$CI_REGISTRY\"; + docker compose pull; + docker compose up -d; + docker image prune -f" environment: - name: development \ No newline at end of file + name: staging \ No newline at end of file From 81f4a5e33ed35f886b0d3f29f2bd8dc2512c8a7c Mon Sep 17 00:00:00 2001 From: kris Date: Thu, 18 Dec 2025 06:50:22 +0000 Subject: [PATCH 03/11] Delete docker-compose.local.yml --- docker-compose.local.yml | 77 ---------------------------------------- 1 file changed, 77 deletions(-) delete mode 100644 docker-compose.local.yml diff --git a/docker-compose.local.yml b/docker-compose.local.yml deleted file mode 100644 index cdc4652d..00000000 --- a/docker-compose.local.yml +++ /dev/null @@ -1,77 +0,0 @@ -services: - postgresdb: - image: postgres:alpine - restart: always - ports: - - "${DB_PORT_HOST:-5542}:5432" - environment: - POSTGRES_USER: ${DB_USER:-postgres} - POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres} - POSTGRES_DB: ${DB_NAME:-db_lti_erp} - volumes: - - dbdata:/var/lib/postgresql/data - - ./internal/database/init:/docker-entrypoint-initdb.d - networks: [go-network] - healthcheck: - test: - [ - "CMD-SHELL", - "pg_isready -U ${DB_USER:-postgres} -d ${DB_NAME:-db_lti_erp}", - ] - interval: 10s - timeout: 5s - retries: 5 - redis: - image: redis:7-alpine - restart: unless-stopped - ports: - - "${REDIS_PORT_HOST:-6381}:6379" - healthcheck: - test: ["CMD-SHELL", "redis-cli ping | grep PONG"] - interval: 5s - timeout: 3s - retries: 10 - networks: [go-network] - - app: - build: - context: . - dockerfile: Dockerfile.local - image: cosmtrek/air:v1.52.3 - working_dir: /lti-api - volumes: - - .:/lti-api - - ./internal/config/jwtRS256.key:/run/keys/jwtRS256.key - - ./internal/config/jwtRS256.key.pub:/run/keys/jwtRS256.key.pub - command: air -c .air.toml - env_file: - - .env - environment: - DB_HOST: postgresdb - DB_PORT: 5432 - DB_USER: ${DB_USER:-postgres} - DB_PASSWORD: ${DB_PASSWORD:-postgres} - DB_NAME: ${DB_NAME:-db_lti_erp} - REDIS_URL: ${REDIS_URL:-redis://redis:6379/0} - ports: - - "${APP_PORT:-8081}:8081" - depends_on: - postgresdb: - condition: service_healthy - networks: [go-network] - healthcheck: - test: ["CMD-SHELL", "wget -qO- http://localhost:8081/healthz || exit 1"] - interval: 10s - timeout: 3s - retries: 10 - start_period: 10s - -volumes: - dbdata: - go-mod-cache: - go-build-cache: - -networks: - go-network: - name: lti-api_go-network - driver: bridge From e738a97e4c4ba8b6dba9c4ec42219799ea388bce Mon Sep 17 00:00:00 2001 From: kris Date: Thu, 18 Dec 2025 06:50:41 +0000 Subject: [PATCH 04/11] Delete docker-compose.yaml --- docker-compose.yaml | 98 --------------------------------------------- 1 file changed, 98 deletions(-) delete mode 100644 docker-compose.yaml diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index ab6daeba..00000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,98 +0,0 @@ -services: - dev-api-lti: - build: - context: . - dockerfile: Dockerfile - container_name: dev-api-lti - working_dir: /lti-api - command: ["/bin/sh", "scripts/entrypoint.sh"] - ports: - - "8081:8081" - env_file: - - .env - environment: - # override agar koneksi ke container internal - DB_HOST: dev-postgres-lti - DB_PORT: 5432 - REDIS_URL: redis://dev-redis-lti:6379/0 - volumes: - - .:/lti-api - - ./.air.toml:/lti-api/.air.toml:ro - - ./internal/config/jwtRS256.key:/run/keys/jwtRS256.key - - ./internal/config/jwtRS256.key.pub:/run/keys/jwtRS256.key.pub - depends_on: - - dev-postgres-lti - - dev-redis-lti - networks: - - lti-network - healthcheck: - test: ["CMD-SHELL", "wget -qO- http://localhost:8081/healthz || exit 1"] - interval: 10s - timeout: 3s - retries: 10 - start_period: 10s - deploy: - resources: - limits: - cpus: "2.0" - memory: 2G - reservations: - cpus: "1.0" - memory: 512M - - dev-postgres-lti: - image: postgres:15-alpine - container_name: dev-postgres-lti - restart: always - env_file: - - credential/.env.db - ports: - - "5433:5432" - volumes: - - dev-postgres-lti-data:/var/lib/postgresql/data - - ./credential:/docker-entrypoint-initdb.d:ro - networks: - - lti-network - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres} -d ${DB_NAME:-db_lti_erp}"] - interval: 10s - timeout: 5s - retries: 5 - start_period: 5s - deploy: - resources: - limits: - cpus: "1.0" - memory: 2G - reservations: - cpus: "0.5" - memory: 512M - - dev-redis-lti: - image: redis:7-alpine - container_name: dev-redis-lti - restart: always - ports: - - "6380:6379" - networks: - - lti-network - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 10s - timeout: 3s - retries: 10 - deploy: - resources: - limits: - cpus: "0.5" - memory: 512M - reservations: - cpus: "0.2" - memory: 256M - -networks: - lti-network: - driver: bridge - -volumes: - dev-postgres-lti-data: From 30231fabe9ffb77b3f0b582bcea3db7266aa1bae Mon Sep 17 00:00:00 2001 From: kris Date: Thu, 18 Dec 2025 06:51:52 +0000 Subject: [PATCH 05/11] Edit Dockerfile --- Dockerfile | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 87781228..abe12eb9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,20 +1,35 @@ -FROM golang:1.23-alpine +# ========================= +# Builder stage +# ========================= +FROM golang:1.23-alpine AS builder -# Install dependensi dasar -RUN apk add --no-cache git curl bash build-base +RUN apk add --no-cache git ca-certificates tzdata +WORKDIR /app -# Install Air (pakai repo baru air-verse) -RUN go install github.com/air-verse/air@v1.52.3 - -WORKDIR /lti-api - -# Cache dependencies COPY go.mod go.sum ./ RUN go mod download -# Copy source code COPY . . +# Build binary dari cmd/api +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ + go build -trimpath -ldflags="-s -w" -o lti-api ./cmd/api + +# ========================= +# Runtime stage +# ========================= +FROM alpine:3.20 + +RUN apk add --no-cache ca-certificates tzdata curl \ + && adduser -D -H -u 10001 appuser + +WORKDIR /app + +COPY --from=builder /app/lti-api /app/lti-api + +USER appuser + +# Samakan dengan APP_PORT default kamu (8081) EXPOSE 8081 -CMD ["air", "-c", ".air.toml"] +CMD ["/app/lti-api"] \ No newline at end of file From f8aee4be7bdde9d8b7003fd301ac9096eb62cf33 Mon Sep 17 00:00:00 2001 From: M1 AIR Date: Fri, 9 Jan 2026 10:58:11 +0700 Subject: [PATCH 06/11] penyesuaian flow cicid --- .DS_Store | Bin 6148 -> 0 bytes .gitlab-ci.yml | 180 +++++++++++++++++++++++++++++++++++-------------- Dockerfile | 13 ++-- 3 files changed, 137 insertions(+), 56 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 4c14efd89e4d913a63e6242a245ab626c5fffe6d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~&2H2%5XZ;c6-vv8$_)-k;RRMIPY_nwLk~!Zl@Q0W$&ywin^nI=Ipx9$A;b&t z3Oq*p0vve|PVk@c?ADIkazhAtB>SJ(GyeFk9j}SVj8DoPqHQ8dkXVOX$gVK1=M>mL zOCCavv@xP%YN?@mw+_5xK_n0f{A&bw?{3nFUef^`Lf8AZEoOB)LoGfH<`H!COH3wk z7oH_{dO>e#j<^G=Xo2@bn(x+L8to5)^ibm-crqyCn4f5V_4gYlRq*lI1bV7|kH9_b4CP1~oBIHH206rWfF0f!B z2L3B3IFE2>^&0c>eA>Ip#|8D|{i_wIpsl98M0S&(2XD}!ON{Forp7zx4MrHOefowS zch6ZBoAiPvSOq!aCIY>W9Q)IY;9mZ0%m|j;wi@$DAQFfK)&%(bkRq{Ws-0E&bRd%} z0Cb3AF+B5Kf@4zEOtrI$8kn)6P#bFWieYRx%2V~rR6DCSoQz&RjP`8whGL|5w4aJQ znapa;BY{Z3C$M2(xB2{^Tz~)fgW_8x5DENO1k5m>=3`7r&(>gaeAcE&dq`~T*IAVi jWO^LS10Tf?ki<}z@&hze?X02%7XJtchL}YHf0e**B}4%m diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 62acf585..60e132fd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,71 +1,149 @@ stages: - build + - migrate - deploy + - seed + +default: + tags: + - self-hosted-stg + +workflow: + rules: + - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "staging"' + when: always + - when: never variables: DOCKER_BUILDKIT: "1" - DOCKER_DRIVER: overlay2 - DOCKER_HOST: tcp://docker:2375 - DOCKER_TLS_CERTDIR: "" - IMAGE_TAG: "staging_${CI_COMMIT_SHORT_SHA}" IMAGE_NAME: "${CI_REGISTRY_IMAGE}:${IMAGE_TAG}" - IMAGE_LATEST_STG_EC2: "${CI_REGISTRY_IMAGE}:staging_latest" + IMAGE_LATEST: "${CI_REGISTRY_IMAGE}:staging_latest" + DEPLOY_DIR: "/opt/deploy/stg-lti-api" -build:staging: +# ========================= +# BUILD (AUTO) +# ========================= +build_staging: stage: build - image: docker:27.0.3 - services: - - name: docker:27.0.3-dind - command: ["--mtu=1460"] rules: - - if: '$CI_COMMIT_BRANCH == "staging"' - before_script: - - docker info - - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY" - script: - - docker build -t "$IMAGE_NAME" -f Dockerfile . - - docker push "$IMAGE_NAME" - - docker tag "$IMAGE_NAME" "$IMAGE_LATEST_STG_EC2" - - docker push "$IMAGE_LATEST_STG_EC2" + - 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:staging: - stage: deploy - image: alpine:3.20 + 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_COMMIT_BRANCH == "staging"' + - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "staging"' needs: - - job: build:staging + - job: build_staging + artifacts: false + script: | + set -e - before_script: - - apk add --no-cache openssh-client bash ca-certificates - - mkdir -p ~/.ssh - - chmod 700 ~/.ssh + # ✅ 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 - # SSH_PRIVATE_KEY = multiline private key (bukan File) - - printf "%s\n" "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa - - sed -i 's/\r$//' ~/.ssh/id_rsa - - chmod 600 ~/.ssh/id_rsa + # ✅ 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) - - head -n 1 ~/.ssh/id_rsa - - tail -n 1 ~/.ssh/id_rsa + export DATABASE_URL="postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=${DB_SSLMODE:-disable}" + echo "✅ DATABASE_URL ready" - - eval "$(ssh-agent -s)" - - ssh-add ~/.ssh/id_rsa - - ssh-keyscan -H "$SERVER_IP" >> ~/.ssh/known_hosts + # ✅ migrations dari repo + echo "✅ Checking migrations from repo..." + ls -lah "$CI_PROJECT_DIR/internal/database/migrations" - script: - - > - ssh "$SERVER_USER@$SERVER_IP" - "export CI_REGISTRY_USER='$CI_REGISTRY_USER'; - export CI_REGISTRY_PASSWORD='$CI_REGISTRY_PASSWORD'; - export CI_REGISTRY='$CI_REGISTRY'; - set -e; - cd /home/ubuntu/docker/deployment/staging/stg-lti-api; - echo \"\$CI_REGISTRY_PASSWORD\" | docker login -u \"\$CI_REGISTRY_USER\" --password-stdin \"\$CI_REGISTRY\"; - docker compose pull; - docker compose up -d; - docker image prune -f" + 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 - environment: - name: staging \ No newline at end of file + 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 + rules: + - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "staging"' + needs: + - job: migrate_staging + artifacts: false + - job: build_staging + artifacts: false + script: | + set -e + + docker info + echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY" + + 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) + + docker compose pull + docker compose up -d --force-recreate + docker image prune -f + +# ========================= +# SEED (MANUAL) +# ========================= +seed_staging: + stage: seed + rules: + - 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 abe12eb9..32e0688d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,25 +11,28 @@ RUN go mod download COPY . . -# Build binary dari cmd/api +# Build API binary RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -trimpath -ldflags="-s -w" -o lti-api ./cmd/api +# 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 + # ========================= # Runtime stage # ========================= FROM alpine:3.20 -RUN apk add --no-cache ca-certificates tzdata curl \ +RUN apk add --no-cache ca-certificates tzdata curl bash postgresql-client \ && adduser -D -H -u 10001 appuser WORKDIR /app COPY --from=builder /app/lti-api /app/lti-api +COPY --from=builder /app/lti-seed /app/lti-seed USER appuser - -# Samakan dengan APP_PORT default kamu (8081) EXPOSE 8081 -CMD ["/app/lti-api"] \ No newline at end of file +CMD ["/app/lti-api"] From 29933a5df9f0003d7708f7c3b85f9e8579c67b5c Mon Sep 17 00:00:00 2001 From: M1 AIR Date: Fri, 9 Jan 2026 11:17:49 +0700 Subject: [PATCH 07/11] change cicd --- .gitlab-ci.yml | 169 ++++++++++++++++++++++++++++--------------------- 1 file changed, 97 insertions(+), 72 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 60e132fd..65df90e6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -16,10 +16,13 @@ workflow: 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" + + DEPLOY_DIR: "/opt/deploy/stg-lti-api" + COMPOSE_FILE: "docker-compose.yaml" # ========================= # BUILD (AUTO) @@ -28,23 +31,23 @@ build_staging: stage: build 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" + script: + - set -e + - docker info + - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY" - echo "✅ Build image: $IMAGE_NAME" - docker build -t "$IMAGE_NAME" -f Dockerfile . + - echo "✅ Build image: $IMAGE_NAME" + - docker build -t "$IMAGE_NAME" -f Dockerfile . - echo "✅ Push image: $IMAGE_NAME" - docker push "$IMAGE_NAME" + - echo "✅ Push image: $IMAGE_NAME" + - docker push "$IMAGE_NAME" - echo "✅ Tag latest: $IMAGE_LATEST" - docker tag "$IMAGE_NAME" "$IMAGE_LATEST" - docker push "$IMAGE_LATEST" + - echo "✅ Tag latest: $IMAGE_LATEST" + - docker tag "$IMAGE_NAME" "$IMAGE_LATEST" + - docker push "$IMAGE_LATEST" # ========================= -# MIGRATE (AUTO) - migrations diambil dari repo GitLab +# MIGRATE (AUTO) - JOIN COMPOSE NETWORK # ========================= migrate_staging: stage: migrate @@ -53,47 +56,76 @@ migrate_staging: needs: - job: build_staging artifacts: false - script: | - set -e + script: + - set -e + - echo "✅ Running migrations (staging) ..." - # ✅ 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 + # ✅ masuk deploy dir (ada .env + docker-compose) + - cd "$DEPLOY_DIR" + - test -f "$COMPOSE_FILE" || (echo "❌ $COMPOSE_FILE not found in $DEPLOY_DIR" && exit 1) + - test -f .env || (echo "❌ .env not found in $DEPLOY_DIR" && exit 1) - # ✅ 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) + # ✅ load env dari server + - set -a + - . ./.env + - set +a - export DATABASE_URL="postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=${DB_SSLMODE:-disable}" - echo "✅ DATABASE_URL ready" + # ✅ pastikan DB env ada + - 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) - # ✅ migrations dari repo - echo "✅ Checking migrations from repo..." - ls -lah "$CI_PROJECT_DIR/internal/database/migrations" + # ✅ generate DATABASE_URL + - export DATABASE_URL="postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=${DB_SSLMODE:-disable}" + - echo "✅ DATABASE_URL=$DATABASE_URL" - 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 + # ✅ pastikan postgres container hidup supaya network exist + - echo "✅ Ensuring postgres & redis running ..." + - docker compose -f "$COMPOSE_FILE" up -d postgres-sso redis-sso || true + # NOTE: ganti postgres-sso/redis-sso sesuai nama service di docker-compose lti kamu: + # kalau lti compose pakai stg-postgres-lti / stg-redis-lti, ganti di line ini. + + # ✅ ambil network name compose (1st network) + - export COMPOSE_NETWORK="$(docker compose -f "$COMPOSE_FILE" config | awk '/networks:/ {getline; print $1}' | tr -d ':')" + - echo "✅ Compose network key: $COMPOSE_NETWORK" + + # ✅ ambil nama network aktual di docker (prefix foldername_) + - export NETWORK_NAME="$(docker network ls --format '{{.Name}}' | grep "_${COMPOSE_NETWORK}$" | head -n 1)" + - test -n "$NETWORK_NAME" || (echo "❌ Cannot find docker network for compose ($COMPOSE_NETWORK)" && exit 1) + - echo "✅ Docker network detected: $NETWORK_NAME" + + # ✅ migrations dari repo (CI workspace) + - echo "✅ Checking migrations from repo..." + - ls -lah "$CI_PROJECT_DIR/internal/database/migrations" + + # ✅ run migrate (JOIN NETWORK) + - echo "✅ Running migrations via migrate/migrate container ..." + - set +e + - out=$(docker run --rm \ + --network "$NETWORK_NAME" \ + -v "$CI_PROJECT_DIR/internal/database/migrations:/migrations:ro" \ + migrate/migrate:v4.15.2 \ + -path=/migrations -database "$DATABASE_URL" up 2>&1) + - code=$? + - set -e + + - echo "$out" + + # ✅ handle no change properly + - | + if echo "$out" | grep -qi "no change"; then + echo "✅ No change (already up to date)" + exit 0 + fi + + if [ $code -ne 0 ]; then + echo "❌ Migration failed with exit code $code" + exit $code + fi - 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) @@ -107,22 +139,21 @@ deploy_staging: artifacts: false - job: build_staging artifacts: false - script: | - set -e + script: + - set -e + - docker info + - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY" - docker info - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY" + - cd "$DEPLOY_DIR" + - test -f "$COMPOSE_FILE" || (echo "❌ $COMPOSE_FILE not found in $DEPLOY_DIR" && exit 1) + - test -f .env || (echo "❌ .env not found in $DEPLOY_DIR" && exit 1) - 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) - - docker compose pull - docker compose up -d --force-recreate - docker image prune -f + - docker compose -f "$COMPOSE_FILE" pull + - docker compose -f "$COMPOSE_FILE" up -d --force-recreate + - docker image prune -f # ========================= -# SEED (MANUAL) +# SEED (MANUAL) - OPTIONAL # ========================= seed_staging: stage: seed @@ -133,17 +164,11 @@ seed_staging: artifacts: false when: manual allow_failure: false - script: | - set -e + script: + - set -e + - cd "$DEPLOY_DIR" + - test -f "$COMPOSE_FILE" || (echo "❌ $COMPOSE_FILE not found" && exit 1) + - test -f .env || (echo "❌ .env not found" && exit 1) - 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" + - docker compose -f "$COMPOSE_FILE" pull seed || true + - docker compose -f "$COMPOSE_FILE" run --rm seed From b7a3882f20ff2e9e3e270bf5fa3c7d4262e09f2a Mon Sep 17 00:00:00 2001 From: kris Date: Fri, 9 Jan 2026 04:19:24 +0000 Subject: [PATCH 08/11] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 171 ++++++++++++++++++++++++------------------------- 1 file changed, 85 insertions(+), 86 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 65df90e6..637677a4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -21,7 +21,7 @@ variables: IMAGE_NAME: "${CI_REGISTRY_IMAGE}:${IMAGE_TAG}" IMAGE_LATEST: "${CI_REGISTRY_IMAGE}:staging_latest" - DEPLOY_DIR: "/opt/deploy/stg-lti-api" + DEPLOY_DIR: "/opt/deploy/stg-lti-api" COMPOSE_FILE: "docker-compose.yaml" # ========================= @@ -31,20 +31,22 @@ build_staging: stage: build 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" + script: | + set -e + docker info - - echo "✅ Build image: $IMAGE_NAME" - - docker build -t "$IMAGE_NAME" -f Dockerfile . + echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY" - - echo "✅ Push image: $IMAGE_NAME" - - docker push "$IMAGE_NAME" + 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" - - echo "✅ Tag latest: $IMAGE_LATEST" - - docker tag "$IMAGE_NAME" "$IMAGE_LATEST" - - docker push "$IMAGE_LATEST" # ========================= # MIGRATE (AUTO) - JOIN COMPOSE NETWORK @@ -56,76 +58,72 @@ migrate_staging: needs: - job: build_staging artifacts: false - script: - - set -e - - echo "✅ Running migrations (staging) ..." + script: | + set -e + echo "✅ Running migrations (staging) ..." - # ✅ masuk deploy dir (ada .env + docker-compose) - - cd "$DEPLOY_DIR" - - test -f "$COMPOSE_FILE" || (echo "❌ $COMPOSE_FILE not found in $DEPLOY_DIR" && exit 1) - - test -f .env || (echo "❌ .env not found in $DEPLOY_DIR" && exit 1) + cd "$DEPLOY_DIR" + test -f "$COMPOSE_FILE" || (echo "❌ $COMPOSE_FILE not found in $DEPLOY_DIR" && exit 1) + test -f .env || (echo "❌ .env not found in $DEPLOY_DIR" && exit 1) # ✅ load env dari server - - set -a - - . ./.env - - set +a + set -a + . ./.env + set +a - # ✅ pastikan DB env ada - - 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) + # ✅ validasi + 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) - # ✅ generate DATABASE_URL - - export DATABASE_URL="postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=${DB_SSLMODE:-disable}" - - echo "✅ DATABASE_URL=$DATABASE_URL" + export DATABASE_URL="postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=${DB_SSLMODE:-disable}" + echo "✅ DATABASE_URL=$DATABASE_URL" - # ✅ pastikan postgres container hidup supaya network exist - - echo "✅ Ensuring postgres & redis running ..." - - docker compose -f "$COMPOSE_FILE" up -d postgres-sso redis-sso || true - # NOTE: ganti postgres-sso/redis-sso sesuai nama service di docker-compose lti kamu: - # kalau lti compose pakai stg-postgres-lti / stg-redis-lti, ganti di line ini. + # ✅ Pastikan postgres & redis ON (sesuaikan nama service compose kamu!) + echo "✅ Ensuring postgres & redis running ..." + docker compose -f "$COMPOSE_FILE" up -d stg-postgres-lti stg-redis-lti || true - # ✅ ambil network name compose (1st network) - - export COMPOSE_NETWORK="$(docker compose -f "$COMPOSE_FILE" config | awk '/networks:/ {getline; print $1}' | tr -d ':')" - - echo "✅ Compose network key: $COMPOSE_NETWORK" + # ✅ Ambil network key dari compose + COMPOSE_NETWORK_KEY="$(docker compose -f "$COMPOSE_FILE" config | awk '/networks:/ {getline; print $1}' | tr -d ':')" + echo "✅ Compose network key: $COMPOSE_NETWORK_KEY" - # ✅ ambil nama network aktual di docker (prefix foldername_) - - export NETWORK_NAME="$(docker network ls --format '{{.Name}}' | grep "_${COMPOSE_NETWORK}$" | head -n 1)" - - test -n "$NETWORK_NAME" || (echo "❌ Cannot find docker network for compose ($COMPOSE_NETWORK)" && exit 1) - - echo "✅ Docker network detected: $NETWORK_NAME" + # ✅ Cari network name yang dipakai docker + NETWORK_NAME="$(docker network ls --format '{{.Name}}' | grep "_${COMPOSE_NETWORK_KEY}$" | head -n 1)" + test -n "$NETWORK_NAME" || (echo "❌ Cannot find docker network for compose ($COMPOSE_NETWORK_KEY)" && exit 1) - # ✅ migrations dari repo (CI workspace) - - echo "✅ Checking migrations from repo..." - - ls -lah "$CI_PROJECT_DIR/internal/database/migrations" + echo "✅ Docker network detected: $NETWORK_NAME" - # ✅ run migrate (JOIN NETWORK) - - echo "✅ Running migrations via migrate/migrate container ..." - - set +e - - out=$(docker run --rm \ - --network "$NETWORK_NAME" \ - -v "$CI_PROJECT_DIR/internal/database/migrations:/migrations:ro" \ - migrate/migrate:v4.15.2 \ - -path=/migrations -database "$DATABASE_URL" up 2>&1) - - code=$? - - set -e + # ✅ Migrations dari repo (CI workspace) + echo "✅ Checking migrations from repo..." + ls -lah "$CI_PROJECT_DIR/internal/database/migrations" - - echo "$out" + echo "✅ Running migrations via migrate/migrate container" + set +e + out=$(docker run --rm \ + --network "$NETWORK_NAME" \ + -v "$CI_PROJECT_DIR/internal/database/migrations:/migrations:ro" \ + migrate/migrate:v4.15.2 \ + -path=/migrations -database "$DATABASE_URL" up 2>&1) + code=$? + set -e - # ✅ handle no change properly - - | - if echo "$out" | grep -qi "no change"; then - echo "✅ No change (already up to date)" - exit 0 - fi + echo "$out" - if [ $code -ne 0 ]; then - echo "❌ Migration failed with exit code $code" - exit $code - fi + # ✅ Handle no change dengan benar (tidak false-success) + if echo "$out" | grep -qi "no change"; then + echo "✅ No change (already up to date)" + exit 0 + fi + + if [ $code -ne 0 ]; then + echo "❌ Migration failed with exit code $code" + exit $code + fi + + echo "✅ Migration applied successfully" - echo "✅ Migration applied successfully" # ========================= # DEPLOY (AUTO) @@ -139,21 +137,22 @@ deploy_staging: artifacts: false - job: build_staging artifacts: false - script: - - set -e - - docker info - - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY" + script: | + set -e + docker info + echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY" - - cd "$DEPLOY_DIR" - - test -f "$COMPOSE_FILE" || (echo "❌ $COMPOSE_FILE not found in $DEPLOY_DIR" && exit 1) - - test -f .env || (echo "❌ .env not found in $DEPLOY_DIR" && exit 1) + cd "$DEPLOY_DIR" + test -f "$COMPOSE_FILE" || (echo "❌ $COMPOSE_FILE not found in $DEPLOY_DIR" && exit 1) + test -f .env || (echo "❌ .env not found in $DEPLOY_DIR" && exit 1) + + docker compose -f "$COMPOSE_FILE" pull + docker compose -f "$COMPOSE_FILE" up -d --force-recreate + docker image prune -f - - docker compose -f "$COMPOSE_FILE" pull - - docker compose -f "$COMPOSE_FILE" up -d --force-recreate - - docker image prune -f # ========================= -# SEED (MANUAL) - OPTIONAL +# SEED (MANUAL) # ========================= seed_staging: stage: seed @@ -164,11 +163,11 @@ seed_staging: artifacts: false when: manual allow_failure: false - script: - - set -e - - cd "$DEPLOY_DIR" - - test -f "$COMPOSE_FILE" || (echo "❌ $COMPOSE_FILE not found" && exit 1) - - test -f .env || (echo "❌ .env not found" && exit 1) + script: | + set -e + cd "$DEPLOY_DIR" + test -f "$COMPOSE_FILE" || (echo "❌ $COMPOSE_FILE not found" && exit 1) + test -f .env || (echo "❌ .env not found" && exit 1) - - docker compose -f "$COMPOSE_FILE" pull seed || true - - docker compose -f "$COMPOSE_FILE" run --rm seed + docker compose -f "$COMPOSE_FILE" pull seed || true + docker compose -f "$COMPOSE_FILE" run --rm seed \ No newline at end of file From 3d76854273ce239602c5ea589d3fa9f36ea0499c Mon Sep 17 00:00:00 2001 From: kris Date: Fri, 9 Jan 2026 04:27:45 +0000 Subject: [PATCH 09/11] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 637677a4..a46bb3aa 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -49,7 +49,7 @@ build_staging: # ========================= -# MIGRATE (AUTO) - JOIN COMPOSE NETWORK +# MIGRATE (AUTO) # ========================= migrate_staging: stage: migrate From d33119661afbfa8c4d430cb5ec071878a37bf935 Mon Sep 17 00:00:00 2001 From: kris Date: Fri, 9 Jan 2026 08:37:55 +0000 Subject: [PATCH 10/11] Update .gitlab-ci.yml file --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a46bb3aa..b0e3883e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -126,7 +126,7 @@ migrate_staging: # ========================= -# DEPLOY (AUTO) +# DEPLOY (AUTO) # ========================= deploy_staging: stage: deploy From e2d352721cb803203c4ec22c04bc265e6cc64ae0 Mon Sep 17 00:00:00 2001 From: M1 AIR Date: Mon, 12 Jan 2026 11:15:59 +0700 Subject: [PATCH 11/11] Merge from development --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d6a26e97..4a814ebe 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,8 @@ bin/ Makefile docker-compose.local.yml docker-compose.yaml -Dockerfile.local +Dockerfile +.gitlab-ci.yml # Go build cache .gocache/ vendor