1006 lines
26 KiB
Markdown
1006 lines
26 KiB
Markdown
|
||
## Краткое описание
|
||
|
||
Нужно разработать 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 в этой итерации.
|
||
После выполнения выдать список файлов, команды запуска и что проверить вручную. |