- introduce storage.Backend with S3 and local file-system implementations - deliver all blobs via signed GET/HEAD /api/objects/<key> (Range, Content-Type) - stream S3 objects through the backend; remove presigned URL path - share shared HMAC Signer between backends - remove S3_PUBLIC_URL; rename S3_PRESIGN_TTL to OBJECT_URL_TTL - make storage label in health dynamic; nginx proxy_buffering off for objects
164 lines
6.0 KiB
Markdown
164 lines
6.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_SECRET`, `JWT_ACCESS_TTL`, `JWT_REFRESH_TTL`, `HTTP_ADDR`,
|
|
`SSE_HEARTBEAT_INTERVAL`, `SSE_CLIENT_BUFFER`.
|
|
|
|
## Сборка
|
|
|
|
```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
|
|
```
|
|
|
|
## Деплой через 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/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`
|