Skip to main content

Файловая шара - полное развёртывание с нуля до текущего production-состояния

SMR-SRV-OT-SHARE: полное развёртывание с нуля до текущего production-состояния

Версия runbook 1.0
Дата 10.11.2026
Проект Расширение памяти сервера
Основной сервер HP ProLiant DL380 G7
Гипервизор Proxmox VE 9.1.9
VM 103 / SMR-SRV-OT-SHARE
Guest OS Ubuntu Server 24.04.4 LTS
Назначение Пошагово воспроизвести текущую production-архитектуру хранения, файлового сервера, Snapper, Samba, File Browser, DDNS и recovery-backup.
Критическое предупреждение. Эта статья содержит команды, которые могут уничтожить данные: создание/пересоздание RAID, таблицы разделов и Btrfs. Разрушительные команды выделены отдельно. На существующем production-сервере их выполнять нельзя. Перед каждым таким шагом необходимо сверить серийные номера, bay, logical drive, /dev/disk/by-id и наличие резервной копии.
Что считается источником истины. Runbook построен из текущей BookStack-статьи HP D2700/SMR-SRV-OT-SHARE, проектных логов фактической настройки и текущего recovery-процесса. Команды, реально выполнявшиеся в проекте, сохранены максимально буквально.
Что пришлось реконструировать. В исходниках не сохранены буквальные имена Proxmox storage/bridge и исходная команда создания RAID50. Поэтому Proxmox-параметры вынесены в явные переменные, а RAID-команды восстановлены из итоговой раскладки и документированного HPE SSA CLI синтаксиса. Они помечены и требуют отдельной проверки перед разрушительным запуском.

Содержание

  1. Целевое состояние
  2. Подготовка физического сервера и D2700
  3. Создание аппаратных RAID на P411
  4. Установка/проверка Proxmox VE
  5. Создание VM 103
  6. Установка Ubuntu Server
  7. Базовые пакеты, время и QEMU Guest Agent
  8. Сетевая конфигурация и split DNS
  9. Запрет маршрутизации
  10. Передача MAIN-DATA в VM
  11. GPT/Btrfs/mount MAIN-DATA
  12. Btrfs subvolumes
  13. Snapper
  14. Linux-группы и пользователи
  15. POSIX ACL
  16. Samba
  17. Samba recycle и приватные корзины
  18. Автоматическая очистка recycle
  19. Защита smbd от отсутствующего MAIN-DATA
  20. Windows Previous Versions
  21. File Browser Next
  22. DNS/DDNS multi-homed сервера
  23. Windows change_share.bat
  24. Recovery-backup конфигурации
  25. Финальная приёмка
  26. Что намеренно НЕ настраивается
  27. Порядок включения/выключения
  28. Приложение: восстановление из CONFIG/SECRETS

1. Целевое production-состояние

В конце runbook должна получиться следующая архитектура:

Уровень Целевое состояние
Server HP ProLiant DL380 G7
Internal RAID P410i, firmware 6.64, 512 MB cache, внутренний массив под Proxmox
External controller Smart Array P411, Slot 1, firmware 6.64, 512 MB FBWC, PCIe Gen2 ×8
Disk shelf HP StorageWorks D2700, firmware 0150, 25/25 disks OK
MAIN-DATA 12×600 GB, RAID50, 2 parity groups, strip 256 KiB, logicaldrive 1, ~5.46 TiB
RECOVERY 2×146 GB, RAID1, logicaldrive 2, ~136.7 GiB
GLOBAL-SPARE bay 15, 600 GB, shared/dedicated spare
CRITICAL-BACKUP 10×500 GB, RAID50, 2 parity groups, strip 256 KiB, logicaldrive 3, ~3.64 TiB
Hypervisor Proxmox VE 9.1.9
VM VM 103, 4 vCPU host, 8 GiB RAM, 100 GiB system disk, Ubuntu 24.04.4
Data FS Btrfs MAIN-DATA, mount /srv/main-data
Shares 8 Btrfs subvolumes / 8 Samba shares
Snapshots 8 Snapper configs + Windows Previous Versions
Recycle 7 Samba recycle shares; Clonezilla без recycle
Web admin File Browser Next v3.0.0-next, 10.44.100.229:28443
DNS 5 segment FQDN + A/PTR через RFC2136/nsupdate
Backup config CONFIG + encrypted SECRETS + SHA256

2. Подготовка физического сервера и D2700

2.1. Физическая схема

  1. Установить P411 в PCIe Slot 1. В проекте Slot 5 вызывал IOCK/reset/controller lockup.
  2. На P411 должен быть исправный 512 MB FBWC. Первоначальный 256 MB cache-модуль был неисправен.
  3. Подключить внешний SAS: P411 Port 1E → SFF-8088 → D2700 I/O Module A / P1.
  4. Текущий режим — Single Domain. Второй I/O Module/Dual Domain пока не используется.
  5. Проверить firmware: P411 = 6.64, D2700/I/O modules = 0150.
Точная последовательность обновления firmware в проекте выполнялась через HPE SPP/SSA, но буквальные команды обновления в логах не сохранены. Поэтому в runbook не придумывается непроверенная flash-команда: firmware доводится до целевых версий официальным HPE SPP, после чего состояние проверяется ssacli.

2.2. Физическая раскладка D2700

Bays Диски Назначение
1–12 12×600 GB SAS 10K/10.5K MAIN-DATA RAID50
13–14 2×146 GB SAS 15K RECOVERY RAID1
15 1×600 GB SAS GLOBAL-SPARE
16–25 10×500 GB SAS 7.2K CRITICAL-BACKUP RAID50

2.3. Первичная проверка контроллера и дисков

# Выполняется на Proxmox-хосте от root.

command -v ssacli
ssacli ctrl all show status
ssacli ctrl slot=1 show detail
ssacli ctrl slot=1 show config
ssacli ctrl slot=1 pd all show status

Ожидаем: P411 в Slot 1, Controller/Cache/Capacitor = OK, D2700 на Port 1E/Box 1, все физические диски имеют Status OK.

3. Создание аппаратных RAID на P411

Разрушительный раздел. Если массивы уже существуют — пропустить создание и перейти к проверке. Для RAID50 HPE требует задавать количество parity groups; для обеих текущих RAID50 используется 2. Strip size — 256 KiB.

3.1. Команды создания массивов

# ВНИМАНИЕ: РАЗРУШИТЕЛЬНЫЙ ЭТАП.
# Выполнять ТОЛЬКО на пустом P411/D2700 после сверки адресов ВСЕХ дисков.
#
# Перед запуском:
ssacli ctrl slot=1 show config
ssacli ctrl slot=1 pd all show status
ssacli ctrl slot=1 create type=ld help

# Array A / logicaldrive 1 / MAIN-DATA
# 12 x 600 GB, RAID50, 2 parity groups, strip 256 KiB.
ssacli ctrl slot=1 create type=ld \
  drives=1E:1:1,1E:1:2,1E:1:3,1E:1:4,1E:1:5,1E:1:6,1E:1:7,1E:1:8,1E:1:9,1E:1:10,1E:1:11,1E:1:12 \
  raid=50 paritygroups=2 ss=256

# Array B / logicaldrive 2 / RECOVERY
# 2 x 146 GB, RAID1, strip 256 KiB.
ssacli ctrl slot=1 create type=ld \
  drives=1E:1:13,1E:1:14 \
  raid=1 ss=256

# Array C / logicaldrive 3 / CRITICAL-BACKUP
# 10 x 500 GB, RAID50, 2 parity groups, strip 256 KiB.
ssacli ctrl slot=1 create type=ld \
  drives=1E:1:16,1E:1:17,1E:1:18,1E:1:19,1E:1:20,1E:1:21,1E:1:22,1E:1:23,1E:1:24,1E:1:25 \
  raid=50 paritygroups=2 ss=256

# Bay 15 становится общим dedicated spare для всех массивов.
ssacli ctrl slot=1 array all add spares=1E:1:15

# Production cache ratio P411.
ssacli ctrl slot=1 modify cacheratio=25/75

# Итоговая проверка.
ssacli ctrl slot=1 show config
ssacli ctrl slot=1 ld all show detail
ssacli ctrl slot=1 pd all show status

Почему именно так:

  • MAIN-DATA: 12 дисков делятся на 2 группы по 6.
  • CRITICAL-BACKUP: 10 дисков делятся на 2 группы по 5.
  • RAID50 допускает отказ диска в каждой parity group, если два отказа не приходятся на одну группу.
  • Bay 15 назначается через array all add spares, чтобы быть общим spare.

3.2. Эталонный итог

Array A
  logicaldrive 1: RAID 50, ~5.46 TB
  Parity Group 1: bays 1-6
  Parity Group 2: bays 7-12

Array B
  logicaldrive 2: RAID 1, ~136.70 GB
  bays 13-14

Array C
  logicaldrive 3: RAID 50, ~3.64 TB
  Parity Group 1: bays 16-20
  Parity Group 2: bays 21-25

Bay 15
  shared spare

Контроль здоровья:

ssacli ctrl slot=1 show config detail
ssacli ctrl slot=1 ld all show detail
ssacli ctrl slot=1 array all show detail
ssacli ctrl slot=1 pd all show status
В текущем проекте у RAID50 наблюдалось Parity Initialization Status: Queued при Status: OK. Это не блокировало эксплуатацию, но параметр остаётся объектом мониторинга.

4. Установка и базовая проверка Proxmox VE

Гипервизор — Proxmox VE 9.1.9. Системное хранилище находится на внутреннем P410i; production MAIN-DATA на системный массив не помещается.

На полностью чистом сервере сначала:

  1. создать внутренний RAID50 на P410i из 8×146 GB;
  2. установить Proxmox VE 9.1.9 с официального ISO;
  3. задать FQDN SMR-SRV-OT-PROXMOX.baltika.local;
  4. настроить management-сеть хоста согласно локальной инфраструктуре.
IP management-интерфейса Proxmox и имена host bridge в источниках проекта не зафиксированы. Их нельзя честно восстановить из статьи — поэтому ниже они задаются через переменные.

После установки проверить:

pveversion -v
hostnamectl
ip -br address
ip route
pvesm status
cat /etc/pve/storage.cfg
cat /etc/network/interfaces
lspci -nnk | grep -A3 -i 'Smart Array'

Утилита ssacli должна быть установлена на Proxmox-хосте. Если её нет, скачать подходящий официальный HPE SSA CLI package для Debian/Linux и установить локальный пакет:

mkdir -p /root/hpe-ssa
cd /root/hpe-ssa

# Сюда заранее положить официальный HPE ssacli .deb.
ls -lh *.deb

apt install ./ssacli*.deb

command -v ssacli
ssacli ctrl all show status

5. Создание VM 103

Целевые ресурсы VM:

VMID 103
Name SMR-SRV-OT-SHARE
CPU 4 vCPU, type host
RAM 8192 MiB
System disk 100 GiB
Data MAIN-DATA как отдельный raw SCSI device
NIC 7 интерфейсов

5.1. Создание VM с параметрами Proxmox

# Выполняется на Proxmox-хосте.
#
# Значения STORAGE/ISO/BR_* не сохранились в проекте буквально.
# Заполнить их по фактическому /etc/pve/storage.cfg и /etc/network/interfaces.

VMID=103
VMNAME="SMR-SRV-OT-SHARE"

SYS_STORAGE="<PVE_STORAGE_FOR_SYSTEM_DISK>"
ISO_VOL="<PVE_ISO_STORAGE>:iso/ubuntu-24.04.4-live-server-amd64.iso"

BR_INSTALL="<BRIDGE_192.168.1.0_24>"
BR_MGMT="<BRIDGE_VLAN2100_10.44.100.0_24>"
BR_ROZLIV="<BRIDGE_VLAN2002_172.16.68.0_23>"
BR_PIVO="<BRIDGE_VLAN2030_172.16.66.0_23>"
BR_UTILITIES="<BRIDGE_VLAN2040_172.16.70.0_23>"
BR_LEGACY="<BRIDGE_LEGACY_VLAN1_10.255.0.0_16>"
BR_ASTUE="<BRIDGE_VLAN2006_172.17.14.0_24>"

# Сначала посмотреть доступные storage и bridges.
pvesm status
cat /etc/pve/storage.cfg
ip -br link
cat /etc/network/interfaces

# Создаём VM без дисков/NIC.
qm create "$VMID" \
  --name "$VMNAME" \
  --ostype l26 \
  --memory 8192 \
  --cores 4 \
  --cpu host \
  --scsihw virtio-scsi-single \
  --agent enabled=1

# Системный диск 100 GiB.
qm set "$VMID" \
  --scsi0 "${SYS_STORAGE}:100,discard=on,iothread=1"

# ISO Ubuntu Server.
qm set "$VMID" --ide2 "${ISO_VOL},media=cdrom"
qm set "$VMID" --boot order=scsi0

# Семь виртуальных NIC в том же логическом порядке, что и production.
qm set "$VMID" --net0 "virtio,bridge=${BR_INSTALL}"
qm set "$VMID" --net1 "virtio,bridge=${BR_MGMT}"
qm set "$VMID" --net2 "virtio,bridge=${BR_ROZLIV}"
qm set "$VMID" --net3 "virtio,bridge=${BR_PIVO}"
qm set "$VMID" --net4 "virtio,bridge=${BR_UTILITIES}"
qm set "$VMID" --net5 "virtio,bridge=${BR_LEGACY}"
qm set "$VMID" --net6 "virtio,bridge=${BR_ASTUE}"

qm config "$VMID"
qm start "$VMID"
После создания NIC не предполагать вслепую, что Linux назовёт их ровно ens18...ens23/enp2s1. После установки Ubuntu выполнить ip -br link и сверить MAC из qm config 103. Если порядок отличается — адаптировать Netplan к фактическим NIC.

6. Установка Ubuntu Server 24.04.4 LTS

В installer:

  • Hostname: smr-srv-ot-share.
  • Создать административного пользователя baltikashare.
  • Установить OpenSSH server.
  • Системный диск — только 100 GiB виртуальный диск; production logical drive пока не форматировать.
  • Временная сеть установки — 192.168.1.229/24.

После первого входа:

sudo hostnamectl set-hostname smr-srv-ot-share
hostnamectl

sudo apt update
sudo apt full-upgrade -y

cat /etc/os-release
uname -r
ip -br address

7. Базовые пакеты, QEMU Guest Agent и время

7.1. Пакеты

sudo apt update
sudo apt install -y \
  ca-certificates \
  curl \
  nano \
  openssl \
  iputils-ping \
  tcpdump \
  arping \
  bash-completion \
  lsof \
  qemu-guest-agent \
  systemd-timesyncd \
  ufw \
  ethtool \
  rsync \
  acl \
  attr \
  btrfs-progs \
  snapper \
  parted \
  dnsutils \
  samba \
  smbclient \
  samba-vfs-modules

7.2. QEMU Guest Agent

sudo systemctl enable --now qemu-guest-agent
systemctl status qemu-guest-agent --no-pager

7.3. Часовой пояс и NTP

sudo timedatectl set-timezone Europe/Samara
sudo timedatectl set-local-rtc 0
sudo timedatectl set-ntp true

timedatectl

8. Сетевая конфигурация VM и split DNS

Текущая адресация:

Interface Назначение Address DNS
ens18 временная/setup 192.168.1.229/24 192.168.1.1
ens19 management VLAN2100 10.44.100.229/24, GW 10.44.100.1 нет
ens20 Rozliv VLAN2002 172.16.68.229/23 172.16.68.53
ens21 будущий Pivo VLAN2030 пока без IP —
ens22 Utilities VLAN2040 172.16.70.229/23 172.16.70.53
ens23 Legacy VLAN1 + временный Pivo 10.255.0.229/16 + 172.16.66.229/23 10.255.0.53 + 172.16.66.53
enp2s1 ASTUE VLAN2006 172.17.14.229/24 172.17.14.53

8.1. Netplan

sudo mkdir -p /etc/netplan
sudo cp -a /etc/netplan /root/netplan-before-share-$(date +%Y%m%d-%H%M)
sudo nano /etc/netplan/01-smr-srv-ot-share.yaml

Содержимое:

# Файл: /etc/netplan/01-smr-srv-ot-share.yaml
#
# Полный файл реконструирован из подтверждённой production-таблицы интерфейсов
# и текущей split-DNS конфигурации.
#
# Если после создания VM имена NIC отличаются, сначала сверить:
#   ip -br link
# и заменить имена интерфейсов в этом YAML.

network:
  version: 2
  renderer: networkd

  ethernets:

    ens18:
      dhcp4: false
      addresses:
        - 192.168.1.229/24
      nameservers:
        addresses:
          - 192.168.1.1

    ens19:
      dhcp4: false
      addresses:
        - 10.44.100.229/24
      routes:
        - to: default
          via: 10.44.100.1

    ens20:
      dhcp4: false
      addresses:
        - 172.16.68.229/23
      nameservers:
        addresses:
          - 172.16.68.53
        search:
          - "~ot-samara.baltika.ru"
          - "~68.16.172.in-addr.arpa"
          - "~69.16.172.in-addr.arpa"

    ens21:
      dhcp4: false
      # Зарезервирован под будущий перенос Pivo VLAN2030.
      # Текущее production-состояние: IP не назначен.
      optional: true

    ens22:
      dhcp4: false
      addresses:
        - 172.16.70.229/23
      nameservers:
        addresses:
          - 172.16.70.53
        search:
          - "~ot-samara.baltika.ru"
          - "~70.16.172.in-addr.arpa"
          - "~71.16.172.in-addr.arpa"

    ens23:
      dhcp4: false
      addresses:
        - 10.255.0.229/16
        - 172.16.66.229/23
      nameservers:
        addresses:
          - 10.255.0.53
          - 172.16.66.53
        search:
          - "~ot-samara.baltika.ru"
          - "~255.10.in-addr.arpa"
          - "~66.16.172.in-addr.arpa"
          - "~67.16.172.in-addr.arpa"

    enp2s1:
      dhcp4: false
      addresses:
        - 172.17.14.229/24
      nameservers:
        addresses:
          - 172.17.14.53
        search:
          - "~ot-samara.baltika.ru"
          - "~14.17.172.in-addr.arpa"

8.2. Безопасное применение

sudo netplan generate

# При удалённой настройке сначала try:
sudo netplan try --timeout 120

# Если всё корректно:
sudo netplan apply

ip -br address
ip route
resolvectl status

Обязательный итог маршрутизации:

default via 10.44.100.1 dev ens19

На ens18 default gateway отсутствует.

8.3. systemd-resolved

sudo systemctl enable --now systemd-resolved
systemctl status systemd-resolved --no-pager

ls -l /etc/resolv.conf

# Если /etc/resolv.conf не указывает на systemd-resolved:
sudo ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf

resolvectl status
Route-only domains с префиксом ~ направляют запросы конкретной прямой/reverse зоны через DNS соответствующего OT-сегмента. Это и есть split DNS multi-homed сервера.

9. Запрет маршрутизации между OT-сегментами

sudo nano /etc/sysctl.d/99-ot-multihomed.conf
# Файл: /etc/sysctl.d/99-ot-multihomed.conf

# Сервер многосетевой, но НЕ маршрутизатор.
net.ipv4.ip_forward = 0
net.ipv6.conf.all.forwarding = 0

# Loose reverse path filtering для корректной работы multi-homed host.
net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.rp_filter = 2
sudo sysctl --system

sysctl net.ipv4.ip_forward
sysctl net.ipv6.conf.all.forwarding
sysctl net.ipv4.conf.all.rp_filter
sysctl net.ipv4.conf.default.rp_filter

Ожидаем ip_forward=0, IPv6 forwarding = 0 и rp_filter=2.

10. Передача MAIN-DATA из Proxmox в VM

Production logical drive имеет стабильный SCSI WWID:

600508B1001C2ABF1F734AEF851F07C9

На Proxmox используется /dev/disk/by-id, а не нестабильное /dev/sdX.

# Выполняется на Proxmox-хосте.
# Не использовать /dev/sdX: имя может измениться после перезагрузки.

ls -l /dev/disk/by-id/ | grep -i '600508b1001c2abf1f734aef851f07c9'

qm set 103 \
  -scsi2 /dev/disk/by-id/scsi-3600508b1001c2abf1f734aef851f07c9,cache=none,iothread=1,backup=0

qm config 103

backup=0 означает: огромный raw data disk не включается в обычный VM backup. Его защита строится отдельно через Btrfs/Snapper и внешние копии.

11. GPT, Btrfs и монтирование MAIN-DATA

11.1. Идентификация диска внутри Ubuntu

lsblk -o NAME,PATH,SIZE,MODEL,SERIAL,FSTYPE,LABEL,UUID,MOUNTPOINTS
sudo blkid

11.2. Первичное создание partition + Btrfs

# ВНИМАНИЕ: РАЗРУШИТЕЛЬНЫЙ ЭТАП.
# Команды ниже стирают таблицу разделов выбранного logical drive.
#
# СНАЧАЛА найти именно MAIN-DATA по размеру и стабильному идентификатору.

lsblk -o NAME,PATH,SIZE,MODEL,SERIAL,FSTYPE,TYPE,MOUNTPOINTS
ls -l /dev/disk/by-id/
sudo blkid

# В проекте при первичном форматировании MAIN-DATA был виден как /dev/sdc.
# В новом развёртывании /dev/sdX может быть ДРУГИМ.
# Ниже используем переменную после ручной сверки.

DISK="/dev/<MAIN_DATA_DISK>"

# Контроль перед уничтожением.
lsblk -o NAME,PATH,SIZE,MODEL,SERIAL,FSTYPE,MOUNTPOINTS "$DISK"
sudo fdisk -l "$DISK"

# GPT + один раздел на весь logical drive.
sudo parted -s "$DISK" mklabel gpt
sudo parted -s "$DISK" mkpart primary 1MiB 100%
sudo partprobe "$DISK"
sudo udevadm settle

lsblk -o NAME,PATH,SIZE,FSTYPE,MOUNTPOINTS "$DISK"

# Подставить реальный раздел, например /dev/sdc1.
PART="${DISK}1"

# Создаём production Btrfs.
sudo mkfs.btrfs -L MAIN-DATA "$PART"

# Контроль LABEL/UUID.
sudo blkid "$PART"
В первоначальном проекте Btrfs был создан командой mkfs.btrfs -L MAIN-DATA /dev/sdc1. Тогда UUID получился 98e5188a-407c-4d4a-adc1-24d8c90afb32. При новом форматировании UUID закономерно будет другим.

11.3. fstab и mount

sudo mkdir -p /srv/main-data

# В текущем production UUID MAIN-DATA:
# 98e5188a-407c-4d4a-adc1-24d8c90afb32
#
# При полностью новом создании Btrfs UUID будет НОВЫМ.
# Поэтому всегда берём его из blkid:
MAIN_UUID="$(sudo blkid -s UUID -o value /dev/<MAIN_DATA_PARTITION>)"
echo "$MAIN_UUID"

sudo cp /etc/fstab /etc/fstab.bak-$(date +%Y%m%d-%H%M)

echo "UUID=${MAIN_UUID} /srv/main-data btrfs defaults,nofail,x-systemd.device-timeout=10s 0 0" \
  | sudo tee -a /etc/fstab

sudo mount -a

mountpoint /srv/main-data
findmnt /srv/main-data
df -hT /srv/main-data
sudo btrfs filesystem show /srv/main-data
sudo btrfs filesystem usage /srv/main-data

Production mount options намеренно простые: defaults,nofail,x-systemd.device-timeout=10s. Samba отдельно защищена от запуска без этого mountpoint.

12. Создание Btrfs subvolumes

sudo mkdir -p /srv/main-data/baltikashare

sudo btrfs subvolume create /srv/main-data/baltikashare/clonezilla
sudo btrfs subvolume create /srv/main-data/baltikashare/exchange
sudo btrfs subvolume create /srv/main-data/baltikashare/pivoproduction
sudo btrfs subvolume create /srv/main-data/baltikashare/rozliv
sudo btrfs subvolume create /srv/main-data/baltikashare/utilities
sudo btrfs subvolume create /srv/main-data/baltikashare/software
sudo btrfs subvolume create /srv/main-data/baltikashare/documentation
sudo btrfs subvolume create /srv/main-data/baltikashare/ot-admin

sudo btrfs subvolume list /srv/main-data

Должны появиться восемь независимых subvolume:

clonezilla
exchange
pivoproduction
rozliv
utilities
software
documentation
ot-admin

13. Настройка Snapper

13.1. Создание восьми configs

sudo snapper -c clonezilla create-config /srv/main-data/baltikashare/clonezilla
sudo snapper -c exchange create-config /srv/main-data/baltikashare/exchange
sudo snapper -c pivoproduction create-config /srv/main-data/baltikashare/pivoproduction
sudo snapper -c rozliv create-config /srv/main-data/baltikashare/rozliv
sudo snapper -c utilities create-config /srv/main-data/baltikashare/utilities
sudo snapper -c software create-config /srv/main-data/baltikashare/software
sudo snapper -c documentation create-config /srv/main-data/baltikashare/documentation
sudo snapper -c ot-admin create-config /srv/main-data/baltikashare/ot-admin

sudo snapper list-configs

13.2. Production retention policy

for cfg in clonezilla exchange pivoproduction rozliv utilities software documentation ot-admin
do
    sudo snapper -c "$cfg" set-config \
        BACKGROUND_COMPARISON=no \
        EMPTY_PRE_POST_CLEANUP=no \
        TIMELINE_CREATE=yes \
        TIMELINE_CLEANUP=yes \
        TIMELINE_MIN_AGE=1800
done

sudo snapper -c clonezilla set-config \
    TIMELINE_LIMIT_HOURLY=24 \
    TIMELINE_LIMIT_DAILY=30 \
    TIMELINE_LIMIT_WEEKLY=12 \
    TIMELINE_LIMIT_MONTHLY=12 \
    TIMELINE_LIMIT_YEARLY=1

sudo snapper -c exchange set-config \
    TIMELINE_LIMIT_HOURLY=24 \
    TIMELINE_LIMIT_DAILY=7 \
    TIMELINE_LIMIT_WEEKLY=2 \
    TIMELINE_LIMIT_MONTHLY=0 \
    TIMELINE_LIMIT_YEARLY=0

for cfg in pivoproduction rozliv utilities
do
    sudo snapper -c "$cfg" set-config \
        TIMELINE_LIMIT_HOURLY=72 \
        TIMELINE_LIMIT_DAILY=30 \
        TIMELINE_LIMIT_WEEKLY=12 \
        TIMELINE_LIMIT_MONTHLY=12 \
        TIMELINE_LIMIT_YEARLY=0
done

sudo snapper -c software set-config \
    TIMELINE_LIMIT_HOURLY=0 \
    TIMELINE_LIMIT_DAILY=14 \
    TIMELINE_LIMIT_WEEKLY=8 \
    TIMELINE_LIMIT_MONTHLY=6 \
    TIMELINE_LIMIT_YEARLY=0

sudo snapper -c documentation set-config \
    TIMELINE_LIMIT_HOURLY=72 \
    TIMELINE_LIMIT_DAILY=60 \
    TIMELINE_LIMIT_WEEKLY=26 \
    TIMELINE_LIMIT_MONTHLY=24 \
    TIMELINE_LIMIT_YEARLY=3

sudo snapper -c ot-admin set-config \
    TIMELINE_LIMIT_HOURLY=72 \
    TIMELINE_LIMIT_DAILY=90 \
    TIMELINE_LIMIT_WEEKLY=26 \
    TIMELINE_LIMIT_MONTHLY=24 \
    TIMELINE_LIMIT_YEARLY=5

sudo systemctl enable --now snapper-timeline.timer snapper-cleanup.timer
systemctl is-active snapper-timeline.timer snapper-cleanup.timer
Config Hourly Daily Weekly Monthly Yearly
clonezilla 24 30 12 12 1
exchange 24 7 2 0 0
pivoproduction 72 30 12 12 0
rozliv 72 30 12 12 0
utilities 72 30 12 12 0
software 0 14 8 6 0
documentation 72 60 26 24 3
ot-admin 72 90 26 24 5

13.3. Контроль Snapper

for cfg in clonezilla exchange pivoproduction rozliv utilities software documentation ot-admin
do
    echo
    echo "===== $cfg ====="
    sudo snapper -c "$cfg" get-config | \
        grep -E 'SUBVOLUME|BACKGROUND_COMPARISON|TIMELINE_CREATE|TIMELINE_CLEANUP|TIMELINE_LIMIT'
done

sudo btrfs subvolume list /srv/main-data

14. Linux-группы и учётные записи

14.1. Создание групп

# RW/administrative groups.
sudo groupadd admins
sudo groupadd engineers
sudo groupadd clonezilla-rw
sudo groupadd pivoproduction-rw
sudo groupadd rozliv-rw
sudo groupadd utilities-rw
sudo groupadd software-rw
sudo groupadd documentation-rw

# RO groups.
sudo groupadd shared-ro
sudo groupadd pivoproduction-ro
sudo groupadd rozliv-ro
sudo groupadd utilities-ro

# Ubuntu installation user becomes administrator of the file service.
sudo usermod -aG admins baltikashare

getent group admins
getent group engineers
getent group clonezilla-rw
getent group pivoproduction-rw
getent group rozliv-rw
getent group utilities-rw
getent group software-rw
getent group documentation-rw
getent group shared-ro
getent group pivoproduction-ro
getent group rozliv-ro
getent group utilities-ro
Если это не чистая установка, groupadd/useradd нельзя запускать вслепую. Сначала проверить getent group NAME и id USER. При восстановлении существующих данных особенно важно сохранить UID/GID.

14.2. Создание универсальных пользователей

# Универсальные RW/RO-ролевые учётные записи Linux.
# -M: не создавать home.
# -U: создать одноимённую primary group.
# shell nologin: интерактивный Linux login не нужен.

sudo useradd -M -U -s /usr/sbin/nologin \
  -G engineers engineer

sudo useradd -M -U -s /usr/sbin/nologin \
  -G clonezilla-rw clonezilla

sudo useradd -M -U -s /usr/sbin/nologin \
  -G engineers,pivoproduction-rw pivo

sudo useradd -M -U -s /usr/sbin/nologin \
  -G engineers,rozliv-rw rozliv

sudo useradd -M -U -s /usr/sbin/nologin \
  -G engineers,utilities-rw utilities

sudo useradd -M -U -s /usr/sbin/nologin \
  -G software-rw software

sudo useradd -M -U -s /usr/sbin/nologin \
  -G documentation-rw documents

# Только чтение.
sudo useradd -M -U -s /usr/sbin/nologin \
  -G shared-ro,rozliv-ro rozliv_read

sudo useradd -M -U -s /usr/sbin/nologin \
  -G shared-ro,pivoproduction-ro pivo_read

sudo useradd -M -U -s /usr/sbin/nologin \
  -G shared-ro,utilities-ro utilities_read

# Проверка.
id baltikashare
id engineer
id clonezilla
id pivo
id rozliv
id utilities
id software
id documents
id rozliv_read
id pivo_read
id utilities_read

14.3. Samba-пароли

# Для каждого пользователя Samba-пароль задаётся отдельно.
# Пароли НЕ помещать в скрипты, BookStack и shell history.

sudo smbpasswd -a baltikashare
sudo smbpasswd -a engineer
sudo smbpasswd -a clonezilla
sudo smbpasswd -a pivo
sudo smbpasswd -a rozliv
sudo smbpasswd -a utilities
sudo smbpasswd -a software
sudo smbpasswd -a documents
sudo smbpasswd -a rozliv_read
sudo smbpasswd -a pivo_read
sudo smbpasswd -a utilities_read

# Проверка базы Samba.
sudo pdbedit -L

Персонифицированные пользователи создаются по тому же принципу. Пример RW Розлива:

sudo useradd -M -U -s /usr/sbin/nologin \
  -G engineers,rozliv-rw user1

sudo smbpasswd -a user1
id user1

Реальные персональные логины в документацию не заносятся.

15. POSIX ACL и SGID

SGID (chmod 2770) нужен, чтобы новые объекты наследовали основную группу каталога. Default ACL задают права, которые должны появляться у новых файлов/директорий.

15.1. Базовые RW/RO ACL корней

BASE=/srv/main-data/baltikashare

# clonezilla
sudo chown root:clonezilla-rw "$BASE/clonezilla"
sudo chmod 2770 "$BASE/clonezilla"
sudo setfacl -m u::rwx,g::rwx,g:admins:rwx,m::rwx,o::--- "$BASE/clonezilla"
sudo setfacl -m d:u::rwx,d:g::rwx,d:g:admins:rwx,d:m::rwx,d:o::--- "$BASE/clonezilla"

# exchange
sudo chown root:engineers "$BASE/exchange"
sudo chmod 2770 "$BASE/exchange"
sudo setfacl -m u::rwx,g::rwx,g:admins:rwx,m::rwx,o::--- "$BASE/exchange"
sudo setfacl -m d:u::rwx,d:g::rwx,d:g:admins:rwx,d:m::rwx,d:o::--- "$BASE/exchange"

# production profiles
for spec in \
  "pivoproduction:pivoproduction-rw" \
  "rozliv:rozliv-rw" \
  "utilities:utilities-rw"
do
    dir="${spec%%:*}"
    grp="${spec##*:}"

    sudo chown "root:${grp}" "$BASE/$dir"
    sudo chmod 2770 "$BASE/$dir"
    sudo setfacl -m u::rwx,g::rwx,g:admins:rwx,m::rwx,o::--- "$BASE/$dir"
    sudo setfacl -m d:u::rwx,d:g::rwx,d:g:admins:rwx,d:m::rwx,d:o::--- "$BASE/$dir"
done

# software: профильная RW-группа + engineers RO.
sudo chown root:software-rw "$BASE/software"
sudo chmod 2770 "$BASE/software"
sudo setfacl -m u::rwx,g::rwx,g:admins:rwx,g:engineers:r-x,m::rwx,o::--- "$BASE/software"
sudo setfacl -m d:u::rwx,d:g::rwx,d:g:admins:rwx,d:g:engineers:r-x,d:m::rwx,d:o::--- "$BASE/software"

# documentation: профильная RW-группа + engineers RO.
sudo chown root:documentation-rw "$BASE/documentation"
sudo chmod 2770 "$BASE/documentation"
sudo setfacl -m u::rwx,g::rwx,g:admins:rwx,g:engineers:r-x,m::rwx,o::--- "$BASE/documentation"
sudo setfacl -m d:u::rwx,d:g::rwx,d:g:admins:rwx,d:g:engineers:r-x,d:m::rwx,d:o::--- "$BASE/documentation"

# ot-admin — только admins.
sudo chown root:admins "$BASE/ot-admin"
sudo chmod 2770 "$BASE/ot-admin"
sudo setfacl -m u::rwx,g::rwx,m::rwx,o::--- "$BASE/ot-admin"
sudo setfacl -m d:u::rwx,d:g::rwx,d:m::rwx,d:o::--- "$BASE/ot-admin"

15.2. ACL служебного .snapshots

# Snapper создаёт .snapshots как отдельные Btrfs subvolume.
# Даём нужным штатным RW/RO-read groups право ПРОХОДА/ЧТЕНИЯ,
# но НЕ право записи в служебный каталог.

sudo setfacl -m g:admins:r-x,g:clonezilla-rw:r-x,m::r-x \
  /srv/main-data/baltikashare/clonezilla/.snapshots

sudo setfacl -m g:admins:r-x,g:engineers:r-x,m::r-x \
  /srv/main-data/baltikashare/exchange/.snapshots

sudo setfacl -m g:admins:r-x,g:pivoproduction-rw:r-x,m::r-x \
  /srv/main-data/baltikashare/pivoproduction/.snapshots

sudo setfacl -m g:admins:r-x,g:rozliv-rw:r-x,m::r-x \
  /srv/main-data/baltikashare/rozliv/.snapshots

sudo setfacl -m g:admins:r-x,g:utilities-rw:r-x,m::r-x \
  /srv/main-data/baltikashare/utilities/.snapshots

sudo setfacl -m g:admins:r-x,g:software-rw:r-x,g:engineers:r-x,m::r-x \
  /srv/main-data/baltikashare/software/.snapshots

sudo setfacl -m g:admins:r-x,g:documentation-rw:r-x,g:engineers:r-x,m::r-x \
  /srv/main-data/baltikashare/documentation/.snapshots

sudo setfacl -m g:admins:r-x,m::r-x \
  /srv/main-data/baltikashare/ot-admin/.snapshots
Эти ACL соответствуют исторически проверенной модели Previous Versions для штатных RW/engineer-профилей. RO-роли *_read были введены позднее; их Windows Previous Versions отдельно ещё не зафиксированы как пройденный production-тест.

15.3. Read-only ACL без обхода .snapshots/.recycle

# Общая RO-группа: exchange + software + documentation.
for dir in exchange software documentation
do
    BASE="/srv/main-data/baltikashare/$dir"

    sudo find "$BASE" \
      -path "$BASE/.snapshots" -prune -o \
      -path "$BASE/.recycle" -prune -o \
      -exec setfacl -m g:shared-ro:r-X {} +

    sudo find "$BASE" \
      -path "$BASE/.snapshots" -prune -o \
      -path "$BASE/.recycle" -prune -o \
      -type d \
      -exec setfacl -m d:g:shared-ro:r-X {} +
done

# Профильные RO-группы.
for spec in \
  "pivoproduction:pivoproduction-ro" \
  "rozliv:rozliv-ro" \
  "utilities:utilities-ro"
do
    dir="${spec%%:*}"
    grp="${spec##*:}"
    BASE="/srv/main-data/baltikashare/$dir"

    sudo find "$BASE" \
      -path "$BASE/.snapshots" -prune -o \
      -path "$BASE/.recycle" -prune -o \
      -exec setfacl -m "g:${grp}:r-X" {} +

    sudo find "$BASE" \
      -path "$BASE/.snapshots" -prune -o \
      -path "$BASE/.recycle" -prune -o \
      -type d \
      -exec setfacl -m "d:g:${grp}:r-X" {} +
done
Нельзя заменять эти команды на простой setfacl -R. В проекте это уже приводило к проходу внутрь read-only Snapper snapshots (Operation not supported) и к нежелательному попаданию shared-ro в .recycle.

15.4. Проверка ACL

for dir in \
  clonezilla \
  exchange \
  pivoproduction \
  rozliv \
  utilities \
  software \
  documentation \
  ot-admin
do
    echo
    echo "============================================================"
    echo "$dir"
    echo "============================================================"
    getfacl -p "/srv/main-data/baltikashare/$dir"
done

16. Samba — production-конфигурация

Samba работает как standalone server. Active Directory не используется.

16.1. Резервируем дефолтный smb.conf и записываем production

sudo cp /etc/samba/smb.conf \
  /etc/samba/smb.conf.factory-$(date +%Y%m%d-%H%M)

sudo nano /etc/samba/smb.conf

Полное актуальное содержимое:

# ============================================================
# Samba configuration
# Server: SMR-SRV-OT-SHARE
# Role: standalone OT file server
# Data root: /srv/main-data/baltikashare
# ============================================================

[global]

   # Рабочая группа Windows.
   # Используется для standalone Samba без Active Directory.
   workgroup = WORKGROUP

   # Сервер работает автономно и не является контроллером домена.
   server role = standalone server

   # Аутентификация пользователей по логину и паролю.
   security = user

   # Локальная база Samba-пользователей.
   # В неё пользователи добавляются командой smbpasswd -a.
   passdb backend = tdbsam

   # Короткое NetBIOS-имя Samba-сервера.
   # Ограничение NetBIOS — максимум 15 символов.
   # Linux hostname при этом остаётся smr-srv-ot-share.
   netbios name = SMR-OT-SHARE

   # Описание сервера, которое могут видеть SMB-клиенты.
   server string = SMR OT File Server

   # Никогда не преобразовывать неизвестного пользователя в guest.
   map to guest = never

   # Запрещаем usershare и guest usershare.
   usershare max shares = 0
   usershare allow guests = no

   # Samba не используется как print server.
   load printers = no
   disable spoolss = yes
   printcap name = /dev/null

   # Минимально разрешённая версия SMB — SMB2.
   # SMB1/CIFS не разрешаем.
   server min protocol = SMB2_02

   # Логирование.
   log file = /var/log/samba/log.%m
   logging = file
   max log size = 1000

   # PAM.
   obey pam restrictions = yes
   unix password sync = no
   pam password change = no

   # =========================================================
   # Права создаваемых файлов и каталогов
   # =========================================================

   # Обычные файлы:
   # владелец = RW, группа = RW, остальные = NO ACCESS.
   create mask = 0660
   force create mode = 0660

   # Каталоги:
   # владелец = RWX, группа = RWX, остальные = NO ACCESS.
   directory mask = 0770
   force directory mode = 0770

   # Специальное наследование Samba не используем.
   # Default POSIX ACL наследуются файловой системой Linux.
   #
   # inherit permissions = yes переопределяет create/directory mask
   # и в данной схеме использоваться не должен.
   inherit acls = no
   inherit permissions = no

   # =========================================================
   # DOS / Windows attributes
   # =========================================================

   # DOS/Windows-атрибуты храним в extended attributes,
   # а не кодируем UNIX execute-битами.
   store dos attributes = yes
   map archive = no
   map hidden = no
   map system = no


# ============================================================
# EXCHANGE
# admins = RW
# engineers = RW
# shared-ro = RO
# ============================================================

[exchange]

   path = /srv/main-data/baltikashare/exchange
   browseable = yes
   guest ok = no
   read only = no
   valid users = @admins @engineers @shared-ro

   # shared-ro принудительно работает только на чтение.
   read list = @shared-ro

   # =========================================================
   # Служебные каталоги
   # =========================================================

   # Каталог Snapper полностью скрываем от обычного SMB-доступа.
   # Пользователь работает со snapshots через Windows
   # «Свойства» -> «Предыдущие версии».
   veto files = /.snapshots/

   # Сетевую корзину скрываем из обычного списка файлов,
   # но оставляем доступной по прямому SMB-пути.
   hide files = /.recycle/

   # Snapper = Windows Previous Versions.
   # Recycle = сетевая корзина Samba.
   vfs objects = snapper recycle

   # Для каждого SMB-пользователя создаётся отдельная корзина.
   recycle:repository = .recycle/%U
   recycle:keeptree = yes
   recycle:versions = yes
   recycle:touch_mtime = yes
   recycle:directory_mode = 0770
   recycle:subdir_mode = 0770
   recycle:exclude_dir = .recycle


# ============================================================
# CLONEZILLA
# admins = RW
# clonezilla-rw = RW
# ============================================================

[clonezilla]

   path = /srv/main-data/baltikashare/clonezilla
   browseable = yes
   guest ok = no
   read only = no
   valid users = @admins @clonezilla-rw

   # =========================================================
   # Служебные каталоги
   # =========================================================

   # Каталог Snapper полностью скрываем от обычного SMB-доступа.
   # Previous Versions продолжает работать через vfs_snapper.
   veto files = /.snapshots/

   # Только Snapper.
   # vfs_recycle намеренно не включён: большие образы могут
   # одновременно удерживаться корзиной и Btrfs snapshots.
   vfs objects = snapper


# ============================================================
# PIVOPRODUCTION
# admins = RW
# pivoproduction-rw = RW
# pivoproduction-ro = RO
# ============================================================

[pivoproduction]

   path = /srv/main-data/baltikashare/pivoproduction
   browseable = yes
   guest ok = no
   read only = no
   valid users = @admins @pivoproduction-rw @pivoproduction-ro
   read list = @pivoproduction-ro

   # Служебные каталоги.
   veto files = /.snapshots/
   hide files = /.recycle/

   vfs objects = snapper recycle
   recycle:repository = .recycle/%U
   recycle:keeptree = yes
   recycle:versions = yes
   recycle:touch_mtime = yes
   recycle:directory_mode = 0770
   recycle:subdir_mode = 0770
   recycle:exclude_dir = .recycle


# ============================================================
# ROZLIV
# admins = RW
# rozliv-rw = RW
# rozliv-ro = RO
# ============================================================

[rozliv]

   path = /srv/main-data/baltikashare/rozliv
   browseable = yes
   guest ok = no
   read only = no
   valid users = @admins @rozliv-rw @rozliv-ro
   read list = @rozliv-ro

   # Служебные каталоги.
   veto files = /.snapshots/
   hide files = /.recycle/

   vfs objects = snapper recycle
   recycle:repository = .recycle/%U
   recycle:keeptree = yes
   recycle:versions = yes
   recycle:touch_mtime = yes
   recycle:directory_mode = 0770
   recycle:subdir_mode = 0770
   recycle:exclude_dir = .recycle


# ============================================================
# UTILITIES
# admins = RW
# utilities-rw = RW
# utilities-ro = RO
# ============================================================

[utilities]

   path = /srv/main-data/baltikashare/utilities
   browseable = yes
   guest ok = no
   read only = no
   valid users = @admins @utilities-rw @utilities-ro
   read list = @utilities-ro

   # Служебные каталоги.
   veto files = /.snapshots/
   hide files = /.recycle/

   vfs objects = snapper recycle
   recycle:repository = .recycle/%U
   recycle:keeptree = yes
   recycle:versions = yes
   recycle:touch_mtime = yes
   recycle:directory_mode = 0770
   recycle:subdir_mode = 0770
   recycle:exclude_dir = .recycle


# ============================================================
# SOFTWARE
# admins = RW
# software-rw = RW
# engineers = RO
# shared-ro = RO
# ============================================================

[software]

   path = /srv/main-data/baltikashare/software
   browseable = yes
   guest ok = no

   # По умолчанию только чтение.
   read only = yes
   valid users = @admins @engineers @software-rw @shared-ro

   # Запись только для admins и software-rw.
   # shared-ro намеренно отсутствует в write list.
   write list = @admins @software-rw

   # Служебные каталоги.
   veto files = /.snapshots/
   hide files = /.recycle/

   vfs objects = snapper recycle
   recycle:repository = .recycle/%U
   recycle:keeptree = yes
   recycle:versions = yes
   recycle:touch_mtime = yes
   recycle:directory_mode = 0770
   recycle:subdir_mode = 0770
   recycle:exclude_dir = .recycle


# ============================================================
# DOCUMENTATION
# admins = RW
# documentation-rw = RW
# engineers = RO
# shared-ro = RO
# ============================================================

[documentation]

   path = /srv/main-data/baltikashare/documentation
   browseable = yes
   guest ok = no

   # По умолчанию только чтение.
   read only = yes
   valid users = @admins @engineers @documentation-rw @shared-ro

   # Запись только для admins и documentation-rw.
   # shared-ro намеренно отсутствует в write list.
   write list = @admins @documentation-rw

   # Служебные каталоги.
   veto files = /.snapshots/
   hide files = /.recycle/

   vfs objects = snapper recycle
   recycle:repository = .recycle/%U
   recycle:keeptree = yes
   recycle:versions = yes
   recycle:touch_mtime = yes
   recycle:directory_mode = 0770
   recycle:subdir_mode = 0770
   recycle:exclude_dir = .recycle


# ============================================================
# OT-ADMIN
# admins = RW
# Все остальные = NO ACCESS
# ============================================================

[ot-admin]

   path = /srv/main-data/baltikashare/ot-admin
   browseable = yes
   guest ok = no
   read only = no
   valid users = @admins

   # Служебные каталоги.
   veto files = /.snapshots/
   hide files = /.recycle/

   vfs objects = snapper recycle
   recycle:repository = .recycle/%U
   recycle:keeptree = yes
   recycle:versions = yes
   recycle:touch_mtime = yes
   recycle:directory_mode = 0770
   recycle:subdir_mode = 0770
   recycle:exclude_dir = .recycle

16.2. Проверка и запуск

# Проверка синтаксиса конфигурации.
sudo testparm -s

# Перечитать конфигурацию.
sudo systemctl restart smbd
systemctl is-active smbd
sudo systemctl status smbd --no-pager

# Список shares.
smbclient -L //localhost -U baltikashare

# Пример RW-теста engineer в exchange:
smbclient //localhost/exchange -U engineer

# В интерактивной сессии smbclient:
#   put /etc/hostname mode-test.txt
#   ls
#   del mode-test.txt
#   quit

# Проверить режим созданного файла, если тест ещё существует:
ls -l /srv/main-data/baltikashare/exchange/mode-test.txt 2>/dev/null || true
getfacl -p /srv/main-data/baltikashare/exchange/mode-test.txt 2>/dev/null || true

# Проверить, что удалённый файл попал в recycle:
sudo find /srv/main-data/baltikashare/exchange/.recycle \
  -maxdepth 4 \
  -name 'mode-test.txt' \
  -ls

16.3. Матрица доступа

User/role Own production exchange software documentation Other production clonezilla ot-admin
baltikashare/admins RW RW RW RW RW RW RW
engineer — RW RO RO DENY DENY DENY
pivo pivoproduction RW RW RO RO DENY DENY DENY
rozliv rozliv RW RW RO RO DENY DENY DENY
utilities utilities RW RW RO RO DENY DENY DENY
*_read own RO RO RO RO DENY DENY DENY

16.4. Локальный тест RO-профиля

sudo -u rozliv_read bash -c '
for dir in \
    rozliv \
    exchange \
    software \
    documentation \
    pivoproduction \
    utilities \
    clonezilla \
    ot-admin
do
    echo
    echo "===== $dir ====="

    test -r "/srv/main-data/baltikashare/$dir" \
      && echo "READ  : OK" \
      || echo "READ  : DENY"

    test -w "/srv/main-data/baltikashare/$dir" \
      && echo "WRITE : OK" \
      || echo "WRITE : DENY"
done
'

Для rozliv_read фактически подтверждено:

rozliv        READ OK / WRITE DENY
exchange      READ OK / WRITE DENY
software      READ OK / WRITE DENY
documentation READ OK / WRITE DENY
pivoproduction DENY
utilities      DENY
clonezilla     DENY
ot-admin       DENY

17. Samba recycle и приватные корзины

vfs_recycle включён для всех shares кроме clonezilla. Для Clonezilla корзина намеренно отключена: большие образы одновременно удерживались бы и recycle, и Btrfs snapshots.

17.1. Создание production ACL для .recycle

BASE=/srv/main-data/baltikashare

# Создаём корни корзин заранее с правильной основной группой.
for spec in \
  "exchange:engineers" \
  "pivoproduction:pivoproduction-rw" \
  "rozliv:rozliv-rw" \
  "utilities:utilities-rw" \
  "software:software-rw" \
  "documentation:documentation-rw"
do
    dir="${spec%%:*}"
    grp="${spec##*:}"
    R="$BASE/$dir/.recycle"

    sudo mkdir -p "$R"
    sudo chown "root:${grp}" "$R"

    # 1770: RWX owner/group + sticky bit, others deny.
    sudo chmod 1770 "$R"

    # Корень .recycle доступен основной группе и admins.
    sudo setfacl -m \
      u::rwx,g::rwx,g:admins:rwx,m::rwx,o::--- \
      "$R"

    # Каждый новый пользовательский каталог .recycle/%U:
    # владелец RWX, admins RWX, основная группа DENY.
    sudo setfacl -m \
      d:u::rwx,d:g::---,d:g:admins:rwx,d:m::rwx,d:o::--- \
      "$R"
done

# ot-admin: основной group уже admins.
R="$BASE/ot-admin/.recycle"
sudo mkdir -p "$R"
sudo chown root:admins "$R"
sudo chmod 1770 "$R"
sudo setfacl -m u::rwx,g::rwx,m::rwx,o::--- "$R"
sudo setfacl -m d:u::rwx,d:g::rwx,d:m::rwx,d:o::--- "$R"

# Проверка корней.
for dir in exchange pivoproduction rozliv utilities software documentation ot-admin
do
    echo "===== $dir ====="
    getfacl -p "$BASE/$dir/.recycle"
done

Смысл схемы:

  • корень .recycle доступен профильной группе и admins;
  • sticky bit не позволяет пользователю удалить чужую персональную корзину;
  • внутри .recycle/%U профильная группа получает DENY, а admins — полный доступ;
  • File Browser работает как член admins, поэтому администратор видит все корзины.

17.2. Проверка удаления через Samba

# Создать тест через SMB.
smbclient //localhost/exchange -U engineer

# В smbclient:
#   put /etc/hostname recycle-test.txt
#   del recycle-test.txt
#   quit

sudo find /srv/main-data/baltikashare/exchange/.recycle \
  -maxdepth 4 \
  -name 'recycle-test.txt' \
  -ls

18. Автоматическая очистка Samba recycle

Share Retention
exchange 7 дней
pivoproduction 14 дней
rozliv 14 дней
utilities 14 дней
software 14 дней
documentation 30 дней
ot-admin 30 дней
clonezilla recycle выключен

18.1. /usr/local/sbin/samba-recycle-cleanup

sudo nano /usr/local/sbin/samba-recycle-cleanup
#!/usr/bin/env bash

# ============================================================
# Samba recycle cleanup
# Server: SMR-SRV-OT-SHARE
#
# Автоматическая очистка файлов из Samba .recycle
# по времени их нахождения в корзине.
#
# Режимы:
#   --dry-run  только показать, что было бы удалено
#   --delete   реально удалить просроченные файлы
# ============================================================

set -u
set -o pipefail

BASE="/srv/main-data/baltikashare"
MODE="${1:---dry-run}"

case "$MODE" in
    --dry-run)
        DELETE=false
        ;;
    --delete)
        DELETE=true
        ;;
    *)
        echo "Usage: $0 [--dry-run|--delete]"
        exit 2
        ;;
esac

# Не допускаем работу по каталогу на root FS,
# если production MAIN-DATA не смонтирован.
if ! mountpoint -q /srv/main-data; then
    echo "ERROR: /srv/main-data is not mounted."
    echo "Cleanup aborted."
    exit 1
fi

FS_TYPE="$(findmnt -n -o FSTYPE /srv/main-data 2>/dev/null || true)"
if [[ "$FS_TYPE" != "btrfs" ]]; then
    echo "ERROR: /srv/main-data filesystem is '$FS_TYPE', expected 'btrfs'."
    echo "Cleanup aborted."
    exit 1
fi

POLICY=(
    "exchange:7"
    "pivoproduction:14"
    "rozliv:14"
    "utilities:14"
    "software:14"
    "documentation:30"
    "ot-admin:30"
)

echo "============================================================"
echo "Samba recycle cleanup"
echo "Date: $(date '+%Y-%m-%d %H:%M:%S %z')"
echo "Mode: $MODE"
echo "============================================================"

TOTAL_FILES=0
TOTAL_BYTES=0

for ITEM in "${POLICY[@]}"; do
    SHARE="${ITEM%%:*}"
    DAYS="${ITEM##*:}"
    RECYCLE="${BASE}/${SHARE}/.recycle"

    echo
    echo "------------------------------------------------------------"
    echo "Share:     $SHARE"
    echo "Recycle:   $RECYCLE"
    echo "Retention: $DAYS days"
    echo "------------------------------------------------------------"

    if [[ ! -d "$RECYCLE" ]]; then
        echo "INFO: recycle directory does not exist, skipping."
        continue
    fi

    AGE_MINUTES=$((DAYS * 24 * 60))

    while IFS= read -r -d '' FILE; do
        SIZE="$(stat -c '%s' "$FILE" 2>/dev/null || echo 0)"
        TOTAL_FILES=$((TOTAL_FILES + 1))
        TOTAL_BYTES=$((TOTAL_BYTES + SIZE))

        if [[ "$DELETE" == true ]]; then
            echo "DELETE: $FILE"
            rm -f -- "$FILE"
        else
            echo "WOULD DELETE: $FILE"
        fi
    done < <(
        find "$RECYCLE" \
            -xdev \
            -type f \
            -mmin "+${AGE_MINUTES}" \
            -print0
    )

    if [[ "$DELETE" == true ]]; then
        find "$RECYCLE" \
            -xdev \
            -depth \
            -mindepth 1 \
            -type d \
            -empty \
            -delete
    fi
done

echo
echo "============================================================"
echo "Summary"
echo "============================================================"
echo "Matched files: $TOTAL_FILES"
echo "Matched bytes: $TOTAL_BYTES"

if command -v numfmt >/dev/null 2>&1; then
    echo "Matched size:  $(numfmt --to=iec-i --suffix=B "$TOTAL_BYTES")"
fi

if [[ "$DELETE" == true ]]; then
    echo "Result: expired files deleted."
else
    echo "Result: DRY-RUN only. Nothing was deleted."
fi

echo "Finished: $(date '+%Y-%m-%d %H:%M:%S %z')"
sudo chown root:root /usr/local/sbin/samba-recycle-cleanup
sudo chmod 0755 /usr/local/sbin/samba-recycle-cleanup
sudo bash -n /usr/local/sbin/samba-recycle-cleanup

sudo /usr/local/sbin/samba-recycle-cleanup --dry-run

18.2. systemd service

sudo nano /etc/systemd/system/samba-recycle-cleanup.service
[Unit]
Description=Cleanup expired Samba recycle files
Documentation=man:find(1)
RequiresMountsFor=/srv/main-data

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/samba-recycle-cleanup --delete
PrivateTmp=yes
NoNewPrivileges=yes
StandardOutput=journal
StandardError=journal

18.3. systemd timer

sudo nano /etc/systemd/system/samba-recycle-cleanup.timer
[Unit]
Description=Daily Samba recycle cleanup

[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true
RandomizedDelaySec=10m
Unit=samba-recycle-cleanup.service

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now samba-recycle-cleanup.timer

systemctl status samba-recycle-cleanup.timer --no-pager
systemctl list-timers samba-recycle-cleanup.timer --all

sudo journalctl \
  -u samba-recycle-cleanup.service \
  -n 100 \
  --no-pager

19. Защита smbd от запуска без MAIN-DATA

Без защиты каталог /srv/main-data существует и на root filesystem. Если D2700 не смонтирован, Samba могла бы начать принимать данные на системный 100 GiB диск. Поэтому запуск smbd жёстко зависит от mountpoint.

sudo mkdir -p /etc/systemd/system/smbd.service.d
sudo nano /etc/systemd/system/smbd.service.d/main-data.conf
# ============================================================
# SMR-SRV-OT-SHARE
# Защита Samba от запуска без MAIN-DATA
#
# Не позволять smbd стартовать, если production-хранилище
# /srv/main-data не смонтировано.
# ============================================================

[Unit]

# systemd должен поднять все mount units, необходимые
# для доступа к /srv/main-data, до запуска smbd.
RequiresMountsFor=/srv/main-data


[Service]

# /srv/main-data должен быть именно mountpoint.
# Если MAIN-DATA не смонтирован, ExecStartPre завершится
# неуспешно и smbd не запустится.
ExecStartPre=/usr/bin/mountpoint -q /srv/main-data
sudo systemctl daemon-reload
sudo systemctl cat smbd

mountpoint /srv/main-data
findmnt /srv/main-data

sudo systemctl restart smbd
sudo systemctl status smbd --no-pager

20. Windows Previous Versions через Snapper

Samba vfs_snapper публикует Snapper snapshots в Windows как Свойства → Предыдущие версии, при этом сам каталог .snapshots скрыт через veto files.

20.1. Контрольный тест

# Создаём тестовый файл.
echo 'version-1' | sudo tee \
  /srv/main-data/baltikashare/documentation/previous-version-test.txt

# Ручной snapshot №1.
sudo snapper -c documentation create \
  -d "Documentation Previous Versions test v1"

# Меняем файл.
echo 'version-2' | sudo tee \
  /srv/main-data/baltikashare/documentation/previous-version-test.txt

# Ручной snapshot №2.
sudo snapper -c documentation create \
  -d "Documentation Previous Versions test v2"

# Показываем snapshots.
sudo snapper -c documentation list | tail -n 10

На Windows открыть share documentation, свойства файла/папки → Предыдущие версии. Должны появиться snapshots. После теста ненужные ручные snapshots можно удалить по их номерам:

sudo snapper -c documentation list

# Пример:
# sudo snapper -c documentation delete <SNAPSHOT_ID>

21. File Browser Next — административный web-доступ

Version 3.0.0-next
Binary /usr/local/bin/filebrowser
Root /srv/main-data/baltikashare
Bind 10.44.100.229:28443
Linux user filebrowser, supplementary admins
Web user baltikafile, admin
Dotfiles видимы намеренно

21.1. Установка бинарника

# Если используется recovery CONFIG-архив, бинарник уже сохранён там:
#   /usr/local/bin/filebrowser
#
# При чистой установке положить официальный архив File Browser Next
# v3.0.0-next в /tmp и сверить SHA-256.

cd /tmp
sha256sum filebrowser-linux-amd64.tar.gz

# Ожидаем:
# 17a51c2b8fe2eaf88ca4f7a80bd2f2b2e7fe968961bbeb8569a86942696e08b0

tar -xzf filebrowser-linux-amd64.tar.gz

sudo install -o root -g root -m 0755 \
  filebrowser \
  /usr/local/bin/filebrowser

/usr/local/bin/filebrowser version

21.2. Системная учётка и BoltDB

# Системная учётная запись сервиса.
sudo useradd \
  --system \
  --user-group \
  --no-create-home \
  --shell /usr/sbin/nologin \
  filebrowser

sudo usermod -aG admins filebrowser

sudo install -d -o root -g root -m 0755 /etc/filebrowser
sudo install -d -o filebrowser -g filebrowser -m 0750 /var/lib/filebrowser

id filebrowser

# Создание новой BoltDB.
# Эта команда реконструирована из подтверждённых final settings File Browser Next.
sudo -u filebrowser /usr/local/bin/filebrowser \
  -d /var/lib/filebrowser/filebrowser.db \
  config init \
  --address 10.44.100.229 \
  --port 28443 \
  --root /srv/main-data/baltikashare \
  --auth.method json \
  --locale ru \
  --fileMode 0o660 \
  --dirMode 0o770 \
  --minimumPasswordLength 12 \
  --disableExec

# Административная web-учётка.
# Заменить <STRONG_PASSWORD> вручную.
sudo -u filebrowser /usr/local/bin/filebrowser \
  -d /var/lib/filebrowser/filebrowser.db \
  users add baltikafile '<STRONG_PASSWORD>' \
  --scope / \
  --locale ru \
  --perm.admin \
  --perm.execute=false \
  --perm.share=false

# Проверка DB/config.
sudo -u filebrowser /usr/local/bin/filebrowser \
  -d /var/lib/filebrowser/filebrowser.db \
  config cat

sudo -u filebrowser /usr/local/bin/filebrowser \
  -d /var/lib/filebrowser/filebrowser.db \
  users ls
Первоначальная CLI-команда создания BoltDB не сохранилась буквально. Выше приведена реконструкция из фактически подтверждённых final settings File Browser Next v3.0.0-next. При наличии recovery SECRETS предпочтительнее восстановить готовый filebrowser.db, а не создавать его заново.

21.3. systemd unit

sudo nano /etc/systemd/system/filebrowser.service
# ============================================================
# SMR-SRV-OT-SHARE
# File Browser Next
#
# Web-доступ к данным MAIN-DATA.
# Сервис работает от отдельной системной учётной записи
# filebrowser и получает права через группу admins / POSIX ACL.
# ============================================================

[Unit]
Description=File Browser Next
After=network.target
RequiresMountsFor=/srv/main-data

[Service]
Type=simple
User=filebrowser
Group=filebrowser
SupplementaryGroups=admins
WorkingDirectory=/var/lib/filebrowser

# Не допускаем запуск на пустом каталоге /srv/main-data,
# если production Btrfs по какой-либо причине не смонтирован.
ExecStartPre=/usr/bin/mountpoint -q /srv/main-data

ExecStart=/usr/local/bin/filebrowser \
    -d /var/lib/filebrowser/filebrowser.db

Restart=on-failure
RestartSec=5s
NoNewPrivileges=yes
PrivateTmp=yes

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now filebrowser

sudo systemctl status filebrowser --no-pager
sudo ss -ltnp | grep ':28443'

curl -s -o /dev/null -w '%{http_code}\n' \
  http://10.44.100.229:28443/

Ожидаемый HTTP code: 200.

22. DNS/DDNS многосетевого файлового сервера

Авторитетный BIND9 — отдельный инфраструктурный сервис и имеет собственную подробную статью: Установка DNS сервера BIND9 на Ubuntu Server 24.04 с Webmin в Proxmox . Этот runbook не дублирует весь DNS-сервер, но фиксирует необходимые для SHARE зависимости и все команды на стороне SHARE.

От BIND требуются:

  • authoritative zone ot-samara.baltika.ru;
  • reverse zones для 10.255, 172.16.66/67, 68/69, 70/71 и 172.17.14;
  • DNS IP: 10.255.0.53, 172.16.66.53, 172.16.68.53, 172.16.70.53, 172.17.14.53;
  • RFC2136 updates от соответствующих OT-сетей.

22.1. Сегментные имена SHARE

Сегмент FQDN A
Legacy smr-srv-ot-share.legacy.ot-samara.baltika.ru 10.255.0.229
Pivo smr-srv-ot-share.pivo.ot-samara.baltika.ru 172.16.66.229
Rozliv smr-srv-ot-share.rozliv.ot-samara.baltika.ru 172.16.68.229
Utilities smr-srv-ot-share.utilities.ot-samara.baltika.ru 172.16.70.229
ASTUE smr-srv-ot-share.astue.ot-samara.baltika.ru 172.17.14.229

22.2. /usr/local/sbin/ot-ddns-register

sudo tee /usr/local/sbin/ot-ddns-register >/dev/null <<'SCRIPT_EOF'
#!/usr/bin/env bash

# ============================================================
# SMR-SRV-OT-SHARE
# Регистрация интерфейсных DNS-имён многосетевого сервера
# в авторитетном BIND9 через RFC 2136 / nsupdate.
# ============================================================

set -euo pipefail

TTL=3600
ZONE="ot-samara.baltika.ru."

register_dns()
{
    local iface="$1"
    local ip="$2"
    local dns="$3"
    local fqdn="$4"
    local ptr="$5"

    echo
    echo "============================================================"
    echo "Interface : $iface"
    echo "IP        : $ip"
    echo "DNS       : $dns"
    echo "FQDN      : $fqdn"
    echo "============================================================"

    if ! ip -4 addr show dev "$iface" | grep -qw "$ip"; then
        echo "ERROR: IP $ip отсутствует на интерфейсе $iface"
        return 1
    fi

    route_info="$(ip route get "$dns" | head -n1)"
    echo "Route     : $route_info"

    if ! grep -qw "src $ip" <<< "$route_info"; then
        echo "ERROR: запрос к $dns пойдёт не с source IP $ip"
        return 1
    fi

    nsupdate <<EOF
server $dns
zone $ZONE
update delete $fqdn A
update add $fqdn $TTL A $ip
send
EOF

    nsupdate <<EOF
server $dns
update delete $ptr PTR
update add $ptr $TTL PTR $fqdn
send
EOF

    result_a="$(dig +short +norecurse @"$dns" "$fqdn" A | head -n1)"

    if [[ "$result_a" != "$ip" ]]; then
        echo "ERROR: A verification failed: '$result_a'"
        return 1
    fi

    result_ptr="$(dig +short +norecurse @"$dns" -x "$ip" | head -n1)"

    if [[ "$result_ptr" != "$fqdn" ]]; then
        echo "ERROR: PTR verification failed: '$result_ptr'"
        return 1
    fi

    echo "OK: A   $fqdn -> $ip"
    echo "OK: PTR $ip -> $fqdn"
}

# Legacy OT / VLAN 1.
register_dns \
    "ens23" \
    "10.255.0.229" \
    "10.255.0.53" \
    "smr-srv-ot-share.legacy.ot-samara.baltika.ru." \
    "229.0.255.10.in-addr.arpa."

# Пивопроизводство. Пока 172.16.66.229 находится на ens23.
# После миграции VLAN2030 заменить ens23 на ens21.
register_dns \
    "ens23" \
    "172.16.66.229" \
    "172.16.66.53" \
    "smr-srv-ot-share.pivo.ot-samara.baltika.ru." \
    "229.66.16.172.in-addr.arpa."

# Розлив / VLAN2002.
register_dns \
    "ens20" \
    "172.16.68.229" \
    "172.16.68.53" \
    "smr-srv-ot-share.rozliv.ot-samara.baltika.ru." \
    "229.68.16.172.in-addr.arpa."

# Утилиты / VLAN2040.
register_dns \
    "ens22" \
    "172.16.70.229" \
    "172.16.70.53" \
    "smr-srv-ot-share.utilities.ot-samara.baltika.ru." \
    "229.70.16.172.in-addr.arpa."

# АСТУЭ / VLAN2006.
register_dns \
    "enp2s1" \
    "172.17.14.229" \
    "172.17.14.53" \
    "smr-srv-ot-share.astue.ot-samara.baltika.ru." \
    "229.14.17.172.in-addr.arpa."

echo
echo "============================================================"
echo "OT DDNS registration completed successfully."
echo "============================================================"
SCRIPT_EOF

sudo chown root:root /usr/local/sbin/ot-ddns-register
sudo chmod 0755 /usr/local/sbin/ot-ddns-register

22.3. systemd unit DDNS

sudo tee /etc/systemd/system/ot-ddns-register.service >/dev/null <<'UNIT_EOF'
[Unit]
Description=Register SMR-SRV-OT-SHARE DNS records in OT BIND9
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/ot-ddns-register

[Install]
WantedBy=multi-user.target
UNIT_EOF

sudo chown root:root /etc/systemd/system/ot-ddns-register.service
sudo chmod 0644 /etc/systemd/system/ot-ddns-register.service

sudo systemctl daemon-reload
sudo systemctl enable ot-ddns-register.service
sudo systemctl start ot-ddns-register.service
sudo systemctl status ot-ddns-register.service --no-pager
sudo journalctl -u ot-ddns-register.service -n 100 --no-pager

22.4. Проверка A/PTR и split DNS

dig @10.255.0.53 smr-srv-ot-share.legacy.ot-samara.baltika.ru A +short
dig @172.16.66.53 smr-srv-ot-share.pivo.ot-samara.baltika.ru A +short
dig @172.16.68.53 smr-srv-ot-share.rozliv.ot-samara.baltika.ru A +short
dig @172.16.70.53 smr-srv-ot-share.utilities.ot-samara.baltika.ru A +short
dig @172.17.14.53 smr-srv-ot-share.astue.ot-samara.baltika.ru A +short

dig @10.255.0.53 -x 10.255.0.229 +short
dig @172.16.66.53 -x 172.16.66.229 +short
dig @172.16.68.53 -x 172.16.68.229 +short
dig @172.16.70.53 -x 172.16.70.229 +short
dig @172.17.14.53 -x 172.17.14.229 +short

resolvectl query smr-srv-ot-share.rozliv.ot-samara.baltika.ru
resolvectl query 172.16.68.229

Для /23 reverse DNS фактический PTR создаётся только в той /24 reverse-zone, куда реально попадает IP. Например 172.16.68.229 → зона 68, а не одновременно 68 и 69.

23. Windows-клиент: штатная смена Samba-учётки

Windows не допускает разные credentials к одному SMB-серверу в одной logon session. Закрытия Explorer часто недостаточно: остаются mappings, IPC$ и сохранённые credentials.

Сохранить следующий файл как change_share.bat:

@echo off
chcp 65001 >nul
title Смена пользователя SMR-SRV-OT-SHARE

echo ============================================================
echo   SMR-SRV-OT-SHARE - отключение текущих SMB-сессий
echo ============================================================
echo.
echo Будут отключены ТОЛЬКО подключения к файловому серверу SHARE.
echo Подключения к другим серверам и сетевым ресурсам не трогаются.
echo.

powershell.exe -NoProfile -ExecutionPolicy Bypass -Command ^
  "$targets = @(" ^
  "'192.168.1.229'," ^
  "'10.44.100.229'," ^
  "'10.255.0.229'," ^
  "'172.16.66.229'," ^
  "'172.16.68.229'," ^
  "'172.16.70.229'," ^
  "'172.17.14.229'," ^
  "'SMR-SRV-OT-SHARE'," ^
  "'SMR-OT-SHARE'," ^
  "'smr-srv-ot-share.legacy.ot-samara.baltika.ru'," ^
  "'smr-srv-ot-share.pivo.ot-samara.baltika.ru'," ^
  "'smr-srv-ot-share.rozliv.ot-samara.baltika.ru'," ^
  "'smr-srv-ot-share.utilities.ot-samara.baltika.ru'," ^
  "'smr-srv-ot-share.astue.ot-samara.baltika.ru'" ^
  ");" ^
  "$mappings = Get-SmbMapping -ErrorAction SilentlyContinue | Where-Object {" ^
  "  $remote = $_.RemotePath;" ^
  "  $targets | Where-Object { $remote -like ('\\' + $_ + '\*') }" ^
  "};" ^
  "if ($mappings) {" ^
  "  $mappings | Sort-Object RemotePath -Unique | ForEach-Object {" ^
  "    Write-Host ('Отключение: ' + $_.RemotePath);" ^
  "    Remove-SmbMapping -RemotePath $_.RemotePath -Force -UpdateProfile -ErrorAction SilentlyContinue" ^
  "  }" ^
  "} else {" ^
  "  Write-Host 'Явных SMB mappings к SHARE не найдено.'" ^
  "}"

echo.
echo Очистка скрытых IPC$ и известных SMB-ресурсов...
echo.

for %%S in (
    192.168.1.229
    10.44.100.229
    10.255.0.229
    172.16.66.229
    172.16.68.229
    172.16.70.229
    172.17.14.229
    SMR-SRV-OT-SHARE
    SMR-OT-SHARE
    smr-srv-ot-share.legacy.ot-samara.baltika.ru
    smr-srv-ot-share.pivo.ot-samara.baltika.ru
    smr-srv-ot-share.rozliv.ot-samara.baltika.ru
    smr-srv-ot-share.utilities.ot-samara.baltika.ru
    smr-srv-ot-share.astue.ot-samara.baltika.ru
) do (
    net use \\%%S\IPC$ /delete /y >nul 2>&1
    net use \\%%S\exchange /delete /y >nul 2>&1
    net use \\%%S\clonezilla /delete /y >nul 2>&1
    net use \\%%S\pivoproduction /delete /y >nul 2>&1
    net use \\%%S\rozliv /delete /y >nul 2>&1
    net use \\%%S\utilities /delete /y >nul 2>&1
    net use \\%%S\software /delete /y >nul 2>&1
    net use \\%%S\documentation /delete /y >nul 2>&1
    net use \\%%S\ot-admin /delete /y >nul 2>&1
)

echo.
echo Удаление сохраненных учетных данных SHARE...
echo.

for %%S in (
    192.168.1.229
    10.44.100.229
    10.255.0.229
    172.16.66.229
    172.16.68.229
    172.16.70.229
    172.17.14.229
    SMR-SRV-OT-SHARE
    SMR-OT-SHARE
    smr-srv-ot-share.legacy.ot-samara.baltika.ru
    smr-srv-ot-share.pivo.ot-samara.baltika.ru
    smr-srv-ot-share.rozliv.ot-samara.baltika.ru
    smr-srv-ot-share.utilities.ot-samara.baltika.ru
    smr-srv-ot-share.astue.ot-samara.baltika.ru
) do (
    cmdkey /delete:%%S >nul 2>&1
)

echo.
echo ============================================================
echo   Готово.
echo.
echo   SMB-сессии к SMR-SRV-OT-SHARE сброшены.
echo   Остальные серверы и сетевые подключения не затронуты.
echo.
echo   Теперь заново открой нужный ресурс и введи другую учетку.
echo ============================================================
echo.
pause

Использование:

  1. Закончить активное копирование/сохранение файлов.
  2. Закрыть Explorer окна SHARE.
  3. Запустить change_share.bat от того же Windows-пользователя.
  4. Открыть нужный UNC снова и ввести новую Samba-учётку.
Не запускать BAT во время активной записи: он принудительно разрывает SMB mappings к SHARE.

24. Recovery-backup критической конфигурации

После полного развёртывания создаётся отдельный recovery-набор: CONFIG + encrypted SECRETS + SHA256. Он не заменяет backup пользовательских данных.

24.1. /usr/local/sbin/share-config-backup

sudo nano /usr/local/sbin/share-config-backup
#!/usr/bin/env bash

# ============================================================
# SMR-SRV-OT-SHARE
# Backup critical server configuration
#
# Output:
#   CONFIG archive  - ordinary configuration and inventory
#   SECRETS archive - Samba/File Browser databases, encrypted
#   SHA256SUMS      - archive integrity
#
# Destination:
#   /srv/main-data/baltikashare/ot-admin/server-backups
# ============================================================

set -Eeuo pipefail
umask 077

HOST="$(hostname -s)"
STAMP="$(date '+%Y%m%d-%H%M%S')"

DEST="/srv/main-data/baltikashare/ot-admin/server-backups"
WORK="$(mktemp -d "/var/tmp/${HOST}-config-backup.XXXXXX")"

CONFIG_ROOT="${WORK}/config"
SECRET_ROOT="${WORK}/secrets"
INVENTORY="${CONFIG_ROOT}/inventory"

CONFIG_ARCHIVE="${DEST}/${HOST}-config-${STAMP}.tar.gz"
SECRET_PLAIN="${WORK}/${HOST}-secrets-${STAMP}.tar.gz"
SECRET_ARCHIVE="${DEST}/${HOST}-secrets-${STAMP}.tar.gz.enc"
SUMS="${DEST}/SHA256SUMS-${STAMP}.txt"

STOPPED_SERVICES=()

cleanup()
{
    for svc in "${STOPPED_SERVICES[@]:-}"
    do
        systemctl start "$svc" >/dev/null 2>&1 || true
    done

    rm -rf "$WORK"
}

trap cleanup EXIT

echo "============================================================"
echo "SMR-SRV-OT-SHARE configuration backup"
echo "Host:        $HOST"
echo "Timestamp:   $STAMP"
echo "Destination: $DEST"
echo "============================================================"
echo

if ! mountpoint -q /srv/main-data
then
    echo "ERROR: /srv/main-data is not mounted."
    exit 1
fi

FS_TYPE="$(findmnt -n -o FSTYPE /srv/main-data 2>/dev/null || true)"

if [[ "$FS_TYPE" != "btrfs" ]]
then
    echo "ERROR: /srv/main-data is '$FS_TYPE', expected btrfs."
    exit 1
fi

install -d \
    -o root \
    -g admins \
    -m 0770 \
    "$DEST"

mkdir -p "$CONFIG_ROOT" "$SECRET_ROOT" "$INVENTORY"

copy_path()
{
    local SRC="$1"

    if [[ -e "$SRC" || -L "$SRC" ]]
    then
        echo "COPY: $SRC"
        cp -a --parents "$SRC" "$CONFIG_ROOT"
    else
        echo "SKIP: $SRC does not exist"
    fi
}

echo
echo "=== Static configuration ==="

copy_path /etc/samba

copy_path /etc/netplan
copy_path /etc/hostname
copy_path /etc/hosts
copy_path /etc/nsswitch.conf
copy_path /etc/fstab

copy_path /etc/systemd/resolved.conf
copy_path /etc/systemd/timesyncd.conf

copy_path /etc/sysctl.conf
copy_path /etc/sysctl.d

copy_path /etc/ufw

copy_path /etc/snapper
copy_path /etc/default/snapper

copy_path /etc/systemd/system/smbd.service.d

copy_path /usr/local/sbin/samba-recycle-cleanup
copy_path /etc/systemd/system/samba-recycle-cleanup.service
copy_path /etc/systemd/system/samba-recycle-cleanup.timer

copy_path /usr/local/sbin/ot-ddns-register
copy_path /etc/systemd/system/ot-ddns-register.service

copy_path /etc/filebrowser
copy_path /etc/systemd/system/filebrowser.service
copy_path /usr/local/bin/filebrowser

copy_path /usr/local/sbin/share-config-backup

copy_path /etc/passwd
copy_path /etc/group

copy_path /etc/ssh/sshd_config
copy_path /etc/ssh/sshd_config.d

copy_path /etc/apt/sources.list
copy_path /etc/apt/sources.list.d

echo
echo "=== Collecting inventory ==="

hostnamectl > "${INVENTORY}/hostnamectl.txt" 2>&1 || true

ip -br link \
    > "${INVENTORY}/ip-link.txt" 2>&1 || true

ip -br address \
    > "${INVENTORY}/ip-address.txt" 2>&1 || true

ip route show table all \
    > "${INVENTORY}/ip-route-all.txt" 2>&1 || true

ip rule show \
    > "${INVENTORY}/ip-rule.txt" 2>&1 || true

resolvectl status \
    > "${INVENTORY}/resolvectl-status.txt" 2>&1 || true

timedatectl \
    > "${INVENTORY}/timedatectl.txt" 2>&1 || true

lsblk -o \
NAME,PATH,SIZE,TYPE,FSTYPE,LABEL,UUID,PARTUUID,MOUNTPOINTS \
    > "${INVENTORY}/lsblk.txt" 2>&1 || true

blkid \
    > "${INVENTORY}/blkid.txt" 2>&1 || true

findmnt \
    > "${INVENTORY}/findmnt.txt" 2>&1 || true

findmnt /srv/main-data \
    > "${INVENTORY}/findmnt-main-data.txt" 2>&1 || true

btrfs filesystem show /srv/main-data \
    > "${INVENTORY}/btrfs-filesystem-show.txt" 2>&1 || true

btrfs filesystem usage /srv/main-data \
    > "${INVENTORY}/btrfs-filesystem-usage.txt" 2>&1 || true

btrfs subvolume list -t /srv/main-data \
    > "${INVENTORY}/btrfs-subvolumes.txt" 2>&1 || true

snapper list-configs \
    > "${INVENTORY}/snapper-list-configs.txt" 2>&1 || true

testparm -s \
    > "${INVENTORY}/samba-testparm.txt" 2>&1 || true

pdbedit -L -v \
    > "${INVENTORY}/samba-users.txt" 2>&1 || true

getent passwd \
    > "${INVENTORY}/passwd-getent.txt"

getent group \
    > "${INVENTORY}/group-getent.txt"

dpkg-query -W \
    -f='${binary:Package}\t${Version}\n' \
    > "${INVENTORY}/packages.txt" 2>&1 || true

apt-mark showmanual \
    > "${INVENTORY}/packages-manual.txt" 2>&1 || true

systemctl is-enabled \
    smbd \
    filebrowser \
    samba-recycle-cleanup.timer \
    ot-ddns-register.service \
    > "${INVENTORY}/services-enabled.txt" 2>&1 || true

systemctl is-active \
    smbd \
    filebrowser \
    samba-recycle-cleanup.timer \
    > "${INVENTORY}/services-active.txt" 2>&1 || true

systemctl list-timers --all \
    > "${INVENTORY}/systemd-timers.txt" 2>&1 || true

ss -lntup \
    > "${INVENTORY}/listening-ports.txt" 2>&1 || true

ufw status verbose \
    > "${INVENTORY}/ufw-status.txt" 2>&1 || true

echo
echo "=== Collecting ACL state ==="

ACL="${INVENTORY}/acl-important.txt"
BASE="/srv/main-data/baltikashare"

: > "$ACL"

for share in \
    clonezilla \
    exchange \
    pivoproduction \
    rozliv \
    utilities \
    software \
    documentation \
    ot-admin
do
    DIR="${BASE}/${share}"

    if [[ -d "$DIR" ]]
    then
        echo >> "$ACL"
        echo "==================================================" >> "$ACL"
        echo "SHARE: $share" >> "$ACL"
        echo "==================================================" >> "$ACL"

        getfacl -p "$DIR" >> "$ACL" 2>&1 || true

        if [[ -d "${DIR}/.recycle" ]]
        then
            echo >> "$ACL"
            echo "--- .recycle ---" >> "$ACL"

            getfacl -p "${DIR}/.recycle" \
                >> "$ACL" 2>&1 || true

            find "${DIR}/.recycle" \
                -mindepth 1 \
                -maxdepth 1 \
                -type d \
                -exec getfacl -p {} + \
                >> "$ACL" 2>&1 || true
        fi
    fi
done

cat > "${CONFIG_ROOT}/RESTORE_ORDER.txt" <<'EOF'
SMR-SRV-OT-SHARE
Recommended restore order
=========================

1. Install Ubuntu Server and required packages.

2. Recreate local users/groups while preserving required UID/GID
   where existing MAIN-DATA files are retained.

3. Restore:
   /etc/netplan
   /etc/fstab
   /etc/hostname
   /etc/hosts
   relevant sysctl/systemd configuration.

4. Mount MAIN-DATA and verify:
   mountpoint /srv/main-data
   findmnt /srv/main-data

5. Restore Snapper configuration.

6. Restore scripts:
   /usr/local/sbin/samba-recycle-cleanup
   /usr/local/sbin/ot-ddns-register

7. Restore systemd units:
   samba-recycle-cleanup.service/timer
   smbd.service.d/main-data.conf
   filebrowser.service
   ot-ddns-register.service

8. Restore /etc/samba/smb.conf.

9. Restore Samba private database from encrypted SECRETS archive
   ONLY with Samba stopped.

10. Restore File Browser database from encrypted SECRETS archive
    ONLY with File Browser stopped.

11. Run:
    systemctl daemon-reload
    testparm -s
    netplan generate

12. Verify POSIX ACL against:
    inventory/acl-important.txt

13. Start and verify services.

14. Check:
    Samba RW/RO matrix
    Snapper Previous Versions
    recycle
    File Browser
    DDNS A/PTR
    split DNS
EOF

echo
echo "=== Creating configuration archive ==="

tar \
    --acls \
    --xattrs \
    --numeric-owner \
    -C "$CONFIG_ROOT" \
    -czf "$CONFIG_ARCHIVE" \
    .

chown root:admins "$CONFIG_ARCHIVE"
chmod 0640 "$CONFIG_ARCHIVE"

echo
echo "============================================================"
echo "Sensitive database backup"
echo
echo "Samba/File Browser will be stopped briefly."
echo "Do not run this script during active file transfers."
echo "============================================================"
echo

for svc in smbd nmbd winbind filebrowser
do
    if systemctl is-active --quiet "$svc"
    then
        echo "STOP: $svc"
        systemctl stop "$svc"
        STOPPED_SERVICES+=("$svc")
    fi
done

if [[ -d /var/lib/samba ]]
then
    cp -a --parents /var/lib/samba "$SECRET_ROOT"
fi

if [[ -f /var/lib/filebrowser/filebrowser.db ]]
then
    cp -a --parents \
        /var/lib/filebrowser/filebrowser.db \
        "$SECRET_ROOT"
fi

sync

for svc in "${STOPPED_SERVICES[@]:-}"
do
    echo "START: $svc"
    systemctl start "$svc"
done

STOPPED_SERVICES=()

tar \
    --acls \
    --xattrs \
    --numeric-owner \
    -C "$SECRET_ROOT" \
    -czf "$SECRET_PLAIN" \
    .

echo
echo "============================================================"
echo "Enter a password for the encrypted SECRETS archive."
echo "IMPORTANT: without this password the archive cannot be restored."
echo "============================================================"
echo

openssl enc \
    -aes-256-cbc \
    -salt \
    -pbkdf2 \
    -iter 200000 \
    -in "$SECRET_PLAIN" \
    -out "$SECRET_ARCHIVE"

rm -f "$SECRET_PLAIN"

chown root:admins "$SECRET_ARCHIVE"
chmod 0640 "$SECRET_ARCHIVE"

(
    cd "$DEST"

    sha256sum \
        "$(basename "$CONFIG_ARCHIVE")" \
        "$(basename "$SECRET_ARCHIVE")"
) > "$SUMS"

chown root:admins "$SUMS"
chmod 0640 "$SUMS"

echo
echo "============================================================"
echo "BACKUP COMPLETED"
echo "============================================================"
echo
echo "CONFIG:"
echo "  $CONFIG_ARCHIVE"
echo
echo "SECRETS:"
echo "  $SECRET_ARCHIVE"
echo
echo "CHECKSUMS:"
echo "  $SUMS"
echo
echo "Copy all three files to an external PC/storage."
echo "============================================================"
sudo chown root:root /usr/local/sbin/share-config-backup
sudo chmod 0750 /usr/local/sbin/share-config-backup

sudo bash -n /usr/local/sbin/share-config-backup

# bash -n при успехе ничего не печатает.

sudo /usr/local/sbin/share-config-backup

Output:

/srv/main-data/baltikashare/ot-admin/server-backups/
  smr-srv-ot-share-config-YYYYMMDD-HHMMSS.tar.gz
  smr-srv-ot-share-secrets-YYYYMMDD-HHMMSS.tar.gz.enc
  SHA256SUMS-YYYYMMDD-HHMMSS.txt

Проверка:

cd /srv/main-data/baltikashare/ot-admin/server-backups
sha256sum -c SHA256SUMS-YYYYMMDD-HHMMSS.txt
Пароль AES-256/PBKDF2 для SECRETS хранить отдельно от архива. Не прикладывать пароль рядом с *.enc в BookStack/общую папку.

Первый фактически сформированный recovery-набор проекта:

smr-srv-ot-share-config-20261110-154701.tar.gz
smr-srv-ot-share-secrets-20261110-154701.tar.gz.enc
SHA256SUMS-20261110-154701.txt

25. Финальная приёмка

25.1. Проверка Proxmox/железа

# Proxmox host:
pveversion -v
qm config 103
qm status 103

ssacli ctrl all show status
ssacli ctrl slot=1 show detail
ssacli ctrl slot=1 show config
ssacli ctrl slot=1 ld all show detail
ssacli ctrl slot=1 pd all show status

25.2. Проверка Ubuntu целиком

echo "===== OS ====="
hostnamectl
cat /etc/os-release
uname -r

echo "===== NETWORK ====="
ip -br link
ip -br address
ip route
ip rule
resolvectl status

echo "===== ROUTING OFF ====="
sysctl net.ipv4.ip_forward
sysctl net.ipv6.conf.all.forwarding
sysctl net.ipv4.conf.all.rp_filter

echo "===== STORAGE ====="
lsblk -o NAME,PATH,SIZE,FSTYPE,LABEL,UUID,MOUNTPOINTS
findmnt /srv/main-data
sudo btrfs filesystem show /srv/main-data
sudo btrfs filesystem usage /srv/main-data
sudo btrfs subvolume list /srv/main-data

echo "===== SNAPPER ====="
sudo snapper list-configs
systemctl is-active snapper-timeline.timer snapper-cleanup.timer

echo "===== SAMBA ====="
sudo testparm -s
sudo systemctl status smbd --no-pager
sudo pdbedit -L
smbclient -L //localhost -U baltikashare

echo "===== AUX SERVICES ====="
systemctl status qemu-guest-agent --no-pager
systemctl status filebrowser --no-pager
systemctl status samba-recycle-cleanup.timer --no-pager
systemctl status ot-ddns-register.service --no-pager
sudo ss -lntup

25.3. Acceptance matrix

Проверка Ожидаемый результат
P411 Controller/Cache/Capacitor OK; FW 6.64; Slot 1
D2700 25/25 physical disks OK; FW 0150
MAIN-DATA RAID50 OK; ~5.46 TiB
Mount /srv/main-data — Btrfs mountpoint
Subvolumes 8 functional + Snapper .snapshots
Snapper 8 configs, timeline/cleanup active
Default route только via 10.44.100.1 dev ens19
Routing IPv4/IPv6 forwarding = 0
Samba 8 shares, testparm OK, smbd active
Recycle 7 shares; private %U + admins
Clonezilla recycle намеренно отсутствует
Previous Versions просмотр/восстановление работает для штатной модели
File Browser 10.44.100.229:28443, HTTP 200
DDNS 5 A/PTR, unit status=0/SUCCESS
RO profile own + shared areas RO, other profiles DENY
Backup CONFIG + SECRETS.enc + SHA256, external copy

25.4. Windows ручная приёмка

  1. Запустить change_share.bat.
  2. Под rozliv_read открыть rozliv, exchange, software, documentation.
  3. Чтение/копирование с сервера должно работать.
  4. Создание файла/папки, rename, delete, copy на сервер — DENY.
  5. pivoproduction, utilities, clonezilla, ot-admin — DENY.
  6. Повторить для pivo_read и utilities_read.

26. Что намеренно НЕ настраивается

  • NFS — не установлен и не опубликован; внедрение отложено до реальной необходимости.
  • Access Based Enumeration — отключён по принятому решению.
  • CRITICAL-BACKUP job — сам RAID создан, но автоматическая production-копия ещё отдельный следующий этап.
  • RECOVERY TFTP/PXE/Cisco — отдельный проект/статья.
  • Dual Domain D2700 — не используется; требуется отдельная оценка второго P411 path/I/O Module B.
  • hpsa_simple_mode=1 — не фиксируется как постоянная production-настройка; вопрос оставлен отдельным.
  • Firewall management — UFW установлен, но окончательный production hardening management interface ещё отдельный этап.

27. Порядок включения и выключения D2700

27.1. Включение

  1. Подать питание на оба БП D2700.
  2. Включить полку и дождаться инициализации.
  3. Включить DL380 G7.
  4. Проверить POST P411 и доступность logical drives.
  5. После загрузки Proxmox выполнить ssacli ctrl slot=1 show config.

27.2. Выключение

  1. Корректно остановить VM 103.
  2. Корректно остановить Proxmox/DL380.
  3. Дождаться полного выключения сервера.
  4. Перевести D2700 в Standby.
  5. При необходимости снять AC с полки.

28. Приложение: восстановление из CONFIG/SECRETS

Если recovery-набор существует, это предпочтительный путь для максимально точного возврата Samba password DB и File Browser DB.

28.1. Проверить контрольные суммы

sha256sum -c SHA256SUMS-YYYYMMDD-HHMMSS.txt

28.2. Расшифровать SECRETS во временный файл

openssl enc -d \
  -aes-256-cbc \
  -pbkdf2 \
  -iter 200000 \
  -in smr-srv-ot-share-secrets-YYYYMMDD-HHMMSS.tar.gz.enc \
  -out /root/smr-srv-ot-share-secrets.tar.gz

mkdir -p /root/share-restore/secrets
tar -xzf /root/smr-srv-ot-share-secrets.tar.gz \
  -C /root/share-restore/secrets

28.3. CONFIG archive

mkdir -p /root/share-restore/config
tar -xzf smr-srv-ot-share-config-YYYYMMDD-HHMMSS.tar.gz \
  -C /root/share-restore/config

find /root/share-restore/config -maxdepth 4 -type f | sort
cat /root/share-restore/config/RESTORE_ORDER.txt

28.4. Restore order

  1. Восстановить сеть, fstab, hostname и systemd configs.
  2. Смонтировать MAIN-DATA.
  3. Воссоздать users/groups, сохраняя UID/GID, если используются существующие данные.
  4. Восстановить Snapper config и локальные scripts.
  5. Восстановить /etc/samba/smb.conf.
  6. Остановить Samba и только затем заменить /var/lib/samba.
  7. Остановить File Browser и восстановить filebrowser.db.
  8. systemctl daemon-reload, testparm -s, netplan generate.
  9. Сверить ACL с inventory/acl-important.txt.
  10. Запустить сервисы и пройти acceptance matrix.
# Пример остановки перед restore state DB:
sudo systemctl stop smbd filebrowser

# Перед заменой обязательно сохранить текущий state:
sudo cp -a /var/lib/samba \
  /root/var-lib-samba-before-restore-$(date +%Y%m%d-%H%M)

sudo cp -a /var/lib/filebrowser/filebrowser.db \
  /root/filebrowser.db-before-restore-$(date +%Y%m%d-%H%M) 2>/dev/null || true

# Затем копировать восстановленные данные из распакованного SECRETS
# с сохранением owner/mode/xattrs/ACL, например rsync -aHAX.
# Конкретный source path проверить через find:
find /root/share-restore/secrets -maxdepth 5 -print

sudo systemctl daemon-reload
sudo systemctl start smbd filebrowser

sudo testparm -s
sudo systemctl status smbd filebrowser --no-pager
После восстановления удалить незашифрованный временный SECRETS archive или хранить его только на защищённом носителе.

29. Справочные материалы и известные границы runbook

29.1. Параметры, которые нужно заполнить по фактическому хосту

  • Proxmox management IP — в исходниках проекта не сохранён как часть этой статьи.
  • Имена Proxmox bridges для семи сетей.
  • Имя Proxmox storage для 100 GiB system disk.
  • Путь ISO storage.

Все остальные production-значения в этом runbook соответствуют текущему состоянию проекта или явно отмечены как реконструированные.


Runbook 1.0. Сформирован 10.11.2026 на основе production BookStack-статьи, проектных логов настройки SMR-SRV-OT-SHARE/D2700 и зафиксированного recovery-процесса. Пароли, персональные реальные логины и secret-ключи намеренно не включены.