#!/usr/bin/env bash # glchat — служебные команды инстанса (AGENT.md 10.7). # Ставится установщиком в /usr/local/bin/glchat. set -euo pipefail GLCHAT_ETC_DIR="${GLCHAT_ETC_DIR:-/opt/glchat}" COMPOSE_FILE="${GLCHAT_ETC_DIR}/compose.yaml" ENV_FILE="${GLCHAT_ETC_DIR}/.env" COMPOSE="docker compose -f ${COMPOSE_FILE}" usage() { cat <<'EOF' glchat — управление инстансом glchat status состояние контейнеров, версия, здоровье, ресурсы glchat logs [service] логи (follow) glchat restart [service] перезапуск стека или сервиса glchat backup бэкап БД и файлов (age-шифрование) glchat restore <файл> восстановление из бэкапа glchat verify-backup <файл> проверить бэкап, не трогая данные glchat update [--channel X] обновление (бэкап → pull → up → smoke) glchat reset-password сброс пароля пользователя (фаза 1) glchat make-admin выдать права инстанс-админа (фаза 1) glchat remove-admin снять права инстанс-админа (фаза 1) glchat unban снять бан fail2ban glchat fail2ban-status статус jails и забаненных IP glchat profile [tier] показать/применить профиль производительности glchat security-check аудит: SSH, fail2ban, порты, лимиты glchat reindex пересобрать индекс поиска (FTS5) glchat cleanup чистка: сессии, аудит, файлы без ссылок glchat create-user создать аккаунты (--count/--prefix/--sessions) glchat change-domain сменить домен glchat uninstall [--keep-data] удалить стек glchat version версия CLI и приложения EOF } require_stack() { [ -f "$COMPOSE_FILE" ] || { echo "glchat: не найден ${COMPOSE_FILE}; сначала запустите install.sh" >&2 exit 1 } } cmd_status() { require_stack local meta version health echo "== compose ==" $COMPOSE ps echo meta="$(curl -fsS "http://127.0.0.1:8080/api/v1/meta" 2>/dev/null || true)" version="$(printf '%s' "$meta" | sed -n 's/.*"version":"\([^"]*\)".*/\1/p')" health="$(curl -fsS "http://127.0.0.1:8080/readyz" 2>/dev/null || echo '{"status":"unreachable"}')" echo "== app ==" echo "version: ${version:-unknown}" echo "readyz: ${health}" echo echo "== ресурсы ==" docker stats --no-stream --format 'table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}' 2>/dev/null || true echo df -h "${GLCHAT_ETC_DIR}/data" 2>/dev/null | tail -1 || true } cmd_logs() { require_stack $COMPOSE logs -f --tail=200 "$@" } cmd_restart() { require_stack if [ "$#" -gt 0 ]; then $COMPOSE restart "$@" else $COMPOSE restart fi } cmd_backup() { require_stack bash "${GLCHAT_ETC_DIR}/backup.sh" "$@" } cmd_restore() { require_stack bash "${GLCHAT_ETC_DIR}/restore.sh" "$@" } cmd_update() { require_stack bash "${GLCHAT_ETC_DIR}/update.sh" "$@" } cmd_unban() { [ "$#" -ge 1 ] || { echo "использование: glchat unban " >&2 exit 1 } fail2ban-client unban "$1" } cmd_fail2ban_status() { fail2ban-client status for jail in $(fail2ban-client status | sed -n 's/.*Jail list:\s*//p' | tr ',' ' '); do echo fail2ban-client status "${jail// /}" done } cmd_profile() { if [ "$#" -eq 0 ]; then 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 return fi GLCHAT_ETC_DIR="$GLCHAT_ETC_DIR" bash "${GLCHAT_ETC_DIR}/install.sh" --profile "$1" --reconfigure --yes } cmd_security_check() { bash "${GLCHAT_ETC_DIR}/security-check.sh" } cmd_change_domain() { [ "$#" -ge 1 ] || { echo "использование: glchat change-domain " >&2 exit 1 } GLCHAT_ETC_DIR="$GLCHAT_ETC_DIR" bash "${GLCHAT_ETC_DIR}/install.sh" --domain "$1" --reconfigure --yes } cmd_uninstall() { require_stack local keep_data=0 [ "${1:-}" = "--keep-data" ] && keep_data=1 echo "Контейнеры glchat будут удалены. Данные: $([ "$keep_data" = 1 ] && echo 'сохраняются' || echo 'УДАЛЯЮТСЯ')" read -r -p "Продолжить? (yes/no): " reply [ "$reply" = "yes" ] || { echo "отменено" exit 0 } systemctl disable --now glchat.service 2>/dev/null || true $COMPOSE --profile embedded-proxy down --remove-orphans || true if [ "$keep_data" = 0 ]; then rm -rf "${GLCHAT_ETC_DIR}/data" fi rm -f /etc/systemd/system/glchat.service systemctl daemon-reload echo "Стек удалён. Каталог ${GLCHAT_ETC_DIR} оставлен для ручной проверки." } cmd_version() { echo "glchat CLI 0.1.0 (phase 0)" [ -f "$ENV_FILE" ] && grep -E '^(APP_VERSION|IMAGE_TAG|DOMAIN|PROFILE)=' "$ENV_FILE" || true } # cmd_verify_backup проверяет архив бэкапа: расшифровка, целостность базы и # состав данных (AGENT.md 10.8). cmd_verify_backup() { require_stack [ -n "${1:-}" ] || { echo "использование: glchat verify-backup <файл.tar.gz[.age]>" >&2 exit 2 } GLCHAT_ETC_DIR="${GLCHAT_ETC_DIR}" bash "${GLCHAT_ETC_DIR}/verify-backup.sh" "$@" } cmd_reindex() { require_stack ${COMPOSE} exec -T app glchat reindex } # cmd_cleanup запускает чистку данных вручную: те же правила, что у # периодического обслуживания (AGENT.md 6.4). cmd_cleanup() { require_stack ${COMPOSE} exec -T app glchat cleanup "$@" } # cmd_create_user создаёт аккаунты командой обслуживания: регистрация может # быть выключена, а для нагрузочного теста нужны десятки пользователей # (AGENT.md 10.7, 11.4). Флаг --sessions сразу выдаёт сессии для генератора. cmd_create_user() { require_stack ${COMPOSE} exec -T app glchat create-user "$@" } main() { local cmd="${1:-help}" shift || true case "$cmd" in status) cmd_status "$@" ;; logs) cmd_logs "$@" ;; restart) cmd_restart "$@" ;; backup) cmd_backup "$@" ;; restore) cmd_restore "$@" ;; update) cmd_update "$@" ;; reset-password | make-admin | remove-admin) echo "glchat ${cmd}: команда появится в фазе 1 (аккаунты и инстанс)." >&2 exit 1 ;; unban) cmd_unban "$@" ;; fail2ban-status) cmd_fail2ban_status "$@" ;; profile) cmd_profile "$@" ;; security-check) cmd_security_check "$@" ;; reindex) cmd_reindex "$@" ;; verify-backup) cmd_verify_backup "$@" ;; cleanup) cmd_cleanup "$@" ;; create-user) cmd_create_user "$@" ;; change-domain) cmd_change_domain "$@" ;; uninstall) cmd_uninstall "$@" ;; version) cmd_version "$@" ;; help | --help | -h) usage ;; *) echo "glchat: неизвестная команда '$cmd'" >&2 usage >&2 exit 1 ;; esac } main "$@"