diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 4c14efd8..00000000 Binary files a/.DS_Store and /dev/null differ 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"]