dockerd-rootless-setuptool.sh 13 KB

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