diff --git a/SPEC.md b/SPEC.md new file mode 100644 index 0000000..c1ca0d2 --- /dev/null +++ b/SPEC.md @@ -0,0 +1,943 @@ +# docker-git — Техническое задание (ТЗ) + +> Документ описывает что делает система, как она работает и почему. +> Служит основой для переписывания на Rust или любом другом языке. + +--- + +## 1. Что такое docker-git + +**docker-git** — CLI-инструмент и HTTP API для создания изолированных Docker-контейнеров разработчика под каждый GitHub-репозиторий (или issue/PR). + +**Проблема, которую решает:** +- Вместо того чтобы вручную клонировать репозиторий, настраивать окружение, прокидывать credentials и порты — одна команда создаёт готовое рабочее окружение +- Несколько задач (issue-123, pr-456) в одном репозитории работают **изолированно**, без конфликтов портов +- Credentials (GitHub токены, Codex, Claude API ключи) хранятся **один раз** и переиспользуются всеми контейнерами +- Package cache (pnpm/npm/yarn) **общий** для всех контейнеров — не скачивать зависимости заново + +--- + +## 2. Архитектура системы + +``` +┌──────────────────────────────────────────────────────┐ +│ Пользователь │ +│ CLI (docker-git clone ) │ +│ TUI (интерактивное меню) │ +│ HTTP API (REST) │ +└────────────────────┬─────────────────────────────────┘ + │ +┌────────────────────▼─────────────────────────────────┐ +│ Бизнес-логика (CORE) │ +│ • Парсинг команд • Генерация шаблонов │ +│ • Управление проектами • Auth management │ +│ • State repo sync • Scrap export/import │ +└────────────────────┬─────────────────────────────────┘ + │ +┌────────────────────▼─────────────────────────────────┐ +│ Внешние системы (SHELL) │ +│ • Docker CLI • Git / GitHub API │ +│ • Файловая система • SSH │ +│ • Tmux • ActivityPub │ +└──────────────────────────────────────────────────────┘ +``` + +--- + +## 3. Файловая структура на хосте + +``` +~/.docker-git/ ← корень всех проектов +├── authorized_keys ← публичный SSH ключ хоста +├── .orch/ +│ ├── env/ +│ │ └── global.env ← общие credentials (GH_TOKEN, CLAUDE_KEY и т.д.) +│ └── auth/ +│ ├── gh/ ← gh CLI кэш (shared) +│ └── codex/ ← Codex auth (shared, если CODEX_SHARE_AUTH=1) +├── .cache/ +│ ├── git-mirrors/ ← зеркала git репозиториев +│ └── packages/ +│ ├── pnpm/ ← общий pnpm store +│ ├── npm/ ← общий npm cache +│ └── yarn/ ← общий yarn cache +│ +└── / + └── / + ├── docker-compose.yml + ├── Dockerfile + ├── entrypoint.sh + ├── docker-git.json ← метаданные проекта + └── .orch/ + ├── env/ + │ └── project.env ← per-project overrides + └── auth/ + └── codex/ ← per-project Codex auth (если не shared) +``` + +**Workspace (issue/PR):** +Если проект привязан к issue или PR, создаётся подпапка: +``` +~/.docker-git///issue-123/ +~/.docker-git///pr-456/ +``` + +--- + +## 4. Типы данных + +### 4.1 ProjectConfig (хранится в docker-git.json) + +``` +ProjectConfig { + repoUrl: string // https://github.com/owner/repo + repoRef: string // issue-123 | pr-456 | main + containerName: string // dg-owner-repo-issue-123 + sshPort: u16 // 2222 + networkMode: NetworkMode // shared | project + createdAt: timestamp +} +``` + +### 4.2 TemplateConfig (входные данные для генерации шаблонов) + +``` +TemplateConfig { + // Имена + containerName: string // dg-owner-repo + serviceName: string // owner-repo + volumeName: string // dg-owner-repo-vol + networkName: string // dg-owner-repo-net | docker-git-shared + + // Пути на хосте + projectDir: string // ~/.docker-git/owner/repo + authorizedKeys: string // ~/.docker-git/authorized_keys + globalEnvFile: string // ~/.docker-git/.orch/env/global.env + projectEnvFile: string // ~/.docker-git/owner/repo/.orch/env/project.env + codexAuthDir: string // ~/.docker-git/.orch/auth/codex | per-project path + pnpmCacheDir: string // ~/.docker-git/.cache/packages/pnpm + npmCacheDir: string // ~/.docker-git/.cache/packages/npm + yarnCacheDir: string // ~/.docker-git/.cache/packages/yarn + + // Параметры + sshPort: u16 + networkMode: NetworkMode + pnpmVersion: string // "10.27.0" + repoUrl: string + repoRef: string + agentLabel: string // "codex" | "claude" | "none" + + // Опции + enablePlaywright: bool + shareCodexAuth: bool +} +``` + +### 4.3 ProjectItem (runtime-состояние) + +``` +ProjectItem { + projectDir: string + displayName: string // "owner/repo issue-123" + repoUrl: string + repoRef: string + containerName: string + isRunning: bool + sshPort: u16 +} +``` + +### 4.4 Command (дискриминированный union) + +``` +Command = + | Create(CreateParams) + | Menu + | Attach(AttachParams) + | Status + | DownAll + | StateInit(StateParams) + | StatePull | StatePush | StateSync | StateStatus | StateCommit | StatePath + | AuthGithubLogin(AuthParams) | AuthGithubStatus | AuthGithubLogout + | AuthCodexLogin(AuthParams) | AuthCodexStatus | AuthCodexLogout + | AuthClaudeLogin(AuthParams) | AuthClaudeStatus | AuthClaudeLogout + | ScrapExport(ScrapParams) | ScrapImport(ScrapParams) + | McpPlaywrightUp(McpParams) + | SessionsList | SessionsKill | SessionsLogs + | Apply +``` + +--- + +## 5. Команды CLI + +### 5.1 `clone` / `create` + +Создаёт новый проект из URL репозитория. + +**Вход:** +``` +docker-git clone [options] + --port SSH порт (по умолчанию: автоматически свободный от 2222) + --name имя контейнера (по умолчанию: выводится из URL) + --network-mode shared | project (по умолчанию: shared) + --force пересоздать если существует (удалить volumes) + --force-env перезаписать только .env файлы + --run-up / --no-run-up запускать ли контейнер сразу (по умолчанию: да) + --open-ssh открыть SSH сессию после запуска + --agent codex | claude | none + --enable-mcp-playwright добавить Playwright sidecar +``` + +**Алгоритм:** + +``` +1. Распарсить URL: + - https://github.com/owner/repo → owner=owner, repo=repo, ref=main + - https://github.com/owner/repo/issues/N → ref=issue-N + - https://github.com/owner/repo/pull/N → ref=pr-N + +2. Определить имена (детерминировано из URL): + slug = "owner-repo" или "owner-repo-issue-N" + containerName = "dg-" + slug + serviceName = slug + volumeName = "dg-" + slug + "-vol" + projectDir = ~/.docker-git/owner/repo[/issue-N] + +3. Найти свободный SSH порт: + - Начать с 2222 (или переданного --port) + - Проверять nc / ss пока порт не будет свободен + +4. Проверить существование: + - Если projectDir существует и нет --force → ошибка "уже существует" + - Если --force → удалить docker compose volumes, оставить .orch/ + +5. Создать файлы: + - Записать Dockerfile (из шаблона) + - Записать docker-compose.yml (из шаблона) + - Записать entrypoint.sh (из шаблона, chmod +x) + - Создать .orch/env/project.env (пустой если не существует) + - Записать docker-git.json (метаданные) + +6. Если --run-up (по умолчанию true): + - docker compose up -d --build + - Повторить до 3 раз при DNS/Hub ошибках + +7. Если --open-ssh: + - Подождать пока SSH порт откроется (polling 1s, timeout 60s) + - Запустить SSH сессию + +8. Если настроен state repo: + - git add + commit + push метаданных +``` + +--- + +### 5.2 `menu` (по умолчанию без аргументов) + +Интерактивное TUI для управления проектами. + +**Алгоритм:** +``` +1. Проверить stdin.is_tty() + - Нет → вывести список проектов в stdout и выйти + - Да → запустить TUI + +2. TUI состояние: + view: Menu | Create | Select | AuthMenu + projects: Vec + selected: Option + busy: bool + +3. Загрузка при старте: + - Сканировать ~/.docker-git рекурсивно на docker-git.json + - Запустить docker ps → список running контейнеров + - Сопоставить проекты с running статусом + +4. Вид Menu: + - Список проектов (стрелки вверх/вниз для выбора) + - [Enter] → Select view для выбранного проекта + - [n] → Create view + - [a] → AuthMenu view + - [q] → выход + +5. Вид Select (для выбранного проекта): + - [u] → docker compose up + - [d] → docker compose down + - [s] → SSH attach + - [l] → показать logs + - [x] → удалить проект + - [Esc] → назад в Menu + +6. Вид Create (форма): + - Поле: repoUrl (обязательно) + - Поле: sshPort (default: auto) + - Поле: agentMode (codex | claude | none) + - [Enter] → запустить create команду + - [Esc] → отмена + +7. Вид AuthMenu: + - Управление GitHub / Codex / Claude токенами +``` + +--- + +### 5.3 `attach` + +Подключиться SSH к запущенному контейнеру. + +``` +docker-git attach [] + - Если project-dir не указан → показать меню выбора + - Читает docker-git.json → sshPort, containerName + - Проверяет что контейнер запущен (docker ps) + - Запускает: ssh -i ~/.ssh/id_rsa -p dev@127.0.0.1 + - Наследует stdin/stdout/stderr (интерактивный TTY) +``` + +--- + +### 5.4 `status` + +Вывести все проекты с их статусом. + +``` +Выход (таблица): + ПРОЕКТ СТАТУС ПОРТ URL + owner/repo running 2222 https://github.com/owner/repo + owner/repo issue-123 stopped 2223 https://github.com/owner/repo/issues/123 +``` + +--- + +### 5.5 `down-all` + +Остановить все запущенные контейнеры docker-git. + +``` +- Найти все projectDir с docker-git.json +- Для каждого: docker compose down +- Параллельно (но вывод упорядоченный) +``` + +--- + +### 5.6 Auth команды + +#### `auth github login` +``` +Опции: + --token Сохранить токен напрямую + --label Метка для токена (default: "default") + --web Открыть browser OAuth flow + +Хранение: + ~/.docker-git/.orch/env/global.env + Формат строки: GIT_AUTH_