**Self-hosted система инвентаризации домашней/малой инфраструктуры с автосканированием сети, глубоким сканированием хостов и созданием карточек устройств/сервисов из найденного.** Ключевая цепочка: ``` скан сети→ найденные хосты→ подтверждение человеком→ карточки устройств→ добавление доступов→ глубокий скан хоста→ найденные сервисы/контейнеры/домены/бэкапы→ подтверждение человеком→ живая карта инфраструктуры ``` И важная поправка под Hermes/owl-alpha: **не давать кодеру “построй весь Домовой” одним заходом**. Лучше давать стадии по 1–2 экрана/модуля за раз, иначе он может нагенерировать архитектурной каши. --- # 1. Базовый стек Я бы оставил стек максимально простой и твой: ``` Backend: PHP 8.3+ Framework: Slim Framework 4 DB: MariaDB / MySQL Frontend: Bootstrap 5.3 + htmx + Alpine.js при необходимости Templates: PHP templates или Twig Auth: локальная авторизация Deploy: Docker Compose Scanning: PHP services + системные read-only команды SSH: phpseclib Encryption: defuse/php-encryption или libsodium ``` Slim 4 ставится через Composer как `slim/slim:"4.*"`, а Bootstrap сейчас живёт в ветке 5.3, у которой официальная документация ведёт current major release v5.x и последнюю ветку 5.3.x. Для SSH из PHP нормально подходит phpseclib 3.x, он ставится через Composer как `phpseclib/phpseclib:~3.0`. Для шифрования секретов можно взять `defuse/php-encryption`, это PHP-библиотека для шифрования данных ключом/паролем. --- # 2. Composer-зависимости Минимальный набор: ``` composer require slim/slim:"4.*" composer require slim/psr7 composer require php-di/php-di composer require monolog/monolog composer require vlucas/phpdotenv composer require ramsey/uuid composer require symfony/process composer require phpseclib/phpseclib:"~3.0" composer require defuse/php-encryption ``` Миграции и тесты: ``` composer require robmorgan/phinx composer require --dev phpunit/phpunit ``` Опционально: ``` composer require twig/twig composer require nesbot/carbon ``` Я бы **не тащил ORM** на первом этапе. Обычный PDO + Repository-классы. Так проще, прозрачнее и меньше магии. --- # 3. Системные зависимости на хосте Для контейнера/хоста, где крутится Домовой: ``` php-cli php-fpm php-mysql php-curl php-mbstring php-xml php-zip mariadb-client iproute2 iputils-ping arp-sca nnmap net-tools dnsutils avahi-utils smbclient / nbtscan опционально snmp опционально openssh-client openssl docker-cli опционально ``` Но важно: **nmap и arp-scan — необязательные backends**, а не обязательная основа. Первый MVP может жить на: ``` ping sweep ARP table TCP connect scan reverse DNS ``` А потом уже добавить nmap/arp-scan как улучшение. --- # 4. Границы безопасности Это надо заложить сразу, иначе проект может превратиться в мутный сетевой сканер. ## Жёсткие правила MVP ``` 1. Сканируются только явно добавленные пользователем диапазоны. 2. По умолчанию разрешены только private/local ranges: - 10.0.0.0/8 - 172.16.0.0/12 - 192.168.0.0/16 - fd00::/8 - link-local 3. Никаких brute force. 4. Никаких exploit/probe. 5. Никаких попыток подобрать пароли. 6. Deep scan только после явного добавления доступа. 7. Все команды deep scan — read-only. 8. Секреты в UI никогда не показываются открытым текстом. 9. Любой найденный объект сначала попадает в "Найденное", а не сразу в основной инвентарь. 10. Всё, что делает сканер, логируется. ``` Идеология: ``` Домовой не чинит и не ломает. Домовой смотрит, запоминает и предлагает. ``` ## Архитектура выполнения сканирования HTTP-запрос НЕ должен выполнять сканирование напрямую. Сканирование сети и deep scan выполняются отдельным CLI worker-ом: 1. Web UI создаёт scan_job со статусом "pending". 2. CLI worker (bin/run-scan-worker.php) забирает pending-задачи из базы и выполняет их. 3. Web UI показывает статус задачи через htmx polling. Worker должен: - обрабатывать SIGTERM для graceful shutdown; - запускать только одну задачу за раз (для MVP); - обновлять статус scan_jobs в процессе; - логировать прогресс. Аргументы запуска worker-а: ``` php bin/run-scan-worker.php # забрать одну pending-задачу и выполнить php bin/run-scan-worker.php --loop # бесконечный цикл обработки ``` Стратегии сканирования сети (по приоритету): Приоритет 1 — если доступны arp-scan или nmap: - arp-scan для быстрого обнаружения MAC + IP - nmap для диапазонного скана с портами Приоритит 2 — системные команды, работающие без дополнительных PAK-тов: - ip neigh / arp table для ARP-записей - ping sweep с жёстким timeout (200ms) - TCP connect scan с timeout=200ms, concurrency=50 Приоритет 3 — fallback: - только ping sweep, если ничего другого не доступно Порядок имеет значение — быстрые методы сначала, медленные как fallback. Для /24 сети с 18 портами TCP-скан может занять минуты. Поэтому CLI worker обязателен, а PHP-скан внутри HTTP-запроса запрещён. --- # 5. Архитектура проекта ``` domovoy/ app/ Controllers/ AuthController.php DashboardController.php DiscoveryController.php DeviceController.php CredentialController.php HostScanController.php ServiceController.php DocumentController.php Services/ Discovery/ NetworkScanner.php PingScanner.php TcpPortScanner.php ArpTableReader.php MdnsScanner.php HostFingerprintService.php HostScan/ SshClientFactory.php LinuxHostScanner.php DockerScanner.php NginxScanner.php CronScanner.php SystemdScanner.php BackupHintScanner.php Inventory/ DeviceService.php ServiceInventoryService.php MergeSuggestionService.php RelationService.php Security/ CredentialVault.php CommandWhitelist.php SecretMasker.php Jobs/ ScanJobRunner.php JobQueue.php Analysis/ RiskAnalyzer.php DiffAnalyzer.php Repositories/ UserRepository.php ScanJobRepository.php DiscoveredHostRepository.php DeviceRepository.php CredentialRepository.php HostScanRepository.php ServiceRepository.php RelationRepository.php AuditLogRepository.php Middleware/ AuthMiddleware.php CsrfMiddleware.php public/ index.php assets/ css/ js/ templates/ layout.php auth/ dashboard/ discovery/ devices/ credentials/ services/ documents/ migrations/ storage/ logs/ scans/ reports/ bin/ console run-scan-worker.php docker/ docker-compose.yml composer.json .env.example README.md ``` --- # 6. Главные сущности БД ## `users` ``` id username password_hash created_at updated_at ``` ## `network_ranges` ``` id name cidr enabled created_at updated_at ``` Пример: ``` Home LAN — 192.168.1.0/24 WireGuard LAN — 10.12.1.0/24 ``` ## `scan_jobs` ``` id type status started_at finished_at error_message created_by created_at ``` Типы: ``` network_discovery host_deep_scan docker_scan nginx_scan cron_scan ``` Статусы: ``` pending running done failed cancelled ``` ## `discovered_hosts` Это всё, что нашёл сканер, но пользователь ещё не подтвердил. ``` id scan_job_id ip_address mac_address hostname vendor detected_os open_ports_json protocols_json fingerprint_json confidence status matched_device_id first_seen last_seen created_at updated_at ``` Статусы: ``` new accepted merged ignored ignored_always ``` ## `devices` Это уже подтверждённые железки/виртуалки. ``` id name type description primary_ip mac_address hostname vendor os_name os_version location importance status created_at updated_at ``` Типы: ``` server router nas desktop laptop phone printer iot vm container_host unknown ``` ## `credentials` Доступы к устройствам. ``` id device_id type name username port auth_method encrypted_secret encrypted_private_key public_key_fingerprint last_test_status last_test_at created_at updated_at ``` Типы: ``` ssh snmp http_api routeros manual ``` Для MVP реально нужен только `ssh`. ## `host_scans` Результаты глубокого скана устройства. ``` id device_id credential_id scan_job_id status raw_json_path summary_json started_at finished_at error_message created_at ``` ## `detected_services` То, что найдено на хосте, но ещё не подтверждено как сервис. ``` id host_scan_id device_id kind name source port protocol raw_json suggested_service_id status created_at updated_at ``` `kind`: ``` open_port systemd_unit docker_container nginx_vhost cron_job backup_hint database_hint ``` ## `services` Подтверждённые сервисы. ``` id name type description device_id status importance url main_port created_at updated_at ``` Типы: ``` web_app database reverse_proxy backup monitoring storage git media system unknown ``` ## `service_endpoints` ``` id service_id protocol host port url is_public created_at updated_at ``` ## `domains` ``` id domain target_service_id target_device_id source notes created_at updated_at ``` ## `relations` Универсальный граф связей. ``` id from_type from_id to_type to_id relation_type created_at ``` Типы связей: ``` runs_on depends_on proxied_by uses_database uses_volume backed_up_by exposes resolves_to ``` ## `documents` ``` id entity_type entity_id title body_markdown created_at updated_at ``` Для заметок/runbook-ов. ## `audit_log` ``` id user_id action entity_type entity_id details_json created_at ``` --- # 7. Логика сканирования сети ## Что делает network discovery На входе: ``` CIDR: 192.168.1.0/24 ``` На выходе: ``` список DiscoveredHost ``` Методы первого MVP: ``` 1. Ping sweep 2. TCP connect scan по базовым портам 3. чтение локальной ARP-таблицы 4. reverse DNS lookup 5. определение vendor по MAC OUI, если MAC известен ``` Базовые порты: ``` 22 SSH 23 Telnet 53 DNS 80 HTTP 443 HTTPS 445 SMB 548 AFP 631 CUPS 3306 MySQL 5432 PostgreSQL 6379 Redis 8000 HTTP-alt 8080 HTTP-alt 8443 HTTPS-alt 9000 Portainer/various 9090 Prometheus/various 9100 Printer/Node exporter ``` Позже добавить: ``` mDNS NetBIOS SNMP nmap backend router DHCP leases importer ``` --- # 8. Логика deep scan по SSH После того как пользователь создал устройство и добавил SSH-доступ, можно нажать: ``` [Тест подключения] [Глубокий скан] ``` ## Команды Linux scan Только read-only: ``` hostname hostnamectl uname -a cat /etc/os-release ip -j addr ip route ss -tulpen df -h lsblk -J mount systemctl list-units --type=service --all --no-pager systemctl list-timers --all --no-pager crontab -l cat /etc/crontab ls -la /etc/cron.d /etc/cron.daily /etc/cron.hourly /etc/cron.weekly /etc/cron.monthly docker ps --format '{{json .}}' docker network ls --format '{{json .}}' docker volume ls --format '{{json .}}' docker compose ls --format json ``` Команды должны идти через **whitelist**, а не через произвольный shell. То есть не так: ``` $ssh->exec($userCommand); ``` А так: ``` $scanner->runAllowedCommand('linux.hostnamectl'); ``` И уже внутри: ``` 'linux.hostnamectl' => ['hostnamectl'] ``` Формат команд в CommandWhitelist — массивы аргументов, НЕ строки. Это важно: docker-команды содержат {{json .}}, что ломает и Symfony Process, и Twig-шаблонизатор. Массив аргументов не требует экранирования. ЗАПРЕЩЕНО: ``` 'linux.docker_ps' => "docker ps --format '{{json .}}'" ``` ПРАВИЛЬНО: ``` 'linux.docker_ps' => ['docker', 'ps', '--format', '{{json .}}'] ``` ## SSH таймауты Все SSH-операции должны иметь жёсткие таймауты. Конфигурируются через .env: ``` SSH_CONNECT_TIMEOUT_SECONDS=5 SSH_AUTH_TIMEOUT_SECONDS=10 SSH_COMMAND_TIMEOUT_SECONDS=8 SSH_TOTAL_SCAN_TIMEOUT_SECONDS=60 SSH_RETRY_COUNT=0 ``` Для MVP retry отключён — лучше быстро и честно упасть, чем зависнуть. Каждый collector внутри deep scan обрабатывает timeout отдельно — ошибка одного collector-а не роняет весь scan. --- # 9. Сервисные сканеры ## DockerScanner Собирает: ``` containers images ports mounts volumes networks labels restart policy health status compose project ``` Из контейнеров создаёт `detected_services`. Пример: ``` container: nextcloud-app image: nextcloud:apache ports: 8080:80 mounts: /srv/nextcloud/data:/var/www/html/data suggested service type: web_app ``` ## NginxScanner Читает: ``` /etc/nginx/nginx.conf /etc/nginx/sites-enabled/* /etc/nginx/conf.d/* ``` Вынимает: ``` server_name listen proxy_pass root ssl_certificate ssl_certificate_key path, но ключ не читать access_log error_log ``` Важно: приватные ключи сертификатов не читать. ## CronScanner Читает: ``` user crontab /etc/crontab /etc/cron.d/* ``` И создаёт кандидатов: ``` cron_job backup_hint maintenance_task unknown_task ``` ## BackupHintScanner Ищет в командах/скриптах признаки: ``` rsync borg restic rclone tar zip mysqldump mariadb-dump pg_dump sqlite dump docker exec ... dump scp sftp ``` Но вывод должен быть осторожным: ``` Похоже на backup job ``` а не: ``` Это точно полноценный бэкап ``` --- # 10. Экранная структура ## Dashboard ``` Домовой Устройства: 12 Сервисы: 18 Новые находки: 7 Требуют внимания: 4 Последний скан сети: сегодня 14:22 ``` Блоки: ``` - Новые найденные устройства - Сервисы без подтверждённого бэкапа - Хосты без свежего скана - Истекающие сертификаты - Последние изменения ``` ## Discovery ``` Сканирование сети [Добавить диапазон] [Запустить скан] Таблица: IP | Hostname | MAC | Vendor | Ports | Статус | Действия ``` Действия: ``` [Создать устройство] [Объединить] [Игнорировать] [Детали] ``` ## Devices Карточка устройства: ``` Название Тип IP MAC Hostname Vendor OS Роль Важность Заметки Доступы Найденные сервисы Подтверждённые сервисы Документы История ``` ## Credentials Для SSH: ``` Название доступа Host Port Username Auth method: - password - private key Password/private key [Тест] [Сохранить] ``` ## Host Scan На карточке устройства: ``` [Глубокий скан] Результаты: - Система - Открытые порты - Docker - Systemd - Cron - Nginx - Возможные сервисы - Возможные бэкапы ``` ## Найденные сервисы ``` Название | Тип | Источник | Устройство | Порт | Уверенность | Действия ``` Действия: ``` [Создать сервис] [Объединить] [Игнорировать] [Детали] ``` --- # 11. CI/CD и smoke tests Создать файл scripts/check.sh: ```bash #!/usr/bin/env bash set -euo pipefail echo "=== composer validate ===" composer validate --no-check-publish echo "=== composer install ===" composer install --no-interaction --prefer-dist echo "=== PHP syntax check ===" find app public bin -name "*.php" -print0 | xargs -0 -n1 php -l echo "=== docker compose config ===" docker compose config > /dev/null echo "=== docker compose up ===" docker compose up -d --build echo "=== phinx migrate (test DB) ===" docker compose exec app php vendor/bin/phinx migrate echo "=== HTTP health check ===" curl -sI http://localhost:8080/login | head -1 echo "=== ALL CHECKS PASSED ===" ``` Головное правило: итерация не считается завершённой, пока check.sh не прошёл успешно без ошибок. Для следующих итераций добавлять свои проверки: - network scan: "docker compose exec app php bin/console scan:test" - device CRUD: curl к /devices, /devices/create - credentials: тест SSH mock-connection Не переходить к следующей итерации без успешного запуска check.sh. Обязательно показывать реальный вывод, а не утверждать "готово". --- # 12. План реализации ## Этап 0. Каркас проекта Цель: приложение открывается, есть логин, база, миграции, layout. Результат: ``` - docker-compose поднимает app + db - есть /login - есть /dashboard - есть миграции - есть базовый UI ``` ## Этап 1. Инвентарь устройств вручную Цель: можно создавать карточки устройств руками. Результат: ``` - CRUD устройств - типы устройств - заметки - список устройств - карточка устройства ``` ## Этап 2. Сканирование сети Цель: можно добавить диапазон и найти хосты. Результат: ``` - CRUD network_ranges - запуск network scan - таблица discovered_hosts - ping/tcp scan - ARP table enrichment - reverse DNS ``` ## Этап 3. Превращение найденного в устройство Цель: найденный хост можно принять в инвентарь. Результат: ``` - кнопка "Создать устройство" - кнопка "Игнорировать" - простое объединение с существующим устройством - discovered_host получает статус ``` ## Этап 4. SSH-доступы Цель: к устройству можно добавить SSH-доступ и проверить его. Результат: ``` - CRUD credentials - шифрование секрета - тест подключения - сохранение результата теста ``` ## Этап 5. Deep scan Linux-хоста Цель: по SSH собрать базовую информацию. Результат: ``` - hostname/os/ip/ports/disk/systemd/timers/cron - сохранение raw scan JSON - отображение summary ``` ## Этап 6. Docker scan Цель: найти контейнеры и предложить сервисы. Результат: ``` - docker ps - docker inspect для контейнеров - volumes/networks/ports - detected_services kind=docker_container ``` ## Этап 7. Nginx scan Цель: найти домены и proxy_pass. Результат: ``` - чтение nginx-конфигов - парсинг server_name/listen/proxy_pass - detected_services kind=nginx_vhost - domains/proxy suggestions ``` ## Этап 8. Создание сервисов из найденного Цель: пользователь подтверждает найденный сервис. Результат: ``` - service proposals - create service from detected_service - relation Device → Service - endpoint creation ``` ## Этап 9. Cron/backup hints Цель: находить задачи, похожие на бэкапы. Результат: ``` - cron parser - systemd timer parser - backup keyword detection - detected_services kind=backup_hint ``` ## Этап 10. Риски Цель: простые предупреждения. Результат: ``` - сервис без backup_hint - публичный web endpoint без документации - SSH открыт - нет свежего deep scan - TLS скоро истекает, если cert найден ``` ## Этап 11. История и diff Цель: видеть, что изменилось между сканами. Результат: ``` - история scan jobs - сравнение open ports - новые/пропавшие хосты - новые/пропавшие контейнеры ``` --- # 13. Как давать задание Hermes/owl-alpha Я бы не давал ему “весь проект” целиком. Лучше так: ``` 1. Сначала дать общее ТЗ и попросить создать каркас. 2. Потом отдельной задачей миграции. 3. Потом отдельной задачей CRUD устройств. 4. Потом отдельной задачей network scan. 5. Потом отдельной задачей SSH credentials. ``` Для owl-alpha особенно важно: ``` - запрещать переписывать уже готовое без причины; - требовать маленькие коммиты; - требовать список изменённых файлов; - требовать инструкции запуска; - требовать self-check; - не разрешать выдумывать несуществующие зависимости; - не разрешать делать произвольный shell executor; - не разрешать писать "чистый JS" без обоснования; - запрещать строковые шаблоны для docker-команд; - требовать запуск scripts/check.sh после каждой итерации; - требовать показать реальный вывод check.sh, а не утверждать "готово". ``` Головное правило: итерация не считается завершённой, пока check.sh не прошёл успешно. Без smoke test — нет завершения. ---