Files
lxdapi-web-server/install.sh

1052 lines
33 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
cd /root >/dev/null 2>&1
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
REGEX=("debian|astra" "ubuntu")
RELEASE=("Debian" "Ubuntu")
CMD=("$(grep -i pretty_name /etc/os-release 2>/dev/null | cut -d \" -f2)" "$(lsb_release -sd 2>/dev/null)")
SYS="${CMD[0]}"
[[ -n $SYS ]] || exit 1
for ((int = 0; int < ${#REGEX[@]}; int++)); do
if [[ $(echo "$SYS" | tr '[:upper:]' '[:lower:]') =~ ${REGEX[int]} ]]; then
SYSTEM="${RELEASE[int]}"
[[ -n $SYSTEM ]] && break
fi
done
if [[ "$SYSTEM" != "Debian" && "$SYSTEM" != "Ubuntu" ]]; then
echo -e "${RED}[ERR]${NC} 此脚本仅支持 Debian 和 Ubuntu 系统"
exit 1
fi
if [ ! -d "/usr/local/bin" ]; then
mkdir -p /usr/local/bin
fi
log() { echo -e "$1"; }
ok() { log "${GREEN}[OK]${NC} $1"; }
info() { log "${BLUE}[INFO]${NC} $1"; }
warn() { log "${YELLOW}[WARN]${NC} $1"; }
err() { log "${RED}[ERR]${NC} $1"; exit 1; }
print_step() {
local step=$1
local total=$2
local title=$3
echo
echo "========================================"
echo " 步骤 $step/$total: $title"
echo "========================================"
echo
}
reading() { read -rp "$(echo -e "${GREEN}$1${NC}")" "$2"; }
sed_compatible() {
if echo "test" | sed -E 's/test/ok/' >/dev/null 2>&1; then
sed -E "$@"
else
sed -r "$@"
fi
}
service_manager() {
local action=$1
local service_name=$2
case "$action" in
enable)
systemctl enable "$service_name" 2>/dev/null
;;
disable)
systemctl disable "$service_name" 2>/dev/null
;;
start)
systemctl start "$service_name" 2>/dev/null
;;
stop)
systemctl stop "$service_name" 2>/dev/null
;;
restart)
systemctl restart "$service_name" 2>/dev/null
;;
daemon-reload)
systemctl daemon-reload 2>/dev/null
;;
is-active)
systemctl is-active --quiet "$service_name" 2>/dev/null
return $?
;;
esac
return 0
}
set_locale() {
utf8_locale=$(locale -a 2>/dev/null | grep -i -m 1 -E "utf8|UTF-8")
export DEBIAN_FRONTEND=noninteractive
if [[ -z "$utf8_locale" ]]; then
warn "未找到 UTF-8 语言环境"
else
export LC_ALL="$utf8_locale"
export LANG="$utf8_locale"
export LANGUAGE="$utf8_locale"
ok "语言环境设置为 $utf8_locale"
fi
}
install_package() {
package_name=$1
if dpkg -l 2>/dev/null | grep -q "^ii.*$package_name"; then
ok "$package_name 已安装"
else
apt-get install -y $package_name >/dev/null 2>&1
if [ $? -ne 0 ]; then
apt-get install -y $package_name --fix-missing >/dev/null 2>&1
fi
if dpkg -l 2>/dev/null | grep -q "^ii.*$package_name"; then
ok "$package_name 已安装"
else
warn "$package_name 安装失败"
fi
fi
}
get_available_space() {
local available_space
available_space=$(df -BG / | awk 'NR==2 {gsub("G","",$4); print $4}')
echo "$available_space"
}
install_base_packages() {
info "更新软件包列表..."
apt-get update >/dev/null 2>&1
apt-get autoremove -y >/dev/null 2>&1
install_package wget
install_package curl
install_package sudo
install_package unzip
install_package iptables-persistent
install_package nginx
if dpkg -l lxcfs 2>/dev/null | grep -q "^ii"; then
warn "检测到 deb 版 lxcfs正在移除以避免与 snap 版 LXD 冲突..."
systemctl stop lxcfs 2>/dev/null || true
systemctl disable lxcfs 2>/dev/null || true
apt-get remove -y lxcfs >/dev/null 2>&1
ok "deb 版 lxcfs 已移除,将使用 snap 版 LXD 内置的 lxcfs"
fi
if systemctl is-active --quiet nginx; then
ok "nginx 服务已运行"
else
service_manager start nginx
service_manager enable nginx
ok "nginx 服务已启动并设置为自动启动"
fi
}
install_lxd() {
lxd_snap=$(dpkg -l | awk '/^[hi]i/{print $2}' | grep -ow snap)
lxd_snapd=$(dpkg -l | awk '/^[hi]i/{print $2}' | grep -ow snapd)
if [[ "$lxd_snap" =~ ^snap.* ]] && [[ "$lxd_snapd" =~ ^snapd.* ]]; then
ok "snap 已安装"
else
info "开始安装 snap..."
apt-get update >/dev/null 2>&1
install_package snapd
fi
snap_core=$(snap list core 2>/dev/null)
snap_lxd=$(snap list lxd 2>/dev/null)
if [[ "$snap_core" =~ core.* ]] && [[ "$snap_lxd" =~ lxd.* ]]; then
ok "LXD 已安装"
lxd_lxc_detect=$(lxc list 2>/dev/null)
if [[ "$lxd_lxc_detect" =~ "snap-update-ns failed with code1".* ]]; then
service_manager restart apparmor
snap restart lxd
else
ok "环境检测无问题"
fi
else
info "开始安装 LXD..."
snap install lxd --channel=latest/stable 2>/dev/null
if [[ $? -ne 0 ]]; then
snap remove lxd 2>/dev/null
snap install core 2>/dev/null
snap install lxd --channel=latest/stable 2>/dev/null
fi
snap alias lxd.lxc lxc 2>/dev/null
snap alias lxd.lxd lxd 2>/dev/null
if [ ! -f /etc/profile.d/snap.sh ]; then
echo 'export PATH=$PATH:/snap/bin' > /etc/profile.d/snap.sh
fi
export PATH=$PATH:/snap/bin
if ! command -v lxc >/dev/null 2>&1; then
err 'lxc 路径有问题,请检查 snap alias'
fi
ok "LXD 安装完成"
fi
info "配置 LXD..."
snap set lxd lxcfs.flags="-l" 2>/dev/null
snap set lxd daemon.debug=false 2>/dev/null
snap restart lxd 2>/dev/null
sleep 3
ok "LXD 已配置lxcfs legacy 模式 + 关闭调试)"
}
configure_resources() {
while true; do
reading "请选择存储后端类型 lvm/btrfs/zfs [lvm]" storage_backend
storage_backend=${storage_backend:-lvm}
if [[ "$storage_backend" =~ ^(lvm|zfs|btrfs)$ ]]; then
break
else
warn "请输入 lvm、btrfs 或 zfs"
fi
done
if [[ "$SYSTEM" == "Debian" && "$storage_backend" == "zfs" ]]; then
warn "Debian 系统需要从源码编译 ZFS预计耗时 10-30 分钟"
reading "是否继续?(y/n) [n]" confirm_zfs
confirm_zfs=${confirm_zfs:-n}
if [[ ! "$confirm_zfs" =~ ^[yY]$ ]]; then
info "已取消 ZFS请重新选择存储后端"
configure_resources
return
fi
fi
while true; do
reading "是否需要指定存储池的自定义路径?(y/n) [n]" use_custom_path
use_custom_path=${use_custom_path:-n}
if [[ "$use_custom_path" =~ ^[yYnN]$ ]]; then
break
else
warn "请输入 y 或 n"
fi
done
if [[ "$use_custom_path" =~ ^[yY]$ ]]; then
while true; do
reading "请输入自定义存储路径 [/opt/disk]" storage_path
storage_path=${storage_path:-/opt/disk}
if [[ -n "$storage_path" && "$storage_path" =~ ^/.+ ]]; then
if [ ! -d "$storage_path" ]; then
mkdir -p "$storage_path" 2>/dev/null
if [ $? -eq 0 ]; then
ok "已创建目录:$storage_path"
break
else
warn "创建目录失败,请检查权限或尝试其他路径"
fi
else
break
fi
else
warn "请输入以 / 开头的有效绝对路径"
fi
done
else
storage_path=""
fi
while true; do
reading "宿主机需要开设多大的存储池?单位 GB需要 10G 则输入 10" disk_nums
if [[ "$disk_nums" =~ ^[1-9][0-9]*$ ]]; then
break
else
warn "输入无效,请输入一个正整数"
fi
done
}
create_sparse_file() {
local file_path="$1"
local size_gb="$2"
if dd if=/dev/zero of="$file_path" bs=1G count=0 seek="${size_gb}" 2>/dev/null; then
ok "使用 dd 创建稀疏文件成功: $file_path ${size_gb}GB"
return 0
else
warn "dd 创建失败,尝试使用 truncate..."
if command -v truncate >/dev/null 2>&1; then
if truncate -s "${size_gb}G" "$file_path" 2>/dev/null; then
ok "使用 truncate 创建稀疏文件成功: $file_path ${size_gb}GB"
return 0
else
err "truncate 创建失败"
return 1
fi
else
err "truncate 命令不可用,无法创建稀疏文件"
return 1
fi
fi
}
create_lvm_storage_pool() {
local storage_path="$1"
local disk_nums="$2"
local loop_file="$storage_path/lvm_pool.img"
ok "创建 LVM 存储池..."
if [ -f "$loop_file" ]; then
warn "检测到旧的循环文件,正在清理..."
losetup -d $(losetup -j "$loop_file" | cut -d: -f1) 2>/dev/null || true
rm -f "$loop_file"
fi
ok "创建稀疏文件:$loop_file ${disk_nums}GB..."
if ! create_sparse_file "$loop_file" "$disk_nums"; then
return 1
fi
echo "$loop_file" > "$storage_path/lvm_loop_file.txt"
/snap/bin/lxc storage create default lvm source="$loop_file" 2>&1
return $?
}
create_zfs_storage_pool() {
local storage_path="$1"
local disk_nums="$2"
local loop_file="$storage_path/zfs_pool.img"
ok "创建 ZFS 存储池..."
# 清理旧的 LXD 存储池
if /snap/bin/lxc storage show default >/dev/null 2>&1; then
warn "检测到旧的 LXD 存储池,正在清理..."
for container in $(/snap/bin/lxc list -c n --format csv 2>/dev/null); do
/snap/bin/lxc stop "$container" --force 2>/dev/null
/snap/bin/lxc delete "$container" --force 2>/dev/null
done
for image in $(/snap/bin/lxc image list -c f --format csv 2>/dev/null); do
/snap/bin/lxc image delete "$image" 2>/dev/null
done
/snap/bin/lxc storage delete default 2>/dev/null || true
fi
# 清理旧的 zpool
if zpool list lxd_zpool >/dev/null 2>&1; then
warn "检测到旧的 ZFS 池,正在清理..."
zpool destroy lxd_zpool 2>/dev/null || true
fi
# 清理旧文件
if [ -f "$loop_file" ]; then
rm -f "$loop_file"
fi
# 创建存储目录
mkdir -p "$storage_path"
# 创建稀疏文件
ok "创建稀疏文件:$loop_file ${disk_nums}GB..."
if ! dd if=/dev/zero of="$loop_file" bs=1G count=0 seek="${disk_nums}" 2>/dev/null; then
err "创建稀疏文件失败"
return 1
fi
# 创建 loop 设备
ok "创建 loop 设备..."
local loop_dev=$(losetup -f --show "$loop_file")
if [ -z "$loop_dev" ]; then
err "创建 loop 设备失败"
return 1
fi
ok "Loop 设备: $loop_dev"
# 创建 ZFS 池(使用 loop 设备而不是文件)
ok "创建 ZFS 池..."
if ! zpool create -f lxd_zpool "$loop_dev"; then
warn "ZFS 池创建失败"
losetup -d "$loop_dev" 2>/dev/null
rm -f "$loop_file"
return 1
fi
# 保存配置
echo "$loop_file" > "$storage_path/zfs_loop_file.txt"
echo "$loop_dev" > "$storage_path/zfs_loop_device.txt"
# 配置开机自动恢复 loop 设备和导入 zpool
ok "配置开机自动导入..."
cat > /usr/local/bin/zpool-import-lxd.sh << EOF
#!/bin/bash
LOOP_FILE="$loop_file"
# 检查 zpool 是否已导入
zpool list lxd_zpool >/dev/null 2>&1 && exit 0
# 创建 loop 设备
LOOP_DEV=\$(losetup -f --show "\$LOOP_FILE" 2>/dev/null)
if [ -z "\$LOOP_DEV" ]; then
exit 1
fi
# 导入 zpool
zpool import -d /dev lxd_zpool 2>/dev/null || zpool import -f lxd_zpool 2>/dev/null
EOF
chmod +x /usr/local/bin/zpool-import-lxd.sh
cat > /etc/systemd/system/zpool-import-lxd.service << 'EOF'
[Unit]
Description=Import LXD ZFS Pool
Before=snap.lxd.daemon.service
After=local-fs.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/zpool-import-lxd.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable zpool-import-lxd.service 2>/dev/null
ok "开机自动导入服务已配置"
# 创建 LXD 存储池
/snap/bin/lxc storage create default zfs source=lxd_zpool 2>&1
return $?
}
create_btrfs_storage_pool() {
local storage_path="$1"
local disk_nums="$2"
local loop_file="$storage_path/btrfs_pool.img"
ok "创建 Btrfs 存储池..."
if [ -f "$loop_file" ]; then
warn "检测到旧的循环文件,正在清理..."
losetup -d $(losetup -j "$loop_file" | cut -d: -f1) 2>/dev/null || true
rm -f "$loop_file"
fi
ok "创建稀疏文件:$loop_file ${disk_nums}GB..."
if ! create_sparse_file "$loop_file" "$disk_nums"; then
return 1
fi
echo "$loop_file" > "$storage_path/btrfs_loop_file.txt"
/snap/bin/lxc storage create default btrfs source="$loop_file" 2>&1
return $?
}
create_storage_pool_with_custom_path() {
local backend="$1"
local storage_path="$2"
local disk_nums="$3"
mkdir -p "$storage_path"
case "$backend" in
lvm)
create_lvm_storage_pool "$storage_path" "$disk_nums"
;;
zfs)
create_zfs_storage_pool "$storage_path" "$disk_nums"
;;
btrfs)
create_btrfs_storage_pool "$storage_path" "$disk_nums"
;;
esac
return $?
}
execute_storage_init() {
local backend="$1"
local temp
local status
if [ -n "$storage_path" ]; then
if create_storage_pool_with_custom_path "$backend" "$storage_path" "$disk_nums"; then
ok "$backend 存储池创建成功"
/snap/bin/lxd init --auto >/dev/null 2>&1 || true
temp="Storage pool created successfully"
status=0
else
temp="Failed to create $backend storage pool"
status=1
fi
else
temp=$(/snap/bin/lxd init --storage-backend "$backend" --storage-create-loop "$disk_nums" --storage-pool default --auto 2>&1)
status=$?
fi
echo "$temp"
return $status
}
init_storage_backend() {
local backend="${storage_backend:-lvm}"
ok "使用 $backend 类型,存储池大小为 $disk_nums GB"
case "$backend" in
lvm)
install_package lvm2
;;
zfs)
if command -v zfs >/dev/null 2>&1 && command -v zpool >/dev/null 2>&1; then
ok "ZFS 已安装,跳过安装步骤"
elif [[ "$SYSTEM" == "Debian" ]]; then
info "Debian 系统开始编译安装 ZFS..."
if ! bash <(curl -sL https://raw.githubusercontent.com/xkatld/lxdapi-web-server/refs/heads/v2.0.0-main/build_zfs_on_debian.sh); then
err "ZFS 编译安装失败"
fi
else
install_package zfs-dkms
install_package zfsutils-linux
fi
# 让 LXD 使用系统的 ZFS 工具(解决 ZFS 2.3.0 兼容性问题)
info "配置 LXD 使用系统 ZFS 工具..."
snap set lxd zfs.external=true 2>/dev/null
snap restart lxd 2>/dev/null
sleep 3
ok "LXD ZFS 外部工具模式已启用"
;;
btrfs)
install_package btrfs-progs
;;
esac
local temp
temp=$(execute_storage_init "$backend")
local status=$?
info "初始化存储:"
echo "$temp"
if echo "$temp" | grep -q "lxd.migrate" && [ $status -ne 0 ]; then
/snap/bin/lxd.migrate
temp=$(execute_storage_init "$backend")
status=$?
echo "$temp"
fi
if [ $status -eq 0 ]; then
ok "使用 $backend 初始化成功"
echo "$backend" >/usr/local/bin/lxd_storage_type
return 0
else
err "使用 $backend 初始化失败"
return 1
fi
}
setup_storage() {
if [ -f "/usr/local/bin/lxd_reboot" ]; then
rm -f /usr/local/bin/lxd_reboot
info "检测到上次安装未完成,已清理重启标志"
fi
init_storage_backend
}
download_and_import_image() {
local image_name="$1"
local arch="$2"
local base_url="$3"
local image_url="${base_url}/${image_name}-${arch}.tar.gz"
info "下载: ${image_name}-${arch}.tar.gz"
local temp_file=$(mktemp)
if wget -q --show-progress -O "$temp_file" "$image_url" 2>&1; then
info "导入到 LXD..."
if lxc image import "$temp_file" --alias "$image_name" 2>/dev/null; then
ok "成功导入: $image_name"
else
warn "导入失败: $image_name"
fi
rm -f "$temp_file"
else
warn "下载失败: ${image_name}"
rm -f "$temp_file"
fi
}
import_container_images() {
info "检测系统架构..."
sys_arch=$(uname -m)
case $sys_arch in
x86_64)
arch="amd64"
ok "检测到架构: x86_64"
;;
aarch64|arm64)
arch="arm64"
ok "检测到架构: $sys_arch"
;;
*)
err "不支持的架构: $sys_arch,仅支持 amd64 和 arm64"
;;
esac
IMAGES_BASE_URL="https://github.com/xkatld/zjmf-lxd-server/releases/download/images"
declare -A IMAGE_MAP
IMAGE_MAP=(
[1]="alma8" [2]="alma9" [3]="alma10"
[4]="alpine319" [5]="alpine320" [6]="alpine321" [7]="alpine322" [8]="alpineEdge"
[9]="amazon2023"
[10]="centos9" [11]="centos10"
[12]="debian11" [13]="debian12" [14]="debian13"
[15]="fedora41" [16]="fedora42"
[17]="oracle8" [18]="oracle9"
[19]="rocky8" [20]="rocky9" [21]="rocky10"
[22]="suse155" [23]="suse156" [24]="suseTumbleweed"
[25]="ubuntu2204" [26]="ubuntu2404" [27]="ubuntu2410"
)
echo
echo "============================================================================================================"
echo " 1) alma8 2) alma9 3) alma10 4) alpine319 5) alpine320 "
echo " 6) alpine321 7) alpine322 8) alpineEdge 9) amazon2023 10) centos9 "
echo "11) centos10 12) debian11 13) debian12 14) debian13 15) fedora41 "
echo "16) fedora42 17) oracle8 18) oracle9 19) rocky8 20) rocky9 "
echo "21) rocky10 22) suse155 23) suse156 24) suseTumbleweed "
echo "25) ubuntu2204 26) ubuntu2404 27) ubuntu2410 "
echo "============================================================================================================"
echo
reading "请输入镜像编号,多个用逗号分隔如 1,2,3 或输入 all 全部导入 [默认: 2,5,13,26]" image_choices
image_choices=${image_choices:-"2,5,13,26"}
if [[ "$image_choices" == "all" ]]; then
selected_images=(${IMAGE_MAP[@]})
else
IFS=',' read -ra choices <<< "$image_choices"
selected_images=()
for choice in "${choices[@]}"; do
choice=$(echo "$choice" | xargs)
if [[ -n "${IMAGE_MAP[$choice]}" ]]; then
selected_images+=("${IMAGE_MAP[$choice]}")
fi
done
fi
if [[ ${#selected_images[@]} -eq 0 ]]; then
warn "未选择任何镜像"
return
fi
echo
ok "已选择 ${#selected_images[@]} 个镜像"
echo
current=0
for img in "${selected_images[@]}"; do
((current++))
echo "[$current/${#selected_images[@]}]"
download_and_import_image "$img" "$arch" "$IMAGES_BASE_URL"
echo
done
}
deploy_lxdapi() {
info "检测系统架构..."
sys_arch=$(uname -m)
case $sys_arch in
x86_64)
arch="amd64"
ok "检测到架构: x86_64"
;;
aarch64|arm64)
arch="arm64"
ok "检测到架构: $sys_arch"
;;
*)
err "不支持的架构: $sys_arch"
;;
esac
info "获取最新版本..."
latest_tag=$(curl -s https://api.github.com/repos/xkatld/lxdapi-web-server/releases/latest | grep '"tag_name"' | sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p')
if [ -z "$latest_tag" ]; then
err "无法获取最新版本信息"
fi
ok "最新版本: $latest_tag"
download_url="https://github.com/xkatld/lxdapi-web-server/releases/download/${latest_tag}/lxdapi-linux-${arch}.tar.gz"
info "下载 lxdapi..."
info "下载地址: $download_url"
temp_file=$(mktemp)
if wget -q --show-progress -O "$temp_file" "$download_url" 2>&1; then
ok "下载完成"
else
rm -f "$temp_file"
err "下载失败"
fi
info "解压到 /opt/lxdapi..."
mkdir -p /opt/lxdapi
tar -xzf "$temp_file" -C /opt/lxdapi --strip-components=1
rm -f "$temp_file"
}
configure_lxdapi() {
info "配置 lxdapi..."
config_file="/opt/lxdapi/configs/config.yaml"
if [ ! -f "$config_file" ]; then
err "配置文件不存在: $config_file"
fi
reading "请输入服务端口 [8443]" server_port
server_port=${server_port:-8443}
reading "请输入API密钥 [随机生成]" api_hash
if [ -z "$api_hash" ]; then
api_hash=$(openssl rand -hex 16)
ok "API密钥已生成: $api_hash"
fi
reading "请输入流量采集间隔秒数 [30]" traffic_interval
traffic_interval=${traffic_interval:-30}
reading "请输入流量批量更新数量 [5]" traffic_batch_size
traffic_batch_size=${traffic_batch_size:-5}
while true; do
reading "请选择数据库类型 sqlite/mysql/postgres [sqlite]" db_type
db_type=${db_type:-sqlite}
if [[ "$db_type" =~ ^(sqlite|mysql|postgres)$ ]]; then
break
else
warn "请输入 sqlite、mysql 或 postgres"
fi
done
if [[ "$db_type" == "mysql" ]]; then
while true; do
reading "使用本地安装还是远程配置local/remote [local]" mysql_location
mysql_location=${mysql_location:-local}
if [[ "$mysql_location" =~ ^(local|remote)$ ]]; then
break
else
warn "请输入 local 或 remote"
fi
done
if [[ "$mysql_location" == "local" ]]; then
info "安装 MariaDB..."
install_package mariadb-server
service_manager start mariadb
service_manager enable mariadb
mysql_host="localhost"
mysql_port="3306"
mysql_user="lxdapi"
mysql_password=$(openssl rand -hex 8)
mysql_database="lxdapi"
info "创建数据库和用户..."
mysql -u root << EOF
CREATE DATABASE IF NOT EXISTS ${mysql_database};
CREATE USER IF NOT EXISTS '${mysql_user}'@'localhost' IDENTIFIED BY '${mysql_password}';
GRANT ALL PRIVILEGES ON ${mysql_database}.* TO '${mysql_user}'@'localhost';
FLUSH PRIVILEGES;
EOF
ok "MariaDB 数据库已创建"
ok "用户: $mysql_user"
ok "密码: $mysql_password"
else
reading "请输入 MySQL 主机地址:" mysql_host
reading "请输入 MySQL 端口 [3306]" mysql_port
mysql_port=${mysql_port:-3306}
reading "请输入 MySQL 用户名:" mysql_user
reading "请输入 MySQL 密码:" mysql_password
reading "请输入 MySQL 数据库名:" mysql_database
fi
sed -i "s|__MYSQL_HOST__|$mysql_host|g" "$config_file"
sed -i "s|__MYSQL_PORT__|$mysql_port|g" "$config_file"
sed -i "s|__MYSQL_USER__|$mysql_user|g" "$config_file"
sed -i "s|__MYSQL_PASSWORD__|$mysql_password|g" "$config_file"
sed -i "s|__MYSQL_DATABASE__|$mysql_database|g" "$config_file"
elif [[ "$db_type" == "postgres" ]]; then
while true; do
reading "使用本地安装还是远程配置local/remote [local]" postgres_location
postgres_location=${postgres_location:-local}
if [[ "$postgres_location" =~ ^(local|remote)$ ]]; then
break
else
warn "请输入 local 或 remote"
fi
done
if [[ "$postgres_location" == "local" ]]; then
info "安装 PostgreSQL..."
install_package postgresql
service_manager start postgresql
service_manager enable postgresql
postgres_host="localhost"
postgres_port="5432"
postgres_user="lxdapi"
postgres_password=$(openssl rand -hex 8)
postgres_database="lxdapi"
postgres_sslmode="disable"
info "创建数据库和用户..."
sudo -u postgres psql << EOF
CREATE DATABASE ${postgres_database};
CREATE USER ${postgres_user} WITH PASSWORD '${postgres_password}';
GRANT ALL PRIVILEGES ON DATABASE ${postgres_database} TO ${postgres_user};
EOF
ok "PostgreSQL 数据库已创建"
ok "用户: $postgres_user"
ok "密码: $postgres_password"
else
reading "请输入 PostgreSQL 主机地址:" postgres_host
reading "请输入 PostgreSQL 端口 [5432]" postgres_port
postgres_port=${postgres_port:-5432}
reading "请输入 PostgreSQL 用户名:" postgres_user
reading "请输入 PostgreSQL 密码:" postgres_password
reading "请输入 PostgreSQL 数据库名:" postgres_database
reading "请输入 PostgreSQL SSL模式 [disable]" postgres_sslmode
postgres_sslmode=${postgres_sslmode:-disable}
fi
sed -i "s|__POSTGRES_HOST__|$postgres_host|g" "$config_file"
sed -i "s|__POSTGRES_PORT__|$postgres_port|g" "$config_file"
sed -i "s|__POSTGRES_USER__|$postgres_user|g" "$config_file"
sed -i "s|__POSTGRES_PASSWORD__|$postgres_password|g" "$config_file"
sed -i "s|__POSTGRES_DATABASE__|$postgres_database|g" "$config_file"
sed -i "s|__POSTGRES_SSLMODE__|$postgres_sslmode|g" "$config_file"
fi
while true; do
reading "请选择任务队列后端 memory/redis [memory]" task_backend
task_backend=${task_backend:-memory}
if [[ "$task_backend" =~ ^(memory|redis)$ ]]; then
break
else
warn "请输入 memory 或 redis"
fi
done
if [[ "$task_backend" == "redis" ]]; then
info "安装 Redis..."
install_package redis-server
service_manager start redis-server
service_manager enable redis-server
redis_host="localhost"
redis_port="6379"
redis_password=""
redis_db="0"
ok "Redis 已安装并启动"
sed -i "s|__REDIS_HOST__|$redis_host|g" "$config_file"
sed -i "s|__REDIS_PORT__|$redis_port|g" "$config_file"
sed -i "s|__REDIS_PASSWORD__|$redis_password|g" "$config_file"
sed -i "s|__REDIS_DB__|$redis_db|g" "$config_file"
fi
reading "请输入管理员用户名 [admin]" admin_user
admin_user=${admin_user:-admin}
reading "请输入管理员密码 [随机生成]" admin_pass
if [ -z "$admin_pass" ]; then
admin_pass=$(openssl rand -hex 4)
ok "管理员密码已生成: $admin_pass"
fi
reading "请输入Session密钥 [随机生成]" session_secret
if [ -z "$session_secret" ]; then
session_secret=$(openssl rand -hex 16)
ok "Session密钥已生成: $session_secret"
fi
info "写入配置文件..."
sed -i "s|__SERVER_PORT__|$server_port|g" "$config_file"
sed -i "s|__API_HASH__|$api_hash|g" "$config_file"
sed -i "s|__TRAFFIC_INTERVAL__|$traffic_interval|g" "$config_file"
sed -i "s|__TRAFFIC_BATCH_SIZE__|$traffic_batch_size|g" "$config_file"
sed -i "s|__DB_TYPE__|$db_type|g" "$config_file"
sed -i "s|__TASK_BACKEND__|$task_backend|g" "$config_file"
sed -i "s|__ADMIN_USER__|$admin_user|g" "$config_file"
sed -i "s|__ADMIN_PASS__|$admin_pass|g" "$config_file"
sed -i "s|__SESSION_SECRET__|$session_secret|g" "$config_file"
sed -i "s|__MYSQL_HOST__|localhost|g" "$config_file"
sed -i "s|__MYSQL_PORT__|3306|g" "$config_file"
sed -i "s|__MYSQL_USER__|lxdapi|g" "$config_file"
sed -i "s|__MYSQL_PASSWORD__|password|g" "$config_file"
sed -i "s|__MYSQL_DATABASE__|lxdapi|g" "$config_file"
sed -i "s|__POSTGRES_HOST__|localhost|g" "$config_file"
sed -i "s|__POSTGRES_PORT__|5432|g" "$config_file"
sed -i "s|__POSTGRES_USER__|lxdapi|g" "$config_file"
sed -i "s|__POSTGRES_PASSWORD__|password|g" "$config_file"
sed -i "s|__POSTGRES_DATABASE__|lxdapi|g" "$config_file"
sed -i "s|__POSTGRES_SSLMODE__|disable|g" "$config_file"
sed -i "s|__REDIS_HOST__|localhost|g" "$config_file"
sed -i "s|__REDIS_PORT__|6379|g" "$config_file"
sed -i "s|__REDIS_PASSWORD__||g" "$config_file"
sed -i "s|__REDIS_DB__|0|g" "$config_file"
ok "配置文件已更新"
}
setup_lxdapi_service() {
info "配置 lxdapi 系统服务..."
config_file="/opt/lxdapi/configs/config.yaml"
if [ ! -f "$config_file" ]; then
err "配置文件不存在: $config_file"
fi
if grep -q "__SERVER_PORT__" "$config_file"; then
err "配置文件未完成配置"
fi
sys_arch=$(uname -m)
case $sys_arch in
x86_64)
exec_bin="/opt/lxdapi/lxdapi-amd64"
;;
aarch64|arm64)
exec_bin="/opt/lxdapi/lxdapi-arm64"
;;
*)
err "不支持的架构: $sys_arch"
;;
esac
service_file="/etc/systemd/system/lxdapi.service"
cat > "$service_file" << EOF
[Unit]
Description=LXD API Server
After=network.target lxd.service
Wants=lxd.service
[Service]
Type=simple
User=root
WorkingDirectory=/opt/lxdapi
ExecStart=$exec_bin
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
ok "服务文件已创建: $service_file"
info "重载 systemd 配置..."
systemctl daemon-reload
info "启用开机自启..."
systemctl enable lxdapi
info "启动 lxdapi 服务..."
systemctl start lxdapi
sleep 2
if systemctl is-active --quiet lxdapi; then
ok "lxdapi 服务已启动"
echo
info "===== 服务状态 ====="
systemctl status lxdapi --no-pager | head -10
else
warn "lxdapi 服务启动失败"
echo
info "===== 错误日志 ====="
journalctl -u lxdapi -n 20 --no-pager
fi
}
main() {
print_step "1" "5" "初始化环境"
set_locale
install_base_packages
ok "环境初始化完成"
print_step "2" "5" "安装 LXD"
install_lxd
print_step "3" "5" "配置存储资源"
configure_resources
setup_storage
ok "存储配置完成"
print_step "4" "5" "导入容器镜像"
import_container_images
ok "镜像导入完成"
print_step "5" "5" "部署 lxdapi"
deploy_lxdapi
configure_lxdapi
setup_lxdapi_service
ok "lxdapi 部署完成"
echo
echo "========================================"
echo " lxdapi 安装完成"
echo "========================================"
echo
info "LXD 版本: $(lxd --version)"
info "LXC 版本: $(lxc --version)"
echo
info "===== 1. 网络配置 ====="
lxc network list
echo
info "===== 2. 存储配置 ====="
lxc storage list
echo
info "===== 3. 镜像配置 ====="
lxc image list
echo
info "===== 4. 后端配置 ====="
info "服务端口: $server_port"
info "API密钥: $api_hash"
info "流量间隔: $traffic_interval"
info "批量大小: $traffic_batch_size"
info "数据库: $db_type"
if [[ "$db_type" == "mysql" ]]; then
info "MySQL: $mysql_user@$mysql_host:$mysql_port/$mysql_database"
info "MySQL密码: $mysql_password"
elif [[ "$db_type" == "postgres" ]]; then
info "PostgreSQL: $postgres_user@$postgres_host:$postgres_port/$postgres_database"
info "PostgreSQL密码: $postgres_password"
fi
info "任务队列: $task_backend"
if [[ "$task_backend" == "redis" ]]; then
info "Redis: localhost:6379"
fi
info "管理员: $admin_user"
info "管理员密码: $admin_pass"
info "Session密钥: $session_secret"
echo
info "===== 5. lxdapi 服务状态 ====="
info "等待服务启动..."
sleep 5
systemctl status lxdapi --no-pager -l
}
main