Compare commits
2 Commits
3aba59ef84
...
34c1d8446a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34c1d8446a | ||
|
|
ecd95c5698 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,6 +8,7 @@ dist
|
|||||||
!.env.example
|
!.env.example
|
||||||
docker-compose.override.yml
|
docker-compose.override.yml
|
||||||
.idp-install.json
|
.idp-install.json
|
||||||
|
.idp-infra.secrets
|
||||||
nginx/certs/*
|
nginx/certs/*
|
||||||
!nginx/certs/.gitkeep
|
!nginx/certs/.gitkeep
|
||||||
nginx/conf.d/*.conf
|
nginx/conf.d/*.conf
|
||||||
|
|||||||
@@ -358,7 +358,6 @@ export default function LoginPage() {
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<ProjectTagline className="mt-9 text-center text-sm font-semibold" />
|
<ProjectTagline className="mt-9 text-center text-sm font-semibold" />
|
||||||
<p className="mt-2 text-center text-sm font-bold">Узнать больше</p>
|
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -69,10 +69,6 @@ export default function RegisterPage() {
|
|||||||
{isSubmitting ? 'Отправляем...' : 'Получить код'}
|
{isSubmitting ? 'Отправляем...' : 'Получить код'}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
<div className="mt-5 flex items-start gap-3 rounded-[22px] bg-[#2a2c36] p-4 text-sm">
|
|
||||||
<ShieldCheck className="mt-0.5 h-5 w-5 text-[#8ec5ff]" />
|
|
||||||
<p>Первый зарегистрированный пользователь автоматически станет суперадминистратором.</p>
|
|
||||||
</div>
|
|
||||||
<Button asChild variant="ghost" className="mt-5 w-full text-white hover:bg-[#2a2c36]">
|
<Button asChild variant="ghost" className="mt-5 w-full text-white hover:bg-[#2a2c36]">
|
||||||
<Link href="/auth/login">У меня уже есть ID</Link>
|
<Link href="/auth/login">У меня уже есть ID</Link>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -48,9 +48,7 @@ export function Sidebar({ active }: { active: string }) {
|
|||||||
<LogOut className="h-3.5 w-3.5" />
|
<LogOut className="h-3.5 w-3.5" />
|
||||||
Выйти
|
Выйти
|
||||||
</button>
|
</button>
|
||||||
<p>Русский</p>
|
<p>© 2026</p>
|
||||||
<p>Справка</p>
|
|
||||||
<p>© 2026 Lendry</p>
|
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ RUN npm --workspace @lendry/sso-core run prisma:generate \
|
|||||||
&& npm --workspace @lendry/sso-core run proto:generate \
|
&& npm --workspace @lendry/sso-core run proto:generate \
|
||||||
&& npm --workspace @lendry/sso-core run build
|
&& npm --workspace @lendry/sso-core run build
|
||||||
|
|
||||||
|
COPY apps/sso-core/docker-entrypoint.sh /app/apps/sso-core/docker-entrypoint.sh
|
||||||
|
RUN chmod +x /app/apps/sso-core/docker-entrypoint.sh
|
||||||
|
|
||||||
EXPOSE 3001 50051
|
EXPOSE 3001 50051
|
||||||
|
|
||||||
CMD ["sh", "-c", "npm --workspace @lendry/sso-core run prisma:push && node apps/sso-core/dist/main.js"]
|
CMD ["/app/apps/sso-core/docker-entrypoint.sh"]
|
||||||
|
|||||||
23
apps/sso-core/docker-entrypoint.sh
Normal file
23
apps/sso-core/docker-entrypoint.sh
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "[sso-core] Применение схемы БД (Prisma)..."
|
||||||
|
attempt=0
|
||||||
|
max_attempts=30
|
||||||
|
|
||||||
|
while [ "$attempt" -lt "$max_attempts" ]; do
|
||||||
|
attempt=$((attempt + 1))
|
||||||
|
if npm --workspace @lendry/sso-core run prisma:push; then
|
||||||
|
echo "[sso-core] Схема БД синхронизирована"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
if [ "$attempt" -ge "$max_attempts" ]; then
|
||||||
|
echo "[sso-core] ОШИБКА: не удалось подключиться к PostgreSQL после ${max_attempts} попыток." >&2
|
||||||
|
echo "[sso-core] Проверьте POSTGRES_* в .env и пароль существующего volume postgres_data." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "[sso-core] PostgreSQL недоступен или неверный пароль, повтор ${attempt}/${max_attempts}..."
|
||||||
|
sleep 3
|
||||||
|
done
|
||||||
|
|
||||||
|
exec node apps/sso-core/dist/main.js
|
||||||
@@ -110,10 +110,11 @@ services:
|
|||||||
ldap-auth:
|
ldap-auth:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "node -e \"fetch('http://localhost:3001/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\""]
|
test: ["CMD-SHELL", "node -e \"fetch('http://127.0.0.1:3001/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\""]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 10s
|
||||||
retries: 10
|
retries: 12
|
||||||
|
start_period: 120s
|
||||||
|
|
||||||
api-gateway:
|
api-gateway:
|
||||||
pull_policy: build
|
pull_policy: build
|
||||||
@@ -143,10 +144,11 @@ services:
|
|||||||
minio:
|
minio:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "node -e \"fetch('http://localhost:3000/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\""]
|
test: ["CMD-SHELL", "node -e \"fetch('http://127.0.0.1:3000/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\""]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 10s
|
||||||
retries: 10
|
retries: 12
|
||||||
|
start_period: 60s
|
||||||
|
|
||||||
media-ws:
|
media-ws:
|
||||||
pull_policy: build
|
pull_policy: build
|
||||||
|
|||||||
139
install.sh
139
install.sh
@@ -22,6 +22,7 @@ LOCAL_CONF_DIR="${ROOT_DIR}/nginx/conf.d"
|
|||||||
LOCAL_CERTS_DIR="${ROOT_DIR}/nginx/certs"
|
LOCAL_CERTS_DIR="${ROOT_DIR}/nginx/certs"
|
||||||
COMPOSE_OVERRIDE="${ROOT_DIR}/docker-compose.override.yml"
|
COMPOSE_OVERRIDE="${ROOT_DIR}/docker-compose.override.yml"
|
||||||
STATE_FILE="${ROOT_DIR}/.idp-install.json"
|
STATE_FILE="${ROOT_DIR}/.idp-install.json"
|
||||||
|
INFRA_SECRETS_FILE="${ROOT_DIR}/.idp-infra.secrets"
|
||||||
|
|
||||||
NON_INTERACTIVE=0
|
NON_INTERACTIVE=0
|
||||||
FORCE_ENV="${FORCE_ENV:-0}"
|
FORCE_ENV="${FORCE_ENV:-0}"
|
||||||
@@ -62,6 +63,7 @@ ${BOLD}Lendry ID v${SCRIPT_VERSION}${NC} — установка и управл
|
|||||||
./install.sh --renew-ssl Обновить сертификаты Let's Encrypt
|
./install.sh --renew-ssl Обновить сертификаты Let's Encrypt
|
||||||
./install.sh --restart Перезапуск контейнеров
|
./install.sh --restart Перезапуск контейнеров
|
||||||
./install.sh --status Статус сервисов
|
./install.sh --status Статус сервисов
|
||||||
|
./install.sh --reset-db Сброс PostgreSQL (новый пароль из .env, данные удалятся)
|
||||||
./install.sh --remove-nginx Удалить конфиги Nginx IdP
|
./install.sh --remove-nginx Удалить конфиги Nginx IdP
|
||||||
|
|
||||||
${BOLD}Режим локальной сети (без интернета / AD-домены .local .lpr):${NC}
|
${BOLD}Режим локальной сети (без интернета / AD-домены .local .lpr):${NC}
|
||||||
@@ -85,7 +87,7 @@ ${BOLD}Прочие опции:${NC}
|
|||||||
-h, --help Справка
|
-h, --help Справка
|
||||||
|
|
||||||
${BOLD}Переменные окружения:${NC}
|
${BOLD}Переменные окружения:${NC}
|
||||||
FORCE_ENV=1 Пересоздать .env (секреты будут новые)
|
FORCE_ENV=1 Новые JWT-секреты (пароли БД/MinIO/RabbitMQ сохраняются если volume уже есть)
|
||||||
OFFLINE=1 То же, что --offline
|
OFFLINE=1 То же, что --offline
|
||||||
|
|
||||||
${BOLD}Примеры:${NC}
|
${BOLD}Примеры:${NC}
|
||||||
@@ -117,7 +119,7 @@ parse_args() {
|
|||||||
--renew-ssl) ACTION="renew-ssl"; shift ;;
|
--renew-ssl) ACTION="renew-ssl"; shift ;;
|
||||||
--restart) ACTION="restart"; shift ;;
|
--restart) ACTION="restart"; shift ;;
|
||||||
--status) ACTION="status"; shift ;;
|
--status) ACTION="status"; shift ;;
|
||||||
--remove-nginx) ACTION="remove-nginx"; shift ;;
|
--reset-db) ACTION="reset-db"; shift ;;
|
||||||
--local) INSTALL_LOCAL=1; shift ;;
|
--local) INSTALL_LOCAL=1; shift ;;
|
||||||
--intranet) INSTALL_INTRANET=1; shift ;;
|
--intranet) INSTALL_INTRANET=1; shift ;;
|
||||||
--offline) OFFLINE=1; shift ;;
|
--offline) OFFLINE=1; shift ;;
|
||||||
@@ -637,17 +639,139 @@ write_env_secrets_if_missing() {
|
|||||||
cp .env.example .env 2>/dev/null || touch .env
|
cp .env.example .env 2>/dev/null || touch .env
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$FORCE_ENV" == "1" ]] || [[ "$(env_get POSTGRES_PASSWORD change-me-postgres)" == "change-me-postgres" ]]; then
|
load_infra_secrets_into_env
|
||||||
|
|
||||||
|
if docker_volume_exists postgres_data; then
|
||||||
|
if [[ -n "$(env_get POSTGRES_PASSWORD "")" ]]; then
|
||||||
|
warn "Volume PostgreSQL уже существует — POSTGRES_PASSWORD в .env не меняем"
|
||||||
|
else
|
||||||
|
fail "Volume PostgreSQL существует, но POSTGRES_PASSWORD не найден. Восстановите .env или выполните: ./install.sh --reset-db"
|
||||||
|
fi
|
||||||
|
pg_pass="$(env_get POSTGRES_PASSWORD "$pg_pass")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if docker_volume_exists rabbitmq_data; then
|
||||||
|
warn "Volume RabbitMQ уже существует — RABBITMQ_DEFAULT_PASS не меняем"
|
||||||
|
rmq_pass="$(env_get RABBITMQ_DEFAULT_PASS "$rmq_pass")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if docker_volume_exists minio_data; then
|
||||||
|
warn "Volume MinIO уже существует — MINIO_ROOT_PASSWORD не меняем"
|
||||||
|
minio_pass="$(env_get MINIO_ROOT_PASSWORD "$minio_pass")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local need_secrets=0
|
||||||
|
if [[ "$FORCE_ENV" == "1" ]]; then
|
||||||
|
need_secrets=1
|
||||||
|
elif [[ "$(env_get POSTGRES_PASSWORD change-me-postgres)" == "change-me-postgres" ]]; then
|
||||||
|
need_secrets=1
|
||||||
|
elif [[ "$(env_get JWT_ACCESS_SECRET change-me-access-secret)" == "change-me-access-secret" ]]; then
|
||||||
|
need_secrets=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$need_secrets" -eq 1 ]]; then
|
||||||
env_set POSTGRES_PASSWORD "$pg_pass"
|
env_set POSTGRES_PASSWORD "$pg_pass"
|
||||||
env_set RABBITMQ_DEFAULT_PASS "$rmq_pass"
|
env_set RABBITMQ_DEFAULT_PASS "$rmq_pass"
|
||||||
env_set MINIO_ROOT_PASSWORD "$minio_pass"
|
env_set MINIO_ROOT_PASSWORD "$minio_pass"
|
||||||
env_set JWT_ACCESS_SECRET "$jwt_access"
|
env_set JWT_ACCESS_SECRET "$jwt_access"
|
||||||
env_set JWT_REFRESH_SECRET "$jwt_refresh"
|
env_set JWT_REFRESH_SECRET "$jwt_refresh"
|
||||||
env_set DATA_ENCRYPTION_KEY "$data_key"
|
env_set DATA_ENCRYPTION_KEY "$data_key"
|
||||||
ok "Секреты сгенерированы"
|
save_infra_secrets
|
||||||
|
ok "Секреты сгенерированы / синхронизированы"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compose_project_name() {
|
||||||
|
load_env
|
||||||
|
echo "${COMPOSE_PROJECT_NAME:-lendry-id}"
|
||||||
|
}
|
||||||
|
|
||||||
|
docker_volume_name() {
|
||||||
|
echo "$(compose_project_name)_$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
docker_volume_exists() {
|
||||||
|
docker volume inspect "$(docker_volume_name "$1")" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
save_infra_secrets() {
|
||||||
|
load_env
|
||||||
|
cat >"$INFRA_SECRETS_FILE" <<EOF
|
||||||
|
POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-}
|
||||||
|
RABBITMQ_DEFAULT_PASS=${RABBITMQ_DEFAULT_PASS:-}
|
||||||
|
MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD:-}
|
||||||
|
EOF
|
||||||
|
chmod 600 "$INFRA_SECRETS_FILE" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
load_infra_secrets_into_env() {
|
||||||
|
[[ -f "$INFRA_SECRETS_FILE" ]] || return 0
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
set -a && source "$INFRA_SECRETS_FILE" && set +a
|
||||||
|
[[ -n "${POSTGRES_PASSWORD:-}" ]] && env_set POSTGRES_PASSWORD "$POSTGRES_PASSWORD"
|
||||||
|
[[ -n "${RABBITMQ_DEFAULT_PASS:-}" ]] && env_set RABBITMQ_DEFAULT_PASS "$RABBITMQ_DEFAULT_PASS"
|
||||||
|
[[ -n "${MINIO_ROOT_PASSWORD:-}" ]] && env_set MINIO_ROOT_PASSWORD "$MINIO_ROOT_PASSWORD"
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_postgres_auth() {
|
||||||
|
load_env
|
||||||
|
load_infra_secrets_into_env
|
||||||
|
[[ -f .env ]] || return 0
|
||||||
|
docker_cmd compose ps postgres 2>/dev/null | grep -q postgres || return 0
|
||||||
|
|
||||||
|
local user pass db
|
||||||
|
user="$(env_get POSTGRES_USER lendry)"
|
||||||
|
pass="$(env_get POSTGRES_PASSWORD "")"
|
||||||
|
db="$(env_get POSTGRES_DB lendry_id)"
|
||||||
|
[[ -n "$pass" ]] || return 0
|
||||||
|
|
||||||
|
if docker_cmd compose exec -T -e PGPASSWORD="$pass" postgres \
|
||||||
|
psql -U "$user" -d "$db" -c 'SELECT 1' >/dev/null 2>&1; then
|
||||||
|
ok "PostgreSQL: пароль из .env подходит"
|
||||||
|
save_infra_secrets
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
fail "PostgreSQL: пароль в .env не совпадает с данными в volume (ошибка P1000).
|
||||||
|
|
||||||
|
Это происходит после FORCE_ENV=1 или смены POSTGRES_PASSWORD при существующей БД.
|
||||||
|
|
||||||
|
Решения:
|
||||||
|
1) Сбросить БД (удалит всех пользователей и данные):
|
||||||
|
./install.sh --reset-db --yes && ./install.sh --restart
|
||||||
|
|
||||||
|
2) Вернуть старый POSTGRES_PASSWORD в .env (если сохранили)
|
||||||
|
|
||||||
|
3) Если есть файл ${INFRA_SECRETS_FILE} — скопируйте пароль оттуда в .env"
|
||||||
|
}
|
||||||
|
|
||||||
|
action_reset_db() {
|
||||||
|
load_env
|
||||||
|
local vol
|
||||||
|
vol="$(docker_volume_name postgres_data)"
|
||||||
|
|
||||||
|
if [[ "$NON_INTERACTIVE" -ne 1 ]]; then
|
||||||
|
echo ""
|
||||||
|
warn "Будет удалён volume ${vol} — все пользователи, сессии и настройки в PostgreSQL пропадут."
|
||||||
|
read -r -p "Продолжить? [y/N]: " confirm
|
||||||
|
[[ "$confirm" =~ ^[Yy]$ ]] || fail "Отменено"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Остановка сервисов..."
|
||||||
|
docker_cmd compose --env-file .env --profile proxy down 2>/dev/null || docker_cmd compose --env-file .env down 2>/dev/null || true
|
||||||
|
|
||||||
|
log "Удаление ${vol}..."
|
||||||
|
if docker volume inspect "$vol" >/dev/null 2>&1; then
|
||||||
|
docker volume rm "$vol" || fail "Не удалось удалить ${vol}"
|
||||||
|
else
|
||||||
|
warn "Volume ${vol} не найден — возможно, уже удалён"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ok "PostgreSQL volume удалён. PostgreSQL создастся заново с паролем из .env."
|
||||||
|
save_infra_secrets
|
||||||
|
}
|
||||||
|
|
||||||
persist_domain_env() {
|
persist_domain_env() {
|
||||||
write_env_secrets_if_missing
|
write_env_secrets_if_missing
|
||||||
|
|
||||||
@@ -1319,6 +1443,7 @@ deploy_stack() {
|
|||||||
|
|
||||||
write_compose_override
|
write_compose_override
|
||||||
load_env
|
load_env
|
||||||
|
verify_postgres_auth
|
||||||
|
|
||||||
local compose_cmd=(compose --env-file .env)
|
local compose_cmd=(compose --env-file .env)
|
||||||
if [[ -n "$(env_get DOMAIN_API "")" && "$(env_get INSTALL_MODE local)" != "local" ]]; then
|
if [[ -n "$(env_get DOMAIN_API "")" && "$(env_get INSTALL_MODE local)" != "local" ]]; then
|
||||||
@@ -1328,6 +1453,7 @@ deploy_stack() {
|
|||||||
log "Сборка и запуск контейнеров (10–20 минут при первом запуске)..."
|
log "Сборка и запуск контейнеров (10–20 минут при первом запуске)..."
|
||||||
docker_cmd "${compose_cmd[@]}" up -d --build
|
docker_cmd "${compose_cmd[@]}" up -d --build
|
||||||
|
|
||||||
|
wait_for_service "sso-core" 120
|
||||||
wait_for_service "api-gateway" 90
|
wait_for_service "api-gateway" 90
|
||||||
wait_for_service "frontend" 60
|
wait_for_service "frontend" 60
|
||||||
wait_for_service "docs" 60
|
wait_for_service "docs" 60
|
||||||
@@ -1494,11 +1620,12 @@ show_menu() {
|
|||||||
echo " 4) Перезапуск / пересборка контейнеров"
|
echo " 4) Перезапуск / пересборка контейнеров"
|
||||||
echo " 5) Статус сервисов"
|
echo " 5) Статус сервисов"
|
||||||
echo " 6) Удалить конфиги Nginx IdP"
|
echo " 6) Удалить конфиги Nginx IdP"
|
||||||
|
echo " 7) Сброс PostgreSQL (если ошибка P1000 / пароль БД)"
|
||||||
echo " 0) Выход"
|
echo " 0) Выход"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
local choice
|
local choice
|
||||||
read -r -p "Выберите пункт [0-6]: " choice
|
read -r -p "Выберите пункт [0-7]: " choice
|
||||||
|
|
||||||
case "$choice" in
|
case "$choice" in
|
||||||
1) NON_INTERACTIVE=0; action_full_install ;;
|
1) NON_INTERACTIVE=0; action_full_install ;;
|
||||||
@@ -1507,6 +1634,7 @@ show_menu() {
|
|||||||
4) action_restart ;;
|
4) action_restart ;;
|
||||||
5) show_status ;;
|
5) show_status ;;
|
||||||
6) remove_nginx_configs; ok "Готово" ;;
|
6) remove_nginx_configs; ok "Готово" ;;
|
||||||
|
7) action_reset_db; action_restart ;;
|
||||||
0) exit 0 ;;
|
0) exit 0 ;;
|
||||||
*) warn "Неверный пункт" ;;
|
*) warn "Неверный пункт" ;;
|
||||||
esac
|
esac
|
||||||
@@ -1527,6 +1655,7 @@ main() {
|
|||||||
renew-ssl) action_renew_ssl ;;
|
renew-ssl) action_renew_ssl ;;
|
||||||
restart) action_restart ;;
|
restart) action_restart ;;
|
||||||
status) show_status ;;
|
status) show_status ;;
|
||||||
|
reset-db) action_reset_db ;;
|
||||||
remove-nginx) remove_nginx_configs ;;
|
remove-nginx) remove_nginx_configs ;;
|
||||||
"")
|
"")
|
||||||
if [[ -f .env ]] && [[ "$NON_INTERACTIVE" -ne 1 ]]; then
|
if [[ -f .env ]] && [[ "$NON_INTERACTIVE" -ne 1 ]]; then
|
||||||
|
|||||||
Reference in New Issue
Block a user