feat: add Woodpecker CI + Gitea deployment templates
- .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>
This commit is contained in:
@@ -31,6 +31,10 @@ NODE_ENV=development
|
||||
# --- Email ---
|
||||
# RESEND_API_KEY=
|
||||
|
||||
# --- CI/CD (Woodpecker) ---
|
||||
# WOODPECKER_TOKEN=
|
||||
# WOODPECKER_URL=https://ci.spektr.design
|
||||
|
||||
# --- File Storage ---
|
||||
# S3_BUCKET=
|
||||
# S3_REGION=
|
||||
|
||||
@@ -1,6 +1,69 @@
|
||||
# Woodpecker CI pipeline template.
|
||||
#
|
||||
# Runs on the local backend — image: bash specifies the shell.
|
||||
# Triggers on push to main branch.
|
||||
#
|
||||
# Flow: install → lint-fix (optional) → lint + test → deploy
|
||||
#
|
||||
# Secrets (add in Woodpecker UI → repo or global settings):
|
||||
# gitea_token — Gitea access token (for lint-fix auto-commit back)
|
||||
#
|
||||
# Deploy setup (one-time, as root on VPS):
|
||||
# 1. Create deploy script:
|
||||
# cp scripts/deploy.sh /usr/local/bin/deploy-<project-name>
|
||||
# chmod 755 /usr/local/bin/deploy-<project-name>
|
||||
# chown root:root /usr/local/bin/deploy-<project-name>
|
||||
#
|
||||
# 2. Find Woodpecker agent user:
|
||||
# ps -o user= -p $(pgrep woodpecker-agent)
|
||||
#
|
||||
# 3. Set up sudoers (replace AGENT_USER and script name):
|
||||
# echo 'AGENT_USER ALL=(root) NOPASSWD: /usr/local/bin/deploy-<project-name> *' \
|
||||
# > /etc/sudoers.d/woodpecker-deploy
|
||||
# chmod 0440 /etc/sudoers.d/woodpecker-deploy
|
||||
# visudo -cf /etc/sudoers.d/woodpecker-deploy
|
||||
|
||||
when:
|
||||
- event: push
|
||||
branch: main
|
||||
|
||||
steps:
|
||||
- name: install
|
||||
image: bash
|
||||
commands:
|
||||
- npm ci
|
||||
|
||||
# Optional: auto-fix lint issues and commit back.
|
||||
# Requires gitea_token secret. Remove this step if not needed.
|
||||
# The [CI SKIP] in commit message prevents infinite loops.
|
||||
#
|
||||
# - name: lint-fix
|
||||
# image: bash
|
||||
# environment:
|
||||
# GITEA_TOKEN:
|
||||
# from_secret: gitea_token
|
||||
# commands:
|
||||
# - bash scripts/ci-lint-fix.sh
|
||||
# depends_on: [install]
|
||||
|
||||
- name: lint
|
||||
image: bash
|
||||
commands:
|
||||
- npx eslint .
|
||||
depends_on: [install]
|
||||
|
||||
- name: test
|
||||
image: bash
|
||||
commands:
|
||||
- echo "Hello from Woodpecker CI!"
|
||||
- echo "Build successful"
|
||||
- npm test
|
||||
depends_on: [install]
|
||||
|
||||
# Uncomment when deploy script is set up on VPS:
|
||||
#
|
||||
# - name: deploy
|
||||
# image: bash
|
||||
# environment:
|
||||
# CI: "true"
|
||||
# commands:
|
||||
# - sudo /usr/local/bin/deploy-<project-name> "${CI_WORKSPACE:-.}"
|
||||
# depends_on: [lint, test]
|
||||
|
||||
10
DOCS.md
10
DOCS.md
@@ -51,7 +51,11 @@ Technical index for developers and AI agents. Use this as the entry point to all
|
||||
- `docs/backend/security.md` — authN/Z, secret handling, webhook verification, audit/event logging.
|
||||
- `docs/backend/payment-flow.md` — payment integration (provider-agnostic template; single source of truth for payment flows and webhooks).
|
||||
|
||||
### 4) Examples (`/docs/examples`)
|
||||
### 4) CI/CD & Deployment
|
||||
|
||||
- `docs/ci-cd.md` — Woodpecker CI + Gitea setup, pipeline flow, deploy script, sudoers config, troubleshooting.
|
||||
|
||||
### 5) Examples (`/docs/examples`)
|
||||
|
||||
- `docs/examples/RECOMMENDATIONS-example.md` — filled-in example of `RECOMMENDATIONS.md` for a compliance classifier (Archetype C).
|
||||
|
||||
@@ -68,6 +72,10 @@ Technical index for developers and AI agents. Use this as the entry point to all
|
||||
- `package.json` — project metadata and Node.js engine requirement.
|
||||
- `.env.example` — environment variables template.
|
||||
- `.editorconfig` — editor formatting standards (indentation, line endings).
|
||||
- `.woodpecker.yml` — Woodpecker CI pipeline config (local backend).
|
||||
- `scripts/setup-project.sh` — Automated VPS setup for new projects (user, dir, deploy, sudoers, systemd, nginx).
|
||||
- `scripts/deploy.sh` — VPS deploy script template (rsync + npm ci + systemctl).
|
||||
- `scripts/ci-lint-fix.sh` — ESLint auto-fix with commit-back for CI.
|
||||
|
||||
## Agent Profiles (`/agents`)
|
||||
|
||||
|
||||
114
docs/ci-cd.md
Normal file
114
docs/ci-cd.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# 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>
|
||||
```
|
||||
39
scripts/ci-lint-fix.sh
Executable file
39
scripts/ci-lint-fix.sh
Executable file
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# ci-lint-fix.sh — Run ESLint --fix and commit changes back to Gitea.
|
||||
#
|
||||
# Used by Woodpecker CI. If ESLint auto-fixes anything, commits with
|
||||
# [CI SKIP] to prevent an infinite pipeline loop.
|
||||
#
|
||||
# Requires GITEA_TOKEN secret in Woodpecker.
|
||||
#
|
||||
# Customize LINT_TARGETS and REPO_URL for your project.
|
||||
|
||||
LINT_TARGETS="."
|
||||
GITEA_HOST="${GITEA_HOST:-git.spektr.design}"
|
||||
GITEA_ORG="${GITEA_ORG:-gitea}"
|
||||
REPO_NAME="${CI_REPO_NAME:-$(basename "$(git rev-parse --show-toplevel)")}"
|
||||
|
||||
echo "--- Running ESLint --fix"
|
||||
# shellcheck disable=SC2086
|
||||
npx eslint --fix ${LINT_TARGETS} || true
|
||||
|
||||
if [ -z "$(git diff)" ]; then
|
||||
echo "--- No auto-fixable issues found"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "--- ESLint auto-fixed issues, committing back..."
|
||||
|
||||
git config user.name "Woodpecker CI"
|
||||
git config user.email "ci@${GITEA_HOST}"
|
||||
|
||||
REPO_URL="https://gitea:${GITEA_TOKEN}@${GITEA_HOST}/${GITEA_ORG}/${REPO_NAME}.git"
|
||||
git remote set-url origin "${REPO_URL}"
|
||||
|
||||
git add -A
|
||||
git commit -m "fix: auto-fix eslint issues [CI SKIP]"
|
||||
git push origin HEAD:main
|
||||
|
||||
echo "--- Auto-fix committed and pushed"
|
||||
61
scripts/deploy.sh
Executable file
61
scripts/deploy.sh
Executable file
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# deploy.sh — Deploy project from CI workspace to target directory.
|
||||
#
|
||||
# Works for both manual and Woodpecker CI deployment.
|
||||
#
|
||||
# Usage:
|
||||
# Manual: sudo bash scripts/deploy.sh
|
||||
# CI: sudo /usr/local/bin/deploy-<project-name> /path/to/workspace
|
||||
#
|
||||
# CI setup (one-time, as root on VPS):
|
||||
# cp scripts/deploy.sh /usr/local/bin/deploy-<project-name>
|
||||
# chmod 755 /usr/local/bin/deploy-<project-name>
|
||||
# chown root:root /usr/local/bin/deploy-<project-name>
|
||||
|
||||
# --- Configure these for your project ---
|
||||
INSTALL_DIR="/opt/<project-name>"
|
||||
SERVICE_USER="<project-user>"
|
||||
SERVICE_NAME="<project-name>"
|
||||
HEALTH_URL="http://localhost:3000/api/health"
|
||||
# -----------------------------------------
|
||||
|
||||
SOURCE_DIR="${1:-.}"
|
||||
|
||||
echo "=== Deploy ==="
|
||||
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo "Error: must be root (sudo)"; exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -d "${INSTALL_DIR}" ]]; then
|
||||
echo "Error: ${INSTALL_DIR} not found"; exit 1
|
||||
fi
|
||||
|
||||
echo "[1/3] Syncing files..."
|
||||
rsync -a --delete \
|
||||
--exclude='node_modules' \
|
||||
--exclude='.env' \
|
||||
--exclude='.git' \
|
||||
--exclude='.idea' \
|
||||
"${SOURCE_DIR}/" "${INSTALL_DIR}/"
|
||||
chown -R "${SERVICE_USER}:${SERVICE_USER}" "${INSTALL_DIR}"
|
||||
|
||||
echo "[2/3] Installing dependencies..."
|
||||
sudo -u "${SERVICE_USER}" HOME="${INSTALL_DIR}" bash -c "cd ${INSTALL_DIR} && npm ci --omit=dev"
|
||||
|
||||
echo "[3/3] Restarting service..."
|
||||
systemctl daemon-reload
|
||||
systemctl restart "${SERVICE_NAME}"
|
||||
sleep 2
|
||||
|
||||
if systemctl is-active --quiet "${SERVICE_NAME}"; then
|
||||
echo "Service running"
|
||||
else
|
||||
echo "Error: service failed to start"; exit 1
|
||||
fi
|
||||
|
||||
curl -sf "${HEALTH_URL}" || { echo "FAIL: health check"; exit 1; }
|
||||
|
||||
echo "=== Deploy complete ==="
|
||||
265
scripts/setup-project.sh
Executable file
265
scripts/setup-project.sh
Executable file
@@ -0,0 +1,265 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# setup-project.sh — One-time VPS setup for a new project.
|
||||
#
|
||||
# Run as root on the VPS:
|
||||
# bash setup-project.sh <project-name> <domain> [port]
|
||||
#
|
||||
# Example:
|
||||
# bash setup-project.sh my-app app.spektr.design 3000
|
||||
#
|
||||
# What it does:
|
||||
# 1. Creates service user (no login shell, no home dir)
|
||||
# 2. Creates /opt/<project> directory
|
||||
# 3. Installs deploy script to /usr/local/bin/
|
||||
# 4. Adds sudoers rule for Woodpecker agent
|
||||
# 5. Creates systemd service
|
||||
# 6. Creates nginx config (location blocks for Webuzo)
|
||||
#
|
||||
# After running, you still need to:
|
||||
# - Create .env file at /opt/<project>/.env
|
||||
# - Set up SSL (certbot or Webuzo panel)
|
||||
# - Push code to Gitea to trigger first deploy
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Args
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
PROJECT="${1:?Usage: setup-project.sh <project-name> <domain> [port]}"
|
||||
DOMAIN="${2:?Usage: setup-project.sh <project-name> <domain> [port]}"
|
||||
PORT="${3:-3000}"
|
||||
|
||||
INSTALL_DIR="/opt/${PROJECT}"
|
||||
SERVICE_USER="${PROJECT}"
|
||||
DEPLOY_SCRIPT="/usr/local/bin/deploy-${PROJECT}"
|
||||
|
||||
# Detect Woodpecker agent user
|
||||
AGENT_USER=$(ps -o user= -p "$(pgrep woodpecker-agent)" 2>/dev/null || echo "")
|
||||
if [[ -z "${AGENT_USER}" ]]; then
|
||||
echo "Warning: Woodpecker agent not running. Defaulting to 'git'."
|
||||
AGENT_USER="git"
|
||||
fi
|
||||
|
||||
echo "=== Setting up project: ${PROJECT} ==="
|
||||
echo " Domain: ${DOMAIN}"
|
||||
echo " Port: ${PORT}"
|
||||
echo " Install dir: ${INSTALL_DIR}"
|
||||
echo " User: ${SERVICE_USER}"
|
||||
echo " Agent user: ${AGENT_USER}"
|
||||
echo ""
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Checks
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo "Error: run as root (sudo)"; exit 1
|
||||
fi
|
||||
|
||||
if [[ -d "${INSTALL_DIR}" ]]; then
|
||||
echo "Warning: ${INSTALL_DIR} already exists. Continuing..."
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1. Service user
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
echo "[1/6] Creating service user..."
|
||||
if id "${SERVICE_USER}" &>/dev/null; then
|
||||
echo " User ${SERVICE_USER} already exists"
|
||||
else
|
||||
useradd -r -s /sbin/nologin "${SERVICE_USER}"
|
||||
echo " Created user ${SERVICE_USER}"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2. Project directory
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
echo "[2/6] Creating project directory..."
|
||||
mkdir -p "${INSTALL_DIR}"
|
||||
chown "${SERVICE_USER}:${SERVICE_USER}" "${INSTALL_DIR}"
|
||||
echo " ${INSTALL_DIR} ready"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 3. Deploy script
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
echo "[3/6] Installing deploy script..."
|
||||
cat > "${DEPLOY_SCRIPT}" << DEPLOY
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
INSTALL_DIR="${INSTALL_DIR}"
|
||||
SERVICE_USER="${SERVICE_USER}"
|
||||
SERVICE_NAME="${PROJECT}"
|
||||
HEALTH_URL="http://localhost:${PORT}/api/health"
|
||||
SOURCE_DIR="\${1:-.}"
|
||||
echo "=== Deploy ${PROJECT} ==="
|
||||
if [[ \$EUID -ne 0 ]]; then
|
||||
echo "Error: must be root"; exit 1
|
||||
fi
|
||||
if [[ ! -d "\${INSTALL_DIR}" ]]; then
|
||||
echo "Error: \${INSTALL_DIR} not found"; exit 1
|
||||
fi
|
||||
echo "[1/3] Syncing files..."
|
||||
rsync -a --delete --exclude='node_modules' --exclude='.env' --exclude='.git' --exclude='.idea' "\${SOURCE_DIR}/" "\${INSTALL_DIR}/"
|
||||
chown -R "\${SERVICE_USER}:\${SERVICE_USER}" "\${INSTALL_DIR}"
|
||||
echo "[2/3] Installing dependencies..."
|
||||
sudo -u "\${SERVICE_USER}" HOME="\${INSTALL_DIR}" bash -c "cd \${INSTALL_DIR} && npm ci --omit=dev"
|
||||
echo "[3/3] Restarting service..."
|
||||
systemctl daemon-reload
|
||||
systemctl restart "\${SERVICE_NAME}"
|
||||
sleep 2
|
||||
if systemctl is-active --quiet "\${SERVICE_NAME}"; then
|
||||
echo "Service running"
|
||||
else
|
||||
echo "Error: service failed"; exit 1
|
||||
fi
|
||||
curl -sf "\${HEALTH_URL}" || { echo "FAIL: health check"; exit 1; }
|
||||
echo "=== Deploy complete ==="
|
||||
DEPLOY
|
||||
chmod 755 "${DEPLOY_SCRIPT}"
|
||||
# Strip trailing whitespace (heredoc can add it)
|
||||
sed -i 's/[[:space:]]*$//' "${DEPLOY_SCRIPT}"
|
||||
echo " ${DEPLOY_SCRIPT} installed"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 4. Sudoers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
echo "[4/6] Configuring sudoers..."
|
||||
SUDOERS_FILE="/etc/sudoers.d/woodpecker-${PROJECT}"
|
||||
echo "${AGENT_USER} ALL=(root) NOPASSWD: ${DEPLOY_SCRIPT} *" > "${SUDOERS_FILE}"
|
||||
chmod 0440 "${SUDOERS_FILE}"
|
||||
if visudo -cf "${SUDOERS_FILE}" &>/dev/null; then
|
||||
echo " ${SUDOERS_FILE} — parsed OK"
|
||||
else
|
||||
echo " ERROR: sudoers syntax check failed!"
|
||||
rm -f "${SUDOERS_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 5. Systemd service
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
echo "[5/6] Creating systemd service..."
|
||||
cat > "/etc/systemd/system/${PROJECT}.service" << SERVICE
|
||||
[Unit]
|
||||
Description=${PROJECT}
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=${SERVICE_USER}
|
||||
Group=${SERVICE_USER}
|
||||
WorkingDirectory=${INSTALL_DIR}
|
||||
ExecStart=/usr/bin/node ${INSTALL_DIR}/src/index.js
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
StartLimitBurst=5
|
||||
StartLimitIntervalSec=60
|
||||
|
||||
EnvironmentFile=-${INSTALL_DIR}/.env
|
||||
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectHome=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectControlGroups=true
|
||||
|
||||
LimitNOFILE=65536
|
||||
MemoryMax=256M
|
||||
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=${PROJECT}
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
SERVICE
|
||||
systemctl daemon-reload
|
||||
echo " ${PROJECT}.service created"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 6. Nginx config
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
echo "[6/6] Creating nginx config..."
|
||||
NGINX_DIR="/var/webuzo-data/nginx/custom/domains"
|
||||
if [[ -d "${NGINX_DIR}" ]]; then
|
||||
NGINX_CONF="${NGINX_DIR}/${DOMAIN}.conf"
|
||||
else
|
||||
NGINX_DIR="/etc/nginx/conf.d"
|
||||
NGINX_CONF="${NGINX_DIR}/${PROJECT}.conf"
|
||||
fi
|
||||
|
||||
cat > "${NGINX_CONF}" << 'NGINX'
|
||||
# Static files
|
||||
location / {
|
||||
root __INSTALL_DIR__/public;
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-Frame-Options "DENY" always;
|
||||
add_header Referrer-Policy "no-referrer" always;
|
||||
}
|
||||
|
||||
# API proxy
|
||||
location ^~ /api/ {
|
||||
proxy_pass http://127.0.0.1:__PORT__;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_connect_timeout 10s;
|
||||
proxy_send_timeout 30s;
|
||||
proxy_read_timeout 30s;
|
||||
}
|
||||
|
||||
# WebSocket proxy (if needed)
|
||||
location ^~ /ws/ {
|
||||
proxy_pass http://127.0.0.1:__PORT__;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_pass_header Upgrade;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_connect_timeout 10s;
|
||||
proxy_send_timeout 86400s;
|
||||
proxy_read_timeout 86400s;
|
||||
proxy_buffering off;
|
||||
}
|
||||
NGINX
|
||||
|
||||
sed -i "s|__INSTALL_DIR__|${INSTALL_DIR}|g" "${NGINX_CONF}"
|
||||
sed -i "s|__PORT__|${PORT}|g" "${NGINX_CONF}"
|
||||
|
||||
if nginx -t 2>/dev/null; then
|
||||
systemctl reload nginx
|
||||
echo " ${NGINX_CONF} — nginx reloaded"
|
||||
else
|
||||
echo " Warning: nginx config test failed. Check ${NGINX_CONF} manually."
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Done
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
echo ""
|
||||
echo "=== Setup complete ==="
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Create .env: nano ${INSTALL_DIR}/.env"
|
||||
echo " 2. Push code to Gitea — Woodpecker will deploy automatically"
|
||||
echo " 3. Check: curl -s http://localhost:${PORT}/api/health"
|
||||
echo " 4. Uncomment deploy step in .woodpecker.yml:"
|
||||
echo " sudo /usr/local/bin/deploy-${PROJECT} \"\${CI_WORKSPACE:-.}\""
|
||||
Reference in New Issue
Block a user