MikroTik: налаштування WireGuard та підключення Linux peers
0 (0)

17 Лютого 2026

Ще одна з (багатьох) приємних можливостей MikroTik – вбудована підтримка WireGuard (хоча вона є навіть на дешевому TP-Link Archer).

В моєму сетапі MikroTik RB4011 грає роль такого собі “VPN Hub” – всі клієнти підключаються до нього і об’єднуються в єдину мережу, і роль VPN трохи перебільшена тут дійсно важлива – бо це такий собі gateway, через які всі хости комунікують один з одним, і саме через VPN-тунелі з NAS мій скрипт для створення бекапів (про автоматизацію бекапів буде окремим великим постом, вже є в чернетках) підключається до всіх хостів, аби запустити rsync і стягнути до себе дані.

Крім того, Syncthing теж працює виключно в межах внутрішніх мереж і синхронізує дані між FreeBSD/NAS, ноутбуках з Arch Linux та мобільним телефоном – див. FreeBSD: Home NAS, part 12: синхронізація даних з Syncthing.

Власне сьогодні – налаштування WireGuard на самому MikroTik та на Debian (сервер rtfm.co.ua).

На Arch Linux (домашній ноутбук) все аналогічно до Debian, а для телефона є офіційний клієнт WireGuard, і там все в принципі схоже.

Див. також перший пост по MikroTik – MikroTik: перше знайомство та Getting Started, і далі буде ще, мабуть, ціла серія – вже пачка чернеток є.

Що будемо робити:

  • на MikroTik створимо інтерфейс для WireGuard, назначимо йому IP
  • налаштуємо MikroTik Firewall для трафіку між всіма хостами
  • налаштуємо роути – додамо в домашню мережу
  • створимо WireGuard ключі, додамо WireGuard Peer на MikroTik

Хоча пост вийшов довгий – але насправді все доволі просто.

Головне не плутати приватні і публічні ключі в конфігах – в мене на початку з цим було трохи складностей.

Архітектура мереж і хостів

Схематично вся мережа виглядає так:

Тут:

  • 192.168.0.0/24: мережа офісу, основні хости тут:
    • 192.168.0.1: MikroTik RB4011 – основний роутер
    • 192.168.0.2: FreeBSD з NAS
    • 192.168.0.3: робочий ноутбук з Arch Linux
  • 192.168.100.0/24: домашня мережа
    • 192.168.100.100: домашній ноутбук
  • 10.100.0.0/24: WireGuard VPN
    • 10.100.0.1: MikroTik RB4011
    • 10.100.0.3: домашній ноутбук з Arch Linux
    • 10.100.0.10: сервер rtfm.co.ua в DigitalOcean з Debian Linux

Адресацію буду перероблювати, але поки що так.

Конфігурація WireGuard MikroTik

Офіційна документація – WireGuard.

Отже, мережа VPN 10.100.0.0/24, адреса самого MikroTik в ній – 10.100.0.1.

Додаємо інтерфейс, на якому буде працювати WireGuard – задаємо ім’я, порт, задаємо MTU:

/interface wireguard add name=wg0 listen-port=51820 mtu=1420

(місцями вже без скріншотів, бо робив давненько)

MTU задаємо 1420 – бо дефолтний MTU в Ethernet 1500 байт, а WireGuard додає свої заголовки:

  • IP header: 20 байтів (IPv4) – задається операційною системою роутера
  • UDP header: 8 байтів – аналогічно, роутер
  • WireGuard header: 32 байти – задається WireGuard, де вказується тип повідомлення, індекс піра, аутентифікація

Разом під headers 60 байт: 20 (IP) + 8 (UDP) + 32 (WireGuard), що залишає нам 1440 байт для корисних даних (payload), і ще -20 “запас”.

Див. Header / MTU sizes for Wireguard та мій пост TCP/IP: моделі OSI та TCP/IP, TCP-пакети, Linux sockets і порти.

Перевіряємо:

/interface wireguard print detail where name=wg0

Задаємо адресу інтерфейсу:

/ip address add address=10.100.0.1/24 interface=wg0 comment=wg-hub

Перевіряємо:

/ip address print where interface=wg0

Конфігурація MikroTik Firewall

Нам треба додати дозволи на доступ з інтернету до самого WireGuard на MikroTik, а потім налаштувати доступи між мережами.

Детальніше про фаєрвол на MikroTik теж напишу окремо, теж є в чернетках.

Доступ з інтернету до WireGuard

Включаємо доступ з будь-якого хосту, правило в chain=input:

/ip firewall filter add chain=input protocol=udp dst-port=51820 action=accept comment="allow wireguard"

Або, краще, створюємо список дозволених адрес:

/ip firewall address-list add list=wg-allowed address=46.101.201.123 comment=setevoy-rtfm
/ip firewall address-list add list=wg-allowed address=178.***.236 comment=setevoy-home
/ip firewall address-list add list=wg-allowed address=64.***.***.83 comment=kyiv-work-office

Момент з адресами для дроплетів в DigitalOcean: в мене підключений Reserved IP 67.207.75.157, який використовується на DNS – але для WireGuard використовуємо саме “дефолтний” Public IP від DigitalOcean – 46.101.201.123:

Перевіряємо наш новий address-list:

/ip firewall address-list print where list=wg-allowed

Тепер створюємо правило з цим списком в chain=input:

/ip firewall filter add chain=input protocol=udp dst-port=51820 src-address-list=wg-allowed action=accept comment="allow wireguard from whitelist" place-before=0

Перевіряємо:

/ip firewall filter print where src-address-list~"wg-allowed"

Доступ між внутрішніми мережами

Налаштовуємо доступ між мережами – додаємо правила в chain=forward.

Дозволяємо доступ з мережі VPN 10.100.0.0/24 в локальну мережу офісу 192.168.0.0/24:

/ip firewall filter add chain=forward src-address=10.100.0.0/24 dst-address=192.168.0.0/24 action=accept comment="wg to lan"

І обратно – з офісної мережі в мережу VPN:

/ip firewall filter add chain=forward src-address=192.168.0.0/24 dst-address=10.100.0.0/24 action=accept comment="lan to wg"

Далі – доступ з VPN до домашньої мережі 192.168.100.0/24:

/ip firewall filter add chain=forward src-address=10.100.0.0/24 dst-address=192.168.100.0/24 action=accept comment="wg to home"

І обратно, з дому в мережу VPN:

/ip firewall filter add chain=forward src-address=192.168.100.0/24 dst-address=10.100.0.0/24 action=accept comment="home to wg"

Аналогічно – доступ вже між офісною 192.168.0.0/24  домашньою 192.168.100.0/24.

З офісу в домашню:

/ip firewall filter add chain=forward src-address=192.168.0.0/24 dst-address=192.168.100.0/24 action=accept comment="office to home"

І обратно:

/ip firewall filter add chain=forward src-address=192.168.100.0/24 dst-address=192.168.0.0/24 action=accept comment="home to office"

Перевіряємо:

/ip firewall filter print where comment~"wg|office|home"

MikroTik Routes

MikroTik автоматично створює dynamic connected route для мережі WireGuard (10.100.0.0/24) після створення інтерфейсу wg0, і вже має routes для своїх локальних мереж, які безпосередньо підключені до його інтерфейсів (bridge, ether):

/ip route print

Або виберемо тільки ті, що нам зараз цікаві:

/ip route print where dst-address~"192.168.|10.100"

Тут 10.100.0.0/24 – мережа WireGuard, 192.168.0.0/24 – активна мережа DHCP-серверу на MikroTik, 192.168.88.0/24 – дефолтна мережа DHCP-серверу, зараз не використовується.

Але мережа дома – 192.168.100.0/24, і аби ходити з дому в офіс і навпаки – треба додати ще один роут:

/ip route add dst-address=192.168.100.0/24 gateway=wg0 comment="route to home via wg"

І роути тепер:

Можна переходити до підключення клієнтів.

WireGuard Peer на Linux

Сервер rtfm.co.ua хоститься в DigitalOcean, працює на Debian 12.

Сетап на Arch Linux такий самий, тільки пакет встановлюємо wireguard-toolspacman -S wireguard-tools.

На Debian встановлюємо пакет wireguard з apt:

root@setevoy-do-2023-09-02:~# apt update && apt install -y wireguard

Переходимо в /etc/wireguard/, створюємо ключі:

root@setevoy-do-2023-09-02:/etc/wireguard# cd /etc/wireguard/

root@setevoy-do-2023-09-02:/etc/wireguard# wg genkey | tee /etc/wireguard/privatekey | wg pubkey > /etc/wireguard/publickey

Тут privatekey будемо використовувати для локального інтерфейсу, а publickey потім додамо на MikroTik WireGuard.

На Debian отримуємо значення приватного ключа:

root@setevoy-do-2023-09-02:/etc/wireguard# cat privatekey 
ML+***dmk=

На MikroTik отримуємо public-key:

/interface wireguard print

На Debian створюємо конфіг /etc/wireguard/wg0.conf:

[Interface]
PrivateKey = ML+0***mk=
Address = 10.100.0.10/32

[Peer]
PublicKey = hxz***0o=
Endpoint = 178.***.***.184:51820

AllowedIPs = 10.100.0.0/24,192.168.0.0/24,192.168.100.0/24
PersistentKeepalive = 25

Тут:

  • [Interface]
    • PrivateKey: приватний ключ на самому Debian
    • Address: IP-адреса цього Peer, буде використана для локального інтерфейсу wg0
  • [Peer]
    • PublicKey: публічний ключ з MikroTik
    • Endpoint: зовнішня адреса за якою доступний MikroTik, та порт, на якому WireGuard приймає підключення
    • AllowedIPs: в які мережі може ходити цей peer і для яких будуть створені локальні роути

На Debian отримуємо публічний ключ:

root@setevoy-do-2023-09-02:/etc/wireguard# cat publickey 
x+Pr***0TE=

На MikroTik додаємо новий peer:

/interface wireguard peers add interface=wg0 public-key="x+Pr***0TE=" allowed-address=10.100.0.10/32,192.168.0.0/24,192.168.100.0/24 comment=setevoy-rtfm

В allowed-address на MikroTik задаємо дозвіл на доступ в мережі – перевіряється і для src-addr, і для dst-addr.

Власне – на цьому все.

На Linux запускаємо підключення:

root@setevoy-do-2023-09-02:/etc/wireguard# wg-quick up wg0 
[#] ip link add wg0 type wireguard
[#] wg setconf wg0 /dev/fd/63
[#] ip -4 address add 10.100.0.10/32 dev wg0
[#] ip link set mtu 1420 up dev wg0
[#] ip -4 route add 192.168.100.0/24 dev wg0
[#] ip -4 route add 192.168.0.0/24 dev wg0
[#] ip -4 route add 10.100.0.0/24 dev wg0

Перевіряємо статус:

root@setevoy-do-2023-09-02:/etc/wireguard#wg show
interface: wg0
  public key: x+Pr/***TE=
  private key: (hidden)
  listening port: 59014

peer: hxz***50o=
  endpoint: 178.***.184:51820
  allowed ips: 10.100.0.0/24, 192.168.0.0/24, 192.168.100.0/24
  latest handshake: 1 minute, 51 seconds ago
  transfer: 16.21 KiB received, 21.75 KiB sent
  persistent keepalive: every 25 seconds

На що звертаємо увагу – це latest handshake, який відображає, що підключення активне, і піри один з одним змогли зв’язатись.

Перевіряємо peers на MikroTik:

/interface wireguard peers print where comment="setevoy-rtfm"

Перевіряємо підключення з Linux на офісний ноутбук:

root@setevoy-do-2023-09-02:~# ssh [email protected] -i .ssh/setevoy-work 
[setevoy@setevoy-work ~]$ 

І на домашній ноут – який теж є VPN peer, і у нього дві адреси – 10.100.0.3 в мережі VPN, і 192.168.100.100 в мережі домашнього роутера.

Пробуємо його адресу в VPN:

root@setevoy-do-2023-09-02:~# ping -c 1 10.100.0.3
PING 10.100.0.3 (10.100.0.3) 56(84) bytes of data.
64 bytes from 10.100.0.3: icmp_seq=1 ttl=63 time=116 ms

--- 10.100.0.3 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 115.925/115.925/115.925/0.000 ms

І на його домашню адресу:

root@setevoy-do-2023-09-02:~# ping -c 1 192.168.100.100
PING 192.168.100.100 (192.168.100.100) 56(84) bytes of data.
64 bytes from 192.168.100.100: icmp_seq=1 ttl=63 time=36.2 ms

--- 192.168.100.100 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 36.181/36.181/36.181/0.000 ms

І статус всіх клієнтів на MikriTik зараз:

WireGuard Connection Troubleshooting

Для дебагу може бути корисним додати логування проходження пакетів на фаєрволі MikroTik:

/ip firewall filter add chain=forward src-address=192.168.0.0/24 dst-address=192.168.100.0/24 action=log log-prefix="office->home" place-before=0

/ip firewall filter add chain=forward src-address=192.168.100.0/24 dst-address=192.168.0.0/24 action=log log-prefix="home->office" place-before=0

Потім пінгуємо з офісу додому – тут ще була проблема:

$ ping 192.168.100.100
PING 192.168.100.100 (192.168.100.100) 56(84) bytes of data.
^C
--- 192.168.100.100 ping statistics ---
4 packets transmitted, 0 received, 100% packet loss, time 3090ms

І дивимось логи на MikroTik – тут клієнт вдома на Arch Linux вже підключений, пофіксив:

/log print where message~"office->home|home->office"
 ...
 2026-02-17 14:53:32 firewall,info home->office forward: in:wg0 out:bridge, connection-state:established proto ICMP (type 0, code 0), 192.168.100.100->192.168.0.3, len 84
 2026-02-17 14:53:33 firewall,info office->home forward: in:bridge out:wg0, connection-state:established src-mac c8:a3:62:2e:fd:cb, proto ICMP (type 8, code 0), 192.168.0.3->192.168.100.100, len 84
 2026-02-17 14:53:33 firewall,info home->office forward: in:wg0 out:bridge, connection-state:established proto ICMP (type 0, code 0), 192.168.100.100->192.168.0.3, len 84
 2026-02-17 14:53:34 firewall,info office->home forward: in:bridge out:wg0, connection-state:established src-mac c8:a3:62:2e:fd:cb, proto ICMP (type 8, code 0), 192.168.0.3->192.168.100.100, len 84
 2026-02-17 14:53:34 firewall,info home->office forward: in:wg0 out:bridge, connection-state:established proto ICMP (type 0, code 0), 192.168.100.100->192.168.0.3, len 84

Потім видаляємо, бо буде багато писати:

/ip firewall filter remove [find log-prefix="office->home"]

/ip firewall filter remove [find log-prefix="home->office"]

Або використовуємо tcpdump на хостах.

Наприклад, з офісного ноута пінгуємо домашній:

[setevoy@setevoy-work ~]  $ ping -c 1 192.168.100.100
PING 192.168.100.100 (192.168.100.100) 56(84) bytes of data.
64 bytes from 192.168.100.100: icmp_seq=1 ttl=63 time=107 ms

--- 192.168.100.100 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 106.714/106.714/106.714/0.000 ms

А на домашньому ноуті слухаємо весь ICMP:

root@setevoy-home:~ # tcpdump -i any icmp and host 192.168.100.100
tcpdump: WARNING: any: That device doesn't support promiscuous mode
(Promiscuous mode not supported on the "any" device)
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
14:59:17.433334 wg0   In  IP work.setevoy > setevoy-home: ICMP echo request, id 25635, seq 1, length 64
14:59:17.433381 wg0   Out IP setevoy-home > work.setevoy: ICMP echo reply, id 25635, seq 1, length 64

Якщо треба оновити параметри peer – виконуємо через interface wireguard peers set.

Для домашнього ноута при створені не задав 192.168.100.0/24 в allowed-address, треба було оновити параметр:

/interface wireguard peers set [find comment="setevoy-home"] allowed-address=10.100.0.3/32,192.168.100.0/24,192.168.0.0/24

А через те, що не було 192.168.100.0/24 в allowed-address – не було прямого підключення із 192.168.0.0/24 – бо пакет йшов через WireGuard тунель, приходив на домашній ноутбук на інтерфейс wg0, потім відправлявся на інтерфейс WiFi з адресою 192.168.100.100, але так як цього не було в allowed-address – то пакет дропався.

Готово.

Loading

FreeBSD: Home NAS, part 12: синхронізація даних з Syncthing
0 (0)

14 Лютого 2026

Вже потроху наближаюсь до завершення історії з налаштування домашнього NAS на FreeBSD.

Вже є ZFS pool, є датасети, є моніторинг – можна починати налаштування автоматизації бекапів.

Але якщо на початку все здавалось доволі просто – “просто скопіювати потрібні каталоги з робочого ноутбука”, то чим далі – тим цікавішою виявлялась задача.

Більш детальний опис планування та автоматизації бекапів опишу окремо, а сьогодні познайомимось з ще одною класною утилітою – Syncthing.

Всі частини серії по налаштуванню домашнього NAS на FreeBSD:

Syncthing overview

Отже, для чого вона мені: є кілька хостів (робочий та домашній ноутбуки, ігровий ПК), між якими треба синхронізувати загальні дані.

Загальні дані – це каталоги з фотками, музикою, картинками – все те, що змінюється не дуже часто, і де нема “мусора” типу каталогів .git, logs або tmp.

Такі каталоги повинні бути однаковими між ноутами та ПК і самим NAS, і коли я почав думати як жеж це все синхронізувати – то вперся в проблему того, що дані на будь-якому хості можуть і додатись і видалитись – і треба це все діло відстежувати і копіювати всі зміни.

Rsync чи Rclone тут не дуже підходять, бо у них принцип роботи “master-slave” – є один source of truth, і його зміст контролюється з Rsync/Rclone.

А в данному випадку, коли хостів декілька і кожен може робити власні зміни, які треба “відзеркалити” на інші – треба і інший інструмент, який зможе сам моніторити і копіювати всі зміни.

До того ж є і мобільний телефон з фотками, які хочеться бекапити напряму до NAS, а не в Goolge чи Proton Drive.

Власне, тут на сцену і виходить Syncthing:

  • підключається до кількох хостів
  • для кожного хоста налаштовується які саме локальні каталоги синхронізувати з іншими хостами та які каталоги з інших хостів синхронізувати локально
  • передає дані з шифруванням трафіку

До того ж має зручний Web UI, конфіг зберігає в файлі, який легко бекапити та має клієнтів під Android та iOS, і має чудову документацію.

Трохи забігаючи наперед (бо схема с наступного поста FreeBSD: Hone NAS, part 12: планування бекапів) – роль Syncthing в моєму сетапі виглядає так:

Отже, сьогодні установимо Syncthing на NAS з FreeBSD та на ноутбук з Arch Linux, і подивимось як це все працює.

Установка Syncthing на FreeBSD

Syncthing є в репозиторії, встановлюємо його:

root@setevoy-nas:~ # pkg install syncthing

Додаємо до /etc/rc.conf:

root@setevoy-nas:~ # sysrc syncthing_enable="YES"
syncthing_enable:  -> YES
root@setevoy-nas:~ # sysrc syncthing_user="setevoy"
syncthing_user:  -> setevoy

Файл налаштувань – /usr/local/etc/syncthing/config.xml.

Більшість налаштувань виконуються через Web (хоча є і CLI), але по дефолту Syncthing запускається на localhost.

А так як це FreeBSD без X-серверу – то і браузеру там нема.

Тому редагуємо файл і задаємо IP зовнішнього інтерфейсу, в мене це 192.168.0.2 (хоча адресацію буду перероблювати, коли доберусь до MikroTik та його DHCP):

...
    <gui enabled="true" tls="false" sendBasicAuthPrompt="false">
        <address>192.168.0.2:8384</address>
        <metricsWithoutAuth>false</metricsWithoutAuth>
        <apikey>L2P***eAk</apikey>
        <theme>default</theme>
    </gui>
...

Запускаємо сервіс:

root@setevoy-nas:~ # service syncthing start
Starting syncthing.

Перевіряємо порт:

root@setevoy-nas:~ # sockstat -4 -l | grep 8384
setevoy  syncthing  34083 18  tcp4   192.168.0.2:8384   *:*

Відкриваємо дашборду:

Створення ZFS dataset

Поки знайомлюсь з системою – зробив окремий датасет:

root@setevoy-nas:~ # zfs create nas/syncthing-test
root@setevoy-nas:~ # zfs list nas/syncthing-test
NAME                 USED  AVAIL  REFER  MOUNTPOINT
nas/syncthing-test    96K  2.24T    96K  /nas/syncthing-test

Задаємо власника – бо Syncthing запускається від юзера setevoy (який заданий через syncthing_user="setevoy" в /etc/rc.conf):

root@setevoy-nas:~ # chown setevoy:setevoy /nas/syncthing-test
root@setevoy-nas:~ # ls -ld /nas/syncthing-test
drwxr-xr-x  2 setevoy setevoy 2 Feb 13 19:05 /nas/syncthing-test

Додавання каталогу

Тепер додамо локальний каталог, який можна буде зробити доступним для синхронізації на інших хостах:

Вказуємо ім’я та локальний шлях.

Folder ID залишаємо – це просто унікальний ідентифікатор для використання між хостами:

Тут жеж є налаштування Versioning – зберігання копій, далі подивимось детальніше:

І цікаві опції в Advanced – але це вже іншим разом:

Після додавання нового каталогу він буде збережений в .../syncthing/config.xml:

root@setevoy-nas:~ # cat /usr/local/etc/syncthing/config.xml | grep jmw5s-hotah
    <folder id="jmw5s-hotah" label="syncthing-test" path="/nas/syncthing-test" type="sendreceive" rescanIntervalS="3600" fsWatcherEnabled="true" fsWatcherDelayS="10" fsWatcherTimeoutS="0" ignorePerms="false" autoNormalize="true">

Тепер додаємо першого “клієнта” – хоча Syncthing все ж peer-to-peer архітектура, але конкретно в моєму випадку є окремий сервер чи хаб, а інші хости – це клієнти.

Установка Syncthing на Arch Linux

Теж є в репозиторії, встановлюємо:

[setevoy@setevoy-work ~] $ sudo pacman -S syncthing

Можна поки запустити руками – подивитись на його output:

[setevoy@setevoy-work ~]  $ syncthing
2026-02-13 19:14:21 INF syncthing v2.0.14 "Hafnium Hornet" (go1.25.6 X:nodwarf5 linux-amd64) syncthing@archlinux 2026-02-03 09:05:00 UTC [noupgrade] (log.pkg=main)
2026-02-13 19:14:21 INF Generating key and certificate (cn=syncthing log.pkg=syncthing)
2026-02-13 19:14:21 INF Default config saved; edit to taste (with Syncthing stopped) or use the GUI (path=/home/setevoy/.local/state/syncthing/config.xml log.pkg=syncthing)
2026-02-13 19:14:21 INF Archiving a copy of old config file format (path=/home/setevoy/.local/state/syncthing/config.xml.v0 log.pkg=syncthing)
2026-02-13 19:14:21 INF Calculated our device ID (device=2W2JHRW-T***-2TRDAAF log.pkg=syncthing)
2026-02-13 19:14:21 INF Overall rate limit in use (send="is unlimited" recv="is unlimited" log.pkg=connections)
2026-02-13 19:14:21 INF Using discovery mechanism (identity="global discovery server https://discovery-lookup.syncthing.net/v2/?noannounce" log.pkg=discover)
2026-02-13 19:14:21 INF Using discovery mechanism (identity="global discovery server https://discovery-announce-v4.syncthing.net/v2/?nolookup" log.pkg=discover)
2026-02-13 19:14:21 INF Using discovery mechanism (identity="global discovery server https://discovery-announce-v6.syncthing.net/v2/?nolookup" log.pkg=discover)
2026-02-13 19:14:21 INF Using discovery mechanism (identity="IPv4 local broadcast discovery on port 21027" log.pkg=discover)
2026-02-13 19:14:21 INF Using discovery mechanism (identity="IPv6 local multicast discovery on address [ff12::8384]:21027" log.pkg=discover)
2026-02-13 19:14:21 INF Relay listener starting (id=dynamic+https://relays.syncthing.net/endpoint log.pkg=connections)
2026-02-13 19:14:21 INF QUIC listener starting (address="[::]:22000" log.pkg=connections)
2026-02-13 19:14:21 INF Creating new HTTPS certificate (log.pkg=api)
2026-02-13 19:14:21 INF TCP listener starting (address="[::]:22000" log.pkg=connections)
2026-02-13 19:14:21 INF GUI and API listening (address=127.0.0.1:8384 log.pkg=api)
2026-02-13 19:14:21 INF Access the GUI via the following URL: http://127.0.0.1:8384/ (log.pkg=api)
2026-02-13 19:14:21 INF Loaded configuration (name=setevoy-work log.pkg=syncthing)
2026-02-13 19:14:21 INF Measured hashing performance (perf="1978.89 MB/s" log.pkg=syncthing)

Додавання Remote Devices

Тепер треба Syncthing на Linux додати в пул до Syncthing на FreeBSD.

На Linux йдемо в Actions > Show ID:

(QR дуже ручний для підключення мобільних клієнтів – теж вже робив, працює чудово)

Далі на FreeBSD клікаємо Add Remote Device:

Він відразу в мережі побачив клієнта на Linux-хості (див. Syncthing Discovery Server та Security Principles):

Клікаємо Save, але Linux-клієнт поки що в статусі Disconnected:

Повертаємось до Syncthing на Linux – туди приходить запит на підключення:

І тепер маємо два девайси, об’єднані в мережу.

На FreeBSD:

І на ноутбуці з Linux:

Налаштування Folder Sharing

Тепер подивимось, як працює синхронізація.

На FreeBSD у створеному раніше Folder клікаємо Edit:

Переходимо на вкладку Sharing, вибираємо девайси, і якими хочемо зашарити папку:

Аналогічно до процесу додавання Devices – спочатку нам на Linux-клієнт прийде запит на підтвердження:

Клікаємо Add, задаємо локальний шлях на ноутбуці з Linux:

Перевіряємо, як це все діло працює.

Створимо файл на FreeBSD:

root@setevoy-nas:~ # echo "hello from nas" > /nas/syncthing-test/test1.txt

Дивимось Syncthing output на ноуті – пише що і коли змінилось:

...
2026-02-13 19:23:54 INF Synced file (folder.label=syncthing-test folder.id=jmw5s-hotah folder.type=sendreceive file.name=test1.txt file.modified="2026-02-13 19:23:43.432316 +0200 EET" file.permissions=0644 file.size=15 file.blocksize=131072 blocks.local=0 blocks.download=1 log.pkg=model)
...

І файл тепер є на Linux-клієнті:

[setevoy@setevoy-work ~]  $ ll nas/syncthing-test/
total 4
-rw-r--r-- 1 setevoy setevoy 15 Feb 13 19:23 test1.txt

Перевіримо зворотню синхронізацію – додамо файл на Linux:

[setevoy@setevoy-work ~]  $ echo "hello from laptop" > /home/setevoy/nas/syncthing-test/test2.txt

І через декілька секунд – він є і на FreeBSD:

root@setevoy-nas:~ # cat /nas/syncthing-test/test2.txt 
hello from laptop

Тестуємо видалення:

[setevoy@setevoy-work ~]  $ rm /home/setevoy/nas/syncthing-test/test2.txt

І на FreeBSD він теж зникає:

root@setevoy-nas:~ # ll /nas/syncthing-test/
total 3
drwxr-xr-x  2 setevoy setevoy    3B Feb 13 19:09 .stfolder
-rw-r--r--  1 root    setevoy   15B Feb 13 19:23 test1.txt
-rw-r--r--  1 setevoy setevoy   18B Feb 13 19:25 test2.txt
root@setevoy-nas:~ # ll /nas/syncthing-test/
total 2
drwxr-xr-x  2 setevoy setevoy    3B Feb 13 19:09 .stfolder
-rw-r--r--  1 root    setevoy   15B Feb 13 19:23 test1.txt

Налаштування Versioning для бекапів

Тепер про те, як можна бекапити дані – захист від випадкового видалення.

Документація – File Versioning.

Переходимо в Folder > Edit, вкладка File Versioning:

Тут опції:

  • Trash Can: при видаленні файл переноситься в .stversions
  • Simple: зберігає N останніх версій
  • Staggered: зберігає версії з часом (1h, 1d, 1w і т.д.)
  • External: викликати зовнішній скрипт

Спробуємо з Trash Versioning:

На Linux-клієнті створимо новий файл:

[setevoy@setevoy-work ~]  $ echo "hello from laptop" > /home/setevoy/nas/syncthing-test/test-trash.txt

Чекаємо на його появу на FreeBSD-хості:

root@setevoy-nas:/home/setevoy # ll /nas/syncthing-test/
total 3
drwxr-xr-x  2 setevoy setevoy    3B Feb 13 19:09 .stfolder
-rw-r--r--  1 setevoy setevoy   18B Feb 13 19:33 test-trash.txt
-rw-r--r--  1 root    setevoy   15B Feb 13 19:23 test1.txt

Видаляємо на ноутбуці:

[setevoy@setevoy-work ~]  $ rm /home/setevoy/nas/syncthing-test/test-trash.txt

І через кілька секунд він зникає на FreeBSD:

root@setevoy-nas:/home/setevoy # ll /nas/syncthing-test/
total 3
drwxr-xr-x  2 setevoy setevoy    3B Feb 13 19:09 .stfolder
-rw-r--r--  1 setevoy setevoy   18B Feb 13 19:33 test-trash.txt
-rw-r--r--  1 root    setevoy   15B Feb 13 19:23 test1.txt
root@setevoy-nas:/home/setevoy # ll /nas/syncthing-test/
total 3
drwxr-xr-x  2 setevoy setevoy    3B Feb 13 19:09 .stfolder
drwxr-xr-x  2 setevoy setevoy    3B Feb 13 19:34 .stversions
-rw-r--r--  1 root    setevoy   15B Feb 13 19:23 test1.txt

Але збережений в .stversions/:

root@setevoy-nas:/home/setevoy # ll /nas/syncthing-test/.stversions/
total 1
-rw-r--r--  1 setevoy setevoy   18B Feb 13 19:34 test-trash.txt

Крім того, у Web тепер є кнопочка Versions:

Де показані видалені файли, і які звідси можна відновити:

Для NAS, скоріш за все, зроблю Trash на 30 днів, а довготривалі бекапи будуть через ZFS snaphosts + копіювання на Google/Roton Drive та AWS S3.

Наступні кроки

Ну і тепер можна на Linux-клієнт додати Syncthing в автостарт:

[setevoy@setevoy-work ~]  $ systemctl --user enable syncthing.service
Created symlink '/home/setevoy/.config/systemd/user/default.target.wants/syncthing.service' → '/usr/lib/systemd/user/syncthing.service'.
[setevoy@setevoy-work ~]  $ systemctl --user start syncthing.service
[setevoy@setevoy-work ~]  $ systemctl --user status syncthing.service
● syncthing.service - Syncthing - Open Source Continuous File Synchronization
     Loaded: loaded (/usr/lib/systemd/user/syncthing.service; enabled; preset: enabled)
     Active: active (running) since Fri 2026-02-13 19:41:17 EET; 3s ago
...

Можна додати запуск сервісу без user login – корисно для ребутів, див. loginctl:

[setevoy@setevoy-work ~]  $ loginctl enable-linger setevoy

І додати базову перевірку в Uptime Kuma (про неї теж скоріш за все буду писати ще окремо, в мене Kuma крутиться на окремому хості для “міні-моніторинга” на Raspberry PI):

Є у Syncthing і Prometheus метрики, див. Prometheus-Style Metrics – можна буде додати до VictoriaMetrics і створити Grafana dashboard та алерти.

І варто налаштувати бекапи для файлів Syncthing:

[setevoy@setevoy-work ~]  $ ll ~/.local/state/syncthing
total 40
-rw-r--r-- 1 setevoy setevoy   623 Feb 13 19:14 cert.pem
-rw------- 1 setevoy setevoy 11236 Feb 13 20:15 config.xml
...
-rw------- 1 setevoy setevoy   119 Feb 13 19:14 key.pem
...

Далі почитати і поробити Configuration Tuning, налаштувати Firewall Setup, і уважно перечитати Security Principles.

Наостанок – як Syncthing виглядає на телефоні з Syncthing-Fork:

І клієнт телефона в дашборді на FreeBSD:

Готово.

Loading

Raspberry Pi: перший досвід і установка Raspberry Pi OS Lite
0 (0)

12 Лютого 2026

Для тих, хто не слідкує за апдейтами в Telegram-каналі rtfmcoua або просто перший раз зайшов на мій блог – нагадаю, що останні пару місяці збираю такий собі “self-hosted home stack”, в якому вже є пара MikroTik і ThinkCentre з FreeBSD.

На ThinkCentre / FreeBSD в мене NAS на ZFS mirror pool (див. FreeBSD: Home NAS, part 1), і “центральний моніторинг” з VictoriaMetrics + Grafana (див. FreeBSD: Home NAS, part 10 – моніторинг з VictoriaMetrics).

До цього всього щастя вирішив ще додати окрему машинку під mini-monitoring, плюс там захостити системи типу Glance (див. Glance: налаштування self-hosted home page для браузера), бо ThnikCentre під час довгих блекаутів виключаю (хоча там споживання всього близько 20 Вт-год).

Ну і… Колись пробував Arduino – класно штука, але далі “Hello, World” діло не пішло (принаймні поки що), да і якісь системи типу Uptime Kuma на Arduino не захостиш.

Давно хотів спробувати погратись з Raspberry Pi, тільки раніше не міг придумати “а нахуа?” – і ось, нарешті, з’явилась відповідь на це велике питання.

Вибір Raspberry Pi

Чесно – я особо не вибирав 🙂

Точніше, вибирав, бо “вау, кросівєнько!” – десь випадково побачив Raspberry Pi Compute Module 4 PoE Mini-Computer, уявив, як він класно став би в мою серверну шафу – і вирішив взяти його.

Виглядає він ось так:

Є більш нові Compute Module версії 5 – але для моїх цілей, до того ж для першого досвіду, 4 версії вистачить з головою.

Отже, маємо:

Купував в магазині https://minicomp.com.ua – не реклама, але магазин наче нормальний, відправили швидко, підтримка по телефону/Telegram працює, нарікань нуль.

Єдине, що ще окремо довелось купувати кріплення.

Установка операційної системи

Трохи сексу 🙂

Бо перший раз, і це не “вставити USB-флешку з готовим образом”.

Спочатку хотів встановлювати Debian, і, в принципі, вдалося, але…

Я не зміг залогінитись в систему :facepalm:

Тому просто поставив Raspberry Pi OS Lite, і там все пройшло (точніше – увійшло 🙂 ) без проблем.

Втім, так як перший досвід – то збережу тут процес і для Debian теж.

Установка Raspberry Pi Debian

Качаємо на raspi.debian.net:

[setevoy@setevoy-work ~] $ ls ~/Downloads/Rasp/
debian-13-raspi-arm64-daily.tar.xz

Розпаковуваємо (хоча, як виявилось, можна і без цього – див. простіший варіант далі в Установка Raspberry Pi OS Lite):

[setevoy@setevoy-work ~] $ cd ~/Downloads/Rasp/
[setevoy@setevoy-work ~] $ tar xfp debian-13-raspi-arm64-daily.tar.xz 

В архіві лежить disk.raw – це повний образ диска з вже готовим GPT/MBR та boot partition:

[setevoy@setevoy-work ~] $ fdisk -l ~/Downloads/Rasp/disk.raw 
Disk /home/setevoy/Downloads/Rasp/disk.raw: 3 GiB, 3221225472 bytes, 6291456 sectors
...
Disklabel type: gpt
Disk identifier: 580A523C-E6C1-4021-8A56-D664D3C75FA2

Device                                    Start     End Sectors  Size Type
/home/setevoy/Downloads/Rasp/disk.raw1  1048576 6289407 5240832  2.5G Linux root (ARM-64)
/home/setevoy/Downloads/Rasp/disk.raw15    2048 1048575 1046528  511M EFI System

Partition table entries are not in disk order.

Підключення USB до ноутбука

Отуто теж трохи витратив часу, бо дуже незвична і ніфіга не очевидна схема перемикання на завантаження по USB.

Навіть якась ностальгія по часам, коли на HDD перемикав джампери Primary/Slave.

Картинка для тих, хто не бачив це наживо

На моємо CM4 піни для включення завантаження з USB знайшлись отут:

Зайвого джамперу не було, але можна взяти з FAN/VDD:

Перемикаємо джампер, підключаємо звичайним USB-кабелем до ноутбука, перевіряємо девайси – має з’явитись Broadcom:

[setevoy@setevoy-work ~] $ lsusb | grep Broa
Bus 003 Device 024: ID 0a5c:2711 Broadcom Corp. BCM2711 Boot

Встановлюємо rpiusbboot – утиліта підключиться до Raspberry Pi Compute Module та змонтує її eMMC (embedded MultiMediaCard) диск до ноутбука як звичайну флешку:

[setevoy@setevoy-work ~] $ yay -S rpiusbboot

Запускаємо:

[setevoy@setevoy-work ~] $ sudo rpiusbboot 
RPIBOOT: build-date Feb 12 2026 version 20221215~105525 b41ab04a
Waiting for BCM2835/6/7/2711...
Loading embedded: bootcode4.bin
Sending bootcode.bin
Successful read 4 bytes 
Waiting for BCM2835/6/7/2711...
Loading embedded: bootcode4.bin
Second stage boot server
Cannot open file config.txt
Cannot open file pieeprom.sig
Loading embedded: start4.elf
File read: start4.elf
Cannot open file fixup4.dat
Second stage boot server done

Тепер маємо новий диск в системі:

[setevoy@setevoy-work ~] $ lsblk 
NAME          MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINTS
sda             8:0    1  29.1G  0 disk  

Копіюємо образ, який скачали вище:

[setevoy@setevoy-work ~] $ sudo dd if=~/Downloads/Rasp/disk.raw of=/dev/sda bs=4M status=progress conv=fsync

По завершенню – відключаємо живлення Малинки, повертаємо джампер назад до FAN/VDD, і завантажуємось у звичайному режимі.

Але… Як писав вище – я не не зміг залогінитись.

Є такий gist з дефолтними логінами:паролями – жоден не підійшов.

В документації Debian говориться, що root просто без пароля – але не пускало.

Тому я забив на “чистий” Debian, і просто взяв Raspberry Pi OS Lite, тим більш він все одно Debian-based.

І, мабуть, це для Малинки навіть краще.

До того ж – вперше подивився, як робити переустановку системи на eMMC.

Установка Raspberry Pi OS Lite

Знов переключаємо джампер, підключаємо USB до ноутбука, ще раз запускаємо rpiusbboot.

Видаляємо все з диска на Raspberry (УВАЖНО перевіряємо девайс!):

[setevoy@setevoy-work ~] $ sudo wipefs -a /dev/sda
/dev/sda: 8 bytes were erased at offset 0x00000200 (gpt): 45 46 49 20 50 41 52 54
/dev/sda: 2 bytes were erased at offset 0x000001fe (PMBR): 55 aa
/dev/sda: calling ioctl to re-read partition table: Success

Качаємо образ з сайту Raspberry, отримуємо архів 2025-12-04-raspios-trixie-arm64-lite.img.xz.

Тепер робимо той самий dd, але вже просто передаємо до нього образ через xzcat і pipe:

[setevoy@setevoy-work ~] $ xzcat ~/Downloads/ISO/2025-12-04-raspios-trixie-arm64-lite.img.xz | sudo dd of=/dev/sda bs=4M status=progress conv=fsync

Повертаємо джампер, завантажуємось, і – ура!

Створюємо юзера, логінимось – все працює.

Включення SSH

Suprize – але systemctl start sshd тут не варіант 🙂

Хоча systemd в системі є.

Запускаємо raspi-config:

setevoy@raspberrypi:~ $ sudo raspi-config

Переходимо в Interface Options:

Вибираємо і вмикаємо SSH:

Підключаємось:

[setevoy@setevoy-work ~] $ ssh 192.168.0.61
...
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
setevoy@raspberrypi:~ $ 

Запускаємо апгрейд системи:

setevoy@raspberrypi:~ $ sudo apt update && sudo apt full-upgrade -y

Ну і вже налаштовуємо всякі hostname, timezone і решту потрібних параметрів системи.

Static IP на MikroTik

Трохи про мережу, хоча тут все доволі стандартно – NetworkManager та nmcli.

В мене MikroTik, і зараз Raspberry Pi має динамічний IP з пулу DHCP сервера:

Додаємо статичний lease на MAC-адресу Малинки:

/ip dhcp-server lease add address=192.168.0.5 mac-address=2C:CF:67:59:14:9D comment=setevoy-pi

Видаляємо старий:

/ip dhcp-server lease remove 5

Перевіряємо підключення на Raspberry:

setevoy@raspberrypi:~ $ nmcli device status
DEVICE  TYPE      STATE                   CONNECTION         
eth0    ethernet  connected               Wired connection 1 
lo      loopback  connected (externally)  lo

І виконуємо або sudo nmcli device reapply eth0, або sudo nmcli device disconnect eth0 && sudo nmcli device connect eth0, або просто reboot – і тепер можемо підключатись за новою адресою:

[setevoy@setevoy-work ~] $ ssh 192.168.0.5
...
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Thu Feb 12 11:54:41 2026 from 192.168.0.3
setevoy@raspberrypi:~ $ 

Можна відразу на MikroTik додати новий DNS record:

/ip dns static add name=pi.setevoy address=192.168.0.5 ttl=1d

Перевіряємо:

/ip dns static print where name=pi.setevoy

І перевіряємо з робочого ноутбука:

[setevoy@setevoy-work ~]  $ dig pi.setevoy +short
192.168.0.5

В принципі, на цьому все.

Встановлюємо Docker, Docker Compose, запускаємо всякі Glance і Online Kuma.

Далі Online Kuma можна налаштувати на відправку альортів до, наприклад, ntfy.sh – і мати моніторинг свого моніторингу.

А стоїть моя Малинка ось тут:

Про збірку шафи буду писати окремо в заключній частині по налаштуванню Home NAS.

Loading

Glance: налаштування self-hosted home page для браузера
0 (0)

11 Лютого 2026

Є така прикольна штука, як self-hosted home pages.

Колись побачив їх десь на Reddit, зберіг в закладки, і ось тепер, як в мене є всяка self-hosted тема з NAS (див. FreeBSD: Home NAS, part 1), Grafana і іншими корисними в роботі і побуті речами – то подумав, що було б непогано зробити і собі таку дашборду.

З тих, що в мене збережені, і вони, здається, найбільш популярні – це gethomepage/homepage та glanceapp/glance.

Але Homepage якась більш важка, з купою компонентів – фронтенд, бекенд, якісь рендери, а Glance – простіша, і при цьому, в принципі, має все, що я хотів побачити – хоча місцями це робиться через костилі 🙂

Власне – налаштування Glance.

Робити поки буду локально на ноутбуці з Docker Compose, пізніше перенесу конфіг або на FreeBSD/NAS чи на Raspberry PI з Debian.

Налаштування Glance

Див. документацію Pages & Columns.

Основні компоненти – це Pages, Columns та Widgets:

  • pages: власне, сторінки – можна мати кілька вкладок
  • columns: кожна page розбивається на кілька колонок
  • widgets: і в кожній колонці є набір віджетів

Віджети теж можна групувати і робити вкладки – далі побачимо на прикладі Reddit.

Для погратись – легко запускається з Docker, див. Installation.

Окремих тем нема – все налаштовується через стилі, наприклад:

...
theme:
  background-color: 225 14 15
  primary-color: 157 47 65
  contrast-multiplier: 1.1
...

Але є готові стилі – див. Themes.

Окрім дефолтних віджетів є community widgets, наприклад я там собі взяв NextDNS Stats.

Мої Pages та Widgets

Коротко – приклад того, як це все можна налаштувати.

Так як в мене це все хоститься локально і кудись в GitHub я конфіг зберігати не буду – то всякі токени записав прямо в конфіг, але взагалі для них можна використати змінні оточення – див. Other ways of providing tokens/passwords/secrets.

Перша сторінка – Home з трьома колонками:

Clock, Weather, Calendar

Час, погода і календар:

...
pages:
  - name: Home
    columns:
      # ---------- LEFT ----------
      - size: small
        widgets:
          - type: clock
            hour-format: 24h
            timezones:
              - timezone: Asia/Bangkok
                label: Chiang Mai
              - timezone: America/New_York
                label: New York

          - type: weather
            location: Kyiv
            units: metric

          - type: calendar
...

В clock віджеті додав ще дві зони – бо в США у нас частина розробників, а в Chiang Mai – розробник один, але часто з ним спілкуюсь і постійно згадую яка в нього зараз година.

Центрально колонка – з типом full, і для кожної page треба мати як мінімум одну колонку з full.

Search

Тут віджет search:

...
      # ---------- CENTER ----------
      - size: full
        widgets:
          - type: search
            search-engine: duckduckgo
            new-tab: true
            autofocus: true
            bangs:
              - title: YouTube
                shortcut: "!yt"
                url: https://www.youtube.com/results?search_query={QUERY}
...

Bookmarks

Bookmarks – основні для швидкого доступу, розбиті по категоріям:

...
          - type: bookmarks
            groups:
              - title: Local
                color: 120 70 50
                target: _self
                links:
                  - title: MikroTik Gateway
                    url: http://192.168.0.1/webfig/#IP:DHCP_Server.Leases
...
              - title: AI
                color: 260 50 70
                target: _self
                links:
                  - title: ChatGPT
                    url: https://chat.openai.com/chat
...
              - title: RTFM
                color: 200 50 50
                target: _self
                links:
                  - title: RTFM Main
                    url: https://rtfm.co.ua/
...

Для вибору color можна скористатись colorpicker.dev: перша цифра – сам колір, друга – saturation (насиченість), третя – lightness (яскравість).

Group для Reddit

Далі приклад групування з Group – зробив собі окремі вкладки для різних сабредітів, але двома окремими групами – умовний “Reddit Ukraine” і “Reddit IT”:

...
          - type: group
            widgets:
              - type: reddit
                subreddit: finance_ukr
                show-thumbnails: true
              - type: reddit
                subreddit: durka_ukr
                show-thumbnails: true

          - type: group
            widgets:
              - type: reddit
                subreddit: aws
                show-thumbnails: false
              - type: reddit
                subreddit: archlinux
                show-thumbnails: false
...

Split Column для новин

Наступний віджет – Split Column, де більше стисло новини і якісь цікаві матеріали:

...
          - type: split-column
            max-columns: 2
            widgets:
              - type: lobsters
                sort-by: hot
                limit: 15
                collapse-after: 5
              - type: rss
                title: News
                style: vertical-list
                feeds:
                  - url: https://aws.amazon.com/blogs/aws/feed/
                    title: AWS Blogs
                  - url: https://skeletor.org.ua/?feed=rss2
                    title: Skeletor
...

Ну і більш цікава частина – справа, інформація по статусу сервісів.

GitHub Releases

Віджет releases – останні релізи в GitHub:

...
      - size: small
        widgets:
          - type: releases
            token: github_pat_11A***1jF
            repositories:
              - VictoriaMetrics/VictoriaMetrics
              - pdf/zfs_exporter
              - tess1o/go-ecoflow-exporter
...

Мені за zfs_exporter і go-ecoflow-exporter є сенс слідкувати, бо вони в мене деплояться вручну, див. FreeBSD: Home NAS, part 11 – extended моніторинг з додатковими експортерами.

Хоча, звісно, ніхто не відміняє можливість просто підписатись на апдейти в самому GitHub 🙂

Custom API для NextDNS

Приклад кастомного віджета custom-api – інформація по моєму NextDNS:

...
          - type: custom-api
            title: NextDNS Analytics
            title-url: https://api.nextdns.io/profiles/***/analytics/status
            cache: 1h
            url: https://api.nextdns.io/profiles/***/analytics/status
            headers:
              X-Api-Key: 3f8***457
            template: |
              {{ if eq .Response.StatusCode 200 }}
                <div style="display: flex; justify-content: space-between;">
                  {{ $total := 0.0 }}
                  {{ $blocked := 0.0 }}
                  {{ range .JSON.Array "data" }}
                    {{ $total = add $total (.Int "queries" | toFloat) }}
                    {{ if eq (.String "status") "blocked" }}
                      {{ $blocked = add $blocked (.Int "queries" | toFloat) }}
              ...
                <div style="text-align: center; color: var(--color-negative);">
                  Error: {{ .Response.StatusCode }} - {{ .Response.Status }}
                </div>
              {{ end }}
...

І далі ще буде приклад з власним міні-API сервісом.

Monitor – статуси HTTP-сервісів

Віджет monitor – прикольна штука для відображення статусу сервісів, робить простий GET-запит на вказаний URL, ну і працює (принаймні поки що) тільки з HTTP/S:

...
          - type: monitor
            cache: 1m
            title: Services
            sites:
              - title: RTFM
                url: https://rtfm.co.ua
                icon: /assets/rtfm-icon-2.png
              - title: MikroTik RB4011
                url: http://192.168.0.1
                icon: sh:mikrotik-light
              - title: Grafana
                url: http://nas.setevoy:3000
                icon: sh:grafana
              - title: Jellyfin
                url: http://nas.setevoy:8096
                icon: sh:jellyfin
...

На MikroTik RB4011 (див. MikroTik: перше знайомство та Getting Started), наприклад, з локальної мережі доступний web-інтерфейс, тому через нього можна перевіряти статус.

Аби підключити іконки – шукаємо дефолтні на, наприклад, selfh.st або dashboardicons.com.

Або можна задати кастомні іконки – додаємо файли в каталог /assets і включаємо його в конфігу Glance:

...
server:
  host: 0.0.0.0
  port: 8080
  assets-path: /app/assets
...

Server Stats – CPU, RAM, Disk

Цікава штука server-stats, хоча вона ще в beta:

...
          - type: server-stats
            servers:
              - type: local
                name: setevoy-work
...

Потребує додаткового сервісу – glanceapp/agent, я його поки що просто додав до docker-compose.yaml:

services:
  glance:
    container_name: glance
    image: glanceapp/glance
    restart: unless-stopped
    volumes:
      - ./config:/app/config
      - ./assets:/app/assets
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
    ports:
      - 8080:8080
    env_file: .env

  glance-agent:
    container_name: glance-agent
    image: glanceapp/agent
    ports:
      - 27973:27973

Docker containers

І останній тут – запущені Docker-контейнери, бо іноді забуваю, що щось запущено:

...
          - type: docker-containers
            hide-by-default: false
            running-only: true
...

(про Uptime Kuma теж напишу)

Вся сторінка Home вийшла поки що такою:

Custom API для FreeBSD/NAS

Ну і з цікавих рішень: хочеться з FreeBSD виводити якусь цікаву інформацію.

Active SSH connections

Спершу приклад “активні SSH-юзери” – перевіряємо хто підключений і звідки, виводимо тільки унікальні адреси:

root@setevoy-nas:~ # who | awk '$6 ~ /^\(/ {print $1, $6}' | sort -u
setevoy (10.100.0.3)
setevoy (192.168.0.3)

10.100.0.3 – це я підключений з домашнього ноута через VPN, а 192.168.0.3 – ноутбук в локальній мережі в офісі.

Тепер робимо простий python-скрипт, який буде нашим API-ендпоінтом.

Пишемо файл /usr/local/bin/glance_api.py:

#!/usr/bin/env python3.11
# minimal API server for Glance NAS page
# exposes active SSH sessions as JSON

from http.server import BaseHTTPRequestHandler, HTTPServer
import subprocess
import json

HOST = "192.168.0.2"
PORT = 9001


class Handler(BaseHTTPRequestHandler):

    def do_GET(self):

        if self.path == "/ssh":

            # run who and extract unique SSH sessions
            output = subprocess.check_output(
                "who | awk '$6 ~ /^\\(/ {print $1, $6}' | sort -u",
                shell=True
            ).decode().strip().splitlines()

            sessions = []

            for line in output:
                if line:
                    parts = line.split()
                    sessions.append({
                        "user": parts[0],
                        "ip": parts[1].strip("()")
                    })

            response = {
                "count": len(sessions),
                "sessions": sessions
            }

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps(response).encode())

        else:
            self.send_response(404)
            self.end_headers()


if __name__ == "__main__":
    server = HTTPServer((HOST, PORT), Handler)
    server.serve_forever()

Запускаємо його:

root@setevoy-nas:~ # chmod +x /usr/local/bin/glance_api.py

root@setevoy-nas:~ # /usr/local/bin/glance_api.py

Перевіряємо локально:

root@setevoy-nas:~ # curl 192.168.0.2:9001/ssh
{"count": 2, "sessions": [{"user": "setevoy", "ip": "10.100.0.3"}, {"user": "setevoy", "ip": "192.168.0.3"}]}

Тепер додаємо в Glance новий віджет з типом custom-api:

...
      # ---------- RIGHT ----------
      - size: small
        widgets:
          - type: custom-api
            title: Active SSH
            url: http://nas.setevoy:9001/ssh
            template: |
              {{ $count := .JSON.Int "count" }}

              {{ if eq $count 0 }}
                No active SSH sessions
              {{ else }}
                <ul class="list list-gap-10">
                {{ range .JSON.Array "sessions" }}
                  <li>
                    <span class="color-highlight">{{ .String "user" }}</span>
                    <span class="color-muted">({{ .String "ip" }})</span>
                  </li>
                {{ end }}
                </ul>
              {{ end }}
...

І результат:

Uptime, CPU, ZFS pool status

Додатково можна вивести ще інформацію – uptime, CPU load, etc.

Додаємо в скрипт ще один ендпоінт /status, тепер весь скрипт такий:

#!/usr/bin/env python3.11
# minimal API server for Glance NAS page
# exposes active SSH sessions as JSON

from http.server import BaseHTTPRequestHandler, HTTPServer
import subprocess
import json

HOST = "192.168.0.2"
PORT = 9001


class Handler(BaseHTTPRequestHandler):

    def do_GET(self):

        if self.path == "/ssh":

            # run who and extract unique SSH sessions
            output = subprocess.check_output(
                "who | awk '$6 ~ /^\\(/ {print $1, $6}' | sort -u",
                shell=True
            ).decode().strip().splitlines()

            sessions = []

            for line in output:
                if line:
                    parts = line.split()
                    sessions.append({
                        "user": parts[0],
                        "ip": parts[1].strip("()")
                    })

            response = {
                "count": len(sessions),
                "sessions": sessions
            }

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps(response).encode())

        elif self.path == "/status":

            # get uptime and load averages
            uptime_raw = subprocess.check_output(
                "uptime",
                shell=True
            ).decode().strip()

            # extract load averages
            load_part = uptime_raw.split("load averages:")[1].strip()
            load_values = [x.strip() for x in load_part.split(",")]

            # extract uptime text
            uptime_text = uptime_raw.split(" up ", 1)[1].split(", load averages:", 1)[0].rsplit(",", 1)[0].strip()

            # get zpool info
            zpool_raw = subprocess.check_output(
                "zpool list -H -o name,health,size,alloc,free,capacity",
                shell=True
            ).decode().strip().split()

            zpool = {
                "name": zpool_raw[0],
                "health": zpool_raw[1],
                "size": zpool_raw[2],
                "alloc": zpool_raw[3],
                "free": zpool_raw[4],
                "capacity": zpool_raw[5],
            }

            # get child datasets only
            datasets_raw = subprocess.check_output(
                "zfs list -H -o name,used,avail -r nas | tail -n +2",
                shell=True
            ).decode().strip().splitlines()

            datasets = []

            for line in datasets_raw:
                parts = line.split()
                datasets.append({
                    "name": parts[0],
                    "used": parts[1],
                    "avail": parts[2],
                })

            response = {
                "load": {
                    "1m": load_values[0],
                    "5m": load_values[1],
                    "15m": load_values[2],
                },
                "uptime": uptime_text,
                "zpool": zpool,
                "datasets": datasets
            }

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps(response).encode())

        else:
            self.send_response(404)
            self.end_headers()


if __name__ == "__main__":
    server = HTTPServer((HOST, PORT), Handler)
    server.serve_forever()

Відразу скрипт для rc.d/usr/local/etc/rc.d/glance_api:

#!/bin/sh

# PROVIDE: glance_api
# REQUIRE: NETWORKING
# KEYWORD: shutdown

. /etc/rc.subr

name="glance_api"
rcvar="glance_api_enable"

command="/usr/local/bin/python3.11"
command_args="/usr/local/bin/glance_api.py"
pidfile="/var/run/${name}.pid"

start_cmd="${name}_start"
stop_cmd="${name}_stop"

glance_api_start()
{
    echo "Starting glance_api..."
    daemon -p ${pidfile} ${command} ${command_args}
}

glance_api_stop()
{
    echo "Stopping glance_api..."
    if [ -f ${pidfile} ]; then
        kill `cat ${pidfile}`
        rm -f ${pidfile}
    fi
}

load_rc_config $name
: ${glance_api_enable:=no}

run_rc_command "$1"

Запускаємо:

root@setevoy-nas:~ # chmod +x /usr/local/etc/rc.d/glance_api
root@setevoy-nas:~ # sysrc glance_api_enable=YES
glance_api_enable:  -> YES

root@setevoy-nas:~ # service glance_api start
Starting glance_api...

В Glance додаємо ще один блок:

...
          - type: custom-api
            title: NAS Status
            url: http://nas.setevoy:9001/status
            template: |
              <div>
                <div><b>Uptime:</b> {{ .JSON.String "uptime" }}</div>

                <div style="margin-top:10px;">
                  <b>Load:</b>
                  {{ .JSON.String "load.1m" }} /
                  {{ .JSON.String "load.5m" }} /
                  {{ .JSON.String "load.15m" }}
                </div>

                <div style="margin-top:10px;">
                  <b>ZFS:</b>
                  <span class="color-positive">
                    {{ .JSON.String "zpool.health" }}
                  </span>
                  ({{ .JSON.String "zpool.capacity" }} used)
                </div>

                <div style="margin-top:10px;">
                  <b>Datasets:</b>
                  <ul class="list list-gap-5">
                  {{ range .JSON.Array "datasets" }}
                    <li>
                      {{ .String "name" }} —
                      {{ .String "used" }} used
                    </li>
                  {{ end }}
                  </ul>
                </div>
              </div>
...

І тепер NAS Page виглядає так:

Можна було б в glance_api.py додати і виконання дій через POST – але я не став заморачуватись, та і виконувати команди з дашборди – це вже трохи занадто.

Останній штрих – Chrome/Firefox extension Custom New Tab URL:

Ну а потім, яе Glance переїде на окремий хост – то замінити URL.

Єдиний мінус в Glance – що він не вміє в auto refresh 🙁 Але можна зробити теж через екстешени браузеру.

Loading

FreeBSD: Home NAS, part 11 – extended моніторинг з додатковими експортерами
0 (0)

9 Лютого 2026

В попередньому пості FreeBSD: Home NAS, part 10 – моніторинг з VictoriaMetrics та Grafana налаштували VictoriaMetrics, node_exporter, Grafana, зробили базовий дашборд і базові алерти.

Тепер до цього хочеться додати  трохи більше моніторингу – бачити дані по CPU/RAM процесів, інформацію по SMART та ZFS.

Все, що тут написав – додав в репозиторій setevoy2/nas-monitoring: там і скрипти, і Grafana dashboard.

Всі частини серії по налаштуванню домашнього NAS на FreeBSD:

Установка кастомних експортерів

Не всі експортери мають порти або є в репозиторії – тому опишу, як зробив власний “pseudo FreeBSD port”.

Установка ZFS exporter

Репозиторій експортеру – zfs_exporter.

Взагалі в портах є інший експортер – py-prometheus-zfs, але я вже зробив з цим, і вийшло наче непогане – і цікаве – рішення, тому збережу в блог те, як це налаштував.

І те саме рішення використав для go-ecoflow-exporter.

Отже, у нас є GitHub репозиторій експортеру, в репозиторії є релізи, де можна скачати готовий білд – але не всі підтримують готові файли для FreeBSD.

Зато у всіх є код, і більшість сервісі на Go – тому їх легко зібрати самому.

Ідея доволі проста:

  • скрипт build.sh: завантажити або оновити код експортеру
  • Makefile: для запуску build.sh та копіювання файлу самого експортеру і його rc.d скрипта

Створюємо структуру каталогів:

# mkdir -p /opt/exporters/zfs_exporter/{rc.d,src}

Створення build.sh

Додаємо скрипт – він буде клонити репозиторій і виконувати go build.

Не став заморачуватись з файлом VERSION – просто беремо master бранч, і білдимо з нього.

Пишемо /opt/exporters/zfs_exporter/build.sh:

#!/bin/sh

# stop on first error
set -e

BASE_DIR="/opt/exporters/zfs_exporter"
SRC_DIR="${BASE_DIR}/src/zfs_exporter"
BIN_NAME="zfs_exporter"
REPO_URL="https://github.com/pdf/zfs_exporter.git"

# ensure src dir exists
mkdir -p "${BASE_DIR}/src"

# clone repo if it does not exist
if [ ! -d "${SRC_DIR}" ]; then
    git clone "${REPO_URL}" "${SRC_DIR}"
fi

cd "${SRC_DIR}"

# always update sources
git pull

# build binary into BASE_DIR
go build -o "${BASE_DIR}/${BIN_NAME}"

Задаємо права на запуск:

# chmod +x /opt/exporters/zfs_exporter/build.sh

Запускаємо:

# /opt/exporters/zfs_exporter/build.sh

Перевіряємо:

# ll /opt/exporters/zfs_exporter/src/
total 8
drwxr-xr-x  6 root setevoy  512B Feb  9 13:32 zfs_exporter

І бінарний файл:

# file /opt/exporters/zfs_exporter/zfs_exporter 
/opt/exporters/zfs_exporter/zfs_exporter: ELF 64-bit LSB executable, x86-64, version 1 (FreeBSD) ...

Можна запустити його для перевірки:

# /opt/exporters/zfs_exporter/zfs_exporter
time=2026-02-09T13:42:30.119+02:00 level=INFO source=zfs_exporter.go:40 msg="Starting zfs_exporter" version="(version=, branch=, revision=7af698c8844864eb1e724ed08c47e5a7b4bbcc53)"
time=2026-02-09T13:42:30.120+02:00 level=INFO source=zfs_exporter.go:41 msg="Build context" context="(go=go1.24.12, platform=freebsd/amd64, user=, date=, tags=unknown)"
...
time=2026-02-09T13:42:30.120+02:00 level=INFO source=tls_config.go:354 msg="Listening on" address=[::]:9134
...

Тепер додаємо Makefile, аби простіше буде робити установку і апдейти.

Створення Makefile

Вся логіка апдейту і білда буде в build.sh, а в Makefile просто викликаємо сам скрипт і виконуємо установку файлів в систему:

# simple makefile for zfs_exporter

PREFIX=/usr/local
BIN_NAME=zfs_exporter
BASE_DIR=/opt/exporters/zfs_exporter

.PHONY: build install clean

build:
  $(BASE_DIR)/build.sh

install:
  install -m 0755 $(BASE_DIR)/$(BIN_NAME) $(PREFIX)/bin/$(BIN_NAME)

clean:
  rm -f $(BASE_DIR)/$(BIN_NAME)

Можемо використовувати його як:

# cd /opt/exporters/zfs_exporter
# make build

// або
# make -C /opt/exporters/zfs_exporter build

// або
# make -f /opt/exporters/zfs_exporter/Makefile build

Тепер у нас така структура експортеру:

# tree /opt/exporters/zfs_exporter
/opt/exporters/zfs_exporter
├── Makefile
├── build.sh
├── rc.d
├── src
│   └── zfs_exporter
│       ├── CHANGELOG.md
│       ├── LICENSE
...
│       └── zfs_exporter.go
└── zfs_exporter

Запускаємо для перевірки make build:

# make build 
/opt/exporters/zfs_exporter/build.sh
Already up to date.

І make install:

# make install 
install -m 0755 /opt/exporters/zfs_exporter/zfs_exporter /usr/local/bin/zfs_exporter

Перевіряємо ще раз, вже з /usr/local/bin:

# /usr/local/bin/zfs_exporter
...
time=2026-02-09T13:44:59.651+02:00 level=INFO source=tls_config.go:354 msg="Listening on" address=[::]:9134
...

Створення rc.d скрипта

Пишемо файл /opt/exporters/zfs_exporter/rc.d/zfs_exporter:

#!/bin/sh

# PROVIDE: zfs_exporter
# REQUIRE: DAEMON
# KEYWORD: shutdown

. /etc/rc.subr

name="zfs_exporter"
rcvar="zfs_exporter_enable"

command="/usr/local/bin/zfs_exporter"
pidfile="/var/run/${name}.pid"

# defaults (override in rc.conf)
: ${zfs_exporter_enable:=no}
: ${zfs_exporter_listen_address:=":9134"}
: ${zfs_exporter_extra_flags:=""}
: ${zfs_exporter_log_file:="/var/log/zfs_exporter.log"}

start_cmd="${name}_start"
stop_cmd="${name}_stop"
status_cmd="${name}_status"

zfs_exporter_start()
{
    echo "Starting ${name}"
    /bin/sh -c "${command} --web.listen-address=${zfs_exporter_listen_address} ${zfs_exporter_extra_flags} > ${zfs_exporter_log_file} 2>&1 & echo \$! > ${pidfile}"
}

zfs_exporter_stop()
{
    if [ -f "${pidfile}" ]; then
        kill "$(cat ${pidfile})"
        rm -f "${pidfile}"
        echo "Stopped ${name}"
    else
        echo "${name} is not running"
    fi
}

zfs_exporter_status()
{
    if [ -f "${pidfile}" ] && kill -0 "$(cat ${pidfile})" 2>/dev/null; then
        echo "${name} is running (pid $(cat ${pidfile}))"
    else
        echo "${name} is not running"
        return 1
    fi
}

load_rc_config $name
run_rc_command "$1"

Задаємо права на запуск:

# chmod +x /opt/exporters/zfs_exporter/rc.d/zfs_exporter

Додаємо його копіювання до /usr/local/etc/rc.d в таргет install нашого Makefile:

# simple makefile for zfs_exporter

PREFIX=/usr/local
BIN_NAME=zfs_exporter
BASE_DIR=/opt/exporters/zfs_exporter
RC_DIR=$(PREFIX)/etc/rc.d

.PHONY: build install clean

build:
  $(BASE_DIR)/build.sh

install:
  install -m 0755 $(BASE_DIR)/$(BIN_NAME) $(PREFIX)/bin/$(BIN_NAME)
  install -m 0755 $(BASE_DIR)/rc.d/$(BIN_NAME) $(RC_DIR)/$(BIN_NAME)

clean:
  rm -f $(BASE_DIR)/$(BIN_NAME)

Запускаємо:

# make install 
install -m 0755 /opt/exporters/zfs_exporter/zfs_exporter /usr/local/bin/zfs_exporter
install -m 0755 /opt/exporters/zfs_exporter/rc.d/zfs_exporter /usr/local/etc/rc.d/zfs_exporter

Додаємо до /etc/rc.conf:

# sysrc zfs_exporter_enable="YES"
zfs_exporter_enable:  -> YES

Запускаємо сервіс:

# service zfs_exporter start
Starting zfs_exporter

Перевіряємо статус:

# service zfs_exporter status
zfs_exporter is running (pid 91712)

Перевіряємо лог:

# tail -f /var/log/zfs_exporter.log 
time=2026-02-09T13:52:07.033+02:00 level=INFO source=zfs_exporter.go:40 msg="Starting zfs_exporter" version="(version=, branch=, revision=7af698c8844864eb1e724ed08c47e5a7b4bbcc53)"
...
time=2026-02-09T13:52:07.033+02:00 level=INFO source=tls_config.go:354 msg="Listening on" address=[::]:9134
...

І метрики:

# curl -s localhost:9134/metrics | grep zfs_ | head -5
# HELP zfs_dataset_available_bytes The amount of space in bytes available to the dataset and all its children.
# TYPE zfs_dataset_available_bytes gauge
zfs_dataset_available_bytes{name="nas",pool="nas",type="filesystem"} 2.723599372288e+12
zfs_dataset_available_bytes{name="nas/backups",pool="nas",type="filesystem"} 2.723599372288e+12
zfs_dataset_available_bytes{name="nas/media",pool="nas",type="filesystem"} 2.723599372288e+12

VMAgent описував в попередньому пості в частині Установка VMAgent – додаємо до нього збір метрик з нового експортеру.

Редагуємо /usr/local/etc/prometheus/prometheus.yml, додаємо новий таргет:

...
  - job_name: "zfs_exporter"
    static_configs:
      - targets:
          - "127.0.0.1:9134"
...

Рестартимо vmagent і перевіряємо метрики в VictoriaMetrics:

Exporter upgrade з make

Тут у нас просто кілька кроків:

# service zfs_exporter stop

# make -C /opt/exporters/zfs_exporter build

# make -C /opt/exporters/zfs_exporter install

# service zfs_exporter start

Без VERSION чи тегів або релізів – все максимально просто.

Можна додати до Makefile:

...

upgrade:
        service zfs_exporter stop || true
        $(MAKE) build
        $(MAKE) install
        service zfs_exporter start

Установка go-ecoflow-exporter

Аналогічно зробив для go-ecoflow-exporter, але тут є пару відмінностей, бо треба передати пачку змінних оточення.

Для цього в скрипті /opt/exporters/ecoflow_exporter/rc.d/ecoflow_exporter додаємо export:

...
ecoflow_exporter_start()
{
    echo "Starting ${name}"

    export \
        EXPORTER_TYPE \
        ECOFLOW_EMAIL \
        ECOFLOW_PASSWORD \
        ECOFLOW_DEVICES \
        MQTT_DEVICE_OFFLINE_THRESHOLD_SECONDS \
        DEBUG_ENABLED \
        METRIC_PREFIX \
        SCRAPING_INTERVAL \
        PROMETHEUS_ENABLED \
        PROMETHEUS_PORT

    /bin/sh -c "${command} --listen ${ecoflow_exporter_listen_address} ${ecoflow_exporter_extra_flags} > ${ecoflow_exporter_log_file} 2>&1 & echo \$! > ${pidfile}"
}
...

А значення – через /etc/rc.conf.d і файл /etc/rc.conf.d/ecoflow_exporter:

# cat /etc/rc.conf.d/ecoflow_exporter
ecoflow_exporter_enable="YES"

EXPORTER_TYPE="mqtt"
...
PROMETHEUS_PORT="2112"

Ім’я файлу в rc.conf.d має збігатись з іменем в rc.d, тобто:

# cat /usr/local/etc/rc.d/ecoflow_exporter | grep name=
name="ecoflow_exporter"

Додаємо новий таргет до VMAgent:

...
  - job_name: "ecoflow_exporter"
    static_configs:
      - targets:
          - "127.0.0.1:2112"
...

Установка smartctl_exporter

Він є в портах – smartctl_exporter і в репозиторії FreeBSD, тому просто встановлюємо з pkg:

# pkg install -y smartctl_exporter

Додаємо запуск:

# sysrc smartctl_exporter_enable="YES"

Запускаємо сервіс:

# service smartctl_exporter start

Перевіряємо метрики:

# curl -s 127.0.0.1:9633/metrics | grep smart | grep -v \# | head -5
smartctl_device{ata_additional_product_id="unknown",ata_version="ACS-4 T13/BSR INCITS 529 revision 5",device="ada0" ...

Додаємо VMAgent таргет:

...
  - job_name: smartctl_exporter
    static_configs:
      - targets:
          - 127.0.0.1:9633
...

Але smartctl_exporter по дефолту збирає інформацію тільки для /dev/ada* – а в мене ще є NVMe.

В rc.d скрипті експортеру є glob:

...
smartctl_exporter_devices (string):           Shell glob (like /dev/ada[0-9]) for all devices
...

Задаємо диски в /etc/rc..conf:

...
smartctl_exporter_devices="/dev/ada* /dev/nvme0"
...

NVMe просто ім’я явно, без glob, бо буде тягнути розділи типу /dev/nvme0ns1.

Node Exporter та Textfile

Скільки років користуюсь node_exporter в Kubernetes – не знав, що у нього є така цікава можливість як Textfile, є навіть цілі колекції, див. node-exporter-textfile-collector-scripts.

Що хотілось бачити у себе – це інформацію по CPU/RAM процесів, і спочатку думав просто взяти process_exporter, як це робив в Kubernetes (див. Kubernetes: моніторинг процесів з process-exporter).

Але process_exporter не працює з FreeBSD, бо він всю інформацію збирає з каталогу /proc, який включити можна – але все одно буде не Linux-proc.

Тому зробив інакше – через node_exporter та textfiles, через які збираю температуру CPU та інформацію по процесам.

Перевіряємо, що директорію, з якої директорі node_exporter читає файли з метриками:

root@setevoy-nas:~ # ps aux | grep node_exporter
nobody            2511   0.0  0.2   1264028  13012  -  S    Fri17       1:17.17 /usr/local/bin/node_exporter --web.listen-address=:9100 --collector.textfile.directory=/var/tmp/node_exporter

--collector.textfile.directory=/var/tmp/node_exporter – Окей, включено, можна додавати дані.

Скрипт для CPU temperature, CPU та RAM процесами

Пишемо скрипт /usr/local/bin/process_resources_exporter.sh:

#!/bin/sh

OUT="/var/tmp/node_exporter/process_resources.prom"

# reset file once
{
  echo "# HELP local_process_memory_bytes resident memory size per process"
  echo "# TYPE local_process_memory_bytes gauge"

  echo "# HELP local_process_cpu_percent cpu usage percent per process"
  echo "# TYPE local_process_cpu_percent gauge"

  echo "# HELP node_cpu_temperature_celsius CPU/system temperature via ACPI"
  echo "# TYPE node_cpu_temperature_celsius gauge"
} > "$OUT"

# ----
# top processes by memory (aggregate by process name)
# ----
ps -axo comm,rss | grep -vE '^(idle|pagezero|kernel)' | awk '
{
  gsub(/ /,"_",$1)
  mem[$1] += $2
}
END {
  for (p in mem)
    printf "local_process_memory_bytes{process=\"%s\"} %d\n", p, mem[p] * 1024
}
' | sort -k2 -nr | head -10 >> "$OUT"

# ----
# top processes by cpu (aggregate by process name)
# ----
ps -axo comm,%cpu | grep -vE '^(idle|pagezero|kernel)' | awk '
{
  gsub(/ /,"_",$1)
  cpu[$1] += $2
}
END {
  for (p in cpu)
    printf "local_process_cpu_percent{process=\"%s\"} %.2f\n", p, cpu[p]
}
' | sort -k2 -nr | head -10 >> "$OUT"

# ----
# cpu temperature via ACPI
# ----
TEMP=$(sysctl -n hw.acpi.thermal.tz0.temperature 2>/dev/null | tr -d 'C')

if [ -n "$TEMP" ]; then
  echo "node_cpu_temperature_celsius $TEMP" >> "$OUT"
fi

Запускаємо для тесту:

# chmod +x /usr/local/bin/process_resources_exporter.sh

# /usr/local/bin/process_resources_exporter.sh

Перевіряємо файл /var/tmp/node_exporter/process_resources.prom  з метриками:

# cat /var/tmp/node_exporter/process_resources.prom 
# HELP local_process_memory_bytes resident memory size per process
# TYPE local_process_memory_bytes gauge
# HELP local_process_cpu_percent cpu usage percent per process
# TYPE local_process_cpu_percent gauge
# HELP node_cpu_temperature_celsius CPU/system temperature via ACPI
# TYPE node_cpu_temperature_celsius gauge
local_process_memory_bytes{process="jellyfin"} 456441856
...
local_process_cpu_percent{process="syslogd"} 0.00
node_cpu_temperature_celsius 27.9

І експортер з нього імпортує метрики:

Додаємо скрипт в cron, раз на хвилину буде достатньо:

* * * * * /usr/local/bin/process_resources_exporter.sh

Скрипт для freebsd_update та pkg updates

Аналогічно можна додати інформацію по доступним апдейтам.

Скрипт /usr/local/bin/updates_exporter.sh:

#!/bin/sh

OUT="/var/tmp/node_exporter/updates.prom"

# header
{
  echo "# HELP node_freebsd_update_available FreeBSD base system updates available (1=yes, 0=no)"
  echo "# TYPE node_freebsd_update_available gauge"

  echo "# HELP node_pkg_updates_available Number of pkg updates available"
  echo "# TYPE node_pkg_updates_available gauge"
} > "$OUT"

# --------
# freebsd-update
# --------
FREEBSD_UPDATES=0

# freebsd-update fetch returns:
# - exit 0 even if no updates
# - but prints "No updates needed to update system"
if freebsd-update fetch | grep -q "No updates needed"; then
  FREEBSD_UPDATES=0
else
  FREEBSD_UPDATES=1
fi

echo "node_freebsd_update_available $FREEBSD_UPDATES" >> "$OUT"

# --------
# pkg updates
# --------
# pkg version -l "<" lists outdated packages
PKG_UPDATES=$(pkg version -l "<" 2>/dev/null | wc -l | tr -d ' ')

# fallback safety
PKG_UPDATES=${PKG_UPDATES:-0}

echo "node_pkg_updates_available $PKG_UPDATES" >> "$OUT"

Робимо chmod, додаємо в cron – але тут раз на годину, і перевіряємо метрики в VictoriaMetrics:

Скрипт для Services health

Тут перевіряємо, що сервіси запущені, якщо все ОК – пишемо в метрику service_up значення 1, якщо проблеми – то 0.

Скрипт /usr/local/bin/service_status_exporter.sh:

#!/bin/sh

DIR="/var/tmp/node_exporter"
OUT="$DIR/service_status.prom"
TMP="$DIR/service_status.prom.tmp"

# ------------
# helpers
# ------------

check_proc() {
  pgrep -f "$1" >/dev/null 2>&1
}

check_port() {
  host="$1"
  port="$2"
  nc -z "$host" "$port" >/dev/null 2>&1
}

# ----------------
# write metrics (atomic)
# ----------------

cat <<EOF > "$TMP"
# HELP service_up Service availability status (1 = up, 0 = down)
# TYPE service_up gauge
EOF

# jellyfin
if check_port 127.0.0.1 8096; then
  echo 'service_up{name="jellyfin"} 1' >> "$TMP"
else
  echo 'service_up{name="jellyfin"} 0' >> "$TMP"
fi

# filebrowser
if check_port 127.0.0.1 8080; then
  echo 'service_up{name="filebrowser"} 1' >> "$TMP"
else
  echo 'service_up{name="filebrowser"} 0' >> "$TMP"
fi

# grafana
if check_port 127.0.0.1 3000; then
  echo 'service_up{name="grafana"} 1' >> "$TMP"
else
  echo 'service_up{name="grafana"} 0' >> "$TMP"
fi

# victoria-metrics
if check_port 127.0.0.1 8428; then
  echo 'service_up{name="victoria-metrics"} 1' >> "$TMP"
else
  echo 'service_up{name="victoria-metrics"} 0' >> "$TMP"
fi

# sshd (port only)
if check_port 127.0.0.1 22; then
  echo 'service_up{name="sshd"} 1' >> "$TMP"
else
  echo 'service_up{name="sshd"} 0' >> "$TMP"
fi

# nfsd (process only)
if check_proc nfsd; then
  echo 'service_up{name="nfsd"} 1' >> "$TMP"
else
  echo 'service_up{name="nfsd"} 0' >> "$TMP"
fi

# atomic replace
mv "$TMP" "$OUT"

І метрики:

Grafana дашборд – нові графіки

А тепер це все додаємо в Grafana.

Графік CPU процесами:

topk(5, local_process_cpu_percent)

Аналогічно для пам’яті:

Статус дисків зі SMART:

smartctl_device_smart_status

Стан сервісів:

І все разом тепер:

Залишилось додати алерти – і, в принципі, моніторинг готовий.

Loading

FreeBSD: налаштування DragonFly Mail Agent для пошти root
0 (0)

8 Лютого 2026

В локальну пошту юзера root приходить багато листів від різних periodic задач.

Про perdiodic scripts трохи писав у FreeBSD: Home NAS, part 5 – ZFS pool, datasets, snapshots та моніторинг, тепер налаштувати пересилку пошти на зовнішню адресу.

Бо листи приходять кожного дня, а читати пошту локально незручно:

# mail -u root -H
Mail version 8.1 6/6/93.  Type ? for help.
"/var/mail/root": 58 messages 58 unread
...
 U 57 root@setevoy-nas      Sun Feb  8 03:19  46/1300  "setevoy-nas daily security run output"
 U 58 root@setevoy-nas      Sun Feb  8 03:19  99/3444  "setevoy-nas daily run output"

Аби їх отримувати на зовнішній ящик – додаємо Mail Transport Agent (MTA), який буде робити відправку на задану адресу.

Створення Google Mail App Passwords

Якщо включений 2FA – то використовуємо Google Mail App Passwords.

Пароль відобразить з пробілами – в конфігу DMA їх прибираємо:

Налаштування DragonFly Mail Agent

Документація – DragonFly Mail Agent (DMA) (FreeBSD Handbook) та DMA (Arch Linux Wiki).

Встановлюємо сам DMA:

# pkg install dma

Редагуємо файл /etc/dma/dma.conf:

SMARTHOST smtp.gmail.com
PORT 587

# SMTP authentication
AUTHPATH /etc/dma/auth.conf

SECURETRANSFER 
STARTTLS 

MASQUERADE [email protected]

Редагуємо /etc/dma/auth.conf – задаємо логін, хост і App password, який створили на початку (без пробілів):

[email protected]|smtp.gmail.com:mpd***sra

Задаємо права доступу – з групою mail:

# chown root:mail /etc/dma/auth.conf
chmod 640 /etc/dma/auth.conf

Перевіряємо відправку на звичайну адресу:

# echo "dma alias test" | mail [email protected]

І отримуємо листа:

Але якщо зараз відправити до юзера root, то лист не дійде з помилкою “The recipient address <root> is not a valid RFC 5321 address“.

Додаємо пошту рута до /etc/aliases:

...
root: [email protected]
...

Перевіряємо:

# echo "dma alias test" | mail root

І отримуємо листа “To: root“:

І для перевірки можна запустити виконання всіх periodic:

# periodic daily

І отримуємо листи:

Готово.

Loading

FreeBSD: Home NAS, part 10 – моніторинг з VictoriaMetrics та Grafana
0 (0)

7 Лютого 2026

Нарешті добрався до моніторингу.

Цікаво запустити стандартний стек з VictoriaMetrics + Grafana + Alertmanager не у звичному Kubernetes з Helm-чарту, а просто на хості.

Але підхід той самий, з яким моніторяться сервіси в AWS/Kubernetes – на FreeBSD буде VictoriaMetrics для метрик, Grafana для візуалізації, VMAlert та Alertmanager для алертів.

Хотя в моніторингу моїх EcoFlow зробив алерти через Grafana Alerts, перший раз їх пробував – непогано. Хоча все ж стандартний підхід, коли всі Alert Rules описані в файлах, мені заходить більше.

Всі частини серії по налаштуванню домашнього NAS на FreeBSD:

Оскільки це маленький домашній NAS, до якого доступ тільки в локальній мережі з через VPN – то буду робити без FreeBSD Jails. З ними, може, буду знайомитись ближче іншим разом, бо за всі роки користування FreeBSD (з… 2007 року? десь так) – жодного разу в jails нічого не крутив.

Поїхали.

Установка VictoriaMetrics

VictoriaMetrics є в портах FreeBSD і в репозиторії, хоча порти дещо відрізняються від звичної схеми – далі подивимось ці нюанси.

З репозиторію FreeBSD встановлюємо саму VictoriaMetrics:

root@setevoy-nas:~ # pkg install -y victoria-metrics

Перевіряємо що і куди встановило:

root@setevoy-nas:~ # pkg info -l victoria-metrics | grep -E 'bin|rc.d'
        /usr/local/bin/victoria-metrics
        /usr/local/etc/rc.d/victoria-metrics

У файлі /usr/local/etc/rc.d/victoria-metrics маємо список флагів, які можемо передати через /etc/rc.conf:

root@setevoy-nas:~ # cat /usr/local/etc/rc.d/victoria-metrics | grep victoria_metrics
# PROVIDE: victoria_metrics
name="victoria_metrics"
rcvar="victoria_metrics_enable"
logfile="${logdir}/victoria_metrics.log"
victoria_metrics_args=${victoria_metrics_args-"--storageDataPath=/var/db/victoria-metrics --retentionPeriod=1 --httpListenAddr=:8428"}
victoria_metrics_user="victoria-metrics"

Дані будуть зберігатись в /var/db/victoria-metrics, потім треба буде додати його в бекапи.

Додаємо запуск сервісу до /etc/rc.conf:

root@setevoy-nas:~ # sysrc victoria_metrics_enable="yes"
victoria_metrics_enable:  -> yes

Запускаємо:

root@setevoy-nas:~ # service victoria-metrics start

Перевіряємо порти:

root@setevoy-nas:~ # netstat -an | grep 8428
tcp4       0      0 *.8428                 *.*                    LISTEN

Відкриваємо в браузері – все працює:

Установка node_exporter

Аби побачити в VictoriaMetrics якісь метрики – встановлюємо теж звичний багатьом node_exporter:

root@setevoy-nas:~ # pkg install -y node_exporter

Додаємо його запуск:

root@setevoy-nas:~ # sysrc node_exporter_enable="yes"
node_exporter_enable:  -> yes

Запускаємо:

root@setevoy-nas:~ # service node_exporter start
Starting node_exporter.

Перевіряємо порт:

root@setevoy-nas:~ # netstat -an | grep 9100
tcp46      0      0 *.9100                 *.*                    LISTEN

Установка VMAgent

Отут вже трохи відмінностей, бо окремого FreeBSD port під VMAgent нема – але є загальний пакет vmutils, який встановлює відразу кілька компонентів:

root@setevoy-nas:~ # pkg install -y vmutils

Перевіряємо, що він нам додав:

root@setevoy-nas:~ # pkg info -l vmutils | grep bin
        /usr/local/bin/vmagent
        /usr/local/bin/vmalert
        /usr/local/bin/vmauth
        /usr/local/bin/vmbackup
        /usr/local/bin/vmctl
        /usr/local/bin/vmrestore

Але з vmutils встановлюється тільки один rc.d скрипт, для самого vmagent:

root@setevoy-nas:~ # pkg info -l vmutils | grep rc.d
        /usr/local/etc/rc.d/vmagent

Тому далі для VMAlert будемо писати власний.

Додаємо vmagent в rc.cconf:

root@setevoy-nas:~ # sysrc vmagent_enable="yes"
vmagent_enable:  -> yes

Поки не запускаємо – налаштуємо збір метрик з node_exporter.

Перевіряємо опції, з якими vmagent запускається – шукаємо дефолтний файл конфігу:

root@setevoy-nas:~ # cat /usr/local/etc/rc.d/vmagent
#!/bin/sh
...
vmagent_args=${vmagent_args-"--remoteWrite.tmpDataPath=/var/db/vmagent --promscrape.config=/usr/local/etc/prometheus/prometheus.yml --remoteWrite.url=http://127.0.0.1:8428/api/v1/write --memory.allowedPercent=80"}
...

Додаємо job="node_exporter" до /usr/local/etc/prometheus/prometheus.yml:

global:
  scrape_interval: 15s

scrape_configs:

  - job_name: vmagent
    scrape_interval: 60s
    scrape_timeout: 30s
    metrics_path: "/metrics"
    static_configs:
    - targets:
      - 127.0.0.1:8429
      labels:
        project: vmagent

  - job_name: "node_exporter"
    static_configs:
      - targets:
          - "127.0.0.1:9100"

Перевіряємо синтаксис:

root@setevoy-nas:~ # service vmagent checkconfig; echo $?
0

Запускаємо сервіс:

root@setevoy-nas:~ # service vmagent start

Перевіряємо в VMAgent /targets, http://nas.setevoy:8429/targets:

І метрики в VictoriaMetrics:

Установка Grafana

Теж встановлюємо із репозиторію:

root@setevoy-nas:~ # pkg install -y grafana

Конфіг-файл – /usr/local/etc/grafana/grafana.ini.

Додаємо запуск:

root@setevoy-nas:~ # sysrc grafana_enable="yes"
grafana_enable:  -> yes

Запускаємо:

root@setevoy-nas:~ # service grafana start
Starting grafana.

Для тестів можна взяти готовий дашборд – Node Exporter Full.

VictoriaMetrics Grafana data source на FreeBSD

Додаємо datasource:

Але відразу зловив помилку:

Хоча 100% плагін підписаний, бо не вперше ж його використовую в робочих проектах:

Пробував додати allow_loading_unsigned_plugins до /usr/local/etc/grafana/grafana.ini:

...
allow_loading_unsigned_plugins = victoriametrics-metrics-datasource
...

Або встановити з Grafaca CLI (перший раз не користувався):

root@setevoy-nas:~ # grafana cli plugins install victoriametrics-metrics-datasource
Grafana-server Init Failed: Could not find config defaults, make sure homepath command line parameter is set or working directory is homepat

Не допомогло.

Ну і потім вже глянув логи Grafana:

...
logger=installer.fs t=2026-02-06T17:09:29.946038823+02:00 level=info msg="Downloaded and extracted victoriametrics-metrics-datasource v0.21.0 zip successfully to /var/db/grafana/plugins/victoriametrics-metrics-datasource"
logger=plugins.backend.start t=2026-02-06T17:09:30.466419686+02:00 level=error msg="Could not start plugin backend" pluginId=victoriametrics-metrics-datasource error="fork/exec /var/db/grafana/plugins/victoriametrics-metrics-datasource/victoriametrics_metrics_backend_plugin_freebsd_amd64: no such file or directory"
...

“victoriametrics_metrics_backend_plugin_freebsd_amd64: no such file or directory”

Oh, c’mon…

Не став розбиратись далі – просто можемо використати стандартний плагін Prometheus (але пізніше розробників VictoriaMetrics потім за цю проблему спитаю).

Власне, якщо не зраджує пам’ять – то раніше, коли власного плагіну для Grafana у VictoriaMetrics не було, то ми і використовували дефолтний Prometheus, який йде в комплекті до Grafana:

Додаємо новий data source:

Називаємо його victoria-metrics, задаємо URL:

І тепер все працює:

Налаштування Alerting

I use Arch ntfy.sh BTW.

Дуже прикольний і простий сервіс. Має web, має мобільну апку.  Може напишу якось про нього окремо, бо просто в захваті.

Можна зробити алерти через Telegram – можливо, потім додам і його, але зараз ntfy.sh вистачить з головою.

Отже, VMlert у нас вже є – він рахує правила, які ми йому задаємо, і шле в Alertmanager.

Установка Alertmanager

Теж з репозиторію FreeBSD:

root@setevoy-nas:~ # pkg install -y alertmanager

Додаємо запуск:

root@setevoy-nas:~ # sysrc alertmanager_enable="yes"
alertmanager_enable:  -> yes

Видаляємо дефолтний файл, бо там багато зайвого:

root@setevoy-nas:~ # mv /usr/local/etc/alertmanager/alertmanager.yml /usr/local/etc/alertmanager/alertmanager.yml-default

Пишемо свій конфіг /usr/local/etc/alertmanager/alertmanager.yml:

global:
  resolve_timeout: 5m

route:
  receiver: "ntfy"
  group_by: ["alertname"]
  group_wait: 10s
  group_interval: 5m
  repeat_interval: 4h

receivers:
  - name: "ntfy"
    webhook_configs:
      - url: "https://ntfy.sh/setevoy-alertmanager-alerts"
        http_config:
          authorization:
            type: Bearer
            credentials: "tk_v9c***f2p"
        send_resolved: true

Запускаємо Alertmanager:

root@setevoy-nas:~ # service alertmanager restart

Перевіряємо його дашборд – http://nas.setevoy:9093/#/alerts:

Установка VMAlert

Бінарнік vmalert вже встановлено з пакету vmutils, але для нього нема скриптів для rc.d:

root@setevoy-nas:~ # pkg info -l vmutils | grep rc.d
        /usr/local/etc/rc.d/vmagent
root@setevoy-nas:~ # pkg info -l vmutils | grep bin
        /usr/local/bin/vmagent
        /usr/local/bin/vmalert
        /usr/local/bin/vmauth
        /usr/local/bin/vmbackup
        /usr/local/bin/vmctl
        /usr/local/bin/vmrestore

Тому пишемо власний /usr/local/etc/rc.d/vmalert – він простий, вайбокодиться без проблем:

#!/bin/sh

# PROVIDE: vmalert
# REQUIRE: LOGIN
# KEYWORD: shutdown

. /etc/rc.subr

name="vmalert"
rcvar="vmalert_enable"

load_rc_config $name

: ${vmalert_enable:="NO"}
: ${vmalert_user:="victoria-metrics"}
: ${vmalert_args:="--datasource.url=http://127.0.0.1:8428 --notifier.url=http://127.0.0.1:9093 --rule=/usr/local/etc/vmalert/*.yml"}

pidfile="/var/run/${name}.pid"
command="/usr/sbin/daemon"
procname="/usr/local/bin/vmalert"

command_args="-f -p ${pidfile} ${procname} ${vmalert_args}"

start_cmd="vmalert_start"
stop_cmd="vmalert_stop"

vmalert_start()
{
  echo "Starting vmalert"
  ${command} ${command_args}
}

vmalert_stop()
{
  echo "Stopping vmalert"
  kill `cat ${pidfile}`
}

run_rc_command "$1"

Задаємо права на запуск:

# chmod +x /usr/local/etc/rc.d/vmalert

Додаємо в запуск із системою:

root@setevoy-nas:~ # sysrc vmalert_enable="yes"
vmalert_enable:  -> yes

І запускаємо:

root@setevoy-nas:~ # service vmalert start

Перевіряємо на http://nas.setevoy:8880:

Додавання алертів

Власне, тепер можемо накидати алерти.

Створюємо файл /usr/local/etc/vmalert/node-alerts.yml:

root@setevoy-nas:~ # mkdir -p /usr/local/etc/vmalert/

Описуємо алерт:

groups:
  - name: node-exporter-alerts
    rules:
      - alert: NodeExporterDown
        expr: up{job="node_exporter"} == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "node_exporter down on {{ $labels.instance }}"
          description: "node_exporter is not reachable for more than 1 minute"

Перезапускаємо vmalert:

root@setevoy-nas:~ # service vmalert restart

Для тесту зупиняємо node_exporter:

root@setevoy-nas:~ # service node_exporter stop
Stopping node_exporter.
Waiting for PIDS: 1965.

І отримуємо алерт в VMAlert:

І повідомлення від ntfy.sh.

На телефоні:

Або в web:

Тюнинг Grafana dashboard та node_exporter Memory graphs

Дефолтна дашборда заточена під Linux, і на FreeBSD для правильного відображення графіків у Memory Basic треба трохи затюнити запити і метрики.

Перевіряємо наявні метрики по memory від node_exporter:

{__name__=~"node_memory_.*_bytes"}

Тут з основного маємо:

  • node_memory_size_bytes: total RAM
  • node_memory_free_bytes: реально вільна
  • node_memory_cache_bytes: filesystem cache (reclaimable)
  • node_memory_buffer_bytes: buffers (reclaimable)
  • node_memory_inactive_bytes: inactive pages (reclaimable)
  • node_memory_active_bytes: активно використовується
  • node_memory_wired_bytes: non-reclaimed памʼять (kernel, drivers)

Що нам тут цікаво – це загальна пам’ять, node_memory_free_bytes та node_memory_active_bytes.

Free RAM у FreeBSD – це дійсно free memory, тобто все поза кешами, inactive, wired, buffers і т.д.

Тому панельку з візуалізацією можемо побудувати з такими запитами:

  • загальна пам’ять
node_memory_size_bytes
  • used memory – те, що дійсно зайнято  і не може бути звільнено:
sum
(
    node_memory_active_bytes
    +
    node_memory_wired_bytes
)
  • вільна пам’ять – все поза всякими кешами-буферами і рештою зайнятої пам’яті:
node_memory_free_bytes{instance="$node",job="$job"}
  • swap used, хоча в мене його нема:
node_memory_swap_used_bytes{instance="$node",job="$job"}
  • відобразити % зайнятої памяті від загальної:
(
  node_memory_active_bytes
  + node_memory_wired_bytes
)
/
node_memory_size_bytes
* 100

В результаті маємо такі графіки для пам’яті:

А вся дашборда в мене поки виглядає так (це вже з доданим ZFS Exporter):

Окремо є Small варіант – для виводу на 7-ми дюймовий монітор, який буде стояти в серверній шафі:

Пізніше до дашборду вже пододаю ще корисних графіків і статусів.

Ну і для моніторингу системи і NAS буде корисно додати ще експортерів – smartctl_exporter, zfs_exporter тощо.

Loading

MikroTik: перше знайомство та Getting Started
0 (0)

22 Січня 2026

Давно подумував спробувати MikroTik, але все якось було ліньки розбиратись з RouterOS.

Нарешті, на хвилі зборки Home NAS проекту (див. початок в FreeBSD: Home NAS, part 1 – налаштування ZFS mirror) таки вирішив, що пора оновити і мережевий стек і замінити простенький TP-Link Archer на щось більш серйозне.

Так в мене з’явились два роутери MikroTik: RB4011iGS+RM – основний роутер, і MikroTik hAP ax3 – для WiFi.

До цього в мене були Linksys E4200 (2012-2020), потім з’явився Linksys EA6350 (2020-2024), і останнім був TP-Link Archer AX12 (2024-2025), і коли я перший раз відкрив MikroTik Web UI і подивився можливості… Це як пересісти з Запорожця на Мерседес.

Ну і нарешті – повноцінна консоль і SSH з коробки, а не через кастомні прошивки.

Можливостей в RouterOS дуже багато, тому одним постом по цій темі не обійтись, і в чернетках вже є кілька матеріалів, а почнемо з першого знайомства і початку роботи.

Архітектура моєї мережі

Перш ніж говорити про сам роутер – трохи про мій networking і ролі роутерів MikroTik.

Є дві мережі – “офіс” та дом, в обох “на вході” були TP-Link Archer AX12.

В “офісі” (в лапках, бо це просто сусідня квартира) стоїть ThnikCenter з FreeBSD/NAS, плюс робочий ноутбук і ігровий ПК. В основному всі девайси підключені до роутера кабелями, WiFi тільки для телефону і всяких EcoFlow, робота-пилососа тощо.

Вдома – пара ноутбуків, там вся мережа виключно WiFi.

Обидві мережі об’єднані з VPN, і по старій схемі TP-Link Archer в офісі мав port forwading до WireGuard на FreeBSD, а на FreeBSD були власне WireGuard як хаб та Unbound для локального DNS, плюс всякі Samba/NFS/etc.

Тепер жеж в офісі схема буде іншою:

  • MikroTik RB4011iGS:
    • на нього заходить кабель провайдера (оптика через ONU і далі по квартирі з Ethernet на роутер RB4011)
    • на ньому пізніше буде друге підключення ще одного LTE-роутера з SIM та мобільним інтернетом і автоматичним failover (див. старий пост Networking: коли немає світла – модем 4G ZTE + зовнішня антена: антена та сама, тільки роутер буде Teltonika RUT241)
    • WireGuard тепер буде тут
    • локальний DNS теж тепер буде тут
    • до RB4011iGS кабелями підключені ThinkCentre/NAS, робочий ноутбук та ігровий ПК
  • MikroTik hAP ax3: підключений кабелем до RB4011, пізніше переключу його в режим Access Point, поки стандартний WiFi роутер з власним NAT
  • TP-Link Archer AX12: підключений кабелем до RB4011, на ньому нічого не міняю, бо ліньки перепідключати різні домашні девайси типу дверного дзвоника, пожежної сигналізації, EcoFlow, etc

В домашній мережі не міняється нічого окрім налаштувань WireGuard на домашньому ноуті: раніше він через port-forwarding на офісному роутері підключався до FreeBSD, тепер буде ходити до RB4011.

І окремо – сервер самого rtfm.co.ua в DigitalOcean, який (буде) теж підключений з WireGuard до цієї мережі.

Загальна схема буде виглядати приблизно так:

Перше підключення до MikroTik

Боже, який це кайф – мати повноцінний SSH! Але про SSH трохи далі тут і потім ще окремим постом.

Взагалі MikroTik предоставляє кілька варіантів підключення:

Дефолтний юзер всюди admin. Паролі для MikroTik hAP ax3 зробили на висувному вкладиші (дуже прикольно):

А для RB4011 на паперовому Quick Guide.

Дефолтна мережа 192.168.88.0/24, адреса роутера, відповідно – 192.168.88.1.

WAN-порт на обох роутерах – перший, в нього втикаємо кабель провайдера, а ноутбук/ПК – в будь-який інший.

Для підключення до MikroTik hAP ax3 замість кабеля можна використати його дефолтну WiFi-мережу – теж дасть доступ до управління.

Web UI overview

Інтерфейс стандартний на обох роутерах – на RB4011 навіть є розділ “WiFi”, хоча в нього тільки Ethernet-порти.

Тут і далі в основному буду писати про RB4011, тому скріншоти будуть з нього.

Web має три “режими” – простий для Quick Setup:

Advanced – тут вже доступ до всіх можливостей:

І можна запустити Terminal прямо з Web:

В Design Skin можна вибрати які саме пункти меню будуть відображатись:

WinBox

При запуску утиліта сама сканує мережу і знаходить доступні для підключення девайси MikroTik:

При цьому підключитись можна як по IP, так і по MAC-адресі – на випадок, якщо зламали мережу.

А сам інтерфейс в принципі такий самий, як і в Web – стандартний для RouterOS:

І навіть є темна тема:

SSH

Тут все стандартно – просто з ноутбука/ПК виконуємо “ssh 192.168.88.1” (в мене вже переналаштований DHCP, тому адреса 192.168.0.1):

Можна використовувати ключі замість паролів, потім подивимось як.

Mobile client

І мобільна апка – підключаємось по IP:

Робота в консолі RouterOS

Вебом користуюсь мало, і тут і далі всі налаштування будуть з SSH.

Документація – Command Line Interface та Console.

Дуже цікава можливість Safe Mode: відкотить зміни, якщо зламали доступ і підключення розірвалось без коректного збереження налаштувань.

В RouteOS є повноцінна консоль, яка складається з ієрархічного дерева команд.

Наприклад, якщо в Web пункт меню IP => Firewall:

То в консолі це буде /ip firewall.

Є повноцінне автодоповнення по Tab:

Після переходу в меню можна з F1 подивитись доступні команди:

В документації говориться, що “?” має виводити підказку теж – але на 7+ версії це вже не працює (Reddit).

Замість “?” просто вибираємо команду, а потім F1 або Tab:

Getting started: перші налаштування

У MikroTik дуже класна документація (привіт, Confluence), і є власний розділ Getting started.

Пройдусь по основним речам, які робив на початку роботи.

Деякі скріни старі, тому ім’я хоста там буде “MikroTik” – дефолтне, далі глянемо, як це міняється.

IP теж може бути старий, дефолтний – 192.168.88.1. зараз він 192.168.0.1. Про налаштування DHCP – в наступному пості.

Backup та restore

У MirkoTik є два варіанти створення бекапу – з /export та /system backup.

/export створить текстовий файл з історією команд який можна прочитати – а /system backup створює бінарний файл, який включає в себе все, в тому числі ключі і сертифікати.

Але якщо конфіг переноситься на інший роутер – то system backup може сфейлитись, бо містить в собі прив’язку до конкретного девайсу, а результат з export – просто виконає команди.

/export та /import

Робимо /export в файл:

[setevoy@mikrotik-rb4011-gw] > /export file=init-backup

Тепер він є в Files:

І його можна прочитати.

З scp копіюємо собі на ноут:

[setevoy@setevoy-work ~] $ scp [email protected]:/init-backup.rsc .
[email protected]'s password: 
init-backup.rsc

І читаємо:

[setevoy@setevoy-work ~]  $ cat init-backup.rsc 
# 2026-01-22 15:21:51 by RouterOS 7.21
# software id = BUXG-TCU3
#
# model = RB4011iGS+
# serial number = HK50AXX5M2Y
/interface bridge
add admin-mac=04:F4:1C:89:8B:B3 auto-mac=no comment=defconf name=bridge
/interface wireguard
add listen-port=51820 mtu=1420 name=wg0
/interface list
add comment=defconf name=WAN
add comment=defconf name=LAN
/ip pool
add name=default-dhcp ranges=192.168.88.10-192.168.88.254
add name=vpn ranges=192.168.89.2-192.168.89.255
add name=dhcp_pool_lan ranges=192.168.0.50-192.168.0.200
...

Застосувати файл для відновлення параметрів:

/import file-name=init-backup.rsc

Система просто прочитає всі команди по черзі і виконає їх.

При цьому поточні налаштування не зміняться – якщо не відрізняють від того, що в файлі експорту, але можуть виникати дублі.

Експорт/імпорт не відновить:

  • паролі
  • сертифікати і приватні ключі
  • ліцензію
  • secrets (IPsec, WireGuard private keys)
  • деякі параметри /system

/system backup save та load

Для створення повного бекапу:

/system backup save name=before-change

Для відновлення:

/system backup load name=before-change

При цьому будуть видалені всі поточні налаштування – і відтворені з бекапу.

User management

Рекомендується створити власного юзера з root-правами, і відключити(але не видаляти) дефолтного admin.

Документація – User.

Першим ділом міняємо пароль admin:

[admin@mikrotik-rb4011-gw] > /user set admin password=PASSWORD

Прикольно, що після виконання строка з паролем відразу зникає з консолі.

Вивести всіх юзерів :

/user print

Активні сесії:

/user active print

Створюємо юзера, і задамо ліміт на адреси, з яких буде доступ (хоча ремоут SSH по дефолту і так недоступний на фаєрволі, про нього згодом):

/user add name=setevoy group=full password=PASSWORD address=192.168.0.0/24,192.168.88.0/24

Перевіряємо – або для всіх з /user print detail, або конкретного з where:

/user print detail where name="setevoy"

Змінити пароль або інші атрибути:

/user set [find name="setevoy"] password=NEW_PASSWORD

Або по ID – знаходимо ID з /user print:

І використовуємо його для /user set:

/user set 1 password=NEW_PASSWORD

Підключаємось під новим юзером:

[setevoy@setevoy-work ~]  $ ssh 192.168.0.1
...
[email protected]'s password: 
...
[setevoy@mikrotik-rb4011-gw] >

Апгрейд роутера

Бекапимось 🙂

Хоча завжди можна резетнути до заводських налаштувань, але краще завести звичку створити бекап.

Апгрейд включає в себе два окремих процеси – оновлення RouterOS та апдейти для firmware.

RouterOS upgrade

Документація – Upgrading and installation.

Перевіряємо поточну версію системи:

/system package print

Результат:

Columns: NAME, VERSION, BUILD-TIME, SIZE
# NAME      VERSION  BUILD-TIME           SIZE   
0 routeros  7.18.2   2025-03-11 11:59:04  11.5MiB

Перевіряємо наявність апдейтів:

/system package update check-for-updates

Результат:

[setevoy@MikroTik] > /system package update check-for-updates
            channel: stable                  
  installed-version: 7.18.2                  
     latest-version: 7.21                    
             status: New version is available

Завантажуємо – це тільки загрузка:

/system package update download

Результат:

[setevoy@MikroTik] > /system package update download
            channel: stable                                        
  installed-version: 7.18.2                                        
     latest-version: 7.21                                          
             status: Downloaded, please reboot router to upgrade it

І запускаємо вже сам процес апгрейду:

/system package update install

Система піде в ребут:

[setevoy@MikroTik] > /system package update install
            channel: stable                      
  installed-version: 7.18.2                      
     latest-version: 7.21                        
             status: calculating download size...
Received disconnect from 192.168.88.1 port 22:11: shutdown/reboot
Disconnected from 192.168.88.1 port 22

RouterBOARD (firmware) upgrade

Документація – RouterBOARD.

Перевіряємо поточну версію:

/system routerboard print

В мене це виглядало так:

[setevoy@MikroTik] > /system routerboard print
       routerboard: yes        
             model: RB4011iGS+ 
          revision: r2         
     serial-number: HK50AXX5M2Y
     firmware-type: al2        
  factory-firmware: 7.18.2     
  current-firmware: 7.18.2     
  upgrade-firmware: 7.21

Встановлена 7.18.2, є апгрейд до 7.21.

Запускаємо апгрейд:

/system routerboard upgrade

Результат:

[setevoy@MikroTik] > /system routerboard upgrade
Do you really want to upgrade firmware? [y/n] 
y
[setevoy@MikroTik] > 
14:13:58 echo: system,info,critical Firmware upgraded successfully, please reboot for changes to take effect!

Ребутаємо роутер:

[setevoy@MikroTik] > /system reboot
Reboot, yes? [y/N]: 
y
system will reboot shortly
Connection to 192.168.88.1 closed.

Перевіряємо ще раз:

[setevoy@MikroTik] > /system routerboard print  
       routerboard: yes        
             model: RB4011iGS+ 
          revision: r2         
     serial-number: HK50AXX5M2Y
     firmware-type: al2        
  factory-firmware: 7.18.2     
  current-firmware: 7.21       
  upgrade-firmware: 7.21

System management: основні команди

Корисні команди для роботи з системою.

Вивести події з логу:

/log print

Або з фільтром:

/log print where topics~"error|warning"

Вивести стан системи, версію, аптайм:

/system resource print

Коректно вимкнути систему:

/system shutdown

Перевірка живлення, температури:

/system health print

Навантаження CPU:

/tool profile

Cтан інтерфейсів коротко:

/interface print

Або детально:

/interface print detail

Адреси:

/ip address print

Роути:

/ip route print

Distance тут – пріорітет: можна мати друге інтернет-підключення (як я планую – на ethernet port 2 підключити LTE-роутер з SIM-картою), задати йому Distance == 2, і тоді трафік буде йти через перший порт – якщо він доступний, а якщо ні – то через другий.

Інформація по DNS:

/ip dns print

Виконати ping на якийсь хост:

/ping 8.8.8.8 src-address=192.168.0.1

Або traceroute (динамічний, як mtr на Linux/FreeBSD):

/tool traceroute 8.8.8.8

Коректно перезавантажити або виключити:

/system reboot
/system shutdown

Задати ім’я хоста:

/system identity set name=mikrotik-rb4011-gw

Власне, на цьому для початку все.

Що далі? Next steps

Про що ще думаю писати – частина вже є в чернетках, частину буду (якщо буде час) писати з нуля:

  1. налаштування DHCP
  2. налаштування DNS
  3. SSH і firewall – юзери, аутентифікація по ключам, правила фаєрволу
  4. налаштування WireGuard для підключення Peers
  5. scripts, alerting, monitoring – дуже класна можливість писати скрипти, які можуть слати алерти, див. Scripting
  6. резервний канал інтернету через LTE-роутер
  7. WiFi tuning

Loading

FreeBSD: Home NAS, part 9 – backup даних з rclone до AWS S3 та Google Drive
0 (0)

21 Січня 2026

В попередньому пості серії по налаштуванню Home NAS на FreeBSD знайомились з Restic – утилітою для роботи з бекапами, і яка підтримує шифрування, снапшоти, історію змін, див. FreeBSD: Home NAS, part 8 – backup даних NFS та Samba з restic.

Але окрім архівних даних в S3 хочеться мати “offsite hot copy” в Google Drive та AWS S3, аби мати доступ до даних постійно, і які не треба відновлювати із бекапу, а можна просто скопіювати з CLI або навіть з браузера.

При цьому не хочеться розводити зоопарк різних систем, а працювати з одною, яка буде вміти підключатись і до AWS, і до Google Drive.

Власне, під час пошуку того, як з restic копіювати дані в Google Drive знайшов такий собі “швейцарський ніж” – Rclone.

Всі частини серії по налаштуванню домашнього NAS на FreeBSD:

rclone overview

Rclone (“rsync for cloud storage“) – CLI-утиліта, вміє працювати просто з безліччю різних бекендів – і локальними даними або NFS, Samba, і FTP, і WebDAV, і, звісно, AWS S3 та Google Drive, див. всі в Overview of cloud storage systems.

Основні плюшки системи:

  • написаний на Go
  • можливість одною CLI отримати доступ до даних в Google Drive та S3
  • client-side шифрування даних і імен файлів
  • режими copy та sync, аналогічні rsync
  • є можливість змонтувати ремоут в локальну директорію, і працювати як зі звичайною папкою з даними (див. rclone mount)
  • вміє працювати як “проксі” між двома remotes (наприклад, копіювати дані між Google Drive та S3)
  • має Web GUI

Але при цьому rclone не є саме бекап-системою – не використовує снапшоти, не веде історію змін даних, не відновлює стан “на дату”.

rclone та Google Drive backend

Почнемо з Google Drive, бо це основне, для чого я планую використовувати rclone, але далі налаштуємо і AWS S3.

Документація – Google Drive.

Створення Google API keys для rclone

Для роботи з Google Drive створимо API keys, і цей процес опишу окремо, бо створення ключів в Google трохи заплутане, і я кожного разу шукаю гайд як це робити.

Переходимо в Google API Console, вибираємо існуючий або створюємо новий проект:

Зліва вибираємо “Enabled APIs & services”, клікаємо “Enable APIs”:

В пошуку знаходимо “Google Drive API”:

Вмикаємо його:

 

Переходимо в “OAuth consent screen”:

Переходимо в “Branding”, заповнюємо “App information” – задаємо ім’я, це чисто для нас, вказуємо email:

І внизу в “Developer contact information” ще раз пошту:

 

Зберігаємо, переходимо в “Audience”, перевіряємо, що тут “User type” заданий як External:

Переходимо в “Clients”, потім “Create client”:

Вказуємо “Application type” як Desktop app:

Отримуємо Client ID та Client Secret, зберігаємо собі:

Переходимо до налаштувань підключень вже в самому rclone.

Налаштування Google Drive remote

Виконуємо rclone config, вибираємо “n) New remote“, задаємо ім’я:

...
e/n/d/r/c/s/q> n

Enter name for new remote.
name> nas-google-drive
...

Далі вибираємо з яким бекендом rclone буде працювати.

Для Google Drive це 22 (можна вказати номер, можна ім’я “drive“):

...
22 / Google Drive
   \ (drive)
...

Наступний крок аутентифікація – задаємо ключі:

...
Option client_id.
Google Application Client Id
...
Enter a value. Press Enter to leave empty.
client_id> 377***7i7.apps.googleusercontent.com

Option client_secret.
OAuth Client Secret.
Leave blank normally.
Enter a value. Press Enter to leave empty.
client_secret> GOC***gjX
...

Задаємо рівень доступу – тут можна дати або повний доступ до всього драйву, або, якщо rclone буде тільки для бекапів, то вибрати “Access to files created by rclone only”.

На ноутбуках можна задати повний доступ, а на FreeBSD зробимо “тільки для свої файлів”:

В Advanced можна редагувати параметри типу “use_trash” та “Upload chunk size”, але це можна зробити пізніше – зараз просто тиснемо Enter.

Наступний крок – аутентифікація для отримання токену від Google:

...
Use web browser to automatically authenticate rclone with remote?
 * Say Y if the machine running rclone has a web browser you can use
 * Say N if running rclone on a (remote) machine without web browser access
If not sure try Y. If Y failed, try N.

y) Yes (default)
n) No

Так як це робиться на FreeBSD без браузеру, то вибираємо No – rclone згенерує токен, який треба вказати на машині з браузером, де є інший інстанс rclone:

...
y/n> n

Option config_token.
For this to work, you will need rclone available on a machine that has
a web browser available.
For more help and alternate methods see: https://rclone.org/remote_setup/
Execute the following on the machine with the web browser (same rclone
version recommended):
        rclone authorize "drive" "eyJ***ifQ"
Then paste the result.
Enter a value.

Виконуємо на ноуті:

$ rclone authorize "drive" "eyJ***ifQ"
2026/01/07 16:35:38 NOTICE: Make sure your Redirect URL is set to "http://127.0.0.1:53682/" in your custom config.
2026/01/07 16:35:38 NOTICE: If your browser doesn't open automatically go to the following link: http://127.0.0.1:53682/auth?state=Lf9q_HUVFlBUc2UqlSUqpw
2026/01/07 16:35:38 NOTICE: Log in and authorize rclone for access
2026/01/07 16:35:38 NOTICE: Waiting for code...

Відкривається браузер, вибираємо акаунт:

Дозволяємо доступ:

Отримуємо Success:

А на ноут в консолі, з якої викликали rclone authorize прийде токен:

...
2026/01/07 16:35:38 NOTICE: Waiting for code...
2026/01/07 16:37:33 NOTICE: Got code
Paste the following into your remote machine --->
eyJ...ifQ
<---End paste

Копіюємо його до rclone config на FreeBSD хості:

...
Enter a value.
config_token> eyJ***ifQ
...

І нове підключення готове:

...
Configuration complete.
Options:
- type: drive
- client_id: 377***7i7.apps.googleusercontent.com
- client_secret: GOC***gjX
- scope: drive.file
- token: {"access_token":"ya2***","expires_in":3599}
- team_drive: 
Keep this "nas-google-drive" remote?
y) Yes this is OK (default)
e) Edit this remote
d) Delete this remote

Глянути всі налаштовані бекенди можна з rclone listremotes:

root@setevoy-nas:/home/setevoy # rclone listremotes
nas-google-drive:

Або повна інформація з rclone config show:

root@setevoy-nas:/home/setevoy # rclone config show
[nas-google-drive]
type = drive
client_id = ***7i7.apps.googleusercontent.com
client_secret = GOCSPX-***gjX
scope = drive.file
token = {"access_token":"???"expires_in":3599}
team_drive =

scope = drive.file тут як раз вказує доступ тільки до даних від самого rclone.

Перевіряємо доступ до диску – свторюємо каталог:

root@setevoy-nas:~ # rclone mkdir nas-google-drive:Backups/Rclone

Перевіряємо зміст з rclone lsd та -R (recursive):

root@setevoy-nas:~ # rclone lsd -R nas-google-drive:Backups
           0 2026-01-20 16:04:10        -1 Rclone

Тепер налаштуємо ще AWS S3, а далі подивимось на основні команди для роботи з rclone.

rclone та AWS S3 backend

Документація – Amazon S3 Storage Providers.

Якби rclone був десь в AWS на EC2 або в EKS – то можна було б використати IAM Role, зараз просто зробимо з ключами.

Створення AWS IAM Policy та IAM User

Краще, звісно, зробити окремого юзера з власною політикою, у якого буде доступ до конкретної корзини, а не всього аккаунту.

Створюємо бакет:

Створюємо IAM Policy з повним доступом тільки до цього бакету:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "RcloneNasBackupsFullAccess",
      "Effect": "Allow",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::setevoy-backups-nas",
        "arn:aws:s3:::setevoy-backups-nas/*"
      ]
    }
  ]
}

Зберігаємо:

Створюємо юзера – без доступу до AWS Management Console:

Підключаємо створену вище політику:

Зберігаємо юзера:

Створюємо для нього ключі доступу:

Вибираємо Application running outside AWS:

Зберігаємо ключі:

Налаштування AWS S3 remote

Запускаємо rclone config, вибираємо “New remote”, задаємо ім’я:

root@setevoy-nas:~ # rclone config
Current remotes:

Name                 Type
====                 ====
nas-google-drive     drive

e) Edit existing remote
n) New remote
d) Delete remote
r) Rename remote
c) Copy remote
s) Set configuration password
q) Quit config
e/n/d/r/c/s/q> n

Enter name for new remote.
name> nas-s3-setevoy-backups
...

Далі тип – вибираємо 4 (s3), потім 1 – AWS:

...
Option Storage.
Type of storage to configure.
Choose a number from below, or type in your own value.
 1 / 1Fichier
   \ (fichier)
 2 / Akamai NetStorage
   \ (netstorage)
 3 / Alias for an existing remote
   \ (alias)
 4 / Amazon S3 Compliant Storage Providers including AWS, Alibaba, ArvanCloud, Ceph, ChinaMobile, Cloudflare, DigitalOcean, Dreamhost, Exaba, FlashBlade, GCS, HuaweiOBS, IBMCOS, IDrive, IONOS, LyveCloud, Leviia, Liara, Linode, Magalu, Mega, Minio, Netease, Outscale, OVHcloud, Petabox, RackCorp, Rclone, Scaleway, SeaweedFS, Selectel, StackPath, Storj, Synology, TencentCOS, Wasabi, Qiniu, Zata and others
   \ (s3)
...
Storage> s3

Option provider.
Choose your S3 provider.
Choose a number from below, or type in your own value.
Press Enter to leave empty.
 1 / Amazon Web Services (AWS) S3
   \ (AWS)
...

Далі вибираємо “Enter AWS credentials in the next step” і задаємо ключі:

...
Option env_auth.
Get AWS credentials from runtime (environment variables or EC2/ECS meta data if no env vars).
Only applies if access_key_id and secret_access_key is blank.
Choose a number from below, or type in your own boolean value (true or false).
Press Enter for the default (false).
 1 / Enter AWS credentials in the next step.
   \ (false)
 2 / Get AWS credentials from the environment (env vars or IAM).
   \ (true)
env_auth> 1

Option access_key_id.
AWS Access Key ID.
Leave blank for anonymous access or runtime credentials.
Enter a value. Press Enter to leave empty.
access_key_id> AKI***VXZ

Option secret_access_key.
AWS Secret Access Key (password).
Leave blank for anonymous access or runtime credentials.
Enter a value. Press Enter to leave empty.
secret_access_key> MkP***xJ/
...

Далі регіон бакету – він є в Properties:

Задаємо його:

...
Option region.
Region to connect to.
Choose a number from below, or type in your own value.
Press Enter to leave empty.
   / The default endpoint - a good choice if you are unsure.
 1 | US Region, Northern Virginia, or Pacific Northwest.
   | Leave location constraint empty.
   \ (us-east-1)
...
region> eu-west-1
...

Option endpoint залишаємо без мін, в Option location_constraint ще раз задаємо “eu-west-1“:

Option location_constraint.
Location constraint - must be set to match the Region.
Used when creating buckets only.
Choose a number from below, or type in your own value.
Press Enter to leave empty.
 1 / Empty for US Region, Northern Virginia, or Pacific Northwest
   \ ()
...
 6 / EU (Ireland) Region
   \ (eu-west-1)
...
location_constraint> eu-west-1

Option acl можна пропустити – у нас окрема корзина, в якій вже є всі налаштування ACL.

В server_side_encryption вибираємо “AES256“, далі “None“:

...
Option server_side_encryption.
The server-side encryption algorithm used when storing this object in S3.
Choose a number from below, or type in your own value.
Press Enter to leave empty.
 1 / None
   \ ()
 2 / AES256
   \ (AES256)
 3 / aws:kms
   \ (aws:kms)
server_side_encryption> 2

Option sse_kms_key_id.
If using KMS ID you must provide the ARN of Key.
Choose a number from below, or type in your own value.
Press Enter to leave empty.
 1 / None
   \ ()
 2 / arn:aws:kms:*
   \ (arn:aws:kms:us-east-1:*)
sse_kms_key_id> 1

Далі тип Storage Classes, див The Ultimate Guide to AWS S3 Pricing in 2026.

Можна взяти INTELLIGENT_TIERING:

...
Option storage_class.
The storage class to use when storing new objects in S3.
Choose a number from below, or type in your own value.
Press Enter to leave empty.
 1 / Default
   \ ()
...
 8 / Intelligent-Tiering storage class
   \ (INTELLIGENT_TIERING)
...
storage_class> 8

Зберігаємо – маємо новий бекенд:

...

Edit advanced config?
y) Yes
n) No (default)
y/n> 

Configuration complete.
Options:
- type: s3
- provider: AWS
- access_key_id: AKI***VXZ
- secret_access_key: MkP***zxJ/
- region: eu-west-1
- location_constraint: eu-west-1
- server_side_encryption: AES256
- storage_class: INTELLIGENT_TIERING
Keep this "nas-s3-setevoy-backups" remote?
y) Yes this is OK (default)
e) Edit this remote
d) Delete this remote
y/e/d> 

Current remotes:

Name                 Type
====                 ====
nas-google-drive     drive
nas-s3-setevoy-backups s3
...

Для роботи з S3 бакетом використовуємо формат remote_name:backet_name.

Створюємо в бакеті файл healthcheck.txt і каталог test – використовуємо rclone rcat:

root@setevoy-nas:~ # echo test | rclone rcat nas-s3-setevoy-backups:setevoy-backups-nas/test/healthcheck.txt

Перевіряємо зміст корзини з rclone ls:

root@setevoy-nas:~ # rclone ls nas-s3-setevoy-backups:setevoy-backups-nas/test
        5 healthcheck.txt

Rclone та шифрування

Задля більшої безпеки rclone може шифрувати свій локальний конфігураційний файл, а для безпечного зберігання даних в remote backends – може шифрувати дані там.

Rclone remote Crypt backend

Документація – Crypt.

crypt створюється як окремий бекенд, але використовує вже існуючий.

Наприклад, маючи nas-google-drive можна створити новий storage backend nas-google-drive-crypted і використовувати його: він буде таким собі “проксі” – ми пишемо дані “в нього”, він виконує шифрування, а потім “під капотом”, аби записати файли в Google Drive, використовує “оригінальний” бекенд nas-google-drive.

Створюємо новий remote:

root@setevoy-nas:~ # rclone config
Current remotes:

Name                 Type
====                 ====
nas-google-drive     drive
nas-s3-setevoy-backups s3

e) Edit existing remote
n) New remote
d) Delete remote
r) Rename remote
c) Copy remote
s) Set configuration password
q) Quit config
e/n/d/r/c/s/q> n

Enter name for new remote.
name> nas-google-drive-crypted

В типі вибираємо “15 – Encrypt/Decrypt a remote”:

...
Option Storage.
Type of storage to configure.
Choose a number from below, or type in your own value.
...
15 / Encrypt/Decrypt a remote
   \ (crypt)
...
Storage> crypt

Далі вказуємо “source backend”, в якому будуть зашифровані дані.

Тут важливий момент: можна вказати або весь storage як nas-google-drive:Backups/Rclone (корінь для rclone в моєму випадку) – або створити в ньому окремий каталог, в якому будуть зберігатись зашифровані дані:

...
Option remote.
Remote to encrypt/decrypt.
Normally should contain a ':' and a path, e.g. "myremote:path/to/dir",
"myremote:bucket" or maybe "myremote:" (not recommended).
Enter a value.
remote> nas-google-drive:Backups/Rclone/Vault

Далі є варіанти – шифрувати імена файлів і каталогів чи ні, дефолт – шифрувати:

...
Option filename_encryption.
How to encrypt the filenames.
Choose a number from below, or type in your own value of type string.
Press Enter for the default (standard).
   / Encrypt the filenames.
 1 | See the docs for the details.
   \ (standard)
 2 / Very simple filename obfuscation.
   \ (obfuscate)
   / Don't encrypt the file names.
 3 | Adds a ".bin", or "suffix" extension only.
   \ (off)
filename_encryption> 

Option directory_name_encryption.
Option to either encrypt directory names or leave them intact.
NB If filename_encryption is "off" then this option will do nothing.
Choose a number from below, or type in your own boolean value (true or false).
Press Enter for the default (true).
 1 / Encrypt directory names.
   \ (true)
 2 / Don't encrypt directory names, leave them intact.
   \ (false)
directory_name_encryption> 

І останнє – вказати пароль і опціонально salt:

...
Option password.
Password or pass phrase for encryption.
Choose an alternative below.
y) Yes, type in my own password
g) Generate random password
y/g> y
Enter the password:
password:
Confirm the password:
password:

Option password2.
Password or pass phrase for salt.
Optional but recommended.
Should be different to the previous password.
Choose an alternative below. Press Enter for the default (n).
y) Yes, type in my own password
g) Generate random password
n) No, leave this optional password blank (default)
y/g/n>

Готово:

...
Configuration complete.
Options:
- type: crypt
- remote: nas-google-drive:Backups/Rclone/Vault
- password: *** ENCRYPTED ***
Keep this "nas-google-drive-crypted" remote?
y) Yes this is OK (default)
e) Edit this remote
d) Delete this remote
y/e/d> 

Current remotes:

Name                 Type
====                 ====
nas-google-drive     drive
nas-google-drive-crypted crypt
nas-s3-setevoy-backups s3

Тепер, якщо скопіюємо туди текстовий файл, то прочитати його зможемо тільки з rclone:

root@setevoy-nas:~ # rclone copy /root/rclone-copy.txt nas-google-drive-crypted:

Якщо глянути його в Google Drive, то побачимо щось таке:

Якщо просто напряму просто завантажити собі на ноутбук із Google Drive Web UI, то дані теж недоступні:

[setevoy@setevoy-work ~]  $ file Temp/56ncq9f6nnvup446abn28tno20 
Temp/56ncq9f6nnvup446abn28tno20: data

[setevoy@setevoy-work ~]  $ cat Temp/56ncq9f6nnvup446abn28tno20 
����~���ܻv� ���ڰ�~qz
����zG�4nNEQ

Але все нормально читається з rclone:

root@setevoy-nas:~ # rclone cat nas-google-drive-crypted:rclone-copy.txt
test

І можна відновити собі локально з rclone copy або rclone copyto:

root@setevoy-nas:~ # rclone copyto nas-google-drive-crypted:rclone-copy.txt /home/setevoy/decrypted-rclone-copy.txt

root@setevoy-nas:~ # cat /home/setevoy/decrypted-rclone-copy.txt
test

rclone та config file ecnryption

rclone зберігає всі свої налаштування в файлі ~/.config/rclone/rclone.conf, і по дефолту файл не шифрується:

root@setevoy-nas:~ # cat ~/.config/rclone/rclone.conf
[nas-google-drive]
type = drive
client_id = ***.apps.googleusercontent.com
client_secret = GOCSPX-***
scope = drive.file
token = {"access_token":***","expiry":"2026-01-17T18:09:01.116266823+02:00","expires_in":3599}
team_drive =

Але можна задати пароль:

root@setevoy-nas:~ # rclone config encryption set
Enter NEW configuration password:
password:
Confirm NEW configuration password:
password:

Тепер файл зашифровано:

root@setevoy-nas:~ # cat ~/.config/rclone/rclone.conf
# Encrypted rclone configuration File

RCLONE_ENCRYPT_V0:
g3M***LSA=

А при роботі з rclone він буде просити ввести пароль для читання конфігу:

root@setevoy-nas:~ # rclone config show
Enter configuration password:

Для автоматизації в скриптах або cron пароль можна передати через змінну RCLONE_CONFIG_PASS.

Див. rclone config encryption та rclone config show.

Основні можливості і команди

Головні команди, принаймні при використанні rclone для бекапів, це rclone copy та rclone sync.

З найбільш цікавих команд варто глянути такі:

  • rclone bisync: повністю двосторонньо синхронізує src та dst (копіює і видаляє)
  • rclone cat та rclone rcat: читання з remote у stdout або запис у remote зі stdout
  • rclone delete та rclone deletefile: видалення даних з можливістю використання фільтрів (--exclude/--exclude)
  • rclone ls та rclone lsd: ls – список файлів з розмірами, lsd – список директорій
  • rclone mkdir: створення директорії на remote
  • rclone mount: монтує remote як файлову систему (FUSE)
  • rclone move: переносить файли (copy + delete з src)
  • rclone ncdu: інтерактивний перегляд використання місця на remote
  • rclone rmdir: видалення порожньої директорії
  • rclone rmdirs: рекурсивне видалення порожніх директорій
  • rclone purge: повне видалення каталогу з усім вмістом
  • rclone size: показує кількість файлів і загальний розмір
  • rclone test speed: тест швидкості upload/download до remote
  • rclone tree: показує дерево каталогів

Корисні Flags

Див. всі в Global Flags.

Найбільш цікаві:

  • --check-first: виконати перевірку даних між src та dst до початку копіювання
  • --checksum: порівнювати файл між src та dst не за size+mtime, а по MD5SUM checksum – повільніше, але точніше, корисно для критичних даних
  • --immutable: не змінювати файл в dst, якщо він відрізняється від src, а впасти з помилкою
  • --interactive: підтвердження змін вручну
  • --dry-run: тестове виконання без копіювання
  • --progress: відобразити процес
  • --transfers N: кількість файлів, які копіюються одночасно (дефолт 4)
  • --create-empty-src-dirs: якщо src каталог порожній – то створити порожній каталог в dst (не працює з S3)
  • --exclude та --exclude-from, --include та --include-from: список або файл зі списком даних, які треба включити або виключити з копіювання, див. Filter
  • --log-file: куди писати лог (корисно для автоматизації)
  • --fast-list: створює один великий список директорій і файлів, який тримає в пам’яті, а не для кожної директорії окремо (більше пам’яті – але швидше і менше API-запитів до dst)
  • --update: пропустити файли, modification time яких на dst новіший, ніж в src
  • --human-readable: використовувати формат Ki/Mi/Gi

Використання rclone copy та rclone copyto

rclone copy просто копіює файл або директорію до заданого remote.

Якщо src – каталог з підкаталогами, то будуть скопійовані всі дані і збережено структуру каталогів.

Наприклад, маємо локально каталоги з файлами:

root@setevoy-nas:~ # tree /tmp/new/
/tmp/new/
└── another
    └── dir
        ├── a.txt
        └── sub
            └── b.txt

Запускаємо rclone copy:

root@setevoy-nas:~ # rclone copy /tmp/new/ nas-google-drive:Backups/Rclone

Перевіряємо на remote:

root@setevoy-nas:~ # rclone tree nas-google-drive:Backups/Rclone/
/
└── another
    └── dir
        ├── a.txt
        └── sub
            └── b.txt

Аналогічно, можемо просто скопіювати окремий файл в новий каталог:

root@setevoy-nas:~ # rclone copy /root/rclone-copy.txt nas-google-drive:Backups/Rclone/rclone-dir

І тепер:

root@setevoy-nas:~ # rclone ls nas-google-drive:Backups/Rclone
        5 rclone-dir/rclone-copy.txt
        2 another/dir/a.txt
        2 another/dir/sub/b.txt

Але з rclone copy не можна вказати нове ім’я файлу, тобто:

root@setevoy-nas:~ # rclone copy /root/rclone-copy.txt nas-google-drive:Backups/Rclone/rclone-dir/new-rclone-copy.txt

Створить нову директорію, а не файл з ім’ям new-rclone-copy.txt:

root@setevoy-nas:~ # rclone ls nas-google-drive:Backups/Rclone
        5 rclone-dir/rclone-copy.txt
        5 rclone-dir/new-rclone-copy.txt/rclone-copy.txt

Для копіювання з новим іменем використовуємо rclone copyto:

root@setevoy-nas:~ # rclone copyto /root/rclone-copy.txt nas-google-drive:Backups/Rclone/rclone-dir-copyto/new-rclone-copyto.txt

В результаті:

root@setevoy-nas:~ # rclone ls nas-google-drive:Backups/Rclone
        5 rclone-dir-copyto/new-rclone-copyto.txt
...

Використання rclone sync

rclone sync виконує повну синхронізацію між src та dst: якщо в src файл було видалено – він видалиться і на dst. Див. також rclone bisync.

Корисні флаги тут:

  • --backup-dir: на dst не видаляти файл, який змінився в src, а зберегти в окрему директорію
  • --delete-after та --delete-before: видаляти дані до або після успішного копіювання
  • --suffix: додати суфікс до даних, які змінились

З rclone purge видаляємо створені під час тестів вище дані (видалить і саму директорію):

root@setevoy-nas:~ # rclone purge nas-google-drive:Backups/Rclone/
root@setevoy-nas:~ # rclone mkdir nas-google-drive:Backups/Rclone/

Виконуємо rclone sync:

root@setevoy-nas:~ # rclone sync /tmp/new/ nas-google-drive:Backups/Rclone/

Отримуємо аналогічну структуру на remote:

root@setevoy-nas:~ # rclone tree nas-google-drive:Backups/Rclone/
/
└── another
    └── dir
        ├── a.txt
        └── sub
            └── b.txt

І приклад того, як працює --backup-dir.

Змінимо зміст файлу в src:

root@setevoy-nas:~ # echo updated > /tmp/new/another/dir/a.txt

Виконуємо rclone sync і вказуємо --backup-dir:

root@setevoy-nas:~ # rclone sync /tmp/new/ nas-google-drive:Backups/Rclone --backup-dir nas-google-drive:Backups/Rclone-changed/$(date +%Y-%m-%d-%H-%M-%S)

Але --backup-dir має бути поза dst – тобто не можна робити rclone sync /path/src/ nas-google-drive:path/dst/ --backup-dir path/dst/backupDir.

Тепер у нас в корні Backups/ є новий каталог Rclone-changed/:

root@setevoy-nas:~ # rclone tree nas-google-drive:Backups/
/
├── Rclone
│   └── another
│       └── dir
│           ├── a.txt
│           └── sub
│               └── b.txt
└── Rclone-changed
    └── 2026-01-21-12-08-33
        └── another
            └── dir
                └── a.txt

В якому збережено оригінальну копію файлу a.txt:

root@setevoy-nas:~ # rclone cat nas-google-drive:Backups/Rclone-changed/2026-01-21-12-08-33/another/dir/a.txt
a

А в Backups/Rclone/another/dir/a.txt у нас оновлений файл:

root@setevoy-nas:~ # rclone cat nas-google-drive:Backups/Rclone/another/dir/a.txt
updated

Ну і на цьому, мабуть, все.

Тепер можна створити кілька cron і налаштувати бекап бекапів.

Loading

Arch Linux: “містичні” таймаути з DNS та “в пошуках Ethernet-істини”
0 (0)

19 Січня 2026

Вже пару місяців, як на робочому ноуті Lenovo ThinkPad T14 Gen 5 з Arch Linux виникла проблема з відкриттям нових сайтів – перші 10-15 секунд сайт завантажується “шматочками”, наприклад:

Але потім “розчехляється”, і все починає працювати чудово:

Нарешті, як почав налаштовувати нормальну домашню мережу з VPN (див. FreeBSD: Home NAS, part 3 – WireGuard VPN, Linux peer та routing), а потім для неї – DNS (див. FreeBSD: Home NAS, part 4 – локальний DNS з Unbound), то дійшли руки розібратись і з цієї проблемою.

І проблема виявилась дуже цікавою. Причину шукав довго, і перепровірив купу різних налаштувань – від IPv6 і DNS до драйверу мережової карти.

Головне, що проблема не те щоб була критичною – в цілому інет працював, а тому я іноді починав шукати причину, потім закидував, потім знов повертався.

The issue: “communications error to 192.168.0.1#53: timed out”

Що цікаво, що проблема спостерігалась тільки на Ethernet-підключені – на WiFi все працювало чудово.

А на Ethernet репродьюсилось на різних кабелях і через різні роутери.

Значить – що? Значить – або Сєня щось наковиряв руками у своєму Linux, або десь колись прилетів “кривий” апдейт чи до ядра, чи до драйвера, чи до якось бібліотеки.

Вже не пам’ятаю чому, але спершу грішив на DNS, бо ми ж знаємо, що:

І такі да – під час спроб зарепродьюсити це вдалось саме з DNS, під час тестів з dig – тому довго копав в цю сторону.

Виглядала проблема так: робимо dig, 10-15 запитів проходять нормально, а потім прилітає “communications error to 192.168.0.1#53: timed out“:

$ time dig google.com +short @192.168.0.1
;; communications error to 192.168.0.1#53: timed out
...

real    0m5.018s
user    0m0.004s
sys     0m0.008s

Ну і це виглядало, як дійсно причина того, що сайти тупили з загрузкою контенту: якщо DNS періодично відвалюється, а сайти мають купу додаткових скриптів і картинок, які підвантажуються з інших ресурсів – то поки всі хости разрезолвляться, поки отримаємо всі адреси, поки почнеться завантаження – як раз маємо цю затримку в кількадесят секунд.

Логічно? Так.

Тому і всі подальше тести я робив вже в циклі з dig:

$ for i in {1..50}; do { time dig +nocookie +noedns +tries=1 +time=2 google.com >/dev/null; } 2>&1; done
...
real    0m0.016s
...
real    0m2.015s
...
real    0m0.013s
...
real    0m1.392s

І такий результат був постійно – пачка запитів проходить нормально – “real 0m0.016s“, а потім на якомусь одному – таймаут і “real 0m2.015s” (бо +time=2 – чекати 2 секунди, а не дефолтні 5).

Ця ж проблема була видна з tcpdump: в 09:57:47 запит відправлений, але відповіді не отримано, через 2 секунди, в 09:57:49 – новий запит, і на нього вже відповідь прийшла:

...
09:57:47.717951 IP setevoy-work.40923 > _gateway.domain: 13058+ [1au] A? google.com. (51)
09:57:49.729589 IP setevoy-work.45441 > _gateway.domain: 63641+ [1au] A? google.com. (51)
09:57:49.730249 IP _gateway.domain > setevoy-work.45441: 63641 6/4/4 A 142.250.109.101, A 142.250.109.100, A 142.250.109.139, A 142.250.109.138, A 142.250.109.102, A 142.250.109.113 (260)
...

Аналогічно видна проблема з strace:

$ strace -r -e trace=network dig google.com
...
     0.002788 socket(AF_INET, SOCK_DGRAM, IPPROTO_IP) = 15
...
     ;; communications error to 192.168.0.1#53: timed out
     5.005754 socket(AF_INET, SOCK_DGRAM, IPPROTO_IP) = 16
...

Тут в 0.002788 відкритий сокет для відправки запиту, а через 5 секунд (5.005754) – бо зараз dig запускався без +time=2 – відкривається новий сокет для нового запиту, бо на попередній відповіді не було.

В пошуках Немо проблеми

Тут опишу що взагалі перевіряв – квест вийшов той ще.

Хоча записав не все, робив більше, але основне зберіг – вже давно є звичка закидувати в чорнетку поста на RTFM під час дебагу проблем.

Перевірка DNS в Linux

Перше – що з DNS в системі?

В /etc/resolv.conf заданий роутер:

# Generated by NetworkManager
nameserver 192.168.0.1

Міняємо на 1.1.1.1 чи на 8.8.8.8 – проблема залишається.

Окей… Може, в системі ще якийсь активний резолвер, і починається “DNS-гонка в ядрі” – запит “блукає” між ними?

Перевіряємо systemd-resolved – ні, не запущений:

$ systemctl status systemd-resolved
○ systemd-resolved.service - Network Name Resolution
     Loaded: loaded (/usr/lib/systemd/system/systemd-resolved.service; disabled; preset: enabled)
     Active: inactive (dead)
...

Може, dnsmasq?

Теж виключений:

$ systemctl status dnsmasq
○ dnsmasq.service - dnsmasq - A lightweight DHCP and caching DNS server
     Loaded: loaded (/usr/lib/systemd/system/dnsmasq.service; disabled; preset: disabled)
     Active: inactive (dead)
...

Значить, DNS-запити йдуть напряму до роутера, і… Що? Тупить роутер з відповідями? До нього не доходять запити – іноді губляться?

Що це може бути?

  • локальний firewall на Linux чи роутері?
    • ні – вимикав, проблема залишалась
  • race між кількома локальними DNS-сервісами?
    • виключили вище
  • power management мережевої карти – вона уходить в sleep?
    • маловірогідно, але далі перевіряв і це
  • баг драйвера мережевої карти?
    • можливо, бо проблема з’явилась не так давно, до цього на цьому ноуті і цій системі все працювало без проблем
  • якісь проблеми конкретно з UDP?
    • теж ніж – робив dig +tcp google.com, проблема залишалась
  • відповідь на DNS-запит повертається з іншого IP?
    • екзотична ідея, але як варіант – на роутері кілька мережевих інтерфейсів, об’єднаних в bridge, і – теоретично – роутер може відправити відповідь з іншої
    • але це прям щось дуже неординарне, та і проблема виникала однаково на різних роутерах, і раніше її не було

IPv6 та DNS

Не пам’ятаю чому, але десь на початку грішив на IPv6 під час виконання DNS.

/etc/gai.conf керує алгоритмом вибору адрес у glibc (GAI = getaddrinfo()), і визначає яку адресу (IPv4 чи IPv6) програма, яка робила DNS-запит вибере першою у випадку, якщо DNS повернув і A, і AAAA записи.

Можна включити IPv4 first – розкоментувати строку:

...
precedence ::ffff:0:0/96 100
...

Перевіряємо, що повернеться першим – адреса IPv4, чи IPv6:

$ getent ahosts google.com
142.250.130.100 STREAM google.com
...  
2a00:1450:4025:800::64 STREAM 
...

Першим IPv4, але теж не допомогло.

Пробував виключити в ядрі IPv6 взагалі:

$ sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1
$ sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1

Тут здавалось, що проблема знайдена – бо перший раз все пройшло без проблем, але ні – потім знов таймаути.

NIC Offloading

NIC Offloading – це коли частина операцій виконується на самому мережевому інтерфейсі, тобто offload деяких задач з CPU ноутбука на контролер карти.

Перевіряємо активні з ethtool -k:

$ sudo ethtool -k enp0s31f6 | grep on
rx-checksumming: on
tx-checksumming: on
        tx-checksum-ip-generic: on
scatter-gather: on
        tx-scatter-gather: on
tcp-segmentation-offload: on
...
generic-segmentation-offload: on
generic-receive-offload: on
rx-vlan-offload: on
tx-vlan-offload: on
receive-hashing: on
...

Самі цікаві тут:

  • TSO (TCP Segmentation Offloading): процесор віддає карті один великий шматок даних (наприклад, 64 КБ), а карта сама “нарізає” його на маленькі TCP-пакети по 1500 байт
  • GSO (Generic Segmentation Offloading): те саме, що й TSO, але більш універсальне (працює не лише для TCP)
  • GRO (Generic Receive Offloading): зворотний процес – карта отримує багато дрібних пакетів, “склеює” їх в один великий і лише тоді віддає процесору, що економить ресурси CPU
  • RX та TX Checksum Offloading: карта сама перевіряє контрольні суми (CRC) вхідних пакетів – якщо пакет “битий”, карта його просто викидає, навіть не повідомляючи операційну систему

По черзі вимикаємо їх, і перевіряємо:

  • sudo ethtool -K enp0s31f6 gro off: не допомогло
  • sudo ethtool -K enp0s31f6 gso off: не допомогло
  • sudo ethtool -K enp0s31f6 tso off: не допомогло
  • sudo ethtool -K enp0s31f6 rx off: не допомогло, і стало навіть гірше

Насправді те, що після відключення RX Checksum Offloading стало гірше – вже було підказкою: якщо до цього мережева карта сама фільтрувала помилки, то тепер вони всі повалили до ядра, що створило додаткове навантаження і хаос у черзі пакетів, тому корисні DNS-відповіді стали губитися ще частіше.

NIC Power Management

EEE (Energy Efficient Ethernet) має зменшувати витрати енергії на роботу карти.

Перевіряємо:

$ sudo ethtool --show-eee enp0s31f6
EEE settings for enp0s31f6:
enabled - active
17 (us)
        Supported EEE link modes:  100baseT/Full
                                   1000baseT/Full
        Advertised EEE link modes:  100baseT/Full
                                    1000baseT/Full
        Link partner advertised EEE link modes:  100baseT/Full
                                                 1000baseT/Full

Зараз “enabled – active” – вимикаємо:

$ sudo ethtool --set-eee enp0s31f6 eee off

Не допомогло.

Ще пробував так: запускаємо ping з короткими інтервалами, аби карта не засинала:

$ ping -i 0.2 192.168.0.1

І одночасно запускаємо цикл з dig – але проблема залишається.

Окремо перевіряв налаштування Runtime Power Management:

Знаходимо адресу PCI для девайсу enp0s31f6:

$ ls -l /sys/class/net/enp0s31f6/device
lrwxrwxrwx 1 root root 0 Jan 19 09:38 /sys/class/net/enp0s31f6/device -> ../../../0000:00:1f.6

Або:

an 19 09:38 /sys/class/net/enp0s31f6/device -> ../../../0000:00:1f.6
[setevoy@setevoy-work ~]  $ lspci -D | grep Ethernet
0000:00:1f.6 Ethernet controller: Intel Corporation Ethernet Connection (18) I219-LM (rev 20)

І перевіряємо параметри power:

$ cat /sys/bus/pci/devices/0000:00:1f.6/power/control
on

on” – включена постійно, значить не має вимикатись.

Драйвер та Message Signaled Interrupts

Перевіряв драйвер:

$ lspci -k -s 00:1f.6
00:1f.6 Ethernet controller: Intel Corporation Ethernet Connection (18) I219-LM (rev 20)
        Subsystem: Lenovo Device 2327
        Kernel driver in use: e1000e
        Kernel modules: e1000e

Мережевий контролер – Intel I219-LM, і драйвер e1000e, про який пишуть, що він “капризний”.

Параметри interrupts:

$ cat /proc/interrupts | grep -i enp0s31f6 ... IR-PCI-MSI-0000:00:1f.6 0-edge enp0s31f6

IR-PCI-MSI-0000:00:1f.6 – драйвер використовує MSI (Message Signaled Interrupts), яка начебто на Linux може давати drops для UDP на деяких картах Intel.

Створив файл /etc/modprobe.d/e1000e.conf, задав interrupt mode в legacy (див. Linux* Driver for Intel(R) Ethernet Network Connection):

options e1000e IntMode=0

Ребутнувся, перевірив:

$ cat /proc/interrupts | grep -i enp0s31f6
  19:     240716         ...  IR-IO-APIC   19-fasteoi   enp0s31f6

Не допомогло – проблема все ще залишалась.

Та і dig +tcp google.com все одно працював з проблемами.

Final: rx_crc_errors та зменшення швидкості

Ну і те, що спочатку пропустив – перевірка помилок на інтерфейсі.

Пропустив, бо кількість помилок не росла під час тестів:

$ ip -s link show enp0s31f6
3: enp0s31f6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether c4:c6:e6:e7:e4:26 brd ff:ff:ff:ff:ff:ff
    RX:  bytes packets errors dropped  missed   mcast           
     750558152  589207    104       0       0       0 
    TX:  bytes packets errors dropped carrier collsns           
      40067575  157761      0       2       0       0 
    altname enxc4c6e6e7e426

Або з ethtool:

$ sudo ethtool -S enp0s31f6 | grep -E "errors|missed|dropped|timeout|tx_aborted" | grep -v ": 0"
     rx_errors: 114
     tx_dropped: 26
     rx_crc_errors: 57

rx_crc_errors каже, що проблема з цілісністю пакетів, і – якщо з роутерам і кабелем все в порядку (а проблема спостерігалась на різних роутерах і з різними кабелями) – то скоріш за все проблема в самому RJ-45 на ноутбуці, хоча контакти виглядають нормально.

Спробував примусово зменшити швидкість на інтерфейсі з гігабіта до 100 Mbps:

$ sudo ethtool -s enp0s31f6 speed 100 duplex full autoneg on

І чудо! Все працює!

Повертаємо знов 1000:

$ sudo ethtool -s enp0s31f6 speed 1000 duplex full autoneg on

І проблема знову з’являється.

Можна було б просто залишити 100 Mbps – але ж я не для того підключений по кабелю і плачу за гігабітний GPON?

Благо, вдома є кілька USB-адаптерів з Ethernet, перемкнув кабель на нього:

$ ip a s enp0s13f0u2u3
2: enp0s13f0u2u3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether c8:4d:44:29:27:6b brd ff:ff:ff:ff:ff:ff
    altname enxc84d4429276b
    inet 192.168.0.198/24 brd 192.168.0.255 scope global dynamic noprefixroute enp0s13f0u2u3
...

Є гігабіт і Full Duplex:

$ sudo ethtool enp0s13f0u2u3
Settings for enp0s13f0u2u3:
        Supported ports: [ TP    MII ]
        Supported link modes:   10baseT/Half 10baseT/Full
                                100baseT/Half 100baseT/Full
                                1000baseT/Half 1000baseT/Full
        ...
        Speed: 1000Mb/s
        Duplex: Full
        ...
                               drv probe link timer ifdown ifup rx_err tx_err tx_queued intr tx_done rx_status pktdata hw wol
        Link detected: yes

І тепер все працює без проблем.

Loading