Posts tagged with “system administration”

L2/L3 Сегментация и изоляция домашней сети на роутере Mikrotik

Written by skeirss

Задача: изолировать мобильные и IoT-устройства от компьютера, сервера и других устройств, подключенных кабелем на LAN-порты на роутере Mikrotik с помощью VLAN.

Замечание: подключиться к роутеру лучше через IP, а не через MAC из-за некоторых технических особенностей. И не забыть включить safe-mode.

Зачем нужна L2 и L3-изоляция

IoT - недоверенный, плохо патчащийся и потенциально уже скомпрометированный сегмент, поэтому его лучше держать в отдельном L2-домене с default-deny L3-политикой в сторону LAN, оставляя только явно разрешённые направления (обычно только исходящий интернет + при необходимости точечные "дыры" к серверу автоматизации).

L2-изоляция через VLAN блокирует ARP-spoofing, ARP-poisoning и sniffing трафика внутри одного broadcast-домена - без неё скомпрометированная IoT-лампочка технически может перехватывать/подменять трафик соседних хостов.

Также VLAN ограничевает L2-discovery-протоколы (mDNS, SSDP/UPnP, NetBIOS, LLMNR) от расползания между сегментами - иначе любое IoT-устройство видит все узлы в локальной сети, которые мы не хотим, чтобы их видели.

Главная идея

Отделить wifi от основной сети сразу двумя слоями защиты:

  • VLAN разделяет сети на уровне L2 и не позволяет устройствам видеть друг друга как участников одного Ethernet-сегмента;
  • Firewall на L3 уровне контролирует, какой трафик маршрутизатор может передавать между этими разными сетями.

Краткий план действий

  • Выносим беспроводные порты wlan1 и wlan2 в изолированный VLAN
  • LAN порты ether2-5 оставляем на нативном VLAN'e (PVID 1), то есть отдельно второй VLAN не создаем
  • Создадим отдельную подсеть для wifi-устройств (192.168.99.1/24) и настроим DHCP-сервер
  • Настроим firewall, сделаем исключение для смартфона, чтобы можно было передавать файлы по wifi с помощью kde connect и других подобных приложений (не забыть сделать ip-адрес смартфона статическим на dhcp-сервере)

Приблизительная схема сети

Что делаем по шагам

Шаг 1: Создаем VLAN для Wi-Fi и назначаем IP

Создадим виртуальный интерфейс для VLAN 99 поверх бриджа и выдадим ему адрес:

/interface vlan
add interface=bridge name=vlan99-wifi vlan-id=99

/ip address
add address=192.168.99.1/24 interface=vlan99-wifi

Шаг 2: Настраиваем DHCP-сервер для Wi-Fi

Создадим пул, настроим сеть и поднимем DHCP-сервер на интерфейсе vlan99-wifi:

/ip pool
add name=pool-vlan99 ranges=192.168.99.10-192.168.99.254

/ip dhcp-server
add address-pool=pool-vlan99 interface=vlan99-wifi name=dhcp-vlan99 disabled=no

/ip dhcp-server network
add address=192.168.99.0/24 dns-server=192.168.99.1 gateway=192.168.99.1

Шаг 3: Настраиваем порты Bridge (VLAN Filtering)

Теперь мы назначаем портам wlan нужный PVID. Проводные порты (ether2-5) мы не трогаем, они останутся в PVID 1, что сохранит их работу и статические IP-адреса в сети 192.168.88.0/24.

/interface bridge port
# Мобильные устройства отправляют нетегированный трафик, поэтому admit-only-untagged
set [find interface=wlan1] pvid=99 frame-types=admit-only-untagged-and-priority-tagged
set [find interface=wlan2] pvid=99 frame-types=admit-only-untagged-and-priority-tagged

Шаг 4: Таблица Bridge VLAN и включение фильтрации

Указываем, что процессор роутера (сам bridge) должен иметь доступ к VLAN 99 (тегированный трафик), чтобы работал наш DHCP и шлюз.

/interface bridge vlan
add bridge=bridge tagged=bridge vlan-ids=99

# Включаем фильтрацию VLAN на бридже
/interface bridge set bridge vlan-filtering=yes

Шаг 5: Изоляция L3 (Firewall) и исключение для KDE Connect

Сейчас сети разделены на уровне L2, но маршрутизатор по умолчанию маршрутизирует трафик между всеми своими интерфейсами. Нужно запретить хождение пакетов между подсетями в файрволе.

# Изоляция на уровне подсети: 
/ip firewall filter
add action=drop chain=forward comment="Isolate WiFi from LAN 88" dst-address=192.168.88.0/24 \
    src-address=192.168.99.0/24
add action=drop chain=forward comment="Isolate WiFi from LAN 77" dst-address=192.168.77.0/24 \
    src-address=192.168.99.0/24
add action=drop chain=forward comment="Isolate LAN 88 from WiFi" dst-address=192.168.99.0/24 \
    src-address=192.168.88.0/24

# Исключения для смартфона с адресом 192.168.99.254
add action=accept chain=forward comment="Allow Phone to PC (KDE Connect)" dst-address=192.168.88.34 \
    src-address=192.168.99.254
add action=accept chain=forward comment="Allow PC to Phone (KDE Connect)" dst-address=192.168.99.254 \
    src-address=192.168.88.34

Важный нюанс про KDE Connect. KDE Connect ищет устройства в сети с помощью широковещательных UDP-пакетов (UDP Broadcast). Широковещательные пакеты никогда не пересекают границы VLAN из-за этого телефон и компьютер не увидят друг друга автоматически. Чтобы это исправить нужно добавить устройство по IP вручную в приложении KDE Connect.

Базовая настройка завершена. Проверяем, что всё работает. Скорее всего интернет через wifi-сегмент работать не будет из-за дефолтного firewall-правила на микротике:

/ip firewall filter add action=drop chain=input comment="defconf: drop all not coming from LAN" in-interface-list=!LAN

Проблему можно решить просто добавив интерфейс VLAN для wifi в LAN-лист, но это не очень безопасно, так как wifi-сети таким образом дается то же доверие, что и проводной LAN - то есть телефон (и вообще любой, кто подключится к wifi) сможет ломиться в Winbox/SSH/API роутера. Это прямо противоречит цели изоляции.

Правильнее - не добавлять vlan99-wifi в LAN, а точечно разрешить только то, что реально нужно (DHCP + DNS), до финального drop-правила:

/ip firewall filter
add chain=input action=accept in-interface=vlan99-wifi protocol=udp dst-port=67-68 comment="WiFi: allow DHCP to router"
add chain=input action=accept in-interface=vlan99-wifi protocol=udp dst-port=53 comment="WiFi: allow DNS to router"
add chain=input action=accept in-interface=vlan99-wifi protocol=tcp dst-port=53 comment="WiFi: allow DNS to router"

На этом настрйка завершена. Если наблюдаются какие-то проблемы, то велика вероятность в неправильной расстановке firewall-правил.

Borgmatic backup issue

Written by skeirss

Problem

When running borgmatic create prune via the systemd service (borgmatic.service), the backup process hung indefinitely during the create phase after the log line Processing files … and the warning /var/lib/docker/containers/...-json.log: file changed while we backed it up.

The hang occurred only when the database hooks were active (mariadb_databases and sqlite_databases). These hooks automatically enable --read-special and cause borgmatic to include temporary dump files, which in turn triggered extensive special-file exclusions under /var/lib/docker. A broad exclusion - /var/lib/docker/** prevented the hang but also omitted all Docker volume data (persistent application files located under /var/lib/docker/volumes/).

Manual execution of borgmatic create from an interactive root shell completed successfully, confirming the issue was specific to the non-interactive systemd execution environment combined with Docker’s overlay2 layers, container logs, and runtime files.

Root Cause

  • Database hooks force --read-special and interact with rapidly changing or special files inside /var/lib/docker.

  • Borg’s file processing becomes blocked when encountering Docker overlay files, container log files that change during backup, and certain pseudo-files in the overlay2 directory.

  • The broad Docker exclusion resolved the hang but removed the desired volume backups.

Solution

Retain the database hooks (they provide consistent, point-in-time dumps) and replace the broad Docker exclusion with granular exclusions that target only the ephemeral/problematic paths. Update the exclude_patterns section in /etc/borgmatic/config.yaml as follows (add/replace the Docker-related lines):

exclude_patterns:
...
  - /var/lib/docker/overlay2/**
  - /var/lib/docker/containers/**
  - /var/lib/docker/image/**
  - /var/lib/docker/tmp/**
  - /var/lib/docker/volumes/*/_data/*.log

More info about this issue in the docs: https://torsion.org/borgmatic/how-to/backup-your-databases/

Organazing home backup system

Written by skeirss

I've organized my first home automated backup system and tried my best to describe it (the system seems a bit complicated to me, so in case something goes wrong I'll definitely need a reference). Since this is my first attempt at setting up a backup system, it may have some flaws.

Backup System Documentation

TL;DR

  • All backups are stored on NAS (192.168.88.40)
  • Storage path: /srv/backups, /mnt/media/backups (for media)
  • Tools: borg + borgmatic + bash scripts + ansible (optional, for bootstrapping)
  • Current Clients: arch-pc, vps-nl, NAS itself
  • Check status: systemctl status borgmatic.timer
  • Logs: journalctl -u borgmatic.service
  • Restore: see "Restore" section

Architecture

[arch-pc] ──(borgmatic.timer -> daily at 22:00)──borgmatic {/etc/borgmatic.d/arch-pc.yaml} ──▶ [NAS]──/mnt/backups/arch-pc (External HDD)

[vps-nl] ──(borgmatic.timer -> daily at 16:00 Server time (21:00 EKT))──borgmatic {/etc/borgmatic/config.yaml} ──▶ [NAS]──/mnt/backups/vps-nl (External HDD)

[arch-pc-media] ──media-backup.sh (running manually)──borgmatic {/etc/borgmatic.d/arch-pc-media.yaml} ──▶ [NAS]──/mnt/media/backups/arch-pc-media (External HDD)

[nas] ──(borgmatic.timer)──borgmatic {/etc/borgmatic/config.yaml}──▶ /mnt/backups/nas (External HDD)

Networking

arch-pc → NAS
  • Direct SSH connection
  • SSH Alias: home-borg (described in /root/.ssh/config)
  • Path: ssh://home-borg/mnt/backups/arch-pc
arch-pc-media → NAS
VPS → NAS
  • FRP tunnel (/etc/frp/frpc.toml) from VPS to NAS
  • Mapping: VPS:[redacted] → NAS:3315
  • SSH Alias: home-borg (described in /root/.ssh/config)
  • Path: ssh://home-borg/mnt/backups/vps-nl
NAS → NAS
  • No network, direct data transfer

Storage Layout

/mnt/backups/

arch-pc/

vps-nl/

nas/

/run/media/bernd/backups/

arch-pc-media/

Archive format: {hostname}-{timestamp}

Monitoring

Notifications:

  • Telegram bot

Triggers:

  • failure

Script:

  • /usr/local/bin/send_message_tg.sh

Restore

Ansible bootstrapping for NAS: https://github.com/bernd32/Ansible-NAS-Bootstrap

List archives: borg list /path/to/repo or outside the NAS: borgmatic repo-list --config /etc/borgmatic.d/arch-pc.yaml

Mount the archive to /mnt/borg (*): sudo borgmatic mount --config /etc/borgmatic.d/arch-pc.yaml --archive archpc-2025-10-03T21:25:12 --mount-point /mnt/borg

Unmount: sudo borgmatic umount --mount-point /mnt/borg

Extract a file from the archive: sudo borgmatic extract --config /etc/borgmatic.d/arch-pc.yaml --archive archpc-2025-10-03T21:25:12 --path /path/to/file/to/extact --destination /tmp

Restore all files from the archive: borgmatic extract --config /etc/borgmatic.d/arch-pc.yaml --archive archpc-2025-10-03T21:25:12

(*) fuse2 and python-llfuse packages should be installed

DB restore

The borgmatic configuration file used for the original backup must be available on the host.

Case 1. Restore all configured databases from the latest archive: /root/.local/bin/borgmatic restore --config /etc/borgmatic/config.yaml --archive latest

Case 2. Restore a specific database (e.g., only Bookstack’s MariaDB): 1) Stop all containers that use the mariadb container, e.g.: docker stop bookstack; 2) borgmatic restore --config /etc/borgmatic/config.yaml --archive latest --database bookstack

Case 3. Restore only the Chyrp-lite SQLite database: 1) Stop the container; 2) /root/.local/bin/borgmatic restore --config /etc/borgmatic/config.yaml --archive latest --database chyrp

Troubleshooting

Case 1. borg.crypto.key.ArchiveTAMInvalid: Data integrity error: Archive authentication did not verify error when trying to open an archive

Solution: This problem is related to Trusted Archive Metadata (TAM). The solution is to repair the archives:

su - 

BORG_WORKAROUNDS=ignore_invalid_archive_tam borg upgrade --archives-tam /path/to/repo

Check: borg check /path/to/repo

Automatic consistency checks and file restore testing

Borgmatic performs integrity checks automatically on a periodic basis stated in the borgmatic configuration: https://torsion.org/borgmatic/reference/configuration/consistency-checks/

(updated 2026-09-24) File restore test. A weekly automated test that confirms my Borg backups can actually be restored. Every Sunday at 18:00, a systemd timer runs a custom shell script that does the following: for each repository listed in /etc/borg-extraction-test.conf, the script takes the latest archive and picks a random non-empty file from it. It gets that file's SHA-256 from Borg, computed directly from the repository data, then extracts the file to a temporary directory and compares its checksum with the reference. The extracted file is deleted afterwards. A single summary report (✅/❌ per repo, with archive, file and any errors) is sent to my Telegram through /usr/local/bin/send_message_tg.sh. The script exits with an error status if any repo fails, so systemd also flags the run as failed.

Components:

/usr/local/bin/borg-verify.sh: the check script

/etc/borg-verify.conf: repo list with a password source per repo (mode 600)

borg-verify.service and borg-verify.timer: the schedule

To-Do

  • Docker containers configs + DB backup and restore (done)
  • There are two weak points in the current setup: the NAS backing up to itself (so this is not really a backup, just versioning), and the lack of an offsite backup. Both issues could be addressed by periodically copying backup archives to an encrypted external drive stored elsewhere. (done)

Flowchart

flowchart