add custom cert install sh

This commit is contained in:
lendry
2026-06-26 08:41:19 +03:00
parent 4e78a81eb1
commit 0f1b360684
2 changed files with 213 additions and 14 deletions

View File

@@ -11,9 +11,14 @@ NGINX_MODE=auto
NGINX_HTTP_PORT=80
NGINX_HTTPS_PORT=443
# SSL: none (HTTP) | selfsigned (HTTPS локально) | letsencrypt (интернет)
# SSL: none (HTTP) | selfsigned (HTTPS локально) | custom (свои файлы) | letsencrypt (интернет)
SSL_TYPE=none
# Папка со своими сертификатами (для SSL_TYPE=custom); install.sh копирует файлы в nginx/certs/custom/
SSL_CERT_DIR=
SSL_CERT_FULLCHAIN=
SSL_CERT_KEY=
# Домены без протокола (install.sh подставит PUBLIC_* URL)
DOMAIN_API=
DOMAIN_FRONTEND=

View File

@@ -10,7 +10,7 @@
set -euo pipefail
SCRIPT_VERSION="2.2.4"
SCRIPT_VERSION="2.2.5"
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$ROOT_DIR"
@@ -20,6 +20,8 @@ NGINX_ENABLED="/etc/nginx/sites-enabled"
CERTBOT_WEBROOT="/var/www/certbot"
LOCAL_CONF_DIR="${ROOT_DIR}/nginx/conf.d"
LOCAL_CERTS_DIR="${ROOT_DIR}/nginx/certs"
CUSTOM_CERT_TARGET_DIR="${LOCAL_CERTS_DIR}/custom"
DEFAULT_CERT_UPLOAD_DIR="${LOCAL_CERTS_DIR}/upload"
COMPOSE_OVERRIDE="${ROOT_DIR}/docker-compose.override.yml"
STATE_FILE="${ROOT_DIR}/.idp-install.json"
INFRA_SECRETS_FILE="${ROOT_DIR}/.idp-infra.secrets"
@@ -42,6 +44,9 @@ CLI_DOMAIN_MINIO=""
CLI_DOMAIN_MINIO_CONSOLE=""
CLI_EMAIL=""
CLI_SSL_TYPE=""
CLI_CERT_DIR=""
CLI_CERT_FULLCHAIN=""
CLI_CERT_KEY=""
CLI_NGINX_HTTP_PORT=""
CLI_NGINX_HTTPS_PORT=""
@@ -64,7 +69,7 @@ ${BOLD}Lendry ID v${SCRIPT_VERSION}${NC} — установка и управл
./install.sh Интерактивное меню
./install.sh --install Полная установка
./install.sh --configure-domains Изменить домены / Nginx / SSL
./install.sh --renew-ssl Обновить сертификаты Let's Encrypt
./install.sh --renew-ssl Обновить сертификаты (Let's Encrypt / custom / selfsigned)
./install.sh --restart Перезапуск контейнеров
./install.sh --docker-prune Только очистка Docker (без пересборки)
./install.sh --fix-proxy Исправить 502: host Nginx + порты 3000/3002/3003
@@ -74,7 +79,10 @@ ${BOLD}Lendry ID v${SCRIPT_VERSION}${NC} — установка и управл
${BOLD}Режим локальной сети (без интернета / AD-домены .local .lpr):${NC}
--intranet Локальная сеть: HTTP или самоподписанный HTTPS
--ssl TYPE none | selfsigned | letsencrypt (по умолчанию в --intranet: none)
--ssl TYPE none | selfsigned | custom | letsencrypt
--cert-dir PATH Папка со своими сертификатами (для --ssl custom)
--cert-fullchain FILE Файл сертификата в --cert-dir (по умолчанию: fullchain.pem / cert.pem)
--cert-key FILE Файл ключа в --cert-dir (по умолчанию: privkey.pem / key.pem)
--nginx-http-port PORT HTTP-порт Nginx (если 80 занят, например 8080)
--nginx-https-port PORT HTTPS-порт Nginx (если 443 занят, например 8443)
--offline Не скачивать Docker из интернета (только если уже установлен)
@@ -101,6 +109,12 @@ ${BOLD}Переменные окружения:${NC}
COMPOSE_PARALLEL_LIMIT=4 Параллельная сборка сервисов (по умолчанию 4)
${BOLD}Примеры:${NC}
./install.sh --intranet --install --yes \\
--api-domain api.idpmvk.lpr \\
--frontend-domain id.idpmvk.lpr \\
--docs-domain docs.idpmvk.lpr \\
--ssl custom --cert-dir /opt/idp-certs
./install.sh --intranet --install --yes \\
--api-domain api.idpmvk.lpr \\
--frontend-domain id.idpmvk.lpr \\
@@ -138,6 +152,9 @@ parse_args() {
--intranet) INSTALL_INTRANET=1; shift ;;
--offline) OFFLINE=1; shift ;;
--ssl) CLI_SSL_TYPE="${2:-}"; shift 2 ;;
--cert-dir) CLI_CERT_DIR="${2:-}"; shift 2 ;;
--cert-fullchain) CLI_CERT_FULLCHAIN="${2:-}"; shift 2 ;;
--cert-key) CLI_CERT_KEY="${2:-}"; shift 2 ;;
--nginx-mode) NGINX_MODE="${2:-auto}"; shift 2 ;;
--nginx-http-port) CLI_NGINX_HTTP_PORT="${2:-}"; shift 2 ;;
--nginx-https-port) CLI_NGINX_HTTPS_PORT="${2:-}"; shift 2 ;;
@@ -192,7 +209,7 @@ preflight_checks() {
NGINX_MODE="docker"
fi
[[ -f docker-compose.yml ]] || fail "Запустите скрипт из корня репозитория (рядом с docker-compose.yml)"
mkdir -p "$LOCAL_CONF_DIR" "$LOCAL_CERTS_DIR"
mkdir -p "$LOCAL_CONF_DIR" "$LOCAL_CERTS_DIR" "$DEFAULT_CERT_UPLOAD_DIR"
}
detect_nginx_mode() {
@@ -726,7 +743,7 @@ derive_public_urls() {
fi
local scheme="http"
[[ "$ssl_type" == "letsencrypt" || "$ssl_type" == "selfsigned" ]] && scheme="https"
[[ "$ssl_type" == "letsencrypt" || "$ssl_type" == "selfsigned" || "$ssl_type" == "custom" ]] && scheme="https"
PUBLIC_API_URL="${scheme}://${DOMAIN_API}"
PUBLIC_FRONTEND_URL="${scheme}://${DOMAIN_FRONTEND}"
@@ -760,7 +777,7 @@ derive_public_urls() {
if [[ "$INSTALL_LOCAL" -eq 1 ]]; then
INSTALL_MODE="local"
elif [[ "$INSTALL_INTRANET" -eq 1 || "$ssl_type" == "none" || "$ssl_type" == "selfsigned" ]]; then
elif [[ "$INSTALL_INTRANET" -eq 1 || "$ssl_type" == "none" || "$ssl_type" == "selfsigned" || "$ssl_type" == "custom" ]]; then
INSTALL_MODE="intranet"
elif [[ -n "${DOMAIN_API:-}" ]]; then
INSTALL_MODE="production"
@@ -803,14 +820,15 @@ choose_ssl_type_interactive() {
echo " 1) HTTP — локальная сеть, без сертификатов (http://api.idpmvk.lpr)"
echo " 2) HTTPS самоподписанный — локальная сеть, браузер покажет предупреждение"
echo " 3) HTTPS Let's Encrypt — нужен интернет и публичный DNS"
echo " 4) HTTPS свои сертификаты — загрузите fullchain.pem и privkey.pem в папку на сервере"
echo ""
local ssl_choice
if [[ "$INSTALL_INTRANET" -eq 1 ]]; then
read -r -p "Выберите [1-3] [1]: " ssl_choice
read -r -p "Выберите [1-4] [1]: " ssl_choice
ssl_choice="${ssl_choice:-1}"
else
read -r -p "Выберите [1-3] [3]: " ssl_choice
read -r -p "Выберите [1-4] [3]: " ssl_choice
ssl_choice="${ssl_choice:-3}"
fi
@@ -818,6 +836,7 @@ choose_ssl_type_interactive() {
1) SSL_TYPE="none" ;;
2) SSL_TYPE="selfsigned" ;;
3) SSL_TYPE="letsencrypt" ;;
4) SSL_TYPE="custom" ;;
*) SSL_TYPE="$default" ;;
esac
}
@@ -899,10 +918,14 @@ configure_domains_interactive() {
choose_ssl_type_interactive "$(env_get SSL_TYPE none)"
case "$SSL_TYPE" in
none | selfsigned | letsencrypt) ;;
*) fail "Некорректный SSL_TYPE: $SSL_TYPE (none | selfsigned | letsencrypt)" ;;
none | selfsigned | custom | letsencrypt) ;;
*) fail "Некорректный SSL_TYPE: $SSL_TYPE (none | selfsigned | custom | letsencrypt)" ;;
esac
if [[ "$SSL_TYPE" == "custom" ]]; then
prompt_custom_cert_settings
fi
if [[ "$SSL_TYPE" == "letsencrypt" ]]; then
if [[ "$INSTALL_INTRANET" -eq 1 && "$NON_INTERACTIVE" -ne 1 ]]; then
warn "Let's Encrypt требует доступ из интернета. Для чистой локальной сети выберите HTTP или selfsigned."
@@ -1082,6 +1105,9 @@ persist_domain_env() {
env_set PUBLIC_WS_URL "${PUBLIC_WS_URL}"
env_set USE_NGINX_SSL "${USE_NGINX_SSL:-false}"
env_set CERTBOT_EMAIL "${CERTBOT_EMAIL:-}"
env_set SSL_CERT_DIR "${SSL_CERT_DIR:-$(env_get SSL_CERT_DIR "")}"
env_set SSL_CERT_FULLCHAIN "${SSL_CERT_FULLCHAIN:-$(env_get SSL_CERT_FULLCHAIN "")}"
env_set SSL_CERT_KEY "${SSL_CERT_KEY:-$(env_get SSL_CERT_KEY "")}"
env_set MINIO_PUBLIC_ENDPOINT "${MINIO_PUBLIC_ENDPOINT:-localhost:9000}"
env_set MINIO_USE_SSL "${MINIO_USE_SSL:-false}"
@@ -1335,6 +1361,12 @@ nginx_cert_base_path() {
else
echo "${LOCAL_CERTS_DIR}/shared-intranet"
fi
elif [[ "$ssl_type" == "custom" ]]; then
if [[ "$NGINX_MODE" == "docker" ]]; then
echo "/etc/nginx/certs/custom"
else
echo "${LOCAL_CERTS_DIR}/custom"
fi
elif [[ "$NGINX_MODE" == "docker" ]]; then
echo "/etc/nginx/certs/${domain}"
else
@@ -1371,6 +1403,150 @@ collect_self_signed_sans() {
(IFS=,; echo "${sans[*]}")
}
find_custom_cert_file() {
local dir="$1"
local preferred="${2:-}"
shift 2 || true
local -a fallbacks=("$@")
local name
if [[ -n "$preferred" ]]; then
[[ -f "${dir}/${preferred}" ]] || fail "Файл не найден: ${dir}/${preferred}"
echo "${dir}/${preferred}"
return 0
fi
for name in "${fallbacks[@]}"; do
if [[ -f "${dir}/${name}" ]]; then
echo "${dir}/${name}"
return 0
fi
done
return 1
}
resolve_custom_cert_source_dir() {
if [[ -n "$CLI_CERT_DIR" ]]; then
SSL_CERT_DIR="$CLI_CERT_DIR"
elif [[ -n "${SSL_CERT_DIR:-}" ]]; then
:
elif [[ -n "$(env_get SSL_CERT_DIR "")" ]]; then
SSL_CERT_DIR="$(env_get SSL_CERT_DIR "")"
elif [[ -d "$DEFAULT_CERT_UPLOAD_DIR" ]]; then
SSL_CERT_DIR="$DEFAULT_CERT_UPLOAD_DIR"
fi
}
prompt_custom_cert_settings() {
resolve_custom_cert_source_dir
local default_dir="${SSL_CERT_DIR:-$DEFAULT_CERT_UPLOAD_DIR}"
local default_fullchain="${CLI_CERT_FULLCHAIN:-$(env_get SSL_CERT_FULLCHAIN "")}"
local default_key="${CLI_CERT_KEY:-$(env_get SSL_CERT_KEY "")}"
if [[ "$NON_INTERACTIVE" -eq 1 ]]; then
SSL_CERT_DIR="${SSL_CERT_DIR:-$default_dir}"
SSL_CERT_FULLCHAIN="${SSL_CERT_FULLCHAIN:-$default_fullchain}"
SSL_CERT_KEY="${SSL_CERT_KEY:-$default_key}"
[[ -d "$SSL_CERT_DIR" ]] || fail "Папка с сертификатами не найдена: $SSL_CERT_DIR (укажите --cert-dir PATH)"
return 0
fi
echo ""
echo -e "${BOLD}Свои SSL-сертификаты:${NC}"
echo " Положите на сервер файлы сертификата и ключа, затем укажите папку."
echo " Поддерживаются имена: fullchain.pem / cert.pem и privkey.pem / key.pem"
echo " Рекомендуемая папка: ${DEFAULT_CERT_UPLOAD_DIR}/"
echo ""
prompt SSL_CERT_DIR "Папка с сертификатами на сервере" "$default_dir"
[[ -d "$SSL_CERT_DIR" ]] || fail "Папка не найдена: $SSL_CERT_DIR"
prompt_optional SSL_CERT_FULLCHAIN "Имя файла сертификата (полная цепочка, пусто = авто)" "$default_fullchain"
prompt_optional SSL_CERT_KEY "Имя файла приватного ключа (пусто = авто)" "$default_key"
}
validate_custom_certificate_pair() {
local fullchain="$1"
local key="$2"
[[ -f "$fullchain" ]] || fail "Файл сертификата не найден: $fullchain"
[[ -f "$key" ]] || fail "Файл ключа не найден: $key"
[[ -r "$fullchain" ]] || fail "Нет прав на чтение сертификата: $fullchain"
[[ -r "$key" ]] || fail "Нет прав на чтение ключа: $key"
has_cmd openssl || return 0
openssl x509 -in "$fullchain" -noout >/dev/null 2>&1 \
|| fail "Некорректный файл сертификата: $fullchain"
if openssl rsa -in "$key" -check -noout >/dev/null 2>&1 \
|| openssl ec -in "$key" -check -noout >/dev/null 2>&1 \
|| openssl pkey -in "$key" -noout >/dev/null 2>&1; then
:
else
fail "Некорректный файл приватного ключа: $key"
fi
if ! openssl x509 -noout -modulus -in "$fullchain" 2>/dev/null \
| cmp -s <(openssl rsa -noout -modulus -in "$key" 2>/dev/null) - 2>/dev/null \
&& ! openssl x509 -noout -modulus -in "$fullchain" 2>/dev/null \
| cmp -s <(openssl ec -noout -modulus -in "$key" 2>/dev/null) - 2>/dev/null \
&& ! openssl x509 -noout -pubkey -in "$fullchain" 2>/dev/null \
| cmp -s <(openssl pkey -pubout -in "$key" 2>/dev/null) - 2>/dev/null; then
warn "Не удалось проверить соответствие сертификата и ключа — продолжаем установку"
fi
}
install_custom_certificates() {
local force="${1:-}"
load_env
resolve_custom_cert_source_dir
SSL_CERT_DIR="${SSL_CERT_DIR:-$(env_get SSL_CERT_DIR "")}"
SSL_CERT_FULLCHAIN="${SSL_CERT_FULLCHAIN:-$(env_get SSL_CERT_FULLCHAIN "")}"
SSL_CERT_KEY="${SSL_CERT_KEY:-$(env_get SSL_CERT_KEY "")}"
[[ -n "$SSL_CERT_DIR" ]] || fail "Не задана папка с сертификатами (SSL_CERT_DIR или --cert-dir)"
[[ -d "$SSL_CERT_DIR" ]] || fail "Папка с сертификатами не найдена: $SSL_CERT_DIR"
local src_fullchain src_key
src_fullchain="$(find_custom_cert_file "$SSL_CERT_DIR" "$SSL_CERT_FULLCHAIN" \
fullchain.pem cert.pem certificate.pem server.crt)" \
|| fail "Не найден файл сертификата в ${SSL_CERT_DIR}. Положите fullchain.pem или cert.pem, либо укажите --cert-fullchain"
src_key="$(find_custom_cert_file "$SSL_CERT_DIR" "$SSL_CERT_KEY" \
privkey.pem key.pem server.key)" \
|| fail "Не найден файл ключа в ${SSL_CERT_DIR}. Положите privkey.pem или key.pem, либо укажите --cert-key"
validate_custom_certificate_pair "$src_fullchain" "$src_key"
if [[ "$force" != "--force" \
&& -f "${CUSTOM_CERT_TARGET_DIR}/fullchain.pem" \
&& -f "${CUSTOM_CERT_TARGET_DIR}/privkey.pem" \
&& cmp -s "$src_fullchain" "${CUSTOM_CERT_TARGET_DIR}/fullchain.pem" \
&& cmp -s "$src_key" "${CUSTOM_CERT_TARGET_DIR}/privkey.pem" ]]; then
log "Сертификаты уже установлены: ${CUSTOM_CERT_TARGET_DIR}/"
return 0
fi
log "Установка своих сертификатов из ${SSL_CERT_DIR}..."
mkdir -p "$CUSTOM_CERT_TARGET_DIR"
cp "$src_fullchain" "${CUSTOM_CERT_TARGET_DIR}/fullchain.pem"
cp "$src_key" "${CUSTOM_CERT_TARGET_DIR}/privkey.pem"
chmod 644 "${CUSTOM_CERT_TARGET_DIR}/fullchain.pem" 2>/dev/null || true
chmod 600 "${CUSTOM_CERT_TARGET_DIR}/privkey.pem" 2>/dev/null || true
env_set SSL_CERT_DIR "$SSL_CERT_DIR"
env_set SSL_CERT_FULLCHAIN "$SSL_CERT_FULLCHAIN"
env_set SSL_CERT_KEY "$SSL_CERT_KEY"
ok "Сертификаты установлены: ${CUSTOM_CERT_TARGET_DIR}/"
if has_cmd openssl; then
log "Срок действия: $(openssl x509 -in "${CUSTOM_CERT_TARGET_DIR}/fullchain.pem" -noout -enddate 2>/dev/null | cut -d= -f2-)"
log "Домены (SAN): $(openssl x509 -in "${CUSTOM_CERT_TARGET_DIR}/fullchain.pem" -noout -ext subjectAltName 2>/dev/null | sed 's/ *//g' || echo "не указаны")"
fi
}
generate_shared_self_signed_cert() {
local cert_dir="${LOCAL_CERTS_DIR}/shared-intranet"
local base sans cn
@@ -1489,7 +1665,7 @@ server {
}
EOF
elif [[ "$ssl_type" == "selfsigned" ]]; then
elif [[ "$ssl_type" == "selfsigned" || "$ssl_type" == "custom" ]]; then
cat <<EOF
server {
listen 80;
@@ -1929,6 +2105,11 @@ setup_nginx_proxy() {
write_all_nginx_configs "selfsigned"
ok "Nginx: HTTPS самоподписанный — импортируйте CA в доверенные на клиентах или примите предупреждение браузера"
;;
custom)
install_custom_certificates
write_all_nginx_configs "custom"
ok "Nginx: HTTPS со своими сертификатами из ${CUSTOM_CERT_TARGET_DIR}/"
;;
letsencrypt)
setup_firewall
install_certbot
@@ -2064,6 +2245,10 @@ refresh_host_nginx_configs() {
generate_all_self_signed_certs
write_all_nginx_configs "selfsigned" || warn "Не удалось обновить Nginx (нужен sudo?)"
;;
custom)
install_custom_certificates
write_all_nginx_configs "custom" || warn "Не удалось обновить Nginx (нужен sudo?)"
;;
letsencrypt)
write_all_nginx_configs "letsencrypt" || warn "Не удалось обновить Nginx (нужен sudo?)"
;;
@@ -2100,6 +2285,10 @@ show_status() {
echo ""
echo " INSTALL_MODE: $(env_get INSTALL_MODE local)"
echo " SSL_TYPE: $(env_get SSL_TYPE none)"
if [[ "$(env_get SSL_TYPE none)" == "custom" ]]; then
echo " SSL_CERT_DIR: $(env_get SSL_CERT_DIR -)"
echo " SSL установлен: ${CUSTOM_CERT_TARGET_DIR}/"
fi
echo " NGINX_MODE: $(env_get NGINX_MODE auto)"
echo " USE_NGINX_SSL: $(env_get USE_NGINX_SSL false)"
echo " DOMAIN_API: $(env_get DOMAIN_API -)"
@@ -2196,8 +2385,13 @@ action_renew_ssl() {
apply_intranet_selfsigned_runtime
ok "Самоподписанные сертификаты пересозданы, frontend обновлён"
;;
custom)
install_custom_certificates --force
write_all_nginx_configs "custom"
ok "Свои сертификаты переустановлены из $(env_get SSL_CERT_DIR -)"
;;
none)
fail "SSL отключён (SSL_TYPE=none). Выберите --ssl selfsigned или letsencrypt."
fail "SSL отключён (SSL_TYPE=none). Выберите --ssl selfsigned, custom или letsencrypt."
;;
esac
}
@@ -2255,7 +2449,7 @@ show_menu() {
echo ""
echo " 1) Полная установка / переустановка стека"
echo " 2) Настроить / изменить домены и Nginx + SSL"
echo " 3) Обновить SSL-сертификаты (selfsigned / Let's Encrypt)"
echo " 3) Обновить SSL-сертификаты (selfsigned / custom / Let's Encrypt)"
echo " 4) Перезапуск / пересборка контейнеров (+ очистка Docker)"
echo " 5) Статус сервисов"
echo " 6) Удалить конфиги Nginx IdP"