Полезные ссылки
1. https://wiki.samba.org/index.php/Rotating_LVM_snapshots_for_shadow_copy - основная статья по настройке2
2. https://www.ucartz.com/clients/knowledgebase/1328/How-to-add-or-delete-a-samba-user-under-Linux.html#:~:text=To%20add%20a%20new%20user,to%20add%20the%20samba%20user. - добавление пользователя в samba
3. https://www.samba.org/samba/docs/current/man-html/vfs_shadow_copy2.8.html - официальное описание параметров модуля shadow_copy2
4. https://help.ubuntu.ru/wiki/samba_shadow_copy - есть полезная информация, но скрипт ротации на perl
5. https://habr.com/ru/articles/318388/ - статья на хабре, скрипт отличается от официального, используется rsync
6. https://wiki.gentoo.org/wiki/Samba_shadow_copies - хороший пример с конфигом, коротко и ясно
7. https://www.dmosk.ru/miniinstruktions.php?mini=samba-zfs-snapshots - пример настройки на zfs, понятное описание параметров конфига
8. https://serveradmin.ru/logirovanie-operatsiy-s-faylami-v-samba/ - настройка аудита, статья не актуальна, в комментариях ссылки на официальную инструкцию, поменялись значения поддерживаемых vfs, их корректные названия нужно смотреть в документации, соответствующей установленной версии samba
9. https://www.samba.org/samba/docs/4.13/man-html/vfs_full_audit.8.html - официальная инструкция для версии самба 4.13
Настройка Samba, описание параметров из статьи (7)
Открываем конфигурационный файл, чтобы настроить общую папку:
vi /etc/samba/smb.conf
Предполагается, что наш файловый сервер уже настроен и в нем есть общая папка Архив. Чтобы у нас была возможность просматривать ее снапшоты, добавляем:
[Архив]
...
vfs objects = shadow_copy2
shadow:basedir = /data
shadow:snapdir = .zfs/snapshot
shadow:format = GMT-%Y.%m.%d_%H.%M.%S
shadow:sort = desc
shadow:localtime = yes
* где:
vfs objects — дополнения, которые используются Samba в операциях ввода вывода VFS. Если мы используем несколько подсистем, то перечисляем их через запятую.
shadow:basedir — корневой каталог, относительно которого выполняется поиск снапшотов.
shadow:snapdir — каталог, в котором находятся снапшоты. Мы его задаем относительно shadow:basedir. Итого, в нашем примере будет /data/.zfs/snapshot.
shadow:format — формат созданного снапшота. Тут важно, чтобы он совпадал с форматом создания (который мы задали в скрипте).
shadow:sort — в каком порядке сортируем список. В нашем примере сверху будут наиболее свежие.
shadow:localtime — определяет используются ли имена моментальных снимков в формате UTC/GMT или по местному времени.
Перезапускаем самбу, чтобы применить настройки:
systemctl restart smbd
Install the script into /root/bin/smbsnap
Create a crontab like this:
0 12 * * 1-5 root /root/bin/smbsnap autosnap all 0
0 7 * * 2-5 root /root/bin/smbsnap autosnap all 1
0 7 * * 1 root /root/bin/smbsnap autosnap all 2
0 8 * * * root /root/bin/smbsnap clean all
3,33 * * * * root /root/bin/smbsnap autoresize all
Под каждую общую папку выделен отдельный LVM корень.
root@astra:/home/astra-admin# pvs
PV VG Fmt Attr PSize PFree
/dev/sda5 astra-vg lvm2 a-- <39,52g 36,00m
/dev/sdb vg_share lvm2 a-- <5,00g 1020,00m
root@astra:/home/astra-admin# vgs
VG #PV #LV #SN Attr VSize VFree
astra-vg 1 2 0 wz--n- <39,52g 36,00m
vg_share 1 1 0 wz--n- <5,00g 1020,00m
root@astra:/home/astra-admin# lvs
LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert
root astra-vg -wi-ao---- 38,53g
swap_1 astra-vg -wi-ao---- 976,00m
lv_share vg_share -wi-a----- 4,00g
!!! ВАЖНОЕ ЗАМЕЧАНИЕ. Чтобы было возможно создавать снапшоты LVM, благодаря которым и работает механизм теневого копирования в данном конкретном случае, необходимо, чтобы у VG было свободное место для создания снапшотов, т.е. LV не занимал весь доступный в VG объем свободного места. Также нужно учитывать, что может потребоваться увеличение размера снапшота, места в VG не должно быть слишком мало.
root@astra:/home/astra-admin# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 40G 0 disk
├─sda1 8:1 0 487M 0 part /boot
├─sda2 8:2 0 1K 0 part
└─sda5 8:5 0 39,5G 0 part
├─astra--vg-root 253:4 0 38,5G 0 lvm /
└─astra--vg-swap_1 253:5 0 976M 0 lvm [SWAP]
sdb 8:16 0 5G 0 disk
└─vg_share-lv_share 253:1 0 4G 0 lvm
sr0 11:0 1 4,4G 0 rom
Скрипт из статьи (1) автоматически определяет, где будут храниться снапшоты, но это не удобно, т.к. скрипт создает снапшот в корне LVM диска, в нашем случает корень смонтирован в /data/share/ в этой папке и будут создаваться снапшоты и каждый следующий снапшот будет содержать также и предыдущие снапшоты. Чтобы этого избежать, переменная VolumePath была вынесена из скрипта в конфигурационный файл /etc/samba/smbsnap.conf
При такой конфигурации можно вручную задать где будут храниться снапшоты, а в конфиге smb.conf достаточно указать в разделе описания общей папки, по какому пути система должна искать эти снапшоты
root@astra:/home/astra-admin# cat /etc/samba/smb.conf
[global]
workgroup = WORKGROUP
; interfaces = 127.0.0.0/8 eth0
; bind interfaces only = yes
log file = /var/log/samba/log.%m
max log size = 1000
logging = file
panic action = /usr/share/samba/panic-action %d
server role = standalone server
obey pam restrictions = yes
unix password sync = yes
passwd program = /usr/bin/passwd %u
passwd chat = *Enter\snew\s*\spassword:* %n\n *Retype\snew\s*\spassword:* %n\n *password\supdated\ssuccessfully* .
pam password change = yes
map to guest = bad user
; logon path = \\%N\profiles\%U
; logon drive = H:
; logon script = logon.cmd
; add user script = /usr/sbin/adduser --quiet --disabled-password --gecos "" %u
; add machine script = /usr/sbin/useradd -g machines -c "%u machine account" -d /var/lib/samba -s /bin/false %u
; add group script = /usr/sbin/addgroup --force-badname %g
; include = /home/samba/etc/smb.conf.%m
; idmap config * : backend = tdb
; idmap config * : range = 3000-7999
; idmap config YOURDOMAINHERE : backend = tdb
; idmap config YOURDOMAINHERE : range = 100000-999999
; template shell = /bin/bash
usershare allow guests = yes
log level = 1 vfs:1
vfs objects = acl_xattr full_audit
full_audit:prefix = %u|%I|%S
full_audit:success = connect, open, mkdir, rmdir, unlink, write, create_file, rename
full_audit:failure = connect, open, mkdir, rmdir, unlink, write, create_file, rename
full_audit:facility = LOCAL5
full_audit:priority = INFO
#full_audit:syslog = true
map acl inherit = yes
# log level = 3
#vfs objects = shadow_copy2
# С помощью этого параметра можно указать точку монтирования файловой системы, содержащей путь к ресурсу. Обычно эта точка монтирования определяется автоматически. Но для тестов, может быть удобно иметь возможность указать ее.
# shadow:mountpoint = /data/share/pm_share
# Путь к каталогу, в котором файловая система ресурса хранит свои моментальные снимки. Если указан абсолютный путь, то он используется как есть. Если указан относительный путь, то он берется относительно точки монтирования файловой системы корня ресурса. (См. shadow:mountpoint).
#shadow:snapdir = /data/.snapshots/
[pm_share]
#force create mode = 0676
#force directory mode = 0771
path = /data/share/pm_share
read only = No
valid users = share-user
writable = yes
public = yes
create mask = 0640
directory mask = 0755
write list = share-user
vfs objects = shadow_copy2
inherit acls = yes
shadow:snapdir = /snapshots
shadow:sort = desc
root@astra:/home/astra-admin# cat /etc/samba/smbsnap.conf
SnapVolumes=('/dev/vg_share/lv_share;500;500;1000;,nouuid')
SnapSets=(2 5 20)
OffDays="Sat Sun"
VolumePath=/snapshots
dev/vg_share/lv_share - lvm (/dev/vg_share/lv_share) отформатирован в xfs
2000 - начальный размер снапшота (SnapSize), на vg должно быть достаточное количество свободного места для снапшота
500 - (FreeSize)
1000 - (ReSize)
nouuid - опции монтирования снапшота (mount /dev/vg_share/lv_share-0-2024.03.13-09.32.00 /data/share/@GMT-2024.03.13-09.32.00 -o ro,nouuid)
root@astra:/home/astra-admin# cat /root/bin/smbsnap
#!/bin/bash
#
# written by Christian Schwamborn
# bugs and suggestions to:
# christian.schwamborn[you-know-what-comes-here]nswit.de
#
# published under GNU General Public License v.2
# version 1.0.3 (2007-12-13)
#
# Authors: Christian Schwamborn [CS]
# Schlomo Schapiro [GSS] sschapiro[you-know-what-comes-here]probusiness.de
#
# History:
# 1.0.1 (2006-11-21) CS initial release
# 1.0.2 (2007-01-08) CS snapshottime now in GMT
# 1.0.3 (2007-12-13) GSS added support for extra mount options (for XFS)
#
# You are using this scrip at your own risk, the author is not responsible
# for data damage or losses in any way.
#
# What this is:
# You can use this script to create and manage snapshots for the Samba
# VFS module shadow_copy.
#
# How to use:
# The script provides some commanline parameters which are usefull for
# start/stop scrips (i.e. mount and unmount). Other parameters are usefull
# for cronjobs - add this, for a usual snapshot scenario (without trailing #)
# to your crontab:
#
# 0 12 * * 1-5 root /usr/local/sbin/smbsnap autosnap 0 0
# 0 7 * * 2-5 root /usr/local/sbin/smbsnap autosnap 0 1
# 0 7 * * 1 root /usr/local/sbin/smbsnap autosnap 0 2
# 0 8 * * * root /usr/local/sbin/smbsnap clean all
# 3,33 * * * * root /usr/local/sbin/smbsnap autoresize all
#
# This takes snapshots at 7:00 and 12:00 every workday and checks every hour
# if a snapshot needs to be resized.
#
# The script has some flaws:
# -This script currently works only with LVM2, no EVMS support yet
# -XFS should be easy to implement, but it isn't yet
# -You must not use dashes in your volumegroups or logical volumes
# -Be carefull with the configuration, the parameters are not completely
# checked right now, as the same for the command line parameters
# -You have to keep track of the freespace of your volumegroups
# -Be aware, that if your snapshots grow faster than you assumed, they will
# become unusable. With the configuration shown above, this script checks
# every 30 minutes if the snapshots are in the need of a resize. If
# someone has a better idea how to check the snapshots than periodical,
# let me know plaese.
#
# This script is written for the bash, other shells might work, it also uses
# some external commands: mount, umount, grep, date, bc, logger, lvcreate,
# lvremove, lvresize
#
# There are currently three variables that have to be configured:
# -SnapVolumes is an array, every element of that array represents a logical
# volume that is configured for snapshots. Each element is a comma seperated
# list, which consists of the logical volume itself (i.e. /dev/GROUP1/foo),
# the start size of the snapshot (in megabytes), the freespace which should
# be maintained (in megabytes), the space added, when a snapshot is
# resized (also in megabytes) and optionally additional mount options required
# for mounting the snapshot, like ",nouuid" for XFS. Please add the leading ","
# because this parameter will be appended to "mount -o ro" *verbatim*.
# The number of an element is used as a reference when calling the script
# -SnapSets is also an array, currently every element just represents the
# age (in days) of a snapshot of the specific snapshot-set.
# -OffDays is a simple string with the none work days.
#
# The script will figure out by itself where to mount the snapshots, but the
# original logical volumes has to be mounted fist.
#
# Copy and adjust the following three variables (without #) to a blank file in
# /etc/samba and name it smbsnap.conf. If you place the configuration file
# elsewhere, make sure to adjust the path below.
#
# SnapVolumes=('/dev/GROUP/foo;2000;500;1000;,nouuid' '/dev/GROUP/bar;3000;1000;2000')
# SnapSets=(2 5 20)
# OffDays="Sat Sun"
#
# NOTE TO USERS OF PREVIOUS VERSION !!
#
# The delimiter changed from , to ; to support adding multiple mount options
#
# please convert your smbsnap.conf with sed -e 's/,/;/g' -i /etc/samba/smbsnap.conf
#
# Sorry for the invonvenience ...
#
###############################################################################
. /etc/samba/smbsnap.conf
export LANG=en_US.UTF-8
export LANGUAGE=en_US:en
SnapDate=$(date -u +%Y.%m.%d-%H.%M).00
[ -z "${1}" ] || Command=${1}
[ -z "${2}" ] || LVolume=${2}
[ -z "${3}" ] || SnapSet=${3}
ExtraMountOptions=
# process a single snapshot
# arguments: Command
# needs: SnapShot, VolumePath, SnapSets, OffDays, FreeSize, ReSize
# provides: na.
# local: cmd, SnapShotPath, CurrSnapSets, Count, Expire, Parameters, SnapState, CurrSize, FillPercet, CurrFreeSize
function DoSnap()
{
cmd=${1}
SnapShotPath=${VolumePath}/@GMT-$(echo ${SnapShot##*/} | cut -f3-4 -d\-)
case ${cmd} in
# to mount snapshots
mount)
[ -d ${SnapShotPath} ] || mkdir ${SnapShotPath} || \
logger "${0}: ***error*** - unable to create mountpoint for ${SnapShot}"
if mount | grep -q ${SnapShotPath}; then
logger "${0}: ***error*** - snapshot ${SnapShot} is allready mounted to ${SnapShotPath}"
else
mount ${SnapShot} ${SnapShotPath} -o ro$ExtraMountOptions >/dev/null 2>&1 || \
logger "${0}: ***error*** - can not mount ${SnapShot} to ${SnapShotPath}"
fi
;;
# to unmount a snapshots
umount)
if mount | grep -q ${SnapShotPath}; then
umount -f -l ${SnapShotPath} >/dev/null 2>&1 || \
logger "${0}: ***error*** - can not unmount ${SnapShot} mounted to ${SnapShotPath}"
else
logger "${0}: ***error*** - snapshot ${SnapShot} is not mounted to ${SnapShotPath}"
fi
;;
# to remove expired snapshots
clean)
CurrSnapSet=$(echo ${SnapShot##*/} | cut -f2 -d\-)
if [ ${CurrSnapSet} -ge 0 ] && [ ${CurrSnapSet} -lt ${#SnapSets[@]} ]; then
Expire=$(echo ${SnapSets[${CurrSnapSet}]} | cut -f1 -d,)
# add off-days, if any, to the expire time; we just count work-days
declare -i Count=1
while [ ${Expire} -ge ${Count} ];do
echo ${OffDays} | grep -q $(date -d "-${Count} day" +%a) && Expire=$((${Expire} + 1))
Count=$((${Count} + 1))
done
# compare date now minus expire-time with the snapshot-date
if [ $(( $(date +%s) - ${Expire}*24*60*60 - 12*60*60)) -gt \
$(date -d "$(echo ${SnapShot##*/} | cut -f3 -d\- | tr \. \-) \
$(echo ${SnapShot##*/} | cut -f4 -d\- | tr \. \:)" +%s) ]; then
# unmount snapshot
if mount | grep -q ${SnapShotPath}; then
umount -f ${SnapShotPath} >/dev/null 2>&1 || \
logger "${0}: ***error*** - can not unmount ${SnapShot} mounted to ${SnapShotPath}"
fi
if ! mount | grep -q ${SnapShotPath}; then
# remove mount-directory
if [ -d ${SnapShotPath} ]; then
rmdir ${SnapShotPath} || \
logger "${0}: ***error*** - unable to remove mountpoint for ${SnapShot}"
fi
# finally remove snapshot
if lvremove -f ${SnapShot} >/dev/null 2>&1;then
logger "${0}: successfully removed outdated snapshot ${SnapShot}"
else
logger "${0}: ***error*** - can not remove logical volume ${SnapShot}"
fi
fi
fi
else
logger "${0}: ***error*** - snapshot-set #${CurrSnapSet} of snaphot ${SnapShot} is not configured"
fi
;;
# to check periodical if the snapshots have to be resized
autoresize)
Parameters="--options lv_size,snap_percent --noheadings --nosuffix --separator , --unbuffered --units m"
SnapState=$(lvs ${Parameters} ${SnapShot})
CurrSize=$(echo ${SnapState} | cut -f1 -d,)
FillPercet=$(echo ${SnapState} | cut -f2 -d,)
CurrFreeSize=$(echo "${CurrSize}-${CurrSize}/100*${FillPercet}" | bc)
if ! [ $(echo "${CurrFreeSize} > ${FreeSize}" | bc) -eq 1 ]; then
if lvresize -L +${ReSize}M ${SnapShot} >/dev/null 2>&1; then
logger "${0}: successfully resized snapshot ${SnapShot}"
else
logger "${0}: ***error*** - an error occurred while resizing ${SnapShot}"
fi
fi
;;
esac
}
# invoked if all snapshots of a volume are processed
# arguments: Command
# needs: VolumeDevice, VolumePath, SnapSet & functions: DoSnap
# provides: SnapShot
# local: snapset_tmp, cmd
function DoAllSnaps()
{
cmd=${1}
[ -z "${SnapSet}" ] || snapset_tmp="${SnapSet}-"
# checkout if the configured volume exists and is mounted
if [ -b ${VolumeDevice} ]; then
if mount | grep -q "${VolumePath} "; then
# process all snapshots of the volume and, if given, of a specific snapshot-set
for SnapShot in ${VolumeDevice}-${snapset_tmp}*; do
if [ ${SnapShot} = "${VolumeDevice}-${snapset_tmp}*" ]; then
logger "${0}: ***error*** - no backupset #${SnapSet} found for ${VolumeDevice}"
else
DoSnap ${cmd}
fi
done
else
logger "${0}: ***error*** - logical volume ${VolumeDevice} not mounted to ${VolumePath}"
fi
else
logger "${0}: ***error*** - logical volume ${VolumeDevice} does not exist"
fi
}
# creates a new snapshot and mounts it
# arguments: na.
# needs: VolumeDevice, VolumePath, SnapSet, SnapSize, SnapDate & functions: DoAllSnaps, DoSnap
# provides: SnapShot
# local: na.
function MakeSnap ()
{
case ${SnapSet} in
[0-9])
if [ "${Command}" = "autosnap" ]; then DoAllSnaps "clean"; fi
SnapShot=${VolumeDevice}-${SnapSet}-${SnapDate}
if lvcreate -L${SnapSize}M -s -n ${SnapShot##*/} ${VolumeDevice} >/dev/null 2>&1; then
logger "${0}: successfully created new snapshot ${SnapShot}"
else
logger "${0}: ***error*** - an error occurred while creating snapshot ${SnapShot}"
fi
DoSnap "mount"
;;
*)
echo "usage: ${0} snap|autosnap <LV number | all> <Snap-Set Number>"
;;
esac
}
# sets some variables and splits the way for certain commands
# arguments: one object of the array SnapVolumes
# needs: Command, & functions: DoAllSnaps MakeSnap
# provides: SnapVolume, VolumeDevice, PVGroupName, LVolumeName, VolumePath, SnapSize, FreeSize, ReSize
# local: na.
function SecondChoice ()
{
SnapVolume=${1}
VolumeDevice=$(echo ${SnapVolume} | cut -f1 -d\;)
PVGroupName=$(echo ${VolumeDevice} | cut -f3 -d/)
LVolumeName=$(echo ${VolumeDevice} | cut -f4 -d/)
#VolumePath=$(mount | grep ^/dev[[:alnum:]/]*${PVGroupName}.${LVolumeName}[\ ] | cut -f3 -d' ')
SnapSize=$(echo ${SnapVolume} | cut -f2 -d\;)
FreeSize=$(echo ${SnapVolume} | cut -f3 -d\;)
ReSize=$(echo ${SnapVolume} | cut -f4 -d\;)
ExtraMountOptions=$(echo ${SnapVolume} | cut -f5 -d\;)
case ${Command} in
mount|umount|clean|autoresize)
DoAllSnaps ${Command}
;;
snap|autosnap)
MakeSnap
;;
esac
}
# decides if all configured volumes are processed or just a specific one
# arguments: na.
# needs: Command, LVolume, SnapVolumes & functions: SecondChoice
# provides: na.
# local: snp
case ${Command} in
mount|umount|snap|clean|autosnap|autoresize)
case ${LVolume} in
all)
for snp in ${SnapVolumes[@]}; do
SecondChoice ${snp}
done
;;
[0-9])
if [ ${LVolume} -ge 0 ] && [ ${LVolume} -lt ${#SnapVolumes[@]} ]; then
SecondChoice ${SnapVolumes[LVolume]}
else
logger "${0}: ***error*** - there is no configured logical volume #${LVolume} for snapshots"
fi
;;
*)
echo "usage: ${0} <command> <LV number | all> [<Snap-Set Number>]"
;;
esac
;;
*)
echo "usage: ${0} <command> <LV number | all> [<Snap-Set Number>]"
echo
echo " valid commands are:"
echo " mount - to mount snapshots"
echo " umount - to unmount snapshots"
echo " snap - to make a new snapshot"
echo " clean - to cleanup outdated snapshots"
echo " autosnap - normally used for cronjobs to cleanup"
echo " outdates snapshots an create a new one"
echo " autoresize - for a periodical check if snapshots"
echo " needs to be resized"
echo
echo " <LV number> is the number of a logical volume, configured for"
echo " snapshots in SnapVolumes, or simply 'all' for all volumes"
echo " <Snap-Set Number> is the number of the snapshot-set, configured"
echo " in SnapSet. It is optional, except for the commands 'snap' and"
echo " 'autosnap'"
echo
;;
esac
Настройка аудита производилась по статье (8), но она не актуальна в части поддерживаемых vfs, мой анализ показал, что для аудита достаточно следующих параметров
create_file renameat mkdirat unlinkat pwrite_send pwrite_recv
Охватывают создание файлов и папок, изменение их содержимого, переименование и удаление.
Актуальный список доступен в документации (9)
Моя конфигурация сетевой папки с аудитом, если секцию аудита добавить в описание папки, то можно настроить отдельный файл для логов каждой папки, изменяя facility
[pm_share]
path = /data/share/pm_share
read only = No
valid users = share-user
writable = yes
public = yes
create mask = 0640
directory mask = 0755
write list = share-user
vfs objects = shadow_copy2
inherit acls = yes
shadow:snapdir = /snapshots
shadow:sort = desc
vfs objects = full_audit
full_audit:prefix = %u|%I|%S
full_audit:success = create_file renameat mkdirat unlinkat pwrite_send pwrite_recv
full_audit:failure = create_file renameat mkdirat unlinkat pwrite_send pwrite_recv
full_audit:facility = LOCAL5
full_audit:priority = INFO
full_audit:syslog = true
Можно включить раздел логирования в конец секции [global], тогда не будет необходимости прописывать данную секцию для каждой общей папки
....
vfs objects = full_audit
full_audit:prefix = %u|%I|%S
full_audit:success = create_file renameat mkdirat unlinkat pwrite_send pwrite_recv
full_audit:failure = create_file renameat mkdirat unlinkat pwrite_send pwrite_recv
full_audit:facility = LOCAL5
full_audit:priority = INFO
full_audit:syslog = true
Настройка syslog-ng, лог аудита пишется в файл /var/log/samba/audit.log
destination d_samba_audit { file("/var/log/samba/audit.log"); };
filter f_samba_audit { facility(local5); };
log { source(s_src); filter(f_samba_audit); destination(d_samba_audit); };