Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS
📢
Канал в Telegram @stendap_sogodni
🦣
@stendap_sogodni@shevtsov.me в Федиверсі
24.10.2025
Всі способи, якими я хостив PHP
-
Перші спроби були на Windows. Про Linux та тим паче macOS я нічого не знав. Здається що під Windows була готова збірка Apache+PHP+MySQL (може то був WAMP?), але можливо я ставив їх окремо. Запамʼятався графічний інтерфейс вебсервера Apache з рожевим пером. Все було супер просто, бо файли з диска ставали сторінками сайту. Та попри цю простоту, я навіть — як це зараз модно казати — селф-хостив форум — так, це було на Windows.
-
На шаред-хостінгу. Це коли хостинг дає тобі теку на FTP, ти туди заливаєш ті ж файли та вони стають сайтом. Власне, розроблялося все теж на Windows, а що там було на сервері — мені було мало цікаво.
-
На першій роботі через nginx. Перший раз довелося дізнатися, що взагалі-то на світі існує не тільки Apache. Та зʼявилися речі, які локально не працюють — хоча не пригадаю, які саме. (Ой, на тім роботі ще була купа Macromedia Flex, але то ціла інша історія.)
-
На виділеному хостингу… поки через Apache. Бо Apache був набагато простіше в налаштуванні, ніж nginx - та й PHP там був вбудований. Встановив сервер, дописав трохи конфігурації — і можеш так само копіювати файли, як і раніше.
-
Там же ж, але вже із nginx та PHP-FPM. Це фактично сервер застосунків для PHP - запускається окремим процесом, кешує скомпільований код, та служить бекендом для nginx. Спонукало перейти те, що я тримав також кілька застосунків на Rails, та вже nginx став більш зрозумілим рішенням — зворотнім проксі — ніж Apache, який пропонував підхід “все в одному”.
Цікаво, що починався PHP з парадигмою “я вказую шлях до файлу зі скриптом на сервері та викликаю його”, а прийшов до “застосунок є сервісом, який обробляє запити”. І тепер скриптові мови не так вже й відрізняються від компільованих.
23.10.2025
Як я написав форум з нуля, а потім переписав
…Багато років тому, ще до соціальних мереж, в Дніпрі був студентський форум. Створили його Діма та Макс, форум зібрав навколо себе вагому кількість користувачів, це було цікаво та весело, зʼявилася спільнота, яка щотижня збиралася офлайн.
Але в певний момент форум зник. Ну, тобто сайт перестав працювати. Я вже не памʼятаю чому — чи то технічні проблеми, чи то організаційні. Так чи інакше, спільнота залишилася, сайту немає, а власники кудись ділися — може, відпочивати поїхали.
Зате був я. Я на той час захоплювався програмуванням ігор, а про вебтехнології знав небагато. Ну, сайт в мене вже був, але ж то HTML на якомусь безплатному хостингу. На PHP я може щось зовсім іграшкове до того робив.
Отже, думаю, немає старого форуму — напишу новий! І написав. За кілька днів в мене вже була робоча версія форуму на PHP+MySQL. До того ж я не знав, де його хостити, тому кілька тижнів форум жив в мене на локальній машині, доступній ще й через GPRS. Кожна сесія давала мені нову IP адресу, яку я оголошував десь по ICQ та форум оживав. Головне, що спільноті було де спілкуватися.
А потім власники першого форуму повернулися, відновили його, та форумів стало два. Я, звісно, свій не захотів зупиняти, тому кілька місяців в нас були політичні сутички, розколи і таке інше. От, наприклад, тоді я дізнався, що форум не обовʼязково писати з нуля, а можна взяти готовий рушій. У них був vBulletin, або в народі — булка.
Хоча хіба справжній програміст буде брати готовий рушій? Та ні… Коли я почав розуміти, що написаний за тиждень форум був написаний жахливо — а весь його код був у кількох величезних файлах — то взяв та переписав рушій наново ще раз, з нуля. Друга версія була значно краще продумана та навіть трохи страждала від “синдрому другої версії”, тобто випуск її трохи затягнувся.
Звісно, потім зʼявилися соціальні мережі та вбили обидва форуми. Тільки згодом я зрозумів, наскільки це визначна, але дурна праця — писати форум з нуля. Що тут сказати? Принаймні цей проєкт приніс мені першу роботу.
22.10.2025
Авторизація OAuth з iOS
Невдовзі мій стохастичний таймтрекер Ping набуде інтеграції з Beeminder. (Beeminder це сервіс для обліку просування до цілей — який штрафує тебе, якщо ти не виконуєш обіцянок.) Власне, ідею стохастичного таймтрекінгу винайшли саме автори цього сервісу, тому така інтеграція зробить Ping дійсно корисним для однієї дуже важливої спільноти.
Інтеграція починається з автентифікації. Вона у Beeminder потребує діалогу OAuth. Я, звісно, багато разів проходив таку автентифікацію в чужих застосунках на iOS - знаєш, коли відкривається браузер і ти там вводиш логін/пароль. Але не мав гадки, як її реалізують.
Виявилось, все ще простіше, ніж я думав. Браузер відкриває не сам застосунок, а фреймворк Authentication Services. Нам достатньо побудувати URL для авторизації та створити ASWebAuthenticationSession. Та другий момент — щоб повернутися назад, URL редиректу повинен мати особливу схему, щось на кшталт ping://beeminder-auth. Коли браузер побачить цю схему, то завершить автентифікацію та поверне нам остаточний URL - в якому сидить токен.
Гадаю, таким саме чином можна зробити авторизацію через Google або інші провайдери OAuth. А мені залишалося тільки зберегти токен. Для чого є API Keychain services. Я, якщо чесно, спочатку збирався просто в базу зберігати, але виявилось, що є безпечніший спосіб.
21.10.2025
Fail Fast проти Hang In There
Є дві протилежні поведінки застосунку, коли виникають проблеми з його залежностями, або взагалі непередбачені ситуації. (Назви я тільки що вигадав.)
Fail Fast: якщо ми опинилися в ненормальному стані, то за майже визначенням програма не зможе з нього вибратись. Отже, краще, що можна зробити — це зупинити застосунок та дозволити супервайзеру запустити його наново. Переваги цього підходу: з чистого аркуша не виникне внутрішніх розбіжностей, бо ми точно знаємо, що кожна підсистема буде знову в передбаченому стані. Плюс, такий підхід робить застосунок простіше.
Hang In There: застосунок повинен триматися, хай би що трапилося. Кожну помилку пробувати наново. За браком залежності продовжувати без неї, в обмеженому режимі. Взагалі ніколи не зупиняти застосунок, поки не отримаємо на то сигнал. Переваги тут — що не буде перезапуску, власне.
Обидва підходи мають своє застосування. Колись я вважав, що в сучасній інфраструктурі з контейнерами Fail Fast - очевидний вибір. Але дедалі більше хочеться робити застосунки, які не будуть перезапускатися. Зокрема, перезапуск ризикує зробити із поганого гірше, як зі мною вже траплялось.
Вчорашній простій AWS us-east-1 це підкреслив, бо в нашому випадку найдовше не було можливості запускати нові задачі. Отже, те, що трималося — працювало, а те, що очікувало на перезапуск — просто впало до закінчення інциденту. (До речі, також не могли стартувати лямбди, а вони можуть бути тільки Fail Fast.)
Хоча здається, був і протилежний приклад: черги SQS не спричинили відмову споживачів, а просто припинили доставляти повідомлення до перезапуску клієнтів. Поки не зрозумів, чому так сталося.
20.10.2025
Робота із Windows по SSH
Є в мене домашній сервер на Windows. Точніше не так: в мене є ігровий компʼютер на Windows, але оскільки в ньому потужна відеокарта та й решта заліза, він чудово підходить для важких задач, як-от бібліотеки Plex. У 2023 я писав, як перекодовував фільмотеку у HEVC.
Тоді я робив всю роботу на макбуці, а на Windows потім тільки запускав bat-файл для перекодування. Для обмеженої задачі того вистачало. Але тепер стикнувся із тим, що читання файлів у випадковому порядку — з мережевого диска значно повільніше за локальне. Та аналіз кодеків утилітою Mediainfo не закінчився за декілька годин. Отже, вирішив запускати скрипти безпосередньо на Windows.
Але, власне працювати на Windows в мене не було бажання аж зовсім. Зате у Windows є вбудований сервер SSH. Це вже половина справи — можна відкривати термінал та щось там робити. Було б чудово, аби в Windows були нормальні команди оболонки, а не dir замість ls і так далі — але жити можна. От тільки ж Ruby немає.
Ну то й що, chocolatey робить встановлення Ruby в одну команду: choco install ruby mediainfo-cli. Серйозно, без Chocolatey на Windows взагалі немає чого робити. (Також можна було встановити Mise, але поки потреби немає.)
Залишилося питання того, як доставити скрипт на компʼютер із Windows. Окрім того, що можна було змонтувати мережевий диск та редагувати файл прямо там, можна скористуватися звичайним scp, бо він також працює без жодних проблем. Я чомусь про мережевий диск забув, а зробив собі скрипт по типу scp script.rb pc:. && ssh -tt -x 'ruby script.rb'.
От, власне, і все, результату досягнено, те що мережею займало години, локально відбулося за хвилини. Також допомогла рівночасність та клас Thread::Queue, але про то іншим разом.
17.10.2025
Висновки зі спостереження за глюкозою
Кілька висновків від мого експерименту з монітором глюкози. Відразу попереджаю, що я не лікар, а також я ставив монітор з допитливості, а не за медичним призначенням. А ще я не надто педантичний з визначеннями. Отже.
Глюкоза, як, може, всі знають — це паливо нашого організму. Та я раніше вважав, що рівень глюкози в крові й показує “рівень бака” - зростає після їди, та спадає поки голодуєш. Тож очікував побачити звʼязок по типу “перед обідом в мене провал глюкози, а отже важко змусити себе працювати”.
Така аналогія виявилася хибною. Насамперед тому, що “бак” глюкози — це печінка (а потім жирова тканина і так далі, але поки для нас важливо, що це точно не кров.) Кров є лише “буфером”, тобто містить глюкозу, потрібну найближчим часом, а також спожиту з їжі.
Метафора “рівень глюкози = розмір буфера” краще пояснює сенс вимірювати цей рівень. По-перше, він в мене майже ніколи не падав нижче норми, навіть після голодування майже добу. Навпаки, залишався стабільним, майже незмінним. Так що не було таких ситуацій, щоб “мозку нема чого їсти”. Це пояснення ліні можна сміливо скасовувати.
А от після їди “буфер зростає”. Травна система вкидає в кров глюкозу, яку печінка повинна встигнути забрати. Від різного харчування глюкоза вкидається з різною швидкістю, та є низка причин уникати “швидкої” їжі та “переповнених буферів”. Тому, власне, найбільше відкриттів для мене було про “швидкість” різних страв (глікемічний індекс), та, гадаю, це те, що буде цікаво перевірити на собі кожному.
Бо результати здивували. Ну, приємно було побачити, що моє звичайне харчування дуже гладко впливає на графік. Солодощі впливають менше, якщо їсти їх на десерт, а не окремо. А найгірший пік був від хот-дога з заправки — чи то там дійсно багато швидких вуглеводів, чи то допомогло пасивне сидіння за кермом. Ще від ролів — теж здивували. Також солодкі напої помітно бʼють. А алкогольні — не дуже. А ще їжа з Макдональдза на диво помірна. (Хоча, можливо як раз вони-то рецепти вивіряли ретельно.)
Якщо підсумувати… З одного боку не думаю, що стеження за глюкозою здатно суттєво змінити якість життя (як це продають деякі нішеві інфлюєнсери), проте дослідити для себе власну дієту — незамінно. Раджу тільки не ставити монітор спонтанно, а обрати період, коли в тебе буде і нормальне харчування, і особливі випадки.
16.10.2025
Моя система ведення нотаток
В інтернеті безліч описів систем, зазвичай епічно складних — всілякі PARA, zettelkasten, графи знань. Може скластися враження, що ведення нотаток потребує академічної ретельності та відданості. Що ж, я так ніколи не робив, мій “граф нотаток” має вигляд тисяч незʼєднаних вузлів, а все-таки (все ж таки) я щодня покладаюся на ведення нотаток та моя система практично не підводить.
Отже, головна ідея моєї системи: система хороша настільки, наскільки легко в ній знайти те, що потрібно.
Як я шукаю? Майже завжди через плагін OmniSearch для Obsidian. (Власне, це тільки продовження моїх давніх звичок користуватися пошуком замість навігації — через LustyJuggler, Alfred, Sublime Text Search in Project, та Cmd+P у VSCode/Cursor.)
Виходить, мені не дуже важливі теки, теги, звʼязки між нотатками та інша організація. Головне — це наявність влучних ключових слів. На них і будується система організації. Як будується? Якщо потрібне знаходиться, але не з першої спроби — доповнюю ключові слова тими, по якім не знайшов.
Наприклад: якщо в мене була нотатка jeans із посиланнями на гарні джинси, але я намагався знайти її за пошуком pants, то коли нарешті згадаю та знайду, допишу в нотатку також слово pants. (А ще часто джинси та штани.)
Звʼязки між нотатками додаю тоді, коли знаю, що при перегляді однієї нотатки корисно буде побачити й іншу. Наприклад, ймовірно в нотатці clothing буде посилання на jeans. Таких нотаток-каталогів небагато.
Кілька тек в мене є для впорядкування нотаток, які дійсно важливо тримати разом. Наприклад, зони відповідальності. Їх зручно передивитися всі по черзі, та пошук в цьому не допомагає.
Це, звісно, дуже спрощений приклад, бо на практиці нотаток в мене тисячі, і там будуть і jeans from X tv show, і recommended jeans makers, і winter pants, і ще багато всього. Але цей впорядкований хаос залишається корисним — навіть без жорсткої системи.
Втім насамперед потрібно навчитися записувати корисне хоч у якомусь вигляді. І це, я вам скажу, вже нелегка справа. Тож не раджу відразу брати та ускладнювати собі життя складними системами. Ми ж не всі академіки.
15.10.2025
Міст з узагальненими типами на TypeScript
У Firebase, окрім стандартного для JavaScript пакету firebase, є також неофіційний пакет React Native Firebase. Він побудований на “рідних” інтеграціях для iOS та Android, а тому має переваги саме на мобільному пристрої.
От тільки є проблема: хоч RNFirebase майже збігається зі стандартним Firebase за API, але типи для TypeScript оголошує власні. Що з одного боку логічно, бо кожен має власну реалізацію цих типів — наприклад, і там, і там є клас CollectionReference. А з іншого — унеможливлює написання спільного коду, що буде працювати й в браузері, і в React Native. Або чи правда унеможливлює?
(Окремим аспектом тут є те, що не так давно Firebase перейшов з класового API на функціональний. Тобто замість app.firestore().collection("path") тепер є collection(getFirestore(app), "path"). Що і спрощує, і ускладнює задачу.)
Отже, що я зробив? Для початку, весь шар, який звертається до Firebase, в мене прихований в клас. Методи цього класу працюють вже з простими обʼєктами. Можна було б на цьому й зупинитися, розставити всередині класу примусові типи.
Якщо трохи серйозніше ставитись, то можна тепер зробити дві версії класу. Кожна буде імпортувати відповідний пакет та мати всередині повністю сумісні типи. Це реалізація якогось там патерну, не памʼятаю якого. Але доведеться повторити купу коду. (Та й, очевидно, підтримувати в майбутньому.)
Тому знайшов ще краще рішення — оголосив інтерфейс (насправді в ньому більше й параметрів, і методів:)
interface FirebaseAPI<CollectionReference> {
collection(path: string): CollectionReference;
}
class FirebaseLayer<CollectionReference> {
constructor(api: FirebaseAPI<CollectionReference>) {}
}
export let firebaseLayer: FirebaseLayer<unknown>;
Тепер залишається окремо для мобільної та вебверсії реалізувати інтерфейс з конкретними типами та передати цю реалізацію в шар абстракції. А решта коду зовсім не цікавиться тим, які конкретні типи всередині.
Так вийшло мати на 100% покритий типами код та, можна сказати, мінімум повторення (а повторення все ж є, бо реалізації інтерфейсу майже однакові.)
14.10.2025
Правильні помилки в API
Чи знаєте ви, що існує аж цілий RFC про повернення помилок з API? Ось: RFC 9457 - Problem Details for HTTP APIs. Виглядають ці помилки трохи дивно, як на мене:
{
"type": "https://docs.leonid.codes/no-mana",
"title": "We require more mana",
"detail": "Teleportation spell requires 20 mana, but you have 15",
"spell": "teleportation",
"mana_required": 20,
"mana_available": 15
}
Тут type - це URL, який може вказувати на документацію про помилку. Що мені дуже подобається. title - назва помилки, яка є незмінною. А detail - текстове пояснення, яке вже може містити подробиці про конкретний випадок. Нарешті, окрім цих полів можна додавати будь-які власні.
Альтернативою є формат помилок зі специфікації JSON API. Причому назва JSON API може й загальна, але специфікація досить конкретна та суворо обмежує структуру відповідей. Щодо помилок, то вона вимагає на верхньому рівні ключ errors, а в середині — цілий масив обʼєктів помилок — кожна з яких недалека від RFC 9457.
Логіка така, що в такому повідомленні є й частини, які може прочитати людина, зокрема й в журналі, а також є інформація для машинної обробки. Але чого тут немає, так це success: true або status: "ok", які я звик бачити. Знаєте, чому?
Бо найважливіша частина повідомлення про помилку — це статус HTTP. Яких у нас великий вибір. Та вкрай важливо визначити та передати доцільний статус вашої помилки. Тоді зміст відповіді вже несе суто уточнювальний характер.
Є така тенденція ігнорувати статус та дивитися тільки на зміст. Наприклад, на фронтенді так може “простіше” робити. Чув і такі думки, що статуси HTTP тільки для критичних помилок, а якщо сервер “в тямі”, то може вже давати 200-й із success: false, errors: .... Це все від нерозуміння основ протоколу HTTP.
Все це особливо відчутно у статично типізованій мові, де потрібно знати структуру відповіді до того, як ти її прочитаєш. Та й взагалі динамічна типізація погано впливає на якість API, бо дозволяє безтурботно повертати будь-що будь-де.
13.10.2025
React Native: екосистема на піску
Пів року тому я намагався оновити Сінтру до сучасних версій React Native та всього повʼязаного. Не виходило. Нарешті, на цих вихідних взявся за це серйозно та ціною кількох годин все ж поборов!
Корінна проблема React Native в тому, що його існування є, певною мірою, хаком. Так, в теорії, писати під всі системи на знайомому JS/TS - дуже зручно. Але реалізація проста тільки в парадигмі компʼютера, де кожна мова рівноправна. Принаймні на iOS, це точно не так (про Android тут нічого не знаю.) JavaScript виконувався раніше на рушії, який не розрахований на повноцінні застосунки, та упирався в суттєві гальма.
Тому історія розвитку React Native - це поступовий пошук кращої моделі виконання для застосунків, написаних на JavaScript. Зверни увагу, остання “нова ера” в цій історії розпочалася 5 днів тому.
Хотілося б вірити, що це значить, що тепер RN набуде стабільності та розробники бібліотек зможуть наздогнати його та вибудувати стабільну екосистему. Бо поки що вони очевидно відстають. Ба більше, чим більше несумісних версій існує паралельно, тим гірше досвід розробника застосунків (тобто мій.)
Наприклад, що було з Сінтрою цього разу: React Native Navigation нарешті вийшов із підтримкою… але не найновішого RN, звісно, а тільки версії 0.77, та яка січня 2025. Тепер React Native Firebase поки не підтримує стандартний режим збірки застосунків та потребує статичних бібліотек. На жаль в RNN поки тільки наздоганяють стандартні вимоги, тому в них немає прямої сумісності зі статичною збіркою. На щастя, хтось зробив патч для поточної версії RNN, яка робить її сумісною. Та після застосування цього патчу, нарешті, принаймні зібралися залежності.
О, і ще, все це хоча б запускається на новому XCode 26, бо в цьому й була мета капітальних оновлень. Можна рухатись далі.

