diff --git a/! Tasks - planned action.md b/! Tasks - planned action.md index ed67d34..cdbadd0 100644 --- a/! Tasks - planned action.md +++ b/! Tasks - planned action.md @@ -4,10 +4,34 @@ AI анализ: > [!note]- ai prompt > Роль: девопс инженер, эксперт в области linux и сетевой инфраструктуры HomeLab. > Задача: проанализировать общую идею, текущую аппаратную и программную конфигурацию, сервисы и основные аспекты. В случае необходимости подели на подзадачи. Задавай уточняющие вопросы и найди узкие места в архитектуре. В случае спорных вариантов предложи альтернативное решение, для построения надежного HomeLab сервера. +> > Общая идея: построение надежного HomeLab сервера для домашнего использования, с целью совершенствования навыков сетевого инженера. Изучение linux и клиент-серверной архитектуры, внутренних сервисов, архитектуры сетей, работа с LXC и docker контейнерами. Косвенно, при построении я получаю полезные сервисы, которые можно использовать дома и при отладки своих своих внутренних Pet проектов. Возможность подключения к своему серверу из любой точки мира, где есть интернет -> Аппаратная конфигурация: 1. Роутер keenetic giga kn1011. 2. PC - NUC на i3-8109U CPU с 16GB DDR. 3. NAS WD My Book с HDD 500GB. Соединение PC-роутер LAN 1G порт1. NAS - роутер LAN порт2. Роутер имеет статический, "белый" IP. -> Программная конфигурация: на PC установлен Poroxmox Virtual Environment 9.1.9 который имеет следующую конфигурацию: LXC - gitea, adguard, debian, homepage, docker, nginxproxymanager, jellyfin. Все LXС контейнеры имеют тип Unprivileged. Так же установлен HomeAssistant OS в VM. Дополнительно в LXC docker установлены образы Portainer, HomeLable, Drawio и qBittorrent. В Proxmox прокинута SMB Public папка c NAS WD, с автомонтированием папки mnt/share в fstab. Каталог mnt/share подключен к следующим LXC (docker и jellyfin) В docker контейнере она используется как рабочий каталог для qBittorrent. Jellyfin использует ее как медиатеку с фильмами и музыкой. Так же к NUC подключен Sonoff USB - ZigBee dongle который проброшен в VM HomeAssistan. SSH доступ к Proxmox: вход по паролю - запрещен, прописаны rsa25595 ключи, стандартный порт изменен. -> Сервисы: На текущий момент настроены и активно используются следующие сервисы: 1 VM HomeAssistant OS - управляет сценариями умного дома (скрипты автоматизации, zigbee устройства и тд. 2 LXC Gitea - как основной домашний репозиторий для pet проектов и wiki obsidian. 3 LXC Jellyfin - основной сриминговый медиасервер для музыки, фильмов и сериалов используется в связке с qbittorrent докер образом, который развернут в LXC docker. 4 LXC HomePage - основная web страница HomeLab, для удобства доступа к ресурсам. 5 Adguard - использовался долгое время, как основной DNS сервер, фильтруя трафик, пока я не задумал устанвить NginxProxyserver, на время экспериментов adguard остановлен. 6 LXC Debian - предусмотрен как контейнер с ОС debian для отработки различных сценариев внутри ОС, там установлен VPN Xray vless, с панелью 3x-ui. ВПН пока не используется 7. LXC docker - задумывался как отдельный LXC контейнер для отработки навыков взаимодействия с docker (установка,запуск,настройка образов). Сейчас там установлены следующие images: homelable - визуализация сетевой инфраструктуры (пощупал, но пока не настроил. планирую вернуться к этому) drawio - как собственный ресурс. Практически не используется,нужен ли? и qbittorren - используется для закачки torrent образов +> +> Аппаратная конфигурация: +> 1. Роутер keenetic giga kn1011. +> 2. PC - NUC на i3-8109U CPU с 16GB DDR. +> 3. NAS OpenmediaVault с RAID1 8TB на Intel(R) Celeron(R) CPU 1037U @ 1.80GHz с 4GB DDR. +> +> Соединение PC-роутер LAN1. NAS - роутер LAN2. Роутер имеет статический, "белый" IP. +> Программная конфигурация NUC: +> 1. Poroxmox Virtual Environment 9.1.9 который имеет следующую конфигурацию: +> 1. LXC - gitea +> 2. LXC - adguard +> 3. LXC - debian +> 4. LXC - homepage +> 5. LXC - docker включающие следующие образы: +> 1. Portainer +> 2. HomeLable +> 3. Drawio +> 4. qBittorrent +> 6. LXC - nginxproxymanager +> 7. LXC - jellyfin. +> 2. Все LXС контейнеры имеют тип Unprivileged. +> 3. HomeAssistant OS в VM +> 4. В Proxmox VE прокинута SMB Public папка c NAS WD, с автомонтированием папки mnt/share в fstab. Каталог mnt/share подключен к следующим LXC (docker и jellyfin) В docker контейнере она используется как рабочий каталог для qBittorrent. Jellyfin использует ее как медиатеку с фильмами и музыкой. Так же к NUC подключен Sonoff USB - ZigBee dongle который проброшен в VM HomeAssistan. SSH доступ к Proxmox: вход по паролю - запрещен, прописаны rsa25595 ключи, стандартный порт изменен. +> +> Сервисы: На текущий момент настроены и активно используются следующие сервисы: +> 1 VM HomeAssistant OS - управляет сценариями умного дома (скрипты автоматизации, zigbee устройства и тд. 2 LXC Gitea - как основной домашний репозиторий для pet проектов и wiki obsidian. 3 LXC Jellyfin - основной сриминговый медиасервер для музыки, фильмов и сериалов используется в связке с qbittorrent докер образом, который развернут в LXC docker. 4 LXC HomePage - основная web страница HomeLab, для удобства доступа к ресурсам. 5 Adguard - использовался долгое время, как основной DNS сервер, фильтруя трафик, пока я не задумал устанвить NginxProxyserver, на время экспериментов adguard остановлен. 6 LXC Debian - предусмотрен как контейнер с ОС debian для отработки различных сценариев внутри ОС, там установлен VPN Xray vless, с панелью 3x-ui. ВПН пока не используется 7. LXC docker - задумывался как отдельный LXC контейнер для отработки навыков взаимодействия с docker (установка,запуск,настройка образов). Сейчас там установлены следующие images: homelable - визуализация сетевой инфраструктуры (пощупал, но пока не настроил. планирую вернуться к этому) drawio - как собственный ресурс. Практически не используется,нужен ли? и qbittorren - используется для закачки torrent образов > Основные аспекты: 1. Для доступа из внешнего мира в роутере переадресация портов: Проброшены TCP/UDP порты к UI Proxmox VE, HomeAssistant VM, Gitea и Jellyfin LXC. 2 В роутере разделение на VLAN и подсети не настроено. Роутер имеет возможность настройки системы доменных для использования сервисов. 3 NAS планирую менять на что-то более надежное с возможностью RAID-1. Резервное копирование пока не настроено. Улучшения: diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json index 6f84526..7d00784 100644 --- a/.obsidian/workspace.json +++ b/.obsidian/workspace.json @@ -13,12 +13,12 @@ "state": { "type": "markdown", "state": { - "file": "HomeLab/! Services.md", + "file": "! Tasks - planned action.md", "mode": "source", "source": false }, "icon": "lucide-file", - "title": "! Services" + "title": "! Tasks - planned action" } } ] @@ -190,11 +190,24 @@ }, "active": "26f4162438aca424", "lastOpenFiles": [ - "HomeLab/Raspberry PI5 + 3.2 SPI TFT (ILI9341).md", - "HomeLab/Hermes Agent.md", + "HomeLab/Hermes/hermes-desktop-connect-guide.md", + "HomeLab/Hermes/Hermes Agent.md", + "HomeLab/Hermes/amnezia-vpn-keenetic-kn1011.md", + "HomeLab/Proxmox/Proxmox HA manual install.md", + "HomeLab/Proxmox/Proxmox - settings.md", + "HomeLab/Proxmox/Proxmox - Installation - Backups.md", + "HomeLab/Proxmox/HAOS.md", + "HomeLab/Proxmox/Gitea.md", "HomeLab/! Services.md", - "HomeLab/ssh config.md", + "HomeLab/Docker - General.md", + "HomeLab/Docker - qBittorent.md", "HomeLab/HomePage.md", + "HomeLab/Nginx Proxy Manager.md", + "HomeLab/ssh config.md", + "HomeLab/Hermes/ssd1306-rpi5-guide.md", + "HomeLab/Hermes/oled_cpu_mon.py", + "HomeLab/Hermes", + "HomeLab/Raspberry PI5 + 3.2 SPI TFT (ILI9341).md", "! Tasks - planned action.md", "().md", "HomeLab/Files/Pasted image 20260427212721.png", @@ -203,23 +216,12 @@ "HomeLab/Radxa E25.md", "HomeLab/wireshark.md", "HomeLab/Valtwarden.md", - "HomeLab/Docker - qBittorent.md", "HomeLab/Amnezia for Keenetic.md", - "HomeLab/Proxmox/Proxmox - Installation - Backups.md", - "HomeLab/Proxmox/Proxmox - settings.md", - "HomeLab/Proxmox/HAOS.md", - "HomeLab/Docker - General.md", "Linux/CLI/СLI FileSystem.md", "Linux/CLI/СLI Kernel and System.md", "Linux/CLI/СLI Hardware (i2c,uart,gpio).md", - "HomeLab/Nginx Proxy Manager.md", - "HomeLab/Proxmox/Gitea.md", - "HomeLab/Proxmox/Proxmox HA manual install.md", "README.md", - "Git/Git.md", - "Linux/CLI/СLI Boot & Device tree.md", "Pasted image 20260517222623.png", - "VPN/VPN (WireGuard & 3x-UI VLESS Reality).md", "VPN/XRAY VLESS Guide.pdf", "VPN", "Excalidraw", diff --git a/HomeLab/Hermes Agent.md b/HomeLab/Hermes/Hermes Agent.md similarity index 62% rename from HomeLab/Hermes Agent.md rename to HomeLab/Hermes/Hermes Agent.md index 17d8478..111435d 100644 --- a/HomeLab/Hermes Agent.md +++ b/HomeLab/Hermes/Hermes Agent.md @@ -1,7 +1,5 @@ Описание на Habr: -Настройки находятся в `/opt/homepage/config/` - AI провайдер: polza.ai API: `pza_emOW7E0WsoXli4c85T5t_j2q1_Nk2dIT` diff --git a/HomeLab/Hermes/amnezia-vpn-keenetic-kn1011.md b/HomeLab/Hermes/amnezia-vpn-keenetic-kn1011.md new file mode 100644 index 0000000..7bf5fd4 --- /dev/null +++ b/HomeLab/Hermes/amnezia-vpn-keenetic-kn1011.md @@ -0,0 +1,184 @@ +# Настройка Amnezia VPN на роутере Keenetic Giga KN-1011 + +Роутер Keenetic Giga (KN-1011) под управлением KeeneticOS поддерживает подключение к VPN через протоколы **OpenVPN** и **WireGuard**. Amnezia VPN умеет экспортировать конфигурации в оба формата, что позволяет подключить роутер к вашему собственному VPN-серверу, развёрнутому через Amnezia. + +--- + +## 1. Экспорт конфигурации из Amnezia VPN + +### 1.1. На устройстве, где установлен Amnezia VPN + +1. Откройте приложение **Amnezia VPN**. +2. Перейдите на вкладку **Сервер** (Server) / **Подключения** (Connections). +3. Нажмите кнопку **Поделиться** (Share) / **Экспорт** (Export) рядом с вашим подключением. +4. Выберите формат экспорта: + + | Если роутер использует | Выберите формат | + |------------------------|----------------| + | OpenVPN | **For OpenVPN connection** (`.ovpn`) | + | WireGuard (родной) | **For WireGuard connection** (`.conf`) | + + > **Протокол AmneziaWG (awg) НЕ поддерживается роутером Keenetic.** + > Если у вас включён AmneziaWG, временно переключитесь в приложении на обычный WireGuard или OpenVPN, экспортируйте конфиг, затем можно вернуть обратно. + +5. Сохраните полученный файл на компьютер (`.ovpn` или `.conf`). + +### 1.2. Если доступна только ссылка `vpn://` + +На серверах Amnezia Self-hosted и Premium иногда конфиг выдаётся в виде URI `vpn://...`. Его нужно преобразовать: откройте этот URI в приложении Amnezia, выберите нужный протокол (OpenVPN или WireGuard), после чего экспортируйте как описано выше. + +--- + +## 2. Установка необходимого компонента на роутере + +Зайдите в веб-интерфейс роутера: +- **Адрес:** `http://my.keenetic.net` или `http://192.168.1.1` +- **Логин/пароль:** по умолчанию `admin` / `1234` (если не меняли) + +### 2.1. Для OpenVPN + +1. Перейдите: **Управление** → **Общие настройки системы** (или **System Settings**). +2. В блоке **Обновление KeeneticOS и компонентов** нажмите **Выбор компонентов** (Component options). +3. Найдите компонент **OpenVPN-клиент и сервер** (OpenVPN client and server). +4. Установите флажок и нажмите **Сохранить**. +5. Роутер скачает и установит компонент (может потребоваться перезагрузка). + +### 2.2. Для WireGuard + +1. Перейдите: **Управление** → **Общие настройки системы**. +2. Нажмите **Выбор компонентов**. +3. Найдите компонент **WireGuard VPN**. +4. Установите флажок → **Сохранить**. + +--- + +## 3. Создание VPN-подключения + +### 3.1. Через OpenVPN (.ovpn) + +1. Откройте экспортированный файл `.ovpn` в любом текстовом редакторе (Блокнот, Notepad++). +2. Выделите ВЕСЬ текст файла (Ctrl+A) и скопируйте (Ctrl+C). + +3. В веб-интерфейсе роутера перейдите: + **Интернет** → **Другие подключения** (Other connections). + +4. В блоке **VPN-подключения** нажмите **Создать подключение** (Create connection). + +5. Заполните: + + | Поле | Значение | + |-------------------------|----------| + | **Использовать для выхода в интернет** | ☑ (включить) | + | **Имя подключения** | Например `Amnezia-OVPN` | + | **Тип (протокол)** | **OpenVPN** | + | **Конфигурация OpenVPN** | Вставьте скопированный текст (Ctrl+V) | + | **Получать маршруты с удалённой стороны** | ☑ (включить) | + +6. При необходимости нажмите **Показать дополнительные настройки** и укажите: + - **Подключаться через** → «Любое интернет-подключение» + - **Расписание работы** → выключить (или настроить по желанию) + +7. Нажмите **Сохранить**, затем переведите переключатель в положение **Вкл**. + +#### Типичные проблемы с OpenVPN на Keenetic + +| Ошибка в логе | Решение | +|--------------|---------| +| `auth-user-pass without inline credentials` | Найдите и удалите строку `auth-user-pass` — она должна быть заменена на блок `...` с логином и паролем внутри | +| `block-outside-dns` | Добавьте в конфиг строку: `pull-filter ignore "block-outside-dns"` | +| `cipher BF-CBC` / `keysize` | Замените `cipher BF-CBC` на: `data-ciphers AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305` и `data-ciphers-fallback AES-256-CBC` | +| `Unrecognized option: client-ip` | Добавьте: `ignore-unknown-option client-ip block-ipv6` | + +> Лог OpenVPN доступен в веб-интерфейсе: **Диагностика** → **Системный журнал**, фильтр по `OpenVPN0`. + +### 3.2. Через WireGuard (.conf) + +Keenetic умеет импортировать WireGuard-конфигурацию напрямую из файла: + +1. В веб-интерфейсе перейдите: **Интернет** → **Другие подключения**. + +2. В блоке **WireGuard** нажмите **Импорт из файла** (Import from a file). + +3. Выберите файл `.conf`, экспортированный из Amnezia. + +4. После импорта нажмите **Сохранить**. + +5. Включите подключение переключателем в положение **Вкл**. + +Если импорт из файла недоступен (старая версия KeeneticOS), настройте вручную: + +1. Нажмите **Создать подключение** → тип **WireGuard**. +2. Заполните поля из файла `.conf`: + + | Поле в веб-интерфейсе | Значение из `.conf` | + |-----------------------|---------------------| + | Приватный ключ (PrivateKey) | `[Interface] PrivateKey = ...` | + | Адрес (Address) | `[Interface] Address = ...` | + | DNS-сервер (DNS) | `[Interface] DNS = ...` | + | Открытый ключ пира (Peer PublicKey) | `[Peer] PublicKey = ...` | + | Endpoint (адрес сервера:порт) | `[Peer] Endpoint = ...` | + | Разрешённые IP (AllowedIPs) | `[Peer] AllowedIPs = ...` | + | PersistentKeepalive | `[Peer] PersistentKeepalive = ...` | + +3. Сохраните и включите. + +> ⚠️ **Важно:** родной WireGuard на Keenetic **не поддерживает** параметры обфускации AmneziaWG (`Jc`, `Jmin`, `Jmax`, `S1`, `S2`, `H1`–`H4`). Если в вашем конфиге есть эти параметры — используйте OpenVPN, либо переключитесь в Amnezia на обычный WireGuard перед экспортом. + +--- + +## 4. Настройка приоритетов и политик трафика + +Чтобы весь трафик роутера шёл через VPN: + +1. Перейдите: **Интернет** → **Приоритеты подключений** (Connection priorities). +2. На вкладке **Правила по умолчанию**: + - Найдите созданное VPN-подключение + - Перетащите его мышью на самый верх списка + - Нажмите **Сохранить** + +3. Если хотите направлять через VPN только определённые устройства: + - Перейдите на вкладку **Привязка политик** (Policy bindings) + - Создайте правило, выбрав устройство и нужное VPN-подключение + +--- + +## 5. Проверка подключения + +1. Перейдите: **Диагностика** → **Ping**. +2. Введите `8.8.8.8` и нажмите **Проверить**. +3. Убедитесь, что пинг проходит. +4. Дополнительно проверьте внешний IP-адрес роутера на главной странице (должен совпадать с IP вашего VPN-сервера). + +Просмотр статуса VPN: +- **Интернет** → **Другие подключения** — переключатель должен быть зелёным (Вкл). +- **Диагностика** → **Системный журнал** — поиск по `OpenVPN0` или `Wireguard0`. + +--- + +## 6. Если что-то не работает + +| Симптом | Вероятная причина | +|---------|-------------------| +| Подключение не включается | Не установлен компонент OpenVPN/WireGuard в системе | +| OpenVPN: ошибка в логе | Несовместимая директива — см. таблицу в п. 3.1 | +| WireGuard: нет соединения | Конфиг содержит параметры AmneziaWG — используйте OpenVPN | +| VPN есть, но интернет не работает | Неправильные приоритеты подключений (п. 4) | +| Нет доступа к локальным ресурсам | В OpenVPN-конфиге `redirect-gateway` или в `AllowedIPs = 0.0.0.0/0` — роутер не видит локальную сеть. Используйте AllowedIPs по умолчанию от провайдера или отключите VPN для локальных адресов | + +--- + +## 7. Сводка — что поддерживается на KN-1011 + +| Протокол | Поддержка | Способ подключения | +|----------|-----------|-------------------| +| **OpenVPN** (TUN, UDP/TCP) | ✅ Полная | Вставка текста `.ovpn` в поле конфигурации | +| **WireGuard** (обычный) | ✅ Полная | Импорт `.conf` файла или ручной ввод | +| **AmneziaWG** (awg) | ❌ **НЕ поддерживается** | Требуется OpenWRT с кастомным ядром | +| **OpenVPN over Cloak** | ❌ Не поддерживается | Только через сторонний клиент | +| **XRay (VLESS Reality)** | ❌ Не поддерживается | Только через сторонний клиент | + +**Вывод:** для Keenetic используйте **OpenVPN** (рекомендуется) или обычный **WireGuard** без обфускации. + +--- + +> **Источники:** официальная документация Keenetic (support.keenetic.com), документация Amnezia (docs.amnezia.org), GitHub amnezia-vpn/amnezia-client. \ No newline at end of file diff --git a/HomeLab/Hermes/hermes-desktop-connect-guide.md b/HomeLab/Hermes/hermes-desktop-connect-guide.md new file mode 100644 index 0000000..9e70c12 --- /dev/null +++ b/HomeLab/Hermes/hermes-desktop-connect-guide.md @@ -0,0 +1,76 @@ +# Подключение Hermes Desktop (Windows) к серверу + +Общая схема: + + [Windows] --LAN--> [Сервер 192.168.1.44:9119] + Hermes Desktop Hermes Dashboard + (тот же агент, что в этом сеансе) + +--- + +## 1. Что уже сделано на сервере + +- Dashboard запущен на `0.0.0.0:9119` (слушает все интерфейсы) +- Basic auth включена: логин `admin`, пароль `agent` +- Dashboard **автоматически стартует при загрузке сервера** (systemd-сервис `hermes-dashboard.service`) +- При падении перезапускается автоматически через 10 секунд + +Проверить статус можно с сервера: + + curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:9119/api/health + # → 200 + + systemctl status hermes-dashboard.service + # → active (running) + +--- + +## 2. Подключение из Hermes Desktop (Windows) + +1. Открой Hermes Desktop на Windows +2. Нажми `Ctrl+K` (Cmd+K на Mac) — откроется палитра команд +3. Набери **Remote Gateway** и выбери `Connect to Remote Gateway...` (или найди в Settings → Gateway → Remote) +4. Введи данные: + - **URL сервера:** `http://192.168.1.44:9119` + - **Логин:** `admin` + - **Пароль:** `agent` +5. После подключения в левой панели появится серверный профиль — можно выбирать его и общаться как обычно. + +> Если Windows подключён через VPN или из другой подсети — IP может отличаться. +> Узнать текущий IP сервера: `hostname -I` + +--- + +## 3. Управление сервисом (на сервере) + +Сервис уже установлен и активен. Команды управления: + +| Действие | Команда | +|----------|---------| +| Статус | `systemctl status hermes-dashboard.service` | +| Стоп | `systemctl stop hermes-dashboard.service` | +| Старт | `systemctl start hermes-dashboard.service` | +| Рестарт | `systemctl restart hermes-dashboard.service` | +| Отключить автозапуск | `systemctl disable hermes-dashboard.service` | +| Логи (свежие) | `journalctl -u hermes-dashboard.service -n 50` | +| Логи (хвост) | `journalctl -u hermes-dashboard.service -f` | + +--- + +## 4. Безопасность + +- Доступ к dashboard возможен из **любого устройства в той же LAN** (подсеть 192.168.1.0/24), кто знает логин/пароль +- За пределами LAN dashboard **недоступен** — сервер не торчит в интернет +- Пароль хранится в `/root/.hermes/config.yaml` — не свети этот файл +- Для доступа из интернета — облачный relay (Nous Portal) или VPN/SSH-туннель + +--- + +## 5. Что если не работает + +| Симптом | Решение | +|---------|---------| +| Desktop пишет "Connection refused" | Сервис не запущен → `systemctl start hermes-dashboard.service` | +| 401 Unauthorized | Неправильный логин или пароль — проверь в `config.yaml` секцию `basic_auth` | +| Desktop видит сервер, но не отвечает | `hermes doctor` на сервере — проверить модель и соединение | +| Нет соединения после ребута сервера | Сервис включён в автозапуск, проверь статус `systemctl status hermes-dashboard.service` | diff --git a/HomeLab/Hermes/oled_cpu_mon.py b/HomeLab/Hermes/oled_cpu_mon.py new file mode 100644 index 0000000..7031734 --- /dev/null +++ b/HomeLab/Hermes/oled_cpu_mon.py @@ -0,0 +1,74 @@ +import time, os +from luma.core.interface.serial import i2c +from luma.oled.device import ssd1306 +from PIL import Image, ImageDraw, ImageFont + +serial = i2c(port=1, address=0x3C) +device = ssd1306(serial) +font = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 10) +font_big = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 16) +device.cleanup = lambda: None + + +def get_cpu(): + with open('/proc/loadavg') as f: + parts = f.read().split() + load = float(parts[0]) + cores = os.cpu_count() or 4 + percent = (load / cores) * 100 + return min(percent, 100), load + + +def get_ram(): + with open('/proc/meminfo') as f: + lines = f.readlines() + mem_total = int([l for l in lines if 'MemTotal' in l][0].split()[1]) + mem_avail = int([l for l in lines if 'MemAvailable' in l][0].split()[1]) + used = mem_total - mem_avail + return used / mem_total * 100, used // 1024, mem_total // 1024 + + +def get_temp(): + try: + with open('/sys/class/thermal/thermal_zone0/temp') as f: + return int(f.read()) / 1000 + except: + return 0 + + +while True: + cpu_pct, load = get_cpu() + ram_pct, used_mb, total_mb = get_ram() + temp = get_temp() + + img = Image.new('1', device.size, 'black') + draw = ImageDraw.Draw(img) + + draw.rectangle((0, 0, 127, 63), outline='white', fill='black') + + bar_w = 90 + bar_h = 12 + + # CPU bar + draw.rectangle((32, 4, 32 + bar_w, 4 + bar_h), outline='white', fill='black') + fill_w = int(bar_w * cpu_pct / 100) + if fill_w > 0: + draw.rectangle((32 + 1, 4 + 1, 32 + fill_w, 4 + bar_h - 1), fill='white') + draw.text((2, 5), 'CPU', font=font, fill='white') + draw.text((32 + bar_w + 4, 3), f'{cpu_pct:.0f}%', font=font_big, fill='white') + draw.text((2, 20), f'Load: {load:.2f}', font=font, fill='white') + + # RAM bar + draw.rectangle((32, 36, 32 + bar_w, 36 + bar_h), outline='white', fill='black') + fill_w2 = int(bar_w * ram_pct / 100) + if fill_w2 > 0: + draw.rectangle((32 + 1, 36 + 1, 32 + fill_w2, 36 + bar_h - 1), fill='white') + draw.text((2, 38), 'RAM', font=font, fill='white') + draw.text((32 + bar_w + 4, 36), f'{ram_pct:.0f}%', font=font_big, fill='white') + + # Temp and PID at bottom + draw.text((2, 52), f'Temp: {temp:.1f}C', font=font, fill='white') + draw.text((72, 52), f'PID: {os.getpid()}', font=font, fill='white') + + device.display(img) + time.sleep(1) \ No newline at end of file diff --git a/HomeLab/Hermes/ssd1306-rpi5-guide.md b/HomeLab/Hermes/ssd1306-rpi5-guide.md new file mode 100644 index 0000000..901492c --- /dev/null +++ b/HomeLab/Hermes/ssd1306-rpi5-guide.md @@ -0,0 +1,443 @@ +# Подключение OLED дисплея SSD1306 128x64 к Raspberry Pi 5 + +## Содержание + +1. [Подключение по I2C](#1-подключение-по-i2c) +2. [Настройка I2C на RPi5](#2-настройка-i2c-на-rpi5) +3. [Установка ПО](#3-установка-по) +4. [Проверка дисплея](#4-проверка-дисплея) +5. [Статическое изображение](#5-статическое-изображение) +6. [CPU/Монитор в реальном времени](#6-cpumонитор-в-реальном-времени) +7. [Systemd-сервис для автостарта](#7-systemd-сервис-для-автостарта) +8. [Решение проблем](#8-решение-проблем) +9. [Приложение: полный код скриптов](#9-приложение-полный-код-скриптов) + +--- + +## 1. Подключение по I2C + +SSD1306 подключается по I2C-шине. Пины I2C на Raspberry Pi 5: + +| Пин | Название | GPIO | Назначение | +|-----|----------|------|------------| +| 3 | SDA | GPIO2 | I2C Data | +| 5 | SCL | GPIO3 | I2C Clock | +| 1 | 3.3V | — | Питание | +| 6 | GND | — | Земля | + +**Схема подключения:** +``` +RPi5 OLED SSD1306 +GPIO2 (Pin 3) ─────── SDA +GPIO3 (Pin 5) ─────── SCL +3.3V (Pin 1) ─────── VCC +GND (Pin 6) ─────── GND +``` + +**Важно:** Для I2C нужны подтягивающие резисторы (pull-up) 4.7 кОм на линиях SDA и SCL к 3.3V. На большинстве готовых OLED-модулей они уже впаяны — дополнительно ставить не нужно. + +--- + +## 2. Настройка I2C на RPi5 + +На Raspberry Pi 5 I2C по умолчанию **отключён** в `/boot/firmware/config.txt`. + +### 2.1. Включение I2C + +```bash +sudo sed -i 's/#dtparam=i2c_arm=on/dtparam=i2c_arm=on/' /boot/firmware/config.txt +sudo reboot +``` + +**Проверка после перезагрузки:** + +```bash +sudo /usr/sbin/i2cdetect -y 1 +``` + +Вывод (пример — если дисплей подключён): +``` + 0 1 2 3 4 5 6 7 8 9 a b c d e f +00: -- -- -- -- -- -- -- -- +... +30: -- -- -- -- -- -- -- -- -- -- -- -- 3c -- -- -- +... +``` + +Адрес **0x3C** — стандартный для SSD1306 (может быть 0x3D, проверяйте по даташиту). + +**Заметка:** `i2cdetect` лежит в `/usr/sbin/` — этого каталога нет в PATH обычного пользователя. Используйте полный путь или `sudo`. + +### 2.2. Альтернатива: raspi-config + +```bash +sudo raspi-config +# → Interface Options → I2C → Enable +sudo reboot +``` + +--- + +## 3. Установка ПО + +### 3.1. Системные пакеты + +```bash +sudo apt-get install -y i2c-tools python3-pip python3-smbus python3-pil +``` + +### 3.2. Виртуальное окружение Python + +На Raspberry Pi OS Bookworm используется `external-managed-environment` — `pip install` напрямую не работает. Обязательно создаём venv: + +```bash +python3 -m venv ~/oled-env +source ~/oled-env/bin/activate +``` + +### 3.3. Установка luma.oled + +```bash +pip install luma.oled Pillow +``` + +Проверка установки: +```bash +python3 -c "from luma.core.interface.serial import i2c; from luma.oled.device import ssd1306; print('OK')" +``` + +--- + +## 4. Проверка дисплея + +После включения I2C и установки ПО — быстрый тест сканирует шину и зажигает пиксели: + +```bash +source ~/oled-env/bin/activate +python3 -c " +from luma.core.interface.serial import i2c +from luma.oled.device import ssd1306 +from PIL import Image, ImageDraw + +serial = i2c(port=1, address=0x3C) +device = ssd1306(serial) + +img = Image.new('1', device.size, 'black') +draw = ImageDraw.Draw(img) +draw.rectangle((0, 0, 127, 63), outline='white', fill='black') +draw.text((20, 25), 'Hello!', fill='white') + +device.display(img) +device.cleanup = lambda: None +" +``` + +Если дисплей показывает рамку и текст — всё работает. + +--- + +## 5. Статическое изображение + +Скрипт `/home/emb/oled_show.py` — выводит статический текст и завершается, оставляя изображение на дисплее. + +```python +#!/home/emb/oled-env/bin/python3 +import sys, os, signal +from luma.core.interface.serial import i2c +from luma.oled.device import ssd1306 +from PIL import Image, ImageDraw, ImageFont + +serial = i2c(port=1, address=0x3C) +device = ssd1306(serial) + +img = Image.new('1', device.size, 'black') +draw = ImageDraw.Draw(img) +font = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 11) + +draw.rectangle((0, 0, 127, 63), outline='white', fill='black') +draw.text((2, 2), 'RPi5 Ready!', font=font, fill='white') +draw.text((2, 16), 'I2C 128x64', font=font, fill='white') +draw.text((2, 30), '192.168.1.46', font=font, fill='white') +draw.text((2, 50), 'by Hermes', font=font, fill='white') + +device.display(img) +device.cleanup = lambda: None # <-- КЛЮЧЕВОЙ МОМЕНТ +print('OLED ready') +``` + +### Почему `device.cleanup = lambda: None`? + +Библиотека `luma.oled` в деструкторе объекта `device` вызывает команду 0xAE (DISPLAY OFF), что гасит дисплей при завершении скрипта. Замена `cleanup` на пустую функцию предотвращает это. + +Если вам нужно, чтобы скрипт работал постоянно (и дисплей не гас _никогда_), добавьте `signal.pause()` в конец — процесс останется висеть. + +--- + +## 6. CPU/Монитор в реальном времени + +Скрипт `/home/emb/oled_cpu_mon.py` — отображает загрузку CPU, RAM и температуру с обновлением раз в секунду. + +```python +import time, os +from luma.core.interface.serial import i2c +from luma.oled.device import ssd1306 +from PIL import Image, ImageDraw, ImageFont + +serial = i2c(port=1, address=0x3C) +device = ssd1306(serial) +font = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 10) +font_big = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 16) +device.cleanup = lambda: None + + +def get_cpu(): + with open('/proc/loadavg') as f: + parts = f.read().split() + load = float(parts[0]) + cores = os.cpu_count() or 4 + percent = (load / cores) * 100 + return min(percent, 100), load + + +def get_ram(): + with open('/proc/meminfo') as f: + lines = f.readlines() + mem_total = int([l for l in lines if 'MemTotal' in l][0].split()[1]) + mem_avail = int([l for l in lines if 'MemAvailable' in l][0].split()[1]) + used = mem_total - mem_avail + return used / mem_total * 100, used // 1024, mem_total // 1024 + + +def get_temp(): + try: + with open('/sys/class/thermal/thermal_zone0/temp') as f: + return int(f.read()) / 1000 + except: + return 0 + + +while True: + cpu_pct, load = get_cpu() + ram_pct, used_mb, total_mb = get_ram() + temp = get_temp() + + img = Image.new('1', device.size, 'black') + draw = ImageDraw.Draw(img) + + draw.rectangle((0, 0, 127, 63), outline='white', fill='black') + + # CPU bar + bar_w = 90 + bar_h = 12 + draw.rectangle((32, 4, 32 + bar_w, 4 + bar_h), outline='white', fill='black') + fill_w = int(bar_w * cpu_pct / 100) + if fill_w > 0: + draw.rectangle((32 + 1, 4 + 1, 32 + fill_w, 4 + bar_h - 1), fill='white') + draw.text((2, 5), 'CPU', font=font, fill='white') + draw.text((32 + bar_w + 4, 3), f'{cpu_pct:.0f}%', font=font_big, fill='white') + draw.text((2, 20), f'Load: {load:.2f}', font=font, fill='white') + + # RAM bar + draw.rectangle((32, 36, 32 + bar_w, 36 + bar_h), outline='white', fill='black') + fill_w2 = int(bar_w * ram_pct / 100) + if fill_w2 > 0: + draw.rectangle((32 + 1, 36 + 1, 32 + fill_w2, 36 + bar_h - 1), fill='white') + draw.text((2, 38), 'RAM', font=font, fill='white') + draw.text((32 + bar_w + 4, 36), f'{ram_pct:.0f}%', font=font_big, fill='white') + + # Temp and PID at bottom + draw.text((2, 52), f'Temp: {temp:.1f}C', font=font, fill='white') + draw.text((72, 52), f'PID: {os.getpid()}', font=font, fill='white') + + device.display(img) + time.sleep(1) +``` + +### Визуальная схема расположения на дисплее: + +``` +┌─────────────────────────────────────────┐ +│ CPU ████████████████░░░░ 45% │ +│ Load: 1.80 │ +│ RAM ██████████░░░░░░░░ 32% │ +│ Temp: 52.3C PID: 2535 │ +└─────────────────────────────────────────┘ +``` + +--- + +## 7. Systemd-сервис для автостарта + +Чтобы скрипт запускался автоматически при включении RPi5 и перезапускался при падении — используем systemd. + +### 7.1. Создание сервиса + +**Предварительно:** убедитесь, что модуль `i2c-dev` загружается при старте: + +```bash +echo i2c-dev | sudo tee /etc/modules-load.d/i2c-dev.conf +``` + +Создать сервис: + +```bash +sudo tee /etc/systemd/system/oled-display.service > /dev/null << 'EOF' +[Unit] +Description=OLED Display Service +After=multi-user.target + +[Service] +Type=simple +User=emb +ExecStart=/home/emb/oled-env/bin/python3 /home/emb/oled_cpu_mon.py +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target +EOF +``` + +| Параметр | Значение | Пояснение | +|----------|----------|-----------| +| `User=emb` | emb | Запуск от вашего пользователя | +| `ExecStart` | venv/bin/python3 + скрипт | Полный путь к интерпретатору | +| `Restart=always` | — | Перезапуск при любом завершении | +| `RestartSec=5` | 5 | Пауза между перезапусками | + +### 7.2. Активация и управление + +```bash +sudo systemctl daemon-reload # перечитать конфиги +sudo systemctl enable oled-display.service # автостарт при boot +sudo systemctl restart oled-display.service # запустить сейчас +``` + +### 7.3. Базовые команды + +```bash +# Статус +sudo systemctl status oled-display.service + +# Остановка +sudo systemctl stop oled-display.service + +# Запуск +sudo systemctl start oled-display.service + +# Отключение автостарта +sudo systemctl disable oled-display.service + +# Логи +sudo journalctl -u oled-display.service -n 20 +``` + +### 7.4. Смена скрипта + +Чтобы переключить сервис на другой скрипт (например, со статического на монитор): + +```bash +sudo sed -i 's|oled_show.py|oled_cpu_mon.py|' /etc/systemd/system/oled-display.service +sudo systemctl daemon-reload +sudo systemctl restart oled-display.service +``` + +### 7.5. Обновление скрипта без простоя + +Если сервис уже работает: + +```bash +sudo systemctl stop oled-display.service +# редактируем /home/emb/oled_cpu_mon.py +sudo systemctl restart oled-display.service +``` + +--- + +## 8. Решение проблем + +### 8.1. Дисплей пустой после запуска + +**Причина:** деструктор luma.oled отключает дисплей. + +**Решение:** `device.cleanup = lambda: None` перед выходом из скрипта. + +### 8.2. I2C device not found после перезагрузки (стартовая гонка) + +**Симптом:** сервис падает в логах с `I2C device not found: /dev/i2c-1`, хотя после ручного запуска всё работает. + +**Причина:** systemd запускает сервис до загрузки модуля `i2c-dev`. + +**Решение:** добавить ожидание устройства в скрипте: + +```python +import os, time +for attempt in range(30): + if os.path.exists('/dev/i2c-1'): + break + time.sleep(1) +else: + raise RuntimeError('I2C device /dev/i2c-1 not found after 30s') +``` + +Также можно загружать модуль принудительно: `echo i2c-dev | sudo tee /etc/modules-load.d/i2c-dev.conf` + +```bash +sudo /usr/sbin/i2cdetect -y 1 +``` + +Если адрес не отображается: +- Проверьте физическое подключение пинов SDA/SCL/3.3V/GND +- Включите I2C в `/boot/firmware/config.txt` и перезагрузите +- Проверьте `ls /dev/i2c*` — если шины нет, загрузите модуль: `sudo modprobe i2c-dev` + +### 8.3. pip install не работает + +Ошибка: `error: externally-managed-environment` + +**Причина:** Raspberry Pi OS Bookworm включает PEP 668. + +**Решение:** всегда используйте venv. + +```bash +python3 -m venv ~/oled-env +source ~/oled-env/bin/activate +pip install luma.oled Pillow +``` + +### 8.4. i2cdetect: command not found + +**Причина:** `/usr/sbin/` не в PATH обычного пользователя. + +**Решение:** `sudo /usr/sbin/i2cdetect -y 1` + +### 8.5. systemd-сервис падает + +```bash +sudo journalctl -u oled-display.service -n 20 +``` + +Типичные ошибки: +- `SyntaxError` — проверьте скрипт на синтаксис +- `ModuleNotFoundError` — проверьте путь в `ExecStart` (должен вести к python из venv) +- `PermissionError` — файл скрипта должен быть читаемым для пользователя + +### 8.6. Дисплей показывает мусор/рябь + +- Сделайте полную инициализацию контроллера (последовательность 0xAE ... 0xAF) +- Проверьте питание — 3.3V стабильно? +- Убедитесь что нет конфликта адресов (другие I2C устройства на той же шине) + +--- + +## 9. Приложение: полный код скриптов + +### 9.1. static_text.py — статический текст + +Полный код доступен в файле `/home/emb/oled_show.py` на целевой системе. + +### 9.2. cpu_mon.py — монитор CPU/RAM/Температура + +Полный код доступен в файле `/home/emb/oled_cpu_mon.py` на целевой системе. + +Создано: Hermes Agent · Raspberry Pi 5 · Debian Bookworm · SSD1306 128×64 I2C \ No newline at end of file