69715ecd06
Co-authored-by: Cursor <cursoragent@cursor.com>
551 lines
16 KiB
Markdown
551 lines
16 KiB
Markdown
# PhotoHost — Фото-хостинг
|
||
|
||
Современный фото-хостинг на **Python (Flask)**, **PostgreSQL** и **Docker Compose**.
|
||
|
||
- Красивая главная страница с drag-and-drop загрузкой
|
||
- **Регистрация и авторизация** пользователей
|
||
- **Личный кабинет** — загрузка и управление своими фото
|
||
- **Админ-панель** — пользователи, фото, статистика
|
||
- **Автоматическое создание первого администратора** через `.env`
|
||
- Галерея загруженных фото
|
||
- Копирование прямых ссылок на изображения
|
||
- Хранение метаданных в PostgreSQL, файлов — в Docker volume
|
||
|
||
---
|
||
|
||
## Структура проекта
|
||
|
||
```
|
||
fotohost/
|
||
├── app/
|
||
│ ├── __init__.py # Flask-приложение, Flask-Login
|
||
│ ├── models.py # User, Photo
|
||
│ ├── auth.py # Регистрация, вход, выход
|
||
│ ├── routes.py # Главная, загрузка, личный кабинет
|
||
│ ├── admin.py # Админ-панель
|
||
│ ├── bootstrap.py # Миграция схемы, первый admin
|
||
│ ├── auth_utils.py # Декораторы доступа
|
||
│ ├── templates/ # HTML-шаблоны
|
||
│ └── static/ # CSS и JavaScript
|
||
├── uploads/ # Локальная папка (в Docker — volume)
|
||
├── docker-compose.yml # Оркестрация web + PostgreSQL
|
||
├── Dockerfile
|
||
├── wsgi.py # Точка входа для Gunicorn
|
||
├── requirements.txt
|
||
├── .env.example
|
||
└── README.md
|
||
```
|
||
|
||
---
|
||
|
||
## Развёртывание на Ubuntu 24.04
|
||
|
||
Подробная пошаговая инструкция для чистого сервера Ubuntu 24.04 LTS.
|
||
|
||
### 1. Подключение к серверу
|
||
|
||
```bash
|
||
ssh user@YOUR_SERVER_IP
|
||
```
|
||
|
||
Замените `user` на имя пользователя и `YOUR_SERVER_IP` на IP-адрес сервера.
|
||
|
||
### 2. Обновление системы
|
||
|
||
```bash
|
||
sudo apt update && sudo apt upgrade -y
|
||
```
|
||
|
||
### 3. Установка необходимых пакетов
|
||
|
||
```bash
|
||
sudo apt install -y ca-certificates curl gnupg git
|
||
```
|
||
|
||
### 4. Установка Docker
|
||
|
||
Docker официально поддерживается на Ubuntu 24.04.
|
||
|
||
```bash
|
||
# Добавить GPG-ключ Docker
|
||
sudo install -m 0755 -d /etc/apt/keyrings
|
||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||
sudo chmod a+r /etc/apt/keyrings/docker.gpg
|
||
|
||
# Добавить репозиторий Docker
|
||
echo \
|
||
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
|
||
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
|
||
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||
|
||
# Установить Docker Engine и Compose plugin
|
||
sudo apt update
|
||
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||
```
|
||
|
||
Проверка установки:
|
||
|
||
```bash
|
||
sudo docker run hello-world
|
||
docker compose version
|
||
```
|
||
|
||
### 5. Добавить пользователя в группу docker (опционально)
|
||
|
||
Чтобы не использовать `sudo` перед каждой командой docker:
|
||
|
||
```bash
|
||
sudo usermod -aG docker $USER
|
||
newgrp docker
|
||
```
|
||
|
||
### 6. Копирование проекта на сервер
|
||
|
||
**Вариант A — через Git:**
|
||
|
||
```bash
|
||
cd ~
|
||
git clone https://git.evilfox.cc/test2/fotohost.git fotohost
|
||
cd fotohost
|
||
```
|
||
|
||
**Вариант B — через SCP с локального компьютера:**
|
||
|
||
```bash
|
||
# Выполнить на локальной машине (Windows PowerShell / Linux)
|
||
scp -r ./fotohost user@YOUR_SERVER_IP:~/
|
||
```
|
||
|
||
```bash
|
||
# На сервере
|
||
cd ~/fotohost
|
||
```
|
||
|
||
**Вариант C — создать файлы вручную** — скопируйте содержимое проекта в каталог `~/fotohost`.
|
||
|
||
### 7. Настройка переменных окружения
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
nano .env
|
||
```
|
||
|
||
Измените значения в `.env`:
|
||
|
||
```env
|
||
POSTGRES_USER=photohost
|
||
POSTGRES_PASSWORD=YOUR_STRONG_DB_PASSWORD
|
||
POSTGRES_DB=photohost
|
||
DATABASE_URL=postgresql://photohost:YOUR_STRONG_DB_PASSWORD@db:5432/photohost
|
||
|
||
SECRET_KEY=random_string_min_32_chars
|
||
MAX_UPLOAD_MB=10
|
||
APP_PORT=8080
|
||
|
||
# Первый администратор (создаётся автоматически при первом запуске)
|
||
ADMIN_USERNAME=admin
|
||
ADMIN_EMAIL=admin@example.com
|
||
ADMIN_PASSWORD=YOUR_STRONG_ADMIN_PASSWORD
|
||
```
|
||
|
||
Сгенерировать случайный `SECRET_KEY`:
|
||
|
||
```bash
|
||
python3 -c "import secrets; print(secrets.token_hex(32))"
|
||
```
|
||
|
||
> **Первый администратор:** при первом запуске, если в базе нет ни одного admin, создаётся пользователь из `ADMIN_USERNAME` / `ADMIN_EMAIL` / `ADMIN_PASSWORD`. Обязательно смените пароль в `.env` до деплоя.
|
||
|
||
Альтернатива — создать admin вручную через CLI:
|
||
|
||
```bash
|
||
docker compose exec web flask create-admin
|
||
```
|
||
|
||
### 8. Запуск приложения
|
||
|
||
```bash
|
||
docker compose up -d --build
|
||
```
|
||
|
||
Проверка статуса контейнеров:
|
||
|
||
```bash
|
||
docker compose ps
|
||
```
|
||
|
||
Ожидаемый результат — оба сервиса `running`:
|
||
|
||
| Сервис | Контейнер | Порт |
|
||
|--------|----------------|-------------|
|
||
| web | photohost-web | 8080 → 8000 |
|
||
| db | photohost-db | 5432 (внутр.) |
|
||
|
||
Просмотр логов:
|
||
|
||
```bash
|
||
docker compose logs -f web
|
||
```
|
||
|
||
### 9. Проверка работы
|
||
|
||
Откройте в браузере:
|
||
|
||
```
|
||
http://YOUR_SERVER_IP:8080
|
||
```
|
||
|
||
Загрузите тестовое изображение — оно должно появиться в галерее.
|
||
|
||
Войдите как admin (`/auth/login`) → откройте **Админку** (`/admin`).
|
||
|
||
### 10. Открытие порта в файрволе (UFW)
|
||
|
||
Если включён UFW:
|
||
|
||
```bash
|
||
sudo ufw allow 8080/tcp
|
||
sudo ufw allow OpenSSH
|
||
sudo ufw enable
|
||
sudo ufw status
|
||
```
|
||
|
||
### 11. Автозапуск при перезагрузке сервера
|
||
|
||
Docker Compose с `restart: unless-stopped` уже перезапускает контейнеры. Убедитесь, что Docker включён:
|
||
|
||
```bash
|
||
sudo systemctl enable docker
|
||
sudo systemctl start docker
|
||
```
|
||
|
||
---
|
||
|
||
## Обновление до новой версии на сервере
|
||
|
||
Когда выходит новая версия в Git, обновите проект на сервере без потери данных (БД и фото хранятся в Docker volumes, файл `.env` не перезаписывается).
|
||
|
||
### Быстрое обновление (последняя версия из `main`)
|
||
|
||
```bash
|
||
cd ~/fotohost
|
||
git pull origin main
|
||
docker compose up -d --build
|
||
docker compose ps
|
||
docker compose logs --tail=50 web
|
||
```
|
||
|
||
### Обновление до конкретного релиза (рекомендуется)
|
||
|
||
Список доступных версий:
|
||
|
||
```bash
|
||
cd ~/fotohost
|
||
git fetch --tags
|
||
git tag -l
|
||
```
|
||
|
||
Пример — установить релиз **v1.0-beta**:
|
||
|
||
```bash
|
||
cd ~/fotohost
|
||
git fetch --tags
|
||
git checkout v1.0-beta
|
||
docker compose up -d --build
|
||
docker compose ps
|
||
```
|
||
|
||
Вернуться на последнюю dev-версию из `main`:
|
||
|
||
```bash
|
||
cd ~/fotohost
|
||
git checkout main
|
||
git pull origin main
|
||
docker compose up -d --build
|
||
```
|
||
|
||
### Перед обновлением (рекомендуется)
|
||
|
||
```bash
|
||
cd ~/fotohost
|
||
|
||
# Бэкап базы данных
|
||
docker compose exec db pg_dump -U photohost photohost > backup_$(date +%Y%m%d_%H%M).sql
|
||
|
||
# Проверить, не появились ли новые переменные в .env.example
|
||
diff .env .env.example || true
|
||
nano .env
|
||
```
|
||
|
||
Если в `.env.example` появились новые строки — добавьте их в свой `.env` вручную.
|
||
|
||
### После обновления — проверка
|
||
|
||
```bash
|
||
docker compose ps
|
||
curl -I http://127.0.0.1:8080
|
||
docker compose logs --tail=100 web
|
||
```
|
||
|
||
Откройте сайт в браузере и проверьте вход, загрузку фото и админку.
|
||
|
||
### Если что-то пошло не так — откат на предыдущий тег
|
||
|
||
```bash
|
||
cd ~/fotohost
|
||
git checkout v1.0-beta
|
||
docker compose up -d --build
|
||
```
|
||
|
||
> **Важно:** команда `docker compose up -d --build` пересобирает контейнер `web`, но **не удаляет** volumes с PostgreSQL и загруженными фото.
|
||
|
||
---
|
||
|
||
## Регистрация, авторизация и роли
|
||
|
||
| URL | Описание |
|
||
|-----|----------|
|
||
| `/auth/register` | Регистрация нового пользователя |
|
||
| `/auth/login` | Вход (логин или email) |
|
||
| `/auth/logout` | Выход |
|
||
| `/cabinet/` | Личный кабинет — мои фото |
|
||
| `/cabinet/profile` | Настройки профиля, смена пароля |
|
||
| `/admin/` | Панель администратора (только admin) |
|
||
| `/admin/users` | Управление пользователями |
|
||
| `/admin/groups` | Группы: квота диска, лимиты папок и фото |
|
||
| `/admin/banners` | Рекламные баннеры на сайте |
|
||
| `/admin/photos` | Все фото на сервере |
|
||
|
||
**Права доступа:**
|
||
- Загрузка фото — только авторизованным пользователям
|
||
- Удаление фото — владелец или администратор
|
||
- Админка — только пользователи с `is_admin=True`
|
||
|
||
---
|
||
|
||
## Релиз v1.4
|
||
|
||
**Лимиты групп пользователей**
|
||
|
||
- В `/admin/groups` администратор задаёт для каждой группы:
|
||
- **Квота диска** (МБ, `0` = без лимита)
|
||
- **Максимум папок** на пользователя (`0` = без лимита)
|
||
- **Максимум фото** на пользователя (`0` = без лимита)
|
||
- Лимиты проверяются при создании папки и загрузке фото
|
||
- В личном кабинете отображается использование квот
|
||
|
||
**Переменные `.env` для группы по умолчанию:**
|
||
|
||
```env
|
||
DEFAULT_GROUP_QUOTA_MB=100
|
||
DEFAULT_GROUP_MAX_FOLDERS=10
|
||
DEFAULT_GROUP_MAX_PHOTOS=500
|
||
```
|
||
|
||
**Рекламные баннеры**
|
||
|
||
- Управление в `/admin/banners`
|
||
- Позиции: главная (под hero), личный кабинет, подвал
|
||
- URL изображения, опциональная ссылка при клике, порядок сортировки, вкл/выкл
|
||
|
||
**Обновление до v1.4 на сервере:**
|
||
|
||
```bash
|
||
cd ~/fotohost
|
||
git fetch --tags
|
||
git checkout v1.4
|
||
docker compose up -d --build
|
||
```
|
||
|
||
---
|
||
|
||
## Полезные команды
|
||
|
||
| Действие | Команда |
|
||
|-----------------------|----------------------------------|
|
||
| Остановить | `docker compose down` |
|
||
| Перезапустить | `docker compose restart` |
|
||
| Пересобрать | `docker compose up -d --build` |
|
||
| Логи web | `docker compose logs -f web` |
|
||
| Логи БД | `docker compose logs -f db` |
|
||
| Зайти в контейнер web | `docker compose exec web bash` |
|
||
| Зайти в PostgreSQL | `docker compose exec db psql -U photohost -d photohost` |
|
||
|
||
---
|
||
|
||
## Резервное копирование
|
||
|
||
### База данных
|
||
|
||
```bash
|
||
docker compose exec db pg_dump -U photohost photohost > backup_$(date +%Y%m%d).sql
|
||
```
|
||
|
||
Восстановление:
|
||
|
||
```bash
|
||
cat backup_20250606.sql | docker compose exec -T db psql -U photohost -d photohost
|
||
```
|
||
|
||
### Загруженные фото
|
||
|
||
Фото хранятся в Docker volume `uploads_data`. Список volumes:
|
||
|
||
```bash
|
||
docker volume ls
|
||
```
|
||
|
||
Бэкап volume:
|
||
|
||
```bash
|
||
docker run --rm -v photohost_uploads_data:/data -v $(pwd):/backup alpine tar czf /backup/uploads_backup.tar.gz -C /data .
|
||
```
|
||
|
||
---
|
||
|
||
## Настройка домена и HTTPS (Nginx + Let's Encrypt)
|
||
|
||
### Установка Nginx и Certbot
|
||
|
||
```bash
|
||
sudo apt install -y nginx certbot python3-certbot-nginx
|
||
```
|
||
|
||
### Конфиг Nginx
|
||
|
||
```bash
|
||
sudo nano /etc/nginx/sites-available/photohost
|
||
```
|
||
|
||
```nginx
|
||
server {
|
||
listen 80;
|
||
server_name photos.example.com;
|
||
|
||
client_max_body_size 15M;
|
||
|
||
location / {
|
||
proxy_pass http://127.0.0.1:8080;
|
||
proxy_set_header Host $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;
|
||
}
|
||
}
|
||
```
|
||
|
||
```bash
|
||
sudo ln -s /etc/nginx/sites-available/photohost /etc/nginx/sites-enabled/
|
||
sudo nginx -t
|
||
sudo systemctl reload nginx
|
||
```
|
||
|
||
### SSL-сертификат
|
||
|
||
```bash
|
||
sudo certbot --nginx -d photos.example.com
|
||
```
|
||
|
||
Откройте порты 80 и 443:
|
||
|
||
```bash
|
||
sudo ufw allow 'Nginx Full'
|
||
```
|
||
|
||
---
|
||
|
||
## Локальная разработка (без Docker)
|
||
|
||
```bash
|
||
# Установить PostgreSQL локально или запустить только БД:
|
||
docker compose up -d db
|
||
|
||
python3 -m venv venv
|
||
source venv/bin/activate # Windows: venv\Scripts\activate
|
||
pip install -r requirements.txt
|
||
|
||
cp .env.example .env
|
||
# Измените DATABASE_URL на localhost:
|
||
# DATABASE_URL=postgresql://photohost:photohost_secret@localhost:5432/photohost
|
||
|
||
export FLASK_APP=wsgi.py
|
||
python wsgi.py
|
||
```
|
||
|
||
Приложение будет доступно на `http://localhost:8000`.
|
||
|
||
> Для локального запуска только БД добавьте в `docker-compose.yml` для сервиса `db` строку `ports: - "5432:5432"`.
|
||
|
||
---
|
||
|
||
## API
|
||
|
||
| Метод | URL | Описание |
|
||
|-------|---------------|-----------------------------|
|
||
| GET | `/` | Главная страница |
|
||
| POST | `/auth/register` | Регистрация |
|
||
| POST | `/auth/login` | Вход |
|
||
| GET | `/cabinet/` | Личный кабинет |
|
||
| GET | `/admin/` | Админ-панель |
|
||
| GET | `/admin/banners` | Управление рекламными баннерами |
|
||
| POST | `/upload` | Загрузка фото (auth) |
|
||
| GET | `/uploads/<filename>` | Прямая ссылка на файл |
|
||
| GET | `/api/photos` | JSON-список всех фото |
|
||
| POST | `/delete/<id>` | Удаление фото |
|
||
|
||
Пример ответа `/api/photos`:
|
||
|
||
```json
|
||
[
|
||
{
|
||
"id": 1,
|
||
"url": "/uploads/abc123.jpg",
|
||
"original_name": "photo.jpg",
|
||
"file_size": 245760,
|
||
"size_human": "240.0 КБ",
|
||
"created_at": "2025-06-06T12:00:00+00:00"
|
||
}
|
||
]
|
||
```
|
||
|
||
---
|
||
|
||
## Устранение неполадок
|
||
|
||
**Контейнер web не запускается**
|
||
|
||
```bash
|
||
docker compose logs web
|
||
```
|
||
|
||
Частая причина — БД ещё не готова. Healthcheck в `docker-compose.yml` решает это; подождите 30 секунд и перезапустите:
|
||
|
||
```bash
|
||
docker compose restart web
|
||
```
|
||
|
||
**Ошибка подключения к PostgreSQL**
|
||
|
||
Проверьте, что пароли в `.env` совпадают в `POSTGRES_PASSWORD` и `DATABASE_URL`.
|
||
|
||
**Фото не загружаются (413 Request Entity Too Large)**
|
||
|
||
Увеличьте `MAX_UPLOAD_MB` в `.env` и `client_max_body_size` в Nginx.
|
||
|
||
**Порт 8080 занят**
|
||
|
||
Измените `APP_PORT=9090` в `.env` и перезапустите:
|
||
|
||
```bash
|
||
docker compose down && docker compose up -d
|
||
```
|
||
|
||
---
|
||
|
||
## Технологии
|
||
|
||
- Python 3.12, Flask 3, Flask-Login, Gunicorn
|
||
- PostgreSQL 16
|
||
- SQLAlchemy, Pillow
|
||
- Docker & Docker Compose
|