- app_settings table (PostgreSQL migration 000010, SQLite 000002) - internal/settings repository (Get/Set/Ensure) wired into store.Repos - secret generated with crypto/rand on first start and persisted; reused on restart - drop required JWT_SECRET from env/config; app.New takes a TokenManager and storage.Open takes the resolved secret (URL_SECRET still overrides) - docs updated
191 lines
8.0 KiB
Markdown
191 lines
8.0 KiB
Markdown
# 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` и заполните значения:
|
|
|
|
```bash
|
|
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 — встроенный режим без внешней БД,
|
|
**только для одного экземпляра приложения на файл** (без горизонтального
|
|
масштабирования).
|
|
|
|
## Сборка
|
|
|
|
```bash
|
|
make build # frontend -> backend/static -> go:embed -> bin/shared-album
|
|
```
|
|
|
|
Отдельные шаги:
|
|
|
|
```bash
|
|
make frontend # npm ci && npm run build, копирует статику в backend/static
|
|
make backend # CGO_ENABLED=0 go build -tags nodynamic -o bin/shared-album
|
|
```
|
|
|
|
> `-tags nodynamic` обязателен: он заставляет `gen2brain/webp` использовать
|
|
> транспилированный в Go libwebp вместо динамической загрузки через purego,
|
|
> благодаря чему бинарник остаётся полностью статическим.
|
|
|
|
Проверка:
|
|
|
|
```bash
|
|
file bin/shared-album # => statically linked
|
|
```
|
|
|
|
## Миграции
|
|
|
|
Миграции встроены в бинарник и применяются автоматически при старте сервера.
|
|
Отдельно:
|
|
|
|
```bash
|
|
make migrate # go run ./cmd/migrate — применяет up-миграции к pg.localnet
|
|
```
|
|
|
|
## Локальный запуск
|
|
|
|
```bash
|
|
make run # соберёт backend и запустит ./bin/shared-album
|
|
# или напрямую:
|
|
./bin/shared-album
|
|
```
|
|
|
|
Сервер слушает `HTTP_ADDR` (по умолчанию `:8080`). Health-check:
|
|
|
|
```bash
|
|
curl -s localhost:8080/api/health # {"status":"ok","db":"ok","s3":"ok"}
|
|
```
|
|
|
|
Разработка фронтенда с HMR:
|
|
|
|
```bash
|
|
cd frontend && npm run dev # Vite проксирует /api на :8080
|
|
```
|
|
|
|
## Тесты
|
|
|
|
```bash
|
|
make test
|
|
# интеграционные тесты репозиториев требуют тестовую БД:
|
|
TEST_DATABASE_URL='postgres://user:pass@pg.localnet:5432/galo?sslmode=disable' \
|
|
cd backend && go test ./internal/repo/ -v
|
|
```
|
|
|
|
## Реконсиляция хранилища
|
|
|
|
Сборка мусора воркера управляется журналом `objects` и не сканирует хранилище, поэтому
|
|
блобы без строки в журнале (после сброса БД в обход журнала и т.п.) накапливаются.
|
|
Ручная сверка:
|
|
|
|
```bash
|
|
go run ./cmd/reconcile # отчёт: сироты (есть в хранилище, нет в журнале) и пропавшие
|
|
go run ./cmd/reconcile --delete # удалить сироты старше --min-age (по умолчанию 1h)
|
|
```
|
|
|
|
- По умолчанию ничего не удаляется; удаление только по явному `--delete`.
|
|
- Область — префикс `objects/`; блобы старого формата `albums/` не затрагиваются.
|
|
- Пропавшие ключи (в журнале есть, в хранилище нет) только сообщаются.
|
|
|
|
## Деплой через systemd
|
|
|
|
1. Соберите бинарник: `make build`.
|
|
2. Создайте пользователя и каталог:
|
|
|
|
```bash
|
|
sudo useradd --system --home /opt/shared-album album
|
|
sudo mkdir -p /opt/shared-album
|
|
sudo cp bin/shared-album /opt/shared-album/
|
|
sudo cp .env /opt/shared-album/.env
|
|
sudo chown -R album:album /opt/shared-album
|
|
```
|
|
|
|
3. Установите unit:
|
|
|
|
```bash
|
|
sudo cp deploy/shared-album.service /etc/systemd/system/
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable --now shared-album
|
|
sudo journalctl -u shared-album -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/me`
|
|
- `GET|POST /api/albums`, `GET|PATCH|DELETE /api/albums/:id`
|
|
- `GET /api/albums/:id/members`, `POST /api/albums/:id/invites`,
|
|
`POST /api/invites/:token/accept`, `PATCH|DELETE /api/albums/:id/members/:user_id`
|
|
- `GET|POST /api/albums/:id/photos`, `GET|DELETE /api/photos/:id`,
|
|
`GET /api/photos/:id/download`
|
|
- `POST|DELETE /api/photos/:id/likes`, `GET|POST /api/photos/:id/comments`,
|
|
`DELETE /api/comments/:id`
|
|
- `GET /api/sse/albums/:id`, `GET /api/sse/me`
|
|
- `POST|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`
|