mirror of
https://codeberg.org/privacy1st/arch-installer
synced 2024-12-23 02:16:05 +01:00
import from privacy1st/arch
This commit is contained in:
commit
a99cba2f6e
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/.idea/
|
29
PKGBUILD
Normal file
29
PKGBUILD
Normal file
@ -0,0 +1,29 @@
|
||||
# Maintainer: Daniel Langbein <daniel@systemli.org>
|
||||
_pkgname=installer
|
||||
_reponame=arch
|
||||
pkgname="de-p1st-$_pkgname"
|
||||
pkgver=0.2.2
|
||||
pkgrel=1
|
||||
pkgdesc="Installer for Arch Linux written in Bash"
|
||||
arch=('any')
|
||||
url="https://codeberg.org/privacy1st/${_reponame}"
|
||||
license=('MIT')
|
||||
depends=('dialog')
|
||||
makedepends=('git')
|
||||
backup=(etc/"${pkgname}"/installer.cfg) # Use relative paths without leading slash
|
||||
source=("git+${url}.git")
|
||||
sha256sums=('SKIP')
|
||||
|
||||
package() {
|
||||
cd "${_reponame}/pkg/${pkgname}"
|
||||
|
||||
install -Dm0544 de-p1st-installer.sh "$pkgdir"/usr/bin/"${pkgname}"
|
||||
|
||||
install -Dm0644 lib/block-device.sh "$pkgdir"/usr/lib/"${pkgname}"/block-device.sh
|
||||
install -Dm0644 lib/user-input.sh "$pkgdir"/usr/lib/"${pkgname}"/user-input.sh
|
||||
install -Dm0644 lib/util.sh "$pkgdir"/usr/lib/"${pkgname}"/util.sh
|
||||
|
||||
install -Dm0644 -o0 installer.cfg "$pkgdir"/etc/"${pkgname}"/installer.cfg
|
||||
install -Dm0644 -o0 example-vbox.cfg "$pkgdir"/etc/"${pkgname}"/example-vbox.cfg
|
||||
install -Dm0644 -o0 example-headless-docker.cfg "$pkgdir"/etc/"${pkgname}"/example-headless-docker.cfg
|
||||
}
|
66
README.md
Normal file
66
README.md
Normal file
@ -0,0 +1,66 @@
|
||||
# Arch Linux Installer
|
||||
|
||||
## Running the installer
|
||||
|
||||
### Via ssh and custom livemedium
|
||||
|
||||
1) Build custom [archiso](https://gitlab.archlinux.org/archlinux/archiso) live medium using docker in privileged mode:
|
||||
|
||||
```shell
|
||||
# dependencies: docker, docker-compose
|
||||
|
||||
cd build-iso
|
||||
# sudo systemctl start docker
|
||||
sudo docker-compose run archiso
|
||||
ls out/out_dir/*.iso
|
||||
```
|
||||
|
||||
**On the target computer:**
|
||||
|
||||
2) Boot into the live medium
|
||||
3) Connect to the Internet
|
||||
* [wifi instructions](https://wiki.archlinux.org/title/Iwd#iwctl)
|
||||
|
||||
**On some other computer:**
|
||||
|
||||
4) Connect to target computer via `ssh`
|
||||
* See [build-iso/out/out_dir/ssh-host-fingerprints](../../build-iso/out/out_dir/ssh-host-fingerprints)
|
||||
for verification of the ssh fingerprint of the target computer
|
||||
5) (Optional) Copy and adjust one of the example configurations (`/etc/de-p1st-installer/*.cfg`) to `/etc/de-p1st-installer/installer.cfg`
|
||||
* If this step is skipped, then (most) of the configuration can be entered interactively
|
||||
* TODO: if `ADDITIONAL_PKGS` is empty, interactively ask for it.
|
||||
6) Run the installer:
|
||||
|
||||
```shell
|
||||
de-p1st-installer
|
||||
```
|
||||
|
||||
### Via official livemedium
|
||||
|
||||
1) Boot into [official live medium](https://archlinux.org/download/)
|
||||
2) Add to `/etc/pacman.conf`:
|
||||
|
||||
```shell
|
||||
[de-p1st]
|
||||
SigLevel = Optional TrustAll
|
||||
Server = https://arch.p1st.de
|
||||
```
|
||||
|
||||
3) (Optional) Install screen with a `screenrc` configuration file for long scrollback history
|
||||
|
||||
```shell
|
||||
pacman -Sy
|
||||
pacman -S de-p1st-screen
|
||||
|
||||
# And then start it with:
|
||||
screen
|
||||
```
|
||||
|
||||
4) Install `de-p1st-installer` to get the install script
|
||||
|
||||
```shell
|
||||
pacman -Sy
|
||||
pacman -S de-p1st-installer
|
||||
```
|
||||
|
||||
5) Continue with step (5) of "Via custom livemedium"
|
481
de-p1st-installer.sh
Executable file
481
de-p1st-installer.sh
Executable file
@ -0,0 +1,481 @@
|
||||
#!/bin/bash
|
||||
|
||||
# load config
|
||||
source /etc/de-p1st-installer/installer.cfg || { exit 1; }
|
||||
|
||||
# load functions
|
||||
source /usr/lib/de-p1st-installer/util.sh || { exit 1; }
|
||||
source /usr/lib/de-p1st-installer/user-input.sh || { exit 1; }
|
||||
source /usr/lib/de-p1st-installer/block-device.sh || { exit 1; }
|
||||
|
||||
function main() {
|
||||
# @pre
|
||||
# bash libraries imported
|
||||
# @post
|
||||
# installation finished
|
||||
|
||||
check_network || return $?
|
||||
system_time || return $?
|
||||
# in: BOOT_FIRMWARE, FS, HOSTNAME, USERNAME, USER_PWD, FDE, LUKS_PWD; (all variables are optional)
|
||||
# out: BOOT_FIRMWARE, FS, HOSTNAME, USERNAME, USER_PWD, FDE, LUKS_PWD (if FDE='true')
|
||||
get_user_input || return $?
|
||||
# in: CPU_VENDOR (optional)
|
||||
# out: CPU_VENDOR
|
||||
get_cpu_vendor || return $?
|
||||
|
||||
# in: FS
|
||||
# out: FS_DEFAULT_MOUNT_OPTIONS
|
||||
get_default_mount_options || return $?
|
||||
# in: FS
|
||||
# out: FS_ADDITIONAL_MOUNT_OPTIONS
|
||||
get_additional_mount_options || return $?
|
||||
|
||||
# in: TARGET_BLOCK_DEVICE, BOOT_FIRMWARE
|
||||
# out: BOOT_PART, LUKS_PART
|
||||
partition || return $?
|
||||
# in: BOOT_FIRMWARE, BOOT_PART, LUKS_PART, FDE, LUKS_PWD, FS
|
||||
# out: LUKS_PART_UUID (if FDE='true'), DATA_PART
|
||||
format || return $?
|
||||
|
||||
# Combine default and additional mount options
|
||||
# out: FS_MOUNT_OPTIONS
|
||||
{
|
||||
TMP1=("${FS_DEFAULT_MOUNT_OPTIONS[@]}" "${FS_ADDITIONAL_MOUNT_OPTIONS[@]}") || return $?
|
||||
# Join array elements by ","
|
||||
join_by ',' TMP1 FS_MOUNT_OPTIONS || return $?
|
||||
}
|
||||
|
||||
mount_partitions || return $?
|
||||
|
||||
# in: BOOT_FIRMWARE, PACSTRAP_INTERACTIVE (optional)
|
||||
run_pacstrap || return $?
|
||||
# in: FS
|
||||
run_genfstab || return $?
|
||||
|
||||
# in: HOSTNAME, FQDN (optional), STATIC_IP (optional), IPV6_CAPABLE (optional)
|
||||
config_hostname_and_hosts || return $?
|
||||
# in: USERNAME, USER_PWD, ROOT_PWD (optional)
|
||||
user_and_pwd || return $?
|
||||
|
||||
sudo arch-chroot /mnt mkinitcpio -P || return $?
|
||||
# in: TARGET_BLOCK_DEVICE, FDE, LUKS_PART_UUID
|
||||
bootloader || return $?
|
||||
|
||||
if [ "${LEAVE_MOUNTED}" = 'true' ]; then
|
||||
echo 'Leaving partitions below /mnt mounted and '"${DATA_PART}"' opened.'
|
||||
else
|
||||
sudo umount -R /mnt || return $?
|
||||
if [ "${FDE}" = 'true' ] ; then
|
||||
sudo cryptsetup luksClose "$(basename "${DATA_PART}")" || return $?
|
||||
fi
|
||||
fi
|
||||
echo 'Finished installation without errors!'
|
||||
}
|
||||
|
||||
function check_network() {
|
||||
echo 'Sending ping to wikipedia.de ...'
|
||||
ping -c 1 wikipedia.de >/dev/null || {
|
||||
echo 'Pleas set up network access.'
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
function system_time() {
|
||||
# Use timedatectl(1) to ensure the system clock is accurate
|
||||
timedatectl set-ntp true
|
||||
}
|
||||
|
||||
function increase_cow_space() {
|
||||
# May be useful when running 'pacman -Syu' on the live medium.
|
||||
# Usually not necessary!
|
||||
|
||||
# make sure that we are on a live medium:
|
||||
findmnt /run/archiso/cowspace >/dev/null || {
|
||||
echo 'Not on live medium, did not increase cowspace!'
|
||||
return 1
|
||||
}
|
||||
|
||||
echo 'Increasing cowspace partition of live medium ...'
|
||||
sudo mount -o remount,size=2G /run/archiso/cowspace || return $?
|
||||
}
|
||||
|
||||
function get_user_input() {
|
||||
# @post
|
||||
# BOOT_FIRMWARE: 'uefi' | 'bios'
|
||||
# FS: 'BTRFS' | 'EXT4' | 'F2FS'
|
||||
# FS_BTRFS_SUBVOL_LAYOUT: 'root_only' | '@root@home'
|
||||
# HOSTNAME
|
||||
# USERNAME, USER_PWD
|
||||
# FDE: 'true' | 'false'
|
||||
# LUKS_PWD: only set if FDE='true'
|
||||
|
||||
get_block_devices_with_size || return $?
|
||||
single_choice_if_empty TARGET_BLOCK_DEVICE 'Select target device for installation' BLOCK_DEVICE_SIZES || return $?
|
||||
|
||||
if [ "${BOOT_FIRMWARE}" = 'autodetect' ]; then
|
||||
# Detect boot firmware type: https://askubuntu.com/a/162573
|
||||
|
||||
# Check exit code; if 0 EFI, else BIOS.
|
||||
# "-q": tell grep to output nothing
|
||||
if dmesg | grep -q 'EFI v'; then
|
||||
echo 'Detected EFI boot.'
|
||||
BOOT_FIRMWARE='uefi'
|
||||
else
|
||||
echo 'Detected BIOS boot'
|
||||
BOOT_FIRMWARE='bios'
|
||||
fi
|
||||
|
||||
else # If $BOOT_FIRMWARE is empty: Let user select BIOS type
|
||||
TMP1=('uefi' 'Newer mainboards' \
|
||||
'bios' 'Legacy BIOS on older mainboards')
|
||||
single_choice_if_empty BOOT_FIRMWARE 'Select your bios type' TMP1 || return $?
|
||||
fi
|
||||
|
||||
TMP1=('BTRFS' 'Allows snapshots and dynamic extension of the FS' \
|
||||
'EXT4' 'Default FS of many distributions' \
|
||||
'F2FS' 'Flash-Friendly-FS for SSD or NVMe')
|
||||
single_choice_if_empty FS 'Select filesystem to use' TMP1 || return $?
|
||||
|
||||
if [ "${FS}" = 'BTRFS' ]; then
|
||||
TMP1=('root_only' 'Just one subvolume for "/".' \
|
||||
'@root@home' 'Two subvolumes @ and @home. This configuration allows usage of "Timeshift".')
|
||||
single_choice_if_empty FS_BTRFS_SUBVOL_LAYOUT 'Select your preferred subvolume layout' TMP1 || return $?
|
||||
fi
|
||||
|
||||
ask_user_if_empty HOSTNAME 'Enter hostname:' || return $?
|
||||
ask_user_if_empty USERNAME 'Enter username:' || return $?
|
||||
|
||||
if [ -z "${USER_PWD}" ]; then
|
||||
ask_user_if_empty USER_PWD 'Enter a user password:' || return $?
|
||||
ask_user_if_empty USER_PWD2 'Please enter the password again:' || return $?
|
||||
# shellcheck disable=SC2153
|
||||
[[ "${USER_PWD}" == "${USER_PWD2}" ]] || {
|
||||
echo 'Passwords did not match';
|
||||
exit 1;
|
||||
}
|
||||
fi
|
||||
|
||||
TMP1=('true' 'Yes' 'false' 'No')
|
||||
single_choice_if_empty FDE 'Shall Full-Disk-Encryption be enabled?' TMP1 || return $?
|
||||
|
||||
if [ "${FDE}" = 'true' ] && [ -z "${LUKS_PWD}" ]; then
|
||||
ask_user_if_empty LUKS_PWD 'Enter a disk encryption password:' || return $?
|
||||
ask_user_if_empty LUKS_PWD2 'Please enter the password again:' || return $?
|
||||
# shellcheck disable=SC2153
|
||||
[[ "${LUKS_PWD}" == "${LUKS_PWD2}" ]] || {
|
||||
echo 'Passwords did not match';
|
||||
exit 1;
|
||||
}
|
||||
fi
|
||||
}
|
||||
|
||||
function get_default_mount_options() {
|
||||
# @pre
|
||||
# FS
|
||||
# @post
|
||||
# FS_DEFAULT_MOUNT_OPTIONS (array)
|
||||
|
||||
FS_DEFAULT_MOUNT_OPTIONS=()
|
||||
|
||||
case "${FS}" in
|
||||
BTRFS)
|
||||
# "compress=lzo": archwiki -> Btrfs#Compression
|
||||
# "compress=zstd:1":
|
||||
# -> https://btrfs.wiki.kernel.org/index.php/Compression#What_are_the_differences_between_compression_methods.3F
|
||||
# -> https://fedoraproject.org/wiki/Changes/BtrfsTransparentCompression#Q:_Why_use_zstd:1_specifically.3F
|
||||
#
|
||||
# "Enable compression (better performance, longer flash lifespan)"
|
||||
FS_DEFAULT_MOUNT_OPTIONS+=('compress=zstd:1')
|
||||
;;
|
||||
EXT4)
|
||||
# https://wiki.archlinux.org/index.php/Ext4#Enabling_metadata_checksums
|
||||
# If the CPU supports SSE 4.2, make sure the crc32c_intel kernel module is loaded
|
||||
FS_DEFAULT_MOUNT_OPTIONS+=('metadata_csum')
|
||||
;;
|
||||
F2FS)
|
||||
# When mounting the filesystem, specify compress_algorithm=(lzo|lz4|zstd|lzo-rle).
|
||||
# Using compress_extension=txt will cause all txt files to be compressed by default.
|
||||
FS_DEFAULT_MOUNT_OPTIONS+=('compress_algorithm=lz4')
|
||||
;;
|
||||
*)
|
||||
echo 'Filesystem '"${FS}"' not yet supported!'
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function get_additional_mount_options() {
|
||||
# @pre
|
||||
# FS
|
||||
# @post
|
||||
# FS_ADDITIONAL_MOUNT_OPTIONS (array)
|
||||
|
||||
case "${FS}" in
|
||||
BTRFS)
|
||||
# noatime, nodiratime:
|
||||
# - The atime options do impact drive performance;
|
||||
# - noatime implies nodiratime, one does not need to specify both;
|
||||
# - The noatime option fully disables writing file access times to the drive every time you read a file.
|
||||
# This works well for almost all applications, except for those that need to know if a file has been
|
||||
# read since the last time it was modified.
|
||||
TMP1=('noatime' 'Don'\''t write file/folder access times' 'on' 'ssd' 'Enable if using SSD/NVMe' 'off')
|
||||
;;
|
||||
EXT4)
|
||||
TMP1=('noatime' 'Don'\''t write file/folder access times' 'on')
|
||||
;;
|
||||
F2FS)
|
||||
# shellcheck disable=SC2034
|
||||
TMP1=('noatime' 'Don'\''t write file/folder access times' 'on')
|
||||
;;
|
||||
*)
|
||||
echo 'Filesystem '"${FS}"' not yet supported!'
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
multi_choice_if_empty FS_ADDITIONAL_MOUNT_OPTIONS 'Select mount options' TMP1 || return $?
|
||||
}
|
||||
|
||||
function mount_partitions() {
|
||||
# @pre
|
||||
# FS, FS_BTRFS_SUBVOL_LAYOUT, FS_MOUNT_OPTIONS, DATA_PART, BOOT_PART
|
||||
|
||||
if [ "${FS}" = 'BTRFS' ]; then
|
||||
case "${FS_BTRFS_SUBVOL_LAYOUT}" in
|
||||
'root_only')
|
||||
# Nothing special; same steps as for a regular FS
|
||||
echo 'Mounting data partition with options: '"${FS_MOUNT_OPTIONS}"
|
||||
sudo mount -o "${FS_MOUNT_OPTIONS}" "${DATA_PART}" /mnt || return $?
|
||||
;;
|
||||
'@root@home')
|
||||
# Timeshift BTRFS subvol layout:
|
||||
# https://github.com/teejee2008/timeshift#supported-system-configurations
|
||||
|
||||
# Mount top level subvolume
|
||||
sudo mount -o subvolid=5 "${DATA_PART}" /mnt || return $?
|
||||
# Create subvolumes @ and @home
|
||||
sudo btrfs subvolume create /mnt/@ || return $?
|
||||
sudo btrfs subvolume create /mnt/@home || return $?
|
||||
# List the created subvolumes
|
||||
sudo btrfs subvolume list /mnt || return $?
|
||||
# Umount the top level subvolume
|
||||
sudo umount -R /mnt || return $?
|
||||
|
||||
echo 'Mounting @ and @home subvolumes with options: '"${FS_MOUNT_OPTIONS}"
|
||||
sudo mount -o 'subvol=@,'"${FS_MOUNT_OPTIONS}" "${DATA_PART}" /mnt || return $?
|
||||
sudo mkdir /mnt/home || return $?
|
||||
sudo mount -o 'subvol=@home,'"${FS_MOUNT_OPTIONS}" "${DATA_PART}" /mnt/home || return $?
|
||||
;;
|
||||
*)
|
||||
echo 'BTRFS subvolume layout '"${FS_BTRFS_SUBVOL_LAYOUT}"' not supported!'
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
else
|
||||
echo 'Mounting data partition with options: '"${FS_MOUNT_OPTIONS}"
|
||||
sudo mount -o "${FS_MOUNT_OPTIONS}" "${DATA_PART}" /mnt || return $?
|
||||
fi
|
||||
|
||||
echo 'Mounting boot partition ...'
|
||||
sudo mkdir /mnt/boot || return $?
|
||||
sudo mount "${BOOT_PART}" /mnt/boot || return $?
|
||||
}
|
||||
|
||||
function run_pacstrap() {
|
||||
# @pre
|
||||
# BOOT_FIRMWARE
|
||||
# PACSTRAP_INTERACTIVE: optional, 'true'
|
||||
|
||||
echo 'Running pacstrap ...'
|
||||
PKGS=("${ADDITIONAL_PKGS[@]}")
|
||||
|
||||
case "${BOOT_FIRMWARE}" in
|
||||
uefi)
|
||||
PKGS+=('de-p1st-base')
|
||||
;;
|
||||
bios)
|
||||
echo 'Not yet implemented'
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
echo 'Not yet implemented!'
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# If CPU_VENDOR is not empty, then
|
||||
if [ -n "${CPU_VENDOR}" ]; then
|
||||
case "${CPU_VENDOR}" in
|
||||
amd)
|
||||
PKGS+=('de-p1st-ucode-amd')
|
||||
;;
|
||||
intel)
|
||||
PKGS+=('de-p1st-ucode-intel')
|
||||
;;
|
||||
none)
|
||||
PKGS+=('de-p1st-ucode-placeholder')
|
||||
;;
|
||||
*)
|
||||
echo 'Invalid option: '"${CPU_VENDOR}"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
local args=()
|
||||
if [ "${PACSTRAP_INTERACTIVE}" = 'true' ]; then
|
||||
args+=('-i') # run interactively
|
||||
fi
|
||||
args+=('/mnt')
|
||||
|
||||
sudo pacstrap "${args[@]}" "${PKGS[@]}" || return $?
|
||||
}
|
||||
|
||||
function run_genfstab() {
|
||||
# @pre
|
||||
# FS
|
||||
|
||||
echo 'Generating fstab ...'
|
||||
local fstab
|
||||
fstab="$(genfstab -U /mnt)"
|
||||
|
||||
case "${FS}" in
|
||||
BTRFS)
|
||||
# Remove "subvolid=..." mount option but leave "subvol=..." mount option
|
||||
fstab=$(printf '%s' "${fstab}" | sed 's/,subvolid=[^,\s]\+//') || return $?
|
||||
# Check if fstab does still contain subvolid mount option
|
||||
if printf '%s' "${fstab}" | grep -q 'subvolid='; then
|
||||
echo 'This should not happen!'
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
EXT4)
|
||||
true
|
||||
;;
|
||||
F2FS)
|
||||
true
|
||||
;;
|
||||
*)
|
||||
echo 'Filesystem '"${FS}"' not yet supported!'
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
printf '%s' "${fstab}" | sudo tee /mnt/etc/fstab >/dev/null || return $?
|
||||
}
|
||||
|
||||
function config_hostname_and_hosts() {
|
||||
# @pre
|
||||
# HOSTNAME
|
||||
# FQDN: optional, e.g. sub.domain.de
|
||||
# STATIC_IP: optional, e.g. 93.133.433.133
|
||||
# IPV6_CAPABLE: optional, 'true'
|
||||
|
||||
echo 'Set hostname ...'
|
||||
echo "${HOSTNAME}" | sudo tee /mnt/etc/hostname >/dev/null || return $?
|
||||
|
||||
echo 'Create hosts file ...'
|
||||
# If the system has a permanent IP address, it should be used instead of 127.0.1.1.
|
||||
# * https://wiki.archlinux.org/index.php/Installation_guide#Network_configuration
|
||||
|
||||
# Desirable entries IPv4/IPv6:
|
||||
# * https://man.archlinux.org/man/hosts.5#EXAMPLES
|
||||
|
||||
# If FQDN not given, use $HOSTNAME.localdomain instead
|
||||
FQDN="${FQDN:="${HOSTNAME}.localdomain"}"
|
||||
# If STATIC_IP not given, use 127.0.1.1 instead
|
||||
STATIC_IP="${STATIC_IP:=127.0.1.1}"
|
||||
|
||||
echo '# The following lines are desirable for IPv4 capable hosts
|
||||
127.0.0.1 localhost
|
||||
# 127.0.1.1 is often used for the FQDN of the machine
|
||||
'"${STATIC_IP} ${FQDN} ${HOSTNAME}" | sudo tee /mnt/etc/hosts >/dev/null || return $?
|
||||
|
||||
if [ "${IPV6_CAPABLE}" = 'true' ]; then
|
||||
echo '
|
||||
# The following lines are desirable for IPv6 capable hosts
|
||||
::1 localhost ip6-localhost ip6-loopback
|
||||
ff02::1 ip6-allnodes
|
||||
ff02::2 ip6-allrouters' | sudo tee -a /mnt/etc/hosts >/dev/null || return $?
|
||||
fi
|
||||
}
|
||||
|
||||
function user_and_pwd() {
|
||||
# @pre
|
||||
# USERNAME
|
||||
# USER_PWD
|
||||
# ROOT_PWD (optional)
|
||||
|
||||
echo 'Adding user and changing shell to /bin/zsh ...'
|
||||
# -m: create home
|
||||
# -U: Create a group with the same name as the user, and add the user to this group.
|
||||
sudo arch-chroot /mnt useradd -m -s /usr/bin/zsh -g wheel "${USERNAME}" || return $?
|
||||
sudo arch-chroot /mnt chsh -s /usr/bin/zsh || return $?
|
||||
|
||||
# If ROOT_PWD is not given, the use USER_PWD for root user
|
||||
ROOT_PWD="${ROOT_PWD:="${USER_PWD}"}"
|
||||
|
||||
printf '%s:%s' "${USERNAME}" "${USER_PWD}" | sudo chpasswd --root /mnt || return $?
|
||||
printf '%s:%s' "root" "${ROOT_PWD}" | sudo chpasswd --root /mnt || return $?
|
||||
}
|
||||
|
||||
function bootloader() {
|
||||
# @pre
|
||||
# TARGET_BLOCK_DEVICE
|
||||
# FDE: 'true' | 'false'
|
||||
# LUKS_PART_UUID: required if FDE='true'
|
||||
|
||||
echo 'Installing grub ...'
|
||||
case "${BOOT_FIRMWARE}" in
|
||||
uefi)
|
||||
# portable fallback efi name for grub:
|
||||
# * https://www.rodsbooks.com/efi-bootloaders/installation.html#alternative-naming
|
||||
# * arch-chroot /mnt cp /boot/EFI/GRUB/grubx64.efi /boot/EFI/BOOT/bootx64.efi
|
||||
sudo arch-chroot /mnt grub-install --target=x86_64-efi --bootloader-id=GRUB --efi-directory=/boot --removable || return $?
|
||||
;;
|
||||
bios)
|
||||
sudo arch-chroot /mnt grub-install --target=i386-pc "${TARGET_BLOCK_DEVICE}" || return $?
|
||||
;;
|
||||
*)
|
||||
echo 'Not yet implemented!'
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo 'Generating /boot/grub/grub.cfg ...'
|
||||
{
|
||||
case "${FDE}" in
|
||||
true)
|
||||
# /etc/default/grub is managed by Holo. Therefore we should not manually modify it.
|
||||
# Instead, we create a holosript which writes $LUKS_PART_UUID into GRUB_CMDLINE_LINUX of /etc/default/grub
|
||||
{
|
||||
# Assert
|
||||
grep --quiet '^GRUB_CMDLINE_LINUX=""$' < /mnt/etc/default/grub || return
|
||||
|
||||
# Use filename .../20- for the holoscript so that it gets executed after the one from de-p1st-grub
|
||||
local holoScriptDir=/mnt/usr/share/holo/files/20-de-p1st-installer/etc/default/
|
||||
# The holoscript shall contain one 'sed "..."' command
|
||||
sudo mkdir -p "${holoScriptDir}" || return $?
|
||||
sudo echo '#!/bin/sh
|
||||
sed "s|^GRUB_CMDLINE_LINUX=\"\"\$|GRUB_CMDLINE_LINUX=\"cryptdevice=/dev/disk/by-uuid/'"${LUKS_PART_UUID}"':crypt\"|"' \
|
||||
| sudo tee "${holoScriptDir}"/grub.holoscript || return $?
|
||||
sudo chmod 0544 "${holoScriptDir}"/grub.holoscript || return $?
|
||||
}
|
||||
# Then we apply the holoscript
|
||||
sudo arch-chroot /mnt holo apply --force file:/etc/default/grub || return $?
|
||||
;;
|
||||
false)
|
||||
true
|
||||
;;
|
||||
*)
|
||||
echo 'Invalid option: '"${FDE}"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# And finally run grub-mkconfig
|
||||
sudo arch-chroot /mnt grub-mkconfig -o /boot/grub/grub.cfg || return $?
|
||||
}
|
||||
}
|
||||
|
||||
main "$@"
|
28
example-headless-docker.cfg
Normal file
28
example-headless-docker.cfg
Normal file
@ -0,0 +1,28 @@
|
||||
# Example config for a headless root server running docker
|
||||
|
||||
FQDN=v2202012136854137045.happysrv.de
|
||||
STATIC_IP=45.83.105.88
|
||||
IPV6_CAPABLE=true
|
||||
HOSTNAME=p1st-arch-1
|
||||
USERNAME=yoda
|
||||
|
||||
TARGET_BLOCK_DEVICE=/dev/sda
|
||||
BOOT_PART_SIZE=500 # MiB
|
||||
FS=BTRFS
|
||||
FS_ADDITIONAL_MOUNT_OPTIONS=('noatime')
|
||||
|
||||
CPU_VENDOR=none
|
||||
BOOT_FIRMWARE=uefi
|
||||
|
||||
LEAVE_MOUNTED=false
|
||||
PACSTRAP_INTERACTIVE=true
|
||||
|
||||
ADDITIONAL_PKGS=()
|
||||
# to skip pacman selections
|
||||
ADDITIONAL_PKGS+=('mkinitcpio' 'de-p1st-kernel-lts' 'de-p1st-ucode-placeholder' 'qemu-guest-agent')
|
||||
# remote ssh access
|
||||
ADDITIONAL_PKGS+=('de-p1st-ssh' 'de-p1st-ssh-key-yoda')
|
||||
# docker and docker-compose
|
||||
ADDITIONAL_PKGS+=('de-p1st-docker')
|
||||
# cronie
|
||||
ADDITIONAL_PKGS+=('de-p1st-cronie')
|
64
example-vbox.cfg
Normal file
64
example-vbox.cfg
Normal file
@ -0,0 +1,64 @@
|
||||
# Example config for testing in VirtualBox
|
||||
|
||||
# FQDN=domain.name.of.this.host.de
|
||||
# STATIC_IP=123.123.123.123
|
||||
# IPV6_CAPABLE=true
|
||||
HOSTNAME=yodaTest
|
||||
USERNAME=yoda
|
||||
|
||||
# One should rather enter these interactively than saving in this cfg.
|
||||
USER_PWD='test'
|
||||
FDE=false # enable FULL-DISK-ENCRYPTION
|
||||
LUKS_PWD='test'
|
||||
|
||||
# If unset, then USER_PWD will be used for ROOT_PWD
|
||||
# ROOT_PWD=test
|
||||
|
||||
TARGET_BLOCK_DEVICE=/dev/sda # /dev/vda for Virtual Machine Manager
|
||||
BOOT_PART_SIZE=500 # MiB
|
||||
FS=BTRFS
|
||||
FS_BTRFS_SUBVOL_LAYOUT='@root@home' # Subvolume layout that is supported by Timeshift
|
||||
FS_ADDITIONAL_MOUNT_OPTIONS=('noatime')
|
||||
|
||||
# If not booted into the target system, these values should be set:
|
||||
# CPU_VENDOR: 'autodetect' | 'amd' | 'intel' | 'none'
|
||||
CPU_VENDOR=autodetect
|
||||
# BOOT_FIRMWARE: 'autodetect' | 'uefi' | 'bios'
|
||||
BOOT_FIRMWARE=uefi
|
||||
|
||||
# If set to 'true', then the data, boot and luks partitions
|
||||
# will be left mounted/opened for manual inspection
|
||||
# after the installation
|
||||
LEAVE_MOUNTED=true
|
||||
PACSTRAP_INTERACTIVE=true
|
||||
|
||||
############## ADDITIONAL_PKGS from here on ##############
|
||||
ADDITIONAL_PKGS=()
|
||||
|
||||
# Specify the preferred providers for initramfs, de-p1st-kernel, de-p1st-ucode, de-p1st-sddm-theme
|
||||
ADDITIONAL_PKGS+=('mkinitcpio' 'de-p1st-kernel-lts' 'de-p1st-ucode-placeholder' 'de-p1st-sddm-theme-nordic')
|
||||
# VBox guest additions
|
||||
ADDITIONAL_PKGS+=('virtualbox-guest-utils')
|
||||
# XFCE4 desktop with HiDPI
|
||||
ADDITIONAL_PKGS+=('de-p1st-gpu-generic' 'de-p1st-xfce4-hidpi' 'de-p1st-sddm-tablet')
|
||||
# To be able to install ungoogled-chromium
|
||||
ADDITIONAL_PKGS+=('de-p1st-pacman-repo-chromium')
|
||||
|
||||
# If your network is unsafe, e.g. if you live in Germany, then include a VPN client
|
||||
ADDITIONAL_PKGS+=('riseup-vpn')
|
||||
# Smartcard (e.g. Nitrokey or Yubikey)
|
||||
ADDITIONAL_PKGS+=('de-p1st-smartcard')
|
||||
|
||||
|
||||
# Other programs
|
||||
ADDITIONAL_PKGS+=('firefox' 'signal-desktop' 'nextcloud-client' 'keepassxc' 'xournalpp' 'zotero')
|
||||
# Development
|
||||
ADDITIONAL_PKGS+=('intellij-idea-ultimate-edition' 'intellij-idea-ultimate-edition-jre')
|
||||
# Document viewer
|
||||
ADDITIONAL_PKGS+=('evince')
|
||||
# Gallery / image viewer
|
||||
ADDITIONAL_PKGS+=('nomacs' 'qt5-imageformats')
|
||||
# LibreOffice with spell checking (de, us) and hyphenation rules (de, us)
|
||||
ADDITIONAL_PKGS+=('libreoffice-fresh')
|
||||
ADDITIONAL_PKGS+=('hunspell-de' 'hunspell-en_US')
|
||||
ADDITIONAL_PKGS+=('hyphen' 'hyphen-de' 'hyphen-en')
|
4
installer.cfg
Normal file
4
installer.cfg
Normal file
@ -0,0 +1,4 @@
|
||||
# You can find some examples in the same directory
|
||||
# that can be used as starting point:
|
||||
# ./example-*.cfg
|
||||
# Just copy one of those over this file.
|
191
lib/block-device.sh
Normal file
191
lib/block-device.sh
Normal file
@ -0,0 +1,191 @@
|
||||
#
|
||||
# lsblk
|
||||
#
|
||||
# -d, --nodeps Do not print holder devices or slaves.
|
||||
# -p, --paths Print full device paths.
|
||||
# -l, --list Produce output in the form of a list.
|
||||
# -n, --noheadings
|
||||
# -x, --sort column
|
||||
|
||||
function get_uuid() {
|
||||
# arg $1: partition
|
||||
# arg $2: variable name to store UUID
|
||||
|
||||
local -n ptr=$2 || return $?
|
||||
ptr="$(blkid -o value -s UUID "${1}")" || return $?
|
||||
}
|
||||
|
||||
function get_partitions(){
|
||||
# arg $1: block-device
|
||||
# @post
|
||||
# PARTITIONS (array with one partition per entry)
|
||||
|
||||
# Remove first line of output (which is just the block device $1 itself)
|
||||
# with sed: sed 1d
|
||||
PARTITIONS="$(lsblk -pln -o name "${1}" | sed 1d)" || return $?
|
||||
|
||||
newline_separated_to_array PARTITIONS PARTITIONS || return $?
|
||||
}
|
||||
|
||||
function get_block_devices() {
|
||||
# @post
|
||||
# BLOCK_DEVICES (array with one entry for each block device)
|
||||
|
||||
# Get list of devices, one per line
|
||||
BLOCK_DEVICES="$(lsblk -dplnx size -o name | grep -Ev 'boot|rpmb|loop' | tac)" || return $?
|
||||
|
||||
newline_separated_to_array BLOCK_DEVICES BLOCK_DEVICES || return $?
|
||||
}
|
||||
|
||||
function get_block_devices_with_size() {
|
||||
# @post
|
||||
# BLOCK_DEVICE_SIZES (array with two entries for each block device: device path and device size)
|
||||
|
||||
# Get list of devices and their sizes, one pair per line
|
||||
# Use sed to remove multiple white spaces: sed 's|\s\s*| |'
|
||||
BLOCK_DEVICE_SIZES="$(lsblk -dplnx size -o name,size | grep -Ev 'boot|rpmb|loop' | sed 's|\s\s*| |' | tac)" || return $?
|
||||
|
||||
newline_to_space BLOCK_DEVICE_SIZES || return $?
|
||||
space_separated_to_array BLOCK_DEVICE_SIZES BLOCK_DEVICE_SIZES || return $?
|
||||
}
|
||||
|
||||
|
||||
function partition() {
|
||||
# Creates two partitions:
|
||||
# - BOOT_PART by default 261mb
|
||||
# - LUKS_PART rest of the device for (encrypted) data
|
||||
#
|
||||
# @pre
|
||||
# TARGET_BLOCK_DEVICE
|
||||
# BOOT_FIRMWARE
|
||||
# @post
|
||||
# BOOT_PART
|
||||
# LUKS_PART
|
||||
|
||||
# if BOOT_PART_SIZE not given, set to default value
|
||||
BOOT_PART_SIZE="${BOOT_PART_SIZE:='261'}"
|
||||
# if too small, print warning and exit
|
||||
if [ "${BOOT_PART_SIZE}" -lt '261' ]; then
|
||||
echo 'BOOT_PART_SIZE should be larger than 260!';
|
||||
return 1;
|
||||
fi
|
||||
|
||||
# As our data partition is encrypted,
|
||||
# we need a separate boot partition!
|
||||
case "${BOOT_FIRMWARE}" in
|
||||
uefi)
|
||||
# EFI boot partition
|
||||
#
|
||||
# Create a partition with fat32 as the file system type and set the
|
||||
# esp flag on it.
|
||||
sudo parted --script "${TARGET_BLOCK_DEVICE}" -- mklabel gpt \
|
||||
mkpart ESP fat32 2Mib "${BOOT_PART_SIZE}MiB" \
|
||||
set 1 esp on \
|
||||
mkpart primary "${BOOT_PART_SIZE}MiB" 100% || return $?
|
||||
|
||||
get_partitions "${TARGET_BLOCK_DEVICE}" || return $?
|
||||
BOOT_PART="${PARTITIONS[0]}"
|
||||
LUKS_PART="${PARTITIONS[1]}"
|
||||
;;
|
||||
bios)
|
||||
# > On a BIOS/GPT configuration, a BIOS boot partition is required. GRUB embeds its `core.img`
|
||||
# > into this partition.
|
||||
# > For parted set/activate the flag bios_grub on the partition.
|
||||
#
|
||||
# archwiki -> GRUB#GUID_Partition_Table_(GPT)_specific_instructions
|
||||
# https://www.gnu.org/software/grub/manual/grub/html_node/BIOS-installation.html#BIOS-installation
|
||||
sudo parted --script "${TARGET_BLOCK_DEVICE}" -- mklabel gpt \
|
||||
mkpart primary 1MiB 2MiB \
|
||||
set 1 bios_grub on \
|
||||
mkpart primary 2MiB "${BOOT_PART_SIZE}MiB" \
|
||||
mkpart primary "${BOOT_PART_SIZE}MiB" 100% || return $?
|
||||
|
||||
get_partitions "${TARGET_BLOCK_DEVICE}" || return $?
|
||||
BOOT_PART="${PARTITIONS[2]}"
|
||||
LUKS_PART="${PARTITIONS[3]}"
|
||||
;;
|
||||
*)
|
||||
echo 'Expected uefi or bios but got '"${BOOT_FIRMWARE}"' instead!'
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo 'boot partition: '"${BOOT_PART}"
|
||||
echo 'luks partition: '"${LUKS_PART}"
|
||||
}
|
||||
|
||||
function format() {
|
||||
# Formats BOOT_PART and LUKS_PART.
|
||||
# If encryption is enabled (FDE='true'), then an encrypted partition will be created at LUKS_PART
|
||||
# which can be accessed under DATA_PART.
|
||||
# If encryption is disabled, then DATA_PART is identical to LUKS_PART.
|
||||
#
|
||||
# @pre
|
||||
# BOOT_FIRMWARE
|
||||
# BOOT_PART
|
||||
# LUKS_PART
|
||||
# LUKS_PWD
|
||||
# FDE: 'true' | 'false'
|
||||
# FS
|
||||
# @post
|
||||
# LUKS_PART_UUID: only set if FDE='true'
|
||||
# DATA_PART: if FDE='true', then set to /dev/mapper/$(basename "${LUKS_PART}")
|
||||
# boot partition formatted
|
||||
# luks partition formatted and accessible under DATA_PART
|
||||
|
||||
echo 'Wiping old signatures from partitions ...'
|
||||
sudo wipefs "${BOOT_PART}" || return $?
|
||||
sudo wipefs "${LUKS_PART}" || return $?
|
||||
|
||||
echo 'Formatting boot partition '"${BOOT_PART}"' ...'
|
||||
sudo mkfs.fat -F32 "${BOOT_PART}" || return $?
|
||||
|
||||
case "${FDE}" in
|
||||
true)
|
||||
# Note:
|
||||
# FDE: GRUB does support LUKS2 since this commit: https://git.savannah.gnu.org/cgit/grub.git/commit/?id=365e0cc3e7e44151c14dd29514c2f870b49f9755
|
||||
# -> Using "--type luks1" is no longer required.
|
||||
echo 'Creating encrypted luks partition '"${LUKS_PART}"' ...'
|
||||
printf '%s' "${LUKS_PWD}" | sudo cryptsetup luksFormat \
|
||||
--cipher aes-xts-plain64 --key-size 512 --hash sha512 \
|
||||
--iter-time 10000 --use-random "${LUKS_PART}" || return $?
|
||||
|
||||
get_uuid "${LUKS_PART}" LUKS_PART_UUID || return $?
|
||||
|
||||
local luks_name
|
||||
luks_name=$(basename "${LUKS_PART}")
|
||||
DATA_PART="/dev/mapper/${luks_name}"
|
||||
|
||||
# open luks partition
|
||||
printf '%s' "${LUKS_PWD}" | sudo cryptsetup luksOpen "${LUKS_PART}" "${luks_name}" || return $?
|
||||
;;
|
||||
false)
|
||||
DATA_PART="${LUKS_PART}"
|
||||
;;
|
||||
*)
|
||||
echo 'Invalid option: '"${FDE}"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo 'Formatting the data partition '"${DATA_PART}"' ...'
|
||||
case "${FS}" in
|
||||
BTRFS)
|
||||
sudo mkfs.btrfs "${DATA_PART}" || return $?
|
||||
;;
|
||||
EXT4)
|
||||
# archwiki -> Ext4#Enabling_metadata_checksums
|
||||
sudo mkfs.ext4 -O metadata_csum "${DATA_PART}" || return $?
|
||||
;;
|
||||
F2FS)
|
||||
# archwiki -> F2FS#Creating_a_F2FS_file_system
|
||||
# - requires f2fs-tools
|
||||
# - compression: "-O compression" and when mounting the filesystem, specify compress_algorithm=(lzo|lz4|zstd|lzo-rle)
|
||||
sudo mkfs.f2fs -O extra_attr,inode_checksum,sb_checksum,compression "${DATA_PART}" || return $?
|
||||
;;
|
||||
*)
|
||||
echo 'Filesystem '"${FS}"' is not yet supported!'
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
101
lib/user-input.sh
Normal file
101
lib/user-input.sh
Normal file
@ -0,0 +1,101 @@
|
||||
function ask_user_if_empty {
|
||||
# If variable with name $1 is empty, then ask for user input.
|
||||
#
|
||||
# Only one line of user input is allowed.
|
||||
# The input must not be empty.
|
||||
#
|
||||
# arg $1: name of variable to store user input
|
||||
# arg $2: text to display (e.g. "Enter hostname:")
|
||||
if [ "$#" -ne 2 ]; then
|
||||
echo 'ask_user_if_empty requires two args!';
|
||||
return 1
|
||||
fi
|
||||
for i in "$@"; do
|
||||
if [ -z "${i}" ]; then
|
||||
echo 'ask_user_if_empty: all given args must not be empty';
|
||||
return 1;
|
||||
fi
|
||||
done
|
||||
|
||||
local -n ptr=$1 || return $?
|
||||
if [ -z "${ptr}" ]; then
|
||||
# if ptr has no value yet, ask user for input!
|
||||
echo "${2}"
|
||||
read -r ptr || return $?
|
||||
fi
|
||||
|
||||
# check string length to be greater than zero!
|
||||
if [ "${#ptr}" -lt 1 ]; then
|
||||
echo 'The input must not be empty!';
|
||||
return 1;
|
||||
fi
|
||||
}
|
||||
|
||||
function single_choice_if_empty {
|
||||
# If variable with name $1 is empty, then let user select one of the given options.
|
||||
#
|
||||
# arg $1: name of variable to store the selected option
|
||||
# arg $2: text to display
|
||||
# arg $3: name of variable with array of options to display (for each option there must be two entries in the array: Item and description)
|
||||
if [ "$#" -ne 3 ]; then
|
||||
echo 'single_choice_if_empty requires three args!';
|
||||
return 1
|
||||
fi
|
||||
for i in "$@"; do
|
||||
if [ -z "${i}" ]; then
|
||||
echo 'single_choice_if_empty: all given args must not be empty';
|
||||
return 1;
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
local -n ptr=$1 || return $?
|
||||
if [ -z "${ptr}" ]; then
|
||||
# if ptr has no value yet, ask user for input!
|
||||
|
||||
local -n MENU_OPTIONS=$3 || return $?
|
||||
ptr=$(dialog --stdout --menu "${2}" 0 0 0 "${MENU_OPTIONS[@]}") || {
|
||||
echo 'Error during menu selection!'
|
||||
exit 1
|
||||
}
|
||||
clear
|
||||
fi
|
||||
}
|
||||
|
||||
function multi_choice_if_empty {
|
||||
# If variable with name $1 is empty, then let user select one or more of the given options.
|
||||
#
|
||||
# arg $1: name of variable to store array of selected options
|
||||
# arg $2: text to display
|
||||
# arg $3: name of variable with array of options to display (for each option there must be three entries in the array: Item, description, on/off)
|
||||
if [ "$#" -ne 3 ]; then
|
||||
echo 'multi_choice_if_empty requires three args!';
|
||||
return 1
|
||||
fi
|
||||
for i in "$@"; do
|
||||
if [ -z "${i}" ]; then
|
||||
echo 'multi_choice_if_empty: all given args must not be empty';
|
||||
return 1;
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
local -n ptr=$1 || return $?
|
||||
if [ -z "${ptr}" ]; then
|
||||
# if ptr has no value yet, ask user for input!
|
||||
|
||||
local -n MENU_OPTIONS=$3 || return $?
|
||||
TMP1=$(dialog --stdout --checklist "${2}" 0 0 0 "${MENU_OPTIONS[@]}") || {
|
||||
echo 'Error during menu selection!'
|
||||
exit 1
|
||||
}
|
||||
clear
|
||||
|
||||
# Result of dialog is space separated list
|
||||
# Store this as an array
|
||||
# Without newline at last array element: https://unix.stackexchange.com/a/519917/315162
|
||||
# readarray -d " " -t ptr < <(printf '%s' "$TMP1")
|
||||
#
|
||||
space_separated_to_array TMP1 "$1"
|
||||
fi
|
||||
}
|
83
lib/util.sh
Normal file
83
lib/util.sh
Normal file
@ -0,0 +1,83 @@
|
||||
function join_by() {
|
||||
# Join array elements with character $1
|
||||
#
|
||||
# arg $1: delimiter
|
||||
# arg $2: name of source array
|
||||
# arg $3: variable name to store result
|
||||
|
||||
local -n ptr=$2 || return $?
|
||||
local -n ptr2=$3 || return $?
|
||||
ptr2=$(printf ',%s' "${ptr[@]}")
|
||||
ptr2=${ptr2:1}
|
||||
}
|
||||
|
||||
function newline_to_space() {
|
||||
# Replaces all newlines with spaces
|
||||
#
|
||||
# arg $1: name of variable
|
||||
|
||||
local -n ptr=$1 || return $?
|
||||
# Replace newlines with spaces
|
||||
# See bash string substitution: https://gist.github.com/JPvRiel/b279524d3e56229a896477cb8082a72b
|
||||
|
||||
# echo "replacing newlines in str: -->$ptr<--"
|
||||
ptr="${ptr//$'\n'/' '}"
|
||||
# echo "new str is: -->$ptr<--"
|
||||
}
|
||||
|
||||
function newline_separated_to_array() {
|
||||
# Watch out for tailing newlines as these will get an empty array entry at the end.
|
||||
# $1 and $2 can be equal (if the result shall be written to the input variable)
|
||||
#
|
||||
# arg $1: name of variable with newline separated list
|
||||
# arg $2: name of array to store values into
|
||||
|
||||
local -n ptr=$1 || return $?
|
||||
local -n ptr2=$2 || return $?
|
||||
# ptr newline separated list.
|
||||
# Store this as array ptr2.
|
||||
readarray -t ptr2 <<<"${ptr}"
|
||||
}
|
||||
|
||||
function space_separated_to_array() {
|
||||
# arg $1: name of variable with space separated list
|
||||
# arg $2: name of array to store values into
|
||||
|
||||
local -n ptr=$1 || return $?
|
||||
# shellcheck disable=SC2178
|
||||
local -n ptr2=$2 || return $?
|
||||
# ptr space separated list.
|
||||
# Store this as array ptr2.
|
||||
# Without newline at last array element: https://unix.stackexchange.com/a/519917/315162
|
||||
readarray -d ' ' -t ptr2 < <(printf '%s' "${ptr}")
|
||||
}
|
||||
|
||||
function get_cpu_vendor() {
|
||||
# @pre
|
||||
# CPU_VENDOR ("", "autodetect")
|
||||
# @post
|
||||
# CPU_VENDOR ("amd", "intel", "none", "")
|
||||
|
||||
if [[ -z "${CPU_VENDOR}" ]] || [[ "${CPU_VENDOR}" == 'autodetect' ]]; then
|
||||
|
||||
# If run virtual environment (e.g. VirtualBox) then no CPU microcode is required
|
||||
if cat /proc/cpuinfo | grep '^flags.*hypervisor' >/dev/null; then
|
||||
echo 'Detected virtual environment.'
|
||||
CPU_VENDOR='none'
|
||||
|
||||
else
|
||||
local vendor_id
|
||||
vendor_id=$(cat /proc/cpuinfo | grep vendor_id | head -1 | sed 's|vendor_id\s*:\s*||')
|
||||
|
||||
if [ "${vendor_id}" = 'AuthenticAMD' ]; then
|
||||
CPU_VENDOR='amd'
|
||||
elif [ "${vendor_id}" = 'GenuineIntel' ]; then
|
||||
CPU_VENDOR='intel'
|
||||
else
|
||||
echo 'Unknown CPU vendor'
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
fi
|
||||
}
|
39
name-reference-test.sh
Normal file
39
name-reference-test.sh
Normal file
@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
# source: https://stackoverflow.com/a/52678279/6334421
|
||||
|
||||
# A variable can be assigned the nameref attribute using the -n option
|
||||
# to the declare or local builtin commands (see Bash Builtins) to create
|
||||
# a nameref, or a reference to another variable. This allows variables to
|
||||
# be manipulated indirectly.
|
||||
|
||||
function text_input {
|
||||
# If variable of name $1 is zero, then ask user for input.
|
||||
# Only one line user input is allowed.
|
||||
# User input must not be empty.
|
||||
#
|
||||
# $1: variable name to store input to
|
||||
# $2: text to display (e.g. "Enter hostname:")
|
||||
if [ -z "${1}" ] || [ -z "${2}" ]; then
|
||||
echo 'text_input requires two args!';
|
||||
return 1
|
||||
fi
|
||||
|
||||
local -n ptr=$1 || return $?
|
||||
if [ -z "${ptr}" ]; then
|
||||
echo "${2}"
|
||||
read -r ptr || return $?
|
||||
fi
|
||||
|
||||
# check string length to be greater than zero!
|
||||
if [ "${#ptr}" -lt 1 ]; then
|
||||
echo 'text_input must not be empty!';
|
||||
return 1;
|
||||
fi
|
||||
}
|
||||
|
||||
text_input foo 'Enter something funny:'
|
||||
echo "Input: ${foo}"
|
||||
|
||||
bar=''
|
||||
text_input bar 'Enter something even funnier:'
|
||||
echo "Input: ${bar}"
|
Loading…
Reference in New Issue
Block a user