![](https://habrastorage.org/getpro/habr/post_images/d7d/914/0de/d7d9140dee091181154ffe4dc34d1400.jpg)
Задача: написать скрипт, который умеет загружать новую версию себя с удаленного сервера и самообновляться.
Практика автообновлений широко применяется среди десктоп-клиентов и операционных систем, но в вебе встречается редко. Однако, для скриптов, где одна установка обеспечивает работу одного ресурса (а это, фактически, все скрипты, которые ставишь себе на хостинг), автоматизированная возможность установки обновлений не менее важна, чем для десктопов. К веб-сервисам это, конечно, отношения не имеет.
Технически выполнить обновление скрипта не так уж и сложно — загрузить обновление с удаленного сервера и установить его поверх работающей версии — однако, есть некоторые нюансы. Мы в Вебасисте применяем практику установки удаленных обновлений уже давно и накопили большой опыт работы со всевозможными конфигурациями серверов хостинг-провайдеров. Поделимся опытом.
Это не туториал, поэтому вот сразу окончательный вариант скрипта: www.webasyst.com/etc/ru/selfupdate-1 (скрипт index.php; ≈20 КБ).
Скрипт содержит класс selfUpdate, который выполняет обновление самого себя (скачивает обновленную версию файла index.php и заменяет ей работающую в текущий момент).
Под катом рассмотрим как устроен процесс обновления и какие могут возникнуть проблемы при обновлении.
Как запустить скрипт?
Просто скачайте скрипт и загрузите в папку на сервере. Запустив скрипт в браузере, вы увидите заголовок Hello World и кнопки обновления скрипта.
Для того, чтобы обновление скрипта работало, нужно включить права на запись для корневой папки, куда установлен скрипт. Это необходимо, потому что скрипт будет создавать в этой папке поддиректорию для загрузки в нее обновленного скрипта с удаленного сервера. Если запускаете скрипт на локалхосте в Windows (например, на основе пакета «Денвер»), то права доступа предоставлять не надо — все будет работать само собой.
Скрипт index.php загружает с сервера свою обновленную версию как есть (не в архиве) и заменяет себя на нее. Обновление скриптов, в которых много файлов, в реальности отличается только тем, что с сервера загружается архив со скриптами. В остальном же все работает по такой же схеме: загрузил файлы — обновил файлы.
Загрузка файла с удаленного сервера
За обновлением скрипт идет по тому же адресу, который был представлен выше (http://www.webasyst.com/etc/ru/selfupdate-1/). Этот источник обновлений отдает «обновление» в виде вложения (для наглядности мы сделали две вариации: V1 выводит надпись Hello world, и V2 показывает сумму трех случайных чисел).
Загрузка файла с удаленного сервера из скрипта возможна двумя способами:
Через fopen(): Да, через обычный fopen(). Но для работы этой функции необходимо, чтобы в настройках PHP (php.ini) был установлен параметр allow_fopen_url = On и в списке поддерживаемых протоколов был HTTP.
Через cURL: Этот вариант предпочительнее, так как более гибкий. Например, при использовании cURL можно сделать возобновление загрузки в случае, если она была прервана.
Обновление работающего скрипта
Файл PHP-скрипта, работающего в текущий момент, не блокируется системой по записи, и поэтому его можно перезаписать. В связи с этим схема установки обновления в нашем примере следующая: файл загружается в отдельную подпапку (/updates/download/), проверяется правильно ли загрузился файл (соответствует ли размер скачанного файла размеру, заявленному в заголовках ответа, а также проверкой md5-хеша файла), и затем работающий скрипт index.php перезаписывается обновленным файлом.
В случае с одним файлом все тривиально: скачал и перезаписал. Интереснее, когда с сервера загружается архив со скриптами. Интереснее тем, что тут есть три варианта (стратегии) распаковки:
1) скачанный архив можно распаковывать поверх работающих скриптов: хороший вариант, но на момент распаковки это сломает работу системы, будет даунтайм;
2) скопировать текущую версию работающих скриптов во временную папку, распаковать поверх этой папки скачанный архив, рабочую версию переместить в директорию бекапов, а на её место обновленный код: вариант плох тем, что предполагает сохранение устаревших файлов;
3) распаковать архив в отдельную папку, а затем подменить работающую папку обновленной версией: этот вариант наиболее интересен, потому что предполагает наименьший даунтайм в работе скриптов.
Проблемы
Основные проблемы, возникающие при обновлении:
― max_input_time (ограничение на время операций ввода-вывода): скачивание файлов значительного объема может не уложиться в максимально допустимое время;
― max_execution_time: процесс обновления может упереться в ограничение на общее время работы скрипта. Особенность в том, что такого не может случиться при загрузке файла с удаленного сервера, т.к. PHP не считает время на операции с файлами (хотя, есть особенности в Windows-системах), однако, ограничение становится критичным при распаковке архивов значительных размеров;
― доступность сервера обновлений: помимо отсутствия подходящего транспорта для загрузки обновления (посредством fopen или curl) могут иметь место проблемы с сетью (DNS, роутинг, firewall);
― разрывы соединения с сервером во время скачивания файла: необходимо проверять целостность файла после загрузки, но для этого сервер, который выдает обновление, должен уметь отдавать и md5-хеши файлов.
Перечисленные проблемы характерны для загрузки больших архивов. Для обновления одного двадцатикилобайтного index.php это не актуальны (за исключением необходимости проверить целостность загруженного файл).
Загрузку больших архивов мы планируем рассмотреть в следующем посте, где представим вторую версию класса selfUpdate с поддержкой загрузки файлов (с прогрессбаром!), распаковку архива и возобновление загрузки в случае сбоя.