Программы: Исправляем динамичность IP-адреса
18.10.2018


Где-то три месяца назад я жаловался на то, что приказал долго жить универсальный скрипт по синхронизации динамического IP-адреса сервера с доменами — ddclient. Он поддерживал API многих сервисов динамического (dynamic) DNS, в том числе и Cloudflare. Однако, поддержка последнего однажды сломалась, а разбираться в огромном скрипте на незнакомом мне Perl'е как-то не особо хотелось. В итоге на какое-то время я забил на идею домашнего хостинга и выключил сервер. Сегодня мы вновь вернёмся к этому вопросу, ведь я наконец-то его решил!

Обождав какое-то время, отдохнув и набравшись сил, я решил посмотреть по сторонам. Оказалось, в gist'ах легко можно найти небольшие bash-скрипты, дёргающие Cloudflare API прямо через curl. В комментариях даже есть ссылка на форк с готовыми конфигами к systemd! Мне они не очень понравились, так что я написал свой вариант. В моём варианте параметры передаются не в самом скрипте, а через переменные окружения, описанные в файле сервиса. Поменять нужно следующие параметры:

User=<имя пользователя, от имени которого будет запускаться скрипт>
ExecStart=<путь к директории со скриптом>/cloudflare-update-record.sh
WorkingDirectory=<путь к директории со скриптом или любой другой путь для временных файлов>
Environment="CLOUDFLARE_AUTH_EMAIL=<логин в Cloudflare>"
Environment="CLOUDFLARE_API_KEY=<ключ к Cloudflare API>"
Environment="CLOUDFLARE_ZONE_NAME=<название зоны (домен второго уровня)>"
Environment="CLOUDFLARE_RECORD_NAME=<домен с А-записью>"

В принципе на этом можно было бы и закончить мой рассказ, но так уж получилось, что я ленивый дурак и долгое время игнорировал возможность создания CNAME-записей в настройках доменов. Вернее просто не соизволил ознакомиться с другими видами записей, кроме А, и даже не догадывался о существовании возможности указать реальный IP-адрес только для одного домена, а дальше ссылаться на него со всех остальных! А поскольку скрипт выше позволяет обновлять только один домен, я принялся модифицировать его, но в итоге так ни к чему и не пришёл, а только в очередной раз вымотал себе нервы: упаси боже ещё хоть раз писать эти дурацкие bash-скрипты с их неинтуитивным синтаксисом, дурацкими пайпами и утилитами с кучей «магических» аргументов!

В итоге я наткнулся на пакет cloudflare-ddns, написанный на Пайтоне и позволяющий с большей гибкостью управлять DNS-записями. Он оказался не без багов, которые впоследствии мне пришлось фиксить (и перетащить к себе половину кода модуля :/ ). Однако в результате был рождён пакет cfdyndns-updater.

На самом деле он должен был называться cfddns-updater, на что намекает название как самого модуля внутри, так и исполняемого файла — cfddns_updater. Чем же вызвано такое несоответствие? Моей глупостью и неосторожностью. Поторопившись и залив релизную версию немного раньше времени, я удалил только что созданный проект cfddns-updater. И как оказалось впоследствии, это было серьёзной ошибкой! PyPI — это не GitHub — удалить проект, как репозиторий, и пересоздать его под тем же именем невозможно. PyPI просто не позволяет этого! Таким образом, одно неосторожное движение начинающего «пакетёра» делает целое название потерянным для общества навеки… Простите меня, пожалуйста :(

Что же он делает? По сути это просто более удобная обёртка над модулем cloudflare_ddns, исправляющая некоторые ошибки и позволяющая задавать конфигурацию в виде YAML-файла (спасибо библиотеке PyYAML). При этом скрипт запускает вечный цикл, который периодически просыпается ото сна и проверяет, изменился ли IP-адрес и не пора ли обновить DNS-запись.

Для работы скрипта нужен Python версии 3.6 или новее. Установить пакет можно, как обычно, с помощью pip'а:

pip install cfdyndns-updater

Дальше следует создать конфигурационный файл. Разместить его можно в домашней директории под названием .cloudflare-ddns-config. Для POSIX-совместимых систем также поддерживается путь к глобальному файлу конфигурации, который используется вместо пользовательского при его отсутствии: /etc/cloudflare-ddns-config. Впрочем, путь к файлу с конфигуацией всегда можно передать явно в качестве единственного опционального аргумента, принимаемого скриптом:

cfddns_updater config.yml

Сам файл конфигурации выглядит как-то так:

email: <логин в Cloudflare>
api_key: <ключ к Cloudflare API>
periodicity: <таймаут между проверками в секундах>
domains:
  - example.org    # 'proxied: true' подразумевается
  - www.example.org
  - domain: ssh.example.org
    proxied: false

В репозитории также лежат примеры конфигурационных файлов для разных системных менеджеров. Их можно использовать, чтобы настроить автоматический запуск и перезапуск скрипта при падении.