btrfs , linux , OYB软件 , 贝壳

使用btrfs-sync轻松同步BTRFS快照

补充最后的BTRFS工具 btrfs -snp (这允许我们安排快照),我想共享一个新工具来本地或远程同步它们,以实现有效的数据冗余。

btrfs -snp 我们可以在不同的BTRFS系统中复制BTRFS快照,并以比使用传统方式高效得多的方式拥有版本化子卷的第二个副本 同步 .

产品特点

  • 通过SSH进行本地或远程同步
  • 简单语法
  • 进度指示
  • 支持 z 要么 pbzip2 压缩以节省带宽
  • 保留政策
  • 自动增量同步
  • 克朗 友好

用法

语法类似于 scp

 用法 :
   btrfs -sync [options] <src> [<src>...] [[user@]host:]<dir>

  -k|--keep NUM     keep only last <NUM> sync'ed snapshots
  -d|--delete       delete snapshots in <dst> that don't exist in <src>
  -z|--xz           use  z      compression. Saves bandwidth, but uses one CPU
  -Z|--pbzip2       use pbzip2 compression. Saves bandwidth, but uses all CPUs
  -q|--quiet        don't display progress
  -v|--verbose      display more information
  -h|--help         show usage

<src> can either be a single snapshot,  要么  a folder containing snapshots
<user> requires privileged permissions at <host> for the 'btrfs' command

例子

手册

将家庭快照同步到USB驱动器

#  btrfs -sync /home/user/.snapshots /media/USBdrive/home-snapshots

将家庭快照同步到另一台计算机上的USB驱动器

#  btrfs -sync /home/user/.snapshots user@server:/media/USBdrive/home-snapshots

将家庭的一个快照同步到另一台计算机上的USB驱动器

#  btrfs -sync /home/user/.snapshots/monthly_2018-02-08_200102 user@server:/media/USBdrive/home-snapshots

仅将房屋的每月快照同步到另一台计算机上的USB驱动器

#  btrfs -sync /home/user/.snapshots/monthly_* user@server:/media/USBdrive/home-snapshots

利用 –verboseÂ获得更多详细信息

#  btrfs -sync --verbose --delete /home/user/.snapshots user@server:/media/USBdrive/home-snapshots
* Skip existing '/home/user/.snapshots/monthly_2018-01-09_200102'
* Skip existing '/home/user/.snapshots/monthly_2018-02-08_200102'
* Skip existing '/home/user/.snapshots/weekly_2018-02-09_140102'
* Skip existing '/home/user/.snapshots/weekly_2018-02-16_150102'
* Skip existing '/home/user/.snapshots/weekly_2018-02-23_150102'
* Skip existing '/home/user/.snapshots/weekly_2018-03-02_180102'
* Skip existing '/home/user/.snapshots/daily_2018-03-03_000101'
* Skip existing '/home/user/.snapshots/daily_2018-03-04_080101'
* Skip existing '/home/user/.snapshots/daily_2018-03-05_100102'
* Skip existing '/home/user/.snapshots/daily_2018-03-06_100102'
* Skip existing '/home/user/.snapshots/daily_2018-03-07_110102'
* Synchronizing '/home/user/.snapshots/hourly_2018-03-08_090101' using seed '.snapshots/hourly_2018-03-07_090101'...
time elapsed [0:00:24] | rate [11.1MiB/s] | total size [ 132MiB]
* Synchronizing '/home/user/.snapshots/hourly_2018-03-08_100101' using seed '.snapshots/hourly_2018-03-09_090101'...
time elapsed [0:01:05] | rate [11.1MiB/s] | total size [ 275MiB]
* Deleting non existent snapshots...
Delete subvolume (no-commit): '/media/USBdrive/home-snapshots/hourly_2018-03-08_090101'
Delete subvolume (no-commit): '/media/USBdrive/home-snapshots/hourly_2018-03-08_100101'
克朗

每天通过Internet同步,仅保留最后50个

cat > /etc/cron.daily/btrfs-sync <<EOF
#!/bin/bash
/usr/local/sbin/btrfs-sync --quiet --keep 50 --xz /home user@host:/path/to/snaps
EOF
chmod +x /etc/cron.daily/btrfs-sync

局域网中的每日同步,镜像快照目录

cat > /etc/cron.daily/btrfs-sync <<EOF
#!/bin/bash
/usr/local/sbin/btrfs-sync --quiet --delete /home user@host:/path/to/snaps
EOF
chmod +x /etc/cron.daily/btrfs-sync

安装

得到 脚本 并使其可执行。您可以分两行执行此操作,但最好先对其进行检查。不要盲目相信任何人。

sudo wget //raw.githubusercontent.com/nachoparker/btrfs-sync/master/btrfs-sync -O /usr/local/sbin/btrfs-sync
sudo chmod +x /usr/local/sbin/btrfs-sync

建议设置指定的用户以接收具有以下内容的快照: 苏度 进入  btrfs Â命令。

  • 创建一个 btrfs 两端的用户
$ sudo adduser  btrfs 
  • 在发送机中创建公钥
$ sudo -u  btrfs  ssh-keygen
  • 授予无密码访问权限 btrfs 远程计算机上的用户。
$ sudo -u  btrfs  ssh-copy-id  btrfs @<ip>
  • 授予权限 btrfs 用户使用 btrfs 在两端。建立档案
# visudo /etc/sudoers.d/90_btrfs-sync

与内容

  btrfs   ALL=(root:nobody) NOPASSWD:NOEXEC: /bin/btrfs

如果您想从 克朗 ,您可能必须首先安装它,因为某些发行版已经完全用systemd计时器代替了它。

在Arch Linux中,我就是这种情况。就我而言,我安装了 克罗妮 .

克罗妮 默认情况下会将输出记录到系统日志中,但是如果您想要旧样式的cron邮件,则可以设置电子邮件系统。

另外,请注意,您可以使用 慢性的 如果您只希望仅在出现问题时才进行日志记录。

与之比较 同步

这两种方法的主要区别在于BTRFS在 水平,而 同步 文件 水平。

因为 同步 适用于文件,它不会检测到诸如重命名或移动文件之类的内容,因此只能再次发送。另外,它需要分析目标位置上的现有文件,以查看它们是否已更新。

为了实现这一点,它可以分析修改日期和大小(相对较快),也可以比较两端文件块的校验和(健壮但较慢)。无论如何,当您将整个分区与成千上万个文件同步时,将产生大量的处理开销。

这种方法的优点是,您可以排除某些文件或文件夹,其中BTRFS按 全有或全无 时尚。

另一方面,BTRFS了解块,因为它是一个 COW文件系统,它已经知道快照和下一个快照之间已更改了哪些字节。如果我们重命名该文件,则仅更改了一些微小的元数据,并且BTRFS知道我们没有’只需再传输那几个字节,就无需再次传输整个文件。

当大文件在内部更改时(例如我们一直在使用的虚拟机的映像文件),也会发生同样的情况。

这就是为什么COW文件系统中的快照具有如此高的空间利用率的原因,这使我们能够创建大容量卷的即时安全副本,而在更改其中的文件时仅占用额外的空间。

显然,缺点是您两端都需要一个BTRFS文件系统,但是为什么我们要坚持现在拥有更现代,功能更强大的旧文件系统呢?

 

#!/bin/bash

#
# Simple  脚本  that synchronizes BTRFS snapshots locally  要么  through SSH.
#  产品特点  compression, retention policy and automatic incremental sync
#
#  用法 :
#   btrfs -sync [options] <src> [<src>...] [[user@]host:]<dir>
#
#  -k|--keep NUM     keep only last <NUM> sync'ed snapshots
#  -d|--delete       delete snapshots in <dst> that don't exist in <src>
#  -z|--xz           use  z      compression. Saves bandwidth, but uses one CPU
#  -Z|--pbzip2       use pbzip2 compression. Saves bandwidth, but uses all CPUs
#  -q|--quiet        don't display progress
#  -v|--verbose      display more information
#  -h|--help         show usage
#
# <src> can either be a single snapshot,  要么  a folder containing snapshots
# <user> requires privileged permissions at <host> for the 'btrfs' command
#
#  克朗  example: daily synchronization over the internet, keep only last 50
#
# cat > /etc/cron.daily/btrfs-sync <<EOF
# #!/bin/bash
# /usr/local/sbin/btrfs-sync -q -k50 -z /home user@host:/path/to/snaps
# EOF
# chmod +x /etc/cron.daily/btrfs-sync
#
# Copyleft 2018  通过  Ignacio Nunez Hernanz <nacho _a_t_ ownyourbits _d_o_t_ com>
# GPL licensed (see end of  文件 ) *  利用  at your own risk!
#
# More at //ownyourbits.com
#

# help
print_usage() {
  echo " 用法 : 
  $BIN [options] <src> [<src>...] [[user@]host:]<dir>

  -k|--keep NUM     keep only last <NUM> sync'ed snapshots
  -d|--delete       delete snapshots in <dst> that don't exist in <src>
  -z|--xz           use  z      compression. Saves bandwidth, but uses one CPU
  -Z|--pbzip2       use pbzip2 compression. Saves bandwidth, but uses all CPUs
  -q|--quiet        don't display progress
  -v|--verbose      display more information
  -h|--help         show usage

<src> can either be a single snapshot,  要么  a folder containing snapshots
<user> requires privileged permissions at <host> for the 'btrfs' command

Cron example: daily synchronization over the internet, keep only last 50

cat > /etc/cron.daily/btrfs-sync <<EOF
#!/bin/bash
/usr/local/sbin/btrfs-sync -q -k50 -z /home user@host:/path/to/snaps
EOF
chmod +x /etc/cron.daily/btrfs-sync
"
}

echov() { [[ "$VERBOSE" == 1 ]] && echo "$@" || true; }

#----------------------------------------------------------------------------------------------------------

# parse arguments
BIN="${0##*/}"
KEEP=0
ZIP=cat PIZ=cat
SILENT=">/dev/null"

OPTS=$( getopt -o hqzZk:dv -l quiet -l help -l  z  -l pbzip2 -l keep: -l delete -l  冗长的  -- "$@" 2>/dev/null )
[[ $? -ne 0 ]] && { echo "error parsing arguments"; exit 1; }
eval set -- "$OPTS"

while true; do
  case "$1" in
    -h|--help   ) print_usage; exit  0 ;;
    -q|--quiet  ) QUIET=1    ; shift 1 ;;
    -d|--delete ) DELETE=1   ; shift 1 ;;
    -k|--keep   ) KEEP=$2    ; shift 2 ;;
    -z|--xz     ) ZIP=xz     PIZ=(  z      -d ); shift 1 ;;
    -Z|--pbzip2 ) ZIP=pbzip2 PIZ=( pbzip2 -d ); shift 1 ;;
    -v|--verbose) SILENT=""  VERBOSE=1        ; shift 1 ;;
    --)                shift;  break   ;;
  esac
done

SRC=( "${@:1:$#-1}" )
DST="${@: -1}"

# detect remote dst argument
[[ "$DST" =~ : ]] && {
  NET="$( sed 's|:.*||' <<<"$DST" )"
  DST="$( sed 's|.*:||' <<<"$DST" )"
  SSH=( ssh -o ServerAliveInterval=5 -o ConnectTimeout=1 "$NET" )
}
[[ "$SSH" != "" ]] && DST_CMD=( ${SSH[@]} ) || DST_CMD=( eval )

#----------------------------------------------------------------------------------------------------------

# checks

## general checks
[[ $# -lt 2      ]]            && { print_usage                                ; exit 1; }
[[ ${EUID} -ne 0 ]]            && { echo "Must be run  如  root. Try 'sudo $BIN'"; exit 1; }
${DST_CMD[@]} true &>/dev/null || { echo "SSH访问错误 $NET"            ; exit 1; }

## src checks
while read entry; do SRCS+=( "$entry" ); done < <( 
  for s in "${SRC[@]}"; do
    src="$(cd "$s" &>/dev/null && pwd)" || { echo "$s not found"; exit 1; } #abspath
     btrfs  subvolume show "$src" &>/dev/null && echo "0|$src" || \
    for dir in $( ls -drt "$src"/* 2>/dev/null ); do
       btrfs  subvolume show "$dir" &>/dev/null || continue
      DATE="$(  btrfs  su sh "$dir" | grep "Creation time:" | awk '{ print $3, $4 }' )"
      SECS=$( date -d "$DATE" +"%s" )
      echo "$SECS|$dir"
    done
  done | sort -V | sed 's=.*|=='
)
[[ ${#SRCS[@]} -eq 0 ]] && { echo "no BTRFS subvolumes found"; exit 1; }

## check pbzip2
[[ "$ZIP" == "pbzip2" ]] && {
                    type pbzip2 &>/dev/null && \
    "${DST_CMD[@]}" type pbzip2 &>/dev/null || {
      echo "INFO: 'pbzip2' not installed on both ends, fallback to 'xz'"
      ZIP=xz PIZ=unxz 
  }
}

## use 'pv' command if available
PV=( pv -F"time elapsed [%t] | rate %r | total size [%b]" )
[[ "$QUIET" == "1" ]] && PV=( cat ) || type pv &>/dev/null || {
  echo "INFO: install the 'pv' package in  要么 der to get a progress indicator"
  PV=( cat )
}

#----------------------------------------------------------------------------------------------------------

# sync snapshots

## get dst snapshots ( DSTS, DST_UUIDS )
get_dst_snapshots() {
  local DST="$1"
  unset DSTS DST_UUIDS
  while read entry; do
    DST_UUIDS+=( "$( sed 's=|.*==' <<<"$entry" )" )
    DSTS+=(      "$( sed 's=.*|==' <<<"$entry" )" )
  done < <( 
    "${DST_CMD[@]}" "
      DSTS=( \$( ls -d \"$DST\"/* 2>/dev/null ) )
      for dst in \${DSTS[@]}; do
        UUID=\$( sudo  btrfs  su sh \"\$dst\" 2>/dev/null | grep 'Received UUID' | awk '{ print \$3 }' )
        [[ \"\$UUID\" == \"-\" ]] || [[ \"\$UUID\" == \"\" ]] && continue
        echo \"\$UUID|\$dst\"
      done" 
  )
}

## sync incrementally
sync_snapshot() {
  local SRC="$1"
  local ID LIST PATH_ DATE SECS SEED SEED_PATH SEED_ARG

  # detect existing
  SRC_UUID=$(  btrfs  su sh "$SRC" | grep "UUID:" | head -1 | awk '{ print $2 }' )
  for id in "${DST_UUIDS[@]}"; do
    [[ "$SRC_UUID" == "$id" ]] && { echov "* Skip existing '$SRC'"; return 0; }
  done

  # try to get most recent src snapshot that exists in dst to use  如  a seed
  LIST="$(  btrfs  subvolume list -su "$SRC" )"
  SEED=$( 
    for id in "${DST_UUIDS[@]}"; do
      ID=$(btrfs su sh -u "$id" "$SRC" 2>/dev/null|grep "UUID:"|head -1|awk '{print $2}')
      PATH_=$( awk "{ if ( \$14 == \"$ID\" ) print \$16       }" <<<"$LIST" )
      DATE=$(  awk "{ if ( \$14 == \"$ID\" ) print \$11, \$12 }" <<<"$LIST" )

      [[ "$ID" == "" ]] || [[ "$PATH_" == "$( basename "$SRC" )" ]] && continue

      SECS=$( date -d "$DATE" +"%s" )
      echo "$SECS|$PATH_"
    done | sort -V | tail -1 | cut -f2 -d'|'
  )

  # incremental sync argument
  [[ "$SEED" != "" ]] && {
    SEED_PATH="$( dirname "$SRC" )/$( basename $SEED )"
    [[ -d "$SEED_PATH" ]] && 
      SEED_ARG=( -p "$SEED_PATH" ) || \
      echo "INFO: couldn't find $SEED_PATH. Non-incremental mode"
  }

  # do it
  echo -n "* Synchronizing '$src'"
  [[ "$SEED_ARG" != "" ]] && echov -n " using seed '$SEED'"
  echo "..."

  {  btrfs  send -q ${SEED_ARG[@]} "$SRC" \
    | "$ZIP" \
    | "${PV[@]}" \
    | "${DST_CMD[@]}" "${PIZ[@]} | sudo  btrfs  receive \"$DST\" 2>&1 | grep -v '^At subvol '" \
    || exit 1; } | grep -v "^At snapshot "
  get_dst_snapshots "$DST" # sets DSTS DST_UUIDS
}

#----------------------------------------------------------------------------------------------------------

# sync all snapshots found in src
get_dst_snapshots "$DST" # sets DSTS DST_UUIDS
for src in "${SRCS[@]}"; do
  sync_snapshot "$src"
done

#----------------------------------------------------------------------------------------------------------

# retention policy
[[ "$KEEP" != 0 ]] && \
  [[ ${#DSTS[@]} -gt $KEEP ]] && \
  echov "* Pruning old snapshots..." && \
  for (( i=0; i < $(( ${#DSTS[@]} - KEEP )); i++ )); do
    PRUNE_LIST+=( "${DSTS[$i]}" )
  done && \
  ${DST_CMD[@]} sudo  btrfs  subvolume delete "${PRUNE_LIST[@]}" $SILENT

# delete flag
[[ "$DELETE" == 1 ]] && \
  for dst in "${DSTS[@]}"; do
    FOUND=0
    for src in "${SRCS[@]}"; do
      [[ "$( basename $src )" == "$( basename $dst )" ]] && { FOUND=1; break; }
    done
    [[ "$FOUND" == 0 ]] && DEL_LIST+=( "$dst" )
  done
[[ "$DEL_LIST" != "" ]] && \
  echov "* Deleting non existent snapshots..." && \
  ${DST_CMD[@]} sudo  btrfs  subvolume delete "${DEL_LIST[@]}" $SILENT

# License
#
# This  脚本  is free  软件 ; you can redistribute it and/or modify it
# under the terms of the GNU General Public License  如  published  通过 
# the Free Software Foundation; either version 2 of the License,  要么 
# (at your option) any later version.
#
# This  脚本  is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY  要么  FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this  脚本 ; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place, Suite 330,

作者: 纳乔帕克

谦虚地分享我认为有用的东西 [ 的github 码头工人 hub ]

12 评论 s

  1. (已删除降价)

    嘿,玉米片,
    感谢您提供的出色工具!

    不幸的是我无法使它工作

    在两台计算机上创建了btrfs用户
    获得与我执行的无密码SSH访问远程
    ssh-copy-id -i〜/ .ssh / id_rsa.pub btrfs @

    须藤-u btrfs ssh-copy-id btrfs @没有’t work for me.
    现在能够不用密码访问ssh btrfs @并在远程上授予btrfs完整的sudo权限,我尝试运行
    须藤btrfs-sync–详细/ media / backup_local btrfs @:/ media / backup_remote /
    但是得到
    SSH访问错误 btrfs @192.168.178.55。您是否具有无密码的登录设置,并具有/ media / backup_remote的足够权限?
    在远程上,文件夹/ media / backup_remote由btrfs拥有,并且权限设置为777

    我做错了什么?
    本地计算机:PRETTY_NAME =”Raspbian GNU / Linux 9(拉伸)”
    远程计算机:PRETTY_NAME =”Debian GNU / Linux 10(破坏者)”(通过softy安装的ncp)

    谢谢

  2. 为什么脚本要通过sudo运行?如果您已经在两台机器上都为btrfs用户赋予了无密码sudo权限,那将有什么需求?我认为设置btrfs用户的确切目的是您不要’不必以root身份使用ssh。如果脚本是通过sudo运行的,那么ssh命令当然也由root执行,而不是btrfs执行。因此必须添加根’s将目标计算机上的.ssh / authorized_keys的pubkey而不是btrfs’就像上面的ssh-copy-id命令一样。
    但是同样,如果整个脚本都是通过sudo运行的,那么没有必要向用户btrfs授予btrfs命令的sudo权限。这是什么主意?

  3. 这里的所有示例都是推送备份。
    因此,我想知道,您能否以拉模式在备份服务器上运行btrfs-sync?

  4. 通常,这仅适用于卷/快照而不适用于BTRFS文件系统吗?假设我只想将一个BTRFS格式的磁盘同步到另一个磁盘(没有子卷或快照),并且我想利用COW的优势,这样就不会再次复制大的重命名文件。此脚本不能这样做吗?任何BTRFS工具都可以做到吗?谢谢!

发表评论

您的电子邮件地址不会被公开。 必需的地方已做标记 *