# 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`
---
## Релиз v2.1
**GDPR и cookies**
- Политика конфиденциальности: `/legal/privacy`
- Политика cookies: `/legal/cookies`
- GDPR-права: `/legal/gdpr`
- Баннер согласия на cookies
- Экспорт данных и удаление аккаунта в профиле
**Passkey (WebAuthn)**
- Регистрация passkey в профиле
- Вход кнопкой «Войти с Passkey» на странице входа
- Переменные `WEBAUTHN_RP_ID`, `WEBAUTHN_ORIGIN` в `.env`
**Управление сессиями**
- Список активных сессий в профиле (устройство, IP, время)
- Завершение отдельной сессии или всех кроме текущей
**Обновлённая админка**
- Боковое меню, карточки статистики, улучшенная вёрстка
**Обновление до v2.1 на сервере:**
```bash
cd ~/fotohost
git fetch --tags
git checkout v2.1
# добавьте WEBAUTHN_RP_ID и WEBAUTHN_ORIGIN в .env
docker compose up -d --build
```
---
## Релиз v2.0
**Загрузка по прямым ссылкам**
- В форме загрузки вкладка «Ссылки»
- Вставьте одну или несколько прямых URL на изображения (HTTP/HTTPS)
- Поддерживаются те же форматы и лимиты, что и при обычной загрузке
**Поделиться: BBCode, HTML, QR**
- На каждой фотографии кнопки **Ссылка**, **BBCode** и **QR**
- BBCode для форумов: `[img]https://...[/img]`
- HTML для сайтов: `
`
- QR-код открывается в модальном окне с быстрым копированием всех форматов
**Обновление до v2.0 на сервере:**
```bash
cd ~/fotohost
git fetch --tags
git checkout v2.0
docker compose up -d --build
```
---
## Релиз 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 изображения, опциональная ссылка при клике, порядок сортировки, вкл/выкл
**Git deploy из админки**
- Исправлена ошибка `could not lock config file .git/config: Permission denied`
- Fetch/checkout без записи в `.git/config`, автоматическое восстановление прав
**Обновление до 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` | Управление рекламными баннерами |
| GET | `/photo//qr` | QR-код для прямой ссылки на фото |
| POST | `/upload` | Загрузка фото или по URL (auth) |
| GET | `/uploads/` | Прямая ссылка на файл |
| GET | `/api/photos` | JSON-список всех фото |
| POST | `/delete/` | Удаление фото |
Пример ответа `/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
```
**502 Bad Gateway (Nginx)**
Nginx не может достучаться до контейнера `web`. Проверьте:
```bash
cd ~/fotohost
docker compose ps
docker compose logs --tail=100 web
curl -I http://127.0.0.1:8080/health
```
Частые причины после обновления:
1. Контейнер `photohost-web` не запущен или перезапускается — смотрите логи `docker compose logs web`
2. В Nginx указан неверный порт — должен совпадать с `APP_PORT` из `.env` (по умолчанию `8080`):
```nginx
proxy_pass http://127.0.0.1:8080;
```
3. База данных ещё не готова — подождите 30–60 секунд и выполните `docker compose restart web`
---
## Технологии
- Python 3.12, Flask 3, Flask-Login, Gunicorn
- PostgreSQL 16
- SQLAlchemy, Pillow
- Docker & Docker Compose