DEV Community

Alex M. Schapelle for Otomato

Posted on

Forging Ogun With Bash

Welcome back gentle reader, I am Silent-Mobius, aka Alex M. Schapelle, your humanoid circuit-board object that will guide you with topics of open-source, software and tools.

In our last article we started to develop a tool that will automate our ISO files configuration.
In this article we'll forge into a tool.

Forging OGUN for editing ISO

Before diving in to the code, I'd suggest, we acknowledge the fact that most of the code today, including mine, are kept in remote repository based mostly but not implicitly on git. You are welcome to visit the Tool repository on gitlab, leave a comment, suggest a feature and clone the tool for your own adventure.

As of now, we have satisfied dependencies, created code snippets and have created working environment. All is left is to combine it all in to one file and add more features. The code below is the updated version of the Tool, which we named OGUN:


#!/usr/bin/env bash

############################ Special Header: Start ################################
# Created by Silent-Mobius, Zero-Pytagoras aka Alex M. Schapelle
# Purpose: Manipulate ISO files
# Version:  01.03.86
# <Manipulate ISO files to configure custom ISO files manually and  automoatically inside CI/CD pipelines
#(C) 2023  Silent Mobius aka Alex M. Schapelle 

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.

# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

set -o errexit
set -o pipefail
set -x

# Global variables 
PROJECT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
CONFIG="$HOME/.config/ogun"
ISO_FOLDER="$PROJECT/iso"
YAML="$PROJECT/config.yaml"
OUT_FOLDER="$PROJECT/iso"
SQUASHFS_FOLDER="$PROJECT/squashfs-root"
DEPENDENCIES=(p7zip-full git genisoimage fakeroot pwgen whois xorriso isolinux binutils squashfs-tools yq)
SEPERATOR="#######################"
############################ Special Header: End ################################
# Messages 
_msg_need_root="[!] Escalate permissions with sudo or switch to user root"
_msg_not_support="[!] $ID Distribution is not supported yet "
_msg_reassemble_error="[!] Wrong Value Passed: Please Read Usage with -h flag"


# Value files imports
. /etc/os-release


function main(){

    cd $PROJECT

    if [[ "$#" == 0 ]];then
        usage
        exit 0
    fi

    if [[ $UID -eq 0 ]] || [[ $EUID -eq 0 ]];then

        while getopts "chdvDHRSV" tasks
            do
                case ${tasks[@]} in 
                    D)  while getopts "h:i:" OPTS # Disassemble squash and iso files
                        do
                            case "${OPTS[@]}" in
                                h) d_usage; exit 0;;
                                i) ISO_FILE=$OPTARG ;;
                                *) d_usage; exit 0;;
                            esac
                        done
                            disassemble_iso $ISO_FILE ${OUT_FOLDER##*/}
                            disassemble_squashfile  $ISO_FOLDER
                            ;;
                    R) while getopts "h:i:o:" OPTS # Reassemble squash and iso files
                            do
                                case "${OPTS[@]}" in
                                    h) r_usage; exit 0;;
                                    i) ISO_FOLDER=$OPTARG;;
                                    o) OS_VERSION=$OPTARG;; 
                                    *) r_usage; exit 0;;
                                esac
                            done
                                SQUASH_FILE=$(get_squashfs_file "$SQUASHFS_FOLDER")
                                reassemble_squashfile  $SQUASHFS_FOLDER ${SQUASH_FILE##*/}
                                sign_md5  $ISO_FOLDER
                                reassemble_iso $OS_VERSION $ISO_FOLDER
                            ;;
                    S)  while getopts "h:u:v:" OPTS  # Setup variables and sed them
                            do
                                case "${OPTS[@]}" in
                                    h) s_usage; exit 0;;
                                    v) OS_VERSION=$OPTARG;;
                                    *) s_usage; exit 0;;
                                esac
                            done
                                chroot_squash_folder_with_install_config  $SQUASHFS_FOLDER
                            ;;
                    c) rm -rf "$OUT_FOLDER" "$PROJECT/$SQUASHFS_FOLDER"
                        ;;
                    d) check_dependencies
                        ;;
                    h|H)
                        usage
                        exit 0;;

                    v|V) version
                         exit 0;;

                    *)  usage
                        exit 0
                        ;;
            esac

        done
    else
        deco "$_msg_need_root"
    fi
    }


function deco(){
    IN="$*"
        printf "\n$SEPERATOR\n %s\n$SEPERATOR\n" "$IN"
    }


function version(){
    VERSION=$(grep -m1 '# Version:  ' $0 | awk '{print $3}')
        deco "[>] Version: $VERSION"
    }

function d_usage(){
    deco "
        [>>] -h : Disassembly function descriptionss
        [>>] -i : ISO file which to disassemble
        [>>] Example: $0 -D -i ubuntu-22.04.3-live-server-amd64.iso 
        "
    }

function r_usage(){
    deco "
        [>>] -h : Reassemble function description
        [>>] -i : Exported ISO folder path. E.g. iso folder
        [>>] Example: $0 -R -i iso
        "ca
    }

function s_usage(){
    deco "
        [>>] -h : Setup function descriptions
        [>>] -v : OS version --> major version number : 20 and 22 in case of ubuntu
        [>>] Example: $0 -S -v 22 
        "
    }

function usage(){
    clear
    deco "[!] Incorect Usage:
            [>] -c: Clean up the working 
            [>] -d: Dependency check
            [>] -h|H: This help message
            [>] -D: Disassemble ISO and SQuashfs file
                [>>] -h : this help function
                [>>] -i : ISO file which to disassemble
                [>>] Example: $0 -D -i ubuntu-22.04.3-live-server-amd64.iso   
            [>] -R: Reassemble ISO and SQuashfs file
                [>>] -h : Reassemble function description
                [>>] -i : Exported ISO folder path. E.g. iso folder
                [>>] Example: $0 -R -i iso
            [>] -S: Setup Configuration for Configuration
                [>>] -h : this help function
                [>>] -v : OS version 12, 20 and 22 for major definitions
                [>>] Example: $0 -S -v 22 
        "
    }



function disassemble_iso(){ 
        local IN=$1 # full path is required ...
        local OUT=$2
            7z x -y $IN -o$OUT
    }

function get_squashfs_file(){
        local IN=$1
        local min=100
        local tmp_squash_file=''
            SQUASH_FILES=($(find $IN -name '*.squashfs'))
            for squash_file in "${SQUASH_FILES[@]}"
                do
                    if [[ ${squash_file##*/} == 'filesystem.squashfs' ]];then
                        echo $squash_file
                        break
                    fi
                    if [[ ${#squash_file} -lt $min ]] || [[ ${#squash_file} -le $min ]];then
                        min=${#squash_file}
                        tmp_squash_file=${squash_file}
                    fi
                done
            echo "$tmp_squash_file"
    }

function disassemble_squashfile(){ 
        local PATH_TO_SQUASH_FOLDER=$1 
        local SQUASH_FILE_PATH=$(get_squashfs_file "$PATH_TO_SQUASH_FOLDER")
            mv "$SQUASH_FILE_PATH" "./${SQUASH_FILE_PATH##*/}"
            unsquashfs "./${SQUASH_FILE_PATH##*/}"
            rm -rf $PROJECT/${SQUASH_FILE_PATH##*/}
    }


function reassemble_squashfile(){
    local squash_folder=$1
    local squash_file=$2
        mksquashfs $squash_folder $squash_file -comp xz -b 1M -noappend
        mv "$squash_file" "$OUT_FOLDER/isolinux/"
    }


function reassemble_iso(){
    local system_version=$1
    local iso_folder=$2
        if [[ ! ${system_version} ]] || [[ ${#system_version} -gt 2 ]];then
            deco $_msg_reassemble_error 
            exit 1
        else
            if [[ ${system_version} -eq 22 ]];then
                xorriso -as mkisofs  -r   -V 'Unattended Custom Install'  --grub2-mbr ./BOOT/1-Boot-NoEmul.img -partition_offset 16   --mbr-force-bootable \
                -append_partition 2 28732ac11ff8d211ba4b00a0c93ec93b ./BOOT/2-Boot-NoEmul.img   -appended_part_as_gpt \
                -iso_mbr_part_type a2a0d0ebe5b9334487c068b6b72699c7   -c '/boot.catalog'   -b '/boot/grub/i386-pc/eltorito.img' \
                -no-emul-boot -boot-load-size 4 -boot-info-table --grub2-boot-info   -eltorito-alt-boot   -e '--interval:appended_partition_2:::' \
                -no-emul-boot -o "iso-deb${system_version}.iso" $iso_folder
            else
                xorriso -as mkisofs -r -V "Unattended Custom Install" -J -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot\
                -boot-load-size 4 -isohybrid-mbr /usr/lib/ISOLINUX/isohdpfx.bin -boot-info-table -input-charset utf-8 -eltorito-alt-boot\
                -e boot/grub/efi.img -no-emul-boot -isohybrid-gpt-basdat -o "iso-deb${system_version}.iso" $iso_folder
            fi
        fi    
    }


function sign_md5(){
        local iso_folder=$1
        md5sum "$iso_folder/.disk/info" > "$iso_folder/md5sum.txt"
        sed -i "s|$iso_folder/|./|g" "$iso_folder/md5sum.txt"
    }


function chroot_squash_folder_with_install_config(){
        local CHROOT=$1
        # readarray -t repos < <(read_yaml_config '.repositories|values[]'|tr -d '"')
        readarray -t packages < <(read_yaml_config .install.apt_package_list[]|tr -d '"')
        # TODO: implement cert install correctly
            curl -sL https://www.virtualbox.org/download/oracle_vbox_2016.asc -o "$CHROOT/usr/share/keyrings/oracle-virtualbox-2016.gpg"
            curl -sL https://gitlab.com/paulcarroty/vscodium-deb-rpm-repo/raw/master/pub.gpg -o "$CHROOT/usr/share/keyrings/vscodium-archive-keyring.gpg"
            curl -sL https://apt.releases.hashicorp.com/gpg -o "$CHROOT/usr/share/keyrings/hashicorp-archive-keyring.gpg"
        # TODO: repositories are not still added in key valye manner:
        # TODO: implementation: key: name of repo | value :string of repo

        chroot $CHROOT /bin/bash -c "
            echo 'nameserver 8.8.8.8' > /etc/resolv.conf
            apt-get update
            DEBIAN_FRONTEND=noninteractive apt-get install -y ${packages[@]}
            apt-cache clean
            rm -rf /var/cache/apt/archives
            rm -rf /var/lib/apt/lists
            echo '' > /etc/resolv.conf
            history -c
        "

    }

#################################### utils ################################################3



function is_root(){
        if [[ $UID -eq 0 ]] || [[ $EUID -eq 0 ]]; then
            return 0
        fi
        return 1
    }


function check_dependencies(){
        if [[ ! -e "${CONFIG}/.dependencies_exists" ]];then
            if [[ ! "$CONFIG" ]];then
                mkdir -p "$CONFIG"
            fi
            if [[ $ID == 'debian' ]] || [[ $ID == 'ubuntu' ]];then
                INSTALLER='apt-get'
            else
                deco "$_msg_not_support"
                exit 1
            fi
            for package in "${DEPENDENCIES[@]}"
                do
                    if ! which "$package" > /dev/null 2>&1;then
                        $INSTALLER -y install "$package"
                    fi
                done
        fi
    }

function read_yaml_config(){
    local config_table=$1
    local array_list=""
        for element in $(yq -r < $YAML $config_table)
            do 
                array_list=("${array_list[@]}" "$element")
            done
        echo "${array_list[@]}"
    }


function set_repositories(){
    repo_name=$1
    repo_address=$2
        touch "$CHROOT/etc/apt/sources.list.d/$repo_name.list"
        echo "$repo_address"| tee "$CHROOT/etc/apt/sources.list.d/$repo_name"
    }

function is_exists(){
    local _file=$1
        if [[ -e $_file ]];then
            return 0
        fi
        return 1
    }

########
# Main  - _- _- _- _- _- _- _- Do Not Remove - _- _- _- _- _- _- _- _- _- _- _- _- _ 
########
main "$@"

Enter fullscreen mode Exit fullscreen mode

[+] Note: In order to use OGUN, configuration file is required, thus DO NOT try to RUN the script YET.
[+] Note: Configuration is suppose to be named config.yaml, and for now it supports, only package install from repositories, and needs to be structured as associate dictinary including array of packages installed.
[$] Explanation: I love using environment variables in conjunction with positional arguments and optional arguments, so as we continue to run commands, we'll also write bash functions in most general way possible and combine them with types of variables mentioned before. All and all it will allow us to convert bash functions in to shell based tool for manipulating ISO type files.

Although short article, it has a complex code, and lots of fixes to do, which you may follow on gitlab and You may continue reading about using ogun in CI/CD with Jenkins in our next article
Till then, remember: Do Try To Have Some Fun

Top comments (0)