PHP-скрипт, который обновляет сам себя



    Задача: написать скрипт, который умеет загружать новую версию себя с удаленного сервера и самообновляться.

    Практика автообновлений широко применяется среди десктоп-клиентов и операционных систем, но в вебе встречается редко. Однако, для скриптов, где одна установка обеспечивает работу одного ресурса (а это, фактически, все скрипты, которые ставишь себе на хостинг), автоматизированная возможность установки обновлений не менее важна, чем для десктопов. К веб-сервисам это, конечно, отношения не имеет.

    Технически выполнить обновление скрипта не так уж и сложно — загрузить обновление с удаленного сервера и установить его поверх работающей версии — однако, есть некоторые нюансы. Мы в Вебасисте применяем практику установки удаленных обновлений уже давно и накопили большой опыт работы со всевозможными конфигурациями серверов хостинг-провайдеров. Поделимся опытом.

    Это не туториал, поэтому вот сразу окончательный вариант скрипта: 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 с поддержкой загрузки файлов (с прогрессбаром!), распаковку архива и возобновление загрузки в случае сбоя.
    Webasyst
    28.03
    Company
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 58

      0
      И без cURL можно сделать возобновления закачки(HTTP_RANGE). А как вы поступите если обновление затрагивает только некоторые файлы(разбросанные по разным папкам)? 3-й вариант не подойдет(если я все правильно понял).
        0
        Первый вариант точно сработает.
        0
        Каковы направления применения предложенного решения?
          +19
          Хочешь — вправо, хочешь — влево )
            +12
            Образование на своем сайте security hole вселенского масштаба, по-моему. То, что предлагается, позволяет на по сути запустить совершенно произвольный код на сервере — немногим лучше eval(входящая-строчка).
              0
              Ботнет удобно обновлять…
              +2
              Я для подобных целей храню актуальную версию всех скриптов (у меня не php, а микс из баша и дотнета, но сути дела не меняет) в deb-пакете и обновляю по крону централизованно из репозитария. При желании можно напихать в пакет скриптов для запуска при установке или удалении. Очень удобно и надёжно, а главное — без велосипедов.
                +10
                Ещё, как простейший вариант, можно использовать git или svn. Удобно, быстро, и файлы не надо качать целиком, а только diff'ы.
                  0
                  Я давно о таком мечтаю, чтобы как только новая версия в репозитории, он ее оп и установил, думаю займусь завтра этим вопросом.
                    +1
                    svn post commit вам в руки.

                    Обычный хук, который будет делать обновление на веб-морде по любому обновлению стейбл-бранча.
                      0
                      Откуда мой сервер узнает об обновлении бранча сервера разработчиков, которые обо мне в общем случае не знают?
                        0
                        Просто периодически делать update?
                          0
                          А причём здесь хуки тогда? Или имеется в виду сложная система деплоя, когда после апдейта на сервере (или вообще на третьей машине) из рабочей копии заливается в рабочий каталог и, возможно, запускаются миграции БД и т. п.?
                    0
                    Этот вариант, конечно, был бы самым удобным, но он будет работать далеко на всех хостингах: на своем сервере так и нужно делать, а вот на шаред-хостинге не получится.
                      0
                      на шаред-хостинге

                      И много вы видели шаред-хостингов с Curl, с ini_set, и.т.д?

                      Даже SSH может похвастаться не каждый, а на тех где есть, обновление делается намного проще. Точнее две строчки одна wget вторая (если ПО не состоит из одного файла) tar

                      (Можно делать это и из РНР, через всякие system/exec/passthru но это тоже извращение да и чаще всего на «шариках» они отключены)
                        +1
                        Curl и ini_set есть на очень многих хостингах (говорю по опыту установки продукта Shop-Script, который за много лет поработал на очень многих хостингах и в рунете, и вне него).
                          0
                          А вам, по хорошему, надо было расписать всю вашу систему обновлений от начала и до конца в одной статье, без размазывания на 33 статьи на хабре :)
                          0
                          Если бы вы имели удовольствие поддерживать друпальные (или подобные) сайты, вы бы так не говорили :) Обновление core и в добавок кучи сторонних модулей вручную то еще удовольствие, сплошной wget→tar→diff→patch→db_update→«ищем почему все перестало работать»→«omfg, как это откатить обратно?».
                          У авторов вебасиста стоит именно такая задача → упростить обновление всей этой среды.
                            +1
                            Я имею удовольствие заниматься подобными вещами уже дай бог памяти 6-7 лет, и согласен что «упростить обновление» очень и очень хорошее, и главное очень нужное занятие. (На многие CMS в этом плане без слёз в прямом смысле смотреть невозможно. И это только смотреть, обновлять вообще можно только со слезами.)

                            Однако делать это надо по уму, а не «как попало» вот дело то в чём.
                              0
                              Согласен, но я не уверен, что будет лучше если, разработчики ядра и модулей будут предоставлять bash скрипты сделанные «по уму» для обновлений и отката?
                              Вообще интересно, почему никто этим не занимается?
                                0
                                Этим обычно (в опенсорсе) принято выкладывать дистриб, а юзер дальше крутится сам. В разработках по контракту обычно в ТЗ пишут «хочу как там». Вот и вся математика.

                                Но те студии/фрилансеры/итд которые встречались с пунктом «обновление без проблем» в ТЗ чаще всего разрабатывают изначально так, чтоб это было возможно. (чтоб новые модули подключались без правки файлов из дистриба, чтоб конфиги и шаблоны при извлечении из дистриба не заменялись, и.т.д.) в итоге автообновление в 99% заключается в wget+tar и всё…
                                  0
                                  То что так принято, это понятно. Мой вопрос в том − почему так принято? Почему до сих пор эту проблему не решили даже в крупных проектах типа друпала?
                                  Я в теме не разбирался, но должны же быть какие то причины видимо, вот какие вам известно?
                                    0
                                    Да просто никто не думал об этом в таком разрезе. (И не важно, крупные типа друпала, мелкие типа «Вася Пупкин Корпорейшин»)
                                    Те кто думали давно уже что нибудь придумали. (кто-то лучше, кто-то хуже)
                                      0
                                      да ладно «не думал»!? все комьюнити на половину забиты вопросами связанными с проблемами с обновлениями.
                                        0
                                        Значит плевать они хотели на комьюнити, им самим удобно, и хоть ты тресни. Мне что ли вам объяснять как программисты нынче работают? (вы Хабр почитайте, и этот топик включительно)
                                      0
                                      [sarcasm]Как обычно — разработчикам это не нужно.Они не хостят сайты на шаред-хостингах, обновляются через шелл и т. п.[/sarcasm]
                                        0
                                        даже не на шареде обновить полугодовалый друпал может вылиться в приличный геморрой.
                            0
                            Для шаред-хостинга у меня была мысль брать список изменений из git'а и обновлять файлы через консольный интерфейс winscp.
                          0
                          для таких автообновлений есть замечательная чтука как различные оптимизаторы типа APC или eAccelerator.
                          Им можно либо запретить самостоятельно проверять mtime файлов, тогда можно делать со скритами все что угодно, хоть занулять — до некой команды оп код просто не будет обновлен. Либо просто поставить интервал проверки в некое разумное значение( минута ), вы же не меняете на самом деле файлы постоянно.
                          Ну или написать на node.js маленькое приложение для копирования файлов.
                          Массовые асинхронные операции над файлами очень любимы линуксами( если речь идет о полном копировании файлов это может быть раз в 20 быстрее rsync )
                            +18
                            В случае обновления одного файла небольших размеров (в зависимости от настроек хостинга) максимум что необходимо это
                            <?php
                            $url = 'http://localhost/self.txt';
                            if ($updated = file_get_contents($url)) {
                                file_put_contents(basename($_SERVER["PHP_SELF"]), $updated);
                            }
                            ?>

                            Если файл не скачается (ошибка итд) put_contents не сработает, если сработал то всё прекрасно обновится без сбоев и прочего. Остальное это лирика. никакие доп-проверки и Curl-ыне нужны. Выдумывание проблем из ничего.

                            В случае с использованием update-механизма в больших проектах (из кучи файлов разбитых по папкам, обновлений БД) рассчитанных как на большую универсальность, так и наоборот на высочайшую надёжность, всё делается совершенно другими путями. Хотя чаще там даже не стоит этот вопрос в таком разрезе.

                            Касаемо кода, даже не дайте мне начать… По вашему allow_fopen_url может оказаться выключенным а ini_set нет?
                            Убило просто даже начало, вот ваш код
                            @ini_set('magic_quotes_gpc', 'off');
                            @ini_set('magic_quotes_runtime', 'off');

                            (дальше читаю через силу, и местами не могу сдержать смех)
                              +3
                              100%. Никогда не понимал любителей писать километровый код для пустяковой задачи, да ещё и заворачивать в классы всё подряд.
                                0
                                >всё делается совершенно другими путями
                                очень интересно какими?
                                  0
                                  Есть много разумных и не очень путей.

                                  Начнём с того что почти никто (в здравом уме) не делает авто-обновление прямо в РНР (есть исключительные случаи, но о них не сегодня)

                                  Вообще авто-обновление мало кто делает, чаще всего где нибудь в админке (или просто в шапке когда человек залогинен как админ) вставляется так или иначе информер о том что новая версия есть (понравилось элегантное решение через просто картинку с надписью «последняя версия х.yy.zzz» в png/gif много трафика это не займёт, а за одно нет инклуда удалённого кода)

                                  А для процесса обновления (мне нравится этот вариант) пишется уже .sh скрипт где:
                                  (В случае автоматики можно запускать его по крону)
                                  1. Запрашивается удалённый сервер на предмет обновлённой версии
                                  2. Во временную папку (её размещение указывается в конфиге) качается tar (gz или bz2) архив
                                  3. С помощью tar разжимается в ту же временную папку (боян, но подчеркну что недоступную извне)
                                  4. Если скачивание/разжатие прошло удачно, то новые файлы передвигаются вместо старых.

                                  (Такое делать надо замечу в продуктах где в дистрибе нет какого нибудь config.php как в том же рнрВВ2 а где есть config.sample который в последствии нужно скопировать/переименовать как например в рнрMyAdmin чтоб при обновлении не затереть конфиг на рабочем сервере)

                                  Замечу, можно такое делать и на РНР, причём не используя sh/csh/bash/итд (хотя я не встречал, ибо будут нужны танцы с бубном по поводу chmod который надо будет менять, по поводу set_time_limit который также меняется далеко не везде, опять же будет множество танцев с выяснением способа скачки, сокеты/курл/фопен/итд) короче на РНР это делать очень неудобно.
                                    0
                                    Забыл упомянуть что старые в свою очередь файлы, перемещаются в тоже время во временную папку, для возможности «Rollback»
                                      +2
                                      Увы, гораздо чаще возникает необходимость обновлять установленные скрипты в условиях хостинга с жесткими ограничениями на средства (без ssh, кронов и прочих удобств) и ресурсы (время выполнения, память, доступ к сети — все это приходится пробовать обойти автоматом, без привлечения пользователя), странными настройками окружения (права на файлы, распакованные через контрольную, самим скриптом и загруженные через FTP не совпадают, что приводит к различным проблемам с записью/модификацией файлов) и выполняется само обновление пользователями, для которых chmod созвучно ругательству.
                                      Поэтому и пишется велосипед на PHP (гарантировать других методов в общем случае нельзя), который и обновляет скрипты (новые/модифицированные файлы продолжают принадлежать пользователю, из под которого запущен сервер), полная версия которого выполняет описанную вами процедуру, сдобренную костылями.
                                      Так что скрипт обновления рассчитан на «исключительный случай», его полная версия может быть интересна как раз набором костылей, а не собственно идеей обновления.
                                        0
                                        Да, гораздо чаще всё идёт через (_|_) и на шаред-хостингах практически гарантировано что будет именно так (их настраивают чаще всего для безопасности и не всегда грамотно)

                                        Однако велосипеды на РНР чаще всего не спасают, а только усугубляют проблему.

                                        Но если вы и правы по поводу интересности костылей, то пусть он бы их и описал. (Увы я вижу лишь идею, причём далеко не самую удачную.)
                                          0
                                          Костыли в этом скрипте не значительны (некоторые из комментариев к коду вида //KNOWHOW описывают тот или иной костыль) и оно и вправду сейчас выглядит лишь идеей, их будет больше когда скрипт будет учиться докачивать обновления, распаковывать архив кусками, то есть уметь возобновлять свою работу, если его таки прихлопнут.
                                    0
                                    Если я правильно понимаю, то это создатели Shop-Script. Вот в этом скрипте инет магазина тоже есть авто обновление одной кнопкой. Каждый раз себе говорю «Не нажимай!», а потом сижу и плачу над километровыми высерами PHP и правлю ручками код.
                                    +10
                                    Где троллейбус?
                                    0
                                    Ещё с задачей обновления очень хорошо справляется rsync.
                                      +4
                                      По шапке вам, господа, за такой хромой велосипед. Давать коду права на перезапись самого себя очень опасно.
                                        0
                                        Вот вам идея — хит.
                                        Сделайте обновление сразу из svn! )
                                          0
                                          На своих серверах так и делаем. Но в общем случае на каком-нибудь виртуальном шаред-хостинге это не будет работать.
                                            0
                                            зато будет работать шелл-скриптинг + кронтаб. Этого в большинстве случаев достаточно.
                                          +2
                                          Обновить вызываемый скрипт — не проблема. Попробуйте подобным образом обновить демон, да так, чтобы он после этой операции продолжил работать (обновлённым). И, да, вам бы хотя бы подписи туда прикрутить что ли.
                                            0
                                            Читал статью в надежде увидеть способ обновления скриптов которые работают непрерывно (демоны, например).
                                              +1
                                              Недавно сделал такое на Perl. Могу описать.
                                                +1
                                                Было бы интересно почитать.
                                            • UFO just landed and posted this here
                                                +1
                                                Делать обновление сайта на PHP, это как из пушки по воробьям.

                                                Для этого подойдет шелл-скриптинг банальный, зачем нагружать веб-сервер (как я понял из статьи это будет запускаться из браузера)?

                                                Код вообще не выдерживает никакой критики:

                                                1) форматирование
                                                2) вывод ексепшена на экран
                                                3) каша в файле из класса и логики

                                                В первую очередь убивал бы программиста за такое форматирование. Ну и вообще по коду много странных решений.
                                                  0
                                                  На очень многих шаред-хостингах о шелл-скриптинге приходится только мечтать…
                                                  0
                                                  на этой неделе писал подобную фичу, только наоборот:
                                                  центральный сервер указывает куче скриптов самообновиться на разных хостах… при этом посылает бейз64 зашифрованный контент, подписанный контрольной суммой…
                                                  на серваках, ясно понятно, никакие фопены/курлы/сокеты не нужны… да и выглядит проще в разы :)
                                                    0
                                                    Года полтора назад тоже разрабатывал систему обновлений. Сначала скрипт засылал на центральный сервер список версий модулей ПО, потом получал список доступных обновлений. Обновление представляло собой архив с обязательным install.pl. Архив скачивался скриптом, разжимался, а выполнение install.pl форкалось.
                                                      0
                                                      Давно пора, у нас уже как года 3 такая штука стоит + утилита из вод Windows для обновления, когда сервер не разрешает ftp соединение с сервером обновлений, у Битрикса Сайтапдет есть тоже давно (я правда не вдавался в его детали). Прогресс на лицо, молодцы. Ну под эту дудочку еще удаленный web-install осталось сделать и будет полный зачет.
                                                        0
                                                        Я видел кучу шаред хостингов, в которых к php-файлам скриптов нет доступа не только на запись, но и на чтение из других скриптов (у меня была хитрая система инклюдинга «только то что нужно», но для нее надо было считывать пару строк в скриптах). Так вот на таких хостингах система не работала принципиально. Причем права на каталоги и файлы выставлял максимальные — бесполезно, блокирована сама возможность работы с содержимым скрипта из другого скрипта.

                                                        Думаю, такое автообновление в таих же условиях тоже не будет работать, т.к. не сможет переписать файл скрипта командой из скрипта.
                                                          0
                                                          Или я плохо искал, или обещанного продолжения так и нету(

                                                          Only users with full accounts can post comments. Log in, please.