Перед уроком
Обери свою групу й себе зі списку. Тоді відповіді й перевірки завдань запишуться саме на тебе.
Закрити бота на замок: список людей і зміни в базі бачить і робить лише власник за своїм номером чату (ADMIN_IDS у .env), чужим бот ввічливо відмовляє, а файли з секретами не можуть прочитати інші учні на сервері.
Замість <логін> підставляєш свій
логін виду bohdan.g9: ім’я, крапка, номер групи. Кутові дужки не пишуться.
Кабінет — сторінка, де проти кожного критерію стоїть відмітка, зарахований він чи ні.
У прикладах на цій сторінці скрізь написано bohdan.g9 — читай це як свій
логін і виправляй у кожній команді, яку копіюєш.
Зараз твій бот віддає список усіх, хто йому писав, кожному, хто знає команду, а /add, /edit і /del виконує для будь-кого: хто завгодно може написати боту й стерти запис у базі. Адреси бота ніхто не ховає — його знайдуть за назвою в пошуку Telegram. Сьогодні ти зачиниш ці двері. Список і всі зміни працюватимуть лише для тебе, а чужим бот відповість ввічливою відмовою. Для всіх лишиться команда /stats із двома числами: скільки людей писали боту і скільки повідомлень вони надіслали.
Як бот дізнається, що це ти? У кожного повідомлення Telegram дописує номер того, хто його написав (chat_id), і підробити цей номер не можна. Ти запишеш свій номер у .env як ADMIN_IDS, а бот перед кожною небезпечною командою звірятиме номер зі списком. Пароль і сторінка входу для цього не потрібні. Для сміливих є ще необов’язковий PIN: кілька цифр, які бот зберігає не як є, а у вигляді «відбитка» (хеша; пояснимо в розділі 3).
А ще ти розберешся з правами файлів у Linux і переконаєшся, що .env із токеном і база з іменами однокласників не читаються нікому, крім тебе.
Наприкінці покажеш: відмову чужому на /users, /add і /del (у try_bot.py з чужим номером і в Telegram з другого акаунта), список для себе, числа з /stats без жодного імені, права -rw------- у .env і app.db.
Перевіряємо лише своєУсі перевірки сьогодні — на твоєму власному боті: чужий номер ти імітуєш сам, через try_bot.py --id …. Бота однокласника з другого акаунта «пробувати на міцність» не можна без його згоди: ти не знаєш, що саме він зберігає в базі, і чужі дані дивитися так само заборонено, як змінювати. Помітив, що в когось команди досі відкриті, — скажи йому про це, а не користуйся.
Витік даних — це коли чужі люди бачать те, що їм бачити не можна. Не обов’язково через злам: найчастіше дані просто лежать відкрито, і хтось на них натрапляє. Саме так зараз із твоєю командою /users. Хто написав її боту, отримає для кожної людини ім’я, нік у Telegram, коли вона писала вперше й востаннє, скільки разів, і твою примітку про неї. Незнайомець, однокласник, бот-перебирач — не має значення: бот відповідає всім однаково.
«Але ж мого бота ніхто не знає» — не захист. Назва бота лежить у пошуку Telegram, її видно на скриншотах і в чаті класу, а нік бота можна просто вгадати. Адреса і назва — не пароль.
Чим це погано, якщо там «лише імена»? Ім’я, нік і час, коли людина пише, разом — це вже персональні дані: відомості, за якими можна впізнати конкретну людину. Незнайомець дізнається, що Оля з ніком @olya_kosmos щовечора о десятій пише боту про космос, — і може знайти її в Telegram, назвавшись «другом із гуртка». Люди писали твоєму боту, довіряючи тобі, а не всьому інтернету. На занятті 04 ти вирішив зберігати лише мінімум; сьогодні — показувати цей мінімум лише тому, кому він потрібен.
Що закрити, а що лишити відкритим? Правило таке: усе, що стосується конкретних людей або змінює дані, — лише власнику. Усе, з чого про конкретну людину нічого не дізнаєшся, — можна всім.
| Команда | Хто може після сьогодні | Чому |
|---|---|---|
/start, /help, /ask | усі | Звичайна розмова з ботом, жодних чужих даних |
/stats | усі | Лише два числа: скільки людей і скільки повідомлень. Чиїх — не видно |
/users, /list | лише власник | Імена й ніки — персональні дані |
/add, /edit, /del | лише власник | Змінюють базу |
Замок ставимо у самому боті, на початку обробки кожного повідомлення, а не «десь збоку». Пояснення — просте: чужа людина може написати будь-яку команду будь-коли. Приховати команду з підказки /help нічого не захистить, а от перевірка в коді, яку відвідувач не може обійти, — захищає.
Тарас каже: «Мій /users можна не закривати. Назву бота я нікому не давав, а в /help цієї команди немає». Чи правий він?
Перевірку «хто ти» називають автентифікацією, а рішення «що тобі можна» — авторизацією. У сайтів автентифікація — це пароль і сторінка входу. У бота все простіше: Telegram уже перевірив людину, коли вона зайшла у свій акаунт, і до кожного повідомлення дописав її номер:
{"message": {"from": {"id": 100200300, "first_name": "Богдан"},
"chat": {"id": 100200300},
"text": "/users"}}Число 100200300 — це і є from.id (номер людини) та chat.id (номер чату; у приватній розмові вони збігаються). Ім’я та нік людина може змінити на які завгодно — написати «Богдан» може будь-хто. А от номер Telegram ставить сам, і підробити його в повідомленні, яке приходить боту, не можна. Тому доступ дають за номером, а не за іменем.
Правило авторизації в нас одне: номер у списку ADMIN_IDS — адмін, може все; решта — лише відкрите. Список лежить у ~/app/.env, поруч із токеном:
ADMIN_IDS=100200300
ADMIN_IDS=100200300,777888999Один рядок із одним номером або кількома через кому (другий рядок — лише приклад, у файлі має бути один рядок ADMIN_IDS). Готовий модуль access.py читає цей рядок і дає боту одну функцію:
refusal = access.check(sender.get("id"), text)
if refusal:
return refusalЯкщо людині цю команду не можна, check() повертає текст відмови, і бот просто відповідає ним та більше нічого не робить: до бази справа не доходить. Якщо можна — повертає None («нічого заважати»), і бот працює як раніше. Відмова — це зрозуміла відповідь: «Ця команда лише для власника бота», а не мовчання і не рядки з помилкою. Порядок важливий: перевірка стоїть першою, ще до того, як бот розбереться, що саме в команді (скажімо, чи є в ній номер запису). З тим, хто не має права, бот не обговорює, що не так у його даних.
Чому не просто один власник? Бот може обслуговувати вчителя й помічника, тому в ADMIN_IDS можна кілька номерів. А якщо номер змінити в .env і перезапустити службу, право отримує інша людина: пароля передавати не треба.
Номер — не секрет, але й не прикрасаСвій номер можна показувати: самого по собі він нічого не відкриває. Небезпечно інше: якщо хтось дізнається токен бота. Тоді він зможе писати від імені бота. Тому токен ніколи не публікують, а файл .env закривають від сусідів (розділ 4).
Як користуватися ШІ-помічникомБезпека — тема, де ШІ добре пояснює: «Що таке chat_id у Telegram?», «Чим автентифікація відрізняється від авторизації?». Але в чат ніколи не вставляють вміст .env, токени, PIN, хеші й справжні номери чужих людей — навіть «щоб ШІ подивився, чи все правильно». Замість значень показуй назви: ADMIN_IDS=…. Кожну відповідь перевіряй на своєму боті, а на занятті пояснюй своїми словами: викладач може показати будь-який рядок access.py.
Зловмисник змінив своє ім’я в Telegram на «Богдан» і поставив таку саму аватарку. Чи отримає він доступ до /users?
Номер чату відповідає на питання «хто пише», але не на питання «чи пише справді він». Телефон адміна можна залишити без нагляду, і тоді хтось інший відкриє розмову з ботом і напише /del. Тому для найнебезпечнішої команди (видалення) додають ще й PIN: кілька цифр, які ти вводиш командою /pin і які діють п’ять хвилин. Цей PIN бот зберігає не як є, а як хеш.
Хеш — «відбиток» тексту: рядок однакової довжини, який отримують із PIN за незмінним правилом. З PIN хеш порахувати легко, а з хеша PIN — ні. Спробуй на сервері (ці команди нічого не змінюють):
python3 -c "import hashlib; print(hashlib.sha256(b'sonechko7').hexdigest())"
python3 -c "import hashlib; print(hashlib.sha256(b'sonechko7').hexdigest())"
python3 -c "import hashlib; print(hashlib.sha256(b'Sonechko7').hexdigest())"Перші дві надрукують однаковий рядок із 64 символів — 020320950c4f…71a00e9a: той самий текст завжди дає той самий результат. Третя відрізняється однією великою літерою, а рядок вийде зовсім інший: 7c1fe0e31d5d…4834712a. На цьому тримається перевірка: бот рахує хеш введеного PIN і порівнює з тим, що лежить у .env. Збіглося — PIN правильний, хоча сам PIN бот ніде не зберігав.
Але одного sha256 мало. Він дуже швидкий, тож той, хто вкрав файл, перебирає варіанти за долі секунди. І однаковий PIN дає однаковий хеш. Від цього рятують сіль (випадковий рядок, свій для кожного хеша, який додають перед хешуванням) і спеціальний повільний алгоритм scrypt, що є в стандартній бібліотеці Python. У нашому pin.py до хеша додано і те, і те:
$scrypt$n=16384,r=8,p=1$226c93a469f6d5426ab0078b91e8f3e4$2d8545f1eb174ad008d4fc1f0eb15095150879886fb0a9459bb134af922fb5e9
scrypt назва алгоритму
n=16384,r=8,p=1 налаштування: скільки роботи й пам’яті
226c93a4…f3e4 сіль: 16 випадкових байтів, записаних 32 символами
2d8545f1…5e9 сам хеш: 32 байти, записані 64 символамиДля цікавихПеревірку check_pin() написано так, щоб вона порівнювала хеші функцією hmac.compare_digest(): вона витрачає однаковий час, хоч би де рядки розійшлися, а звичайне == зупиняється на першому відмінному символі, і за часом відповіді можна було б підбирати символ за символом.
Скільки захисту дає такий PIN? Менше, ніж довгий пароль. Чотири цифри — лише 10 000 варіантів, і якщо хтось отримає твій файл .env, він переб’є їх за хвилини, навіть із scrypt. Тому PIN захищає від людини, яка взяла твій телефон, але не від викрадення файлів: для цього є права файлів (розділ 4) і правило «токен не в git». Навіщо тоді хеш? Щоб PIN не лежав у файлі відкритим текстом, а файл не був готовою відповіддю.
Ще одна деталь. PIN вводять у терміналі через getpass.getpass(), а не пишуть у команді (pin.py 1234). Усе, що набираєш у терміналі, записується в ~/.bash_history, а поки команда працює, її рядок видно в списку процесів сервера (ps) усім, хто на нього зайшов. А ще екран може бачити весь клас. І команду /pin у Telegram після введення видали з чату.
Ти й однокласниця поставили однаковий PIN 482916. У ваших .env рядки ADMIN_PIN_HASH=… починаються однаково ($scrypt$n=16384,r=8,p=1$), а далі зовсім різні. Чому?
Усі замки в боті марні, якщо сусід по серверу просто прочитає файл ~/app/.env з твоїм BOT_TOKEN і списком ADMIN_IDS або скопіює собі ~/data/app.db з іменами. На нашому сервері працює вся група, і в кожного свій користувач Linux — обліковий запис із власним іменем (твій логін) і номером. Подивись, хто ти для системи:
idВідповідь буде схожа на uid=1023(bohdan.g9) gid=1023(bohdan.g9) groups=1023(bohdan.g9),1001(klub): номер і ім’я користувача, твоя основна група (у Linux у кожного користувача є група з тим самим ім’ям, і в ній лише він) і додаткова група klub, у якій усі учні, — через неї сервер дозволяє вам входити за паролем. Числа в тебе будуть інші.
Кожен файл і кожна тека мають власника, групу і права для трьох кіл: власника, групи й усіх інших. Їх показує ls -l:
-rw-------. 1 bohdan.g9 bohdan.g9 312 … .env
drwxr-x---. 2 bohdan.g9 bohdan.g9 4096 … data
- або d тип: звичайний файл чи тека (directory)
rw- власник: читати (r), писати (w), запускати (x) — тут без x
--- група: нічого
--- усі інші: нічого
. позначка SELinux — додаткового захисту на нашому AlmaLinux; на неї можна не звертати уваги
bohdan.g9 власник
bohdan.g9 групаДля теки букви означають трохи інше: r — бачити список файлів, w — створювати й видаляти в ній файли, x — заходити в неї й діставати файл за назвою.
Права часто записують трьома цифрами — по одній на коло. Кожна буква має вагу: r = 4, w = 2, x = 1, і ваги додають. rw- = 4 + 2 = 6, r-x = 4 + 1 = 5, --x = 1, --- = 0. Змінює права команда chmod: chmod 600 .env — «власникові читати й писати, решті нічого».
| Що | Права | Що це дає |
|---|---|---|
~/app/.env, ~/data/app.db | 600, -rw------- | Читаєш і змінюєш лише ти — і твоя служба, бо вона працює від твого імені |
~/app | 700, drwx------ | У теку з кодом не зайде ніхто, крім тебе |
~/data, ~/logs, ~/backups | 750, drwxr-x--- | Тобі — усе, групі — заходити й читати (але група тут лише твоя), іншим — нічого |
домашня тека ~ | 711, drwx--x--x | Інші можуть пройти крізь неї до ~/www, але не бачать, що в ній лежить |
~/www і сторінки в ній | 755 і 644 | Читати можуть усі — так nginx віддає твій сайт світові |
Права на теці й права на файлі — два різні замки. .env лежить у закритій теці ~/app, але якщо колись ти відчиниш теку (скажімо, chmod 755 ~/app, щоб показати другові файл), файл із правами 644 одразу стане читабельним для всіх. Тому закривають і теку, і сам файл. Автоперевірки L07.6 і L07.7 дивляться саме на права файлів: у групи й інших не має бути жодної букви.
Хто ще є на сервері? nginx — окремий користувач, програма, що віддає сайти: вона читає твою ~/www, бо там права «читати всім», і не дістає до ~/app і ~/data. І root — головний користувач, адміністратор сервера. Для нього права файлів не діють, він може прочитати будь-що. Тому на шкільному сервері не тримають нічого справді особистого: адміністратор технічно бачить усе, і це треба знати.
Linux так само береже паролі власних користувачів — і твій пароль для входу по SSH теж:
ls -l /etc/passwd /etc/shadowУ /etc/passwd (права -rw-r--r--, читати всім) — список користувачів, а на місці пароля стоїть x: «шукай деінде». Хеші паролів лежать в /etc/shadow, і в нього права ----------: не прочитає ніхто, крім root. Та сама ідея, що й у твоїй адмінці: хеш замість пароля і файл, закритий від сторонніх.
ls -l ~/app/.env показує -rw-r--r--. 1 bohdan.g9 bohdan.g9 312 … .env. Що кажуть ці права?
| Слово | Що означає |
|---|---|
| витік даних | Дані потрапили до тих, кому не призначалися; найчастіше — просто лежали відкрито |
| персональні дані | Відомості, за якими можна впізнати конкретну людину: ім’я, нік, коли вона буває в мережі |
| автентифікація і авторизація | Перевірка «хто ти» (тут — номер чату) і рішення «що тобі можна» (тут — список ADMIN_IDS) |
| chat_id, from.id | Номер чату й номер людини, які Telegram сам дописує до кожного повідомлення; підробити їх не можна |
| ADMIN_IDS | Рядок у .env зі списком номерів тих, кому можна небезпечні команди |
| відмова | Зрозуміла відповідь боту на команду, яку людині не можна, — без помилки й без виконання |
| хеш | «Відбиток» тексту: рядок однакової довжини, обчислений за незмінним правилом; назад у текст не перетворюється |
| сіль | Випадковий рядок, який додають перед хешуванням; свій для кожного хеша, лежить поруч із ним |
| scrypt | Навмисно повільний алгоритм хешування, якому потрібно багато пам’яті; є в hashlib |
| hmac.compare_digest | Порівняння рядків, яке за часом не видає, де саме вони розійшлися |
| getpass | Функція Python, що питає пароль чи PIN, не показуючи його на екрані |
| користувач і група Linux | Обліковий запис на сервері й набір користувачів, яким можна дати спільні права |
| права доступу, rwx | Читати, писати, запускати (для теки — заходити) окремо для власника, групи й інших |
| chmod 600, 700, 644, 755, 711 | Змінити права; кожна цифра — сума r = 4, w = 2, x = 1 для власника, групи, інших |
| root | Адміністратор сервера; права файлів його не зупиняють |
Виконуй кроки по черзі. Де в команді стоїть $USER, нічого не підставляй — ця змінна вже містить твій логін. А <логін> разом із кутовими дужками заміни на свій, наприклад bohdan.g9. Номери й числа в прикладах умовні — у тебе будуть свої. Команди бота нижче названо так, як у нашому курсі (/users — з заняття 04, /list, /add, /edit, /del — з заняття 05). Якщо в твого бота вони звуться інакше, підстав свої назви.
ssh <логін>@91.219.61.4
cd ~/app
systemctl --user is-active club-bot
python3 try_bot.py --id 777 --name Чужий "/users"Має бути active, а потім список людей, які писали твоєму боту: try_bot.py --id 777 імітує чужу людину з номером 777 (у Telegram тебе звуть інакше), і бот віддає їй усе. Саме це ти сьогодні закриваєш. inactive — служба зупинена: systemctl --user start club-bot. Ще дві перевірки перед роботою:
git status --short
cut -d= -f1 .envgit status --short має не надрукувати нічого; якщо видно файли, спершу збережи їх: git add -A і git commit -m "Заняття 06: доробки". cut -d= -f1 .env друкує з кожного рядка .env лише те, що стоїть до знака =, — назви без значень, тож жоден токен на екрані не з’явиться. Має бути щонайменше BOT_TOKEN, LLM_BASE_URL, LLM_API_KEY і LLM_MODEL.
Напиши своєму боту в Telegram /start — бот запам’ятав тебе в базі (заняття 04), тож номер можна прочитати звідти:
sqlite3 ~/data/app.db "SELECT chat_id, name, last_seen FROM users WHERE chat_id IS NOT NULL ORDER BY last_seen DESC LIMIT 5"Перший рядок — ти (найсвіжіший last_seen): наприклад 100200300|Богдан|2026-11-04T16:20:11. Число перед іменем — твій номер. Інший спосіб — написати в Telegram боту @userinfobot. Без Telegram номер вигадай сам — наприклад 1: його використовує try_bot.py, коли не вказано --id. Усі перевірки нижче вдома робляться саме так.
cd ~/app
echo "" >> .env
echo "ADMIN_IDS=100200300" >> .env
cut -d= -f1 .env
grep -c "^ADMIN_IDS=" .env
ls -l .env100200300 впиши свій номер з кроку 2;>> — дописати в кінець файлу. Одна дужка > замість двох стерла б увесь .env разом із BOT_TOKEN і налаштуваннями ШІ. Перш ніж натиснути Enter, переконайся, що дужок дві;.env не закінчувався переходом на новий, наступний запис приклеївся б до нього;ADMIN_IDS має бути один: кілька номерів пишуть через кому в одному рядку. Виконай команду echo "ADMIN_IDS=…" один раз.Друга покаже назви, серед них ADMIN_IDS. Третя — 1. Остання має починатися з -rw-------: >> прав не змінює, тож 600 з заняття 02 лишилося. Якщо там інакше — chmod 600 .env. Показало 2 чи більше — рядок записано двічі: пункт «Секрет записано двічі або .env затерто» внизу сторінки.
cp /opt/club/kurs/07/access.py /opt/club/kurs/07/pin.py ~/app/
less ~/app/access.pyКоли cp отримує кілька файлів, останнє слово — тека, куди їх скласти. cp: cannot stat — подивись ls /opt/club/kurs/07/ і, якщо файлів там немає, скажи викладачеві. У less гортай стрілками й Пробілом, вийти — q. Знайди в access.py: рядок ADMIN_COMMANDS — список команд, які тепер лише для адмінів; функцію admin_ids(), що читає ADMIN_IDS з оточення; функцію check(), яка повертає відмову або None; і stats_text() із двома числами. Сам файл не змінюй: хіба що доповни ADMIN_COMMANDS власними командами, які показують людей чи змінюють базу.
Відкрий nano ~/app/bot.py. Треба три невеликі вставки. Перша — разом з іншими імпортами на початку файлу (за абеткою, поруч з import env):
import accessДруга — у функції handle_update(), одразу після рядка if not text: return None (там, де вже є sender), але перед першим if text.startswith(...):
refusal = access.check(sender.get("id"), text) # заняття 07
if refusal:
return refusalТретя — публічна статистика. Поруч з іншими командами (наприклад, під обробкою /help) додай:
if text.startswith("/stats"): # заняття 07
return access.stats_text()Відступ у функції — чотири пробіли, як у сусідніх рядків. Збережи (Ctrl+O, Enter), вийди (Ctrl+X) і перевір файл тим самим Python, яким його запускає служба:
cd ~/app
/opt/club/venv/bin/python3 -c "import bot" && echo готовоМає бути готово. ModuleNotFoundError: No module named 'access' — access.py не лежить у ~/app, повернись до кроку 4. NameError: name 'access' is not defined — пропущено import access. IndentationError — відступ у вставці не збігається з сусідніми рядками.
Ще одне: автоперевірка шукає команди за назвами з паспорта проєкту. Відкрий nano ~/app/project.yaml і в розділі commands: заповни порожній рядок stats:, а не дописуй новий (два однакові ключі зіпсують файл):
commands:
...
stats: "/stats" # заняття 07Відступи — по два пробіли, без Tab. Перевір: python3 -c "import yaml; print(yaml.safe_load(open('project.yaml'))['commands']['stats'])" надрукує /stats. ScannerError чи ParserError — помилка формату в рядку з номером після слова line (найчастіше Tab замість пробілів).
Повтори запити з кроку 1 — від чужого номера 777, який не в ADMIN_IDS:
cd ~/app
python3 try_bot.py --id 777 --name Чужий "/users"
python3 try_bot.py --id 777 --name Чужий "/add Хакер | зламано"
python3 try_bot.py --id 777 --name Чужий "/del 1"
python3 try_bot.py --id 777 --name Чужий "/stats"
python3 try_bot.py --id 777 --name Чужий "/ask що таке порт?"Перші три мають закінчитися відповіддю Ця команда лише для власника бота.: і список, і зміни зачинено. Четверта — Людей, які писали боту: 7. Повідомлень: 42. з твоїми числами: статистику бачать усі. П’ята — звичайна відповідь ШІ (або чемне «ШІ не підключено») — розмова з ботом працює як раніше. Бот запам’ятав і «Чужого» з номером 777 у таблиці users (так працює remember() з заняття 04) — ми приберемо його в кроці 7. Тепер те саме від тебе (підстав свій номер з кроку 2 або 1, якщо Telegram немає і ти записав ADMIN_IDS=1):
python3 try_bot.py --id 100200300 --name Богдан "/users"Тепер замість відмови — список. Якщо чужому показано список — у access.py немає цієї команди в ADMIN_COMMANDS або вставку з кроку 5 зроблено нижче за обробку команди. Якщо відмову отримуєш ти — номер у ADMIN_IDS інший, ніж --id: grep ^ADMIN_IDS= .env.
sqlite3 ~/data/app.db "SELECT id, name FROM users WHERE name='Хакер' OR id=1"
sqlite3 ~/data/app.db "DELETE FROM users WHERE chat_id=777"Перша команда: рядка «Хакер» немає, а запис з id=1 (якщо він був) на місці. Жоден із чужих запитів не дійшов до бази: відмова мусить спрацювати до зміни, а не після. Друга прибирає з таблиці «Чужого» з номером 777, якого бот запам’ятав, поки ти його імітував. Відмови бот ще й записує у журнал служби (номер і команда, без тексту повідомлення):
tail -n 5 ~/logs/club-bot.logРядки на зразок 2026-11-04T16:25:02 відмова: 777 спробував /add з’являться, коли відповідати починає справжня служба (крок 8), а не try_bot.py: він пише у ваш термінал.
systemctl --user restart club-bot
sleep 3
systemctl --user is-active club-bot
tail -n 5 ~/logs/club-bot.logМає бути active. Служба читає код і .env лише під час старту, і без перезапуску працювала б по-старому, з відчиненими дверима. У Telegram напиши своєму боту /users — список; а тепер попроси друга чи подругу (або візьми другий акаунт) написати боту /users і /stats: у відповідь — відмова і два числа. Це критерій L07.9, його оцінює викладач. Без Telegram — покажи викладачеві вивід try_bot.py з кроку 6.
cd ~/app
python3 pin.py
grep -c "^ADMIN_PIN_HASH=" .env
ls -l .env
systemctl --user restart club-botСкрипт спитає PIN (4–10 цифр): — набери і натисни Enter; на екрані не з’явиться нічого, навіть зірочок: так працює getpass. Потім ще раз:. Має бути PIN збережено, 1 і -rw-------. У Telegram: /del 5 відповість Це небезпечна команда. Спершу введи /pin і свій PIN.; після /pin 482916 (твій PIN) — PIN вірний…, і п’ять хвилин /del працює. Хибний PIN — PIN хибний., а після п’яти спроб бот просить зачекати. Видали повідомлення з PIN з чату. Зміна PIN — той самий pin.py ще раз. Подивитися, що саме лежить у файлі, можна так: grep ^ADMIN_PIN_HASH= .env | cut -c1-40 — буде початок хеша ADMIN_PIN_HASH=$scrypt$n=16384,r=8, а самого PIN у файлі немає.
id
ls -ld ~ ~/app ~/data ~/www
ls -l ~/app/.env ~/data/app.dbid покаже тебе й твої групи, як у теорії. ls -ld показує права самих тек (без -d ls показав би їхній вміст): drwx--x--x у домашньої теки (711; на цьому сервері в кінці може стояти ще крапка — це службова позначка захисту системи), drwx------ у app (700), drwxr-x--- у data (750), drwxr-xr-x у www (755). Останній рядок — головні файли: в обох на початку має бути -rw-------. Якщо хоч в одного інакше — закрий обидва (повторний chmod нічого не зламає):
chmod 600 ~/data/app.db ~/app/.env
ls -l ~/app/.env ~/data/app.db
cd ~/app && python3 try_bot.py --id 777 "/stats"chmod нічого не друкує, а ls -l тепер показує -rw------- в обох. Остання команда — твої два числа, як у кроці 6: бот читає базу як раніше, бо працює від твого імені, а власникові 600 дозволяє все. Уважно з назвами: chmod 600 ~/data — без /app.db — забрав би з теки букву x, і бот уже не зміг би відкрити базу. Тепер подивись, як Linux береже власні паролі:
ls -l /etc/passwd /etc/shadow
cat /etc/shadow/etc/passwd — -rw-r--r--, /etc/shadow — ----------, а cat відповість cat: /etc/shadow: Permission denied. Хешів паролів усіх користувачів сервера не прочитає ніхто, крім root. Та сама ідея, що й у твого PIN: хеш замість самого значення і файл, закритий від сторонніх.
cd ~/app
git check-ignore -v .env
git log --all --oneline -- .envПерша має назвати правило, через яке Git ігнорує .env, наприклад .gitignore:12:.env .env (номер рядка в тебе може бути інший). Друга шукає .env в усій історії, в усіх гілках, і має не надрукувати нічого: файл ніколи не потрапляв у коміт. Якщо перша мовчить або друга надрукувала хоч один рядок — пункт «Автоперевірка: L07.6–L07.8 не зараховано» внизу сторінки.
Сьогодні в коміт підуть access.py, pin.py і зміни в bot.py. А от секрети лишаються на сервері: .env Git ігнорує, а база лежить у ~/data, поза репозиторієм.
cd ~/app
git status --shortМають бути M bot.py і нові файли ?? access.py, ?? pin.py (?? — Git бачить файл уперше). Файлу .env у списку бути не повинно: сьогодні в ньому з’явилися нові рядки, і саме тому перед кожним git add -A дивляться на цей список. Якщо .env там є — нічого не додавай і дивись пункт «Автоперевірка: L07.6–L07.8 не зараховано».
git add -A
git commit -m "Заняття 07: доступ за chat_id (ADMIN_IDS), чужим відмова, публічна /stats"
git push origin main
git pushgit commit відповість рядком [main …] Заняття 07: … і підсумком 3 files changed. Перший push оновлює ~/repo.git на сервері, другий — GitHub. Онови сторінку репозиторію: там з’явилися access.py і pin.py, а .env і бази немає — і не має бути. git push без слів відповідає помилкою про upstream — виконай один раз git push -u github main, як на занятті 05.
Сьогодні автоперевірка дивиться на твого бота двома парами очей. Перша — очі чужого: вона створює копію твоєї бази, кладе в неї людину зі «своїм» іменем і питає бота від імені людини, якої немає в ADMIN_IDS. L07.2: на /users і /list чужий не має побачити це ім’я і має отримати відповідь, яка відрізняється від «Не знаю такої команди». L07.4: після спроб /add, /edit і /del від чужого записи в копії бази мають лишитися без змін. L07.5: чужий на /stats отримує числа без жодного імені. Друга — очі адміна: L07.3 питає від імені людини з ADMIN_IDS і вимагає, щоб /users чи /list показали записи. L07.1 читає ~/app/.env і вимагає в ньому рядок ADMIN_IDS із номерами. L07.6 і L07.7 дивляться на права ~/app/.env і ~/data/app.db: у групи й інших не має бути жодної букви. L07.8 перевіряє, що .env існує, Git його ігнорує і в жодному коміті його ніколи не було. L07.9 — живу відмову в Telegram — оцінює викладач.
Перевірка не змінює твою справжню базу, не пише тобі в Telegram і не витрачає ліміт ШІ. Натисни «Перевірити практичну частину» у розділі «Перевірка виконаних завдань» нижче на сторінці. PIN у автоперевірці не бере участі: він необов’язковий.
| Код | Що перевіряється | Якщо не зараховано |
|---|---|---|
| L07.1 | у .env задано ADMIN_IDS — номери адмінів автоперевірка | У ~/app/.env немає рядка ADMIN_IDS=число (кілька номерів — через кому, один рядок). Свій номер: sqlite3 ~/data/app.db "SELECT chat_id, name FROM users ORDER BY last_seen DESC LIMIT 3" |
| L07.2 | чужому бот не показує список людей автоперевірка | Від чужого номера /users або /list показують людей. Додай import access, виклик access.check(sender.get("id"), text) перед першим if text.startswith(...) у handle_update (крок 5) і перезапусти службу. |
| L07.3 | власнику список працює автоперевірка | Від номера з ADMIN_IDS команди /users чи /list не показали записів. Перевір, що команда зі списком є в боті (заняття 04–05) і що номер у .env — той, з яким пробуєш. |
| L07.4 | чужий не змінює базу автоперевірка | Чужий номер змінив базу командою /add, /edit чи /del. Перевірка access.check має стояти ПЕРЕД обробкою цих команд, а їхні назви — в ADMIN_COMMANDS у access.py. |
| L07.5 | публічна /stats дає лише числа без імен автоперевірка | Від чужого номера /stats має показати кількість людей, але без жодного імені. Додай у handle_update if text.startswith("/stats"): return access.stats_text() і не клади /stats в ADMIN_COMMANDS. |
| L07.6 | .env не читається сусідами автоперевірка | chmod 600 ~/app/.env |
| L07.7 | база не читається сусідами автоперевірка | Файл бази можуть прочитати інші учні. chmod 600 ~/data/app.db |
| L07.8 | .env ніколи не потрапляв у Git автоперевірка | Секрет у Git-історії вважай украденим: новий BOT_TOKEN у @BotFather, а .env — у .gitignore. |
| L07.9 | живий бот: чужий акаунт отримує відмову на /users, а власник бачить список викладач | Позначку ставить викладач на занятті або після нього. |
Кожне питання оцінюється відразу в браузері — це лишається так само. Ця кнопка лише надсилає підсумок учителю, щоб відповіді не зникли разом із закритою вкладкою.
Список критеріїв і що саме вони означають — у таблиці вище, «Що перевіряє автоматика». Тут — та сама перевірка, але прямо зараз, без очікування на найближчий прохід за розкладом.
ADMIN_IDS його не додавай, а лише подумай: яку одну команду ти йому довірив би, а яку ні? Допиши в ~/app/README.md розділ ## Хто має доступ: що відкрито для всіх, що лише для власника, чому, і як власник додає ще одного адміна (без токена й PIN, звісно). Три-п’ять речень своїми словами. Розділи ## Що це і ## Як запустити не чіпай./export зі списком у вигляді тексту), і додай її в ADMIN_COMMANDS. Перевір через try_bot.py --id 777 (відмова) і зі своїм номером (працює).PIN_COMMANDS ще одну команду, яку варто захистити. Поясни, чому PIN із чотирьох цифр захищає від чужих рук, але не від викрадення файлу .env.check(), admin_ids() і check_pin(). Зокрема: чому перевірка стоїть першою у handle_update(); чому доступ тримається на номері, а не на імені; навіщо відмова — текст, а не помилка; що означають цифри в 600, 700, 711; чому токен бота треба закривати і правами, і Git.git add -A, git commit -m "Заняття 07: розділ «Хто має доступ» у README", git push origin main і git push. Перед git add — git status --short: .env у списку бути не повинно.Ознаки: systemctl --user is-active club-bot пише activating або failed, а бот у Telegram мовчить. Причину назве Python — сам або в журналі:
cd ~/app
/opt/club/venv/bin/python3 -c "import bot"
tail -n 30 ~/logs/club-bot.logДивись на останній рядок звіту Traceback:
ModuleNotFoundError: No module named 'access' (або 'pin') — файлу немає в ~/app: ls ~/app/access.py, і якщо його немає — крок 4;NameError: name 'access' is not defined — пропущено import access (крок 5);SyntaxError чи IndentationError — дужки або відступи у вставках кроку 5. Не бачиш помилки очима — git diff bot.py покаже все, що ти змінив сьогодні;Виправ і зроби systemctl --user restart club-bot. Якщо служба надто часто падала, systemd перестає її підіймати й пише start request repeated too quickly — тоді спершу systemctl --user reset-failed club-bot, потім restart.
Перевірка читає ~/app/.env і вимагає один рядок ADMIN_IDS= з числами через кому. Найчастіші причини: рядок не дописано (крок 3); у номері є літери, лапки або пробіл усередині; замість номера стоїть ім’я чи нік (@bohdan — це не номер); порожнє значення. Перевір: grep -c "^ADMIN_IDS=" ~/app/.env має дати 1. Номер — це лише цифри, наприклад 100200300.
Перевірка пише, яка саме команда пройшла. Що робити:
на /users чужий побачив записи — вставку access.check(...) з кроку 5 зроблено нижче за обробку цієї команди або її нема. Вона має стояти перед першим if text.startswith(...);відповідь на /users збігається з «Не знаю такої команди» — бот узагалі не знає цієї команди, а перевірка очікує саме відмову. Додай команду в ADMIN_COMMANDS у access.py (вона там уже є для /users, /list, /add, /edit, /del) і перевір, що import access у bot.py є;чужий змінив базу — шукай у bot.py команди, які пишуть у базу, але мають іншу назву, ніж у ADMIN_COMMANDS. Додай їх у список;try_bot.py пускає чужого — ADMIN_IDS містить номер 777 (ти сам його додав під час досліду): grep ^ADMIN_IDS= .env.Перевірка питає від імені людини з ADMIN_IDS і чекає, що /users або /list покажуть записи. Найчастіше: команди зі списком немає зовсім (заняття 04–05), у access.py зайва команда, а в боті її нема, або номер у .env не збігається з номером, з яким ти пробуєш. Спробуй python3 try_bot.py --id <свій номер> "/users": має бути список, а не відмова.
Чужий на /stats має отримати відповідь із числом людей, але без жодного імені. Ця команда лише для власника на /stats — ти додав /stats у ADMIN_COMMANDS, прибери. Не знаю такої команди — немає вставки if text.startswith("/stats") (крок 5) або вона стоїть нижче за загальне «невідома команда». у project.yaml порожнє commands.stats — не заповнено рядок у кроці 5. Помилка no such table: users — таблиці немає: python3 init_db.py.
Якщо ADMIN_IDS опинився у файлі двічі — служба візьме перший, автоперевірка інший. Прибери старі рядки й запиши один:
cd ~/app
sed -i '/^ADMIN_IDS=/d' .env
echo "ADMIN_IDS=100200300" >> .env
grep -c "^ADMIN_IDS=" .env
ls -l .env
systemctl --user restart club-botsed -i редагує файл на місці, а /^ADMIN_IDS=/d означає «видали рядки, що починаються з ADMIN_IDS=»; решту .env він не чіпає. grep -c має сказати 1, ls -l — -rw------- (інакше chmod 600 .env).
Якщо ж замість >> вийшло > і в .env лишився один рядок, відновлюй решту. BOT_TOKEN візьми в @BotFather (/mybots → твій бот → API Token). Рядки ШІ з заняття 06 для учнів до 18 років такі: LLM_BASE_URL=http://127.0.0.1/llm/v1, LLM_API_KEY=school, LLM_MODEL=openrouter/free. Вписуй їх через nano ~/app/.env, кожен з нового рядка, потім ls -l .env (має бути -rw-------) і restart. Перевір бота: python3 try_bot.py "/start".
Кожна перевірка пише причину. Що вона означає:
права … — 644: сусіди по серверу можуть його прочитати — chmod 600 ~/app/.env ~/data/app.db (крок 10);.gitignore не приховує .env — допиши правило: echo ".env" >> ~/app/.gitignore (дві дужки!) і закоміть .gitignore. у історії є .env — файл колись потрапив у коміт, і все, що в ньому було, вважай украденим. Спершу заміни секрети: новий токен бота в @BotFather (/revoke, потім новий токен у .env), потім restart. Далі git rm --cached .env і коміт — Git перестане стежити за файлом. А стару історію чистять окремою командою, яка переписує всі коміти, — це роби лише разом із викладачем.