#!/bin/bash # Antergos NeXT Offline Installer # Stolen with love from Valve's SteamOS repair_device.sh # "If it works for a Steam Deck, it works for you." -- some drunk developer set -eu SQUASHFS="/run/artix/bootmnt/LiveOS/rootfs.img" # ── Stolen from SteamOS: pretty output ── sh_c() { [[ $_sh_c_colors -le 0 ]] || echo -ne "\e[${*:-0}m"; } _sh_c_colors=0 [[ -n $TERM && -t 1 && ${TERM,,} != dumb ]] && _sh_c_colors=$(tput colors 2>/dev/null || echo 0) estat() { echo -e "$(sh_c 32 1)::$(sh_c) $*"; } einfo() { echo -e "$(sh_c 34 1)::$(sh_c) $*"; } ewarn() { echo -e "$(sh_c 33 1);;$(sh_c) $*"; } eerr() { echo -e "$(sh_c 31 1)!!$(sh_c) $*" >&2; } die() { eerr "${1:-script terminated}"; exit 1; } cmd() { echo -e "$(sh_c 30 1)+$(sh_c) $*"; "$@"; } # ── Stolen from SteamOS: read heredoc into var ── readvar() { IFS= read -r -d '' "$1" || true; } # ── Stolen from SteamOS: partition helpers ── diskpart() { echo "${DISK}${DISK_SUFFIX}$1"; } # ── Stolen from SteamOS: zenity prompts ── prompt_confirm() { zenity --title "$1" --question --ok-label "Proceed" --cancel-label "Cancel" --no-wrap --text "$2" 2>/dev/null } prompt_msg() { zenity --title "$1" --info --no-wrap --text "$2" 2>/dev/null } # ── Pre-flight ── [[ $EUID -eq 0 ]] || die "Must be run as root" if [[ ! -f "$SQUASHFS" ]]; then die "Cannot find rootfs squashfs at $SQUASHFS. Are you booted from an Antergos NeXT ISO?" fi # ── WARNING: NO DE ── prompt_msg "Antergos NeXT Offline Installer" \ "This will install a BARE MINIMUM system — no desktop environment, no apps, just the base system + Calamares. After boot, connect to the internet and run: sudo pacman -Sy plasma-meta (or xfce4, or whatever DE you want) This is for people who want a minimal base to build on, or who are trapped in a cave with no internet. This WILL DESTROY ALL DATA on the target disk." prompt_confirm "Select target disk" \ "This will list available disks. Choose carefully — ALL DATA WILL BE DESTROYED." || die "Aborted by user" # ── Select disk ── DISK=$(lsblk -dno NAME,SIZE,MODEL,TYPE | grep disk | \ zenity --list --title "Select target disk" \ --column "Device" --column "Size" --column "Model" \ --print-column=1 --width=500 --height=300 2>/dev/null | tail -1) [[ -n "$DISK" ]] || die "No disk selected" DISK="/dev/$DISK" [[ -b "$DISK" ]] || die "$DISK is not a block device" # Determine partition suffix (p for NVMe/mmcblk, nothing for sdX) if [[ "$DISK" =~ /dev/nvme || "$DISK" =~ /dev/mmcblk ]]; then DISK_SUFFIX="p" else DISK_SUFFIX="" fi prompt_confirm "DESTROY $DISK?" \ "Target: $DISK All partitions will be wiped. This cannot be undone. Type YES below to confirm." || die "Aborted by user" # ── Partition ── estat "Partitioning $DISK..." cmd sfdisk --delete "$DISK" 2>/dev/null || true readvar PARTITION_TABLE << END_PTABLE label: gpt $(diskpart 1): size=512MiB, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B $(diskpart 2): size=300MiB, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F $(diskpart 3): type=0FC63DAF-8483-4772-8E79-3D69D8477DE4 END_PTABLE echo "$PARTITION_TABLE" | cmd sfdisk "$DISK" PART_ESP=$(diskpart 1) PART_SWAP=$(diskpart 2) PART_ROOT=$(diskpart 3) # ── Format ── estat "Formatting partitions..." cmd mkfs.fat -F32 -n ESP "$PART_ESP" cmd mkswap -L swap "$PART_SWAP" cmd mkfs.btrfs -f -L antergos "$PART_ROOT" # ── Create btrfs subvolumes ── estat "Creating btrfs subvolumes..." cmd mkdir -p /mnt/btrfs cmd mount "$PART_ROOT" /mnt/btrfs cmd btrfs subvolume create /mnt/btrfs/@ cmd btrfs subvolume create /mnt/btrfs/@home cmd btrfs subvolume create /mnt/btrfs/@cache cmd btrfs subvolume create /mnt/btrfs/@log cmd btrfs subvolume create /mnt/btrfs/@snapshots cmd umount /mnt/btrfs # ── Mount subvolumes ── estat "Mounting subvolumes..." TARGET=/mnt/target cmd mkdir -p "$TARGET" cmd mount -o subvol=@ "$PART_ROOT" "$TARGET" cmd mkdir -p "$TARGET/{home,var/cache,var/log,.snapshots,boot}" cmd mount -o subvol=@home "$PART_ROOT" "$TARGET/home" cmd mount -o subvol=@cache "$PART_ROOT" "$TARGET/var/cache" cmd mount -o subvol=@log "$PART_ROOT" "$TARGET/var/log" cmd mount -o subvol=@snapshots "$PART_ROOT" "$TARGET/.snapshots" cmd mount "$PART_ESP" "$TARGET/boot" # ── Extract squashfs ── estat "Extracting rootfs to $TARGET... (this will take a while)" cmd unsquashfs -f -d "$TARGET" "$SQUASHFS" # ── Generate fstab ── estat "Generating fstab with btrfs subvolumes..." ROOT_UUID=$(blkid -o value -s UUID "$PART_ROOT") SWAP_UUID=$(blkid -o value -s UUID "$PART_SWAP") ESP_UUID=$(blkid -o value -s UUID "$PART_ESP") cat > "$TARGET/etc/fstab" << FSTAB # /etc/fstab generated by Antergos NeXT Offline Installer # Stolen from SteamOS, adapted for your sins — now with btrfs snapshots UUID=$ROOT_UUID / btrfs subvol=@,defaults,noatime,compress=zstd 0 1 UUID=$ROOT_UUID /home btrfs subvol=@home,defaults,noatime,compress=zstd 0 0 UUID=$ROOT_UUID /var/cache btrfs subvol=@cache,defaults,noatime,compress=zstd 0 0 UUID=$ROOT_UUID /var/log btrfs subvol=@log,defaults,noatime,compress=zstd 0 0 UUID=$ROOT_UUID /.snapshots btrfs subvol=@snapshots,defaults,noatime 0 0 UUID=$SWAP_UUID none swap sw 0 0 UUID=$ESP_UUID /boot vfat defaults,noatime 0 2 FSTAB # ── Chroot setup ── estat "Setting up chroot for bootloader..." cmd mount --bind /dev "$TARGET/dev" cmd mount --bind /proc "$TARGET/proc" cmd mount --bind /sys "$TARGET/sys" cmd mount --bind /run "$TARGET/run" # Generate initramfs estat "Generating initramfs..." cmd chroot "$TARGET" /bin/bash -c "mkinitcpio -P" 2>/dev/null || true # Install GRUB estat "Installing GRUB..." cmd chroot "$TARGET" /bin/bash -c "grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=Antergos" cmd chroot "$TARGET" /bin/bash -c "grub-mkconfig -o /boot/grub/grub.cfg" 2>/dev/null || true # Set hostname and hosts cmd chroot "$TARGET" /bin/bash -c "echo antergos > /etc/hostname" cmd chroot "$TARGET" /bin/bash -c "echo '127.0.1.1 antergos.localdomain antergos' >> /etc/hosts" # Root password prompt PASSWD=$(zenity --password --title="Set root password" --text="Enter root password for the installed system" 2>/dev/null) if [[ -n "$PASSWD" ]]; then echo "root:$PASSWD" | cmd chroot "$TARGET" chpasswd fi # Create user USERNAME=$(zenity --entry --title="Create user" --text="Enter username for daily use:" 2>/dev/null) if [[ -n "$USERNAME" ]]; then cmd chroot "$TARGET" useradd -m -G wheel,audio,video,storage -s /bin/bash "$USERNAME" UPASS=$(zenity --password --title="User password" --text="Enter password for $USERNAME:" 2>/dev/null) [[ -n "$UPASS" ]] && echo "$USERNAME:$UPASS" | cmd chroot "$TARGET" chpasswd cmd chroot "$TARGET" sed -i 's/^# %wheel ALL=(ALL:ALL) ALL/%wheel ALL=(ALL:ALL) ALL/' /etc/sudoers fi # ── Enable services via artix-service ── estat "Enabling services..." for sv in NetworkManager dbus acpid bluetoothd cronie cupsd dhcpcd power-profiles-daemon syslog-ng userspawn; do cmd chroot "$TARGET" artix-service enable "$sv" 2>/dev/null || true done # ── Configure snapper for automatic snapshots ── estat "Setting up snapper..." cmd chroot "$TARGET" snapper -c root create-config / cmd chroot "$TARGET" sed -i 's/^TIMELINE_CREATE="no"/TIMELINE_CREATE="yes"/' /etc/snapper/configs/root cmd chroot "$TARGET" sed -i 's/^TIMELINE_LIMIT_HOURLY="[0-9]*"/TIMELINE_LIMIT_HOURLY="6"/' /etc/snapper/configs/root cmd chroot "$TARGET" sed -i 's/^TIMELINE_LIMIT_DAILY="[0-9]*"/TIMELINE_LIMIT_DAILY="7"/' /etc/snapper/configs/root cmd chroot "$TARGET" sed -i 's/^TIMELINE_LIMIT_WEEKLY="[0-9]*"/TIMELINE_LIMIT_WEEKLY="4"/' /etc/snapper/configs/root cmd chroot "$TARGET" sed -i 's/^TIMELINE_LIMIT_MONTHLY="[0-9]*"/TIMELINE_LIMIT_MONTHLY="0"/' /etc/snapper/configs/root cat > "$TARGET/etc/cron.hourly/snapper-timeline" << 'CRON' #!/bin/bash # Automatic btrfs snapshots via snapper /usr/bin/snapper -c root cleanup timeline /usr/bin/snapper -c root cleanup number CRON cmd chroot "$TARGET" chmod +x /etc/cron.hourly/snapper-timeline # Also run cleanup daily to be safe cat > "$TARGET/etc/cron.daily/snapper-cleanup" << 'CRON' #!/bin/bash # Clean up old btrfs snapshots /usr/bin/snapper -c root cleanup timeline /usr/bin/snapper -c root cleanup number CRON cmd chroot "$TARGET" chmod +x /etc/cron.daily/snapper-cleanup # ── Cleanup ── estat "Cleaning up..." for d in dev proc sys run; do umount -l "$TARGET/$d" 2>/dev/null || true done for m in home var/log var/cache .snapshots boot; do umount "$TARGET/$m" 2>/dev/null || true done cmd umount "$TARGET" # ── Sync before reboot ── estat "Syncing filesystems..." cmd sync # ── Final warning ── prompt_msg "Installation complete" \ "Antergos NeXT has been installed to $DISK. Filesystem: btrfs with subvolumes (@, @home, @cache, @log, @snapshots) Snapshots: snapper creates hourly snapshots, keeps 6 hourly + 7 daily + 4 weekly (no monthly — those are your problem) Remember: NO DESKTOP ENVIRONMENT was installed. Boot up, log in as root or $USERNAME, connect to the internet, and run: sudo pacman -Sy plasma-meta (or whatever DE tickles your fancy) Choose Proceed to reboot, or Cancel to stay in the live environment." if zenity --question --title "Reboot?" --text "Reboot now?" 2>/dev/null; then cmd reboot fi