TL;DR 最近存在一个问题,运行一些非自由软件时,它们老喜欢搞事情,比如在后台放一个云同步服务,你把软件关了那个云同步还不会一起关;亦或会闲得没事往你电脑里拉屎,不仅占空间,而且还多处一堆奇奇怪怪的文件夹。
这个时候,就要请出我们的老朋友,bwrap了!它可以仅授权某些文件夹的访问,也可以仅授权某些文件夹的只读访问,也可以更改应用看到的文件夹结构,直观体验就像安卓上的存储空间隔离 by Rikka 一样。
这是项目官方的仓库 。
安装 安装起来十分简单啊,直接一行命令就行了:
1 sudo pacman -S bubblewrap
使用 直接保存这个脚本,然后在它后面接要运行的应用程序名就行,已经做了英特尔和nVidia双显卡自动检测兼容;也可以直接source这个脚本,然后直接在命令行里bwrap_run xxx就行,不过为了安全,这样会跳过bind_maps那块。 举个例子:
1 2 3 4 # 从本地脚本运行 ./bwrap_run.sh bash# source 后可以直接运行,但会跳过 bind_maps bwrap_run bash
这样会打开一个沙箱内的bash,然后在沙箱里再运行不放心的软件就行了,亦或运行wine!这样就不用担心wine闲得没事把你每个磁盘都挂上然后你运行的某些exe往里面拉屎了!
当你在沙箱的shell里运行exit后,整个沙箱内的所有子进程都会被一起结束!十分的纯净,不留痕!软件运行过程中拉的屎都存在内存里,当沙箱结束后会一并从内存中删除,全程0接触物理磁盘!
这是完整脚本:
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 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 # !/usr/bin/bash bwrap_run() { ( set -euo pipefail # =================== 配置 =================== local REAL_HOME="$HOME" local ROOT_DIR="$(cd "$(dirname "$0")" && pwd)" local USER_ID="$(id -u)" local GROUP_ID="$(id -g)" # 允许访问但不准写入的文件夹白名单 local ro_dirs=( # 系统配置 "$REAL_HOME/.local/share/fonts" ) # 允许读写的文件夹白名单 local allowed_dirs=( # Proton "/home/admin/Documents/bwrap_transfer" "$REAL_HOME/.local/share/Steam" # UV "$REAL_HOME/.cache/uv" "$REAL_HOME/.local/share/uv" # WPS "$REAL_HOME/.config/Kingsoft" "/opt/kingsoft" # HMCL "$REAL_HOME/.local/share/hmcl" ) # 跨磁盘允许读写用这个加白 local bind_maps_force=() local bind_maps=( "/run/media/admin/games/uncompress::gamesuncompress_bwrap" ) local bind_maps_created=() if [[ ! -v bwrap_run_from_script ]]; then bind_maps=() echo "警告: 从source的函数运行, 跳过挂载bind_maps" fi # 环境变量 local env_vars=( "LANG=zh_CN.UTF-8" "LC_ALL=zh_CN.UTF-8" # NVIDIA(有则生效) "__GL_THREADED_OPTIMIZATIONS=1" ) echo "脚本目录: $ROOT_DIR" echo "用户 HOME: $REAL_HOME" echo "UID:GID: $USER_ID:$GROUP_ID" echo "ro_dirs:" for d in "${ro_dirs[@]}"; do echo " - $d"; done echo "allowed_dirs:" for d in "${allowed_dirs[@]}"; do echo " - $d"; done echo "bind_maps:" for m in "${bind_maps[@]}"; do echo " - $m"; done echo "bind_maps_force:" for m in "${bind_maps_force[@]}"; do echo " - $m"; done # =================== GPU 类型检测 =================== detect_gpu() { if command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi -L >/dev/null 2>&1; then printf '%s' "nvidia" return fi if ls /dev/nvidia* >/dev/null 2>&1; then printf '%s' "nvidia" return fi for card in /sys/class/drm/card*; do local vendor_file="$card/device/vendor" [ -r "$vendor_file" ] || continue local vendor_hex="$(tr -d ' \n' <"$vendor_file")" case "$vendor_hex" in *8086*) printf 'intel'; return ;; *10de*) printf 'nvidia'; return ;; *1002*) printf 'intel'; return ;; esac done printf 'unknown' } local GPU_TYPE="$(detect_gpu || true)" echo "检测到 GPU 类型: $GPU_TYPE" # =================== XDG_RUNTIME_DIR / Wayland =================== local XDG_RUNTIME_DIR="/run/user/$USER_ID" local WAYLAND_SOCKET="" if [ -d "$XDG_RUNTIME_DIR" ]; then for s in "$XDG_RUNTIME_DIR"/wayland-*; do [ -S "$s" ] || continue local WAYLAND_SOCKET="$s" break done fi [ -n "$WAYLAND_SOCKET" ] && echo "Wayland socket: $WAYLAND_SOCKET" # =================== 构建 bwrap 参数 =================== local bargs=( --unshare-pid # 创建独立 PID namespace --die-with-parent # 父进程退出时 bwrap 自动杀掉 --setenv HOME "$REAL_HOME" --setenv XDG_RUNTIME_DIR "$XDG_RUNTIME_DIR" --chdir "$ROOT_DIR" --tmpfs /tmp # 给 bwrap 内程序干净 tmpfs --bind /dev/shm /dev/shm # 对 MIT-SHM 或 X11 必须的共享内存 ) [ -n "$WAYLAND_SOCKET" ] && bargs+=( --setenv WAYLAND_DISPLAY "$(basename "$WAYLAND_SOCKET")" ) # =================== 系统库绑定 =================== for d in /usr /bin /sbin /etc ; do # /opt [ -d "$d" ] && bargs+=( --ro-bind "$d" "$d" ) done [ -d /usr/lib ] && bargs+=( --ro-bind /usr/lib /usr/lib ) [ -d /usr/lib32 ] && bargs+=( --ro-bind /usr/lib32 /usr/lib32 ) [ -d /usr/lib32 ] && bargs+=( --ro-bind /usr/lib32 /lib32 ) [ -d /lib ] && bargs+=( --ro-bind /lib /lib ) [ -d /lib64 ] && bargs+=( --ro-bind /lib64 /lib64 ) bargs+=( --proc /proc ) bargs+=( --ro-bind /sys /sys ) [ -f /etc/resolv.conf ] && bargs+=( --ro-bind /etc/resolv.conf /etc/resolv.conf ) [ -d /usr/share/fonts ] && bargs+=( --ro-bind /usr/share/fonts /usr/share/fonts ) # =================== /dev & /dev/shm & runtime =================== [ -d /dev ] && bargs+=( --dev-bind /dev /dev ) [ -d /dev/shm ] && bargs+=( --bind /dev/shm /dev/shm ) [ -d "$XDG_RUNTIME_DIR" ] && bargs+=( --bind "$XDG_RUNTIME_DIR" "$XDG_RUNTIME_DIR" ) # =================== GPU 绑定 =================== case "$GPU_TYPE" in nvidia) echo "绑定 NVIDIA 设备节点" for ndev in /dev/nvidia*; do [ -e "$ndev" ] && bargs+=( --dev-bind "$ndev" "$ndev" ) done [ -d /dev/dri ] && bargs+=( --dev-bind /dev/dri /dev/dri ) ;; intel|unknown) [ -d /dev/dri ] && bargs+=( --dev-bind /dev/dri /dev/dri ) ;; esac # =================== 根目录绑定 =================== if [[ "$ROOT_DIR" == "$HOME" ]]; then echo "警告: ROOT_DIR 目录与用户主目录相同! 本次未授权访问主目录" else bargs+=( --bind "$ROOT_DIR" "$ROOT_DIR" ) fi # =================== ro_dirs =================== for d in "${ro_dirs[@]}"; do [ -e "$d" ] && bargs+=( --ro-bind "$d" "$d" ) done # =================== allowed_dirs =================== for d in "${allowed_dirs[@]}"; do [ -e "$d" ] && bargs+=( --bind "$d" "$d" ) done # =================== 注入环境变量 =================== for kv in "${env_vars[@]}"; do key="${kv%%=*}" val="${kv#*=}" bargs+=( --setenv "$key" "$val" ) done # =================== bind_maps =================== echo "actual bind_maps:" for m in "${bind_maps[@]}"; do host="${m%%::*}" rel="${m##*::}" target="$ROOT_DIR/$rel" [ ! -d "$host" ] && continue [ ! -d "$target" ] && mkdir -p "$target" && bind_maps_created+=("$target") [ -e "$host" ] && bargs+=( --bind "$host" "$target" ) echo " - ${host} -> ${target}" done echo "actual bind_maps_force:" for m in "${bind_maps_force[@]}"; do host="${m%%::*}" rel="${m##*::}" target="$ROOT_DIR/$rel" [ ! -d "$host" ] && continue [ ! -d "$target" ] && mkdir -p "$target" && bind_maps_created+=("$target") [ -e "$host" ] && bargs+=( --bind "$host" "$target" ) echo " - ${host} -> ${target}" done echo "bind_maps_created:" for m in "${bind_maps_created[@]}"; do echo " - $m"; done # =================== PATH =================== bargs+=( --setenv PATH "/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:$PATH" ) # 如果要source 的话,这段是需要加上的 # 1) 注入一个安全的、简洁的 PS1 / 清理会搞事的 prompt 变量 bargs+=( --setenv PS1 '\u@\h:\w\\$ ' ) bargs+=( --setenv PROMPT_COMMAND '' ) bargs+=( --setenv RPROMPT '' ) bargs+=( --setenv VSCODE_INJECTION '' ) # 视具体环境可选 # 如果你还有别的在 host 导出的 prompt 变量(比如 ZSH_THEME),也可以在这里清空: bargs+=( --setenv ZLE_RPROMPT '' ) # 2) 决定要执行的命令:若用户传 "bash" ,用干净交互 bash;否则执行传入命令 if [ "${#@}" -ge 1 ] && [ "$1" = "bash" ]; then shift local CMD=( bash --noprofile --norc -i "$@" ) else # preserve original args local CMD=( "$@" ) fi # trap cleanup EXIT INT TERM echo "==========" # =================== 执行 bwrap =================== # 外层 trap ,确保脚本退出时杀掉 bwrap(额外保险) # bwrap "${bargs[@]} " -- "$@ " bwrap "${bargs[@]}" -- "${CMD[@]}" if [[ -v bwrap_run_from_script ]]; then echo "变量 bwrap_run_from_script 已设置" echo "==========" echo -e "\n执行清理操作..." # bind_maps_created+=("/tmp/114514" ) for index in "${!bind_maps_created[@]}"; do # echo "元素 $index: ${bind_maps_created[$index]}" rm -rv "${bind_maps_created[$index]}" done echo "清理完成。" fi ) } if [[ ${BASH_SOURCE[0]} == $0 ]]; then bwrap_run_from_script=1 bwrap_run $@ fi