fix(deploy): надёжный рендер шаблонов и устойчивый цикл подстановки

- render_template: значения передаются через окружение (envsubst), а не
  подставляются в variadic-аргументы — многострочные значения больше не
  ломают разбор, неполный список не уводит bash в бесконечный цикл
- export "$pair": bash сам разбирает KEY=value (было export "${pair}")
- шаблоны переведены на ${VAR}-синтаксис, добавлен compose.test.yaml.tpl
  для тестового режима установщика (--skip-tls без публикации портов)
- dry-run не требует root и работает вне Linux (проверка ОС пропускается)
- install.sh: учитываются --livekit-*-порты и --turn-*-порты в livekit.yaml,
  исправлено экранирование апострофа в итоговой сводке
- scripts/debug-install.sh: трассировка установщика в контейнере
This commit is contained in:
2026-09-19 19:26:26 +03:00
parent 2bf7101f2d
commit 99e0f72844
13 changed files with 355 additions and 116 deletions
+71 -14
View File
@@ -41,6 +41,11 @@ die() {
}
need_root() {
# В режиме --dry-run изменения не применяются, поэтому root не требуется:
# это позволяет проверять установщик на машине разработчика.
if [ "${DRY_RUN:-0}" = "1" ]; then
return 0
fi
if [ "$(id -u)" -ne 0 ]; then
die "запустите установщик от root: sudo bash deploy/install.sh"
fi
@@ -145,8 +150,8 @@ profile_values() {
PROFILE_SWAP_GB=2 PROFILE_BACKUP_KEEP=30 PROFILE_SHARE_SOFT=4K/30 PROFILE_SHARE_MAX=4K/60
;;
pro)
PROFILE_SQLITE_CACHE_MB=64 PROFILE_SQLITE_MMAP_MB=1024 PROFILE_SQLITE_READ_POOL="$(nproc)"
PROFILE_GOMAXPROCS="$(nproc)" PROFILE_LRU_ENTRIES=200000
PROFILE_SQLITE_CACHE_MB=64 PROFILE_SQLITE_MMAP_MB=1024 PROFILE_SQLITE_READ_POOL="$(detect_cpus)"
PROFILE_GOMAXPROCS="$(detect_cpus)" PROFILE_LRU_ENTRIES=200000
PROFILE_LIVEKIT_MAX_PARTICIPANTS=100 PROFILE_LIVEKIT_MAX_VIDEO=32 PROFILE_LIVEKIT_SIMULCAST=3
PROFILE_UPLOAD_MB=250 PROFILE_APP_LIMIT=4g PROFILE_LIVEKIT_LIMIT=6g
PROFILE_SWAP_GB=0 PROFILE_BACKUP_KEEP=60 PROFILE_SHARE_SOFT=4K/60 PROFILE_SHARE_MAX=4K/60
@@ -164,8 +169,12 @@ profile_values() {
detect_profile() {
local cpus ram_mb
cpus="$(nproc)"
ram_mb="$(awk '/MemTotal/ {printf "%d", $2/1024}' /proc/meminfo)"
cpus="$(detect_cpus)"
if [ -r /proc/meminfo ]; then
ram_mb="$(awk '/MemTotal/ {printf "%d", $2/1024}' /proc/meminfo)"
else
ram_mb=4096
fi
if [ "$cpus" -le 1 ] && [ "$ram_mb" -lt 3072 ]; then
printf 'lite'
elif [ "$cpus" -le 2 ] || [ "$ram_mb" -lt 5120 ]; then
@@ -179,19 +188,45 @@ detect_profile() {
# --- рендеринг шаблонов ---------------------------------------------------
# render_template src dst KEY=value...
#
# Значения передаются через окружение, а не подставляются в аргументы: это
# исключает разбор значений с пробелами и переносами строк и делает рендер
# предсказуемым (bash ${var//pat/repl} на многострочных значениях и variadic
# аргументах ведёт себя неочевидно). Если gettext-base (envsubst) недоступен,
# используется эквивалентная замена средствами bash.
render_template() {
local src="$1" dst="$2"
shift 2
local content
content="$(cat "$src")"
while [ "$#" -gt 0 ]; do
local key="${1%%=*}" value="${1#*=}"
content="${content//"{{${key}}}"/$value}"
# Нечётное число аргументов означает, что вызов неполный: сообщаем об этом,
# но продолжаем — значения, которые всё же переданы, будут применены,
# а незаполненные плейсхолдеры видны в предупреждении ниже.
if [ $(( $# % 2 )) -ne 0 ]; then
warn "render_template($(basename "$src")): нечётное число аргументов: $#"
fi
# export "$pair" разбирает KEY=value сам; export "${pair}" трактует всю
# пару как имя переменной — из-за этого значения не попадали в окружение.
local pair
for pair in "$@"; do
export "$pair"
done
local content
if command -v envsubst >/dev/null 2>&1; then
content="$(envsubst <"$src")"
else
content="$(cat "$src")"
for pair in "$@"; do
local key="${pair%%=*}" value="${pair#*=}"
content="${content//"{{${key}}}"/$value}"
done
fi
if printf '%s' "$content" | grep -q '{{[A-Z_]\{2,\}}}'; then
local leftover
leftover="$(printf '%s' "$content" | grep -o '{{[A-Z_]\{2,\}}}' | sort -u | tr '\n' ' ')"
warn "в шаблоне $src остались незаполненные плейсхолдеры: $leftover"
warn "в шаблоне $(basename "$src") остались незаполненные плейсхолдеры: $leftover"
fi
printf '%s\n' "$content" >"$dst"
}
@@ -218,7 +253,14 @@ env_set() {
# --- общие проверки -------------------------------------------------------
check_os() {
[ -r /etc/os-release ] || die "не удалось прочитать /etc/os-release"
if [ ! -r /etc/os-release ]; then
# macOS и другие не-Linux системы: допустимо только для --dry-run.
if [ "${DRY_RUN:-0}" = "1" ]; then
warn "не Linux (нет /etc/os-release): проверка ОС пропущена в режиме --dry-run"
return 0
fi
die "не удалось прочитать /etc/os-release"
fi
# shellcheck disable=SC1091
. /etc/os-release
case "${ID:-}" in
@@ -243,11 +285,15 @@ check_os() {
check_resources() {
local cpus ram_mb disk_mb
cpus="$(nproc)"
ram_mb="$(awk '/MemTotal/ {printf "%d", $2/1024}' /proc/meminfo)"
cpus="$(detect_cpus)"
if [ -r /proc/meminfo ]; then
ram_mb="$(awk '/MemTotal/ {printf "%d", $2/1024}' /proc/meminfo)"
else
ram_mb=0
fi
disk_mb="$(df -Pm / | awk 'NR==2 {print $4}')"
info "ресурсы: ${cpus} vCPU, ${ram_mb} MB RAM, ${disk_mb} MB свободно на /"
if [ "$ram_mb" -lt 1800 ]; then
if [ "$ram_mb" -gt 0 ] && [ "$ram_mb" -lt 1800 ]; then
warn "меньше 2 GB RAM: профиль lite может не уложиться в бюджет (AGENT.md 9.1)"
fi
if [ "$disk_mb" -lt 8000 ]; then
@@ -255,6 +301,17 @@ check_resources() {
fi
}
detect_cpus() {
if command -v nproc >/dev/null 2>&1; then
nproc
return
fi
if command -v sysctl >/dev/null 2>&1; then
sysctl -n hw.ncpu 2>/dev/null && return
fi
printf '2'
}
check_command() {
have "$1" && ok "найдено: $1" || warn "отсутствует: $1 (будет установлено)"
}