aboutsummaryrefslogtreecommitdiff
path: root/debian-container.sh
blob: be2b1f5fe515b25914bd0400711531057890f02f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#!/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