Как стать автором
Обновить

Делаем резервное копирование сайта с помощью git и Makefile

Время на прочтение7 мин
Количество просмотров7K

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


В статье рассказывается, как сделать статические версии веб-страниц для их выдачи сервером и как поместить их в репозиторий для контроля версий и резервного копирования. При этом статические и медиафайлы могут храниться отдельно и архивироваться другими средствами (статика обычно помещается в репозиторий для программного кода сайта). Метод работает также для страниц с Unicode-именами (например, для кириллических доменов). В конце приведён работающий Makefile.


Автор пользуется стеком django/uwsgi/nginx, виртуальным выделенным сервером под управлением GNU/Linux, но содержание статьи почти не зависит от конкретных технологий.


Загружаем страницы


Сохранять страницы сайта мы будем с помощью стандартной программы wget. Мы будем сохранять каждый сайт в отдельную директорию (которая, возможно, не связана с доменным именем сайта).


Внутри каждой директории страницы будут сохраняться рекурсивно с помощью ключа wget -r (предполагается, что ко всем страницам можно перейти по ссылкам с главной). По умолчанию рекурсивное копирование идёт вплоть до 5-го уровня, но это можно изменить с помощью ключа -l.


Если мы храним медиа и статические файлы отдельно от текстовых страниц, то соответствующие директории игнорируются с помощью ключа -X.


Полная команда выглядит так:


mkdir primer
cd primer
wget -r -nH -X медиа,static --restrict-file-names=nocontrol пример.рф

-nH означает --no-host-directories. По умолчанию wget -r example.com поместит всё в директорию example.com/, а эта опция отменяет создание директории с именем хоста.


Опция --restrict-file-names указывает на экранирование символов в URL при создании локальных файлов. Значение nocontrol означает отключение экранирования и очень важно для сохранения страниц с кириллическими ссылками. Без неё страницы сохраняются в файлы с немного изменёнными именами, и не совсем понятно, как их выдавать серверу. К сожалению для пользователей Windows, --restrict-file-names=nocontrol у них не будет работать, это известная проблема.


Добавляем в git


Новый репозиторий создаётся с помощью команды git init. По умолчанию он создаётся внутри текущей директории в папке .git, однако мы хотим, чтобы серверу были доступны только файлы, которые соответствуют названиям открытых страниц сайта. Поэтому полная команда, создающая чистый (bare) репозиторий в папке ../.git-primer, выглядит так:


git init --bare ../.git-primer

Чтобы далее пользоваться таким нестандартным репозиторием, нужно передавать git опции git-dir и work-tree:


git --git-dir=../.git-primer --work-tree=. add .

Пишем Makefile


Начнём с объявления наших проектов:


SITES := example primer
all : $(SITES)
.PHONY : $(SITES)

В переменной SITES содержатся названия наших проектов. Целью по умолчанию является первая цель all, то есть для выполнения всех действий достаточно будет набрать одну команду make. Все цели в SITES являются фиктивными (PHONY): рецепт для каждой из них будет выполняться независимо от существования директории и времени её изменения.
Базовое введение в make можно прочитать, например, здесь, а основным руководством является info make (оригинал, перевод).


Правило для каждого из проектов выглядит так:


$(SITES) : 
    if [[ -d .git-$@ ]]; \
    then \
        $(get-data); \
        $(mgit) add . && \
        if [[ -n "`$(mgit) status --porcelain`" ]]; then \
            $(mgit) commit -m "Update $@."; \
        fi \
    else \
        $(init-git); \
    fi

Данное правило по сути является одной командой shell.
$@ — автоматическая переменная, в которой содержится название текущей цели (например, primer).
Сначала мы проверяем, существует ли директория .git-primer. Если да, то переходим в директорию проекта, скачиваем страницы, добавляем их в git.
Если содержание страниц не изменилось, то git ничего не добавит, но в этом случае commit будет вызывать ошибку и остановку исполнения Makefile. Поэтому сначала мы вызываем git status с опцией --porcelain, которая предназначена для использования в скриптах. Если длина строки вывода git status --porcelain не нулевая, то мы можем делать commit (отсюда).


get-data, mgit и init-git — это заготовленные (canned) рецепты в Makefile. Например, mgit — это вызов git c указанием директории с файлами репозитория и рабочего каталога:


define mgit =
    git --git-dir=../.git-$@ --work-tree=.
endef

Canned recipes создаются, когда одна последовательность команд может использоваться в нескольких рецептах. Они могут состоять из нескольких строк, каждая из которых автоматически выделяется табуляцией в рецептах (более точно, символом .RECIPEPREFIX). В нашем примере отступ сделан лишь для удобства чтения Makefile.
Во время исполнения рецептов каждая строка заготовленных последовательностей интерпретируется как отдельная строка рецепта, то есть, в частности, в них можно использовать автоматические переменные для данной цели.


Полный Makefile выглядит так:


SITES          := primer example
SERVERHOST     := example
# пример.рф в punicode
SERVERHOSTNAME := xn--e1afmkfd.xn--p1ai
SERVERPATH     := ~/archive

all : $(SITES)

.PHONY : $(SITES)

# target-specific variables
primer : DOMAIN := пример.рф
primer : EXCLUDEDIRS := медиа,static
example : DOMAIN := example.com

ifeq ($(SERVERHOSTNAME),$(shell hostname))
# Server
define mgit =
    git --git-dir=../.git-$@ --work-tree=.
endef

define init-git =
    mkdir -p $@ && \
    $(get-data) && \
    git init --bare ../.git-$@ && \
    $(mgit) add . && \
    $(mgit) commit -m "Initial commit of $@."
endef

define get-data = 
    cd $@ && \
    wget -r -nH -X $(EXCLUDEDIRS) --restrict-file-names=nocontrol $(DOMAIN)
endef

else
# Workstation
define init-git =
    git clone $(SERVERHOST):$(SERVERPATH)/.git-$@ $@
endef
endif

$(SITES) : 
ifeq ($(SERVERHOSTNAME),$(shell hostname))
# Server
    if [[ -d .git-$@ ]]; \
    then \
        $(get-data); \
        $(mgit) add . && \
        if [[ -n "`$(mgit) status --porcelain`" ]]; then \
            $(mgit) commit -m "Update $@."; \
        fi \
    else \
        $(init-git); \
    fi
else
# Workstation
    if [[ -d $@/.git ]]; \
    then \
        cd $@ && git pull; \
    else \
        $(init-git); \
    fi
endif

В четвёртом абзаце идут целе-зависимые (target-specific) переменные: для каждой цели можно установить собственное значение этой переменной. Эти значения передаются также в зависимости (prerequisites) каждой из целей и в используемые заготовленные рецепты, то есть мы можем быть уверены, что рецепт для каждого сайта будет выполнен с правильным именем сайта и его директории.
Для каждого проекта мы можем передать свои не архивируемые директории через переменную EXCLUDEDIRS или оставить её пустой. Аналогично можно менять имя сервера для архивирования с рабочего компьютера (SERVERHOST) и путь на сервере к директории с архивом сайтов (SERVERPATH). Для простоты, в этом примере все сайты находятся на одном сервере и архивируются в одной директории.
Поскольку каждая строка рецепта (в том числе заготовленного) выполняется в отдельной оболочке shell, то чтобы переход в директорию оставался в силе для следующих команд, мы пользуемся оператором "и" && и экранированием конца строки \ .


Далее идёт условная конструкция Makefile: с помощью команды shell hostname мы проверяем, запускается ли make на сервере или на локальном компьютере. Строки, не удовлетворяющие текущей ветви условной директивы, полностью игнорируются Makefile.


Различие между локальным и серверным репозиториями

Локальный компьютер служит прежде всего для хранения данных, поэтому на него мы только копируем данные с сервера (git pull), а для удобства локальной работы с git (просмотра логов или версий файлов) мы пользуемся структурой репозитория по умолчанию (обычным репозиторием в папке .git).
В обоих случаях достаточно одной команды make. Для автоматического копирования можно использовать планировщик cron. Чтобы каждый раз не вводить пароль для доступа на сервер, генерируются ssh-ключи.


Для удобства работы на сервере можно из директории текущего сайта создать псевдоним (alias) git с заданной конфигурацией:


alias mgit="git --work-tree=. --git-dir=../.git-${PWD##*/}"

Переменная ${PWD##*/} содержит название текущей директории без пути к ней и входит в стандарт POSIX, то есть может использоваться во всех поддерживающих POSIX оболочках.


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


Сервер

После запуска make директория архивов выглядит так:


$ ls -a
.  ..  .git-example  .git-primer  Makefile  example primer
$ ls -a primer
.  ..  index.html  про-собак  про-котов
$ # предполагается, что с главной страницы пример.рф есть ссылки на пример.рф/про-собак и пример.рф/про-котов

Файл конфигурации nginx для пример.рф может выглядеть так:


server {
    server_name xn--e1afmkfd.xn--p1ai;
    charset     utf-8;

    location = / {
        root /home/user/archive/primer;
        try_files /index.html =404;
    }

    location / {
        root /home/user/archive/primer;
        default_type "text/html";
        try_files $uri =404;
    }

    location = /index.html { return 404; } 
}

Первый location соответствует главной странице сайта, пример.рф. Она сохраняется wget как файл index.html. Если он не находится, выдаётся ошибка 404.


Для всех остальных URI проверяются файлы в директории primer с названием URI. Если они не найдены, то выдаётся 404.


В конце, чтобы избежать дублирования контента, мы явно запрещаем доступ по ссылке пример.рф/index.html (404). Чуть более подробно об этой конфигурации написано здесь.


Заключение


Резервное копирование сайтов можно делать с помощью стандартных инструментов wget, git, make. Можно копировать все страницы сайта или исключать медиа- и ряд других файлов настолько точно, насколько это позволяет wget. Аналогично, с помощью .gitignore, можно контролировать, какие статические страницы будут добавляться в репозиторий для резервного копирования, а какие нет. Makefile позволяет гибко управлять различными конфигурациями для различных проектов. Полный пример Makefile для клиента и для сервера выше при этом содержит лишь около 60 строк.


Предполагается, что изменение и добавление контента сайта происходит через стандартные механизмы, то есть для этого запускается CMS или CMF. Если это происходит редко, то после работы их можно отключать, освобождая ресурсы системы и выдавая сохранённые статические страницы. Пример более полной автоматизации, возможно, заслуживает отдельной статьи.


Предложенный метод подходит прежде всего для небольших проектов, которые обновляются редко, поэтому вопросы производительности и безопасности здесь почти не рассматривались. Поскольку мы указали wget не экранировать символы из URI, то в случае, если произвольные пользователи могут добавлять файлы на сайт, экранирование или запрет на их добавление должны происходить сразу.


Сохранение версий содержимого сайта также может осуществляться через базу данных при изменении его страниц. Но для этого нужна поддержка версий моделями CMF, а также больший контроль за дампом базы данных (полностью копировать его после любого редактирования страниц). В предложенном методе в случае небольшого изменения содержимого в репозиторий будет добавлено только это изменение, и не требуется использование полной копии БД. Кроме того, сгенерированные статические страницы могут напрямую использоваться сервером или просматриваться в браузере (изменение дизайна или другого программного кода сайта при этом тоже будет копироваться).


Альтернативные программы для резервного копирования перечислены здесь. Для хранения и синхронизации медиафайлов стоит обратить внимание на git-annex. Отделение .git репозитория от рабочего дерева также успешно используется для управления конфигурационными файлами пользователя (dotfiles). Сегодня существуют серверы, которые напрямую поддерживают работу с git-репозиториями.

Теги:
Хабы:
Всего голосов 15: ↑8 и ↓7+1
Комментарии7

Публикации