chore: stop tracking local-only files

This commit is contained in:
2026-03-03 09:37:38 -08:00
parent 4d8516b2e1
commit d220516183
13 changed files with 6 additions and 1853 deletions
-78
View File
@@ -1,78 +0,0 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"os"
"github.com/perfect-panel/server/internal/config"
"github.com/perfect-panel/server/pkg/conf"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
configPath := flag.String("config", "etc/ppanel.yaml", "config file path")
query := flag.String("query", "", "sql query")
flag.Parse()
if *query == "" {
fmt.Fprintln(os.Stderr, "query is required")
os.Exit(1)
}
var cfg config.Config
conf.MustLoad(*configPath, &cfg)
dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?%s", cfg.MySQL.Username, cfg.MySQL.Password, cfg.MySQL.Addr, cfg.MySQL.Dbname, cfg.MySQL.Config)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
fmt.Fprintf(os.Stderr, "connect db failed: %v\n", err)
os.Exit(1)
}
rows, err := db.Raw(*query).Rows()
if err != nil {
fmt.Fprintf(os.Stderr, "query failed: %v\n", err)
os.Exit(1)
}
defer rows.Close()
columns, err := rows.Columns()
if err != nil {
fmt.Fprintf(os.Stderr, "read columns failed: %v\n", err)
os.Exit(1)
}
result := make([]map[string]interface{}, 0)
for rows.Next() {
values := make([]interface{}, len(columns))
pointers := make([]interface{}, len(columns))
for i := range values {
pointers[i] = &values[i]
}
if err = rows.Scan(pointers...); err != nil {
fmt.Fprintf(os.Stderr, "scan row failed: %v\n", err)
os.Exit(1)
}
rowMap := make(map[string]interface{}, len(columns))
for i, col := range columns {
v := values[i]
if b, ok := v.([]byte); ok {
rowMap[col] = string(b)
continue
}
rowMap[col] = v
}
result = append(result, rowMap)
}
encoder := json.NewEncoder(os.Stdout)
if err = encoder.Encode(result); err != nil {
fmt.Fprintf(os.Stderr, "encode result failed: %v\n", err)
os.Exit(1)
}
}
@@ -1,346 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
BASE_URL="${BASE_URL:-http://127.0.0.1:8080}"
CONFIG_PATH="${CONFIG_PATH:-etc/ppanel.yaml}"
EMAIL_A="${EMAIL_A:-family_cleanup_$(date +%s)@example.com}"
DEVICE_A="${DEVICE_A:-qa-cleanup-device-a-$(date +%s)}"
DEVICE_B="${DEVICE_B:-qa-cleanup-device-b-$(date +%s)}"
SEND_CODE_RETRY_WAIT="${SEND_CODE_RETRY_WAIT:-61}"
SEND_CODE_MAX_RETRY="${SEND_CODE_MAX_RETRY:-8}"
VERIFY_CODE_PROVIDER="${VERIFY_CODE_PROVIDER:-}"
VERIFY_CODE_OVERRIDE="${VERIFY_CODE_OVERRIDE:-}"
REGISTER_CODE_OVERRIDE="${REGISTER_CODE_OVERRIDE:-}"
SECURITY_CODE_OVERRIDE="${SECURITY_CODE_OVERRIDE:-}"
DELETE_ACCOUNT_CODE_OVERRIDE="${DELETE_ACCOUNT_CODE_OVERRIDE:-}"
ALLOW_INTERACTIVE_INPUT="${ALLOW_INTERACTIVE_INPUT:-auto}"
ENABLE_REDIS_CODE_FALLBACK="${ENABLE_REDIS_CODE_FALLBACK:-1}"
REDIS_CLI_BIN="${REDIS_CLI_BIN:-redis-cli}"
REDIS_HOST="${REDIS_HOST:-127.0.0.1}"
REDIS_PORT="${REDIS_PORT:-6379}"
REDIS_DB="${REDIS_DB:-0}"
REDIS_PASSWORD="${REDIS_PASSWORD:-}"
BIND_VERIFY_RETRY="${BIND_VERIFY_RETRY:-1}"
bold() { printf "\033[1m%s\033[0m\n" "$*"; }
info() { printf "[INFO] %s\n" "$*" >&2; }
warn() { printf "[WARN] %s\n" "$*" >&2; }
fail() { printf "[FAIL] %s\n" "$*" >&2; exit 1; }
require_cmd() {
command -v "$1" >/dev/null 2>&1 || fail "缺少命令: $1"
}
json_code() { jq -r '.code // ""' <<<"$1"; }
json_msg() { jq -r '.msg // ""' <<<"$1"; }
can_prompt_input() {
if [[ "$ALLOW_INTERACTIVE_INPUT" == "1" ]]; then
return 0
fi
if [[ "$ALLOW_INTERACTIVE_INPUT" == "0" ]]; then
return 1
fi
[[ -t 0 ]]
}
redis_get_key_raw() {
local key="$1"
command -v "$REDIS_CLI_BIN" >/dev/null 2>&1 || return 1
local -a args
args=("$REDIS_CLI_BIN" -h "$REDIS_HOST" -p "$REDIS_PORT" -n "$REDIS_DB" --raw)
if [[ -n "$REDIS_PASSWORD" ]]; then
args+=(-a "$REDIS_PASSWORD")
fi
"${args[@]}" GET "$key" 2>/dev/null || return 1
}
get_verify_code_from_redis() {
local email="$1"
local typ="$2"
local scene key raw code
if [[ "$ENABLE_REDIS_CODE_FALLBACK" != "1" ]]; then
return 1
fi
case "$typ" in
1) scene="register" ;;
2) scene="security" ;;
4) scene="delete_account" ;;
*) scene="unknown" ;;
esac
key="auth:verify:email:${scene}:${email}"
raw="$(redis_get_key_raw "$key" || true)"
[[ -n "$raw" ]] || return 1
code="$(jq -r '.code // ""' <<<"$raw" 2>/dev/null || true)"
[[ -n "$code" && "$code" != "null" ]] || return 1
printf "%s" "$code"
return 0
}
api_request() {
local method="$1"
local path="$2"
local body="${3:-}"
local token="${4:-}"
local -a args
args=(-sS -X "$method" "${BASE_URL}${path}" -H "Content-Type: application/json")
if [[ -n "$token" ]]; then
args+=(-H "Authorization: ${token}")
fi
if [[ "$method" != "GET" ]]; then
args+=(-d "$body")
fi
curl "${args[@]}"
}
assert_success() {
local resp="$1"
local step="$2"
local code
code="$(json_code "$resp")"
if [[ "$code" != "200" ]]; then
fail "${step} 失败, code=${code}, msg=$(json_msg "$resp"), resp=${resp}"
fi
}
token_or_keep() {
local resp="$1"
local fallback="$2"
local token
token="$(jq -r '.data.token // ""' <<<"$resp")"
if [[ -n "$token" && "$token" != "null" ]]; then
printf "%s" "$token"
else
printf "%s" "$fallback"
fi
}
device_login() {
local identifier="$1"
local user_agent="$2"
local payload resp token
payload="$(jq -nc --arg identifier "$identifier" --arg user_agent "$user_agent" '{identifier:$identifier,user_agent:$user_agent}')"
resp="$(api_request "POST" "/v1/auth/login/device" "$payload")"
assert_success "$resp" "设备登录(${identifier})"
token="$(jq -r '.data.token // ""' <<<"$resp")"
[[ -n "$token" && "$token" != "null" ]] || fail "设备登录(${identifier})返回空 token: ${resp}"
printf "%s" "$token"
}
send_code_with_retry() {
local email="$1"
local typ="$2"
local payload resp code retry
payload="$(jq -nc --arg email "$email" --argjson type "$typ" '{email:$email,type:$type}')"
retry=0
while true; do
resp="$(api_request "POST" "/v1/common/send_code" "$payload")"
code="$(json_code "$resp")"
if [[ "$code" == "200" ]]; then
printf "%s" "$resp"
return 0
fi
if [[ "$code" == "401" ]]; then
retry=$((retry + 1))
if (( retry > SEND_CODE_MAX_RETRY )); then
fail "发送验证码达到重试上限, email=${email}, type=${typ}, resp=${resp}"
fi
warn "验证码发送频率限制,等待 ${SEND_CODE_RETRY_WAIT}s 后重试(${retry}/${SEND_CODE_MAX_RETRY})..."
sleep "${SEND_CODE_RETRY_WAIT}"
continue
fi
fail "发送验证码失败, email=${email}, type=${typ}, code=${code}, msg=$(json_msg "$resp"), resp=${resp}"
done
}
get_verify_code() {
local email="$1"
local typ="$2"
local send_resp code
send_resp="$(send_code_with_retry "$email" "$typ")"
code="$(jq -r '.data.code // ""' <<<"$send_resp")"
if [[ -n "$code" && "$code" != "null" ]]; then
info "验证码已从接口返回(type=${typ})"
printf "%s" "$code"
return 0
fi
code="$(get_verify_code_from_redis "$email" "$typ" || true)"
if [[ -n "$code" ]]; then
info "验证码已从 Redis 回读(type=${typ})"
printf "%s" "$code"
return 0
fi
if [[ -n "$VERIFY_CODE_PROVIDER" ]]; then
code="$("$VERIFY_CODE_PROVIDER" "$email" "$typ" || true)"
if [[ -n "$code" ]]; then
info "验证码由 VERIFY_CODE_PROVIDER 提供(type=${typ})"
printf "%s" "$code"
return 0
fi
fi
if [[ "$typ" == "1" && -n "$REGISTER_CODE_OVERRIDE" ]]; then
printf "%s" "$REGISTER_CODE_OVERRIDE"
return 0
fi
if [[ "$typ" == "2" && -n "$SECURITY_CODE_OVERRIDE" ]]; then
printf "%s" "$SECURITY_CODE_OVERRIDE"
return 0
fi
if [[ "$typ" == "4" && -n "$DELETE_ACCOUNT_CODE_OVERRIDE" ]]; then
printf "%s" "$DELETE_ACCOUNT_CODE_OVERRIDE"
return 0
fi
if [[ -n "$VERIFY_CODE_OVERRIDE" ]]; then
printf "%s" "$VERIFY_CODE_OVERRIDE"
return 0
fi
if ! can_prompt_input; then
fail "验证码不可自动获取(type=${typ}, email=${email})"
fi
read -r -p "请输入邮箱 ${email} 的验证码(type=${typ})" code
[[ -n "$code" ]] || fail "验证码为空"
printf "%s" "$code"
}
bind_email_with_verification() {
local token="$1"
local email="$2"
local code="$3"
local payload
payload="$(jq -nc --arg email "$email" --arg code "$code" '{email:$email,code:$code}')"
api_request "POST" "/v1/public/user/bind_email_with_verification" "$payload" "$token"
}
bind_email_with_verification_auto() {
local token="$1"
local email="$2"
local typ="$3"
local max_retry="${4:-$BIND_VERIFY_RETRY}"
local attempt=0 code resp api_code
while true; do
code="$(get_verify_code "$email" "$typ")"
resp="$(bind_email_with_verification "$token" "$email" "$code")"
api_code="$(json_code "$resp")"
if [[ "$api_code" == "200" ]]; then
printf "%s" "$resp"
return 0
fi
if [[ "$api_code" == "70001" && "$attempt" -lt "$max_retry" ]]; then
warn "绑定邮箱验证码校验失败,重新获取验证码重试($((attempt+1))/${max_retry})..."
attempt=$((attempt + 1))
continue
fi
fail "绑定邮箱(${email}) 失败, code=${api_code}, msg=$(json_msg "$resp"), resp=${resp}"
done
}
delete_account() {
local token="$1"
local email="$2"
local code="$3"
local payload resp
payload="$(jq -nc --arg email "$email" --arg code "$code" '{email:$email,code:$code}')"
resp="$(api_request "POST" "/v1/public/user/delete_account" "$payload" "$token")"
assert_success "$resp" "注销账号(${email})"
printf "%s" "$resp"
}
db_query_json() {
local query="$1"
GOCACHE=/tmp/go-build go run ./script/db_query.go --config "$CONFIG_PATH" --query "$query"
}
assert_db_field_equals() {
local json="$1"
local field="$2"
local expected="$3"
local actual
actual="$(jq -r ".[0].${field} // \"\"" <<<"$json")"
if [[ "$actual" != "$expected" ]]; then
fail "DB校验失败: 字段 ${field} 期望=${expected}, 实际=${actual}, 数据=${json}"
fi
}
main() {
require_cmd curl
require_cmd jq
require_cmd go
bold "家庭组注销清理校验脚本"
info "BASE_URL=${BASE_URL}"
info "CONFIG_PATH=${CONFIG_PATH}"
info "EMAIL_A=${EMAIL_A}"
info "DEVICE_A=${DEVICE_A}"
info "DEVICE_B=${DEVICE_B}"
local token_a token_b
local bind_a_resp bind_b_resp del_b_resp del_a_resp
local family_id owner_user_id member_user_id
local code_delete
local q_member_before q_member_after q_owner_after q_family_after
bold "步骤1: A设备登录并绑定邮箱A"
token_a="$(device_login "$DEVICE_A" "qa-cleanup-device-a")"
bind_a_resp="$(bind_email_with_verification_auto "$token_a" "$EMAIL_A" 1)"
token_a="$(token_or_keep "$bind_a_resp" "$token_a")"
bold "步骤2: B设备登录并绑定邮箱A加入家庭"
token_b="$(device_login "$DEVICE_B" "qa-cleanup-device-b")"
bind_b_resp="$(bind_email_with_verification_auto "$token_b" "$EMAIL_A" 2)"
token_b="$(token_or_keep "$bind_b_resp" "$token_b")"
family_id="$(jq -r '.data.family_id // ""' <<<"$bind_b_resp")"
owner_user_id="$(jq -r '.data.owner_user_id // ""' <<<"$bind_b_resp")"
member_user_id="$(jq -r '.data.user_id // ""' <<<"$bind_b_resp")"
[[ -n "$family_id" && "$family_id" != "null" ]] || fail "无法获取 family_id: ${bind_b_resp}"
[[ -n "$owner_user_id" && "$owner_user_id" != "null" ]] || fail "无法获取 owner_user_id: ${bind_b_resp}"
[[ -n "$member_user_id" && "$member_user_id" != "null" ]] || fail "无法获取 member_user_id: ${bind_b_resp}"
q_member_before="$(db_query_json "SELECT status, role, family_id FROM user_family_member WHERE user_id = ${member_user_id} ORDER BY id DESC LIMIT 1")"
assert_db_field_equals "$q_member_before" "status" "1"
info "B注销前家庭状态校验通过(status=1)"
bold "步骤3: 注销设备B账号,检查B离组"
code_delete="$(get_verify_code "$EMAIL_A" 4)"
del_b_resp="$(delete_account "$token_b" "$EMAIL_A" "$code_delete")"
info "B注销结果: $(jq -c '.data' <<<"$del_b_resp")"
q_member_after="$(db_query_json "SELECT status, role, family_id FROM user_family_member WHERE user_id = ${member_user_id} ORDER BY id DESC LIMIT 1")"
assert_db_field_equals "$q_member_after" "status" "3"
info "B注销后家庭状态校验通过(status=3)"
bold "步骤4: 注销设备A(家庭主)账号,检查家庭解散"
code_delete="$(get_verify_code "$EMAIL_A" 4)"
del_a_resp="$(delete_account "$token_a" "$EMAIL_A" "$code_delete")"
info "A注销结果: $(jq -c '.data' <<<"$del_a_resp")"
q_owner_after="$(db_query_json "SELECT status, role, family_id FROM user_family_member WHERE user_id = ${owner_user_id} ORDER BY id DESC LIMIT 1")"
assert_db_field_equals "$q_owner_after" "status" "3"
info "A注销后主账号家庭状态校验通过(status=3)"
q_family_after="$(db_query_json "SELECT status FROM user_family WHERE id = ${family_id} ORDER BY id DESC LIMIT 1")"
assert_db_field_equals "$q_family_after" "status" "0"
info "家庭状态校验通过(status=0)"
bold "校验通过 ✅ 注销后家庭成员与家庭状态已正确更新"
}
main "$@"
-335
View File
@@ -1,335 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
BASE_URL="${BASE_URL:-http://127.0.0.1:8080}"
EMAIL_A="${EMAIL_A:-family34_a_$(date +%s)@example.com}"
DEVICE_A="${DEVICE_A:-qa34-device-a-$(date +%s)}"
DEVICE_B="${DEVICE_B:-qa34-device-b-$(date +%s)}"
SEND_CODE_RETRY_WAIT="${SEND_CODE_RETRY_WAIT:-61}"
SEND_CODE_MAX_RETRY="${SEND_CODE_MAX_RETRY:-8}"
VERIFY_CODE_PROVIDER="${VERIFY_CODE_PROVIDER:-}"
VERIFY_CODE_OVERRIDE="${VERIFY_CODE_OVERRIDE:-}"
REGISTER_CODE_OVERRIDE="${REGISTER_CODE_OVERRIDE:-}"
SECURITY_CODE_OVERRIDE="${SECURITY_CODE_OVERRIDE:-}"
ALLOW_INTERACTIVE_INPUT="${ALLOW_INTERACTIVE_INPUT:-auto}"
ENABLE_REDIS_CODE_FALLBACK="${ENABLE_REDIS_CODE_FALLBACK:-1}"
REDIS_CLI_BIN="${REDIS_CLI_BIN:-redis-cli}"
REDIS_HOST="${REDIS_HOST:-127.0.0.1}"
REDIS_PORT="${REDIS_PORT:-6379}"
REDIS_DB="${REDIS_DB:-0}"
REDIS_PASSWORD="${REDIS_PASSWORD:-}"
BIND_VERIFY_RETRY="${BIND_VERIFY_RETRY:-1}"
bold() { printf "\033[1m%s\033[0m\n" "$*"; }
info() { printf "[INFO] %s\n" "$*" >&2; }
warn() { printf "[WARN] %s\n" "$*" >&2; }
fail() { printf "[FAIL] %s\n" "$*" >&2; exit 1; }
require_cmd() {
command -v "$1" >/dev/null 2>&1 || fail "缺少命令: $1"
}
json_code() { jq -r '.code // ""' <<<"$1"; }
json_msg() { jq -r '.msg // ""' <<<"$1"; }
can_prompt_input() {
if [[ "$ALLOW_INTERACTIVE_INPUT" == "1" ]]; then
return 0
fi
if [[ "$ALLOW_INTERACTIVE_INPUT" == "0" ]]; then
return 1
fi
[[ -t 0 ]]
}
redis_get_key_raw() {
local key="$1"
command -v "$REDIS_CLI_BIN" >/dev/null 2>&1 || return 1
local -a args
args=("$REDIS_CLI_BIN" -h "$REDIS_HOST" -p "$REDIS_PORT" -n "$REDIS_DB" --raw)
if [[ -n "$REDIS_PASSWORD" ]]; then
args+=(-a "$REDIS_PASSWORD")
fi
"${args[@]}" GET "$key" 2>/dev/null || return 1
}
get_verify_code_from_redis() {
local email="$1"
local typ="$2"
local scene key raw code
if [[ "$ENABLE_REDIS_CODE_FALLBACK" != "1" ]]; then
return 1
fi
case "$typ" in
1) scene="register" ;;
2) scene="security" ;;
*) scene="unknown" ;;
esac
key="auth:verify:email:${scene}:${email}"
raw="$(redis_get_key_raw "$key" || true)"
[[ -n "$raw" ]] || return 1
code="$(jq -r '.code // ""' <<<"$raw" 2>/dev/null || true)"
[[ -n "$code" && "$code" != "null" ]] || return 1
printf "%s" "$code"
}
api_request() {
local method="$1"
local path="$2"
local body="${3:-}"
local token="${4:-}"
local -a args
args=(-sS -X "$method" "${BASE_URL}${path}" -H "Content-Type: application/json")
if [[ -n "$token" ]]; then
args+=(-H "Authorization: ${token}")
fi
if [[ "$method" != "GET" ]]; then
args+=(-d "$body")
fi
curl "${args[@]}"
}
assert_success() {
local resp="$1"
local step="$2"
local code
code="$(json_code "$resp")"
if [[ "$code" != "200" ]]; then
fail "${step} 失败, code=${code}, msg=$(json_msg "$resp"), resp=${resp}"
fi
}
token_or_keep() {
local resp="$1"
local fallback="$2"
local token
token="$(jq -r '.data.token // ""' <<<"$resp")"
if [[ -n "$token" && "$token" != "null" ]]; then
printf "%s" "$token"
else
printf "%s" "$fallback"
fi
}
device_login() {
local identifier="$1"
local user_agent="$2"
local payload resp token
payload="$(jq -nc --arg identifier "$identifier" --arg user_agent "$user_agent" '{identifier:$identifier,user_agent:$user_agent}')"
resp="$(api_request "POST" "/v1/auth/login/device" "$payload")"
assert_success "$resp" "设备登录(${identifier})"
token="$(jq -r '.data.token // ""' <<<"$resp")"
[[ -n "$token" && "$token" != "null" ]] || fail "设备登录(${identifier})返回空 token: ${resp}"
printf "%s" "$token"
}
send_code_with_retry() {
local email="$1"
local typ="$2"
local payload resp code retry
payload="$(jq -nc --arg email "$email" --argjson type "$typ" '{email:$email,type:$type}')"
retry=0
while true; do
resp="$(api_request "POST" "/v1/common/send_code" "$payload")"
code="$(json_code "$resp")"
if [[ "$code" == "200" ]]; then
printf "%s" "$resp"
return 0
fi
if [[ "$code" == "401" ]]; then
retry=$((retry + 1))
if (( retry > SEND_CODE_MAX_RETRY )); then
fail "发送验证码达到重试上限, email=${email}, type=${typ}, resp=${resp}"
fi
warn "验证码发送频率限制,等待 ${SEND_CODE_RETRY_WAIT}s 后重试(${retry}/${SEND_CODE_MAX_RETRY})..."
sleep "${SEND_CODE_RETRY_WAIT}"
continue
fi
fail "发送验证码失败, email=${email}, type=${typ}, code=${code}, msg=$(json_msg "$resp"), resp=${resp}"
done
}
get_verify_code() {
local email="$1"
local typ="$2"
local send_resp code
send_resp="$(send_code_with_retry "$email" "$typ")"
code="$(jq -r '.data.code // ""' <<<"$send_resp")"
if [[ -n "$code" && "$code" != "null" ]]; then
info "验证码已从接口返回(type=${typ})"
printf "%s" "$code"
return 0
fi
code="$(get_verify_code_from_redis "$email" "$typ" || true)"
if [[ -n "$code" ]]; then
info "验证码已从 Redis 回读(type=${typ})"
printf "%s" "$code"
return 0
fi
if [[ -n "$VERIFY_CODE_PROVIDER" ]]; then
code="$("$VERIFY_CODE_PROVIDER" "$email" "$typ" || true)"
if [[ -n "$code" ]]; then
info "验证码由 VERIFY_CODE_PROVIDER 提供(type=${typ})"
printf "%s" "$code"
return 0
fi
fi
if [[ "$typ" == "1" && -n "$REGISTER_CODE_OVERRIDE" ]]; then
printf "%s" "$REGISTER_CODE_OVERRIDE"
return 0
fi
if [[ "$typ" == "2" && -n "$SECURITY_CODE_OVERRIDE" ]]; then
printf "%s" "$SECURITY_CODE_OVERRIDE"
return 0
fi
if [[ -n "$VERIFY_CODE_OVERRIDE" ]]; then
printf "%s" "$VERIFY_CODE_OVERRIDE"
return 0
fi
if ! can_prompt_input; then
fail "验证码不可自动获取(type=${typ}, email=${email})"
fi
read -r -p "请输入邮箱 ${email} 的验证码(type=${typ})" code
[[ -n "$code" ]] || fail "验证码为空"
printf "%s" "$code"
}
bind_email_with_verification() {
local token="$1"
local email="$2"
local code="$3"
local payload
payload="$(jq -nc --arg email "$email" --arg code "$code" '{email:$email,code:$code}')"
api_request "POST" "/v1/public/user/bind_email_with_verification" "$payload" "$token"
}
bind_email_with_verification_auto() {
local token="$1"
local email="$2"
local typ="$3"
local max_retry="${4:-$BIND_VERIFY_RETRY}"
local attempt=0 code resp api_code
while true; do
code="$(get_verify_code "$email" "$typ")"
resp="$(bind_email_with_verification "$token" "$email" "$code")"
api_code="$(json_code "$resp")"
if [[ "$api_code" == "200" ]]; then
printf "%s" "$resp"
return 0
fi
if [[ "$api_code" == "70001" && "$attempt" -lt "$max_retry" ]]; then
warn "绑定邮箱验证码校验失败,重新获取验证码重试($((attempt+1))/${max_retry})..."
attempt=$((attempt + 1))
continue
fi
fail "绑定邮箱(${email}) 失败, code=${api_code}, msg=$(json_msg "$resp"), resp=${resp}"
done
}
get_devices() {
local token="$1"
local resp
resp="$(api_request "GET" "/v1/public/user/devices" "" "$token")"
assert_success "$resp" "获取设备列表"
printf "%s" "$resp"
}
find_device_id() {
local devices_resp="$1"
local identifier="$2"
jq -r --arg identifier "$identifier" '.data.list[]? | select(.identifier == $identifier) | .id' <<<"$devices_resp" | head -n1
}
assert_device_exists() {
local devices_resp="$1"
local identifier="$2"
jq -e --arg identifier "$identifier" '.data.list | any(.identifier == $identifier)' <<<"$devices_resp" >/dev/null \
|| fail "设备列表中未找到 identifier=${identifier}, resp=${devices_resp}"
}
unbind_device() {
local token="$1"
local device_id="$2"
local payload resp
payload="$(jq -nc --arg id "$device_id" '{id:($id|tonumber)}')"
resp="$(api_request "PUT" "/v1/public/user/unbind_device" "$payload" "$token")"
assert_success "$resp" "解绑设备(id=${device_id})"
}
assert_token_invalid() {
local token="$1"
local who="$2"
local resp code
resp="$(api_request "GET" "/v1/public/user/info" "" "$token")"
code="$(json_code "$resp")"
if [[ "$code" == "200" ]]; then
fail "${who} 预期已失效,但 token 仍可访问: ${resp}"
fi
info "${who} token 已失效,符合预期(code=${code}, msg=$(json_msg "$resp"))"
}
main() {
require_cmd curl
require_cmd jq
bold "家庭组快速回归: 场景3/4"
info "BASE_URL=${BASE_URL}"
info "EMAIL_A=${EMAIL_A}"
info "DEVICE_A=${DEVICE_A}"
info "DEVICE_B=${DEVICE_B}"
local token_a token_b token_a2
local bind_resp devices_resp
local device_id_a device_id_b
bold "预置: A设备登录并绑定邮箱A"
token_a="$(device_login "$DEVICE_A" "qa34-device-a")"
bind_resp="$(bind_email_with_verification_auto "$token_a" "$EMAIL_A" 1)"
token_a="$(token_or_keep "$bind_resp" "$token_a")"
bold "场景3: B绑定邮箱A后踢A"
token_b="$(device_login "$DEVICE_B" "qa34-device-b")"
bind_resp="$(bind_email_with_verification_auto "$token_b" "$EMAIL_A" 2)"
token_b="$(token_or_keep "$bind_resp" "$token_b")"
devices_resp="$(get_devices "$token_b")"
assert_device_exists "$devices_resp" "$DEVICE_A"
assert_device_exists "$devices_resp" "$DEVICE_B"
device_id_a="$(find_device_id "$devices_resp" "$DEVICE_A")"
[[ -n "$device_id_a" ]] || fail "场景3 无法找到设备A ID"
unbind_device "$token_b" "$device_id_a"
assert_token_invalid "$token_a" "设备A(被B踢后)"
info "场景3通过"
bold "场景4: A重登绑定邮箱A后踢B"
token_a2="$(device_login "$DEVICE_A" "qa34-device-a-relogin")"
bind_resp="$(bind_email_with_verification_auto "$token_a2" "$EMAIL_A" 2)"
token_a2="$(token_or_keep "$bind_resp" "$token_a2")"
devices_resp="$(get_devices "$token_a2")"
assert_device_exists "$devices_resp" "$DEVICE_A"
assert_device_exists "$devices_resp" "$DEVICE_B"
device_id_b="$(find_device_id "$devices_resp" "$DEVICE_B")"
[[ -n "$device_id_b" ]] || fail "场景4 无法找到设备B ID"
unbind_device "$token_a2" "$device_id_b"
assert_token_invalid "$token_b" "设备B(被A踢后)"
info "场景4通过"
bold "场景3/4 快速回归完成 ✅"
}
main "$@"
-394
View File
@@ -1,394 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
BASE_URL="${BASE_URL:-http://127.0.0.1:8080}"
EMAIL_A="${EMAIL_A:-family_a_$(date +%s)@example.com}"
DEVICE_A="${DEVICE_A:-qa-device-a-$(date +%s)}"
DEVICE_B="${DEVICE_B:-qa-device-b-$(date +%s)}"
DEVICE_C="${DEVICE_C:-qa-device-c-$(date +%s)}"
EMAIL_LOGIN_CODE="${EMAIL_LOGIN_CODE:-202511}"
SEND_CODE_RETRY_WAIT="${SEND_CODE_RETRY_WAIT:-61}"
SEND_CODE_MAX_RETRY="${SEND_CODE_MAX_RETRY:-8}"
VERIFY_CODE_PROVIDER="${VERIFY_CODE_PROVIDER:-}"
VERIFY_CODE_OVERRIDE="${VERIFY_CODE_OVERRIDE:-}"
REGISTER_CODE_OVERRIDE="${REGISTER_CODE_OVERRIDE:-}"
SECURITY_CODE_OVERRIDE="${SECURITY_CODE_OVERRIDE:-}"
ALLOW_INTERACTIVE_INPUT="${ALLOW_INTERACTIVE_INPUT:-auto}"
ENABLE_REDIS_CODE_FALLBACK="${ENABLE_REDIS_CODE_FALLBACK:-1}"
REDIS_CLI_BIN="${REDIS_CLI_BIN:-redis-cli}"
REDIS_HOST="${REDIS_HOST:-127.0.0.1}"
REDIS_PORT="${REDIS_PORT:-6379}"
REDIS_DB="${REDIS_DB:-0}"
REDIS_PASSWORD="${REDIS_PASSWORD:-}"
BIND_VERIFY_RETRY="${BIND_VERIFY_RETRY:-1}"
bold() { printf "\033[1m%s\033[0m\n" "$*"; }
info() { printf "[INFO] %s\n" "$*" >&2; }
warn() { printf "[WARN] %s\n" "$*" >&2; }
fail() { printf "[FAIL] %s\n" "$*" >&2; exit 1; }
require_cmd() {
command -v "$1" >/dev/null 2>&1 || fail "缺少命令: $1"
}
json_code() { jq -r '.code // ""' <<<"$1"; }
json_msg() { jq -r '.msg // ""' <<<"$1"; }
can_prompt_input() {
if [[ "$ALLOW_INTERACTIVE_INPUT" == "1" ]]; then
return 0
fi
if [[ "$ALLOW_INTERACTIVE_INPUT" == "0" ]]; then
return 1
fi
[[ -t 0 ]]
}
redis_get_key_raw() {
local key="$1"
command -v "$REDIS_CLI_BIN" >/dev/null 2>&1 || return 1
local -a args
args=("$REDIS_CLI_BIN" -h "$REDIS_HOST" -p "$REDIS_PORT" -n "$REDIS_DB" --raw)
if [[ -n "$REDIS_PASSWORD" ]]; then
args+=(-a "$REDIS_PASSWORD")
fi
"${args[@]}" GET "$key" 2>/dev/null || return 1
}
get_verify_code_from_redis() {
local email="$1"
local typ="$2"
local scene key raw code
if [[ "$ENABLE_REDIS_CODE_FALLBACK" != "1" ]]; then
return 1
fi
case "$typ" in
1) scene="register" ;;
2) scene="security" ;;
*) scene="unknown" ;;
esac
key="auth:verify:email:${scene}:${email}"
raw="$(redis_get_key_raw "$key" || true)"
[[ -n "$raw" ]] || return 1
code="$(jq -r '.code // ""' <<<"$raw" 2>/dev/null || true)"
[[ -n "$code" && "$code" != "null" ]] || return 1
printf "%s" "$code"
return 0
}
api_request() {
local method="$1"
local path="$2"
local body="${3:-}"
local token="${4:-}"
local -a args
args=(-sS -X "$method" "${BASE_URL}${path}" -H "Content-Type: application/json")
if [[ -n "$token" ]]; then
args+=(-H "Authorization: ${token}")
fi
if [[ "$method" != "GET" ]]; then
args+=(-d "$body")
fi
curl "${args[@]}"
}
assert_success() {
local resp="$1"
local step="$2"
local code
code="$(json_code "$resp")"
if [[ "$code" != "200" ]]; then
fail "${step} 失败, code=${code}, msg=$(json_msg "$resp"), resp=${resp}"
fi
}
token_or_keep() {
local resp="$1"
local fallback="$2"
local token
token="$(jq -r '.data.token // ""' <<<"$resp")"
if [[ -n "$token" && "$token" != "null" ]]; then
printf "%s" "$token"
else
printf "%s" "$fallback"
fi
}
device_login() {
local identifier="$1"
local user_agent="$2"
local payload resp token
payload="$(jq -nc --arg identifier "$identifier" --arg user_agent "$user_agent" '{identifier:$identifier,user_agent:$user_agent}')"
resp="$(api_request "POST" "/v1/auth/login/device" "$payload")"
assert_success "$resp" "设备登录(${identifier})"
token="$(jq -r '.data.token // ""' <<<"$resp")"
[[ -n "$token" && "$token" != "null" ]] || fail "设备登录(${identifier})返回空 token: ${resp}"
printf "%s" "$token"
}
email_login() {
local email="$1"
local code="$2"
local payload resp token
payload="$(jq -nc --arg email "$email" --arg code "$code" --arg identifier "web-login-$(date +%s)" --arg user_agent "qa-web" '{email:$email,code:$code,identifier:$identifier,user_agent:$user_agent}')"
resp="$(api_request "POST" "/v1/auth/login/email" "$payload")"
assert_success "$resp" "邮箱登录(${email})"
token="$(jq -r '.data.token // ""' <<<"$resp")"
[[ -n "$token" && "$token" != "null" ]] || fail "邮箱登录(${email})返回空 token: ${resp}"
printf "%s" "$token"
}
send_code_with_retry() {
local email="$1"
local typ="$2"
local payload resp code retry
payload="$(jq -nc --arg email "$email" --argjson type "$typ" '{email:$email,type:$type}')"
retry=0
while true; do
resp="$(api_request "POST" "/v1/common/send_code" "$payload")"
code="$(json_code "$resp")"
if [[ "$code" == "200" ]]; then
printf "%s" "$resp"
return 0
fi
if [[ "$code" == "401" ]]; then
retry=$((retry + 1))
if (( retry > SEND_CODE_MAX_RETRY )); then
fail "发送验证码达到重试上限, email=${email}, type=${typ}, resp=${resp}"
fi
warn "验证码发送频率限制,等待 ${SEND_CODE_RETRY_WAIT}s 后重试(${retry}/${SEND_CODE_MAX_RETRY})..."
sleep "${SEND_CODE_RETRY_WAIT}"
continue
fi
fail "发送验证码失败, email=${email}, type=${typ}, code=${code}, msg=$(json_msg "$resp"), resp=${resp}"
done
}
get_verify_code() {
local email="$1"
local typ="$2"
local send_resp code
send_resp="$(send_code_with_retry "$email" "$typ")"
code="$(jq -r '.data.code // ""' <<<"$send_resp")"
if [[ -n "$code" && "$code" != "null" ]]; then
info "验证码已从接口返回(type=${typ})"
printf "%s" "$code"
return 0
fi
code="$(get_verify_code_from_redis "$email" "$typ" || true)"
if [[ -n "$code" ]]; then
info "验证码已从 Redis 回读(type=${typ})"
printf "%s" "$code"
return 0
fi
if [[ -n "$VERIFY_CODE_PROVIDER" ]]; then
code="$("$VERIFY_CODE_PROVIDER" "$email" "$typ" || true)"
if [[ -n "$code" ]]; then
info "验证码由 VERIFY_CODE_PROVIDER 提供(type=${typ})"
printf "%s" "$code"
return 0
fi
fi
if [[ "$typ" == "1" && -n "$REGISTER_CODE_OVERRIDE" ]]; then
info "使用 REGISTER_CODE_OVERRIDE 作为验证码(type=${typ})"
printf "%s" "$REGISTER_CODE_OVERRIDE"
return 0
fi
if [[ "$typ" == "2" && -n "$SECURITY_CODE_OVERRIDE" ]]; then
info "使用 SECURITY_CODE_OVERRIDE 作为验证码(type=${typ})"
printf "%s" "$SECURITY_CODE_OVERRIDE"
return 0
fi
if [[ -n "$VERIFY_CODE_OVERRIDE" ]]; then
info "使用 VERIFY_CODE_OVERRIDE 作为验证码(type=${typ})"
printf "%s" "$VERIFY_CODE_OVERRIDE"
return 0
fi
if ! can_prompt_input; then
fail "验证码不可自动获取(type=${typ}, email=${email})。请设置 VERIFY_CODE_PROVIDER 或 REGISTER_CODE_OVERRIDE/SECURITY_CODE_OVERRIDE,或开启交互输入(ALLOW_INTERACTIVE_INPUT=1)"
fi
read -r -p "请输入邮箱 ${email} 的验证码(type=${typ})" code
[[ -n "$code" ]] || fail "验证码为空"
printf "%s" "$code"
}
bind_email_with_verification() {
local token="$1"
local email="$2"
local code="$3"
local payload resp
payload="$(jq -nc --arg email "$email" --arg code "$code" '{email:$email,code:$code}')"
resp="$(api_request "POST" "/v1/public/user/bind_email_with_verification" "$payload" "$token")"
printf "%s" "$resp"
}
bind_email_with_verification_auto() {
local token="$1"
local email="$2"
local typ="$3"
local max_retry="${4:-$BIND_VERIFY_RETRY}"
local attempt=0 code resp api_code
while true; do
code="$(get_verify_code "$email" "$typ")"
resp="$(bind_email_with_verification "$token" "$email" "$code")"
api_code="$(json_code "$resp")"
if [[ "$api_code" == "200" ]]; then
printf "%s" "$resp"
return 0
fi
if [[ "$api_code" == "70001" && "$attempt" -lt "$max_retry" ]]; then
warn "绑定邮箱验证码校验失败,重新获取验证码重试($((attempt+1))/${max_retry})..."
attempt=$((attempt + 1))
continue
fi
fail "绑定邮箱(${email}) 失败, code=${api_code}, msg=$(json_msg "$resp"), resp=${resp}"
done
}
get_devices() {
local token="$1"
local resp
resp="$(api_request "GET" "/v1/public/user/devices" "" "$token")"
assert_success "$resp" "获取设备列表"
printf "%s" "$resp"
}
find_device_id() {
local devices_resp="$1"
local identifier="$2"
jq -r --arg identifier "$identifier" '.data.list[]? | select(.identifier == $identifier) | .id' <<<"$devices_resp" | head -n1
}
assert_device_exists() {
local devices_resp="$1"
local identifier="$2"
jq -e --arg identifier "$identifier" '.data.list | any(.identifier == $identifier)' <<<"$devices_resp" >/dev/null \
|| fail "设备列表中未找到 identifier=${identifier}, resp=${devices_resp}"
}
unbind_device() {
local token="$1"
local device_id="$2"
local payload resp
payload="$(jq -nc --arg id "$device_id" '{id:($id|tonumber)}')"
resp="$(api_request "PUT" "/v1/public/user/unbind_device" "$payload" "$token")"
assert_success "$resp" "解绑设备(id=${device_id})"
}
assert_token_invalid() {
local token="$1"
local who="$2"
local resp code
resp="$(api_request "GET" "/v1/public/user/info" "" "$token")"
code="$(json_code "$resp")"
if [[ "$code" == "200" ]]; then
fail "${who} 预期已下线/失效,但 token 仍可访问: ${resp}"
fi
info "${who} token 已失效,符合预期(code=${code}, msg=$(json_msg "$resp"))"
}
main() {
require_cmd curl
require_cmd jq
bold "家庭组场景回归脚本"
info "BASE_URL=${BASE_URL}"
info "EMAIL_A=${EMAIL_A}"
info "DEVICE_A=${DEVICE_A}"
info "DEVICE_B=${DEVICE_B}"
info "DEVICE_C=${DEVICE_C} (本脚本当前未使用)"
info "EMAIL_LOGIN_CODE=${EMAIL_LOGIN_CODE}"
info "ALLOW_INTERACTIVE_INPUT=${ALLOW_INTERACTIVE_INPUT}"
info "ENABLE_REDIS_CODE_FALLBACK=${ENABLE_REDIS_CODE_FALLBACK}"
info "BIND_VERIFY_RETRY=${BIND_VERIFY_RETRY}"
local token_a token_b token_a2 token_b2 token_web
local bind_resp devices_resp
local device_id_a device_id_b device_id_b2
bold "场景1: 设备A登录并绑定邮箱A"
token_a="$(device_login "$DEVICE_A" "qa-device-a")"
bind_resp="$(bind_email_with_verification_auto "$token_a" "$EMAIL_A" 1)"
token_a="$(token_or_keep "$bind_resp" "$token_a")"
devices_resp="$(get_devices "$token_a")"
assert_device_exists "$devices_resp" "$DEVICE_A"
device_id_a="$(find_device_id "$devices_resp" "$DEVICE_A")"
[[ -n "$device_id_a" ]] || fail "场景1 无法找到设备A ID"
info "场景1完成: deviceA.id=${device_id_a}"
bold "场景2: 邮箱A网页登录"
token_web="$(email_login "$EMAIL_A" "$EMAIL_LOGIN_CODE")"
[[ -n "$token_web" ]] || fail "场景2 邮箱登录 token 为空"
info "场景2完成: 邮箱登录成功"
bold "场景3: 设备B登录并绑定邮箱A,然后B踢A"
token_b="$(device_login "$DEVICE_B" "qa-device-b")"
bind_resp="$(bind_email_with_verification_auto "$token_b" "$EMAIL_A" 2)"
token_b="$(token_or_keep "$bind_resp" "$token_b")"
jq -e '.data.family_joined == true' <<<"$bind_resp" >/dev/null || warn "场景3: family_joined 非 trueresp=${bind_resp}"
devices_resp="$(get_devices "$token_b")"
assert_device_exists "$devices_resp" "$DEVICE_A"
assert_device_exists "$devices_resp" "$DEVICE_B"
device_id_a="$(find_device_id "$devices_resp" "$DEVICE_A")"
[[ -n "$device_id_a" ]] || fail "场景3 无法从B视角找到A设备ID"
unbind_device "$token_b" "$device_id_a"
assert_token_invalid "$token_a" "设备A(被B踢后)"
info "场景3完成: B已踢A"
bold "场景4: 设备A重新登录并绑定邮箱A,然后A踢B"
token_a2="$(device_login "$DEVICE_A" "qa-device-a-relogin")"
bind_resp="$(bind_email_with_verification_auto "$token_a2" "$EMAIL_A" 2)"
token_a2="$(token_or_keep "$bind_resp" "$token_a2")"
devices_resp="$(get_devices "$token_a2")"
assert_device_exists "$devices_resp" "$DEVICE_A"
assert_device_exists "$devices_resp" "$DEVICE_B"
device_id_b="$(find_device_id "$devices_resp" "$DEVICE_B")"
[[ -n "$device_id_b" ]] || fail "场景4 无法从A视角找到B设备ID"
unbind_device "$token_a2" "$device_id_b"
assert_token_invalid "$token_b" "设备B(被A踢后)"
info "场景4完成: A已踢B"
bold "场景5: 设备B登录后执行退出(删除当前设备)"
token_b2="$(device_login "$DEVICE_B" "qa-device-b-relogin")"
bind_resp="$(bind_email_with_verification_auto "$token_b2" "$EMAIL_A" 2)"
token_b2="$(token_or_keep "$bind_resp" "$token_b2")"
devices_resp="$(get_devices "$token_b2")"
device_id_b2="$(find_device_id "$devices_resp" "$DEVICE_B")"
[[ -n "$device_id_b2" ]] || fail "场景5 无法找到当前设备B ID"
unbind_device "$token_b2" "$device_id_b2"
assert_token_invalid "$token_b2" "设备B(主动退出后)"
info "场景5完成: B退出后 token 失效"
bold "全部场景执行完成 ✅"
}
main "$@"
-53
View File
@@ -1,53 +0,0 @@
#!/bin/bash
OS_TYPE=$(uname)
ARCH_TYPE=$(uname -m)
if [[ "$OS_TYPE" == "Linux" ]]; then
echo "The current operating system is Linux"
if [[ "$ARCH_TYPE" == "x86_64" ]]; then
echo "Format api file"
./generate/gopure-linux-amd64 api format --dir ./apis
echo "Architecture: amd64"
./generate/gopure-linux-amd64 api go -api *.api -dir . -style goZero
elif [[ "$ARCH_TYPE" == "aarch64" ]]; then
echo "Format api file"
./generate/gopure-linux-arm64 api format --dir ./apis
echo "Architecture: arm64"
./generate/gopure-linux-arm64 api go -api *.api -dir . -style goZero
else
echo "Unrecognized architecture: $ARCH_TYPE"
fi
elif [[ "$OS_TYPE" == "Darwin" ]]; then
echo "The current operating system is macOS"
if [[ "$ARCH_TYPE" == "x86_64" ]]; then
echo "Format api file"
./generate/gopure-darwin-amd64 api format --dir ./apis
echo "Architecture: amd64"
./generate/gopure-darwin-amd64 api go -api *.api -dir . -style goZero
elif [[ "$ARCH_TYPE" == "arm64" ]]; then
echo "Format api file"
./generate/gopure-darwin-arm64 api format --dir ./apis
echo "Architecture: arm64"
./generate/gopure-darwin-arm64 api go -api *.api -dir . -style goZero
else
echo "Unrecognized architecture: $ARCH_TYPE"
fi
elif [[ "$OS_TYPE" == "CYGWIN"* || "$OS_TYPE" == "MINGW"* ]]; then
echo "The current operating system is Windows"
if [[ "$ARCH_TYPE" == "x86_64" ]]; then
echo "Format api file"
./generate/gopure-amd64.exe api format --dir ./apis
echo "Architecture: amd64"
./generate/gopure-amd64.exe api go -api *.api -dir . -style goZero
elif [[ "$ARCH_TYPE" == "arm64" ]]; then
echo "Format api file"
./generate/gopure-arm64.exe api format --dir ./apis
echo "Architecture: arm64"
./generate/gopure-arm64.exe api go -api *.api -dir . -style goZero
else
echo "Unrecognized architecture: $ARCH_TYPE"
fi
else
echo "Unrecognized operating system: $OS_TYPE"
fi
-96
View File
@@ -1,96 +0,0 @@
#!/bin/bash
# 检查是否以 root 用户运行
if [ "$(id -u)" -ne 0 ]; then
echo "请以 root 用户运行此脚本"
exit 1
fi
# 系统检测,确定使用的包管理工具
if [ -f /etc/debian_version ]; then
# Ubuntu / Debian 系统
PKG_MANAGER="apt-get"
elif [ -f /etc/redhat-release ]; then
# CentOS 系统
PKG_MANAGER="yum"
else
echo "不支持的系统"
exit 1
fi
# 检查 jq 是否已安装,若未安装则自动安装
if ! command -v jq &> /dev/null; then
echo "jq 未安装,正在安装 jq ..."
if [ "$PKG_MANAGER" == "apt-get" ]; then
apt-get update && apt-get install -y jq
elif [ "$PKG_MANAGER" == "yum" ]; then
yum install -y jq
else
echo "无法安装 jq,未知的包管理器"
exit 1
fi
fi
# 获取最新的版本号
VERSION=$(curl -s https://api.github.com/repos/perfect-panel/ppanel/releases/latest | jq -r .tag_name)
if [ "$VERSION" == "null" ]; then
echo "无法获取最新版本号,请检查网络或 GitHub API 状态"
exit 1
fi
# 安装路径
INSTALL_DIR="/opt/ppanel-server"
SERVICE_NAME="ppanel"
# 下载并解压二进制文件
echo "开始下载 ppanel 二进制文件,版本:$VERSION ..."
wget https://github.com/perfect-panel/ppanel/releases/download/$VERSION/ppanel-server-linux-amd64.tar.gz -O /tmp/ppanel-server-linux-amd64.tar.gz
# 创建安装目录
if [ ! -d "$INSTALL_DIR" ]; then
mkdir -p "$INSTALL_DIR"
fi
# 解压文件到安装目录
echo "解压文件到 $INSTALL_DIR ..."
tar -zxvf /tmp/ppanel-server-linux-amd64.tar.gz -C "$INSTALL_DIR" --strip-components=1
# 给二进制文件赋予执行权限
chmod +x "$INSTALL_DIR/ppanel-server"
# 创建 systemd 服务文件
echo "创建 systemd 服务文件 ..."
cat > /etc/systemd/system/$SERVICE_NAME.service <<EOF
[Unit]
Description=PPANEL Server
After=network.target
[Service]
ExecStart=$INSTALL_DIR/ppanel-server
Restart=always
User=root
WorkingDirectory=$INSTALL_DIR
[Install]
WantedBy=multi-user.target
EOF
# 重新加载 systemd 服务
echo "重新加载 systemd 配置 ..."
systemctl daemon-reload
# 启动服务
echo "启动 ppanel 服务 ..."
systemctl start $SERVICE_NAME
# 设置开机自启
echo "设置服务开机自启 ..."
systemctl enable $SERVICE_NAME
# 输出服务状态
echo "服务已启动,状态如下:"
systemctl status $SERVICE_NAME
# 提示配置文件
echo "请通过 http://服务器地址:8080/init 初始化系统配置"