Дуже рідко бувають проблеми з апгрейдами на Arch Linux, а така ситуація, як сьогодні, в мене за майже 10 років користування системою вперше.
Отже, що трапилось:
встановив апгрейди з pacman -Syu
після установки sudo reboot зависав
ребутнутись вдалось тільки з sudo reboot -f (force)
після ребуту – X.Org запустився, SDDM теж, Plasma теж – але на робочому столі все зависало і не реагувало на клаву і мишку
Дичина якась 🙂
Пофіксити вдалось, хоча так і не зрозумів що саме і чому впало.
Проблема #1: sudo reboot та “Failed to connect to system scope bus via local transport”
Це насправді було першим дзвіночком.
В логах при виконанні sudo reboot з’являлось повідомлення про:
...
Call to Reboot failed: Connection timed out
Failed to connect to system scope bus via local transport: Connection refused
...
Явно проблема з D-Bus/systemd, і саме тому допомогло sudo reboot -f (чи з sudo shutdown -r now, щось з них двох спрацювало), бо в такому випадку systemd не намагається координувати shutdown через system bus і напряму викликає примусовий перезапуск.
Окей – ребутнулись, і тут виникла друга – сама цікава – проблема.
KDE Plasma “висить” після апгрейду і ребуту
Беру в лапки “Plasma висить”, бо проблема все ж була не в ній – але проявлялось саме там.
Отже, ребунувся, залогінився, робочий стіл і сервіси запустились:
Але на цьому – все.
Ніякої реакції на клаву і мишку.
Перша підозра – проблема з KDE Wallet або GNOME Keyring – бо все виглядало так, що зависання виникає після їх старту.
При цьому SSH працював, і я зміг підключатись з іншого ноутбука аби подебажити і пофіксити.
Хоча xorg-server і xorg-xinit вже були, бо я на X11, а не глючному Wayland.
Далі по SSH прибив поточну сесію:
$ sudo systemctl stop sddm
На ноутбуці через Alt + F2 зайшов в консоль (до речі… дивно, що це спрацювало, але ок), і запустив startx.
X11 та xterm завантажились – і тут жеж все знов перестало реагувати на клаву-мишку.
…
Краса…)
А отже, проблема виникає не на рівні Plasma чи конкретного desktop environment, а нижче – на рівні ядра, systemd, input stack та їх ініціалізації.
Fix attempt #3: pacman.log та “Failed to connect to system scope bus via local transport: Connection refused”
Окей – пішов дивитись логи pacman, і ось тут було дещо цікаве:
[2025-12-29T09:33:25+0200] [PACMAN] Running 'pacman -Syu --noconfirm'
[2025-12-29T09:33:25+0200] [PACMAN] synchronizing package lists
[2025-12-29T09:33:28+0200] [PACMAN] starting full system upgrade
...
[2025-12-29T09:33:49+0200] [ALPM] upgraded systemd (258.3-1 -> 259-1)
[2025-12-29T09:33:49+0200] [ALPM-SCRIPTLET] Creating group 'empower' with GID 946.
[2025-12-29T09:35:20+0200] [ALPM-SCRIPTLET] Failed to connect to system scope bus via local transport: Connection refused
[2025-12-29T09:35:20+0200] [ALPM-SCRIPTLET] Failed to connect to system scope bus via local transport: Connection refused
[2025-12-29T09:35:20+0200] [ALPM-SCRIPTLET] :: This is a systemd feature update. You may want to have a look at
[2025-12-29T09:35:20+0200] [ALPM-SCRIPTLET] NEWS for what changed, or if you observe unexpected behavior:
[2025-12-29T09:35:20+0200] [ALPM-SCRIPTLET] /usr/share/doc/systemd/NEWS
[2025-12-29T09:35:20+0200] [ALPM] upgraded polkit (126-2 -> 127-2)
[2025-12-29T09:35:20+0200] [ALPM-SCRIPTLET] Failed to connect to system scope bus via local transport: Connection refused
...
[2025-12-29T09:35:26+0200] [ALPM] running '30-systemd-catalog.hook'...
[2025-12-29T09:35:26+0200] [ALPM] running '30-systemd-daemon-reload-system.hook'...
[2025-12-29T09:35:26+0200] [ALPM-SCRIPTLET] Failed to connect to system scope bus via local transport: Connection refused
[2025-12-29T09:35:26+0200] [ALPM] running '30-systemd-daemon-reload-user.hook'...
[2025-12-29T09:35:27+0200] [ALPM-SCRIPTLET] Failed to connect to system scope bus via local transport: Connection refused
[2025-12-29T09:35:27+0200] [ALPM] running '30-systemd-hwdb.hook'...
[2025-12-29T09:35:27+0200] [ALPM] running '30-systemd-restart-marked.hook'...
[2025-12-29T09:35:27+0200] [ALPM-SCRIPTLET] Failed to connect to system scope bus via local transport: Connection refused
....
[2025-12-29T09:35:30+0200] [ALPM-SCRIPTLET] ==> Building image from preset: /etc/mkinitcpio.d/linux.preset: 'default'
[2025-12-29T09:35:30+0200] [ALPM-SCRIPTLET] ==> Using default configuration file: '/etc/mkinitcpio.conf'
[2025-12-29T09:35:30+0200] [ALPM-SCRIPTLET] -> -k /boot/vmlinuz-linux -g /boot/initramfs-linux.img
...
[2025-12-29T09:35:33+0200] [ALPM-SCRIPTLET] ==> Creating zstd-compressed initcpio image: '/boot/initramfs-linux.img'
[2025-12-29T09:35:33+0200] [ALPM-SCRIPTLET] -> Early uncompressed CPIO image generation successful
[2025-12-29T09:35:33+0200] [ALPM-SCRIPTLET] ==> Initcpio image generation successful
[2025-12-29T09:35:33+0200] [ALPM] running 'dbus-reload.hook'...
[2025-12-29T09:35:33+0200] [ALPM-SCRIPTLET] Failed to connect to system scope bus via local transport: Connection refused
...
І згадуємо першу проблему – що нормальний reboot не працював з тою самою помилкою підключення до системної шини (через /run/dbus/system_bus_socket).
І система не змогла виконати всі hooks, і в результаті після рестарту частина системних сервісів зависала.
Найімовірніше, це торкнулось компонентів, які залежать від udev, logind та input stack. В результаті X.Org і display manager стартували, але пристрої вводу (keyboard/mouse) опинились у неконсистентному стані, без коректної обробки подій.
Final fix: установка ядра linux-lts (але діло не в LTS)
Тут в мене з’явилась підозра, що проблема в ядрі і Intel-дайверах, і вирішив спробувати встановити LTS-ядро:
$ pacman -S linux-lts linux-lts-header
Правда, після встановлення забув зробити grub-mkconfig, а тому в меню GRUB “Advanced options for Arch Linux” нове ядро не з’явилось, і я просто загрузився зі старим.
Але… Саме після цього все запрацювало.
Чому – бо під час встановлення LTS заново запустились всі хуки, бо перший апдейт відбувався в момент оновлення systemd і D-Bus, а другий – у вже стабільному systemd-оточенні:
Коротше – класичний приклад неочевидної “магії” systemd, D-Bus та системних hooks під час апгрейду.
Lessons learned
якщо під час pacman -Syu з’являються помилки systemd/dbus, їх не варто ігнорувати – а я звик, що апгрейди проходять без проблем, і не подивився на pacman output в консолі, а відразу почав ребутати машину
зависання GUI не завжди означає проблему в Destop Environment або Wayland/X11
повторний запуск системних hooks у стабільному середовищі може повністю виправити систему
Прийшов час трохи привести в порядок SSH на самому FreeBSD та на клієнтах – ноутбуках з Arch Linux, бо на домашніх машинках досі використовую парольну аутентифікацію.
Власне, описані нижче налаштування не специфічні ні для FreeBSD, ні для Linux, бо SSH server один і той самий на всіх системах (OpenSSH_9.9p2 на FreeBSD 14.3 і OpenSSH_10.2p1 на Arch Linux).
хост з FreeBSD/NAS: доступний тільки в локальній мережі і VPN, тому SSH brute force не очікується (але при параної або на публічно доступних серверах можна додати Fail2Ban чи SSHGuard)
sshd: друга лінія захисту, базові налаштування доступу
SSH та аутентифікація по ключам
Перше і основне – це налаштувати доступ по ключам замість парольної аутентифікації.
Зробимо це, потім доступ по паролям відключимо взагалі.
На клієнті, ноутбуці з Linux, створюємо ключі:
[setevoy@setevoy-work ~] $ ssh-keygen -t ed25519 -C "setevoy@setevoy"
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/setevoy/.ssh/id_ed25519): /home/setevoy/.ssh/freebsd-nas
Enter passphrase for "/home/setevoy/.ssh/freebsd-nas" (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/setevoy/.ssh/freebsd-nas
Your public key has been saved in /home/setevoy/.ssh/freebsd-nas.pub
Копіюємо на хост з FreeBSD:
[setevoy@setevoy-work ~] $ ssh-copy-id -i /home/setevoy/.ssh/freebsd-nas [email protected]
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/setevoy/.ssh/freebsd-nas.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
([email protected]) Password for setevoy@setevoy-nas:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh -i /home/setevoy/.ssh/freebsd-nas '[email protected]'"
and check to make sure that only the key(s) you wanted were added.
Перевіряємо файл ~/.ssh/authorized_keys на FreeBSD у юзера setevoy:
root@setevoy-nas:/home/setevoy # op account add
Enter your sign-in address (example.1password.com): my.1password.com
Enter the email address for your account on my.1password.com: <ACCOUNT_EMAIL>
Enter the Secret Key for [email protected] on my.1password.com: <SECRET_KEY>
Enter the password for [email protected] at my.1password.com:
Enter your six-digit authentication code: <OTP_CODE>
Now run 'eval $(op signin)' to sign in.
Перевіряємо акаунти:
root@setevoy-nas:/home/setevoy # op account list
SHORTHAND URL EMAIL USER ID
my https://my.1password.com [email protected] 7BS***KMM
Логінимось:
root@setevoy-nas:/home/setevoy # eval $(op signin)
Enter the password for [email protected] at my.1password.com:
І маємо доступ до сікретів.
Наприклад, отримати ключ, який додамо далі:
root@setevoy-nas:/home/setevoy # op item get "FreeBSD NAS SSH"
ID: ulz***4ce
Title: FreeBSD NAS SSH
Vault: Personal (wb7***guq)
Created: 1 week ago
Updated: 1 week ago by Arseny
Favorite: false
Tags: FreeBSD,SSH
Version: 1
Category: LOGIN
Fields:
password: [use 'op item get ulz***4ce --reveal' to reveal]
username: setevoy
Виконуємо ssh nas.setevoy, 1Password запросить підтвердження – пароль самого 1Password:
І тепер все працює через його SSH Agent:
[setevoy@setevoy-work ~] $ ssh nas.setevoy
...
Last login: Sat Dec 27 09:03:15 2025 from 192.168.0.4
FreeBSD 14.3-RELEASE (GENERIC) releng/14.3-n271432-8c9ce319fef7
Welcome to FreeBSD!
...
[setevoy@setevoy-nas ~]$
Як працює SSH Agent
При підключені SSH-клієнт:
читає конфігурацію (~/.ssh/config, опції CLI)
підключається до SSH-агента (у нашому випадку 1Password) і отримує список доступних публічних ключів
по черзі пропонує серверу публічні ключі, які в нього є (з агента і, за відсутності обмежень, з ~/.ssh/)
sshd на сервері дивиться в ~/.ssh/authorized_keys конкретного юзера і перевіряє, чи є там запропонований публічний ключ
коли сервер знаходить збіг, він приймає цей публічний ключ і надсилає клієнту дані для підпису (випадковий набір чисел)
клієнт передає ці дані SSH-агенту, який володіє відповідним приватним ключем
агент створює криптографічний підпис приватним ключем і повертає його клієнту
сервер перевіряє підпис публічним ключем і завершує аутентифікацію
Глянути це можемо з ssh -v user@host:
[setevoy@setevoy-work ~] $ ssh -v [email protected]
debug1: OpenSSH_10.2p1, OpenSSL 3.6.0 1 Oct 2025
debug1: Reading configuration data /home/setevoy/.ssh/config
...
debug1: Connecting to nas.setevoy [192.168.0.2] port 22.
debug1: Connection established.
...
debug1: Authenticating to nas.setevoy:22 as 'setevoy'
...
debug1: Will attempt key: RTFM RSA SHA256:nedb3Qgpkxgu57MRP7/eXShHgw6N6b7SjZ3S1rNyFb4 agent
debug1: Will attempt key: FreeBSD-NAS ED25519 SHA256:ZuJ77z6BNMwra41BiTKDrJSSQrDJ0/u+4wZRCvGJMpA agent
debug1: Will attempt key: /home/setevoy/.ssh/id_rsa
debug1: Will attempt key: /home/setevoy/.ssh/id_ecdsa
debug1: Will attempt key: /home/setevoy/.ssh/id_ecdsa_sk
debug1: Will attempt key: /home/setevoy/.ssh/id_ed25519 ED25519 SHA256:X8L1lCBQz8Bk7K5rMGqiE+tlSthCbgaqK7ryLZ6gVWU
debug1: Will attempt key: /home/setevoy/.ssh/id_ed25519_sk
debug1: Offering public key: RTFM RSA SHA256:nedb3Qgpkxgu57MRP7/eXShHgw6N6b7SjZ3S1rNyFb4 agent
debug1: Authentications that can continue: publickey
debug1: Offering public key: FreeBSD-NAS ED25519 SHA256:ZuJ77z6BNMwra41BiTKDrJSSQrDJ0/u+4wZRCvGJMpA agent
debug1: Server accepts key: FreeBSD-NAS ED25519 SHA256:ZuJ77z6BNMwra41BiTKDrJSSQrDJ0/u+4wZRCvGJMpA agent
Authenticated to nas.setevoy ([192.168.0.2]:22) using "publickey".
...
Last login: Sun Dec 28 13:44:51 2025 from 192.168.0.4
FreeBSD 14.3-RELEASE (GENERIC) releng/14.3-n271432-8c9ce319fef7
Welcome to FreeBSD!
...
[setevoy@setevoy-nas ~]$
І для ключів “Will attempt key: RTFM RSA [...] та FreeBSD-NAS [...] як раз бачимо, що вони були отримані від agent.
SSH Agent та помилка “Too many authentication failures”
Вище бачили, що передається ціла пачка ключів, і якщо їх багато – то сервер може відкинути подальшу аутентифікацію.
Кількість спроб задається на сервері параметром MaxAuthTries (дефолт “6”) в /etc/ssh/sshd_config. Тобто, якщо маємо 7 ключів, і перші 6 не підійшли – то підключитись не зможемо.
Аби вказати SSH клієнту який конкретно приватний ключ з агента використовувати – вказуємо публічний ключ та задаємо IdentitiesOnly:
root@setevoy-nas:/home/setevoy # sshd -T
port 22
addressfamily any
listenaddress 0.0.0.0:22
...
Наприклад, чи дозволений логін root:
root@setevoy-nas:/home/setevoy # sshd -T | grep root
permitrootlogin no
Редагуємо конфіг /etc/ssh/sshd_config, задаємо мінімальні параметри, і нехай буде PermitRootLogin no вказаний тут явно.
Плюс явно вказуємо дозвіл на PubkeyAuthentication та PasswordAuthentication – парольну аутентифікацію відключимо пізніше, коли впевнимось, що все працює:
PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication yes
Перевіряємо синтаксис з sshd -t, якщо все добре – то команда просто нічого не виведе:
...
# allow SSH from Office LAN (192.168.0.0/24) to FreeBSD host
pass in log on em0 proto tcp from 192.168.0.0/24 to (em0) port 22 keep state
# allow SSH from Home network (192.168.100.0/24) to FreeBSD host
pass in log on em0 proto tcp from 192.168.100.0/24 to (em0) port 22 keep state
# allow SSH from VPN clients to FreeBSD host
pass in on wg0 proto tcp from 10.8.0.0/24 to (wg0) port 22 keep state
...
Можна мережі вказати як { 192.168.0.0/24, 192.168.100.0/24, 10.8.0.0/24 }, але для SSH вирішив залишити в такому вигляді, аби було явно видно.
Далі можна додати ще трохи параметрів:
PermitRootLogin no: заборона підключення root, це вже є
PubkeyAuthentication yes: аутентифікація по ключам, теж вже є
PasswordAuthentication yes: пароль аутентифікація, поки залишаємо включеною
ChallengeResponseAuthentication no: вимикаємо keyboard-interactive аутентифікацію через PAM, яка не потрібна без 2FA
тут ще нюанс в тому, що навіть якщо відключити PasswordAuthentication, але ChallengeResponseAuthentication буде “yes” і при UsePAM = “yes” – то система все одно може запросити парольну аутентифікацію
AuthorizedKeysFile .ssh/authorized_keys: це дефолтне значення, але задамо явно
Перевіряємо ще раз з sshd -t, виконуємо reload, перевіряємо підключення з ключем з клієнта.
Якщо все ОК – то додаємо ще трохи тюнингу:
PasswordAuthentication no: тепер відключаємо парольну аутентифікацію
AuthenticationMethods publickey: явно заборонити всі механізми окрім ключів
MaxSessions 2: максимальна кількість активних сесій на одне TCP-підключення
LoginGraceTime 30: кількість секунд, за яку клієнт має пройти аутентифікацію
Для ще більшої безпеки – можна налаштувати інший порт замість 22 (наприклад, задати Port 2222), обмежити IP, на яких слухає sshd (параметр ListenAddress 192.168.0.2), і навіть налаштувати Two-factor Authentication For SSH – але для домашнього сервера з доступом тільки з локальних мереж це вже overkill.
Продовжуємо налаштовувати домашній NAS на FreeBSD.
Власне, NAS – Network System, і хочеться мати до нього доступ з інших девайсів – з Linux та Windows хостів, з телефонів, телевізорів.
Тут у нас на вибір дві основні опції – Samba та NFS. Можна, звісно, згадати і sshfs – але це рішення точно не для домашньої мережі (хоча простіше).
Я для себе вирішив для Windows (на ігровому ПК), Android телефонів та Android TV зробити доступ з Samba share – а NFS буде чисто для Linux систем для бекапів.
В цьому пості налаштуємо Samba на FreeBSD, а в наступному – додамо NFS.
без можливості логіна в систему – /usr/sbin/nologin
але якщо планується виконувати локальні операції від юзера – то створюємо з -s /bin/sh, а SSH все одно буде заблокований в sshd config (про SSH буде окремим постом)
і з -g smbshare вказуємо primary group
Перевіряємо юзера:
root@setevoy-nas:/home/setevoy # id smbshare
uid=1004(smbshare) gid=1004(smbshare) groups=1004(smbshare)
І права доступу до каталога тепер – можна глянути розширену інформацію з getfacl, аби отримати POSIX/NFSv4 ACL, які ZFS використовує замість класичних Unix-бітів:
Samba буде встановлювати власні права доступу, тому в параметрах ZFS ACL треба переключити режими.
Samba керує правами доступу самостійно, тому для ZFS ACL потрібно увімкнути режими passthrough, щоб ZFS не змінював ACL.
Перевіряємо параметри датасету зараз:
root@setevoy-nas:/home/setevoy # zfs get acltype,aclmode,aclinherit nas/share
NAME PROPERTY VALUE SOURCE
nas/share acltype nfsv4 default
nas/share aclmode discard default
nas/share aclinherit restricted default
Задаємо aclmode та aclinherit в значення passthrough – тоді ZFS буде використовувати ті права, які задає Samba:
root@setevoy-nas:/home/setevoy # zfs set aclmode=passthrough nas/share
root@setevoy-nas:/home/setevoy # zfs set aclinherit=passthrough nas/share
Перевіряємо тепер:
root@setevoy-nas:/home/setevoy # zfs get acltype,aclmode,aclinherit nas/share
NAME PROPERTY VALUE SOURCE
nas/share acltype nfsv4 default
nas/share aclmode passthrough local
nas/share aclinherit passthrough local
На відміну від acltype=off, який повністю вимикає ACL і залишає лише POSIX-права, режим passthrough дозволяє Samba керувати ACL без втручання ZFS.
Надалі зміни в директорії /nas/share краще виконувати від імені smbshare або root, а не звичайного користувача, щоб не порушувати модель прав доступу Samba.
Або більше детально, просто явно вказуємо деякі дефолтні опції:
[global]
workgroup = WORKGROUP
security = user
[shared]
path = /nas/share
read only = no
browseable = yes
valid users = @smbshare
create mask = 0660
directory mask = 2770
Перевіряємо синтаксис:
root@setevoy-nas:/home/setevoy # testparm
Load smb config files from /usr/local/etc/smb4.conf
Loaded services file OK.
Weak crypto is allowed
Server role: ROLE_STANDALONE
Press enter to see a dump of your service definitions
# Global parameters
[global]
security = USER
idmap config * : backend = tdb
[shared]
create mask = 0660
directory mask = 02770
path = /nas/share
read only = No
valid users = @smbshare
Додаємо Samba-юзера:
root@setevoy-nas:/home/setevoy # smbpasswd -a smbshare
New SMB password:
Retype new SMB password:
Added user smbshare.
smbpasswd збереже його пароль в /var/db/samba4/private/passdb.tdb.
Запускаємо сервіс:
root@setevoy-nas:~ # service samba_server start
Performing sanity check on Samba configuration: OK
Starting nmbd.
Starting smbd.
root@setevoy-nas:/home/setevoy # smbclient //localhost/shared -U smbshare
Password for [WORKGROUP\smbshare]:
Try "help" to get a list of possible commands.
smb: \>
Робимо list files – поки тут пусто:
smb: \> ls
. D 0 Fri Dec 26 15:46:55 2025
.. D 0 Fri Dec 26 15:46:55 2025
3771191492 blocks of size 1024. 3771191396 blocks available
Копіюємо щось із системи:
smb: \> put /etc/hosts hosts.test
putting file /etc/hosts as \hosts.test (252.7 kb/s) (average 252.7 kb/s)
smb: \> ls hosts.test
hosts.test A 1035 Fri Dec 26 16:14:29 2025
3771191480 blocks of size 1024. 3771191380 blocks available
І тепер маємо файл в /nas/share:
root@setevoy-nas:/home/setevoy # ls -l /nas/share
total 5
-rw-rw---- 1 smbshare smbshare 1035 Dec 26 16:14 hosts.test
cifs-utils: утиліти для роботи з Samba share через CIFS:
mount -t cifs
запису в /etc/fstab
systemd automount
Перевіряємо підключення з Linux до Samba на FreeBSD:
[setevoy@setevoy-work ~] $ smbclient //192.168.0.2/shared -U smbshare
Can't load /etc/samba/smb.conf - run testparm to debug it
Password for [WORKGROUP\smbshare]:
Try "help" to get a list of possible commands.
smb: \>
Ще раз list – все на місці:
smb: \> ls hosts.test
hosts.test A 1035 Fri Dec 26 16:14:29 2025
3771191480 blocks of size 1024. 3771191380 blocks available
Налаштування /etc/fstab та systemd-automount
Якщо хочемо, аби шара підключалась автоматично – налаштуємо /etc/fstab та systemd-automount.
На клієнті створюємо каталог, в який буде підключатись /nas/share з серверу:
Через файл mnt-nas\x2dshared.automount systemd відстежує доступ до /mnt/nas-shared, і як тільки ми виконаємо якусь дію (наприклад, cd /mnt/nas-shared) – systemd виконає mnt-nas\x2dshared.mount, який власне підключить розділ.
57.2 MB/s, тобто 457.6 Mbit/s. При тому, що ноут зараз на WiFi – цілком нормальна швидкість.
Підключення Samba клієнтів
І приклади того, як до Samba підключитись з різних девайсів.
Samba та KDE Dolphin
Взагалі, ми вже зробили automount через systemd, але можна відкрити напряму з KDE Dolphin file manager – вказуємо адресу smb://192.168.0.2, вводимо логін-пароль:
root@setevoy-nas:~ # camcontrol devlist
<Samsung SSD 870 EVO 4TB SVT03B6Q> at scbus0 target 0 lun 0 (pass0,ada0)
<Samsung SSD 870 EVO 4TB SVT03B6Q> at scbus1 target 0 lun 0 (pass1,ada1)
<CT500P310SSD8 VACR001> at scbus7 target 0 lun 1 (pass3,nda0)
Тут в мене ada1 та ada2 – це SATA-диски для самого NAS storage, а nda0 – NVMe, на якому встановлена система (там зараз UFS, але потім, скоріш за все, перевстановлю із ZFS теж).
Імена пристроїв на кшталт /dev/ada0, /dev/ada1 або /dev/nda0 можуть змінюватися залежно від порядку підключення дисків, а тому напряму використовувати їх у ZFS не рекомендується – далі створимо власні GPT labels.
Створення GPT tables
Про всяк випадок – видаляємо існуючі таблиці розділів.
ВАЖЛИВО: таблиця розділів буде знищена. Дані залишаються на носії, але стануть недоступними.
В моєму випадку – диски нові, тому бачимо помилку “Invalid argument“:
Для подальшої роботи з дисками створимо постійні GPT lables – іменовані ідентифікатори розділів, які зберігаються в GPT-таблиці розділів і читаються при старті системи.
Вони не змінюються після reboot, не залежать від порядку SATA портів, не залежать від того, як ядро виявило диск.
root@setevoy-nas:~ # glabel status
Name Status Components
gpt/zfs_disk1 N/A ada0p1
gptid/67ebfac9-dcce-11f0-98bf-00d861f3bff0 N/A ada0p1
diskid/DISK-S758NX0Y701757D N/A ada0
gpt/zfs_disk2 N/A ada1p1
gptid/6a9c3ee5-dcce-11f0-98bf-00d861f3bff0 N/A ada1p1
diskid/DISK-S758NX0Y701756A N/A ada1
Диски готові – переходимо до ZFS, нам треба:
налаштувати ZFS pool з mirror
створити datasets
подивитись на шифрування даних
перевірити, як працювати зі snapshots
І в кінці окремо поговоримо про моніторинг дисків та ZFS pool.
Створення ZFS mirror pool
Використовуємо такі параметри:
ashift=12: розмір фізичного сектора, який ZFS використовує для I/O
розмір сектора визначається як 2^ashift байт, тобто 2¹² = 4096 байт
замінити значення ashift після створення Pool не можна
atime=off: вимикаємо оновлення часу доступу до файлів, зменшує кількість зайвих записів на диск
compression=lz4: швидка компресія даних на диску з мінімальним CPU-оверхедом
xattr: налаштування зберігання атрибутів файлі:
xattr=on: старий і default варіант, extended attributes зберігаються як окремі приховані файли
xattr=sa: xattr зберігаються безпосередньо в dnode файлу (аналог inode в UFS/ext4), без створення окремих прихованих файлів – менше звернень до диска і краща продуктивність
mirror: використовуємо ZFS mirror (аналог RAID1) – дані синхронно записуються на обидва диски (див. також vdev)
root@setevoy-nas:~ # zpool status
pool: nas
state: ONLINE
config:
NAME STATE READ WRITE CKSUM
nas ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
gpt/zfs_disk1 ONLINE 0 0 0
gpt/zfs_disk2 ONLINE 0 0 0
errors: No known data errors
Додаємо підключення при ребутах:
root@setevoy-nas:/home/setevoy # sysrc zfs_enable=YES
zfs_enable: NO -> YES
Створення ZFS datasets
ZFS dataset – це окрема файлова система всередині ZFS pool, яка має власні властивості (compression, quota, mountpoint тощо) і керується незалежно від інших datasets.
Зараз у нас один, корневий dataset:
root@setevoy-nas:~ # zfs list
NAME USED AVAIL REFER MOUNTPOINT
nas 420K 3.51T 96K /nas
Корінь pool – це технічний корінь, а не місце для даних, тому створимо кілька окремих.
Не факт, що в мене датасети залишаться такими і надалі, але просто як ідея того, як можна розділити простір на дисках:
nas/data: основний dataset для збереження всяких даних типу музики-фільмів
nas/backups: сюди можна буде копіювати якісь periodic бекапи з робочого і домашнього ноутбуків
nas/private: зашифрований розділ для приватних даних та/або баз даних типу KeePass або бекапів 1Password
nas/shared: загальнодоступний dataset для доступу з телефонів і ноутбуків через Samba share (про налаштування Samba – окремим постом, вже є в чернетках)
Створюємо новий датасет з іменем nas/data:
root@setevoy-nas:~ # zfs create nas/data
Перевіряємо список ще раз:
root@setevoy-nas:~ # zfs list
NAME USED AVAIL REFER MOUNTPOINT
nas 540K 3.51T 96K /nas
nas/data 96K 3.51T 96K /nas/data
І каталог цього датасету:
root@setevoy-nas:~ # ls -la /nas/data/
total 1
drwxr-xr-x 2 root wheel 2 Dec 19 13:41 .
drwxr-xr-x 3 root wheel 3 Dec 19 13:41 ..
Він жеж має власну точку підключення:
root@setevoy-nas:/home/setevoy # mount | grep data
nas/data on /nas/data (zfs, local, noatime, nfsv4acls)
Яка задана у властивостях датасету:
root@setevoy-nas:/home/setevoy # zfs get mountpoint nas/data
NAME PROPERTY VALUE SOURCE
nas/data mountpoint /nas/data default
Додамо ще один:
root@setevoy-nas:~ # zfs create nas/backups
Ще раз список:
root@setevoy-nas:~ # zfs list
NAME USED AVAIL REFER MOUNTPOINT
nas 696K 3.51T 96K /nas
nas/backups 96K 3.51T 96K /nas/backups
nas/data 96K 3.51T 96K /nas/data
Шифрування dataset
Хочеться мати окремий dataset для чутливих даних, тому глянемо, як це зроблено у ZFS.
включити шифрування можна тільки створенні dataset – але можна відключити після
якщо батьківський dataset не зашифрований – дочірній можна зашифрувати
якщо батьківський зашифрований – дочірні успадковують його шифрування
Створимо новий dataset, вказуємо, що шифрується з паролем:
root@setevoy-nas:~ # zfs create -o encryption=on -o keyformat=passphrase -o keylocation=prompt nas/private
Enter new passphrase:
Re-enter new passphrase:
Перевіряємо цей датасет:
root@setevoy-nas:~ # zfs get encryption,keyformat,keylocation nas/private
NAME PROPERTY VALUE SOURCE
nas/private encryption aes-256-gcm -
nas/private keyformat passphrase -
nas/private keylocation prompt local
Як це буде виглядати після reboot:
pool nas імпортується
nas/private буде заблокований, і mountpoint не зʼявиться, доки вручну не ввести пароль і не розблокувати його
Для розблокування потім використовуємо:
root@setevoy-nas:~ # zfs load-key nas/private
root@setevoy-nas:~ # zfs mount nas/private
На відміну від, наприклад, шифрування розділів з LUKS – ZFS дозволяє змінювати пароль для зашифрованого dataset без перешифрування даних. Фактично змінюється лише ключ, який захищає основний ключ шифрування.
Якщо хочемо змінити пароль – виконуємо zfs change-key:
root@setevoy-nas:~ # zfs change-key nas/private
Enter new passphrase for 'nas/private':
Замість використання паролю можемо створити файл-ключ, який буде використаний про ребутах для підключення датасету.
root@setevoy-nas:/home/setevoy # zfs get encryption,keyformat,keylocation nas/private
NAME PROPERTY VALUE SOURCE
nas/private encryption aes-256-gcm -
nas/private keyformat raw -
nas/private keylocation file:///root/nas-private-pass.key local
Ребутаємо машину:
root@setevoy-nas:/home/setevoy # shutdown -r now
Shutdown NOW!
shutdown: [pid 13519]
І потім перевіряємо розділи:
root@setevoy-nas:/home/setevoy # mount | grep nas
nas on /nas (zfs, local, noatime, nfsv4acls)
nas/backups on /nas/backups (zfs, local, noatime, nfsv4acls)
nas/data on /nas/data (zfs, local, noatime, nfsv4acls)
Та з zfs list:
root@setevoy-nas:/home/setevoy # zfs list
NAME USED AVAIL REFER MOUNTPOINT
nas 200G 3.32T 112K /nas
nas/backups 200K 500G 104K /nas/backups
nas/data 96K 3.51T 96K /nas/data
nas/private 200K 3.32T 200K /nas/private
Налаштування dataset quotas
ZFS datasets підтримують задання квот на розмір датасетів – тобто, максимальний розмір, який він може займати.
Зручно, наприклад, аби якийсь backups/ випадково не забив весь диск.
Є ще dataset reservation, але про це трохи далі.
Задамо ліміт в 500 гігабайт на nas/backups:
root@setevoy-nas:~ # zfs set quota=500G nas/backups
Перевіряємо:
root@setevoy-nas:~ # zfs get quota,used,available nas/backups
NAME PROPERTY VALUE SOURCE
nas/backups quota 500G local
nas/backups used 96K -
nas/backups available 500G -
Налаштування dataset reservation
ZFS дозволяє зарезервувати місце для датасету, гарантуючи доступний простір незалежно від заповненості пулу.
Важливо, що ZFS Reservation резервує місце незалежно від того, використовується воно чи ні.
Задамо мінімально доступний розмір основного датасету nas/data:
root@setevoy-nas:~ # zfs set reservation=200G nas/data
Перевіряємо:
root@setevoy-nas:~ # zfs get reservation,used,available nas/data
NAME PROPERTY VALUE SOURCE
nas/data reservation 200G local
nas/data used 96K -
nas/data available 3.51T -
Аби змінити reservation чи видалити – просто раз виконуємо zfs set з новим значенням:
root@setevoy-nas:~ # zfs set reservation=100G nas/data
root@setevoy-nas:~ # zfs set reservation=none nas/data
Використання ZFS snapshots
ZFS Snapshots – це миттєві read-only знімки стану dataset, які дозволяють швидко відкотитись до попереднього стану.
ZFS працює за принципом COW (Copy On Write), тобто при зміні в блоках даних – зміни робляться в новому блоку, а старі блоки не перезаписуються, поки на них є активні посилання
при створенні снапшоту ZFS не копіює дані, а створює таке посилання на цей блок
далі, коли ми робимо зміни в даних датасета, для якого є снапшот – то зміни на диску робляться в нових блоках даних, а доступ до старих зберігається через снапшот
root@setevoy-nas:/home/setevoy # zfs list -t snapshot nas/data@test-snap
NAME USED AVAIL REFER MOUNTPOINT
nas/data@test-snap 0B - 104K -
Його атрибути:
root@setevoy-nas:/home/setevoy # zfs get creation,used,referenced nas/data@test-snap
NAME PROPERTY VALUE SOURCE
nas/data@test-snap creation Sat Dec 20 15:46 2025 -
nas/data@test-snap used 0B -
nas/data@test-snap referenced 104K -
Відновлення зі snapshot
Снапшоти зберігаються в каталозі .zfs датасета – /nas/data/.zfs/snapshot/:
root@setevoy-nas:/home/setevoy # ll /nas/data/.zfs/snapshot/test-snap/
total 1
-rw-r--r-- 1 root wheel 10 Dec 20 15:45 test-snap.txt
І звідси ми можемо отримати доступ і до нашого тестового файлу:
root@setevoy-nas:/home/setevoy # rm /nas/data/test-snap.txt
root@setevoy-nas:/home/setevoy # file /nas/data/test-snap.txt
/nas/data/test-snap.txt: cannot open `/nas/data/test-snap.txt' (No such file or directory)
Аби відновити зі снапшоту – можна або просто скопіювати з каталога /nas/data/.zfs/snapshot/test-snap/ з cp, або, якщо треба відкотити весь датасет, то використати zfs rollback – але в такому разі всі зміни, які були зроблені після створення снапшоту будуть втрачені:
Ну і ZFS Boot Environments працюють через ті самі снапшоти – під час виконання freebsd-update install автоматично створюється копія даних, на яку можна відкотитись в разі проблем.
Взагалі ZFS Boot Environments дуже цікава штука, може, окремо про неї напишу.
Замість повного rollback – аби не перезаписувати дані на поточному датасеті – можна зробити клонування снапшоту в новий датасет:
root@setevoy-nas:/home/setevoy # cat /usr/local/etc/periodic/daily//402.zfSnap
#!/bin/sh
# If there is a global system configuration file, suck it in.
#
if [ -r /etc/defaults/periodic.conf ]; then
. /etc/defaults/periodic.conf
source_periodic_confs
fi
...
Включити запуск скриптів можна в файлі /etc/periodic.conf (або, краще, /etc/periodic.conf.local):
Запустити виконання всіх daily задач, для яких в /etc/defaults/periodic.conf задано “YES” – з командою periodic:
root@setevoy-nas:/home/setevoy # periodic daily
І тепер нас є новий снапшот:
root@setevoy-nas:/home/setevoy # zfs list -t snapshot
NAME USED AVAIL REFER MOUNTPOINT
...
nas/data@daily-2025-12-21_16.41.03--1w 0B - 104K -
Моніторинг ZFS
Для моніторингу у нас є цілий набір утиліт – як дефолтні від самої файлової системи, так і додаткові, які можна встановити окремо.
Є класний документ Monitoring ZFS, хоча і 2017 року, але все ще актуальний.
З основного, чим можемо користуватись і що бажано моніторити:
SMART: перевірка самих дисків
zpool status: перевірка, що нема проблем на самих ZFS pools
zfs scrub: не зовсім про моніторинг, але може показати проблеми
zpool events: події пулів
arcstats: корисно перевіряти ефективність роботи кешу ZFS
Перевірка S.M.A.R.T. для SSD
Диски зовсім нові, але just in case і на майбутнє – налаштуємо S.M.A.R.T. (Self-Monitoring, Analysis, and Reporting Technology).
Встановлюємо пакет:
root@setevoy-nas:~ # pkg install smartmontools
І перевіряємо стан дисків:
root@setevoy-nas:~ # smartctl -a /dev/ada0
root@setevoy-nas:~ # smartctl -a /dev/ada1
Нам цікаві в основному такі показники:
SMART overall-health self-assessment test result: PASSED: перевірку пройдено (але зазвичай FAILED тут з’являється, коли все зовсім погано)
помилки:
Reallocated_Sector_Ct:
кількість битих секторів, які диск знайшов і замінив резервними
0 – ідеально, зростання цього значення – поганий сигнал
Runtime_Bad_Block:
кількість некоректних блоків, знайдених під час нормальної роботи або деградацію лінка (наприклад, падіння швидкості SATA)
0 – ідеально, зростання цього значення – поганий сигнал
Uncorrectable_Error_Cnt:
кількість помилок читання/запису, які неможливо було виправити
0 – обов’язково має залишатися, зростання – вже серйозна проблема
Wear/Used reserve:
Wear_Leveling_Count:
показує знос елементів памʼяті SSD
0 означає, що диск практично новий або знос мінімальний
Used_Rsvd_Blk_Cnt_Tot:
скільки резервних блоків уже використано для заміни зношених
0 – ідеальний стан
Power_On_Hours:
кількість годин, протягом яких диск був увімкнений
83 години – диски тільки нещодавно купив і підключив
CRC_Error_Count:
кількість помилок передачі даних між диском і контролером (кабель, порт)
0 – норма, зростання часто означає проблеми з кабелем, а не з самим диском
Total_LBAs_Written / Host_Writes / NAND_Writes:
скільки даних реально записано на диск
порівнюємо зі TBW (Total Bytes Written) від виробника
в моєму випадку Total_LBAs_Written = 77982, де LBA – це Logical Block Address, який зазвичай SMART рахує по 512 байт, тобто записано на диск ~40 мегабайт – при заявлених Samsung 2400 TB
Temperature / Temperature_Celsius / Airflow_Temperature_Cel
температура дисків, в мене зараз 28 градусів
SMART Periodic
Для запуска SMART є власні скрипти в /usr/local/etc/periodic.
Аби включити перевірку і репорти – додаємо запуск smartd в автостарт:
І налаштовуємо periodic в /etc/periodic.conf.local.
Аби включити перевірку – треба явно задати диски, по яким цю перевірку робити:
daily_status_smart_devices="/dev/ada0 /dev/ada1"
Результат в репорті:
ZFS Pool Status
zpool status вже запускали вище, тепер додамо запуск по крону і відправку повідомлень.
Скрипти для ZFS /etc/periodic/daily/, наприклад /etc/periodic/daily/404.status-zfs.
Додаємо до /etc/periodic.conf.local:
daily_status_zfs_enable="YES"
Запускаємо скрипти:
root@setevoy-nas:/home/setevoy # periodic daily
Отримуємо листа:
root@setevoy-nas:/home/setevoy # mail -u root
Mail version 8.1 6/6/93. Type ? for help.
"/var/mail/root": 12 messages 4 new 12 unread
...
N 12 root@setevoy-nas Sun Dec 21 17:20 82/3368 "setevoy-nas daily run output"
&
Під час scrub ZFS порівнює checksum кожного блоку зі збереженим значенням і у разі виявлення помилки система фіксує її в логах та, за наявності mirror, автоматично відновлює дані з другої копії.
Оскільки для цього виконується багато I/O операцій, тому не варто запускати scrubbing часто – раз на місяць буде достатньо.
Запускаємо вручну:
root@setevoy-nas:/home/setevoy # zpool scrub nas
І перевіряємо в zpool status:
root@setevoy-nas:/home/setevoy # zpool status
pool: nas
state: ONLINE
scan: scrub repaired 0B in 00:00:00 with 0 errors on Fri Dec 19 16:54:04 2025
config:
NAME STATE READ WRITE CKSUM
nas ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
gpt/zfs_disk1 ONLINE 0 0 0
gpt/zfs_disk2 ONLINE 0 0 0
errors: No known data errors
Тепер в scan є “scrub repaired 0B” – все добре.
Скрипт – /etc/periodic/daily/800.scrub-zfs, в якому перевіряється значення daily_scrub_zfs_default_threshold, і, якщо пройшло більше днів, ніж задано в threshold – то запускається zpool scrub.
З daily_scrub_zfs_pools можна вказати, які саме пули перевіряти.
З zpool events можна перевірити всі останні події в пулі:
root@setevoy-nas:/home/setevoy # zpool events
TIME CLASS
Dec 20 2025 16:46:39.702350180 sysevent.fs.zfs.history_event
Dec 20 2025 16:46:39.711350328 ereport.fs.zfs.config_cache_write
Dec 20 2025 16:46:39.711350328 sysevent.fs.zfs.config_sync
Dec 20 2025 16:46:39.711350328 sysevent.fs.zfs.pool_import
Dec 20 2025 16:46:39.712349914 sysevent.fs.zfs.history_event
Dec 20 2025 16:46:39.720349727 sysevent.fs.zfs.config_sync
Dec 20 2025 14:46:44.749348450 sysevent.fs.zfs.config_sync
...
Dec 21 2025 17:26:30.905209986 sysevent.fs.zfs.history_event
Аби побачити деталі – додаємо -z:
root@setevoy-nas:/home/setevoy # zpool events -v
TIME CLASS
Dec 20 2025 16:46:39.702350180 sysevent.fs.zfs.history_event
version = 0x0
class = "sysevent.fs.zfs.history_event"
pool = "nas"
pool_guid = 0x2f9ad6b17a5e8426
pool_state = 0x0
pool_context = 0x0
history_hostname = ""
history_internal_str = "pool version 5000; software version zfs-2.2.7-0-ge269af1b3; uts 14.3-RELEASE 1403000 amd64"
history_internal_name = "open"
history_txg = 0x2d65
history_time = 0x6946b6cf
time = 0x6946b6cf 0x29dd0364
eid = 0x1
...
А з zpool history можна подивитись всі команди, які запускались:
root@setevoy-nas:/home/setevoy # zfs-stats -E
------------------------------------------------------------------------
ZFS Subsystem Report Sun Dec 21 18:07:59 2025
------------------------------------------------------------------------
ARC Efficiency: 104.72 k
Cache Hit Ratio: 99.87% 104.58 k
Cache Miss Ratio: 0.13% 137
Actual Hit Ratio: 99.87% 104.58 k
Data Demand Efficiency: 100.00% 0
CACHE HITS BY CACHE LIST:
Most Recently Used: 23.21% 24.28 k
Most Frequently Used: 76.79% 80.30 k
Most Recently Used Ghost: 0.00% 0
Most Frequently Used Ghost: 0.00% 0
CACHE HITS BY DATA TYPE:
Demand Data: 0.00% 0
Prefetch Data: 0.00% 0
Demand Metadata: 99.96% 104.54 k
Prefetch Metadata: 0.04% 39
CACHE MISSES BY DATA TYPE:
Demand Data: 0.00% 0
Prefetch Data: 0.00% 0
Demand Metadata: 91.24% 125
Prefetch Metadata: 8.76% 12
Або використати zfs-mon:
Ну і по моніторингу ZFS цього, мабуть, вистачить.
Ще окремо будемо говорити вже про повноцінний моніторинг системи – планую з VictoriaMetrics, і тоді можна буде додати якийсь експортер для ZFS, наприклад zfs_exporter.
Але зараз, аби підключитись до якогось хосту в мережах треба вказувати IP-адресу.
Можна, звісно, прописувати все в файлах /etc/hosts, але це і не дуже зручно, і будуть клієнти типу Android-телефонів, і взагалі – хочеться все красівоє.
Тому зробимо локальний DNS, на якому буде централізований DNS для всієї інфраструктури:
власну локальну DNS-зону .setevoy
DNS-відповіді для .setevoy, що залежать від мережі, з якої приходить запит (office/home/VPN)
резолвінг зовнішніх доменів через forward-DNS (Cloudflare/Google)
Я колись мав BIND, який обслуговував навіть зону самого rtfm.co.ua – але для маленької домашньої мережі це зовсім overkill.
Мав справу з dnsmasq – простий, швидкий, вміє в DHCP, але трохи обмежений в можливостях.
І Unbound – рідний для FreeBSD, теж простий і швидкий, вміє в DNSSEC validation прям з коробки, вміє в views/access-control – те, що треба.
Routers та DHCP зі Static IP
Взагалі, можна було б заморочитись і автоматизувати навіть видачу IP для хостів в мережах, але, по-перше, ну камон – в мене 3 машинки у двох мережах 🙂
По-друге – робочий ноут і ThinkCenter знаходяться в офісі, а домашній ноутбук – вдома. Тобто, робити DHCP на хості з FreeBSD – така собі затєя, бо домашній ноутбук може бути не підключеним до VPN.
Тому – прибиваємо все статично в налаштуваннях роутера (чекаю, чекаю, коли до мене доїде MikroTik hAP ax3!):
Тепер для робочого ноута, коли він підключений по Ethernet, адреса завжди буде 192.168.0.3.
root@setevoy-nas:/home/setevoy # service unbound start
Перевіряємо локально, з хоста з FreeBSD з drill замість dig:
root@setevoy-nas:/home/setevoy # drill nas.setevoy @127.0.0.1
...
;; ANSWER SECTION:
nas.setevoy. 3600 IN A 192.168.0.2
...
І якийсь зовнішній домен:
root@setevoy-nas:/home/setevoy # drill google.com @127.0.0.1
...
;; ANSWER SECTION:
google.com. 300 IN A 142.251.98.101
google.com. 300 IN A 142.251.98.139
...
Тепер при підключенні VPN буде задаватись 10.8.0.1 як основний, і адреса домашнього роутера 192.168.100.1 як fallback опція.
Ще можна буде налаштувати split-DNS, задавши Domains = ~setevoy, аби до 10.8.0.1 робити запити тільки для зони .setevoy, див. systemd-resolved та systemd-networkd.
Налаштування forward-only DNS
З поточним конфігом наш unbound виконує повний рекурсивний пошук.
Тобто, коли ми з клієнта робимо запит google.com – то:
unbound звертається до root-серверів DNS
там шукає Name Servers для зони .com
звертається до цих Name Servers, запитуючи інформацію про Name Servers для google.com
і тільки після цього йде на Name Servers google.com, звідки бере інформацію по IP, яку потім повертає клієнту
Натомість можемо налаштувати forward-only, і тоді Unbound одним запитом сходить на 1.1.1.1 або 8.8.8.8, і відразу поверне відповідь клієнту.
root@setevoy-nas:/home/setevoy # unbound-checkconf && service unbound reload
Тепер Unbound в порядку пріорітету спочатку перевірить свою локальну зону, якщо google.com в нього на обслуговуванні нема – то він переадресує запит до Cloudflare або Google.
Налаштування логів
Робив для того, аби подивись як впливає налаштування forward-zone – тому теж нехай тут буде.
Тепер відключаємо forward-zone, з робочого ноутбука робимо запит про google.com до нашого DNS:
[setevoy@setevoy-work ~] $ time dig +short google.com @192.168.0.2
...
real 0m0.109s
Час виконання – 0.109s.
Дивимось лог Unbound:
...
info: 192.168.0.3 google.com. A IN
...
info: priming . IN NS
info: sending query: . NS IN
info: reply from <.> 199.7.83.42#53
info: priming successful for . NS IN
...
info: sending query: com. A IN
info: reply from <.> 202.12.27.33#53
info: query response was REFERRAL
...
info: sending query: google.com. A IN
info: reply from <google.com.> 216.239.38.10#53
info: query response was ANSWER
...
info: 192.168.0.3 google.com. A IN NOERROR 0.101574
...
Тут добре видно, як Unbound працює як рекурсивний резолвер :
спочатку виконує запит до root-зони “.“, де отримує Name Servers для зони .com
далі звертається до Name Servers зони .com, де отримує адреси Name Servers зони google.com
і лише після цього звертається вже до Name Servers домену google.com, отримуючи фінальну A-відповідь з IP-адресами серверів Google
А тепер повертаємо forward-zone, але залишаємо відключеним кешування (cache-min/max-ttl) і робимо запит з клієнта ще раз:
[setevoy@setevoy-work ~] $ time dig +short google.com @192.168.0.2
...
real 0m0.016s
Тепер час на отримання відповіді – 0.030s, а без forward-zone він був 0.109s (або з логу – google.com. A IN NOERROR 0.101574). Майже в 3 раз швидше. Іноді буває і 0.01s.
І логи Unbound тепер набагато менші:
...
debug: forwarding request
...
debug: ip4 1.1.1.1 port 53
debug: ip4 8.8.8.8 port 53
info: sending query: google.com. A IN
debug: sending to target: <.> 8.8.8.8#53
...
info: 192.168.0.3 google.com. A IN NOERROR 0.039006
...
Все, що він зробив – це передав запит до 8.8.8.8 і повернув результат, отриманий від нього.
Unbound DNS views для різних мереж
Зараз у нас з дому, який за VPN, при запиті nas.setevoy повернеться адреса 192.168.0.1:
...
local-data: "nas.setevoy. A 192.168.0.2"
...
Але якщо хочемо ходити чисто через нетворк VPN – можемо розділити зони з views і для кожної мережі створити власні зони:
server:
interface: 0.0.0.0
do-daemonize: yes
do-ip6: no
hide-identity: yes
hide-version: yes
# map client networks to views
access-control-view: 127.0.0.0/8 office
access-control-view: 192.168.0.0/24 office
access-control-view: 10.8.0.0/24 vpn
#######################
### Hosts list note ###
#######################
# -Office:
# - setevoy-work: Arch Linux laptop
# - setevoy-pc: Arch Linux/Windows gaming PC
# - setevoy-nas: FreeBSD ThinkCentre
# - TP-Link router
# - Home:
# - setevoy-home: Arch Linux laptop
# - TP-Link router
################
# OFFICE VIEW #
################
view:
name: "office"
local-zone: "setevoy." static
local-data: "nas.setevoy. A 192.168.0.2"
local-data: "work.setevoy. A 192.168.0.3"
local-data: "pc.setevoy. A 192.168.0.4"
#############
# VPN VIEW #
#############
view:
name: "vpn"
local-zone: "setevoy." static
local-data: "nas.setevoy. A 10.8.0.1"
# not VPN-connected, but Home has routing to the 192.168.0.0/24
local-data: "work.setevoy. A 192.168.0.3"
local-data: "pc.setevoy. A 192.168.0.4"
#################
# FORWARD ONLY #
#################
forward-zone:
name: "."
forward-addr: 1.1.1.1
forward-addr: 8.8.8.8
Тут в access-control-view: 10.8.0.0/24 vpn прив’язує клієнтів з VPN-мережі 10.8.0.0/24 до view з ім’ям vpn, для якого далі окремо описується DNS-логіка.
Конкретно в моєму сетапі домашній ноутбук все одно має роутинг в мережу 192.168.0.0/24 через 10.8.0.1, і можна повертати адреси з неї – але в цілому штука корисна.
Перевіряємо, застосовуємо:
root@setevoy-nas:/home/setevoy # unbound-checkconf && service unbound reload
unbound-checkconf: no errors in /usr/local/etc/unbound/unbound.conf
Перевіряємо з дому – отримуємо адресу з мережі VPN:
Основна ідея – поєднати (нарешті!) мій “офіс” і квартиру, а пізніше, можливо, ще і підключити сервер, на якому зараз працює rtfm.co.ua – аби бекапи блогу і баз даних зберігати відразу на ZFS пулі з RAID домашнього сервера.
WireGuard vs OpenVPN
Коли діло дійшло до вибору який конкретно VPN сервер вибрати, то я спочатку думав взяти OpenVPN – бо працюю з ним не один рік, і на RTFM про нього навіть є якісь матеріали.
Але, трохи подумавши вирішив, що для домашнього VPN рішення типу OpenVPN або Pritunl будуть трохи оверхедом, і можна спробувати WireGuard.
Системи дуже різні, але якщо коротко, то:
WireGuard набагато менший по коду – наприклад, Linux-реалізація це близько 4000 строк в ядрі, тоді як в OpenVPN це близько 100,000 строк в user space
WireGuard працює як модуль ядра – обробка пакетів і криптографія виконуються безпосередньо в kernel space, а OpenVPN є user space сервісом і працює через TCP або UDP socket та взаємодіє з ядром через стандартний мережевий стек ядра
туди ж – шифрування, бо WireGuard має вбудовану криптографію, яка є частиною самого протоколу і працює в kernel space, а OpenVPN використовує стандартний SSL/TLS стек (OpenSSL, LibreSSL тощо) в user space, що додає складність і накладні витрати CPU/RAM
модель роботи WireGuard – peer-to-peer, тобто протокол не має вбудованих ролей “сервер” чи “клієнт”, є лише Peers з ключами і дозволеними IP, тоді як OpenVPN побудований навколо класичної клієнт-серверної архітектури
В результаті WireGuard можна сприймати не як окремий сервіс, а як зашифрований мережевий інтерфейс, тоді як OpenVPN залишається класичним прикладним VPN-сервісом.
“офіс”: окрема локальна мережа 192.168.0.0/24, на вході – роутер TP-LINK Archer AX12
в цій мережі є робочий ноутбук з Arch Linux і Lenovo ThinkCentre з FreeBSD
на FreeBSD буде NAS, NFS і, власне, саме WireGuard
хоча Archer AX12 має власні вбудовані OpenVPN і WireGuard – але хочеться зробити самому, руками, плюс все ж більше контролю
дома: там мережа 192.168.100.0/24, на вході точно такий же роутер Archer AX12
там єдиний клієнт – це домашній ноутбук з Arch Linux
І що я хочу зробити:
на FreeBSD буде WireGuard в ролі VPN-сервера
на роутері Archer AX12 буде NAT port-forwarding для підключення до WireGuard на FreeBSD
мережа VPN – 10.8.0.1/24
на FreeBSD – Paket Filter firewall для контролю трафіка
обидва ноутбуки повинні мати доступ один до одного і до майбутнього NAS на FreeBSD
Як це в результаті виглядає схематично:
Запуск WireGuard на FreeBSD
У FreeBSD (власне, як і в Linux) WireGuard – це kernel module + userspace tools: основна “робоча” частина завантажується як модуль ядра, а для роботи з ним встановлюється окремий пакет.
роутити інтернет через VPN не потрібно – тільки трафік між мережами дома і офісу
Конфіг pf зараз – мінімалістичний, з попереднього поста:
allowed_tcp_ports = "{ 22 }"
allowed_clients = "{ 192.168.0.0/24, 192.168.1.0/24 }"
set skip on lo
block all
# allow ssh only from specific hosts
pass in proto tcp from $allowed_clients to any port $allowed_tcp_ports keep state
# allow all outgoing traffic
pass out all keep state
Що до нього треба додати:
дозволити вхідні UDP-з’єднання на порт WireGuard (51820) для handshake
дозволити трафік з VPN-мережі 10.8.0.0/24 до самого FreeBSD-хоста (ping, SSH)
дозволити транзитний трафік з VPN-мережі 10.8.0.0/24 до локальних мереж офісу і дому (192.168.0.0/24 та 192.168.100.0/24)
дозволити ICMP і SSH з VPN-мережі та домашньої мережі до FreeBSD-хоста
дозволити вихідний трафік з FreeBSD
Я додав макроси в конфіг, але поки пишу і тестую – вказую всі порти та адреси явно прямо в конфігу, простіше читати.
Тепер /etc/pf.conf буде виглядати так:
##################
### Interfaces ###
##################
# lan_if = "em0"
# wg_if = "wg0"
################
### Networks ###
################
# lan_net = "192.168.0.0/24"
# home_net = "192.168.100.0/24"
# wg_net = "10.8.0.0/24"
# vpn_nets = "{ 10.8.0.0/24, 192.168.100.0/24 }"
################
### Services ###
################
# ssh_ports = "{ 22 }"
# wg_port = "51820"
######################
### Basic settings ###
######################
# do not filter loopback traffic
set skip on lo
######################
### Default policy ###
######################
# block everything by default
block all
#######################
### Inbound traffic ###
#######################
### SSH
# allow SSH from Office LAN (192.168.0.0/24) to FreeBSD host
pass in log on em0 proto tcp from 192.168.0.0/24 to (em0) port 22 keep state
# allow SSH from Home network (192.168.100.0/24) to FreeBSD host
pass in log on em0 proto tcp from 192.168.100.0/24 to (em0) port 22 keep state
# allow SSH from VPN clients to FreeBSD host
pass in on wg0 proto tcp from 10.8.0.0/24 to (wg0) port 22 keep state
### VPN
# allow WireGuard handshake (UDP/51820) on LAN interface
pass in on em0 proto udp to (em0) port 51820 keep state
# allow VPN clients (10.8.0.0/24) to access FreeBSD host itself
# this allows ping, ssh, etc. to the wg0 address
pass in on wg0 from 10.8.0.0/24 to (wg0) keep state
# allow VPN clients to access Office LAN (192.168.0.0/24)
pass in on wg0 from 10.8.0.0/24 to 192.168.0.0/24 keep state
# allow VPN clients to access Home network (192.168.100.0/24)
pass in on wg0 from 10.8.0.0/24 to 192.168.100.0/24 keep state
# allow ICMP (ping) from VPN clients to FreeBSD host
pass in on wg0 proto icmp from 10.8.0.0/24 to (wg0) keep state
# allow ICMP (ping) from Home network to FreeBSD host
pass in on em0 proto icmp from 192.168.100.0/24 to (em0) keep state
############################
### outbound traffic ###
############################
# allow all outbound traffic from FreeBSD
pass out keep state
Перевіряємо синтаксис:
root@setevoy-nas:/home/setevoy # pfctl -vnf /etc/pf.conf
set skip on { lo }
block drop all
pass in log on em0 inet proto tcp from 192.168.0.0/24 to (em0) port = ssh flags S/SA keep state
pass in log on em0 inet proto tcp from 192.168.100.0/24 to (em0) port = ssh flags S/SA keep state
pass in on wg0 inet from 10.8.0.0/24 to (wg0) flags S/SA keep state
pass in on wg0 inet proto icmp from 10.8.0.0/24 to (wg0) keep state
pass in on wg0 inet from 10.8.0.0/24 to 192.168.0.0/24 flags S/SA keep state
pass in on wg0 inet from 10.8.0.0/24 to 192.168.100.0/24 flags S/SA keep state
pass in on em0 inet proto icmp from 192.168.100.0/24 to (em0) keep state
pass in on em0 proto udp from any to (em0) port = 51820 keep state
pass out all flags S/SA keep state
Виконуємо reload:
root@setevoy-nas:/home/setevoy # service pf reload
Reloading pf rules.
Тепер можемо підготувати запуск WireGuard.
Конфігурація WireGuard
Тут все дуже просто – створити ключі, написати конфіг-файл.
Створення ключів
Комунікація і криптографія у WireGuard побудована на стандартній схемі асиметричних ключів:
на “сервері” зберігається приватний ключі
на клієнті вказується публічний
під час handshake клієнт переконується, що підключився саме до того сервера, публічний ключ якого він знає
а далі, з цими ключами, відбувається і шифрування даних
Слово “сервер” все ж беру в лапки, бо, як було зазначено вище – WireGuard це P2P, а не client-server.
Після установки wireguard-tools створюється каталог /usr/local/etc/wireguard – переходимо туди, і з wg genkey створюємо приватний та публічний ключ:
root@setevoy-nas:/home/setevoy # cd /usr/local/etc/wireguard
root@setevoy-nas:/usr/local/etc/wireguard # wg genkey | tee server.key | wg pubkey > server.pub
root@setevoy-nas:/usr/local/etc/wireguard # chmod 600 server.key
root@setevoy-nas:/usr/local/etc/wireguard # ll
total 12
-rw------- 1 root wheel 45 Dec 17 15:58 server.key
-rw-r--r-- 1 root wheel 45 Dec 17 15:58 server.pub
Базовий конфіг для WireGuard
Можна створити кілька різних конфігурацій в /usr/local/etc/wireguard/, кожен на власному порті та/або IP та з власним ключем і мати кілька різних VPN-підключень, а керувати ними використовуючи ім’я файлу – wg0, wg1, etc.
Блок Interface визначає параметри WireGuard-інтерфейсу wg0 – його IP-адресу, UDP-порт і приватний ключ, який використовується для шифрування трафіку.
Тут жеж можна вказати які DNS використовувати, чи робити апдейт в таблицях маршрутизації клієнтів (default – true) і запуск скриптів з PreUp, PostUp, PreDown, PostDown.
Запускаємо сам WireGuard:
root@setevoy-nas:/home/setevoy # wg-quick up wg0
[#] ifconfig wg create name wg0
[#] wg setconf wg0 /dev/stdin
[#] ifconfig wg0 inet 10.8.0.1/24 alias
[#] ifconfig wg0 mtu 1420
[#] ifconfig wg0 up
[+] Backgrounding route monitor
Перевіряємо інтерфейс:
root@setevoy-nas:/home/setevoy # ifconfig wg0
wg0: flags=10080c1<UP,RUNNING,NOARP,MULTICAST,LOWER_UP> metric 0 mtu 1420
options=80000<LINKSTATE>
inet 10.8.0.1 netmask 0xffffff00
groups: wg
nd6 options=109<PERFORMNUD,IFDISABLED,NO_DAD>
Та статус WireGuard:
root@setevoy-nas:/home/setevoy # wg show
interface: wg0
public key: xLWA/FgF3LBswHD5Z1uZZMOiCbtSvDaUOOFjH4IF6W8=
private key: (hidden)
listening port: 51820
Поки у нас нема ніяких клієнтів – переходимо до них.
TP-Link Dynamic DNS та NAT port-forwarding
Аби з дому підключатись до хосту з FreeBSD і WireGuard – на роутері в офісі додаємо форвард портів:
protocol: UDP
зовнішній порт на роутері: 51830 (трохи замаскувати від ботів)
куди форвардити: 192.168.0.2 (хост з FreeBSD)
на який порт форвардити: 51830 (WireGuard на em0 на FreeBSD)
На TP-Link Archer AX12 це виглядає так:
Якщо Internet IP в офісі динамічний – в Archer AX12 є можливість налаштування Dynamic DNS:
Хоча в мене він статичний, але DDNS заради інтересу налаштував з https://www.noip.com.
Запуск WireGuard на Arch Linux
В Linux все аналогічно – в ядрі є модулі, нам треба тільки встановити пакет з утилітами.
Тут в AllowedIPs вказуємо мережі в які, по-перше, буде доступ взагалі, по-друге – вони будуть додані в таблиці маршрутизації (“Acts as a routing table and access control list“).
Запускаємо на клієнті:
[root@setevoy-wg-test setevoy]# wg-quick up wg0
[#] ip link add dev wg0 type wireguard
[#] wg setconf wg0 /dev/fd/63
[#] ip -4 address add 10.8.0.3/24 dev wg0
[#] ip link set mtu 1420 up dev wg0
[#] ip -4 route add 10.8.0.3/32 dev wg0
[#] ip -4 route add 192.168.0.0/24 dev wg0
Тут:
ip -4 address add: Interface - Address заданий для wg0
ip -4 route add 10.8.0.3/32 та 192.168.0.0/24: додані нові роути через інтерфейс wg0 для мереж VPN та офісної локалки
Перевіряємо:
root@setevoy-home:/etc/wireguard # ip r s 10.8.0.0/24
10.8.0.0/24 dev wg0 proto kernel scope link src 10.8.0.3
root@setevoy-home:/etc/wireguard # ip r s 192.168.0.0/24
192.168.0.0/24 dev wg0 scope link
Додаємо Peer на сервері, файл /usr/local/etc/wireguard/wg0.conf тепер буде таким:
В принципі, на цьому вже майже все готове – доступ є, все працює.
Але чого хочеться ще – це мати прямий доступ з домашнього ноута на робочий і з робочого на домашній, бо на робочому ноуті VPN нема – він там і не потрібен, бо FreeBSD/NAS в тій самій локальній мережі.
Конфігурація cross-LAN доступу
Тож що треба зробити – це налаштувати прямий доступ між ноутами в домашній мережі 192.168.100.0/24 і офісній 192.168.0.0/24, бо зараз з робочого ноутбука на ноутбук вдома і навпаки доступ не працює.
Картина зараз така:
IP ноута в офісі: 192.168.0.165
IP ноута вдома: 192.168.100.205
на робочому ноуті WireGuard нема
з офісу на домашній ноут конекта нема
з дому на робочий ноут конекта нема
з дому на FreeBSD конект є
Налаштування Routing tables
Поки робимо – закоментуємо block all в /etc/pf.conf, потім до нього повернемось.
В результаті того, що зараз будемо робити – вийде ось така схема: тут головне – це маршрути, спеціально робив схемою, аби те, що буде описано далі було простіше зрозуміти:
Перевіряємо роути з домашнього ноута на FreeBSD:
root@setevoy-home:/etc/wireguard # ip route get 192.168.0.2
192.168.0.2 dev wg0 src 10.8.0.3 uid 0
І на робочий ноут:
root@setevoy-home:/etc/wireguard # ip route get 192.168.0.165
192.168.0.165 dev wg0 src 10.8.0.3 uid 0
Трафік йде через wg0, і Source Address для пакета задається як 10.8.0.3.
А на робочому ноуті роут на домашній ноут йде через 192.168.0.1:
[setevoy@setevoy-work ~] $ ip route get 192.168.100.205
192.168.100.205 via 192.168.0.1 dev wlan0 src 192.168.0.165 uid 1000
Тут 192.168.0.1 – дефолтний гейтвей, роутер в офісі, який нічого не знає про домашню мережу 192.168.100.0/24.
Тому перше – додаємо роут в домашню мережу через хост з FreeBSD:
[setevoy@setevoy-work ~] $ sudo ip route add 192.168.100.0/24 via 192.168.0.2
Перевіряємо ще раз:
[setevoy@setevoy-work ~] $ ip route get 192.168.100.205
192.168.100.205 via 192.168.0.2 dev wlan0 src 192.168.0.165 uid 1000
Тепер є контакт з офісу додому:
[setevoy@setevoy-work ~] $ ping 192.168.100.205 -c 1
PING 192.168.100.205 (192.168.100.205) 56(84) bytes of data.
64 bytes from 192.168.100.205: icmp_seq=1 ttl=63 time=62.0 ms
--- 192.168.100.205 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
Але з домашнього все ще нема, бо з дому ми шлемо:
з домашнього ноута з IP 192.168.100.205
через FreeBSD з IP 192.168.0.2
на робочий ноутбук з IP 192.168.0.165
Але у нас з домашнього ноутбука задається Source IP як 10.8.0.3:
root@setevoy-home:/etc/wireguard # ip route get 192.168.0.165
192.168.0.165 dev wg0 src 10.8.0.3 uid 0
Бо маршрут в 192.168.0.0/24 заданий через VPN інтерфейс wg0:
root@setevoy-home:/etc/wireguard # ip r s 192.168.0.0/24
192.168.0.0/24 dev wg0 scope link
А у wg0 заданий IP 10.8.0.3:
root@setevoy-home:/etc/wireguard # ip a s wg0
20: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
link/none
inet 10.8.0.3/24 scope global wg0
А робочий ноут нічого про мережу 10.8.0.0/24 не знає і не може повернути відповідь.
Тому на робочому ноуті додаємо ще один маршрут:
[setevoy@setevoy-work ~] $ sudo ip route add 10.8.0.0/24 via 192.168.0.2 dev wlan0
Перевіряємо:
[setevoy@setevoy-work ~] $ ip r s 10.8.0.0/24
10.8.0.0/24 via 192.168.0.2 dev wlan0
І тепер з домашнього ноута на робочий доступ теж є:
root@setevoy-home:/etc/wireguard # ping -c1 192.168.0.165
PING 192.168.0.165 (192.168.0.165) 56(84) bytes of data.
64 bytes from 192.168.0.165: icmp_seq=1 ttl=63 time=6.19 ms
--- 192.168.0.165 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
Аби ці роути додати постійно – можна зробити з NetworkManager CLI.
Видаляємо те, що додали вручну:
[setevoy@setevoy-work ~] $ sudo ip route del 10.8.0.0/24 via 192.168.0.2
[setevoy@setevoy-work ~] $ sudo ip route del 192.168.100.0/24 via 192.168.0.2
Знаходимо ім’я підключення:
[setevoy@setevoy-work ~] $ nmcli connection show
NAME UUID TYPE DEVICE
setevoy-tp-link-21-5 3a12a60d-7b37-4c20-b573-d27c47a94ae5 wifi wlan0
...
[setevoy@setevoy-work ~] $ nmcli connection show setevoy-tp-link-21-5 | grep ipv4.routes
ipv4.routes: { ip = 10.8.0.0/24, nh = 192.168.0.2 }; { ip = 192.168.100.0/24, nh = 192.168.0.2 }
Перезапускаємо підключення:
[setevoy@setevoy-work ~] $ sudo nmcli connection down setevoy-tp-link-21-5 && sudo nmcli connection up setevoy-tp-link-21-5
Connection 'setevoy-tp-link-21-5' successfully deactivated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/15)
Connection successfully activated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/16)
Перевіряємо роути тепер:
[setevoy@setevoy-work ~] $ ip route get 10.8.0.3
10.8.0.3 via 192.168.0.2 dev wlan0 src 192.168.0.165 uid 1000
[setevoy@setevoy-work ~] $ ip route get 192.168.100.205
192.168.100.205 via 192.168.0.2 dev wlan0 src 192.168.0.165 uid 1000
Тепер у нас є ping з офісного ноутбука на домашній:
[setevoy@setevoy-work ~] $ ping -c1 192.168.100.205
PING 192.168.100.205 (192.168.100.205) 56(84) bytes of data.
64 bytes from 192.168.100.205: icmp_seq=1 ttl=63 time=5.95 ms
--- 192.168.100.205 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
І з домашнього на робочий:
root@setevoy-home:/etc/wireguard # ping -c1 192.168.0.165
PING 192.168.0.165 (192.168.0.165) 56(84) bytes of data.
64 bytes from 192.168.0.165: icmp_seq=1 ttl=63 time=5.67 ms
--- 192.168.0.165 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
Налаштування Packet Filter
Але якщо ми включимо block all в pf, то підключення з офісу на домашній ноут зламається, бо у нас зараз правила тільки для FreeBSD host:
...
# allow SSH from Office LAN (192.168.0.0/24) to FreeBSD host
pass in log on em0 proto tcp from 192.168.0.0/24 to (em0) port 22 keep state
...
# allow ICMP (ping) from Home network to FreeBSD host
pass in on em0 proto icmp from 192.168.100.0/24 to (em0) keep state
...
Тут:
перше правило – дозволяє SSH з офісної мережі на IP інтерфейсу em0 хоста з FreeBSD
друге – дозволяє ping з домашньої мережі на IP інтерфейсу em0 хоста з FreeBSD
Тому додаємо ще два правила – з SSH і ping з офісу додому:
...
# allow SSH from Office network to Home network
pass in on em0 proto tcp from 192.168.0.0/24 to 192.168.100.0/24 port 22 keep state
...
# allow ICMP from Home network to Office network
pass in on em0 proto icmp from 192.168.0.0/24 to 192.168.100.0/24 keep state
...
Перевіряємо, перечитуємо конфіг pf:
root@setevoy-nas:/usr/local/etc/wireguard # pfctl -vnf /etc/pf.conf && service pf reload
set skip on { lo }
block drop log all
pass in log on em0 inet proto tcp from 192.168.0.0/24 to (em0) port = ssh flags S/SA keep state
pass in log on em0 inet proto tcp from 192.168.100.0/24 to (em0) port = ssh flags S/SA keep state
pass in on wg0 inet from 10.8.0.0/24 to (wg0) flags S/SA keep state
pass in on wg0 inet proto icmp from 10.8.0.0/24 to (wg0) keep state
pass in on wg0 inet from 10.8.0.0/24 to 192.168.0.0/24 flags S/SA keep state
pass in on wg0 inet from 10.8.0.0/24 to 192.168.100.0/24 flags S/SA keep state
pass in on em0 inet proto tcp from 192.168.0.0/24 to 192.168.100.0/24 port = ssh flags S/SA keep state
pass in on em0 inet proto icmp from 192.168.0.0/24 to 192.168.100.0/24 keep state
pass in on em0 proto udp from any to (em0) port = 51820 keep state
pass out all flags S/SA keep state
Reloading pf rules.
І тепер у нас є пінг з дому в офіс:
root@setevoy-home:/etc/wireguard # ping -c1 192.168.0.165
PING 192.168.0.165 (192.168.0.165) 56(84) bytes of data.
64 bytes from 192.168.0.165: icmp_seq=1 ttl=63 time=8.09 ms
--- 192.168.0.165 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
##################
### Interfaces ###
##################
# lan_if = "em0"
# wg_if = "wg0"
################
### Networks ###
################
# lan_net = "192.168.0.0/24"
# home_net = "192.168.100.0/24"
# wg_net = "10.8.0.0/24"
# vpn_nets = "{ 10.8.0.0/24, 192.168.100.0/24 }"
################
### Services ###
################
# ssh_ports = "{ 22 }"
# wg_port = "51820"
######################
### Basic settings ###
######################
# do not filter loopback traffic
set skip on lo
######################
### Default policy ###
######################
# block everything by default
block log all
#######################
### Inbound traffic ###
#######################
### SSH
# allow SSH from Office LAN (192.168.0.0/24) to FreeBSD host
pass in log on em0 proto tcp from 192.168.0.0/24 to (em0) port 22 keep state
# allow SSH from Home network (192.168.100.0/24) to FreeBSD host
pass in log on em0 proto tcp from 192.168.100.0/24 to (em0) port 22 keep state
# allow SSH from VPN clients to FreeBSD host
pass in on wg0 proto tcp from 10.8.0.0/24 to (wg0) port 22 keep state
### NEW
# allow SSH from Office netwrok to Home network
pass in on em0 proto tcp from 192.168.0.0/24 to 192.168.100.0/24 port 22 keep state
### TEST
# allow Office LAN to reach Home LAN via WireGuard
#pass in on em0 from 192.168.0.0/24 to 192.168.100.0/24 keep state
#pass out on wg0 from 192.168.0.0/24 to 192.168.100.0/24 keep state
# allow Home LAN to reach Office LAN via WireGuard
#pass in on wg0 from 192.168.100.0/24 to 192.168.0.0/24 keep state
#pass out on em0 from 192.168.100.0/24 to 192.168.0.0/24 keep state
### VPN
# allow WireGuard handshake (UDP/51820) on LAN interface
pass in on em0 proto udp to (em0) port 51820 keep state
# allow VPN clients (10.8.0.0/24) to access FreeBSD host itself
# this allows ping, ssh, etc. to the wg0 address
pass in on wg0 from 10.8.0.0/24 to (wg0) keep state
# allow VPN clients to access Office LAN (192.168.0.0/24)
pass in on wg0 from 10.8.0.0/24 to 192.168.0.0/24 keep state
# allow VPN clients to access Home network (192.168.100.0/24)
pass in on wg0 from 10.8.0.0/24 to 192.168.100.0/24 keep state
#
#pass in on em0 from 192.168.0.0/24 to 192.168.100.0/24 keep state
#pass in on wg0 from 192.168.100.0/24 to 192.168.0.0/24 keep state
### ICMP
# allow ICMP from VPN clients to FreeBSD host
pass in on wg0 proto icmp from 10.8.0.0/24 to (wg0) keep state
# allow ICMP from Home network to FreeBSD host
#pass in on em0 proto icmp from 192.168.100.0/24 to (em0) keep state
# allow ICMP from Home network to Office network
pass in on em0 proto icmp from 192.168.0.0/24 to 192.168.100.0/24 keep state
############################
### outbound traffic ###
############################
# allow all outbound traffic from FreeBSD
pass out keep state
Активні підключення в pftop:
Тут:
In 192.168.0.165:50286 => 192.168.0.2:22: SSH робочий ноут на FreeBSD
In 178.***.***.236:56432 => 192.168.0.2:51820: підключення з дому через NAT Port-forwarding на офісному роутері до VPN на FreeBSD
In 10.8.0.3:39442 => 192.168.0.165:22: SSH з дому на робочий ноут
Out 10.8.0.1:50589 => 10.8.0.3:22: SSH з FreeBSD на домашній
P.S. Який це дикий кайф – оцей во “traditional networking” а не ці всі AWS VPC і його subnets…
Зараз в системі є три “штатних” фаєрволи – Packet Filter (PF), IP Firewall (IPFW) та IP Filter (IPF):
pf: зараз фактично дефолтна опція, був портований до FreeBSD з OpenBSD
ipfw: “історичний” фаєрвол ля FreeBSD, був доданий ще в 90-х роках
ipf: open source фаєрвол, портований в багато різних систем, в т.ч. OpenBSD та FreeBSD
Фаєрволи діляться на кілька типів: “inclusive” або “exclusive”, та “stateful” або “stateless”:
inclusive/exclusive: поведінка за замовчуванням – або дозволяти трафік, якщо для нього немає явного правила block (inclusive), або блокувати весь трафік, якщо для нього немає явного правила allow (exclusive)
stateful/stateless: чи зберігає firewall стан підключень, у stateful-режимі відповідний вхідний трафік для вже встановленого зʼєднання дозволяється автоматично, а у stateless-режимі firewall не зберігає стан і перевіряє кожен пакет окремо за правилами
Давайте спробуємо pf – потикав його, виглядає доволі простим, має всі потрібні можливості і приємний синтаксис.
Для управління pf є окрема CLI утиліта pfctl, для логування запитів – pflog, а для перевірки того, що зараз відбувається у pf – утиліта pftop.
quick: корисна штука – чи перевіряти правила далі, чи, якщо пакет підпадає під поточне правило, зупинити обробку і не перевіряти решту правил (далі буде приклад)
interface: імʼя інтерфейсу або групи, до яких застосовується правило (якщо не вказано – правило діє на всі інтерфейси)
Важливо: pf працює за принципом “last match wins” – firewall перевіряє всі правила від першого до останнього, і вирішальним є останнє правило, під яке підпадає пакет.
В ipfw, навпаки – важливий номер правила, і спрацьовує перше правило, умови якого відповідають пакету (“first match wins”). В ipf (ipfilter) використовується така ж модель – правила також обробляються за принципом first match wins.
Далі буде приклад того, як порядок правил впливає на доступи.
З коробки в системі є набір прикладів:
root@setevoy-nas:/home/setevoy # ls -l /usr/share/examples/pf/
total 44
-r--r--r-- 1 root wheel 1233 Jun 6 2025 ackpri
-r--r--r-- 1 root wheel 925 Jun 6 2025 faq-example1
-r--r--r-- 1 root wheel 3129 Jun 6 2025 faq-example2
-r--r--r-- 1 root wheel 4711 Jun 6 2025 faq-example3
-r--r--r-- 1 root wheel 1062 Jun 6 2025 pf.conf
-r--r--r-- 1 root wheel 848 Jun 6 2025 queue1
-r--r--r-- 1 root wheel 1110 Jun 6 2025 queue2
-r--r--r-- 1 root wheel 480 Jun 6 2025 queue3
-r--r--r-- 1 root wheel 900 Jun 6 2025 queue4
-r--r--r-- 1 root wheel 256 Jun 6 2025 spamd
Додамо для перевірки просте правило – блокувати всі підключення окрім SSH з мого робочого ноутбуку.
Знаходимо IP ноутбука:
[setevoy@setevoy-work ~] $ ip a s wlan0
5: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
...
inet 192.168.0.165/24 brd 192.168.0.255 scope global dynamic noprefixroute wlan0
...
inet 192.168.0.164/24 brd 192.168.0.255 scope global secondary dynamic noprefixroute wlan0
...
Додаємо правила в /etc/pf.conf:
# skip loopback traffic
set skip on lo
# default deny
block all
# allow SSH only from specific hosts
pass in proto tcp from { 192.168.0.165, 192.168.0.164 } to any port 22 keep state
# allow all outgoing traffic
pass out all keep state
Тут ми:
ігноруємо трафік на loopback interface
блокуємо все
дозволяємо вхідний трафік на порт 22 (SSH) з двох адрес – 192.168.0.165 і 192.168.0.164
дозволяємо весь outgoing трафік
Замість port 22 можна вказати ім’я “ssh” – тоді pf перевірить список імен і портів у файлі /etc/services:
Аби перевірити синтаксис /etc/pf.conf, але не застосовувати правила – виконуємо pfctl -vnf:
-v: verbose (можна два v для ще більшої деталізації)
-n: no apply, тільки перевірка синтаксису
-f: ім’я файлу для перевірки
root@setevoy-nas:/home/setevoy # pfctl -vnf /etc/pf.conf
set skip on { lo }
block drop all
pass in inet proto tcp from 192.168.0.165 to any port = ssh flags S/SA keep state
pass in inet proto tcp from 192.168.0.164 to any port = ssh flags S/SA keep state
pass out all flags S/SA keep state
Тут звертаємо увагу на дві цікаві речі:
flags S/SA: pf застосовує правило лише до початку TCP-зʼєднання (SYN), щоб створити state, і подальші пакети цього підключення обробляються вже через state table, а не через повну перевірку правил
keep state: це як раз про stateful/stateless – pf зберігає стан зʼєднання в state table, і якщо пакет належить до вже встановленого підключення, він пропускається без повторної перевірки всього ruleset
Хоча ми не задавали TCP-флаги явно в /etc/pf.conf, pf автоматично додає flags S/SA до stateful TCP-правил для коректної обробки нових зʼєднань.
Тестовий запуск і SSH
Важливий момент: якщо робимо по SSH, то при запуску pf підключення буде розірване, а якщо помилились в правилах – то не зможемо підключитись.
Запускаємо pf на 2 хвилини, перевіряємо, чи працює SSH.
Якщо робили по SSH – підключення буде розірване (бо не було SYN/SYN-ACK на момент роботи pf), перепідключаємось по SSH ще раз.
І, якщо все ОК, то вже стартуємо сам сервіс pf та pflog для логування:
root@setevoy-nas:/home/setevoy # service pflog start
Starting pflog.
root@setevoy-nas:/home/setevoy # service pf start
Enabling pf
Головне правило при налаштуванні pf по SSH – не виконувати start/restart, бо підключення буде розірване.
Натомість якщо треба змінити конфіг – то використовуємо спочатку pfctl -n для перевірки синтаксису, а потім pfctl -f /etc/pf.conf або service pf reload.
Можемо глянути з nmap з робочої машинки як наш сервер виглядає “ззовні”:
[setevoy@setevoy-work ~] $ nmap -Pn -p 1-1024 192.168.0.181
...
Not shown: 1023 filtered tcp ports (no-response)
PORT STATE SERVICE
22/tcp open ssh
Nmap done: 1 IP address (1 host up) scanned in 7.02 seconds
Власне, тільки 22 порт і бачимо, і то тільки тому, що nmap запускався з хоста, якому дозволений SSH.
Packet Filter Rules Ordering
Простий приклад того, як порядок правил впливає на те, чи буде пакет пропущений до системи, тобто підхід “last match wins”.
Поки закоментуємо block all, робимо pfctl -f /etc/pf.conf або service pf reload, і запускаємо “сервер” з NetCat, який слухає порт 10000:
root@setevoy-nas:/home/setevoy # nc -v -l 10000
Тепер з іншою машини відправляємо текст:
[setevoy@setevoy-work ~] $ echo Test | nc 192.168.0.2 10000
192.168.0.2 10000 (ndmp) open
І він приходить на “сервер” – все працює:
root@setevoy-nas:/home/setevoy # nc -v -l 10000
Connection from 192.168.0.165 58018 received!
Test
Тепер робимо тест: спочатку додаємо pass port 10000передblock all:
...
# this will not work
pass in proto tcp to any port 10000 keep state
# default deny
block all
...
Робимо reload:
root@setevoy-nas:/home/setevoy # pfctl -vnf /etc/pf.conf && service pf reload
Запускаємо на сервері nc -v -l 10000, пробуємо на клієнті ще раз, з -w вказуємо таймаут для підключення, отримуємо помилку “Connection timed out”:
$ echo Test | nc -v -w 3 192.168.0.2 10000
192.168.0.2 10000 (ndmp): Connection timed out
А тепер переносимо pass після block:
...
# default deny
block all
# this will work
pass in proto tcp to any port 10000 keep state
...
Ще раз service pf reload, ще раз запускаємо nc -v -l 10000, і тепер з клієнта:
$ echo Test | nc -v -w 3 192.168.0.2 10000
192.168.0.2 10000 (ndmp) open
Знов маємо “Test” на сервері.
Packet Filter та quick в rules
Про quick вже згадував в Rules Syntax, давайте глянемо, як це працює на ділі.
Повертаємо pass 10000 перед block:
...
# allow nc demo
pass in proto tcp to any port 10000 keep state
block all
...
Робимо reload, перевіряємо з nc – знов отримуємо “Connection timed out”.
А тепер, не змінюючи порядок – додамо до правила з pass in 10000 ключ quick:
...
# allow nc demo
pass in quick proto tcp to any port 10000 keep state
block all
...
Тепер підключення працює, бо запит попав під правило з pass і pf не перевіряє правила далі, а просто пропускає пакет.
Packet Filter logging з pflog
Запуск pflog ми вже додали в /etc/rc.conf і зробили service pflog start, але зараз він нічого не пише.
Аби включити запис логів для правила – додаємо його після action і, якщо є, то direction, наприклад:
...
# default deny
block log all
...
Перевіряємо конфіг, застосовуємо зміни:
root@setevoy-nas:/home/setevoy # pfctl -nf /etc/pf.conf
root@setevoy-nas:/home/setevoy # service pf reload
pflog для логування створює окремий інтерфейс – pflog0 (ім’я можна перевизначити з pflog_flags):
Тому читаємо його не cat/less, а теж tcpdump – тільки замість -i (interface) вказуємо -r і файл (“read from a saved packet file rather than to read packets from a network interface“):
root@setevoy-nas:/home/setevoy # tcpdump -n -e -ttt -r /var/log/pflog | head
reading from file /var/log/pflog, link-type PFLOG (OpenBSD pflog file), snapshot length 116
00:00:00.000000 rule 0/0(match): block in on em0: 192.168.0.165.17500 > 255.255.255.255.17500: UDP, length 131
00:00:00.000100 rule 0/0(match): block in on em0: 192.168.0.165.17500 > 192.168.0.255.17500: UDP, length 131
...
Логи для SSH only
Зараз у нас логуються всі заблоковані запити, і їх може бути дуже багато.
Якщо хочемо записувати події тільки по SSH – то прибираємо log з block all і додаємо нове правило з block log port 22:
...
# default deny
block all
# log and block SSH from everyone else
block log proto tcp to any port 22
# allow SSH only from specific hosts
pass in proto tcp from { 192.168.0.165, 192.168.0.164 } to any port 22 keep state
...
Тепер у нас блокується все по дефолту, але без записів в лог, і блокується SSH для всіх окрім 192.168.0.165, 192.168.0.164, але подія зберігається в лог:
Якщо хочемо логувати ще і всі нові дозволені підключення – просто додаємо log до pass in 22:
...
# allow SSH only from specific hosts
pass in log proto tcp from { 192.168.0.165, 192.168.0.164 } to any port 22 keep state
...
І отримуємо запис з “pass” в лог-файлі:
...
00:00:00.000000 rule 2/0(match): pass in on em0: 192.168.0.165.60218 > 192.168.0.2.22: [...]
...
Packet Filter та макроси
Macros, по суті, це як змінні в програмуванні. Точніше кажучи, це аналог const – вони підставляються під час завантаження конфігурації і не змінюється під час роботи pf.
Наприклад, можемо створити список портів і IP-адрес, для яких дозволений доступ:
І далі використовуємо їх в правилах як $ssh_clients та $allowed_tcp_ports:
# allowed tcp ports
allowed_tcp_ports = "{ 22, 10000 }"
# allowed ssh clients
ssh_clients = "{ 192.168.0.4, 192.168.0.165, 192.168.0.164 }"
# skip loopback traffic
set skip on lo
# default deny
block all
# restrict ssh source addresses
pass in proto tcp from $ssh_clients to any port $allowed_tcp_ports keep state
# allow all outgoing traffic
pass out all keep state
Тепер, якщо нам треба додати порт або адресу – не треба шукати їх по всьому файлі, а просто міняємо значення в одному місці, де “оголошується” макрос.
Packet Filter та Tables
Ідея використання Tables аналогічна до макросів – ми створюємо “змінну”, яка містить набір значень (IP-адрес або мереж), які будуть використовуватись в правилах.
Але якщо макроси використовуються для спрощення роботи з конфігураційним файлом – то таблиці призначені для великих списків, бо пошук по них відбувається дуже швидко.
Крім того, таблиці можемо міняти під час роботи pf без необхідності робити service pf reload для застосування змін.
Виносимо список адрес в таблицю з іменем ssh_clients:
І потім використовуємо в правилах – тільки тепер замість $ssh_clients вказуємо як <ssh_clients>:
# macros
allowed_tcp_ports = "{ 22, 10000 }"
# ssh allowed clients
#ssh_clients = "{ 192.168.0.4, 192.168.0.165, 192.168.0.164 }"
table <ssh_clients> { 192.168.0.4, 192.168.0.165, 192.168.0.164 }
set skip on lo
# allow nc demo
pass in quick proto tcp to any port 10000 keep state
block all
# allow ssh only from specific hosts
pass in proto tcp from <ssh_clients> to any port 22 keep state
...
Тепер у нас все ще є доступ з робочого ноутбуку, але не буде з хоста 192.168.0.86:
...
00:00:01.012829 rule 1/0(match): block in on em0: 192.168.0.86.50451 > 192.168.0.2.22: [...]
...
Tables в окремих файлах
Якщо списки великі – їх можна винести в додаткові файли, які потім підключають через /etc/pf.conf, наприклад – робимо /etc/pf_clients.conf з тими самими адресами:
А у /etc/pf.conf замість “table <ssh_clients> { 192.168.0.4, 192.168.0.165, 192.168.0.164 }” вказуємо таблицю з persist file:
...
# ssh allowed clients
table <ssh_clients> persist file "/etc/pf_clients.conf"
...
# allow ssh only from specific hosts
pass in proto tcp from <ssh_clients> to any port 22 keep state
...
pfctl та зміни в Tables
Перевіряємо таблицю зараз:
root@setevoy-nas:/home/setevoy # pfctl -t ssh_clients -T show
192.168.0.0/24
!192.168.0.86
Тепер, якщо хочемо видалити !192.168.0.86 – виконуємо:
Якщо хочемо відновити правила, які є у файлі /etc/pf_clients.conf – робимо або service pf reload, або pfctl replace і файл, з якого треба взяти правила:
Anchors дозволяють описувати правила в окремих файлах і підключати їх до основного /etc/pf.conf. Це схоже на використання окремих файлів для tables (наприклад, /etc/pf_clients.conf), але замість списків адрес anchors містять повноцінні rulesets.
Крім того, anchors можуть динамічно завантажуватись і змінюватись за допомогою pfctl під час роботи pf.
Створюємо іменований anchor:
...
block all
# allow ssh only from specific hosts
pass in proto tcp from <ssh_clients> to any port 22 keep state
# new dynamic anchor here
anchor "ssh"
# allow all outgoing traffic
pass out all keep state
Це – динамічний anchor, бо для нього не вказаний файл з правилами, і правила в ньому при service pf restart будуть видалені, але при service pf reload залишаться.
Можемо додати правило прямо “на льоту”:
root@setevoy-nas:/home/setevoy # echo 'pass in proto tcp from <ssh_clients> to any port 22 keep state' | pfctl -a ssh -f -
Перевіряємо:
root@setevoy-nas:/home/setevoy # pfctl -a ssh -s rules
pass in proto tcp from <ssh_clients> to any port = ssh flags S/SA keep state
Або створюємо файл, і виконуємо pfctl -a ssh -f /path/to/anchor.conf.
Інший варіант – заповнювати anchors під час reload чи restart, вказавши файл прямо до /etc/pf.conf в самому anchor.
Наприклад, додаємо нове правило:
root@setevoy-nas:/home/setevoy # cat /etc/pf.anchors/ssh
pass in proto tcp from 10.0.0.100 to any port 22 keep state
І описуємо його load у /etc/pf.conf:
...
anchor "ssh"
load anchor "ssh" from "/etc/pf.anchors/ssh"
# allow all outgoing traffic
pass out all keep state
Перевіряємо конфіг:
root@setevoy-nas:/home/setevoy # pfctl -vnf /etc/pf.conf
...
pass out all flags S/SA keep state
Loading anchor ssh from /etc/pf.anchors/ssh
pass in inet proto tcp from 10.0.0.100 to any port = ssh flags S/SA keep state
Виконуємо reload:
root@setevoy-nas:/home/setevoy # service pf reload
Reloading pf rules.
І ще раз дивимось правила в anchor "ssh":
root@setevoy-nas:/home/setevoy # pfctl -a ssh -s rules
pass in inet proto tcp from 10.0.0.100 to any port = ssh flags S/SA keep state
Ну і на цьому, мабуть, все, хоча можливостей у pf ще багато – див. посилання нижче.
Установку будемо робити по SSH з bsdinstall – завантажимо систему в режимі LiveCD, включимо SSH, і далі вже з робочого ноута виконаємо установку системи.
На віртуальній машині є три диски – аналогічно тому, як це буде на ThinkCentre:
Вибираємо Live System:
Логінимось з root:
Піднімаємо мережу:
# ifconfig em0 up
# dhclient em0
Налаштування SSH на FreeBSD LiveCD
Для SSH нам треба буде задати пароль root і робити зміни в /etc/ssh/sshd_config, але зараз це все не працює, бо система змонтована в read-only:
Дивимось, які розділи є зараз:
І робимо “dirty hack:
монтуємо нову файлову систему tmpfs в RAM на /mnt
копіюємо туди вміст /etc з LiveCD
монтуємо tmpfs поверх /etc (перекриваючи read-only каталог з ISO)
копіюємо підготовлені файли з /mnt назад у новий /etc
Виконуємо:
# mount -t tmpfs tmpfs /mnt
# cp -a /etc/* /mnt/
# mount -t tmpfs tmpfs /etc
# cp -a /mnt/* /etc/
Синтаксис mount для tmptfs – mount -t <fstype> <source> <mountpoint>, в source значення обов’язкове, тому ще раз вказуємо tmpfs.
Тепер задаємо пароль з passwd і стартуємо sshd з onestart:
# passwd
# service sshd onestart
Але SSH все ще не пустить, бо по дефолту логін root заборонений:
Вибираємо що будемо додавати в систему – ports треба, src опціонально, на реальному NAS точно варте:
Disk partitioning
Будемо робити мінімальну розбивку диска, тому вибираємо Manual:
Встановлювати систему будемо на ada0, вибираємо його і Create:
Далі треба вибрати схему, тут, в принципів, стандартно для 2025 року – GPT:
Підтверджуємо зміни, і маємо нову partition table на системному ada0:
Розділ freebsd-boot
Далі треба створити самі розділи.
Ще раз вибираємо ada0, Create, і створюємо розділ під freebsd-boot.
Це тільки зараз на віртуальній машині, на самому ThinkCentre тут будемо робити тип efi з розміром мегабайт у 200-500.
Зараз задаємо:
Type: freebsd-boot
Size: 512K
Mountpoint: empty
Label: empty
Підтверджуємо, йдемо до наступного розділу.
Розділ freebsd-swap
Ще раз Create, додаємо Swap.
Враховуючи що у нас на ThninkCentre будуть:
8 – 16 GB RAM
без sleep/hibernate
буде UFS і ZFS
То 2 гігабайти вистачить.
Задаємо:
Type: freebsd-swap
Size: 2GB
Mountpoint: empty
Label: empty
Розділ root з UFS
Основна система буде на UFS, бо вона дуже стабільна, не потребує для роботи RAM, швидко монтується, проста у відновленні, без складних механізмів кешування.
Задаємо:
Type: freebsd-ufs
Size: 14GB
Mountpoint: /
Label: rootfs – просто ім’я для нас
Решту дисків налаштуємо пізніше, зараз вибираємо Finish і Commit:
Finihsing installation
Чекаємо завершення копіювання:
Налаштовуємо мережу:
Таймзону:
В System Configuration – sshd, без мишки, включаємо ntpd і powerd:
System Hardening – враховуючи що це буде домашній NAS, але буду, мабуть, відкривати доступ ззовні, хоч і за фаєрволом – то є сенс трохи затюнити security:
read_msgbuf: дозволяємо dmesg тільки root
proc_debug: дозволяємо ptrace тільки root
random_pid: номера PID в більш рандомному порядку
clear_tmp: чистимо /tmp при ребутах
secure_console: вимагаємо пароль root при логіні з фізичної консолі
Вся “магія” ZFS – що в ньому все йде “з коробки” – не треба окремий LVM і його групи, не треба mdadm для RAID.
Для роботи з дисками в ZFS основна утиліта – zpool, для роботи з даними (datasets, файловими системами, снапшотами) – zfs.
Для об’єднання одного або кількох дисків у єдиний логічний storage ZFS використовує pool – в Linux LVM аналог volume group.
Створюємо пул:
root@test-nas-1:/home/setevoy # zpool create tank mirror ada1p1 ada2p1
Тут tank – ім’я пула, в mirror вказуємо, що це буде RAID1, і передаємо список розділів, які в цей пул включаються.
Перевіряємо:
root@test-nas-1:/home/setevoy # zpool status
pool: tank
state: ONLINE
config:
NAME STATE READ WRITE CKSUM
tank ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
ada1p1 ONLINE 0 0 0
ada2p1 ONLINE 0 0 0
errors: No known data errors
ZFS відразу монтує цей пул в /tank:
root@test-nas-1:/home/setevoy # mount
/dev/ada0p3 on / (ufs, local, soft-updates, journaled soft-updates)
devfs on /dev (devfs)
tank on /tank (zfs, local, nfsv4acls)
Якщо хочемо змінити маунтпоінт – виконуємо zfs set mountpoint:
root@test-nas-1:/home/setevoy # zfs set mountpoint=/data tank
І він відразу монтується в новий каталог:
root@test-nas-1:/home/setevoy # mount
/dev/ada0p3 on / (ufs, local, soft-updates, journaled soft-updates)
devfs on /dev (devfs)
tank on /data (zfs, local, nfsv4acls)
В продовження теми логів AWS Load Balancer: в попередньому пості Golang: запис логів AWS Loab Balancer до VictoriaLogs зробили збір логів з власним logs collector на Golang, тепер треба з цих логів отримати щось корисне.
Раніше, коли у нас на проекті була Loki, ми з її RecordingRules створювали метрики, з яких потім малювали дашборди в Grafana і мали алерти з Alertmanager.
Тепер треба це перенести до VictoriaMetrics, у якої є власні RecordingRules, з якими VMAlert може виконувати запити до VictoriaLogs.
час target response time на конкретних ендпоінтах нашого Backend API
VictoriaLogs LogsQL ALB logs parsing
ALB access logs має чітко визначений формат полів (який, правда, іноді змінюється), див. документацію Access log entries.
Знаючи ці поля – з LogsQL pipes можемо їх розпарсити і створити потрібні fields, які потім будемо використовувати для обчислень.
extract pipe
Перший pipe, який використаємо – extract, з яким читаємо дані з логу і створюємо поля.
Всі поля нам не потрібні, деякі поля створюються ще на етапі збору логів в Go (описаний у Створення Log parser), тому задаємо <_> на ті поля, які хочемо скіпнути, і тепер маємо таку конструкцію:
Нам потрібно буде робити час відповіді і помилки по конкретним ендпоінтам нашого Backend API, і нам вистачить першої частини URI.
Тобто зі строки “https://staging.api.challenge.example.co:443/roadmap/media/123 HTTP/2.0” треба отримати тільки “/roadmap”.
Сам запит йде в полі request_line:
Тут можемо використати extract_regexp pipe з яким отримуємо першу частину після “/” і зберігаємо результат в regex named capture group з ім’ям uri_path:
Але зараз avg() рахує значення зі всіх numeric log fields:
avg returns the average value over the given numeric log fields .
Тому користі з цього нуль.
Натомість можемо вказати яке саме поле використовувати, вказуємо його аргументом до avg() – avg(target_processing_time):
{stream="alb"}
...
|stats by (domain_name) avg(target_processing_time) as avg_target_processing_time
Результат:
math pipe
Ще хочеться бачити загальний час виконання запитів, але в полях ALB у нас три окремі значення – request_processing_time, target_processing_time та response_processing_time.
Робити три окремі метрики, а потім рахувати в Grafana не хочеться, тому можемо зробити простіше – використати LogsQL math(): спочатку рахуємо суму по всім трьом полям, а потім отриманий результат передаємо до avg():
{stream="alb"}
...
| math (request_processing_time + target_processing_time + response_processing_time) as total_processing_time
|stats by (domain_name) avg(total_processing_time) as avg_total_processing_time
Результат:
Хоча насправді сенсу особо нема, бо в 99.9% випадків 99.9% часу все одно займає target_processing_time.
AWS Athena та AWS ALB logs
“Довіряй – але перевіряй”.
Тому зробимо табличку в AWS Athena, і потім з неї будемо робити запити для перевірки.
Тепер маємо таблицю, яка дозволяє виконувати SQL-запити до логів, що зберігаються в S3:
Перевіряємо поля:
SELECT elb_status_code, count(*)
FROM alb_access_logs
WHERE date(from_iso8601_timestamp(time)) = date('2025-12-04')
GROUP BY elb_status_code
ORDER BY elb_status_code;
Результат:
Ну і для додатково користуємось метриками з CloudWatch.
Recording Rules з VictoriaLogs
Розібрались з тим, як нам отримати поля, сформували поля – можна переходити до Recording Rules та метрик.
Але перед тим, як почати писати rules – є момент, який треба уточнити:
у нас логи з S3 збираються раз на 5 хвилин (“ELB publishes a log file for each load balancer node every 5 minutes” – документація Access log files)
дефолтний Recording Rules evaluation time – 1 хвилина (див. Configuration)
і якщо момент виконання rule не співпаде з моментом запуску log parser і у VictoriaLogs логів на цей момент не буде – то і наш rule не спрацює, і нічого не створить
Тоді задача, яка мала запуститись у, наприклад, 12:00:00 – буде запущена в 12:15:00, але вона буде шукати логи в проміжок часу 12:00:00 – 12:01:00.
Ну а в логах, в нашому конкретному випадку, timestamp парситься з самих логів, тому в VictoriaLogs час буде такий, як було збережено самим лоад-балансером.
Планування Recording Rules
Повернемось до початку – що хочеться бачити:
помилки 4хх – ALB та target
помилки 5хх – ALB та target
загальна кількість реквестів на ALB та по конкретних ендпоінтах нашого Backend API
час відповіді загальна
час target response time на конкретних ендпоінтах
Тобто задача, фактично, зводиться к трьом показникам – кількість помилок, кількість реквестів, час відповіді.
Як ми це можемо зробити з Recording Rules і метриками:
4хх і 5хх помилки по полям elb_status_code:
з фільтром filter elb_status_code :~ "40[1-4]|50[0-4]" отримуємо всі логи, в яких є elb_status_code з 4хх або 5хх
потім зі stats і rate() рахуємо кількість знайдених log records, в яких були 4хх або 5хх помилки
потім в дашбордах і алертах робимо фільтри по 4хх або 5хх (хоча можна і окремі метрики робити)
target_status_code – аналогічно
загальна кількість реквестів на секунду по домену:
кожен запис в логах == окремий HTTP query
тому можемо просто робити stats by (elb_id, domain_name) rate() – групуємо по elb_id, domain_name, аби потім мати фільтри в Grafana і алертах
загальна кількість реквестів на ендпоінт
аналогічно, тільки додаємо uri_path – stats by (elb_id, domain_name, uri_path) rate()
в uri_path у нас тільки перша частина запиту, їх не так багато різних, тому проблеми з high cardinality у VictoriaLogs не буде
ALB total response time і на конкретних ендпоінтах – тут є варіанти 🙂 див далі
Пам’ятаємо про high cardinality і в VictoriaMetrics: все, що ми використаємо у stats by () потім буде збережено в labels метрик, а тому робити групування по всяким всякі trace_id не варто – при потребі це можна буде перевіряти в самих логах, а в алертах і Grafana воно нам не треба.
Почнемо з самої простої – просто рахуємо скільки записів було отримано за останній evalution interval, тобто 1 хвилину, потім з rate() отримуємо кількість змін на секунду:
Деплоїмо, чекаємо 5-10 хвилин, перевіряємо в VictoriaMetrics:
О 10:13 маємо значення 4.83.
Перевіряємо в CloudWatch, де з math просто робимо “sum requests in 1 minute /60”:
Ті ж 4.83, правда о 10:12.
Можемо ще перевірити з Athena, яку підготували раніше, і з таким запитом:
SELECT
date_trunc('minute', from_iso8601_timestamp(time)) AS minute,
count(*) / 60.0 AS rps_estimated
FROM alb_access_logs
WHERE from_iso8601_timestamp(time) >= now() - interval '1' hour
GROUP BY 1
ORDER BY 1 DESC;
Ті ж цифри, тільки теж о 10:12:
ALB 4xx/5xx Errors rule
Далі робимо per second rate помилок – та ж сама логіка, тільки додаємо filter elb_status_code :~ "40[1-4]":
В CloudWtach – те саме, тільки знов на 1 хвилину раніше:
ALB Target Processing Time rule
Наступним хочеться отримати те, що в CloudWatch у нас приходить в метриці TargetResponseTime, але мати можливість отримати і загальний час по всьому LoadBalancer – і по окремим URI.
Проблема зі stats by () і avg()
Перше рішення, яке прийшло в голову – це просто взяти середнє по кожному ендпоінту, потім взяти середнє по всім отриманням значенням – і має бути +/- те самий результат, що в CloudWatch Target Response Time з Average.
Але якщо зробити метрику так:
- record: vmlogs:alb:stats:elb_target_processing_time:avg
debug: false
expr: |
{stream="alb"}
...
| stats by (elb_id, domain_name, uri_path) avg(target_processing_time) as avg_target_processing_time
І потім для отримання загального середнього значення у VictoriaMetrics виконувати avg():
Бо у нас в stats() результати рахуються для кожної групи (elb_id, domain_name, uri_path).
Тобто, якщо взяти такі приклади і використати тільки uri_path:
/uri-1: 200 requests по 0.1 секунді:
sum: 200*0.1 == 20 sec
avg: 200*0.1/200 == 0.1 sec
/uri-2: 10 requests по 2 секунди:
sum: 10*2 == 20 sec
avg: 10*2/10 == 2 sec
Якщо ми будемо рахувати результат як avg(vmlogs:alb:stats:elb_target_processing_time:avg) – то для отримання avg() ми візьмемо значення з кожної метрики:
А як CloudWatch обчислює Average взагалі, і по TargetResponseTime?
Він бере кількість отриманих метрик, Sample count – о 12:52 це було 143:
Потім бере суму по всім отриманим метрикам, Sum – в 12:52 це було 22:
І ділить суму на кількість – 22/143 == 0.1538:
Або якщо повернутись до нашого прикладу вище – то це сума часу по обом ендпоінтам поділена на загальну кількість запитів: (/uri-1 sum + /uri-2 sum) / (/uri-1 requests + /uri-1 requests):
(20+20)/210 == 0.19
Тобто реальний середній час відповіді нашого умовного ALB – 0.19 секунди, а не 1.05 у випадку з avg() або тим більш не 2.1 sec у випадку з sum().
Отже, якщо ми хочемо мати і загальний середній час по всьому ALB, і середній час по кожному ендпоінту – то у нас два варіанти:
або в stats() треба брати суму, а не avg(), а потім отриманий результат ділити на загальну кількість запитів
або мати окрему метрику на загальний середній час – і окрему на середній час по кожному ендпоінту
Замість vmlogs:alb:stats:elb_target_processing_time:count можна було б взяти vmlogs:alb:stats:elb_requests_total:count, яку ми робили вище – але в метриках target_processing_time ми прибираємо значення “-1”, яке буває в target_processing_time якщо підключення розірване чи завершилось помилкою, а в vmlogs:alb:stats:elb_requests_total:count у нас всі реквести без виключення.
І з цими двома метриками ми можемо отримати середнє значення часу відповіді по кожному ендпоінту так:
sum(
sum(vmlogs:alb:stats:elb_target_processing_time:sum) by (uri_path)
/
sum(vmlogs:alb:stats:elb_target_processing_time:count) by (uri_path)
)
by (uri_path)
Тут у нас в 12:47 ендпоінт /chats відповідав 0.54 секунди.
І те саме значення буде в самому першому варіанті:
А аби отримати середній загальний час відповіді по всьому лоад-балансеру – можемо або використати метрики vmlogs:alb:stats:elb_target_processing_time:sum і vmlogs:alb:stats:elb_target_processing_time:count так:
Я давно фанат ThinkPad, дуже люблю всю їхню лінійку.
Нещодавно десь зустрів модель X200, які випускались з 2008 року – просто десь побачив картинку, і дуже захотів собі в “колекцію”. Неочікувано – але він навіть знайшовся в продажу на OLX, тому купив собі цей чудо-девайс.
ThinkPad X200 overview
Він… Ну – це просто бімба 🙂
Перше, що відрізняє цей ноутбук від інших моделей – це поворотний екран на 360 градусів, і це “фірмова фішка” моделі саме X200 Tablet, бо є X200 без цього.
Друге – це тачскрін.
Зовнішній вигляд
Виглядає ThinkPad X200 Tablet так:
На борту навіть є dial-up модем!
Ну і, звісно, ніяких HDMI – для зовнішнього монітору тільки VGA.
Хоча HDMI 1.0 з’явився ще у 2003, але тоді він використовувався для DVD-плеєрів та телевізорах, а ноутбуки в ті роки ще йшли переважно з VGA.
Замовив перехідник VGA на HDMI, бо в моєму моніторі DELL U3421WE ніяких VGA, само собою, вже нема – подивимось, чи спрацює.
Справа є фізичний перемикач для відключення WiFi/Bluetooth, хоча в моїй моделі блютузу нема:
В комплекті йде стилус:
ThinkPad X200 Hardware
Звісно, залізо вже стареньке:
дисплей: 12.1 дюймів, 1440×900 точок
процесор: Intel Core 2 Duo SU9400, 1.4 GHz
відео: Integrated card Intel 4500MHD
оперативна пам’ять: DDR3-1066 MHz
в офіційній документації Lenovo завжди писали, що максимум пам’яті 4 гігабайти, але гугол каже, що 8 гіг працює без проблем
поки що в мене 4 GB, замовив нові 2 планки по 4 ГБ, подивимось чи запрацює
зараз (2025 рік) DDR3-1066 MHz на 4 ГБ коштує 440 гривень 🙂
диск: SATA, але сам диск HDD, 250 ГБ, 5400 RPM
начебто є можливість замінити диск на SSD, але корпус ще не розбирав, потім подивлюся, і якщо можна – то поставлю якийсь SSD
мережа:
Ethernet 1000 bmps
WiFi: 802.11a, 802.11g, 802.11n
Встановлення FreeBSD
Чому FreeBSD? Бо це моя перша UNIX-система, на якій я вперше збирав ядро, ще у 2006 чи 2007 році, і потім до 2012 чи 2013 це була моя основна система на моїх перших сервера.
Моя перша версія FreeBSD була… не пам’ятаю точно, чи то 5, чи то 6. Але згодом, після виходу FreeBSD 9 перейшов на Linux (CentOS). Правда чому саме – теж не пам’ятаю. Щось пов’язане зі змінами в роботі з пакетами і FreeBSD ports.
Ну і раз вже налаштовуємо “раритетний” ноутбук – то чому б не спробувати і “раритетну” систему? 🙂
Трохи ностальгії по тим часам, коли вчився працювати з портами FreeBSD, коли вперше розбирався з runlevels в Linux часів SysV init і їхніми аналогами у FreeBSD – режимами завантаження системи.
Хоча, звісно, встановлювати будемо актуальну версію FreeBSD, 14.3 на сьогодняшній день.
Єдине, що тачскрін завести не вдалося, і, судячи з результатів гуглінгу, не вдасться – бо FreeBSD просто не має драйвера.
Тому я все ж встановлю FreeBSD, пограюсь і опишу якісь деталі, але потім на цьому ноутбуці буде Arch Linux – там хоч і довелось трохи попрацювати напильником, але тачскрін працює відмінно.
Вибір образу FreeBSD
Для FreeBSD існує три гілки системи:
RELEASE: основна стабільна версія, апдейти тільки security + critical fixes – максимум стабільності, мінімум сюрпризів
STABLE: гілка розробки, з якої формується майбутній RELEASE – вже достатньо стабільна, але все ще можуть бути баги
CURRENT: гілка активної розробки з усіма новими фічами і плюшками – мінімум стабільності, максимум нових експерементальних штук
FreeBSD має окремі версії для майже всіх платформ:
Для нашого ThinkPad X200 вибираємо amd64, завантажуємо образ зі сторінки Get FreeBSD, і починаємо установку.
Встановлення FreeBSD на віртуальну машину
Аби не робити 100500 фотографій екрану – процес установки FreeBSD покажу на віртуалці з QEMU/KVM, бо принципової різниці нема.
По віртуалізації QEMU/KVM в Linux є чорнетка, допишу якось, давно лежить.
У FreeBSD дуже приємний інсталятор bsdinstall, з яким можна зробити все і відразу. І, як на мене – він зручніший за archintsall, з яким я взагалі не подружився і Arch Linux завжди встановлюю руками:
Вибираємо 1, далі маємо вибір – або запустити установку, або використати ISO як live-cd:
Задаємо ім’я хоста:
Далі вибір компонентів.
Нам тут потрібні тільки порти, “ports – Ports tree“, і можна додати “src – System source tree” – тут весь код FreeBSD, корисно, якщо хочеться зібрати власне ядро і для установку апдейдів з утилітою freebsd-update, про неї трохи далі:
Наступний крок – розбивка диску:
ZFS: файлова система з підтримкою snapshots, RAID і можливістю зміни розміру розділів
з’явилась у 2005 році для операційної системи Solaris від Sun Microsystems, і на той час була революційною системою
UFS: класична для FreeBSD файлова система – дуже стабільна, мінімум використання RAM, але і не має всіх фіч ZFS
Залишаємо дефолтну опцію “Auto (ZFS)”:
Забігаючи наперед – ось так потім виглядають розділи вже на самому ноутбуку:
І навіть пропонується відразу налаштувати RAID – але це явно не наш кейс:
Вибираємо диск для установки:
Чекаємо завершення установки:
Задаємо пароль root:
Вибираємо мережевий інтерфейс:
Це установка на віртуалку, WiFi тут нема.
Але на самому ноуті можна відразу налаштувати і його, хоча в моєму випадку з ThinkPad X200 потім довелось трохи робити руками, далі покажу.
Залишаємо DHCP:
Вибираємо таймзону:
Після чого налаштовуємо базові параметри системи:
local_unbound: локальний DNS-кеш + DNSSEC валідація, для домашнього ноута можна не включати
sshd: включаємо
moused: підтримка миші в консолі – користі мало, але виглядає прикольно 🙂
ntpd: синхронізація часу
powerd: динамічне керування частотою CPU, дуже корисно, особливо для ноутбука – зберігати час роботи батареї
dumpdev: допомагає дебажити kernel panic
Далі налаштування для security – цікаво, але для домашнього ноута можна пропустити, має сенс для серверів:
Детально можна почитати в документації Section 2.8.4, “Enabling Hardening Security Options”, але коротко пройдемось по опціях, бо тут проявляються перші (якщо не говорити про файлу систему) відмінності від Linux:
hide_uids і hide_gids: ховає процеси інших користувачів системи (root, звісно, бачить все)
hide_jail: ховає процеси, які запущені в jail
FreeBSD Jails – аналог “класичних” контейнерів в Linux, але з більше жорсткою ізоляцією
і з’явились вони ще до того, як в Linux ядрі взагалі з’явилась підтримка чогось схожого:
jails були додані у FreeBSD ще у 2000 році, з FreeBSD 4.0
в Linux на той час був тільки chroot, де ізоляція була дуже слабка, і можна було легко вийти за межі “контейнера” і тримати доступ до всієї системи
і тільки у 2006-2008 в Linux з’явились cgroups, аж в 2008-2013 були додані namespaces, і тільки після цього, у 2013 році, з’явився Docker і контейнери в Linux, якими ми їх знаємо зараз
proc_debug: вимикає для звичайних користувачів можливість дебажити чужі процеси і обмежує частину інформації з /proc
random_pid: якщо включено, то PID задається з рандомним зміщенням номеру, а не по черзі
clear_tmp: у FreeBSD каталог /tmp по дефолту не очищається при ребуті, з clear_tmp це можна включити
в Linux залежить від дистрибутива і того, як саме монтується /tmp, бо зазвичай це tmpfs в RAM, який, звісно, очищається
FreeBSD свідомо не очищає його, бо політика FreeBSD – “адміністратор сам вирішує, що треба робити системі“
disable_syslogd: заборона відкриття мережевого сокету для демона syslogd – на ноуті можна включити
але сам syslogd продовжує використання локального Unix socket /var/run/log для роботи
secure_console: блокує root-вхід з консолі без пароля при завантаженні системи в single-user mode (див. Chapter 15. The FreeBSD Booting Process – у FreeBSD дуже класна документація)
disable_dtrace: DTrace у FreeBSD – аналог strace/eBPF в Linux, для трасування процесів, системних викликів і роботи ядра, і має можливість вносити “на льоту” зміни в пам’ять та ядро; disable_dtrace блокує цю можливість навіть для root
Окей – залишаємо тут все по дефолту, і переходимо до user management:
Додаємо юзера:
У FreeBSD є можливість вказати Login class, і це фішка чисто FreeBSD, див. Configuring Login Classes.
Ну і власне на цьому установка завершена.
В останньому вікні є можливість щось дотюнити:
Ребутаємось, і маємо нову систему:
І екран загрузки вже на самому ноуті:
FreeBSD load options
Коротко по доступних опціях:
Boot Multi user (Enter): стандартний запуск, монтуються файлові системи, запускаються сервіси із /etc/rc.conf
аналог в Linux – звичайний boot у runlevel/systemd multi-user.target
Boot Single user: коли система зламалась, і треба щось пофіксити
запускається тільки ядро і мінімальний shell
“/” монтується в read-only (можна перемонтувати в r/w)
аналог в Linux – init=/bin/bash або systemd.unit=rescue.target
аналог в Linux – GRUB console, але FreeBSD loader глибше інтегрований з системою
Reboot: reboot 🙂
Cons: Video: фішка FreeBSD, задає тип для system console – video console (звичайний екран) або serial console
Kernel: default/kernel: вибір ядра для завантаження
при апгрейдах попередня версія ядра зберігається у /boot/kernel.old/, і при проблемах можна завантажити інші ядра
Boot Options: додаткові параметри для завантаження – відключити ACPI, DMA, ZFS cache, etc
Повертаємось до ноутбука.
Налаштування WiFi
При встановленні на ноутбук побачило карту:
І мережі:
Але після налаштування і завершення установки інтерфейсу нема:
Робимо вручну.
З pciconf -lv перевіряємо пристрої на PCI/PCI-Express шині:
Нас цікавить device iwn – “Intel WiFi Link 5300“:
The Intel® WiFi Link 5300 Series is a family of IEEE 802.11a/b/g/Draft-N1 wireless network adapters that operate in both the 2.4 GHz and 5.0 GHz spectra
Тут FreeBSD – це user-facing пакети, а FreeBSD-kmods – репозиторій для додаткових модулів ядра (nvidia-driver, virtualbox-kmod тощо).
FreeBSD Ports Collection – каталог із source code пакетів та Makefiles, розбиті по категоріям, наприклад порт для xfce4-conf буде в директорії /usr/ports/x11/:
root@setevoy-x200:/home/setevoy # ls -1 /usr/ports/x11/xfce4-conf/
Makefile
distinfo
files
pkg-descr
pkg-plist
Використовувати порти варто, якщо потрібні якісь особливі параметри для зборки та установки, але здебільшого все можна встановлювати з pkg.
Апгрейд системи і пакетів
Тут у нас є дві окремі частини – сама система, і пакети, які ми встановлюємо з pkg.
Для апгрейду системи у FreeBSD є класна утиліта freebsd-update – дозволяє і отримувати останні апдейти, і виконувати апгрейд самої системи.
Аби отримати і встановити вручну – робимо:
# freebsd-update fetch
# freebsd-update install
fetch – завантажити останні апдейти, install – встановити їх.
Або можна відразу додати в cron:
@daily root freebsd-update cron
freebsd-update fetch та freebsd-update install виконають апгрейд ядра, системних утиліт в /bin, /sbin, /usr/bin, бібліотеки в /lib, /usr/lib і драйверів.
Для апгрейду пакетів, які ми встановлюємо з pkg – використовуємо аналогічні команди:
# pkg update
# pkg upgrade
Аналогічно до freebsd-update – з update отримуємо оновлення, з upgrade встановлюємо їх.
# pkg install xorg drm-kmod
...
Number of packages to be installed: 318
The process will require 3 GiB more space.
431 MiB to be downloaded.
...
Встановлюємо сам XFCE – теж немаленький:
# pkg install xfce xfce4-goodies
...
Number of packages to be installed: 317
The process will require 1 GiB more space.
248 MiB to be downloaded.
...
Але році у 2010 встановлював KDE з ports – то процес зборки зайняв багато годин, бо pkg тоді ще не було, і в ті часи майже все встановлювалось із ports collection.
Була і тоді система pkg_tools, але в ній не було системи залежностей, пакети качались по FTP, а апгрейд пакетів виконувався фактично через видалення і встановлення заново.
До /etc/rc.conf додаємо запуск DBus (потрібен для XFCE) та Slim:
sysrc dbus_enable="YES"
sysrc slim_enable="YES"
Створюємо файл ~/.xinitrcв домашній директорії юзера, аби Slim знав що запускати:
# echo "exec startxfce4" > /home/setevoy/.xinitrc
Ребутаємо машинку:
# reboot
І вуаля – все готово:
Можна перевірити з яким драйвером працює відео:
# cat /var/log/Xorg.0.log | grep -E "Driver|scfb|vesa"
[ 145.813] X.Org Video Driver: 25.2
[ 145.862] (==) Matched scfb as autoconfigured driver 2
[ 145.862] (==) Matched vesa as autoconfigured driver 3
[ 145.864] Module class: X.Org Video Driver
[ 145.864] ABI class: X.Org Video Driver, version 25.2
[ 145.864] (II) LoadModule: "scfb"
[ 145.864] (II) Loading /usr/local/lib/xorg/modules/drivers/scfb_drv.so
[ 145.864] (II) Module scfb: vendor="X.Org Foundation"
[ 145.864] ABI class: X.Org Video Driver, version 25.2
[ 145.864] (II) LoadModule: "vesa"
[ 145.864] (II) Loading /usr/local/lib/xorg/modules/drivers/vesa_drv.so
[ 145.864] (II) Module vesa: vendor="X.Org Foundation"
[ 145.864] Module class: X.Org Video Driver
[ 145.865] ABI class: X.Org Video Driver, version 25.2
[ 145.865] (II) modesetting: Driver for Modesetting Kernel Drivers: kms
[ 145.865] (II) scfb: driver for wsdisplay framebuffer: scfb
[ 145.865] (II) VESA: driver for VESA chipsets: vesa
[ 145.873] (WW) Falling back to old probe method for scfb
[ 145.873] scfb trace: probe start
[ 145.873] scfb trace: probe done
[ 145.874] ABI class: X.Org Video Driver, version 25.2
[ 145.875] ABI class: X.Org Video Driver, version 25.2
[ 146.006] (II) UnloadModule: "scfb"
[ 146.006] (II) Unloading scfb
scfb чимось не сподобався, буде з vesa, але на цьому залізі взагалі не принципово.
Додаємо інші корисні пакети:
# pkg install vim sudo bash
Я вже звик до bash, тому встановлюю і його.
Аби змінити юзеру його shell – використовуємо chsh:
# chsh -s /usr/local/bin/bash setevoy
chsh: user information updated
Але, як казав на початку – на такому старому залізі під FreeBSD не працює тачскрін, тому потім все ж встановлю Arch Linux – з ним вже тестив, там тачскрін пальцями і стилусом працює без проблем.