Downloading ISO Build related files - Handy Script

I offer this script to "simplify" the download of files related to a specified build. The only one that needs "extra work" is the choice for "server".



Script [ UTIL__Distro_GetRelatedDownloads.sh ]:

#!/bin/sh

###
###	Version 3.0 - Updated to incorporate checksum of the ISO file
###
###	Version 2.0 - Updated to allow for selection of
###	              either Daily Build or Production Release
###	              as well as limited selection of Versions
###

doRelease=0
doTorrent=0
getAll=0
dbg=0
sep="<><><><><><><><><><><><><><><><><><><><>"


	mode=""
	resume="--continue"
#	addSuffix="--adjust-extension"
	allSources="--span-hosts"
#	makeLocal="--convert-links"
#	getParts="--page-requisites"

while [ $# -gt 0 ]
do
	case $1 in
		"--release" )
			doRelease=1
			shift
			;;
		"--daily" )
			doRelease=0
			shift
			;;
		"--torrent" )
			doTorrent=1
			shift
			;;
		"--download" )
			getAll=1
			shift
			;;
		"--showfiles" )
			getAll=2
			shift
			;;
		"--verbose" )
			mode="$1"
			dbg=1
			shift
			;;
		"--debug" )
			mode="$1"
			dbg=2
			shift
			;;
		* )
			echo "\n\t Invalid option used.  Only valid options:  [ --download ] | [ --showfiles ] \n" ; exit 1
			;;
	esac
done

chooseBuild()
{
	buildList=$(basename "$0" ".sh" ).buildslist
	rm -f buildslist

	#    ubuntu-base/
	#    ubuntu-core-desktop/
	#    ubuntu-core-installer/
	#    ubuntu-core/
	#    ubuntu-unity/
	#    ubuntu-wsl/
	#    ubuntukylin/

	echo "\n NOTES:
 	Download of Daily Builds is the default setting (--daily flag).
	To download production releases, run with '--release' flag.

 	if the '--torrent' flag is set on the command line,
	downloading of the *.iso file will be suppressed.


 Choose which ${scope} BUILD you wish to download ...

	[1]   ubuntu-mate
	[2]   ubuntu (Gnome)
	[3]   edubuntu
	[4]   kubuntu
	[5]   lubuntu
	[6]   xubuntu
	[7]   ubuntu-budgie
	[8]   ubuntucinnamon
	[9]   ubuntustudio"

	if [ ${doRelease} -eq 0 ]
	then
		echo "
	[10]  ubuntu-mini-iso
	[11]  ubuntu-server"
	fi
		echo "
	[Q]   Quit

 Enter selection [${choiceRange}|Q] => \c" ; read ans

	if [ -z "${ans}" ] ; then  ans="Q" ; fi


	case "${ans}" in
		1 )	isoBuild="ubuntu-mate" ;;
		2 )	isoBuild="ubuntu" ;;
		3 )	isoBuild="edubuntu" ;;
		4 )	isoBuild="kubuntu" ;;
		5 )	isoBuild="lubuntu" ;;
		6 )	isoBuild="xubuntu" ;;
		7 )	isoBuild="ubuntu-budgie" ;;
		8 )	isoBuild="ubuntucinnamon" ;;
		9 )	isoBuild="ubuntustudio" ;;
		10 )	isoBuild="ubuntu-mini-iso" ;;
		11 )	isoBuild="ubuntu-server" ;;
		q* | Q* )	echo "" ; exit ;;
	esac
} #chooseBuild_Daily()

getBuildsVersion()
{
	url_DIR="https://cdimage.ubuntu.com/${isoBuild}/releases/"
	localFile="-O ${localVers}"

	#rm -f ${localName}
	if [ -s "${localVers}" ]
	then
		echo "\n Using existing file '${localVers}' ..."
	else
		echo "\n${sep}${sep}${sep}\n Downloading information page for ${scope} ISO build  '${isoBuild}' ...\n"
		test ${dbg} -eq 2 && echo " COMMAND:  wget ${mode} ${resume} ${addSuffix} ${allSources} ${makeLocal} ${getParts} ${localFile} '${url_DIR}'"
		wget ${mode} ${resume} ${addSuffix} ${allSources} ${makeLocal} ${getParts} ${localFile} "${url_DIR}"

		if [ $? -gt 0 ]
		then
			echo "\n\t Unable to proceed because a list of versions is not available for that ISO build.\n" ; exit 1
		fi
	fi

#more "${localVers}"

	thisYear=$(date '+%y' )
	thisMth=$(date '+%m' )
	rm -f ${jobList}
	rm -f ${jobList}.full

	#echo $thisYear $thisMth

	awk 'BEGIN{
		pr=0 ;
	}{
		if( pr == 1 ){
			print $0 ;
			if( $0 ~ /<[/]ul>/ ){
				exit ;
			} ;
		}else{
			if( $0 ~ /<ul>/ ){
				pr=1 ;
				print $0 ;
			} ;
		} ;
	}' <${localVers} | grep '[1-9]' |
	awk '{
		pos=index( $0, "<li><a href=\"" ) ;
		if( pos > 0 ){
			rem=substr( $0 , pos+13 ) ;
			#print rem ;
			posE=index( rem, "\">" ) ;
			printf("%s\n", substr( rem , 1, posE-2 ) ) ;
		} ;
	}' | sort -r > ${jobList}.full

	if [ -s ${jobList}.full ]
	then
		test ${dbg} -eq 2 && { 
			echo " == DEBUG == RAW LIST OF VERSIONS AVAILABLE ===" ;
			awk '{ printf("\t | %s\n", $0 ) ; }' <${jobList}.full ;
			echo "" ; } 
	else
		echo "\n ABORT ... Could not identify the associated versions for the ${scope} ISO builds."
		echo " Process Abandonned!\n"
		exit 1 
	fi
	
	awk -v yr="${thisYear}" -v mth="${thisMth}" '{
		ref=0.0+sprintf("%02d.%02d", yr, mth ) ;
		lst=ref-2.5 ;
		if( $1 < ref && $1 >= lst ){
			print $0 ;
		} ;
	}' <${jobList}.full >${jobList}

	if [ -s ${jobList} ]
	then
		test ${dbg} -eq 2 && { 
			echo " == DEBUG == REDUCED SET OF VERSIONS AVAILABLE ===" ;
			awk '{ printf("\t | %s\n", $0 ) ; }' <${jobList} ;
			echo "" ; } 
	else
		echo "\n ABORT ... Could not identify the associated versions for the ${scope} ISO builds."
		echo " Process Abandonned!\n"
		exit 1 
	fi
	
	echo "\n The following are the available ${scope} ISO build versions available:\n"

	awk '{ printf("\t %s\n", $0 ) ; }' <${jobList}

	echo "\n Please enter your choice of ${scope} version => \c" ; read buildVers
	if [ -z "${buildVers}" ]
	then
		echo "\n ABORT ... No selection entered!\n"
		echo " Process Abandonned!\n"
		exit 2
	fi
	
	testor=$(grep '^'${buildVers}'$' ${jobList} )

	if [ -n "${testor}" ]
	then
		test ${dbg} -eq 2 && echo "\n DEBUG ... version = ${testor} "
	else
		echo "\n ABORT ... Version '${buildVers}' entered is not available."
		echo " Process Abandonned!\n"
		exit 2
	fi
} #getBuildsVersion()

getBuildsInfo()
{
	echo "\n${sep}${sep}${sep}\n Downloading checksum files for ISO build '${isoBuild}' ...\n"
	wget ${mode} ${resume} ${addSuffix} ${allSources} ${makeLocal} ${getParts} "${url_DIR}/SHA256SUMS"

	echo "\n${sep}${sep}${sep}\n"
	wget ${mode} ${resume} ${addSuffix} ${allSources} ${makeLocal} ${getParts} "${url_DIR}/SHA256SUMS.gpg"

	echo ""

	buildLabel=$(head -1 SHA256SUMS | awk '{ print $2 ; exit }' | sed 's+^[*]++' | sed 's+[.]iso$++' )

	echo "\n Shared identifier string for ISO build files:  '${buildLabel}' ..."
} #getBuildsInfo()

getBuildsList()
{
	rm -f ${jobList}

	if [ -s "${localName}" ]
	then
		echo "\n Using existing file '${localName}' ..."
	else
		echo "\n${sep}${sep}${sep}\n Downloading information page for ISO build  '${isoBuild}' ...\n"
		test ${dbg} -eq 2 && echo " COMMAND:  wget ${mode} ${resume} ${addSuffix} ${allSources} ${makeLocal} ${getParts} ${localFile} '${url_DIR}'"
		wget ${mode} ${resume} ${addSuffix} ${allSources} ${makeLocal} ${getParts} ${localFile} "${url_DIR}"
	fi

	awk 'BEGIN{
		pr=0 ;
	}{
		if( pr == 1 ){
			print $0 ;
			if( $0 ~ /<[/]table>/ ){
				exit ;
			} ;
		}else{
			if( $0 ~ /<table>/ ){
				pr=1 ;
				print $0 ;
			} ;
		} ;
	}' <${localName} | grep '/cdicons/' |
	awk -v prefix="${url_DIR}" '{
		pos=index( $0, "<td><a href=\"" ) ;
		if( pos > 0 ){
			rem=substr( $0 , pos+13 ) ;
			#print rem ;
			posE=index( rem, "\">" ) ;
			printf("%s%s\n", prefix, substr( rem , 1, posE-1 ) ) ;
		} ;
	}' >${jobList}

	grep '/SHA256SUMS' ${jobList} >${jobList}.reduced
	grep -v '/SHA256SUMS' ${jobList} | grep "${buildLabel}" >>${jobList}.reduced

	mv ${jobList}.reduced ${jobList}
} #getBuildsList()

reviewContinue()
{
	echo "\n ISO build '${isoBuild}' lists the following associated files:"
	awk '{ printf("\t | %s\n", $0 ) ; }' <${jobList}

	case ${getAll} in
		0 )	echo "\n Proceed with download of all files identified? [y|N] => \c" ; read ans
			if [ -z "${ans}" ] ; then  ans="N" ; fi
			;;
		1 )	ans="y" ;;
		2 )	ans="N" ;;
	esac
} #reviewContinue()


getBuildsComp()
{
	case "${ans}" in
		y* | Y* )
			for suf in iso iso.zsync list manifest
			do
				if [ ${suf} = "iso" -a ${doTorrent} -eq 1 ]
				then
					suf="torrent"
				fi

				item="${buildLabel}.${suf}"
				testor=$(grep "${item}" ${jobList} )
				if [ -n "${testor}" ]
				then
					echo "\n${sep}${sep}${sep}\n Downloading build file '${buildLabel}.${suf}' ..."
					test ${dbg} -eq 2 && echo " COMMAND:  wget ${mode} ${resume} ${addSuffix} ${allSources} ${makeLocal} ${getParts} '${url_DIR}/${buildLabel}.${suf}'\n"
					wget ${mode} ${resume} ${addSuffix} ${allSources} ${makeLocal} ${getParts} "${url_DIR}/${buildLabel}.${suf}"

					if [ $? -ne 0 ]
					then
						echo " File '${buildLabel}.${suf}' was missing ... or failed to download ..."
						if [ "${suf}" = "iso" ]
						then
							test -s "${buildLabel}.${suf}" && echo "\t Retaining partial download for later re-attempt ..."
							ls -l "${buildLabel}.${suf}" | awk '{ printf("\t %s\n", $0 ) ; }'
						else
							rm -i "${buildLabel}.${suf}"
						fi
					fi
				else
					echo " NOTE:  File '${buildLabel}.${suf}' is not listed as an ISO build related item ..."
				fi
			done
			;;
		* )	echo "\n That list of files has been saved in '${jobList}'.\n" ; exit ;;
	esac
} #getBuildsComponent()

verifyISO()
{
	# resolute-live-server-amd64.iso: OK
	chkMsgs="${buildLabel}.checksums"

	if [ $(sha256sum -c SHA256SUMS 2>"${chkMsgs}" | grep "${buildLabel}.${suf}" | 
		awk '{ if( $2 == "OK" ){ print $2 ; } ; }' ) = "OK" ]
	then
		echo "\n Download of image '${buildLabel}.${suf}' was successful."
		if [ -s "${chkMsgs}" ]
		then
			echo "\n Other messages generated by 'sha256sum':"
			awk '{ printf("\t | %s\n", $0 ) ; }' <"${chkMsgs}"
		fi
		echo ""
		exit 0
	else
		echo "\n Checksum for '${buildLabel}.${suf}' image FAILED!\n"
		exit 1
	fi
}


################################################

jobList=$(basename "$0" ".sh" ).joblist
rm -f jobList

if [ ${doRelease} -eq 1 ]
then
	scope="Release"
 	choiceRange="1-9"

	chooseBuild

	localVers="${isoBuild}_versions.html"
	localFile="-O ${localName}"

	getBuildsVersion

	url_DIR="https://cdimage.ubuntu.com/${isoBuild}/releases/${buildVers}/release/"
	test ${dbg} -eq 2 && { echo "\t DEBUG ... url_DIR = ${url_DIR}" ; }

else
	scope="Daily"
 	choiceRange="1-11"

	chooseBuild

	url_DIR="https://cdimages.ubuntu.com/${isoBuild}/daily-live/current/"
	test ${dbg} -eq 2 && { echo "\t DEBUG ... url_DIR = ${url_DIR}" ; }
fi


localName="${isoBuild}_details.html"
localFile="-O ${localName}"

getBuildsInfo
getBuildsList
reviewContinue
getBuildsComp
verifyISO

exit 0
exit 0
exit 0


Log of sample session:



 NOTES:
 	Download of Daily Builds is the default setting (--daily flag).
	To download production releases, run with '--release' flag.

 	if the '--torrent' flag is set on the command line,
	downloading of the *.iso file will be suppressed.


 Choose which BUILD you wish to download ...

	[1]   ubuntu-mate
	[2]   ubuntu (Gnome)
	[3]   edubuntu
	[4]   kubuntu
	[5]   lubuntu
	[6]   xubuntu
	[7]   ubuntu-budgie
	[8]   ubuntucinnamon
	[9]   ubuntustudio

	[10]  ubuntu-mini-iso
	[11]  ubuntu-server

	[Q]   Quit

	Enter selection [1-11] => 10

<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
 Downloading checksum files for ISO build 'ubuntu-mini-iso' ...

--2026-01-30 22:09:23--  https://cdimages.ubuntu.com/ubuntu-mini-iso/daily-live/current//SHA256SUMS
Resolving cdimages.ubuntu.com (cdimages.ubuntu.com)... 91.189.91.124, 185.125.190.37, 2620:2d:4000:1::17, ...
Connecting to cdimages.ubuntu.com (cdimages.ubuntu.com)|91.189.91.124|:443... connected.
HTTP request sent, awaiting response... 416 Requested Range Not Satisfiable

    The file is already fully retrieved; nothing to do.


<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>

--2026-01-30 22:09:24--  https://cdimages.ubuntu.com/ubuntu-mini-iso/daily-live/current//SHA256SUMS.gpg
Resolving cdimages.ubuntu.com (cdimages.ubuntu.com)... 91.189.91.124, 185.125.190.37, 2620:2d:4000:1::17, ...
Connecting to cdimages.ubuntu.com (cdimages.ubuntu.com)|91.189.91.124|:443... connected.
HTTP request sent, awaiting response... 416 Requested Range Not Satisfiable

    The file is already fully retrieved; nothing to do.



 Shared identifier string for ISO build files:  'resolute-mini-iso-amd64' ...

 Using existing file 'ubuntu-mini-iso_details.html' ...

 ISO build 'ubuntu-mini-iso' lists the following associated files:
	 | https://cdimages.ubuntu.com/ubuntu-mini-iso/daily-live/current/SHA256SUMS
	 | https://cdimages.ubuntu.com/ubuntu-mini-iso/daily-live/current/SHA256SUMS.gpg
	 | https://cdimages.ubuntu.com/ubuntu-mini-iso/daily-live/current/resolute-mini-iso-amd64.iso
	 | https://cdimages.ubuntu.com/ubuntu-mini-iso/daily-live/current/resolute-mini-iso-amd64.iso.zsync

 Proceed with download of all files identified? [y|N] => y

<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
 Downloading build file 'resolute-mini-iso-amd64.iso' ...
--2026-01-30 22:09:48--  https://cdimages.ubuntu.com/ubuntu-mini-iso/daily-live/current//resolute-mini-iso-amd64.iso
Resolving cdimages.ubuntu.com (cdimages.ubuntu.com)... 91.189.91.124, 185.125.190.37, 2620:2d:4000:1::17, ...
Connecting to cdimages.ubuntu.com (cdimages.ubuntu.com)|91.189.91.124|:443... connected.
HTTP request sent, awaiting response... 416 Requested Range Not Satisfiable

    The file is already fully retrieved; nothing to do.


<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
 Downloading build file 'resolute-mini-iso-amd64.iso.zsync' ...
--2026-01-30 22:09:48--  https://cdimages.ubuntu.com/ubuntu-mini-iso/daily-live/current//resolute-mini-iso-amd64.iso.zsync
Resolving cdimages.ubuntu.com (cdimages.ubuntu.com)... 91.189.91.124, 185.125.190.37, 2620:2d:4000:1::17, ...
Connecting to cdimages.ubuntu.com (cdimages.ubuntu.com)|91.189.91.124|:443... connected.
HTTP request sent, awaiting response... 416 Requested Range Not Satisfiable

    The file is already fully retrieved; nothing to do.

 NOTE:  File 'resolute-mini-iso-amd64.list' is not listed as an ISO build related item ...
 NOTE:  File 'resolute-mini-iso-amd64.manifest' is not listed as an ISO build related item ...
4 Likes

For now, the code uses the first line in the SHA256SUM file to identify the ISO to download.

The server case is more complicated and needs some additional logic, which I haven't fleshed out yet.

However ... I also wrote a bit of code logic to identify the ARCH for different server platforms, but I'm not sure how to encorporate that into the rest.

getArchBits()
{
	thisArch=$( inxi -C 2>&1 |
		awk -v dbg="${dbg}" '{
			if( $1 == "Info:" ){
				posA=index( $0, "model:" ) ;
				posB=index( $0, "bits:" ) ;

				remA=substr( $0, posA, posB-(posA) ) ;
				remB=substr( $0, posB ) ;

				if( dbg == 1 ){ print remA | "cat 1>&2" } ;
				if( dbg == 1 ){ print remB | "cat 1>&2" } ;

				n=split( remA, vals ) ;
				arch=tolower(vals[2]) ;

				n=split( remB, vals ) ;
				bits=vals[2] ;

				printf("%s%s\n", arch, bits ) ;
				exit ;
			} ;
		}' )
}

Maybe some kind soul will take on the task of merging those two for a "consistent" whole that is workable?