github.com/moby/docker@v26.1.3+incompatible/contrib/dockerd-rootless-setuptool.sh (about) 1 #!/bin/sh 2 # dockerd-rootless-setuptool.sh: setup tool for dockerd-rootless.sh 3 # Needs to be executed as a non-root user. 4 # 5 # Typical usage: dockerd-rootless-setuptool.sh install --force 6 # 7 # Documentation: https://docs.docker.com/go/rootless/ 8 set -eu 9 10 # utility functions 11 INFO() { 12 /bin/echo -e "\e[104m\e[97m[INFO]\e[49m\e[39m $@" 13 } 14 15 WARNING() { 16 /bin/echo >&2 -e "\e[101m\e[97m[WARNING]\e[49m\e[39m $@" 17 } 18 19 ERROR() { 20 /bin/echo >&2 -e "\e[101m\e[97m[ERROR]\e[49m\e[39m $@" 21 } 22 23 # constants 24 DOCKERD_ROOTLESS_SH="dockerd-rootless.sh" 25 SYSTEMD_UNIT="docker.service" 26 CLI_CONTEXT="rootless" 27 28 # CLI opt: --force 29 OPT_FORCE="" 30 # CLI opt: --skip-iptables 31 OPT_SKIP_IPTABLES="" 32 33 # global vars 34 ARG0="$0" 35 DOCKERD_ROOTLESS_SH_FLAGS="" 36 BIN="" 37 SYSTEMD="" 38 CFG_DIR="" 39 XDG_RUNTIME_DIR_CREATED="" 40 USERNAME="" 41 USERNAME_ESCAPED="" 42 43 # run checks and also initialize global vars 44 init() { 45 # OS verification: Linux only 46 case "$(uname)" in 47 Linux) ;; 48 49 *) 50 ERROR "Rootless Docker cannot be installed on $(uname)" 51 exit 1 52 ;; 53 esac 54 55 # User verification: deny running as root 56 if [ "$(id -u)" = "0" ]; then 57 ERROR "Refusing to install rootless Docker as the root user" 58 exit 1 59 fi 60 61 # set BIN 62 if ! BIN="$(command -v "$DOCKERD_ROOTLESS_SH" 2> /dev/null)"; then 63 ERROR "$DOCKERD_ROOTLESS_SH needs to be present under \$PATH" 64 exit 1 65 fi 66 BIN=$(dirname "$BIN") 67 68 # set SYSTEMD 69 if systemctl --user show-environment > /dev/null 2>&1; then 70 SYSTEMD=1 71 fi 72 73 # HOME verification 74 if [ -z "${HOME:-}" ] || [ ! -d "$HOME" ]; then 75 ERROR "HOME needs to be set" 76 exit 1 77 fi 78 if [ ! -w "$HOME" ]; then 79 ERROR "HOME needs to be writable" 80 exit 1 81 fi 82 83 # Set USERNAME from `id -un` and potentially protect backslash 84 # for windbind/samba domain users 85 USERNAME=$(id -un) 86 USERNAME_ESCAPED=$(echo $USERNAME | sed 's/\\/\\\\/g') 87 88 # set CFG_DIR 89 CFG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}" 90 91 # Existing rootful docker verification 92 if [ -w /var/run/docker.sock ] && [ -z "$OPT_FORCE" ]; then 93 ERROR "Aborting because rootful Docker (/var/run/docker.sock) is running and accessible. Set --force to ignore." 94 exit 1 95 fi 96 97 # Validate XDG_RUNTIME_DIR and set XDG_RUNTIME_DIR_CREATED 98 if [ -z "${XDG_RUNTIME_DIR:-}" ] || [ ! -w "$XDG_RUNTIME_DIR" ]; then 99 if [ -n "$SYSTEMD" ]; then 100 ERROR "Aborting because systemd was detected but XDG_RUNTIME_DIR (\"$XDG_RUNTIME_DIR\") is not set, does not exist, or is not writable" 101 ERROR "Hint: this could happen if you changed users with 'su' or 'sudo'. To work around this:" 102 ERROR "- try again by first running with root privileges 'loginctl enable-linger <user>' where <user> is the unprivileged user and export XDG_RUNTIME_DIR to the value of RuntimePath as shown by 'loginctl show-user <user>'" 103 ERROR "- or simply log back in as the desired unprivileged user (ssh works for remote machines, machinectl shell works for local machines)" 104 exit 1 105 fi 106 export XDG_RUNTIME_DIR="$HOME/.docker/run" 107 mkdir -p -m 700 "$XDG_RUNTIME_DIR" 108 XDG_RUNTIME_DIR_CREATED=1 109 fi 110 111 instructions="" 112 # instruction: uidmap dependency check 113 if ! command -v newuidmap > /dev/null 2>&1; then 114 if command -v apt-get > /dev/null 2>&1; then 115 instructions=$( 116 cat <<- EOI 117 ${instructions} 118 # Install newuidmap & newgidmap binaries 119 apt-get install -y uidmap 120 EOI 121 ) 122 elif command -v dnf > /dev/null 2>&1; then 123 instructions=$( 124 cat <<- EOI 125 ${instructions} 126 # Install newuidmap & newgidmap binaries 127 dnf install -y shadow-utils 128 EOI 129 ) 130 elif command -v yum > /dev/null 2>&1; then 131 instructions=$( 132 cat <<- EOI 133 ${instructions} 134 # Install newuidmap & newgidmap binaries 135 yum install -y shadow-utils 136 EOI 137 ) 138 else 139 ERROR "newuidmap binary not found. Please install with a package manager." 140 exit 1 141 fi 142 fi 143 144 # instruction: iptables dependency check 145 faced_iptables_error="" 146 if ! command -v iptables > /dev/null 2>&1 && [ ! -f /sbin/iptables ] && [ ! -f /usr/sbin/iptables ]; then 147 faced_iptables_error=1 148 if [ -z "$OPT_SKIP_IPTABLES" ]; then 149 if command -v apt-get > /dev/null 2>&1; then 150 instructions=$( 151 cat <<- EOI 152 ${instructions} 153 # Install iptables 154 apt-get install -y iptables 155 EOI 156 ) 157 elif command -v dnf > /dev/null 2>&1; then 158 instructions=$( 159 cat <<- EOI 160 ${instructions} 161 # Install iptables 162 dnf install -y iptables 163 EOI 164 ) 165 elif command -v yum > /dev/null 2>&1; then 166 instructions=$( 167 cat <<- EOI 168 ${instructions} 169 # Install iptables 170 yum install -y iptables 171 EOI 172 ) 173 else 174 ERROR "iptables binary not found. Please install with a package manager." 175 exit 1 176 fi 177 fi 178 fi 179 180 # instruction: ip_tables module dependency check 181 if ! grep -q ip_tables /proc/modules 2> /dev/null && ! grep -q ip_tables /lib/modules/$(uname -r)/modules.builtin 2> /dev/null; then 182 faced_iptables_error=1 183 if [ -z "$OPT_SKIP_IPTABLES" ]; then 184 instructions=$( 185 cat <<- EOI 186 ${instructions} 187 # Load ip_tables module 188 modprobe ip_tables 189 EOI 190 ) 191 fi 192 fi 193 194 # set DOCKERD_ROOTLESS_SH_FLAGS 195 if [ -n "$faced_iptables_error" ] && [ -n "$OPT_SKIP_IPTABLES" ]; then 196 DOCKERD_ROOTLESS_SH_FLAGS="${DOCKERD_ROOTLESS_SH_FLAGS} --iptables=false" 197 fi 198 199 # instruction: Debian and Arch require setting unprivileged_userns_clone 200 if [ -f /proc/sys/kernel/unprivileged_userns_clone ]; then 201 if [ "1" != "$(cat /proc/sys/kernel/unprivileged_userns_clone)" ]; then 202 instructions=$( 203 cat <<- EOI 204 ${instructions} 205 # Set kernel.unprivileged_userns_clone 206 cat <<EOT > /etc/sysctl.d/50-rootless.conf 207 kernel.unprivileged_userns_clone = 1 208 EOT 209 sysctl --system 210 EOI 211 ) 212 fi 213 fi 214 215 # instruction: RHEL/CentOS 7 requires setting max_user_namespaces 216 if [ -f /proc/sys/user/max_user_namespaces ]; then 217 if [ "0" = "$(cat /proc/sys/user/max_user_namespaces)" ]; then 218 instructions=$( 219 cat <<- EOI 220 ${instructions} 221 # Set user.max_user_namespaces 222 cat <<EOT > /etc/sysctl.d/51-rootless.conf 223 user.max_user_namespaces = 28633 224 EOT 225 sysctl --system 226 EOI 227 ) 228 fi 229 fi 230 231 # instructions: validate subuid/subgid files for current user 232 if ! grep -q "^$USERNAME_ESCAPED:\|^$(id -u):" /etc/subuid 2> /dev/null; then 233 instructions=$( 234 cat <<- EOI 235 ${instructions} 236 # Add subuid entry for ${USERNAME} 237 echo "${USERNAME}:100000:65536" >> /etc/subuid 238 EOI 239 ) 240 fi 241 if ! grep -q "^$USERNAME_ESCAPED:\|^$(id -u):" /etc/subgid 2> /dev/null; then 242 instructions=$( 243 cat <<- EOI 244 ${instructions} 245 # Add subgid entry for ${USERNAME} 246 echo "${USERNAME}:100000:65536" >> /etc/subgid 247 EOI 248 ) 249 fi 250 251 # fail with instructions if requirements are not satisfied. 252 if [ -n "$instructions" ]; then 253 ERROR "Missing system requirements. Run the following commands to" 254 ERROR "install the requirements and run this tool again." 255 if [ -n "$faced_iptables_error" ] && [ -z "$OPT_SKIP_IPTABLES" ]; then 256 ERROR "Alternatively iptables checks can be disabled with --skip-iptables ." 257 fi 258 echo 259 echo "########## BEGIN ##########" 260 echo "sudo sh -eux <<EOF" 261 echo "$instructions" | sed -e '/^$/d' 262 echo "EOF" 263 echo "########## END ##########" 264 echo 265 exit 1 266 fi 267 # TODO: support printing non-essential but recommended instructions: 268 # - sysctl: "net.ipv4.ping_group_range" 269 # - sysctl: "net.ipv4.ip_unprivileged_port_start" 270 # - external binary: slirp4netns 271 # - external binary: fuse-overlayfs 272 273 # check RootlessKit functionality. RootlessKit will print hints if something is still unsatisfied. 274 # (e.g., `kernel.apparmor_restrict_unprivileged_userns` constraint) 275 if ! rootlesskit true; then 276 ERROR "RootlessKit failed, see the error messages and https://rootlesscontaine.rs/getting-started/common/ ." 277 exit 1 278 fi 279 } 280 281 # CLI subcommand: "check" 282 cmd_entrypoint_check() { 283 init 284 # requirements are already checked in init() 285 INFO "Requirements are satisfied" 286 } 287 288 # CLI subcommand: "nsenter" 289 cmd_entrypoint_nsenter() { 290 # No need to call init() 291 pid=$(cat "$XDG_RUNTIME_DIR/dockerd-rootless/child_pid") 292 exec nsenter --no-fork --wd="$(pwd)" --preserve-credentials -m -n -U -t "$pid" -- "$@" 293 } 294 295 show_systemd_error() { 296 n="20" 297 ERROR "Failed to start ${SYSTEMD_UNIT}. Run \`journalctl -n ${n} --no-pager --user --unit ${SYSTEMD_UNIT}\` to show the error log." 298 ERROR "Before retrying installation, you might need to uninstall the current setup: \`$0 uninstall -f ; ${BIN}/rootlesskit rm -rf ${HOME}/.local/share/docker\`" 299 if journalctl -q -n ${n} --user --unit ${SYSTEMD_UNIT} | grep -qF "/run/xtables.lock: Permission denied"; then 300 ERROR "Failure likely related to https://github.com/moby/moby/issues/41230" 301 ERROR "This may work as a workaround: \`sudo dnf install -y policycoreutils-python-utils && sudo semanage permissive -a iptables_t\`" 302 fi 303 } 304 305 # install (systemd) 306 install_systemd() { 307 mkdir -p "${CFG_DIR}/systemd/user" 308 unit_file="${CFG_DIR}/systemd/user/${SYSTEMD_UNIT}" 309 if [ -f "${unit_file}" ]; then 310 WARNING "File already exists, skipping: ${unit_file}" 311 else 312 INFO "Creating ${unit_file}" 313 cat <<- EOT > "${unit_file}" 314 [Unit] 315 Description=Docker Application Container Engine (Rootless) 316 Documentation=https://docs.docker.com/go/rootless/ 317 318 [Service] 319 Environment=PATH=$BIN:/sbin:/usr/sbin:$PATH 320 ExecStart=$BIN/dockerd-rootless.sh $DOCKERD_ROOTLESS_SH_FLAGS 321 ExecReload=/bin/kill -s HUP \$MAINPID 322 TimeoutSec=0 323 RestartSec=2 324 Restart=always 325 StartLimitBurst=3 326 StartLimitInterval=60s 327 LimitNOFILE=infinity 328 LimitNPROC=infinity 329 LimitCORE=infinity 330 TasksMax=infinity 331 Delegate=yes 332 Type=notify 333 NotifyAccess=all 334 KillMode=mixed 335 336 [Install] 337 WantedBy=default.target 338 EOT 339 systemctl --user daemon-reload 340 fi 341 if ! systemctl --user --no-pager status "${SYSTEMD_UNIT}" > /dev/null 2>&1; then 342 INFO "starting systemd service ${SYSTEMD_UNIT}" 343 ( 344 set -x 345 if ! systemctl --user start "${SYSTEMD_UNIT}"; then 346 set +x 347 show_systemd_error 348 exit 1 349 fi 350 sleep 3 351 ) 352 fi 353 ( 354 set -x 355 if ! systemctl --user --no-pager --full status "${SYSTEMD_UNIT}"; then 356 set +x 357 show_systemd_error 358 exit 1 359 fi 360 DOCKER_HOST="unix://$XDG_RUNTIME_DIR/docker.sock" $BIN/docker version 361 systemctl --user enable "${SYSTEMD_UNIT}" 362 ) 363 INFO "Installed ${SYSTEMD_UNIT} successfully." 364 INFO "To control ${SYSTEMD_UNIT}, run: \`systemctl --user (start|stop|restart) ${SYSTEMD_UNIT}\`" 365 INFO "To run ${SYSTEMD_UNIT} on system startup, run: \`sudo loginctl enable-linger ${USERNAME}\`" 366 echo 367 } 368 369 # install (non-systemd) 370 install_nonsystemd() { 371 INFO "systemd not detected, ${DOCKERD_ROOTLESS_SH} needs to be started manually:" 372 echo 373 echo "PATH=$BIN:/sbin:/usr/sbin:\$PATH ${DOCKERD_ROOTLESS_SH} ${DOCKERD_ROOTLESS_SH_FLAGS}" 374 echo 375 } 376 377 cli_ctx_exists() { 378 name="$1" 379 "${BIN}/docker" --context=default context inspect -f "{{.Name}}" "${name}" > /dev/null 2>&1 380 } 381 382 cli_ctx_create() { 383 name="$1" 384 host="$2" 385 description="$3" 386 "${BIN}/docker" --context=default context create "${name}" --docker "host=${host}" --description "${description}" > /dev/null 387 } 388 389 cli_ctx_use() { 390 name="$1" 391 "${BIN}/docker" --context=default context use "${name}" > /dev/null 392 } 393 394 cli_ctx_rm() { 395 name="$1" 396 "${BIN}/docker" --context=default context rm -f "${name}" > /dev/null 397 } 398 399 # CLI subcommand: "install" 400 cmd_entrypoint_install() { 401 init 402 # requirements are already checked in init() 403 if [ -z "$SYSTEMD" ]; then 404 install_nonsystemd 405 else 406 install_systemd 407 fi 408 409 if cli_ctx_exists "${CLI_CONTEXT}"; then 410 INFO "CLI context \"${CLI_CONTEXT}\" already exists" 411 else 412 INFO "Creating CLI context \"${CLI_CONTEXT}\"" 413 cli_ctx_create "${CLI_CONTEXT}" "unix://${XDG_RUNTIME_DIR}/docker.sock" "Rootless mode" 414 fi 415 416 INFO "Using CLI context \"${CLI_CONTEXT}\"" 417 cli_ctx_use "${CLI_CONTEXT}" 418 419 echo 420 INFO "Make sure the following environment variable(s) are set (or add them to ~/.bashrc):" 421 if [ -n "$XDG_RUNTIME_DIR_CREATED" ]; then 422 echo "# WARNING: systemd not found. You have to remove XDG_RUNTIME_DIR manually on every logout." 423 echo "export XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR}" 424 fi 425 echo "export PATH=${BIN}:\$PATH" 426 echo 427 INFO "Some applications may require the following environment variable too:" 428 echo "export DOCKER_HOST=unix://${XDG_RUNTIME_DIR}/docker.sock" 429 echo 430 431 } 432 433 # CLI subcommand: "uninstall" 434 cmd_entrypoint_uninstall() { 435 init 436 # requirements are already checked in init() 437 if [ -z "$SYSTEMD" ]; then 438 INFO "systemd not detected, ${DOCKERD_ROOTLESS_SH} needs to be stopped manually:" 439 else 440 unit_file="${CFG_DIR}/systemd/user/${SYSTEMD_UNIT}" 441 ( 442 set -x 443 systemctl --user stop "${SYSTEMD_UNIT}" 444 ) || : 445 ( 446 set -x 447 systemctl --user disable "${SYSTEMD_UNIT}" 448 ) || : 449 rm -f "${unit_file}" 450 INFO "Uninstalled ${SYSTEMD_UNIT}" 451 fi 452 453 if cli_ctx_exists "${CLI_CONTEXT}"; then 454 cli_ctx_rm "${CLI_CONTEXT}" 455 INFO "Deleted CLI context \"${CLI_CONTEXT}\"" 456 fi 457 unset DOCKER_HOST 458 unset DOCKER_CONTEXT 459 cli_ctx_use "default" 460 INFO 'Configured CLI to use the "default" context.' 461 INFO 462 INFO 'Make sure to unset or update the environment PATH, DOCKER_HOST, and DOCKER_CONTEXT environment variables if you have added them to `~/.bashrc`.' 463 INFO "This uninstallation tool does NOT remove Docker binaries and data." 464 INFO "To remove data, run: \`$BIN/rootlesskit rm -rf $HOME/.local/share/docker\`" 465 } 466 467 # text for --help 468 usage() { 469 echo "Usage: ${ARG0} [OPTIONS] COMMAND" 470 echo 471 echo "A setup tool for Rootless Docker (${DOCKERD_ROOTLESS_SH})." 472 echo 473 echo "Documentation: https://docs.docker.com/go/rootless/" 474 echo 475 echo "Options:" 476 echo " -f, --force Ignore rootful Docker (/var/run/docker.sock)" 477 echo " --skip-iptables Ignore missing iptables" 478 echo 479 echo "Commands:" 480 echo " check Check prerequisites" 481 echo " nsenter Enter into RootlessKit namespaces (mostly for debugging)" 482 echo " install Install systemd unit (if systemd is available) and show how to manage the service" 483 echo " uninstall Uninstall systemd unit" 484 } 485 486 # parse CLI args 487 if ! args="$(getopt -o hf --long help,force,skip-iptables -n "$ARG0" -- "$@")"; then 488 usage 489 exit 1 490 fi 491 eval set -- "$args" 492 while [ "$#" -gt 0 ]; do 493 arg="$1" 494 shift 495 case "$arg" in 496 -h | --help) 497 usage 498 exit 0 499 ;; 500 -f | --force) 501 OPT_FORCE=1 502 ;; 503 --skip-iptables) 504 OPT_SKIP_IPTABLES=1 505 ;; 506 --) 507 break 508 ;; 509 *) 510 # XXX this means we missed something in our "getopt" arguments above! 511 ERROR "Scripting error, unknown argument '$arg' when parsing script arguments." 512 exit 1 513 ;; 514 esac 515 done 516 517 command="${1:-}" 518 if [ -z "$command" ]; then 519 ERROR "No command was specified. Run with --help to see the usage. Maybe you want to run \`$ARG0 install\`?" 520 exit 1 521 fi 522 523 if ! command -v "cmd_entrypoint_${command}" > /dev/null 2>&1; then 524 ERROR "Unknown command: ${command}. Run with --help to see the usage." 525 exit 1 526 fi 527 528 # main 529 "cmd_entrypoint_${command}" "$@"