Files
AI_template/scripts/setup-project.sh
olekhondera 99773cdbda fix: nginx headers at server block level; add Webuzo detection to deploy
deploy.sh:
- add step 3: nginx config apply with Webuzo/standard detection
- configurable via NGINX_CONF_SRC and NGINX_DOMAIN variables

setup-project.sh:
- move add_header to server block level (inside location / they are
  overridden by Webuzo's regex location and never sent)
- detect Webuzo nginx binary (/usr/local/apps/nginx/sbin/nginx)
  instead of hardcoded systemctl reload nginx

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 01:21:39 +02:00

275 lines
8.6 KiB
Bash
Executable File

#!/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'
# Security headers — at server block level so they are inherited by all
# location blocks (including Webuzo's own regex location).
# Do NOT move into location blocks: on Webuzo the regex location
# location ~ (\.php|shtml|/)$ takes priority and blocks inheritance.
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;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# Static files
location / {
root __INSTALL_DIR__/public;
index index.html;
try_files $uri $uri/ /index.html;
}
# 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}"
NGINX_BIN="/usr/local/apps/nginx/sbin/nginx"
if [[ ! -x "${NGINX_BIN}" ]]; then
NGINX_BIN="nginx"
fi
if "${NGINX_BIN}" -t 2>/dev/null; then
"${NGINX_BIN}" -s reload
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:-.}\""