- .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>
115 lines
3.6 KiB
Markdown
115 lines
3.6 KiB
Markdown
# 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)
|
|
|
|
```bash
|
|
# 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:
|
|
1. Create service user (`useradd -r -s /sbin/nologin`)
|
|
2. Create `/opt/<project>` directory
|
|
3. Install deploy script to `/usr/local/bin/deploy-<project>`
|
|
4. Add sudoers rule for Woodpecker agent user
|
|
5. Create systemd service
|
|
6. Create nginx config
|
|
|
|
### Deploy script details
|
|
|
|
`scripts/deploy.sh` does:
|
|
1. rsync files (excludes node_modules, .env, .git)
|
|
2. `npm ci --omit=dev` as service user (with `HOME=` set to avoid npm cache errors)
|
|
3. `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:
|
|
|
|
```bash
|
|
# 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>
|
|
```
|