- АХТУНГ, ВАХТУНГ, БАХТУНГ, ДРЯХТУНГ, АЛЯРМ, ВНИМАНИЕ
- ЭТОТ СКРИПТ ПРЕДОСТАВЛЯЕТСЯ КАК ЕСТЬ, Я НЕ НЕСУ НИКАКОЙ
- ОТВЕТСТВЕННОСТИ ЯВНОЙ ИЛИ КОСВЕННОЙ, ВАМ ЛИБО КОМУ ТО ЕЩЁ
- ПО ПРИЧИНЕ, ПОРЧИ И/ИЛИ УНИЧТОЖЕНИИ ВАШИХ ДАННЫХ 
- ЕСЛИ ВЫ НЕСОГЛАСНЫ, Я ЗАПРЕЩАЮ ВАМ ЗАПУСКАТЬ ЭТОТ КОД
- ДЕЙСТВУЙТЕ НА СВОЙ СТРАХ И РИСК, ВАШИ ЛЮБЫЕ СОМНЕНИЯ 
- КАСАТЕЛЬНО СКРИПТА ДОЛЖНЫ ВЕСТИ К ОТКАЗУ ОТ ЕГО ИСПОЛЬЗОВАНИЯ
Всё, теперь если у вас что-то пойдёт не так, то я чист.
И меня никто не будет ругать, гы :) Просто будте внимательный с тем,
какой каталог указывается для сохранения бекапов. А то ведь так и затереть
чего важного можно случайно.
Вопщем решил поделится прост.  Пусть будет.
Простой скрипт ручного бекапа наиболее важных файлов, использует
rsync и zip Настройка производится внутри стрипта. Описание
настройки бекапа тоже распологается внутри скрипта.
Типичное использование это ручной запуск в конце дня или по
завершению какой либо важной работы или перед сном :)
Используйте для бекапа, отдельный диск где нет ничего кроме бекапа!
Просто иначе, в бекапе нет никакого смысла.
Лично я просто на панельку кнопочку сделал, и перед отплытием в люльку тыкаю кнопочку.
Комп я не выключаю обычно, а если выключаю до явно дожидаюсь окончания бекапа, не проверял что
будет если увести в сон во время бекапа, так что, лучше дождаться окончания.
Зависимости
mkdir zip rsync notify-send lua
Использование
Настроить скрипт, после чего просто его запускать. Так как вам удобно.
- Обязательные параметры
- sourceкаталог или файл источник
- destкаталог приёмник
 
- Дополнительные параметры
- secureприменить пароль (требуется- zip=true)
- zipсжать в архив
- mirrorсделать точную копию и обновить им текущий каталог бекапа( или архив если- zip=true)
- versionsсделать точную копию и пометить датой в отдельном каталоге (или архиве если- zip=true)
 
Пример поля конфигурации:
- {secure=true, zip=true, versions=true, source='~/.ssh/', dest='ssh'};
Значит что нужно сделать рекурсивный бекап каталога с ключами ssh и расположить
бекап в каталоге с именем ssh. secure=true означает что данные будут запаролены, а
zip что будут сжаты, versions=true означает что каждый новый бекап будет
создавать новую копию ~/.ssh помеченную датой. Описание того какие есть ключи
и как они работают друг с другом есть внутри скрипта, вместе с примерами уже
готовой настройки.
Ключи можно опускать (не указывать) обязательными являются только source и dest
Не указанные ключи считаются заданными как false так что вот такая запись валидна
- {source='~/Изображения/', dest='картинки'}
И означает сделать копию каталога ~/Изображения в каталоге картинки внутри общего
каталога бекапа backup_path=... при этом если это второй или последующий бекап и в
каталоге ~/Изображения/ была удалена картинка, она не будет удалена в каталоге
картинки это сделано специально, дабы максимизировать сохраняемые данные.
Если нужна точная копия с удалением того чего уже нет в каталоге источнике то нужно
дополнительно явно указать опцию mirror=true это же правило работает
если включить флаг zip=true будет всё тоже самое, но уже в виде архива.
На заметку
В каталоге относительно котрого исполняется скрипт будет лог бекапа, на рабочий стол будут приходить
уведомление о текущем статусе выполнения, в случае ошибки будет показaн код возврата
В лог попадает информация о том какая именно команда потерпела неудачу, пароль присутцвующий в команде
заменяется на звёздочки. В зависимости от типа настроенного бекапа(первый бекап будет всегда долгим),
а вот последующие при отсуцтвии изменений будут быстрыми исключение это mirror и versions оба
создают точную копию с нуля, а не пытаются обновить текущее содержимое бекапа. В случае если любая
программа вернёт код возврата отличный от 0 выполнение всего бекапа прерывается, и повторно он не
запустится пока не будет удалён lock файл, это сделано специально, чтобы выяснить причину, исправить её
и в ручном режиме удалить lock файл название котрого есть в срипте, в логах и уведомлении на рабочий стол.:
Если вы бекапите например ~./vimrc в dest каталог myvimrc то он будет там лежать как есть, с точкой.
Так что так же чтобы его увидеть в каталоге бекапа нужно нажимать ctrl+h. Ну, я так. На всякий случай.
Удалять из бекапов лишнее, и вообще контролировать влезет ли всё, это ваша забота =)
Нет опции отключения уведомлений. Предпочитаю явно знать что всё прошло гладко.
Логи бекапа тоже руками удалять.
Дадада, пароль на zip для ssh и gpg ключей хахахаха, но хоть что-то.
Сам внешний накопитель держите шифрованным если туда важное сохраняется.
Сам сриптик. Взял его как есть.
#!/usr/env lua
local backup_rule =
{
    --каталог куда будут размещаться все бекапы
    backup_path = '/media/$USER/STORAGE_SSD/backup';
    --пароль если используется secure=true, лучше использовать файл с паролем
    backup_pass = nil;
    --пароль для важныйх файлов, можно записать в файл дабы не светить
    backup_pass_file = "/home/$USER/Документы/backup_pass";
    ---------------------------------------------------------------------------
    backup_list =
    {
    -- backup critical --
    {secure=true, zip=true, versions=true, source='~/.ssh/',     dest='ssh'    };
    {secure=true, zip=true, versions=true, source='~/.gnupg/',   dest='gpg'    };
    {secure=true, zip=true, mirror=true,   source='~/.config/',  dest='cfg'    };
    {secure=true, zip=true, mirror=true,   source='~/.mozilla/', dest='firefox'};
    {secure=true, zip=true, mirror=true,   source='~/Документы/',dest='doc'    };
    -- diffrerent media --
    {secure=false,zip=true, mirror=true, source='~/Книги/', dest='book'};
    {secure=false,zip=true, mirror=true, source='~/Музыка/',dest='music'};
    -- backup multimedia --
    {secure=false, zip=false, versions=false, source='~/FAMILY_ARCHIVE/', dest='family'};
    -- backup dev stuff  --
    {secure=false, zip=false, versions=true, source='~/.gitconfig',dest='gitconfig'};
    {secure=false, zip=false, versions=true, source='~/.vimrc',    dest='vimrc'};
    {secure=false, zip=false, versions=true, source='~/.bashrc',   dest='bashrc'};
    {secure=false, zip=true,  versions=true, source='~/.vim/',     dest='vim'};
    -- backups source code --
    {secure=false, zip=true, mirror=true, source='/mnt/STORAGE/', dest='code'};
    };
};
-------------------------------------------------------------------------------
-- :secure=true   - шифровать ли сжатый бекап с паролем.
--                  создать архив и запаролить, zip должен быть zip=true
--                  явное указание zip=false или отсуцтвие ключа создаст ошибку
-- ----------------------------------------------------------------------------
-- :secure=false  - тоже самое поведение что и при zip=false
-- ----------------------------------------------------------------------------
-- :zip=true      - создать архив и добавить в него файлы если их нет в архиве
--                  если version=false то обновлять архив, добавляя новые
--                  файлы и обновля существующие, не удаляет отсуцтвующие
-------------------------------------------------------------------------------
-- :zip=false     - создавать бекап в каталоге dest если versions=false
--                  то клонировать source в dest, при повторном бекапе добавлять
--                  новые файлы и обновлять текущие, не удалять отсуцтвующие
--                  если versions=true создавать отдельный бекап в новом
--                  каталоге с датой бекапа, полностью отельный бекап.
-------------------------------------------------------------------------------
-- :versions=true - если zip=false то создать новый каталог с датой бекапа
--                  если zip=true  то создать новый архив с датой бекапа
-------------------------------------------------------------------------------
-- :mirror=true   - если zip=true то создаётся точная сжатая копия источника
--                  если при этом versions=false то текущий архив удаляет из
--                  себя файлы которых больше нет в источнике
--                  если zip=false то вместо архива создаётся точная копия
--                  источника с удалением файлов из каталога бекапа если их
--                  нет в источнике, тоже самое зеркалирование/клонирование
-------------------------------------------------------------------------------
-- :source        - файл или каталог который нужно бекапить, каталоги
--                  обрабатываются рекурсивно бекапя всё что есть
-------------------------------------------------------------------------------
-- :dest          - каталог внутри носителя для бекапа, оно же префикс
--                  добавляющийся к каталогам и/или архивам при versions=true
--                  отражает то бекап чего делается ОБЯЗАН БЫТЬ УНИКАЛЬНЫМ!!!!
-------------------------------------------------------------------------------
--        Если какого либо ключа нет то он считается равным false
-------------------------------------------------------------------------------
-- В случае если zip mkdir или rsync завершаться с кодом отличным от нуля
-- бекап тут же прекращается с уведомлением на рабочий стол и в лог ошибок
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
---- исполняет все команды, в случае ошибки прерывает всю работу
-------------------------------------------------------------------------------
local function execute(rule,command,exit_if_fail)
    local success, meta, code = os.execute(command..' >> backup-info.log');
    if (not success) then
       -- hide pass from output
       -- info log and messages
       if rule.backup_pass then
          command = command:gsub(rule.backup_pass,"********");
       end
       local msg = string.format('BACKUP ERROR %s %s %s',command,meta,code);
       io.stderr:write(msg..'\n');
       io.open('backup-error.log','a+'):write(msg);
       execute({},'notify-send  "'..msg..'" -u critical',false);
       if (exit_if_fail == true) then
          os.exit(127);
       end
       return success;
    end
    return success;
end
-------------------------------------------------------------------------------
--- информирует об ошибке и прерывает всю работу
-------------------------------------------------------------------------------
local function backup_fail(msg)
      io.stderr:write(msg..'\n');
      io.open('backup-error.log','a+'):write('\n'..msg..'\n');
      execute({},'notify-send -u critical "'..msg..'"',false);
      os.exit(127);
end
-------------------------------------------------------------------------------
--- просто информирует
-------------------------------------------------------------------------------
local function backup_info(msg)
      io.stdout:write(msg..'\n');
      local log = io.open('backup-info.log','a+');
      log:write('\n'..msg..'\n');
      log:close();
      execute({},'notify-send -u normal "'..msg..'"',false);
end
-------------------------------------------------------------------------------
--- проверяет используется ли в конфигурации пароль
-------------------------------------------------------------------------------
function check_need_pass(rule)
    for name,val in pairs(rule.backup_list) do
        if val.secure and val.secure == true then
           return true;
        end
    end
    return false;
end
-------------------------------------------------------------------------------
--- делает бекап K.O. :)
-------------------------------------------------------------------------------
local function make_backup(rule)
    if (rule.backup_path) then
       -- replaice $USER in path if we have ite
       rule.backup_path = rule.backup_path:gsub('$USER',os.getenv('USER'));
    else
       backup_fail('BACKUP ERROR backup_path not set, no place to save backup',true);
    end
    -- we use password?
    if (check_need_pass(rule) == true) then
        -- check pass contained in configuration
        if not rule.backup_pass and not rule.backup_pass_file then
           backup_fail('BACKUP ERROR you use secure option but backup_pass or backup_pass_file no set!',true);
        end
        -- if pass_file set read the password
        -- and override/write rule.backup_pass
        if (rule.backup_pass_file) then
           -- replaice $USER in path if we have ite
           rule.backup_pass_file = rule.backup_pass_file:gsub('$USER',os.getenv('USER'));
           local pass_file = io.open(rule.backup_pass_file);
           if (pass_file) then
              if rule.backup_pass then
                 backup_info('BACKUP WARNING rewrite backup_pass from backup_pass_file');
              end
              rule.backup_pass = pass_file:read('*l');
           else
              backup_fail('BACKUP ERROR '..rule.backup_pass_file..' no readable!',true);
           end
           pass_file:close();
        end
        -- maybe pass use only numbers force to string
        rule.backup_pass = tostring(rule.backup_pass);
        -- check small pass, no need but jut be
        if not rule.backup_pass or #rule.backup_pass <= 4 then -- kek cheburek 
           backup_info('BACKUP WARNING you backup_pass small, less or equal 4 symbols');
        end
    end
    -- info about starting
    backup_info("BACKUP START "..os.date());
    --first check, we have storage and access?
    execute(rule,'mkdir -p '..rule.backup_path, true);
    -- lockfile + uuid for exclude collisions
    local lockfile = '.lock-938448fc-1742-43e9-bff8-e2acf6d29710';
    -- check/create lock file
    -- if backup failed you self
    -- delete lock file your hands
    if not io.open(rule.backup_path..'/'..lockfile,'r') then
       io.open(rule.backup_path..'/'..lockfile,'w'):write('lock');
    else
       backup_fail("OTHER BACKUP PROCESS IS WORKING, SKIPPING THIS PROCESS\n"..
                   "IF YOU SURE DELETE LOCK FILE "..rule.backup_path..'/'..lockfile..'\n',true);
       ----------------------------------------------------------------------
       -- uncomment if after backup error no need locked backup for fix error
       ----------------------------------------------------------------------
       -- os.remove(rule.backup_path..'/'..lockfile);
    end
    for id,item in pairs(rule.backup_list) do
        local date = os.date("%Y-%m-%d_%X");
        local path = rule.backup_path;
        local pass = rule.backup_pass;
        local dest = item.dest;
        local source = item.source;
        if (not source) then
           backup_fail('BACKUP ERROR source path not set, what files backup?',true)
        end
        if (not dest) then
           backup_fail('BACKUP ERROR dest path not set, wheare dir name to save?',true)
        end
        execute(rule,'mkdir -p '..path..'/'..dest,true);
        backup_info('BACKUP: '..source);
        ------------------- secure: versionize backup data ---------------------
        ------------------------------------------------------------------------
        if (item.secure and item.zip and item.versions) then
            local target = string.format('%s/%s/%s-%s.zip',path,dest,dest,date);
            execute(rule,'zip -r9P '..pass..' '..target..' '..source,true);
            goto next_item;
        end
        ---------------- secure: update and append backup data -----------------
        ------------------------------------------------------------------------
        if (item.secure and item.zip and not item.versions) then
            local target = string.format('%s/%s/%s.zip',path,dest,dest);
            local opts = item.mirror and ' -rFS9P ' or ' -ru9P ';
            execute(rule,'zip '..opts..' '..pass..' '..target..' '..source,true);
            goto next_item;
        end
        ----------- no secure: zipped update and append backup data ------------
        ------------------------------------------------------------------------
        if (not item.secure and item.zip and not item.versions) then
           local target = string.format('%s/%s/%s.zip',path,dest,dest);
           local opts = item.mirror and ' -rFS9 ' or ' -ru9 ';
           execute(rule,'zip '..opts..' '..target..' '..source,true);
           goto next_item;
        end
        ----- no secure: zipped, versionize update and append backup data ------
        ------------------------------------------------------------------------
        if (not item.secure and item.zip and item.versions ) then
           local target = string.format('%s/%s/%s-%s.zip',path,dest,dest,date);
           execute(rule,'zip -r9 '..target..' '..source,true);
           goto next_item;
        end
        --------- no secure: versionize update and append, no delete -----------
        ------------------------------------------------------------------------
        if (not item.secure and not item.zip and item.versions ) then
           local target = string.format('%s/%s/%s-%s/',path,dest,dest,date);
           execute(rule,'mkdir -p '..target);
           execute(rule,'rsync -arv '..source..' '..target,true);
           goto next_item;
        end
        ----------------- no secure: update and append, no delete --------------
        ------------------------------------------------------------------------
        if (not item.secure and not item.zip and not item.versions) then
           local target = string.format('%s/%s/',path,dest);
           execute(rule,'mkdir -p '..target);
           local opts = item.mirror and ' -arv --delete ' or ' -arv ';
           execute(rule,'rsync '..opts..' '..source..' '..target,true);
           goto next_item;
        end
        ------------------------------------------------------------------------
        if (item.secure and not item.zip) then
            backup_fail('BACKUP ERROR UNSUPPORT SECURE WITCHOUT ZIP ARCHIVE :(');
        end
        ::next_item::
    end
    backup_info("BACKUP END "..os.date());
    os.remove(rule.backup_path..'/'..lockfile);
end
-- check depends
execute({},"zip --version",true);
execute({},"rsync --version",true);
--execute({},"mkdir --version",true);
execute({},"notify-send --version",true);
-- start backup now
make_backup(backup_rule)
Всё. Досвиданья.






