SAMBA: Неправильно отображается информация о размерах папки лежащей на BTRFS

Типичной ситуацией является некорректное отображение общего размера сетевой шары, если она лежит на BTRFS-рейде — вместо действительного размера тома показывается сумма размеров всех дисков в рейде. Для исправления этой ситуации можно воспользоваться опцией «dfree command», которая позволяет указать кастомный исполняемый файл, которые будет подсчитывать и возвращать данные о размерах диска.

Данные опции нужно добавить в конфигурацию шары:

/etc/samba/smb.conf
 
[Public]
    ....
    dfree command = /etc/samba/btrfs-dfree.py
    dfree cache time = 60
    force user = root
    force group = root
    ....

Здесь стоит обратить внимание на опцию «force user = root», которая с одной стороны принудительно сделает рута владельцем новых файлов и папок, что может быть вам не нужно, но также позволит выполняться скрипту с рутовыми правами — это необходимо для получения таких данных из вывода «btrfs filesystem usage» как «ratio» — коэффициент данных на рейде, учитывающий сколько копий данных фактически на рейде расположено, и «free (estimated) min» —  более точное значение свободного места.

Если в ваши планы не входит менять юзера с дефолтного, то свободное место будет браться скриптом из «Free (statfs, df)», а для показа точного общего размера сетевой шары можно вручную в скрипте указать значение «ratio», присвоив его переменной «CUSTOMRATIO».

Итак, создаем скрипт «/etc/samba/btrfs-dfree.py» (спасибо проекту openmediavault, я использовал их скрипт «omv-btrfs-dfree» как основу), не забываем сделать его исполняемым. Скрипт на гитхабе

/etc/samba/btrfs-dfree.py
 
#!/usr/bin/env python3
import os
import re
import subprocess
import sys
from systemd import journal
CUSTOMRATIO = 0 # set ratio of your btrfs volume if samba user is not root (smb.conf: force user = root)
LOGGING = 0 # 0 - off, 1 - on. journalctl -t 'SMBUS'
def main():
    path = os.path.realpath(sys.argv[1] if len(sys.argv) > 0 else '.')
    output = subprocess.run(
        ['btrfs', 'filesystem', 'usage', '--raw', '-T', path],
        capture_output=True
    ).stdout.decode()
    re_matches_for_total = r'.+Device size:\s+(\d+)\n.+'
    matches_for_total = re.match(
        re_matches_for_total,
        output, re.DOTALL
    )
    if os.getuid() == 0: # if samba user is root we can get ratio to calculate total space
        re_matches_for_ratio = r'.+Data ratio:\s+(\d+\.\d+).+'
        matches_for_ratio = re.match(
            re_matches_for_ratio,
            output, re.DOTALL
        )
        ratio = float(matches_for_ratio.group(1))
    else: # else set ratio = 1 but can get wrong total size
        if CUSTOMRATIO == 0:
            ratio = 1
        else:
            ratio = CUSTOMRATIO
    if os.getuid() == 0: # if user is root we can get estimated (minimum) free space
        re_matches_for_available = r'.+Free \(estimated\):\s+\d+\s+\(min: (\d+)\)\n.+'
    else: # otherwise only 'statfs' free
        re_matches_for_available = r'.+Free \(statfs, df\):\s+(\d+)\n.+'
    matches_for_available = re.match(
        re_matches_for_available,
        output, re.DOTALL
    )
    if matches_for_total is not None:
        totalb = matches_for_total.group(1)
        availableb = matches_for_available.group(1)
        if LOGGING == 1:
            journal.stream('SMBUS').write(f'SMB VARS: userid={os.getuid()}, ratio={ratio}, availableb={availableb}, totalb={totalb}')
            for line in output.splitlines():
                journal.stream('SMBUS').write(f'{line}')
        total =     round(int(totalb) // (1024 * ratio))
        available = round(int(availableb) // 1024)
    else:
        output = subprocess.run(
            ['df', '--portability', '--print-type', '--block-size=1K', path],
            capture_output=True
        ).stdout.decode()
        _, _, total, _, available, *_ = output.split('\n')[1].split()
    sys.stdout.write(f'{total} {available} 1024\n')
if __name__ == "__main__":
    sys.exit(main())

Проверить работу скрипта можно выполнив его вручную, указав в качестве аргумента путь к папке на btrfs:

Общая сетевая папка NFS+SAMBA с правами для всех

Цель: расшарить директорию через NFS и CIFS так, чтобы для обоих протоколов был общий доступ и файлы, залитые через один протокол, были доступны на чтение и запись через второй.

NFS

Метод, до которого дошел я (возможно не единственный), реализуется через дополнительного пользователя на сервере, с которого шарится директория. В моем случае это пользователь «user» с UID 1000 (входит в одноименную группу с GID 1000).

Файл /etc/exports выглядит так

/etc/exports
 
  1. "/share/Public" *(rw,nohide,async,no_subtree_check,crossmnt,all_squash,anonuid=1000,anongid=1000)

ключевыми параметрами являются:

  1. all_squash — опция говорит что все подключения будут считаться анонимными
  2. anonuid=1000 — определяет UID пользователя от имени которого будет осуществляться доступ для анонимных подключений
  3. anongid=1000 — аналогично определяет GID

Таким образом все файлы и папки, создаваемые на сервере через NFS будут иметь владельца «user»

 
 
ls -la /share/Public/test_dir/
 
 
total 12
drwxr-xr-x  3 user user 4096 Jan 14 16:18  .
drwxrwxrwx 14 user user 4096 Jan 14 16:18  ..
drwxr-xr-x  2 user user 4096 Jan 14 16:18 'nfs dir'
-rw-r--r--  1 user user    0 Jun 19  2020 'nfs file.txt'

Настройка монтирования через autofs:

 
 
  1. public -fstype=nfs4,rw,soft,tcp,retry=0 "192.168.1.2:/share/Public"

 

SAMBA

Теперь можно переходить к настройке самбы. Приведу весь свой конфиг /etc/samba/smb.conf

/etc/samba/smb.conf
 

    workgroup = WORKGROUP
    dns proxy = no
    log file = /var/log/samba/log.%m
    max log size = 200
    panic action = /usr/share/samba/panic-action %d
    server role = standalone server
    passdb backend = tdbsam
    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
    security = user
    usershare allow guests = yes
    acl allow execute always = true
    allow insecure wide links = yes
    # WinXP/Android X-Plore compatibility:
    ntlm auth = yes
    server min protocol = NT1
[Public]
    path = /share/Public
    mangled names = no
    writeable = yes
    follow symlinks = yes
    wide links = yes
    browsable = yes
    guest ok = yes
    guest only = yes
    force user = user
    force group = user
    create mask = 666
    directory mask = 777
  #recycle
    vfs objects = recycle
    recycle:repository = .Trash-1000/files/%U
    recylce:exclude_dir = /.Trash-1000
    recycle:keeptree = Yes
    recycle:versions = Yes
    recycle:touch = Yes

Здесь ключевыми являются опции директории:

  1. guest ok = yes — пускать юзера без аутентификации
  2. guest only = yes — пускать только анонимусов
  3. force user = user — работа с файлами ведется от имени указанного юзера «user» (аналог nfs-ного «anonuid=1000»)
  4. force group = user — аналогично для группы (аналог nfs-ного «anongid=1000»)

Настройка монтирования через autofs:

 
 
  1. public -fstype=cifs,rw,iocharset=utf8,file_mode=0666,dir_mode=0777,uid=1000,gid=1000 ://192.168.1.2/Public

 

Корзина

Отдельно прокомментирую вот это кусок

 
 
#recycle
    vfs objec ts = recycle
    recycle:repository = .Trash-1000/files/%U
    recylce:exclude_dir = /.Trash-1000
    recycle:keeptree = Yes
    recycle:versions = Yes
    recycle:touch = Yes

Дело в том, что при удалении файлов из директории, смонтированной через NFS, скорее всего (это зависит от файлового менеджера)  в точке монтирования будет создана директория корзины «.Trash-1000». В то же время клиенты Windows для сетевых путей корзину не используют, но именно для этого существуют опции самбы, которые включают сетевую корзину и позволяют указать — в какой именно директории хранить удаляемые файлы. В моем случае опция «recycle:repository» указывает правило хранения, которое соответствует правилам удалению в корзину в линуксе. То есть, при такой настройке, файлы что удаленные в линуксе через NFS, что в виндусе через SMB — попадут в один каталог. В винде, конечно, они не появятся в виндовой «корзине», но как минимум их можно будет достать из папки «.Trash-1000».

Однако при этом возникает парадоксальная ситуация, что в винде данную «корзину» очистить не получится, так как она лежит в той же шаре. То есть удаляемые файлы будут валиться в нее же. Поэтому указывается дополнительная опция, исключающая саму папку корзины из действия самба-корзины.