ПРОГРЕС:
0%

Урок 11 МІНІ-ПРОЄКТ

Час зібрати все вивчене в один справжній продукт. На кінець уроку в тебе буде сторінка http://91.219.61.30:<порт>/pulse.html, яка сама оновлюється кожні 30 секунд і показує живий стан сервера: час роботи, памʼять, диск, твоє імʼя. Це твій перший «бекенд + фронтенд» проєкт.

🧰 Що згадаємо й що нового

01

Перший скрипт: системна інформація в термінал

Підключаємось і починаємо
старт

Заходь на сервер (як у завданні 01) і створюй файл проєкту в домашній папці:

ssh [email protected]
cd ~
nano pulse.py

Встав цей код. Пояснення кожного рядка — у коментарях:

import subprocess          # щоб викликати команди shell з Python
import datetime            # щоб отримати поточний час

def run(cmd):
    # виконує команду в shell і повертає її вивід як рядок
    return subprocess.check_output(cmd, shell=True, text=True).strip()

print("⏱  Час:    ", datetime.datetime.now())
print("⏳ Uptime: ", run("uptime -p"))
print("💾 Памʼять:", run("free -h | awk '/Mem:/ {print $3 \"/\" $2}'"))
print("📀 Диск:  ", run("df -h / | awk 'NR==2 {print $3 \"/\" $2}'"))

Збережи (Ctrl+O, Enter, Ctrl+X) і запусти:

python3 pulse.py

Маєш побачити приблизно:

⏱  Час:     2026-05-02 14:31:08.421
⏳ Uptime:  up 12 days, 4 hours, 2 minutes
💾 Памʼять: 1.4G/3.8G
📀 Диск:   18G/40G
💡 Що тут нового? Модуль subprocess — це міст між Python і командним рядком. run("uptime -p") робить те саме, що ти набрав би в терміналі вручну, тільки результат потрапляє в Python.
02

Замість print — пишемо HTML-сторінку

Python генерує файл у твою ~/www/
генератор

Тепер той самий скрипт буде не друкувати в термінал, а записувати HTML у твою публічну папку. Сторінка одразу стане доступна в інтернеті.

Відкрий nano pulse.py і заміни весь вміст на:

import subprocess
import datetime

USER = "bodko.v"          # ← ЗАМІНИ на свій логін!
OUT  = f"/home/{USER}/www/pulse.html"

def run(cmd):
    return subprocess.check_output(cmd, shell=True, text=True).strip()

now    = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
uptime = run("uptime -p")
mem    = run("free -h | awk '/Mem:/ {print $3 \"/\" $2}'")
disk   = run("df -h / | awk 'NR==2 {print $3 \"/\" $2}'")

html = f"""<!doctype html>
<html lang="uk"><head><meta charset="utf-8">
<meta http-equiv="refresh" content="30">
<title>Pulse — {USER}</title>
<style>
  body {{ font-family:monospace; background:#0a0a12; color:#e8e8f3;
         padding:40px; max-width:600px; margin:auto; }}
  h1 {{ color:#fcee0a; }}
  .row {{ display:flex; justify-content:space-between;
          padding:8px 0; border-bottom:1px solid #222; }}
  .v {{ color:#00d9ff; }}
  .ok{{ color:#39ff14; }}
</style></head><body>
  <h1>💓 Pulse — {USER}</h1>
  <div class="row"><span>Оновлено</span><span class="v">{now}</span></div>
  <div class="row"><span>Uptime</span><span class="v">{uptime}</span></div>
  <div class="row"><span>Памʼять</span><span class="v">{mem}</span></div>
  <div class="row"><span>Диск</span><span class="v">{disk}</span></div>
  <p class="ok">● online</p>
</body></html>"""

with open(OUT, "w") as f:
    f.write(html)

print(f"✓ Записано {len(html)} байт у {OUT}")

Запусти й перевір у браузері:

python3 pulse.py
# має вивести: ✓ Записано XXXX байт у /home/bodko.v/www/pulse.html

Тепер відкрий: http://91.219.61.30:9000/pulse.html (заміни порт на свій).

💡 Зверни увагу на <meta http-equiv="refresh" content="30"> — це команда браузеру самому перевантажувати сторінку кожні 30 секунд.
⚠️ Якщо побачиш 403 Forbidden — у файла немає прав на читання. Виконай: chmod 644 ~/www/pulse.html.
03

Цикл: оновлюємо самі, без перезапуску

Скрипт має жити постійно
while True

Зараз скрипт спрацьовує один раз. Хочемо — щоб він сам перезаписував файл кожні 30 секунд. Додамо нескінченний цикл.

На самому початку файла додай:

import time

А весь блок «отримання даних + запис файла» оберни в while True:. Кінець скрипта тепер виглядає так:

while True:
    now    = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    uptime = run("uptime -p")
    mem    = run("free -h | awk '/Mem:/ {print $3 \"/\" $2}'")
    disk   = run("df -h / | awk 'NR==2 {print $3 \"/\" $2}'")

    html = f"""..."""   # шаблон HTML такий самий, як у Стадії 2

    with open(OUT, "w") as f:
        f.write(html)

    print(f"[{now}] оновлено")
    time.sleep(30)        # почекати 30 секунд

Запусти й залиш у терміналі:

python3 pulse.py

Кожні 30 секунд має зʼявлятись новий рядок [2026-05-02 14:32:07] оновлено. Тримай браузер відкритим — побачиш як міняється час.

Зупини скрипт Ctrl+C. У наступній стадії запустимо його у фоні.

💡 Як працює while True? Це цикл, який ніколи не завершується сам. Кожна ітерація = одне оновлення сторінки. time.sleep(30) приспає процес і не вантажитиме CPU.
04

Запуск у фоні через nohup

Щоб працювало навіть після виходу з SSH
фон

Згадай завдання 08 — nohup від'єднує процес від твого SSH-зʼєднання. Скрипт виживе після того, як ти закриєш термінал.

nohup python3 pulse.py > pulse.log 2>&1 &
echo $!         # ← запамʼятай цей номер (PID)

Перевір, що процес живий:

ps -p <PID>
# має показати рядок з python3

Спостерігай за логом у реальному часі:

tail -f pulse.log
# кожні 30 сек має зʼявлятись "[YYYY-MM-DD HH:MM:SS] оновлено"
# Ctrl+C — щоб припинити спостерігати (процес не зупиниться)

Тепер можеш сміливо exit з SSH — сторінка pulse.html й далі оновлюватиметься 24/7.

05

Перевірка і прибирання

Знайти, перевірити, зупинити
контроль

Уяви: ти зайшов завтра, $! вже забув, але хочеш зупинити свій pulse. Як знайти?

Знайти свій процес

# усі твої python-процеси
ps -u $USER | grep python

# шукаємо саме pulse.py за назвою
pgrep -af pulse.py

Виведе щось типу:

847291 python3 pulse.py

Перевірити, що сторінка справді оновлюється

# curl бачить заголовок Last-Modified — час останнього запису файла
curl -sI http://91.219.61.30:9000/pulse.html | grep -i modified
# має показати свіжий час, не старіший за 30 сек

Зупинити

kill <PID>
# або одразу всіх python-процесів від твого імені:
pkill -u $USER -f pulse.py

Перевір, що дійсно зупинено:

pgrep -af pulse.py
# нічого не виводить — процес мертвий ✓
⚠️ Не залишай скрипт навічно, якщо більше не використовуєш — навіть простий цикл займає невеликий шматочок памʼяті, а на одному сервері нас 34 людини.

Бонус: правильний шлях — cron

Робить те саме, але без while True
бонус

У реальних проєктах nohup + while True — це костиль для навчання. Промисловий стандарт — планувальник cron: він сам запускає твій скрипт за розкладом, і тобі не треба тримати процес у памʼяті.

Перероби скрипт

Видали з pulse.py:

  • рядок import time
  • рядок while True:
  • рядок time.sleep(30)
  • відступ у тілі циклу — все має йти на верхньому рівні

Тобто скрипт знов виконує запис один раз і завершується.

Налаштуй cron

crontab -e

Якщо вперше — спитає редактор, обери nano. У файл, що відкриється, додай один рядок в кінець:

* * * * * /usr/bin/python3 /home/bodko.v/pulse.py >> /home/bodko.v/pulse.log 2>&1

Збережи (Ctrl+O, Ctrl+X). cron сам видасть:

crontab: installing new crontab

Розшифровка п'яти зірочок

* * * * *  команда
│ │ │ │ │
│ │ │ │ └─ день тижня (0-6, 0=неділя)
│ │ │ └─── місяць (1-12)
│ │ └───── день місяця (1-31)
│ └─────── година (0-23)
└───────── хвилина (0-59)

* * * * *   = щохвилини
*/5 * * * * = кожні 5 хвилин
0 9 * * 1   = щопонеділка о 9:00

Перевір

crontab -l                        # показати свій список завдань
tail -f pulse.log                 # бачити, як cron додає рядки
💡 Чому це краще? Якщо твій скрипт з якоїсь причини впаде — cron спокійно запустить наступну ітерацію. while True впаде один раз — і все, mert. Cron — як вічний двигун.

Як прибрати cron

crontab -e
# видали свій рядок, збережи

# або видалити всі свої cron-завдання разом:
crontab -r

🎯 Самоперевірка

5 запитань. Спочатку дай свою відповідь у голові — потім розгорни.

1. У чому різниця між SSH і SFTP?
SSH — захищене зʼєднання для роботи в командному рядку сервера (виконуєш команди).
SFTP — той самий канал, але для передачі файлів (перетягуєш мишкою).
Обидва використовують той самий порт 22 і ті самі логін/пароль.
2. Я запустив python3 bot.py, потім закрив SSH — і бот помер. Чому?
Коли SSH-сесія закривається, Linux надсилає її процесам сигнал SIGHUP (hang up — «трубку поклали»), і вони завершуються. Виправлення: запускати через nohup … &, tmux або зробити сервіс через systemd.
3. Що робить кожен символ у команді nohup python3 bot.py > log.txt 2>&1 &?
nohup — ігнорувати SIGHUP, не вмирати при виході з SSH.
> log.txt — стандартний вивід (print) у файл log.txt.
2>&1 — потік помилок (stderr, номер 2) направити туди ж, куди й вивід (stdout, номер 1).
& — у фон, термінал одразу вільний.
4. Я не памʼятаю PID свого скрипта. Як знайти й вбити його за назвою файла?
Знайти: pgrep -af pulse.py — покаже PID + повну команду.
Вбити одним рухом: pkill -u $USER -f pulse.py.
-u $USER — тільки твої процеси, -f — шукати в повному рядку команди (а не тільки в назві бінарника).
5. Чому у завданні 10 у нас TOKEN = "..." прямо в коді — це погана практика?
Бо токен — це пароль бота. Якщо файл потрапить на GitHub, у Slack-чат, на скріншот — будь-хто зможе керувати твоїм ботом, читати чужі повідомлення, спамити з твого імені.
Правильно: зберігати в .env файлі, який не комітиться в git, і читати через os.environ["TOKEN"].