#!/usr/bin/env bash set -Eeuo pipefail 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" PUBLIC_URL="" SKIP_DEPS="false" FORCE="false" usage() { cat <<'EOF' LightOps Server 一键安装脚本 用法: bash install-server.sh [选项] 选项: --repo Git 仓库地址,默认 https://gitea.kmux.cn/Eeveid/lightOps.git --branch Git 分支,默认 main --install-dir 安装目录,默认 /opt/lightops --config-dir 配置目录,默认 /etc/lightops --bind 监听地址,默认 0.0.0.0:8080 --public-url 面板访问地址,默认根据本机 IP 生成 http://:8080 --skip-deps 跳过系统依赖、Rust、Node.js 检查安装 --force 仓库存在未提交改动时仍继续,谨慎使用 -h, --help 显示帮助 示例: curl -fsSL https://gitea.kmux.cn/Eeveid/lightOps/raw/branch/main/scripts/install-server.sh | bash curl -fsSL https://gitea.kmux.cn/Eeveid/lightOps/raw/branch/main/scripts/install-server.sh | bash -s -- --public-url https://panel.example.com EOF } while [[ $# -gt 0 ]]; do case "$1" in --repo) REPO_URL="${2:?缺少 --repo 参数值}" shift 2 ;; --branch) BRANCH="${2:?缺少 --branch 参数值}" shift 2 ;; --install-dir) INSTALL_DIR="${2:?缺少 --install-dir 参数值}" shift 2 ;; --config-dir) CONFIG_DIR="${2:?缺少 --config-dir 参数值}" shift 2 ;; --bind) BIND_ADDR="${2:?缺少 --bind 参数值}" shift 2 ;; --public-url) PUBLIC_URL="${2:?缺少 --public-url 参数值}" shift 2 ;; --skip-deps) SKIP_DEPS="true" shift ;; --force) FORCE="true" shift ;; -h|--help) usage exit 0 ;; *) echo "未知参数:$1" >&2 usage >&2 exit 2 ;; esac done if [[ "$(id -u)" -ne 0 ]]; then echo "请使用 root 用户运行,例如:curl -fsSL ... | bash" >&2 exit 1 fi if [[ "$(uname -s)" != "Linux" ]]; then echo "LightOps Server 一键安装当前只支持 Linux 服务器" >&2 exit 1 fi if ! command -v systemctl >/dev/null 2>&1; then echo "未检测到 systemd,当前一键安装脚本需要 systemd 管理服务" >&2 exit 1 fi case "$INSTALL_DIR" in ""|"/"|"/opt"|"/etc"|"/usr"|"/usr/local") echo "安装目录过于危险:$INSTALL_DIR" >&2 exit 1 ;; esac if [[ "$INSTALL_DIR" == *" "* || "$CONFIG_DIR" == *" "* ]]; then echo "安装目录和配置目录暂不支持空格" >&2 exit 1 fi log() { printf '\033[1;32m[LightOps]\033[0m %s\n' "$*" } warn() { printf '\033[1;33m[LightOps]\033[0m %s\n' "$*" >&2 } fail() { printf '\033[1;31m[LightOps]\033[0m %s\n' "$*" >&2 exit 1 } detect_public_url() { local port port="${BIND_ADDR##*:}" local ip ip="$(hostname -I 2>/dev/null | awk '{print $1}')" if [[ -z "$ip" ]]; then ip="127.0.0.1" fi echo "http://${ip}:${port}" } install_packages_apt() { export DEBIAN_FRONTEND=noninteractive apt-get update apt-get install -y ca-certificates curl git build-essential pkg-config sqlite3 } install_packages_generic() { if command -v apt-get >/dev/null 2>&1; then install_packages_apt elif command -v dnf >/dev/null 2>&1; then dnf install -y ca-certificates curl git gcc gcc-c++ make pkgconf-pkg-config sqlite elif command -v yum >/dev/null 2>&1; then yum install -y ca-certificates curl git gcc gcc-c++ make pkgconfig sqlite elif command -v pacman >/dev/null 2>&1; then pacman -Sy --noconfirm ca-certificates curl git base-devel pkgconf sqlite else fail "未识别系统包管理器,请手动安装 curl、git、C/C++ 构建工具、pkg-config、sqlite3" fi } node_major() { node -v 2>/dev/null | sed -E 's/^v([0-9]+).*/\1/' || true } ensure_node() { local major major="$(node_major)" if [[ -n "$major" && "$major" -ge 18 ]] && command -v npm >/dev/null 2>&1; then log "Node.js 已安装:$(node -v)" return fi if command -v apt-get >/dev/null 2>&1; then log "安装 Node.js 20" curl -fsSL https://deb.nodesource.com/setup_20.x | bash - apt-get install -y nodejs elif command -v dnf >/dev/null 2>&1; then dnf install -y nodejs npm elif command -v yum >/dev/null 2>&1; then yum install -y nodejs npm elif command -v pacman >/dev/null 2>&1; then pacman -Sy --noconfirm nodejs npm else fail "无法自动安装 Node.js,请手动安装 Node.js 18 或更高版本" fi major="$(node_major)" if [[ -z "$major" || "$major" -lt 18 ]]; then fail "Node.js 版本过低,需要 18 或更高版本,当前:$(node -v 2>/dev/null || echo 未安装)" fi } ensure_rust() { if command -v cargo >/dev/null 2>&1; then log "Rust 已安装:$(cargo --version)" return fi log "安装 Rust 稳定版工具链" curl --proto '=https' --tlsv1.2 -fsSL https://sh.rustup.rs | sh -s -- -y --profile minimal # shellcheck disable=SC1091 source "$HOME/.cargo/env" if ! command -v cargo >/dev/null 2>&1; then fail "Rust 安装失败,请检查网络或手动安装 rustup" fi } ensure_deps() { if [[ "$SKIP_DEPS" == "true" ]]; then warn "已跳过依赖安装检查" return fi log "安装系统依赖" install_packages_generic ensure_node ensure_rust } prepare_repo() { log "准备代码仓库:$INSTALL_DIR" mkdir -p "$(dirname "$INSTALL_DIR")" if [[ -d "$INSTALL_DIR/.git" ]]; then cd "$INSTALL_DIR" if [[ "$FORCE" != "true" && -n "$(git status --porcelain)" ]]; then fail "安装目录存在未提交改动:$INSTALL_DIR。请先处理改动,或使用 --force" fi git remote set-url origin "$REPO_URL" || true git fetch origin "$BRANCH" git checkout "$BRANCH" git pull --ff-only origin "$BRANCH" elif [[ -e "$INSTALL_DIR" && -n "$(find "$INSTALL_DIR" -mindepth 1 -maxdepth 1 2>/dev/null | head -n 1)" ]]; then fail "安装目录已存在且不是 Git 仓库:$INSTALL_DIR" else rm -rf "$INSTALL_DIR" git clone --branch "$BRANCH" "$REPO_URL" "$INSTALL_DIR" fi } build_project() { log "构建前端" cd "$INSTALL_DIR/web" if [[ -f package-lock.json ]]; then npm ci else npm install fi npm run build log "构建 Server 和 Agent" cd "$INSTALL_DIR" cargo build --release -p lightops-server -p lightops-agent install -m 0755 "$INSTALL_DIR/target/release/lightops-server" "$INSTALL_DIR/lightops-server" install -m 0755 "$INSTALL_DIR/target/release/lightops-agent" "$INSTALL_DIR/lightops-agent" install -m 0755 "$INSTALL_DIR/target/release/lightops-server" /usr/local/bin/lightops-server install -m 0755 "$INSTALL_DIR/target/release/lightops-agent" /usr/local/bin/lightops-agent } write_config() { mkdir -p "$CONFIG_DIR" "$INSTALL_DIR" if [[ -z "$PUBLIC_URL" ]]; then PUBLIC_URL="$(detect_public_url)" fi local jwt_secret if command -v openssl >/dev/null 2>&1; then jwt_secret="$(openssl rand -hex 32)" else jwt_secret="$(date +%s%N)-$(hostname)-$RANDOM-$RANDOM" fi if [[ -f "$CONFIG_DIR/server.toml" ]]; then log "保留已有配置:$CONFIG_DIR/server.toml" return fi log "生成 Server 配置:$CONFIG_DIR/server.toml" cat >"$CONFIG_DIR/server.toml" </etc/systemd/system/lightops-server.service <&2 || true fail "LightOps Server 启动失败" fi } print_result() { local port port="${BIND_ADDR##*:}" cat <