diff --git a/docs/containers/container-200.md b/docs/containers/container-200.md index a4f3584..21f0074 100644 --- a/docs/containers/container-200.md +++ b/docs/containers/container-200.md @@ -14,8 +14,8 @@ - **Доступ:** SSH под пользователем **admin** (не root): `ssh admin@192.168.1.200` с хоста 192.168.1.150 или из LAN. Для выполнения команд с правами root: `sudo ...`. **Диски:** -- **Корневой диск** (sda1): 35 GB, занято **~29 GB (87%)** — система, образы/кэш в пределах корня. **Критично:** мало свободного места; при росте логов или обновлениях возможны сбои. Следить за местом и логированием (см. TODO). -- **Данные** (sdb1): 344 GB, смонтирован в **/mnt/data**, занято ~177 GB (55%). Здесь: библиотека Immich, БД PostgreSQL, Docker root, containerd, Ollama, данные deduper. +- **Корневой диск** (sda1): 50 GB — система, образы/кэш в пределах корня. Логи Docker ограничены (см. ниже). +- **Данные** (sdb1): 350 GB, смонтирован в **/mnt/data**. Здесь: библиотека Immich, БД PostgreSQL, Docker root, containerd, Ollama, данные deduper. --- @@ -184,10 +184,10 @@ sudo resize2fs /dev/sdb1 ## Логи и ротация - **Базовая политика (как в LXC):** на ВМ настроен logrotate `/etc/logrotate.d/homelab-lxc.conf` — 14 дней, 50 MB, 5 архивов, сжатие (системные логи в `/var/log`). На ВМ 200 пакет `logrotate` был установлен вручную (в образе по умолчанию не было); после установки активен таймер `logrotate.timer`. Подробнее: [Logrotate — базовая политика homelab](../maintenance/logrotate/README.md). -- **Docker:** данные Docker (образы, контейнеры, overlay) хранятся в **/mnt/data/docker** (Docker Root Dir). Логи контейнеров — драйвер **json-file** без ограничения размера и количества файлов (Config:{} у immich_server и immich_postgres). При активной работе логи могут разрастаться и занимать место на **корневом** разделе (если логи пишутся на корень) или в overlay на /mnt/data — уточнить расположение логов контейнеров (часто в /mnt/data/docker/containers). В любом случае ограничение логов не задано (см. TODO). +- **Docker:** данные Docker (образы, контейнеры, overlay) хранятся в **/mnt/data/docker** (Docker Root Dir). Логи контейнеров — драйвер **json-file** с ограничениями в `/etc/docker/daemon.json`: `max-size: "10m"`, `max-file: "3"` (до 30 MB на контейнер). Логи пишутся в `/mnt/data/docker/containers`. - **Системный logrotate:** стандартные правила (apt, dpkg, cloud-init, unattended-upgrades, wtmp) плюс homelab-lxc.conf. Отдельных правил для Immich или Docker нет. -**Риск:** корневой диск заполнен на 87%. Рост логов, обновления и кэш могут привести к нехватке места. Необходимо ограничить логи Docker и следить за местом на корне (см. TODO). +Корневой диск расширен до 50 GB; логи Docker ограничены. --- @@ -208,18 +208,17 @@ sudo resize2fs /dev/sdb1 ## Уязвимости и риски 1. **Секреты в .env:** В `/opt/immich/.env` и `/opt/immich-deduper/.env` хранятся пароли БД, API-ключи (IMMICH_API_KEY, GEMINI_API_KEY), креды для deduper (PSQL_*). Файлы не должны попадать в публичный репозиторий. Ограничить права (chmod 600), хранить бэкапы в защищённом месте. -2. **Корневой диск 87%:** Критично мало свободного места. При 100% возможны сбои обновлений и работы сервисов. Срочно: освободить место и/или перенести часть данных на /mnt/data, ограничить логи Docker (см. TODO). -3. **Логи Docker без лимитов:** Ротация не настроена — возможен рост логов и заполнение диска. -4. **Доступ по портам:** Сервисы доступны по 2283, 2284, 8001, 8501, 8086 из LAN. Снаружи доступ только через NPM (https://immich.katykhin.ru → 2283). Не пробрасывать порты напрямую в интернет. -5. **GPU и PCI-passthrough:** ВМ использует hostpci0 (VGA). Убедиться, что драйверы NVIDIA и доступ к GPU корректны для immich_machine_learning. +2. **Корневой диск:** Расширен до 50 GB; логи Docker ограничены (10m × 3 файла на контейнер). Следить за местом при обновлениях. +3. **Доступ по портам:** Сервисы доступны по 2283, 2284, 8001, 8501, 8086 из LAN. Снаружи доступ только через NPM (https://immich.katykhin.ru → 2283). Не пробрасывать порты напрямую в интернет. +4. **GPU и PCI-passthrough:** ВМ использует hostpci0 (VGA). Убедиться, что драйверы NVIDIA и доступ к GPU корректны для immich_machine_learning. --- ## TODO по ВМ 200 - [x] **Базовая политика logrotate:** для системных логов настроена (homelab-lxc.conf — 14 дней, 50 MB, 5 архивов, как в LXC). См. [Logrotate — базовая политика homelab](../maintenance/logrotate/README.md). -- [ ] **Корневой диск:** Снизить использование корня (87%). Варианты: перенести логи Docker на /mnt/data (если сейчас пишутся на корень), очистить старые образы/кэш (`docker system prune` с осторожностью), увеличить размер корневого диска ВМ в Proxmox. Настроить мониторинг и оповещение при заполнении >90%. -- [ ] **Логи Docker:** Включить ограничение размера логов для всех контейнеров Immich и deduper: в `docker-compose.yml` добавить для каждого сервиса `logging: driver: json-file options: max-size: "100m" max-file: "3"` или задать default в `/etc/docker/daemon.json`. Убедиться, что Docker Root Dir остаётся на /mnt/data и логи не пишутся на корень. После изменений перезапустить контейнеры. +- [x] **Корневой диск:** Расширен до 50 GB (было 35 GB). Логи Docker ограничены. +- [x] **Логи Docker:** В `/etc/docker/daemon.json` заданы `log-driver: json-file`, `max-size: "10m"`, `max-file: "3"`. Логи в /mnt/data/docker/containers. - [ ] **Права на конфиги:** Ограничить доступ к .env (chmod 600), не коммитить в публичные репозитории. - [ ] **Резервное копирование:** Регулярный бэкап критичных данных (оценка размеров на момент документации): - **`/mnt/data/library`** — библиотека Immich (фото, видео, превью). ~148 GB. Основной объём; бэкап обязателен (внешний диск, сетевое хранилище). diff --git a/docs/containers/host-proxmox.md b/docs/containers/host-proxmox.md index 94d3a25..9bbb1fb 100644 --- a/docs/containers/host-proxmox.md +++ b/docs/containers/host-proxmox.md @@ -45,6 +45,17 @@ | backup-restic-yandex.sh | 04:00 | restic → Yandex (без photos) | | backup-restic-yandex-photos.sh | 04:10 | restic → Yandex (только photos) | +### Дашборд мониторинга + +| Компонент | Назначение | +|-----------|------------| +| homelab-dashboard.service | Дашборд homelab (хост, контейнеры, сервисы) на порту 19998 | +| /root/scripts/dashboard/ | Скрипты: dashboard-exporter.py, dashboard-server.py, index.html | +| deploy-dashboard.sh | Деплой дашборда на хост | +| add-to-homepage.sh | Добавить ссылку в Homepage (CT 103) | + +**URL:** http://192.168.1.150:19998 + ### Мониторинг и уведомления | Скрипт | Назначение | diff --git a/docs/monitoring/dashboard-plan.md b/docs/monitoring/dashboard-plan.md new file mode 100644 index 0000000..1f7f516 --- /dev/null +++ b/docs/monitoring/dashboard-plan.md @@ -0,0 +1,146 @@ +# План реализации дашборда мониторинга homelab + +Дашборд для Netdata (http://192.168.1.150:19999) с блоками: хост, контейнеры, критические сервисы. + +--- + +## Текущее состояние (по результатам проверки на сервере) + +### Netdata +- **Версия:** v2.9.0 +- **Режим:** локальный, Cloud отключён +- **API:** http://localhost:19999/api/v1/ — доступен + +### Доступные метрики + +| Блок | Метрика | Chart / источник | Статус | +|------|---------|------------------|--------| +| **Хост** | CPU total | `system.cpu` (user, system, nice, iowait, …) | ✅ | +| | RAM total | `system.ram` | ✅ | +| | Disk usage | `disk_space./`, `disk_space./mnt/backup`, `disk_space./mnt/nextcloud-hdd`, `disk_space./tank`, … (в API: URL-encode слэши) | ✅ | +| | iowait | `system.cpu` dimension `iowait` | ✅ | +| | load | `system.load` (load1, load5, load15) | ✅ | +| **Контейнеры** | CPU % | `cgroup_.cpu_limit` (used) | ✅ | +| | RAM % | `cgroup_.mem_utilization` | ✅ | +| | Disk % | скрипт `pct exec ID -- df -P /` | ✅ кастомный экспортер | +| | OOM count | `/sys/fs/cgroup/lxc/ID/memory.events` (oom_kill) | ✅ кастомный экспортер | +| **Сервисы** | Immich, Nextcloud, nginx, VPN | ссылки на charts Netdata | ✅ без response time/connections | + +### Контейнеры в cgroups (по данным Netdata) +- `nginx` (CT 100) +- `nextcloud` (CT 101) +- `gitea` (CT 103) +- `paperless` (CT 104) +- `rag-service` (CT 105) +- `misc` (CT 107, Invidious) +- `galene` (CT 108) +- `local-vpn` (CT 109) +- `qemu_immich` (VM 200) + +--- + +## Решения (по ответам пользователя) + +1. **Disk % по контейнерам** — в приоритете. I/O не нужен. Реализация: скрипт на хосте, `pct exec ID -- df -P /` для каждого LXC, VM 200 — отдельно (`qm guest exec` или аналог). +2. **OOM** — `cgroup memory.events` (oom_kill) по каждому контейнеру. Путь: `/sys/fs/cgroup/lxc/ID/memory.events` (LXC), для VM — cgroup QEMU. +3. **Response time / open connections** — отложено, не требуется. +4. **Размещение** — на хосте (192.168.1.150). +5. **Netdata Cloud** — не рассматривается. + +--- + +## Варианты реализации дашборда + +### Вариант A: Netdata Cloud — не используется (Cloud отключён) + +### Вариант B: Кастомная HTML-страница (выбран) +- Страница на хосте (192.168.1.150), которая: + - запрашивает Netdata API (`/api/v1/data?chart=...`) + - рендерит блоки: хост, таблица контейнеров, сервисы +- Плюсы: полный контроль, работает без Cloud +- Минусы: нужна разработка и хостинг страницы + +### Вариант C: Доработка стандартного дашборда Netdata +- `dashboard_info.js` — изменение порядка/группировки charts +- Плюсы: используем встроенный UI +- Минусы: ограниченная кастомизация, в v2 подход мог измениться + +--- + +## Рекомендуемый план (поэтапно) + +### Этап 1: Дашборд на базе Netdata API (Вариант B) +Создать кастомную HTML-страницу с тремя блоками. + +**Блок 1 — Хост** +- CPU total: `system.cpu` (сумма user+system или 100-idle) +- RAM total: `system.ram` (used, cached, free) +- Disk usage: `disk_space./`, `disk_space./mnt/backup`, `disk_space./mnt/nextcloud-hdd`, `disk_space./tank` (avail/used %) +- iowait: `system.cpu` dimension iowait +- load: `system.load` (load15) + +**Блок 2 — Контейнеры (таблица)** +- Колонки: имя, CPU %, RAM %, Disk %, OOM count +- CPU/RAM: `cgroup_.cpu_limit`, `cgroup_.mem_utilization` (Netdata API) +- Disk %: кастомный API (скрипт `pct exec ID -- df -P /` + парсинг) +- OOM: кастомный API (LXC: `/sys/fs/cgroup/lxc/ID/memory.events`, VM 200: `/sys/fs/cgroup/qemu.slice/200.scope/memory.events` → oom_kill) + +**Блок 3 — Критические сервисы** +- Immich, Nextcloud, nginx, VPN — ссылки на charts Netdata (cgroup_*, app.nginx_*) +- Response time / open connections — не требуются + +### Этап 2: Кастомный экспортер (скрипт + HTTP API) +Скрипт на хосте, запускаемый по таймеру или по запросу: +- **Disk %:** для каждого LXC (100–109) — `pct exec ID -- df -P /`; для VM 200 — `qm guest exec` или fallback (lvs/zfs) +- **OOM:** чтение `oom_kill` из `/sys/fs/cgroup/lxc/ID/memory.events` (LXC), `/sys/fs/cgroup/qemu.slice/200.scope/memory.events` (VM 200) +- Отдача JSON на HTTP (например, порт 19998 или через nginx на хосте) + +### Этап 3: Размещение и интеграция +- Дашборд: статика на хосте (nginx или python -m http.server), запросы к Netdata API (localhost:19999) и кастомному API +- Добавить ссылку в Homepage (services.yaml) + +--- + +## VM 200 (Immich) — RAM после увеличения + +При увеличении RAM с 6 до 10 GB «потребление» может визуально «упасть» по нескольким причинам: + +1. **Процент vs абсолютное значение** — 32% от 10 GB ≈ 3.2 GB. Та же нагрузка при 6 GB давала бы ~53%. Дашборд показывает RAM % (cgroup mem_utilization). +2. **Сброс кэша** — при нехватке памяти гость держит кэш; после добавления RAM ядро может освободить кэш, и «used» уменьшается. +3. **Balloon** — virtio-balloon мог забирать память при 6 GB; после увеличения лимита balloon отдаёт память гостю, но реальное использование приложений может остаться ~3 GB. + +**Проверка:** `qm guest exec 200 -- free -h` (требует qemu-guest-agent в гостевой ОС) — смотреть `Mem: used` внутри гостя. + +--- + +## Реализовано (2026-02-28) + +- **URL дашборда:** http://192.168.1.150:19998 +- **Ссылка в Homepage:** добавлена (Сервисы → Homelab Dashboard) +- **Скрипты:** `scripts/dashboard/` (exporter, server, index.html, deploy, add-to-homepage) +- **Systemd:** `homelab-dashboard.service` (порт 19998) + +--- + +## Маппинг CT/VM → cgroup name (Netdata) + +| ID | Назначение | cgroup name | +|----|------------|-------------| +| 100 | nginx | cgroup_nginx | +| 101 | nextcloud | cgroup_nextcloud | +| 103 | gitea | cgroup_gitea | +| 104 | paperless | cgroup_paperless | +| 105 | rag-service | cgroup_rag-service | +| 107 | misc (Invidious) | cgroup_misc | +| 108 | galene | cgroup_galene | +| 109 | local-vpn | cgroup_local-vpn | +| 200 | immich (VM) | cgroup_qemu_immich | + +--- + +## Связанные документы + +- [netdata-proxmox-setup.md](netdata-proxmox-setup.md) — установка и алерты +- [smartd-setup.md](smartd-setup.md) — SMART дисков +- [container-100](../containers/container-100.md) — NPM, log-dashboard +- [architecture](../architecture/architecture.md) — обзор контейнеров diff --git a/docs/monitoring/netdata-proxmox-setup.md b/docs/monitoring/netdata-proxmox-setup.md index 41779ed..9992207 100644 --- a/docs/monitoring/netdata-proxmox-setup.md +++ b/docs/monitoring/netdata-proxmox-setup.md @@ -196,7 +196,16 @@ Netdata на хосте видит общие метрики (CPU, RAM, диск --- +## Дашборд homelab + +Кастомный дашборд с метриками хоста, контейнеров и сервисов: **http://192.168.1.150:19998** + +Ссылка добавлена в Homepage (Сервисы → Homelab Dashboard). Деплой: `scripts/dashboard/deploy-dashboard.sh`. Подробнее: [dashboard-plan.md](dashboard-plan.md). + +--- + ## Связанные документы +- [dashboard-plan.md](dashboard-plan.md) — план и реализация кастомного дашборда - [smartd-setup.md](smartd-setup.md) — SMART и диски - [backup-howto](../backup/backup-howto.md) — бэкапы diff --git a/scripts/dashboard/add-to-homepage.sh b/scripts/dashboard/add-to-homepage.sh new file mode 100644 index 0000000..05d8e15 --- /dev/null +++ b/scripts/dashboard/add-to-homepage.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Добавить Homelab Dashboard в Homepage (services.yaml на CT 103) +# Запуск: с хоста Proxmox — pct exec 103 -- bash -s < /root/scripts/dashboard/add-to-homepage.sh + +set -e + +SERVICES_YAML="${SERVICES_YAML:-/opt/docker/homepage/config/services.yaml}" + +if [ ! -f "$SERVICES_YAML" ]; then + echo "ERROR: $SERVICES_YAML not found" + exit 1 +fi + +if grep -q "Homelab Dashboard" "$SERVICES_YAML" 2>/dev/null; then + echo "Homelab Dashboard already in services.yaml" + exit 0 +fi + +# Вставить после блока Netdata (ping: http://192.168.1.150:19999) +sed -i '/ping: http:\/\/192.168.1.150:19999$/a\ + - Homelab Dashboard:\ + icon: mdi-chart-box\ + href: http://192.168.1.150:19998\ + description: Мониторинг хоста, контейнеров, сервисов\ + target: _blank +' "$SERVICES_YAML" + +echo "Added Homelab Dashboard to services.yaml" diff --git a/scripts/dashboard/dashboard-exporter.py b/scripts/dashboard/dashboard-exporter.py new file mode 100644 index 0000000..2ad1ce2 --- /dev/null +++ b/scripts/dashboard/dashboard-exporter.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +""" +Экспортер метрик для дашборда homelab: disk % и OOM по контейнерам/VM. +Запуск: python3 dashboard-exporter.py (выводит JSON в stdout) +""" + +import json +import subprocess +import sys +from pathlib import Path + +# Маппинг: (vmid, type) -> (name, cgroup_name для Netdata) +CONTAINERS = [ + (100, "lxc", "nginx", "cgroup_nginx"), + (101, "lxc", "nextcloud", "cgroup_nextcloud"), + (103, "lxc", "gitea", "cgroup_gitea"), + (104, "lxc", "paperless", "cgroup_paperless"), + (105, "lxc", "rag-service", "cgroup_rag-service"), + (107, "lxc", "misc", "cgroup_misc"), + (108, "lxc", "galene", "cgroup_galene"), + (109, "lxc", "local-vpn", "cgroup_local-vpn"), + (200, "qemu", "immich", "cgroup_qemu_immich"), +] + +LXC_CGROUP = Path("/sys/fs/cgroup/lxc") +QEMU_CGROUP_200 = Path("/sys/fs/cgroup/qemu.slice/200.scope") + + +def get_disk_pct_lxc(vmid: int) -> float | None: + """Disk % для LXC через pct exec df.""" + try: + r = subprocess.run( + ["pct", "exec", str(vmid), "--", "df", "-P", "/"], + capture_output=True, + text=True, + timeout=10, + ) + if r.returncode != 0: + return None + lines = r.stdout.strip().split("\n") + if len(lines) < 2: + return None + # Формат: Filesystem 1K-blocks Used Available Use% Mounted + parts = lines[-1].split() + if len(parts) >= 5: + use_pct = parts[4].rstrip("%") + return float(use_pct) + except (subprocess.TimeoutExpired, ValueError): + pass + return None + + +def get_disk_pct_vm200() -> float | None: + """Disk % для VM 200 через lvs (fallback, т.к. qm guest exec часто недоступен).""" + try: + r = subprocess.run( + ["lvs", "-o", "data_percent", "--noheadings", "pve/vm-200-disk-0"], + capture_output=True, + text=True, + timeout=5, + ) + if r.returncode != 0: + return None + val = r.stdout.strip() + if val: + return float(val) + except (subprocess.TimeoutExpired, ValueError): + pass + return None + + +def get_oom_count(vmid: int, vmtype: str) -> int | None: + """OOM count из cgroup memory.events.""" + if vmtype == "lxc": + path = LXC_CGROUP / str(vmid) / "memory.events" + elif vmtype == "qemu" and vmid == 200: + path = QEMU_CGROUP_200 / "memory.events" + else: + return None + if not path.exists(): + return None + try: + text = path.read_text() + for line in text.splitlines(): + if line.startswith("oom_kill "): + return int(line.split()[1]) + except (OSError, ValueError): + pass + return None + + +def main() -> None: + result = {"containers": [], "ok": True} + for vmid, vmtype, name, cgroup_name in CONTAINERS: + disk_pct = None + if vmtype == "lxc": + disk_pct = get_disk_pct_lxc(vmid) + elif vmtype == "qemu" and vmid == 200: + disk_pct = get_disk_pct_vm200() + oom = get_oom_count(vmid, vmtype) + result["containers"].append({ + "vmid": vmid, + "name": name, + "cgroup_name": cgroup_name, + "disk_pct": disk_pct, + "oom_count": oom, + }) + print(json.dumps(result, ensure_ascii=False)) + + +if __name__ == "__main__": + main() diff --git a/scripts/dashboard/dashboard-server.py b/scripts/dashboard/dashboard-server.py new file mode 100644 index 0000000..570ee80 --- /dev/null +++ b/scripts/dashboard/dashboard-server.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +""" +HTTP-сервер дашборда homelab: статика, /api/containers, прокси к Netdata. +Порт: 19998 (по умолчанию). +""" + +import json +import os +import subprocess +import sys +import urllib.request +from http.server import HTTPServer, BaseHTTPRequestHandler +from pathlib import Path + +PORT = int(os.environ.get("DASHBOARD_PORT", "19998")) +NETDATA_URL = os.environ.get("NETDATA_URL", "http://127.0.0.1:19999") +SCRIPT_DIR = Path(__file__).resolve().parent +EXPORTER = SCRIPT_DIR / "dashboard-exporter.py" + + +class DashboardHandler(BaseHTTPRequestHandler): + def log_message(self, format, *args): + pass # подавить вывод в консоль + + def send_json(self, data: dict, status: int = 200): + self.send_response(status) + self.send_header("Content-Type", "application/json; charset=utf-8") + self.send_header("Access-Control-Allow-Origin", "*") + self.end_headers() + self.wfile.write(json.dumps(data, ensure_ascii=False).encode("utf-8")) + + def send_html(self, html: bytes, status: int = 200): + self.send_response(status) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Cache-Control", "no-cache") + self.end_headers() + self.wfile.write(html) + + def do_GET(self): + path = self.path.split("?")[0].rstrip("/") or "/" + if path == "/": + self.serve_index() + elif path == "/api/containers": + self.serve_containers() + elif path.startswith("/api/netdata"): + self.proxy_netdata() + else: + self.send_error(404) + + def serve_index(self): + html_file = SCRIPT_DIR / "index.html" + if html_file.exists(): + self.send_html(html_file.read_bytes()) + else: + self.send_error(404, "index.html not found") + + def serve_containers(self): + try: + r = subprocess.run( + [sys.executable, str(EXPORTER)], + capture_output=True, + text=True, + timeout=30, + cwd=str(SCRIPT_DIR), + ) + if r.returncode != 0: + self.send_json({"ok": False, "error": r.stderr or "exporter failed"}, 500) + return + data = json.loads(r.stdout) + self.send_json(data) + except subprocess.TimeoutExpired: + self.send_json({"ok": False, "error": "timeout"}, 504) + except json.JSONDecodeError as e: + self.send_json({"ok": False, "error": str(e)}, 500) + except Exception as e: + self.send_json({"ok": False, "error": str(e)}, 500) + + def proxy_netdata(self): + qs = self.path.split("?", 1)[1] if "?" in self.path else "" + url = f"{NETDATA_URL}/api/v1/data?{qs}" if qs else f"{NETDATA_URL}/api/v1/data" + try: + req = urllib.request.Request(url) + with urllib.request.urlopen(req, timeout=10) as resp: + data = resp.read() + self.send_response(200) + self.send_header("Content-Type", resp.headers.get("Content-Type", "application/json")) + self.send_header("Access-Control-Allow-Origin", "*") + self.end_headers() + self.wfile.write(data) + except Exception as e: + self.send_json({"error": str(e)}, 502) + + +def main(): + server = HTTPServer(("0.0.0.0", PORT), DashboardHandler) + print(f"Dashboard server on http://0.0.0.0:{PORT}", file=sys.stderr) + try: + server.serve_forever() + except KeyboardInterrupt: + pass + + +if __name__ == "__main__": + main() diff --git a/scripts/dashboard/deploy-dashboard.sh b/scripts/dashboard/deploy-dashboard.sh new file mode 100644 index 0000000..d71c889 --- /dev/null +++ b/scripts/dashboard/deploy-dashboard.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# Деплой дашборда homelab на хост Proxmox +# Запуск: с хоста Proxmox или ssh root@192.168.1.150 'bash -s' < scripts/dashboard/deploy-dashboard.sh +# Или из репозитория: ./scripts/dashboard/deploy-dashboard.sh (копирует из текущей директории) + +set -e + +# REPO_ROOT: корень репозитория (содержит scripts/dashboard/) +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="${REPO_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}" +DASHBOARD_SRC="${REPO_ROOT}/scripts/dashboard" +DEST="/root/scripts/dashboard" +SYSTEMD_DEST="/etc/systemd/system" + +log() { echo "[$(date -Iseconds)] $*"; } + +log "Deploying homelab dashboard..." + +mkdir -p "$DEST" +if [ "$(realpath "$DASHBOARD_SRC")" != "$(realpath "$DEST")" ]; then + cp -v "${DASHBOARD_SRC}/dashboard-exporter.py" "$DEST/" + cp -v "${DASHBOARD_SRC}/dashboard-server.py" "$DEST/" + cp -v "${DASHBOARD_SRC}/index.html" "$DEST/" +fi +chmod +x "${DEST}/dashboard-exporter.py" "${DEST}/dashboard-server.py" + +if [ -f "${REPO_ROOT}/scripts/systemd/homelab-dashboard.service" ]; then + cp -v "${REPO_ROOT}/scripts/systemd/homelab-dashboard.service" "$SYSTEMD_DEST/" +fi +systemctl daemon-reload +systemctl enable homelab-dashboard.service +systemctl restart homelab-dashboard.service + +log "Dashboard deployed. URL: http://192.168.1.150:19998" +log "Status: $(systemctl is-active homelab-dashboard.service)" diff --git a/scripts/dashboard/index.html b/scripts/dashboard/index.html new file mode 100644 index 0000000..1fd00e8 --- /dev/null +++ b/scripts/dashboard/index.html @@ -0,0 +1,210 @@ + + + + + + Homelab Dashboard + + + +

Homelab Dashboard

+ +
+

Блок 1 — Хост

+
+
CPU %
+
RAM
+
Load
+
iowait %
+
Disk /
+
Disk backup
+
Disk nextcloud-hdd
+
Disk tank
+
+
+ +
+

Блок 2 — Контейнеры

+ + + + + +
КонтейнерCPU %RAM %Disk %OOM
+
+ +
+

Блок 3 — Критические сервисы

+ +
+ +
+
+ + + + diff --git a/scripts/systemd/homelab-dashboard.service b/scripts/systemd/homelab-dashboard.service new file mode 100644 index 0000000..7f0f044 --- /dev/null +++ b/scripts/systemd/homelab-dashboard.service @@ -0,0 +1,19 @@ +# Дашборд мониторинга homelab (хост, контейнеры, сервисы) +# Порт 19998, статика + API + прокси к Netdata + +[Unit] +Description=Homelab Dashboard (monitoring) +After=network-online.target netdata.service +Wants=network-online.target + +[Service] +Type=simple +ExecStart=/usr/bin/python3 /root/scripts/dashboard/dashboard-server.py +WorkingDirectory=/root/scripts/dashboard +Restart=on-failure +RestartSec=5 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target