[LightLayer]研究了一下官网API,搓了一个检测流量和续费的脚本,可以TG推送

Flanker 2026-08-21 16:51 1



说到这里就要顺嘴严肃批评DMIT了,连个API都给不出来....


使用方法


mkdir /opt/lightlayer
cd /opt/lightlayer
nano install-lightlayer.sh

---复制下面的 粘贴保存------

chmod +x install-lightlayer.sh
bash /opt/lightlayer/install-lightlayer.sh

下面是脚本


#!/usr/bin/env bash
set -Eeuo pipefail

# ============================================================
# LightLayer Monitor - Final Installer
# Install root: /opt/lightlayer
#
# Features:
# - LightLayer API query
# - Interactive service viewer
# - Metered / unlimited traffic detection
# - Port bandwidth display
# - Telegram 4-service compact table
# - DUE + TTL countdown
# - .env secrets (0600)
# - systemd daily persistent report
# ============================================================

INSTALL_DIR="/opt/lightlayer"
APP="${INSTALL_DIR}/lightlayer.sh"
ENV_FILE="${INSTALL_DIR}/.env"
STATE_DIR="${INSTALL_DIR}/state"
SYMLINK="/usr/local/bin/lightlayer"

SERVICE_FILE="/etc/systemd/system/lightlayer-report.service"
TIMER_FILE="/etc/systemd/system/lightlayer-report.timer"

SERVICE_NAME="lightlayer-report.service"
TIMER_NAME="lightlayer-report.timer"

if [[ ${EUID} -ne 0 ]]; then
echo "[ERROR] Please run as root."
exit 1
fi

say() { printf '%s\n' "$*"; }
ok() { printf '[OK] %s\n' "$*"; }
warn() { printf '[WARN] %s\n' "$*" >&2; }
die() { printf '[ERROR] %s\n' "$*" >&2; exit 1; }

install_deps() {
local missing=()
command -v curl >/dev/null 2>&1 || missing+=(curl)
command -v jq >/dev/null 2>&1 || missing+=(jq)

if ((${#missing[@]} == 0)); then
ok "Dependencies already installed."
return
fi

say "Installing dependencies: ${missing[*]}"

if command -v apt-get >/dev/null 2>&1; then
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y curl jq ca-certificates
elif command -v apk >/dev/null 2>&1; then
apk add --no-cache curl jq ca-certificates
elif command -v dnf >/dev/null 2>&1; then
dnf install -y curl jq ca-certificates
elif command -v yum >/dev/null 2>&1; then
yum install -y curl jq ca-certificates
else
die "Unsupported package manager. Please install curl and jq manually."
fi
}

shell_quote() {
printf '%q' "$1"
}

write_env() {
local key="$1"
local value="$2"
printf '%s=' "$key" >>"$ENV_FILE"
shell_quote "$value" >>"$ENV_FILE"
printf '\n' >>"$ENV_FILE"
}

validate_time() {
[[ "$1" =~ ^([01][0-9]|2[0-3]):[0-5][0-9]$ ]]
}

echo
echo "============================================================"
echo " LightLayer Monitor - Final Installer"
echo "============================================================"
echo
echo "Install directory: ${INSTALL_DIR}"
echo

install_deps

if [[ -e "$INSTALL_DIR" ]]; then
echo
warn "${INSTALL_DIR} already exists."
read -rp "Type YES to replace it completely: " REPLACE
[[ "$REPLACE" == "YES" ]] || die "Installation cancelled."

systemctl stop "$TIMER_NAME" 2>/dev/null || true
systemctl disable "$TIMER_NAME" 2>/dev/null || true
systemctl stop "$SERVICE_NAME" 2>/dev/null || true

rm -rf -- "$INSTALL_DIR"
fi

echo
read -rp "LightLayer account email: " LL_EMAIL
read -rsp "LightLayer account password: " LL_PASSWORD
echo

read -rsp "Telegram Bot Token: " TG_TOKEN
echo
read -rp "Telegram Channel Chat ID (@channel or -100...): " TG_CHAT_ID

read -rp "Daily report time [09:00]: " REPORT_TIME
REPORT_TIME="${REPORT_TIME:-09:00}"
validate_time "$REPORT_TIME" || die "Invalid time format. Use HH:MM."

read -rp "Report timezone [Asia/Shanghai]: " REPORT_TZ
REPORT_TZ="${REPORT_TZ:-Asia/Shanghai}"

if [[ ! -e "/usr/share/zoneinfo/${REPORT_TZ}" ]]; then
warn "Timezone file /usr/share/zoneinfo/${REPORT_TZ} not found."
read -rp "Continue anyway? [y/N]: " TZ_CONTINUE
[[ "${TZ_CONTINUE,,}" == "y" ]] || die "Installation cancelled."
fi

[[ -n "$LL_EMAIL" ]] || die "LightLayer email cannot be empty."
[[ -n "$LL_PASSWORD" ]] || die "LightLayer password cannot be empty."
[[ -n "$TG_TOKEN" ]] || die "Telegram Bot Token cannot be empty."
[[ -n "$TG_CHAT_ID" ]] || die "Telegram Chat ID cannot be empty."

mkdir -p "$INSTALL_DIR" "$STATE_DIR"
chmod 700 "$INSTALL_DIR" "$STATE_DIR"

: >"$ENV_FILE"

write_env "LIGHTLAYER_EMAIL" "$LL_EMAIL"
write_env "LIGHTLAYER_PASSWORD" "$LL_PASSWORD"
write_env "TELEGRAM_BOT_TOKEN" "$TG_TOKEN"
write_env "TELEGRAM_CHAT_ID" "$TG_CHAT_ID"
write_env "REPORT_TZ" "$REPORT_TZ"

cat >>"$ENV_FILE" <<'ENV_EOF'

# Port bandwidth overrides.
# API currently does not expose a reliable Mbps/Gbps field for these plans,
# so known Service IDs are mapped here. Change only .env when plans change.
BANDWIDTH_58534=1G
BANDWIDTH_46627=10M
BANDWIDTH_32144=10M
BANDWIDTH_32122=10M
ENV_EOF

chmod 600 "$ENV_FILE"
chown root:root "$ENV_FILE"

unset LL_PASSWORD TG_TOKEN

cat >"$APP" <<'APP_EOF'
#!/usr/bin/env bash
set -Eeuo pipefail

BASE_DIR="/opt/lightlayer"
ENV_FILE="${BASE_DIR}/.env"
API_BASE="https://account.lightlayer.net/api"

TOKEN=""
SERVICES_JSON=""

if [[ ! -r "$ENV_FILE" ]]; then
echo "[ERROR] Missing ${ENV_FILE}" >&2
exit 1
fi

# shellcheck disable=SC1090
source "$ENV_FILE"

: "${LIGHTLAYER_EMAIL:?Missing LIGHTLAYER_EMAIL}"
: "${LIGHTLAYER_PASSWORD:?Missing LIGHTLAYER_PASSWORD}"
: "${TELEGRAM_BOT_TOKEN:?Missing TELEGRAM_BOT_TOKEN}"
: "${TELEGRAM_CHAT_ID:?Missing TELEGRAM_CHAT_ID}"
REPORT_TZ="${REPORT_TZ:-Asia/Shanghai}"

if [[ -t 1 ]]; then
RED=$'\033[0;31m'
GREEN=$'\033[0;32m'
YELLOW=$'\033[1;33m'
CYAN=$'\033[0;36m'
BOLD=$'\033[1m'
DIM=$'\033[2m'
RESET=$'\033[0m'
else
RED='' GREEN='' YELLOW='' CYAN='' BOLD='' DIM='' RESET=''
fi

line() {
printf '%*s\n' 98 '' | tr ' ' '-'
}

die() {
printf '%s[ERROR]%s %s\n' "$RED" "$RESET" "$*" >&2
exit 1
}

warn() {
printf '%s[WARN]%s %s\n' "$YELLOW" "$RESET" "$*" >&2
}

ok() {
printf '%s[OK]%s %s\n' "$GREEN" "$RESET" "$*"
}

cleanup() {
[[ -n "${TOKEN:-}" ]] || return 0

{
printf 'url = "%s/logout"\n' "$API_BASE"
printf 'header = "Authorization: Bearer %s"\n' "$TOKEN"
} | curl -sS --config - \
--connect-timeout 5 \
--max-time 10 \
-X POST \
>/dev/null 2>&1 || true

TOKEN=""
}

trap cleanup EXIT INT TERM

api_login() {
local response

response="$(
jq -nc \
--arg username "$LIGHTLAYER_EMAIL" \
--arg password "$LIGHTLAYER_PASSWORD" \
'{username:$username,password:$password}' |
curl -fsS \
--connect-timeout 10 \
--max-time 30 \
-X POST \
"${API_BASE}/login" \
-H 'Content-Type: application/json' \
--data-binary @-
)" || die "LightLayer login request failed."

TOKEN="$(jq -r '.token // empty' <<<"$response")"

[[ -n "$TOKEN" ]] || die "LightLayer login failed: token missing."
}

api_get() {
local path="$1"

{
printf 'url = "%s%s"\n' "$API_BASE" "$path"
printf 'header = "Authorization: Bearer %s"\n' "$TOKEN"
} | curl -fsS --config - \
--connect-timeout 10 \
--max-time 30
}

load_services() {
SERVICES_JSON="$(api_get "/service")" || die "Unable to load service list."

jq -e '.services | type == "array"' \
>/dev/null 2>&1 <<<"$SERVICES_JSON" ||
die "Unexpected /service response."
}

days_left() {
local due="$1"

if [[ ! "$due" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
echo "-"
return
fi

local due_ts now_ts
due_ts="$(TZ="$REPORT_TZ" date -d "${due} 23:59:59" +%s 2>/dev/null)" || {
echo "-"
return
}
now_ts="$(TZ="$REPORT_TZ" date +%s)"

echo $(( (due_ts - now_ts) / 86400 ))
}

ttl_text() {
local days="$1"

if [[ ! "$days" =~ ^-?[0-9]+$ ]]; then
printf -- "--"
elif (( days < 0 )); then
printf "EXP"
elif (( days == 0 )); then
printf "0d"
else
printf "%dd" "$days"
fi
}

due_short() {
local due="$1"

if [[ "$due" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
date -d "$due" '+%y-%m-%d' 2>/dev/null || printf '%s' "$due"
else
printf -- "--"
fi
}

fmt_bytes_short() {
local bytes="${1:-0}"

awk -v n="$bytes" '
BEGIN {
gb=n/1000000000
if (gb >= 1000) printf "%.2fT", gb/1000
else printf "%.2fG", gb
}
'
}

fmt_gb_short() {
local gb="${1:-0}"

awk -v n="$gb" '
BEGIN {
if (n >= 1000) printf "%.2fT", n/1000
else printf "%.2fG", n
}
'
}

fmt_gb_long() {
local gb="${1:-0}"

awk -v n="$gb" '
BEGIN {
if (n >= 1000) printf "%.3f TB", n/1000
else printf "%.3f GB", n
}
'
}

fmt_bytes_long() {
local bytes="${1:-0}"

awk -v n="$bytes" '
BEGIN {
gb=n/1000000000
if (gb >= 1000) printf "%.3f TB", gb/1000
else printf "%.3f GB", gb
}
'
}

percent() {
local used="${1:-0}"
local total="${2:-0}"

awk -v u="$used" -v t="$total" '
BEGIN {
if (t <= 0) printf "0.0"
else printf "%.1f", u/t*100
}
'
}

progress_bar() {
local pct="${1:-0}"
local width=28
local fill empty

fill="$(
awk -v p="$pct" -v w="$width" '
BEGIN {
n=int(p*w/100+0.5)
if(n<0)n=0
if(n>w)n=w
print n
}
'
)"

empty=$((width-fill))

printf '['
printf '%*s' "$fill" '' | tr ' ' '#'
printf '%*s' "$empty" '' | tr ' ' '-'
printf ']'
}

html_escape() {
local s="$1"
s="${s//&/&amp;}"
s="${s//</&lt;}"
s="${s//>/&gt;}"
printf '%s' "$s"
}

bandwidth_for() {
local sid="$1"
local name="$2"
local key="BANDWIDTH_${sid}"
local value="${!key:-}"

if [[ -n "$value" ]]; then
printf '%s' "$value"
return
fi

case "$name" in
SJ-VP04-L) printf '1G' ;;
SG-VP02-A) printf '10M' ;;
'HK-VP02-A(SY)') printf '10M' ;;
TW-VP03-A) printf '10M' ;;
*) printf -- '--' ;;
esac
}

service_basic() {
local sid="$1"

jq -c --arg sid "$sid" '
.services[]
| select((.id|tostring) == $sid)
' <<<"$SERVICES_JSON"
}

get_service_data() {
local sid="$1"

SERVICE_DETAIL="$(api_get "/service/${sid}" 2>/dev/null || printf '{}')"
RESOURCE_JSON="$(api_get "/service/${sid}/resources" 2>/dev/null || printf '{}')"
IP_JSON="$(api_get "/service/${sid}/ip" 2>/dev/null || printf '{}')"
VMS_JSON="$(api_get "/service/${sid}/vms" 2>/dev/null || printf '{}')"
}

extract_metrics() {
local sid="$1"
local basic
basic="$(service_basic "$sid")"

NAME="$(
jq -r '.service.name // .name // empty' <<<"$SERVICE_DETAIL"
)"
[[ -n "$NAME" ]] || NAME="$(jq -r '.name // "-"' <<<"$basic")"

LABEL="$(
jq -r '.service.domain // .domain // "-"' <<<"$SERVICE_DETAIL"
)"

STATUS="$(
jq -r '.service.status // .status // empty' <<<"$SERVICE_DETAIL"
)"
[[ -n "$STATUS" ]] || STATUS="$(jq -r '.status // "-"' <<<"$basic")"

CYCLE="$(
jq -r '.service.billingcycle // .billingcycle // empty' <<<"$SERVICE_DETAIL"
)"
[[ -n "$CYCLE" ]] || CYCLE="$(jq -r '.billingcycle // "-"' <<<"$basic")"

CREATED="$(
jq -r '.service.date_created // .date_created // "-"' <<<"$SERVICE_DETAIL"
)"

DUE="$(
jq -r '.service.next_due // .next_due // empty' <<<"$SERVICE_DETAIL"
)"
[[ -n "$DUE" ]] || DUE="$(jq -r '.next_due // "-"' <<<"$basic")"

NEXT_INVOICE="$(
jq -r '.service.next_invoice // .next_invoice // "-"' <<<"$SERVICE_DETAIL"
)"

PRICE="$(
jq -r '.service.total // .total // empty' <<<"$SERVICE_DETAIL"
)"
[[ -n "$PRICE" ]] || PRICE="$(jq -r '.total // "-"' <<<"$basic")"

IPV4="$(
jq -r '
[
.ips[]?
| select(.status == "assigned")
| .ipaddress
]
| first // "-"
' <<<"$IP_JSON"
)"

if [[ "$IPV4" == "-" ]]; then
IPV4="$(
jq -r '
[
(.vms // {})
| to_entries[]
| .value.ipv4? // empty
]
| first // "-"
' <<<"$VMS_JSON"
)"
fi

VMID="$(
jq -r '
[
(.vms // {})
| to_entries[]
| .value.id? // empty
]
| first // "-"
' <<<"$VMS_JSON"
)"

VM_STATUS="$(
jq -r '
[
(.vms // {})
| to_entries[]
| .value.status? // empty
]
| unique
| join(",")
' <<<"$VMS_JSON"
)"
[[ -n "$VM_STATUS" ]] || VM_STATUS="-"

CPU_CORES="$(
jq -r '
[
(.vms // {})
| to_entries[]
| (.value.cpus? // .value.cores? // 0)
| tonumber
]
| add // 0
' <<<"$VMS_JSON" 2>/dev/null || printf '0'
)"

RAM_MB="$(
jq -r '
[
(.vms // {})
| to_entries[]
| (.value.memory? // 0)
| tonumber
]
| add // 0
' <<<"$VMS_JSON" 2>/dev/null || printf '0'
)"

DISK_GB="$(
jq -r '
[
(.vms // {})
| to_entries[]
| (.value.disk? // 0)
| tonumber
]
| add // 0
' <<<"$VMS_JSON" 2>/dev/null || printf '0'
)"

RX_BYTES="$(
jq -r '
[
(.vms // {})
| to_entries[]
| (.value.bandwidth.data_received? // 0)
| tonumber
]
| add // 0
' <<<"$VMS_JSON" 2>/dev/null || printf '0'
)"

TX_BYTES="$(
jq -r '
[
(.vms // {})
| to_entries[]
| (.value.bandwidth.data_sent? // 0)
| tonumber
]
| add // 0
' <<<"$VMS_JSON" 2>/dev/null || printf '0'
)"

VM_USED_GB="$(
awk -v rx="$RX_BYTES" -v tx="$TX_BYTES" '
BEGIN { printf "%.9f", (rx+tx)/1000000000 }
'
)"

RESOURCE_HAS_TRAFFIC=0
if jq -e '
(.total | type == "object")
and (.limit | type == "object")
and (.total | has("data_combined"))
and (.limit | has("data_combined"))
' >/dev/null 2>&1 <<<"$RESOURCE_JSON"; then
RESOURCE_HAS_TRAFFIC=1
fi

TRAFFIC_TOTAL="$(
jq -r '.total.data_combined // 0' <<<"$RESOURCE_JSON"
)"
TRAFFIC_REMAIN="$(
jq -r '.limit.data_combined // 0' <<<"$RESOURCE_JSON"
)"

TRAFFIC_MODE="unknown"
TRAFFIC_USED="$VM_USED_GB"
TRAFFIC_PERCENT="--"

if (( RESOURCE_HAS_TRAFFIC == 1 )); then
if awk -v t="$TRAFFIC_TOTAL" 'BEGIN {exit !(t > 0)}'; then
TRAFFIC_MODE="metered"
TRAFFIC_USED="$(
awk -v t="$TRAFFIC_TOTAL" -v r="$TRAFFIC_REMAIN" '
BEGIN {
u=t-r
if(u<0)u=0
printf "%.9f", u
}
'
)"
TRAFFIC_PERCENT="$(percent "$TRAFFIC_USED" "$TRAFFIC_TOTAL")"
else
TRAFFIC_MODE="unlimited"
TRAFFIC_TOTAL="0"
TRAFFIC_REMAIN="0"
TRAFFIC_USED="$VM_USED_GB"
TRAFFIC_PERCENT="--"
fi
fi

PORT="$(bandwidth_for "$sid" "$NAME")"
DAYS="$(days_left "$DUE")"
TTL="$(ttl_text "$DAYS")"
}

traffic_table_fields() {
case "$TRAFFIC_MODE" in
metered)
USED_LIMIT="$(fmt_gb_short "$TRAFFIC_USED")/$(fmt_gb_short "$TRAFFIC_TOTAL")"
LEFT_TEXT="$(fmt_gb_short "$TRAFFIC_REMAIN")"
USE_TEXT="${TRAFFIC_PERCENT}%"
;;
unlimited)
USED_LIMIT="$(fmt_gb_short "$TRAFFIC_USED")/INF"
LEFT_TEXT="INF"
USE_TEXT="--"
;;
*)
USED_LIMIT="N/A"
LEFT_TEXT="N/A"
USE_TEXT="--"
;;
esac
}

print_detail() {
local sid="$1"

while true; do
get_service_data "$sid"
extract_metrics "$sid"

clear 2>/dev/null || true

printf '%s%sLightLayer · %s%s\n' "$CYAN" "$BOLD" "$NAME" "$RESET"
line

printf '%-18s %s\n' "Service ID" "$sid"
printf '%-18s %s\n' "VMID" "$VMID"
printf '%-18s %s\n' "Label" "$LABEL"
printf '%-18s %s / %s\n' "Status" "$STATUS" "$VM_STATUS"
printf '%-18s %s\n' "IPv4" "$IPV4"
printf '%-18s %s\n' "Port" "$PORT"
printf '%-18s %s vCPU / %s MB / %s GB\n' "Spec" "$CPU_CORES" "$RAM_MB" "$DISK_GB"

echo
printf '%-18s %s\n' "Created" "$CREATED"
printf '%-18s %s\n' "Billing cycle" "$CYCLE"
printf '%-18s %s\n' "Price" "$PRICE"
printf '%-18s %s\n' "Next invoice" "$NEXT_INVOICE"
printf '%-18s %s (%s)\n' "Next due" "$DUE" "$TTL"

echo
printf '%sTraffic%s\n' "$BOLD" "$RESET"
line

case "$TRAFFIC_MODE" in
metered)
printf '%-18s %s / %s\n' \
"Used / Limit" \
"$(fmt_gb_long "$TRAFFIC_USED")" \
"$(fmt_gb_long "$TRAFFIC_TOTAL")"

printf '%-18s %s\n' \
"Remaining" \
"$(fmt_gb_long "$TRAFFIC_REMAIN")"

printf '%-18s %s%% ' "Usage" "$TRAFFIC_PERCENT"
progress_bar "$TRAFFIC_PERCENT"
echo
;;
unlimited)
printf '%-18s %s\n' "Plan" "Unlimited traffic"
printf '%-18s %s\n' "Current transfer" "$(fmt_gb_long "$TRAFFIC_USED")"
;;
*)
printf '%-18s %s\n' "Traffic" "Unavailable"
;;
esac

printf '%-18s %s\n' "RX" "$(fmt_bytes_long "$RX_BYTES")"
printf '%-18s %s\n' "TX" "$(fmt_bytes_long "$TX_BYTES")"

echo
line
echo "[Enter] Back [R] Refresh [Q] Quit"
read -r -n1 key || true
echo

case "${key,,}" in
r) continue ;;
q) exit 0 ;;
*) return ;;
esac
done
}

print_summary() {
printf '%s%sLightLayer All Services%s\n' "$CYAN" "$BOLD" "$RESET"
line
printf '%-7s %-18s %-15s %-5s %-10s %-6s %-22s\n' \
"ID" "NAME" "IPv4" "PORT" "DUE" "TTL" "TRAFFIC"
line

while IFS= read -r sid; do
get_service_data "$sid"
extract_metrics "$sid"
traffic_table_fields

printf '%-7s %-18.18s %-15s %-5s %-10s %-6s %-22s\n' \
"$sid" "$NAME" "$IPV4" "$PORT" "$DUE" "$TTL" "$USED_LIMIT"
done < <(jq -r '.services[].id | tostring' <<<"$SERVICES_JSON")
}

build_report() {
local report_time total_count active_count metered_count unlimited_count
local nearest_days=999999 nearest_name="" nearest_due=""
local rows=""

report_time="$(TZ="$REPORT_TZ" date '+%Y-%m-%d %H:%M %Z')"
total_count="$(jq '.services | length' <<<"$SERVICES_JSON")"
active_count=0
metered_count=0
unlimited_count=0

while IFS= read -r sid; do
get_service_data "$sid"
extract_metrics "$sid"
traffic_table_fields

if [[ "${STATUS,,}" == "active" ]]; then
active_count=$((active_count+1))
fi

case "$TRAFFIC_MODE" in
metered) metered_count=$((metered_count+1)) ;;
unlimited) unlimited_count=$((unlimited_count+1)) ;;
esac

if [[ "$DAYS" =~ ^[0-9]+$ ]] && (( DAYS < nearest_days )); then
nearest_days="$DAYS"
nearest_name="$NAME"
nearest_due="$DUE"
fi

local short_due
short_due="$(due_short "$DUE")"

printf -v row '%-15.15s %-5s %-13s %-9s %-5s %-8s %-6s' \
"$NAME" "$PORT" "$USED_LIMIT" "$LEFT_TEXT" "$USE_TEXT" "$short_due" "$TTL"

rows+="${row}"$'\n'
done < <(jq -r '.services[].id | tostring' <<<"$SERVICES_JSON")

local table
table=$'NODE PORT USED/LIMIT LEFT USE DUE TTL\n'
table+=$'-------------------------------------------------------------------\n'
table+="$rows"

local safe_table safe_nearest
safe_table="$(html_escape "$table")"
safe_nearest="$(html_escape "${nearest_name:-N/A}")"

REPORT_HTML="<b>🟩 LightLayer Traffic Report</b>
<code>${active_count}/${total_count} Active</code> · <code>$(html_escape "$report_time")</code>

<pre>${safe_table}</pre>
📊 Metered ${metered_count} · Unlimited ${unlimited_count}"

if [[ -n "$nearest_name" ]]; then
REPORT_HTML+="
⏳ Next due: <b>${safe_nearest}</b> · ${nearest_days}d · <code>$(html_escape "$nearest_due")</code>"
fi
}

telegram_send() {
local text="$1"
local response

response="$(
{
printf 'url = "https://api.telegram.org/bot%s/sendMessage"\n' "$TELEGRAM_BOT_TOKEN"
} | curl -fsS --config - \
--connect-timeout 10 \
--max-time 30 \
-X POST \
--data-urlencode "chat_id=${TELEGRAM_CHAT_ID}" \
--data-urlencode "parse_mode=HTML" \
--data-urlencode "disable_web_page_preview=true" \
--data-urlencode "text=${text}"
)" || die "Telegram request failed."

if ! jq -e '.ok == true' >/dev/null 2>&1 <<<"$response"; then
warn "Telegram returned an error:"
jq -r '.description // "Unknown Telegram error"' <<<"$response" >&2
return 1
fi
}

send_report() {
build_report
telegram_send "$REPORT_HTML"
ok "Telegram report sent."
}

interactive_menu() {
while true; do
clear 2>/dev/null || true

printf '%s%sLightLayer Services%s\n' "$CYAN" "$BOLD" "$RESET"
line
printf '%-4s %-7s %-22s %-10s %-12s %-10s\n' \
"NO." "ID" "NAME" "STATUS" "DUE" "CYCLE"
line

jq -r '
.services[]
| [
(.id|tostring),
(.name // "-"),
(.status // "-"),
(.next_due // "-"),
(.billingcycle // "-")
]
| @tsv
' <<<"$SERVICES_JSON" |
awk -F '\t' '{
printf "%-4d %-7s %-22.22s %-10s %-12s %-10s\n",
NR,$1,$2,$3,$4,$5
}'

echo
echo "Number: details A: all summary S: send Telegram R: refresh Q: quit"
read -rp "> " choice

case "${choice,,}" in
q)
exit 0
;;
r)
load_services
;;
a)
clear 2>/dev/null || true
print_summary
echo
read -rp "Press Enter to return..." _
;;
s)
send_report
sleep 2
;;
*)
if [[ "$choice" =~ ^[0-9]+$ ]]; then
local count sid
count="$(jq '.services | length' <<<"$SERVICES_JSON")"
if (( choice >= 1 && choice <= count )); then
sid="$(jq -r ".services[$((choice-1))].id | tostring" <<<"$SERVICES_JSON")"
print_detail "$sid"
else
warn "Invalid service number."
sleep 1
fi
else
warn "Use service number, A, S, R or Q."
sleep 1
fi
;;
esac
done
}

usage() {
cat <<'HELP'
Usage:
lightlayer Interactive mode
lightlayer --summary Show all services
lightlayer --report Print Telegram HTML report
lightlayer --send Send Telegram report
lightlayer --help Show help
HELP
}

main() {
command -v curl >/dev/null 2>&1 || die "curl is required."
command -v jq >/dev/null 2>&1 || die "jq is required."

api_login
load_services

case "${1:-}" in
"")
interactive_menu
;;
--summary)
print_summary
;;
--report)
build_report
printf '%s\n' "$REPORT_HTML"
;;
--send)
send_report
;;
--help|-h)
usage
;;
*)
usage
exit 1
;;
esac
}

main "$@"
APP_EOF

chmod 700 "$APP"
chown root:root "$APP"

ln -sfn "$APP" "$SYMLINK"

cat >"$SERVICE_FILE" <<'SERVICE_EOF'
[Unit]
Description=LightLayer daily Telegram report
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
User=root
WorkingDirectory=/opt/lightlayer
ExecStart=/opt/lightlayer/lightlayer.sh --send
UMask=0077
Nice=10
SERVICE_EOF

HH="${REPORT_TIME%:*}"
MM="${REPORT_TIME#*:}"

cat >"$TIMER_FILE" <<TIMER_EOF
[Unit]
Description=LightLayer daily Telegram report timer

[Timer]
OnCalendar=*-*-* ${HH}:${MM}:00 ${REPORT_TZ}
Persistent=true
AccuracySec=1min
Unit=${SERVICE_NAME}

[Install]
WantedBy=timers.target
TIMER_EOF

chmod 644 "$SERVICE_FILE" "$TIMER_FILE"

bash -n "$APP" || die "Generated application failed bash syntax check."

systemctl daemon-reload
systemctl enable --now "$TIMER_NAME"

echo
ok "Application installed: ${APP}"
ok "Secrets file: ${ENV_FILE} (0600)"
ok "Command link: ${SYMLINK}"
ok "Timer enabled: ${TIMER_NAME}"

echo
echo "============================================================"
echo "API verification"
echo "============================================================"
echo

if "$APP" --summary; then
echo
ok "LightLayer API verification passed."
else
warn "API verification failed. Check ${ENV_FILE}."
fi

echo
read -rp "Send one Telegram test report now? [Y/n]: " SEND_TEST
SEND_TEST="${SEND_TEST:-Y}"

if [[ "${SEND_TEST,,}" == "y" ]]; then
if "$APP" --send; then
ok "Telegram test passed."
else
warn "Telegram test failed. Check Bot Token / Chat ID / bot channel permissions."
fi
fi

echo
echo "============================================================"
echo "Installation complete"
echo "============================================================"
echo
echo "Interactive:"
echo " lightlayer"
echo
echo "Summary:"
echo " lightlayer --summary"
echo
echo "Preview Telegram HTML:"
echo " lightlayer --report"
echo
echo "Send Telegram now:"
echo " lightlayer --send"
echo
echo "Timer:"
echo " systemctl status ${TIMER_NAME} --no-pager"
echo " systemctl list-timers ${TIMER_NAME} --no-pager"
echo
echo "Logs:"
echo " journalctl -u ${SERVICE_NAME} -n 50 --no-pager"
echo
echo "Edit secrets / bandwidth mapping:"
echo " nano ${ENV_FILE}"
echo
最新回复 (5)
  • nsnav 08-21 16:53
    1

    谢谢,马上学习一下

  • mrshyi 08-21 17:25
    2

    ^-^

  • madolche 08-21 17:29
    3

    再研究研究,可以抢鸡 ^-^

  • Flanker 楼主 08-21 17:37
    4

    @madolche #3 API限流很厉害的,我随便刷几下就429了...

  • 阿鑫 08-21 22:15
    5

    @Flanker #4 使用教程呢,直接部署在服务器就行了吗

* 帖子来源NodeSeek
返回