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:
@@ -1,7 +1,7 @@
|
||||
# Сгенерировано deploy/install.sh — не редактируйте вручную.
|
||||
# Режим: {{PROXY_MODE}} | Домен: {{DOMAIN}}
|
||||
# Режим: ${PROXY_MODE} | Домен: ${DOMAIN}
|
||||
{
|
||||
email {{ACME_EMAIL}}
|
||||
email ${ACME_EMAIL}
|
||||
admin off
|
||||
log {
|
||||
output file /var/log/caddy/access.log {
|
||||
@@ -22,7 +22,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
{{DOMAIN}} {
|
||||
${DOMAIN} {
|
||||
encode zstd gzip
|
||||
import security_headers
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
{{FILES_DOMAIN}} {
|
||||
${FILES_DOMAIN} {
|
||||
encode zstd gzip
|
||||
import security_headers
|
||||
handle {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# блок ниже нужно вставить в конфиг вашего прокси (например, Caddyfile) и перезагрузить его.
|
||||
# Приложение и LiveKit слушают только 127.0.0.1.
|
||||
{
|
||||
email {{ACME_EMAIL}}
|
||||
email ${ACME_EMAIL}
|
||||
}
|
||||
|
||||
(glchat_security_headers) {
|
||||
@@ -17,7 +17,7 @@
|
||||
}
|
||||
|
||||
# --- glchat: начало блока -------------------------------------------------
|
||||
{{DOMAIN}} {
|
||||
${DOMAIN} {
|
||||
encode zstd gzip
|
||||
import glchat_security_headers
|
||||
log {
|
||||
@@ -31,7 +31,7 @@
|
||||
# WebSocket Gateway (фаза 1): длинные соединения без таймаутов.
|
||||
@gateway path /gateway
|
||||
handle @gateway {
|
||||
reverse_proxy 127.0.0.1:{{APP_PORT}} {
|
||||
reverse_proxy 127.0.0.1:${APP_PORT} {
|
||||
transport http {
|
||||
read_timeout 1h
|
||||
write_timeout 1h
|
||||
@@ -42,7 +42,7 @@
|
||||
# LiveKit сигналинг (WebSocket).
|
||||
@rtc path /rtc*
|
||||
handle @rtc {
|
||||
reverse_proxy 127.0.0.1:{{LIVEKIT_WS_PORT}} {
|
||||
reverse_proxy 127.0.0.1:${LIVEKIT_WS_PORT} {
|
||||
transport http {
|
||||
read_timeout 1h
|
||||
write_timeout 1h
|
||||
@@ -52,16 +52,16 @@
|
||||
|
||||
# API и статика клиента.
|
||||
handle {
|
||||
reverse_proxy 127.0.0.1:{{APP_PORT}}
|
||||
reverse_proxy 127.0.0.1:${APP_PORT}
|
||||
}
|
||||
}
|
||||
|
||||
# Пользовательский контент — отдельный origin (изоляция cookies/CSP).
|
||||
{{FILES_DOMAIN}} {
|
||||
${FILES_DOMAIN} {
|
||||
encode zstd gzip
|
||||
import glchat_security_headers
|
||||
handle {
|
||||
reverse_proxy 127.0.0.1:{{APP_PORT}}
|
||||
reverse_proxy 127.0.0.1:${APP_PORT}
|
||||
}
|
||||
}
|
||||
# --- glchat: конец блока --------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
# Сгенерировано deploy/install.sh — не редактируйте вручную, изменения перезапишутся.
|
||||
# Шаблон: deploy/compose.yaml.tpl. Профиль: ${PROFILE}
|
||||
name: glchat
|
||||
|
||||
x-logging: &default-logging
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
x-app-env: &app-env
|
||||
DOMAIN: "${DOMAIN}"
|
||||
FILES_DOMAIN: "${FILES_DOMAIN}"
|
||||
INSTANCE_NAME: "${INSTANCE_NAME}"
|
||||
LISTEN_ADDR: "0.0.0.0:8080"
|
||||
DATA_DIR: "/app/data"
|
||||
WEB_ROOT: "/app/web"
|
||||
LOG_LEVEL: "${LOG_LEVEL}"
|
||||
LOG_FORMAT: "json"
|
||||
SQLITE_READ_POOL: "${SQLITE_READ_POOL}"
|
||||
SQLITE_CACHE_SIZE: "${SQLITE_CACHE_SIZE}"
|
||||
SQLITE_MMAP_SIZE: "${SQLITE_MMAP_SIZE}"
|
||||
GOMAXPROCS: "${GOMAXPROCS}"
|
||||
MAX_UPLOAD_SIZE: "${MAX_UPLOAD_SIZE}"
|
||||
SHUTDOWN_TIMEOUT_SECONDS: "20"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: ${APP_IMAGE}
|
||||
container_name: glchat-app
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
<<: *app-env
|
||||
ports:
|
||||
- "127.0.0.1:${APP_PORT}:8080"
|
||||
volumes:
|
||||
- "${DATA_DIR}/db:/app/data/db"
|
||||
- "${DATA_DIR}/files:/app/data/files"
|
||||
- "${DATA_DIR}/backups:/app/data/backups"
|
||||
logging: *default-logging
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "${APP_CPUS}"
|
||||
memory: ${APP_MEMORY_LIMIT}
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8080/healthz"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
start_period: 20s
|
||||
retries: 3
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
|
||||
livekit:
|
||||
image: livekit/livekit-server:${LIVEKIT_IMAGE_TAG}
|
||||
container_name: glchat-livekit
|
||||
restart: unless-stopped
|
||||
command: ["--config", "/etc/livekit.yaml"]
|
||||
environment:
|
||||
LIVEKIT_KEYS: "${LIVEKIT_API_KEY}: ${LIVEKIT_API_SECRET}"
|
||||
# Порты LiveKit не публикуются: тестовый режим --skip-tls (проверка установщика в контейнере).
|
||||
ports: []
|
||||
volumes:
|
||||
- ./livekit/livekit.yaml:/etc/livekit.yaml:ro
|
||||
logging: *default-logging
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "${LIVEKIT_CPUS}"
|
||||
memory: ${LIVEKIT_MEMORY_LIMIT}
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:7880"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
start_period: 15s
|
||||
retries: 3
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_BIND_SERVICE
|
||||
|
||||
caddy:
|
||||
image: caddy:${CADDY_IMAGE_TAG}
|
||||
container_name: glchat-caddy
|
||||
restart: unless-stopped
|
||||
profiles: ["${CADDY_PROFILE}"]
|
||||
ports:
|
||||
- "${HTTP_PORT}:80"
|
||||
- "${HTTPS_PORT}:443"
|
||||
- "${HTTPS_PORT}:443/udp"
|
||||
volumes:
|
||||
- ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- ./caddy/data:/data
|
||||
- ./caddy/config:/config
|
||||
- ./caddy/logs:/var/log/caddy
|
||||
logging: *default-logging
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "${CADDY_CPUS}"
|
||||
memory: ${CADDY_MEMORY_LIMIT}
|
||||
healthcheck:
|
||||
test: ["CMD", "caddy", "version"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
start_period: 10s
|
||||
retries: 3
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
+32
-29
@@ -1,5 +1,5 @@
|
||||
# Сгенерировано deploy/install.sh — не редактируйте вручную, изменения перезапишутся.
|
||||
# Шаблон: deploy/compose.yaml.tpl. Профиль: {{PROFILE}}
|
||||
# Шаблон: deploy/compose.yaml.tpl. Профиль: ${PROFILE}
|
||||
name: glchat
|
||||
|
||||
x-logging: &default-logging
|
||||
@@ -9,24 +9,24 @@ x-logging: &default-logging
|
||||
max-file: "3"
|
||||
|
||||
x-app-env: &app-env
|
||||
DOMAIN: "{{DOMAIN}}"
|
||||
FILES_DOMAIN: "{{FILES_DOMAIN}}"
|
||||
INSTANCE_NAME: "{{INSTANCE_NAME}}"
|
||||
DOMAIN: "${DOMAIN}"
|
||||
FILES_DOMAIN: "${FILES_DOMAIN}"
|
||||
INSTANCE_NAME: "${INSTANCE_NAME}"
|
||||
LISTEN_ADDR: "0.0.0.0:8080"
|
||||
DATA_DIR: "/app/data"
|
||||
WEB_ROOT: "/app/web"
|
||||
LOG_LEVEL: "{{LOG_LEVEL}}"
|
||||
LOG_LEVEL: "${LOG_LEVEL}"
|
||||
LOG_FORMAT: "json"
|
||||
SQLITE_READ_POOL: "{{SQLITE_READ_POOL}}"
|
||||
SQLITE_CACHE_SIZE: "{{SQLITE_CACHE_SIZE}}"
|
||||
SQLITE_MMAP_SIZE: "{{SQLITE_MMAP_SIZE}}"
|
||||
GOMAXPROCS: "{{GOMAXPROCS}}"
|
||||
MAX_UPLOAD_SIZE: "{{MAX_UPLOAD_SIZE}}"
|
||||
SQLITE_READ_POOL: "${SQLITE_READ_POOL}"
|
||||
SQLITE_CACHE_SIZE: "${SQLITE_CACHE_SIZE}"
|
||||
SQLITE_MMAP_SIZE: "${SQLITE_MMAP_SIZE}"
|
||||
GOMAXPROCS: "${GOMAXPROCS}"
|
||||
MAX_UPLOAD_SIZE: "${MAX_UPLOAD_SIZE}"
|
||||
SHUTDOWN_TIMEOUT_SECONDS: "20"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: {{APP_IMAGE}}
|
||||
image: ${APP_IMAGE}
|
||||
container_name: glchat-app
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
@@ -34,17 +34,17 @@ services:
|
||||
environment:
|
||||
<<: *app-env
|
||||
ports:
|
||||
- "127.0.0.1:{{APP_PORT}}:8080"
|
||||
- "127.0.0.1:${APP_PORT}:8080"
|
||||
volumes:
|
||||
- "{{DATA_DIR}}/db:/app/data/db"
|
||||
- "{{DATA_DIR}}/files:/app/data/files"
|
||||
- "{{DATA_DIR}}/backups:/app/data/backups"
|
||||
- "${DATA_DIR}/db:/app/data/db"
|
||||
- "${DATA_DIR}/files:/app/data/files"
|
||||
- "${DATA_DIR}/backups:/app/data/backups"
|
||||
logging: *default-logging
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "{{APP_CPUS}}"
|
||||
memory: {{APP_MEMORY_LIMIT}}
|
||||
cpus: "${APP_CPUS}"
|
||||
memory: ${APP_MEMORY_LIMIT}
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8080/healthz"]
|
||||
interval: 30s
|
||||
@@ -57,21 +57,24 @@ services:
|
||||
- ALL
|
||||
|
||||
livekit:
|
||||
image: livekit/livekit-server:{{LIVEKIT_IMAGE_TAG}}
|
||||
image: livekit/livekit-server:${LIVEKIT_IMAGE_TAG}
|
||||
container_name: glchat-livekit
|
||||
restart: unless-stopped
|
||||
command: ["--config", "/etc/livekit.yaml"]
|
||||
environment:
|
||||
LIVEKIT_KEYS: "{{LIVEKIT_API_KEY}}: {{LIVEKIT_API_SECRET}}"
|
||||
ports:{{LIVEKIT_PORTS}}
|
||||
LIVEKIT_KEYS: "${LIVEKIT_API_KEY}: ${LIVEKIT_API_SECRET}"
|
||||
ports:
|
||||
- "127.0.0.1:${LIVEKIT_WS_PORT}:7880"
|
||||
- "${LIVEKIT_TCP_PORT}:7881"
|
||||
- "${LIVEKIT_UDP_PORT}:7882/udp"
|
||||
volumes:
|
||||
- ./livekit/livekit.yaml:/etc/livekit.yaml:ro
|
||||
logging: *default-logging
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "{{LIVEKIT_CPUS}}"
|
||||
memory: {{LIVEKIT_MEMORY_LIMIT}}
|
||||
cpus: "${LIVEKIT_CPUS}"
|
||||
memory: ${LIVEKIT_MEMORY_LIMIT}
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:7880"]
|
||||
interval: 30s
|
||||
@@ -86,14 +89,14 @@ services:
|
||||
- NET_BIND_SERVICE
|
||||
|
||||
caddy:
|
||||
image: caddy:{{CADDY_IMAGE_TAG}}
|
||||
image: caddy:${CADDY_IMAGE_TAG}
|
||||
container_name: glchat-caddy
|
||||
restart: unless-stopped
|
||||
profiles: ["{{CADDY_PROFILE}}"]
|
||||
profiles: ["${CADDY_PROFILE}"]
|
||||
ports:
|
||||
- "{{HTTP_PORT}}:80"
|
||||
- "{{HTTPS_PORT}}:443"
|
||||
- "{{HTTPS_PORT}}:443/udp"
|
||||
- "${HTTP_PORT}:80"
|
||||
- "${HTTPS_PORT}:443"
|
||||
- "${HTTPS_PORT}:443/udp"
|
||||
volumes:
|
||||
- ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- ./caddy/data:/data
|
||||
@@ -103,8 +106,8 @@ services:
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "{{CADDY_CPUS}}"
|
||||
memory: {{CADDY_MEMORY_LIMIT}}
|
||||
cpus: "${CADDY_CPUS}"
|
||||
memory: ${CADDY_MEMORY_LIMIT}
|
||||
healthcheck:
|
||||
test: ["CMD", "caddy", "version"]
|
||||
interval: 30s
|
||||
|
||||
@@ -11,7 +11,7 @@ action = iptables-allports[name=glchat-auth]
|
||||
|
||||
[sshd]
|
||||
enabled = true
|
||||
port = {{SSH_PORT}}
|
||||
port = ${SSH_PORT}
|
||||
maxretry = 5
|
||||
findtime = 10m
|
||||
bantime = 1h
|
||||
|
||||
+25
-19
@@ -193,6 +193,10 @@ interactive_defaults() {
|
||||
|
||||
detect_ssh_port() {
|
||||
local port="22" conf="/etc/ssh/sshd_config"
|
||||
if [ ! -r "$conf" ] && [ ! -d /etc/ssh ]; then
|
||||
printf '%s' "$port"
|
||||
return 0
|
||||
fi
|
||||
if [ -r "$conf" ]; then
|
||||
local found
|
||||
found="$(awk '/^[[:space:]]*Port[[:space:]]+[0-9]+/ {print $2; exit}' "$conf")"
|
||||
@@ -547,12 +551,14 @@ ensure_swap() {
|
||||
ok "swap ${want_gb} GB активирован"
|
||||
}
|
||||
|
||||
|
||||
|
||||
render_all_templates() {
|
||||
info "генерируем конфиги в ${TARGET_DIR}"
|
||||
mkdir -p "${TARGET_DIR}"/{caddy/logs,caddy/data,caddy/config,livekit,fail2ban,nftables}
|
||||
if [ "$DRY_RUN" = "1" ]; then
|
||||
log_file_only "DRY-RUN: рендер шаблонов compose/Caddy/LiveKit/fail2ban/nftables/systemd"
|
||||
else
|
||||
mkdir -p "${TARGET_DIR}"/{caddy/logs,caddy/data,caddy/config,livekit,fail2ban,nftables}
|
||||
cp "${DEPLOY_DIR}/lib/common.sh" "${TARGET_DIR}/lib-common.sh" 2>/dev/null || true
|
||||
cp "${DEPLOY_DIR}/"*.sh "${TARGET_DIR}/" 2>/dev/null || true
|
||||
cp "${DEPLOY_DIR}/fail2ban/filter-glchat-auth.conf" "${TARGET_DIR}/fail2ban/"
|
||||
@@ -575,20 +581,14 @@ render_all_templates() {
|
||||
LIVEKIT_PUBLISH="true"
|
||||
[ "$SKIP_TLS" = "1" ] && LIVEKIT_PUBLISH="false"
|
||||
|
||||
# Публикация портов LiveKit отключается в тестовом режиме (--skip-tls),
|
||||
# когда установщик проверяется внутри контейнера.
|
||||
local livekit_ports
|
||||
if [ "$LIVEKIT_PUBLISH" = "true" ]; then
|
||||
livekit_ports="
|
||||
- \"127.0.0.1:${LIVEKIT_WS_PORT}:7880\"
|
||||
- \"${LIVEKIT_TCP_PORT}:7881\"
|
||||
- \"${LIVEKIT_UDP_PORT}:7882/udp\""
|
||||
else
|
||||
livekit_ports=" []"
|
||||
# Вариант compose выбирается по режиму: в тестовом прогоне (--skip-tls)
|
||||
# порты LiveKit не публикуются, чтобы не занимать порты хост-машины.
|
||||
local compose_template="${DEPLOY_DIR}/compose.yaml.tpl"
|
||||
if [ "$LIVEKIT_PUBLISH" != "true" ]; then
|
||||
compose_template="${DEPLOY_DIR}/compose.test.yaml.tpl"
|
||||
fi
|
||||
LIVEKIT_PORTS="$livekit_ports"
|
||||
|
||||
render_template "${DEPLOY_DIR}/compose.yaml.tpl" "${TARGET_DIR}/compose.yaml" \
|
||||
render_template "${compose_template}" "${TARGET_DIR}/compose.yaml" \
|
||||
"DOMAIN=${DOMAIN}" "FILES_DOMAIN=${FILES_DOMAIN_RESOLVED}" "INSTANCE_NAME=${INSTANCE_NAME}" \
|
||||
"PROFILE=${PROFILE_RESOLVED}" "APP_IMAGE=${APP_IMAGE}" "IMAGE_TAG=${IMAGE_TAG}" \
|
||||
"APP_PORT=${APP_PORT}" "LIVEKIT_WS_PORT=${LIVEKIT_WS_PORT}" \
|
||||
@@ -604,7 +604,6 @@ render_all_templates() {
|
||||
"CADDY_CPUS=1.0" "CADDY_MEMORY_LIMIT=256m" \
|
||||
"CADDY_IMAGE_TAG=${CADDY_IMAGE_TAG}" "LIVEKIT_IMAGE_TAG=${LIVEKIT_IMAGE_TAG}" \
|
||||
"CADDY_PROFILE=${CADDY_PROFILE}" "LIVEKIT_PUBLISH=${LIVEKIT_PUBLISH}" \
|
||||
"LIVEKIT_PORTS=${LIVEKIT_PORTS}" \
|
||||
"LIVEKIT_API_KEY=${LIVEKIT_API_KEY}" "LIVEKIT_API_SECRET=${LIVEKIT_API_SECRET}" \
|
||||
"HTTP_PORT=${HTTP_PORT}" "HTTPS_PORT=${HTTPS_PORT}"
|
||||
|
||||
@@ -632,10 +631,15 @@ render_all_templates() {
|
||||
TURN_EXTERNAL_TLS="$turn_external_tls"
|
||||
|
||||
render_template "${DEPLOY_DIR}/livekit/livekit.yaml.tpl" "${TARGET_DIR}/livekit/livekit.yaml" \
|
||||
"LIVEKIT_WS_PORT=7880" "LIVEKIT_TCP_PORT=7881" "LIVEKIT_UDP_PORT=7882" \
|
||||
"LIVEKIT_WS_PORT=${LIVEKIT_WS_PORT}" \
|
||||
"LIVEKIT_TCP_PORT=${LIVEKIT_TCP_PORT}" \
|
||||
"LIVEKIT_UDP_PORT=${LIVEKIT_UDP_PORT}" \
|
||||
"LIVEKIT_USE_EXTERNAL_IP=${use_external_ip}" \
|
||||
"TURN_ENABLED=${TURN_ENABLED}" "TURN_DOMAIN=turn.${DOMAIN}" \
|
||||
"TURN_UDP_PORT=3478" "TURN_TLS_PORT=5349" "TURN_EXTERNAL_TLS=${turn_external_tls}" \
|
||||
"TURN_ENABLED=${TURN_ENABLED}" \
|
||||
"TURN_DOMAIN=turn.${DOMAIN}" \
|
||||
"TURN_UDP_PORT=${TURN_UDP_PORT}" \
|
||||
"TURN_TLS_PORT=${TURN_TLS_PORT}" \
|
||||
"TURN_EXTERNAL_TLS=${turn_external_tls}" \
|
||||
"LIVEKIT_MAX_PARTICIPANTS=${PROFILE_LIVEKIT_MAX_PARTICIPANTS}" \
|
||||
"LIVEKIT_DISABLE_SIMULCAST=${disable_simulcast}"
|
||||
|
||||
@@ -872,6 +876,8 @@ build_or_pull_image() {
|
||||
--build-arg "VERSION=${IMAGE_TAG}" \
|
||||
--build-arg "COMMIT=$(git -C "$REPO_DIR" rev-parse --short HEAD 2>/dev/null || echo none)" \
|
||||
--build-arg "BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
||||
--build-arg "GOPROXY=${GOPROXY:-https://proxy.golang.org,direct}" \
|
||||
--build-arg "GOSUMDB=${GOSUMDB:-sum.golang.org}" \
|
||||
"$REPO_DIR" || die "сборка образа не удалась"
|
||||
ok "образ собран: $(docker images --format '{{.Repository}}:{{.Tag}} {{.Size}}' "$APP_IMAGE" | head -1)"
|
||||
}
|
||||
@@ -955,7 +961,7 @@ print_summary() {
|
||||
* Не забудьте про внешний firewall провайдера (если есть).
|
||||
EOF
|
||||
if [ "$SKIP_TLS" = "0" ] && [ "$PROXY_MODE" = "embedded" ]; then
|
||||
printf ' * Сертификат Let%s Encrypt выпускается автоматически при первом запросе.\n' "'"
|
||||
printf " * Сертификат Let's Encrypt выпускается автоматически при первом запросе.\n"
|
||||
fi
|
||||
printf '\n'
|
||||
}
|
||||
@@ -989,7 +995,7 @@ main() {
|
||||
else
|
||||
EFFECTIVE_UPLOAD_MB="$MAX_UPLOAD_MB"
|
||||
fi
|
||||
PROFILE_APP_CPUS="$(nproc)"
|
||||
PROFILE_APP_CPUS="$(detect_cpus)"
|
||||
PROFILE_LIVEKIT_CPUS="1.0"
|
||||
PROFILE_SWAPPINESS=10
|
||||
|
||||
|
||||
+71
-14
@@ -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 (будет установлено)"
|
||||
}
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
# Сгенерировано deploy/install.sh — не редактируйте вручную.
|
||||
# Документация: https://docs.livekit.io/home/self-hosting/deployment/
|
||||
port: {{LIVEKIT_WS_PORT}}
|
||||
port: ${LIVEKIT_WS_PORT}
|
||||
bind_addresses:
|
||||
- "0.0.0.0"
|
||||
|
||||
rtc:
|
||||
tcp_port: {{LIVEKIT_TCP_PORT}}
|
||||
udp_port: {{LIVEKIT_UDP_PORT}}
|
||||
use_external_ip: {{LIVEKIT_USE_EXTERNAL_IP}}
|
||||
tcp_port: ${LIVEKIT_TCP_PORT}
|
||||
udp_port: ${LIVEKIT_UDP_PORT}
|
||||
use_external_ip: ${LIVEKIT_USE_EXTERNAL_IP}
|
||||
# Синхронизация портов с публичным адресом: контейнер публикует порты напрямую.
|
||||
port_range_start: 0
|
||||
port_range_end: 0
|
||||
|
||||
turn:
|
||||
enabled: {{TURN_ENABLED}}
|
||||
domain: {{TURN_DOMAIN}}
|
||||
udp_port: {{TURN_UDP_PORT}}
|
||||
tls_port: {{TURN_TLS_PORT}}
|
||||
external_tls: {{TURN_EXTERNAL_TLS}}
|
||||
enabled: ${TURN_ENABLED}
|
||||
domain: ${TURN_DOMAIN}
|
||||
udp_port: ${TURN_UDP_PORT}
|
||||
tls_port: ${TURN_TLS_PORT}
|
||||
external_tls: ${TURN_EXTERNAL_TLS}
|
||||
|
||||
room:
|
||||
auto_create: true
|
||||
empty_timeout: 300
|
||||
departure_timeout: 20
|
||||
max_participants: {{LIVEKIT_MAX_PARTICIPANTS}}
|
||||
max_participants: ${LIVEKIT_MAX_PARTICIPANTS}
|
||||
enable_remote_unmute: false
|
||||
|
||||
audio:
|
||||
@@ -34,7 +34,7 @@ audio:
|
||||
|
||||
video:
|
||||
dynacast: true
|
||||
disable_simulcast: {{LIVEKIT_DISABLE_SIMULCAST}}
|
||||
disable_simulcast: ${LIVEKIT_DISABLE_SIMULCAST}
|
||||
|
||||
logging:
|
||||
level: info
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Сгенерировано deploy/install.sh — не редактируйте вручную.
|
||||
# Ограничения против флуда и перебора (AGENT.md 9.7). Один IP: не более {{CONNLIMIT}} соединений
|
||||
# к 80/443 и не более {{CONN_RATE}} новых соединений в секунду.
|
||||
# Ограничения против флуда и перебора (AGENT.md 9.7). Один IP: не более ${CONNLIMIT} соединений
|
||||
# к 80/443 и не более ${CONN_RATE} новых соединений в секунду.
|
||||
table inet glchat {
|
||||
set web_ports {
|
||||
type inet_service
|
||||
elements = { {{WEB_PORTS}} }
|
||||
elements = { ${WEB_PORTS} }
|
||||
}
|
||||
|
||||
chain input_guard {
|
||||
@@ -14,12 +14,12 @@ table inet glchat {
|
||||
ct state established,related accept
|
||||
|
||||
# Ограничение одновременных соединений с одного источника.
|
||||
tcp dport @web_ports ct state new meter glchat_connlimit size 65535 { ip saddr limit over {{CONNLIMIT}} } drop
|
||||
tcp dport @web_ports ct state new meter glchat_connlimit size 65535 { ip saddr limit over ${CONNLIMIT} } drop
|
||||
|
||||
# Ограничение скорости появления новых соединений.
|
||||
tcp dport @web_ports ct state new meter glchat_rate size 65535 { ip saddr limit rate over {{CONN_RATE}}/second } drop
|
||||
tcp dport @web_ports ct state new meter glchat_rate size 65535 { ip saddr limit rate over ${CONN_RATE}/second } drop
|
||||
|
||||
# Защита от SYN-флуда.
|
||||
tcp flags & (fin|syn|rst|ack) == syn limit rate over {{SYN_RATE}}/second drop
|
||||
tcp flags & (fin|syn|rst|ack) == syn limit rate over ${SYN_RATE}/second drop
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user