Установка редакции Node
Редакция Node работает как постоянная служба: один процесс, база SQLite на диске, больше ставить нечего — ни внешней базы данных, ни кеша, ни очереди. Это руководство ведёт от пустого каталога до рабочего экземпляра, включая то, что большинство руководств обходит стороной: системную службу, обратный прокси, TLS, резервные копии и обновление.
Что понадобится#
| Составляющая | Версия | Зачем |
|---|---|---|
| Node.js | 22 или новее | Продукт опирается на встроенный запуск тестов и на свежие интерфейсы. |
| Компилятор C | build-essential | better-sqlite3 собирается при установке, если для вашей платформы нет готового двоичного пакета. |
| Локальный диск | — | SQLite и загруженные файлы. Никогда сетевая шара: см. ниже. |
Машины с одним ядром и 512 МБ памяти хватит на несколько десятков человек. Важна не мощность, а диск: он должен быть локальным и резервируемым.
1. Получить и установить#
git clone <ваш-репозиторий> /var/www/toutadmin
cd /var/www/toutadmin
npm ci --omit=dev
npm ci, а не npm install: он ставит ровно то, что описано в файле
блокировки, и никогда его не переписывает. На сервере уехавшая версия — это сбой, которого
никто не понимает.
better-sqlite3 — единственная зависимость, собирающая код на C.
sudo apt install -y build-essential python3 решает почти все случаи.
2. Настроить#
cp .env.example .env
Всё задаётся переменными окружения — читаемыми из .env или выставляемыми вашим
диспетчером служб. Ни одна не обязательна: без .env экземпляр поднимается на
порту 3000 и отправляет вас в мастер установки.
| Переменная | По умолчанию | Что делает |
|---|---|---|
PORT | 3000 | Порт, на котором слушают. |
NODE_ENV | — | production в рабочем режиме: строгие куки, без подробных следов. |
SESSION_SECRET | создаётся | Скрепляет сессии. Если оставить пустым, создаётся в data/session.key. |
INSTALL_TOKEN | — | Требуется мастером до установки. Рекомендуется на открытом сервере. |
TRUST_PROXY | — | 1 за доверенным прокси — и только там. |
DB_PATH | data/app.sqlite | База данных. |
UPLOAD_DIR, CV_DIR, VAULT_DIR, SIGN_DIR, DOCS_DIR, BACKUP_DIR | внутри data/ | Папки файлов: фотографии, резюме, сейф, папка на подпись, поступившие документы, архивы. |
LOGIN_RATE_LIMIT | 10 | Попыток входа за четверть часа с одного адреса. |
GLOBAL_RATE_LIMIT | 300 | Запросов в минуту с одного адреса. |
API_RATE_LIMIT | — | Вызовов API в минуту на один токен. |
SESSION_IDLE_MINUTES | 60 | Бездействие, после которого сессия обрывается. |
SESSION_MAX_HOURS | 12 | Предельный срок, который не продлевает никакаяактивность. |
ADMIN_EMAIL, ADMIN_PASSWORD | — | Установка без интерфейса: создаёт администратора при запуске. |
Смена SESSION_SECRET разом выкидывает всех. Задайте его один раз, создайте
случайным
(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))") и
сохраните вместе с остальным. Никогда не оставляйте его в репозитории git.
Поставить установочный токен#
Между первым запуском и вашим приходом в мастер экземпляр принадлежит тому, кто его
нашёл: кто пришёл первым, тот и создаёт учётную запись администратора. Поэтому
задайте INSTALL_TOKEN до того, как откроете порт:
node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"
# затем в .env:
INSTALL_TOKEN=c3f1…
Мастер сравнивает его за постоянное время — обычное сравнение позволило бы угадывать токен посимвольно — и записывает каждый отказ. После установки мастер закрывается сам.
Установка без интерфейса#
Для автоматического развёртывания ADMIN_EMAIL и ADMIN_PASSWORD
создают администратора при запуске, минуя мастер. Затем уберите их: пароль в окружении
службы читает всякий, кто читает эту службу.
3. Права и где лежат данные#
sudo useradd --system --home /var/www/toutadmin --shell /usr/sbin/nologin toutadmin
sudo chown -R toutadmin:toutadmin /var/www/toutadmin/data
sudo chmod 750 /var/www/toutadmin/data
sudo chmod 640 /var/www/toutadmin/.env
Код может остаться только для чтения; писать нужно лишь в data/. SQLite создаёт
соседние файлы (-wal, -shm): важна папка, а не только сама
база.
NFS и SMB лгут о блокировке файлов. SQLite полагается на эту блокировку, чтобы не допустить двух одновременных записей: на сетевой шаре база в конце концов портится без предупреждения. Локальный диск, всегда, — и уезжает наружу резервная копия, а не база.
4. Служба#
Запущенный вручную продукт останавливается, как только вы закроете терминал. Отдайте его systemd:
# /etc/systemd/system/toutadmin.service
[Unit]
Description=Toutadmin
After=network.target
[Service]
Type=simple
User=toutadmin
WorkingDirectory=/var/www/toutadmin
EnvironmentFile=/var/www/toutadmin/.env
ExecStart=/usr/bin/node src/server.js
Restart=always
RestartSec=5
# Службе нужно писать только в data/.
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/www/toutadmin/data
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now toutadmin
sudo systemctl status toutadmin
journalctl -u toutadmin -f
Пять строк ужесточения — не украшение: ProtectSystem=strict делает всю файловую
систему недоступной для записи, а ReadWritePaths открывает единственную папку,
которой это нужно. Уязвимость произвольной записи тогда достаёт лишь до
data/.
5. Обратный прокси#
Никогда не выставляйте порт 3000 прямо в интернет: он не умеет TLS, и учиться ему незачем.
server {
listen 443 ssl http2;
server_name intranet.primer.ru;
ssl_certificate /etc/letsencrypt/live/intranet.primer.ru/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/intranet.primer.ru/privkey.pem;
client_max_body_size 20M; # загрузки в сейф, поступившие документы
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
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;
}
}
server {
listen 80;
server_name intranet.primer.ru;
return 301 https://$host$request_uri;
}
Заодно заставьте службу слушать только петлевой интерфейс, чтобы порт 3000 был доступен лишь прокси.
TLS#
sudo certbot --nginx -d intranet.primer.ru
Продукт выставляет заголовок Strict-Transport-Security, как только соединение
зашифровано, и никогда открытым текстом: объявленный с незашифрованной страницы он не был бы
прочитан, а локальную пробу запер бы в https на полгода.
TRUST_PROXY идёт вместе с прокси — и только с ним
Эта переменная велит серверу верить адресу источника, объявленному в заголовке. За nginx
это то, что нужно: иначе все запросы кажутся идущими с 127.0.0.1, и ограничения
по адресу ничего не защищают. Без прокси впереди всё наоборот: любой объявит адрес по своему
выбору и обойдёт ограничения.
6. Мастер установки#
Откройте свой домен. Пока нет ни одной учётной записи, любой адрес перенаправляет на
/installation. Пять шагов:
- Язык экземпляра, из 16.
- Требования, проверенные и показанные.
- Организация: название, которое видно всюду.
- Годовой отпуск, назначаемый каждому новому штатному сотруднику.
- Учётная запись администратора: адрес и пароль не короче двенадцати знаков.
Как только появляется учётная запись, /installation перенаправляет на страницу
входа: мастер закрылся сам, вручную удалять нечего.
7. Периодический обход#
В отличие от редакции PHP, планировать нечего: сервер несёт собственный планировщик, который просыпается каждый час и делает восемь вещей.
| Что делает обход | Гарантия |
|---|---|
| Закрывает учётные записи с истёкшим договором | Проверяется также при входе и при открытии панели |
| Выставляет счета по подписке, срок которых настал | Уникальный индекс не даёт выставить дважды |
| Превращает сроки в уведомления | Ключ устранения повторов: один срок напоминает один раз |
| Чистит прочитанные уведомления и журнал аудита | Согласно выбранному сроку хранения |
| Забирает бухгалтерский почтовый ящик (IMAP) | Если приём настроен |
| Опустошает очередь веб-перехватчиков с их повторами | Пять попыток, затем отказ |
| Создаёт автоматическую копию и выносит её наружу | Когда истёк заданный промежуток |
Каждая операция идемпотентна: повторный обход не выставит счёт дважды и не уведомит дважды. Именно это позволяет перезапускать службу в любой момент не задумываясь.
8. Проверить, что всё работает#
# тесты (в вашу базу они не пишут)
npm test
# демонстрационный экземпляр, чтобы походить
node scripts/seed-demo.js
Затем в интерфейсе: заведите сотрудника, войдите под ним, загрузите вложение, запустите резервную копию вручную и проверьте её на экране Резервные копии. Эти четыре действия затрагивают базу, файлы, права и архив.
9. Обновление#
# 1. сначала резервная копия, всегда — с экрана «Резервные копии»
# 2. код
cd /var/www/toutadmin
git pull
npm ci --omit=dev
# 3. перезапуск; база обновляется сама при старте
sudo systemctl restart toutadmin
journalctl -u toutadmin -n 30 --no-pager
Схема развивается идемпотентными миграциями: повторить обновление ничего не ломает.
data/ и .env не трогают никогда.
Откат#
Верните код на прежнюю версию и перезапустите. Миграции не удаляют столбцов: перенесённая база остаётся читаемой прежней версией, если в журнале версий не сказано иного. Если сомневаетесь — восстановите архив, снятый на шаге 1.
10. Резервировать по-настоящему#
Архив tar.gz несёт базу и пять папок с файлами. База копируется встроенным в
SQLite онлайновым резервированием, дающим согласованную копию даже во время записи —
редакция PHP добивается того же через VACUUM INTO. Каждый файл несёт свою
контрольную сумму SHA-256, проверяемую при восстановлении.
Архив, оставшийся на сервере, который он защищает, не защищает ничего: настройте внешнее
хранилище (FTPS или Google Drive) на экране Резервные копии и подпишите
веб-перехватчик на sauvegarde.echec, чтобы узнавать о неудачном выносе.
Подробности — на странице
Резервное копирование и восстановление.
Сначала попробовать локально#
npm install
cp .env.example .env
npm run dev # перезапускается при каждом изменении
Откройте http://localhost:3000. Локально оставьте NODE_ENV пустым:
в режиме production куки помечены «защищёнными» и не сохранятся на открытом
соединении — вы будете ходить по кругу на странице входа.
Перейти на редакцию PHP или прийти с неё#
Обе редакции делят одну схему — 142 таблиц, 1311 столбцов — и один
формат паролей. Остановите одну, скопируйте app.sqlite и папки файлов, запустите
другую: преобразования нет. См. Две редакции.
Документация Toutadmin — собрана 2026-09-13. Самостоятельный сайт, независимый от программы.