feat: PostgreSQL 17 вместо SQLite
pg + connect-pg-simple, async routes, docker-compose, скрипт setup-postgres. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Shop
|
||||
|
||||
Интернет-магазин на **Node.js** с локальной базой **SQLite**.
|
||||
Интернет-магазин на **Node.js** и **PostgreSQL 17**.
|
||||
|
||||
## Возможности
|
||||
|
||||
@@ -12,119 +12,125 @@
|
||||
## Требования
|
||||
|
||||
- Node.js 18+
|
||||
- PostgreSQL 17
|
||||
- npm
|
||||
- На Linux для сборки `better-sqlite3`: `build-essential`, `python3`
|
||||
|
||||
---
|
||||
|
||||
## PostgreSQL 17
|
||||
|
||||
### Docker (разработка / тест)
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
# БД: postgresql://shop:shop@127.0.0.1:5432/shop
|
||||
```
|
||||
|
||||
### Ubuntu (сервер)
|
||||
|
||||
```bash
|
||||
apt update
|
||||
apt install -y postgresql-17 postgresql-client-17
|
||||
|
||||
# Пользователь и база shop
|
||||
cd /opt/shop
|
||||
sudo bash scripts/setup-postgres-ubuntu.sh
|
||||
```
|
||||
|
||||
Схема таблиц: `postgres/init/01_schema.sql` (применяется при старте приложения и при первом запуске Docker).
|
||||
|
||||
Сессии хранятся в PostgreSQL (таблица `session`, создаётся автоматически).
|
||||
|
||||
---
|
||||
|
||||
## Быстрый развёртывание на Ubuntu
|
||||
|
||||
Скопируйте блок целиком на чистый сервер Ubuntu 22.04 / 24.04 (от root или через `sudo`):
|
||||
|
||||
```bash
|
||||
# 1. Зависимости системы
|
||||
# 1. Система + Node.js 20
|
||||
apt update
|
||||
apt install -y git curl build-essential python3
|
||||
|
||||
# 2. Node.js 20 LTS
|
||||
apt install -y git curl
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
|
||||
apt install -y nodejs
|
||||
|
||||
# 3. Клонирование (подставьте URL вашего репозитория)
|
||||
# 2. PostgreSQL 17
|
||||
apt install -y postgresql-17 postgresql-client-17
|
||||
|
||||
# 3. Код
|
||||
cd /opt
|
||||
git clone <URL_РЕПОЗИТОРИЯ> shop
|
||||
cd shop
|
||||
|
||||
# 4. Настройка окружения
|
||||
# 4. БД
|
||||
bash scripts/setup-postgres-ubuntu.sh
|
||||
|
||||
# 5. Окружение
|
||||
cp .env.example .env
|
||||
# Сгенерируйте секрет сессии:
|
||||
sed -i "s/change-me-to-a-long-random-string/$(openssl rand -hex 32)/" .env
|
||||
# Проверьте DATABASE_URL в .env
|
||||
|
||||
# 5. Установка приложения
|
||||
# 6. Приложение
|
||||
npm install --omit=dev
|
||||
|
||||
# 6. Caddy (HTTPS + прокси) — см. раздел ниже; для проверки без домена:
|
||||
npm start
|
||||
```
|
||||
|
||||
Без Caddy сайт на **http://IP_СЕРВЕРА:3000**. С Caddy — **https://ваш-домен** (любой, указанный в `Caddyfile`).
|
||||
Проверка:
|
||||
|
||||
В `.env` для production задайте (уже есть в `.env.example`):
|
||||
```bash
|
||||
curl -s http://127.0.0.1:3000/health
|
||||
# {"ok":true,"service":"shop","database":"postgresql"}
|
||||
```
|
||||
|
||||
### Переменные `.env`
|
||||
|
||||
```env
|
||||
PORT=3000
|
||||
HOST=127.0.0.1
|
||||
NODE_ENV=production
|
||||
TRUST_PROXY=1
|
||||
SESSION_SECRET=ваш-длинный-секрет
|
||||
SESSION_SECRET=длинный-секрет
|
||||
DATABASE_URL=postgresql://shop:shop@127.0.0.1:5432/shop
|
||||
```
|
||||
|
||||
`HOST=127.0.0.1` — Node слушает только localhost; снаружи доступ через Caddy.
|
||||
|
||||
При первом запуске создаются `data/shop.db`, `data/sessions.db` и демо-товары.
|
||||
| Переменная | Описание |
|
||||
|------------|----------|
|
||||
| `DATABASE_URL` | Строка подключения PostgreSQL |
|
||||
| `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD`, `PGDATABASE` | Альтернатива `DATABASE_URL` |
|
||||
| `HOST` | `127.0.0.1` в production (доступ через Caddy) |
|
||||
|
||||
---
|
||||
|
||||
## Запуск как служба (systemd)
|
||||
|
||||
Чтобы магазин работал после перезагрузки сервера:
|
||||
|
||||
```bash
|
||||
cp /opt/shop/deploy/shop.service /etc/systemd/system/shop.service
|
||||
|
||||
# Код обновляйте от root; НЕ делайте chown -R на весь /opt/shop (ломает git pull)
|
||||
cd /opt/shop
|
||||
git config --global --add safe.directory /opt/shop
|
||||
git pull
|
||||
npm install --omit=dev
|
||||
|
||||
# Запись только в data/ — для пользователя службы www-data
|
||||
mkdir -p /opt/shop/data
|
||||
chown -R www-data:www-data /opt/shop/data
|
||||
chmod +x /opt/shop/scripts/diagnose-502.sh 2>/dev/null || true
|
||||
|
||||
# Код — root, служба — www-data (только чтение кода достаточно)
|
||||
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
|
||||
```
|
||||
|
||||
Логи: `journalctl -u shop -f`
|
||||
`EnvironmentFile=/opt/shop/.env` должен содержать `DATABASE_URL`.
|
||||
|
||||
---
|
||||
|
||||
## Проверка после установки
|
||||
|
||||
Перед настройкой Caddy убедитесь, что Node отвечает локально:
|
||||
|
||||
```bash
|
||||
cd /opt/shop
|
||||
systemctl restart shop
|
||||
sleep 1
|
||||
|
||||
curl -s http://127.0.0.1:3000/health
|
||||
# {"ok":true,"service":"shop"}
|
||||
|
||||
ss -tlnp | grep 3000
|
||||
# LISTEN ... 127.0.0.1:3000 ... node
|
||||
|
||||
journalctl -u shop -n 5 --no-pager
|
||||
# должна быть строка: Магазин: http://127.0.0.1:3000
|
||||
# Магазин: http://127.0.0.1:3000 (PostgreSQL)
|
||||
```
|
||||
|
||||
Если в логе только «База уже содержит товары…» и служба сразу останавливается — обновите код: `git pull` (нужна актуальная версия `seed.js`).
|
||||
|
||||
После успешной проверки настройте Caddy и выполните:
|
||||
|
||||
```bash
|
||||
systemctl reload caddy
|
||||
```
|
||||
|
||||
Обновление на сервере одной командой:
|
||||
Обновление:
|
||||
|
||||
```bash
|
||||
bash /opt/shop/scripts/server-update.sh
|
||||
@@ -132,17 +138,9 @@ bash /opt/shop/scripts/server-update.sh
|
||||
|
||||
---
|
||||
|
||||
## Caddy — SSL и reverse proxy (рекомендуется)
|
||||
## Caddy — SSL и reverse proxy
|
||||
|
||||
[Caddy](https://caddyserver.com/) автоматически выпускает и продлевает сертификаты Let's Encrypt.
|
||||
|
||||
**Перед установкой:**
|
||||
|
||||
1. Домен указывает на IP сервера (A-запись).
|
||||
2. Открыты порты **80** и **443** в файрволе.
|
||||
3. Служба `shop` запущена и слушает `127.0.0.1:3000`.
|
||||
|
||||
### Установка Caddy на Ubuntu
|
||||
**Перед Caddy:** `curl http://127.0.0.1:3000/health` → OK.
|
||||
|
||||
```bash
|
||||
apt install -y debian-keyring debian-archive-keyring apt-transport-https
|
||||
@@ -150,21 +148,17 @@ curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
|
||||
| gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
|
||||
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
|
||||
| tee /etc/apt/sources.list.d/caddy-stable.list
|
||||
apt update
|
||||
apt install -y caddy
|
||||
```
|
||||
apt update && apt install -y caddy
|
||||
|
||||
### Конфигурация
|
||||
|
||||
В репозитории лежит пример: `caddy/Caddyfile.example`.
|
||||
|
||||
```bash
|
||||
# Замените shop.example.com и email на свои
|
||||
cp /opt/shop/caddy/Caddyfile.example /etc/caddy/Caddyfile
|
||||
nano /etc/caddy/Caddyfile
|
||||
nano /etc/caddy/Caddyfile # ваш домен и email
|
||||
|
||||
caddy validate --config /etc/caddy/Caddyfile
|
||||
systemctl enable caddy
|
||||
systemctl reload caddy
|
||||
```
|
||||
|
||||
Пример `/etc/caddy/Caddyfile`:
|
||||
Пример `Caddyfile`:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
@@ -177,178 +171,59 @@ shop.example.com {
|
||||
}
|
||||
```
|
||||
|
||||
Проверка и перезапуск:
|
||||
Порты **80/443** открыть; **5432** и **3000** наружу не публиковать.
|
||||
|
||||
```bash
|
||||
caddy validate --config /etc/caddy/Caddyfile
|
||||
systemctl enable caddy
|
||||
systemctl reload caddy
|
||||
systemctl status caddy
|
||||
```
|
||||
|
||||
Сайт откроется по домену из блока `Caddyfile` (в примере — `shop.example.com`).
|
||||
|
||||
Логи Caddy: `journalctl -u caddy -f`
|
||||
|
||||
### Файрвол (ufw)
|
||||
|
||||
```bash
|
||||
ufw allow 22/tcp
|
||||
ufw allow 80/tcp
|
||||
ufw allow 443/tcp
|
||||
ufw enable
|
||||
```
|
||||
|
||||
Порт **3000** наружу не открывайте — к приложению ходят только через Caddy.
|
||||
|
||||
### Порядок запуска после перезагрузки
|
||||
|
||||
1. `shop` (Node.js на `127.0.0.1:3000`)
|
||||
2. `caddy` (прокси + HTTPS)
|
||||
|
||||
### HTTP 502 при рабочем SSL
|
||||
|
||||
**SSL есть, 502 — значит Caddy жив, а Node на `127.0.0.1:3000` не отвечает.**
|
||||
|
||||
На сервере выполните:
|
||||
### HTTP 502
|
||||
|
||||
```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/data` |
|
||||
| `dubious ownership` / нет `git pull` | `chown -R root:root /opt/shop` + `safe.directory` (см. выше) |
|
||||
| В `.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` |
|
||||
| В логе seed и сразу `Deactivated` | `git pull` — старый `seed.js` вызывал `process.exit` до старта сервера |
|
||||
|
||||
**Быстрое восстановление:**
|
||||
|
||||
```bash
|
||||
cd /opt/shop
|
||||
git config --global --add safe.directory /opt/shop
|
||||
git pull
|
||||
npm install --omit=dev
|
||||
mkdir -p data
|
||||
chown -R www-data:www-data /opt/shop/data
|
||||
systemctl restart shop
|
||||
curl -s http://127.0.0.1:3000/health # должен быть {"ok":true,...}
|
||||
systemctl reload caddy
|
||||
```
|
||||
|
||||
**Ошибка `dubious ownership` при `git pull`:**
|
||||
|
||||
Вы сделали `chown -R www-data` на весь каталог. Верните владельца репозиторию root и оставьте `data/` за www-data:
|
||||
|
||||
```bash
|
||||
chown -R root:root /opt/shop
|
||||
chown -R www-data:www-data /opt/shop/data
|
||||
git config --global --add safe.directory /opt/shop
|
||||
git pull
|
||||
```
|
||||
|
||||
Пока `curl http://127.0.0.1:3000/health` не возвращает OK — HTTPS через Caddy будет отдавать 502.
|
||||
|
||||
### Другие проблемы
|
||||
|
||||
| Симптом | Решение |
|
||||
| Причина | Решение |
|
||||
|--------|---------|
|
||||
| Нет сертификата | DNS, порты 80/443, верный домен в `Caddyfile` |
|
||||
| Редирект-цикл / нет cookies | В `.env`: `TRUST_PROXY=1`, `NODE_ENV=production` |
|
||||
|
||||
---
|
||||
|
||||
## Обновление с Git
|
||||
|
||||
```bash
|
||||
cd /opt/shop
|
||||
bash scripts/server-update.sh
|
||||
```
|
||||
|
||||
Или вручную:
|
||||
|
||||
```bash
|
||||
cd /opt/shop
|
||||
git config --global --add safe.directory /opt/shop
|
||||
git pull
|
||||
npm install --omit=dev
|
||||
mkdir -p data && chown -R www-data:www-data data
|
||||
systemctl restart shop
|
||||
curl -s http://127.0.0.1:3000/health
|
||||
systemctl reload caddy # если меняли Caddyfile
|
||||
```
|
||||
| PostgreSQL не запущен | `systemctl start postgresql` |
|
||||
| Неверный `DATABASE_URL` | проверить `.env`, `psql` |
|
||||
| Node не слушает 3000 | `journalctl -u shop -f` |
|
||||
| Caddy без backend | сначала `curl /health`, потом `reload caddy` |
|
||||
|
||||
---
|
||||
|
||||
## Локальная разработка
|
||||
|
||||
```bash
|
||||
npm install
|
||||
docker compose up -d
|
||||
cp .env.example .env
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Сайт: [http://localhost:3000](http://localhost:3000)
|
||||
|
||||
## Переменные окружения
|
||||
|
||||
| Переменная | Описание | По умолчанию |
|
||||
|------------------|---------------------------|----------------|
|
||||
| `PORT` | Порт HTTP-сервера | `3000` |
|
||||
| `HOST` | Адрес привязки | `0.0.0.0` (dev), `127.0.0.1` (prod) |
|
||||
| `NODE_ENV` | Режим (`production` — secure cookies) | — |
|
||||
| `TRUST_PROXY` | Доверять заголовкам Caddy (`1`) | — |
|
||||
| `SESSION_SECRET` | Секрет для сессий | dev-значение |
|
||||
|
||||
## Скрипты npm
|
||||
|
||||
| Команда | Описание |
|
||||
|----------------|----------------------------------|
|
||||
| `npm start` | Запуск сервера (production) |
|
||||
| `npm run dev` | Запуск с автоперезагрузкой |
|
||||
| `npm run seed` | Заполнение каталога, если пуст |
|
||||
| Команда | Описание |
|
||||
|---------|----------|
|
||||
| `npm start` | Запуск сервера |
|
||||
| `npm run dev` | С автоперезагрузкой |
|
||||
| `npm run seed` | Демо-товары (если каталог пуст) |
|
||||
|
||||
## База данных
|
||||
|
||||
Локально в каталоге `data/`:
|
||||
|
||||
- `shop.db` — товары, пользователи, заказы
|
||||
- `sessions.db` — сессии
|
||||
|
||||
Каталог `data/` в git не попадает (см. `.gitignore`). Делайте резервные копии:
|
||||
|
||||
```bash
|
||||
cp -a data/shop.db data/shop.db.bak
|
||||
```
|
||||
|
||||
## Структура проекта
|
||||
## Структура
|
||||
|
||||
```
|
||||
caddy/
|
||||
Caddyfile.example — пример reverse proxy + SSL
|
||||
deploy/
|
||||
shop.service — unit для systemd
|
||||
postgres/init/01_schema.sql
|
||||
docker-compose.yml — PostgreSQL 17 локально
|
||||
caddy/Caddyfile.example
|
||||
deploy/shop.service
|
||||
scripts/
|
||||
diagnose-502.sh — проверка при 502
|
||||
server-update.sh — git pull + restart + health
|
||||
setup-postgres-ubuntu.sh
|
||||
diagnose-502.sh
|
||||
server-update.sh
|
||||
src/
|
||||
server.js — точка входа
|
||||
db.js — схема SQLite
|
||||
seed.js — демо-данные
|
||||
routes/ — маршруты
|
||||
views/ — шаблоны EJS
|
||||
public/css/ — стили
|
||||
```
|
||||
|
||||
## Миграция с SQLite
|
||||
|
||||
Старая версия хранила данные в `data/*.db`. После перехода на PostgreSQL выполните `git pull`, настройте `DATABASE_URL`, `npm install` — при первом запуске создастся схема и демо-каталог (если таблица `products` пуста). Пользователей и заказы из SQLite нужно переносить вручную или заново зарегистрироваться.
|
||||
|
||||
## Репозиторий
|
||||
|
||||
```bash
|
||||
|
||||
Reference in New Issue
Block a user