增强一键安装输出和随机初始化

This commit is contained in:
2026-05-25 01:45:31 +08:00
parent 7d6a06839c
commit 89b464d73e
2 changed files with 221 additions and 11 deletions

View File

@@ -83,7 +83,7 @@ curl -fsSL https://gitea.kmux.cn/Eeveid/lightOps/raw/branch/main/scripts/install
curl -fsSL https://gitea.kmux.cn/Eeveid/lightOps/raw/branch/main/scripts/install-server.sh | bash -s -- --public-url https://panel.example.com
```
安装脚本会自动安装基础依赖、Rust、Node.js拉取远程仓库构建前端和 Rust 二进制,写入 `/etc/lightops/server.toml`,注册并启动 `lightops-server` systemd 服务。安装完成后访问 `/init` 初始化管理员
安装脚本会自动安装基础依赖、Rust、Node.js随机选择未占用端口,拉取远程仓库,构建前端和 Rust 二进制,写入 `/etc/lightops/server.toml`,注册并启动 `lightops-server` systemd 服务。首次安装会自动生成随机管理员账号和密码,并在安装完成后输出内网地址、本机地址、公网地址、端口、用户名和密码。首次凭据会保存到 `/etc/lightops/initial-admin.txt`,请登录面板后立即修改密码
常用参数:
@@ -91,7 +91,7 @@ curl -fsSL https://gitea.kmux.cn/Eeveid/lightOps/raw/branch/main/scripts/install
--repo <url> Git 仓库地址
--branch <name> Git 分支,默认 main
--install-dir <path> 安装目录,默认 /opt/lightops
--bind <addr> 监听地址,默认 0.0.0.0:8080
--bind <addr> 监听地址,默认随机选择未占用端口
--public-url <url> 面板外部访问地址
--skip-deps 跳过依赖安装检查
```

View File

@@ -5,8 +5,14 @@ REPO_URL="https://gitea.kmux.cn/Eeveid/lightOps.git"
BRANCH="main"
INSTALL_DIR="/opt/lightops"
CONFIG_DIR="/etc/lightops"
BIND_ADDR="0.0.0.0:8080"
BIND_ADDR=""
BIND_ADDR_SET="false"
BIND_ADDR_SOURCE="auto"
ADMIN_USER=""
ADMIN_PASS=""
PUBLIC_URL=""
PUBLIC_URL_SET="false"
PUBLIC_URL_SOURCE="auto"
SKIP_DEPS="false"
FORCE="false"
@@ -22,8 +28,8 @@ LightOps Server 一键安装脚本
--branch <name> Git 分支,默认 main
--install-dir <path> 安装目录,默认 /opt/lightops
--config-dir <path> 配置目录,默认 /etc/lightops
--bind <addr> 监听地址,默认 0.0.0.0:8080
--public-url <url> 面板访问地址,默认根据本机 IP 生成 http://<ip>:8080
--bind <addr> 监听地址,默认随机选择一个空闲端口
--public-url <url> 面板访问地址,默认根据本机内网 IP 和随机端口生成
--skip-deps 跳过系统依赖、Rust、Node.js 检查安装
--force 仓库存在未提交改动时仍继续,谨慎使用
-h, --help 显示帮助
@@ -54,10 +60,14 @@ while [[ $# -gt 0 ]]; do
;;
--bind)
BIND_ADDR="${2:?缺少 --bind 参数值}"
BIND_ADDR_SET="true"
BIND_ADDR_SOURCE="user"
shift 2
;;
--public-url)
PUBLIC_URL="${2:?缺少 --public-url 参数值}"
PUBLIC_URL_SET="true"
PUBLIC_URL_SOURCE="user"
shift 2
;;
--skip-deps)
@@ -131,6 +141,110 @@ detect_public_url() {
echo "http://${ip}:${port}"
}
primary_lan_ip() {
local ip
ip="$(hostname -I 2>/dev/null | awk '{print $1}')"
if [[ -z "$ip" ]]; then
ip="127.0.0.1"
fi
echo "$ip"
}
external_ip() {
curl -fsS --max-time 3 https://api.ipify.org 2>/dev/null || true
}
port_in_use() {
local port="$1"
if command -v ss >/dev/null 2>&1; then
ss -H -ltn 2>/dev/null | awk '{print $4}' | grep -Eq "[:.]${port}$" && return 0
fi
if command -v netstat >/dev/null 2>&1; then
netstat -ltn 2>/dev/null | awk '{print $4}' | grep -Eq "[:.]${port}$" && return 0
fi
if command -v lsof >/dev/null 2>&1; then
lsof -iTCP:"$port" -sTCP:LISTEN -n -P >/dev/null 2>&1 && return 0
fi
return 1
}
random_port() {
local value
if command -v shuf >/dev/null 2>&1; then
shuf -i 20000-49999 -n 1
return
fi
value="$(od -An -N2 -tu2 /dev/urandom 2>/dev/null | tr -d ' ')"
echo $((20000 + value % 30000))
}
choose_bind_addr() {
local host port candidate i
if [[ "$BIND_ADDR_SET" == "true" ]]; then
port="${BIND_ADDR##*:}"
if [[ ! "$port" =~ ^[0-9]+$ || "$port" -lt 1 || "$port" -gt 65535 ]]; then
fail "监听端口无效:$BIND_ADDR"
fi
if [[ "$BIND_ADDR_SOURCE" == "user" ]] && port_in_use "$port"; then
fail "端口 $port 已被占用,请使用 --bind 指定其他端口"
fi
return
fi
host="0.0.0.0"
for i in $(seq 1 80); do
candidate="$(random_port)"
if ! port_in_use "$candidate"; then
BIND_ADDR="${host}:${candidate}"
log "已选择空闲端口:$candidate"
return
fi
done
fail "未能找到可用端口,请使用 --bind 手动指定"
}
random_token() {
local length="${1:-20}"
if command -v openssl >/dev/null 2>&1; then
openssl rand -hex "$((length / 2 + 4))" | cut -c1-"$length"
else
od -An -N"$((length / 2 + 4))" -tx1 /dev/urandom | tr -d ' \n' | cut -c1-"$length"
fi
}
json_escape() {
local value="$1"
value="${value//\\/\\\\}"
value="${value//\"/\\\"}"
value="${value//$'\n'/\\n}"
printf '%s' "$value"
}
toml_string_value() {
local key="$1"
local file="$2"
sed -nE "s/^[[:space:]]*${key}[[:space:]]*=[[:space:]]*\"(.*)\"[[:space:]]*$/\\1/p" "$file" | tail -n 1
}
load_existing_config_values() {
local config_file existing_bind existing_public_url
config_file="$CONFIG_DIR/server.toml"
if [[ ! -f "$config_file" ]]; then
return
fi
existing_bind="$(toml_string_value "bind" "$config_file")"
existing_public_url="$(toml_string_value "public_url" "$config_file")"
if [[ "$BIND_ADDR_SOURCE" != "user" && -n "$existing_bind" ]]; then
BIND_ADDR="$existing_bind"
BIND_ADDR_SET="true"
BIND_ADDR_SOURCE="existing"
log "沿用已有监听地址:$BIND_ADDR"
fi
if [[ "$PUBLIC_URL_SET" != "true" && -n "$existing_public_url" ]]; then
PUBLIC_URL="$existing_public_url"
PUBLIC_URL_SOURCE="existing"
fi
}
install_packages_apt() {
export DEBIAN_FRONTEND=noninteractive
apt-get update
@@ -304,35 +418,131 @@ EOF
start_service() {
log "启动 LightOps Server"
systemctl restart lightops-server
sleep 2
if ! systemctl is-active --quiet lightops-server; then
journalctl -u lightops-server -n 120 --no-pager >&2 || true
fail "LightOps Server 启动失败"
wait_service
}
wait_service() {
local i
for i in $(seq 1 30); do
if systemctl is-active --quiet lightops-server; then
if curl -fsS --max-time 2 "http://127.0.0.1:${BIND_ADDR##*:}/" >/dev/null 2>&1; then
return
fi
fi
sleep 1
done
journalctl -u lightops-server -n 120 --no-pager >&2 || true
fail "LightOps Server 启动失败或 HTTP 接口未就绪"
}
database_file() {
echo "$INSTALL_DIR/lightops.db"
}
admin_exists() {
local db_file
db_file="$(database_file)"
if [[ ! -f "$db_file" ]]; then
return 1
fi
local count
count="$(sqlite3 "$db_file" "SELECT COUNT(*) FROM users;" 2>/dev/null || echo 0)"
[[ "${count:-0}" -gt 0 ]]
}
initialize_admin() {
local credential_file payload init_url response
credential_file="$CONFIG_DIR/initial-admin.txt"
if admin_exists; then
log "检测到已有管理员账号,跳过随机初始化"
ADMIN_USER=""
ADMIN_PASS=""
return
fi
ADMIN_USER="lightops_$(random_token 8 | tr 'A-Z' 'a-z')"
ADMIN_PASS="$(random_token 24)"
payload="{\"username\":\"$(json_escape "$ADMIN_USER")\",\"password\":\"$(json_escape "$ADMIN_PASS")\"}"
init_url="http://127.0.0.1:${BIND_ADDR##*:}/api/auth/init"
log "初始化随机管理员账号"
response="$(curl -fsS --max-time 10 -H 'Content-Type: application/json' -d "$payload" "$init_url" 2>/dev/null || true)"
if [[ "$response" == *"管理员已初始化"* ]]; then
log "检测到管理员已初始化,跳过随机初始化"
ADMIN_USER=""
ADMIN_PASS=""
return
fi
if [[ -z "$response" || "$response" != *'"success":true'* ]]; then
fail "管理员初始化失败请查看服务日志journalctl -u lightops-server -n 120 --no-pager"
fi
cat >"$credential_file" <<EOF
LightOps 首次安装管理员凭据
用户名:$ADMIN_USER
密码:$ADMIN_PASS
请登录面板后立即修改密码。
EOF
chmod 0600 "$credential_file"
}
print_result() {
local port
local lan_ip wan_ip local_url lan_url wan_url credential_file
port="${BIND_ADDR##*:}"
lan_ip="$(primary_lan_ip)"
wan_ip="$(external_ip)"
local_url="http://127.0.0.1:${port}"
lan_url="http://${lan_ip}:${port}"
credential_file="$CONFIG_DIR/initial-admin.txt"
if [[ "$PUBLIC_URL_SOURCE" != "auto" && -n "$PUBLIC_URL" ]]; then
wan_url="$PUBLIC_URL"
elif [[ -n "$wan_ip" ]]; then
wan_url="http://${wan_ip}:${port}"
else
wan_url="未检测到公网 IP可使用 --public-url 指定"
fi
cat <<EOF
LightOps Server 已安装完成
访问地址:$PUBLIC_URL
初始化入口:$PUBLIC_URL/init
内网地址:$lan_url
本机地址:$local_url
公网地址:$wan_url
监听端口:$port
安装目录:$INSTALL_DIR
配置文件:$CONFIG_DIR/server.toml
服务名称lightops-server
查看日志journalctl -u lightops-server -f
首次凭据:$credential_file
EOF
if [[ -n "$ADMIN_USER" && -n "$ADMIN_PASS" ]]; then
cat <<EOF
管理员账号:$ADMIN_USER
管理员密码:$ADMIN_PASS
EOF
else
cat <<EOF
管理员账号:已存在,未重置
管理员密码:已存在,未重置
EOF
fi
cat <<EOF
请登录面板后修改管理员密码。
如果服务器启用了防火墙,请放行 TCP $port。
EOF
}
ensure_deps
load_existing_config_values
choose_bind_addr
prepare_repo
build_project
write_config
write_service
start_service
initialize_admin
print_result