diff options
| author | Mhd Sulhan <ms@kilabit.info> | 2015-08-31 02:16:31 +0700 |
|---|---|---|
| committer | Mhd Sulhan <ms@kilabit.info> | 2015-08-31 02:16:31 +0700 |
| commit | ed8328cb365d14d468e17e5088543e1cb3da4cb0 (patch) | |
| tree | 5d572e07d17848aeb92b33ac0cd4973b425f6664 | |
| download | arch-docker-ed8328cb365d14d468e17e5088543e1cb3da4cb0.tar.xz | |
Script to create docker images based on Arch Linux on x86_64.
| -rw-r--r-- | README.md | 35 | ||||
| -rwxr-xr-x | arch-base/bootstrap.sh | 90 | ||||
| -rwxr-xr-x | arch-base/create-image.sh | 6 | ||||
| -rwxr-xr-x | arch-base/create-rootfs.sh | 34 | ||||
| -rw-r--r-- | arch-base/pacman.conf | 99 | ||||
| -rwxr-xr-x | arch-base/pacstrap.sh | 380 |
6 files changed, 644 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..4e4c3ad --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# arch-docker + +Script to create docker images based on Arch Linux on x86_64. + +## Base Image + +Size: 114M. + +Base image generated using modified `pacstrap` script from +`arch-install-script` package. This image use or containts, + +* `arch-base` as hostname, +* `UTC` as timezone, +* `en_GB.UTF-8` as locale, +* `ansi`, `cygwin`, `linux`, `screen-256color`, `vt100`, +`vt220`, and `xterm` in terminfo; +* explicitly installed packages are `bash`, `coreutils`, `ca-certificates` +, and `pacman`. + +To generate rootfs, execute + +``` +# ./create-rootfs.sh +``` + +This will create directory `arch-rootfs`, mounted using `tmpfs`, in the current +directory. You can modified the rootfs, and import back to docker using, + +``` +# ./create-image.sh +``` + +This will create and import image name as `sulhan/arch-base:latest`. + +NOTE: remember to change the image name in `create-image.sh` if needed. diff --git a/arch-base/bootstrap.sh b/arch-base/bootstrap.sh new file mode 100755 index 0000000..971f728 --- /dev/null +++ b/arch-base/bootstrap.sh @@ -0,0 +1,90 @@ +#!/bin/bash + +## (1) set hostname. +## (2) set timezone to UTC. +## (3) set locale to en_GB.UTF-8. +## (4) generate locale. +## (5) set locale preferences. +## (6) stripping binaries. +## (7) remove unneeded files. +## (8) remove unneeded packages. + +shopt -s extglob + +## (0) +export LANG=C.UTF-8 + +## (1) +echo "==> set hostname." +echo "arch-base" > /etc/hostname + +## (2) +echo "==> set timezone to UTC." +cp /usr/share/zoneinfo/UTC /etc/localtime + +## (3) +echo "==> set locale to en_GB.UTF-8." +echo "en_GB.UTF-8 UTF-8" > /etc/locale.gen + +## (4) +echo "==> generate locale." +/usr/bin/locale-gen + +## (5) +echo "==> set locale preferences." +echo "LANG=en_GB.UTF-8" > "$rootfs"/etc/locale.conf +echo "LC_MESSAGES=C.UTF-8" >> "$rootfs"/etc/locale.conf + +## (6) +echo "==> striping binaries." +cd / + +find usr/bin -type f \( -perm -0100 \) -print | + xargs file | + sed -n '/executable .*not stripped/s/: TAB .*//p' | + xargs -rt strip --strip-unneeded + +find usr/lib -type f \( -perm -0100 \) -print | + xargs file | + sed -n '/executable .*not stripped/s/: TAB .*//p' | + xargs -rt strip --strip-unneeded + +## (7) +echo "==> cleaning." + +cd / +rm -r usr/share/perl5/* +rm -r usr/share/locale/* +rm -r usr/share/man/* + +find usr/share/i18n/charmaps/ \! -name "UTF-8.gz" -delete +find usr/share/i18n/locales/ \! -name "en_GB" -delete + +find usr/share/terminfo/ \ + \! -name ansi \ + \! -name cygwin \ + \! -name linux \ + \! -name screen-256color \ + \! -name vt100 \ + \! -name vt220 \ + \! -name xterm \ + -delete + +rm -r usr/share/texinfo/* +rm -r usr/share/zoneinfo/* +rm -r usr/share/doc/* +rm -r usr/share/iana-etc/* +rm -r usr/share/info/* +rm -r usr/share/gtk-doc/* +rm -r usr/share/readline/* +rm -r usr/share/licenses/* + +rm -r usr/include/* + + +## (8) remove unneeded packages. +pacman -Rs --noconfirm sed binutils db +pacman -Rdd --noconfirm perl + +rm -r var/cache/pacman/pkg/* +rm -r var/log/* diff --git a/arch-base/create-image.sh b/arch-base/create-image.sh new file mode 100755 index 0000000..c6b97d8 --- /dev/null +++ b/arch-base/create-image.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +ROOTFS=arch-rootfs + +sudo tar --numeric-owner --xattrs --acls -C "$ROOTFS" -c . | + docker import - sulhan/arch-base:latest diff --git a/arch-base/create-rootfs.sh b/arch-base/create-rootfs.sh new file mode 100755 index 0000000..4921af7 --- /dev/null +++ b/arch-base/create-rootfs.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +## +## (1) set root fs. +## (2) create root fs directory. +## (3) mount root fs as tmpfs. +## (4) run pacstrap. +## (5) copy bootstrap script and default pacman config. +## (6) run bootstrap script in new root fs. +## (7) cleaning. +## + +## (1) +export ROOTFS=arch-rootfs + +## (2) +mkdir -p "$ROOTFS" + +## (3) +umount -R "$ROOTFS" +mount -t tmpfs -o size=400M tmpfs "$ROOTFS" + +## (4) +./pacstrap.sh -c -d "$ROOTFS" bash coreutils ca-certificates pacman sed binutils + +## (5) +cp ./pacman.conf "$ROOTFS/etc/" +cp ./bootstrap.sh "$ROOTFS/" + +## (6) +arch-chroot "$ROOTFS" /bin/sh -c /bootstrap.sh + +## (7) +rm "$ROOTFS"/bootstrap.sh diff --git a/arch-base/pacman.conf b/arch-base/pacman.conf new file mode 100644 index 0000000..3af75e5 --- /dev/null +++ b/arch-base/pacman.conf @@ -0,0 +1,99 @@ +# +# /etc/pacman.conf +# +# See the pacman.conf(5) manpage for option and repository directives + +# +# GENERAL OPTIONS +# +[options] +# The following paths are commented out with their default values listed. +# If you wish to use different paths, uncomment and update the paths. +#RootDir = / +#DBPath = /var/lib/pacman/ +#CacheDir = /var/cache/pacman/pkg/ +#LogFile = /var/log/pacman.log +#GPGDir = /etc/pacman.d/gnupg/ +HoldPkg = pacman glibc +#XferCommand = /usr/bin/curl -C - -f %u > %o +#XferCommand = /usr/bin/wget --passive-ftp -c -O %o %u +#CleanMethod = KeepInstalled +#UseDelta = 0.7 +Architecture = auto + +# Pacman won't upgrade packages listed in IgnorePkg and members of IgnoreGroup +#IgnorePkg = +#IgnoreGroup = + +#NoUpgrade = +#NoExtract = + +# Misc options +#UseSyslog +Color +TotalDownload +CheckSpace +VerbosePkgLists + +# By default, pacman accepts packages signed by keys that its local keyring +# trusts (see pacman-key and its man page), as well as unsigned packages. +SigLevel = Required DatabaseOptional +LocalFileSigLevel = Optional +#RemoteFileSigLevel = Required + +# NOTE: You must run `pacman-key --init` before first using pacman; the local +# keyring can then be populated with the keys of all official Arch Linux +# packagers with `pacman-key --populate archlinux`. + +# +# REPOSITORIES +# - can be defined here or included from another file +# - pacman will search repositories in the order defined here +# - local/custom mirrors can be added here or in separate files +# - repositories listed first will take precedence when packages +# have identical names, regardless of version number +# - URLs will have $repo replaced by the name of the current repo +# - URLs will have $arch replaced by the name of the architecture +# +# Repository entries are of the format: +# [repo-name] +# Server = ServerName +# Include = IncludePath +# +# The header [repo-name] is crucial - it must be present and +# uncommented to enable the repo. +# + +# The testing repositories are disabled by default. To enable, uncomment the +# repo name header and Include lines. You can add preferred servers immediately +# after the header, and they will be used before the default mirrors. + +#[testing] +#Include = /etc/pacman.d/mirrorlist + +[core] +Include = /etc/pacman.d/mirrorlist + +[extra] +Include = /etc/pacman.d/mirrorlist + +#[community-testing] +#Include = /etc/pacman.d/mirrorlist + +[community] +Include = /etc/pacman.d/mirrorlist + +# If you want to run 32 bit applications on your x86_64 system, +# enable the multilib repositories as required here. + +#[multilib-testing] +#Include = /etc/pacman.d/mirrorlist + +#[multilib] +#Include = /etc/pacman.d/mirrorlist + +# An example of a custom package repository. See the pacman manpage for +# tips on creating your own repositories. +#[custom] +#SigLevel = Optional TrustAll +#Server = file:///home/custompkgs diff --git a/arch-base/pacstrap.sh b/arch-base/pacstrap.sh new file mode 100755 index 0000000..280eafa --- /dev/null +++ b/arch-base/pacstrap.sh @@ -0,0 +1,380 @@ +#!/bin/bash + +# +# Assumptions: +# 1) User has partitioned, formatted, and mounted partitions on /mnt +# 2) Network is functional +# 3) Arguments passed to the script are valid pacman targets +# 4) A valid mirror appears in /etc/pacman.d/mirrorlist +# + +shopt -s extglob + +# generated from util-linux source: libmount/src/utils.c +declare -A pseudofs_types=([anon_inodefs]=1 + [autofs]=1 + [bdev]=1 + [binfmt_misc]=1 + [cgroup]=1 + [configfs]=1 + [cpuset]=1 + [debugfs]=1 + [devfs]=1 + [devpts]=1 + [devtmpfs]=1 + [dlmfs]=1 + [fuse.gvfs-fuse-daemon]=1 + [fusectl]=1 + [hugetlbfs]=1 + [mqueue]=1 + [nfsd]=1 + [none]=1 + [pipefs]=1 + [proc]=1 + [pstore]=1 + [ramfs]=1 + [rootfs]=1 + [rpc_pipefs]=1 + [securityfs]=1 + [sockfs]=1 + [spufs]=1 + [sysfs]=1 + [tmpfs]=1) + +# generated from: pkgfile -vbr '/fsck\..+' | awk -F. '{ print $NF }' | sort +declare -A fsck_types=([cramfs]=1 + [exfat]=1 + [ext2]=1 + [ext3]=1 + [ext4]=1 + [ext4dev]=1 + [jfs]=1 + [minix]=1 + [msdos]=1 + [reiserfs]=1 + [vfat]=1 + [xfs]=1) + +out() { printf "$1 $2\n" "${@:3}"; } +error() { out "==> ERROR:" "$@"; } >&2 +msg() { out "==>" "$@"; } +msg2() { out " ->" "$@";} +die() { error "$@"; exit 1; } + +ignore_error() { + "$@" 2>/dev/null + return 0 +} + +in_array() { + local i + for i in "${@:2}"; do + [[ $1 = "$i" ]] && return 0 + done + return 1 +} + +chroot_add_mount() { + mount "$@" && CHROOT_ACTIVE_MOUNTS=("$2" "${CHROOT_ACTIVE_MOUNTS[@]}") +} + +chroot_maybe_add_mount() { + local cond=$1; shift + if eval "$cond"; then + chroot_add_mount "$@" + fi +} + +chroot_setup() { + CHROOT_ACTIVE_MOUNTS=() + [[ $(trap -p EXIT) ]] && die '(BUG): attempting to overwrite existing EXIT trap' + trap 'chroot_teardown' EXIT + + chroot_maybe_add_mount "! mountpoint -q '$1'" "$1" "$1" --bind && + chroot_add_mount proc "$1/proc" -t proc -o nosuid,noexec,nodev && + chroot_add_mount sys "$1/sys" -t sysfs -o nosuid,noexec,nodev,ro && + ignore_error chroot_maybe_add_mount "[[ -d '$1/sys/firmware/efi/efivars' ]]" \ + efivarfs "$1/sys/firmware/efi/efivars" -t efivarfs -o nosuid,noexec,nodev && + chroot_add_mount udev "$1/dev" -t devtmpfs -o mode=0755,nosuid && + chroot_add_mount devpts "$1/dev/pts" -t devpts -o mode=0620,gid=5,nosuid,noexec && + chroot_add_mount shm "$1/dev/shm" -t tmpfs -o mode=1777,nosuid,nodev && + chroot_add_mount run "$1/run" -t tmpfs -o nosuid,nodev,mode=0755 && + chroot_add_mount tmp "$1/tmp" -t tmpfs -o mode=1777,strictatime,nodev,nosuid +} + +chroot_teardown() { + umount "${CHROOT_ACTIVE_MOUNTS[@]}" + unset CHROOT_ACTIVE_MOUNTS +} + +try_cast() ( + _=$(( $1#$2 )) +) 2>/dev/null + +valid_number_of_base() { + local base=$1 len=${#2} i= + + for (( i = 0; i < len; i++ )); do + try_cast "$base" "${2:i:1}" || return 1 + done + + return 0 +} + +mangle() { + local i= chr= out= + + unset {a..f} {A..F} + + for (( i = 0; i < ${#1}; i++ )); do + chr=${1:i:1} + case $chr in + [[:space:]\\]) + printf -v chr '%03o' "'$chr" + out+=\\ + ;; + esac + out+=$chr + done + + printf '%s' "$out" +} + +unmangle() { + local i= chr= out= len=$(( ${#1} - 4 )) + + unset {a..f} {A..F} + + for (( i = 0; i < len; i++ )); do + chr=${1:i:1} + case $chr in + \\) + if valid_number_of_base 8 "${1:i+1:3}" || + valid_number_of_base 16 "${1:i+1:3}"; then + printf -v chr '%b' "${1:i:4}" + (( i += 3 )) + fi + ;; + esac + out+=$chr + done + + printf '%s' "$out${1:i}" +} + +optstring_match_option() { + local candidate pat patterns + + IFS=, read -ra patterns <<<"$1" + for pat in "${patterns[@]}"; do + if [[ $pat = *=* ]]; then + # "key=val" will only ever match "key=val" + candidate=$2 + else + # "key" will match "key", but also "key=anyval" + candidate=${2%%=*} + fi + + [[ $pat = "$candidate" ]] && return 0 + done + + return 1 +} + +optstring_remove_option() { + local o options_ remove=$2 IFS=, + + read -ra options_ <<<"${!1}" + + for o in "${!options_[@]}"; do + optstring_match_option "$remove" "${options_[o]}" && unset 'options_[o]' + done + + declare -g "$1=${options_[*]}" +} + +optstring_normalize() { + local o options_ norm IFS=, + + read -ra options_ <<<"${!1}" + + # remove empty fields + for o in "${options_[@]}"; do + [[ $o ]] && norm+=("$o") + done + + # avoid empty strings, reset to "defaults" + declare -g "$1=${norm[*]:-defaults}" +} + +optstring_append_option() { + if ! optstring_has_option "$1" "$2"; then + declare -g "$1=${!1},$2" + fi + + optstring_normalize "$1" +} + +optstring_prepend_option() { + local options_=$1 + + if ! optstring_has_option "$1" "$2"; then + declare -g "$1=$2,${!1}" + fi + + optstring_normalize "$1" +} + +optstring_get_option() { + local opts o + + IFS=, read -ra opts <<<"${!1}" + for o in "${opts[@]}"; do + if optstring_match_option "$2" "$o"; then + declare -g "$o" + return 0 + fi + done + + return 1 +} + +optstring_has_option() { + local "${2%%=*}" + + optstring_get_option "$1" "$2" +} + +dm_name_for_devnode() { + read dm_name <"/sys/class/block/${1#/dev/}/dm/name" + if [[ $dm_name ]]; then + printf '/dev/mapper/%s' "$dm_name" + else + # don't leave the caller hanging, just print the original name + # along with the failure. + print '%s' "$1" + error 'Failed to resolve device mapper name for: %s' "$1" + fi +} + +fstype_is_pseudofs() { + (( pseudofs_types["$1"] )) +} + +fstype_has_fsck() { + (( fsck_types["$1"] )) +} + + +hostcache=0 +copykeyring=1 +copymirrorlist=1 + +usage() { + cat <<EOF +usage: ${0##*/} [options] root [packages...] + + Options: + -C config Use an alternate config file for pacman + -c Use the package cache on the host, rather than the target + -d Allow installation to a non-mountpoint directory + -G Avoid copying the host's pacman keyring to the target + -i Avoid auto-confirmation of package selections + -M Avoid copying the host's mirrorlist to the target + + -h Print this help message + +pacstrap installs packages to the specified new root directory. If no packages +are given, pacstrap defaults to the "base" group. + +EOF +} + +if [[ -z $1 || $1 = @(-h|--help) ]]; then + usage + exit $(( $# ? 0 : 1 )) +fi + +(( EUID == 0 )) || die 'This script must be run with root privileges' + +while getopts ':C:cdGiM' flag; do + case $flag in + C) + pacman_config=$OPTARG + ;; + d) + directory=1 + ;; + c) + hostcache=1 + ;; + i) + interactive=1 + ;; + G) + copykeyring=0 + ;; + M) + copymirrorlist=0 + ;; + :) + die '%s: option requires an argument -- '\''%s'\' "${0##*/}" "$OPTARG" + ;; + ?) + die '%s: invalid option -- '\''%s'\' "${0##*/}" "$OPTARG" + ;; + esac +done +shift $(( OPTIND - 1 )) + +(( $# )) || die "No root directory specified" +newroot=$1; shift +pacman_args=("${@:-base}") + +if (( ! hostcache )); then + pacman_args+=(--cachedir="$newroot/var/cache/pacman/pkg") +fi + +if (( ! interactive )); then + pacman_args+=(--noconfirm) +fi + +if [[ $pacman_config ]]; then + pacman_args+=(--config="$pacman_config") +fi + +[[ -d $newroot ]] || die "%s is not a directory" "$newroot" +if ! mountpoint -q "$newroot" && (( ! directory )); then + die '%s is not a mountpoint!' "$newroot" +fi + +# create obligatory directories +msg 'Creating install root at %s' "$newroot" +mkdir -m 0755 -p "$newroot"/var/{cache/pacman/pkg,lib/pacman,log} "$newroot"/{dev,run,etc} +mkdir -m 1777 -p "$newroot"/tmp +mkdir -m 0555 -p "$newroot"/{sys,proc} + +## copy pacman db from host to newroot +cp -r /var/lib/pacman/sync "$newroot"/var/lib/pacman/ + +# mount API filesystems +chroot_setup "$newroot" || die "failed to setup chroot %s" "$newroot" + +msg 'Installing packages to %s' "$newroot" +if ! pacman -r "$newroot" -Sy "${pacman_args[@]}"; then + die 'Failed to install packages to new root' +fi + +if (( copykeyring )); then + # if there's a keyring on the host, copy it into the new root, unless it exists already + if [[ -d /etc/pacman.d/gnupg && ! -d $newroot/etc/pacman.d/gnupg ]]; then + cp -a /etc/pacman.d/gnupg "$newroot/etc/pacman.d/" + fi +fi + +if (( copymirrorlist )); then + # install the host's mirrorlist onto the new root + cp -a /etc/pacman.d/mirrorlist "$newroot/etc/pacman.d/" +fi + +# vim: et ts=2 sw=2 ft=sh: |
