diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bf6056b..c9a49f4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,64 +3,96 @@ stages: - test - deploy -# The container registry (port 5050) is not reachable from the runners, so -# nothing is pushed. Once it is, add a job that pushes $CI_REGISTRY_IMAGE. - -# Checks that the Dockerfile builds. Uses the runner host's Docker daemon, so -# the runner needs /var/run/docker.sock in [runners.docker] volumes (no dind). +# Builds the Docker image and pushes it to the GitLab container registry on +# port 443 (not 5050). Uses Docker-in-Docker (dind); the runner must have +# privileged = true and no host docker.sock mounted into services. TLS must +# stay off (DOCKER_TLS_CERTDIR: "") or the daemon listens on 2376 only. +# Tagged with the commit SHA; :latest is also pushed on the default branch. +# Registry auth (CI_REGISTRY_USER / CI_REGISTRY_PASSWORD) comes from GitLab's +# own predefined CI/CD variables, scoped to this job's token — never hardcode +# registry credentials here. Real, long-lived secrets belong in +# Settings > CI/CD > Variables (Masked + Protected; File type for keys/certs), +# never committed to this file. build: stage: build - image: docker:27 - tags: - - local + image: docker:29.8.1 + services: + - docker:29.8.1-dind + variables: + DOCKER_HOST: tcp://docker:2375 + DOCKER_DRIVER: overlay2 + DOCKER_TLS_CERTDIR: "" + REGISTRY_HOST: registry.gitlab.winsoo.org + IMAGE: $REGISTRY_HOST/$CI_PROJECT_PATH rules: - - if: $CI_PIPELINE_SOURCE == "merge_request_event" - - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + - if: $CI_PIPELINE_SOURCE == "push" script: - - docker build --pull -t chat-ci:$CI_JOB_ID . + - | + echo "Waiting for dind daemon..." + for i in $(seq 1 60); do + if docker info >/dev/null 2>&1; then + echo "dind daemon is up" + break + fi + sleep 1 + done + - docker info + - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$REGISTRY_HOST" + - docker build --pull -t "$IMAGE:$CI_COMMIT_SHORT_SHA" . + - docker push "$IMAGE:$CI_COMMIT_SHORT_SHA" + - | + if [ "$CI_COMMIT_BRANCH" = "$CI_DEFAULT_BRANCH" ]; then + docker tag "$IMAGE:$CI_COMMIT_SHORT_SHA" "$IMAGE:latest" + docker push "$IMAGE:latest" + fi after_script: - - docker rmi chat-ci:$CI_JOB_ID || true + - docker rmi "$IMAGE:$CI_COMMIT_SHORT_SHA" 2>/dev/null || true test: stage: test image: debian:13 - tags: - - local services: # no PostGIS needed: the settings use the plain postgresql backend + # (django.contrib.gis is only used for GDAL/GEOS-backed fields, not a + # postgis-flavoured DB engine) - name: postgres:17 alias: chat_db - name: redis:7 - alias: redis rules: - - if: $CI_PIPELINE_SOURCE == "merge_request_event" - - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + - if: $CI_PIPELINE_SOURCE == "push" cache: key: pip paths: - .cache/pip variables: PIP_CACHE_DIR: $CI_PROJECT_DIR/.cache/pip + # Service containers are configured from these; the app settings below + # reference them so the two sides can't drift apart. + # Dummy, throwaway values that only ever talk to the job's own service + # containers — safe to keep in the repo. Real secrets never belong in a + # test job; put them in Settings > CI/CD > Variables instead. # postgres service POSTGRES_USER: ci POSTGRES_PASSWORD: ci-password POSTGRES_DB: chat_db - # app settings; dummy values only, real secrets never belong in a test job + # app settings, derived from the service config above DEBUG: "true" - DB_NAME: chat_db - DB_USER: ci - DB_PASSWORD: ci-password + DB_NAME: $POSTGRES_DB + DB_USER: $POSTGRES_USER + DB_PASSWORD: $POSTGRES_PASSWORD DB_HOST: chat_db DB_PORT: "5432" - REDIS_BASE_URL: redis://redis:6379/1 - ACCOUNTS_BASE_PUBLIC_URL: https://accounts.invalid - OAUTH2_PROVIDER_BASE_PUBLIC_URL: https://accounts.invalid/oauth2 - OAUTH2_PROVIDER_BASE_PRIVATE_URL: https://accounts.invalid/oauth2 - OAUTH2_PROVIDER_CLIENT_ID: ci - OAUTH2_PROVIDER_CLIENT_SECRET: ci - MINIO_ENDPOINT: minio.invalid - MINIO_ACCESS_KEY: ci - MINIO_SECRET_KEY: ci + REDIS_BASE_URL: redis://redis:6379/2 + # ACCOUNTS_BASE_PUBLIC_URL, OAUTH2_PROVIDER_BASE_PUBLIC_URL, + # OAUTH2_PROVIDER_BASE_PRIVATE_URL, OAUTH2_PROVIDER_CLIENT_ID and + # OAUTH2_PROVIDER_CLIENT_SECRET are required by main/settings.py (no + # default) but are NOT set here — set them in + # Settings > CI/CD > Variables so they come from the environment instead + # of being hardcoded in this file. + # MinIO/Mattermost settings all have Python-side defaults and are only + # touched lazily inside services the test suite mocks out, so no live + # minio/mattermost service is needed here (unlike accounts, which checks + # its buckets on startup). before_script: # keep in sync with the Dockerfile - apt-get update @@ -81,9 +113,7 @@ test: # the docker CLI. The compose stack keeps a git checkout of this repo (mounted # at /app), so this updates that checkout and restarts the service. # Set DEPLOY_DIR in Settings > CI/CD > Variables (the directory that holds -# docker-compose.yml). DEPLOY_CHECKOUT and DEPLOY_SERVICE default to `chat`; -# override them there if the checkout directory or compose service is named -# differently. +# docker-compose.yml and the ./chat checkout). deploy_staging: stage: deploy tags: @@ -92,13 +122,11 @@ deploy_staging: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH when: manual allow_failure: true # otherwise the pipeline shows "blocked" until someone deploys - resource_group: staging # never run two deploys at once + variables: + DEPLOY_SERVICE: chat environment: name: staging - variables: - DEPLOY_CHECKOUT: chat - DEPLOY_SERVICE: chat script: - - cd "${DEPLOY_DIR:?set DEPLOY_DIR in Settings > CI/CD > Variables}" - - git -C "$DEPLOY_CHECKOUT" pull --ff-only origin "$CI_DEFAULT_BRANCH" + - cd "$DEPLOY_DIR" + - git -C "$DEPLOY_SERVICE" pull --ff-only origin "$CI_DEFAULT_BRANCH" - docker compose up -d --build "$DEPLOY_SERVICE"