Preparation and Installation of the base system

  1. Connect the USB drive and boot from the Arch Linux ISO
  2. Make sure the system is booted in UEFI mode. The following command should display the directory contents without error
    • ls /sys/firmware/efi/efivars
  3. Connect to the internet (if not already). Assuming wireless:
    • iwctl
    • station <interface> scan
    • station <interface> get-networks
    • station <interface> connect <network-name>
  4. Run fdisk to create Linux partitions
    • fdisk /dev/<your-disk>
    • Create an empty GPT partition table using the g command
      • # WARNING: This will erase the entire disk.

        Command (m for help): g
        Created a new GPT disklabel (GUID: ...)
    • Create the EFI partition (/dev/<your-disk-efi>)
      • Command (m for help): n
        Partition number: <Press Enter>
        First sector: <Press Enter>
        Last sector, +/-sectors or +/-size{K,M,G,T,P}: +250M

        Command (m for help): t
        Partition type or alias (type L to list all): uefi
    • Create the Boot partition (/dev/<your-disk-boot>)
      • Command (m for help): n
        Partition number: <Press Enter>
        First sector: <Press Enter>
        Last sector, +/-sectors or +/-size{K,M,G,T,P}: +512M

        Command (m for help): t
        Partition type or alias (type L to list all): linux
    • Create the LUKS partition (/dev/<your-disk-luks>)
      • Command (m for help): n
        Partition number: <Press Enter>
        First sector: <Press Enter>
        Last sector, +/-sectors or +/-size{K,M,G,T,P}: <Press Enter>

        Command (m for help): t
        Partition type or alias (type L to list all): linux
    • Print the partition table using the p command and check that everything is OK
      • Command (m for help): p
    • Write changes to the disk using the w command. (Make sure you know what you’re doing before running this command)
      • Command (m for help): w
  5. Format the EFI and Boot Partitions
    • mkfs.fat -F 32 /dev/<your-disk-efi>
      mkfs.ext4 /dev/<your-disk-boot>
  6. Set up the encrypted partition. You can choose any other name instead of cryptlvm
    • cryptsetup --use-random luksFormat /dev/<your-disk-luks>
      cryptsetup luksOpen /dev/<your-disk-luks> cryptlvm
  7. Create an LVM volume group
    • pvcreate /dev/mapper/cryptlvm
      vgcreate vg0 /dev/mapper/cryptlvm
  8. Create LVM partitions; We create logical volumes for swap, root (/), and home (/home). Leave 256MiB of free space in the volume group because the e2scrub command requires the LVM volume group to have at least 256MiB of unallocated space to dedicate to the snapshot.
    • lvcreate --size 4G vg0 --name swap
      lvcreate -l +100%FREE vg0 --name root
      lvreduce --size -256M vg0/root
  9. Format logical volumes
    • mkswap /dev/vg0/swap
      mkfs.ext4 /dev/vg0/root
  10. Mount new filesystems
    • mount /dev/vg0/root /mnt
      mount --mkdir /dev/<your-disk-efi> /mnt/efi
      mount --mkdir /dev/<your-disk-boot> /mnt/boot
      swapon /dev/vg0/swap
  11. Install the base system. We also install some useful packages like gitvim, and sudo
    • pacstrap -K /mnt base linux linux-firmware openssh git vim sudo
  12. Generate /etc/fstab. This file can be used to define how disk partitions, various other block devices, or remote filesystems should be mounted into the filesystem
    • genfstab -U /mnt >> /mnt/etc/fstab
  13. Chroot into the new system
    • arch-chroot /mnt /bin/bash
  14. Set TimeZone
    • ln -s /usr/share/zoneinfo/<Region>/<City> /etc/localtime
  15. Run hwclock(8) to generate /etc/adjtime
    • hwclock --systohc
  16. Set Locale
    • vim /etc/locale.gen #uncomment en_US.UTF-8 UTF-8
      locale-gen
      echo LANG=en_US.UTF-8 > /etc/locale.conf
  17. Set hostname
    • echo <new-hostname> /etc/hostname
  18. Create a user
    • useradd -m -G wheel --shell /bin/bash <your-username>
      passwd <your-username>
      visudo # ---> Uncomment "%wheel ALL=(ALL) ALL"
  19. Configure mkinitcpio with modules needed to create the initramfs image
    • pacman -S lvm2
      vim /etc/mkinitcpio.conf
      # ---> Add 'encrypt' and 'lvm2' to HOOKS before 'filesystems'
      mkinitcpio -P
  20. Setup GRUB
    • pacman -S grub efibootmgr
      grub-install --target=x86_64-efi --efi-directory=/efi --bootloader-id=GRUB

      In /etc/default/grub edit the line GRUB_CMDLINE_LINUX as follows. Don’t forget to replace /dev/<your-disk-luks> with the appropriate path.

      GRUB_CMDLINE_LINUX="cryptdevice=/dev/<your-disk-luks>:cryptlvm root=/dev/vg0/root"

      Now generate the main GRUB configuration file

      grub-mkconfig -o /boot/grub/grub.cfg
  21. Install networkmanager package and enable NetworkManager service to ensure you have Internet connectivity after rebooting
    • pacman -S networkmanager
      systemctl enable NetworkManager
  22. Exit new system and unmount all filesystems
    • exit
      umount -R /mnt
      swapoff -a

      Arch has been installed! Reboot

      reboot

Installation of Cinnamon

  1. After reboot, login
  2. Install Cinnamon and some basic extras
    • sudo pacman -S cinnamon nemo-fileroller xdg-user-dirs gnome-terminal gnome-system-monitor gnome-screenshot lightdm lightdm-gtk-greeter lightdm-gtk-greeter-settings lightdm-slick-greeter
  3. You can either roll with the default greeter, or install slick-greeter
    • sudo vim /etc/lightdm/lightdm.conf
    • Look for greeter-session and change its value

      greeter-session=lightdm-slick-greeter
    • Save and quit vim
  4. Reboot

And there you have it! Arch Linux with Cinnamon. Enjoy!

Found this out on the Interwebs. It’s a DOS music player from 1998, long before I had frequented the halls of Napster. Of course, the MPAA smacked us all down and 86’d all of the sharing for most of us. I’m pretty sure they would have litigated mix tapes, had they the ability to. Anyway, kind of cool to be able to run this in DOSBox and experience how people first experienced extracted music.

So, the BASH downloader worked well enough for what it was. Then I decided to do some functional upgrades. It got so complex for a BASH script that I decided to make it a C# app. The whole dotnet platform is kind of the new Java, so why not roll with it? Besides, I’m getting pretty good at this dotnet on Linux thing–all my servers that I run are Linux (mostly Debian). I used to be pretty big on PHP on that platform, but as dotnet has matured that’s gone by the wayside.

The end result was pretty good, I think. Fairly simple service-layer architecture, with one service for dealing with episode management, and one for managing the metadata. I have it set up as a scheduled task that runs every four hours on my Plex box. It’s done fairly well, after ironing out the kinks over a couple weeks.

The neat little part of it was Main was as simple as:

private static async Task Main(string[] args)
    {
        var serviceProvider = ServiceConfiguration
            .AddServices()
            .BuildServiceProvider();

        using (var scope = serviceProvider.CreateScope())
        {
            var episodeProcessing = scope.ServiceProvider.GetService<IEpisodeProcessing>();
            if (episodeProcessing != null)
            {
                await episodeProcessing.ProcessEpisodes();
            }
        }
    }

Much like the BASH script, it pulls from a list that I define–though, I threw that list into a SQLite db. Still thinking about how I want to manage the list. Possibly a web app, possibly an Electron app. A web app would be pretty simple, but I’ve poked at Electron a bit and I do dig how it’s pretty much a web app in spirit. We’ll see. It’s not like I add podcasts to my list very often, so I can ponder a bit.

A long time ago, in a galaxy far, far away…in the late 20th century…a young chap started out in Computer Science at the University of Colorado in Denver.

Things were going pretty well. Second semester hit, and the adventure really began. There was a course in object oriented development in C++ in this lad’s early CS career. He hadn’t seen objects before, and only had a notional understanding of what they were—if even that.

A project that took pretty much all of the semester was to create a set of classes to manipulate and display a linked list. This was his first foray into abstract data types. It was quite the effort across that semester, but it got done—and, not only was a program which handled CRUD for a linked list the end result, but an indexed one—to allow you to access values non-sequentially as if it were an array.

It was a cool project in the end, and this lad looks back with fondness at those early days, when all of it was so new.

Flash forward nearly three decades to the advent of LLM, Large Language Models.

It’s become a bit of a hobby of mine to throw things at ChatGPT, one of the more popular LLM generators these days, to see what it can do.

I gave it the basic requirements of that very same semester project freshman year. It generated a whole class, complete with a main driver, in mere seconds. You can’t help but feel a mix of fascination and a bit of melancholy as you watch all of that effort (which I could now do in less than an hour, myself) do it all in just a mere moment.

Also makes me wonder how creative professors have to get these days to come up with a project that can’t be easily generated 😂

ChatGPT

ChatGPT helps you get answers, find inspiration and be more productive. It is free to use and easy to try. Just ask and ChatGPT can help with writing, learning, brainstorming and more.

I’m sure I’ll be modifying this more over time. But, it reads from a text file (podcasts.txt) in the given DOWNLOAD_PATH–this file contains the RSS URL(s) for the podcast(s). It organizes the podcasts into directories, if those directories don’t already exist. It uses eyeD3 to add metadata (including cover art). Works fairly well, though I’m still stomping bugs.

Maybe next I’ll put together a nice little frontend for it–maybe convert the whole thing to a Blazor app. But, for now, it does the thing and it’s fun being a script kiddie 🙂

#!/bin/bash

create_path() {
    TITLE_PATH="${DOWNLOAD_PATH}/${1}"

    if [ ! -d "${TITLE_PATH}" ]; then
       mkdir "${TITLE_PATH}"
    fi
}

sanitize_filename() {
  local filename="$1"
  # Replace unfriendly characters with underscore
  sanitized=$(echo "$filename" | sed 's/[\/:*?"<>|]/_/g')
  echo "$sanitized"
}

dl_podcast() {
    local RSS_FEED_URL=$1

    curl -s "$RSS_FEED_URL" > /tmp/rss_feed.xml

    title=$(get_podcast_title "${RSS_FEED_URL}")

    create_path "${title}"

    # Initialize arrays to store episode titles and enclosure URLs
    titles=()
    urls=()

    # Extract episode titles and enclosure URLs
    mapfile -t titles < <(xmllint --xpath '//item/title/text()' /tmp/rss_feed.xml)
    mapfile -t urls < <(xmllint --xpath '//item/enclosure/@url' /tmp/rss_feed.xml)

    # Print the episode titles and enclosure URLs
    j=${#titles[@]}
    padtowidth=${#j}
    for ((i=0; i<${#titles[@]}; i++)); do
	ep_title=$(echo "${titles[$i]}" | sed -e 's/<!\[CDATA\[//' -e 's/\]\]>//')
        url=`echo ${urls[$i]} | sed "s/url=//g"`
        url=`echo ${url} | sed "s/\"//g"`
        filenum=`printf "%0*d\n" $padtowidth $j`
        file="${filenum} $ep_title.mp3"
        file=$(sanitize_filename "${file}")

        if [ ! -f "${TITLE_PATH}/${file}" ]; then
            echo "Downloading: ${file} (${url})"
            wget -qO "${TITLE_PATH}/${file}" "${url}"
	    /usr/bin/eyeD3 --track "${filenum}" --disc-num "1" "${TITLE_PATH}/${file}"
	    /usr/bin/eyeD3 --add-image="/tmp/cover.jpg":FRONT_COVER "${TITLE_PATH}/${file}"
        fi
        j=`expr ${j} - 1`
    done

    unset ${TITLE_PATH}
}

get_podcast_title() {
    local url="${1}"
    local title="$(curl -s "$url" | grep -oP '<title>\K[^<]+' | sed -n '2p')"
    echo "${title}"
}

get_podcast_cover() {
    local url=$1
    local cover_url=$(curl -s "$url" | grep -oP '<itunes:image\s+href="\K[^"]+' | head -n 1)

    curl -s -o /tmp/cover.jpg ${cover_url}
}

cleanup() {
    rm /tmp/cover.jpg
    rm /tmp/rss_feed.xml
}

read_lines_into_array() {
    local file=$1

    # Check if file exists
    if [ ! -f "$file" ]; then
        echo "Error: File $file not found."
        exit 1
    fi

    # Read the file line by line and append each line to the array
    mapfile -t lines_array < "${file}"

    # Return the array
    echo "${lines_array[@]}"
}

process_podcasts() {
    local -n podcasts=$1

    for link in "${podcasts[@]}"; do
        get_podcast_cover "$link"

        title=$(get_podcast_title "$link")

        dl_podcast $link
    done
}

main()
{
    DOWNLOAD_PATH="<PODCAST DIRECTORY GOES HERE>"
    TITLE_PATH=${DOWNLOAD_PATH}

    if [ $# -ne 1 ]; then
        echo "Usage: $0 <rss url>"
        exit 1
    fi

    file=$1

    # Call the function and store the result in an array
    declare -A links
    links=$(read_lines_into_array "$file")

    # Print the contents of the resulting array
    process_podcasts links
    cleanup
}

main "$@"

Been working on a podcast downloader for use with Plex, since Plex decided to take away podcasts–basically I keep the ones I wanna retain long-term in a library collection now. The downloader is a simple BASH script. Seems to be nearly perfected, even adds metadata (track #, cover art, etc) to the downloaded mp3.

I think my last little issue is that I need to have it rename all the files whenever episodes hit a factor of 10 (e.g., if there are 10 episodes, episode one should be “01 – Title”, when it hits 100, episode one should be renamed to “001 – Title”). It’s a little thing–I just like my files sorting properly by name, no matter what OS I’m listing them in. And Linux sorts in lexicographical by default, as opposed to natural order 🙄

Some guys work in the garage on Saturday mornings–I work on code 🤣

Windows Terminal handles opacity better than any other CLI ever did. I played with opacity some time back with Powershell before Windows Terminal came along, but the whole window became opaque, as opposed to just the content pane. I tend to set console opacity first-thing whenever I get a Mac, and it’s good that it can now be integrated into my Windows setup habits 😀 One can do this with the entirety of Windows Terminal, or narrow it to any specific command-line app you want.