Pro
Установка с использованием docker на RedOS
Полная документация к скрипту pro.sh — Установка и настройка Questionnaire Studio Pro (QSPRO)
Общее описание системы
Данный Скрипт — это автоматизированный установщик для комплексного решения Questionnaire Studio Pro (QSPRO), в Debian-подобных системах, предназначенного для создания, запуска и анализа опросов и анкет.
Система включает:
Веб-приложение QSPRO (на .NET) — основной фронтенд и бэкенд
PostgreSQL — основная СУБД
PgBouncer — пуллер соединений к PostgreSQL
Caddy — обратный прокси-сервер
JupyterHub — среда для аналитики и Python/R/SQL
Cronicle — планировщик задач (аналог cron)
Noсodb — Low-code платформа для визуального редактирования и загрузки данных в postgres
Memcached — общее key-value хранилище
Все компоненты запускаются через Docker и Docker Compose, что обеспечивает изоляцию, масштабируемость и переносимость.
Обновите систему и Установите базовые утилиты:
dnf update -y
dnf install -y curl wget gnupg2
Установите docker и docker-compose:
dnf install -y jq docker docker-compose
systemctl enable --now docker
Минимальные требования к inotify:
fs.inotify.max_user_instances
fs.inotify.max_user_watches
fs.inotify.max_queued_events
вот так
nano /etc/sysctl.d/99-inotify.conf
вставить туда
fs.inotify.max_user_instances = 524288
fs.inotify.max_user_watches = 1048576
fs.inotify.max_queued_events = 163840
сохраняем настройку
sysctl --system
перезагрузка
reboot
проверка
cat /proc/sys/fs/inotify/max_user_instances
cat /proc/sys/fs/inotify/max_user_watches
cat /proc/sys/fs/inotify/max_queued_events
locales:
настройки locales должны соответствовать русской локали ru_RU.UTF-8
nano /etc/default/locale
вставить туда
LANG=ru_RU.UTF-8
LC_ALL=ru_RU.UTF-8
перезагрузить
reboot
Создайте рабочую директорию:
Это может быть как просто /opt так и любая другая специально выделенная директория , в которой скрипт будет создавать все необходимые файлы
Открытие портов (Опционально):
Если используется фаервол (ufw), разрешите порты:
sudo firewall-cmd --permanent --add-port=80/tcp
sudo firewall-cmd --permanent --add-port=443/tcp
sudo firewall-cmd --permanent --add-port=8080-8085/tcp
sudo firewall-cmd --permanent --add-port=81/tcp
sudo firewall-cmd --permanent --add-port=82/tcp
sudo firewall-cmd --reload
Запуск скрипта (пошаговая инструкция)
chmod +x pro.sh
./pro.sh
Скрипт должен запускаться от пользователя с sudo-доступом, так как он устанавливает пакеты и изменяет системные настройки.
Режимы установки
После запуска скрипт предложит выбрать режим:
Выберите режим:
1 - использовать существующие volumes (по умолчанию)
2 - удалить всё и поставить заново
Введите 1 или 2 [1]:
*при вводе пустого значения - будет считаться 1 режим
|
Режим 1: Использовать существующие данные (предпочтительно)
Сохраняются:
Базы данных
Конфигурации
Пользовательские файлы
Подходит для обновления или перезапуска
Скрипт прочитает .env, appsettings.json, пароли и т.д.
Режим 2: Полная переустановка
Удаляются:
Все Контейнеры
Все образы
Все волюмы
Папка questionnaire-studio/
Пользователь etl
Создаются:
Новые пароли
Новые SSH-ключи
Новые конфигурационные файлы
Подходит для чистого развертывания или тестирования
В режиме 2 все данные будут утеряны. Рекомендуется делать бэкап (описаны ниже).
|
Этапы работы скрипта
Этап 1: Проверка и установка зависимостей
Устанавливает curl, openssh-client, locales, jq (если отсутствуют)
Устанавливает локаль ru_RU.UTF-8 (важно для корректного отображения русского языка)
Если локаль не применяется, потребуется перезагрузка сервера.
|
Этап 2: Проверка системных требований
Проверяет наличие docker-compose
Проверяет доступ к docker.expasys.ru
Этап 3: Выбор версии QSPRO
Скрипт:
Запрашивает список тегов с https://docker.expasys.ru/v2/questionnaire-studio-pro/tags/list
Фильтрует теги по формату ГГГГ.ММ.ДД.НННН
Выводит список доступных версий
Предлагает выбрать:
Номер версии
L — для latest
Ввести вручную (например, 2024.10.01.1234)
Этап 4: Выбор локали и часового пояса
Язык: фиксированно ru_RU.UTF-8
Часовой пояс: выбор из списка (по умолчанию Europe/Moscow)
Этап 5: Генерация паролей
Автоматически генерируются пароли:
POSTGRES_PASSWORD — для пользователя postgres
QSPRO_PASSWORD — для пользователя qspro (приложение)
DWH_PASSWORD — для пользователя dwh (аналитика)
ETL_PASSWORD — для пользователя etl (JupyterHub)
Пароли генерируются как UUID без дефисов.
|
Этап 6: Генерация SSH-ключей
Ключи создаются в questionnaire-studio/expasys_keys/
Имя: expasys_ssh_rsa (приватный) и expasys_ssh_rsa.pub (публичный)
Используются для SSH-доступа от Cronicle к JupyterHub
Этап 7: Создание конфигурационных файлов
Скрипт генерирует:
.env — переменные окружения
docker-compose.yml — оркестрация сервисов
appsettings.json — настройки .NET-приложения
pgbouncer.ini, userlist.txt, postgresql.conf — настройки БД
Caddyfile — прокси-конфигурация
jupyterhub_config.py — конфигурация JupyterHub
requirements.txt — Python-пакеты для аналитики
Этап 8: Настройка inotify
Скрипт проверяет и устанавливает параметры ядра Linux:
fs.inotify.max_user_instances=524288
fs.inotify.max_user_watches=1048576
fs.inotify.max_queued_events=163840
Этап 9: Запуск контейнеров
Происходит поэтапно:
Скачивание образов (docker-compose pull)
Запуск db, pgbouncer, app-master — для миграций
Создание пользователей и БД
Настройка pg_hba.conf (scram-sha-256)
Запуск остальных сервисов: caddy, jupyterhub, cronicle, nocodb, воркеры
Этап 10: Настройка SSH-интеграции
Устанавливается openssh-server в jupyterhub
Настройка sshd_config
Копирование ключей
Тест SSH-подключения от cronicle к jupyterhub
Создание тестовой задачи в cronicle
Структура проекта после установки
./questionnaire-studio/
├── .env
├── appsettings.json
├── Caddyfile
├── cronicle_task.json (временный файл)
├── DirectoryPath/ (volume)
├── docker-compose.yml
├── docker.logs
├── Dockerfile.jupyterhub
├── Dockerfile.pgbouncer
├── Dockerfile.postgres
├── expasys_keys/
│ ├── expasys_ssh_rsa
│ └── expasys_ssh_rsa.pub
├── expasysbi_install.log
├── jupyterhub_config.py
├── pgbouncer.ini
├── postgresql.conf
├── requirements.txt
├── test_script.py (временный файл)
├── userlist.txt
├── version.app.txt
├── views/ (volume)
└── wwwroot/ (volume)
Доступ к сервисам
QSPRO (мастер) 8080 http://localhost:8080
QSPRO (воркер 1) 8081 http://localhost:8081
... ... ...
QSPRO (воркер 5) 8085 http://localhost:8085
Caddy (прокси) 80 http://localhost
JupyterHub 81 http://localhost:81
Cronicle 82 http://localhost:82
NoCode 6433 http://localhost:6433
Caddy перенаправляет / → мастер-нода, /bi → мастер и т.д.
|
Логи и диагностика:
Скрипт создает:
docker.logs — логи всех контейнеров
expasysbi_install.log — конфигурационные файлы и настройки
Команды для проверки:
docker-compose ps # статус контейнеров
docker-compose logs app-master # логи мастера
docker-compose logs pgbouncer # логи пула
Дополнительные возможности: SSH, Cronicle, JupyterHub
JupyterHub
Пользователь: etl
Пароль: смотри в файле /questionnaire-studio/.env в переменной ETL_PASSWORD
Доступ: http://localhost:81
Поддержка: Python, R, .NET, SQL (через jupysql)
Cronicle
Планировщик задач
Пользователь: admin
Пароль: admin
Может запускать Python-скрипты на JupyterHub через SSH
Интерфейс: http://localhost:82
SSH-интеграция
cronicle → jupyterhub через SSH-ключ
Позволяет автоматизировать ETL-процессы
узнать ip JupyterHub
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' jupyterhub
Секреты и безопасность
Где хранятся пароли:
.env — содержит все пароли
userlist.txt — пароли для pgbouncer
expasysbi_install.log — содержит пароли в открытом виде!
Не передавайте expasysbi_install.log по небезопасным каналам!
|
Рекомендации:
Удалите expasysbi_install.log после установки
Ограничьте доступ к папке questionnaire-studio/
Используйте .env в .gitignore, если в Git
Бэкапы
Создание бэкапа:
заходим в контейнер:
docker exec -it db bash
создаем бэкап:
pg_dump -U qspro -Fc expasys > expasys_docker_backup.dump
* он создастся в текущей директории докера
|
выходим из контейнера:
exit
копируем файл бэкапа из докера на хост-машину в "укромное место" (в моем случае это выглядит так):
docker cp db:/expasys_docker_backup.dump /opt/questionnaire-studio/expasys_docker_backup.dump
Восстановление базы данных из бэкапов
копируем из хостовой машины в контейнер db:
docker cp /opt/questionnaire-studio/expasys_docker_backup.dump db:/expasys_docker_backup.dump
заходим в контейнер:
docker exec -it db bash
восстанавливаем бд expasys из бэкап файла
pg_restore -U qspro -d expasys -v /expasys_docker_backup.dump
заходим и проверяем:
psql -U qspro -d expasys
\c expasys
\dt
Вольюмы
Все Волюмы находяться по стандартному пути пути:
/var/lib/docker/volumes/
Пример:
brw------- 1 root root 8, 1 авг 21 10:36 backingFsBlockDev
-rw------- 1 root root 65536 авг 21 11:40 metadata.db
drwx-----x 3 root root 4096 авг 21 11:40 questionnaire-studio_caddy_data
drwx-----x 3 root root 4096 авг 21 11:39 questionnaire-studio_data
drwx-----x 3 root root 4096 авг 21 11:40 questionnaire-studio_jupyterhub_config
drwx-----x 3 root root 4096 авг 21 11:40 questionnaire-studio_jupyterhub_data
drwx-----x 3 root root 4096 авг 21 11:40 questionnaire-studio_nocodb_data
drwx-----x 3 root root 4096 авг 21 11:39 questionnaire-studio_views
drwx-----x 3 root root 4096 авг 21 11:39 questionnaire-studio_wwwroot
Типичные ошибки и решения:
docker-compose not found Не установлен Установите docker-compose
curl: command not found Не установлен sudo apt install curl
pg_isready timeout Проблемы с PostgreSQL Проверьте docker logs db
SSH connection failed Ключи не совпадают Перезапустите в режиме 2
inotify limits too low Системные ограничения Увеличьте в /etc/sysctl.conf
appsettings.json not found Файл удален Скопируйте из контейнера или режим 2
user etl not found Пользователь удален Режим 2 или вручную создать
Caddyfile
Caddyfile находится в /questionnaire-studio/
классический выглядит так
http://redosdocker.bi.expasys.online {
handle /bi {
reverse_proxy http://app-master:5000
}
handle /Design/* {
reverse_proxy http://app-master:5000
}
handle /View/* {
reverse_proxy http://app-master:5000
}
handle /admin/bi {
reverse_proxy http://app-master:5000
}
handle /admin/event-journal {
reverse_proxy http://app-master:5000
}
handle /* {
reverse_proxy http://app-master:5000 http://app-worker1:5000 http://app-worker2:5000 http://app-worker3:5000 http://app-worker4:5000 http://app-worker5:5000 http://app-worker6:5000 http://app-worker7:5000 http://app-worker8:5000 http://app-worker9:5000 http://app-worker10:5000 {
lb_policy cookie custom-caddy-cookie b17f21741fca637d2f4ee4c441abc72639067a09d3320a7045055da13724292c688ebc83d3da38da6b6c9900e3bfcb739a470f9bff49e87eee13078a18452ba4
health_uri /about
health_interval 3s
health_timeout 5s
health_status 200
}
}
}
http://redosdocker.dwh.expasys.online {
reverse_proxy http://nocodb:8080
}
http://redosdocker.etl.expasys.online {
redir / /studio
reverse_proxy http://jupyterhub:8000 {
header_up Host {http.request.host}
header_up X-Real-IP {http.request.remote.host}
}
}
http://redosdocker.cron.expasys.online {
reverse_proxy http://cronicle:3012
}
https://redosdocker.bi.expasys.online {
# tls /etc/caddy/certs/fullchain.pem /etc/caddy/certs/privkey.pem
handle /bi {
reverse_proxy http://app-master:5000
}
handle /Design/* {
reverse_proxy http://app-master:5000
}
handle /View/* {
reverse_proxy http://app-master:5000
}
handle /admin/bi {
reverse_proxy http://app-master:5000
}
handle /admin/event-journal {
reverse_proxy http://app-master:5000
}
handle /* {
reverse_proxy http://app-master:5000 http://app-worker1:5000 http://app-worker2:5000 http://app-worker3:5000 http://app-worker4:5000 http://app-worker5:5000 http://app-worker6:5000 http://app-worker7:5000 http://app-worker8:5000 http://app-worker9:5000 http://app-worker10:5000 {
lb_policy cookie custom-caddy-cookie b17f21741fca637d2f4ee4c441abc72639067a09d3320a7045055da13724292c688ebc83d3da38da6b6c9900e3bfcb739a470f9bff49e87eee13078a18452ba4
health_uri /about
health_interval 3s
health_timeout 5s
health_status 200
}
}
}
https://redosdocker.dwh.expasys.online {
reverse_proxy http://nocodb:8080
}
https://redosdocker.etl.expasys.online {
redir / /studio
reverse_proxy http://jupyterhub:8000 {
header_up Host {http.request.host}
header_up X-Real-IP {http.request.remote.host}
}
}
https://redosdocker.cron.expasys.online {
reverse_proxy http://cronicle:3012
}
|
учтите что при готовых сертах (вдруг их принесли на флешке ) разкоментите строку, и положите их в нужный файл
# tls /etc/caddy/certs/fullchain.pem /etc/caddy/certs/privkey.pem
|
и DockerFile.yaml
- /opt/questionnaire-studio/certs/:/etc/caddy/selfsigned.crt:ro
- /opt/questionnaire-studio/certs/:/etc/caddy/selfsigned.key:ro
|
кладите серты сюда:
/questionnaire-studio/certs/
/questionnaire-studio/certs/
|
или сюда в зависимости от системы (иногда кадди не понимает что это папки а не файлы поэтому первый вариант предпочтительней)
/questionnaire-studio/certs/selfsigned.key/
/questionnaire-studio/certs/selfsigned.crt/
|
Если не хотите тратить сертификаты , и запускать все на локалке
{
auto_https off
}
:80 {
handle /* {
reverse_proxy http://app-master:5000 http://app-worker1:5000 http://app-worker2:5000 http://app-worker3:5000 http://app-worker4:5000 http://app-worker5:5000 {
lb_policy cookie custom-caddy-cookie b17f21741fca637d2f4ee4c441abc72639067a09d3320a7045055da13724292c688ebc83d3da38da6b6c9900e3bfcb739a470f9bff49e87eee13078a18452ba4 {
fallback round_robin
}
health_uri /about
health_interval 3s
health_timeout 5s
health_status 200
}
}
handle /bi {
reverse_proxy http://app-master:5000
}
handle /Design/* {
reverse_proxy http://app-master:5000
}
handle /View/* {
reverse_proxy http://app-master:5000
}
handle /admin/bi {
reverse_proxy http://app-master:5000
}
handle /admin/event-journal {
reverse_proxy http://app-master:5000
}
}
:6433 {
reverse_proxy http://nocodb:8080
}
:81 {
redir / /studio
reverse_proxy http://jupyterhub:8000 {
header_up Host {http.request.host}
header_up X-Real-IP {http.request.remote.host}
}
}
:82 {
reverse_proxy http://cronicle:3012
}
|
в данном варианте redosdocker , в кадди меняется на :

