iu ec8d907565 feat(storage): serve media through backend and add local FS backend
- 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
2026-10-11 09:15:03 +03:00

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_SECRET, JWT_ACCESS_TTL, JWT_REFRESH_TTL, HTTP_ADDR, SSE_HEARTBEAT_INTERVAL, SSE_CLIENT_BUFFER.

Сборка

make build      # frontend -> backend/static -> go:embed -> bin/shared-album

Отдельные шаги:

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, благодаря чему бинарник остаётся полностью статическим.

Проверка:

file bin/shared-album     # => statically linked

Миграции

Миграции встроены в бинарник и применяются автоматически при старте сервера. Отдельно:

make migrate    # go run ./cmd/migrate — применяет up-миграции к pg.localnet

Локальный запуск

make run        # соберёт backend и запустит ./bin/shared-album
# или напрямую:
./bin/shared-album

Сервер слушает 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

Деплой через systemd

  1. Соберите бинарник: make build.

  2. Создайте пользователя и каталог:

    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:

    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
S
Description
No description provided
Readme
4.5 MiB
0.2.0
Latest
2026-10-23 20:09:06 +00:00
Languages
Kotlin 44.2%
Go 32.7%
Svelte 12.1%
TypeScript 9%
Python 0.9%
Other 0.9%