aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Qin <richard.qin.x@gmail.com>2026-10-28 23:13:09 +0800
committerRichard Qin <richard.qin.x@gmail.com>2026-10-28 23:13:09 +0800
commite7236907dfe3e9130d532d5e8a7f2b17af13df1c (patch)
tree3548beab69f96ad5d669152b248d0ab15769dafe
parent36cf23ae3ba7295ca4021258681996fbbba9b14d (diff)
downloadrutils-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.sh100
-rw-r--r--debian-container/.gitignore2
-rw-r--r--debian-container/README24
-rwxr-xr-xdebian-container/debian-container.sh44
-rw-r--r--debian-container/lib/common.sh42
-rw-r--r--debian-container/lib/config.sh11
-rw-r--r--debian-container/lib/container.sh56
-rw-r--r--debian-container/lib/mount.sh30
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"
+}
+