dockerd-rootless-setuptool.sh 14 KB

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