I'm trying to automate my Arch Linux installation using a bash script. The script would allow me to choose between installing Arch on a single SSD or using a combination of SSD (for root and home) and HDD (for specific user directories like Downloads, Documents, etc.). I'd like some feedback on my approach and any potential improvements.
Here are the key features of my script:
- Allows selection of filesystem (btrfs, ext4, or LUKS encryption)
- Supports both BIOS and UEFI systems
- Detects and installs appropriate graphics drivers
- Sets up GRUB bootloader with a custom theme
- Configures user accounts and sudo access
- Handles different partition schemes based on disk selection
I'm particularly interested in feedback on:
- The overall structure and flow of the script
- Handling of different filesystem options, especially LUKS encryption
- The approach to setting up the secondary HDD for user directories
- Any potential security issues or best practices I might have missed
- Ways to make the script more robust and error-resistant
Snippets:
...
select_option() {
local options=("$@")
local num_options=${#options[@]}
local selected=0
local last_selected=-1
while true; do
if [ "$last_selected" -ne -1 ]; then
printf "033円[%sA" "$num_options"
fi
if [ "$last_selected" -eq -1 ]; then
printf "%b\n" "Please select an option using the arrow keys and Enter:"
fi
for i in "${!options[@]}"; do
if [ "$i" -eq "$selected" ]; then
printf "> %s\n" "${options[$i]}"
else
printf " %s\n" "${options[$i]}"
fi
done
last_selected=$selected
read -r -n1 key
case $key in
A) # Up arrow
selected=$((selected - 1))
if [ "$selected" -lt 0 ]; then
selected=$((num_options - 1))
fi
;;
B) # Down arrow
selected=$((selected + 1))
if [ "$selected" -ge "$num_options" ]; then
selected=0
fi
;;
'') # Enter key
break
;;
esac
done
return "$selected"
}
...
...
filesystem() {
printf "%b\n" "
Please Select your file system for both boot and root
"
options=("btrfs" "ext4" "luks" "exit")
select_option "${options[@]}"
case $? in
0) FS=btrfs;;
1) FS=ext4;;
2)
set_password "LUKS_PASSWORD"
FS=luks
;;
3) exit ;;
*) printf "%b\n" "Wrong option please select again"; filesystem;;
esac
if [ -n "$SECONDARY_DISK" ]; then
printf "%b\n" "
Formatting secondary disk with the same filesystem...
"
case $FS in
btrfs) mkfs.btrfs -L DATA "$SECONDARY_DISK" -f ;;
ext4) mkfs.ext4 -L DATA "$SECONDARY_DISK" ;;
luks)
printf "%s" "$LUKS_PASSWORD" | cryptsetup -y -v luksFormat "$SECONDARY_DISK" -
printf "%s" "$LUKS_PASSWORD" | cryptsetup open "$SECONDARY_DISK" DATA -
mkfs.btrfs -L DATA /dev/mapper/DATA
;;
esac
fi
}
createsubvolumes() {
btrfs subvolume create /mnt/@
btrfs subvolume create /mnt/@home
btrfs subvolume create /mnt/@var
btrfs subvolume create /mnt/@tmp
btrfs subvolume create /mnt/@.snapshots
}
mountallsubvol() {
mount -o "$MOUNT_OPTIONS",subvol=@home "$partition3" /mnt/home
mount -o "$MOUNT_OPTIONS",subvol=@tmp "$partition3" /mnt/tmp
mount -o "$MOUNT_OPTIONS",subvol=@var "$partition3" /mnt/var
mount -o "$MOUNT_OPTIONS",[email protected] "$partition3" /mnt/.snapshots
if [ -n "$SECONDARY_DISK" ]; then
mkdir -p /mnt/mnt/data
if [ "$FS" = "luks" ]; then
mount -o "$MOUNT_OPTIONS" /dev/mapper/DATA /mnt/mnt/data
else
mount -o "$MOUNT_OPTIONS" "$SECONDARY_DISK" /mnt/mnt/data
fi
mkdir -p /mnt/mnt/data/{Downloads,Documents,Music,Pictures,Videos,Public}
fi
}
...
Are there any improvements or considerations I should keep in mind for this approach? Any potential pitfalls or edge cases I should be aware of?
Full script link is here.
1 Answer 1
interaction vs CLI
Suppose I decided to create a host, then in the course of tinkering with it I trashed it, so now I want to rebuild from scratch in the identical way.
I worry about reproducibility. Maybe I remember exactly what I originally entered? Or went to the trouble of recording it in a paper notebook?
Consider breaking this into a pair of programs:
- front end interactive script which prompts and then writes a .json file
- CLI script which reads the JSON config and performs the indicated actions
That way I have JSON notes automatically written down for me. And I can version control them, and share them with colleagues.
modulo
Thank you for the ANSI CSI escape sequence comments, such as "up arrow".
selected=$((selected - 1))
if [ "$selected" -lt 0 ]; then
selected=$((num_options - 1))
fi
Clearly this works.
But prefer to use bash's % modulo operator.
secrets
set_password "LUKS_PASSWORD"
Secrets don't belong in source code, which after all will wind up in a git repo.
Prefer to read credential material from a JSON config file, an env var, or even a command-line argument.
mkfs
We see "Formatting secondary disk" and then it happens. Very CLI style, no y-or-n "are you sure?" prompt, that's cool.
Maybe sleep 3 so we have a chance to curse and hit CTRL/C ?
Maybe probe the hardware to validate that partition in some way?
Maybe attempt to mount it, and if successful
- don't nuke it, or
- verify someone performed
touch PLEASE-DESTROY.txtin that FS ?
I don't know what the appropriate level of paranoia is. I'm just making suggestions that it might be slightly higher than the current level of zero.
printf "%s" "$LUKS_PASSWORD" | cryptsetup ...
printf "%s" "$LUKS_PASSWORD" | cryptsetup ...
Hmmm, looks like I failed to correctly interpret
what that set_password line was about.
Oh, well!
mountpoint
mkdir -p /mnt/mnt/data
I'm sure that's correct.
I'm just surprised.
Perhaps it merits an explanatory # comment.
lint
In someplace like
https://github.com/fam007e/fun007/tree/master/ArchInstallScript
you might consider adding a Makefile with a
recipe which will e.g. run shellcheck
in response to make lint.
(And/or create a GitHub Action to do that,
not unlike how you attest build provenance.)
automated tests
This seems like a high stakes script which a given individual might seldom run. The sort of thing that keeps you on the edge of your seat.
There is a very nice beginning of a README file; thank you.
It could benefit from some further details on "preparing storage", even if that is just an example transcript from a successful run. British highway signs go to some trouble to announce, "approaching city X", "a few miles to X", then you hit the roundabout, then a sign reassures you, "really close to X now!". Users like to know what to expect from a "dangerous" script, and like to be able to verify "what is normal output?" after running it.
VM
Here is the biggest gap I noticed. Suppose I have a filesystem with a ton of disk space, which I don't plan to partition. Making a Docker image with one FS or another seems like a natural use case. Consider writing up an example of using this script in a containerized setting.