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