Split Jenkins CI into visible stages for Coolify ERPNext validation.
Each check runs in its own pipeline stage (readiness, compose, image pull) so Jenkins shows clear pass/fail per section. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
e9cbc9f88f
commit
f57072667c
5 changed files with 128 additions and 53 deletions
61
Jenkinsfile
vendored
61
Jenkinsfile
vendored
|
|
@ -1,16 +1,69 @@
|
||||||
|
// ERPNext → Coolify production CI (Forgejo: epistemophiliac/erpnext)
|
||||||
pipeline {
|
pipeline {
|
||||||
agent any
|
agent any
|
||||||
options { timestamps() }
|
|
||||||
|
options {
|
||||||
|
timestamps()
|
||||||
|
disableConcurrentBuilds()
|
||||||
|
buildDiscarder(logRotator(numToKeepStr: '20'))
|
||||||
|
}
|
||||||
|
|
||||||
stages {
|
stages {
|
||||||
stage('CI') {
|
stage('Verify') {
|
||||||
steps {
|
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 {
|
post {
|
||||||
success {
|
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.'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,15 +45,19 @@ Install the **Gitea** plugin in Jenkins, then use **Gitea** as the branch source
|
||||||
- Username: `epistemophiliac`
|
- Username: `epistemophiliac`
|
||||||
- Password: Forgejo personal access token (repo read scope)
|
- Password: Forgejo personal access token (repo read scope)
|
||||||
|
|
||||||
## What the pipeline does
|
## Pipeline stages
|
||||||
|
|
||||||
- `scripts/ci/ci-readiness.sh`
|
| Stage | What it checks |
|
||||||
- `scripts/ci/validate-docker-compose.sh`
|
|-------|----------------|
|
||||||
- `docker compose config` (Coolify-safe compose)
|
| **Verify** | `Jenkinsfile`, `docker-compose.yml`, `example.env` present |
|
||||||
- `docker pull frappe/erpnext:<version>`
|
| **Production readiness** | `ci-readiness.sh` + `validate-docker-compose.sh` (Coolify rules, no secrets tracked) |
|
||||||
- Archives `dist/docker-compose.coolify.yml`
|
| **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:<ERPNEXT_VERSION>` 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
|
## Troubleshooting
|
||||||
|
|
||||||
|
|
@ -76,7 +80,7 @@ Jenkins must be in the host **docker** group. On the Coolify host:
|
||||||
stat -c '%g' /var/run/docker.sock
|
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.
|
Do **not** run Jenkins as `user: 0:0` — it breaks `jenkins_home` ownership and git caches.
|
||||||
|
|
||||||
|
|
|
||||||
43
scripts/ci/jenkins-bootstrap.sh
Executable file
43
scripts/ci/jenkins-bootstrap.sh
Executable file
|
|
@ -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 <<EOF
|
||||||
|
export DOCKER='$DOCKER'
|
||||||
|
export COMPOSE='$COMPOSE'
|
||||||
|
EOF
|
||||||
|
|
||||||
|
$DOCKER version
|
||||||
|
$COMPOSE version
|
||||||
9
scripts/ci/jenkins-pull-image.sh
Executable file
9
scripts/ci/jenkins-pull-image.sh
Executable file
|
|
@ -0,0 +1,9 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
source .ci-bin/ci-env.sh
|
||||||
|
|
||||||
|
VERSION="$(grep -E '^ERPNEXT_VERSION=' example.env | cut -d= -f2)"
|
||||||
|
$DOCKER pull "frappe/erpnext:${VERSION}"
|
||||||
|
echo "frappe/erpnext:${VERSION} OK"
|
||||||
|
|
@ -1,52 +1,18 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# erpnext Jenkins CI — revision 4
|
# Local / all-in-one CI runner (same checks as Jenkinsfile stages).
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
echo "=== erpnext CI revision 4 ==="
|
echo "=== erpnext CI (local) ==="
|
||||||
echo "commit: $(git rev-parse --short HEAD 2>/dev/null || echo unknown)"
|
echo "commit: $(git rev-parse --short HEAD 2>/dev/null || echo unknown)"
|
||||||
echo "workspace: ${PWD}"
|
|
||||||
|
|
||||||
chmod +x scripts/ci/*.sh
|
chmod +x scripts/ci/*.sh
|
||||||
bash scripts/ci/ci-readiness.sh .
|
bash scripts/ci/ci-readiness.sh .
|
||||||
bash scripts/ci/validate-docker-compose.sh .
|
bash scripts/ci/validate-docker-compose.sh .
|
||||||
|
bash scripts/ci/jenkins-bootstrap.sh
|
||||||
|
|
||||||
mkdir -p .ci-bin dist
|
# shellcheck source=/dev/null
|
||||||
|
source .ci-bin/ci-env.sh
|
||||||
if [ ! -x .ci-bin/docker ]; then
|
mkdir -p dist
|
||||||
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
|
|
||||||
|
|
||||||
sed '/exclude_from_hc:/d' docker-compose.yml > dist/docker-compose.coolify.yml
|
sed '/exclude_from_hc:/d' docker-compose.yml > dist/docker-compose.coolify.yml
|
||||||
$COMPOSE -f dist/docker-compose.coolify.yml config -q
|
$COMPOSE -f dist/docker-compose.coolify.yml config -q
|
||||||
|
bash scripts/ci/jenkins-pull-image.sh
|
||||||
VERSION="$(grep -E '^ERPNEXT_VERSION=' example.env | cut -d= -f2)"
|
|
||||||
$DOCKER pull "frappe/erpnext:${VERSION}"
|
|
||||||
echo "frappe/erpnext:${VERSION} OK"
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue