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