diff --git a/Jenkinsfile b/Jenkinsfile index 346905f..fd249ff 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,16 +1,69 @@ +// ERPNext → Coolify production CI (Forgejo: epistemophiliac/erpnext) pipeline { agent any - options { timestamps() } + + options { + timestamps() + disableConcurrentBuilds() + buildDiscarder(logRotator(numToKeepStr: '20')) + } + stages { - stage('CI') { + stage('Verify') { steps { - sh 'bash scripts/ci/jenkins-run.sh' + sh ''' + echo "=== ERPNext Coolify CI ===" + echo "commit: $(git rev-parse --short HEAD)" + echo "branch: ${BRANCH_NAME:-main}" + test -f Jenkinsfile + test -f docker-compose.yml + test -f example.env + ''' + } + } + + stage('Production readiness') { + steps { + sh ''' + chmod +x scripts/ci/*.sh + bash scripts/ci/ci-readiness.sh . + bash scripts/ci/validate-docker-compose.sh . + ''' + } + } + + stage('Bootstrap Docker tools') { + steps { + sh 'bash scripts/ci/jenkins-bootstrap.sh' + } + } + + stage('Compose validate') { + steps { + sh ''' + source .ci-bin/ci-env.sh + mkdir -p dist + sed '/exclude_from_hc:/d' docker-compose.yml > dist/docker-compose.coolify.yml + $COMPOSE -f dist/docker-compose.coolify.yml config -q + echo "Coolify-safe compose validates" + ''' + } + } + + stage('Verify ERPNext image') { + steps { + sh 'bash scripts/ci/jenkins-pull-image.sh' } } } + post { success { - archiveArtifacts artifacts: 'dist/docker-compose.coolify.yml', onlyIfSuccessful: true + archiveArtifacts artifacts: 'dist/docker-compose.coolify.yml', fingerprint: true, onlyIfSuccessful: true + echo 'CI passed — safe to deploy docker-compose.yml on Coolify (set DB_PASSWORD, SITE_NAME, ADMIN_PASSWORD).' + } + failure { + echo 'CI failed — do not deploy to Coolify until this build is green.' } } } diff --git a/docs/JENKINS.md b/docs/JENKINS.md index 165ef8d..b993f57 100644 --- a/docs/JENKINS.md +++ b/docs/JENKINS.md @@ -45,15 +45,19 @@ Install the **Gitea** plugin in Jenkins, then use **Gitea** as the branch source - Username: `epistemophiliac` - Password: Forgejo personal access token (repo read scope) -## What the pipeline does +## Pipeline stages -- `scripts/ci/ci-readiness.sh` -- `scripts/ci/validate-docker-compose.sh` -- `docker compose config` (Coolify-safe compose) -- `docker pull frappe/erpnext:` -- Archives `dist/docker-compose.coolify.yml` +| Stage | What it checks | +|-------|----------------| +| **Verify** | `Jenkinsfile`, `docker-compose.yml`, `example.env` present | +| **Production readiness** | `ci-readiness.sh` + `validate-docker-compose.sh` (Coolify rules, no secrets tracked) | +| **Bootstrap Docker tools** | Static `docker` + `docker-compose` in `.ci-bin/`, socket access | +| **Compose validate** | `docker compose config` on Coolify-stripped compose (no `exclude_from_hc`, no host ports) | +| **Verify ERPNext image** | `docker pull frappe/erpnext:` from `example.env` | -This validates the repo; **Coolify deploy is separate**. +**Post-success:** archives `dist/docker-compose.coolify.yml` (the compose file Coolify actually parses). + +This validates the repo before deploy; **Coolify deploy is separate** (set `DB_PASSWORD`, `SITE_NAME`, `ADMIN_PASSWORD`, domain on `frontend:8080`). ## Troubleshooting @@ -76,7 +80,7 @@ Jenkins must be in the host **docker** group. On the Coolify host: stat -c '%g' /var/run/docker.sock ``` -Set that number as `DOCKER_GID` on the **jenkins** Coolify service (default `999`), redeploy Jenkins, rebuild. +Set that number as `DOCKER_GID` on the **jenkins** Coolify service (was wrong at `999` on this host — use **`991`**), redeploy Jenkins, rebuild. Do **not** run Jenkins as `user: 0:0` — it breaks `jenkins_home` ownership and git caches. diff --git a/scripts/ci/jenkins-bootstrap.sh b/scripts/ci/jenkins-bootstrap.sh new file mode 100755 index 0000000..48e4e29 --- /dev/null +++ b/scripts/ci/jenkins-bootstrap.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# Install docker + docker-compose CLI and verify socket access (Jenkins on Coolify). +set -euo pipefail + +mkdir -p .ci-bin dist + +if [ ! -x .ci-bin/docker ]; then + echo "Downloading docker CLI..." + curl -fsSL "https://download.docker.com/linux/static/stable/x86_64/docker-27.4.1.tgz" \ + | tar xz --strip-components=1 -C .ci-bin docker/docker + chmod +x .ci-bin/docker +fi + +if [ ! -x .ci-bin/docker-compose ]; then + echo "Downloading docker-compose..." + curl -fsSL "https://github.com/docker/compose/releases/download/v2.32.4/docker-compose-linux-x86_64" \ + -o .ci-bin/docker-compose + chmod +x .ci-bin/docker-compose +fi + +DOCKER=./.ci-bin/docker +COMPOSE=./.ci-bin/docker-compose + +if ! $DOCKER version >/dev/null 2>&1; then + if command -v sudo >/dev/null 2>&1 && sudo -n $DOCKER version >/dev/null 2>&1; then + DOCKER="sudo $DOCKER" + COMPOSE="sudo $COMPOSE" + else + echo "ERROR: cannot access /var/run/docker.sock" + echo "Set DOCKER_GID on the Jenkins Coolify service to: $(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo unknown)" + ls -la /var/run/docker.sock 2>/dev/null || true + id + exit 1 + fi +fi + +cat > .ci-bin/ci-env.sh </dev/null || echo unknown)" -echo "workspace: ${PWD}" chmod +x scripts/ci/*.sh bash scripts/ci/ci-readiness.sh . bash scripts/ci/validate-docker-compose.sh . +bash scripts/ci/jenkins-bootstrap.sh -mkdir -p .ci-bin dist - -if [ ! -x .ci-bin/docker ]; then - echo "Downloading docker CLI..." - curl -fsSL "https://download.docker.com/linux/static/stable/x86_64/docker-27.4.1.tgz" \ - | tar xz --strip-components=1 -C .ci-bin docker/docker - chmod +x .ci-bin/docker -fi - -if [ ! -x .ci-bin/docker-compose ]; then - echo "Downloading docker-compose..." - curl -fsSL "https://github.com/docker/compose/releases/download/v2.32.4/docker-compose-linux-x86_64" \ - -o .ci-bin/docker-compose - chmod +x .ci-bin/docker-compose -fi - -DOCKER=./.ci-bin/docker -COMPOSE=./.ci-bin/docker-compose -if ! $DOCKER version >/dev/null 2>&1; then - if command -v sudo >/dev/null 2>&1 && sudo -n $DOCKER version >/dev/null 2>&1; then - DOCKER="sudo $DOCKER" - COMPOSE="sudo $COMPOSE" - else - echo "ERROR: Jenkins cannot access /var/run/docker.sock (permission denied)." - echo "Fix in Coolify: jenkins service needs user 0:0 or group_add matching host docker GID." - ls -la /var/run/docker.sock 2>/dev/null || true - id - exit 1 - fi -fi - -$DOCKER version -$COMPOSE version - +# shellcheck source=/dev/null +source .ci-bin/ci-env.sh +mkdir -p dist sed '/exclude_from_hc:/d' docker-compose.yml > dist/docker-compose.coolify.yml $COMPOSE -f dist/docker-compose.coolify.yml config -q - -VERSION="$(grep -E '^ERPNEXT_VERSION=' example.env | cut -d= -f2)" -$DOCKER pull "frappe/erpnext:${VERSION}" -echo "frappe/erpnext:${VERSION} OK" +bash scripts/ci/jenkins-pull-image.sh