From 13598217999e45ad6e4123453a56b305b327b7d2 Mon Sep 17 00:00:00 2001 From: grendervill Date: Sat, 19 Sep 2026 20:28:29 +0300 Subject: [PATCH] =?UTF-8?q?feat(deploy):=20=D0=B0=D0=B4=D1=80=D0=B5=D1=81?= =?UTF-8?q?=20=D0=BF=D1=80=D0=B8=D0=B2=D1=8F=D0=B7=D0=BA=D0=B8=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=BF=D1=80=D0=BE=D0=BA=D1=81=D0=B8=20=D0=BF?= =?UTF-8?q?=D0=BE=20=D1=82=D1=83=D0=BD=D0=BD=D0=B5=D0=BB=D1=8E=20=D0=B8=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA=D0=B0=20=D1=81=D0=B5?= =?UTF-8?q?=D1=80=D1=82=D0=B8=D1=84=D0=B8=D0=BA=D0=B0=D1=82=D0=B0=20TURN?= =?UTF-8?q?=20=D1=81=20=D0=B2=D0=BD=D0=B5=D1=88=D0=BD=D0=B5=D0=B3=D0=BE=20?= =?UTF-8?q?=D1=83=D0=B7=D0=BB=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - compose: app и LiveKit публикуются на GLCHAT_APP_BIND/GLCHAT_LIVEKIT_BIND (по умолчанию 127.0.0.1, для схемы «VPS-прокси + домашний сервер» — адрес в WireGuard) - install.sh: --app-bind-addr/--livekit-bind-addr, --turn-cert-remote* для загрузки сертификата по SSH, glchat-cert-refresh умеет тянуть его с внешнего узла --- deploy/compose.test.yaml.tpl | 2 +- deploy/compose.yaml.tpl | 9 ++-- deploy/install.sh | 92 +++++++++++++++++++++++++++++++++--- 3 files changed, 91 insertions(+), 12 deletions(-) diff --git a/deploy/compose.test.yaml.tpl b/deploy/compose.test.yaml.tpl index 39ba396..08eff4d 100644 --- a/deploy/compose.test.yaml.tpl +++ b/deploy/compose.test.yaml.tpl @@ -34,7 +34,7 @@ services: environment: <<: *app-env ports: - - "127.0.0.1:${APP_PORT}:8080" + - "${GLCHAT_APP_BIND}:${APP_PORT}:8080" volumes: - "${GLCHAT_DATA_DIR}/db:/app/data/db" - "${GLCHAT_DATA_DIR}/files:/app/data/files" diff --git a/deploy/compose.yaml.tpl b/deploy/compose.yaml.tpl index aafc618..11c7094 100644 --- a/deploy/compose.yaml.tpl +++ b/deploy/compose.yaml.tpl @@ -35,7 +35,7 @@ services: environment: <<: *app-env ports: - - "127.0.0.1:${APP_PORT}:8080" + - "${GLCHAT_APP_BIND}:${APP_PORT}:8080" volumes: - "${GLCHAT_DATA_DIR}/db:/app/data/db" - "${GLCHAT_DATA_DIR}/files:/app/data/files" @@ -65,9 +65,10 @@ services: environment: 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" + - "${GLCHAT_LIVEKIT_BIND}:${LIVEKIT_WS_PORT}:7880" + # Медиапорты: тот же адрес привязки (для инстанса за NAT это адрес туннеля). + - "${GLCHAT_LIVEKIT_BIND}:${LIVEKIT_TCP_PORT}:7881" + - "${GLCHAT_LIVEKIT_BIND}:${LIVEKIT_UDP_PORT}:7882/udp" volumes: - ./livekit/livekit.yaml:/etc/livekit.yaml:ro - ${GLCHAT_TURN_CERT_DIR}:/etc/certs:ro diff --git a/deploy/install.sh b/deploy/install.sh index 0b42041..e15bce3 100755 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -61,6 +61,13 @@ TURN_RELAY_HOST="${TURN_RELAY_HOST:-}" TURN_RELAY_PORT="${TURN_RELAY_PORT:-3478}" TURN_RELAY_TLS_PORT="${TURN_RELAY_TLS_PORT:-5349}" TLS_BY_PROXY="${TLS_BY_PROXY:-false}" +APP_BIND_ADDR="${APP_BIND_ADDR:-127.0.0.1}" +LIVEKIT_BIND_ADDR="${LIVEKIT_BIND_ADDR:-127.0.0.1}" +TURN_CERT_REMOTE="${TURN_CERT_REMOTE:-}" +TURN_CERT_REMOTE_CRT="${TURN_CERT_REMOTE_CRT:-}" +TURN_CERT_REMOTE_KEY="${TURN_CERT_REMOTE_KEY:-}" +TURN_CERT_SSH_KEY="${TURN_CERT_SSH_KEY:-}" +TURN_CERT_SSH_PORT="${TURN_CERT_SSH_PORT:-22}" TURN_TLS_PORT="${TURN_TLS_PORT:-5349}" PROXY_MODE="${PROXY_MODE:-embedded}" PROFILE="${PROFILE:-auto}" @@ -126,6 +133,14 @@ install.sh — установка glchat (AGENT.md 10). --turn-relay-tls-port TLS-порт внешнего релея (по умолчанию 5349) --tls-terminated-by-proxy TLS терминирует внешний прокси: свои 80/443 не занимаем, но ссылки в API отдаются как https/wss + --app-bind-addr адрес app (по умолчанию 127.0.0.1); для прокси по + WireGuard укажите адрес в туннеле, например 10.77.0.13 + --livekit-bind-addr адрес LiveKit (по умолчанию 127.0.0.1) + --turn-cert-remote тянуть сертификат TURN/TLS с внешнего узла по SSH + --turn-cert-remote-crt <путь> путь к сертификату на внешнем узле + --turn-cert-remote-key <путь> путь к ключу на внешнем узле + --turn-cert-ssh-key <файл> приватный SSH-ключ для загрузки сертификата + --turn-cert-ssh-port <порт> SSH-порт внешнего узла (по умолчанию 22) --turn-tls TURN TLS (по умолчанию 5349) --external-proxy не занимать 80/443: отдать маршруты внешнему прокси (AGENT.md 10.13) --no-files-subdomain не использовать files. (fallback /files/*) @@ -177,6 +192,13 @@ parse_args() { --turn-relay-port) TURN_RELAY_PORT="${2:?}"; shift 2 ;; --turn-relay-tls-port) TURN_RELAY_TLS_PORT="${2:?}"; shift 2 ;; --tls-terminated-by-proxy) TLS_BY_PROXY="true"; shift ;; + --app-bind-addr) APP_BIND_ADDR="${2:?}"; shift 2 ;; + --livekit-bind-addr) LIVEKIT_BIND_ADDR="${2:?}"; shift 2 ;; + --turn-cert-remote) TURN_CERT_REMOTE="${2:?}"; shift 2 ;; + --turn-cert-remote-crt) TURN_CERT_REMOTE_CRT="${2:?}"; shift 2 ;; + --turn-cert-remote-key) TURN_CERT_REMOTE_KEY="${2:?}"; shift 2 ;; + --turn-cert-ssh-key) TURN_CERT_SSH_KEY="${2:?}"; shift 2 ;; + --turn-cert-ssh-port) TURN_CERT_SSH_PORT="${2:?}"; shift 2 ;; --turn-tls) TURN_TLS_PORT="${2:?}"; shift 2 ;; --profile) PROFILE="${2:?}"; shift 2 ;; --max-upload) MAX_UPLOAD_MB="${2:?}"; shift 2 ;; @@ -293,10 +315,13 @@ resolve_derived_values() { GLCHAT_DATA_DIR="${GLCHAT_DATA_DIR}" GLCHAT_CADDY_PROFILE="${CADDY_PROFILE}" GLCHAT_TURN_CERT_DIR="${TURN_CERT_DIR}" + GLCHAT_APP_BIND="${APP_BIND_ADDR}" + GLCHAT_LIVEKIT_BIND="${LIVEKIT_BIND_ADDR}" export FILES_DOMAIN_RESOLVED CADDY_PROFILE LIVEKIT_PUBLISH TURN_EXTERNAL_TLS \ livekit_extra_codecs \ PROFILE_LIVEKIT_DISABLE_SIMULCAST PROFILE_LIVEKIT_USE_EXTERNAL_IP \ GLCHAT_DATA_DIR GLCHAT_CADDY_PROFILE GLCHAT_TURN_CERT_DIR \ + GLCHAT_APP_BIND GLCHAT_LIVEKIT_BIND \ TURN_DOMAIN_RESOLVED TURN_CERT_DIR TURN_RELAY_HOST_RESOLVED TURN_SECRET_RESOLVED } @@ -317,8 +342,10 @@ validate_inputs() { fi # LiveKit требует сертификат для TURN/TLS (проверено на v1.9.7: без него # сервер не стартует). Либо передаём файлы, либо отключаем TURN. - if [ "$TURN_ENABLED" = "true" ] && [ "$DRY_RUN" != "1" ] && [ -z "$TURN_CERT_SRC" ]; then - die "TURN включён, но не задан сертификат: передайте --turn-cert/--turn-key или запустите с --no-turn" + if [ "$TURN_ENABLED" = "true" ] && [ "$DRY_RUN" != "1" ] && + [ -z "$TURN_CERT_SRC" ] && [ -z "$TURN_CERT_REMOTE" ] && + [ ! -f "${TARGET_DIR}/certs/turn.crt" ]; then + die "TURN включён, но сертификат не задан: используйте --turn-cert/--turn-key, --turn-cert-remote или --no-turn" fi } @@ -548,6 +575,8 @@ TLS_BY_PROXY=${TLS_BY_PROXY} GLCHAT_DATA_DIR=${GLCHAT_DATA_DIR} GLCHAT_CADDY_PROFILE=${CADDY_PROFILE} GLCHAT_TURN_CERT_DIR=${GLCHAT_TURN_CERT_DIR} +GLCHAT_APP_BIND=${GLCHAT_APP_BIND} +GLCHAT_LIVEKIT_BIND=${GLCHAT_LIVEKIT_BIND} TURN_RELAY_HOST=${TURN_RELAY_HOST_RESOLVED} TURN_RELAY_PORT=${TURN_RELAY_PORT} TURN_RELAY_TLS_PORT=${TURN_RELAY_TLS_PORT} @@ -722,6 +751,28 @@ turn_shared_secret() { # install_turn_certificate копирует сертификат TURN/TLS туда, где его читает # контейнер LiveKit. Продление выполняет glchat-cert-refresh.timer. +fetch_remote_turn_certificate() { + [ -n "$TURN_CERT_REMOTE" ] || return 1 + local ssh_opts=(-o BatchMode=yes -o StrictHostKeyChecking=accept-new -p "$TURN_CERT_SSH_PORT") + [ -n "$TURN_CERT_SSH_KEY" ] && ssh_opts+=(-i "$TURN_CERT_SSH_KEY") + command -v ssh >/dev/null 2>&1 || { + warn "ssh не найден: не могу забрать сертификат с ${TURN_CERT_REMOTE}" + return 1 + } + ssh "${ssh_opts[@]}" "$TURN_CERT_REMOTE" "cat '${TURN_CERT_REMOTE_CRT}'" >"${TURN_CERT_DIR}/turn.crt" 2>/dev/null || { + warn "не удалось получить сертификат с ${TURN_CERT_REMOTE}:${TURN_CERT_REMOTE_CRT}" + rm -f "${TURN_CERT_DIR}/turn.crt" + return 1 + } + ssh "${ssh_opts[@]}" "$TURN_CERT_REMOTE" "cat '${TURN_CERT_REMOTE_KEY}'" >"${TURN_CERT_DIR}/turn.key" 2>/dev/null || { + warn "не удалось получить ключ с ${TURN_CERT_REMOTE}:${TURN_CERT_REMOTE_KEY}" + rm -f "${TURN_CERT_DIR}/turn.key" + return 1 + } + chmod 640 "${TURN_CERT_DIR}/turn.crt" "${TURN_CERT_DIR}/turn.key" + return 0 +} + install_turn_certificate() { [ "$TURN_ENABLED" = "true" ] || return 0 if [ "$DRY_RUN" = "1" ]; then @@ -730,6 +781,11 @@ install_turn_certificate() { fi mkdir -p "$TURN_CERT_DIR" chmod 750 "$TURN_CERT_DIR" + if [ -n "$TURN_CERT_REMOTE" ]; then + fetch_remote_turn_certificate && ok "сертификат TURN/TLS загружен с ${TURN_CERT_REMOTE}" || + warn "сертификат TURN/TLS не загружен: TURN не запустится" + return 0 + fi if [ -n "$TURN_CERT_SRC" ]; then install -m 640 "$TURN_CERT_SRC" "${TURN_CERT_DIR}/turn.crt" install -m 640 "${TURN_CERT_SRC_KEY:?нужен --turn-key}" "${TURN_CERT_DIR}/turn.key" @@ -776,6 +832,7 @@ render_all_templates() { "CADDY_IMAGE_TAG=${CADDY_IMAGE_TAG}" "LIVEKIT_IMAGE_TAG=${LIVEKIT_IMAGE_TAG}" \ "GLCHAT_CADDY_PROFILE=${CADDY_PROFILE}" \ "GLCHAT_TURN_CERT_DIR=${TURN_CERT_DIR}" \ + "GLCHAT_APP_BIND=${APP_BIND_ADDR}" "GLCHAT_LIVEKIT_BIND=${LIVEKIT_BIND_ADDR}" \ "LIVEKIT_API_KEY=${LIVEKIT_API_KEY}" "LIVEKIT_API_SECRET=${LIVEKIT_API_SECRET}" \ "HTTP_PORT=${HTTP_PORT}" "HTTPS_PORT=${HTTPS_PORT}" @@ -833,21 +890,42 @@ render_all_templates() { # install_cert_refresh копирует обновлённый сертификат TURN и перезапускает LiveKit. install_cert_refresh() { [ "$TURN_ENABLED" = "true" ] || return 0 - [ -n "$TURN_CERT_SRC" ] || return 0 + [ -n "$TURN_CERT_SRC" ] || [ -n "$TURN_CERT_REMOTE" ] || return 0 [ "$DRY_RUN" = "1" ] && return 0 cat >/usr/local/sbin/glchat-cert-refresh <&2 - exit 1 +TMP_CRT="\$(mktemp)"; TMP_KEY="\$(mktemp)" +trap 'rm -f "\$TMP_CRT" "\$TMP_KEY"' EXIT + +if [ -n "\$REMOTE" ]; then + ssh_opts="-o BatchMode=yes -o StrictHostKeyChecking=accept-new -p \$SSH_PORT" + [ -n "\$SSH_KEY" ] && ssh_opts="\$ssh_opts -i \$SSH_KEY" + # shellcheck disable=SC2086 + ssh \$ssh_opts "\$REMOTE" "cat '\$REMOTE_CRT'" >"\$TMP_CRT" || exit 1 + # shellcheck disable=SC2086 + ssh \$ssh_opts "\$REMOTE" "cat '\$REMOTE_KEY'" >"\$TMP_KEY" || exit 1 +else + if [ ! -r "\$SRC_CRT" ] || [ ! -r "\$SRC_KEY" ]; then + echo "glchat-cert-refresh: исходный сертификат недоступен" >&2 + exit 1 + fi + cp "\$SRC_CRT" "\$TMP_CRT" + cp "\$SRC_KEY" "\$TMP_KEY" fi +SRC_CRT="\$TMP_CRT"; SRC_KEY="\$TMP_KEY" before="\$(sha256sum "\$DST_DIR/turn.crt" 2>/dev/null | cut -d' ' -f1 || true)" install -m 640 "\$SRC_CRT" "\$DST_DIR/turn.crt" install -m 640 "\$SRC_KEY" "\$DST_DIR/turn.key"