ЯдроКодаподготовка к экзаменам
Учебная платформа

Загружаем материалы

Подготавливаем материалы и навигацию по разделу.

От новой Ubuntu VM до сайта: HTTPS, уведомления и ротация логов

Автор: · Обновлено

Рабочий сервер — это воспроизводимая последовательность с контрольными точками. На учебной Ubuntu VM настроим доступ, HTTP-сайт, HTTPS и эксплуатационные проверки. Для публичного сертификата нужны собственный домен, правильные DNS-записи и доступность проверки центра сертификации; локальный пример не выпускает сертификат автоматически.

Подготовить доступ и обновления

Создайте отдельного администратора средствами Ubuntu, добавьте его публичный ключ, проверьте владельца домашнего каталога и права SSH-файлов. В новом терминале убедитесь, что работает вход и нужные команды sudo. Сохраните консоль VM, затем примените настройки SSH из [урока 47](/lessons/without-university/linux-developer-foundations/linux-developer-47). Не отключайте текущий доступ до проверки следующего.

Перед включением UFW разрешите фактический SSH-порт и проверьте сетевой экран провайдера. Для сайта нужны TCP 80 и 443; открытое правило не запускает слушающий процесс. Выполните обновление индексов APT, изучите план обновления пакетов, установите согласованные обновления и проверьте необходимость reboot. Автоматические security updates настраиваются отдельно с контролем результатов.

Сначала HTTP и точный virtual host

Установите nginx штатным пакетом. Создайте каталог сайта /srv/lab-site с читаемым index.html, разрешите пользователю Nginx проход по всем каталогам пути и создайте конфигурацию сервера:

server {
    listen 80;
    server_name lab.example.org;
    root /srv/lab-site;
    index index.html;
    location / {
        try_files $uri $uri/ =404;
    }
}

Замените домен своим, включите файл в конфигурацию Nginx по правилам пакета. sudo nginx -t проверяет синтаксис и доступность необходимых файлов; только после успеха применяют sudo systemctl reload nginx. Проверьте curl --resolve lab.example.org:80:127.0.0.1 http://lab.example.org/ непосредственно на VM и внешний запрос с другой машины. Первый запрос проверяет выбор virtual host, но специально обходит публичный DNS. Конфигурация Nginx.

Если работает страница по умолчанию, проверьте server_name, Host запроса и полный вывод nginx -T. При 403 исследуйте права всех каталогов пути, индексный файл и политики доступа; при 404 — соответствие URI и root. Эти ошибки не исправляются одинаковым chmod 777.

HTTPS и продление

Установите Certbot способом, который поддерживает выбранная Ubuntu, вместе с интеграцией Nginx. Проверьте A/AAAA-записи: недоступный IPv6 тоже может помешать проверке домена. Для обычного HTTP-01 порт 80 должен быть доступен извне.

sudo certbot --nginx -d lab.example.org
sudo certbot certificates
sudo certbot renew --dry-run
systemctl list-timers --all | grep certbot

Первая команда изменяет конфигурацию и получает настоящий сертификат для вашего домена. Не выполняйте её для example.org. После выпуска проверьте HTTPS без -k, срок действия и перенаправление HTTP. renew --dry-run использует тестовый процесс продления и позволяет проверить challenge, но не обещает, что будущая сеть и DNS останутся исправными. Способ расписания зависит от установки Certbot: это может быть timer, cron или механизм snap. Зафиксируйте именно свой. Руководство Certbot.

Уведомление: от df до подтверждённой отправки

Сначала воспроизведите весь маршрут на локальном HTTP mock, который вернёт {"ok":true,"result":{"message_id":17}}. Реальный адаптер подключают только к своему разрешённому чату. В защищённом каталоге создайте curl.conf с режимом 600:

url = "http://127.0.0.1:18081/sendMessage"

Для настоящего Telegram URL заменяется на https://api.telegram.org/bot<TOKEN>/sendMessage внутри этого файла. Не передавайте токен в argv, shell history или журнал. Ограничьте доступ к каталогу, не добавляйте verbose и не публикуйте файл. chat_id сам по себе токеном аутентификации не является; всё равно избегайте ненужной публикации идентификатора.

Чтобы локальный endpoint действительно существовал, сохраните mock-telegram.py и запустите python3 mock-telegram.py 18081 в отдельном терминале:

import http.server
import json
import sys
import urllib.parse

class Handler(http.server.BaseHTTPRequestHandler):
    def do_POST(self):
        size = int(self.headers.get("Content-Length", "0"))
        fields = urllib.parse.parse_qs(self.rfile.read(size).decode())
        valid = bool(fields.get("chat_id") and fields.get("text"))
        rate = self.path == "/rate"
        response = {"ok": valid and self.path == "/sendMessage"}
        if rate:
            response["parameters"] = {"retry_after": 3}
        self.send_response(429 if rate else 200)
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        self.wfile.write(json.dumps(response).encode())

    def log_message(self, *args):
        pass

server = http.server.HTTPServer(("127.0.0.1", int(sys.argv[1])), Handler)
print(server.server_address[1], flush=True)
server.serve_forever()

Сначала дождитесь напечатанного порта. Mock слушает только loopback и никуда не пересылает сообщения. URL /sendMessage возвращает ok=true при обоих полях, /false — ok=false, /rate — HTTP429 с retry_after. Для каждой проверки меняйте только URL в собственном curl.conf; завершите mock через Ctrl+C после упражнения. Если порт занят, выберите свободный и одинаково измените команду запуска и URL.

Сохраните notify-disk.sh. Аргументы — порог, проверяемый путь, curl.conf и chat_id. Коды выхода: 0 — порог не превышен; 1 — превышен, сообщение принято; 2 — ошибочный ввод или замер; 3 — ошибка уведомления.

#!/usr/bin/env bash
set -uo pipefail
limit=${1:-80}
path=${2:-.}
config=${3:-}
chat_id=${4:-}
[[ $limit =~ ^(0|[1-9][0-9]?|100)$ && -r $config && -n $chat_id ]] || exit 2
if ! report=$(LC_ALL=C df -P -- "$path"); then exit 2; fi
used=$(awk 'NR==2 {gsub(/%/, "", $5); print $5}' <<< "$report")
[[ $used =~ ^[0-9]+$ ]] || exit 2
(( used >= limit )) || exit 0
notify_tmp=$(mktemp -d) || exit 2
trap 'rm -f -- "$notify_tmp/message.txt" "$notify_tmp/response.json"; rmdir -- "$notify_tmp"' EXIT
printf 'Disk usage: %s%%; threshold: %s%%; path: %s\n' \
  "$used" "$limit" "$path" > "$notify_tmp/message.txt"
curl_status=0
http_status=$(curl --config "$config" --silent --show-error \
  --fail-with-body --connect-timeout 3 --max-time 10 \
  --output "$notify_tmp/response.json" --write-out '%{http_code}' \
  --data-urlencode "chat_id=$chat_id" \
  --data-urlencode "text@$notify_tmp/message.txt") || curl_status=$?
if (( curl_status != 0 )); then
  printf 'Notification failed: curl=%s http=%s\n' "$curl_status" "$http_status" >&2
  if [[ $http_status == 429 ]]; then
    jq -r '.parameters.retry_after // "not supplied"' "$notify_tmp/response.json" >&2
  fi
  exit 3
fi
[[ $http_status =~ ^2[0-9][0-9]$ ]] || exit 3
jq -e '.ok == true' "$notify_tmp/response.json" >/dev/null || exit 3
printf 'Notification accepted; disk=%s%%\n' "$used"
exit 1

Порог 0 на существующем пути гарантированно включает отправку в тесте. Локальный ответ HTTP 200/ok=true должен дать код 1; HTTP 200/ok=false, HTTP 429 и тайм-аут — 3; отсутствующий путь — 2 и ни одного запроса. Текст передаётся URL-кодированным вместе с chat_id. Успешный HTTP без ok=true не считается успешной отправкой. При 429 скрипт выводит retry_after, но не устраивает бесконечные повторы: расписание должно отложить следующую попытку. Между запусками полезно хранить состояние тревоги, объединять повторы и отправлять восстановление после нормализации. Inode проверяют отдельным замером df -i. Контракт sendMessage.

Ротация с настоящим переоткрытием файла

У Nginx используйте правило штатного пакета, проверив его postrotate. Для своего приложения договоритесь о поддерживаемом механизме переоткрытия. В следующей лаборатории Python-процесс реализует SIGUSR1 специально для этой задачи. Создайте writer.py:

import signal
import sys

log = open(sys.argv[1], "a", buffering=1)

def reopen(signum, frame):
    global log
    log.close()
    log = open(sys.argv[1], "a", buffering=1)
    log.write("after-reopen\n")

signal.signal(signal.SIGUSR1, reopen)
log.write("before-rotation\n")
while True:
    signal.pause()

Запустите его только на своём временном файле:

rotation_lab=$(mktemp -d)
python3 writer.py "$rotation_lab/app.log" &
writer_pid=$!

Дождитесь строки before-rotation в app.log, чтобы обработчик сигнала успел установиться. Затем создайте правило; here-doc раскрывает только ваши временный путь и PID:

cat > "$rotation_lab/rotate.conf" <<EOF
$rotation_lab/app.log {
    daily
    rotate 7
    compress
    delaycompress
    missingok
    notifempty
    create 0600
    postrotate
        kill -USR1 $writer_pid
    endscript
}
EOF
logrotate --debug --state "$rotation_lab/state" "$rotation_lab/rotate.conf"
logrotate --force --state "$rotation_lab/state" "$rotation_lab/rotate.conf"

Create без owner/group сохраняет их от исходного файла, а режим 0600 ограничивает доступ. После обработки сигнала старый app.log.1 должен содержать before-rotation, новый app.log — after-reopen. Проверьте stat -c '%i %a %n' "$rotation_lab/app.log" "$rotation_lab/app.log.1": разные inode и новый режим подтверждают смену файла. Завершите только свой процесс через kill -TERM "$writer_pid" и wait "$writer_pid", обработав ожидаемое завершение от сигнала.

Daily задаёт расписание ротации, rotate — число архивов, delaycompress оставляет последний архив несжатым до следующей ротации. Отдельный state-файл не затрагивает системный logrotate. Copytruncate имеет окно потери записей между копированием и обрезанием; его выбирают только при осознанных ограничениях приложения. Для реальной службы заменяют сигнал сохранённому PID на штатный механизм конкретного service manager и задают её пользователя/группу. Поведение logrotate.

Задание: сохраните доказательства HTTP/HTTPS, dry-run продления, четырёх веток уведомления и смены inode после ротации. Отметьте отдельно локальный тест протокола и настоящую доставку: успешный mock не подтверждает права бота или существование внешнего чата.

Проверить срок локального сертификата

Для локального PEM-сертификата openssl x509 -in certificate.pem -noout -enddate покажет дату окончания. openssl x509 -in certificate.pem -noout -checkend 604800 проверяет, не истечёт ли он в ближайшие семь суток, и возвращает ненулевой код при близком истечении. Этот тест не проверяет доверенную цепочку, соответствие домену и отзыв сертификата; успешный HTTPS-клиент проверяет другой набор условий. Ошибку чтения PEM также нельзя выдавать за нормальный срок действия.

Проверки OpenSSL x509 различают дату истечения и полноценную проверку доверия.

Источники