- .woodpecker.yml: full pipeline template (install → lint-fix → lint → test → deploy) - scripts/setup-project.sh: one-command VPS setup (user, dir, deploy, sudoers, systemd, nginx) - scripts/deploy.sh: deploy script template (rsync + npm ci + systemctl + health check) - scripts/ci-lint-fix.sh: ESLint auto-fix with [CI SKIP] commit-back - docs/ci-cd.md: complete CI/CD documentation and troubleshooting - .env.example: added WOODPECKER_TOKEN - DOCS.md: added CI/CD section Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
3.6 KiB
CI/CD — Woodpecker + Gitea
Overview
Pipeline runs on Woodpecker CI with local backend (no Docker) on the same VPS.
Triggered by push to main branch via Gitea webhook.
Pipeline Flow
push to main → install → lint-fix (optional) → lint + test → deploy
Steps
| Step | What it does |
|---|---|
| install | npm ci — clean install dependencies |
| lint-fix | ESLint --fix, auto-commits back with [CI SKIP] |
| lint | ESLint check — fails pipeline on errors |
| test | npm test — fails pipeline on test failures |
| deploy | Runs deploy script via sudo on the VPS |
lint-fix auto-commit
When ESLint can auto-fix issues (formatting, var → const, == → ===, etc.),
the pipeline commits the fix back to Gitea with [CI SKIP] in the message.
Woodpecker natively skips pipelines for commits containing [CI SKIP].
Requires gitea_token secret — a Gitea access token with repo write permission.
Configuration
.woodpecker.yml
Located at project root. Uses image: bash for local backend (specifies shell, not Docker image).
Secrets (Woodpecker UI)
| Secret | Where | Purpose |
|---|---|---|
gitea_token |
Global or repo | Gitea access token for lint-fix auto-commit |
Add secrets in Woodpecker UI → Settings → Secrets (repo level) or global level.
Deploy Setup (one-time on VPS)
Automated setup (recommended)
# On VPS as root:
bash setup-project.sh <project-name> <domain> [port]
# Example:
bash setup-project.sh my-app app.spektr.design 3000
This creates: service user, /opt/<project> directory, deploy script, sudoers rule, systemd service, nginx config.
After running, create .env and push code to Gitea.
Manual setup
If you need more control, see scripts/setup-project.sh for individual steps:
- Create service user (
useradd -r -s /sbin/nologin) - Create
/opt/<project>directory - Install deploy script to
/usr/local/bin/deploy-<project> - Add sudoers rule for Woodpecker agent user
- Create systemd service
- Create nginx config
Deploy script details
scripts/deploy.sh does:
- rsync files (excludes node_modules, .env, .git)
npm ci --omit=devas service user (withHOME=set to avoid npm cache errors)systemctl restart+ health check
The script runs as root via sudo. Only this specific script is allowed in sudoers.
Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
secret "X" not found |
Secret not added in Woodpecker | Add in UI → Settings → Secrets |
Invalid or missing image |
Missing image: field |
Use image: bash for local backend |
Permission denied on deploy |
Sudoers not configured | Follow deploy setup above |
command not found for deploy script |
Script not at /usr/local/bin/ |
Copy and chmod 755 |
npm EACCES mkdir /home/user |
Service user has no home dir | Set HOME=/opt/<project> in deploy script |
| Pipeline not triggered | Webhook issue or [CI SKIP] in message |
Check Gitea webhook settings |
Gitea Access Token
Create in Gitea: Settings → Applications → Generate New Token.
Permissions needed: repo (read/write).
Woodpecker API
Check pipeline status programmatically:
# List recent pipelines
curl -H "Authorization: Bearer $TOKEN" \
https://ci.spektr.design/api/repos/<REPO_ID>/pipelines?page=1&per_page=5
# Get specific pipeline
curl -H "Authorization: Bearer $TOKEN" \
https://ci.spektr.design/api/repos/<REPO_ID>/pipelines/<NUMBER>
Store token in .env.local (gitignored):
WOODPECKER_TOKEN=<your-token>