mirror of
https://github.com/anten-ka/gotelegram_pro.git
synced 2026-05-19 10:26:05 +00:00
v2.5.0: compact traffic history retention
This commit is contained in:
@@ -453,6 +453,8 @@ switch_language ru|en
|
||||
|
||||
Функции: overview, проверка сайта на HTTP 200, service status/restart, чтение/запись `[access.users]`, enable/disable ключей через `/api/users/<name>/enabled`, генерация proxy links, общий traffic history из `/opt/gotelegram/stats_history.csv`, per-user traffic history из `/opt/gotelegram/user_stats_history.csv` с периодами 15m/1h/24h/month, current stats из `/run/gotelegram/stats_current.json`, список/создание backup, структурированные journal logs (`service`, `ok`, `exit_code`, `line_count`, `text`).
|
||||
|
||||
Traffic retention: обе CSV-истории хранятся максимум 365 дней. Последние 31 день остаются с поминутным разрешением, более старые точки автоматически уплотняются до одной последней cumulative-точки в час. Чистка/уплотнение запускается не чаще одного раза в час, а per-user сбор пишет данные не чаще одного раза в минуту, даже если systemd-loop вызывает `stats_collect` каждую секунду. Параметры можно переопределить переменными `STATS_RETENTION_DAYS`, `STATS_MINUTE_RETENTION_DAYS`, `STATS_CLEANUP_INTERVAL`.
|
||||
|
||||
### 13.1.1 Shared TCP/443 with 3x-ui/Xray
|
||||
|
||||
`lib/shared443.sh` добавляет управляемую схему shared-443 через nginx stream `ssl_preread`:
|
||||
|
||||
@@ -147,6 +147,8 @@ CLI и бот переведены на русский и английский.
|
||||
|
||||
В админке есть dashboard, проверка сайта `https://домен/` на HTTP 200, статус сервисов, полезный блок «кто слушает порт 443» по данным `ss`, управление ключами `[access.users]` с добавлением, удалением и быстрым отключением через switch, генерация ссылок, traffic history по периодам 15 минут / 1 час / 24 часа / месяц с переключателем график/строки, такая же статистика по каждому ключу, кнопка разового обновления статистики, кнопка перезапуска сборщика, список бекапов и просмотр логов с количеством строк и статусом `journalctl`.
|
||||
|
||||
История трафика хранится максимум 1 год. Чтобы файлы не разрастались, последние 31 день пишутся поминутно, а более старая история автоматически уплотняется до одной точки в час. Для обычного просмотра 15 минут / 1 час / 24 часа / месяц детализация остаётся полной.
|
||||
|
||||
## 8.1 3x-ui / VLESS на том же 443
|
||||
|
||||
Один порт `443` не могут одновременно слушать `telemt` и Xray напрямую. Для совместной работы используется схема shared-443: публичный `443` занимает nginx stream-диспетчер, goTelegram `telemt` переносится на `127.0.0.1:7443`, сайт остаётся на `127.0.0.1:8443`, а inbound 3x-ui/Xray нужно в панели перенести на внутренний адрес, например `127.0.0.1:9443`.
|
||||
|
||||
@@ -691,9 +691,26 @@ def load_user_stats_history(name: str | None = None, limit: int | None = 240) ->
|
||||
|
||||
def latest_user_stats() -> dict[str, dict[str, Any]]:
|
||||
latest: dict[str, dict[str, Any]] = {}
|
||||
for row in load_user_stats_history(limit=None):
|
||||
if row["epoch"] >= latest.get(row["user"], {}).get("epoch", 0):
|
||||
latest[row["user"]] = row
|
||||
if not USER_HISTORY_FILE.exists():
|
||||
return latest
|
||||
try:
|
||||
with USER_HISTORY_FILE.open("r", encoding="utf-8", newline="") as fh:
|
||||
for row in csv.DictReader(fh):
|
||||
user = str(row.get("user") or "").strip()
|
||||
if not USER_RE.match(user):
|
||||
continue
|
||||
item = {
|
||||
"epoch": _int_value(row.get("epoch")),
|
||||
"user": user,
|
||||
"total_octets": _int_value(row.get("total_octets")),
|
||||
"current_connections": _int_value(row.get("current_connections")),
|
||||
"active_unique_ips": _int_value(row.get("active_unique_ips")),
|
||||
"recent_unique_ips": _int_value(row.get("recent_unique_ips")),
|
||||
}
|
||||
if item["epoch"] >= latest.get(user, {}).get("epoch", 0):
|
||||
latest[user] = item
|
||||
except OSError:
|
||||
return {}
|
||||
return latest
|
||||
|
||||
|
||||
|
||||
100
lib/stats.sh
100
lib/stats.sh
@@ -17,6 +17,11 @@ SNAPSHOTS_DIR="$STATS_DIR/snapshots"
|
||||
CURRENT_SNAPSHOT="$STATS_DIR/stats_current.json"
|
||||
CONFIG_FILE="/opt/gotelegram/config.json"
|
||||
TELEMT_CONFIG_FILE="/etc/telemt/config.toml"
|
||||
STATS_RETENTION_DAYS="${STATS_RETENTION_DAYS:-365}"
|
||||
STATS_MINUTE_RETENTION_DAYS="${STATS_MINUTE_RETENTION_DAYS:-31}"
|
||||
STATS_CLEANUP_INTERVAL="${STATS_CLEANUP_INTERVAL:-3600}"
|
||||
STATS_CLEANUP_STAMP="$STATS_DIR/last_history_cleanup"
|
||||
USER_STATS_COLLECT_STAMP="$STATS_DIR/last_user_stats_minute"
|
||||
|
||||
# Initialize stats infrastructure
|
||||
stats_init() {
|
||||
@@ -124,7 +129,7 @@ EOF
|
||||
if [[ "$last_ts" -eq 0 ]] || [[ $((current_minute - last_ts)) -ge 60 ]]; then
|
||||
echo "$current_minute,$proxy_bytes,$site_bytes" >> "$HISTORY_FILE" 2>/dev/null
|
||||
|
||||
# Cleanup old entries (keep only 365 days)
|
||||
# Cleanup/compact history at most once per hour.
|
||||
stats_cleanup_history
|
||||
fi
|
||||
fi
|
||||
@@ -163,10 +168,17 @@ stats_collect_users() {
|
||||
command -v curl &>/dev/null || return 0
|
||||
command -v jq &>/dev/null || return 0
|
||||
|
||||
if [[ -f "$USER_STATS_COLLECT_STAMP" ]] && [[ "$(cat "$USER_STATS_COLLECT_STAMP" 2>/dev/null)" == "$current_minute" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local existing_users=""
|
||||
existing_users=$(awk -F, -v ts="$current_minute" '$1 == ts { print $2 }' "$USER_HISTORY_FILE" 2>/dev/null || true)
|
||||
|
||||
local user payload total conns active_ips recent_ips
|
||||
while IFS= read -r user; do
|
||||
[[ -n "$user" ]] || continue
|
||||
if awk -F, -v ts="$current_minute" -v user="$user" '$1 == ts && $2 == user { found=1 } END { exit found ? 0 : 1 }' "$USER_HISTORY_FILE" 2>/dev/null; then
|
||||
if printf '%s\n' "$existing_users" | grep -Fxq "$user"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
@@ -183,6 +195,7 @@ stats_collect_users() {
|
||||
echo "$current_minute,$user,$total,$conns,$active_ips,$recent_ips" >> "$USER_HISTORY_FILE" 2>/dev/null
|
||||
done < <(stats_active_users)
|
||||
|
||||
echo "$current_minute" > "$USER_STATS_COLLECT_STAMP" 2>/dev/null || true
|
||||
stats_cleanup_user_history
|
||||
}
|
||||
|
||||
@@ -348,20 +361,66 @@ show_traffic_stats() {
|
||||
} >&2
|
||||
}
|
||||
|
||||
# Clean up history older than 365 days
|
||||
_stats_positive_int() {
|
||||
local value="${1:-0}"
|
||||
[[ "$value" =~ ^[0-9]+$ ]] && [[ "$value" -gt 0 ]] && echo "$value" || echo "$2"
|
||||
}
|
||||
|
||||
stats_should_cleanup() {
|
||||
local stamp="$1"
|
||||
local now last interval
|
||||
mkdir -p "$STATS_DIR" 2>/dev/null || true
|
||||
now=$(date +%s)
|
||||
interval=$(_stats_positive_int "$STATS_CLEANUP_INTERVAL" 3600)
|
||||
last=$(cat "$stamp" 2>/dev/null || echo 0)
|
||||
[[ "$last" =~ ^[0-9]+$ ]] || last=0
|
||||
if (( now - last < interval )); then
|
||||
return 1
|
||||
fi
|
||||
echo "$now" > "$stamp" 2>/dev/null || true
|
||||
return 0
|
||||
}
|
||||
|
||||
stats_retention_cutoffs() {
|
||||
local now retention_days minute_days
|
||||
now=$(date +%s)
|
||||
retention_days=$(_stats_positive_int "$STATS_RETENTION_DAYS" 365)
|
||||
minute_days=$(_stats_positive_int "$STATS_MINUTE_RETENTION_DAYS" 31)
|
||||
if (( minute_days > retention_days )); then
|
||||
minute_days="$retention_days"
|
||||
fi
|
||||
echo "$((now - retention_days * 86400)) $((now - minute_days * 86400))"
|
||||
}
|
||||
|
||||
# Keep history for at most one year. Recent points stay per-minute; older
|
||||
# points are compacted to one last cumulative snapshot per hour.
|
||||
stats_cleanup_history() {
|
||||
if [[ ! -f "$HISTORY_FILE" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
local now=$(date +%s)
|
||||
local ts_365d=$((now - 31536000))
|
||||
local temp_file=$(mktemp)
|
||||
stats_should_cleanup "$STATS_CLEANUP_STAMP" || return 0
|
||||
|
||||
local retention_cutoff minute_cutoff temp_file
|
||||
read -r retention_cutoff minute_cutoff <<< "$(stats_retention_cutoffs)"
|
||||
temp_file=$(mktemp)
|
||||
|
||||
# Keep header + entries from last 365 days
|
||||
{
|
||||
head -1 "$HISTORY_FILE"
|
||||
awk -F, -v ts="$ts_365d" '$1 >= ts' "$HISTORY_FILE" | tail -n +2
|
||||
awk -F, -v keep="$retention_cutoff" -v minute="$minute_cutoff" '
|
||||
BEGIN { OFS="," }
|
||||
NR == 1 { next }
|
||||
$1 !~ /^[0-9]+$/ { next }
|
||||
$1 < keep { next }
|
||||
$1 >= minute { print $1, $2, $3; next }
|
||||
{
|
||||
bucket = int($1 / 3600)
|
||||
compact[bucket] = $1 OFS $2 OFS $3
|
||||
}
|
||||
END {
|
||||
for (bucket in compact) print compact[bucket]
|
||||
}
|
||||
' "$HISTORY_FILE" | sort -t, -k1,1n
|
||||
} > "$temp_file" 2>/dev/null
|
||||
|
||||
mv "$temp_file" "$HISTORY_FILE" 2>/dev/null
|
||||
@@ -372,13 +431,28 @@ stats_cleanup_user_history() {
|
||||
return
|
||||
fi
|
||||
|
||||
local now=$(date +%s)
|
||||
local ts_365d=$((now - 31536000))
|
||||
local temp_file=$(mktemp)
|
||||
stats_should_cleanup "${STATS_CLEANUP_STAMP}.users" || return 0
|
||||
|
||||
local retention_cutoff minute_cutoff temp_file
|
||||
read -r retention_cutoff minute_cutoff <<< "$(stats_retention_cutoffs)"
|
||||
temp_file=$(mktemp)
|
||||
|
||||
{
|
||||
head -1 "$USER_HISTORY_FILE"
|
||||
awk -F, -v ts="$ts_365d" '$1 >= ts' "$USER_HISTORY_FILE" | tail -n +2
|
||||
awk -F, -v keep="$retention_cutoff" -v minute="$minute_cutoff" '
|
||||
BEGIN { OFS="," }
|
||||
NR == 1 { next }
|
||||
$1 !~ /^[0-9]+$/ { next }
|
||||
$1 < keep { next }
|
||||
$1 >= minute { print $1, $2, $3, $4, $5, $6; next }
|
||||
{
|
||||
bucket = $2 SUBSEP int($1 / 3600)
|
||||
compact[bucket] = $1 OFS $2 OFS $3 OFS $4 OFS $5 OFS $6
|
||||
}
|
||||
END {
|
||||
for (bucket in compact) print compact[bucket]
|
||||
}
|
||||
' "$USER_HISTORY_FILE" | sort -t, -k1,1n -k2,2
|
||||
} > "$temp_file" 2>/dev/null
|
||||
|
||||
mv "$temp_file" "$USER_HISTORY_FILE" 2>/dev/null
|
||||
@@ -516,5 +590,5 @@ remove_stats_collector() {
|
||||
# Export functions for external use
|
||||
export -f stats_init stats_collect stats_collect_users stats_active_users stats_read_current stats_calculate_rates
|
||||
export -f show_traffic_stats format_bytes format_rate toggle_stats
|
||||
export -f stats_cleanup_history stats_cleanup_user_history install_stats_collector remove_stats_collector
|
||||
export -f stats_cleanup_history stats_cleanup_user_history stats_should_cleanup stats_retention_cutoffs install_stats_collector remove_stats_collector
|
||||
export -f json_get
|
||||
|
||||
Reference in New Issue
Block a user