alpine-make-rootfs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. #!/bin/sh
  2. # vim: set ts=4 sw=4:
  3. #---help---
  4. # Usage: alpine-make-rootfs [options] [--] <dest> [<script> [<script-opts...>]]
  5. #
  6. # This script creates Alpine Linux rootfs for containers. It must be run as
  7. # root - to create files with correct permissions and use chroot (optional).
  8. # If $APK is not available on the host system, then static apk-tools
  9. # specified by $APK_TOOLS_URI is downloaded and used.
  10. #
  11. # Arguments:
  12. # <dest> Path where to write the rootfs. It may be:
  13. # - path with suffix .tar, .tar.bz2, .tbz, .tar.gz, .tgz,
  14. # or tar.xz to create a TAR archive;
  15. # - other path to keep rootfs as a directory;
  16. # - or "-" to dump TAR archive (w/o compression) to STDOUT.
  17. #
  18. # <script> Path of script to execute after installing base system in
  19. # the prepared rootfs and before clean-up. Use "-" to read
  20. # the script from STDIN; if it doesn't start with a shebang,
  21. # "#!/bin/sh -e" is prepended.
  22. #
  23. # <script-opts> Arguments to pass to the script.
  24. #
  25. # Options and Environment Variables:
  26. # -b --branch ALPINE_BRANCH Alpine branch to install; used only when
  27. # --repositories-file is not specified. Default is
  28. # latest-stable.
  29. #
  30. # -s --fs-skel-dir FS_SKEL_DIR Path of directory which content to recursively copy
  31. # (using rsync) into / of the rootfs.
  32. #
  33. # --fs-skel-chown FS_SKEL_CHOWN Force all files from FS_SKEL_DIR to be owned by the
  34. # specified USER:GROUP.
  35. #
  36. # --keys-dir KEYS_DIR Path of directory with Alpine keys to copy into
  37. # the rootfs. Default is /etc/apk/keys. If does not exist,
  38. # keys for x86_64 embedded in this script will be used.
  39. #
  40. # -m --mirror-uri ALPINE_MIRROR URI of the Aports mirror to fetch packages; used only
  41. # when --repositories-file is not specified. Default is
  42. # http://dl-cdn.alpinelinux.org/alpine.
  43. #
  44. # -C --no-cleanup (CLEANUP) Don't umount and remove temporary directories when done.
  45. #
  46. # --no-default-pkgs (DEFAULT_PKGS) Don't install the default base packages (alpine-baselayout
  47. # busybox busybox-suid musl-utils), i.e. only the packages
  48. # specified in --packages will be installed. Use only if
  49. # you know what are you doing!
  50. #
  51. # -p --packages PACKAGES Additional packages to install into the rootfs.
  52. #
  53. # -r --repositories-file REPOS_FILE Path of repositories file to copy into the rootfs.
  54. # If not specified, a repositories file will be created with
  55. # Alpine's main and community repositories on --mirror-uri.
  56. #
  57. # -c --script-chroot (SCRIPT_CHROOT) Bind <script>'s directory (or CWD if read from STDIN) at
  58. # /mnt inside the rootfs dir and chroot into the rootfs
  59. # before executing <script>. Otherwise <script> is executed
  60. # in the current directory and $ROOTFS variable points to
  61. # the rootfs directory.
  62. #
  63. # -d --temp-dir TEMP_DIR Path where to create a temporary directory; used for
  64. # downloading apk-tools when not available on the host
  65. # sytem or for rootfs when <dest> is "-" (i.e. STDOUT).
  66. # This path must not exist! Defaults to using `mktemp -d`.
  67. #
  68. # -t --timezone TIMEZONE Timezone to set (e.g. Europe/Prague). Default is to leave
  69. # timezone UTC.
  70. #
  71. # -h --help Show this help message and exit.
  72. #
  73. # -v --version Print version and exit.
  74. #
  75. # APK APK command to use. Default is "apk".
  76. #
  77. # APK_OPTS Options to pass into apk on each execution.
  78. # Default is "--no-progress".
  79. #
  80. # APK_TOOLS_URI URL of apk-tools binary to download if $APK is not found
  81. # on the host system. Default is x86_64 apk.static from
  82. # https://gitlab.alpinelinux.org/alpine/apk-tools/-/packages.
  83. #
  84. # APK_TOOLS_SHA256 SHA-256 checksum of $APK_TOOLS_URI.
  85. #
  86. # Each option can be also provided by environment variable. If both option and
  87. # variable is specified and the option accepts only one argument, then the
  88. # option takes precedence.
  89. #
  90. # https://github.com/alpinelinux/alpine-make-rootfs
  91. #---help---
  92. set -eu
  93. readonly PROGNAME='alpine-make-rootfs'
  94. readonly VERSION='0.7.1'
  95. # Base Alpine packages to install in rootfs.
  96. readonly ALPINE_BASE_PKGS='alpine-baselayout busybox busybox-suid musl-utils'
  97. # Alpine APK keys for verification of packages for x86_64.
  98. readonly ALPINE_KEYS='
  99. alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1yHJxQgsHQREclQu4Ohe\nqxTxd1tHcNnvnQTu/UrTky8wWvgXT+jpveroeWWnzmsYlDI93eLI2ORakxb3gA2O\nQ0Ry4ws8vhaxLQGC74uQR5+/yYrLuTKydFzuPaS1dK19qJPXB8GMdmFOijnXX4SA\njixuHLe1WW7kZVtjL7nufvpXkWBGjsfrvskdNA/5MfxAeBbqPgaq0QMEfxMAn6/R\nL5kNepi/Vr4S39Xvf2DzWkTLEK8pcnjNkt9/aafhWqFVW7m3HCAII6h/qlQNQKSo\nGuH34Q8GsFG30izUENV9avY7hSLq7nggsvknlNBZtFUcmGoQrtx3FmyYsIC8/R+B\nywIDAQAB
  100. alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwlzMkl7b5PBdfMzGdCT0\ncGloRr5xGgVmsdq5EtJvFkFAiN8Ac9MCFy/vAFmS8/7ZaGOXoCDWbYVLTLOO2qtX\nyHRl+7fJVh2N6qrDDFPmdgCi8NaE+3rITWXGrrQ1spJ0B6HIzTDNEjRKnD4xyg4j\ng01FMcJTU6E+V2JBY45CKN9dWr1JDM/nei/Pf0byBJlMp/mSSfjodykmz4Oe13xB\nCa1WTwgFykKYthoLGYrmo+LKIGpMoeEbY1kuUe04UiDe47l6Oggwnl+8XD1MeRWY\nsWgj8sF4dTcSfCMavK4zHRFFQbGp/YFJ/Ww6U9lA3Vq0wyEI6MCMQnoSMFwrbgZw\nwwIDAQAB
  101. alpine-devel@lists.alpinelinux.org-6165ee59.rsa.pub:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAutQkua2CAig4VFSJ7v54\nALyu/J1WB3oni7qwCZD3veURw7HxpNAj9hR+S5N/pNeZgubQvJWyaPuQDm7PTs1+\ntFGiYNfAsiibX6Rv0wci3M+z2XEVAeR9Vzg6v4qoofDyoTbovn2LztaNEjTkB+oK\ntlvpNhg1zhou0jDVYFniEXvzjckxswHVb8cT0OMTKHALyLPrPOJzVtM9C1ew2Nnc\n3848xLiApMu3NBk0JqfcS3Bo5Y2b1FRVBvdt+2gFoKZix1MnZdAEZ8xQzL/a0YS5\nHd0wj5+EEKHfOd3A75uPa/WQmA+o0cBFfrzm69QDcSJSwGpzWrD1ScH3AK8nWvoj\nv7e9gukK/9yl1b4fQQ00vttwJPSgm9EnfPHLAtgXkRloI27H6/PuLoNvSAMQwuCD\nhQRlyGLPBETKkHeodfLoULjhDi1K2gKJTMhtbnUcAA7nEphkMhPWkBpgFdrH+5z4\nLxy+3ek0cqcI7K68EtrffU8jtUj9LFTUC8dERaIBs7NgQ/LfDbDfGh9g6qVj1hZl\nk9aaIPTm/xsi8v3u+0qaq7KzIBc9s59JOoA8TlpOaYdVgSQhHHLBaahOuAigH+VI\nisbC9vmqsThF2QdDtQt37keuqoda2E6sL7PUvIyVXDRfwX7uMDjlzTxHTymvq2Ck\nhtBqojBnThmjJQFgZXocHG8CAwEAAQ==
  102. '
  103. # List of directories to remove when empty.
  104. readonly UNNECESSARY_DIRS='
  105. /home /media/cdrom /media/floppy /media/usb /mnt /srv /usr/local/bin
  106. /usr/local/lib /usr/local/share
  107. '
  108. # An opaque string used to detect changes in resolv.conf.
  109. readonly RESOLVCONF_MARK="### created by $PROGNAME ###"
  110. # Name used as a "virtual package" for temporarily installed packages.
  111. readonly VIRTUAL_PKG=".make-$PROGNAME"
  112. : ${APK:="apk"}
  113. : ${APK_OPTS:="--no-progress"}
  114. : ${APK_TOOLS_URI:="https://gitlab.alpinelinux.org/api/v4/projects/5/packages/generic/v2.14.8/x86_64/apk.static"}
  115. : ${APK_TOOLS_SHA256:="041139e57010b8b79e0011854c2116dbad7d2f5679460c40a87796b53fa87bc8"}
  116. # Set pipefail if supported.
  117. if ( set -o pipefail 2>/dev/null ); then
  118. set -o pipefail
  119. fi
  120. # For compatibility with systems that does not have "realpath" command.
  121. if ! command -v realpath >/dev/null; then
  122. alias realpath='readlink -f'
  123. fi
  124. die() {
  125. printf '\033[1;31mERROR:\033[0m %s\n' "$@" >&2 # bold red
  126. exit 1
  127. }
  128. einfo() {
  129. printf '\n\033[1;36m> %s\033[0m\n' "$@" >&2 # bold cyan
  130. }
  131. # Prints help and exists with the specified status.
  132. help() {
  133. sed -En '/^#---help---/,/^#---help---/p' "$0" | sed -E 's/^# ?//; 1d;$d;'
  134. exit ${1:-0}
  135. }
  136. # Cleans the host system. This function is executed before exiting the script.
  137. cleanup() {
  138. set +eu
  139. trap '' EXIT HUP INT TERM # unset trap to avoid loop
  140. if [ "$INSTALL_HOST_PKGS" = yes ] && _apk info --quiet --installed $VIRTUAL_PKG; then
  141. _apk del $VIRTUAL_PKG >&2
  142. fi
  143. if [ -d "$TEMP_DIR" ]; then
  144. rm -Rf "$TEMP_DIR"
  145. fi
  146. if [ -d "$rootfs" ]; then
  147. umount_recursively "$rootfs" \
  148. || die "Failed to unmount mounts inside $rootfs!"
  149. [ "$rootfs" = "$ROOTFS_DEST" ] || rm -Rf "$rootfs"
  150. fi
  151. }
  152. _apk() {
  153. "$APK" $APK_OPTS "$@"
  154. }
  155. # Writes Alpine APK keys embedded in this script into directory $1.
  156. dump_alpine_keys() {
  157. local dest_dir="$1"
  158. local content file line
  159. mkdir -p "$dest_dir"
  160. for line in $ALPINE_KEYS; do
  161. file=${line%%:*}
  162. content=${line#*:}
  163. printf -- "-----BEGIN PUBLIC KEY-----\n$content\n-----END PUBLIC KEY-----\n" \
  164. > "$dest_dir/$file"
  165. done
  166. }
  167. # Binds the directory $1 at the mountpoint $2 and sets propagation to private.
  168. mount_bind() {
  169. mkdir -p "$2"
  170. mount --bind "$1" "$2"
  171. mount --make-private "$2"
  172. }
  173. # Prepares chroot at the specified path.
  174. prepare_chroot() {
  175. local dest="$1"
  176. mkdir -p "$dest"/proc
  177. mount -t proc none "$dest"/proc
  178. mount_bind /dev "$dest"/dev
  179. mount_bind /sys "$dest"/sys
  180. install -D -m 644 /etc/resolv.conf "$dest"/etc/resolv.conf
  181. echo "$RESOLVCONF_MARK" >> "$dest"/etc/resolv.conf
  182. }
  183. # Sets up timezone $1 in Alpine rootfs.
  184. setup_timezone() {
  185. local timezone="$1"
  186. local rootfs="${2:-}"
  187. _apk add --root "$rootfs" tzdata
  188. install -D "$rootfs"/usr/share/zoneinfo/$timezone \
  189. "$rootfs"/etc/zoneinfo/$timezone
  190. ln -sf zoneinfo/$timezone "$rootfs"/etc/localtime
  191. _apk del --root "$rootfs" tzdata
  192. }
  193. # Unmounts all filesystems under the directory tree $1 (must be absolute path).
  194. umount_recursively() {
  195. local mount_point="$(realpath "$1")"
  196. cat /proc/mounts \
  197. | cut -d ' ' -f 2 \
  198. | { grep "^$mount_point/" || true; } \
  199. | sort -r \
  200. | xargs -r -n 1 umount
  201. }
  202. # Downloads the specified file using wget and checks checksum.
  203. wgets() (
  204. local url="$1"
  205. local sha256="$2"
  206. local dest="${3:-.}"
  207. cd "$dest" \
  208. && wget -T 10 --no-verbose "$url" \
  209. && echo "$sha256 ${url##*/}" | sha256sum -c
  210. )
  211. # Writes STDIN into file $1 and sets it executable bit. If the content does not
  212. # start with a shebang, prepends "#!/bin/sh -e" before the first line.
  213. write_script() {
  214. local filename="$1"
  215. cat > "$filename.tmp"
  216. if ! grep -q -m 1 '^#!' "$filename.tmp"; then
  217. echo "#!/bin/sh -e" > "$filename"
  218. fi
  219. cat "$filename.tmp" >> "$filename"
  220. rm "$filename.tmp"
  221. chmod +x "$filename"
  222. }
  223. #============================= M a i n ==============================#
  224. opts=$(getopt -n $PROGNAME -o b:m:Cp:r:s:cd:t:hV \
  225. -l branch:,fs-skel-chown:,fs-skel-dir:,keys-dir:,mirror-uri:,no-cleanup,no-default-pkgs,packages:,repositories-file:,script-chroot,temp-dir:,timezone:,help,version \
  226. -- "$@") || help 1 >&2
  227. eval set -- "$opts"
  228. while [ $# -gt 0 ]; do
  229. n=2
  230. case "$1" in
  231. -b | --branch) ALPINE_BRANCH="$2";;
  232. -s | --fs-skel-dir) FS_SKEL_DIR="$2";;
  233. --fs-skel-chown) FS_SKEL_CHOWN="$2";;
  234. --keys-dir) KEYS_DIR="$2";;
  235. -m | --mirror-uri) ALPINE_MIRROR="$2";;
  236. -C | --no-cleanup) CLEANUP='no'; n=1;;
  237. --no-default-pkgs) DEFAULT_PKGS='no'; n=1;;
  238. -p | --packages) PACKAGES="${PACKAGES:-} $2";;
  239. -r | --repositories-file) REPOS_FILE="$2";;
  240. -c | --script-chroot) SCRIPT_CHROOT='yes'; n=1;;
  241. -d | --temp-dir) TEMP_DIR="$2";;
  242. -t | --timezone) TIMEZONE="$2";;
  243. -h | --help) help 0;;
  244. -V | --version) echo "$PROGNAME $VERSION"; exit 0;;
  245. --) shift; break;;
  246. esac
  247. shift $n
  248. done
  249. [ $# -ne 0 ] || help 1 >&2
  250. ROOTFS_DEST="$1"; shift
  251. SCRIPT=
  252. [ $# -eq 0 ] || { SCRIPT="$1"; shift; }
  253. [ "$(id -u)" -eq 0 ] || die 'This script must be run as root!'
  254. [ ! -e "${TEMP_DIR:-}" ] || die "Temp path $TEMP_DIR must not exist!"
  255. : ${ALPINE_BRANCH:="latest-stable"}
  256. : ${ALPINE_MIRROR:="http://dl-cdn.alpinelinux.org/alpine"}
  257. : ${CLEANUP:="yes"}
  258. : ${DEFAULT_PKGS:="yes"}
  259. : ${FS_SKEL_CHOWN:=}
  260. : ${FS_SKEL_DIR:=}
  261. : ${KEYS_DIR:="/etc/apk/keys"}
  262. : ${PACKAGES:=}
  263. : ${REPOS_FILE:=}
  264. : ${SCRIPT_CHROOT:="no"}
  265. : ${TEMP_DIR:="$(mktemp -d /tmp/$PROGNAME.XXXXXX)"}
  266. : ${TIMEZONE:=}
  267. case "$ALPINE_BRANCH" in
  268. [0-9]*) ALPINE_BRANCH="v$ALPINE_BRANCH";;
  269. esac
  270. host_pkgs=''
  271. case "$ROOTFS_DEST" in
  272. *.tar.bz2 | *.tbz) tar_opts='-cj';;
  273. *.tar.gz | *.tgz) tar_opts='-cz';;
  274. *.tar.xz) tar_opts='-cJ'; host_pkgs="$host_pkgs xz";;
  275. *.tar | -) tar_opts='-c';;
  276. *) tar_opts='';;
  277. esac
  278. [ -z "$FS_SKEL_DIR" ] || host_pkgs="$host_pkgs rsync"
  279. [ "$PACKAGES" ] || [ "$DEFAULT_PKGS" = 'yes' ] || \
  280. die 'No packages specified to be installed!'
  281. rootfs="$ROOTFS_DEST"
  282. if [ "$ROOTFS_DEST" = '-' ]; then
  283. rootfs="$TEMP_DIR/rootfs"
  284. elif [ "$tar_opts" ]; then
  285. rootfs="${rootfs%.*}"
  286. rootfs="${rootfs%.tar}"
  287. fi
  288. script_file="$SCRIPT"
  289. if [ "$SCRIPT" = '-' ]; then
  290. script_file="$TEMP_DIR/.setup.sh"
  291. write_script "$script_file"
  292. fi
  293. if [ "$script_file" ]; then
  294. script_file=$(realpath "$script_file")
  295. fi
  296. if [ -f /etc/alpine-release ]; then
  297. : ${INSTALL_HOST_PKGS:="yes"}
  298. else
  299. : ${INSTALL_HOST_PKGS:="no"}
  300. fi
  301. [ "$CLEANUP" = no ] || trap cleanup EXIT HUP INT TERM
  302. #-----------------------------------------------------------------------
  303. if [ "$INSTALL_HOST_PKGS" = yes ] && [ "$host_pkgs" ]; then
  304. einfo "Installing $host_pkgs on host system"
  305. _apk add -t $VIRTUAL_PKG $host_pkgs >&2
  306. fi
  307. #-----------------------------------------------------------------------
  308. if ! command -v "$APK" >/dev/null; then
  309. einfo "$APK not found, downloading static apk-tools"
  310. wgets "$APK_TOOLS_URI" "$APK_TOOLS_SHA256" "$TEMP_DIR"
  311. APK="$TEMP_DIR/apk.static"
  312. chmod +x "$APK"
  313. fi
  314. #-----------------------------------------------------------------------
  315. einfo 'Installing system'
  316. mkdir -p "$rootfs"/etc/apk/keys
  317. if [ -f "$REPOS_FILE" ]; then
  318. install -m 644 "$REPOS_FILE" "$rootfs"/etc/apk/repositories
  319. else
  320. cat > "$rootfs"/etc/apk/repositories <<-EOF
  321. $ALPINE_MIRROR/$ALPINE_BRANCH/main
  322. $ALPINE_MIRROR/$ALPINE_BRANCH/community
  323. EOF
  324. fi
  325. if [ -d "$KEYS_DIR" ]; then
  326. cp "$KEYS_DIR"/* "$rootfs"/etc/apk/keys/
  327. else
  328. dump_alpine_keys "$rootfs"/etc/apk/keys/
  329. fi
  330. if [ "$DEFAULT_PKGS" = 'yes' ]; then
  331. _apk add --root "$rootfs" --initdb $ALPINE_BASE_PKGS >&2
  332. fi
  333. _apk add --root "$rootfs" --initdb $PACKAGES >&2
  334. if ! [ -f "$rootfs"/etc/alpine-release ]; then
  335. if _apk info --root "$rootfs" --quiet alpine-release >/dev/null; then
  336. _apk add --root "$rootfs" alpine-release
  337. else
  338. # In Alpine <3.17, this package contains /etc/os-release,
  339. # /etc/alpine-release and /etc/issue, but we don't wanna install all
  340. # its dependencies (e.g. openrc).
  341. _apk fetch --root "$rootfs" --stdout alpine-base \
  342. | tar -xz -C "$rootfs" etc >&2
  343. fi
  344. fi
  345. # Disable root log in without password.
  346. sed -i 's/^root::/root:*:/' "$rootfs"/etc/shadow
  347. [ -e "$rootfs"/var/run ] || ln -s /run "$rootfs"/var/run
  348. #-----------------------------------------------------------------------
  349. if [ "$TIMEZONE" ]; then
  350. einfo "Setting timezone $TIMEZONE"
  351. setup_timezone "$TIMEZONE" "$rootfs" >&2
  352. fi
  353. #-----------------------------------------------------------------------
  354. if [ "$FS_SKEL_DIR" ]; then
  355. einfo "Copying content of $FS_SKEL_DIR into rootfs"
  356. [ "$FS_SKEL_CHOWN" ] \
  357. && rsync_opts="--chown $FS_SKEL_CHOWN" \
  358. || rsync_opts='--numeric-ids'
  359. rsync --archive --info=NAME2 --whole-file $rsync_opts "$FS_SKEL_DIR"/ "$rootfs"/ >&2
  360. # rsync may modify perms of the rootfs dir itself, so make sure it's correct.
  361. install -d -m 0755 -o root -g root "$rootfs"
  362. fi
  363. #-----------------------------------------------------------------------
  364. if [ "$SCRIPT" ]; then
  365. script_name="${script_file##*/}"
  366. if [ "$SCRIPT_CHROOT" = 'no' ]; then
  367. einfo "Executing script: $script_name $*"
  368. ROOTFS="$rootfs" "$script_file" "$@" >&2 || die 'Script failed'
  369. else
  370. einfo 'Preparing chroot'
  371. _apk add --root "$rootfs" -t "$VIRTUAL_PKG" apk-tools >&2
  372. prepare_chroot "$rootfs"
  373. if [ "$SCRIPT" = '-' ]; then
  374. cp "$script_file" "$rootfs/$script_name"
  375. bind_dir="$(pwd)"
  376. script_file2="/$script_name"
  377. else
  378. bind_dir="$(dirname "$script_file")"
  379. script_file2="./$script_name"
  380. fi
  381. echo "Mounting $bind_dir to /mnt inside chroot" >&2
  382. mount_bind "$bind_dir" "$rootfs"/mnt
  383. einfo "Executing script in chroot: $script_name $*"
  384. chroot "$rootfs" \
  385. /bin/sh -c "cd /mnt && $script_file2 \"\$@\"" -- "$@" >&2 \
  386. || die 'Script failed'
  387. [ "$SCRIPT" = '-' ] && rm -f "$rootfs/$script_name"
  388. umount_recursively "$rootfs"
  389. fi
  390. fi
  391. #-----------------------------------------------------------------------
  392. einfo 'Cleaning-up rootfs'
  393. if _apk info --root "$rootfs" --quiet --installed "$VIRTUAL_PKG"; then
  394. _apk del --root "$rootfs" --purge "$VIRTUAL_PKG" >&2
  395. fi
  396. if grep -qw "$RESOLVCONF_MARK" "$rootfs"/etc/resolv.conf 2>/dev/null; then
  397. rm "$rootfs"/etc/resolv.conf
  398. fi
  399. rm -Rf "$rootfs"/dev/*
  400. if [ -f "$rootfs"/sbin/apk ]; then
  401. rm -Rf "$rootfs"/var/cache/apk/*
  402. else
  403. rm -Rf "$rootfs"/etc/apk "$rootfs"/lib/apk "$rootfs"/var/cache/apk
  404. fi
  405. for dir in $UNNECESSARY_DIRS; do
  406. rmdir -p "$rootfs$dir" 2>/dev/null || true
  407. done
  408. #-----------------------------------------------------------------------
  409. if [ "$tar_opts" ]; then
  410. einfo 'Creating rootfs archive'
  411. tar -C "$rootfs" $tar_opts --numeric-owner -f "$ROOTFS_DEST" .
  412. if [ -f "$ROOTFS_DEST" ] && [ "${SUDO_UID:-}" ] && [ "${SUDO_GID:-}" ]; then
  413. chown "$SUDO_UID:$SUDO_GID" "$ROOTFS_DEST"
  414. fi
  415. ls -la "$ROOTFS_DEST" >&2
  416. fi