1
\$\begingroup\$

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:

  1. Allows selection of filesystem (btrfs, ext4, or LUKS encryption)
  2. Supports both BIOS and UEFI systems
  3. Detects and installs appropriate graphics drivers
  4. Sets up GRUB bootloader with a custom theme
  5. Configures user accounts and sudo access
  6. 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.

\$\endgroup\$
0

1 Answer 1

2
\$\begingroup\$

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:

  1. front end interactive script which prompts and then writes a .json file
  2. 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.txt in 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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.