Правила работы в репозитории: команды, архитектура, миграции, конфигурация, фронтенд, OpenSpec и конвенции коммитов. Отдельный раздел про прогоны с эмулятором: instrumented-тесты и запуск AVD могут зависнуть, прогресс нужно проверять примерно раз в минуту, а не ждать молча.
9.3 KiB
AGENTS.md
Инструкции для агентов, работающих в этом репозитории. Читай вместе с README.md
(актуальный источник правды) и project.md (исходное ТЗ).
Что это
Self-hosted фотоальбом: Go-бэкенд с вшитой через go:embed статикой SvelteKit,
один статический бинарник (CGO_ENABLED=0), без Docker. Плюс отдельный Android-клиент.
По умолчанию (DB_BACKEND=sqlite, STORAGE_BACKEND=fs) работает полностью локально,
без Postgres и S3. Postgres/Garage (pg.localnet, s3.localnet) — опциональные слои.
Команды
Все команды — из корня репозитория. Голый make только печатает список целей.
make build # frontend -> backend/static -> go:embed -> bin/galo
make frontend # npm run build (инкрементально), копирует статику в backend/static
make backend # CGO_ENABLED=0 go build -tags nodynamic -o ../bin/galo
make run # frontend + backend + запуск ./bin/galo
make migrate # применяет только Postgres-миграции (cmd/migrate)
make test # cd backend && go test ./... (только бэкенд!)
make testdata # наполняет запущенный инстанс через scripts/seed.py
make android # cd android && ./gradlew :app:assembleDebug
make install-android-app # сборка + установка debug-APK на устройство
-tags nodynamicобязателен: без негоgen2brain/webpтянет динамический libwebp и бинарник перестаёт быть статическим.- Порядок сборки важен:
frontendдолжен отработать доbackend, иначеgo:embed all:staticвшивает толькоbackend/static/.gitkeepи бинарник выходит без UI. make testне запускает фронтенд-тесты; отдельных make-целей для них нет:cd frontend && npm run test(vitest, окружениеnode, файлыsrc/**/*.test.ts) иcd frontend && npm run check(svelte-check/typecheck).- Один Go-тест:
cd backend && go test ./internal/photos/ -run TestName -v. - Интеграционные тесты Postgres-репозиториев пропускаются без
TEST_DATABASE_URL:TEST_DATABASE_URL='postgres://user:pass@pg.localnet:5432/galo?sslmode=disable' \ cd backend && go test ./internal/repo/ -v - Тесты видео (
internal/e2e) сами пропускаются, если нетffmpeg/ffprobe. Тесты e2e репозиториев поднимают SQLite вt.TempDir()и внешних сервисов не требуют.
Тесты на эмуляторе
Instrumented-прогоны (:app:connectedDebugAndroidTest) и сам запуск AVD могут зависнуть —
не дожидайся их молча. Пока идёт прогон, проверяй прогресс примерно раз в минуту: жив ли
процесс, обновляются ли результаты, отвечает ли устройство:
pgrep -fa "GradleWrapperMain|Gradle Test Executor"
ls -la android/app/build/outputs/androidTest-results/connected/debug/
adb devices
Если прогресса нет дольше пары минут — снимай прогон (kill), а не жди дальше, и разбирайся
по дампу потоков (jstack <pid> JVM тестов). То же относится к обычным unit-тестам: они тоже
идут в отдельной JVM, и её зависание видно только по тому, что результаты перестали
обновляться.
Архитектура
- Go-модуль
galo(backend/go.mod, Go 1.26). Точки входа:backend/cmd/server,cmd/migrate,cmd/reconcile,cmd/backfill-exif. - Доменные пакеты в
backend/internal/<domain>(albums, auth, members, photos, social, notifications, sharelinks, storage, sse, settings, video). Каждый содержит сервис, HTTP-обработчики иRepository-интерфейс. internal/store/store.go— единственное место, которое знает конкретные реализации. При добавлении метода вRepositoryнужно реализовать его в обоих вариантах: Postgres (NewStore/NewPostgres) и SQLite (NewSQLiteStore/NewSQLite).backend/internal/app— маршруты/статика;internal/repo/contract— контрактные тесты, которые гоняют одинаковый набор проверок для SQLite и Postgres.backend/embed.goвшиваетstatic(фронтенд) иmigrations/**. Паттерны эмбеда —migrations/*.sql migrations/sqlite/*.sql: новые файлы должны им соответствовать.- Права проверяются на уровне сервиса, не только в UI. Блобы наружу отдаются только
через бэкенд:
GET /api/objects/<key>с HMAC-подписью, один маршрут для авторизованных и для share-ссылок. Ключи объектов непрозрачны:objects/{object_id}/{role}. - SSE (
/api/sse/...) авторизуется через?token=, т.к.EventSourceне шлёт заголовки. Для nginx критиченproxy_buffering off. android/— самостоятельный Gradle-модуль, серверный и веб-код не трогает; использует уже существующий HTTP API. Тесты:cd android && ./gradlew test.
Миграции (частый источник ошибок)
Схема поддерживается в двух диалектах с независимой нумерацией:
- Postgres:
backend/migrations/NNNNNN_*.{up,down}.sql - SQLite:
backend/migrations/sqlite/NNNNNN_*.{up,down}.sql
Любое изменение схемы нужно добавить в оба каталога, номера версий не обязаны совпадать
(сейчас они разошлись). Миграции вшиты в бинарник и применяются автоматически при старте
сервера для выбранного бэкенда. make migrate применяет только Postgres.
cmd/reconcile существует для сверки хранилища с журналом objects (сборка мусора
журналом управляется и хранилище не сканирует).
Конфигурация и данные
.envчитается вручную вcmd/*/main.goиз.env, затем../.env(без dotenv-библиотеки).- Секреты подписи JWT и URL объектов не задаются в env: генерируются при первом старте и
лежат в слое метаданных (
app_settings), поэтому переживают перезапуск и рестарт SQLite. - Локальные артефакты в корне (
galo.db*,/data/,bin/,backend/static/*,frontend/build/) в git не попадают — не коммить их.
Фронтенд
- SvelteKit 2 + Svelte 5 (runes),
adapter-staticсfallback: index.html, Tailwind v4. - Тёмная/светлая/нейтральная темы через
data-themeна<html>и семантические CSS-токены. В компонентах — только семантические классы (bg-bg-elevated,text-mutedи т.п.), никакихdark:префиксов. - Vite dev-сервер проксирует
/apiнаlocalhost:8080(см.vite.config.ts).
OpenSpec
Спецификации — в openspec/specs/<capability>/spec.md; завершённые изменения —
в openspec/changes/archive/<YYYY-MM-DD-name>. Активных изменений сейчас нет.
Workflow-команды и скиллы лежат в .opencode/ и требуют CLI openspec
(в этом окружении он может быть не установлен — проверь command -v openspec).
При изменении поведения сначала обновляй/создавай спеку, а не только код.
Конвенции репозитория
- Ветка
develop; коммиты — Conventional Commits с русским описанием (feat(realtime): ...,test(android): ...,chore: ...). Функциональные ветки вливаются вdevelopчерез merge-коммиты. project.md— исходное ТЗ и местами устарел (например, называет S3 бэкендом по умолчанию и старую раскладку ключейalbums/...). Ориентируйся наREADME.mdи код.