rebostness improvements, partition naming fix, and added autologin
This commit is contained in:
+229
-174
@@ -1,217 +1,264 @@
|
|||||||
#!/bin/sh
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
# User inputs
|
# User inputs
|
||||||
HOSTNAME=""
|
HOSTNAME=""
|
||||||
ROOT_PASSWORD=""
|
ROOT_PASSWORD=""
|
||||||
USERNAME=""
|
USERNAME=""
|
||||||
USER_PASSWORD=""
|
USER_PASSWORD=""
|
||||||
|
WANT_AUTOLOGIN=""
|
||||||
|
|
||||||
|
# Cleanup on failure
|
||||||
|
cleanup() {
|
||||||
|
local exit_code=$?
|
||||||
|
if [ $exit_code -ne 0 ]; then
|
||||||
|
echo "An error occurred during installation. Unmounting..."
|
||||||
|
umount -A -R /mnt 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
install_deps() {
|
install_deps() {
|
||||||
echo "You will need commands like mkfs.ext4, sgisk and pacstrap."
|
echo "You will need commands like mkfs.ext4, sgdisk and pacstrap."
|
||||||
printf "Try to install them with pacman (if you are on arch linux)? [Y/n] "
|
printf "Try to install them with pacman (if you are on arch linux)? [Y/n] "
|
||||||
read CONFIRMATION
|
read -r CONFIRMATION
|
||||||
case "$CONFIRMATION" in
|
case "$CONFIRMATION" in
|
||||||
"n" | "N")
|
[nN]*)
|
||||||
echo "Operation canceled."
|
echo "Operation canceled."
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
"" | "y" | "Y")
|
"" | [yY]*)
|
||||||
echo "Installing dependencies"
|
echo "Installing dependencies"
|
||||||
sudo pacman -S --needed --noconfirm dosfstools gptfdisk arch-install-scripts
|
sudo pacman -S --needed --noconfirm dosfstools gptfdisk arch-install-scripts
|
||||||
echo ""
|
echo ""
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Invalid input. Defaulting to 'No'."
|
echo "Invalid input. Defaulting to 'No'."
|
||||||
exit 0
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
send_status_message() {
|
send_status_message() {
|
||||||
term_width=$(tput cols 2>/dev/null || echo 80)
|
local term_width
|
||||||
msg="$1"
|
term_width=$(tput cols 2>/dev/null || echo 80)
|
||||||
edge=$(printf '%*s' "$term_width" '' | tr ' ' '=')
|
local msg="$1"
|
||||||
printf "\n%s\n" "$edge"
|
local edge
|
||||||
printf "%*s\n" $(((${#msg} + term_width) / 2)) "$msg"
|
edge=$(printf '%*s' "$term_width" '' | tr ' ' '=')
|
||||||
printf "%s\n\n" "$edge"
|
printf "\n%s\n" "$edge"
|
||||||
|
printf "%*s\n" $(((${#msg} + term_width) / 2)) "$msg"
|
||||||
|
printf "%s\n\n" "$edge"
|
||||||
}
|
}
|
||||||
|
|
||||||
get_config_inputs() {
|
get_config_inputs() {
|
||||||
send_status_message "CONFIGURATION: Getting user inputs"
|
send_status_message "CONFIGURATION: Getting user inputs"
|
||||||
|
|
||||||
while [ -z "$HOSTNAME" ]; do
|
while [ -z "$WANT_AUTOLOGIN" ]; do
|
||||||
printf "Enter hostname: "
|
printf "Do you want to enable autologin? (TTY only) [y/n]: "
|
||||||
read HOSTNAME
|
read -r WANT_AUTOLOGIN
|
||||||
|
case "$WANT_AUTOLOGIN" in
|
||||||
|
[yY]*) WANT_AUTOLOGIN="y" ;;
|
||||||
|
[nN]*) WANT_AUTOLOGIN="n" ;;
|
||||||
|
*) WANT_AUTOLOGIN="" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
|
||||||
if [ -z "$HOSTNAME" ]; then
|
while [ -z "$HOSTNAME" ]; do
|
||||||
echo "Hostname cannot be empty."
|
printf "Enter hostname: "
|
||||||
continue
|
read -r HOSTNAME
|
||||||
fi
|
# Validate hostname
|
||||||
break
|
if [[ ! "$HOSTNAME" =~ ^[a-zA-Z0-9.-]+$ ]]; then
|
||||||
done
|
echo "Invalid hostname. Use alphanumeric, dots, and hyphens only."
|
||||||
echo ""
|
HOSTNAME=""
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
|
||||||
while [ -z "$ROOT_PASSWORD" ]; do
|
while [ -z "$ROOT_PASSWORD" ]; do
|
||||||
printf "Enter root password: "
|
printf "Enter root password: "
|
||||||
read -s ROOT_PASSWORD
|
read -rs ROOT_PASSWORD
|
||||||
printf "\nConfirm root password: "
|
printf "\nConfirm root password: "
|
||||||
read -s CONFIRM_ROOT_PASSWORD
|
read -rs CONFIRM_ROOT_PASSWORD
|
||||||
|
echo ""
|
||||||
|
|
||||||
if [ "$ROOT_PASSWORD" != "$CONFIRM_ROOT_PASSWORD" ]; then
|
if [ "$ROOT_PASSWORD" != "$CONFIRM_ROOT_PASSWORD" ] || [ -z "$ROOT_PASSWORD" ]; then
|
||||||
echo "Root passwords do not match."
|
echo "Root passwords do not match or are empty."
|
||||||
ROOT_PASSWORD=""
|
ROOT_PASSWORD=""
|
||||||
CONFIRM_ROOT_PASSWORD=""
|
continue
|
||||||
continue
|
fi
|
||||||
fi
|
done
|
||||||
break
|
echo ""
|
||||||
done
|
|
||||||
echo ""
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
while [ -z "$USERNAME" ]; do
|
while [ -z "$USERNAME" ]; do
|
||||||
printf "Enter username: "
|
printf "Enter username: "
|
||||||
read USERNAME
|
read -r USERNAME
|
||||||
|
# Validate username
|
||||||
|
if [[ ! "$USERNAME" =~ ^[a-z_][a-z0-9_-]*$ ]]; then
|
||||||
|
echo "Invalid username. Must start with lowercase/underscore and contain no spaces."
|
||||||
|
USERNAME=""
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
|
||||||
if [ -z "$USERNAME" ]; then
|
while [ -z "$USER_PASSWORD" ]; do
|
||||||
echo "Username cannot be empty."
|
printf "Enter user password: "
|
||||||
continue
|
read -rs USER_PASSWORD
|
||||||
fi
|
printf "\nConfirm user password: "
|
||||||
break
|
read -rs CONFIRM_USER_PASSWORD
|
||||||
done
|
echo ""
|
||||||
echo ""
|
|
||||||
|
|
||||||
while [ -z "$USER_PASSWORD" ]; do
|
if [ "$USER_PASSWORD" != "$CONFIRM_USER_PASSWORD" ] || [ -z "$USER_PASSWORD" ]; then
|
||||||
printf "Enter user password: "
|
echo "User passwords do not match or are empty."
|
||||||
read -s USER_PASSWORD
|
USER_PASSWORD=""
|
||||||
printf "\nConfirm user password: "
|
continue
|
||||||
read -s CONFIRM_USER_PASSWORD
|
fi
|
||||||
|
done
|
||||||
if [ "$USER_PASSWORD" != "$CONFIRM_USER_PASSWORD" ]; then
|
|
||||||
echo "User passwords do not match."
|
|
||||||
USER_PASSWORD=""
|
|
||||||
CONFIRM_USER_PASSWORD=""
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
break
|
|
||||||
done
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to get drive name
|
# Function to get drive name
|
||||||
DISK=""
|
DISK=""
|
||||||
get_drive() {
|
get_drive() {
|
||||||
send_status_message "DRIVE SETUP: Selecting and confirming target drive"
|
send_status_message "DRIVE SETUP: Selecting and confirming target drive"
|
||||||
|
|
||||||
lsblk
|
printf '%-8s %-8s %-30s %s\n' NAME SIZE MODEL
|
||||||
printf "\nEnter the drive name (e.g., sda, sdb, vda, nvme0n1): "
|
lsblk # -do NAME,SIZE,MODEL
|
||||||
read DISK
|
printf "\nEnter the drive name (e.g., sda, nvme0n1): "
|
||||||
if [ -z "${DISK}" ]; then
|
read -r DISK
|
||||||
echo "No drive name entered. Exiting."
|
if [ -z "${DISK}" ]; then
|
||||||
exit 1
|
echo "No drive name entered. Exiting."
|
||||||
fi
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
DISK="/dev/${DISK}"
|
DISK="/dev/${DISK}"
|
||||||
PART_PREFIX="$DISK"
|
if [ ! -b "$DISK" ]; then
|
||||||
case "$DISK" in
|
echo "Error: ${DISK} is not a valid block device."
|
||||||
*nvme*n*) PART_PREFIX="${DISK}p" ;; # nvme devices need a 'p' before partition number
|
exit 1
|
||||||
*) PART_PREFIX="${DISK}" ;;
|
fi
|
||||||
esac
|
|
||||||
|
|
||||||
printf "WARNING: All data on %s will be erased. Are you sure? [Y/n] " "${DISK}"
|
PART_PREFIX="$DISK"
|
||||||
read CONFIRMATION
|
if [[ "$DISK" =~ [0-9]$ ]]; then
|
||||||
case "$CONFIRMATION" in
|
PART_PREFIX="${DISK}p"
|
||||||
"n" | "N")
|
fi
|
||||||
echo "Operation canceled."
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
"" | "y" | "Y")
|
|
||||||
echo "Proceeding with the operation on ${DISK}."
|
|
||||||
printf "Wiping drive in "
|
|
||||||
for i in 3 2 1; do
|
|
||||||
printf '%d...' $i
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
echo ""
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Invalid input. Defaulting to 'No'."
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
echo "Drive ${DISK} confirmed for installation."
|
printf "WARNING: All data on %s will be erased. Are you sure? [Y/n] " "${DISK}"
|
||||||
|
read -r CONFIRMATION
|
||||||
|
case "$CONFIRMATION" in
|
||||||
|
[nN]*)
|
||||||
|
echo "Operation canceled."
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
"" | [yY]*)
|
||||||
|
echo "Proceeding with the operation on ${DISK}."
|
||||||
|
printf "Wiping drive in "
|
||||||
|
for i in 3 2 1; do
|
||||||
|
printf '%d...' "$i"
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Invalid input. Defaulting to 'No'."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "Drive ${DISK} confirmed for installation."
|
||||||
}
|
}
|
||||||
|
|
||||||
setup_partitions() {
|
setup_partitions() {
|
||||||
send_status_message "PARTITIONING: Creating partitions and filesystems"
|
send_status_message "PARTITIONING: Creating partitions and filesystems"
|
||||||
|
|
||||||
if [ ! -d "/sys/firmware/efi" ]; then # Checking for BIOS system
|
if [ ! -d "/sys/firmware/efi" ]; then
|
||||||
echo "BIOS systems are not supported."
|
echo "BIOS systems are not supported."
|
||||||
fi
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
umount -A -R /mnt # make sure everything is unmounted before we start
|
echo "Ensuring $DISK is not in use..."
|
||||||
sgdisk -Z ${DISK} # zap all on disk
|
grep "${DISK}" /proc/swaps | cut -d' ' -f1 | xargs -r swapoff || true
|
||||||
sgdisk -a 2048 -o ${DISK} # new GPT disk 2048 alignment
|
grep "${DISK}" /proc/mounts | cut -d' ' -f2 | xargs -r umount -l || true
|
||||||
|
wipefs -a "${DISK}" || true
|
||||||
|
sgdisk -Z "${DISK}" || true
|
||||||
|
|
||||||
# Create partitions
|
sgdisk -a 2048 -o "${DISK}" # new GPT disk 2048 alignment
|
||||||
sgdisk -n 1::+1G --typecode=1:ef00 --change-name=1:"BOOT" ${DISK} # partition 1 (UEFI boot partition)
|
|
||||||
sgdisk -n 2::+4G --typecode=2:8200 --change-name=2:"SWAP" ${DISK} # partition 2 (Swap partition)
|
|
||||||
sgdisk -n 3::-0 --typecode=3:8300 --change-name=3:"ROOT" ${DISK} # partition 3 (Root), default start, remaining
|
|
||||||
partprobe ${DISK} # reread partition table to ensure it is correct
|
|
||||||
|
|
||||||
# Create filesystems
|
# Create partitions
|
||||||
mkfs.fat -F32 -n "BOOT" ${PART_PREFIX}1
|
sgdisk -n 1::+1G --typecode=1:ef00 --change-name=1:"BOOT" "${DISK}"
|
||||||
mkswap -f ${PART_PREFIX}2
|
sgdisk -n 2::-0 --typecode=2:8300 --change-name=2:"ROOT" "${DISK}"
|
||||||
mkfs.ext4 -F -L "ROOT" ${PART_PREFIX}3
|
# sgdisk -n 3::+4G --typecode=3:8200 --change-name=3:"SWAP" "${DISK}"
|
||||||
|
|
||||||
# Mount
|
partprobe "${DISK}"
|
||||||
mount ${PART_PREFIX}3 /mnt
|
sleep 2
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "Failed to mount the ROOT partition."
|
|
||||||
exit 2 # Exit if mounting ROOT partition fails
|
|
||||||
fi
|
|
||||||
|
|
||||||
mount --mkdir ${PART_PREFIX}1 /mnt/boot
|
# Create filesystems
|
||||||
if [ $? -ne 0 ]; then
|
mkfs.fat -F32 -n "BOOT" "${PART_PREFIX}1"
|
||||||
echo "Failed to mount the BOOT partition."
|
mkfs.ext4 -F -L "ROOT" "${PART_PREFIX}2"
|
||||||
exit 2 # Exit if mounting BOOT partition fails
|
# mkswap -f -L "SWAP" "${PART_PREFIX}3"
|
||||||
fi
|
|
||||||
|
|
||||||
swapon ${PART_PREFIX}2
|
# Mount
|
||||||
|
mount -t ext4 "${PART_PREFIX}2" /mnt
|
||||||
|
mount --mkdir -t vfat "${PART_PREFIX}1" /mnt/boot
|
||||||
|
# swapon "${PART_PREFIX}3"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Setup systemdboot as the bootloader
|
# Setup systemdboot as the bootloader
|
||||||
setup_bootloader() {
|
setup_bootloader() {
|
||||||
send_status_message "BOOTLOADER: Installing and configuring systemd-boot"
|
send_status_message "BOOTLOADER: Installing and configuring systemd-boot"
|
||||||
|
|
||||||
# Run bootctl inside chroot
|
arch-chroot /mnt bootctl install
|
||||||
arch-chroot /mnt bootctl install
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "Error: Failed to install systemd-boot."
|
|
||||||
exit 3
|
|
||||||
fi
|
|
||||||
|
|
||||||
BOOTLOADER_ENTRY="/mnt/boot/loader/entries/arch.conf"
|
BOOTLOADER_ENTRY="/mnt/boot/loader/entries/arch.conf"
|
||||||
ARCH_ENTRY_TEMPLATE="/mnt/usr/share/systemd/bootctl/arch.conf"
|
|
||||||
|
|
||||||
# Get PARTUUID for the root partition
|
PARTUUID=$(blkid -s PARTUUID -o value "${PART_PREFIX}2")
|
||||||
PARTUUID=$(blkid | grep "${PART_PREFIX}3" | awk -F= '{print $NF}' | tr -d '\"')
|
|
||||||
|
|
||||||
if [ -z "$PARTUUID" ]; then
|
if [ -z "$PARTUUID" ]; then
|
||||||
echo "Error: Unable to find PARTUUID for ${DISK}3."
|
echo "Error: Unable to find PARTUUID for ${PART_PREFIX}2."
|
||||||
exit 3
|
exit 3
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create a new entry based on the template and PARTUUID
|
echo "Creating bootloader entry with PARTUUID=$PARTUUID"
|
||||||
echo "Creating bootloader entry with PARTUUID=$PARTUUID"
|
|
||||||
|
|
||||||
sed "s/^options.*/options root=PARTUUID=$PARTUUID rw splash/" "$ARCH_ENTRY_TEMPLATE" >"$BOOTLOADER_ENTRY"
|
cat <<EOF > "$BOOTLOADER_ENTRY"
|
||||||
|
title Arch Linux
|
||||||
|
linux /vmlinuz-linux
|
||||||
|
initrd /initramfs-linux.img
|
||||||
|
options root=PARTUUID=$PARTUUID rw splash
|
||||||
|
EOF
|
||||||
|
|
||||||
if [ $? -eq 0 ]; then
|
echo "default arch.conf" > /mnt/boot/loader/loader.conf
|
||||||
echo "Successfully created bootloader entry: $BOOTLOADER_ENTRY"
|
}
|
||||||
else
|
|
||||||
echo "Error: Failed to create the bootloader entry."
|
setup_autologin() {
|
||||||
exit 3
|
if [ "$WANT_AUTOLOGIN" != "y" ]; then
|
||||||
fi
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
send_status_message "CONFIG: Configuring autologin for $USERNAME"
|
||||||
|
|
||||||
|
cat > "/mnt/etc/systemd/system/autologin@.service" <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Autologin on %I
|
||||||
|
After=systemd-user-sessions.service
|
||||||
|
Conflicts=getty@%i.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=-/sbin/agetty --autologin $USERNAME --noclear %I \$TERM
|
||||||
|
Type=idle
|
||||||
|
Restart=always
|
||||||
|
TTYPath=/dev/%I
|
||||||
|
TTYReset=yes
|
||||||
|
TTYVHangup=yes
|
||||||
|
TTYVTDisallocate=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
arch-chroot /mnt systemctl disable lemurs
|
||||||
|
arch-chroot /mnt systemctl enable autologin@tty1.service
|
||||||
}
|
}
|
||||||
|
|
||||||
# >------ Main execution flow ------<
|
# >------ Main execution flow ------<
|
||||||
@@ -235,37 +282,45 @@ setup_partitions
|
|||||||
send_status_message "BASE INSTALLATION: Installing base system with pacstrap"
|
send_status_message "BASE INSTALLATION: Installing base system with pacstrap"
|
||||||
pacstrap -K /mnt base linux linux-firmware networkmanager lemurs sudo
|
pacstrap -K /mnt base linux linux-firmware networkmanager lemurs sudo
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "Error: Failed to install base system."
|
echo "Error: Failed to install base system."
|
||||||
exit 4
|
exit 4
|
||||||
fi
|
fi
|
||||||
|
|
||||||
send_status_message "SYSTEM CONFIG: Generating fstab"
|
send_status_message "SYSTEM CONFIG: Generating fstab"
|
||||||
genfstab -U /mnt >>/mnt/etc/fstab
|
genfstab -U /mnt >> /mnt/etc/fstab
|
||||||
|
|
||||||
# Setup base locales
|
# Setup base locales
|
||||||
send_status_message "SYSTEM CONFIG: Setting timezone, locales, and hostname"
|
send_status_message "SYSTEM CONFIG: Setting timezone, locales, and hostname"
|
||||||
ln -sf /usr/share/zoneinfo/Europe/Paris /mnt/etc/localtime
|
ln -sf /usr/share/zoneinfo/Europe/Paris /mnt/etc/localtime
|
||||||
echo 'en_US.UTF-8 UTF-8' >>/mnt/etc/locale.gen
|
echo 'en_US.UTF-8 UTF-8' >> /mnt/etc/locale.gen
|
||||||
arch-chroot /mnt locale-gen
|
arch-chroot /mnt locale-gen
|
||||||
echo LANG=en_US.UTF-8 >/mnt/etc/locale.conf
|
echo "LANG=en_US.UTF-8" > /mnt/etc/locale.conf
|
||||||
echo $HOSTNAME >/mnt/etc/hostname
|
echo "$HOSTNAME" > /mnt/etc/hostname
|
||||||
|
|
||||||
send_status_message "USER SETUP: Creating user and setting passwords"
|
send_status_message "USER SETUP: Creating user and setting passwords"
|
||||||
# Set root password
|
# Set root password
|
||||||
arch-chroot /mnt bash -c "echo 'root:$ROOT_PASSWORD' | chpasswd"
|
arch-chroot /mnt chpasswd <<EOF
|
||||||
|
root:$ROOT_PASSWORD
|
||||||
|
EOF
|
||||||
|
|
||||||
# Create user and set password
|
# Create user and set password
|
||||||
arch-chroot /mnt useradd -m $USERNAME
|
arch-chroot /mnt useradd -m "$USERNAME"
|
||||||
arch-chroot /mnt bash -c "echo '$USERNAME:$USER_PASSWORD' | chpasswd"
|
arch-chroot /mnt chpasswd <<EOF
|
||||||
|
$USERNAME:$USER_PASSWORD
|
||||||
|
EOF
|
||||||
|
|
||||||
# Setup wheel group
|
# Setup wheel group
|
||||||
arch-chroot /mnt usermod -aG wheel $USERNAME
|
arch-chroot /mnt usermod -aG wheel "$USERNAME"
|
||||||
arch-chroot /mnt bash -c "echo '%wheel ALL=(ALL) ALL' | EDITOR='tee -a' visudo"
|
echo "%wheel ALL=(ALL:ALL) ALL" > /mnt/etc/sudoers.d/10-wheel
|
||||||
|
chmod 440 /mnt/etc/sudoers.d/10-wheel
|
||||||
|
|
||||||
send_status_message "SERVICES: Enabling NetworkManager and lemurs"
|
send_status_message "SERVICES: Enabling NetworkManager and lemurs"
|
||||||
# Setup services and display manager
|
# Setup services and display manager
|
||||||
arch-chroot /mnt systemctl enable NetworkManager lemurs
|
arch-chroot /mnt systemctl enable NetworkManager lemurs
|
||||||
|
|
||||||
|
# Configure autologin for lemurs if requested
|
||||||
|
setup_autologin
|
||||||
|
|
||||||
setup_bootloader
|
setup_bootloader
|
||||||
|
|
||||||
send_status_message "CLEANUP: Unmounting all filesystems"
|
send_status_message "CLEANUP: Unmounting all filesystems"
|
||||||
|
|||||||
Reference in New Issue
Block a user