Compare commits
13 Commits
master
...
github/for
Author | SHA1 | Date | |
---|---|---|---|
|
0e14a1a13b | ||
|
f40fc1f2b0 | ||
|
bc1b449f1f | ||
|
7881d7ebce | ||
|
9aa95365c2 | ||
|
2b9027a0ab | ||
|
05d231aff9 | ||
|
c21e56e88e | ||
|
e13ce65908 | ||
|
092226862b | ||
|
4be53d3488 | ||
|
e3f9cb6498 | ||
|
33cf482649 |
@ -1,26 +0,0 @@
|
||||
# EditorConfig
|
||||
# https://editorconfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file, utf-8 charset, 2 space
|
||||
# indentation, remove any whitespace characters preceding newline characters.
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
# for shfmt
|
||||
switch_case_indent = true
|
||||
binary_next_line = true
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# Tab indentation (no size specified)
|
||||
[Makefile]
|
||||
indent_style = tab
|
23
.github/workflows/differential-shellcheck.yml
vendored
Normal file
23
.github/workflows/differential-shellcheck.yml
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
name: Differential ShellCheck
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Repository checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Differential ShellCheck
|
||||
uses: redhat-plumbers-in-action/differential-shellcheck@latest
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
@ -1,27 +1,6 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
stages:
|
||||
- check
|
||||
|
||||
.pacman_install:
|
||||
before_script:
|
||||
# NOTE: Install latest archlinux-keyring before upgrading system. In the
|
||||
# future this should not be needed anymore when we can guarantee a valid
|
||||
# keyring for longer:
|
||||
# https://gitlab.archlinux.org/archlinux/archlinux-keyring/-/issues/4
|
||||
- echo -e "\e[0Ksection_start:`date +%s`:pacman[collapsed=true]\r\e[0KInstalling dependencies"
|
||||
- pacman -Sy --needed --noconfirm archlinux-keyring
|
||||
- pacman --noconfirm -Syu --needed asciidoc make shellcheck m4
|
||||
- echo -e "\e[0Ksection_end:`date +%s`:pacman\r\e[0K"
|
||||
|
||||
check:
|
||||
stage: check
|
||||
extends: .pacman_install
|
||||
shellcheck:
|
||||
stage: test
|
||||
image: koalaman/shellcheck-alpine:latest
|
||||
script:
|
||||
- make shellcheck
|
||||
|
||||
test:
|
||||
stage: check
|
||||
extends: .pacman_install
|
||||
script:
|
||||
- make check
|
||||
- shellcheck common
|
||||
- shellcheck arch-chroot.in genfstab.in pacstrap.in
|
||||
|
39
Makefile
39
Makefile
@ -1,20 +1,18 @@
|
||||
VER=26
|
||||
|
||||
PREFIX = /usr
|
||||
PREFIX = /usr/local
|
||||
|
||||
BINPROGS = \
|
||||
future-chroot \
|
||||
arch-chroot \
|
||||
genfstab \
|
||||
pacstrap
|
||||
|
||||
MANS = \
|
||||
doc/future-chroot.8 \
|
||||
doc/arch-chroot.8 \
|
||||
doc/genfstab.8 \
|
||||
doc/pacstrap.8
|
||||
|
||||
BASH = bash
|
||||
ZSHCOMP := $(wildcard completion/zsh/*)
|
||||
BASHCOMP := $(wildcard completion/bash/*)
|
||||
|
||||
all: $(BINPROGS) man
|
||||
man: $(MANS)
|
||||
@ -25,13 +23,7 @@ _v_GEN_0 = @echo " GEN " $@;
|
||||
|
||||
edit = $(V_GEN) m4 -P $@.in >$@ && chmod go-w,+x $@
|
||||
|
||||
future-chroot: future-chroot.in common
|
||||
$(edit)
|
||||
|
||||
genfstab: genfstab.in fstab-helpers
|
||||
$(edit)
|
||||
|
||||
pacstrap: pacstrap.in common
|
||||
%: %.in common
|
||||
$(edit)
|
||||
|
||||
doc/%: doc/%.asciidoc doc/asciidoc.conf
|
||||
@ -44,18 +36,15 @@ check: all
|
||||
@for f in $(BINPROGS); do bash -O extglob -n $$f; done
|
||||
@r=0; for t in test/test_*; do $(BASH) $$t || { echo $$t fail; r=1; }; done; exit $$r
|
||||
|
||||
shellcheck: $(BINPROGS)
|
||||
shellcheck -W 99 --color $(BINPROGS)
|
||||
shellcheck -W 99 --color -x test/test_*
|
||||
|
||||
install: all
|
||||
install -d $(DESTDIR)$(PREFIX)/sbin
|
||||
install -m 0755 $(BINPROGS) $(DESTDIR)$(PREFIX)/sbin
|
||||
install -d $(DESTDIR)$(PREFIX)/share/zsh/site-functions
|
||||
install -m 0644 $(ZSHCOMP) $(DESTDIR)$(PREFIX)/share/zsh/site-functions
|
||||
install -d $(DESTDIR)$(PREFIX)/share/bash-completion/completions
|
||||
install -m 0644 $(BASHCOMP) $(DESTDIR)$(PREFIX)/share/bash-completion/completions
|
||||
install -d $(DESTDIR)$(PREFIX)/share/man/man8
|
||||
install -m 0644 $(MANS) $(DESTDIR)$(PREFIX)/share/man/man8
|
||||
install -dm755 $(DESTDIR)$(PREFIX)/bin
|
||||
install -m755 $(BINPROGS) $(DESTDIR)$(PREFIX)/bin
|
||||
install -Dm644 completion/_archinstallscripts.zsh $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_archinstallscripts
|
||||
cd completion; for comp in *.bash; do \
|
||||
install -Dm644 $$comp $(DESTDIR)$(PREFIX)/share/bash-completion/completions/$${comp%%.*}; \
|
||||
done;
|
||||
for manfile in $(MANS); do \
|
||||
install -Dm644 $$manfile -t $(DESTDIR)$(PREFIX)/share/man/man$${manfile##*.}; \
|
||||
done;
|
||||
|
||||
.PHONY: all man clean check shellcheck install
|
||||
.PHONY: all clean install uninstall
|
||||
|
@ -1,14 +1,12 @@
|
||||
# Modify Arch Install Scripts
|
||||
|
||||
# Future Install Scripts
|
||||
# Arch Install Scripts
|
||||
|
||||
This is a small suite of scripts aimed at automating some menial
|
||||
tasks when installing [Future Linux](https://www.futurelinux.xyz).
|
||||
tasks when installing [Arch Linux](https://www.archlinux.org).
|
||||
|
||||
## Requirements
|
||||
|
||||
* GNU coreutils (>= v8.15)
|
||||
* util-linux (>= 2.39)
|
||||
* util-linux (>= 2.23)
|
||||
* POSIX awk
|
||||
* bash (>= 4.1)
|
||||
* asciidoc (for generating man pages)
|
||||
|
87
arch-chroot.in
Normal file
87
arch-chroot.in
Normal file
@ -0,0 +1,87 @@
|
||||
#!/bin/bash
|
||||
|
||||
shopt -s extglob
|
||||
|
||||
# m4_include() is recognized as a function definition
|
||||
# shellcheck source=common disable=SC1073,SC1065,SC1064,SC1072
|
||||
m4_include(common)
|
||||
|
||||
setup=chroot_setup
|
||||
unshare=0
|
||||
userspec=''
|
||||
chroot_args=()
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
usage: ${0##*/} [options] chroot_dir [command [arguments...]]
|
||||
|
||||
Options:
|
||||
-N Run in unshare mode as a regular user
|
||||
-u <user>[:group] Specify non-root user and group (optional) to use
|
||||
|
||||
-h Print this help message
|
||||
|
||||
If 'command' is unspecified, arch-chroot will launch /bin/bash.
|
||||
|
||||
Note that when using arch-chroot, the target chroot directory *should* be a
|
||||
mountpoint. This ensures that tools such as pacman(8) or findmnt(8) have an
|
||||
accurate hierarchy of the mounted filesystems within the chroot.
|
||||
If your chroot target is not a mountpoint, you can bind mount the directory on
|
||||
itself to make it one, i.e. 'mount --bind chroot_dir chroot_dir'.
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
if [[ -z $1 || $1 = @(-h|--help) ]]; then
|
||||
usage
|
||||
exit $(( $# ? 0 : 1 ))
|
||||
fi
|
||||
|
||||
while getopts ':Nu:' flag; do
|
||||
case $flag in
|
||||
N)
|
||||
setup=unshare_setup
|
||||
unshare=1
|
||||
;;
|
||||
u)
|
||||
userspec="$OPTARG"
|
||||
;;
|
||||
:)
|
||||
die "%s: option requires an argument -- '%s'" "${0##*/}" "$OPTARG"
|
||||
;;
|
||||
?)
|
||||
die "%s: invalid option -- '%s'" "${0##*/}" "$OPTARG"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift $(( OPTIND - 1 ))
|
||||
|
||||
(( $# )) || die 'No chroot directory specified'
|
||||
chrootdir="$1"; shift
|
||||
|
||||
if ! mountpoint -q "$chrootdir"; then
|
||||
warning '%s is not a mountpoint, and thus may cause undesirable side effects. (See help for more info)' "$chrootdir"
|
||||
fi
|
||||
|
||||
command=("$@")
|
||||
|
||||
arch-chroot() {
|
||||
check_root
|
||||
|
||||
[[ -d "$chrootdir" ]] || die '%s: not a directory' "$chrootdir"
|
||||
|
||||
$setup "$chrootdir"
|
||||
chroot_add_resolv_conf "$chrootdir" || die 'Failed to setup resolv.conf in chroot'
|
||||
|
||||
[[ $userspec ]] && chroot_args+=(--userspec="$userspec")
|
||||
|
||||
SHELL=/bin/bash $pid_unshare chroot "${chroot_args[@]}" -- "$chrootdir" "${command[@]}"
|
||||
}
|
||||
|
||||
if (( unshare )); then
|
||||
$mount_unshare bash -c "$(declare_all); arch-chroot"
|
||||
else
|
||||
arch-chroot
|
||||
fi
|
||||
|
||||
# vim: et ts=2 sw=2 ft=sh:
|
459
common
459
common
@ -1,103 +1,284 @@
|
||||
#!/hint/bash
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
# shellcheck disable=SC2059 # $1 and $2 can contain the printf modifiers
|
||||
# shellcheck disable=SC2155,SC2064
|
||||
|
||||
# generated from util-linux source: libmount/src/utils.c
|
||||
declare -A pseudofs_types=([anon_inodefs]=1
|
||||
[apparmorfs]=1
|
||||
[autofs]=1
|
||||
[bdev]=1
|
||||
[binder]=1
|
||||
[binfmt_misc]=1
|
||||
[bpf]=1
|
||||
[cgroup]=1
|
||||
[cgroup2]=1
|
||||
[configfs]=1
|
||||
[cpuset]=1
|
||||
[debugfs]=1
|
||||
[devfs]=1
|
||||
[devpts]=1
|
||||
[devtmpfs]=1
|
||||
[dlmfs]=1
|
||||
[dmabuf]=1
|
||||
[drm]=1
|
||||
[efivarfs]=1
|
||||
[fuse]=1
|
||||
[fuse.archivemount]=1
|
||||
[fuse.avfsd]=1
|
||||
[fuse.dumpfs]=1
|
||||
[fuse.encfs]=1
|
||||
[fuse.gvfs-fuse-daemon]=1
|
||||
[fuse.gvfsd-fuse]=1
|
||||
[fuse.lxcfs]=1
|
||||
[fuse.rofiles-fuse]=1
|
||||
[fuse.vmware-vmblock]=1
|
||||
[fuse.xwmfs]=1
|
||||
[fusectl]=1
|
||||
[hugetlbfs]=1
|
||||
[ipathfs]=1
|
||||
[mqueue]=1
|
||||
[nfsd]=1
|
||||
[none]=1
|
||||
[nsfs]=1
|
||||
[overlay]=1
|
||||
[pipefs]=1
|
||||
[proc]=1
|
||||
[pstore]=1
|
||||
[ramfs]=1
|
||||
[resctrl]=1
|
||||
[rootfs]=1
|
||||
[rpc_pipefs]=1
|
||||
[securityfs]=1
|
||||
[selinuxfs]=1
|
||||
[smackfs]=1
|
||||
[sockfs]=1
|
||||
[spufs]=1
|
||||
[sysfs]=1
|
||||
[tmpfs]=1
|
||||
[tracefs]=1
|
||||
[vboxsf]=1
|
||||
[virtiofs]=1)
|
||||
|
||||
# generated from: pkgfile -vbr '/fsck\..+' | awk -F. '{ print $NF }' | sort
|
||||
declare -A fsck_types=([btrfs]=0 # btrfs doesn't need a regular fsck utility
|
||||
[cramfs]=1
|
||||
[erofs]=1
|
||||
[exfat]=1
|
||||
[ext2]=1
|
||||
[ext3]=1
|
||||
[ext4]=1
|
||||
[f2fs]=1
|
||||
[fat]=1
|
||||
[jfs]=1
|
||||
[minix]=1
|
||||
[msdos]=1
|
||||
[reiserfs]=1
|
||||
[vfat]=1
|
||||
[xfs]=1)
|
||||
|
||||
# shellcheck disable=SC2059
|
||||
out() { printf "$1 $2\n" "${@:3}"; }
|
||||
error() { out "==> ERROR:" "$@"; } >&2
|
||||
warning() { out "==> WARNING:" "$@"; } >&2
|
||||
msg() { out "==>" "$@"; }
|
||||
msg2() { out " ->" "$@"; }
|
||||
warning() { out "==> WARNING:" "$@"; } >&2
|
||||
error() { out "==> ERROR:" "$@"; } >&2
|
||||
die() { error "$@"; exit 1; }
|
||||
|
||||
ignore_error() {
|
||||
"$@" 2>/dev/null
|
||||
return 0
|
||||
"$@" 2>/dev/null || return 0
|
||||
}
|
||||
|
||||
in_array() {
|
||||
local i
|
||||
local -n _arr="$1"
|
||||
|
||||
for i in "${_arr[@]}"; do
|
||||
[[ "$i" = "$1" ]] && return 0
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
rev_array() {
|
||||
local -n _arr="$1"
|
||||
|
||||
mapfile -t _arr < <(printf '%s\n' "${_arr[@]}" | tac)
|
||||
}
|
||||
|
||||
check_root() {
|
||||
(( EUID == 0 )) || die 'This script must be run with root privileges'
|
||||
}
|
||||
|
||||
resolve_link() {
|
||||
local link="$1" root="$2" target
|
||||
|
||||
if [[ ! $root ]]; then
|
||||
target="$(realpath -eq "$link")"
|
||||
else
|
||||
# This is tricky to do. We read from $link in a loop
|
||||
# and prepend it with $root if it's not under it.
|
||||
# This can't handle e.g. $root is /mnt and $link
|
||||
# is /mnt/1 pointing to /mnt/2, which should actually
|
||||
# be /mnt/mnt/2. Luckily these edge cases shouldn't
|
||||
# bother most of the time.
|
||||
|
||||
root="$(realpath -e "$root")"
|
||||
target="$link"
|
||||
while [[ -L "$target" ]]; do
|
||||
target="$(readlink -m "$target")"
|
||||
if [[ "$target" != "$root"/* ]]; then
|
||||
target="$root/${target#/}"
|
||||
fi
|
||||
done
|
||||
|
||||
# Normalize the path and make sure the resolved target exists
|
||||
target="$(realpath -eq "$target")"
|
||||
fi
|
||||
|
||||
printf '%s' "$target"
|
||||
}
|
||||
|
||||
chroot_add_mount() {
|
||||
mount "$@" && CHROOT_ACTIVE_MOUNTS=("$2" "${CHROOT_ACTIVE_MOUNTS[@]}")
|
||||
mount "$@" && CHROOT_ACTIVE_MOUNTS+=("$2")
|
||||
}
|
||||
|
||||
chroot_maybe_add_mount() {
|
||||
local cond=$1; shift
|
||||
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_add_mount --bind /dev "$1/dev" &&
|
||||
chroot_add_mount devpts "$1/dev/pts" -t devpts -o gid=5,mode=0620 &&
|
||||
chroot_add_mount proc $1/proc -t proc &&
|
||||
chroot_add_mount sysfs $1/sys -t sysfs &&
|
||||
chroot_add_mount tmpfs "$1/run" -t tmpfs &&
|
||||
chroot_add_mount tmpfs "$1/dev/shm" -t tmpfs -o nosuid,nodev &&
|
||||
ignore_error chroot_maybe_add_mount "[[ -d '$1/sys/firmware/efi/efivars' ]]" \
|
||||
efivarfs "$1/sys/firmware/efi/efivars" -t efivarfs
|
||||
|
||||
}
|
||||
|
||||
chroot_teardown() {
|
||||
if (( ${#CHROOT_ACTIVE_MOUNTS[@]} )); then
|
||||
umount "${CHROOT_ACTIVE_MOUNTS[@]}"
|
||||
fi
|
||||
unset CHROOT_ACTIVE_MOUNTS
|
||||
}
|
||||
|
||||
chroot_add_mount_lazy() {
|
||||
mount "$@" && CHROOT_ACTIVE_LAZY=("$2" "${CHROOT_ACTIVE_LAZY[@]}")
|
||||
mount "$@" && CHROOT_ACTIVE_LAZY+=("$2")
|
||||
}
|
||||
|
||||
chroot_bind_device() {
|
||||
touch "$2" && CHROOT_ACTIVE_FILES=("$2" "${CHROOT_ACTIVE_FILES[@]}")
|
||||
touch "$2" && CHROOT_ACTIVE_FILES+=("$2")
|
||||
chroot_add_mount "$1" "$2" --bind
|
||||
}
|
||||
|
||||
chroot_add_link() {
|
||||
ln -sf "$1" "$2" && CHROOT_ACTIVE_FILES=("$2" "${CHROOT_ACTIVE_FILES[@]}")
|
||||
ln -sf "$1" "$2" && CHROOT_ACTIVE_FILES+=("$2")
|
||||
}
|
||||
|
||||
chroot_add_resolv_conf() {
|
||||
local src="$(resolve_link /etc/resolv.conf)" dest="$1/etc/resolv.conf"
|
||||
|
||||
# If we don't have a source resolv.conf file, there's nothing useful we can do.
|
||||
if [[ ! -f "$src" ]]; then
|
||||
warning 'Cannot find a usable resolv.conf. DNS requests might fail in chroot'
|
||||
return 0
|
||||
fi
|
||||
|
||||
# If resolv.conf in the chroot is a symlink, we make a backup of it
|
||||
# and create a plain file so we can bind mount to it.
|
||||
# The backup is restore during chroot_teardown().
|
||||
|
||||
if [[ -L "$dest" ]]; then
|
||||
mv "$dest" "$dest.orig"
|
||||
fi
|
||||
touch -a "$dest"
|
||||
|
||||
chroot_add_mount "$src" "$dest" --bind
|
||||
}
|
||||
|
||||
chroot_setup() {
|
||||
CHROOT_ACTIVE_MOUNTS=()
|
||||
local root="$1"
|
||||
local errtrap_old="$(trap -p ERR)" shellopts_old="$SHELLOPTS"
|
||||
|
||||
[[ $(trap -p EXIT) ]] && die 'An EXIT trap already exists (likely a bug)'
|
||||
trap "chroot_teardown $root" EXIT
|
||||
|
||||
trap "$errtrap_old; die 'Failed to setup chroot %s' '$root'" ERR
|
||||
set -e
|
||||
|
||||
chroot_add_mount proc "$root"/proc -t proc -o nosuid,noexec,nodev
|
||||
chroot_add_mount sys "$root"/sys -t sysfs -o nosuid,noexec,nodev,ro
|
||||
ignore_error chroot_maybe_add_mount "[[ -d '$root/sys/firmware/efi/efivars' ]]" \
|
||||
efivarfs "$root"/sys/firmware/efi/efivars -t efivarfs -o nosuid,noexec,nodev
|
||||
chroot_add_mount udev "$root"/dev -t devtmpfs -o mode=0755,nosuid
|
||||
chroot_add_mount devpts "$root"/dev/pts -t devpts -o mode=0620,gid=5,nosuid,noexec
|
||||
chroot_add_mount shm "$root"/dev/shm -t tmpfs -o mode=1777,nosuid,nodev
|
||||
chroot_add_mount run "$root"/run -t tmpfs -o nosuid,nodev,mode=0755
|
||||
chroot_add_mount tmp "$root"/tmp -t tmpfs -o mode=1777,strictatime,nodev,nosuid
|
||||
|
||||
[[ "$shellopts_old" = *'errexit'* ]] || set +e
|
||||
if [[ $errtrap_old ]]; then
|
||||
trap "$errtrap_old" ERR
|
||||
else
|
||||
trap - ERR
|
||||
fi
|
||||
}
|
||||
|
||||
chroot_teardown() {
|
||||
local root="$1"
|
||||
|
||||
if (( ${#CHROOT_ACTIVE_MOUNTS[@]} )); then
|
||||
rev_array CHROOT_ACTIVE_MOUNTS
|
||||
umount "${CHROOT_ACTIVE_MOUNTS[@]}"
|
||||
fi
|
||||
unset CHROOT_ACTIVE_MOUNTS
|
||||
|
||||
if [[ -L "$root"/etc/resolv.conf.orig ]]; then
|
||||
mv "$root"/etc/resolv.conf.orig "$root"/etc/resolv.conf
|
||||
fi
|
||||
}
|
||||
|
||||
unshare_setup() {
|
||||
CHROOT_ACTIVE_MOUNTS=()
|
||||
CHROOT_ACTIVE_LAZY=()
|
||||
CHROOT_ACTIVE_FILES=()
|
||||
[[ $(trap -p EXIT) ]] && die '(BUG): attempting to overwrite existing EXIT trap'
|
||||
trap 'unshare_teardown' EXIT
|
||||
CHROOT_ACTIVE_MOUNTS=() CHROOT_ACTIVE_LAZY=() CHROOT_ACTIVE_FILES=()
|
||||
local root="$1"
|
||||
local errtrap_old="$(trap -p ERR)" shellopts_old="$SHELLOPTS"
|
||||
|
||||
chroot_add_mount_lazy "$1" "$1" --bind &&
|
||||
chroot_add_mount proc "$1/proc" -t proc &&
|
||||
chroot_add_mount sysfs "$1/sys" -t sysfs &&
|
||||
chroot_add_link /proc/self/fd "$1/dev/fd" &&
|
||||
chroot_add_link /proc/self/fd/0 "$1/dev/stdin" &&
|
||||
chroot_add_link /proc/self/fd/1 "$1/dev/stdout" &&
|
||||
chroot_add_link /proc/self/fd/2 "$1/dev/stderr" &&
|
||||
chroot_bind_device /dev/full "$1/dev/full" &&
|
||||
chroot_bind_device /dev/null "$1/dev/null" &&
|
||||
chroot_bind_device /dev/random "$1/dev/random" &&
|
||||
chroot_bind_device /dev/tty "$1/dev/tty" &&
|
||||
chroot_bind_device /dev/urandom "$1/dev/urandom" &&
|
||||
chroot_bind_device /dev/zero "$1/dev/zero" &&
|
||||
chroot_add_mount tmpfs "$1/run" -t tmpfs
|
||||
[[ $(trap -p EXIT) ]] && die 'An EXIT trap already exists (likely a bug)'
|
||||
trap "unshare_teardown $root" EXIT
|
||||
|
||||
trap "$errtrap_old; die 'Failed to setup chroot %s' '$root'" ERR
|
||||
set -e
|
||||
|
||||
chroot_add_mount_lazy "$root" "$root" --bind
|
||||
chroot_add_mount proc "$root"/proc -t proc -o nosuid,noexec,nodev
|
||||
chroot_add_mount_lazy /sys "$root"/sys --rbind
|
||||
chroot_add_link "$root"/proc/self/fd "$root"/dev/fd
|
||||
chroot_add_link "$root"/proc/self/fd/0 "$root"/dev/stdin
|
||||
chroot_add_link "$root"/proc/self/fd/1 "$root"/dev/stdout
|
||||
chroot_add_link "$root"/proc/self/fd/2 "$root"/dev/stderr
|
||||
chroot_bind_device /dev/full "$root"/dev/full
|
||||
chroot_bind_device /dev/null "$root"/dev/null
|
||||
chroot_bind_device /dev/zero "$root"/dev/zero
|
||||
chroot_bind_device /dev/random "$root"/dev/random
|
||||
chroot_bind_device /dev/urandom "$root"/dev/urandom
|
||||
chroot_bind_device /dev/tty "$root"/dev/tty
|
||||
chroot_add_mount run "$root"/run -t tmpfs -o nosuid,nodev,mode=0755
|
||||
chroot_add_mount tmp "$root"/tmp -t tmpfs -o mode=1777,strictatime,nodev,nosuid
|
||||
|
||||
[[ "$shellopts_old" = *'errexit'* ]] || set +e
|
||||
if [[ $errtrap_old ]]; then
|
||||
trap "$errtrap_old" ERR
|
||||
else
|
||||
trap - ERR
|
||||
fi
|
||||
}
|
||||
|
||||
unshare_teardown() {
|
||||
chroot_teardown
|
||||
chroot_teardown "$1"
|
||||
|
||||
if (( ${#CHROOT_ACTIVE_LAZY[@]} )); then
|
||||
rev_array CHROOT_ACTIVE_LAZY
|
||||
umount --lazy "${CHROOT_ACTIVE_LAZY[@]}"
|
||||
fi
|
||||
unset CHROOT_ACTIVE_LAZY
|
||||
|
||||
if (( ${#CHROOT_ACTIVE_FILES[@]} )); then
|
||||
rev_array CHROOT_ACTIVE_FILES
|
||||
rm "${CHROOT_ACTIVE_FILES[@]}"
|
||||
fi
|
||||
unset CHROOT_ACTIVE_FILES
|
||||
}
|
||||
|
||||
pid_unshare="unshare --fork --pid"
|
||||
# shellcheck disable=SC2034
|
||||
mount_unshare="$pid_unshare --mount --map-auto --map-root-user --setuid 0 --setgid 0"
|
||||
|
||||
# This outputs code for declaring all variables to stdout. For example, if
|
||||
@ -116,3 +297,173 @@ declare_all() {
|
||||
# Then declare functions
|
||||
declare -pf
|
||||
}
|
||||
|
||||
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
|
||||
local {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 ))"
|
||||
local {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_one_option() {
|
||||
local options=() target="$2"
|
||||
local -n _optstring_match="$1"
|
||||
|
||||
IFS=, read -ra options <<<"$_optstring_match"
|
||||
if [[ "$target" != *'='* ]]; then
|
||||
options=("${options[@]%%=*}")
|
||||
fi
|
||||
|
||||
in_array options "$target"
|
||||
}
|
||||
|
||||
optstring_get_options() {
|
||||
local i options=()
|
||||
local -n _ret="$1" _optstring_get="$2"
|
||||
local -i got=0
|
||||
|
||||
IFS=, read -ra options <<<"$_optstring_get"
|
||||
|
||||
shift 2
|
||||
for i in "${!options[@]}"; do
|
||||
if optstring_match_one_option 'options[i]' "$1"; then
|
||||
_ret+=(["${options[i]%%=*}"]="${options[i]//*=}")
|
||||
got=1
|
||||
fi
|
||||
shift || break
|
||||
done
|
||||
|
||||
(( got ))
|
||||
}
|
||||
|
||||
optstring_from_array() {
|
||||
local optstring
|
||||
local -n _optarray="$1"
|
||||
|
||||
optstring="$(printf ',%s' "${_optarray[@]}")"
|
||||
optstring="${optstring:1}" # Remove the leading comma
|
||||
|
||||
printf '%s' "$optstring"
|
||||
}
|
||||
|
||||
optstring_normalize() {
|
||||
local i options=()
|
||||
local -n _optstring_norm="$1"
|
||||
|
||||
IFS=, read -ra options <<<"$_optstring_norm"
|
||||
|
||||
# remove empty fields
|
||||
for i in "${!options[@]}"; do
|
||||
[[ ${options[i]} ]] || unset 'options[i]'
|
||||
done
|
||||
|
||||
# avoid empty strings, reset to "defaults"
|
||||
if (( ! ${#options[@]} )); then
|
||||
_optstring_norm="defaults"
|
||||
else
|
||||
_optstring_norm="$(optstring_from_array options)"
|
||||
fi
|
||||
}
|
||||
|
||||
optstring_remove_options() {
|
||||
local i options=() target="$2"
|
||||
local -n _optstring_remove="$1"
|
||||
|
||||
IFS=, read -ra options <<<"$_optstring_remove"
|
||||
|
||||
for i in "${!options[@]}"; do
|
||||
optstring_match_one_option 'options[i]' "$target" && unset 'options[i]'
|
||||
done
|
||||
|
||||
_optstring_remove="$(optstring_from_array options)"
|
||||
}
|
||||
|
||||
optstring_append_one_option() {
|
||||
local option="$2"
|
||||
local -n _optstring_append="$1"
|
||||
|
||||
if ! optstring_match_one_option _optstring_append "$option"; then
|
||||
_optstring_append+=",$option"
|
||||
fi
|
||||
|
||||
optstring_normalize _optstring_append
|
||||
}
|
||||
|
||||
optstring_prepend_one_option() {
|
||||
local option="$2"
|
||||
local -n _optstring_prepend="$1"
|
||||
|
||||
if ! optstring_match_one_option _optstring_prepend "$option"; then
|
||||
_optstring_prepend="$option,$_optstring_prepend"
|
||||
fi
|
||||
|
||||
optstring_normalize _optstring_prepend
|
||||
}
|
||||
|
||||
dm_name_for_devnode() {
|
||||
read -r 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.
|
||||
error 'Failed to resolve device mapper name for: %s' "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
fstype_is_pseudofs() {
|
||||
(( pseudofs_types["$1"] )) || findmnt --pseudo "$1" &>/dev/null
|
||||
}
|
||||
|
||||
fstype_has_fsck() {
|
||||
(( fsck_types["$1"] == 0 )) && return 1
|
||||
(( fsck_types["$1"] )) || command -v "fsck.$1" &>/dev/null
|
||||
}
|
||||
|
||||
# vim: et ts=2 sw=2 ft=sh:
|
||||
|
@ -1,20 +1,12 @@
|
||||
#compdef pacstrap
|
||||
#compdef pacstrap genfstab arch-chroot
|
||||
|
||||
_pacstrap_args=(
|
||||
'-h[display help]'
|
||||
)
|
||||
|
||||
_pacstrap_args_nonh=(
|
||||
'-C[Use an alternate config file for pacman]:config file:_files -/'
|
||||
'-c[Use the package cache on the host, rather than the target]'
|
||||
'-D[Skip pacman dependency checks]'
|
||||
'-G[Avoid copying the host pacman keyring to the target]'
|
||||
'-i[Prompt for package confirmation when needed (run interactively)]'
|
||||
'-K[Initialize an empty pacman keyring in the target (implies -G)]'
|
||||
'-M[Avoid copying the host mirrorlist to the target]'
|
||||
'-N[Run in unshare mode as a regular user]'
|
||||
'-P[Copy the host pacman config to the target]'
|
||||
'-U[Use pacman -U to install packages]'
|
||||
'(-h --help)-c[Use the package cache on the host, rather than the target]'
|
||||
'(--help -h)-i[Avoid auto-confirmation of package selections]'
|
||||
)
|
||||
|
||||
|
||||
@ -59,6 +51,17 @@ _pacstrap_none(){
|
||||
"$_longopts[@]" \
|
||||
}
|
||||
|
||||
_genfstab_args=(
|
||||
'-h[display help]'
|
||||
)
|
||||
_genfstab_args_nonh=(
|
||||
'(--help -h)-p[Avoid printing pseudofs mounts]'
|
||||
'(-U --help -h)-L[Use labels for source identifiers]'
|
||||
'(-L --help -h)-U[Use UUIDs for source identifiers]'
|
||||
)
|
||||
|
||||
_arch_chroot_args=( '-h[display help]' )
|
||||
|
||||
_longopts=( '--help[display help]' )
|
||||
|
||||
_pacstrap(){
|
||||
@ -91,10 +94,69 @@ _pacstrap(){
|
||||
fi
|
||||
}
|
||||
|
||||
_genfstab(){
|
||||
if [[ -z ${(M)words:#--help} && -z ${(M)words:#-*h} ]]; then
|
||||
case $words[CURRENT] in
|
||||
-p*|-L*|-U*)
|
||||
_arguments -s : \
|
||||
"$_genfstab_args_nonh[@]"
|
||||
;;
|
||||
-*)
|
||||
_arguments -s : \
|
||||
"$_genfstab_args[@]" \
|
||||
"$_genfstab_args_nonh[@]" \
|
||||
"$_longopts[@]"
|
||||
;;
|
||||
--*)
|
||||
_arguments -s : \
|
||||
"$_longopts[@]"
|
||||
;;
|
||||
*)
|
||||
_arguments \
|
||||
"$_genfstab_args[@]" \
|
||||
"$_genfstab_args_nonh[@]" \
|
||||
"$_longopts[@]" \
|
||||
":*:_path_files -/"
|
||||
;;
|
||||
esac
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
_arch_chroot(){
|
||||
if [[ -z ${(M)words:#--help} && -z ${(M)words:#-*h} ]]; then
|
||||
case $words[CURRENT] in
|
||||
-*)
|
||||
_arguments -s : \
|
||||
"$_arch_chroot_args[@]" \
|
||||
"$_longopts[@]" \
|
||||
;;
|
||||
--*)
|
||||
_arguments -s : \
|
||||
"$_longopts[@]"
|
||||
;;
|
||||
*)
|
||||
_arguments \
|
||||
':*:_path_files -/'
|
||||
;;
|
||||
esac
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
_install_scripts(){
|
||||
case "$service" in
|
||||
pacstrap)
|
||||
_pacstrap "$@";;
|
||||
_pacstrap "$@"
|
||||
;;
|
||||
genfstab)
|
||||
_genfstab "$@";;
|
||||
arch-chroot)
|
||||
_arch_chroot "$@";;
|
||||
*)
|
||||
_message "Error";;
|
||||
esac
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
_future_chroot() {
|
||||
_arch_chroot() {
|
||||
compopt +o dirnames
|
||||
local cur prev opts i
|
||||
_init_completion -n : || return
|
||||
opts="-N -u -r -h"
|
||||
opts="-N -u -h"
|
||||
|
||||
for i in "${COMP_WORDS[@]:1:COMP_CWORD-1}"; do
|
||||
if [[ -d ${i} ]]; then
|
||||
@ -22,4 +22,4 @@ _future_chroot() {
|
||||
compopt -o dirnames
|
||||
}
|
||||
|
||||
complete -F _future_chroot future-chroot
|
||||
complete -F _arch_chroot arch-chroot
|
@ -3,7 +3,7 @@ _genfstab() {
|
||||
local cur prev words cword
|
||||
_init_completion || return
|
||||
|
||||
local opts="-f -L -p -P -t -U -h"
|
||||
local opts="-f -L -P -p -t -U -h"
|
||||
|
||||
case ${prev} in
|
||||
-f)
|
@ -1,31 +0,0 @@
|
||||
#compdef future-chroot
|
||||
|
||||
# NOTE: nearly everything here is borrowed from the chroot completion
|
||||
local -a args=(
|
||||
'(-h --help)'{-h,--help}'[display help]'
|
||||
'-N[Run in unshare mode as a regular user]'
|
||||
'-u[The non-root user and optional group to use]: :->userspecs'
|
||||
'-r[Do not change the resolv.conf within the chroot]'
|
||||
'1:new root directory:_directories'
|
||||
'*:::command:_normal'
|
||||
)
|
||||
|
||||
local ret=1
|
||||
|
||||
_arguments $args && ret=0
|
||||
|
||||
# @todo user:group specs are probably used often enough to justify making a type
|
||||
# function for this (see also `chown`, `cpio`, `rsync`, ...)
|
||||
|
||||
[[ $state == userspecs ]] &&
|
||||
if compset -P '*:*:'; then
|
||||
ret=1
|
||||
elif compset -P '*:'; then
|
||||
_groups && ret=0
|
||||
elif compset -S ':*'; then
|
||||
_users && ret=0
|
||||
else
|
||||
_users -qS : && ret=0
|
||||
fi
|
||||
|
||||
return ret
|
@ -1,13 +0,0 @@
|
||||
#compdef genfstab
|
||||
|
||||
local -a args=(
|
||||
'(-h --help)'{-h,--help}'[display help]'
|
||||
'-p[Avoid printing pseudofs mounts]'
|
||||
'-f[Restrict output to mountpoints matching the prefix FILTER]'
|
||||
'(-U -L)-t[Use TAG for source identifiers]:tag:(LABEL UUID PARTLABEL PARTUUID)'
|
||||
'(-U -t)-L[Use labels for source identifiers]'
|
||||
'(-L -t)-U[Use UUIDs for source identifiers]'
|
||||
':*:_path_files -/'
|
||||
)
|
||||
|
||||
_arguments $args
|
47
doc/arch-chroot.8.asciidoc
Normal file
47
doc/arch-chroot.8.asciidoc
Normal file
@ -0,0 +1,47 @@
|
||||
arch-chroot(8)
|
||||
==============
|
||||
|
||||
Name
|
||||
----
|
||||
arch-chroot - enhanced chroot command
|
||||
|
||||
Synopsis
|
||||
--------
|
||||
arch-chroot [options] chroot_dir [command [arguments...]]
|
||||
|
||||
Description
|
||||
-----------
|
||||
arch-chroot wraps the linkman:chroot[1] command while ensuring that important
|
||||
functionality is available, e.g. mounting '/dev', '/proc' and other API
|
||||
filesystems, or exposing linkman:resolv.conf[5] to the chroot.
|
||||
|
||||
If 'command' is unspecified, arch-chroot will launch */bin/bash*.
|
||||
|
||||
[NOTE]
|
||||
======
|
||||
The target chroot directory *should* be a mountpoint. This ensures that tools
|
||||
such as linkman:pacman[8] or linkman:findmnt[8] have an accurate hierarchy of
|
||||
the mounted filesystems within the chroot. If your chroot target is not a
|
||||
mountpoint, you can bind mount the directory on itself to make it a one, i.e.
|
||||
|
||||
'mount --bind chroot_dir chroot_dir'
|
||||
======
|
||||
|
||||
Options
|
||||
-------
|
||||
|
||||
*-N*::
|
||||
Run in unshare mode. This will use linkman:unshare[1] to create a new
|
||||
mount and user namespace, allowing regular users to create new system
|
||||
installations.
|
||||
|
||||
*-u <user>[:group]*::
|
||||
Specify non-root user and group (optional) to use.
|
||||
|
||||
*-h*::
|
||||
Output syntax and command line options.
|
||||
|
||||
See Also
|
||||
--------
|
||||
|
||||
linkman:chroot[1], linkman:proc[5], linkman:sysfs[5]
|
@ -1,53 +0,0 @@
|
||||
future-chroot(8)
|
||||
==============
|
||||
|
||||
Name
|
||||
----
|
||||
future-chroot - enhanced chroot command
|
||||
|
||||
Synopsis
|
||||
--------
|
||||
future-chroot [options] chroot-dir [command] [arguments...]
|
||||
|
||||
Description
|
||||
-----------
|
||||
future-chroot wraps the linkman:chroot[1] command while ensuring that important
|
||||
functionality is available, e.g. mounting '/dev/', '/proc' and other API
|
||||
filesystems, or exposing linkman:resolv.conf[5] to the chroot.
|
||||
|
||||
If 'command' is unspecified, future-chroot will launch */bin/bash*.
|
||||
|
||||
[NOTE]
|
||||
======
|
||||
The target chroot-dir *should* be a mountpoint. This ensures that tools such as
|
||||
linkman:pacman[8] or linkman:findmnt[8] have an accurate hierarchy of the
|
||||
mounted filesystems within the chroot. If your chroot target is not a
|
||||
mountpoint, you can bind mount the directory on itself to make it a mountpoint,
|
||||
i.e.:
|
||||
|
||||
'mount --bind /your/chroot /your/chroot'
|
||||
======
|
||||
|
||||
|
||||
Options
|
||||
-------
|
||||
|
||||
*-N*::
|
||||
Run in unshare mode. This will use linkman:unshare[1] to create a new
|
||||
mount and user namespace, allowing regular users to create new system
|
||||
installations.
|
||||
|
||||
*-u <user>[:group]*::
|
||||
Specify non-root user and optional group to use.
|
||||
|
||||
*-r*::
|
||||
Do not change the resolv.conf within the chroot. This means that the resolver
|
||||
might not work in the chroot, which could be the required state.
|
||||
|
||||
*-h*::
|
||||
Output syntax and command line options.
|
||||
|
||||
See Also
|
||||
--------
|
||||
|
||||
linkman:pacman[8]
|
@ -26,12 +26,12 @@ Options
|
||||
*-L*::
|
||||
Use labels for source identifiers (shortcut for '-t LABEL').
|
||||
|
||||
*-p*::
|
||||
Exclude pseudofs mounts (default behavior).
|
||||
|
||||
*-P*::
|
||||
Include pseudofs mounts.
|
||||
|
||||
*-p*::
|
||||
Exclude pseudofs mounts (default behavior).
|
||||
|
||||
*-t* <tag>::
|
||||
Use 'tag' for source identifiers (should be one of: 'LABEL', 'UUID',
|
||||
'PARTLABEL', 'PARTUUID').
|
||||
@ -45,4 +45,4 @@ Options
|
||||
See Also
|
||||
--------
|
||||
|
||||
linkman:pacman[8]
|
||||
linkman:fstab[5], linkman:file-hierarchy[7], linkman:filesystems[5]
|
||||
|
236
fstab-helpers
236
fstab-helpers
@ -1,236 +0,0 @@
|
||||
#!/hint/bash
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
# generated from util-linux source: libmount/src/utils.c
|
||||
declare -A pseudofs_types=([anon_inodefs]=1
|
||||
[apparmorfs]=1
|
||||
[autofs]=1
|
||||
[bdev]=1
|
||||
[binder]=1
|
||||
[binfmt_misc]=1
|
||||
[bpf]=1
|
||||
[cgroup]=1
|
||||
[cgroup2]=1
|
||||
[configfs]=1
|
||||
[cpuset]=1
|
||||
[debugfs]=1
|
||||
[devfs]=1
|
||||
[devpts]=1
|
||||
[devtmpfs]=1
|
||||
[dlmfs]=1
|
||||
[dmabuf]=1
|
||||
[drm]=1
|
||||
[efivarfs]=1
|
||||
[fuse]=1
|
||||
[fuse.archivemount]=1
|
||||
[fuse.avfsd]=1
|
||||
[fuse.dumpfs]=1
|
||||
[fuse.encfs]=1
|
||||
[fuse.gvfs-fuse-daemon]=1
|
||||
[fuse.gvfsd-fuse]=1
|
||||
[fuse.lxcfs]=1
|
||||
[fuse.rofiles-fuse]=1
|
||||
[fuse.vmware-vmblock]=1
|
||||
[fuse.xwmfs]=1
|
||||
[fusectl]=1
|
||||
[hugetlbfs]=1
|
||||
[ipathfs]=1
|
||||
[mqueue]=1
|
||||
[nfsd]=1
|
||||
[none]=1
|
||||
[nsfs]=1
|
||||
[overlay]=1
|
||||
[pipefs]=1
|
||||
[proc]=1
|
||||
[pstore]=1
|
||||
[ramfs]=1
|
||||
[resctrl]=1
|
||||
[rootfs]=1
|
||||
[rpc_pipefs]=1
|
||||
[securityfs]=1
|
||||
[selinuxfs]=1
|
||||
[smackfs]=1
|
||||
[sockfs]=1
|
||||
[spufs]=1
|
||||
[sysfs]=1
|
||||
[tmpfs]=1
|
||||
[tracefs]=1
|
||||
[vboxsf]=1
|
||||
[virtiofs]=1)
|
||||
|
||||
# generated from: pkgfile -vbr '/fsck\..+' | awk -F. '{ print $NF }' | sort
|
||||
declare -A fsck_types=([btrfs]=0 # btrfs doesn't need a regular fsck utility
|
||||
[cramfs]=1
|
||||
[erofs]=1
|
||||
[exfat]=1
|
||||
[ext2]=1
|
||||
[ext3]=1
|
||||
[ext4]=1
|
||||
[f2fs]=1
|
||||
[fat]=1
|
||||
[jfs]=1
|
||||
[minix]=1
|
||||
[msdos]=1
|
||||
[reiserfs]=1
|
||||
[vfat]=1
|
||||
[xfs]=1)
|
||||
|
||||
# shellcheck disable=SC2059 # $1 and $2 can contain the printf modifiers
|
||||
out() { printf "$1 $2\n" "${@:3}"; }
|
||||
error() { out "==> ERROR:" "$@"; } >&2
|
||||
die() { error "$@"; exit 1; }
|
||||
|
||||
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=
|
||||
local {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 ))
|
||||
local {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.
|
||||
error 'Failed to resolve device mapper name for: %s' "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
fstype_is_pseudofs() {
|
||||
(( pseudofs_types["$1"] ))
|
||||
}
|
||||
|
||||
fstype_has_fsck() {
|
||||
(( fsck_types["$1"] ))
|
||||
}
|
124
future-chroot.in
124
future-chroot.in
@ -1,124 +0,0 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
shopt -s extglob
|
||||
|
||||
unshare=0
|
||||
keepresolvconf=0
|
||||
|
||||
m4_include(common)
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
usage: ${0##*/} chroot-dir [command] [arguments...]
|
||||
|
||||
-h Print this help message
|
||||
-N Run in unshare mode as a regular user
|
||||
-u <user>[:group] Specify non-root user and optional group to use
|
||||
-r Do not change the resolv.conf within the chroot
|
||||
|
||||
If 'command' is unspecified, ${0##*/} will launch /bin/bash.
|
||||
|
||||
Note that when using future-chroot, the target chroot directory *should* be a
|
||||
mountpoint. This ensures that tools such as pacman(8) or findmnt(8) have an
|
||||
accurate hierarchy of the mounted filesystems within the chroot.
|
||||
|
||||
If your chroot target is not a mountpoint, you can bind mount the directory on
|
||||
itself to make it a mountpoint, i.e. 'mount --bind /your/chroot /your/chroot'.
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
resolve_link() {
|
||||
local target=$1
|
||||
local root=$2
|
||||
|
||||
# If a root was given, make sure it ends in a slash.
|
||||
[[ -n $root && $root != */ ]] && root=$root/
|
||||
|
||||
while [[ -L $target ]]; do
|
||||
target=$(readlink -m "$target")
|
||||
# If a root was given, make sure the target is under it.
|
||||
# Make sure to strip any leading slash from target first.
|
||||
[[ -n $root && $target != $root* ]] && target=$root${target#/}
|
||||
done
|
||||
|
||||
printf %s "$target"
|
||||
}
|
||||
|
||||
chroot_add_resolv_conf() {
|
||||
local chrootdir=$1
|
||||
local src
|
||||
local dest="$chrootdir/etc/resolv.conf"
|
||||
|
||||
src=$(resolve_link /etc/resolv.conf)
|
||||
|
||||
# If we don't have a source resolv.conf file, there's nothing useful we can do.
|
||||
[[ -e $src ]] || return 0
|
||||
|
||||
if [[ ! -e "$dest" && ! -h "$dest" ]]; then
|
||||
# There may be no resolv.conf in the chroot. In this case, we'll just exit.
|
||||
# The chroot environment must not be concerned with DNS resolution.
|
||||
return 0
|
||||
fi
|
||||
|
||||
chroot_add_mount "$src" "$dest" -c --bind
|
||||
}
|
||||
|
||||
future-chroot() {
|
||||
(( EUID == 0 )) || die 'This script must be run with root privileges'
|
||||
|
||||
[[ -d $chrootdir ]] || die "Can't create chroot on non-directory %s" "$chrootdir"
|
||||
|
||||
$setup "$chrootdir" || die "failed to setup chroot %s" "$chrootdir"
|
||||
if (( ! keepresolvconf )); then
|
||||
chroot_add_resolv_conf "$chrootdir" || die "failed to setup resolv.conf"
|
||||
fi
|
||||
|
||||
if ! mountpoint -q "$chrootdir"; then
|
||||
warning "$chrootdir is not a mountpoint. This may have undesirable side effects."
|
||||
fi
|
||||
|
||||
chroot_args=()
|
||||
[[ $userspec ]] && chroot_args+=(--userspec "$userspec")
|
||||
|
||||
SHELL=/bin/bash $pid_unshare chroot "${chroot_args[@]}" -- "$chrootdir" "${args[@]}"
|
||||
}
|
||||
|
||||
while getopts ':hNu:r' flag; do
|
||||
case $flag in
|
||||
h)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
N)
|
||||
unshare=1
|
||||
;;
|
||||
u)
|
||||
userspec=$OPTARG
|
||||
;;
|
||||
r)
|
||||
keepresolvconf=1
|
||||
;;
|
||||
:)
|
||||
die '%s: option requires an argument -- '\''%s'\' "${0##*/}" "$OPTARG"
|
||||
;;
|
||||
?)
|
||||
die '%s: invalid option -- '\''%s'\' "${0##*/}" "$OPTARG"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift $(( OPTIND - 1 ))
|
||||
|
||||
(( $# )) || die 'No chroot directory specified'
|
||||
chrootdir=$1
|
||||
shift
|
||||
|
||||
args=("$@")
|
||||
if (( unshare )); then
|
||||
setup=unshare_setup
|
||||
$mount_unshare bash -c "$(declare_all); future-chroot"
|
||||
else
|
||||
setup=chroot_setup
|
||||
future-chroot
|
||||
fi
|
188
genfstab.in
188
genfstab.in
@ -1,61 +1,60 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
shopt -s extglob
|
||||
|
||||
m4_include(fstab-helpers)
|
||||
# m4_include() is recognized as a function definition
|
||||
# shellcheck source=common disable=SC1073,SC1065,SC1064,SC1072
|
||||
m4_include(common)
|
||||
|
||||
bytag=src
|
||||
pseudofs=0
|
||||
|
||||
write_source() {
|
||||
local src=$1 spec= label= uuid= comment=()
|
||||
local src spec comments=()
|
||||
|
||||
label=$(lsblk -rno LABEL "$1" 2>/dev/null)
|
||||
uuid=$(lsblk -rno UUID "$1" 2>/dev/null)
|
||||
src="$(mangle "$1")"
|
||||
local -A sources=([LABEL]="$(mangle "$(lsblk -rno LABEL "$1" 2>/dev/null)")"
|
||||
[UUID]="$(lsblk -rno UUID "$1" 2>/dev/null)"
|
||||
[PARTLABEL]="$(mangle "$(lsblk -rno PARTLABEL "$1" 2>/dev/null)")"
|
||||
[PARTUUID]="$(lsblk -rno PARTUUID "$1" 2>/dev/null)"
|
||||
)
|
||||
|
||||
# bind mounts do not have a UUID!
|
||||
|
||||
case $bytag in
|
||||
'')
|
||||
[[ $uuid ]] && comment=("UUID=$uuid")
|
||||
[[ $label ]] && comment+=("LABEL=$(mangle "$label")")
|
||||
;;
|
||||
LABEL)
|
||||
spec=$label
|
||||
[[ $uuid ]] && comment=("$src" "UUID=$uuid")
|
||||
;;
|
||||
UUID)
|
||||
spec=$uuid
|
||||
comment=("$src")
|
||||
[[ $label ]] && comment+=("LABEL=$(mangle "$label")")
|
||||
;;
|
||||
*)
|
||||
[[ $uuid ]] && comment=("$1" "UUID=$uuid")
|
||||
[[ $label ]] && comment+=("LABEL=$(mangle "$label")")
|
||||
[[ $bytag ]] && spec=$(lsblk -rno "$bytag" "$1" 2>/dev/null)
|
||||
;;
|
||||
esac
|
||||
|
||||
[[ $comment ]] && printf '# %s\n' "${comment[*]}"
|
||||
|
||||
if [[ $spec ]]; then
|
||||
printf '%-20s' "$bytag=$(mangle "$spec")"
|
||||
if [[ ${sources["$bytag"]} ]]; then
|
||||
spec="$bytag"
|
||||
comments+=("$src")
|
||||
else
|
||||
printf '%-20s' "$(mangle "$src")"
|
||||
spec=src
|
||||
fi
|
||||
|
||||
for tag in "${!sources[@]}"; do
|
||||
if [[ ${sources["$tag"]} && "$tag" != "$spec" ]]; then
|
||||
comments+=("$tag=${sources["$tag"]}")
|
||||
fi
|
||||
done
|
||||
|
||||
(( ${#comments[@]} )) && printf '# %s\n' "${comments[*]}"
|
||||
|
||||
if [[ "$spec" != "src" ]]; then
|
||||
printf '%-20s' "$spec=${sources["$spec"]}"
|
||||
else
|
||||
printf '%-20s' "$src"
|
||||
fi
|
||||
}
|
||||
|
||||
optstring_apply_quirks() {
|
||||
local varname=$1 fstype=$2
|
||||
local fstype="$2"
|
||||
local -n _optstring="$1"
|
||||
|
||||
# SELinux displays a 'seclabel' option in /proc/self/mountinfo. We can't know
|
||||
# if the system we're generating the fstab for has any support for SELinux (as
|
||||
# one might install Arch from a Fedora environment), so let's remove it.
|
||||
optstring_remove_option "$varname" seclabel
|
||||
optstring_remove_options _optstring seclabel
|
||||
|
||||
# Prune 'relatime' option for any pseudofs. This seems to be a rampant
|
||||
# default which the kernel often exports even if the underlying filesystem
|
||||
# doesn't support it. Example: https://bugs.archlinux.org/task/54554.
|
||||
if awk -v fstype="$fstype" '$1 == fstype { exit 1 }' /proc/filesystems; then
|
||||
optstring_remove_option "$varname" relatime
|
||||
if fstype_is_pseudofs "$fstype"; then
|
||||
optstring_remove_options _optstring relatime
|
||||
fi
|
||||
|
||||
case $fstype in
|
||||
@ -63,25 +62,15 @@ optstring_apply_quirks() {
|
||||
# Having only one of subvol= and subvolid= is enough for mounting a btrfs subvolume
|
||||
# And having subvolid= set prevents things like 'snapper rollback' to work, as it
|
||||
# updates the subvolume in-place, leaving subvol= unchanged with a different subvolid.
|
||||
if optstring_has_option "$varname" subvol; then
|
||||
optstring_remove_option "$varname" subvolid
|
||||
if optstring_match_one_option _optstring subvol; then
|
||||
optstring_remove_options _optstring subvolid
|
||||
fi
|
||||
;;
|
||||
f2fs)
|
||||
# These are build-time or runtime-unchangeable options for f2fs.
|
||||
# The former means that kernels supporting the options will only
|
||||
# provide the negative versions of these (e.g. noacl), and vice versa
|
||||
# These are Kconfig options for f2fs. Kernels supporting the options will
|
||||
# only provide the negative versions of these (e.g. noacl), and vice versa
|
||||
# for kernels without support.
|
||||
# The latter means that the options can only be specified/changed
|
||||
# during the initial mount but not remount.
|
||||
optstring_remove_option "$varname" noacl,acl,nouser_xattr,user_xattr,atgc
|
||||
;;
|
||||
vfat)
|
||||
# Before Linux v3.8, "cp" is prepended to the value of the codepage.
|
||||
if optstring_get_option "$varname" codepage && [[ $codepage = cp* ]]; then
|
||||
optstring_remove_option "$varname" codepage
|
||||
optstring_append_option "$varname" "codepage=${codepage#cp}"
|
||||
fi
|
||||
optstring_remove_options _optstring noacl,acl,nouser_xattr,user_xattr
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@ -96,13 +85,13 @@ usage: ${0##*/} [options] root
|
||||
-p Exclude pseudofs mounts (default behavior)
|
||||
-P Include pseudofs mounts
|
||||
-t <tag> Use TAG for source identifiers (TAG should be one of: LABEL,
|
||||
UUID, PARTLABEL, PARTUUID)
|
||||
UUID, PARTLABEL, PARTUUID)
|
||||
-U Use UUIDs for source identifiers (shortcut for -t UUID)
|
||||
|
||||
-h Print this help message
|
||||
|
||||
genfstab generates output suitable for addition to an fstab file based on the
|
||||
devices mounted under the mountpoint specified by the given root.
|
||||
genfstab generates output suitable for addition to an fstab file based on
|
||||
the devices mounted under the mountpoint specified by the given root.
|
||||
|
||||
EOF
|
||||
}
|
||||
@ -114,15 +103,12 @@ fi
|
||||
|
||||
while getopts ':f:LPpt:U' flag; do
|
||||
case $flag in
|
||||
f)
|
||||
prefixfilter="$OPTARG"
|
||||
;;
|
||||
L)
|
||||
bytag=LABEL
|
||||
;;
|
||||
U)
|
||||
bytag=UUID
|
||||
;;
|
||||
f)
|
||||
prefixfilter=$OPTARG
|
||||
;;
|
||||
P)
|
||||
pseudofs=1
|
||||
;;
|
||||
@ -130,41 +116,46 @@ while getopts ':f:LPpt:U' flag; do
|
||||
pseudofs=0
|
||||
;;
|
||||
t)
|
||||
bytag=${OPTARG^^}
|
||||
bytag="${OPTARG^^}"
|
||||
;;
|
||||
U)
|
||||
bytag=UUID
|
||||
;;
|
||||
:)
|
||||
die '%s: option requires an argument -- '\''%s'\' "${0##*/}" "$OPTARG"
|
||||
die "%s: option requires an argument -- '%s'" "${0##*/}" "$OPTARG"
|
||||
;;
|
||||
?)
|
||||
die '%s: invalid option -- '\''%s'\' "${0##*/}" "$OPTARG"
|
||||
die "%s: invalid option -- '%s'" "${0##*/}" "$OPTARG"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift $(( OPTIND - 1 ))
|
||||
|
||||
(( $# )) || die "No root directory specified"
|
||||
root=$(realpath -mL "$1"); shift
|
||||
(( $# )) || die 'No root directory specified'
|
||||
root="$(realpath -mL "$1")"; shift
|
||||
|
||||
if ! mountpoint -q "$root"; then
|
||||
die "$root is not a mountpoint"
|
||||
die '%s: not a mountpoint' "$root"
|
||||
fi
|
||||
|
||||
# handle block devices
|
||||
while read -r src target fstype opts fsroot; do
|
||||
if (( !pseudofs )) && fstype_is_pseudofs "$fstype"; then
|
||||
findmnt -Recvruno SOURCE,TARGET,FSTYPE,OPTIONS,FSROOT "$root" |
|
||||
while read -r src target fstype opts fsroot; do
|
||||
if (( ! pseudofs )) && fstype_is_pseudofs "$fstype"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
[[ $target = "$prefixfilter"* ]] || continue
|
||||
[[ "$target" = "$prefixfilter"* ]] || continue
|
||||
|
||||
# default 5th and 6th columns
|
||||
dump=0 pass=2
|
||||
|
||||
src=$(unmangle "$src")
|
||||
target=$(unmangle "$target")
|
||||
target=${target#$root}
|
||||
src="$(unmangle "$src")"
|
||||
target="$(unmangle "$target")"
|
||||
target="${target#"$root/"}"
|
||||
[[ "$target" != '/'* ]] && target="/$target"
|
||||
|
||||
if (( !foundroot )) && findmnt "$src" "$root" >/dev/null; then
|
||||
if (( ! foundroot )) && findmnt "$src" "$root" >/dev/null; then
|
||||
# this is root. we can't possibly have more than one...
|
||||
pass=1 foundroot=1
|
||||
fi
|
||||
@ -174,19 +165,29 @@ while read -r src target fstype opts fsroot; do
|
||||
pass=0
|
||||
fi
|
||||
|
||||
if [[ $fsroot != / && $fstype != btrfs ]]; then
|
||||
if [[ "$fsroot" != "/" && "$fstype" != "btrfs" ]]; then
|
||||
# it's a bind mount
|
||||
src=$(findmnt -funcevo TARGET "$src")$fsroot
|
||||
src="/${src#$root/}"
|
||||
if [[ $src -ef $target ]]; then
|
||||
mapfile -t bind_srcs < <(findmnt -uncevo TARGET "$src")
|
||||
for bind_src in "${bind_srcs[@]}"; do
|
||||
if [[ -d "$bind_src$fsroot" ]]; then
|
||||
src="$bind_src$fsroot"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
src="${src#"$root/"}"
|
||||
[[ "$src" != '/'* ]] && src="/$src"
|
||||
|
||||
if [[ "$src" = "$target" ]]; then
|
||||
# hrmm, this is weird. we're probably looking at a file or directory
|
||||
# that was bound into a chroot from the host machine. Ignore it,
|
||||
# because this won't actually be a valid mount. Worst case, the user
|
||||
# just re-adds it.
|
||||
continue
|
||||
fi
|
||||
|
||||
fstype=none
|
||||
opts+=,bind
|
||||
opts+=',bind'
|
||||
pass=0
|
||||
fi
|
||||
|
||||
@ -195,44 +196,41 @@ while read -r src target fstype opts fsroot; do
|
||||
fuseblk)
|
||||
# well-behaved FUSE filesystems will report themselves as fuse.$fstype.
|
||||
# this is probably NTFS-3g, but let's just make sure.
|
||||
if ! newtype=$(lsblk -no FSTYPE "$src") || [[ -z $newtype ]]; then
|
||||
if ! newtype="$(lsblk -no FSTYPE "$src")" || [[ -z $newtype ]]; then
|
||||
# avoid blanking out fstype, leading to an invalid fstab
|
||||
error 'Failed to derive real filesystem type for FUSE device on %s' "$target"
|
||||
else
|
||||
fstype=$newtype
|
||||
fstype="$newtype"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
optstring_apply_quirks "opts" "$fstype"
|
||||
optstring_apply_quirks opts "$fstype"
|
||||
|
||||
# write one line
|
||||
write_source "$src"
|
||||
printf '\t%-10s' "/$(mangle "${target#/}")" "$fstype" "$opts"
|
||||
printf '\t%-10s' "$(mangle "$target")" "$fstype" "$opts"
|
||||
printf '\t%s %s' "$dump" "$pass"
|
||||
printf '\n\n'
|
||||
done < <(findmnt -Recvruno SOURCE,TARGET,FSTYPE,OPTIONS,FSROOT "$root")
|
||||
done
|
||||
|
||||
# handle swaps devices
|
||||
{
|
||||
# ignore header
|
||||
read
|
||||
read -r
|
||||
|
||||
while read -r device type _ _ prio; do
|
||||
options=defaults
|
||||
if (( prio >= 0 )); then
|
||||
options+=,pri=$prio
|
||||
options+=",pri=$prio"
|
||||
fi
|
||||
|
||||
# skip files marked deleted by the kernel
|
||||
[[ $device = *'\040(deleted)' ]] && continue
|
||||
[[ "$device" = *'\040(deleted)' ]] && continue
|
||||
|
||||
# skip devices not part of the prefix
|
||||
[[ $device = "$prefixfilter"* ]] || continue
|
||||
|
||||
if [[ $type = file ]]; then
|
||||
printf '%-20s' "${device#${root%/}}"
|
||||
elif [[ $device = /dev/dm-+([0-9]) ]]; then
|
||||
if [[ "$type" = "file" ]]; then
|
||||
printf '%-20s' "${device#"${root%/}"}"
|
||||
elif [[ "$device" = "/dev/dm-"+([0-9]) ]]; then
|
||||
# device mapper doesn't allow characters we need to worry
|
||||
# about being mangled, and it does the escaping of dashes
|
||||
# for us in sysfs.
|
||||
@ -241,6 +239,8 @@ done < <(findmnt -Recvruno SOURCE,TARGET,FSTYPE,OPTIONS,FSROOT "$root")
|
||||
write_source "$(unmangle "$device")"
|
||||
fi
|
||||
|
||||
printf '\t%-10s\t%-10s\t%-10s\t0 0\n\n' 'none' 'swap' "$options"
|
||||
printf '\t%-10s\t%-10s\t%-10s\t0 0\n\n' none swap "$options"
|
||||
done
|
||||
} </proc/swaps
|
||||
|
||||
# vim: et ts=2 sw=2 ft=sh:
|
||||
|
164
pacstrap.in
164
pacstrap.in
@ -1,5 +1,4 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
#
|
||||
# Assumptions:
|
||||
@ -11,17 +10,20 @@
|
||||
|
||||
shopt -s extglob
|
||||
|
||||
# m4_include() is recognized as a function definition
|
||||
# shellcheck source=common disable=SC1073,SC1065,SC1064,SC1072
|
||||
m4_include(common)
|
||||
|
||||
hostcache=0
|
||||
copykeyring=1
|
||||
initkeyring=0
|
||||
copymirrorlist=1
|
||||
pacman_args=()
|
||||
pacmode=-Sy
|
||||
unshare=0
|
||||
copyconf=0
|
||||
copyconfig=0
|
||||
pacman_config=/etc/pacman.conf
|
||||
|
||||
m4_include(common)
|
||||
pacman_args=()
|
||||
pacmode='-Sy'
|
||||
setup=chroot_setup
|
||||
unshare=0
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
@ -37,55 +39,16 @@ usage: ${0##*/} [options] root [packages...]
|
||||
-M Avoid copying the host's mirrorlist to the target
|
||||
-N Run in unshare mode as a regular user
|
||||
-P Copy the host's pacman config to the target
|
||||
-U Use pacman -U to install packages
|
||||
-U Use 'pacman -U' to install packages
|
||||
|
||||
-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.
|
||||
pacstrap installs packages to the specified new root directory.
|
||||
If no packages are given, pacstrap defaults to the 'base' metapackage.
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
pacstrap() {
|
||||
(( EUID == 0 )) || die 'This script must be run with root privileges'
|
||||
|
||||
# create obligatory directories
|
||||
msg 'Creating install root at %s' "$newroot"
|
||||
# shellcheck disable=SC2174 # permissions are perfectly fine here
|
||||
mkdir -m 0755 -p "$newroot"/var/{cache/pacman/pkg,lib/pacman,log} "$newroot"/{dev,run,etc/pacman.d}
|
||||
# shellcheck disable=SC2174 # permissions are perfectly fine here
|
||||
mkdir -m 1777 -p "$newroot"/tmp
|
||||
# shellcheck disable=SC2174 # permissions are perfectly fine here
|
||||
mkdir -m 0555 -p "$newroot"/{sys,proc}
|
||||
|
||||
# mount API filesystems
|
||||
$setup "$newroot" || die "failed to setup chroot %s" "$newroot"
|
||||
|
||||
if [[ ! -d $newroot/etc/pacman.d/gnupg ]]; then
|
||||
if (( initkeyring )); then
|
||||
pacman-key --gpgdir "$newroot"/etc/pacman.d/gnupg --init
|
||||
elif (( copykeyring )) && [[ -d /etc/pacman.d/gnupg ]]; then
|
||||
# if there's a keyring on the host, copy it into the new root
|
||||
cp -a --no-preserve=ownership /etc/pacman.d/gnupg "$newroot/etc/pacman.d/"
|
||||
fi
|
||||
fi
|
||||
|
||||
msg 'Installing packages to %s' "$newroot"
|
||||
if ! $pid_unshare pacman -r "$newroot" "${pacman_args[@]}"; then
|
||||
die 'Failed to install packages to new root'
|
||||
fi
|
||||
|
||||
if (( copymirrorlist )); then
|
||||
# install the host's mirrorlist onto the new root
|
||||
cp -a /etc/pacman.d/mirrorlist "$newroot/etc/pacman.d/"
|
||||
fi
|
||||
|
||||
if (( copyconf )); then
|
||||
cp -a "$pacman_config" "$newroot/etc/pacman.conf"
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ -z $1 || $1 = @(-h|--help) ]]; then
|
||||
usage
|
||||
exit $(( $# ? 0 : 1 ))
|
||||
@ -94,20 +57,20 @@ fi
|
||||
while getopts ':C:cDGiKMNPU' flag; do
|
||||
case $flag in
|
||||
C)
|
||||
pacman_config=$OPTARG
|
||||
;;
|
||||
D)
|
||||
pacman_args+=(-dd)
|
||||
pacman_config="$OPTARG"
|
||||
;;
|
||||
c)
|
||||
hostcache=1
|
||||
;;
|
||||
i)
|
||||
interactive=1
|
||||
D)
|
||||
pacman_args+=(-dd)
|
||||
;;
|
||||
G)
|
||||
copykeyring=0
|
||||
;;
|
||||
i)
|
||||
interactive=1
|
||||
;;
|
||||
K)
|
||||
initkeyring=1
|
||||
;;
|
||||
@ -115,53 +78,96 @@ while getopts ':C:cDGiKMNPU' flag; do
|
||||
copymirrorlist=0
|
||||
;;
|
||||
N)
|
||||
setup=unshare_setup
|
||||
unshare=1
|
||||
;;
|
||||
P)
|
||||
copyconf=1
|
||||
copyconfig=1
|
||||
;;
|
||||
U)
|
||||
pacmode=-U
|
||||
pacmode='-U'
|
||||
;;
|
||||
:)
|
||||
die '%s: option requires an argument -- '\''%s'\' "${0##*/}" "$OPTARG"
|
||||
die "%s: option requires an argument -- '%s'" "${0##*/}" "$OPTARG"
|
||||
;;
|
||||
?)
|
||||
die '%s: invalid option -- '\''%s'\' "${0##*/}" "$OPTARG"
|
||||
die "%s: invalid option -- '%s'" "${0##*/}" "$OPTARG"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift $(( OPTIND - 1 ))
|
||||
|
||||
(( $# )) || die "No root directory specified"
|
||||
newroot=$1
|
||||
shift
|
||||
(( $# )) || die 'No root directory specified'
|
||||
newroot="$1"; shift
|
||||
[[ -d "$newroot" ]] || die '%s: not a directory' "$newroot"
|
||||
|
||||
[[ -d $newroot ]] || die "%s is not a directory" "$newroot"
|
||||
|
||||
tmpfile="$(mktemp -t pacman.conf.XXXX)"
|
||||
cp "$pacman_config" "$tmpfile"
|
||||
sed -i 's/^DownloadUser/#&/' "$tmpfile"
|
||||
pacman_config="$tmpfile"
|
||||
|
||||
pacman_args+=("$pacmode" "${@:-base}" --config="$pacman_config" --disable-sandbox)
|
||||
|
||||
if (( ! hostcache )); then
|
||||
pacman_args+=(--cachedir="$newroot/var/cache/pacman/pkg")
|
||||
fi
|
||||
pacman_args+=("$pacmode" "${@:-base}" --config="$pacman_config")
|
||||
|
||||
if (( ! interactive )); then
|
||||
pacman_args+=(--noconfirm)
|
||||
fi
|
||||
|
||||
set -e
|
||||
gpgdir="$(pacman-conf --config="$pacman_config" GPGDir)"
|
||||
cachedir="$(pacman-conf --config="$pacman_config" CacheDir)"
|
||||
if (( copyconfig )); then
|
||||
dbpath="$newroot$(pacman-conf --config="$pacman_config" DBPath)"
|
||||
logfile="$newroot$(pacman-conf --config="$pacman_config" LogFile)"
|
||||
cachedir_target="$newroot$cachedir"
|
||||
gpgdir_target="$newroot$gpgdir"
|
||||
|
||||
pacman_args+=(--dbpath="$dbpath" --logfile="$logfile")
|
||||
else
|
||||
dbpath="$newroot"/var/lib/pacman
|
||||
logfile="$newroot"/var/log/pacman.log
|
||||
cachedir_target="$newroot"/var/cache/pacman/pkg
|
||||
gpgdir_target="$newroot"/etc/pacman.d/gnupg
|
||||
fi
|
||||
|
||||
if (( ! hostcache )); then
|
||||
cachedir="$cachedir_target"
|
||||
fi
|
||||
pacman_args+=(--cachedir="$cachedir")
|
||||
set +e
|
||||
|
||||
pacstrap() {
|
||||
check_root
|
||||
|
||||
# create obligatory directories
|
||||
msg 'Creating install root at %s' "$newroot"
|
||||
mkdir -m 0755 -p "$dbpath" "$cachedir_target" "$(dirname "$logfile")" "$newroot"/{dev,run,etc/pacman.d}
|
||||
mkdir -m 1777 -p "$newroot"/tmp
|
||||
mkdir -m 0555 -p "$newroot"/{sys,proc}
|
||||
|
||||
# mount API filesystems
|
||||
$setup "$newroot"
|
||||
|
||||
if [[ ! -d "$gpgdir_target" ]]; then
|
||||
if (( initkeyring )); then
|
||||
pacman-key --gpgdir "$gpgdir_target" --init
|
||||
elif (( copykeyring )) && [[ -d "$gpgdir" ]]; then
|
||||
# if there's a keyring on the host, copy it into the new root
|
||||
cp -aT --no-preserve=ownership "$gpgdir" "$gpgdir_target"
|
||||
fi
|
||||
fi
|
||||
|
||||
msg 'Installing packages to %s' "$newroot"
|
||||
$pid_unshare pacman --root "$newroot" "${pacman_args[@]}" || die 'Failed to install packages to new root'
|
||||
|
||||
if (( copymirrorlist )); then
|
||||
# install the host's mirrorlist onto the new root
|
||||
cp -a /etc/pacman.d/mirrorlist "$newroot"/etc/pacman.d/ || warning "Failed to copy the host's mirrorlist to new root"
|
||||
fi
|
||||
|
||||
if (( copyconfig )); then
|
||||
cp -a "$pacman_config" "$newroot"/etc/pacman.conf
|
||||
fi
|
||||
}
|
||||
|
||||
if (( unshare )); then
|
||||
setup=unshare_setup
|
||||
$mount_unshare bash -c "$(declare_all); pacstrap"
|
||||
else
|
||||
setup=chroot_setup
|
||||
pacstrap
|
||||
fi
|
||||
|
||||
# TODO: There is a trap check on exit. Need to rework the trap handling with
|
||||
# hook-ins/callbacks to remove aux files
|
||||
rm "$tmpfile"
|
||||
# vim: et ts=2 sw=2 ft=sh:
|
||||
|
@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
. ./fstab-helpers
|
||||
. "${1:-./common}"
|
||||
. ./test/common
|
||||
|
||||
ASSERT_streq ' deleted' "$(unmangle "$(mangle ' deleted')")"
|
||||
|
@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
. ./fstab-helpers
|
||||
. "${1:-./common}"
|
||||
. ./test/common
|
||||
|
||||
optstring=rw,relatime,fd=29,pgrp=1,timeout=300,minproto=5,maxproto=5,direct
|
||||
@ -26,10 +26,8 @@ EXPECT_success optstring_has_option optstring maxproto
|
||||
|
||||
EXPECT_failure optstring_get_option optstring proto
|
||||
EXPECT_success optstring_get_option optstring maxproto
|
||||
# shellcheck disable=SC2154 # set via the optstring helper above
|
||||
ASSERT_streq "$maxproto" "5"
|
||||
EXPECT_success optstring_get_option optstring timeout
|
||||
# shellcheck disable=SC2154 # set via the optstring helper above
|
||||
ASSERT_streq "$timeout" "300"
|
||||
|
||||
optstring_remove_option optstring pgrp
|
||||
|
@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
. ./fstab-helpers
|
||||
. "${1:-./common}"
|
||||
. ./test/common
|
||||
|
||||
EXPECT_success valid_number_of_base 16 feedfacebeef
|
||||
|
Loading…
Reference in New Issue
Block a user