Compare commits

...

4 Commits

Author SHA1 Message Date
grendervill b2a48d8008 fix(cli): glchat profile сохраняет форму развёртывания
Переключение профиля — повторная установка, и без флагов она переписала бы
compose на встроенный прокси. Теперь CLI восстанавливает домен, режим прокси,
адреса привязки, внешний TURN и ignore-ip из .env.
2026-09-22 20:24:38 +03:00
grendervill aa33f2f14d chore(security): обоснованное исключение GO-2026-5932 для trivy
openpgp из x/crypto не импортируется (govulncheck: 0 вызовов), фиксированной
версии у находки нет — иначе trivy с --exit-code 1 валит прогон AppSec.
2026-09-22 20:18:50 +03:00
grendervill 77f81dc076 docs(cli): подсказка, что unban снимает баны своего хоста 2026-09-22 20:18:15 +03:00
grendervill 3bac21ad24 fix(instance): публичная информация отдаёт voice_enabled
Поле никогда не заполнялось, поэтому /api/v1/instance всегда сообщал
voice_enabled:false, хотя голос настроен (клиент читает признак из /meta,
но контракт 6.5 обещает его и здесь).
2026-09-22 20:15:12 +03:00
3 changed files with 61 additions and 7 deletions
+5
View File
@@ -0,0 +1,5 @@
# GO-2026-5932: golang.org/x/crypto/openpgp — пакет не поддерживается.
# Мы его не импортируем: govulncheck подтверждает 0 вызовов уязвимых символов,
# фиксированной версии у находки нет. Остальные сканеры (govulncheck, gitleaks)
# чистые; см. docs/DECISIONS.md D-057.
GO-2026-5932
+48 -2
View File
@@ -9,6 +9,12 @@ ENV_FILE="${GLCHAT_ETC_DIR}/.env"
COMPOSE="docker compose -f ${COMPOSE_FILE}" COMPOSE="docker compose -f ${COMPOSE_FILE}"
# env_get читает значение из .env инстанса (пусто, если ключа нет).
env_get() {
[ -f "$ENV_FILE" ] || return 0
sed -n "s/^$1=//p" "$ENV_FILE" | tail -1
}
usage() { usage() {
cat <<'EOF' cat <<'EOF'
glchat — управление инстансом glchat — управление инстансом
@@ -23,7 +29,8 @@ glchat — управление инстансом
glchat reset-password <email> сброс пароля пользователя (фаза 1) glchat reset-password <email> сброс пароля пользователя (фаза 1)
glchat make-admin <email> выдать права инстанс-админа (фаза 1) glchat make-admin <email> выдать права инстанс-админа (фаза 1)
glchat remove-admin <email> снять права инстанс-админа (фаза 1) glchat remove-admin <email> снять права инстанс-админа (фаза 1)
glchat unban <ip> снять бан fail2ban glchat unban <ip> снять бан fail2ban (jail'ы этого хоста;
на внешнем прокси — fail2ban-client там)
glchat fail2ban-status статус jails и забаненных IP glchat fail2ban-status статус jails и забаненных IP
glchat profile [tier] показать/применить профиль производительности glchat profile [tier] показать/применить профиль производительности
glchat security-check аудит: SSH, fail2ban, порты, лимиты glchat security-check аудит: SSH, fail2ban, порты, лимиты
@@ -107,13 +114,52 @@ cmd_fail2ban_status() {
done done
} }
# cmd_profile показывает текущий профиль или переключает его. Переключение —
# это повторная установка, поэтому форма развёртывания (внешний прокси, адреса
# привязки, внешний TURN) восстанавливается из .env: иначе `glchat profile lite`
# переписал бы compose на встроенный прокси и сломал инстанс за чужим прокси.
cmd_profile() { cmd_profile() {
if [ "$#" -eq 0 ]; then if [ "$#" -eq 0 ]; then
echo "профиль: $(sed -n 's/^PROFILE=//p' "$ENV_FILE" 2>/dev/null || echo unknown)" echo "профиль: $(sed -n 's/^PROFILE=//p' "$ENV_FILE" 2>/dev/null || echo unknown)"
grep -E '^(SQLITE_|GOMAXPROCS|MAX_UPLOAD_SIZE|APP_CPUS|APP_MEMORY_LIMIT)' "$ENV_FILE" 2>/dev/null || true grep -E '^(SQLITE_|GOMAXPROCS|MAX_UPLOAD_SIZE|APP_CPUS|APP_MEMORY_LIMIT)' "$ENV_FILE" 2>/dev/null || true
return return
fi fi
GLCHAT_ETC_DIR="$GLCHAT_ETC_DIR" bash "${GLCHAT_ETC_DIR}/install.sh" --profile "$1" --reconfigure --yes require_stack
local args=()
local domain app_bind livekit_bind proxy_mode relay_host
domain="$(env_get DOMAIN)"
[ -n "$domain" ] || {
echo "glchat: в ${ENV_FILE} нет DOMAIN" >&2
exit 1
}
args=(--profile "$1" --reconfigure --yes --domain "$domain")
proxy_mode="$(env_get PROXY_MODE)"
if [ "$proxy_mode" = "external" ]; then
args+=(--external-proxy)
[ "$(env_get TLS_BY_PROXY)" = "true" ] && args+=(--tls-terminated-by-proxy)
fi
app_bind="$(env_get GLCHAT_APP_BIND)"
[ -n "$app_bind" ] && args+=(--app-bind-addr "$app_bind")
livekit_bind="$(env_get GLCHAT_LIVEKIT_BIND)"
[ -n "$livekit_bind" ] && args+=(--livekit-bind-addr "$livekit_bind")
if [ "$(env_get TURN_ENABLED)" = "false" ]; then
args+=(--no-turn)
fi
relay_host="$(env_get TURN_RELAY_HOST)"
if [ -n "$relay_host" ]; then
args+=(--turn-relay-host "$relay_host")
[ -n "$(env_get TURN_RELAY_PORT)" ] && args+=(--turn-relay-port "$(env_get TURN_RELAY_PORT)")
[ -n "$(env_get TURN_RELAY_TLS_PORT)" ] && args+=(--turn-relay-tls-port "$(env_get TURN_RELAY_TLS_PORT)")
[ -n "$(env_get TURN_RELAY_TLS_HOST)" ] && args+=(--turn-relay-tls-host "$(env_get TURN_RELAY_TLS_HOST)")
fi
local ip
for ip in $(env_get FAIL2BAN_IGNOREIP); do
args+=(--ignore-ip "$ip")
done
[ -n "$(env_get ADMIN_EMAIL)" ] && args+=(--admin "$(env_get ADMIN_EMAIL)")
[ -n "$(env_get ADMIN_PASSWORD)" ] && args+=(--password "$(env_get ADMIN_PASSWORD)")
echo "==> переключение профиля на $1 (форма развёртывания берётся из .env)"
GLCHAT_ETC_DIR="$GLCHAT_ETC_DIR" bash "${GLCHAT_ETC_DIR}/install.sh" "${args[@]}"
} }
cmd_security_check() { cmd_security_check() {
+8 -5
View File
@@ -768,11 +768,14 @@ func (s *Server) instancePayload(ctx context.Context) (instancePayload, error) {
Version: s.cfg.Version, Version: s.cfg.Version,
RegistrationEnabled: settings.RegistrationEnabled, RegistrationEnabled: settings.RegistrationEnabled,
AllowGuildCreation: settings.AllowGuildCreation, AllowGuildCreation: settings.AllowGuildCreation,
MaxGuildsPerUser: settings.MaxGuildsPerUser, // Признак «голос настроен»: клиент берёт его из /meta, но контракт
MaxMembersPerGuild: settings.MaxMembersPerGuild, // публичной информации об инстансе (AGENT.md 6.5) обещает и здесь.
MaxMessageLength: settings.MaxMessageLength, VoiceEnabled: s.cfg.VoiceEnabled(),
UserCount: users, MaxGuildsPerUser: settings.MaxGuildsPerUser,
GuildCount: len(guilds), MaxMembersPerGuild: settings.MaxMembersPerGuild,
MaxMessageLength: settings.MaxMessageLength,
UserCount: users,
GuildCount: len(guilds),
} }
if settings.MainGuildID != 0 { if settings.MainGuildID != 0 {
payload.AdminGuildID = formatSnowflake(settings.MainGuildID) payload.AdminGuildID = formatSnowflake(settings.MainGuildID)