Предыстория
Имелся у меня машрутизатор на Raspberry Pi4 и на таком-же работал Home Assistant. В какой-то момент понадобился дополнительный UpLink в маршрутезаторе, по что был задействован USB-2-Ethernet адаптер. И всё бы было хорошо, но на ядрах 5.15 и выше, USB сетевой адаптер начал сбрасываться под нагрузкой. Некоторое время повозившись в попытках решить проблему, пришёл к выводу, что пора перетянуть маршрутизатор на что-то с большим количеством и не тратить время на решение проблем USB на RPi. Раз уж решил перевозить маршрутизатор, то и Home Assistant стоило разместить на одном устройстве с маршрутизатором.
Железо
Поиски адекватных вариантов на ARM с количеством независимых Eth портов не увенчался успехом, поэтому остановился на покупке мини пк на Алиэкспрес, вот такой вариант:
CPU: Intel Celeron N5105
Ethernet: 5xi226V 2.5Gb
Memory: 16Gb
SSD: 256Gb
В общем, железо с большим запасом и вполне приемлемой ценой, мне обошлось чуть менее $250 вместе с доставкой.
Установка OpenWRT
С этим всё по уже имеющимся докам, ни каких заморочек не возникло. Использовал образ, собранный самостоятельно при помощи openwrt-imagebuilder-22.03.3-x86-64. Пересобирал, с целью получить требуемые компоненты непосредственно в образе и не заниматься доустановкой ручками. Скрипт для сборки со списком компонентов:
#!/bin/sh
# Update imagebuilder from https://downloads.openwrt.org/snapshots/targets/x86/64/
# Be shure, to save /root /home and additional config files from /etc
# rpi-eeprom-update -a --- eeprom firmware update
if [ -d ../files ]; then
[ -d ./files ] && rm -rf ./files
cp -a ../files ./
fi
# Set CONFIG_TARGET_ROOTFS_PARTSIZE=4096 in .config file
KERNEL_PARTSIZE=32
ROOTFS_SIZE=4096
sed -i -re "s/^(CONFIG_TARGET_KERNEL_PARTSIZE=).*$/\\1$KERNEL_PARTSIZE/" .config
sed -i -re "s/^(CONFIG_TARGET_ROOTFS_PARTSIZE=).*$/\\1$ROOTFS_SIZE/" .config
sed -i -re "s/256/1024/" target/linux/x86/image/Makefile
PACKAGES='\
-dnsmasq \
\
kmod-usb-net-rtl8152 kmod-ipt-nat kmod-crypto-sha256 kmod-i2c-core kmod-scsi-generic kmod-scsi-core \
kmod-i2c-smbus kmod-i2c-gpio kmod-mii kmod-usb-net kmod-usb-wdm kmod-usb-net-qmi-wwan \
kmod-usb-net-cdc-mbim kmod-usb-serial-option kmod-usb-serial kmod-usb-serial-wwan kmod-usb-net-cdc-ether kmod-usb-storage \
kmod-usb-xhci-hcd kmod-ipt-nat-extra kmod-inet-diag kmod-bonding kmod-kvm-intel kmod-usb-storage-uas \
kmod-ipt-checksum \
\
screen sudo dmesg htop mc \
usbutils bash zsh owfs openvpn-wolfssl openssh-server \
openssh-client nmap tcpdump umbim ethtool-full uhubctl \
bind-client bind-host bind-nslookup bind-server bind-tools bind-dig \
udpxy zabbix-agentd i2c-tools gcc make ntpd \
nginx-ssl bwm-ng nmap lynx usb-modeswitch uqmi \
procps-ng-sysctl procps-ng-watch procps-ng-uptime procps-ng-top procps-ng-tload procps-ng-ps \
python3 python3-setuptools fdisk iptables-mod-checksum \
lua luasocket luasec luabitop iconv idn \
dnsmasq-full block-mount agetty mwan3 bmon \
qemu-firmware-efi qemu-x86_64-softmmu qemu-bridge-helper qemu-img \
python3-paho-mqtt \
\
luci luci-base luci-nginx luci-ssl-nginx \
luci-proto-qmi luci-proto-3g luci-proto-ipv6 luci-proto-ppp luci-proto-ncm \
luci-theme-bootstrap luci-theme-openwrt-2020 luci-proto-bonding \
\
luci-app-udpxy luci-app-advanced-reboot luci-app-acme luci-app-adblock luci-app-acl \
luci-app-aria2 luci-app-bcp38 luci-app-attendedsysupgrade luci-app-clamav luci-app-commands \
luci-app-transmission luci-app-dcwapd luci-app-ddns luci-app-diag-core \
luci-app-dnscrypt-proxy luci-app-firewall luci-app-frpc luci-app-frps luci-app-fwknopd \
luci-app-hd-idle luci-app-https-dns-proxy luci-app-ksmbd luci-app-lxc luci-app-minidlna \
luci-app-mwan3 luci-app-nextdns luci-app-nft-qos luci-app-nlbwmon luci-app-nut \
luci-app-openvpn luci-app-vnstat luci-app-opkg luci-app-qos luci-app-squid \
luci-app-statistics luci-app-upnp luci-app-mwan3\
\
luci-mod-admin-full luci-mod-network luci-mod-status luci-mod-system luci-mod-rpc \
\
luci-i18n-acl-ru luci-i18n-acme-ru luci-i18n-adblock-ru luci-i18n-advanced-reboot-ru luci-i18n-aria2-ru \
luci-i18n-attendedsysupgrade-ru luci-i18n-base-ru luci-i18n-qos-ru luci-i18n-bcp38-ru luci-i18n-bmx7-ru \
luci-i18n-clamav-ru luci-i18n-commands-ru luci-i18n-dcwapd-ru luci-i18n-ddns-ru \
luci-i18n-diag-core-ru luci-i18n-firewall-ru luci-i18n-frpc-ru luci-i18n-frps-ru luci-i18n-dnscrypt-proxy-ru \
luci-i18n-fwknopd-ru luci-i18n-hd-idle-ru luci-i18n-ksmbd-ru luci-i18n-https-dns-proxy-ru luci-i18n-lxc-ru \
luci-i18n-minidlna-ru luci-i18n-mwan3-ru luci-i18n-nextdns-ru luci-i18n-nft-qos-ru luci-i18n-nlbwmon-ru \
luci-i18n-nut-ru luci-i18n-openvpn-ru luci-i18n-opkg-ru luci-i18n-pbr-ru luci-i18n-mwan3-ru \
'
# -wpad-basic-mbedtls -libustream-mbedtls -libmbedtls -cshark
FILES="files/"
make -j $(nproc) image PROFILE=generic ADD_LOCAL_KEY=1 BIN_DIR=$(pwd)/../images/ PACKAGES="$PACKAGES" FILES="$FILES"
Собираем образ, заливаем на USB Flash, загружаемся с него и далее заливаем образ на строенный ssd.
Далее, рекомендую создать дополнительный раздел, такого-же размера, как и rootfs. Это упростит дальнейшее обновление OpenWRT, т.к. стандартные механизмы для x86 не работают. Имея доп раздел, можно на него разворачивать новый rootfs, перекидывать конфиги и после этого переключать загрузку на использование второго rootfs. Первый, в это время, будет резервным старой версии. На этом, с OpenWRT всё. Все остальные его настройки у каждого свои :)
Установка Home Assistant
С этим у меня возникли некоторые сложности - предлагаемый вариант OVA образа работает в QEMU, но со сбоями - то перезагрузится, а то и вовсе - зависнет. Естественно, такой вариант для системы автоматизации ни как не годится. Посему, используем haos_generic-x86-64. Я остановился на пер релизе 10.0.rc3. Первым делом нам потребуется увеличить размер самого образа, что бы при первом запуске, автоматически, подстроился размер раздела (умолчательный в районе 6Гб, что маловато). Я свой образ растянул на 48Гб:
dd if=/dev/zero of=haos_generic-x86-64-10.0.rc3.img seek=HAsize bs=4k count=ResultSize
Здесь, HAsize - указываем размер образа в блоках (6442450944/4096=1572864), ResultSize = желаемый размер образа в блоках, в моём случае, для 48Гб: 12582912.
Далее, создаём скрип для запуска образа в qemu. Тут основная сложность в том, что большинство используют его через libvirt, а его нет собранного для OpenWRT, поэтому привожу сразу свой скрипт для запуска/завершения работы вирт.машины. Самое сложное в нём, это корректное выключение виртуальной машины. Располагаем скрипт в /etc/init.d/qemu-ha
#!/bin/sh /etc/rc.common
START=99
STOP=1
HA_DIR='/Path_where_your_HAOS_image_stored'
IMG='haos_generic-x86-64-10.0.rc3-48Gb.img'
QEMU_OPTS="-enable-kvm -cpu host -smp 2 -m 8G \
-hda $HA_DIR/$IMG \
-device e1000,mac=E2:F2:6A:01:9D:C9,netdev=br0 \
-netdev bridge,br=br-lan,id=br0 \
-smbios type=0,uefi=on \
-bios $HA_DIR/OVMF_CODE.fd \
-action shutdown=poweroff \
-qmp tcp:127.0.0.1:4444,server,nowait \
-vnc :0 "
qemu_pidfile="/var/run/qemu.pid"
start() {
/usr/bin/qemu-system-x86_64 $QEMU_OPTS \
-daemonize &> /var/log/qemu.log
/usr/bin/pgrep qemu-system-x86_64 > $qemu_pidfile
echo "QEMU: Started VM with PID $(cat $qemu_pidfile)."
}
stop() {
echo "QEMU: Sending 'system_powerdown' to VM with PID $(cat $qemu_pidfile)."
nc 127.0.0.1 4444 <<QMP
{ "execute": "qmp_capabilities" }
{ "execute": "input-send-event",
"arguments": { "events": [
{ "type": "key", "data" : { "down": true,
"key": {"type": "qcode", "data": "power" } } } ] } }
QMP
if [ "$?" == "0" ]; then
if [ -e $qemu_pidfile ]; then
if [ -e /proc/$(cat $qemu_pidfile) ]; then
echo "QEMU: Waiting for VM shutdown."
while [ -e /proc/$(cat $qemu_pidfile) ]; do sleep 1s; done
echo "QEMU: VM Process $(cat $qemu_pidfile) finished."
else
echo "QEMU: Error: No VM with PID $(cat $qemu_pidfile) running."
fi
rm -f $qemu_pidfile
else
echo "QEMU: Error: $qemu_pidfile doesn't exist."
fi
else
echo "QEMU: Error: QMP interface can't accept connections or unavailable."
fi
}
Для автоматического запуска, выполняем: /etc/init.d/qemu-ha enable
Ну и запускаем машину: /etc/init.d/qemu-ha start
Для отладки, к машине можно подключиться по VNC, на пример, используя krdc.
И на последок - скрипт, для мониторинга температуры процессора в HA через mqtt:
#!/usr/bin/env python3
# coding=utf-8
# -*- coding: utf-8 -*-
import paho.mqtt.client as mqtt
import time
import json
import os, random
pid=os.getpid()
with open('/var/run/host_mon_mqtt.pid', "w") as f: f.write(str(pid))
broker_address="192.168.1.4"
topic = "Router state"
sensor_data = {'temperature_cpu': 0, 'temperature_board': 0}
########################################
#broker_address="iot.eclipse.org"
print("Creating new instance")
client = mqtt.Client(topic) #create new instance
print("connecting to broker")
client.connect(broker_address) #connect to broker
while not client.is_connected():
client.loop()
client.loop_start() #start the loop
try:
while True:
with open('/sys/class/thermal/thermal_zone1/temp', "r") as f: temp1 = f.read().strip()
with open('/sys/class/thermal/thermal_zone0/temp', "r") as f: temp2 = f.read().strip()
if temp1: temp1 = round(int(temp1)/1000, 2)
if temp2: temp2 = round(int(temp2)/1000, 2)
if (temp1 != sensor_data['temperature_cpu'] or temp2 != sensor_data['temperature_board']):
sensor_data['temperature_cpu'] = temp1
sensor_data['temperature_board'] = temp2
print("Temp is: ", temp1, temp2)
client.publish('zigbee2mqtt/'+topic, json.dumps(sensor_data), 1)
time.sleep(5)
except KeyboardInterrupt:
pass
client.loop_stop()
client.disconnect()
И инит скрипт для него, располагаем /etc/init.d/host_mon_mqtt
#!/bin/sh /etc/rc.common
START=99
STOP=1
prog_name="mqtt-temp.py"
prog="/mnt/Storage/HomeAssistant/tools/${prog_name}"
pidfile="/var/run/host_mon_mqtt.pid"
start() {
$prog > /var/log/${prog_name}.log 2>&1 &
echo "Host monitoring to MQTT: Started with PID $(cat $pidfile)."
}
stop() {
pid=$(cat $pidfile)
echo "Host monitoring to MQTT: PID ${pid} shutdown."
kill $pid
}
Заключение
На данный момент, система работает без сбоев. Запас производительности огромный. Всё стабильно. По завершению обкатки и после первых обновление OpenWRT, постараюсь дополнить.
По факту испытаний, выяснилось, что падения qemu были связаны с ошибками в microcode для kvm на процессорах Intel N5105. Версии микрокода до 0x24000024 (2022-09-02) падают. Лечится очень просто, скачиваем firmware вот тут. В каталог /lib/firmware/intel-ucode и будет стабильно работать.