## Краткое описание Нужно разработать self-hosted web-приложение "Домовой" для инвентаризации домашней и малой серверной инфраструктуры. Главная идея: приложение не требует вручную заносить всю инфраструктуру с нуля. Оно умеет сканировать заданные локальные сетевые диапазоны, находить устройства, предлагать создать карточки устройств, затем после добавления доступа к устройству запускать read-only deep scan по SSH и предлагать создать карточки сервисов, контейнеров, портов, доменов, cron-задач и backup-подсказок. Приложение должно быть безопасным, локальным и read-only в части сканирования. Оно не должно ничего менять на удалённых хостах. ## Основной стек Backend: - PHP 8.3+ - Slim Framework 4 - PDO - MariaDB/MySQL Frontend: - Bootstrap 5.3 - Vanilla JavaScript (только для тривиальных действий) - htmx для частичных обновлений без перезагрузки страницы - server-side rendered templates htmx использовать для: - обновления таблицы scan jobs; - принятия/игнорирования найденного host; - запуска сканирования; - подгрузки деталей без полной перезагрузки; - создания устройства из discovered_host. Alpine.js — только если htmx не покрывает кейс. Никакого "чистый JS потом допишем" — весь JS должен быть заявлен заранее и обоснован. Deploy: - Docker Compose Composer-зависимости: - slim/slim:"4.*" - slim/psr7 - php-di/php-di - monolog/monolog - vlucas/phpdotenv - ramsey/uuid - symfony/process - phpseclib/phpseclib:"~3.0" - defuse/php-encryption composer require robmorgan/phinx Dev-зависимости: composer require --dev phpunit/phpunit Не использовать тяжёлый frontend framework. Не использовать ORM в первой версии. Не использовать произвольное выполнение shell-команд от пользователя. Миграции выполняются через Phinx: php vendor/bin/phinx migrate php vendor/bin/phinx rollback php vendor/bin/phinx create MigrationName Каждая миграция — отдельный класс с методами up() и down(). Файл phinx.php — точка входа для конфигурации Phinx. ## Архитектура выполнения задач HTTP-запросы НЕ должны выполнять сканирование напрямую. Правильная архитектура: 1. Web UI создаёт запись в scan_jobs (status=pending). 2. Отдельный CLI worker (bin/run-scan-worker.php) забирает pending-задачи и выполняет их. 3. Web UI показывает статус задачи через polling (htmx). Это означает: - PHP не висит внутри HTTP-запроса во время сканирования. - Время выполнения HTTP-запроса — миллисекунды. - Worker можно запускать вручную или через cron/supervisor. - Worker должен обрабатывать сигналы (SIGTERM) для graceful shutdown. ## Важные ограничения безопасности 1. Сканировать только явно добавленные пользователем диапазоны. 2. По умолчанию считать допустимыми только private/local сети: - 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. Секреты хранить только в зашифрованном виде. 9. Секреты не выводить в UI и логи. 10. Любой найденный объект сначала попадает в "Найденное", а не автоматически в основной инвентарь. 11. Все действия сканера логировать. 12. Запрещено делать универсальный shell executor, принимающий произвольную команду из UI. ## Главный пользовательский сценарий 1. Пользователь устанавливает приложение через Docker Compose. 2. Заходит в web UI. 3. Создаёт первый локальный аккаунт. 4. Добавляет сетевой диапазон, например 192.168.1.0/24. 5. Запускает скан сети. 6. Видит список найденных хостов. 7. Для нужных хостов нажимает "Создать устройство". 8. Открывает карточку устройства. 9. Добавляет SSH-доступ. 10. Нажимает "Тест подключения". 11. Если тест успешен, нажимает "Глубокий скан". 12. Приложение собирает read-only информацию с хоста. 13. Приложение показывает найденные порты, Docker-контейнеры, systemd-сервисы, cron-задачи, nginx-vhost-ы. 14. Пользователь подтверждает найденные сервисы. 15. Приложение создаёт карточки сервисов и связи между устройством, сервисами, портами, доменами и backup-подсказками. ## Архитектура каталогов Создать структуру: domovoy/ app/ Controllers/ Services/ Discovery/ HostScan/ Inventory/ Security/ Jobs/ Analysis/ Repositories/ Middleware/ 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 ## База данных Нужны миграции для таблиц: ### users - id - username - password_hash - created_at - updated_at ### network_ranges - id - name - cidr - enabled - created_at - updated_at ### scan_jobs - id - type - status - started_at - finished_at - error_message - created_by - created_at type: - network_discovery - host_deep_scan - docker_scan - nginx_scan - cron_scan status: - 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 status: - 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 type: - 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 type: - 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 status: - new - accepted - merged - ignored ### services - id - name - type - description - device_id - status - importance - url - main_port - created_at - updated_at type: - 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 relation_type: - 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 ### audit_log - id - user_id - action - entity_type - entity_id - details_json - created_at ## Web UI ### Общий layout Сделать Bootstrap layout: Левое меню: - Dashboard - Сканирование сети - Устройства - Сервисы - Документы - Настройки Верхняя панель: - название "Домовой" - текущий пользователь - logout ### Dashboard Показывать: - количество устройств - количество сервисов - количество новых находок - количество предупреждений - последний сетевой скан - последние найденные хосты - последние deep scan результаты ### Сканирование сети Страница должна позволять: 1. Добавить network range. 2. Включить/выключить range. 3. Запустить scan. 4. Посмотреть список scan jobs. 5. Посмотреть результаты discovered_hosts. Таблица discovered_hosts: - IP - Hostname - MAC - Vendor - Open ports - Confidence - Status - Actions Actions: - Создать устройство - Объединить с устройством - Игнорировать - Детали ### Устройства Страницы: - список устройств - создание устройства вручную - редактирование устройства - карточка устройства Карточка устройства должна показывать: - основные поля - IP/MAC/hostname/vendor - тип - важность - заметки - доступы - найденные сервисы - подтверждённые сервисы - история сканов - документы ### Доступы На карточке устройства добавить блок SSH-доступов. Для SSH-доступа: - name - username - port - auth_method password/private_key - password/private_key Кнопки: - Тест - Сохранить Тест должен проверять подключение через phpseclib. Секреты хранить в зашифрованном виде. ### Deep scan На карточке устройства добавить кнопку: "Глубокий скан" После запуска: - создать scan_job - выполнить read-only SSH команды - сохранить raw JSON в storage/scans/ - summary сохранить в host_scans.summary_json - создать detected_services ## Network discovery implementation Сделать сервисы: - NetworkScanner - PingScanner - TcpPortScanner - ArpTableReader - HostFingerprintService Первый MVP network scan: 1. Получить список IP из CIDR. 2. Для каждого IP: - ping check - TCP connect scan по базовым портам - reverse DNS lookup 3. После sweep прочитать ARP table: - ip neigh - arp -a как fallback 4. Обогатить найденные хосты MAC-адресами. 5. Если MAC известен — попытаться определить vendor через локальную OUI-базу или оставить пустым. 6. Сохранить discovered_hosts. Базовые TCP-порты: - 22 - 23 - 53 - 80 - 443 - 445 - 548 - 631 - 3306 - 5432 - 6379 - 8000 - 8080 - 8443 - 9000 - 9090 - 9100 Не делать агрессивное сканирование. Добавить timeout. Добавить limit параллельности. ## SSH deep scan implementation Сделать сервисы: - SshClientFactory - LinuxHostScanner - DockerScanner - NginxScanner - CronScanner - SystemdScanner - BackupHintScanner - CommandWhitelist - SecretMasker Все команды должны быть в CommandWhitelist. Запрещено принимать произвольную команду из UI. Формат команд в CommandWhitelist — массивы аргументов, НЕ строки: 'linux.docker_ps' => ['docker', 'ps', '--format', '{{json .}}'] 'linux.hostnamectl' => ['hostnamectl'] 'linux.ip_addr' => ['ip', '-j', 'addr'] ЗАПРЕЩЕНО использовать строковые шаблоны: // НЕПРАВИЛЬНО: 'linux.docker_ps' => "docker ps --format '{{json .}}'" Массивы аргументов безопаснее — не ломаются при передаче через Symfony Process, не конфликтуют с Twig-шаблонами, не требуют экранирования кавычек и фигурных скобок. Разрешённые read-only команды (формат: ключ => массив аргументов): 'linux.hostname' => ['hostname'] 'linux.hostnamectl' => ['hostnamectl'] 'linux.uname' => ['uname', '-a'] 'linux.os_release' => ['cat', '/etc/os-release'] 'linux.ip_addr' => ['ip', '-j', 'addr'] 'linux.ip_route' => ['ip', 'route'] 'linux.ss_tulpen' => ['ss', '-tulpen'] 'linux.df' => ['df', '-h'] 'linux.lsblk' => ['lsblk', '-J'] 'linux.mount' => ['mount'] 'linux.systemctl_units' => ['systemctl', 'list-units', '--type=service', '--all', '--no-pager'] 'linux.systemctl_timers' => ['systemctl', 'list-timers', '--all', '--no-pager'] 'linux.crontab_user' => ['crontab', '-l'] 'linux.crontab_system' => ['cat', '/etc/crontab'] 'linux.crontab_d' => ['ls', '-la', '/etc/cron.d', '/etc/cron.daily', '/etc/cron.hourly', '/etc/cron.weekly', '/etc/cron.monthly'] 'linux.docker_ps' => ['docker', 'ps', '--format', '{{json .}}'] 'linux.docker_networks' => ['docker', 'network', 'ls', '--format', '{{json .}}'] 'linux.docker_volumes' => ['docker', 'volume', 'ls', '--format', '{{json .}}'] 'linux.docker_compose_ls' => ['docker', 'compose', 'ls', '--format', 'json'] Каждая команда выполняется отдельно. Если отдельная команда возвращает ошибку или timeout — записать ошибку в raw JSON этого collector-а, продолжить остальные. ## 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. Если команда недоступна или возвращает ошибку, не падать всем сканом. Записать ошибку конкретного collector-а в raw JSON. ## DockerScanner Если docker доступен: 1. Получить список контейнеров. 2. Для каждого контейнера получить: - name - image - status - ports - mounts - networks - labels - restart policy - health status 3. Не показывать значения секретных env-переменных. 4. По контейнерам создать detected_services kind=docker_container. Секретные имена: - PASSWORD - PASS - SECRET - TOKEN - KEY - PRIVATE - CREDENTIAL ## NginxScanner Если nginx найден: 1. Проверить наличие: - /etc/nginx/nginx.conf - /etc/nginx/sites-enabled/ - /etc/nginx/conf.d/ 2. Прочитать только конфиги. 3. Не читать private key files. 4. Вытащить: - server_name - listen - proxy_pass - root - ssl_certificate 5. Создать detected_services kind=nginx_vhost. 6. Создать domain suggestions. ## CronScanner и BackupHintScanner CronScanner должен собрать: - crontab -l - /etc/crontab - файлы /etc/cron.d BackupHintScanner должен искать признаки: - rsync - borg - restic - rclone - tar - zip - mysqldump - mariadb-dump - pg_dump - sqlite - docker exec - scp - sftp Если найдено, создать detected_services kind=backup_hint. Формулировка в UI: "Похоже на backup job", а не "Это точно backup". ## Создание сервиса из detected_service На странице найденных сервисов добавить действие: "Создать сервис" При создании: 1. Создать services. 2. Создать service_endpoints, если есть порт/url. 3. Создать relation: - Device runs_on Service или - Service exposes Endpoint 4. detected_service.status = accepted ## Merge / deduplication Для discovered_hosts сделать простую логику: Если MAC совпадает с device.mac_address: - предложить merge с высокой уверенностью. Если hostname совпадает: - предложить merge со средней уверенностью. Если IP совпадает: - предложить merge с низкой уверенностью, потому что IP может меняться. В MVP достаточно показывать suggestions в UI. Автоматически не объединять. ## Audit log Логировать: - login - logout - network scan started - host scan started - device created from discovered host - credential created - credential tested - service created from detected service - object ignored - object merged ## Docker Compose Сделать docker-compose.yml: services: - app - db app: - PHP 8.3 - composer install - volume для проекта - volume storage - depends_on db db: - mariadb - volume db_data - env из .env Добавить .env.example: - APP_ENV - APP_SECRET - DB_HOST - DB_PORT - DB_DATABASE - DB_USERNAME - DB_PASSWORD - ENCRYPTION_KEY ## README README должен содержать: 1. Что такое Домовой. 2. Предупреждение: сканировать только свои сети. 3. Установка. 4. Запуск docker-compose. 5. Создание первого пользователя. 6. Добавление network range. 7. Первый scan. 8. Создание устройства из найденного хоста. 9. Добавление SSH-доступа. 10. Запуск deep scan. 11. Где смотреть логи. 12. Как запустить миграции. ## CI/CD и smoke tests Создать файл scripts/check.sh — локальный чек-скрипт, который запускается после каждой итерации. Минимальный набор проверок: #!/usr/bin/env bash set -euo pipefail echo "=== composer validate ===" composer validate --no-check-publish 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 ===" 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 ===" Для следующих итераций добавлять свои проверки: - network scan: "docker compose exec app php bin/console scan:test" - device CRUD: curl к страницам devices - credentials: тест SSH mock-connection Головное правило: итерация не считается завершённой, пока check.sh не прошёл успешно без ошибок. ## Технические требования 1. Код должен быть простым и читаемым. 2. Не делать преждевременно сложную архитектуру. 3. Контроллеры не должны содержать бизнес-логику. 4. SQL изолировать в Repository-классах. 5. Сканеры изолировать в Services. 6. Все shell-команды запускать только через CommandWhitelist. 7. Все ошибки scanner collector-ов сохранять, но не ронять весь scan. 8. UI должен быть рабочим, пусть и простым. 9. Bootstrap использовать без сборки frontend. 10. Vanilla JS только для небольших интерактивных действий. 11. Никаких React/Vue. 12. Никаких внешних SaaS-зависимостей. ## Порядок реализации ### Итерация 1 Сделать каркас: - Slim app - Docker Compose - DB connection - migrations runner - auth - dashboard - layout Результат должен запускаться. ### Итерация 2 Сделать: - network_ranges CRUD - scan_jobs table - discovered_hosts table - NetworkScanner с ping/tcp scan - страницу результатов ### Итерация 3 Сделать: - devices CRUD - создание device из discovered_host - ignore discovered_host - карточку устройства ### Итерация 4 Сделать: - credentials CRUD для SSH - шифрование секрета - тест SSH-подключения через phpseclib ### Итерация 5 Сделать: - HostScan по SSH - LinuxHostScanner - raw JSON сохранение - summary на карточке устройства ### Итерация 6 Сделать: - DockerScanner - detected_services из контейнеров - страницу найденных сервисов ### Итерация 7 Сделать: - создание service из detected_service - service_endpoints - relations ### Итерация 8 Сделать: - NginxScanner - CronScanner - BackupHintScanner ### Итерация 9 Сделать: - RiskAnalyzer - dashboard warnings - scan history - basic diff ## Формат ответа после каждой итерации После работы нужно выдать: 1. Что сделано. 2. Какие файлы созданы/изменены. 3. Как запустить. 4. Какие команды выполнить. 5. Что проверить вручную. 6. Известные ограничения. 7. Что делать следующей итерацией. Не переходить к следующей итерации без успешного запуска текущей. Обязательно запустить scripts/check.sh и показать вывод. Без успешного smoke test итерация не считается завершённой. ## Правила для owl-alpha / LLM-кодера Запрещено: - переписывать уже готовое без причины; - делать большие коммиты — только маленькие атомарные; - выдумывать несуществующие зависимости; - делать произвольный shell executor; - использовать строковые шаблоны для docker-команд; - писать "чистый JS" без обоснования — весь JS заранее заявлен. Требуется: - маленькие коммиты; - список изменённых файлов после каждой итерации; - инструкции запуска; - self-check через scripts/check.sh; - показать реальный вывод smoke test, а не утверждать "готово". ## Итерация 1 Сделай только Итерацию 1 проекта "Домовой". Нужно: - создать каркас Slim 4 приложения; - настроить Docker Compose с app + MariaDB; - сделать .env.example; - сделать простой migrations runner; - сделать таблицу users; - сделать регистрацию первого пользователя через CLI или временную страницу setup; - сделать login/logout; - сделать Dashboard; - сделать Bootstrap layout с меню; - написать README с запуском. Не реализовывать network scan, devices, credentials и deep scan в этой итерации. После выполнения выдать список файлов, команды запуска и что проверить вручную.