Linux: процессы, права и SSH
Оглавление · Жизненный цикл сервиса · Деплой Atmanki
Задача: понять, кто запускает приложение, к каким файлам оно имеет доступ и как удалённая команда попадает на VPS. Нужны основы JS и работа с терминалом. Linux-команды ниже предназначены для учебной машины; macOS отличается устройством служб и некоторыми утилитами. Production-настройки выполняются штатными скриптами.
Программа и процесс
Файл server.js — программа; запущенный Node — процесс с PID, памятью, рабочим каталогом, окружением и открытыми ресурсами. Два запуска одного файла создают разные процессы. Перезапуск теряет память: переменная JS не заменяет запись в БД. Веб-сервер живёт между запросами; обработчик запроса не получает новый процесс автоматически. Это важная граница при переходе от привычной модели PHP-запросов.
Исходник схемы
flowchart LR Shell["Shell: команда и окружение"] --> Process["Процесс Node: PID, память, cwd"] Process --> Streams["stdin / stdout / stderr"] Process --> Resources["Файлы, сокеты, соединения"] Process --> Child["Дочерний процесс: отдельная память"]
Shell разбирает командную строку: кавычки, подстановки и перенаправления.
> пишет stdout в файл, 2> — stderr, | соединяет stdout одной команды со
stdin другой. Успешный exit code означает успех по правилам самой программы:
он не доказывает готовность сайта. В bash set -o pipefail позволяет учитывать
ошибку внутри pipeline; без него статус обычно определяется последней командой.
Не печатайте полное окружение ради диагностики: в нём могут быть токены.
Для наблюдения на Linux достаточно начать с небольшого набора:
pwd
id
ps -eo pid,ppid,user,stat,comm
ss -ltn
systemctl status docker --no-pager
ps показывает процессы, ss — слушающие TCP-сокеты, systemctl — службу Docker
на хосте с systemd. Это разные вопросы. Наличие слушающего порта ещё не проверяет
HTTP-ответ, а состояние Docker daemon не проверяет приложения внутри контейнеров.
Не добавляем отдельный systemd-unit для Next: в Atmanki его запуском управляет Docker.
Сигнал — запрос к процессу
SIGTERM обычно просит завершиться; обработчик может закрыть сервер и соединения.
SIGINT обычно приходит от Ctrl+C. SIGKILL нельзя перехватить: процесс не выполнит
свой cleanup. kill отправляет сигнал, а не обязательно немедленно уничтожает
процесс. Сигналы Linux.
Корректное завершение требует ограниченного времени: прекратить приём новых запросов, закончить или отменить текущие, освободить ресурсы, выйти. Если операция в БД уже зафиксирована, сигнал не отменяет её. Если клиент не получил ответ, результат может быть неизвестен — см. идемпотентность.
Следующий пример проверяет только доставку сигнала своему дочернему процессу. Запустите из терминала с Node 24; он не открывает порт и не обращается к серверу.
node --input-type=module <<'JS'
import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import { once } from "node:events";
const child = spawn(process.execPath, ["-e",
'setInterval(() => {}, 1000); process.on("SIGTERM", () => process.exit(0)); process.send("ready");',
], { stdio: ["ignore", "ignore", "ignore", "ipc"] });
const controller = new AbortController();
const deadline = setTimeout(() => {
controller.abort();
child.kill("SIGKILL");
}, 5000);
try {
const [message] = await once(child, "message", { signal: controller.signal });
assert.equal(message, "ready");
const exited = once(child, "exit", { signal: controller.signal });
child.kill("SIGTERM");
const [code, signal] = await exited;
assert.equal(code, 0);
assert.equal(signal, null);
console.log("SIGTERM обработан; exit code 0");
} finally {
clearTimeout(deadline);
if (child.exitCode === null && child.signalCode === null) child.kill("SIGKILL");
}
JS
Таймер удерживает дочерний процесс; IPC-канал сообщает, что обработчик уже установлен. Это убирает гонку «послать SIGTERM до готовности». Deadline ограничивает ожидание; SIGKILL в cleanup относится только к процессу, созданному примером. Лаборатория не подтверждает graceful shutdown Next/Strapi: его проверяют с реальными запросами в изолированной среде, отдельно от production.
Права — отношение процесса к объекту
У процесса есть числовые идентификаторы пользователя и групп; у файла — владелец, группа и права. Имена пользователей удобны людям, но проверка доступа опирается на идентификаторы. Учётные данные процесса.
В базовой Unix-модели выбирается класс owner, group или other, затем проверяется нужное действие. Разрешения не складываются по всем трём классам. ACL, capabilities и политики безопасности могут добавлять правила; здесь рассматриваем обычные mode bits.
| Бит | Для обычного файла | Для каталога |
|---|---|---|
| r = 4 | Читать содержимое | Получать список имён |
| w = 2 | Изменять содержимое | Изменять записи каталога при наличии x |
| x = 1 | Исполнять | Проходить через каталог, обращаться к известному имени |
640 — owner rw, group r, other ничего; 750 — owner rwx, group rx,
other ничего. Чтобы открыть файл, нужны права прохода через каждый каталог пути.
Чтобы удалить файл, обычно важны права родительского каталога, а не право записи
в сам файл; sticky bit и другие правила могут ограничивать удаление.
Разрешение пути в Linux.
Практика без секретов — отдельный временный каталог:
lesson_dir=$(mktemp -d)
printf 'учебный текст\n' > "$lesson_dir/example.txt"
chmod 700 "$lesson_dir"
chmod 600 "$lesson_dir/example.txt"
ls -ld "$lesson_dir" "$lesson_dir/example.txt"
rm "$lesson_dir/example.txt"
rmdir "$lesson_dir"
Ожидайте drwx------ у каталога и -rw------- у файла. Не применяйте chmod 777 к настоящим секретам ради устранения Permission denied. Сначала проверьте, какой UID обращается к файлу и где именно не хватает доступа.
В контейнере USER node не означает, что пользователь хоста с тем же именем
имеет тот же UID. Для bind mount нужны совместимые числовые права. В Atmanki
web и Strapi работают под node; их Dockerfile и монтирования — источник истины.
Изменение владельца большого дерева данных не заменяет диагностику одного пути.
SSH проверяет две стороны
SSH создаёт защищённое соединение. Сначала клиент должен установить идентичность сервера по host key, затем сервер аутентифицирует пользователя. Публичный ключ в authorized_keys разрешает вход владельцу соответствующего приватного ключа; приватный ключ остаётся на стороне клиента. OpenSSH ssh.
Исходник схемы
sequenceDiagram participant Client as SSH-клиент participant Server as VPS Client->>Server: Установить соединение Server-->>Client: Host key сервера Note over Client: Проверить ожидаемый ключ Client->>Server: Подтвердить владение ключом пользователя Note over Server: Проверить authorized_keys и ограничения Server-->>Client: Выполнить разрешённую команду
known_hosts хранит известные ключи серверов. Новый fingerprint сверяют через доверенный канал, например консоль провайдера; ssh-keyscan сам по себе только получает предъявленный ключ. Изменение host key требует объяснения: переустановка или другой сервер возможны, но автоматическое удаление записи скрывает причину. StrictHostKeyChecking управляет этой проверкой. OpenSSH ssh_config.
Учебная команда после настройки своей машины, с вымышленным доменом:
ssh -o BatchMode=yes -o StrictHostKeyChecking=yes \
-i ~/.ssh/lesson_ed25519 lesson@example.invalid 'id; pwd'
BatchMode отключает интерактивные запросы; без заранее известного host key
строгая проверка завершится ошибкой. Одинарные кавычки передают id; pwd
удалённому shell. Сложные команды с локальными и удалёнными подстановками лучше
оформлять проверяемым скриптом. Не включайте agent forwarding без потребности.
bootstrap.sh создаёт atmanki-deploy, каталог .ssh с mode 0700 и authorized_keys с 0600; ключ ограничен запретом forwarding и PTY. Это не sandbox: пользователь состоит в группе docker, которая даёт полномочия уровня root через Docker daemon. Права группы docker. Доступ к релизу — административная возможность, даже без интерактивного терминала.
Проверьте понимание
- Процесс жив, но порт не слушается. Что подтверждает ps и что проверять дальше?
- У файла 600, у родителя нет x для процесса. Почему чтение не удаётся?
- После перезапуска исчез список в переменной JS. Где должно храниться такое состояние?
- SSH сообщил об изменившемся host key. Как получить подтверждение нового ключа?
- Почему успешная лаборатория SIGTERM не доказывает завершение активного HTTP-запроса?
Дальше — контейнер, готовность и релиз.