diff options
| author | Richard Qin <richard.qin.x@gmail.com> | 2026-10-28 23:13:09 +0800 |
|---|---|---|
| committer | Richard Qin <richard.qin.x@gmail.com> | 2026-10-28 23:13:09 +0800 |
| commit | e7236907dfe3e9130d532d5e8a7f2b17af13df1c (patch) | |
| tree | 3548beab69f96ad5d669152b248d0ab15769dafe | |
| parent | 36cf23ae3ba7295ca4021258681996fbbba9b14d (diff) | |
| download | rutils-e7236907dfe3e9130d532d5e8a7f2b17af13df1c.tar.gz rutils-e7236907dfe3e9130d532d5e8a7f2b17af13df1c.tar.xz | |
Refactor Debian container script into modular components with configurable paths and documentation.
Signed-off-by: Richard Qin <richard.qin.x@gmail.com>
| -rw-r--r-- | debian-container.sh | 100 | ||||
| -rw-r--r-- | debian-container/.gitignore | 2 | ||||
| -rw-r--r-- | debian-container/README | 24 | ||||
| -rwxr-xr-x | debian-container/debian-container.sh | 44 | ||||
| -rw-r--r-- | debian-container/lib/common.sh | 42 | ||||
| -rw-r--r-- | debian-container/lib/config.sh | 11 | ||||
| -rw-r--r-- | debian-container/lib/container.sh | 56 | ||||
| -rw-r--r-- | debian-container/lib/mount.sh | 30 |
8 files changed, 209 insertions, 100 deletions
diff --git a/debian-container.sh b/debian-container.sh deleted file mode 100644 index be2b1f5..0000000 --- a/debian-container.sh +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env bash -# Debian 构建容器 -set -Eeuo pipefail - -SCRIPT=$(readlink -f -- "${BASH_SOURCE[0]}") -IMAGE=$PWD/rootfs.ext4 -ROOT=$PWD/debian-root/ -ACTION=${1:-} - -if [[ ${EUID} -ne 0 ]]; then - echo '请使用 sudo 执行本脚本。' >&2 - exit 1 -fi - -cleanup_mounts() { - umount "$ROOT/dev/pts" 2>/dev/null || true - umount "$ROOT/dev" 2>/dev/null || true - umount "$ROOT/proc" 2>/dev/null || true - umount "$ROOT" 2>/dev/null || true -} - -mount_root() { - mkdir -p "$ROOT" - mount -o loop "$IMAGE" "$ROOT" - mkdir -p "$ROOT/proc" "$ROOT/dev" -} - -mount_virtual_filesystems() { - mount -t proc -o ro,nosuid,nodev,noexec proc "$ROOT/proc" - mount -t tmpfs -o mode=0755,nosuid tmpfs "$ROOT/dev" - mknod -m 0666 "$ROOT/dev/null" c 1 3 - mknod -m 0666 "$ROOT/dev/zero" c 1 5 - mknod -m 0666 "$ROOT/dev/random" c 1 8 - mknod -m 0666 "$ROOT/dev/urandom" c 1 9 - mknod -m 0666 "$ROOT/dev/tty" c 5 0 - mkdir -p "$ROOT/dev/pts" - mount -t devpts -o newinstance,ptmxmode=0666,mode=0620 devpts "$ROOT/dev/pts" - ln -s pts/ptmx "$ROOT/dev/ptmx" -} - -# 防止多个会话同时读写同一 ext4 镜像。 -case "$ACTION" in - init|enter|delete) - mkdir -p /run/lock - exec 9>/run/lock/debian-container.lock - flock -n 9 || { echo 'Debian 容器已经在运行或正在初始化。' >&2; exit 1; } - ;; -esac - -case "$ACTION" in - init) - for tool in debootstrap unshare mkfs.ext4 mount chroot setpriv flock; do - command -v "$tool" >/dev/null || { echo "缺少工具:$tool" >&2; exit 1; } - done - if [[ -e "$IMAGE" ]]; then - echo "镜像已存在:$IMAGE。请使用 enter 进入,避免覆盖容器数据。" >&2 - exit 1 - fi - mkdir -p "$(dirname "$IMAGE")" "$ROOT" - truncate -s 3G "$IMAGE" - mkfs.ext4 -F -q -L DEBIAN_CONTAINER "$IMAGE" - unshare --mount --propagation private -- bash "$SCRIPT" _init - echo '初始化完成。执行 sudo bash debian-container.sh enter 进入容器。' - ;; - _init) - mount_root - trap cleanup_mounts EXIT - debootstrap --variant=minbase stable "$ROOT" http://deb.debian.org/debian - rm -f "$ROOT/etc/resolv.conf" - cp -L /etc/resolv.conf "$ROOT/etc/resolv.conf" - mount_virtual_filesystems - chroot "$ROOT" /usr/bin/env DEBIAN_FRONTEND=noninteractive /usr/bin/apt-get update - chroot "$ROOT" /usr/bin/env DEBIAN_FRONTEND=noninteractive /usr/bin/apt-get install -y passwd util-linux vim-tiny e2fsprogs - echo debian-container > "$ROOT/etc/hostname" - ;; - enter) - [[ -f "$IMAGE" ]] || { echo '没有找到镜像,请先运行 init。' >&2; exit 1; } - # 单独创建网络命名空间,暂时没有联网功能。 - unshare --mount --uts --ipc --pid --net --fork --propagation private -- bash "$SCRIPT" _enter - ;; - _enter) - mount_root - trap cleanup_mounts EXIT - mount_virtual_filesystems - hostname debian-container - echo '已进入独立的 Debian 实验环境;输入 exit 可退出。' - # 完成挂载后撤销一些宿主级危险权限,保留 CAP_LINUX_IMMUTABLE。 - setpriv --bounding-set=-sys_admin,-net_admin,-net_raw,-sys_module,-sys_boot,-sys_time,-sys_rawio,-sys_ptrace,-syslog -- \ - chroot "$ROOT" /bin/bash -l - ;; - delete) - [[ -f "$IMAGE" ]] || { echo 'Debian 镜像已不存在。'; exit 0; } - rm -- "$IMAGE" - echo 'Debian 镜像已删除。' - ;; - *) - echo "用法:sudo bash $0 init|enter|delete" >&2 - exit 2 - ;; -esac diff --git a/debian-container/.gitignore b/debian-container/.gitignore new file mode 100644 index 0000000..842988b --- /dev/null +++ b/debian-container/.gitignore @@ -0,0 +1,2 @@ +rootfs.ext4 +debian-root/ diff --git a/debian-container/README b/debian-container/README new file mode 100644 index 0000000..94a72e8 --- /dev/null +++ b/debian-container/README @@ -0,0 +1,24 @@ +Debian 容器 + +这个工具使用 Linux 的 mount namespace、PID namespace 和 network namespace,创建 +一个最小 Debian 实验环境。 + +用法: + +sudo ./debian-container.sh init +sudo ./debian-container.sh enter +sudo ./debian-container.sh delete + +默认情况下,镜像和挂载目录位于当前目录: + +rootfs.ext4 +debian-root/ + +可以通过环境变量修改路径: + +sudo env \ + RUTILS_DEBIAN_IMAGE=/path/to/rootfs.ext4 \ + RUTILS_DEBIAN_ROOT=/path/to/debian-root \ + ./debian-container.sh enter + +脚本实现按职责拆分在 lib/ 目录中,入口脚本只负责加载模块和分发命令。 diff --git a/debian-container/debian-container.sh b/debian-container/debian-container.sh new file mode 100755 index 0000000..4a52da0 --- /dev/null +++ b/debian-container/debian-container.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: GPL-3.0-or-later +# Copyright (C) 2026 by Richard Qin + +# Debian 构建容器 +set -Eeuo pipefail + +DC_SCRIPT=$(readlink -f -- "${BASH_SOURCE[0]}") +DC_DIR=$(dirname -- "$DC_SCRIPT") + +source "$DC_DIR/lib/config.sh" +source "$DC_DIR/lib/common.sh" +source "$DC_DIR/lib/mount.sh" +source "$DC_DIR/lib/container.sh" + +DC_ACTION=${1:-} + +case "$DC_ACTION" in + help|-h|--help) + dc_usage + exit 0 + ;; +esac + +dc_require_root + +case "$DC_ACTION" in + init|enter|delete) + # 防止多个会话同时读写同一 ext4 镜像。 + dc_acquire_lock + ;; +esac + +case "$DC_ACTION" in + init) dc_init ;; + _init) dc_init_rootfs ;; + enter) dc_enter ;; + _enter) dc_enter_rootfs ;; + delete) dc_delete ;; + *) + dc_usage >&2 + exit 2 + ;; +esac diff --git a/debian-container/lib/common.sh b/debian-container/lib/common.sh new file mode 100644 index 0000000..1fd08b0 --- /dev/null +++ b/debian-container/lib/common.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: GPL-3.0-or-later +# Copyright (C) 2026 by Richard Qin + +dc_die() { + echo "$*" >&2 + exit 1 +} + +dc_require_root() { + [[ ${EUID} -eq 0 ]] || dc_die '请使用 sudo 执行此脚本。' +} + +dc_require_commands() { + local tool + for tool in "$@"; do + command -v "$tool" >/dev/null || dc_die "缺少工具:$tool" + done +} + +dc_acquire_lock() { + mkdir -p "$(dirname -- "$DC_LOCK_FILE")" + exec 9>"$DC_LOCK_FILE" + flock -n 9 || dc_die 'Debian 容器已经在运行或正在初始化。' +} + +dc_usage() { + cat <<EOF +用法:sudo bash $DC_SCRIPT <命令> + +命令: + init 创建 Debian 容器镜像 + enter 进入容器 + delete 删除容器镜像 + +环境变量: + DEBIAN_IMAGE 镜像路径(默认:当前目录/rootfs.ext4) + DEBIAN_ROOT 挂载目录(默认:当前目录/debian-root) + DEBIAN_LOCK_FILE 锁文件路径 +EOF +} + diff --git a/debian-container/lib/config.sh b/debian-container/lib/config.sh new file mode 100644 index 0000000..66b1bfe --- /dev/null +++ b/debian-container/lib/config.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: GPL-3.0-or-later +# Copyright (C) 2026 by Richard Qin + +# Keep runtime data in the directory from which the tool is invoked for now. +# These variables can be overridden by the environment without changing the +# command interface. +DC_IMAGE=${DEBIAN_IMAGE:-"$PWD/rootfs.ext4"} +DC_ROOT=${DEBIAN_ROOT:-"$PWD/debian-root"} +DC_LOCK_FILE=${DEBIAN_LOCK_FILE:-/run/lock/debian-container.lock} + diff --git a/debian-container/lib/container.sh b/debian-container/lib/container.sh new file mode 100644 index 0000000..759d798 --- /dev/null +++ b/debian-container/lib/container.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: GPL-3.0-or-later +# Copyright (C) 2026 by Richard Qin + +dc_init() { + dc_require_commands debootstrap unshare mkfs.ext4 mount chroot setpriv flock + + if [[ -e "$DC_IMAGE" ]]; then + dc_die "镜像已存在:$DC_IMAGE。请使用 enter 进入,避免覆盖容器数据。" + fi + + mkdir -p "$(dirname -- "$DC_IMAGE")" "$DC_ROOT" + truncate -s 3G "$DC_IMAGE" + mkfs.ext4 -F -q -L DEBIAN_CONTAINER "$DC_IMAGE" + unshare --mount --propagation private -- bash "$DC_SCRIPT" _init + echo "初始化完成。执行 sudo bash $DC_SCRIPT enter 进入容器。" +} + +dc_init_rootfs() { + dc_mount_root + trap dc_cleanup_mounts EXIT + + debootstrap --variant=minbase stable "$DC_ROOT" http://deb.debian.org/debian + rm -f "$DC_ROOT/etc/resolv.conf" + cp -L /etc/resolv.conf "$DC_ROOT/etc/resolv.conf" + dc_mount_virtual_filesystems + chroot "$DC_ROOT" /usr/bin/env DEBIAN_FRONTEND=noninteractive /usr/bin/apt-get update + chroot "$DC_ROOT" /usr/bin/env DEBIAN_FRONTEND=noninteractive /usr/bin/apt-get install -y passwd util-linux vim-tiny e2fsprogs + echo debian-container > "$DC_ROOT/etc/hostname" +} + +dc_enter() { + [[ -f "$DC_IMAGE" ]] || dc_die '没有找到镜像,请先运行 init。' + + # 单独创建网络命名空间,暂时没有联网功能。 + unshare --mount --uts --ipc --pid --net --fork --propagation private -- bash "$DC_SCRIPT" _enter +} + +dc_enter_rootfs() { + dc_mount_root + trap dc_cleanup_mounts EXIT + dc_mount_virtual_filesystems + hostname debian-container + echo '已进入独立的 Debian 实验环境;输入 exit 可退出容器。' + + # 完成挂载后撤销一些宿主级危险权限,保留 CAP_LINUX_IMMUTABLE。 + setpriv --bounding-set=-sys_admin,-net_admin,-net_raw,-sys_module,-sys_boot,-sys_time,-sys_rawio,-sys_ptrace,-syslog -- \ + chroot "$DC_ROOT" /bin/bash -l +} + +dc_delete() { + [[ -f "$DC_IMAGE" ]] || { echo 'Debian 镜像已不存在。'; return 0; } + rm -- "$DC_IMAGE" + echo 'Debian 镜像已删除。' +} + diff --git a/debian-container/lib/mount.sh b/debian-container/lib/mount.sh new file mode 100644 index 0000000..0b623c8 --- /dev/null +++ b/debian-container/lib/mount.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: GPL-3.0-or-later +# Copyright (C) 2026 by Richard Qin + +dc_cleanup_mounts() { + umount "$DC_ROOT/dev/pts" 2>/dev/null || true + umount "$DC_ROOT/dev" 2>/dev/null || true + umount "$DC_ROOT/proc" 2>/dev/null || true + umount "$DC_ROOT" 2>/dev/null || true +} + +dc_mount_root() { + mkdir -p "$DC_ROOT" + mount -o loop "$DC_IMAGE" "$DC_ROOT" + mkdir -p "$DC_ROOT/proc" "$DC_ROOT/dev" +} + +dc_mount_virtual_filesystems() { + mount -t proc -o ro,nosuid,nodev,noexec proc "$DC_ROOT/proc" + mount -t tmpfs -o mode=0755,nosuid tmpfs "$DC_ROOT/dev" + mknod -m 0666 "$DC_ROOT/dev/null" c 1 3 + mknod -m 0666 "$DC_ROOT/dev/zero" c 1 5 + mknod -m 0666 "$DC_ROOT/dev/random" c 1 8 + mknod -m 0666 "$DC_ROOT/dev/urandom" c 1 9 + mknod -m 0666 "$DC_ROOT/dev/tty" c 5 0 + mkdir -p "$DC_ROOT/dev/pts" + mount -t devpts -o newinstance,ptmxmode=0666,mode=0620 devpts "$DC_ROOT/dev/pts" + ln -s pts/ptmx "$DC_ROOT/dev/ptmx" +} + |
