Reuse saved installer settings
Этот коммит содержится в:
@@ -34,6 +34,9 @@ API_DOMAIN="${API_DOMAIN:-api.example.ru}"
|
|||||||
WIDGET_WEBROOT="${WIDGET_WEBROOT:-}"
|
WIDGET_WEBROOT="${WIDGET_WEBROOT:-}"
|
||||||
TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN:-}"
|
TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN:-}"
|
||||||
JWT_SECRET="${JWT_SECRET:-}"
|
JWT_SECRET="${JWT_SECRET:-}"
|
||||||
|
INSTALL_STATE_FILE=""
|
||||||
|
REUSE_SAVED_SETTINGS=false
|
||||||
|
NON_INTERACTIVE=false
|
||||||
|
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
@@ -78,10 +81,87 @@ on_error() {
|
|||||||
}
|
}
|
||||||
trap on_error ERR
|
trap on_error ERR
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
WSChat install.sh
|
||||||
|
|
||||||
|
Использование:
|
||||||
|
bash deploy/vps/install.sh
|
||||||
|
bash deploy/vps/install.sh --yes
|
||||||
|
bash deploy/vps/install.sh --reset
|
||||||
|
|
||||||
|
Опции:
|
||||||
|
--yes использовать сохранённые настройки без вопросов, если /opt/ws-chat/install-state.env существует
|
||||||
|
--reset игнорировать сохранённые настройки и спросить всё заново
|
||||||
|
--help показать эту справку
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_args() {
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--yes|-y)
|
||||||
|
NON_INTERACTIVE=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--reset)
|
||||||
|
REUSE_SAVED_SETTINGS=false
|
||||||
|
INSTALL_STATE_FILE="__ignore__"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--help|-h)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
fail "Неизвестная опция: $1"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
safe_load_env() {
|
||||||
|
local file="$1"
|
||||||
|
[[ -f "$file" ]] || return 1
|
||||||
|
|
||||||
|
# Файл создаётся самим установщиком в формате KEY=value. Подгружаем только ожидаемые ключи.
|
||||||
|
while IFS='=' read -r key value; do
|
||||||
|
[[ -z "${key:-}" || "$key" =~ ^# ]] && continue
|
||||||
|
case "$key" in
|
||||||
|
PROJECT_ROOT|SOURCE_PATH|DATA_PATH|LOGS_PATH|BACKUPS_PATH|UPDATES_PATH|WIDGET_DOMAIN|API_DOMAIN|WIDGET_WEBROOT|BACKEND_HOST|BACKEND_PORT|PM2_PROCESS_NAME)
|
||||||
|
printf -v "$key" '%s' "$value"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done < "$file"
|
||||||
|
|
||||||
|
# После загрузки PROJECT_ROOT пересчитываем путь к state-файлу, но source/data оставляем из state.
|
||||||
|
INSTALL_STATE_FILE="${PROJECT_ROOT}/install-state.env"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
find_saved_settings() {
|
||||||
|
if [[ "$INSTALL_STATE_FILE" == "__ignore__" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local default_state="${PROJECT_ROOT}/install-state.env"
|
||||||
|
if [[ -f "$default_state" ]]; then
|
||||||
|
INSTALL_STATE_FILE="$default_state"
|
||||||
|
safe_load_env "$INSTALL_STATE_FILE" || true
|
||||||
|
REUSE_SAVED_SETTINGS=true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
ask() {
|
ask() {
|
||||||
local prompt="$1"
|
local prompt="$1"
|
||||||
local default_value="$2"
|
local default_value="$2"
|
||||||
local result
|
local result
|
||||||
|
if [[ "$NON_INTERACTIVE" == true ]]; then
|
||||||
|
printf "%s" "$default_value"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
read -r -p "$(printf "${BOLD}%s${NC} [%s]: " "$prompt" "$default_value")" result
|
read -r -p "$(printf "${BOLD}%s${NC} [%s]: " "$prompt" "$default_value")" result
|
||||||
if [[ -z "$result" ]]; then
|
if [[ -z "$result" ]]; then
|
||||||
printf "%s" "$default_value"
|
printf "%s" "$default_value"
|
||||||
@@ -92,10 +172,19 @@ ask() {
|
|||||||
|
|
||||||
ask_secret() {
|
ask_secret() {
|
||||||
local prompt="$1"
|
local prompt="$1"
|
||||||
|
local default_value="${2:-}"
|
||||||
local result
|
local result
|
||||||
|
if [[ "$NON_INTERACTIVE" == true ]]; then
|
||||||
|
printf "%s" "$default_value"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
read -r -s -p "$(printf "${BOLD}%s${NC}: " "$prompt")" result
|
read -r -s -p "$(printf "${BOLD}%s${NC}: " "$prompt")" result
|
||||||
printf "\n" >&2
|
printf "\n" >&2
|
||||||
|
if [[ -z "$result" ]]; then
|
||||||
|
printf "%s" "$default_value"
|
||||||
|
else
|
||||||
printf "%s" "$result"
|
printf "%s" "$result"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
generate_secret() {
|
generate_secret() {
|
||||||
@@ -106,6 +195,20 @@ generate_secret() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
read_existing_backend_secret() {
|
||||||
|
local env_file="$SOURCE_PATH/backend/.env"
|
||||||
|
[[ -f "$env_file" ]] || return 0
|
||||||
|
local value
|
||||||
|
value="$(grep -E '^JWT_SECRET=' "$env_file" | head -n1 | cut -d= -f2- || true)"
|
||||||
|
if [[ -n "$value" && -z "$JWT_SECRET" ]]; then
|
||||||
|
JWT_SECRET="$value"
|
||||||
|
fi
|
||||||
|
value="$(grep -E '^TELEGRAM_BOT_TOKEN=' "$env_file" | head -n1 | cut -d= -f2- || true)"
|
||||||
|
if [[ -n "$value" && -z "$TELEGRAM_BOT_TOKEN" ]]; then
|
||||||
|
TELEGRAM_BOT_TOKEN="$value"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
require_root() {
|
require_root() {
|
||||||
if [[ "${EUID}" -ne 0 ]]; then
|
if [[ "${EUID}" -ne 0 ]]; then
|
||||||
fail "Запусти установщик от root: bash deploy/vps/install.sh"
|
fail "Запусти установщик от root: bash deploy/vps/install.sh"
|
||||||
@@ -161,6 +264,7 @@ EOF
|
|||||||
}
|
}
|
||||||
|
|
||||||
write_install_state() {
|
write_install_state() {
|
||||||
|
mkdir -p "$PROJECT_ROOT"
|
||||||
cat > "$PROJECT_ROOT/install-state.env" <<EOF
|
cat > "$PROJECT_ROOT/install-state.env" <<EOF
|
||||||
PROJECT_NAME=${PROJECT_NAME}
|
PROJECT_NAME=${PROJECT_NAME}
|
||||||
PROJECT_ROOT=${PROJECT_ROOT}
|
PROJECT_ROOT=${PROJECT_ROOT}
|
||||||
@@ -187,6 +291,9 @@ print_header() {
|
|||||||
printf "${BOLD}${BLUE}Интерактивный установщик WSChat для VPS${NC}\n"
|
printf "${BOLD}${BLUE}Интерактивный установщик WSChat для VPS${NC}\n"
|
||||||
printf "Сайты и домены FastPanel создаются вручную. Установщик не меняет конфиги FastPanel.\n"
|
printf "Сайты и домены FastPanel создаются вручную. Установщик не меняет конфиги FastPanel.\n"
|
||||||
printf "Админка публикуется в подкаталог виджета: https://<домен-виджета>/admin/\n"
|
printf "Админка публикуется в подкаталог виджета: https://<домен-виджета>/admin/\n"
|
||||||
|
if [[ "$REUSE_SAVED_SETTINGS" == true ]]; then
|
||||||
|
printf "Найдены сохранённые настройки: %s\n" "$INSTALL_STATE_FILE"
|
||||||
|
fi
|
||||||
printf "Файл лога: %s\n\n" "$LOG_FILE"
|
printf "Файл лога: %s\n\n" "$LOG_FILE"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,6 +312,7 @@ PM2-процесс: ${PM2_PROCESS_NAME}
|
|||||||
Webroot виджета: ${WIDGET_WEBROOT:-не указан}
|
Webroot виджета: ${WIDGET_WEBROOT:-не указан}
|
||||||
Каталог админки: ${WIDGET_WEBROOT:-не указан}/admin
|
Каталог админки: ${WIDGET_WEBROOT:-не указан}/admin
|
||||||
Telegram token: $(if [[ -n "$TELEGRAM_BOT_TOKEN" ]]; then echo "указан"; else echo "пусто"; fi)
|
Telegram token: $(if [[ -n "$TELEGRAM_BOT_TOKEN" ]]; then echo "указан"; else echo "пусто"; fi)
|
||||||
|
Сохранённые настройки: ${PROJECT_ROOT}/install-state.env
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
@@ -292,10 +400,20 @@ publish_static_if_configured() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
|
parse_args "$@"
|
||||||
|
find_saved_settings
|
||||||
print_header
|
print_header
|
||||||
require_root
|
require_root
|
||||||
|
|
||||||
current_step "Сбор настроек установки"
|
current_step "Сбор настроек установки"
|
||||||
|
if [[ "$REUSE_SAVED_SETTINGS" == true && "$NON_INTERACTIVE" != true ]]; then
|
||||||
|
read -r -p "Использовать сохранённые настройки? [Y/n]: " use_saved
|
||||||
|
if [[ "$use_saved" =~ ^[Nn]$ ]]; then
|
||||||
|
REUSE_SAVED_SETTINGS=false
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$REUSE_SAVED_SETTINGS" != true ]]; then
|
||||||
PROJECT_ROOT="$(ask "Каталог проекта" "$PROJECT_ROOT")"
|
PROJECT_ROOT="$(ask "Каталог проекта" "$PROJECT_ROOT")"
|
||||||
SOURCE_PATH="${PROJECT_ROOT}/source"
|
SOURCE_PATH="${PROJECT_ROOT}/source"
|
||||||
DATA_PATH="${PROJECT_ROOT}/data"
|
DATA_PATH="${PROJECT_ROOT}/data"
|
||||||
@@ -306,16 +424,21 @@ main() {
|
|||||||
WIDGET_DOMAIN="$(ask "Домен виджета и админки" "$WIDGET_DOMAIN")"
|
WIDGET_DOMAIN="$(ask "Домен виджета и админки" "$WIDGET_DOMAIN")"
|
||||||
API_DOMAIN="$(ask "Домен API" "$API_DOMAIN")"
|
API_DOMAIN="$(ask "Домен API" "$API_DOMAIN")"
|
||||||
WIDGET_WEBROOT="$(ask "Webroot сайта виджета в FastPanel, оставь пустым чтобы пропустить публикацию" "$WIDGET_WEBROOT")"
|
WIDGET_WEBROOT="$(ask "Webroot сайта виджета в FastPanel, оставь пустым чтобы пропустить публикацию" "$WIDGET_WEBROOT")"
|
||||||
TELEGRAM_BOT_TOKEN="$(ask_secret "Telegram bot token, можно оставить пустым")"
|
fi
|
||||||
JWT_SECRET="$(ask "JWT secret" "$(generate_secret)")"
|
|
||||||
|
read_existing_backend_secret
|
||||||
|
TELEGRAM_BOT_TOKEN="$(ask_secret "Telegram bot token, можно оставить пустым" "$TELEGRAM_BOT_TOKEN")"
|
||||||
|
JWT_SECRET="$(ask "JWT secret" "${JWT_SECRET:-$(generate_secret)}")"
|
||||||
|
|
||||||
validate_fastpanel_path "Widget" "$WIDGET_WEBROOT"
|
validate_fastpanel_path "Widget" "$WIDGET_WEBROOT"
|
||||||
print_summary
|
print_summary
|
||||||
|
if [[ "$NON_INTERACTIVE" != true ]]; then
|
||||||
read -r -p "Продолжить установку? [Y/n]: " confirm
|
read -r -p "Продолжить установку? [Y/n]: " confirm
|
||||||
if [[ "$confirm" =~ ^[Nn]$ ]]; then
|
if [[ "$confirm" =~ ^[Nn]$ ]]; then
|
||||||
warn "Установка отменена."
|
warn "Установка отменена."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
current_step "Установка базовых пакетов"
|
current_step "Установка базовых пакетов"
|
||||||
install_packages
|
install_packages
|
||||||
@@ -336,7 +459,7 @@ main() {
|
|||||||
current_step "Установка зависимостей проекта"
|
current_step "Установка зависимостей проекта"
|
||||||
install_dependencies
|
install_dependencies
|
||||||
|
|
||||||
current_step "Запись backend .env"
|
current_step "Запись backend .env и сохранение настроек"
|
||||||
write_backend_env
|
write_backend_env
|
||||||
write_install_state
|
write_install_state
|
||||||
ok "Backend .env записан: $SOURCE_PATH/backend/.env"
|
ok "Backend .env записан: $SOURCE_PATH/backend/.env"
|
||||||
@@ -399,6 +522,7 @@ EOF
|
|||||||
ok "Админка: https://${WIDGET_DOMAIN}/admin/"
|
ok "Админка: https://${WIDGET_DOMAIN}/admin/"
|
||||||
ok "PM2-процесс: ${PM2_PROCESS_NAME}"
|
ok "PM2-процесс: ${PM2_PROCESS_NAME}"
|
||||||
ok "Файл лога: ${LOG_FILE}"
|
ok "Файл лога: ${LOG_FILE}"
|
||||||
|
ok "Сохранённые настройки: ${PROJECT_ROOT}/install-state.env"
|
||||||
warn "Следующий шаг: настрой proxy API-домена в FastPanel и проверь https://${API_DOMAIN}/health"
|
warn "Следующий шаг: настрой proxy API-домена в FastPanel и проверь https://${API_DOMAIN}/health"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user