fix: диагностика 502, /health и unit systemd для Caddy
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -70,31 +70,24 @@ SESSION_SECRET=ваш-длинный-секрет
|
||||
Чтобы магазин работал после перезагрузки сервера:
|
||||
|
||||
```bash
|
||||
cat > /etc/systemd/system/shop.service << 'EOF'
|
||||
[Unit]
|
||||
Description=Shop Node.js
|
||||
After=network.target
|
||||
cp /opt/shop/deploy/shop.service /etc/systemd/system/shop.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=www-data
|
||||
WorkingDirectory=/opt/shop
|
||||
EnvironmentFile=/opt/shop/.env
|
||||
ExecStart=/usr/bin/node src/server.js
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
# Зависимости и сборка (от root, до смены владельца)
|
||||
cd /opt/shop
|
||||
npm install --omit=dev
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
# Права на каталог (если клонировали в /opt/shop)
|
||||
# Владелец — тот же пользователь, что в unit (www-data)
|
||||
chown -R www-data:www-data /opt/shop
|
||||
chmod +x /opt/shop/scripts/diagnose-502.sh
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable shop
|
||||
systemctl start shop
|
||||
systemctl status shop
|
||||
|
||||
# Backend должен ответить:
|
||||
curl -s http://127.0.0.1:3000/health
|
||||
# {"ok":true,"service":"shop"}
|
||||
```
|
||||
|
||||
Логи: `journalctl -u shop -f`
|
||||
@@ -175,11 +168,47 @@ ufw enable
|
||||
1. `shop` (Node.js на `127.0.0.1:3000`)
|
||||
2. `caddy` (прокси + HTTPS)
|
||||
|
||||
### Типичные проблемы
|
||||
### HTTP 502 при рабочем SSL
|
||||
|
||||
**SSL есть, 502 — значит Caddy жив, а Node на `127.0.0.1:3000` не отвечает.**
|
||||
|
||||
На сервере выполните:
|
||||
|
||||
```bash
|
||||
bash /opt/shop/scripts/diagnose-502.sh
|
||||
journalctl -u shop -n 50 --no-pager
|
||||
curl -v http://127.0.0.1:3000/health
|
||||
```
|
||||
|
||||
**Частые причины и исправление:**
|
||||
|
||||
| Причина | Что сделать |
|
||||
|--------|-------------|
|
||||
| Служба `shop` не запущена или падает | `systemctl restart shop`, смотрите логи `journalctl -u shop -f` |
|
||||
| Нет `npm install` / сломан `better-sqlite3` | `cd /opt/shop && npm install --omit=dev` (нужны `build-essential`, `python3`) |
|
||||
| Нет прав на `data/` у `www-data` | `mkdir -p /opt/shop/data && chown -R www-data:www-data /opt/shop` |
|
||||
| В `.env` нет `HOST`/`PORT` | `HOST=127.0.0.1`, `PORT=3000`, затем `systemctl restart shop` |
|
||||
| Неверный путь к `node` в systemd | `which node` → подставьте в `ExecStart` в `/etc/systemd/system/shop.service` |
|
||||
| Caddy стартовал раньше shop | `cp deploy/caddy-after-shop.conf /etc/systemd/system/caddy.service.d/shop.conf` и `daemon-reload` |
|
||||
|
||||
**Быстрое восстановление:**
|
||||
|
||||
```bash
|
||||
cd /opt/shop
|
||||
git pull
|
||||
npm install --omit=dev
|
||||
chown -R www-data:www-data /opt/shop
|
||||
systemctl restart shop
|
||||
curl -s http://127.0.0.1:3000/health # должен быть {"ok":true,...}
|
||||
systemctl reload caddy
|
||||
```
|
||||
|
||||
Пока `curl http://127.0.0.1:3000/health` не возвращает OK — HTTPS через Caddy будет отдавать 502.
|
||||
|
||||
### Другие проблемы
|
||||
|
||||
| Симптом | Решение |
|
||||
|--------|---------|
|
||||
| 502 Bad Gateway | `systemctl status shop`, проверьте `curl http://127.0.0.1:3000` |
|
||||
| Нет сертификата | DNS, порты 80/443, верный домен в `Caddyfile` |
|
||||
| Редирект-цикл / нет cookies | В `.env`: `TRUST_PROXY=1`, `NODE_ENV=production` |
|
||||
|
||||
@@ -244,6 +273,10 @@ cp -a data/shop.db data/shop.db.bak
|
||||
```
|
||||
caddy/
|
||||
Caddyfile.example — пример reverse proxy + SSL
|
||||
deploy/
|
||||
shop.service — unit для systemd
|
||||
scripts/
|
||||
diagnose-502.sh — проверка при 502
|
||||
src/
|
||||
server.js — точка входа
|
||||
db.js — схема SQLite
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# Установка:
|
||||
# mkdir -p /etc/systemd/system/caddy.service.d
|
||||
# cp /opt/shop/deploy/caddy-after-shop.conf /etc/systemd/system/caddy.service.d/shop.conf
|
||||
# systemctl daemon-reload
|
||||
|
||||
[Unit]
|
||||
After=shop.service
|
||||
Wants=shop.service
|
||||
@@ -0,0 +1,20 @@
|
||||
[Unit]
|
||||
Description=Shop Node.js
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=www-data
|
||||
Group=www-data
|
||||
WorkingDirectory=/opt/shop
|
||||
EnvironmentFile=/opt/shop/.env
|
||||
# Путь к node: which node (часто /usr/bin/node)
|
||||
ExecStart=/usr/bin/node src/server.js
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
# Права на запись в data/ и node_modules (если нужно)
|
||||
UMask=0022
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/bin/bash
|
||||
# Диагностика HTTP 502 (Caddy не достучался до Node)
|
||||
set -e
|
||||
|
||||
echo "=== Shop / Caddy 502 diagnostic ==="
|
||||
echo
|
||||
|
||||
echo "1. Служба shop"
|
||||
systemctl is-active shop 2>/dev/null || echo " shop: не установлена или не active"
|
||||
systemctl status shop --no-pager -l 2>/dev/null | head -20 || true
|
||||
echo
|
||||
|
||||
echo "2. Служба caddy"
|
||||
systemctl is-active caddy 2>/dev/null || true
|
||||
echo
|
||||
|
||||
echo "3. Порт 3000"
|
||||
if command -v ss >/dev/null; then
|
||||
ss -tlnp | grep ':3000' || echo " Ничего не слушает порт 3000 — запустите shop"
|
||||
else
|
||||
netstat -tlnp 2>/dev/null | grep ':3000' || echo " Порт 3000 не слушается"
|
||||
fi
|
||||
echo
|
||||
|
||||
echo "4. curl backend"
|
||||
if curl -sf --max-time 3 http://127.0.0.1:3000/health; then
|
||||
echo
|
||||
echo " OK — Node отвечает, проблема скорее в Caddyfile"
|
||||
else
|
||||
echo " FAIL — Node не отвечает на 127.0.0.1:3000"
|
||||
echo " Проверьте: journalctl -u shop -n 50 --no-pager"
|
||||
fi
|
||||
echo
|
||||
|
||||
echo "5. .env (HOST, PORT)"
|
||||
if [ -f /opt/shop/.env ]; then
|
||||
grep -E '^(HOST|PORT|NODE_ENV)=' /opt/shop/.env || true
|
||||
else
|
||||
echo " /opt/shop/.env не найден"
|
||||
fi
|
||||
echo
|
||||
|
||||
echo "6. Права data/"
|
||||
if [ -d /opt/shop/data ]; then
|
||||
ls -la /opt/shop/data
|
||||
else
|
||||
echo " Каталог data/ отсутствует — создайте: mkdir -p /opt/shop/data && chown www-data:www-data /opt/shop/data"
|
||||
fi
|
||||
echo
|
||||
|
||||
echo "7. Node"
|
||||
which node || which nodejs || echo " node не найден в PATH"
|
||||
node -v 2>/dev/null || nodejs -v 2>/dev/null || true
|
||||
@@ -0,0 +1,15 @@
|
||||
const express = require('express');
|
||||
const { db } = require('../db');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/health', (_req, res) => {
|
||||
try {
|
||||
db.prepare('SELECT 1').get();
|
||||
res.json({ ok: true, service: 'shop' });
|
||||
} catch (err) {
|
||||
res.status(503).json({ ok: false, error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
+9
-1
@@ -7,6 +7,7 @@ require('./db');
|
||||
require('./seed');
|
||||
|
||||
const { loadUser } = require('./middleware/auth');
|
||||
const healthRoutes = require('./routes/health');
|
||||
const shopRoutes = require('./routes/shop');
|
||||
const authRoutes = require('./routes/auth');
|
||||
|
||||
@@ -22,6 +23,8 @@ if (process.env.TRUST_PROXY === '1' || isProduction) {
|
||||
app.set('view engine', 'ejs');
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
|
||||
app.use(healthRoutes);
|
||||
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
@@ -62,6 +65,11 @@ app.use((err, req, res, _next) => {
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(PORT, HOST, () => {
|
||||
const server = app.listen(PORT, HOST, () => {
|
||||
console.log(`Магазин: http://${HOST}:${PORT}`);
|
||||
});
|
||||
|
||||
server.on('error', (err) => {
|
||||
console.error('Не удалось запустить сервер:', err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user