一些更新

玩客云首次启动生成随机 mac 地址 #113
自签 SSL 证书修改为首次启动时生成,增强安全性
优化整合包镜像构建脚本和  Dockerfile
修复 docker 启动脚本错误
This commit is contained in:
mofeng-git 2025-05-27 12:43:53 +08:00
parent 64c83be0a4
commit 1642ce73a0
13 changed files with 1027 additions and 815 deletions

View File

@ -12,64 +12,72 @@ on:
- latest
jobs:
build-and-push-image:
build:
runs-on: ubuntu-22.04
container:
image: docker:24.0-cli
env:
TZ: Asia/Shanghai
# 定义环境变量,方便管理
# 修改为你的 Docker Registry 地址,如果是 Docker Hub则为 docker.io (可以省略)
# 如果是 Gitea 内置的包管理器,则为你的 Gitea 实例地址,例如 gitea.example.com:5000
DOCKER_REGISTRY: docker.io
# 推荐使用 Gitea 的内置变量来动态生成镜像名
#IMAGE_NAME: ${{ gitea.repository_owner }}/${{ gitea.repository_name }}
IMAGE_NAME: silentwind0/kvmd:${{ inputs.version }}
image: node:18
env:
TZ: Asia/Shanghai
steps:
- name: Print Build Information
run: |
echo "Workflow triggered for version: ${{ inputs.version }}"
echo "Current time: $(date -u +'%Y-%m-%dT%H:%M:%SZ')"
- name: Checkout code
uses: actions/checkout@v3
- name: Install dependencies
run: |
apt-get update
export DEBIAN_FRONTEND=noninteractive
apt-get install -y --no-install-recommends sudo tzdata node git
apt-get install -y --no-install-recommends \
sudo tzdata docker.io qemu-utils qemu-user-static binfmt-support parted e2fsprogs \
curl tar python3 python3-pip rsync git android-sdk-libsparse-utils coreutils zerofree
apt-get clean
rm -rf /var/lib/apt/lists/*
ln -snf /usr/share/zoneinfo/$TZ /etc/localtime
echo $TZ > /etc/timezone
update-binfmts --enable
env:
DEBIAN_FRONTEND: noninteractive
- name: Checkout Code
uses: actions/checkout@v3
- name: Install Docker Buildx
run: |
# 创建插件目录
mkdir -p ~/.docker/cli-plugins
# 下载 buildx 二进制文件
BUILDX_VERSION="v0.11.2"
curl -L "https://github.com/docker/buildx/releases/download/${BUILDX_VERSION}/buildx-${BUILDX_VERSION}.linux-amd64" -o ~/.docker/cli-plugins/docker-buildx
chmod +x ~/.docker/cli-plugins/docker-buildx
# 验证安装
docker buildx version
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
#- name: Install QEMU
# run: |
# 安装 QEMU 模拟器
#docker run --privileged --rm tonistiigi/binfmt --install all
# 验证 QEMU 安装
#docker buildx inspect --bootstrap
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Create and use new builder instance
run: |
# 创建新的 builder 实例
docker buildx create --name mybuilder --driver docker-container --bootstrap
# 使用新创建的 builder
docker buildx use mybuilder
# 验证支持的平台
docker buildx inspect --bootstrap
#- name: Login to Docker Hub
# uses: docker/login-action@v3
- name: Build multi-arch image
run: |
# 构建多架构镜像
docker buildx build \
--platform linux/amd64,linux/arm64,linux/arm/v7 \
--file ./build/Dockerfile \
--tag silentwind/kvmd:${{ github.event.inputs.version }} \
.
#- name: Login to DockerHub
# uses: docker/login-action@v2
# with:
# username: ${{ secrets.DOCKERHUB_USERNAME }}
# password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and Push
uses: docker/build-push-action@v5
with:
context: .
file: ./build/Dockerfile
platforms: linux/amd64,linux/arm64,linux/arm/v7
#push: true
push: false
tags: ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}
cache-from: type=registry,ref=${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
cache-to: type=registry,ref=${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max

View File

@ -82,14 +82,14 @@ RUN git clone --depth=1 https://gitlab.freedesktop.org/libnice/libnice /tmp/libn
&& tar xf libsrtp-2.2.0.tar.gz \
&& cd libsrtp-2.2.0 \
&& ./configure --prefix=/usr --enable-openssl \
&& make shared_library && make install \
&& make shared_library -j && make install \
&& cd /tmp \
&& rm -rf /tmp/libsrtp* \
&& git clone --depth=1 https://libwebsockets.org/repo/libwebsockets /tmp/libwebsockets \
&& cd /tmp/libwebsockets \
&& mkdir build && cd build \
&& cmake -DLWS_MAX_SMP=1 -DLWS_WITHOUT_EXTENSIONS=0 -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_C_FLAGS="-fpic" .. \
&& make && make install \
&& make -j && make install \
&& cd /tmp \
&& rm -rf /tmp/libwebsockets \
&& git clone --depth=1 https://github.com/meetecho/janus-gateway.git /tmp/janus-gateway \
@ -98,7 +98,7 @@ RUN git clone --depth=1 https://gitlab.freedesktop.org/libnice/libnice /tmp/libn
&& ./configure --enable-static --enable-websockets --enable-plugin-audiobridge \
--disable-data-channels --disable-rabbitmq --disable-mqtt --disable-all-plugins \
--disable-all-loggers --prefix=/usr \
&& make && make install \
&& make -j && make install \
&& cd /tmp \
&& rm -rf /tmp/janus-gateway
@ -116,4 +116,4 @@ RUN mkdir /tmp/lib \
&& cp libevent_core-*.so.7 libbsd.so.0 libevent_pthreads-*.so.7 libspeexdsp.so.1 \
libevent-*.so.7 libjpeg.so.62 libx264.so.164 libyuv.so.0 libnice.so.10 \
/usr/lib/libsrtp2.so.1 /usr/lib/libwebsockets.so.19 \
/tmp/lib/
/tmp/lib/

782
build/build_img.sh Normal file → Executable file
View File

@ -8,19 +8,6 @@ ROOTFS="${ROOTFS:-/tmp/rootfs}"
OUTPUTDIR="${OUTPUTDIR:-/mnt/nfs/lfs/src/output}"
TMPDIR="${TMPDIR:-$SRCPATH/tmp}"
get_git_commit_id() {
if git rev-parse --is-inside-work-tree &>/dev/null; then
git rev-parse --short HEAD 2>/dev/null || echo ""
else
echo ""
fi
}
GIT_COMMIT_ID=$(get_git_commit_id)
DATE=$(date +%y%m%d)
if [ -n "$GIT_COMMIT_ID" ]; then
DATE="${DATE}-${GIT_COMMIT_ID}"
fi
export LC_ALL=C
# 全局变量
@ -33,752 +20,24 @@ DEV_MOUNTED=0
DOCKER_CONTAINER_NAME="to_build_rootfs_$$"
PREBUILT_DIR="/tmp/prebuilt_binaries"
# --- 清理函数 ---
cleanup() {
echo "信息:执行清理操作..."
# 尝试卸载 chroot 环境下的挂载点
if [[ "$DEV_MOUNTED" -eq 1 ]]; then
echo "信息:卸载 $ROOTFS/dev ..."
sudo umount "$ROOTFS/dev" || echo "警告:卸载 $ROOTFS/dev 失败,可能已被卸载"
DEV_MOUNTED=0
fi
if [[ "$SYS_MOUNTED" -eq 1 ]]; then
echo "信息:卸载 $ROOTFS/sys ..."
sudo umount "$ROOTFS/sys" || echo "警告:卸载 $ROOTFS/sys 失败,可能已被卸载"
SYS_MOUNTED=0
fi
if [[ "$PROC_MOUNTED" -eq 1 ]]; then
echo "信息:卸载 $ROOTFS/proc ..."
sudo umount "$ROOTFS/proc" || echo "警告:卸载 $ROOTFS/proc 失败,可能已被卸载"
PROC_MOUNTED=0
fi
# --- 引入模块化脚本 ---
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
source "$SCRIPT_DIR/functions/common.sh"
source "$SCRIPT_DIR/functions/devices.sh"
source "$SCRIPT_DIR/functions/install.sh"
source "$SCRIPT_DIR/functions/packaging.sh"
# 尝试卸载主根文件系统
if [[ "$ROOTFS_MOUNTED" -eq 1 && -d "$ROOTFS" ]]; then
echo "信息:卸载 $ROOTFS ..."
sudo umount "$ROOTFS" || sudo umount -l "$ROOTFS" || echo "警告:卸载 $ROOTFS 失败"
ROOTFS_MOUNTED=0
fi
# 尝试卸载引导文件系统 (如果使用)
if [[ "$BOOTFS_MOUNTED" -eq 1 && -d "$BOOTFS" ]]; then
echo "信息:卸载 $BOOTFS ..."
sudo umount "$BOOTFS" || sudo umount -l "$BOOTFS" || echo "警告:卸载 $BOOTFS 失败"
BOOTFS_MOUNTED=0
fi
# 尝试分离 loop 设备
if [[ -n "$LOOPDEV" && -b "$LOOPDEV" ]]; then
echo "信息:尝试 zerofree $LOOPDEV ..."
sudo zerofree "$LOOPDEV" || echo "警告zerofree $LOOPDEV 失败,可能文件系统不支持或未干净卸载"
echo "信息:分离 loop 设备 $LOOPDEV ..."
sudo losetup -d "$LOOPDEV" || echo "警告:分离 $LOOPDEV 失败"
LOOPDEV=""
fi
# 尝试删除 Docker 容器
echo "信息:检查并删除 Docker 容器 $DOCKER_CONTAINER_NAME ..."
if sudo docker ps -a --format '{{.Names}}' | grep -q "^${DOCKER_CONTAINER_NAME}$"; then
sudo docker rm -f "$DOCKER_CONTAINER_NAME" || echo "警告:删除 Docker 容器 $DOCKER_CONTAINER_NAME 失败"
else
echo "信息Docker 容器 $DOCKER_CONTAINER_NAME 不存在或已被删除。"
fi
# 清理临时目录和挂载点目录
echo "信息:清理临时文件和目录..."
sudo rm -rf "$PREBUILT_DIR"
# 注意:不自动删除 $TMPDIR/rootfs.img 等原始或处理中的镜像文件,由各函数管理
# 只删除挂载点目录本身
if [[ -d "$ROOTFS" ]]; then
sudo rmdir "$ROOTFS" || echo "警告:删除目录 $ROOTFS 失败,可能非空"
fi
if [[ -d "$BOOTFS" ]]; then
sudo rmdir "$BOOTFS" || echo "警告:删除目录 $BOOTFS 失败,可能非空"
fi
echo "信息:清理完成。"
}
# 在打包镜像前调用此函数确保干净卸载所有挂载点和loop设备
unmount_all() {
echo "信息:执行卸载操作,准备打包..."
# 卸载 chroot 环境下的挂载点
if [[ "$DEV_MOUNTED" -eq 1 ]]; then
echo "信息:卸载 $ROOTFS/dev ..."
sudo umount "$ROOTFS/dev" || echo "警告:卸载 $ROOTFS/dev 失败,可能已被卸载"
DEV_MOUNTED=0
fi
if [[ "$SYS_MOUNTED" -eq 1 ]]; then
echo "信息:卸载 $ROOTFS/sys ..."
sudo umount "$ROOTFS/sys" || echo "警告:卸载 $ROOTFS/sys 失败,可能已被卸载"
SYS_MOUNTED=0
fi
if [[ "$PROC_MOUNTED" -eq 1 ]]; then
echo "信息:卸载 $ROOTFS/proc ..."
sudo umount "$ROOTFS/proc" || echo "警告:卸载 $ROOTFS/proc 失败,可能已被卸载"
PROC_MOUNTED=0
fi
# 卸载主根文件系统
if [[ "$ROOTFS_MOUNTED" -eq 1 && -d "$ROOTFS" ]]; then
echo "信息:卸载 $ROOTFS ..."
sudo umount "$ROOTFS" || sudo umount -l "$ROOTFS" || echo "警告:卸载 $ROOTFS 失败"
ROOTFS_MOUNTED=0
fi
# 尝试分离 loop 设备前执行 zerofree如果文件系统支持
if [[ -n "$LOOPDEV" && -b "$LOOPDEV" ]]; then
echo "信息:尝试 zerofree $LOOPDEV ..."
sudo zerofree "$LOOPDEV" || echo "警告zerofree $LOOPDEV 失败,可能文件系统不支持或未干净卸载"
echo "信息:分离 loop 设备 $LOOPDEV ..."
sudo losetup -d "$LOOPDEV" || echo "警告:分离 $LOOPDEV 失败"
LOOPDEV=""
fi
sudo rm -rf "$PREBUILT_DIR"
echo "信息:卸载操作完成,可以安全打包镜像。"
}
# 获取日期与Git版本
GIT_COMMIT_ID=$(get_git_commit_id)
DATE=$(date +%y%m%d)
if [ -n "$GIT_COMMIT_ID" ]; then
DATE="${DATE}-${GIT_COMMIT_ID}"
fi
# --- 注册清理函数 ---
# 在脚本退出、收到错误信号、中断信号、终止信号时执行 cleanup
trap cleanup EXIT ERR INT TERM
# --- 辅助函数 ---
# 查找并设置一个可用的 loop 设备
find_loop_device() {
echo "信息:查找可用的 loop 设备..."
# 只使用 --find 来获取设备名
LOOPDEV=$(sudo losetup --find)
if [[ -z "$LOOPDEV" || ! -e "$LOOPDEV" ]]; then
echo "错误:再次尝试后仍无法找到可用的 loop 设备。" >&2
exit 1
fi
echo "信息:找到可用 loop 设备名:$LOOPDEV"
}
# 检查并创建目录
ensure_dir() {
if [[ ! -d "$1" ]]; then
echo "信息:创建目录 $1 ..."
sudo mkdir -p "$1" || { echo "错误:创建目录 $1 失败" >&2; exit 1; }
fi
}
# 执行 chroot 命令
run_in_chroot() {
echo "信息:在 chroot 环境 ($ROOTFS) 中执行命令..."
sudo chroot --userspec "root:root" "$ROOTFS" bash -ec "$1" || { echo "错误:在 chroot 环境中执行命令失败" >&2; exit 1; }
echo "信息chroot 命令执行完成。"
}
# --- 核心功能函数 ---
write_meta() {
local hostname="$1"
echo "信息:在 chroot 环境中设置主机名/元数据为 $hostname ..."
run_in_chroot "sed -i 's/localhost.localdomain/$hostname/g' /etc/kvmd/meta.yaml"
}
mount_rootfs() {
echo "信息:挂载根文件系统到 $ROOTFS ..."
ensure_dir "$ROOTFS"
sudo mount "$LOOPDEV" "$ROOTFS" || { echo "错误:挂载 $LOOPDEV$ROOTFS 失败" >&2; exit 1; }
ROOTFS_MOUNTED=1
echo "信息:挂载 proc, sys, dev 到 chroot 环境..."
ensure_dir "$ROOTFS/proc"
sudo mount -t proc proc "$ROOTFS/proc" || { echo "错误:挂载 proc 到 $ROOTFS/proc 失败" >&2; exit 1; }
PROC_MOUNTED=1
ensure_dir "$ROOTFS/sys"
sudo mount -t sysfs sys "$ROOTFS/sys" || { echo "错误:挂载 sys 到 $ROOTFS/sys 失败" >&2; exit 1; }
SYS_MOUNTED=1
ensure_dir "$ROOTFS/dev"
sudo mount -o bind /dev "$ROOTFS/dev" || { echo "错误:绑定挂载 /dev 到 $ROOTFS/dev 失败" >&2; exit 1; }
DEV_MOUNTED=1
echo "信息:根文件系统及虚拟文件系统挂载完成。"
}
prepare_dns_and_mirrors() {
echo "信息:在 chroot 环境中准备 DNS 和更换软件源..."
run_in_chroot "
mkdir -p /run/systemd/resolve/ \\
&& touch /run/systemd/resolve/stub-resolv.conf \\
&& printf '%s\\n' 'nameserver 1.1.1.1' 'nameserver 1.0.0.1' > /etc/resolv.conf \\
&& echo '信息:尝试更换镜像源...' \\
&& bash <(curl -sSL https://gitee.com/SuperManito/LinuxMirrors/raw/main/ChangeMirrors.sh) \\
--source mirrors.tuna.tsinghua.edu.cn --upgrade-software false --web-protocol http || echo '警告:更换镜像源脚本执行失败,可能网络不通或脚本已更改'
"
}
delete_armbian_verify(){
echo "信息:在 chroot 环境中修改 Armbian 软件源..."
run_in_chroot "echo 'deb http://mirrors.ustc.edu.cn/armbian bullseye main bullseye-utils bullseye-desktop' > /etc/apt/sources.list.d/armbian.list"
}
prepare_external_binaries() {
local platform="$1" # linux/armhf or linux/amd64 or linux/aarch64
local docker_image="registry.cn-hangzhou.aliyuncs.com/silentwind/kvmd-stage-0"
echo "信息:准备外部预编译二进制文件 (平台: $platform)..."
ensure_dir "$PREBUILT_DIR"
echo "信息:拉取 Docker 镜像 $docker_image (平台: $platform)..."
sudo docker pull --platform "$platform" "$docker_image" || { echo "错误:拉取 Docker 镜像 $docker_image 失败" >&2; exit 1; }
echo "信息:创建 Docker 容器 $DOCKER_CONTAINER_NAME ..."
sudo docker create --name "$DOCKER_CONTAINER_NAME" "$docker_image" || { echo "错误:创建 Docker 容器 $DOCKER_CONTAINER_NAME 失败" >&2; exit 1; }
echo "信息:从 Docker 容器导出文件到 $PREBUILT_DIR ..."
sudo docker export "$DOCKER_CONTAINER_NAME" | sudo tar -xf - -C "$PREBUILT_DIR" || { echo "错误:导出并解压 Docker 容器内容失败" >&2; exit 1; }
echo "信息:预编译二进制文件准备完成,存放于 $PREBUILT_DIR"
# 删除 Docker 容器
sudo docker rm -f "$DOCKER_CONTAINER_NAME" || { echo "错误:删除 Docker 容器 $DOCKER_CONTAINER_NAME 失败" >&2; exit 1; }
}
config_base_files() {
local platform_id="$1" # e.g., "onecloud", "cumebox2"
echo "信息:配置基础文件和目录结构 ($platform_id)..."
echo "信息:创建 KVMD 相关目录..."
ensure_dir "$ROOTFS/etc/kvmd/override.d"
ensure_dir "$ROOTFS/etc/kvmd/vnc"
ensure_dir "$ROOTFS/var/lib/kvmd/msd/images"
ensure_dir "$ROOTFS/var/lib/kvmd/msd/meta"
ensure_dir "$ROOTFS/opt/vc/bin"
ensure_dir "$ROOTFS/usr/share/kvmd"
ensure_dir "$ROOTFS/One-KVM"
ensure_dir "$ROOTFS/usr/share/janus/javascript"
ensure_dir "$ROOTFS/usr/lib/ustreamer/janus"
ensure_dir "$ROOTFS/run/kvmd"
ensure_dir "$ROOTFS/tmp/wheel/"
ensure_dir "$ROOTFS/usr/lib/janus/transports/"
ensure_dir "$ROOTFS/usr/lib/janus/loggers"
echo "信息:复制 One-KVM 源码..."
sudo rsync -a --exclude={.git,.github,output,tmp} . "$ROOTFS/One-KVM/" || { echo "错误:复制 One-KVM 源码失败" >&2; exit 1; }
echo "信息:复制配置文件..."
sudo cp -r configs/kvmd/* configs/nginx configs/janus "$ROOTFS/etc/kvmd/"
sudo cp -r web extras contrib/keymaps "$ROOTFS/usr/share/kvmd/"
sudo cp testenv/fakes/vcgencmd "$ROOTFS/usr/bin/"
sudo cp -r testenv/js/* "$ROOTFS/usr/share/janus/javascript/"
sudo cp "build/platform/$platform_id" "$ROOTFS/usr/share/kvmd/platform" || { echo "错误:复制平台文件 build/platform/$platform_id 失败" >&2; exit 1; }
if [ -f "$SRCPATH/image/$platform_id/rc.local" ]; then
echo "信息:复制设备特定的 rc.local 文件..."
sudo cp "$SRCPATH/image/$platform_id/rc.local" "$ROOTFS/etc/"
fi
echo "信息:从预编译目录复制二进制文件和库..."
sudo cp "$PREBUILT_DIR/tmp/lib/"* "$ROOTFS/lib/"*-linux-*/ || echo "警告:复制 /tmp/lib/* 失败,可能源目录或目标目录不存在或不匹配"
sudo cp "$PREBUILT_DIR/tmp/ustreamer/ustreamer" "$PREBUILT_DIR/tmp/ustreamer/ustreamer-dump" "$PREBUILT_DIR/usr/bin/janus" "$ROOTFS/usr/bin/" || { echo "错误:复制 ustreamer/janus 二进制文件失败" >&2; exit 1; }
sudo cp "$PREBUILT_DIR/tmp/ustreamer/janus/libjanus_ustreamer.so" "$ROOTFS/usr/lib/ustreamer/janus/" || { echo "错误:复制 libjanus_ustreamer.so 失败" >&2; exit 1; }
sudo cp "$PREBUILT_DIR/tmp/wheel/"*.whl "$ROOTFS/tmp/wheel/" || { echo "错误:复制 Python wheel 文件失败" >&2; exit 1; }
sudo cp "$PREBUILT_DIR/usr/lib/janus/transports/"* "$ROOTFS/usr/lib/janus/transports/" || { echo "错误:复制 Janus transports 失败" >&2; exit 1; }
# 禁用 apt-file
if [ -f "$ROOTFS/etc/apt/apt.conf.d/50apt-file.conf" ]; then
echo "信息:禁用 apt-file 配置..."
sudo mv "$ROOTFS/etc/apt/apt.conf.d/50apt-file.conf" "$ROOTFS/etc/apt/apt.conf.d/50apt-file.conf.disabled"
fi
echo "信息:基础文件配置完成。"
}
# --- KVMD 安装与配置 ---
install_base_packages() {
echo "信息:在 chroot 环境中更新源并安装基础软件包..."
run_in_chroot "
apt-get update && \\
apt install -y --no-install-recommends \\
libxkbcommon-x11-0 nginx tesseract-ocr tesseract-ocr-eng tesseract-ocr-chi-sim \\
iptables network-manager curl kmod libmicrohttpd12 libjansson4 libssl3 \\
libsofia-sip-ua0 libglib2.0-0 libopus0 libogg0 libcurl4 libconfig9 \\
python3-pip net-tools && \\
apt clean && \\
rm -rf /var/lib/apt/lists/*
"
}
configure_network() {
local network_type="$1" # "systemd-networkd" or others (default network-manager)
if [ "$network_type" = "systemd-networkd" ]; then
echo "信息:在 chroot 环境中配置 systemd-networkd..."
run_in_chroot "
echo -e '[Match]\\nName=eth0\\n\\n[Network]\\nDHCP=yes\\n\\n[Link]\\nMACAddress=B6:AE:B3:21:42:0C' > /etc/systemd/network/99-eth0.network && \\
systemctl mask NetworkManager && \\
systemctl unmask systemd-networkd && \\
systemctl enable systemd-networkd systemd-resolved
"
else
echo "信息:使用默认的网络管理器 (NetworkManager)..."
# 可能需要确保 NetworkManager 是启用的 (通常默认是)
run_in_chroot "systemctl enable NetworkManager"
fi
}
install_python_deps() {
echo "信息:在 chroot 环境中安装 Python 依赖 (wheels)..."
run_in_chroot "
pip3 install --no-cache-dir --break-system-packages /tmp/wheel/*.whl && \\
pip3 cache purge && \\
rm -rf /tmp/wheel
"
}
configure_kvmd_core() {
echo "信息:在 chroot 环境中安装和配置 KVMD 核心..."
run_in_chroot "
cd /One-KVM && \\
python3 setup.py install && \\
bash scripts/kvmd-gencert --do-the-thing && \\
bash scripts/kvmd-gencert --do-the-thing --vnc && \\
kvmd-nginx-mkconf /etc/kvmd/nginx/nginx.conf.mako /etc/kvmd/nginx/nginx.conf && \\
kvmd -m
"
}
configure_system() {
echo "信息:在 chroot 环境中配置系统级设置 (sudoers, udev, services)..."
run_in_chroot "
cat /One-KVM/configs/os/sudoers/v2-hdmiusb >> /etc/sudoers && \\
cat /One-KVM/configs/os/udev/v2-hdmiusb-rpi4.rules > /etc/udev/rules.d/99-kvmd.rules && \\
echo 'libcomposite' >> /etc/modules && \\
mv /usr/local/bin/kvmd* /usr/bin/ || echo '信息:/usr/local/bin/kvmd* 未找到或移动失败,可能已在/usr/bin' && \\
cp /One-KVM/configs/os/services/* /etc/systemd/system/ && \\
cp /One-KVM/configs/os/tmpfiles.conf /usr/lib/tmpfiles.d/ && \\
mv /etc/kvmd/supervisord.conf /etc/supervisord.conf && \\
chmod +x /etc/update-motd.d/* || echo '警告chmod /etc/update-motd.d/* 失败' && \\
echo 'kvmd ALL=(ALL) NOPASSWD: /etc/kvmd/custom_atx/gpio.sh' >> /etc/sudoers && \\
echo 'kvmd ALL=(ALL) NOPASSWD: /etc/kvmd/custom_atx/usbrelay_hid.sh' >> /etc/sudoers && \\
systemd-sysusers /One-KVM/configs/os/sysusers.conf && \\
systemd-sysusers /One-KVM/configs/os/kvmd-webterm.conf && \\
ln -sf /usr/share/tesseract-ocr/*/tessdata /usr/share/tessdata || echo '警告:创建 tesseract 链接失败' && \\
sed -i 's/8080/80/g' /etc/kvmd/override.yaml && \\
sed -i 's/4430/443/g' /etc/kvmd/override.yaml && \\
chown kvmd -R /var/lib/kvmd/msd/ && \\
systemctl enable kvmd kvmd-otg kvmd-nginx kvmd-vnc kvmd-ipmi kvmd-webterm kvmd-janus kvmd-media && \\
systemctl disable nginx && \\
rm -rf /One-KVM
"
}
install_webterm() {
local arch="$1" # armhf, aarch64, x86_64
local ttyd_arch="$arch"
if [ "$arch" = "armhf" ]; then
ttyd_arch="armv7"
elif [ "$arch" = "amd64" ]; then
ttyd_arch="x86_64" # ttyd 通常用 x86_64
fi
echo "信息:在 chroot 环境中下载并安装 ttyd ($ttyd_arch)..."
run_in_chroot "
curl -L https://gh.llkk.cc/https://github.com/tsl0922/ttyd/releases/download/1.7.7/ttyd.${ttyd_arch} -o /usr/bin/ttyd && \\
chmod +x /usr/bin/ttyd && \\
mkdir -p /home/kvmd-webterm && \\
chown kvmd-webterm /home/kvmd-webterm
"
}
apply_kvmd_tweaks() {
local arch="$1" # armhf, aarch64, x86_64
local device_type="$2" # "gpio" or "video1" or other
local atx_setting=""
local hid_setting=""
echo "信息:根据架构 ($arch) 和设备类型 ($device_type) 调整 KVMD 配置..."
if [ "$arch" = "x86_64" ] || [ "$arch" = "amd64" ]; then
echo "信息:目标平台为 x86_64/amd64 架构,禁用 OTG设置 ATX 为 USBRELAY_HID..."
run_in_chroot "
systemctl disable kvmd-otg && \\
sed -i 's/^ATX=.*/ATX=USBRELAY_HID/' /etc/kvmd/atx.sh && \\
sed -i 's/device: \/dev\/ttyUSB0/device: \/dev\/kvmd-hid/g' /etc/kvmd/override.yaml
"
else
echo "信息::目标平台为 ARM 架构 ($arch)..."
# ARM 架构,配置 HID 为 OTG
hid_setting="otg"
run_in_chroot "
sed -i 's/#type: otg/type: otg/g' /etc/kvmd/override.yaml && \\
sed -i 's/device: \/dev\/ttyUSB0/#device: \/dev\/ttyUSB0/g' /etc/kvmd/override.yaml # 注释掉 ttyUSB0
"
echo "信息:设置 HID 为 $hid_setting"
run_in_chroot "sed -i 's/type: ch9329/type: $hid_setting/g' /etc/kvmd/override.yaml"
# 根据 device_type 配置 ATX
if [ "$device_type" = "gpio" ]; then
echo "信息:电源控制设备类型为 gpio设置 ATX 为 GPIO 并配置引脚..."
atx_setting="GPIO"
run_in_chroot "
sed -i 's/^ATX=.*/ATX=GPIO/' /etc/kvmd/atx.sh && \\
sed -i 's/SHUTDOWNPIN/gpiochip1 7/g' /etc/kvmd/custom_atx/gpio.sh && \\
sed -i 's/REBOOTPIN/gpiochip0 11/g' /etc/kvmd/custom_atx/gpio.sh
"
else
echo "信息:电源控制设备类型不是 gpio ($device_type),设置 ATX 为 USBRELAY_HID..."
atx_setting="USBRELAY_HID"
run_in_chroot "sed -i 's/^ATX=.*/ATX=USBRELAY_HID/' /etc/kvmd/atx.sh"
fi
# 配置视频设备
if [ "$device_type" = "video1" ]; then
echo "信息:视频设备类型为 video1设置视频设备为 /dev/video1..."
run_in_chroot "sed -i 's|/dev/video0|/dev/video1|g' /etc/kvmd/override.yaml"
elif [ "$device_type" = "kvmd-video" ]; then
echo "信息:视频设备类型为 kvmd-video设置视频设备为 /dev/kvmd-video..."
run_in_chroot "sed -i 's|/dev/video0|/dev/kvmd-video|g' /etc/kvmd/override.yaml"
else
echo "信息:使用默认视频设备 /dev/video0..."
fi
fi
echo "信息KVMD 配置调整完成。"
}
# --- 整体安装流程 ---
install_and_configure_kvmd() {
local arch="$1" # 架构: armhf, aarch64, x86_64/amd64
local device_type="$2" # 设备特性: "gpio", "video1", "" (空或其他)
local network_type="$3" # 网络配置: "systemd-networkd", "" (默认 network-manager)
local host_arch="" # Docker 平台架构: arm, aarch64, amd64
# 映射架构名称
case "$arch" in
armhf) host_arch="arm" ;;
aarch64) host_arch="arm64" ;; # docker aarch64 平台名是 arm64
x86_64|amd64) host_arch="amd64"; arch="x86_64" ;; # 统一内部使用 x86_64
*) echo "错误:不支持的架构 $arch"; exit 1 ;;
esac
prepare_external_binaries "linux/$host_arch"
config_base_files "$TARGET_DEVICE_NAME" # 使用全局变量传递设备名
# 特定设备的额外文件配置 (如果存在)
if declare -f "config_${TARGET_DEVICE_NAME}_files" > /dev/null; then
echo "信息:执行特定设备的文件配置函数 config_${TARGET_DEVICE_NAME}_files ..."
"config_${TARGET_DEVICE_NAME}_files"
fi
# 某些镜像可能需要准备DNS和换源
if [[ "$NEED_PREPARE_DNS" = true ]]; then
prepare_dns_and_mirrors
fi
# 可选强制使用特定armbian源
# delete_armbian_verify
# 执行安装步骤
install_base_packages
configure_network "$network_type"
install_python_deps
configure_kvmd_core
configure_system
install_webterm "$arch" # 传递原始架构名给ttyd下载
apply_kvmd_tweaks "$arch" "$device_type"
run_in_chroot "df -h" # 显示最终磁盘使用情况
echo "信息One-KVM 安装和配置完成。"
}
# --- 打包函数 ---
pack_img() {
local device_name_friendly="$1" # e.g., "Vm", "Cumebox2"
local target_img_name="One-KVM_by-SilentWind_${device_name_friendly}_${DATE}.img"
local source_img="$TMPDIR/rootfs.img"
echo "信息:开始打包镜像 ($device_name_friendly)..."
ensure_dir "$OUTPUTDIR"
# 确保在打包前已经正确卸载了所有挂载点和loop设备
if [[ "$ROOTFS_MOUNTED" -eq 1 || "$DEV_MOUNTED" -eq 1 || "$SYS_MOUNTED" -eq 1 || "$PROC_MOUNTED" -eq 1 || -n "$LOOPDEV" && -b "$LOOPDEV" ]]; then
echo "警告发现未卸载的挂载点或loop设备尝试再次卸载..."
unmount_all
fi
echo "信息:移动镜像文件 $source_img$OUTPUTDIR/$target_img_name ..."
sudo mv "$source_img" "$OUTPUTDIR/$target_img_name" || { echo "错误:移动镜像文件失败" >&2; exit 1; }
if [ "$device_name_friendly" = "Vm" ]; then
echo "信息:为 Vm 目标转换镜像格式 (vmdk, vdi)..."
local raw_img="$OUTPUTDIR/$target_img_name"
local vmdk_img="$OUTPUTDIR/One-KVM_by-SilentWind_Vmare-uefi_${DATE}.vmdk"
local vdi_img="$OUTPUTDIR/One-KVM_by-SilentWind_Virtualbox-uefi_${DATE}.vdi"
echo "信息:转换为 VMDK..."
sudo qemu-img convert -f raw -O vmdk "$raw_img" "$vmdk_img" || echo "警告:转换为 VMDK 失败"
echo "信息:转换为 VDI..."
sudo qemu-img convert -f raw -O vdi "$raw_img" "$vdi_img" || echo "警告:转换为 VDI 失败"
fi
echo "信息:镜像打包完成: $OUTPUTDIR/$target_img_name"
}
pack_img_onecloud() {
local target_img_name="One-KVM_by-SilentWind_Onecloud_${DATE}.burn.img"
local rootfs_raw_img="$TMPDIR/rootfs.img"
local rootfs_sparse_img="$TMPDIR/7.rootfs.PARTITION.sparse"
local aml_packer="$SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64"
echo "信息:开始为 Onecloud 打包 burn 镜像..."
ensure_dir "$OUTPUTDIR"
# 确保在打包前已经正确卸载了所有挂载点和loop设备
if [[ "$ROOTFS_MOUNTED" -eq 1 || "$DEV_MOUNTED" -eq 1 || "$SYS_MOUNTED" -eq 1 || "$PROC_MOUNTED" -eq 1 || -n "$LOOPDEV" && -b "$LOOPDEV" ]]; then
echo "警告发现未卸载的挂载点或loop设备尝试再次卸载..."
unmount_all
fi
echo "信息:将 raw rootfs 转换为 sparse image..."
# 先删除可能存在的旧 sparse 文件
sudo rm -f "$rootfs_sparse_img"
sudo img2simg "$rootfs_raw_img" "$rootfs_sparse_img" || { echo "错误img2simg 转换失败" >&2; exit 1; }
sudo rm "$rootfs_raw_img" # 删除 raw 文件,因为它已被转换
echo "信息:使用 AmlImg 工具打包..."
sudo chmod +x "$aml_packer"
sudo "$aml_packer" pack "$OUTPUTDIR/$target_img_name" "$TMPDIR/" || { echo "错误AmlImg 打包失败" >&2; exit 1; }
echo "信息:清理 Onecloud 临时文件..."
sudo rm -f "$TMPDIR/6.boot.PARTITION.sparse" "$TMPDIR/7.rootfs.PARTITION.sparse" "$TMPDIR/dts.img"
echo "信息Onecloud burn 镜像打包完成: $OUTPUTDIR/$target_img_name"
}
# --- 设备特定的 Rootfs 准备函数 ---
onecloud_rootfs() {
local unpacker="$SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64"
local source_image="$SRCPATH/image/onecloud/Armbian_by-SilentWind_24.5.0-trunk_Onecloud_bookworm_legacy_5.9.0-rc7_minimal.burn.img"
local bootfs_img="$TMPDIR/bootfs.img"
local rootfs_img="$TMPDIR/rootfs.img"
local bootfs_sparse="$TMPDIR/6.boot.PARTITION.sparse"
local rootfs_sparse="$TMPDIR/7.rootfs.PARTITION.sparse"
local bootfs_loopdev="" # 存储 bootfs 使用的 loop 设备
local add_size_mb=400
echo "信息:准备 Onecloud Rootfs..."
ensure_dir "$TMPDIR"
ensure_dir "$BOOTFS"
echo "信息:解包 Onecloud burn 镜像..."
sudo "$unpacker" unpack "$source_image" "$TMPDIR" || { echo "错误:解包失败" >&2; exit 1; }
# ... (unpacker logic) ...
echo "信息:转换 bootfs 和 rootfs sparse 镜像到 raw 格式..."
sudo simg2img "$bootfs_sparse" "$bootfs_img" || { echo "错误:转换 bootfs sparse 镜像失败" >&2; exit 1; }
sudo simg2img "$rootfs_sparse" "$rootfs_img" || { echo "错误:转换 rootfs sparse 镜像失败" >&2; exit 1; }
echo "信息:挂载 bootfs 并修复 DTB..."
find_loop_device # 查找一个 loop 设备给 bootfs
bootfs_loopdev="$LOOPDEV" # 保存这个设备名
echo "信息:将 $bootfs_img 关联到 $bootfs_loopdev..."
sudo losetup "$bootfs_loopdev" "$bootfs_img" || { echo "错误:关联 bootfs 镜像到 $bootfs_loopdev 失败" >&2; exit 1; }
sudo mount "$bootfs_loopdev" "$BOOTFS" || { echo "错误:挂载 bootfs ($bootfs_loopdev) 失败" >&2; exit 1; }
BOOTFS_MOUNTED=1
sudo cp "$SRCPATH/image/onecloud/meson8b-onecloud-fix.dtb" "$BOOTFS/dtb/meson8b-onecloud.dtb" || { echo "错误:复制修复后的 DTB 文件失败" >&2; exit 1; }
sudo umount "$BOOTFS" || { echo "警告:卸载 bootfs ($BOOTFS) 失败" >&2; BOOTFS_MOUNTED=0; } # 卸载失败不应中断流程
BOOTFS_MOUNTED=0
echo "信息:分离 bootfs loop 设备 $bootfs_loopdev..."
sudo losetup -d "$bootfs_loopdev" || { echo "警告:分离 bootfs loop 设备 $bootfs_loopdev 失败" >&2; }
# bootfs_loopdev 对应的设备现在是空闲的
echo "信息:扩展 rootfs 镜像 (${add_size_mb}MB)..."
sudo dd if=/dev/zero bs=1M count="$add_size_mb" >> "$rootfs_img" || { echo "错误:扩展 rootfs 镜像失败" >&2; exit 1; }
echo "信息:检查并调整 rootfs 文件系统大小 (在文件上)..."
# 注意e2fsck/resize2fs 现在直接操作镜像文件,而不是 loop 设备
sudo e2fsck -f -y "$rootfs_img" || { echo "警告e2fsck 检查 rootfs 镜像文件失败" >&2; exit 1; }
sudo resize2fs "$rootfs_img" || { echo "错误resize2fs 调整 rootfs 镜像文件大小失败" >&2; exit 1; }
echo "信息:设置 rootfs loop 设备..."
find_loop_device # 重新查找一个可用的 loop 设备 (可能是刚才释放的那个)
echo "信息:将 $rootfs_img 关联到 $LOOPDEV..."
sudo losetup "$LOOPDEV" "$rootfs_img" || { echo "错误:关联 rootfs 镜像到 $LOOPDEV 失败" >&2; exit 1; }
echo "信息Onecloud Rootfs 准备完成。 Loop 设备 $LOOPDEV 已关联 $rootfs_img"
}
cumebox2_rootfs() {
local source_image="$SRCPATH/image/cumebox2/Armbian_25.2.2_Khadas-vim1_bookworm_current_6.12.17_minimal.img"
local target_image="$TMPDIR/rootfs.img"
local offset=$((8192 * 512))
echo "信息:准备 Cumebox2 Rootfs..."
ensure_dir "$TMPDIR"
cp "$source_image" "$target_image" || { echo "错误:复制 Cumebox2 原始镜像失败" >&2; exit 1; }
echo "信息:调整镜像分区大小..."
sudo parted -s "$target_image" resizepart 1 100% || { echo "错误:使用 parted 调整分区大小失败" >&2; exit 1; }
echo "信息:设置带偏移量的 loop 设备..."
find_loop_device # 查找设备名
echo "信息:将 $target_image (偏移 $offset) 关联到 $LOOPDEV..."
sudo losetup --offset "$offset" "$LOOPDEV" "$target_image" || { echo "错误:设置带偏移量的 loop 设备 $LOOPDEV 失败" >&2; exit 1; }
echo "信息:检查并调整文件系统大小 (在 loop 设备上)..."
sudo e2fsck -f -y "$LOOPDEV" || { echo "警告e2fsck 检查 $LOOPDEV 失败" >&2; exit 1; }
sudo resize2fs "$LOOPDEV" || { echo "错误resize2fs 调整 $LOOPDEV 大小失败" >&2; exit 1; }
echo "信息Cumebox2 Rootfs 准备完成loop 设备 $LOOPDEV 已就绪。"
}
chainedbox_rootfs_and_fix_dtb() {
local source_image="$SRCPATH/image/chainedbox/Armbian_24.11.0_rockchip_chainedbox_bookworm_6.1.112_server_2024.10.02_add800m.img"
local target_image="$TMPDIR/rootfs.img"
local boot_offset=$((32768 * 512))
local rootfs_offset=$((1081344 * 512))
local bootfs_loopdev=""
echo "信息:准备 Chainedbox Rootfs 并修复 DTB..."
ensure_dir "$TMPDIR"; ensure_dir "$BOOTFS"
cp "$source_image" "$target_image" || { echo "错误:复制 Chainedbox 原始镜像失败" >&2; exit 1; }
echo "信息:挂载 boot 分区并修复 DTB..."
find_loop_device # 找 loop 给 boot
bootfs_loopdev="$LOOPDEV"
echo "信息:将 $target_image (偏移 $boot_offset) 关联到 $bootfs_loopdev..."
sudo losetup --offset "$boot_offset" "$bootfs_loopdev" "$target_image" || { echo "错误:设置 boot 分区 loop 设备 $bootfs_loopdev 失败" >&2; exit 1; }
sudo mount "$bootfs_loopdev" "$BOOTFS" || { echo "错误:挂载 boot 分区 ($bootfs_loopdev) 失败" >&2; exit 1; }
BOOTFS_MOUNTED=1
sudo cp "$SRCPATH/image/chainedbox/rk3328-l1pro-1296mhz-fix.dtb" "$BOOTFS/dtb/rockchip/rk3328-l1pro-1296mhz.dtb" || { echo "错误:复制修复后的 DTB 文件失败" >&2; exit 1; }
sudo umount "$BOOTFS" || { echo "警告:卸载 boot 分区 ($BOOTFS) 失败" >&2; BOOTFS_MOUNTED=0; }
BOOTFS_MOUNTED=0
echo "信息:分离 boot loop 设备 $bootfs_loopdev..."
sudo losetup -d "$bootfs_loopdev" || { echo "警告:分离 boot 分区 loop 设备 $bootfs_loopdev 失败" >&2; }
echo "信息:设置 rootfs 分区的 loop 设备..."
find_loop_device # 找 loop 给 rootfs
echo "信息:将 $target_image (偏移 $rootfs_offset) 关联到 $LOOPDEV..."
sudo losetup --offset "$rootfs_offset" "$LOOPDEV" "$target_image" || { echo "错误:设置 rootfs 分区 loop 设备 $LOOPDEV 失败" >&2; exit 1; }
# 如果需要 resize rootfs 分区,可以在这里操作 $LOOPDEV
# echo "信息:检查并调整文件系统大小 (在 loop 设备上)..."
# sudo e2fsck -f -y "$LOOPDEV" || { echo "警告e2fsck 检查 $LOOPDEV 失败" >&2; exit 1; }
# sudo resize2fs "$LOOPDEV" || { echo "错误resize2fs 调整 $LOOPDEV 大小失败" >&2; exit 1; }
echo "信息Chainedbox Rootfs 准备完成loop 设备 $LOOPDEV 已就绪。"
}
vm_rootfs() {
local source_image="$SRCPATH/image/vm/Armbian_25.2.1_Uefi-x86_bookworm_current_6.12.13_minimal.img"
local target_image="$TMPDIR/rootfs.img"
local offset=$((540672 * 512))
echo "信息:准备 Vm Rootfs..."
ensure_dir "$TMPDIR"
cp "$source_image" "$target_image" || { echo "错误:复制 Vm 原始镜像失败" >&2; exit 1; }
echo "信息:设置带偏移量的 loop 设备..."
find_loop_device # 查找设备名
echo "信息:将 $target_image (偏移 $offset) 关联到 $LOOPDEV..."
sudo losetup --offset "$offset" "$LOOPDEV" "$target_image" || { echo "错误:设置带偏移量的 loop 设备 $LOOPDEV 失败" >&2; exit 1; }
# Optional resize on $LOOPDEV here if needed
echo "信息Vm Rootfs 准备完成loop 设备 $LOOPDEV 已就绪。"
}
e900v22c_rootfs() {
local source_image="$SRCPATH/image/e900v22c/Armbian_23.08.0_amlogic_s905l3a_bookworm_5.15.123_server_2023.08.01.img"
local target_image="$TMPDIR/rootfs.img"
local offset=$((532480 * 512))
local add_size_mb=400
echo "信息:准备 E900V22C Rootfs..."
ensure_dir "$TMPDIR"
cp "$source_image" "$target_image" || { echo "错误:复制 E900V22C 原始镜像失败" >&2; exit 1; }
echo "信息:扩展镜像文件 (${add_size_mb}MB)..."
sudo dd if=/dev/zero bs=1M count="$add_size_mb" >> "$target_image" || { echo "错误:扩展镜像文件失败" >&2; exit 1; }
echo "信息:调整镜像分区大小 (分区 2)..."
sudo parted -s "$target_image" resizepart 2 100% || { echo "错误:使用 parted 调整分区 2 大小失败" >&2; exit 1; }
echo "信息:设置带偏移量的 loop 设备..."
find_loop_device # 查找设备名
echo "信息:将 $target_image (偏移 $offset) 关联到 $LOOPDEV..."
sudo losetup --offset "$offset" "$LOOPDEV" "$target_image" || { echo "错误:设置带偏移量的 loop 设备 $LOOPDEV 失败" >&2; exit 1; }
echo "信息:检查并调整文件系统大小 (在 loop 设备上)..."
sudo e2fsck -f -y "$LOOPDEV" || { echo "警告e2fsck 检查 $LOOPDEV 失败" >&2; exit 1; }
sudo resize2fs "$LOOPDEV" || { echo "错误resize2fs 调整 $LOOPDEV 大小失败" >&2; exit 1; }
echo "信息E900V22C Rootfs 准备完成loop 设备 $LOOPDEV 已就绪。"
}
octopus_flanet_rootfs() {
local source_image="$SRCPATH/image/octopus-flanet/Armbian_24.11.0_amlogic_s912_bookworm_6.1.114_server_2024.11.01.img"
local target_image="$TMPDIR/rootfs.img"
local boot_offset=$((8192 * 512))
local rootfs_offset=$((1056768 * 512))
local add_size_mb=400
local bootfs_loopdev=""
echo "信息:准备 Octopus-Planet Rootfs..."
ensure_dir "$TMPDIR"; ensure_dir "$BOOTFS"
cp "$source_image" "$target_image" || { echo "错误:复制 Octopus-Planet 原始镜像失败" >&2; exit 1; }
echo "信息:挂载 boot 分区并修改 uEnv.txt (使用 VIM2 DTB)..."
find_loop_device # 找 loop 给 boot
bootfs_loopdev="$LOOPDEV"
echo "信息:将 $target_image (偏移 $boot_offset) 关联到 $bootfs_loopdev..."
sudo losetup --offset "$boot_offset" "$bootfs_loopdev" "$target_image" || { echo "错误:设置 boot 分区 loop 设备 $bootfs_loopdev 失败" >&2; exit 1; }
sudo mount "$bootfs_loopdev" "$BOOTFS" || { echo "错误:挂载 boot 分区 ($bootfs_loopdev) 失败" >&2; exit 1; }
BOOTFS_MOUNTED=1
sudo sed -i "s/meson-gxm-octopus-planet.dtb/meson-gxm-khadas-vim2.dtb/g" "$BOOTFS/uEnv.txt" || { echo "错误:修改 uEnv.txt 失败" >&2; exit 1; }
sudo umount "$BOOTFS" || { echo "警告:卸载 boot 分区 ($BOOTFS) 失败" >&2; BOOTFS_MOUNTED=0; }
BOOTFS_MOUNTED=0
echo "信息:分离 boot loop 设备 $bootfs_loopdev..."
sudo losetup -d "$bootfs_loopdev" || { echo "警告:分离 boot 分区 loop 设备 $bootfs_loopdev 失败" >&2; }
echo "信息:扩展镜像文件 (${add_size_mb}MB)..."
# ... (dd, cat, rm add.img logic) ...
echo "信息:调整镜像分区大小 (分区 2)..."
sudo parted -s "$target_image" resizepart 2 100% || { echo "错误:使用 parted 调整分区 2 大小失败" >&2; exit 1; }
echo "信息:设置 rootfs 分区的 loop 设备..."
find_loop_device # 找 loop 给 rootfs
echo "信息:将 $target_image (偏移 $rootfs_offset) 关联到 $LOOPDEV..."
sudo losetup --offset "$rootfs_offset" "$LOOPDEV" "$target_image" || { echo "错误:设置 rootfs 分区 loop 设备 $LOOPDEV 失败" >&2; exit 1; }
echo "信息:检查并调整文件系统大小 (在 loop 设备上)..."
sudo e2fsck -f -y "$LOOPDEV" || { echo "警告e2fsck 检查 $LOOPDEV 失败" >&2; exit 1; }
sudo resize2fs "$LOOPDEV" || { echo "错误resize2fs 调整 $LOOPDEV 大小失败" >&2; exit 1; }
echo "信息Octopus-Planet Rootfs 准备完成loop 设备 $LOOPDEV 已就绪。"
}
# --- 特定设备的文件配置函数 ---
config_cumebox2_files() {
echo "信息:为 Cumebox2 配置特定文件 (OLED, DTB)..."
ensure_dir "$ROOTFS/etc/oled"
# 注意 DTB 路径可能需要根据实际 Armbian 版本调整
sudo cp "$SRCPATH/image/cumebox2/v-fix.dtb" "$ROOTFS/boot/dtb/amlogic/meson-gxl-s905x-khadas-vim.dtb" || echo "警告:复制 Cumebox2 DTB 失败"
sudo cp "$SRCPATH/image/cumebox2/ssd" "$ROOTFS/usr/bin/" || echo "警告:复制 Cumebox2 ssd 脚本失败"
sudo chmod +x "$ROOTFS/usr/bin/ssd" || echo "警告:设置 ssd 脚本执行权限失败"
sudo cp "$SRCPATH/image/cumebox2/config.json" "$ROOTFS/etc/oled/config.json" || echo "警告:复制 OLED 配置文件失败"
}
config_octopus_flanet_files() {
echo "信息:为 Octopus-Planet 配置特定文件 (model_database.conf)..."
sudo cp "$SRCPATH/image/octopus-flanet/model_database.conf" "$ROOTFS/etc/model_database.conf" || echo "警告:复制 model_database.conf 失败"
}
# --- 构建流程函数 ---
build_target() {
@ -878,7 +137,6 @@ build_target() {
echo "=================================================="
}
# --- 主逻辑 ---
# 检查是否提供了目标参数
@ -892,21 +150,7 @@ fi
set -eo pipefail
# 检查必要的外部工具
for cmd in sudo docker losetup mount umount parted e2fsck resize2fs qemu-img curl tar python3 pip3 rsync git simg2img img2simg dd cat rm mkdir mv cp sed chmod chown ln grep printf id; do
if ! command -v "$cmd" &> /dev/null; then
echo "错误:必需的命令 '$cmd' 未找到。请安装相应软件包。" >&2
exit 1
fi
done
# 检查特定工具 (如果脚本中使用了)
if ! command -v "$SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64" &> /dev/null && [[ "$1" == "onecloud" || "$1" == "all" ]]; then
if [ -f "$SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64" ]; then
echo "信息:找到 AmlImg 工具,尝试设置执行权限..."
sudo chmod +x "$SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64" || echo "警告:设置 AmlImg 执行权限失败"
else
echo "错误:构建 onecloud 需要 '$SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64',但未找到。" >&2
fi
fi
check_required_tools "$1"
# 执行构建
if [ "$1" = "all" ]; then

195
build/functions/common.sh Executable file
View File

@ -0,0 +1,195 @@
#!/bin/bash
# --- 辅助函数 ---
# 获取 Git 提交 ID
get_git_commit_id() {
if git rev-parse --is-inside-work-tree &>/dev/null; then
git rev-parse --short HEAD 2>/dev/null || echo ""
else
echo ""
fi
}
# 查找并设置一个可用的 loop 设备
find_loop_device() {
echo "信息:查找可用的 loop 设备..."
# 只使用 --find 来获取设备名
LOOPDEV=$(sudo losetup --find)
if [[ -z "$LOOPDEV" || ! -e "$LOOPDEV" ]]; then
echo "错误:再次尝试后仍无法找到可用的 loop 设备。" >&2
exit 1
fi
echo "信息:找到可用 loop 设备名:$LOOPDEV"
}
# 检查并创建目录
ensure_dir() {
if [[ ! -d "$1" ]]; then
echo "信息:创建目录 $1 ..."
sudo mkdir -p "$1" || { echo "错误:创建目录 $1 失败" >&2; exit 1; }
fi
}
# 执行 chroot 命令
run_in_chroot() {
echo "信息:在 chroot 环境 ($ROOTFS) 中执行命令..."
sudo chroot --userspec "root:root" "$ROOTFS" bash -ec "$1" || { echo "错误:在 chroot 环境中执行命令失败" >&2; exit 1; }
echo "信息chroot 命令执行完成。"
}
# --- 清理函数 ---
cleanup() {
echo "信息:执行清理操作..."
# 尝试卸载 chroot 环境下的挂载点
if [[ "$DEV_MOUNTED" -eq 1 ]]; then
echo "信息:卸载 $ROOTFS/dev ..."
sudo umount "$ROOTFS/dev" || echo "警告:卸载 $ROOTFS/dev 失败,可能已被卸载"
DEV_MOUNTED=0
fi
if [[ "$SYS_MOUNTED" -eq 1 ]]; then
echo "信息:卸载 $ROOTFS/sys ..."
sudo umount "$ROOTFS/sys" || echo "警告:卸载 $ROOTFS/sys 失败,可能已被卸载"
SYS_MOUNTED=0
fi
if [[ "$PROC_MOUNTED" -eq 1 ]]; then
echo "信息:卸载 $ROOTFS/proc ..."
sudo umount "$ROOTFS/proc" || echo "警告:卸载 $ROOTFS/proc 失败,可能已被卸载"
PROC_MOUNTED=0
fi
# 尝试卸载主根文件系统
if [[ "$ROOTFS_MOUNTED" -eq 1 && -d "$ROOTFS" ]]; then
echo "信息:卸载 $ROOTFS ..."
sudo umount "$ROOTFS" || sudo umount -l "$ROOTFS" || echo "警告:卸载 $ROOTFS 失败"
ROOTFS_MOUNTED=0
fi
# 尝试卸载引导文件系统 (如果使用)
if [[ "$BOOTFS_MOUNTED" -eq 1 && -d "$BOOTFS" ]]; then
echo "信息:卸载 $BOOTFS ..."
sudo umount "$BOOTFS" || sudo umount -l "$BOOTFS" || echo "警告:卸载 $BOOTFS 失败"
BOOTFS_MOUNTED=0
fi
# 尝试分离 loop 设备
if [[ -n "$LOOPDEV" && -b "$LOOPDEV" ]]; then
echo "信息:尝试 zerofree $LOOPDEV ..."
sudo zerofree "$LOOPDEV" || echo "警告zerofree $LOOPDEV 失败,可能文件系统不支持或未干净卸载"
echo "信息:分离 loop 设备 $LOOPDEV ..."
sudo losetup -d "$LOOPDEV" || echo "警告:分离 $LOOPDEV 失败"
LOOPDEV=""
fi
# 尝试删除 Docker 容器
echo "信息:检查并删除 Docker 容器 $DOCKER_CONTAINER_NAME ..."
if sudo docker ps -a --format '{{.Names}}' | grep -q "^${DOCKER_CONTAINER_NAME}$"; then
sudo docker rm -f "$DOCKER_CONTAINER_NAME" || echo "警告:删除 Docker 容器 $DOCKER_CONTAINER_NAME 失败"
else
echo "信息Docker 容器 $DOCKER_CONTAINER_NAME 不存在或已被删除。"
fi
# 清理临时目录和挂载点目录
echo "信息:清理临时文件和目录..."
sudo rm -rf "$PREBUILT_DIR"
# 只删除挂载点目录本身
if [[ -d "$ROOTFS" ]]; then
sudo rmdir "$ROOTFS" || echo "警告:删除目录 $ROOTFS 失败,可能非空"
fi
if [[ -d "$BOOTFS" ]]; then
sudo rmdir "$BOOTFS" || echo "警告:删除目录 $BOOTFS 失败,可能非空"
fi
echo "信息:清理完成。"
}
# 在打包镜像前调用此函数确保干净卸载所有挂载点和loop设备
unmount_all() {
echo "信息:执行卸载操作,准备打包..."
# 卸载 chroot 环境下的挂载点
if [[ "$DEV_MOUNTED" -eq 1 ]]; then
echo "信息:卸载 $ROOTFS/dev ..."
sudo umount "$ROOTFS/dev" || echo "警告:卸载 $ROOTFS/dev 失败,可能已被卸载"
DEV_MOUNTED=0
fi
if [[ "$SYS_MOUNTED" -eq 1 ]]; then
echo "信息:卸载 $ROOTFS/sys ..."
sudo umount "$ROOTFS/sys" || echo "警告:卸载 $ROOTFS/sys 失败,可能已被卸载"
SYS_MOUNTED=0
fi
if [[ "$PROC_MOUNTED" -eq 1 ]]; then
echo "信息:卸载 $ROOTFS/proc ..."
sudo umount "$ROOTFS/proc" || echo "警告:卸载 $ROOTFS/proc 失败,可能已被卸载"
PROC_MOUNTED=0
fi
# 卸载主根文件系统
if [[ "$ROOTFS_MOUNTED" -eq 1 && -d "$ROOTFS" ]]; then
echo "信息:卸载 $ROOTFS ..."
sudo umount "$ROOTFS" || sudo umount -l "$ROOTFS" || echo "警告:卸载 $ROOTFS 失败"
ROOTFS_MOUNTED=0
fi
# 尝试分离 loop 设备前执行 zerofree如果文件系统支持
if [[ -n "$LOOPDEV" && -b "$LOOPDEV" ]]; then
echo "信息:尝试 zerofree $LOOPDEV ..."
sudo zerofree "$LOOPDEV" || echo "警告zerofree $LOOPDEV 失败,可能文件系统不支持或未干净卸载"
echo "信息:分离 loop 设备 $LOOPDEV ..."
sudo losetup -d "$LOOPDEV" || echo "警告:分离 $LOOPDEV 失败"
LOOPDEV=""
fi
sudo rm -rf "$PREBUILT_DIR"
echo "信息:卸载操作完成,可以安全打包镜像。"
}
# 挂载根文件系统
mount_rootfs() {
echo "信息:挂载根文件系统到 $ROOTFS ..."
ensure_dir "$ROOTFS"
sudo mount "$LOOPDEV" "$ROOTFS" || { echo "错误:挂载 $LOOPDEV$ROOTFS 失败" >&2; exit 1; }
ROOTFS_MOUNTED=1
echo "信息:挂载 proc, sys, dev 到 chroot 环境..."
ensure_dir "$ROOTFS/proc"
sudo mount -t proc proc "$ROOTFS/proc" || { echo "错误:挂载 proc 到 $ROOTFS/proc 失败" >&2; exit 1; }
PROC_MOUNTED=1
ensure_dir "$ROOTFS/sys"
sudo mount -t sysfs sys "$ROOTFS/sys" || { echo "错误:挂载 sys 到 $ROOTFS/sys 失败" >&2; exit 1; }
SYS_MOUNTED=1
ensure_dir "$ROOTFS/dev"
sudo mount -o bind /dev "$ROOTFS/dev" || { echo "错误:绑定挂载 /dev 到 $ROOTFS/dev 失败" >&2; exit 1; }
DEV_MOUNTED=1
echo "信息:根文件系统及虚拟文件系统挂载完成。"
}
# 设置元数据
write_meta() {
local hostname="$1"
echo "信息:在 chroot 环境中设置主机名/元数据为 $hostname ..."
run_in_chroot "sed -i 's/localhost.localdomain/$hostname/g' /etc/kvmd/meta.yaml"
}
# 检查必要的外部工具
check_required_tools() {
local required_tools="sudo docker losetup mount umount parted e2fsck resize2fs qemu-img curl tar python3 pip3 rsync git simg2img img2simg dd cat rm mkdir mv cp sed chmod chown ln grep printf id"
for cmd in $required_tools; do
if ! command -v "$cmd" &> /dev/null; then
echo "错误:必需的命令 '$cmd' 未找到。请安装相应软件包。" >&2
exit 1
fi
done
# 检查特定工具 (如果脚本中使用了)
if ! command -v "$SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64" &> /dev/null && [[ "$1" == "onecloud" || "$1" == "all" ]]; then
if [ -f "$SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64" ]; then
echo "信息:找到 AmlImg 工具,尝试设置执行权限..."
sudo chmod +x "$SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64" || echo "警告:设置 AmlImg 执行权限失败"
else
echo "错误:构建 onecloud 需要 '$SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64',但未找到。" >&2
fi
fi
}

212
build/functions/devices.sh Executable file
View File

@ -0,0 +1,212 @@
#!/bin/bash
# --- 设备特定的 Rootfs 准备函数 ---
onecloud_rootfs() {
local unpacker="$SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64"
local source_image="$SRCPATH/image/onecloud/Armbian_by-SilentWind_24.5.0-trunk_Onecloud_bookworm_legacy_5.9.0-rc7_minimal.burn.img"
local bootfs_img="$TMPDIR/bootfs.img"
local rootfs_img="$TMPDIR/rootfs.img"
local bootfs_sparse="$TMPDIR/6.boot.PARTITION.sparse"
local rootfs_sparse="$TMPDIR/7.rootfs.PARTITION.sparse"
local bootfs_loopdev="" # 存储 bootfs 使用的 loop 设备
local add_size_mb=400
echo "信息:准备 Onecloud Rootfs..."
ensure_dir "$TMPDIR"
ensure_dir "$BOOTFS"
echo "信息:解包 Onecloud burn 镜像..."
sudo "$unpacker" unpack "$source_image" "$TMPDIR" || { echo "错误:解包失败" >&2; exit 1; }
echo "信息:转换 bootfs 和 rootfs sparse 镜像到 raw 格式..."
sudo simg2img "$bootfs_sparse" "$bootfs_img" || { echo "错误:转换 bootfs sparse 镜像失败" >&2; exit 1; }
sudo simg2img "$rootfs_sparse" "$rootfs_img" || { echo "错误:转换 rootfs sparse 镜像失败" >&2; exit 1; }
echo "信息:挂载 bootfs 并修复 DTB..."
find_loop_device # 查找一个 loop 设备给 bootfs
bootfs_loopdev="$LOOPDEV" # 保存这个设备名
echo "信息:将 $bootfs_img 关联到 $bootfs_loopdev..."
sudo losetup "$bootfs_loopdev" "$bootfs_img" || { echo "错误:关联 bootfs 镜像到 $bootfs_loopdev 失败" >&2; exit 1; }
sudo mount "$bootfs_loopdev" "$BOOTFS" || { echo "错误:挂载 bootfs ($bootfs_loopdev) 失败" >&2; exit 1; }
BOOTFS_MOUNTED=1
sudo cp "$SRCPATH/image/onecloud/meson8b-onecloud-fix.dtb" "$BOOTFS/dtb/meson8b-onecloud.dtb" || { echo "错误:复制修复后的 DTB 文件失败" >&2; exit 1; }
sudo umount "$BOOTFS" || { echo "警告:卸载 bootfs ($BOOTFS) 失败" >&2; BOOTFS_MOUNTED=0; } # 卸载失败不应中断流程
BOOTFS_MOUNTED=0
echo "信息:分离 bootfs loop 设备 $bootfs_loopdev..."
sudo losetup -d "$bootfs_loopdev" || { echo "警告:分离 bootfs loop 设备 $bootfs_loopdev 失败" >&2; }
# bootfs_loopdev 对应的设备现在是空闲的
echo "信息:扩展 rootfs 镜像 (${add_size_mb}MB)..."
sudo dd if=/dev/zero bs=1M count="$add_size_mb" >> "$rootfs_img" || { echo "错误:扩展 rootfs 镜像失败" >&2; exit 1; }
echo "信息:检查并调整 rootfs 文件系统大小 (在文件上)..."
# 注意e2fsck/resize2fs 现在直接操作镜像文件,而不是 loop 设备
sudo e2fsck -f -y "$rootfs_img" || { echo "警告e2fsck 检查 rootfs 镜像文件失败" >&2; exit 1; }
sudo resize2fs "$rootfs_img" || { echo "错误resize2fs 调整 rootfs 镜像文件大小失败" >&2; exit 1; }
echo "信息:设置 rootfs loop 设备..."
find_loop_device # 重新查找一个可用的 loop 设备 (可能是刚才释放的那个)
echo "信息:将 $rootfs_img 关联到 $LOOPDEV..."
sudo losetup "$LOOPDEV" "$rootfs_img" || { echo "错误:关联 rootfs 镜像到 $LOOPDEV 失败" >&2; exit 1; }
echo "信息Onecloud Rootfs 准备完成。 Loop 设备 $LOOPDEV 已关联 $rootfs_img"
}
cumebox2_rootfs() {
local source_image="$SRCPATH/image/cumebox2/Armbian_25.2.2_Khadas-vim1_bookworm_current_6.12.17_minimal.img"
local target_image="$TMPDIR/rootfs.img"
local offset=$((8192 * 512))
echo "信息:准备 Cumebox2 Rootfs..."
ensure_dir "$TMPDIR"
cp "$source_image" "$target_image" || { echo "错误:复制 Cumebox2 原始镜像失败" >&2; exit 1; }
echo "信息:调整镜像分区大小..."
sudo parted -s "$target_image" resizepart 1 100% || { echo "错误:使用 parted 调整分区大小失败" >&2; exit 1; }
echo "信息:设置带偏移量的 loop 设备..."
find_loop_device # 查找设备名
echo "信息:将 $target_image (偏移 $offset) 关联到 $LOOPDEV..."
sudo losetup --offset "$offset" "$LOOPDEV" "$target_image" || { echo "错误:设置带偏移量的 loop 设备 $LOOPDEV 失败" >&2; exit 1; }
echo "信息:检查并调整文件系统大小 (在 loop 设备上)..."
sudo e2fsck -f -y "$LOOPDEV" || { echo "警告e2fsck 检查 $LOOPDEV 失败" >&2; exit 1; }
sudo resize2fs "$LOOPDEV" || { echo "错误resize2fs 调整 $LOOPDEV 大小失败" >&2; exit 1; }
echo "信息Cumebox2 Rootfs 准备完成loop 设备 $LOOPDEV 已就绪。"
}
chainedbox_rootfs_and_fix_dtb() {
local source_image="$SRCPATH/image/chainedbox/Armbian_24.11.0_rockchip_chainedbox_bookworm_6.1.112_server_2024.10.02_add800m.img"
local target_image="$TMPDIR/rootfs.img"
local boot_offset=$((32768 * 512))
local rootfs_offset=$((1081344 * 512))
local bootfs_loopdev=""
echo "信息:准备 Chainedbox Rootfs 并修复 DTB..."
ensure_dir "$TMPDIR"; ensure_dir "$BOOTFS"
cp "$source_image" "$target_image" || { echo "错误:复制 Chainedbox 原始镜像失败" >&2; exit 1; }
echo "信息:挂载 boot 分区并修复 DTB..."
find_loop_device # 找 loop 给 boot
bootfs_loopdev="$LOOPDEV"
echo "信息:将 $target_image (偏移 $boot_offset) 关联到 $bootfs_loopdev..."
sudo losetup --offset "$boot_offset" "$bootfs_loopdev" "$target_image" || { echo "错误:设置 boot 分区 loop 设备 $bootfs_loopdev 失败" >&2; exit 1; }
sudo mount "$bootfs_loopdev" "$BOOTFS" || { echo "错误:挂载 boot 分区 ($bootfs_loopdev) 失败" >&2; exit 1; }
BOOTFS_MOUNTED=1
sudo cp "$SRCPATH/image/chainedbox/rk3328-l1pro-1296mhz-fix.dtb" "$BOOTFS/dtb/rockchip/rk3328-l1pro-1296mhz.dtb" || { echo "错误:复制修复后的 DTB 文件失败" >&2; exit 1; }
sudo umount "$BOOTFS" || { echo "警告:卸载 boot 分区 ($BOOTFS) 失败" >&2; BOOTFS_MOUNTED=0; }
BOOTFS_MOUNTED=0
echo "信息:分离 boot loop 设备 $bootfs_loopdev..."
sudo losetup -d "$bootfs_loopdev" || { echo "警告:分离 boot 分区 loop 设备 $bootfs_loopdev 失败" >&2; }
echo "信息:设置 rootfs 分区的 loop 设备..."
find_loop_device # 找 loop 给 rootfs
echo "信息:将 $target_image (偏移 $rootfs_offset) 关联到 $LOOPDEV..."
sudo losetup --offset "$rootfs_offset" "$LOOPDEV" "$target_image" || { echo "错误:设置 rootfs 分区 loop 设备 $LOOPDEV 失败" >&2; exit 1; }
echo "信息Chainedbox Rootfs 准备完成loop 设备 $LOOPDEV 已就绪。"
}
vm_rootfs() {
local source_image="$SRCPATH/image/vm/Armbian_25.2.1_Uefi-x86_bookworm_current_6.12.13_minimal.img"
local target_image="$TMPDIR/rootfs.img"
local offset=$((540672 * 512))
echo "信息:准备 Vm Rootfs..."
ensure_dir "$TMPDIR"
cp "$source_image" "$target_image" || { echo "错误:复制 Vm 原始镜像失败" >&2; exit 1; }
echo "信息:设置带偏移量的 loop 设备..."
find_loop_device # 查找设备名
echo "信息:将 $target_image (偏移 $offset) 关联到 $LOOPDEV..."
sudo losetup --offset "$offset" "$LOOPDEV" "$target_image" || { echo "错误:设置带偏移量的 loop 设备 $LOOPDEV 失败" >&2; exit 1; }
echo "信息Vm Rootfs 准备完成loop 设备 $LOOPDEV 已就绪。"
}
e900v22c_rootfs() {
local source_image="$SRCPATH/image/e900v22c/Armbian_23.08.0_amlogic_s905l3a_bookworm_5.15.123_server_2023.08.01.img"
local target_image="$TMPDIR/rootfs.img"
local offset=$((532480 * 512))
local add_size_mb=400
echo "信息:准备 E900V22C Rootfs..."
ensure_dir "$TMPDIR"
cp "$source_image" "$target_image" || { echo "错误:复制 E900V22C 原始镜像失败" >&2; exit 1; }
echo "信息:扩展镜像文件 (${add_size_mb}MB)..."
sudo dd if=/dev/zero bs=1M count="$add_size_mb" >> "$target_image" || { echo "错误:扩展镜像文件失败" >&2; exit 1; }
echo "信息:调整镜像分区大小 (分区 2)..."
sudo parted -s "$target_image" resizepart 2 100% || { echo "错误:使用 parted 调整分区 2 大小失败" >&2; exit 1; }
echo "信息:设置带偏移量的 loop 设备..."
find_loop_device # 查找设备名
echo "信息:将 $target_image (偏移 $offset) 关联到 $LOOPDEV..."
sudo losetup --offset "$offset" "$LOOPDEV" "$target_image" || { echo "错误:设置带偏移量的 loop 设备 $LOOPDEV 失败" >&2; exit 1; }
echo "信息:检查并调整文件系统大小 (在 loop 设备上)..."
sudo e2fsck -f -y "$LOOPDEV" || { echo "警告e2fsck 检查 $LOOPDEV 失败" >&2; exit 1; }
sudo resize2fs "$LOOPDEV" || { echo "错误resize2fs 调整 $LOOPDEV 大小失败" >&2; exit 1; }
echo "信息E900V22C Rootfs 准备完成loop 设备 $LOOPDEV 已就绪。"
}
octopus_flanet_rootfs() {
local source_image="$SRCPATH/image/octopus-flanet/Armbian_24.11.0_amlogic_s912_bookworm_6.1.114_server_2024.11.01.img"
local target_image="$TMPDIR/rootfs.img"
local boot_offset=$((8192 * 512))
local rootfs_offset=$((1056768 * 512))
local add_size_mb=400
local bootfs_loopdev=""
echo "信息:准备 Octopus-Planet Rootfs..."
ensure_dir "$TMPDIR"; ensure_dir "$BOOTFS"
cp "$source_image" "$target_image" || { echo "错误:复制 Octopus-Planet 原始镜像失败" >&2; exit 1; }
echo "信息:挂载 boot 分区并修改 uEnv.txt (使用 VIM2 DTB)..."
find_loop_device # 找 loop 给 boot
bootfs_loopdev="$LOOPDEV"
echo "信息:将 $target_image (偏移 $boot_offset) 关联到 $bootfs_loopdev..."
sudo losetup --offset "$boot_offset" "$bootfs_loopdev" "$target_image" || { echo "错误:设置 boot 分区 loop 设备 $bootfs_loopdev 失败" >&2; exit 1; }
sudo mount "$bootfs_loopdev" "$BOOTFS" || { echo "错误:挂载 boot 分区 ($bootfs_loopdev) 失败" >&2; exit 1; }
BOOTFS_MOUNTED=1
sudo sed -i "s/meson-gxm-octopus-planet.dtb/meson-gxm-khadas-vim2.dtb/g" "$BOOTFS/uEnv.txt" || { echo "错误:修改 uEnv.txt 失败" >&2; exit 1; }
sudo umount "$BOOTFS" || { echo "警告:卸载 boot 分区 ($BOOTFS) 失败" >&2; BOOTFS_MOUNTED=0; }
BOOTFS_MOUNTED=0
echo "信息:分离 boot loop 设备 $bootfs_loopdev..."
sudo losetup -d "$bootfs_loopdev" || { echo "警告:分离 boot 分区 loop 设备 $bootfs_loopdev 失败" >&2; }
echo "信息:调整镜像分区大小 (分区 2)..."
sudo parted -s "$target_image" resizepart 2 100% || { echo "错误:使用 parted 调整分区 2 大小失败" >&2; exit 1; }
echo "信息:设置 rootfs 分区的 loop 设备..."
find_loop_device # 找 loop 给 rootfs
echo "信息:将 $target_image (偏移 $rootfs_offset) 关联到 $LOOPDEV..."
sudo losetup --offset "$rootfs_offset" "$LOOPDEV" "$target_image" || { echo "错误:设置 rootfs 分区 loop 设备 $LOOPDEV 失败" >&2; exit 1; }
echo "信息:检查并调整文件系统大小 (在 loop 设备上)..."
sudo e2fsck -f -y "$LOOPDEV" || { echo "警告e2fsck 检查 $LOOPDEV 失败" >&2; exit 1; }
sudo resize2fs "$LOOPDEV" || { echo "错误resize2fs 调整 $LOOPDEV 大小失败" >&2; exit 1; }
echo "信息Octopus-Planet Rootfs 准备完成loop 设备 $LOOPDEV 已就绪。"
}
# --- 特定设备的文件配置函数 ---
config_cumebox2_files() {
echo "信息:为 Cumebox2 配置特定文件 (OLED, DTB)..."
ensure_dir "$ROOTFS/etc/oled"
# 注意 DTB 路径可能需要根据实际 Armbian 版本调整
sudo cp "$SRCPATH/image/cumebox2/v-fix.dtb" "$ROOTFS/boot/dtb/amlogic/meson-gxl-s905x-khadas-vim.dtb" || echo "警告:复制 Cumebox2 DTB 失败"
sudo cp "$SRCPATH/image/cumebox2/ssd" "$ROOTFS/usr/bin/" || echo "警告:复制 Cumebox2 ssd 脚本失败"
sudo chmod +x "$ROOTFS/usr/bin/ssd" || echo "警告:设置 ssd 脚本执行权限失败"
sudo cp "$SRCPATH/image/cumebox2/config.json" "$ROOTFS/etc/oled/config.json" || echo "警告:复制 OLED 配置文件失败"
}
config_octopus_flanet_files() {
echo "信息:为 Octopus-Planet 配置特定文件 (model_database.conf)..."
sudo cp "$SRCPATH/image/octopus-flanet/model_database.conf" "$ROOTFS/etc/model_database.conf" || echo "警告:复制 model_database.conf 失败"
}

313
build/functions/install.sh Executable file
View File

@ -0,0 +1,313 @@
#!/bin/bash
# --- 预准备 ---
prepare_dns_and_mirrors() {
echo "信息:在 chroot 环境中准备 DNS 和更换软件源..."
run_in_chroot "
mkdir -p /run/systemd/resolve/ \\
&& touch /run/systemd/resolve/stub-resolv.conf \\
&& printf '%s\\n' 'nameserver 1.1.1.1' 'nameserver 1.0.0.1' > /etc/resolv.conf \\
&& echo '信息:尝试更换镜像源...' \\
&& bash <(curl -sSL https://gitee.com/SuperManito/LinuxMirrors/raw/main/ChangeMirrors.sh) \\
--source mirrors.tuna.tsinghua.edu.cn --upgrade-software false --web-protocol http || echo '警告:更换镜像源脚本执行失败,可能网络不通或脚本已更改'
"
}
delete_armbian_verify(){
echo "信息:在 chroot 环境中修改 Armbian 软件源..."
run_in_chroot "echo 'deb http://mirrors.ustc.edu.cn/armbian bullseye main bullseye-utils bullseye-desktop' > /etc/apt/sources.list.d/armbian.list"
}
prepare_external_binaries() {
local platform="$1" # linux/armhf or linux/amd64 or linux/aarch64
local docker_image="registry.cn-hangzhou.aliyuncs.com/silentwind/kvmd-stage-0"
echo "信息:准备外部预编译二进制文件 (平台: $platform)..."
ensure_dir "$PREBUILT_DIR"
echo "信息:拉取 Docker 镜像 $docker_image (平台: $platform)..."
sudo docker pull --platform "$platform" "$docker_image" || { echo "错误:拉取 Docker 镜像 $docker_image 失败" >&2; exit 1; }
echo "信息:创建 Docker 容器 $DOCKER_CONTAINER_NAME ..."
sudo docker create --name "$DOCKER_CONTAINER_NAME" "$docker_image" || { echo "错误:创建 Docker 容器 $DOCKER_CONTAINER_NAME 失败" >&2; exit 1; }
echo "信息:从 Docker 容器导出文件到 $PREBUILT_DIR ..."
sudo docker export "$DOCKER_CONTAINER_NAME" | sudo tar -xf - -C "$PREBUILT_DIR" || { echo "错误:导出并解压 Docker 容器内容失败" >&2; exit 1; }
echo "信息:预编译二进制文件准备完成,存放于 $PREBUILT_DIR"
# 删除 Docker 容器
sudo docker rm -f "$DOCKER_CONTAINER_NAME" || { echo "错误:删除 Docker 容器 $DOCKER_CONTAINER_NAME 失败" >&2; exit 1; }
}
config_base_files() {
local platform_id="$1" # e.g., "onecloud", "cumebox2"
echo "信息:配置基础文件和目录结构 ($platform_id)..."
echo "信息:创建 KVMD 相关目录..."
ensure_dir "$ROOTFS/etc/kvmd/override.d"
ensure_dir "$ROOTFS/etc/kvmd/vnc"
ensure_dir "$ROOTFS/var/lib/kvmd/msd/images"
ensure_dir "$ROOTFS/var/lib/kvmd/msd/meta"
ensure_dir "$ROOTFS/opt/vc/bin"
ensure_dir "$ROOTFS/usr/share/kvmd"
ensure_dir "$ROOTFS/One-KVM"
ensure_dir "$ROOTFS/usr/share/janus/javascript"
ensure_dir "$ROOTFS/usr/lib/ustreamer/janus"
ensure_dir "$ROOTFS/run/kvmd"
ensure_dir "$ROOTFS/tmp/wheel/"
ensure_dir "$ROOTFS/usr/lib/janus/transports/"
ensure_dir "$ROOTFS/usr/lib/janus/loggers"
echo "信息:复制 One-KVM 源码..."
sudo rsync -a --exclude={.git,.github,output,tmp} . "$ROOTFS/One-KVM/" || { echo "错误:复制 One-KVM 源码失败" >&2; exit 1; }
echo "信息:复制配置文件..."
sudo cp -r configs/kvmd/* configs/nginx configs/janus "$ROOTFS/etc/kvmd/"
sudo cp -r web extras contrib/keymaps "$ROOTFS/usr/share/kvmd/"
sudo cp testenv/fakes/vcgencmd "$ROOTFS/usr/bin/"
sudo cp -r testenv/js/* "$ROOTFS/usr/share/janus/javascript/"
sudo cp "build/platform/$platform_id" "$ROOTFS/usr/share/kvmd/platform" || { echo "错误:复制平台文件 build/platform/$platform_id 失败" >&2; exit 1; }
sudo cp scripts/kvmd-gencert scripts/kvmd-bootconfig scripts/kvmd-certbot scripts/kvmd-udev-hdmiusb-check scripts/kvmd-udev-restart-pass build/scripts/kvmd-firstrun.sh "$ROOTFS/usr/bin/"
sudo chmod +x "$ROOTFS/usr/bin/kvmd-gencert" "$ROOTFS/usr/bin/kvmd-bootconfig" "$ROOTFS/usr/bin/kvmd-certbot" "$ROOTFS/usr/bin/kvmd-udev-hdmiusb-check" "$ROOTFS/usr/bin/kvmd-udev-restart-pass" "$ROOTFS/usr/bin/kvmd-firstrun.sh"
if [ -f "$SRCPATH/image/$platform_id/rc.local" ]; then
echo "信息:复制设备特定的 rc.local 文件..."
sudo cp "$SRCPATH/image/$platform_id/rc.local" "$ROOTFS/etc/"
fi
echo "信息:从预编译目录复制二进制文件和库..."
sudo cp "$PREBUILT_DIR/tmp/lib/"* "$ROOTFS/lib/"*-linux-*/ || echo "警告:复制 /tmp/lib/* 失败,可能源目录或目标目录不存在或不匹配"
sudo cp "$PREBUILT_DIR/tmp/ustreamer/ustreamer" "$PREBUILT_DIR/tmp/ustreamer/ustreamer-dump" "$PREBUILT_DIR/usr/bin/janus" "$ROOTFS/usr/bin/" || { echo "错误:复制 ustreamer/janus 二进制文件失败" >&2; exit 1; }
sudo cp "$PREBUILT_DIR/tmp/ustreamer/janus/libjanus_ustreamer.so" "$ROOTFS/usr/lib/ustreamer/janus/" || { echo "错误:复制 libjanus_ustreamer.so 失败" >&2; exit 1; }
sudo cp "$PREBUILT_DIR/tmp/wheel/"*.whl "$ROOTFS/tmp/wheel/" || { echo "错误:复制 Python wheel 文件失败" >&2; exit 1; }
sudo cp "$PREBUILT_DIR/usr/lib/janus/transports/"* "$ROOTFS/usr/lib/janus/transports/" || { echo "错误:复制 Janus transports 失败" >&2; exit 1; }
# 禁用 apt-file
if [ -f "$ROOTFS/etc/apt/apt.conf.d/50apt-file.conf" ]; then
echo "信息:禁用 apt-file 配置..."
sudo mv "$ROOTFS/etc/apt/apt.conf.d/50apt-file.conf" "$ROOTFS/etc/apt/apt.conf.d/50apt-file.conf.disabled"
fi
echo "信息:基础文件配置完成。"
}
# --- KVMD 安装与配置 ---
install_base_packages() {
echo "信息:在 chroot 环境中更新源并安装基础软件包..."
run_in_chroot "
apt-get update && \\
apt install -y --no-install-recommends \\
libxkbcommon-x11-0 nginx tesseract-ocr tesseract-ocr-eng tesseract-ocr-chi-sim \\
iptables network-manager curl kmod libmicrohttpd12 libjansson4 libssl3 \\
libsofia-sip-ua0 libglib2.0-0 libopus0 libogg0 libcurl4 libconfig9 \\
python3-pip net-tools && \\
apt clean && \\
rm -rf /var/lib/apt/lists/*
"
}
configure_network() {
local network_type="$1" # "systemd-networkd" or others (default network-manager)
if [ "$network_type" = "systemd-networkd" ]; then
echo "信息:在 chroot 环境中配置 systemd-networkd..."
# 检查是否为onecloud平台如果是则使用随机MAC地址生成机制
if [ "$TARGET_DEVICE_NAME" = "onecloud" ]; then
echo "信息为onecloud平台配置随机MAC地址生成机制..."
# 复制MAC地址生成脚本
sudo cp "$SCRIPT_DIR/scripts/generate-random-mac.sh" "$ROOTFS/usr/local/bin/"
sudo chmod +x "$ROOTFS/usr/local/bin/generate-random-mac.sh"
# 复制systemd服务文件
sudo cp "$SCRIPT_DIR/services/kvmd-generate-mac.service" "$ROOTFS/etc/systemd/system/"
# 创建初始网络配置文件不包含MAC地址将由脚本生成
run_in_chroot "
echo -e '[Match]\\nName=eth0\\n\\n[Network]\\nDHCP=yes' > /etc/systemd/network/99-eth0.network && \\
systemctl mask NetworkManager && \\
systemctl unmask systemd-networkd && \\
systemctl enable systemd-networkd systemd-resolved && \\
systemctl enable kvmd-generate-mac.service
"
echo "信息onecloud随机MAC地址生成机制配置完成"
fi
else
echo "信息:使用默认的网络管理器 (NetworkManager)..."
# 可能需要确保 NetworkManager 是启用的 (通常默认是)
run_in_chroot "systemctl enable NetworkManager"
fi
}
install_python_deps() {
echo "信息:在 chroot 环境中安装 Python 依赖 (wheels)..."
run_in_chroot "
pip3 install --no-cache-dir --break-system-packages /tmp/wheel/*.whl && \\
pip3 cache purge && \\
rm -rf /tmp/wheel
"
}
configure_kvmd_core() {
echo "信息:在 chroot 环境中安装和配置 KVMD 核心..."
# 复制KVMD首次运行脚本和服务
echo "信息配置KVMD首次运行初始化服务..."
sudo cp "build/services/kvmd-firstrun.service" "$ROOTFS/etc/systemd/system/"
# 安装KVMD但不执行需要在首次运行时完成的操作
run_in_chroot "
cd /One-KVM && \\
python3 setup.py install && \\
systemctl enable kvmd-firstrun.service
"
echo "信息KVMD核心安装完成证书生成等初始化操作将在首次开机时执行"
}
configure_system() {
echo "信息:在 chroot 环境中配置系统级设置 (sudoers, udev, services)..."
run_in_chroot "
cat /One-KVM/configs/os/sudoers/v2-hdmiusb >> /etc/sudoers && \\
cat /One-KVM/configs/os/udev/v2-hdmiusb-rpi4.rules > /etc/udev/rules.d/99-kvmd.rules && \\
echo 'libcomposite' >> /etc/modules && \\
mv /usr/local/bin/kvmd* /usr/bin/ || echo '信息:/usr/local/bin/kvmd* 未找到或移动失败,可能已在/usr/bin' && \\
cp /One-KVM/configs/os/services/* /etc/systemd/system/ && \\
cp /One-KVM/configs/os/tmpfiles.conf /usr/lib/tmpfiles.d/ && \\
mv /etc/kvmd/supervisord.conf /etc/supervisord.conf && \\
chmod +x /etc/update-motd.d/* || echo '警告chmod /etc/update-motd.d/* 失败' && \\
echo 'kvmd ALL=(ALL) NOPASSWD: /etc/kvmd/custom_atx/gpio.sh' >> /etc/sudoers && \\
echo 'kvmd ALL=(ALL) NOPASSWD: /etc/kvmd/custom_atx/usbrelay_hid.sh' >> /etc/sudoers && \\
systemd-sysusers /One-KVM/configs/os/sysusers.conf && \\
systemd-sysusers /One-KVM/configs/os/kvmd-webterm.conf && \\
ln -sf /usr/share/tesseract-ocr/*/tessdata /usr/share/tessdata || echo '警告:创建 tesseract 链接失败' && \\
sed -i 's/8080/80/g' /etc/kvmd/override.yaml && \\
sed -i 's/4430/443/g' /etc/kvmd/override.yaml && \\
chown kvmd -R /var/lib/kvmd/msd/ && \\
systemctl enable kvmd kvmd-otg kvmd-nginx kvmd-vnc kvmd-ipmi kvmd-webterm kvmd-janus kvmd-media && \\
systemctl disable nginx && \\
rm -rf /One-KVM
"
}
install_webterm() {
local arch="$1" # armhf, aarch64, x86_64
local ttyd_arch="$arch"
if [ "$arch" = "armhf" ]; then
ttyd_arch="armv7"
elif [ "$arch" = "amd64" ]; then
ttyd_arch="x86_64" # ttyd 通常用 x86_64
fi
echo "信息:在 chroot 环境中下载并安装 ttyd ($ttyd_arch)..."
run_in_chroot "
curl -L https://gh.llkk.cc/https://github.com/tsl0922/ttyd/releases/download/1.7.7/ttyd.${ttyd_arch} -o /usr/bin/ttyd && \\
chmod +x /usr/bin/ttyd && \\
mkdir -p /home/kvmd-webterm && \\
chown kvmd-webterm /home/kvmd-webterm
"
}
apply_kvmd_tweaks() {
local arch="$1" # armhf, aarch64, x86_64
local device_type="$2" # "gpio" or "video1" or other
local atx_setting=""
local hid_setting=""
echo "信息:根据架构 ($arch) 和设备类型 ($device_type) 调整 KVMD 配置..."
if [ "$arch" = "x86_64" ] || [ "$arch" = "amd64" ]; then
echo "信息:目标平台为 x86_64/amd64 架构,禁用 OTG设置 ATX 为 USBRELAY_HID..."
run_in_chroot "
systemctl disable kvmd-otg && \\
sed -i 's/^ATX=.*/ATX=USBRELAY_HID/' /etc/kvmd/atx.sh && \\
sed -i 's/device: \/dev\/ttyUSB0/device: \/dev\/kvmd-hid/g' /etc/kvmd/override.yaml
"
else
echo "信息::目标平台为 ARM 架构 ($arch)..."
# ARM 架构,配置 HID 为 OTG
hid_setting="otg"
run_in_chroot "
sed -i 's/#type: otg/type: otg/g' /etc/kvmd/override.yaml && \\
sed -i 's/device: \/dev\/ttyUSB0/#device: \/dev\/ttyUSB0/g' /etc/kvmd/override.yaml # 注释掉 ttyUSB0
"
echo "信息:设置 HID 为 $hid_setting"
run_in_chroot "sed -i 's/type: ch9329/type: $hid_setting/g' /etc/kvmd/override.yaml"
# 根据 device_type 配置 ATX
if [ "$device_type" = "gpio" ]; then
echo "信息:电源控制设备类型为 gpio设置 ATX 为 GPIO 并配置引脚..."
atx_setting="GPIO"
run_in_chroot "
sed -i 's/^ATX=.*/ATX=GPIO/' /etc/kvmd/atx.sh && \\
sed -i 's/SHUTDOWNPIN/gpiochip1 7/g' /etc/kvmd/custom_atx/gpio.sh && \\
sed -i 's/REBOOTPIN/gpiochip0 11/g' /etc/kvmd/custom_atx/gpio.sh
"
else
echo "信息:电源控制设备类型不是 gpio ($device_type),设置 ATX 为 USBRELAY_HID..."
atx_setting="USBRELAY_HID"
run_in_chroot "sed -i 's/^ATX=.*/ATX=USBRELAY_HID/' /etc/kvmd/atx.sh"
fi
# 配置视频设备
if [ "$device_type" = "video1" ]; then
echo "信息:视频设备类型为 video1设置视频设备为 /dev/video1..."
run_in_chroot "sed -i 's|/dev/video0|/dev/video1|g' /etc/kvmd/override.yaml"
elif [ "$device_type" = "kvmd-video" ]; then
echo "信息:视频设备类型为 kvmd-video设置视频设备为 /dev/kvmd-video..."
run_in_chroot "sed -i 's|/dev/video0|/dev/kvmd-video|g' /etc/kvmd/override.yaml"
else
echo "信息:使用默认视频设备 /dev/video0..."
fi
fi
echo "信息KVMD 配置调整完成。"
}
# --- 整体安装流程 ---
install_and_configure_kvmd() {
local arch="$1" # 架构: armhf, aarch64, x86_64/amd64
local device_type="$2" # 设备特性: "gpio", "video1", "" (空或其他)
local network_type="$3" # 网络配置: "systemd-networkd", "" (默认 network-manager)
local host_arch="" # Docker 平台架构: arm, aarch64, amd64
# 映射架构名称
case "$arch" in
armhf) host_arch="arm" ;;
aarch64) host_arch="arm64" ;; # docker aarch64 平台名是 arm64
x86_64|amd64) host_arch="amd64"; arch="x86_64" ;; # 统一内部使用 x86_64
*) echo "错误:不支持的架构 $arch"; exit 1 ;;
esac
prepare_external_binaries "linux/$host_arch"
config_base_files "$TARGET_DEVICE_NAME" # 使用全局变量传递设备名
# 特定设备的额外文件配置 (如果存在)
if declare -f "config_${TARGET_DEVICE_NAME}_files" > /dev/null; then
echo "信息:执行特定设备的文件配置函数 config_${TARGET_DEVICE_NAME}_files ..."
"config_${TARGET_DEVICE_NAME}_files"
fi
# 某些镜像可能需要准备DNS和换源
if [[ "$NEED_PREPARE_DNS" = true ]]; then
prepare_dns_and_mirrors
fi
# 可选强制使用特定armbian源
# delete_armbian_verify
# 执行安装步骤
install_base_packages
configure_network "$network_type"
install_python_deps
configure_kvmd_core
configure_system
install_webterm "$arch" # 传递原始架构名给ttyd下载
apply_kvmd_tweaks "$arch" "$device_type"
run_in_chroot "df -h" # 显示最终磁盘使用情况
echo "信息One-KVM 安装和配置完成。"
}

65
build/functions/packaging.sh Executable file
View File

@ -0,0 +1,65 @@
#!/bin/bash
# --- 打包函数 ---
pack_img() {
local device_name_friendly="$1" # e.g., "Vm", "Cumebox2"
local target_img_name="One-KVM_by-SilentWind_${device_name_friendly}_${DATE}.img"
local source_img="$TMPDIR/rootfs.img"
echo "信息:开始打包镜像 ($device_name_friendly)..."
ensure_dir "$OUTPUTDIR"
# 确保在打包前已经正确卸载了所有挂载点和loop设备
if [[ "$ROOTFS_MOUNTED" -eq 1 || "$DEV_MOUNTED" -eq 1 || "$SYS_MOUNTED" -eq 1 || "$PROC_MOUNTED" -eq 1 || -n "$LOOPDEV" && -b "$LOOPDEV" ]]; then
echo "警告发现未卸载的挂载点或loop设备尝试再次卸载..."
unmount_all
fi
echo "信息:移动镜像文件 $source_img$OUTPUTDIR/$target_img_name ..."
sudo mv "$source_img" "$OUTPUTDIR/$target_img_name" || { echo "错误:移动镜像文件失败" >&2; exit 1; }
if [ "$device_name_friendly" = "Vm" ]; then
echo "信息:为 Vm 目标转换镜像格式 (vmdk, vdi)..."
local raw_img="$OUTPUTDIR/$target_img_name"
local vmdk_img="$OUTPUTDIR/One-KVM_by-SilentWind_Vmare-uefi_${DATE}.vmdk"
local vdi_img="$OUTPUTDIR/One-KVM_by-SilentWind_Virtualbox-uefi_${DATE}.vdi"
echo "信息:转换为 VMDK..."
sudo qemu-img convert -f raw -O vmdk "$raw_img" "$vmdk_img" || echo "警告:转换为 VMDK 失败"
echo "信息:转换为 VDI..."
sudo qemu-img convert -f raw -O vdi "$raw_img" "$vdi_img" || echo "警告:转换为 VDI 失败"
fi
echo "信息:镜像打包完成: $OUTPUTDIR/$target_img_name"
}
pack_img_onecloud() {
local target_img_name="One-KVM_by-SilentWind_Onecloud_${DATE}.burn.img"
local rootfs_raw_img="$TMPDIR/rootfs.img"
local rootfs_sparse_img="$TMPDIR/7.rootfs.PARTITION.sparse"
local aml_packer="$SRCPATH/image/onecloud/AmlImg_v0.3.1_linux_amd64"
echo "信息:开始为 Onecloud 打包 burn 镜像..."
ensure_dir "$OUTPUTDIR"
# 确保在打包前已经正确卸载了所有挂载点和loop设备
if [[ "$ROOTFS_MOUNTED" -eq 1 || "$DEV_MOUNTED" -eq 1 || "$SYS_MOUNTED" -eq 1 || "$PROC_MOUNTED" -eq 1 || -n "$LOOPDEV" && -b "$LOOPDEV" ]]; then
echo "警告发现未卸载的挂载点或loop设备尝试再次卸载..."
unmount_all
fi
echo "信息:将 raw rootfs 转换为 sparse image..."
# 先删除可能存在的旧 sparse 文件
sudo rm -f "$rootfs_sparse_img"
sudo img2simg "$rootfs_raw_img" "$rootfs_sparse_img" || { echo "错误img2simg 转换失败" >&2; exit 1; }
sudo rm "$rootfs_raw_img" # 删除 raw 文件,因为它已被转换
echo "信息:使用 AmlImg 工具打包..."
sudo chmod +x "$aml_packer"
sudo "$aml_packer" pack "$OUTPUTDIR/$target_img_name" "$TMPDIR/" || { echo "错误AmlImg 打包失败" >&2; exit 1; }
echo "信息:清理 Onecloud 临时文件..."
sudo rm -f "$TMPDIR/6.boot.PARTITION.sparse" "$TMPDIR/7.rootfs.PARTITION.sparse" "$TMPDIR/dts.img"
echo "信息Onecloud burn 镜像打包完成: $OUTPUTDIR/$target_img_name"
}

View File

@ -109,7 +109,7 @@ if [ ! -f /etc/kvmd/.init_flag ]; then
log_info "已禁用 WebTerm 功能"
rm -r /usr/share/kvmd/extras/webterm
else
cat >> /etc/supervisord.conf << EOF
cat >> /etc/kvmd/supervisord.conf << EOF
[program:kvmd-webterm]
command=/usr/local/bin/ttyd --interface=/run/kvmd/ttyd.sock --port=0 --writable /bin/bash -c '/etc/kvmd/armbain-motd; bash'
@ -125,14 +125,14 @@ EOF
fi
if [ "$NOWEBTERMWRITE" == "1" ]; then
sed -i "s/--writable//g" /etc/supervisord.conf
sed -i "s/--writable//g" /etc/kvmd/supervisord.conf
fi
if [ "$NOVNC" == "1" ]; then
log_info "已禁用 VNC 功能"
rm -r /usr/share/kvmd/extras/vnc
else
cat >> /etc/supervisord.conf << EOF
cat >> /etc/kvmd/supervisord.conf << EOF
[program:kvmd-vnc]
command=python -m kvmd.apps.vnc --run
@ -151,7 +151,7 @@ EOF
log_info "已禁用IPMI功能"
rm -r /usr/share/kvmd/extras/ipmi
else
cat >> /etc/supervisord.conf << EOF
cat >> /etc/kvmd/supervisord.conf << EOF
[program:kvmd-ipmi]
command=python -m kvmd.apps.ipmi --run
@ -241,4 +241,4 @@ if [ "$OTG" == "1" ]; then
fi
log_info "One-KVM 配置文件准备完成,正在启动服务..."
exec supervisord -c /etc/supervisord.conf
exec supervisord -c /etc/kvmd/supervisord.conf

View File

@ -0,0 +1,64 @@
#!/bin/bash
# 为onecloud平台生成随机MAC地址的一次性脚本
# 此脚本在首次开机时执行为eth0网卡生成并应用随机MAC地址
set -e
NETWORK_CONFIG="/etc/systemd/network/99-eth0.network"
LOCK_FILE="/var/lib/kvmd/.mac-generated"
# 检查是否已经执行过
if [ -f "$LOCK_FILE" ]; then
echo "MAC地址已经生成过跳过执行"
exit 0
fi
# 生成随机MAC地址 (使用本地管理的MAC地址前缀)
generate_random_mac() {
# 使用本地管理的MAC地址前缀 (第二位设为2、6、A、E中的一个)
# 这样可以避免与真实硬件MAC地址冲突
printf "02:%02x:%02x:%02x:%02x:%02x\n" \
$((RANDOM % 256)) \
$((RANDOM % 256)) \
$((RANDOM % 256)) \
$((RANDOM % 256)) \
$((RANDOM % 256))
}
echo "正在为onecloud生成随机MAC地址..."
# 生成新的MAC地址
NEW_MAC=$(generate_random_mac)
echo "生成的MAC地址: $NEW_MAC"
# 备份原配置文件
if [ -f "$NETWORK_CONFIG" ]; then
cp "$NETWORK_CONFIG" "${NETWORK_CONFIG}.backup"
fi
# 更新网络配置文件
cat > "$NETWORK_CONFIG" << EOF
[Match]
Name=eth0
[Network]
DHCP=yes
[Link]
MACAddress=$NEW_MAC
EOF
echo "已更新网络配置文件: $NETWORK_CONFIG"
# 创建锁定文件,防止重复执行
mkdir -p "$(dirname "$LOCK_FILE")"
echo "MAC地址生成时间: $(date)" > "$LOCK_FILE"
# 禁用此服务,确保只运行一次
systemctl disable kvmd-generate-mac.service
echo "随机MAC地址生成完成: $NEW_MAC"
echo "服务已自动禁用,下次开机不会再执行"
exit 0

View File

@ -0,0 +1,34 @@
#!/bin/bash
# KVMD首次运行初始化脚本
# 在首次开机时执行KVMD服务启动前的必要初始化操作
set -e
LOCK_FILE="/var/lib/kvmd/.kvmd-firstrun-completed"
# 检查是否已经执行过
[ -f "$LOCK_FILE" ] && { echo "[KVMD-FirstRun] 初始化已完成,跳过执行"; exit 0; }
echo "[KVMD-FirstRun] 开始KVMD首次运行初始化..."
# 1. 生成KVMD主证书
echo "[KVMD-FirstRun] 生成KVMD主证书..."
kvmd-gencert --do-the-thing
# 2. 生成VNC证书
echo "[KVMD-FirstRun] 生成VNC证书..."
kvmd-gencert --do-the-thing --vnc
# 3. 生成nginx配置文件
echo "[KVMD-FirstRun] 生成nginx配置文件..."
kvmd-nginx-mkconf /etc/kvmd/nginx/nginx.conf.mako /etc/kvmd/nginx/nginx.conf || echo "[KVMD-FirstRun] 警告: nginx配置生成失败"
# 创建锁定文件
mkdir -p "$(dirname "$LOCK_FILE")"
echo "KVMD首次运行初始化完成 - $(date)" > "$LOCK_FILE"
# 禁用服务
systemctl disable kvmd-firstrun.service || echo "[KVMD-FirstRun] 警告: 服务禁用失败"
echo "[KVMD-FirstRun] 初始化完成!"

View File

@ -0,0 +1,26 @@
[Unit]
Description=KVMD First Run Initialization (One-time)
Documentation=https://github.com/your-repo/One-KVM
Before=kvmd.service
Before=kvmd-nginx.service
Before=kvmd-otg.service
Before=kvmd-vnc.service
Before=kvmd-ipmi.service
Before=kvmd-webterm.service
Before=kvmd-janus.service
Before=kvmd-media.service
After=local-fs.target
After=network.target
Wants=local-fs.target
ConditionPathExists=!/var/lib/kvmd/.kvmd-firstrun-completed
[Service]
Type=oneshot
ExecStart=/usr/bin/kvmd-firstrun.sh
RemainAfterExit=yes
StandardOutput=journal
StandardError=journal
TimeoutStartSec=300
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,18 @@
[Unit]
Description=Generate Random MAC Address for OneCloud (One-time)
Documentation=https://github.com/your-repo/One-KVM
Before=systemd-networkd.service
Before=network-pre.target
Wants=network-pre.target
After=local-fs.target
ConditionPathExists=!/var/lib/kvmd/.mac-generated
[Service]
Type=oneshot
ExecStart=/usr/local/bin/generate-random-mac.sh
RemainAfterExit=yes
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

33
supervisord.conf Normal file
View File

@ -0,0 +1,33 @@
[program:kvmd-webterm]
command=/usr/local/bin/ttyd --interface=/run/kvmd/ttyd.sock --port=0 --writable /bin/bash -c '/etc/kvmd/armbain-motd; bash'
directory=/
autostart=true
autorestart=true
priority=14
stopasgroup=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes = 0
redirect_stderr=true
[program:kvmd-vnc]
command=python -m kvmd.apps.vnc --run
directory=/
autostart=true
autorestart=true
priority=11
stopasgroup=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes = 0
redirect_stderr=true
[program:kvmd-ipmi]
command=python -m kvmd.apps.ipmi --run
directory=/
autostart=true
autorestart=true
priority=12
stopasgroup=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes = 0
redirect_stderr=true