Database Hardening: - Add docker-compose.db.yml with persistent PostgreSQL volume - Add automatic DB backups every 12h (7 days, 4 weeks, 6 months retention) - Add MinIO S3-compatible storage for assets Assets Migration: - Add MinIO client (lib/assets_client.py) for upload/delete - Update upload endpoint to use MinIO (with local fallback) - Add migration 043_asset_urls_to_text.sql for full HTTPS URLs - Simplify normalizeAssetUrl for S3 URLs Recovery: - Add seed_full_city_reset.py for emergency city recovery - Add DB_RESTORE.md with backup restore instructions - Add SEED_RECOVERY.md with recovery procedures - Add INFRA_ASSETS_MINIO.md with MinIO setup guide Task: TASK_PHASE_DATABASE_HARDENING_AND_ASSETS_MIGRATION_v1
390 lines
9.2 KiB
Markdown
390 lines
9.2 KiB
Markdown
# INFRA_ASSETS_MINIO — Налаштування MinIO для Assets
|
||
|
||
## Огляд
|
||
|
||
DAARION використовує **MinIO** (S3-compatible object storage) для зберігання assets:
|
||
- Логотипи MicroDAO
|
||
- Банери MicroDAO
|
||
- Аватарки агентів
|
||
- Інші статичні файли
|
||
|
||
**Переваги:**
|
||
- Assets не залежать від локального сервера
|
||
- Можливість реплікації на зовнішній S3/R2
|
||
- Масштабованість
|
||
- Простота backup/restore
|
||
|
||
---
|
||
|
||
## 1. Архітектура
|
||
|
||
```
|
||
┌─────────────┐
|
||
│ Frontend │ → https://assets.daarion.space/daarion-assets/...
|
||
└─────────────┘
|
||
│
|
||
↓
|
||
┌─────────────┐
|
||
│ Caddy │ → Reverse proxy
|
||
│ / NGINX │
|
||
└─────────────┘
|
||
│
|
||
↓
|
||
┌─────────────┐
|
||
│ MinIO │ → S3 API (port 9000)
|
||
│ (Docker) │ → Console (port 9001)
|
||
└─────────────┘
|
||
│
|
||
↓
|
||
┌─────────────┐
|
||
│ Volume │ → minio_data (persistent)
|
||
└─────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 2. Docker Compose конфігурація
|
||
|
||
**Файл:** `docker-compose.db.yml`
|
||
|
||
```yaml
|
||
services:
|
||
minio:
|
||
image: minio/minio:latest
|
||
container_name: daarion-minio
|
||
restart: unless-stopped
|
||
command: server /data --console-address ":9001"
|
||
environment:
|
||
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
||
ports:
|
||
- "9000:9000" # S3 API
|
||
- "9001:9001" # Web console
|
||
volumes:
|
||
- minio_data:/data
|
||
```
|
||
|
||
**ENV змінні (.env):**
|
||
```env
|
||
MINIO_ROOT_USER=assets-admin
|
||
MINIO_ROOT_PASSWORD=very-strong-password
|
||
ASSETS_BUCKET=daarion-assets
|
||
ASSETS_PUBLIC_BASE_URL=https://assets.daarion.space/daarion-assets
|
||
MINIO_ENDPOINT=http://minio:9000
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Початкове налаштування
|
||
|
||
### Крок 1: Запустити MinIO
|
||
|
||
```bash
|
||
docker compose -f docker-compose.db.yml up -d minio
|
||
```
|
||
|
||
### Крок 2: Відкрити консоль
|
||
|
||
Відкрити в браузері: `http://localhost:9001` (або `https://minio.daarion.space`)
|
||
|
||
Логін:
|
||
- Username: `assets-admin` (з .env)
|
||
- Password: `very-strong-password` (з .env)
|
||
|
||
### Крок 3: Створити bucket
|
||
|
||
1. Натиснути "Create Bucket"
|
||
2. Назва: `daarion-assets`
|
||
3. Region: залишити за замовчуванням
|
||
4. Натиснути "Create Bucket"
|
||
|
||
### Крок 4: Встановити public read policy
|
||
|
||
1. Відкрити bucket `daarion-assets`
|
||
2. Перейти в "Access Policy"
|
||
3. Вибрати "Public" або "Custom"
|
||
4. Для Custom policy:
|
||
|
||
```json
|
||
{
|
||
"Version": "2012-10-17",
|
||
"Statement": [
|
||
{
|
||
"Effect": "Allow",
|
||
"Principal": {"AWS": ["*"]},
|
||
"Action": ["s3:GetObject"],
|
||
"Resource": ["arn:aws:s3:::daarion-assets/*"]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. DNS налаштування
|
||
|
||
### A/AAAA записи
|
||
|
||
```
|
||
assets.daarion.space → IP NODE1 (для публічного доступу)
|
||
minio.daarion.space → IP NODE1 (опційно, для консолі)
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Reverse Proxy (Caddy)
|
||
|
||
**Файл:** `Caddyfile`
|
||
|
||
```caddy
|
||
# Assets public access
|
||
assets.daarion.space {
|
||
encode gzip
|
||
|
||
reverse_proxy minio:9000 {
|
||
header_up Host {upstream_hostport}
|
||
header_up X-Real-IP {remote_host}
|
||
header_up X-Forwarded-For {remote_host}
|
||
header_up X-Forwarded-Proto {scheme}
|
||
}
|
||
}
|
||
|
||
# MinIO console (опційно)
|
||
minio.daarion.space {
|
||
encode gzip
|
||
|
||
reverse_proxy minio:9001 {
|
||
header_up Host {upstream_hostport}
|
||
header_up X-Real-IP {remote_host}
|
||
header_up X-Forwarded-For {remote_host}
|
||
header_up X-Forwarded-Proto {scheme}
|
||
}
|
||
}
|
||
```
|
||
|
||
**Docker Compose:**
|
||
```yaml
|
||
caddy:
|
||
image: caddy:2
|
||
container_name: daarion-caddy
|
||
restart: unless-stopped
|
||
ports:
|
||
- "80:80"
|
||
- "443:443"
|
||
volumes:
|
||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||
- caddy_data:/data
|
||
- caddy_config:/config
|
||
depends_on:
|
||
- minio
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Reverse Proxy (NGINX)
|
||
|
||
**Файл:** `nginx.conf` (фрагмент)
|
||
|
||
```nginx
|
||
http {
|
||
upstream minio_api {
|
||
server minio:9000;
|
||
}
|
||
|
||
server {
|
||
listen 80;
|
||
server_name assets.daarion.space;
|
||
|
||
client_max_body_size 100M;
|
||
|
||
location / {
|
||
proxy_pass http://minio_api;
|
||
proxy_set_header Host $http_host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 7. Формат URL в БД
|
||
|
||
Після завантаження файлу через API, в БД зберігається повний HTTPS URL:
|
||
|
||
```
|
||
https://assets.daarion.space/daarion-assets/microdao/logo/2025/12/02/abc123def456.png
|
||
```
|
||
|
||
**Структура:**
|
||
- `https://assets.daarion.space` - public domain
|
||
- `/daarion-assets` - bucket name
|
||
- `/microdao/logo` - prefix (тип asset)
|
||
- `/2025/12/02` - дата завантаження
|
||
- `/abc123def456.png` - унікальний ID + розширення
|
||
|
||
---
|
||
|
||
## 8. Backend інтеграція
|
||
|
||
### Upload endpoint (приклад)
|
||
|
||
```python
|
||
from fastapi import UploadFile, File
|
||
from lib.assets_client import upload_asset
|
||
|
||
@router.post("/microdao/{slug}/logo")
|
||
async def upload_logo(slug: str, file: UploadFile = File(...)):
|
||
url = upload_asset(
|
||
file.file,
|
||
file.content_type,
|
||
prefix="microdao/logo",
|
||
filename=file.filename
|
||
)
|
||
# Зберегти url в БД
|
||
await repo.update_logo(slug=slug, logo_url=url)
|
||
return {"logo_url": url}
|
||
```
|
||
|
||
### Клієнт MinIO
|
||
|
||
**Файл:** `services/city-service/lib/assets_client.py`
|
||
|
||
Функції:
|
||
- `upload_asset()` - завантажити файл, повернути URL
|
||
- `delete_asset()` - видалити файл
|
||
- `ensure_bucket()` - переконатися що bucket існує
|
||
|
||
---
|
||
|
||
## 9. Frontend інтеграція
|
||
|
||
**Файл:** `apps/web/src/lib/utils/assetUrl.ts`
|
||
|
||
```typescript
|
||
export function normalizeAssetUrl(url?: string | null): string | null {
|
||
if (!url) return null;
|
||
|
||
// Full HTTPS URLs (from MinIO) - return as-is
|
||
if (url.startsWith('https://') || url.startsWith('http://')) {
|
||
return url;
|
||
}
|
||
|
||
// Legacy local paths - handle fallback
|
||
// ...
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 10. Backup MinIO
|
||
|
||
### Створити backup bucket
|
||
|
||
```bash
|
||
# Використовуючи MinIO Client (mc)
|
||
mc alias set local http://localhost:9000 assets-admin <password>
|
||
mc mirror local/daarion-assets ./backups/minio/
|
||
```
|
||
|
||
### Автоматичний backup (cron)
|
||
|
||
```bash
|
||
# Додати в crontab
|
||
0 2 * * * docker exec daarion-minio mc mirror minio/daarion-assets /backups/minio/$(date +\%F)
|
||
```
|
||
|
||
---
|
||
|
||
## 11. Міграція з локальних файлів
|
||
|
||
### Крок 1: Завантажити існуючі файли в MinIO
|
||
|
||
```bash
|
||
# Використовуючи mc
|
||
mc cp ./services/city-service/static/uploads/microdao/logo/* local/daarion-assets/microdao/logo/
|
||
mc cp ./services/city-service/static/uploads/microdao/banner/* local/daarion-assets/microdao/banner/
|
||
```
|
||
|
||
### Крок 2: Оновити URL в БД
|
||
|
||
```sql
|
||
UPDATE microdao
|
||
SET logo_url = REPLACE(logo_url, '/api/static/uploads/', 'https://assets.daarion.space/daarion-assets/')
|
||
WHERE logo_url LIKE '/api/static/uploads/%';
|
||
```
|
||
|
||
---
|
||
|
||
## 12. Troubleshooting
|
||
|
||
### Проблема: "Access Denied"
|
||
|
||
Перевірити:
|
||
1. Bucket має public read policy
|
||
2. URL правильний (включає bucket name)
|
||
3. DNS налаштований правильно
|
||
|
||
### Проблема: "Connection refused"
|
||
|
||
```bash
|
||
# Перевірити статус
|
||
docker ps | grep minio
|
||
|
||
# Перезапустити
|
||
docker compose -f docker-compose.db.yml restart minio
|
||
```
|
||
|
||
### Проблема: "Bucket does not exist"
|
||
|
||
```bash
|
||
# Створити через mc
|
||
mc mb local/daarion-assets
|
||
|
||
# Або через консоль MinIO
|
||
```
|
||
|
||
---
|
||
|
||
## 13. Моніторинг
|
||
|
||
### Перевірка використання диска
|
||
|
||
```bash
|
||
docker exec daarion-minio du -sh /data
|
||
```
|
||
|
||
### Перевірка кількості об'єктів
|
||
|
||
Через MinIO Console → Bucket → Statistics
|
||
|
||
---
|
||
|
||
## 14. Реплікація на зовнішній S3 (опційно)
|
||
|
||
Для додаткової надійності можна налаштувати реплікацію на AWS S3 або Cloudflare R2:
|
||
|
||
```bash
|
||
# Налаштувати remote
|
||
mc alias set s3 https://s3.amazonaws.com ACCESS_KEY SECRET_KEY
|
||
|
||
# Налаштувати реплікацію
|
||
mc replicate add local/daarion-assets --remote-bucket s3/daarion-assets-backup
|
||
```
|
||
|
||
---
|
||
|
||
## 15. Чекліст налаштування
|
||
|
||
- [ ] MinIO запущений (`docker ps | grep minio`)
|
||
- [ ] Bucket `daarion-assets` створений
|
||
- [ ] Public read policy встановлена
|
||
- [ ] DNS `assets.daarion.space` налаштований
|
||
- [ ] Caddy/NGINX проксує запити до MinIO
|
||
- [ ] Backend використовує `assets_client.py`
|
||
- [ ] Frontend відображає assets з HTTPS URLs
|
||
- [ ] Тестовий upload працює
|
||
|