Заняття 12 · місяць 3

Реліз 1.0 і демо-день

120 хвилин — це заняття довше за решту

Мета заняття: випустити версію 1.0 — поставити на неї незмінну позначку в git і дописати перелік змін — а потім показати робочий сервіс людям, які бачать його вперше й відкривають зі своїх телефонів.

Мій сайт
http://91.219.61.4/u/<логін>/
Мій сабдомен
https://<логін>.<домен>/
Мій кабінет
http://91.219.61.4/k/<логін>/
Вхід на сервер
ssh <логін>@91.219.61.4

Замість <логін> підставляєш свій логін виду bohdan.g9: ім’я, крапка, номер групи. Кутові дужки не пишуться. У прикладах на цій сторінці скрізь стоїть bohdan.g9 — читай це як свій логін і виправляй у кожній команді, яку копіюєш. Кабінет — сторінка, де проти кожного критерію стоїть відмітка, зарахований він чи ні.

<домен> — назва, яку ти записав у файл ~/app/project.yaml на занятті 10, розділ server, поле url. Сабдомен (із заняття 10) — це частина адреси перед доменом: в імені bohdan.g9.klub.example домен — klub.example, а сабдомен — bohdan.g9. Саме цю адресу ти сьогодні даєш гостям, тому перевір її першою справою.

Перші два рядки ведуть на одне й те саме: за обома адресами nginx віддає файли з твоєї теки ~/www, а запити, в адресі яких є /api/, передає твоїй службі. Різниця тільки в тому, як записана адреса самої служби: за сабдоменом це /api/ping, а за адресою з цифрами — /u/<логін>/api/ping. Сторінка звертається до служби за відносною адресою (заняття 06), тому працює за обома.

Що ти зробиш сьогодні

Спершу ти доводиш проєкт до стану, який можна показувати: проганяєш тести, дописуєш CHANGELOG.md щонайменше до восьми записів, ставиш у git тег v1.0 і оновлюєш сервіс на публічній адресі до цієї версії.

Далі — показ. По п’ять хвилин на людину: розповідаєш, для кого сервіс, показуєш три дії й одну історію про те, що в тебе зламалося і як ти це полагодив. Поки ти говориш, гості відкривають твою адресу на своїх телефонах і додають у сервіс справжні записи.

Наприкінці — голосування за три номінації: найкорисніший сервіс, найакуратніший, найстійкіший до злому.

Заняття довге, тому час розписаний наперед:

Частина заняттяЧас
Теорія: п’ять розділів і п’ять питань15 хв
Практика, кроки 1–10: підготовка релізу20 хв
Практика, кроки 11–12: підготовка до виступу8 хв
Крок 13: показ, дванадцять виступів по шість хвилин72 хв
Голосування й підсумок5 хв
Разом120 хв

Теорія

1. Реліз: чим версія відрізняється від чергового коміта

За три місяці в твоєму репозиторії накопичилося кількадесят комітів. Репозиторій (із заняття 03) — це тека, у якій git зберігає всю історію проєкту: кожен збережений стан файлів і порядок, у якому вони змінювалися. У тебе таких тек дві: ~/app, де ти працюєш, і ~/repo.git, куди ти надсилаєш готове. Коміт (теж із заняття 03) — це один збережений стан усіх файлів проєкту з підписом, що саме змінилося. Коміти йдуть один за одним, і серед них є всякі: закінчена робота, половина роботи, виправлення друкарської помилки в коментарі.

Сьогодні з цього ряду треба вибрати один коміт і сказати вголос: ось цей стан ми показуємо людям. Такий вибраний і названий стан називають релізом.

Реліз
Версія програми, яку віддають користувачам. Не будь-який стан коду, а той, про який автор сказав: цей працює, ним можна користуватися.
Версія
Номер, яким позначають реліз, наприклад 1.0. Номер потрібен, щоб можна було назвати конкретний стан програми одним словом.

Приклад із життя. Гра в тебе на телефоні пише «версія 2.14». Розробники за час між попереднім оновленням і цим зробили сотні змін у коді, але користувач бачить один номер і одне речення про те, що нового. Він не читає їхню історію змін по кроках — йому потрібен саме номер.

Навіщо це тобі сьогодні. Під час показу перед десятком людей може статися що завгодно: хтось надішле дивні дані, служба перезапуститься, ти сам випадково збережеш недописаний файл. Якщо в тебе є названий стан коду, повернутися до нього — одна команда. Якщо немає, ти шукатимеш потрібний коміт у списку з сорока рядків, поки гості дивляться.

Служба (із заняття 02) — це твоя програма, яку запускає і стежить за нею сама операційна система: після перезавантаження сервера чи після помилки вона запускає програму знову, і тобі не треба щоразу заходити на сервер і запускати її руками. Твоя служба називається club-api — це ім’я їй дав викладач, коли створював тобі акаунт, і воно однакове в усій групі. Керують службою командою systemctl, а прапорець --user у ній означає «моя особиста служба, а не загальносерверна»: чужих служб ти цією командою не бачиш і не чіпаєш.

Правило на сьогодні Після того як ти поставив тег, код до кінця показу не змінюється. Знайшов помилку — запиши її в FEEDBACK.md і виправ після демо. Правка «на одну хвилинку» посеред демо-дня — найчастіша причина, чому сервіс перестає відповідати саме тоді, коли до нього підійшли гості.

2. Номер версії: що означає 1.0

Номер версії складається з чисел, розділених крапками. У найпоширенішому порядку їх три: 1.4.2. Кожне число відповідає за свій розмір змін і рахується окремо від сусідніх: після 1.9.0 йде 1.10.0, а не 2.0.0, бо дев’ятка просто зростає до десяти.

ЧислоКоли зростаєПриклад
перша Змінилося так багато, що старі звички користувача більше не працюють Був список справ, став календар з іншим виглядом сторінки: 1.4.22.0.0
друга З’явилося щось нове, але все старе працює як працювало Додав кнопку «Поділитися посиланням»: 1.4.21.5.0
третя Тільки виправлення: нічого нового не з’явилося Виправив дату, яка показувалася на день раніше: 1.4.21.4.3

Третє число часто не пишуть, поки його нема чим заповнювати. Тому сьогоднішній тег називається v1.0, а не v1.0.0 — так коротше, і сенс той самий: перший реліз, доповнень і виправлень після нього ще не було. Літера v на початку — від слова version, вона нічого не змінює, але за нею одразу видно, що це номер версії, а не якесь інше число.

Приклад із життя. Ти оновив додаток і бачиш «1.4.3, виправлено помилку, через яку додаток закривався сам при відкритті фото». Останнє число зросло на одиницю — можна очікувати, що все на місці й нічого переучувати не доведеться. Якщо ж номер став «2.0», варто прочитати, що там нового: там щось переробили помітно.

Питання

Ольга випустила версію 1.0. Наступного дня вона помітила, що в повідомленні про помилку написано «Назва не може бути порожня.» замість «Назва не може бути порожньою.», і виправила цей текст. Більше вона нічого не чіпала. Який номер поставити новій версії?

3. Тег у git: незмінна позначка на коміті

У кожного коміта є ідентифікатор — рядок виду 9f3c1ab4e7d2... на сорок символів, який git рахує сам. Скорочено пишуть перші сім: 9f3c1ab. Такий рядок неможливо запам’ятати й незручно диктувати. Тому потрібному коміту дають ім’я.

Тег
Ім’я, прив’язане до конкретного коміта. Після git tag v1.0 усюди, де git очікує ідентифікатор коміта, можна писати v1.0.

Головна властивість тега — він не рухається. Порівняй із гілкою. Гілка — це теж ім’я, прив’язане до коміта, але рухоме: у тебе гілка одна, вона називається master і завжди показує на останній коміт. Щойно ти зробиш новий коміт, ім’я master почне показувати вже на нього. Тег v1.0 показуватиме на той самий коміт і завтра, і через рік, скільки б комітів ти не додав після нього. Через це тег і годиться для релізу: за ним завжди знаходиться саме той стан коду, який бачили гості.

Теги бувають двох видів.

Легкий тег
git tag v1.0 — тільки ім’я і нічого більше. Хто поставив і коли, git не збереже.
Анотований тег
git tag -a v1.0 -m "текст" — окрім імені, git запише автора, дату й повідомлення, яке ти дав після -m. Саме такий ми й ставимо: через півроку буде видно, що це за версія і коли вона з’явилася.

Три команди, які знадобляться на практиці:

git tag -a v1.0 -m "Реліз 1.0: перша версія, показана на демо-дні"
git tag -n
git show v1.0

git tag -n друкує список тегів разом із першим рядком повідомлення. git show v1.0 показує сам тег і коміт, на який він вказує: автора, дату, список змінених файлів.

Теги надсилаються окремо від комітів git push origin master надсилає у ~/repo.git коміти гілки master, але теги лишає на місці. Щоб тег опинився й там, його надсилають окремою командою: git push origin v1.0. Про це забувають частіше, ніж про будь-що інше на цьому занятті, а критерій L12.1 перевіряє саме репозиторій ~/repo.git.

Слово origin у цих командах — не команда git і не гілка, а коротке ім’я, яким твій репозиторій ~/app називає інший репозиторій, куди надсилає роботу. Ти задав його на занятті 03, і показує воно на ~/repo.git. Побачити, куди саме origin веде, можна командою git remote -v.

Питання

Богдан поставив тег v1.0, виконав git push origin master і пішов на перерву. Автоперевірка написала, що тег v1.0 не знайдено. Що сталося?

4. CHANGELOG: перелік змін, написаний для людини

CHANGELOG (читається «ченджлог», від change log — запис змін) — файл, у якому по версіях перелічено, що змінилося в програмі. Лежить у корені репозиторію й називається CHANGELOG.md.

У тебе вже є git log — список усіх комітів. Різниця між ними у тому, для кого вони написані. git log пише про кожен коміт словами того, хто цей код писав: «поправив імпорт», «те саме, але без зайвого запиту». CHANGELOG пише про зміни, які помітив би користувач, і словами, зрозумілими без коду.

Приклад із життя. У магазині додатків під кнопкою «Оновити» є короткий текст «що нового» — це і є CHANGELOG. Ніхто не показує там перелік комітів: він нічого не пояснить людині, яка просто хоче знати, чи полагодили те, що їй заважало.

Як виглядає запис. Кожен рядок — одна зміна, що починається з дієслова: «Додано», «Виправлено», «Прибрано», «Змінено». Порівняй два записи про одне й те саме. У правому стовпці трапляються числа 500 і 422 — це коди відповіді, якими сервер відповідає браузерові на кожен запит. Ти бачив їх на заняттях 05 і 08; коротко: 200 — усе гаразд, сторінку віддано; 404 — за такою адресою нічого немає; 422 — запит дійшов, але дані в ньому не годяться, наприклад порожня назва; 500 — у програмі на сервері сталася помилка, і вона не змогла відповісти.

Так не годитьсяТак годиться
поправив main.py Виправлено помилку 500 при порожній назві: тепер сервіс відповідає 422 й пояснює, що поле обов’язкове
зробив дизайн Додано підписи до полів форми й напис «Поки що жодного запису» для порожнього списку
оптимізація Зменшено вагу сторінки з 812 КБ до 176 КБ: картинки переведено у формат WebP

Ліві записи неможливо прочитати через місяць: у них немає ні того, що змінилося, ні того, для кого це важливо. Праві читаються без коду й без пояснень автора.

Основні записи ти вже приніс із домашнього завдання заняття 11 — там треба було виписати щонайменше вісім змін, поки деталі ще свіжі в пам’яті. Сьогодні залишається перевірити їх, додати те, що ти зробив уже після здачі того завдання, і поставити зверху заголовок версії.

Питання

Іван відкрив CHANGELOG.md і побачив у себе рядок «Виправив баг у db.py». Автоперевірка рахує записи і цей теж порахує. Чому цей рядок усе одно варто переписати?

5. Показ на п’ять хвилин

Демо — це коли ти показуєш працюючий сервіс людям, які його ще не бачили: батькам, друзям, молодшій групі. У тебе рівно п’ять хвилин і хвилина на те, щоб змінити доповідача. Дванадцять людей по шість хвилин — це сімдесят дві хвилини, тобто більша частина сьогоднішнього заняття.

Виступ складається з чотирьох частин у такому порядку.

ЧастинаЧасЩо сказати
Проблема 1 хв Для кого ти це зробив і що цій людині заважало. Одна конкретна людина з іменем, а не «усі підлітки».
Рішення 1 хв Що робить сервіс і чому саме так. Двома-трьома реченнями, без назв бібліотек.
Показ 2 хв Три дії на живій сторінці. Вирішені заздалегідь, а не «зараз щось спробуємо».
Історія поломки 1 хв Що в тебе зламалося за три місяці, як ти про це дізнався і що зробив.

Кілька порад, які економлять час на показі.

Окрема річ, до якої треба підготуватися: гості користуються сервісом по-справжньому. Це означає, що десяток людей одночасно відкриють твою адресу й почнуть щось додавати. Такого не дасть жоден тест, який ти запускаєш у себе. Тому перед показом потрібні дві речі: свіжа копія бази (раптом хтось додасть двадцять порожніх записів і ти захочеш повернути список до попереднього стану) і відкритий журнал служби в сусідньому вікні терміналу, щоб одразу бачити помилки.

Журнал — це записи, які служба веде про свою роботу: коли запустилася, які запити отримала, на чому зупинилася через помилку. Дивляться його командою journalctl --user -u club-api: --user означає «моя особиста служба», а -u club-api — «журнал саме цієї служби, а не всіх одразу».

Питання

Посеред виступу Марка сторінка перестала відкриватися: гості бачать помилку. До кінця його часу лишилося дві хвилини. З чого починати?

Питання

До початку демо лишилося десять хвилин. Софія бачить, що один із п’яти тестів не проходить: test_delete_missing_gives_404. Що робити?

Практика

У прикладах — таблиця habit і адреси /api/habits із занять 04 і 05. Підставляй свої назви: вони записані у project.yaml у розділах database і endpoints (розділи у цьому файлі названі англійською — так їх читає програма перевірки, перекладати їхні назви не можна).

Три слова, які часто трапляються далі на цій сторінці.

API
Читається «ей-пі-ай», від application programming interface. Це та частина твого сервісу, до якої звертається не людина очима, а програма: сторінка в браузері. Адреси, що починаються з /api/, віддають не оформлену сторінку, а самі дані. Звідси й ім’я твоєї служби — club-api.
Адреса API (у project.yaml — розділ endpoints)
Одна конкретна адреса разом зі словом, яке каже, що з нею робити: GET /api/habits — віддай список, POST /api/habits — прийми новий запис, DELETE /api/habits/{id} — видали запис із таким номером. Слово перед адресою браузер надсилає разом із запитом, і сервіс за ним розрізняє, чого від нього хочуть.
JSON
Читається «джейсон». Формат, у якому дані записують текстом так, щоб їх легко читала програма: список у квадратних дужках, кожен запис у фігурних, поле й значення через двокрапку. Саме в ньому GET /api/habits віддає твій список.
Частина перша: підготовка релізу Кроки 1–10, приблизно 20 хвилин. Мета — щоб до початку показу на публічній адресі працювала версія з тегом v1.0.
1

Увійти й подивитися стан репозиторію

ssh bohdan.g9@91.219.61.4
cd ~/app
git status --short
git log --oneline | head -5

git status --short має відповісти порожнечею — це означає, що всі зміни закомічені. Якщо там є рядки, закомить їх зараз:

git add -A
git commit -m "Останні правки перед релізом"

git log --oneline | head -5 друкує п’ять останніх комітів по одному рядку: скорочений ідентифікатор і повідомлення. Подивись на верхній рядок — саме на цей коміт стане тег v1.0, якщо ти більше нічого не змінюватимеш.

2

Прогнати тести

cd ~/app
/opt/club/venv/bin/python -m pytest -q tests

Тут два слова потребують пояснення. /opt/club/venv — це спільна тека, у яку адміністратор один раз установив python разом із бібліотеками курсу (FastAPI та інші). Такий набір називають віртуальним середовищем, скорочено venv. Просто python писати не можна: системний python цих бібліотек не бачить, і тести одразу зупиняться з повідомленням ModuleNotFoundError. pytest — програма, яка знаходить у теці tests усі функції-тести, виконує їх і звітує, які з них дали очікуваний результат. Прапорець -q робить вивід коротким.

Очікуваний вивід — п’ять крапок і рядок про те, що все пройдено:

.....                                                 [100%]
5 passed in 0.71s

Якщо хоч один тест не проходить, зупинись тут і розберись. Це або справжня помилка в коді, або тест, який більше не відповідає тому, як працює сервіс. В обох випадках ставити тег зарано: ./deploy.sh усе одно зупиниться на тестах і не оновить сервіс.

Читай вивід pytest знизу вгору. Усередині тесту стоїть рядок зі словом assert — це вимога «ось це має бути правдою»; якщо вона не справджується, тест зупиняється саме на ній. Pytest друкує цей рядок і два значення поруч: чого тест чекав і що отримав насправді.

3

Останні правки: не більше двох

У тебе є FEEDBACK.md — зауваження трьох людей із домашнього завдання заняття 10. Відкрий його:

less ~/app/FEEDBACK.md

less показує файл посторінково: пробіл — далі, q — вийти.

Вибери звідти щонайбільше дві правки, кожна з яких робиться за чотири хвилини: перейменувати кнопку, дописати підказку під полем, поміняти порядок сортування. Усе інше перенеси у файл PLANS.md — це список того, що робитимеш після курсу.

nano ~/app/PLANS.md

Файли з розширенням .md пишуться у форматі Markdown — той самий, що в README.md із заняття 03. Правил тут потрібно два: рядок, який починається з # і пробілу, стає заголовком, а рядок, який починається з - і пробілу, стає пунктом списку.

# Що зробити після курсу

- Нагадування о 20:00 (просив брат, потрібна робота з часом).
- Сторінка статистики за тиждень.
- Можливість редагувати назву запису, а не тільки видаляти й додавати.

Після кожної правки в коді — тести й коміт:

/opt/club/venv/bin/python -m pytest -q tests
git add -A
git commit -m "Кнопку перейменовано на «Додати до списку» за зауваженням із FEEDBACK"
4

Дописати CHANGELOG.md

Файл ти почав у домашньому завданні заняття 11. Відкрий його й доведи до вигляду нижче: зверху заголовок версії з датою, під ним записи.

nano ~/app/CHANGELOG.md
# Що змінилося

Проєкт: Трекер звичок. Автор: Богдан.
Номер версії: перше число — реліз, друге — нові можливості,
третє — виправлення.

## 1.0 — 13 вересня 2026

Перша версія, показана на демо-дні.

- Додано сторінку зі списком звичок і формою додавання.
- Додано збереження звичок у базу SQLite замість текстового файлу:
  дані не зникають після перезапуску служби.
- Додано адресу GET /api/habits — список звичок у форматі JSON.
- Додано адресу POST /api/habits — додавання звички з форми.
- Додано адресу DELETE /api/habits/{id} — видалення звички.
- Додано вхід власника за паролем: дивитися список може будь-хто,
  додавати й видаляти — тільки я.
- Виправлено помилку 500 при порожній назві звички: тепер відповідь 422
  з поясненням, що поле обов’язкове.
- Виправлено видалення неіснуючої звички: замість помилки 500 приходить 404.
- Додано напис «Поки що жодної звички» замість порожньої сторінки.
- Додано підписи до полів форми, щоб було зрозуміло, що вводити.
- Зменшено вагу сторінки з 812 КБ до 176 КБ: картинки переведено у формат WebP.
- Додано щоденну копію бази й інструкцію RUNBOOK.md на випадок,
  коли сервіс не відповідає.

Розберемо розмітку. Один # на початку рядка — заголовок першого рівня, два ## — заголовок другого рівня, тобто підрозділ. Рядок, що починається з -, стає пунктом списку. Порожній рядок між абзацами обов’язковий: без нього Markdown вважатиме два сусідні рядки одним абзацом.

Дату пиши справжню — ту, коли проходить демо-день. Записів має бути щонайменше вісім, у прикладі їх дванадцять. Перечитай свої: якщо рядок не зрозумілий людині, яка не бачила твого коду, перепиши його за зразком із теорії.

5

Закомітити CHANGELOG

cd ~/app
git add CHANGELOG.md PLANS.md
git commit -m "CHANGELOG: перелік змін від першого заняття до релізу 1.0"
git log --oneline | head -3

Тепер верхній коміт у списку — це той стан, який стане версією 1.0. Далі до кінця показу нічого в коді не змінюємо.

6

Поставити тег v1.0

git tag -a v1.0 -m "Реліз 1.0: перша версія, показана на демо-дні"

Команда нічого не друкує — це нормально. -a означає анотований тег (git збереже автора, дату й повідомлення), -m — саме повідомлення. Тег стає на той коміт, який зараз верхній у гілці.

Перевір, що вийшло:

git tag -n
git show v1.0 --stat | head -20

git tag -n покаже один рядок:

v1.0            Реліз 1.0: перша версія, показана на демо-дні

git show v1.0 --stat друкує спершу сам тег із твоїм іменем і датою, далі коміт, на який він указує, і перелік файлів, змінених у цьому коміті. | head -20 обрізає вивід до двадцяти рядків, щоб він не зайняв увесь екран.

Якщо помилився з іменем Тег видаляється командою git tag -d v1.0 і ставиться заново. Це безпечно, поки тег не надіслано в ~/repo.git. Після надсилання видаляти вже не варто: краще поставити наступний номер.
7

Надіслати коміти й тег у ~/repo.git

git push origin master
git push origin v1.0

Дві окремі команди, і друга не менш важлива за першу: push гілки надсилає коміти, але теги лишає на місці. Саме тут найчастіше зупиняються.

Перевір, що тег з’явився в ~/repo.git:

git -C ~/repo.git tag
git -C ~/repo.git log --oneline | head -3

git -C <тека> означає «виконати команду так, ніби ми в цій теці». Перша команда має надрукувати v1.0, друга — ті самі коміти, що й у ~/app. Якщо тега в списку немає, повтори git push origin v1.0 і прочитай, що git відповість: у відповіді буде причина.

8

Оновити сервіс до цієї версії

cd ~/app
./deploy.sh
echo $?

Скрипт із заняття 09 робить чотири кроки по черзі: забирає коміти з ~/repo.git, проганяє тести, перезапускає службу club-api і перевіряє, що /api/ping відповідає кодом 200 («усе гаразд»).

echo $? друкує код виходу останньої команди. Нуль означає, що скрипт дійшов до кінця. Будь-яке інше число — зупинився на якомусь кроці, і в його виводі написано, на якому саме.

Останній рядок виводу має бути таким:

ГОТОВО: /api/ping відповів 200
9

Перевірити адресу так, як її побачить гість

Спершу з сервера, через публічну адресу. Подивись, яку адресу ти записав у project.yaml:

grep url ~/app/project.yaml
  url: "https://bohdan.g9.klub.example/"

Тепер запиши цю адресу у змінну, щоб не набирати її щоразу. Без лапок і без скісної риски в кінці — риску ми додамо самі в наступних рядках, а дві риски поспіль (//) багато серверів вважають іншою адресою й відповідають 404:

MY=https://bohdan.g9.klub.example
curl -s -o /dev/null -w 'сторінка: %{http_code}\n' "$MY/"
curl -s -o /dev/null -w 'api/ping: %{http_code}\n' "$MY/api/ping"

curl — програма, яка робить той самий запит, що й браузер, але з командного рядка і без малювання сторінки. Прапорці означають:

Обидві перевірки мають надрукувати 200 — «усе гаразд». Критерій L12.2 перевіряє рівно це, тільки під час показу.

Якщо замість коду прийшла помилка curl: (6) Could not resolve host означає, що в адресі помилка в написанні або в полі url записано не те: перевір рядок посимвольно. Помилка зі словами URL using bad/illegal format — ти лишив у змінній лапки з project.yaml: набери адресу без них. Якщо приходить 404, а служба працює, подивись, чи не потрапила в змінну зайва скісна риска в кінці: echo "$MY" має надрукувати адресу, що закінчується назвою домену.

Тепер найважливіше: відкрий адресу з телефона, вимкнувши Wi-Fi. Гості заходитимуть саме так — зі свого мобільного інтернету, а не з класної мережі. Пройди всі три дії, які показуватимеш, і подивись, чи вміщується сторінка в екран без горизонтальної прокрутки.

Попроси сусіда відкрити твою адресу зі свого телефона й додати один запис. Якщо в нього вийшло — адреса робоча для чужих людей, а не тільки для тебе.

10

Свіжа копія бази перед показом

На занятті 11 ти налаштував щоденну копію бази через crontab — особистий розклад команд. Подивись, який саме скрипт там записано, і запусти його руками, щоб копія була зроблена не вчора вночі, а щойно:

crontab -l
ls -lt ~/backups | head -3

crontab -l друкує твій розклад: у рядку буде час і повний шлях до скрипта — щось на зразок /home/bohdan.g9/app/backup.sh, тільки з твоїм логіном замість bohdan.g9. Запусти цей скрипт і перевір, що з’явився новий файл:

~/app/backup.sh
ls -lt ~/backups | head -3

ls -lt сортує за часом зміни, найновіше зверху. Верхній файл має бути датований сьогоднішньою хвилиною. Якщо в тебе скрипт називається інакше — підстав своє ім’я з виводу crontab -l.

Навіщо це зараз: під час показу десяток людей додасть у твій сервіс справжні записи, і серед них можуть трапитися випадкові або порожні. Зі свіжою копією ти повернеш список до того стану, який був до демо, однією командою з RUNBOOK.md.

Частина друга: підготовка до виступу Кроки 11 і 12, приблизно 8 хвилин. Робиться до того, як почнуться виступи.
11

Записати план виступу

План потрібен на папері або у відкритому файлі на телефоні. Тримати його в голові перед десятком незнайомих людей не виходить майже ні в кого.

nano ~/DEMO.md

Цей файл лежить у домашній теці ~, а не в ~/app, як FEEDBACK.md, PLANS.md і CHANGELOG.md. Причина проста: DEMO.md — твоя особиста нотатка, а не частина проєкту, і в репозиторій вона не потрапляє. До того ж після кроку 6 у теці ~/app ми до кінця показу нічого не змінюємо.

# План виступу — 5 хвилин

## Проблема (1 хв)
Для кого: мій молодший брат, 11 років.
Що заважало: забуває, що вже зробив, і о десятій вечора з’ясовує,
що не зробив нічого.

## Рішення (1 хв)
Що робить сервіс: показує список справ на день, кожну можна
відмітити одним дотиком.
Чому саме так: телефон завжди під рукою, зошит — ні.

## Показ (2 хв) — рівно три дії
1. Відкрити сторінку, показати список із чотирьох записів.
2. Додати справу «Вивчити вірш» — вона з’являється зверху одразу.
3. Відмітити її — лічильник унизу змінюється з 2 на 3.

## Історія поломки (1 хв)
Що зламалося: після надсилання порожньої форми сторінка показувала
помилку 500.
Як дізнався: сусід на занятті 08 надіслав порожню назву, і сервіс
відповів помилкою 500.
Що зробив: додав перевірку, тепер приходить 422 і текст
«Назва не може бути порожньою».

## Якщо не відкриється
Запасна адреса: http://91.219.61.4/u/bohdan.g9/
Поки перезапускаю службу, розповідаю, як влаштована база.

Останній розділ — про запасний варіант. Твоя сторінка доступна за двома адресами: за сабдоменом і за адресою з цифрами. Обидві обслуговує той самий nginx, який звертається до тієї самої теки ~/www і до тієї самої служби, тому сторінка за ними однакова. Якщо одна не відкриється, ти назвеш другу, не втрачаючи часу на роздуми. Перевір запасну адресу зараз, разом із основною: відкрий її й пройди ті самі три дії.

Прочитай план уголос із секундоміром. Якщо не вкладаєшся в п’ять хвилин, скорочуй розділ «Рішення»: саме там зазвичай з’являються зайві подробиці.

12

Підготувати робоче місце до виступу

Три речі, зроблені до того, як тебе викличуть.

  1. Вкладка з відкритою головною сторінкою сервісу. Уже відкрита, не закладка.
  2. Друга вкладка з твоїм кабінетом: http://91.219.61.4/k/bohdan.g9/ — там видно зараховані критерії, якщо хтось спитає.
  3. Вікно терміналу з відкритим журналом служби.
journalctl --user -u club-api -f

-f (від follow) означає «показувати нові рядки, поки не зупиню». Рядки з’являтимуться самі, коли гості заходитимуть на твою сторінку. Вийти — Ctrl+C: журнал закриється, служба працюватиме далі.

Дивись на це вікно краєм ока під час показу. Рядок зі словом Traceback або 500 означає, що в коді сталася помилка на чиємусь запиті. Це не привід зупиняти виступ — це матеріал для розмови після нього.

Частина третя: показ і голосування Крок 13, приблизно 77 хвилин: 72 хвилини на дванадцять виступів (по п’ять хвилин на виступ і хвилина на зміну доповідача) і 5 хвилин на голосування та підсумок.
13

Виступ і голосування

Порядок виступів називає викладач. Поки говорить хтось інший:

Голосування наприкінці займає п’ять хвилин: три номінації, у кожній називаєш одну людину, крім себе:

НомінаціяЗа що
НайкориснішийСервісом хочеться користуватися далі, а не тільки подивитися
НайакуратнішийЗрозуміло з першого погляду, що робити на сторінці; на телефоні все вміщується в екран без горизонтальної прокрутки
Найстійкіший до зломуТи пробував зламати й не зміг: порожні поля, чужі запити, довгі назви

Після голосування — останні дві дії на сервері: зберегти зауваження гостей і зробити ще одну копію бази, тепер уже з даними, які додали під час показу.

nano ~/app/FEEDBACK.md
~/app/backup.sh
ls -lt ~/backups | head -2

Що перевіряє автоматика

П’ять критеріїв заняття 12. Чотири з них перевіряє програма, п’ятий — L12.5 — оцінює викладач під час показу. Перевірка запускається за розкладом, тому відмітка в кабінеті з’являється не в ту саму секунду, коли ти виконав команду. Публічну адресу програма бере з ~/app/project.yaml, розділ server, поле url.

КодКритерійЩо це означаєНе зараховано — що робити
L12.1 тег v1.0 існує в репозиторії Позначка з іменем v1.0 має бути в ~/repo.git — у тому репозиторії, куди ти надсилаєш коміти. За нею потім знаходять саме той стан коду, який показували на демо, скільки б комітів не з’явилося пізніше. Перевір, що бачить сам репозиторій: git -C ~/repo.git tag. Порожній вивід означає, що тег лишився тільки в ~/app — надішли його окремою командою git push origin v1.0. Якщо тега немає й у ~/app (git tag -n мовчить), постав його заново: крок 6 практики. Перевір ім’я посимвольно: V1.0 з великою літерою або v.1.0 зайвою крапкою — це інші імена.
L12.2 сервіс відповідає 200 під час демо Програма звертається до твоєї публічної адреси в час показу й чекає код відповіді 200 — «сторінка віддана». Перевіряються головна сторінка й /api/ping. Подивись стан служби: systemctl --user status club-api. Рядок active (running) — служба працює. Якщо там failed, читай причину: journalctl --user -u club-api -n 30. Найчастіші причини за три місяці — друкарська помилка в коді, який зберегли перед самим показом, і незакінчена правка. Запустити службу заново: systemctl --user restart club-api. Якщо служба працює, а адреса не відкривається, перевір поле url у project.yaml: там має бути твоя адреса, а не чужий логін.
L12.3 CHANGELOG.md містить ≥8 записів Програма читає ~/app/CHANGELOG.md і рахує рядки-пункти списку — ті, що починаються з -. Їх має бути щонайменше вісім. Файл має бути закомічений і надісланий: те, чого немає в репозиторії, для перевірки не існує. Порахуй сам: grep -c '^- ' ~/app/CHANGELOG.md-c друкує кількість рядків, що підійшли. Якщо число менше восьми, дописуй: за три місяці змін точно більше, дивись git log --oneline і переписуй коміти словами, зрозумілими користувачеві. Якщо число вісім і більше, а критерій не зарахований, перевір git status --short і зроби git add CHANGELOG.md, git commit, git push origin master.
L12.4 усі тести проходять Програма запускає твої тести з теки ~/app/tests і дивиться, що жоден із них не завершився помилкою. Ті самі п’ять тестів із заняття 09: список повертається, запис додається, погані дані відхиляються, сторонній не видаляє, неіснуюче дає 404. Запусти в себе: cd ~/app && /opt/club/venv/bin/python -m pytest -q tests. Читай вивід із рядків, де стоїть assert: там видно, чого тест чекав і що прийшло насправді. Якщо тести зупиняються з повідомленням ModuleNotFoundError, ти запустив не той python — потрібен саме /opt/club/venv/bin/python. Якщо в тебе на сервері тести проходять, а перевірка каже інакше, значить у ~/repo.git лежить не та версія: git status --short і git push origin master.
L12.5 демо проведено (оцінює викладач) Виступ на п’ять хвилин: проблема, рішення, показ на живій сторінці й історія про те, що зламалося і як ти це полагодив. Гості при цьому користуються твоїм сервісом зі своїх телефонів. Цей критерій не має другої спроби в той самий день, тому підготовка з кроків 11 і 12 важить більше за все інше. Якщо ти пропустив демо-день з поважної причини, час показу призначає викладач окремо.

Домашнє завдання

Курс закінчився, але сервер працює далі: акаунт лишається щонайменше на три місяці, і твоя адреса відкривається так само, як сьогодні. Це завдання — про те, щоб вона такою й лишилася.

Перше. Зауваження гостей — у файл. Поки пам’ятаєш, допиши FEEDBACK.md: що люди питали, де зупинялися, що не змогли знайти. Формат той самий, що після заняття 10, з рядком «сказав дослівно». Потім познач у PLANS.md, які з цих зауважень ти справді збираєшся зробити.

Друге. Випустити версію 1.1 або 1.0.1. Зроби одну правку зі свого списку й повтори весь порядок дій релізу — тепер уже самостійно, без сторінки з підказками:

cd ~/app
# правка в коді
/opt/club/venv/bin/python -m pytest -q tests
nano CHANGELOG.md          # новий розділ ## 1.0.1 із датою
git add -A
git commit -m "Виправлено сортування списку: найновіші записи зверху"
git tag -a v1.0.1 -m "Виправлення після демо-дня"
git push origin master
git push origin v1.0.1
./deploy.sh

Номер вибирай за таблицею з теорії: додав щось нове — v1.1, тільки виправив — v1.0.1. Рядок, що починається з #, у командному рядку — це коментар, він не виконується.

Третє. Раз на тиждень перевіряти, що сервіс живий. Одна команда з телефона чи комп’ютера — відкрити свою адресу. Якщо сторінка не відкрилася, у тебе є RUNBOOK.md із заняття 11: там записано, які команди виконати й куди дивитися.

Четверте. Перечитати README.md очима людини, яка бачить проєкт уперше. Розділи «Що це» і «Як запустити» ти писав на занятті 03, і за три місяці проєкт змінився. Перевір, що команди в README справді працюють, якщо виконати їх по порядку. Через півроку ти сам будеш тією людиною, яка бачить цей код уперше.

Якщо щось не працює

fatal: tag 'v1.0' already exists

Тег із таким іменем уже поставлений — можливо, ти виконав команду двічі. Подивись, на якому він коміті:

git show v1.0 --stat | head -12
git log --oneline | head -3

Якщо тег стоїть на правильному коміті (тому ж, що зверху в git log), нічого робити не треба — переходь до git push origin v1.0. Якщо тег стоїть на старому коміті, а після нього ти зробив ще кілька, видали й постав заново:

git tag -d v1.0
git tag -a v1.0 -m "Реліз 1.0: перша версія, показана на демо-дні"
git tag -n

-d (delete) прибирає тільки позначку, самі коміти лишаються на місці. Але це безпечно, поки тег не надіслано в ~/repo.git. Якщо він уже там, не видаляй його: постав наступний номер v1.0.1. Переставляти вже опублікований тег — спосіб отримати два різні стани коду з однією назвою.

Тег є в ~/app, а в кабінеті критерій не зарахований

Перевір, що бачить сам репозиторій:

git tag -n
git -C ~/repo.git tag

Перша команда друкує тег, друга мовчить — тег не надісланий. Надішли:

git push origin v1.0

Якщо git відповідає Everything up-to-date, а тега в git -C ~/repo.git tag усе одно немає, подивись, чи туди ти надсилаєш:

git remote -v

Вивід — два рядки: git окремо показує, звідки він забирає коміти (fetch) і куди надсилає (push). Між іменем і шляхом стоїть символ табуляції, тому пробіл там може виглядати ширшим:

origin	/home/bohdan.g9/repo.git (fetch)
origin	/home/bohdan.g9/repo.git (push)

Шлях має бути твій — із твоїм логіном. Чужий логін або інший шлях означає, що коміти надсилаються не в той репозиторій: покажи цей вивід викладачеві.

./deploy.sh зупиняється на першому кроці

Найчастіше вивід виглядає так:

==> 1/4 забираємо коміти з ~/repo.git
fatal: Not possible to fast-forward, aborting.

Це означає, що в ~/repo.git є коміти, яких немає в ~/app, і навпаки: історії розійшлися. Скрипт свідомо не намагається їх поєднати сам. Подивись, чим відрізняються:

git fetch origin
git log --oneline HEAD..origin/master
git log --oneline origin/master..HEAD

git fetch забирає інформацію про чужі коміти, нічого не змінюючи в твоїх файлах. Далі два нових слова. HEAD — так git називає коміт, на якому ти зараз стоїш, тобто верхній у твоєму ~/app. origin/master — верхній коміт гілки master у репозиторії ~/repo.git, яким git його запам’ятав під час fetch. Запис A..B між ними читається так: покажи коміти, які є в B і яких немає в A.

Тому перша команда покаже коміти, які є тільки в репозиторії, друга — які є тільки в тебе. Якщо перший список порожній, просто надішли своє: git push origin master. Якщо в обох списках щось є — це та ситуація, яку сьогодні не розв’язують поспіхом перед показом, клич викладача.

Сервіс не відповідає, а до виступу лишилося кілька хвилин

Три команди по черзі, у цьому порядку:

systemctl --user status club-api
journalctl --user -u club-api -n 30
systemctl --user restart club-api

Перша каже, у якому стані служба. active (running) — працює, failed — служба зупинилася через помилку і не змогла запуститися знову. Друга друкує тридцять останніх рядків журналу: читай знизу вгору, останній рядок із Error або Traceback і є причиною. Третя перезапускає службу.

Після перезапуску перевір сервіс прямо на сервері, не через публічну адресу. Для цього потрібен номер твого порту. Порт — число, за яким на одній машині розрізняють програми, що приймають запити з мережі: у тебе такий номер свій, у сусіда інший, тому ваші служби не заважають одна одній. Твій номер записаний у файлі служби, у рядку ExecStart:

grep ExecStart ~/.config/systemd/user/club-api.service
ExecStart=/opt/club/venv/bin/uvicorn main:app --host 127.0.0.1 --port 9536

У твоєму файлі число після --port буде інше — воно твоє на весь курс (заняття 02). Підстав його в команду нижче замість 9536:

curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:9536/api/ping

127.0.0.1 — це адреса «ця сама машина»: запит нікуди не виходить у мережу й не проходить через nginx. Якщо тут 200, а ззовні сторінка не відкривається, справа не в твоєму коді — скажи викладачеві.

Якщо служба зупиняється одразу після запуску, поверни код до стану релізу:

cd ~/app
git stash
git checkout v1.0
systemctl --user restart club-api

git stash прибирає незбережені зміни вбік, щоб вони не заважали; git checkout v1.0 перемикає файли на той стан, який ти позначив тегом.

git checkout v1.0 надрукує великий попереджувальний блок — це нормально Він починається словами You are in 'detached HEAD' state і займає рядків десять. Нічого не зламалося. Так git каже, що ти зараз стоїш не на гілці, а на окремому коміті: ім’я master лишилося там, де було, а файли перед тобою — з тега v1.0. У цьому стані не роби комітів: вони не потраплять у жодну гілку, і після git checkout master знайти їх буде важко. Дочекайся кінця показу.

Після демо повернешся на гілку командою git checkout master — і попередження зникне, — а відкладені зміни дістанеш через git stash pop.

Гість не може відкрити сторінку зі свого телефона

Спочатку з’ясуй, у скількох людей так. Якщо не відкривається в одного, а в решти працює, річ у його телефоні чи мережі. Дай другу адресу — ту, що з цифрами: http://91.219.61.4/u/bohdan.g9/. Вона веде на ту саму теку й ту саму службу, але в ній немає імені, яке треба перетворювати на число: якщо мережа його оператора ще не знає, який IP відповідає твоєму сабдомену, адреса з цифрами відкриється однаково.

Якщо не відкривається в усіх, перевір сервіс командами з попереднього пункту. Коли служба працює й curl на сервері віддає 200, продиктуй адресу ще раз по літерах: у довгому імені легко почути одне замість іншого.

Окремий випадок — гість бачить сторінку, але додати запис не може. Це нормально: додавання й видалення на занятті 07 закриті паролем, і роблять їх тільки з твого входу. Скажи це вголос, інакше гість вирішить, що сервіс зламався.

Під час показу в базі з’явилися випадкові записи

Спокійно допрацюй виступ до кінця — на пів хвилини це не вплине. Після демо подивись, що там:

ls -lt ~/backups | head -3
sqlite3 ~/data/app.db "SELECT id, title FROM habit ORDER BY id DESC LIMIT 10;"

sqlite3 — програма, яка відкриває файл бази й виконує запит, написаний після неї в лапках. Тут запит просить показати номер і назву десяти останніх записів таблиці habit: ORDER BY id DESC — від найбільшого номера до найменшого, LIMIT 10 — не більше десяти рядків. Підстав назву своєї таблиці й своїх полів із project.yaml.

Якщо зайвих записів кілька, видали їх через свою ж сторінку — це швидше й безпечніше за роботу з базою напряму. Якщо їх десятки, відновлюй базу з копії за інструкцією з RUNBOOK.md: саме для цього ти робив свіжу копію на кроці 10. Зверни увагу, що разом із випадковими зникнуть і справжні записи, додані під час показу.