Файловая шара - полное развёртывание с нуля до текущего 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. |
/dev/disk/by-id и наличие резервной копии.Содержание
- Целевое состояние
- Подготовка физического сервера и D2700
- Создание аппаратных RAID на P411
- Установка/проверка Proxmox VE
- Создание VM 103
- Установка Ubuntu Server
- Базовые пакеты, время и QEMU Guest Agent
- Сетевая конфигурация и split DNS
- Запрет маршрутизации
- Передача MAIN-DATA в VM
- GPT/Btrfs/mount MAIN-DATA
- Btrfs subvolumes
- Snapper
- Linux-группы и пользователи
- POSIX ACL
- Samba
- Samba recycle и приватные корзины
- Автоматическая очистка recycle
- Защита smbd от отсутствующего MAIN-DATA
- Windows Previous Versions
- File Browser Next
- DNS/DDNS multi-homed сервера
- Windows change_share.bat
- Recovery-backup конфигурации
- Финальная приёмка
- Что намеренно НЕ настраивается
- Порядок включения/выключения
- Приложение: восстановление из 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. Физическая схема
- Установить P411 в PCIe Slot 1. В проекте Slot 5 вызывал IOCK/reset/controller lockup.
- На P411 должен быть исправный 512 MB FBWC. Первоначальный 256 MB cache-модуль был неисправен.
- Подключить внешний SAS:
P411 Port 1E → SFF-8088 → D2700 I/O Module A / P1. - Текущий режим — Single Domain. Второй I/O Module/Dual Domain пока не используется.
- Проверить firmware: P411 =
6.64, D2700/I/O modules =0150.
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
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
Parity Initialization Status: Queued при Status: OK. Это не блокировало эксплуатацию, но параметр остаётся объектом мониторинга.4. Установка и базовая проверка Proxmox VE
Гипервизор — Proxmox VE 9.1.9. Системное хранилище находится на внутреннем P410i; production MAIN-DATA на системный массив не помещается.
На полностью чистом сервере сначала:
- создать внутренний RAID50 на P410i из 8×146 GB;
- установить Proxmox VE 9.1.9 с официального ISO;
- задать FQDN
SMR-SRV-OT-PROXMOX.baltika.local; - настроить management-сеть хоста согласно локальной инфраструктуре.
После установки проверить:
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"
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
~ направляют запросы конкретной прямой/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"
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
*_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
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 многосетевого файлового сервера
От BIND требуются:
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
Использование:
- Закончить активное копирование/сохранение файлов.
- Закрыть Explorer окна SHARE.
- Запустить
change_share.batот того же Windows-пользователя. - Открыть нужный UNC снова и ввести новую Samba-учётку.
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
*.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 ручная приёмка
- Запустить
change_share.bat. - Под
rozliv_readоткрытьrozliv,exchange,software,documentation. - Чтение/копирование с сервера должно работать.
- Создание файла/папки, rename, delete, copy на сервер — DENY.
pivoproduction,utilities,clonezilla,ot-admin— DENY.- Повторить для
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. Включение
- Подать питание на оба БП D2700.
- Включить полку и дождаться инициализации.
- Включить DL380 G7.
- Проверить POST P411 и доступность logical drives.
- После загрузки Proxmox выполнить
ssacli ctrl slot=1 show config.
27.2. Выключение
- Корректно остановить VM 103.
- Корректно остановить Proxmox/DL380.
- Дождаться полного выключения сервера.
- Перевести D2700 в Standby.
- При необходимости снять 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
- Восстановить сеть, fstab, hostname и systemd configs.
- Смонтировать MAIN-DATA.
- Воссоздать users/groups, сохраняя UID/GID, если используются существующие данные.
- Восстановить Snapper config и локальные scripts.
- Восстановить
/etc/samba/smb.conf. - Остановить Samba и только затем заменить
/var/lib/samba. - Остановить File Browser и восстановить
filebrowser.db. systemctl daemon-reload,testparm -s,netplan generate.- Сверить ACL с
inventory/acl-important.txt. - Запустить сервисы и пройти 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
29. Справочные материалы и известные границы runbook
- Текущая production-статья HP D2700 / SMR-SRV-OT-SHARE
- Отдельная полная статья BIND9
- HPE: Configuring Arrays on Smart Array Controllers
- HPE Smart Storage Administrator documentation
- Proxmox VE documentation
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-ключи намеренно не включены.