mirror of
https://github.com/xkatld/lxdapi-web-server.git
synced 2026-06-09 08:23:51 +08:00
1052 lines
33 KiB
Bash
1052 lines
33 KiB
Bash
#!/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
|