- archive rename-shared-album-to-galo, add-halo-branding - sync specs: ui-branding (new capability)
Galo — многопользовательский фотоальбом
Self-hosted веб-приложение: общие фотоальбомы с ролями, загрузкой фото в S3, live-обновлениями через SSE и доступом по ссылке без аккаунта.
Без Docker: один статичный Go-бинарник (CGO_ENABLED=0) со вшитой статикой
SvelteKit через go:embed, запуск через systemd.
Стек
- Backend: Go, Gin, pgx (PostgreSQL на
pg.localnet), AWS SDK v2 (Garage наs3.localnet, path-style), нативный SSE, golang-migrate, slog. - Frontend: SvelteKit 2 + Svelte 5, adapter-static, TypeScript, TailwindCSS.
- Обработка изображений:
disintegration/imaging+gen2brain/webp+rwcarlsen/goexif(всё чистый Go).
Требования
- Go 1.22+ (сборка проверена на 1.26)
- Node.js 20+ (только для сборки фронтенда)
- Доступ к
pg.localnet:5432(PostgreSQL) иs3.localnet:3900(Garage) make
Конфигурация
Скопируйте .env.example в .env и заполните значения:
cp .env.example .env
Ключевые переменные: PG_*, S3_* (обязательны UsePathStyle=true и регион
garage), JWT_ACCESS_TTL, JWT_REFRESH_TTL, HTTP_ADDR,
SSE_HEARTBEAT_INTERVAL, SSE_CLIENT_BUFFER. Секрет подписи токенов не задаётся
в окружении: он генерируется при первом старте и хранится в слое метаданных
(app_settings).
Слои данных выбираются параметрами (по умолчанию — локальный режим):
STORAGE_BACKEND=fs|s3— где лежат блобы:fs(по умолчанию,FS_ROOT=./data) илиs3(S3/Garage); наружу всегда отдаётся бэкенд (/api/objects/<key>).DB_BACKEND=sqlite|postgres— где лежат метаданные:sqlite(по умолчанию,SQLITE_PATH=./galo.db) илиpostgres. SQLite — встроенный режим без внешней БД, только для одного экземпляра приложения на файл (без горизонтального масштабирования).
Сборка
make build # frontend -> backend/static -> go:embed -> bin/galo
Отдельные шаги:
make frontend # npm ci && npm run build, копирует статику в backend/static
make backend # CGO_ENABLED=0 go build -tags nodynamic -o bin/galo
-tags nodynamicобязателен: он заставляетgen2brain/webpиспользовать транспилированный в Go libwebp вместо динамической загрузки через purego, благодаря чему бинарник остаётся полностью статическим.
Проверка:
file bin/galo # => statically linked
Миграции
Миграции встроены в бинарник и применяются автоматически при старте сервера. Отдельно:
make migrate # go run ./cmd/migrate — применяет up-миграции к pg.localnet
Локальный запуск
make run # соберёт backend и запустит ./bin/galo
# или напрямую:
./bin/galo
Сервер слушает HTTP_ADDR (по умолчанию :8080). Health-check:
curl -s localhost:8080/api/health # {"status":"ok","db":"ok","s3":"ok"}
Разработка фронтенда с HMR:
cd frontend && npm run dev # Vite проксирует /api на :8080
Тесты
make test
# интеграционные тесты репозиториев требуют тестовую БД:
TEST_DATABASE_URL='postgres://user:pass@pg.localnet:5432/galo?sslmode=disable' \
cd backend && go test ./internal/repo/ -v
Реконсиляция хранилища
Сборка мусора воркера управляется журналом objects и не сканирует хранилище, поэтому
блобы без строки в журнале (после сброса БД в обход журнала и т.п.) накапливаются.
Ручная сверка:
go run ./cmd/reconcile # отчёт: сироты (есть в хранилище, нет в журнале) и пропавшие
go run ./cmd/reconcile --delete # удалить сироты старше --min-age (по умолчанию 1h)
- По умолчанию ничего не удаляется; удаление только по явному
--delete. - Область — префикс
objects/; блобы старого форматаalbums/не затрагиваются. - Пропавшие ключи (в журнале есть, в хранилище нет) только сообщаются.
Деплой через systemd
-
Соберите бинарник:
make build. -
Создайте пользователя и каталог:
sudo useradd --system --home /opt/galo album sudo mkdir -p /opt/galo sudo cp bin/galo /opt/galo/ sudo cp .env /opt/galo/.env sudo chown -R album:album /opt/galo -
Установите unit:
sudo cp deploy/galo.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now galo sudo journalctl -u galo -f
nginx (опционально)
Пример в deploy/nginx-sse.conf. Для SSE критично proxy_buffering off и
X-Accel-Buffering: no (последнее сервер выставляет сам).
Структура
backend/
cmd/server/ # точка входа
cmd/migrate/ # применение миграций
internal/config # чтение env
internal/db # pgx + golang-migrate
internal/storage # S3 (Garage) / локальная ФС, отдача через бэкенд
internal/store # фабрика репозиториев (postgres / sqlite)
internal/auth # JWT, bcrypt, refresh
internal/albums # альбомы
internal/members # участники, роли, инвайты
internal/photos # загрузка, варианты, отдача через бэкенд
internal/social # лайки, комментарии
internal/notifications # персональные уведомления
internal/sharelinks # ссылки для просмотра
internal/sse # SSE-хаб
internal/app # маршруты и статика
migrations/ # SQL-миграции
static/ # сюда копируется build SvelteKit
frontend/ # SvelteKit 2 + Svelte 5
deploy/ # systemd unit, пример nginx
API
Полный список эндпоинтов: project.md, раздел 11. Кратко:
POST /api/auth/register|login|refresh|logout,GET /api/auth/meGET|POST /api/albums,GET|PATCH|DELETE /api/albums/:idGET /api/albums/:id/members,POST /api/albums/:id/invites,POST /api/invites/:token/accept,PATCH|DELETE /api/albums/:id/members/:user_idGET|POST /api/albums/:id/photos,GET|DELETE /api/photos/:id,GET /api/photos/:id/downloadPOST|DELETE /api/photos/:id/likes,GET|POST /api/photos/:id/comments,DELETE /api/comments/:idGET /api/sse/albums/:id,GET /api/sse/mePOST|GET /api/albums/:id/share,DELETE /api/albums/:id/share/:link_id,GET /api/share/:token[/photos[/:photo_id[/download]]]GET /api/notifications,POST /api/notifications/:id/read