4
\$\begingroup\$

I have this bash script I've been working on and I have updated it. I was hoping for some advice and/or input on the code.

This script is run directly after a fresh install of Debian, to do:

  • Sets up syntax highlighting in Nano
  • Sets up iptables
  • Sets up ssh
  • Sets up custom bashrc files
  • ls colors
  • Creates users on the system if needed
  • Checks if user has a password set and sets it if not
  • Installs non-free firmware and sets up apt with Virtualbox deb file and multimedia deb in sources.list
  • Installs video and audio codecs, players and related
  • Sets up flash for Mozilla Firefox and creates a cron for weekly updates
  • It updates the system

There was mention of debconf but I've never heard about that.

Could you add some features to the program that are practical, convenient or cool for setting up new installs?

Is there anything in the program that I don't need?

#!/bin/bash -x

########### Copy or Move the accompanied directory called "svaka" to /tmp ######################
################################################################################################

################## shopt (shopt [-pqsu] [-o] [optname …]) = This builtin allows you to change additional shell optional behavior. 
################## -s = Enable (set) each optname.
################## -o = Restricts the values of optname to be those defined for the -o option to the set builtin (see The Set Builtin). 
################## nounset = Treat unset variables and parameters other than the special parameters ‘@’ or ‘*’ as an error when performing parameter expansion. An
# error message will be written to the standard error, and a non-interactive shell will exit.
################## The Set Builtin
#This builtin is so complicated that it deserves its own section. set allows you to change the values of shell options and set the positional parameters, or to
#display the names and values of shell variables. 
shopt -s -o nounset

############################################################
#The set -e option instructs bash to immediately exit if any command [1] has a non-zero exit status. You wouldn't want to set this for your command-line shell, 
#but in a script it's massively helpful. In all widely used general-purpose programming languages, an unhandled runtime error - whether that's a thrown exception
#in Java, or #a segmentation fault in C, or a syntax error in Python - immediately halts execution of the program; subsequent lines are not executed.

#set -u affects variables. When set, a reference to any variable you haven't previously defined - with the exceptions of $* and $@ - is an error, and causes the
#program to immediately exit. Languages like Python, C, Java and more all behave the same way, for all sorts of good reasons. One is so typos don't create new
#variables without you realizing it.

#set -o pipefail
#This setting prevents errors in a pipeline from being masked. If any command in a pipeline fails, that return code will be used as the return code of the whole
#pipeline. By default, the pipeline's return code is that of the last command - even if it succeeds. Imagine finding a sorted list of matching lines in a file:

#    % grep some-string /non/existent/file | sort
#    grep: /non/existent/file: No such file or directory
#    % echo $?
#    0
#set -euo pipefail
#set -euo pipefail
#####33 Also use this↓↓↓↓↓↑↑↑↑↑↑↑↑↑↑
#set -euo pipefail
IFS_OLD=$IFS
IFS=$'\n\t'
#↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
#Setting IFS to $'\n\t' means that word splitting will happen only on newlines and tab characters. This very often produces useful splitting behavior. By default, 
#bash sets this to $' \n\t' - space, newline, tab - which is too eager.
#######################↑↑↑↑↑↑↑↑
#

####### Catch signals that could stop the script
trap : SIGINT SIGQUIT SIGTERM
#################################

####################################################### Setup system to send email with your google/gmail account and sendmail ##############################
######################################################## TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO ##############################


# Configuring Gmail as a Sendmail email relay
#
#
#Introduction
#
#In this configuration tutorial we will guide you through the process of configuring sendmail to be an email relay for your gmail or google apps account. 
#This allows #you to send email from your bash scripts, hosted website or from command line using mail command. 
#Other examples where you can utilize this setting is for a #notification purposes such or failed backups etc.
#Sendmail is just one of many utilities which can be configured to rely on gmail account where the others include #postfix, exim , ssmpt etc. 
#In this tutorial we will use Debian and sendmail for this task.
#Install prerequisites
#
## CODE:apt-get install sendmail mailutils sendmail-bin 
#
#Create Gmail Authentication file
#
## CODE:mkdir -m 700 /etc/mail/authinfo/
## CODE:cd /etc/mail/authinfo/
#
#next we need to create an auth file with a following content. File can have any name, in this example the name is gmail-auth:
#
# CODE: printf 'AuthInfo: "U:root" "I:YOUR GMAIL EMAIL ADDRESS" "P:YOUR PASSWORD"\n' > gmail-auth
#
#Replace the above email with your gmail or google apps email.
#
#Please note that in the above password example you need to keep 'P:' as it is not a part of the actual password.
#
#In the next step we will need to create a hash map for the above authentication file:
#
## CODE:makemap hash gmail-auth < gmail-auth
#
#Configure your sendmail 
#
#Put bellow lines into your sendmail.mc configuration file right above first "MAILER" definition line: ######################################################
#
#define(`SMART_HOST',`[smtp.gmail.com]')dnl
#define(`RELAY_MAILER_ARGS', `TCP $h 587')dnl
#define(`ESMTP_MAILER_ARGS', `TCP $h 587')dnl
#define(`confAUTH_OPTIONS', `A p')dnl
#TRUST_AUTH_MECH(`EXTERNAL DIGEST-MD5 CRAM-MD5 LOGIN PLAIN')dnl
#define(`confAUTH_MECHANISMS', `EXTERNAL GSSAPI DIGEST-MD5 CRAM-MD5 LOGIN PLAIN')dnl
#FEATURE(`authinfo',`hash -o /etc/mail/authinfo/gmail-auth.db')dnl
#############################################################################################################################################################
#Do not put the above lines on the top of your sendmail.mc configuration file !
#
#In the next step we will need to re-build sendmail's configuration. To do that execute:
#
## CODE: make -C /etc/mail
#
#Reload sendmail service:
#
# CODE:/etc/init.d/sendmail reload
#
#and you are done.
#Configuration test
#
#Now you can send an email from your command line using mail command:
#
# CODE: echo "Just testing my sendmail gmail relay" | mail -s "Sendmail gmail Relay" "This email address is being protected from spambots."
#

#######################################################3 Trap signals and exit to send email on it #######################################################
#trap 'echo "Subject: Program finsihed execution" | sendmail -v "This email address is being protected from spambots."' exit # It will mail on normal exit
#trap 'echo "Subject: Program interrupted" | /usr/sbin/sendmail -v "This email address is being protected from spambots."' INT HUP
# it will mail on interrupt or hangup  of the process

# redirect all errors to a file                                                                    #### MUNA setja þetta í sshd_config="#HISTAMIN98"
if [ -w /tmp/svaka ]
then
    exec 2>debianConfigVersion5.3__ERRORS__.txt
else
    echo "can't write error file!"
    exit 127
fi
##################################################################################################### TODO exec 3>cpSuccessCodes.txt ## 
#############################################################################################################


SCRIPTNAME=$(basename "$0")

if [ "$UID" != 0 ]
    then
    echo "This program should be run as root, exiting! now....."
    sleep 3
    exit 1
fi

if [ "$#" -eq 0 ]
then
    echo "RUN AS ROOT...Usage if you want to create users:...$SCRIPTNAME USER_1 USER_2 USER_3 etc."
    echo "If you create users they will be set with a semi strong password which you need to change later as root with the passwd command"
    echo
    echo
    echo "#################### ↓↓↓↓↓↓↓↓↓↓↓ OR ↓↓↓↓↓↓↓↓↓↓ #############################"
    echo
    echo
    echo "RUN AS ROOT...Usage without creating users: $SCRIPTNAME"
    echo
    sleep 10

fi

echo "Here starts the party!"
echo "Setting up server..........please wait!!!!!"
sleep 3

### ↓↓↓↓ Initialization of VARIABLES............NEXT TIME USE "declare VARIABLE" ↓↓↓↓↓↓↓↓↓↓ #####
OAUTH_TOKEN=d6637f7ccf109a0171a2f55d21b6ca43ff053616
WORK_DIR=/tmp/svaka
BASHRC=.bashrc
NANORC=.nanorc
BASHRCROOT=.bashrcroot
SOURCE=sources.list
PORT=""

########### Commands
PWD=$(pwd)

#-----------------------------------------------------------------------↓↓
export DEBIAN_FRONTEND=noninteractive
#-----------------------------------------------------------------------↑↑

################ Enter the working directory where all work happens ##########################################
cd "$WORK_DIR" || { echo "cd $WORK_DIR failed"; exit 127; }

############################### make all files writable, executable and readable in the working directory#########
if ! chown -R root:root "$WORK_DIR"
then
    echo "chown WORK_DIR failed"
    exit 127
fi

if ! chmod -R 750 "$WORK_DIR"
then
    echo "chmod WORK_DIR failed"
    exit 127
fi

############################################################## Check if files exist and are writable #########################################

if [[ ! -f "$WORK_DIR"/.bashrc && ! -w "$WORK_DIR"/.bashrc ]]
then
    echo "missing .bashrc file or is not writable.. exiting now....." && { exit 127; }
fi
if [[ ! -f "$WORK_DIR"/.nanorc && ! -w "$WORK_DIR"/.nanorc ]]
then
    echo "missing .nanorc file or is not writable.. exiting now....." && { exit 127; }
fi
    if [[ ! -f "$WORK_DIR"/.bashrcroot && ! -w "$WORK_DIR"/.bashrcroot ]]
then
    echo "missing .bashrcroot file or is not writable..exiting now....." && { exit 127; }
fi
if [[ ! -f "$WORK_DIR"/sources.list && ! -w "$WORK_DIR"/sources.list ]]
then
    echo "missing sources.list file or is not writable..exiting now....." && { exit 127; }
fi

########################################### Check if PORT is set and if sshd_config is set and if PORT is set in iptables ####################
if [[ $PORT == "" ]] && ! grep -q "#HISTAMIN98" /etc/ssh/sshd_config && ! grep -q $PORT /etc/iptables.up.rules 
then
    echo -n "Please select/provide the port-number for ssh in iptables setup or sshd_config file:"
    read -r port ### when using the "-p" option then the value is stored in $REPLY
    PORT=$port
fi

############################ Check internet connection ##############################
checkInternet()
{
    ping -q -w 1 -c 1 `ip r | grep default | cut -d ' ' -f 3` > /dev/null && return 0 || return 1
}

################ Creating new users #####################1

creatingNewUsers()
{
    for name in "$@"
    do
        if id -u "$name" #>/dev/null 2>&1
        then
            echo "User: $name exists....setting up now!"
            sleep 2
        else
            echo "User: $name does not exists....creating now!"            
            useradd -m -s /bin/bash "$name" #>/dev/null 2>&1
            sleep 2
        fi
    done
}

###########################################################################3
################# GET USERS ON THE SYSTEM ###################################

prepare_USERS.txt()
{
    awk -F: '$3 >= 1000 { print $1 }' /etc/passwd > "$WORK_DIR"/USERS.txt

    chmod 750 "$WORK_DIR"/USERS.txt
    if [[ ! -f "$WORK_DIR"/USERS.txt && ! -w "$WORK_DIR"/USERS.txt ]]
    then
        echo "USERS.txt doesn't exist or is not writable..exiting!"
        sleep 3
        exit 127
    fi
#   if [[ ! "$@" == "" ]]
#   then
#        for user in "$@"
#        do
#            echo "$user" >> /tmp/svaka/USERS.txt || { echo "writing to USERS.txt failed"; exit 127; }
#        done
#    fi
}
###########################################################################33
################33 user passwords2
userPasswords()
{
    if [[ ! -f "$WORK_DIR"/USERS.txt && ! -w "$WORK_DIR"/USERS.txt ]]
    then
        echo "USERS.txt doesn't exist or is not writable..exiting!"
        sleep 3
        exit 127
    fi
    while read -r user
    do
        if [ "$user" = root ]
        then
            continue
        fi
        if [[ $(passwd --status "$user" | awk '{print $2}') = NP ]] || [[ $(passwd --status "$user" | awk '{print $2}') = L ]] 
        then
            echo "$user doesn't have a password."
            echo "Changing password for $user:"
            sleep 3
            echo "$user":"$user""YOURSTRONGPASSWORDHERE12345Áá" | /usr/sbin/chpasswd
            if [ "$?" = 0 ]
                then
                echo "Password for user $user changed successfully"
                sleep 3
            fi
        fi
    done < "$WORK_DIR"/USERS.txt
}

################################################ setting up iptables ####################3
setUPiptables()
{
    #if ! grep -e '-A INPUT -p tcp --dport 80 -j ACCEPT' /etc/iptables.test.rules
    if [[ $(/sbin/iptables-save | grep -c '^\-') -gt 0 ]]
    then
        echo "Iptables already set, skipping..........!"
        sleep 2
    else
        if [ "$PORT" = "" ]
        then
            echo "Port not set for iptables, setting now......."
            echo -n "Setting port now, insert portnumber: "
            read -r port
            PORT=$port
        fi
        if [ ! -f /etc/iptables.test.rules ]
        then
            touch /etc/iptables.test.rules
        else
            cat /dev/null > /etc/iptables.test.rules
        fi

        cat << EOT >> /etc/iptables.test.rules
        *filter

        # Allows all loopback (lo0) traffic and drop all traffic to 127/8 that doesn't use lo0
        -A INPUT -i lo -j ACCEPT
        -A INPUT ! -i lo -d 127.0.0.0/8 -j REJECT

        # Accepts all established inbound connections
        -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

        # Allows all outbound traffic
        # You could modify this to only allow certain traffic
        -A OUTPUT -j ACCEPT

        # Allows HTTP and HTTPS connections from anywhere (the normal ports for websites)
        -A INPUT -p tcp --dport 80 -j ACCEPT
        -A INPUT -p tcp --dport 443 -j ACCEPT

        # Allows SSH connections
        # The --dport number is the same as in /etc/ssh/sshd_config
        -A INPUT -p tcp -m state --state NEW --dport $PORT -j ACCEPT

        # Now you should read up on iptables rules and consider whether ssh access
        # for everyone is really desired. Most likely you will only allow access from certain IPs.

        # Allow ping
        #  note that blocking other types of icmp packets is considered a bad idea by some
        #  remove -m icmp --icmp-type 8 from this line to allow all kinds of icmp:
        #  https://security.stackexchange.com/questions/22711
        -A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT

        # log iptables denied calls (access via dmesg command)
        -A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables denied: " --log-level 7

        # Reject all other inbound - default deny unless explicitly allowed policy:
        -A INPUT -j REJECT
        -A FORWARD -j REJECT

        COMMIT
EOT
        sed "s/^[ \t]*//" -i /etc/iptables.test.rules ## remove tabs and spaces
        /sbin/iptables-restore < /etc/iptables.test.rules || { echo "iptables-restore failed"; exit 127; }
        /sbin/iptables-save > /etc/iptables.up.rules || { echo "iptables-save failed"; exit 127; }
        printf "#!/bin/bash\n/sbin/iptables-restore < /etc/iptables.up.rules" > /etc/network/if-pre-up.d/iptables ## create a script to run iptables on startup
        chmod +x /etc/network/if-pre-up.d/iptables || { echo "chmod +x failed"; exit 127; }
    fi
}

###################################################33 sshd_config4
setUPsshd()
{
    if grep "Port $PORT" /etc/ssh/sshd_config
    then
        echo "sshd already set, skipping!"
        sleep 3
    else

        if [ "$PORT" = "" ]
        then
            echo "Port not set"
            sleep 3
            exit 12
        fi
        users=""
        /bin/cp -f "$WORK_DIR"/sshd_config /etc/ssh/sshd_config
        sed -i "s/Port 22300/Port $PORT/" /etc/ssh/sshd_config
        for user in $(awk -F: '$3 >= 1000 { print $1 }' /etc/passwd)
        do
            users+="${user} "
        done
        if grep "AllowUsers" /etc/ssh/sshd_config
        then
            sed -i "/AllowUsers/c\AllowUsers $users" /etc/ssh/sshd_config
        else
            sed -i "6 a \
            AllowUsers $users" /etc/ssh/sshd_config
        fi

        chmod 644 /etc/ssh/sshd_config
        /etc/init.d/ssh restart
    fi
}

#################################################3333 Remove or comment out DVD/cd line from sources.list5
editSources()
{
    if grep '^# *deb cdrom:\[Debian' /etc/apt/sources.list
    then
        echo "cd already commented out, skipping!"
    else
        sed -i '/deb cdrom:\[Debian GNU\/Linux/s/^/#/' /etc/apt/sources.list
    fi
}

####################################################33 update system6

updateSystem()
{
    apt update && apt upgrade -y
}


###############################################################7
############################# check if programs installed and/or install
checkPrograms()
{
    if [ ! -x /usr/bin/git ] && [ ! -x /usr/bin/wget ] && [ ! -x /usr/bin/curl ] && [ ! -x /usr/bin/gcc ] && [ ! -x /usr/bin/make ]
    then
        echo "Some tools with which to work with data not found installing now......................"
        sleep 2
        apt install -y git wget curl gcc make
    fi
}

#####################################################3 update sources.list and install software ############################################################
updateSources_installSoftware()
{
    if grep "deb http://www.deb-multimedia.org" /etc/apt/sources.list
    then
        echo "Sources are setup already, skipping!"
    else
        /bin/cp -f "$WORK_DIR"/"$SOURCE" /etc/apt/sources.list || { echo "cp failed"; exit 127; }
        chmod 644 /etc/apt/sources.list
        wget http://www.deb-multimedia.org/pool/main/d/deb-multimedia-keyring/deb-multimedia-keyring_2016.8.1_all.deb || { echo "wget failed"; exit 127; }
        dpkg -i deb-multimedia-keyring_2016.8.1_all.deb
        wget -q https://www.virtualbox.org/download/oracle_vbox_2016.asc -O- | sudo apt-key add -
        updateSystem || { echo "update system failed"; exit 127; }
        apt install -y vlc vlc-data browser-plugin-vlc mplayer youtube-dl libdvdcss2 libdvdnav4 libdvdread4 smplayer mencoder build-essential \
        gstreamer1.0-libav gstreamer1.0-plugins-bad gstreamer1.0-vaapi lame libfaac0 aacskeys libbdplus0 libbluray1 audacious audacious-plugins \
        deadbeef kodi audacity cinelerra handbrake-gtk ffmpeg amarok k3b || { echo "some software failed to install!!!!!"; echo "some software failed to install"; \
        sleep 10; }
        ########################## Install flash in Mozilla Firefox ############################################
        wget https://raw.githubusercontent.com/cybernova/fireflashupdate/master/fireflashupdate.sh || { echo "wget flash failed"; sleep 4; exit 127; }
        chmod +x fireflashupdate.sh || { echo "chmod flash failed"; sleep 4; exit 127; }
        ./fireflashupdate.sh
        ######################### Setup the update tool to update flash weekly ###################################3
        chown root:root fireflashupdate.sh || { echo "chown flash failed"; sleep 4; exit 127; }
        /bin/mv fireflashupdate.sh /etc/cron.weekly/fireflashupdate || { echo "mv flash script failed"; sleep 4; exit 127; }

    fi
}

###############################################33  SETUP PORTSENTRY ############################################################
##############################################3                     ############################################################33

setup_portsentry()
{
    if  ! grep -q '^TCP_PORTS="1,7,9,11,15,70,79' /etc/portsentry/portsentry.conf
    then
        if [[ -f /etc/portsentry/portsentry.conf ]]
        then
            /bin/mv /etc/portsentry/portsentry.conf /etc/portsentry/portsentry.old
        fi
        if [[ ! -x /usr/sbin/portsentry ]]
        then
            apt install -y portsentry logcheck
            /bin/cp -f "$WORK_DIR"/portsentry.conf /etc/portsentry/portsentry.conf || { echo "cp portsentry failed"; exit 127; }
            /usr/sbin/service portsentry restart || { echo "service portsentry restart failed"; exit 127; }
        fi
    fi
}

################################### Successful exit then this cleanup ###########################################################3

successfulExit()
{
    IFS=$IFS_OLD
    cd "$HOME" || { echo "cd $HOME failed"; exit 155; }
    rm -rf /tmp/svaka || { echo "Failed to remove the install directory!!!!!!!!"; exit 155; }
}
###############################################################################################################################33
####### Catch the program on successful exit and cleanup
trap successfulExit EXIT
#####################################################3 run methods here↓   ###################################################3
#####################################################                      ###################################################
checkInternet || (echo "no network, bye" && exit 199)
if [[ ! "$*" == "" ]]
then
    creatingNewUsers "$@"
fi
prepare_USERS.txt
userPasswords
setUPiptables
setUPsshd
editSources
updateSystem
#setup_portsentry    ######3 NEEDS WORK ##################################
checkPrograms
updateSources_installSoftware
###########################################################################################################            #####3##
##############################################################################################################3Methods
##########################################3 Disable login for www-data #########
passwd -l www-data
#################################### firmware
apt install -y firmware-linux-nonfree firmware-linux
apt install -y firmware-linux-free intel-microcode
sleep 3
################ NANO SYNTAX-HIGHLIGHTING #####################3
if [ ! -d "$WORK_DIR"/nanorc  ]
then
    if [ "$UID" != 0 ]
    then
        echo "This program should be run as root, goodbye!"
        exit 127

    else
        echo "Setting up Nanorc file for all users....please, wait!"
        if [[ $PWD == "$WORK_DIR" ]]
        then
            echo "Program is in WORK_DIR...success!......."
        else
            echo "not in WORK_DIR...TRYING 'cd WORK_DIR'"
            cd "$WORK_DIR" || { echo "cd failed"; exit 127; }
        fi
        git clone https://$OAUTH_TOKEN:x-auth-basic@github.com/gnihtemoSgnihtemos/nanorc || { echo "git in Nano SYNTAX-HIGHLIGHTING failed"; exit 127; }
        chmod 755 "$WORK_DIR"/nanorc || { echo "chmod in Nano SYNTAX-HIGHLIGHTING failed"; exit 127; }
        cd "$WORK_DIR"/nanorc || { echo "cd in Nano SYNTAX-HIGHLIGHTING failed"; exit 127; }
        make install-global || { echo "make in Nano SYNTAX-HIGHLIGHTING failed"; exit 127; }
        /bin/cp -f "$WORK_DIR/$NANORC" /etc/nanorc || { echo "cp in Nano SYNTAX-HIGHLIGHTING failed"; exit 127; }
        chown root:root /etc/nanorc || { echo "chown in Nano SYNTAX-HIGHLIGHTING failed"; exit 127; }
        chmod 644 /etc/nanorc || { echo "chmod in Nano SYNTAX-HIGHLIGHTING failed"; exit 127; }
        if [ "$?" = 0 ]
        then
            echo "Implementing a custom nanorc file succeeded!"
        else
            echo "Nano setup DID NOT SUCCEED!"
            exit 127
        fi
        echo "Finished setting up nano!"
    fi
fi

################ LS_COLORS SETTINGS and bashrc file for all users #############################
if ! grep 'eval $(dircolors -b $HOME/.dircolors)' /root/.bashrc
then
    echo "Setting root bashrc file....please wait!!!!"
    if /bin/cp -f "$WORK_DIR/$BASHRCROOT" "$HOME"/.bashrc
    then
        echo "Root bashrc copy succeeded!"
        sleep 2
    else
        echo "Root bashrc cp failed, exiting now!"
        exit 127
    fi
    chown root:root "$HOME/.bashrc" || { echo "chown failed"; exit 127; }
    chmod 644 "$HOME/.bashrc" || { echo "failed to chmod"; exit 127; }
    wget https://raw.github.com/trapd00r/LS_COLORS/master/LS_COLORS -O "$HOME"/.dircolors || { echo "wget failed"; exit 127; }
    echo 'eval $(dircolors -b $HOME/.dircolors)' >> "$HOME"/.bashrc || { echo "echo 'eval...dircolors -b'....to bashrc failed"; exit 127; }
fi
while read -r user
do
    if [ "$user" = root ]
    then
        continue
    fi

    sudo -i -u "$user" user="$user" WORK_DIR="$WORK_DIR" BASHRC="$BASHRC" bash <<'EOF'
    if grep 'eval $(dircolors -b $HOME/.dircolors)' "$HOME"/.bashrc
    then
        :
    else
        echo "Setting users=Bashrc files!"
        if /bin/cp -f "$WORK_DIR"/"$BASHRC" "$HOME/.bashrc"
        then
            echo "Copy for $user (bashrc) succeeded!"
            sleep 2
        else
            echo "Couldn't cp .bashrc for user $user"
            exit 127
        fi
        chown $user:$user "$HOME/.bashrc" || { echo "chown failed"; exit 127; }
        chmod 644 "$HOME/.bashrc" || { echo "chmod failed"; exit 127; }
        wget https://raw.github.com/trapd00r/LS_COLORS/master/LS_COLORS -O "$HOME"/.dircolors || { echo "wget failed"; exit 127; }
        echo 'eval $(dircolors -b $HOME/.dircolors)' >> "$HOME"/.bashrc
    fi
EOF
done < "$WORK_DIR"/USERS.txt

echo "Finished setting up your system!"
sleep 2
############ Give control back to these signals
trap SIGINT SIGQUIT SIGTERM
############################

exit 0

Here is the same program under development posted here 2 times in the past:

Bash script to setup new Debian installs.......from 7 months ago

Bash program that sets up and configures the environment for new Debian installs........from 5 months ago

\$\endgroup\$
  • 3
    \$\begingroup\$ Hi! I noticed in your user profile that you are interested in advanced system administration. Have you considered doing these setup tasks "properly" using industry-standard tools like Ansible instead of developing your own standalone scripts? \$\endgroup\$ – 200_success Apr 13 at 13:10
  • \$\begingroup\$ @200_success Hi, yes I'm interested in those but it serves me for learning programming to do it my self and that's why I haven't investigated those. I'm learning bash/shell scripting and python now, and plan on learning C and C++ later \$\endgroup\$ – somethingSomething Apr 13 at 13:19
  • 1
    \$\begingroup\$ Check out redsymbol.net/articles/unofficial-bash-strict-mode and shellcheck.net for some modern bash best practices. \$\endgroup\$ – chicks Apr 13 at 13:42
  • 1
    \$\begingroup\$ Hint on iptables integration, look into systemd for automation. Also specific to your code, consider turning individual bits into scripts; it'll make setting expectations and testing far easier. Furthermore look into source and how it can break out portions of scripted logic into more manageable chunks. Some things do not need a wrapper, specifically updateSystem is unnecessary and dangerous. Finally I'd advise looking into a good IDE, vim and Atom are my favs, but there's plenty of good text editors that'll make your life way easier. \$\endgroup\$ – S0AndS0 Apr 14 at 5:25
  • 1
    \$\begingroup\$ Excellent @somethingSomething! So aside from the Git pane, if ya look at the lower bar of your Atom window (on the right side), you'll find some other Git related short-cuts, eg. branch selector. On the subject of both Bash and administration ya may want to peruse the source code of a project I've recently published for Jekyll Administration, that also includes some swell client scripts; it's designed to be a set of ssh short-cuts for the most part. Though I mainly suggest it because there's lot's of Bash tricks that ya might like. \$\endgroup\$ – S0AndS0 Apr 19 at 5:24
2
\$\begingroup\$

Got a snack and drink? Probably a good idea to get comfy too because it's going to be another one of those answers.

Before diving in though understand that I'm sharing what I know and think will be helpful, there's defiantly more clever people out there with Bash, and perhaps those that are will be kind enough to let us all know if any of the following would lead readers astray.


"Could you add some features to the program that are practical, convenient or cool for setting up new installs?"

Yes, however I don't think either of us would want to look at it after...


Addressing some of your list;

  • Abandon customizing nano and instead focus on vim, be very targeted about plugins and customizing, and try to focus on learning the built in goodies to tweak. For example vimdiff as a merge tool for git is super handy for remote (or local) merge conflict resolution, and vim-gpg allows for editing encrypted files in-place. Combined with either screen or tmux and it's possible to have a similar (or better) experience as what IDEs like Atom provides.

  • iptables and ssh setup is something that should be handled by separate scripts at the very least, and I'll reiterate that systemd combined with iptables is an avenue worth exploring... may be in the future I'll push a related project, when it's ready...

    • Additionally on iptables, look into chains, and insert to keep things organized and in somewhat predictable order respectively. Also check into fail2ban for an easy way of mitigating some forms of hacktackory, it also has templating options for automating changes in rules based off various logged events.
  • Careful, setting-up custom root account bashrc files, especially for a shared server this can cause strangeness in terminal behavior, which can strain team relations, that strain could lead to anger, and Yoda was very clear to where such emotions leads.

  • Tweaking default color output for ls and just about any other core utility is a very, very bad idea... well unless tracking down the legacy code underpinning something server critical that barfed is your idea of a good time... Hint, if it's in the Busybox Bash shell avoid touching the defaults on any distribution. If you must, for some reason, then please use uniquely named aliases instead... as for why, detecting reliably when something is being piped or redirected or displayed to a terminal that can handle it is a special flavor of mysticism I'll not encourage publicly; not for this use-case...

  • Creating new users, set-up, passwords, etc. is something that yet again should be handled by a separate set of scripts. Additionally be careful as to who you assign a password to, could allow for a larger pool of users to brute-force passwords for, and some users should never have a password but instead be locked from these types of authentication.

  • Installation of non-free software in an automated fashion may be asking for trouble, I'm no expert but such habits will be very tough to maintain in a corporate setting. In other-words be really nice to the Waver Department for whatever company lets ya sudo onto their servers.

  • I've already touched on that auto upgrading a system is dangerous, but if you must then consider using script so that issues can later be tracked down with the use of strings, eg...

mkdir -p ~/Documents/logs/script

_a_moment_in_time="$(date +'%Y%m%d-%H%M%S')"
_log_path="${HOME}/Documents/logs/script/${_a_moment_in_time}-apt-get_upgrade.script"

script -ac 'sudo apt-get upgrade && sudo apt-get upgrade -y' "${_log_path}"

... to output a script log file to something like ~/Documents/logs/script/19701231-2359-apt-get_upgrade.script, which can be reviewed in-terminal with something like...

strings "${_log_path}" | more

"Is there anything in the program that I don't need?"

Yes.

"I was hoping for some advice and/or input on the code."

  • Split out things into separate projects, and within those projects split things out into smaller files of functions that contain reusable code snipits; your future self will be more likely to thank your past self.

  • Comments should be comments, documentation should either be accessible via some state within the code or be kept in a different place; Python is a good example of where it's encouraged to have documentation within the code but that's because print(str.__doc__).

  • There's some unnecessary redundant repetition in your code that might be better expressed as a collection of functions that abstract things a bit in that regard. On the subject of functions, some of your names are going to cause ya headaches in the future, I'm looking at prepare_USERS.txt as one that'll one day misbehave.

  • There's lot's of zombie code and characters that cannot be typed on a keyboard, consider removing the latter and editing the former to something either with; conditional logic and configs, or removing unnecessary commented code.

  • Functions and scripts can take a list of arguments, functions can also be passed references to lists (arrays) among other things via local -n _ref_the_first="${1}", which hint, hint, is magic sauce for that project's arg_parser that I linked to in your question's comments; enables passing more than one array ;-)

  • Definitely look into shellcheck as it'll catch typos like if [ "$PORT" = "" ] in your code, output the line number to jump to to fix'em, as well as a message that may or may not be helpful at the time.

  • On the subject of help related things, I'll encourage you to try man, eg man man, man iptables, etc. The documentation built-in on various subjects is extensive. The other handy command I've run across for finding documentation is apropos, eg. apropos iptables

  • traping some of the things you have is asking for trouble in some very bad ways, and other traps are going to be really painful to debug in the future, consider using some bits that my project does to aid debugging instead, eg...

shared_functions/failure.sh

Note, source code within links may be updated or slightly different in formatting, if this is by to much at some later date then roll-back changes to commit ID: 5d307b353fc83cfc3d51461a064e72cdb8f4176a; currently shared under GNU AGPL version 3.

#!/usr/bin/env bash

## Ensure the following line precedes calls to this function via trap
##    set -eE -o functrace
## Use the following line to call this function from other scripts
##    trap 'failure ${LINENO} "${BASH_COMMAND}"' ERR
failure(){
    local _lineno="${1}"
    local _msg="${2}"
    local _code="${3:-0}"
    local _msg_height="$(wc -l <<<"${_msg}")"
    if [ "${_msg_height}" -gt '1' ]; then
        printf 'Line: %s\n%s\n' "${_lineno}" "${_msg}"
    else
        printf 'Line: %s - %s\n' "${_lineno}" "${_msg}"
    fi
    if ((_code)); then exit ${_code}; fi
    return ${_code}
}

errors_tests.sh pulled from jekyll_usermod.sh

Trimmed a bit to keep things a bit targeted in topics being covered.

#!/usr/bin/env bash

## Exit if not running with root/level permissions
if [[ "${EUID}" != '0' ]]; then
  echo "Try: sudo ${0##*/} ${@:---help}"
  exit 1
fi

set -eE -o functrace


#
#    Set defaults for script variables; these maybe overwritten at run-time
#
## ... trimmed for brevity...

#
#    Boilerplate and script scoped defaults
#
## Find true directory script resides in, true name, and true path
__SOURCE__="${BASH_SOURCE[0]}"
while [[ -h "${__SOURCE__}" ]]; do
    __SOURCE__="$(find "${__SOURCE__}" -type l -ls | sed -n 's@^.* -> \(.*\)@\1@p')"
done
__DIR__="$(cd -P "$(dirname "${__SOURCE__}")" && pwd)"
__NAME__="${__SOURCE__##*/}"
__PATH__="${__DIR__}/${__NAME__}"

__AUTHOR__='S0AndS0'
__DESCRIPTION__='Spams error codes for trap example'


#
#    Source useful functions, note may overwrite previous defaults
#
## Provides     'failure'
source "${__DIR__}/shared_functions/failure.sh"
trap 'failure ${LINENO} "${BASH_COMMAND}"' ERR

## ... sourcing of other things, setup and
##     code logic/execution follows for now
##     generate some errors...

usage(){
    cat <<EOF
${__NAME__} 'SPAM'

${__DESCRIPTION__}


--help
    Print this message and exit

spam
    Returns two different error codes, repetition dependent

ham
    Returns error code '3'


# (C) GNU AGPL version 3, 2019, ${__AUTHOR__}
EOF
}

spam_errors(){
    _first_arg="${1,,}"
    case "${_first_arg}" in
        'spam')
            _scrubbed_first_arg="${_first_arg//${_first_arg/%spam/}/ }"
            printf '%s\n' "${_scrubbed_first_arg}" >&2
            return 1
        ;;
        'spam'*'spam')
            _scrubbed_first_arg="${_first_arg//${_first_arg//spam/}/ }"
            printf '%s\n' "${_scrubbed_first_arg}" >&2
            return 2
        ;;
        *'ham'*)
            printf '%s\n' "${_first_arg}" >&2
            return 3
        ;;
        *'help')
            usage
            exit 0
        ;;
    esac
    return 0
}


#
# Do the things...
#
_first_arg="${1:?${__NAME__} requires one argument, eg. ${__NAME__} 'spam'}"
spam_errors "${_first_arg}"
printf 'Escaped argument: %q\n' "${_first_arg}"

Note, spam_errors only exists for the purpose of showing multiple error generating lines as well as what happens when no errors are thrown.

... the gist of which prints out information that helps in tracking down errors. Here's some example input/output...

bash errors_tests.sh 'spam spam'; echo "# Exit Status: $?"
Try: sudo errors_tests.sh spam spam
# Exit Status: 1

sudo bash errors_tests.sh
echo "# Exit Status: $?"
errors_tests.sh: line 71: 1: errors_tests.sh requires one argument, eg. errors_tests.sh spam
# Exit Status: 1

sudo bash errors_tests.sh 'spam'; echo "# Exit Status: $?"
spam
Line: 52 - return 1
# Exit Status: 1

sudo bash errors_tests.sh 'spam ham spam'
_last_exit_status="$?"
echo "# Exit Status: ${_last_exit_status:-0}"
spam spam
Line: 57 - return 2
# Exit Status: 2

sudo bash errors_tests.sh 'ham'
_last_exit_status="${?:-0}"
echo "# Exit Status: ${_last_exit_status}"
ham
Line: 62 - return 3
# Exit Status: 3

sudo bash errors_tests.sh 'lemon jam'
_last_exit_status="$?"
echo "# Exit Status: ${_last_exit_status:-0}"
Escaped argument: lemon\ jam
# Exit Status: 0

Now there's other things going on up there with them examples; string manipulation, fancy source usage based-off something I couldn't unsee from elsewhere on the SO network, all sorts of perversions with parameter substitutions; but the main take-away to take away is that I'm using return n > 0 within functions to trip the trap 'failure ${LINENO} "${BASH_COMMAND}"' ERR, after setting set -eE -o functrace for more info on premature exits. Other things I think may be worth noting;

  • In some implementations of Bash each pipe can be very costly, so try to get things done with built-ins where-ever possible.

  • Regardless of language, erring with info is a solid choice.

  • echo is okay to use in the privacy of one's own terminal, and occasionally out of laziness when no variables are involved within a script, otherwise printf is generally the better choice.

  • That trick within the usage function you might see elsewhere with -EOF which dedents tabs (\t), or 'EOF' to forbid expansion of variables ($var) and sub-shells ($(ls some-dir)); the latter is groovy to use when you don't want to escape (pre'ppend \) a bunch of examples, where as the former should be avoided to keep code accessible.

I could probably go-on, maybe I will in a future update, but for now I think this is a good pausing point to digest some of the above.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.