Инфраструктурное решение для интеграции с HeadHunter API через OAuth2 с reverse-proxy nginx за KeenDNS Cloud и автообновлением токенов через systemd timers.
⚠️ Важно: Этот репозиторий содержит только инфраструктуру OAuth2. Продакшен-приложение (Telegram bot для поиска работы) размещено в отдельном проекте и использует эту инфраструктуру для авторизации и автообновления токенов.
- ✅ OAuth2 Authorization Code Flow для HeadHunter API
- ✅ Reverse-proxy nginx с корректными заголовками для KeenDNS Cloud (SSL termination)
- ✅ Автоматическое обновление токенов через systemd timer (ежедневно в 17:00)
- ✅ Безопасное хранение токенов с правами доступа 600
- ✅ Логирование всех операций через journalctl
- ✅ Тестовый HTTP-сервер для проверки инфраструктуры
flowchart TB
subgraph infra["📦 OAuth2 Infrastructure (Этот репозиторий)"]
direction TB
Nginx[🔄 nginx<br/>HTTP:80→:8000]
subgraph automation["⚙️ Automation"]
direction LR
Timer[⏱️ systemd<br/>Каждые 6h]
Script[📜 refresh.sh<br/>Обновление токенов]
end
TestServer[🧪 test-8000.py<br/>Тестовый сервер]
TokenStore[(🔐 Token Storage<br/>/var/lib/hh-token/token.json)]
end
subgraph prod["🤖 Production App (Отдельный проект)"]
direction TB
TelegramBot[📱 Telegram Bot<br/>Поиск вакансий HH]
FlaskApp[🌐 Flask Application<br/>Обработка OAuth callback на /callback]
TelegramBot -.->|Проект<br/>hh-oauth2-keendns-nginx-systemd| FlaskApp
end
subgraph external["External"]
HHAPI[🏢 HH OAuth2 API<br/>api.hh.ru]
end
%% Connections / Связи между компонентами
%% Main Flow (OAuth) (основной поток):
Nginx -->|1. Proxy :8000| FlaskApp
FlaskApp -->|2. OAuth callback| HHAPI
FlaskApp -->|3. Сохраняет первый tokens| TokenStore
%% Production Flow (работа приложения):
FlaskApp -->|4. Считывание tokens| TokenStore
TelegramBot <-->|5. API запросы| HHAPI
%% Token Refresh Flow (автоматизация):
Timer -.->|6. Trigger| Script
Script -->|7. Обновляет tokens| HHAPI
Script -->|8. Сохраняет new tokens| TokenStore
%% Testing (тестирование):
TestServer -->|9. Альтернатива для<br/>тестирования| Nginx
%% Styling
style Nginx fill:#2E8B57,color:#FFFFFF,stroke:#1a5f3a,stroke-width:2px
style Timer fill:#FFA500,color:#000000,stroke:#cc8400,stroke-width:2px
style Script fill:#FF8C00,color:#FFFFFF,stroke:#cc7000,stroke-width:2px
style TestServer fill:#DAA520,color:#000000,stroke:#b8860b,stroke-width:2px
style TokenStore fill:#9370DB,color:#FFFFFF,stroke:#6a4db8,stroke-width:2px
style TelegramBot fill:#4682B4,color:#FFFFFF,stroke:#1565c0,stroke-width:2px
style FlaskApp fill:#4169E1,color:#FFFFFF,stroke:#2a4ba8,stroke-width:2px
style HHAPI fill:#DC143C,color:#FFFFFF,stroke:#a00000,stroke-width:2px
- 🟢 Зелёный — инфраструктурные компоненты (nginx)
- 🟠 Оранжевый — автоматизация (systemd timer, Bash scripts)
- 🟡 Золотой — тестовые/вспомогательные инструменты (test-8000.py)
- 🟣 Фиолетовый — хранилище данных (Token Storage)
- 🔵 Синий — продакшен-приложение (Telegram Bot, Flask App)
- 🔴 Красный — внешние API (HeadHunter)
Детальная схема потока: см. docs/oauth-flow.md
apt update \&\& apt install -y nginx jq curl python3
git clone https://github.com/dob6bln9l/hh-oauth2-keendns-nginx-systemd.git
cd hh-oauth2-keendns-nginx-systemd
- Откройте https://dev.hh.ru/admin
- Создайте новое приложение
- Укажите Redirect URI:
https://your-domain.keenetic.pro/callback - Скопируйте Client ID и Client Secret
mkdir -p /opt/job-search/telegram-bot
cp .env.example /opt/job-search/telegram-bot/.env
nano /opt/job-search/telegram-bot/.env \# Заполните реальными значениями
chmod 600 /opt/job-search/telegram-bot/.env
Отредактируйте infra/nginx/nginx.conf:
- Замените
your-domain.keenetic.proна ваш реальный домен - Замените
192.168.1.100:8000на IP вашего backend-сервера
cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.backup
cp infra/nginx/nginx.conf /etc/nginx/nginx.conf
nginx -t \&\& systemctl reload nginx
mkdir -p /root/scripts
cp scripts/hh-token-refresh.sh /root/scripts/
chmod 700 /root/scripts/hh-token-refresh.sh
cp infra/systemd/hh-token-refresh.service /etc/systemd/system/
cp infra/systemd/hh-token-refresh.timer /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now hh-token-refresh.timer
systemctl status hh-token-refresh.timer
set -a; source /opt/job-search/telegram-bot/.env; set +a
echo "https://hh.ru/oauth/authorize?response_type=code\&client_id=${HH_CLIENT_ID}&redirect_uri=${HH_REDIRECT_URI}"
Откройте ссылку в браузере, получите code, затем:
CODE="ваш_код_из_браузера"
curl -sS -X POST https://hh.ru/oauth/token -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=authorization_code" -d "client_id=${HH_CLIENT_ID}" -d "client_secret=${HH_CLIENT_SECRET}" -d "code=${CODE}" -d "redirect_uri=${HH_REDIRECT_URI}" -o /var/lib/hh-token/token.json
chmod 600 /var/lib/hh-token/token.json
ACCESS_TOKEN=\$(jq -r .access_token /var/lib/hh-token/token.json)
curl -sS -H "Authorization: Bearer \${ACCESS_TOKEN}" https://api.hh.ru/me | jq '{id, first_name, last_name}'
scripts/test-8000.py — минимальный HTTP-сервер для проверки инфраструктуры без запуска продакшен-приложения:
✅ Что проверяет:
- Работу nginx reverse-proxy
- Маршрутизацию через KeenDNS Cloud
- Обработку OAuth callback (endpoint
/callback) - Доступность через внешний домен и локально
❌ Что НЕ делает:
- Не обрабатывает бизнес-логику
- Не интегрируется с Telegram
- Не работает с базой данных
- Не реализует функционал продакшен-приложения
# В отдельном терминале
python3 scripts/test-8000.py
Вывод:
Starting test server on http://0.0.0.0:8000
# Главная страница
curl -i -H "Host: your-domain.keenetic.pro" http://127.0.0.1/
# Ожидается: ✓ Server is running
# OAuth callback
curl -i "http://127.0.0.1:8000/callback?code=TEST123"
# Ожидается: ✓ Callback OK\nAuthorization code: TEST123
curl -i https://your-domain.keenetic.pro/
curl -i "https://your-domain.keenetic.pro/callback?code=TEST123"
# Логи nginx
tail -f /var/log/nginx/access.log
tail -f /var/log/nginx/oauth_callback.log
# Логи автообновления токенов
journalctl -t hh-token-refresh --since today -f
docker pull ghcr.io/do6pbln9l/hh-oauth2-infra:latest
docker run --rm -p 8000:8000 ghcr.io/do6pbln9l/hh-oauth2-infra:latest
docker pull ghcr.io/do6pbln9l/hh-oauth2-infra:v1.0.0
docker run --rm -p 8000:8000 ghcr.io/do6pbln9l/hh-oauth2-infra:v1.0.0
curl -i http://127.0.0.1:8000/
curl -i "http://127.0.0.1:8000/callback?code=TEST123"
Образ опубликован в GHCR: ghcr.io/do6pbln9l/hh-oauth2-infra
Если на вашем сервере Docker использует storage-driver vfs и блокирует запуск контейнеров, запустите образ на любой машине/VM с overlay2 или проверяйте через GitHub Actions.
После запуска контейнера или нативного развёртывания выполни базовую проверку работоспособности:
# Запусти контейнер
docker run -d --name hh-oauth2-test -p 8000:8000 ghcr.io/do6pbln9l/hh-oauth2-infra:latest
# Проверь главную страницу
curl -i http://localhost:8000/
# Ожидаемый результат: HTTP/1.1 200 OK
# Проверь callback эндпойнт
curl -i http://localhost:8000/callback
# Ожидаемый результат: HTTP/1.1 302 Found (редирект)
# Останови тестовый контейнер
docker stop hh-oauth2-test && docker rm hh-oauth2-test
# Проверь nginx
sudo nginx -t
# Проверь статус сервисов
sudo systemctl status hh-token-refresh.timer
sudo systemctl status nginx
# Проверь логи
sudo journalctl -u hh-token-refresh -n 20
- Главная страница (/) — убеждаемся, что nginx слушает и отдаёт контент
- OAuth callback (/callback) — проверяем редирект-логику для обмена authorization code
- Таймер обновления токенов — подтверждаем, что systemd timer запущен и работает по расписанию
- Логи — быстро находим ошибки конфигурации или проблемы с API HeadHunter
Примечание: Smoke-тест не заменяет полноценное тестирование, но быстро выявляет критичные проблемы развёртывания.
После успешной проверки инфраструктуры:
# Ctrl+C в терминале с test-8000.py
# Или найти процесс:
ps aux | grep test-8000
kill <PID>
Продакшен-приложение должно:
- ✅ Слушать порт 8000
- ✅ Обрабатывать endpoint /callback для OAuth
- ✅ Читать токены из /var/lib/hh-token/token.json
Пример запуска:
cd /opt/job-search/telegram-bot
python3 main.py \# Или ваша команда запуска приложения
Ваше приложение должно использовать эту инфраструктуру:
import json
# Чтение актуального токена
def get_access_token():
with open('/var/lib/hh-token/token.json') as f:
data = json.load(f)
return data['access_token']
# Использование в запросах
import requests
def get_hh_vacancies():
token = get_access_token()
headers = {'Authorization': f'Bearer {token}'}
response = requests.get('https://api.hh.ru/vacancies', headers=headers)
return response.json()
Обработка истечения токена:
При получении 401 от HH API — перечитайте token.json (скрипт автообновления мог обновить токен) и повторите запрос.
.
├── README.md \# Этот файл
├── LICENSE \# MIT License
├── .env.example \# Шаблон переменных окружения
├── .gitignore \# Исключения для git
├── infra/
│ ├── nginx/
│ │ └── nginx.conf \# Конфигурация reverse-proxy
│ └── systemd/
│ ├── hh-token-refresh.service \# systemd unit для обновления токена
│ └── hh-token-refresh.timer \# Таймер (ежедневно в 17:00)
├── scripts/
│ ├── hh-token-refresh.sh \# Скрипт автообновления токена
│ └── test-8000.py \# Тестовый HTTP-сервер
└── docs/
└── oauth-flow.md \# Детальное описание OAuth2 flow
- Секреты хранятся в
/opt/job-search/telegram-bot/.envс правами600 - Токены хранятся в
/var/lib/hh-token/token.jsonс правами600 - Скрипт обновления использует
loggerбез вывода секретов - nginx передаёт
X-Forwarded-Proto: httpsдля корректной работы за SSL termination .gitignoreисключает файлы с секретами и токенами
НИКОГДА не коммитьте:
.envс реальными секретамиtoken.jsonс access/refresh tokens- Файлы с приватными ключами (*.pem, *.key)
Для продакшена добавьте:
- fail2ban для защиты от брутфорса
- nginx rate limiting (
limit_req_zone) - Firewall правила для ограничения доступа к backend
- Мониторинг логов через Prometheus/Grafana
В публичном репозитории используются плейсхолдеры вместо реальных доменов/IP:
- Минимизация поверхности атаки — не раскрываем действующую инфраструктуру
- Универсальность — каждый пользователь подставляет свои значения
- Проверяемость — документация и скрипты доказывают работоспособность
Authorization code одноразовый. Получите новый через браузер.
Убедитесь, что HH_REDIRECT_URI в .env точно совпадает с настройками на dev.hh.ru.
Проверьте, что backend слушает порт 8000:
ss -tlnp | grep :8000
systemctl status hh-token-refresh.timer
journalctl -u hh-token-refresh.service -n 50 -e
curl должен быть одной строкой или с обратными слешами \:
curl -sS -X POST https://hh.ru/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "client_id=${HH_CLIENT_ID}" \
-d "client_secret=${HH_CLIENT_SECRET}" \
-d "code=${CODE}" \
-d "redirect_uri=${HH_REDIRECT_URI}" \
-o /var/lib/hh-token/token.json
systemctl status hh-token-refresh.timer
systemctl list-timers | grep hh-token-refresh
journalctl -t hh-token-refresh -n 50 -e
journalctl -t hh-token-refresh --since today
systemctl start hh-token-refresh.service
journalctl -u hh-token-refresh.service -n 20 -e
- HeadHunter API Docs
- HeadHunter Developer Console
- OAuth2 RFC 6749
- nginx Reverse Proxy Guide
- systemd.timer Manual
MIT License — см. LICENSE
Александр Добрынин / Aleksandr Dobrynin
- GitHub: @dob6bln9l
⭐ Этот проект демонстрирует:
- Настройку OAuth2 Authorization Code Flow
- Reverse-proxy nginx с SSL termination через KeenDNS Cloud
- Автоматизацию ротации токенов через systemd timers
- Bash-скриптинг с безопасным хранением секретов
- Разделение инфраструктуры и бизнес-логики
Все заметные изменения фиксируются в CHANGELOG.md