November 21, 2023

Безкоштовне - не значить легко

Розділю напевне пост на деякі частини:

  • Що за проблема була?
  • Як я вирішував це?
  • Висновок.

Але спочатку трохи введу в суть проблеми

Я почав з деякими людьми робити телеграм бота. (що за бот, для кого та посилання на його потім, ще в розробці). Нуу, ми молоді люди, інтузіасти які не мають багато грошей, щоб орендувати сервера та запускати там, тому ми вирішили обійтися якимось безоплатним хостингом. Час, коли Heroku був безоплатним, вже пройшов і треба шукати альтернативи і їх не дуже так і багато :). Десь дають N годин для працювання боту, десь потрібно вводити дані від картки, а десь просто дають "маленьке" обмеження (наприклад бот працює пів години і треба перезапускати). І ми вибрали PythonAnywhere.

Це напевне найкращий безоплатний хостинг для запуску Python програм. 512 мб для зберігання скриптів та інших файлів, 100 секунд для повної потужності (якщо за 24 години їх вичерпати, то програма потім буде просто трохи повільніше працювати) і те що коли ти закриєш вкладку в браузері, то програма не завершиться тобто бот буде працювати. Але, якщо б все так чудово було, то і поста не було б :). Хтось казав, що бот може працювати тиждень, хтось декілька днів, а в мене працював завжди 24 години і потім треба перезапускати. І це була та проблема - якось автоматизувати перезапуск бота. Думаю я зміг дати тобі зрозуміти, що за проблема була (була і вже немає)


Як я вирішував це?

Спочатку скажу про мою ОС:

  • Назва ОС - Fedora Linux 38 (Workstation Edition)
  • Версія Gnome - 44.6
  • Версія ядра - Linux 6.5.10-200.fc38.x86_64

Навіщо це? Тому що моє рішення я перевіряв тільки на цій машині і тому не для всіх воно підійте. (на Windows напевне тільки Python скрипт запуститься з цього всього). І я не буду вдаватися у всі подробиці, щоб кожен міг зрозуміти. Розібратися у всьому цьому - це вже буде твоя задача, якщо захочеш :)

Для цих всіх хитростей я використовув:

  • Python 3 версії
  • Бібліотеку для Python - Playwright (можна було Selenium, але в мене не підійшов)
  • Bash скрипт
  • Cron
  • Rtcwake

На початку я писав скрипт на Python і чому Playwright? Тому що ця бібліотека сама завантажує потрібні драйвери для працювання з браузером (в Selenium це потрібно робити самому). Я надіюсь, що ви вмієте завантазувати pip :). Почнемо розглядати код:

from playwright.sync_api import sync_playwright

def main() -> None:
   with sync_playwright() as p:
      # Запуск браузера Chromium
      browser = p.chromium.launch()
      page = browser.new_page()

      # Переходимо на потрібний сайт
      page.goto('https://www.pythonanywhere.com/login/?next=/')
      
      # Проходимо авторизацію
      element_id = 'id_auth-username'
      input_element = page.locator(f'#{element_id}')
      input_element.fill('ЛОГІН')
      element_id = 'id_auth-password'
      input_element = page.locator(f'#{element_id}')
      input_element.fill('ПАРОЛЬ')
      button_id = 'id_next'
      button_element = page.locator(f'#{button_id}')
      button_element.click()

      # Очікуємо повного завантаження сторінки
      page.wait_for_load_state('load')

      # Заходимо в Bash
      # Замість N пишемо свій номер Bash
      link_text = 'Bash console N'
      link_element = page.locator(f':text("{link_text}")')
      link_element.click()

      # Чекаємо 10 секунд поки запуститься
      page.wait_for_timeout(10000)

      # Шукаємо консоль, щоб писати команди
      element_id = 'id_console'
      input_element = page.locator(f'#{element_id}')
      page.frame(input_element)

      # Нажимаємо Ctrl + C щоб зупинити код
      page.keyboard.down('Control')
      page.keyboard.press('C')
      page.keyboard.up('Control')
      page.wait_for_timeout(10000)

      # Пишемо якісь команди для запуску, або ще щось
      page.keyboard.type('python main.py')
      page.keyboard.press('Enter')
      page.wait_for_timeout(30000)
      
      # Все успішно, закриваємо програму
      print('---------------Успех---------------')
      browser.close()

if __name__ == '__main__':
   main()

Коментарі я залишив щоб більше людей зрозуміли які не працювали з цим. Також можна написати таку функцію:

page.screenshot(path='photo.png')

Щоб наприклад, побачити потім як працював бот, бо коли ти запускаєш код, то ти не побачиш що в браузері щось робиться. Також одразу кажу - сайти різні і на PythonAnywhere це спрацює, а на якомусь іншому вже ні. Чому так? Тому що на кожному сайті різні назви елементів і щоб їх дізнатися вже потрібно хоч трохи розуміти HTML та знати як подивитися код сайту і там вже можна знайти це все. Також не вийде просто Ctrl + C та Ctrl + V. Бо все це індивідувально і в моєму коді є моменти де потрібно писати свої значення. І саме головне - не забуваємо, що сайти можуть оновлюватися і тому код може стати вже старим і не працювати.

На цьому взагалі вже можна закінчити, бо з цим кодом як раз і є перезапуск програми, АЛЕ моя задача була автоматизувати це. Я хотів зробити так, щоб це робилось само і як же це зробити? Було багато варіантів і кожен має свої плюси та мінуси. Тут також все індивідуально :). В моєму випадку я хотів щоб код перезапускався сам 2 рази в день в один і той час (в 6 ранку і 9 вечора). Тут можна використати Cron (на Windows є альтернативи) і я так зробив. Відкрив термінал на моєму ноутбуці (може потім якось розповім чому я користуюсь тільки ноутбуками) та ввів команду crontab -e для створення нової задачі яка буде запускатися в якийсь час. Відкривається текстовий редактор де потрібно було написати саму задачу, напишемо наприклад так:

00 07 * * * /path/to/python /path/to/main.py

00 та 07 - це означає що задачу запуститься о 7:00

Три * означає що задача буде запускатися кожен день

/path/to/python - треба написати свій шлях до інтерпритатору Python, наприклад /usr/bin/python але якщо він насправді там!!

/path/to/main.py - це вже шлях до самого Python скрипта і він також в кожного різний, наприклад /home/user/python-scripts/main.py

Після цього зберігаємо і виходимо. Все, задача зроблена і код буде запускатися о 7:00 кожен день, якщо шляхи правильні та код також немає помилок. Можна було поставитина на цьому крапку і закінчити, але... Якщо ПК був виключений о 7:00, то код не запуститься). Як це вирішити? Та багато варіантів: можна щось інше використовувати замість Cron, полазити в BIOS або можна використати інші спеціальні методи для пробудження системи. Тут також є різні методи systemd, BIOS і тд і тп. Кожен має свої мінуси та плюси. Я використовував Rtcwake. Ця утиліта проста у використанні, але вона має один мінус - можна поставити таймер на 1 час, а ось щоб пробуджувало кожен день немає такої функції (або я не знайшов) і тут вже я вирішив це також автоматизувати через Bash скрипт.

Мій план був досить простий: через Cron запускати вже Bash в якому буде запускатися Python скрипт та ставитися новий таймер. Звучить легко, а на ділі... Проблема така, що Rtcwake команди можна використовувати тільки з рута (sudo) і я перепробував багато варіантів... В sudoers файлі пробував дати права цій команді, для SELinux писав нові модулі і все що я пробував не дало мені змоги запустити Rtcwake без команди sudo, або щоб при цьому не запитувало пароль (повністю виключати пароль це досить дурна ідея) і я вирішив в самому Bash написати пароль від рута (я дуже сильно наполягаю так не робити, бо це може призвести до досить неприємних ситуацій, а якщо робите, то зробіть це тихо щоб ніхто не знав де лежить цей пароль). Ну наче все вийшло через Rtcwake пробуджує ПК і скрипт може запускатися, але чи насправді все? Ні... Якщо в тебе ПК, то може і так, але в мене ноутбук. Яка ж проблема може бути? Все легко - коли кришка закрита, то ноутбук відразу переходе в сплячий режим. І я вирішив відключити взагалі функцію щоб коли кришка закрита він не переходив в режим сну. В Bash я написав, що якщо закрита, то після закінчення кидати пристрій в режим сну. Ось код:

#!/bin/bash

# Переходжу в папку з скриптом та запускаю скрипт
cd /path/to/project/
python main.py
# Переходжу в корінь щоб не було проблем
cd

# НІКОМУ НЕ РЕКОМЕНДУЮ ТАК РОБИТИ І ПРИДУМАТИ ЩОСЬ КРАЩЕ!!!
password="ПАРОЛЬ"

# Визначаю яка зараз година і якщо 6:00, то ставимо пробудження на сьогодні
# о 21:00, або на завтра о 6:00 якщо зараз 21:00
current_hour=$(date '+%H')
if [ "$current_hour" -eq 6 ]; then
        echo "$password" | sudo -S rtcwake -m no -t $(date -d 'today 21:00:00' +%s)
elif [ "$current_hour" -eq 21 ]; then
        echo "$password" | sudo -S rtcwake -m no -t $(date -d 'tomorrow 06:00:00' +%s)
fi

# Визначаю чи відкрита кришка і якщо закрита, то виключаємо
lid_state=$(cat /proc/acpi/button/lid/LID0/state | awk '{print $2}')
if [ "$lid_state" = "open" ]; then
        echo "is open"
else
    	echo "$password" | sudo -S systemctl suspend
fi

/proc/acpi/button/lid/LID0/state - це мій шлях де лежить ця інформацію і у вас може будти інший

Також я поставив щоб запуск був о 6 ранку, та 9 годині вечора. Що далі робити? Просто робимо нові задачі в crontab -e що буде запуск о 6:01 та 21:01 цього Bash скрипта (на 1 хвиливину пізніше щоб ПК встиг прокинутися та під'єднатися до мережі) і якщо вже наприклад 21:37, то запускаємо в терміналі команду:

sudo rtcwake -m no -t $(date -d 'tomorrow 06:00:00' +%s)

Щоб пристрій зміг прокинутися і все... Далі він буде працювати поки зможе. Якщо ПК був в цей час включений, то Python скрипт відпрацює, поставе новий таймер і все. Rtcwake працює нормально, якщо під час пробудження він включений.

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


Висновок

Автоматизація процесів - це круто (навіть цікаво, якщо тобі це подобається) та покращуєш собі життя, або іншій людині.

Ця автоматизація повністю безкоштовна і я витратив 2 дні щоб це все придумати та зв'язати (я в перше таке роблю і пробував дуже багато варіантів), але можна було просто заплатити і все було б набагато швидше

Якщо захочеш - зможеш. Та не треба ніколи здаватися. Я це робив сам: гуглив, ChatGPT та просто пробував все що міг і в мене вийшло.

Ніколи не здавайтеся і ви все зможете!!