Предыстория
Исторически так сложилось, что я занимаюсь разработкой в digital‑агентстве полного цикла в центре столицы. Знаете, бывают такие компании, которые делают контекст, smm, seo, пилят или допиливают сайты, всяческие автоматизации, интеграции b2b, b2c и т. д.? Вот в такой компании и работаю.
У моей команды много сайтов на поддержке, а в нашей стране много праздников. В праздники интернет продолжает работать, реклама продолжает крутиться, люди продолжают покупать на сайтах, а значит и сайты должны функционировать 24/7 вне зависимости от цвета дня календаря. А как узнать, что все в порядке, если ты жаришь шашлык на даче в 100 км. от столицы? Все очевидно — нужен сервис, который будет мониторить сайты и пинговать дежурного программиста, если что‑то пошло не так.
Честно скажу, сперва я, как ленивый умный человек, решил воспользоваться сторонним сервисом. Погуглил, почитал обзоры на Хабре, посмотрел сервисы и не впечатлился. Не то, что бы ничего интересного не нашел. Но не нашел рабочего продукта, который бы меня устраивал на все 100%. Печально конечно, однако время было перед Новым Годом, я был в отпуске и мне пришла в голову мысль: «А почему бы мне не написать свой сервис для мониторинга сайтов с блекджеком и прочими радостями?» Что еще делать человеку в праздники?

Требования к сервису
Я посмотрел старые кейсы, в которых что‑то пошло не так и решил мониторить только то, что нужно мне, а именно:
HTTP ответ сервера (работает/не работает(и как именно не работает))
Срок действия сертификата (бывало заканчивался сертификат в самый неудобный момент)
Инфу о доменном имени (бывало, что и домен заканчивался внезапно)
Время отклика от сервера (достаточно часто на крупных высоконагруженных проектах эта метрика нужна)
Для минимально рабочей версии вполне достаточно. Алгоритм действий следующий: будем смотреть все это, логировать и класть в базу данных. Если что‑то пошло не так — напишем всем подписанным на этот сайт в телеграм. Ну и сделаем какой‑нибудь веб‑интерфейс с авторизацией в котором можно будет посмотреть логи. Фронт пусть будет Vue3+VUEX на фронте, а с бэком пусть по рест‑апишке общается. Бэк традиционный php+mySql. Звучит достаточно просто. Вперед!
Авторизация
В этом проекте, я мог делать все, что хочу, и не делать то, чего не хочу. Например, мне не хотелось писать свою регистрацию. Даже брать свою уже готовую не хотелось. «Прикрутить стороннюю — вот отличное решение!» — подумал я. Пусть какой‑нибудь другой сервис забирает на себя все эти истории с восстановлением пароля, заходов с непонятных мест и т. д. Я опросил коллег и мой выбор пал на Яндекс (у 100% людей опрошенных оказался аккаунт, который они используют для музыки/такси/шеринга или доставки еды).
Не буду сильно углубляться в детали реализации, официальная документация вполне понятна, хоть и разбросана среди всех документаций Яндекса. Просто основные моменты со ссылками:
Заходим в на страничку, заводим там новое приложение. Получаем ClientID, ClientSecret приложения и указываем адрес, на который перенаправить человека после авторизации.
При помощи ClientID получаем код подтверждения для получения токена пользователя пользователя (посылаем его по ссылке вида: https://oauth.yandex.ru/authorize?response_type=code&client_id=YOU_CLIENT_ID&redirect_uri=URL_TO_REDIRECT)
Имея код подтверждения (он приходит в виде гет-параметра после редиректа на URL_TO_REDIRECT), ClientID и ClientSecret, меняем их на токен пользователя при помощи POST-запроса
Теперь, когда у нас есть токен, поменяем его на данные о пользователе. В нашем случае это аватар и логин. Оформляем по вкусу. ( Я в тот вечер похоже пересидел в документации Яндекса и мои вкусы стали специфичными.)
то чувство, когда понимаешь, что в логине без :first-letter не обойтись
База данных
Итак, пользователь есть, нам надо его где‑то хранить. Поэтому самое время подумать над структурой базы данных. У меня вышло 4 таблички:
users — хранит необходимые данные про пользователя;
sites — название сайта, когда домены/сертификаты заканчиваются;
siteToUser — чтобы понимать кто какие сайты смотрит;
errLog — если ошибка‑пишем, если исправилась тоже пишем.
В 1-ю табличку кладем то, что получили в результате авторизации. Осталось только прикрутить к юзеру телеграм.
Telegram
Про создание ботов для телеграм на php очень много всего написано, я взял 1-ю попавшуюся статью (вроде бы эту) и сделал так же, как написано в ней.
Теперь, когда у нас есть бот, нам надо как‑то связать его с пользователем, для этого юзер берет в ЛК число (которое идентифицирует его) и шлет боту.

Бот посмотрит есть ли он в базе и добавит нас, если будет совпадение.

Если честно, это лишнее движение мне не нравится. С точки зрения юзабилити «скопируй‑пошли» как то не очень. Однако, к моему сожалению, я не придумал другого механизма. Бот не умеет писать первый и надо как‑то понять, кто именно написал боту.
Теперь, когда юзер готов осталось сделать так, чтобы он добавил себе список сайтов, которые хочет мониторить.
Cписок сайтов
Реализация списка сайтов сводится к реализации обычного CRUD (CRD если быть точнее). Добавляем, если нет такого сайта, сразу смотрим его статус и пишем в базу. Если же есть, то просто линкуем юзера и сайт в таблице sitesToUsers. Удаляем просто отлинковкой (все-равно если за сайтом никто не смотрит, то он и не проверяется). Не забываем спросить, точно ли пользователь хочет удалить сайт.

Один момент мне запомнился на этом этапе - написание регулярки для валидации корректности написания доменного имени сайта. Регулярные выражения для меня подобны походу к стоматологу. Регулярная необходимость без которой хотелось бы обойтись. В этот раз вышло как-то так:
const reg = /^http([s]{0,1})\:\/\/([\wёa-я-]{2,}\.)+[\wёa-я-]{2,}$/i
Список сайтов есть. Осталось получить данные по ним.
Чтобы не превращать статью а лонгрид, я не буду вдаваться в подробности реализации, просто расскажу как получал данные и оставлю ссылки на документацию. Если по какому-то моменту появятся вопросы - я отвечу в комментариях, или сообщениях.
HTTP
сделаем массив со всеми возможными кодами ответов
получим ответ при помощи curl (CURLINFO_HTTP_CODE)
PING
По факту пинг — это время ответа сервера, как известно curl тоже умеет это делать, поэтому не будем ничего придумывать, а просто добавим в предыдущий запрос получение еще одного параметра (CURLINFO_CONNECT_TIME_T)

HTTPS
SSL сертификат получаем с помощью контекста потоков и разбираем его при помощи openssl_x509_parse. В итоге нам интересен параметр validTo_time_t который мы и получаем. Результат выглядит так:

WHOIS
Чтобы получить информацию о домене, посылаем TCP запрос (43-й порт) на WHIOS-сервер. Для разных доменных зон, бывают разные WHOIS сервера.
Логика следующая: мы берем домен смотрим какая у него там зона и шлем уже на нужный сервер. В ответе получаем текст, из которого в результате парсинга достаем значение paid-till. (практически для всех доменов этот параметр есть)

В тексте с ответом приходит еще ссылка на правила использования данного WHOIS сервера (для зоны ru выглядит так)

CRON
Теперь, когда мы научились получать данные, надо понять как часто их нужно актуализировать.
Количество дней до окончания сертификата и домена обновляется раз в сутки, поэтому проверяем все сайты раз в сутки на эти значения и если что-то обновилось - актуализирует в базе данных. Предупреждаем всех причастных за 5 дней до окончания сроков. Стараемся уложиться в лимиты для запросов к WHOIS серверам.
Яндекс проверяет http статус сайта раз в 15 минут и если сайт еще через 15 минут лежит - отключает рекламу (если верить техподдержке). Мы делаем быстрее. Статус обновляется раз в 11 минут и если что-то идет не так дежурный программист сразу приступает к решению вопроса. На практике, в 90% случаев сайт поднимается до того, как Яндекс успел среагировать.
Еще немного интерфейса
На этом можно было бы и закончить, но мне (да и нашим клиентам) порой интересна статистика, поэтому мы ее покажем.
текущее состояние сайта (данные актуализируются, когда мы выбираем сайт в списке сайтов)
данные за месяц (помечаем дни без ошибок зеленым, дни с ошибками - красным)
Когда у общества нет цветовой дифференциации штанов, то нет цели! А когда нет цели — нет будущего! (с) Ну и конечно же детализация по дню. Лог выводим просто в окошке, если что-то не так раскрашиваем.
спустя 2 дня мониторинга хабр начал отдавать404 Для отображения пинга используем обычный график от chart.js
наглядно видно, что что-то пошло не так, а потом все починили и все так, как надо.
Результат:
Помните в самом начале, я описывал требования к минимально рабочей версии? Так вот, все они выполнены. Время от времени выскакивают баги, но их все меньше и они достаточно быстро исправляются. В целом вышла достаточно удобная штука, которая мониторит и говорит, если что-то не так. Это решает 99% наших вопросов нашего отдела по мониторингу.
Однако есть еще и другие отделы, поэтому я планирую сделать несколько доработок:
Необходимо осуществлять проверку из разных точек (для начала возьмем города-миллионники в России)
Необходимо будет сделать возможность отслеживания статуса отдельных разделов сайтов (мы же живем в эпоху микросервисов)
Люди любят отчеты, надо добавить рассылку писем с еженедельным и ежемесячным отчетом.
Подумать над масштабированием базы данных. Сейчас все вертится на хостинге, который мы используем как песочницу. Однако, если возникнет необходимость придется поиграть с выделенным серверами и докерами-кубернетисами. (С другой стороны, кому интересно что там было с сайтом несколько месяцев назад? Важно то, что здесь и сейчас) Посмотрим по обстоятельствам.
Конечно же нужно адаптировать сервис под мобилку.
Итоги
Теперь нам гораздо проще отслеживать состояние и обеспечивать бесперебойную работу сайтов наших клиентов 24 часа 7 дней в неделю не взирая на праздники и прочие обстоятельства. Все довольны.

Для тех, кто дочитал до конца ссылка на результат с ограничением в 1 сайт для добавления. Пользуйтесь.