Not So Typical Jumpstart: Part Three - The Scripts
In part three of the series, we ended with a working Custom Jumpstart setup, but you most certainly will be left wanting to do more. That's where the begin and finish scripts come into play -- anything you can do via /bin/sh you can do via Jumpstart. Up until now, the series has been more of a "Typical Jumpstart" tutorial -- this post is where this series of posts earns it's name. Read more for about 200 lines of shell-scripting goodness.
Simple Begin Script: delete_raidctl_vols
As noted in part two, we specified a begin script named delete_raidctl_vols. This script will look for any existing volumes that raidctl can see, and delete them. Obviously, this is a relatively dangerous script, so be careful:
cd /export/home/jumpstart/configs/
cat <<EOD > delete_raidctl_vols
#!/bin/sh
VOLUMES=`/usr/sbin/raidctl | grep '^c' | awk '{ print $1 }'`
if [ -n "$VOLUMES" ]; then
for v in $VOLUMES; do
echo "Deleting volume $v"
/usr/sbin/raidctl -f -d $v
done
fi
EOD
chmod 755 delete_raidctl_vols
Now you have an example of what can be done. Let's get more in-depth!
Tips for Writing a Complex Finish Script
When writing your own finish scripts, keep these tips in mind:
- Keep things modular - when possible, create separate scripts that you call from your primary finish script, passing arguments into it. This makes your code reusable between different Jumpstart installations. In our example, we put the separate scripts in /export/home/jumpstart/configs/scripts/.
- When your finish script is being run, it is being run from within the miniroot with your actual system mounted under /a. This is the biggest cause of mistakes, and they are often hard to find the cause of. For example, if you run the command 'echo "nameserver 1.2.3.4" > /etc/resolv.conf' from your finish script, it will succeed. However, you are actually modifying a file that is stored in RAM which will be lost upon reboot. What you actually want to run from your script is 'echo "nameserver 1.2.3.4" > /a/etc/resolv.conf'
- Your config server root directory and it's subdirectories are mounted and available at the location $SI_CONFIG_DIR. This is a key environment variable that allows you a lot of flexibility.
- To make packages and scripts that aren't aware of an alternate root work properly, use chroot. Most all Sun packages can be installed using 'pkgadd -R /a', but some homegrown packages and quite possibly some of your own scripts all assume that the root directory is at '/'. You can use the chroot command (examples below) to make these scripts and packages work without modification.
- /etc/mnttab and the /proc filesystem aren't visible to chroot unless you make them available. I'll show you how to do this later.
- Use an admin file to install packages without prompting. Examples will be given below.
- Fire off a shell as the last step in your finish script to prevent rebooting after installation. This tip saved me huge amounts of time. The whole point of Jumpstart is that it is automated -- so it reboots the machine when done. At that point, you no longer have access to the miniroot environment. By firing off a shell as your last command in a finish script, you get dropped into the miniroot interactively, where you can look around and see what has worked correctly and why certain things are failing.
- Use install_debug as a last resort. When you boot to the installation, you normally use 'boot net:dhcp - install'. If you change install to 'install_debug', then all shell scripts will be executed with -x. This generates a huge amount of verbose output, but at times it helps in your troubleshooting.
With all that out of the way, here is my webserver_finish script from start to finish:
- #!/bin/sh
- BASE=/a
- export BASE
- MNT=${BASE}/mnt
- export MNT
- ADMIN_FILE=/a/tmp/admin
- export ADMIN_FILE
- SVNGET="${BASE}/usr/sfw/bin/wget -q --no-check-certificate --http-user=xxx \
- --http-password=yyy https%3A%2F%2Fsvn.domain.com/repos/"
- export SVNGET
- #Setup a working mnttab in the /a root
- /sbin/mount -F mntfs mnttab ${BASE}/etc/mnttab
- #Setup a working proc in the /a root
- mount -F lofs /proc ${BASE}/proc
- # Add static routes to appservers that don't go through the lb
- echo "Setting up static routes"
- for i in 12 13 15 23; do
- echo "192.168.1.${i} 192.168.0.1" >> ${BASE}/etc/inet/static_routes
- done
- # Create defaultdomain
- echo "Setting /etc/defaultdomain"
- ${SI_CONFIG_DIR}/scripts/create_defaultdomain.sh
- #Setup /etc/hosts
- echo "Setting /etc/hosts"
- ${SI_CONFIG_DIR}/scripts/create_etc_hosts.sh loghost
- # Setup sendmail aliases
- echo "Setting up Sendmail"
- ${SI_CONFIG_DIR}/scripts/setup_sendmail.sh
- # Setup NTP
- echo "Setting up NTP"
- ${SI_CONFIG_DIR}/scripts/setup_ntp.sh prod
- #Setup /apps dataset
- echo "Setting up /apps and /apps/apache2.2/logs datasets"
- zfs create -o mountpoint=/apps -o atime=off rpool/apps
- zfs create -o mountpoint=/apps/apache2.2/logs -o atime=off rpool/apachelogs
- mkdir /a/apps/customscripts
- #Set quota on home dirs
- zfs set quota=2G rpool/export/home
- #Install webstack
- echo "Installing Glassfish Webstack"
- ${SI_CONFIG_DIR}/scripts/setup_webstack.sh
- #Install companion cd packages
- echo "Installing Companion CD Packages"
- ${SI_CONFIG_DIR}/scripts/install_comp_cd_pkgs.sh SFWrsync SFWcoreu \
- SFWcurl SFWtop
- #Put misbehaving packages in this loop to install via chroot
- for p in ESUscponly; do
- chroot /a /usr/sbin/pkgadd -n -a /tmp/admin \
- -d /mnt/Solaris_sparc/Packages ${p} > /dev/null
- done
- #Build symlinks to certain binaries
- cd ${BASE}/usr/bin/
- for b in rsync top; do
- ln -s ../../opt/sfw/bin/${b} ${b}
- done
- #Install Blastwave support
- echo "Installing Blastwave support"
- ${SI_CONFIG_DIR}/scripts/install_blastwave.sh
- #Install NRPE
- echo "Installing NRPE from Blastwave"
- #${SI_CONFIG_DIR}/scripts/pkgutil_install.sh nrpe
- #Setup SNMPd
- #Setup Apache Log Rotation
- echo "Configuring Apache Log Rotation"
- chroot ${BASE} /usr/sbin/logadm -w Apache22Logs -p1d -C4 \
- -c -t '/apps/apache2.2/logs/archives/$basename.$N.gz' '/apps/apache2.2/logs/*.log'
- echo "Creating \"WebServer Admin\" RBAC Profile"
- AUTHS="solaris.smf.manage.http/sun-apache22,solaris.smf.value.http/sun-apache22"
- echo "WebServer Admin:::Manage Apache and MediaClient:auths=$AUTHS" \
- >> /a/etc/security/prof_attr
- #Setup ndd SMF
- echo "Downloading and installing ndd service"
- ${SI_CONFIG_DIR}/scripts/setup_ndd.sh webserver
- #Setup /etc/system
- echo "Adding entries to /etc/system"
- echo "set sq_max_size=0" >> /a/etc/system
- echo "set rlim_fd_max=2000000" >> /a/etc/system
- echo "set rlim_fd_cur=2000000" >> /a/etc/system
- echo "set maxphys=1048576" >> /a/etc/system
- #Install pca
- echo "Installing Patch Check Advanced and installing all missing/recommended patches"
- ${SI_CONFIG_DIR}/scripts/install_pca.sh
- #Install site.xml bundle
- cp ${SI_CONFIG_DIR}/webserver-site.xml /a/var/svc/profile/site.xml
- #Setup Users, SSH Keys
- echo "Setting up users"
- for u in user1 user2 user3; do
- # Add to webservd group
- # Install ~/.ssh/authorized_keys
- # Generate a random password
- chroot /a /usr/sbin/useradd -P "WebServer Admin" -G webservd -d /export/home/$u -m $u
- chroot /a /usr/bin/passwd -N $u > /dev/null
- mkdir /a/export/home/$u/.ssh/
- touch /a/export/home/$u/.ssh/authorized_keys
- if [ -f ${SI_CONFIG_DIR}/sshkeys/${u}/sshpubkey.txt ]; then
- cp ${SI_CONFIG_DIR}/sshkeys/$u/sshpubkey.txt /a/export/home/$u/.ssh/authorized_keys
- fi
- chmod 700 /a/export/home/$u/.ssh/
- chroot /a /usr/bin/chown -R $u /export/home/$u/.ssh
- done
- #Setup a weekly zpool scrub
- echo "Setting up weekly zpool scrub"
- ${SI_CONFIG_DIR}/scripts/setup_weekly_zpool_scrub.sh
- ${SI_CONFIG_DIR}/scripts/noreboot.sh
Breaking it all down
Lines 1 through 10: Variable Initialization
Here we're just doing some variable initialization. One cool trick I came up with was to install wget into the target system, then use that binary to fetch files out of Subversion.
Lines 13 through 17: Setup mnttab and proc for chroot
In the tips and tricks, I mentioned you might need to do this. I recommend doing it all the time, I haven't found a case where it messes with anything yet.
Lines 19 through 23: Setting Static Routes
Here's how you can set persistent static routes in Solaris 10.
Lines 25 through 27: Creating /etc/defaultdomain
Here is the first example of calling out to a separate script from our finish script. This subscript determines the current domainname and sticks that in /a/etc/defaultdomain. Here's the contents of that script:
- #!/bin/sh
- DN=`grep domain /a/etc/resolv.conf | awk '{ print $2 }'`
- if [ -n "$DN" ]; then
- echo $DN > /a/etc/defaultdomain
- /usr/bin/domainname $DN
- fi
Lines 29 through 31: Setting up /etc/hosts
The use of DHCP for jumpstart can do funny things to the /etc/hosts file, so I just override in in my finish script. Here's our first example of a subscript that takes arguments. The script determines the current hostname and FQDN, and creates aliases for any arguments. Here's scripts/create_etc_hosts.sh:
- #!/bin/sh
- ALIASES=$*
- HOSTNAME=`/usr/bin/hostname`
- DOMAINNAME=`/usr/bin/domainname`
- FQDN="${HOSTNAME}.${DOMAINNAME}"
- IP=`getent hosts $FQDN | awk '{ print $1 }' | head -1`
- cat <<EOD > /a/etc/inet/hosts
- #
- # Internet host table
- #
- ::1 localhost
- 127.0.0.1 localhost
- EOD
- echo "${IP} ${HOSTNAME} ${FQDN} ${ALIASES}" >> /a/etc/hosts
Lines 33 through 35: Setup mail aliases and smarthost configuration
This script echoes a few custom mail aliases to /etc/mail/aliases, and then runs a perl one-liner that sets up sendmail to use the smarthost with a hostname of 'smarthost':
- #!/bin/sh
- cat <<EOD >> /a/etc/mail/aliases
- user1:root
- user2:root
- root:admin@my.com
- EOD
- perl -pi -e 's/^DS/DSmailhost/' /a/etc/mail/sendmail.cf
Lines 37 through 39: Setup NTP
Here I call a script and pass it a parameter of whether the server is in production or not, and use the appropriate NTP servers based on the value of the first argument:
- #!/bin/sh
- case $1 in
- "prod")
- cat <<EOD > /a/etc/inet/ntp.conf
- server 192.168.1.1
- server 10.0.0.7
- server us.pool.ntp.org
- driftfile /var/ntp/ntp.drift
- EOD
- ;;
- *)
- cat <<EOD > /a/etc/inet/ntp.conf
- server 10.0.0.7
- server 192.168.1.1
- server us.pool.ntp.org
- driftfile /var/ntp/ntp.drift
- EOD
- ;;
- esac
Lines 41 through 48: Create extra ZFS datasets, set a quota, and make some directories.
While the jumpstart profile only allows you to create / and /var ZFS datasets, you can create whatever you want using a finish script. We also set a quota on the /export/home dataset.
Lines 50 through 52: Install Sun Glassfish Webstack
Here's our first example of chroot. It's also our first use of the $SI_CONFIG_DIR variable. I have a directory named /export/home/jumpstart/configs/webstack that contains the latest Glassfish Webstack release. I copy that directory over to /var/tmp and then run the installation routine. Finally, I add the RBAC auths for controlling the Webstack Apache to /etc/security/auth_attr:
- #!/bin/sh
- cp -RPp ${SI_CONFIG_DIR}/webstack ${BASE}/var/tmp/
- chroot ${BASE} /var/tmp/webstack/install apache && \
- rm -rf ${BASE}/var/tmp/webstack
- echo "Adding RBAC Authorizations to manage Apache"
- echo "solaris.smf.manage.http/sun-apache22:::Apache2.2 Server management::" \
- >> ${BASE}/etc/security/auth_attr
- echo "solaris.smf.value.http/sun-apache22:::Apache2.2 Server SMF property management::" \
- >> ${BASE}/etc/security/auth_attr
Lines 55 through 64: Installing Companion CD and 3rd Party Packages
I copied over the contents of the Companion CD into /export/home/jumpstart/install/s10u7_companion_cd on a NFS server. This script mounts that directory, creates a package admin file that disables all prompting, and then installs all packages specified as arguments to the script:
- #!/bin/sh
- PKGS=$*
- mount -f nfs nfsserver.mydomain.com:/export/home/jumpstart/install/s10u7_companion_cd ${MNT}
- cat >${ADMIN_FILE} <<DONT_ASK
- mail=
- instance=overwrite
- partial=nocheck
- runlevel=nocheck
- idepend=nocheck
- rdepend=nocheck
- space=nocheck
- setuid=nocheck
- conflict=nocheck
- action=nocheck
- basedir=default
- DONT_ASK
- for p in $PKGS; do
- /usr/sbin/pkgadd -n -a ${ADMIN_FILE} -d ${MNT}/Solaris_sparc/Packages -R ${BASE} ${p} > /dev/null
- done
Lines 66 through 70: Setup a few symlinks
Here's a simple for-do loop that creates some symlinks in /usr/bin that point to their counterparts in /opt/sfw/bin.
Lines 72 through 74: Install pkgutil from Blastwave
This script uses wget to fetch the latest pkgutil from Blastwave, installs it, and updates the catalog:
- #!/bin/sh
- cd /a/tmp
- ${BASE}/usr/sfw/bin/wget -q <a href="http://blastwave.network.com/csw/pkgutil_sparc.pkg<br />
- /usr/sbin/pkgadd" title="http://blastwave.network.com/csw/pkgutil_sparc.pkg<br />
- /usr/sbin/pkgadd">http://blastwave.network.com/csw/pkgutil_sparc.pkg<br />
- /usr/sbin/pkgadd</a> -n -a ${ADMIN_FILE} -d /a/tmp/pkgutil_sparc.pkg -R ${BASE} CSWpkgutil
- chroot /a /opt/csw/bin/pkgutil -y --catalog
- cd
Lines 76 through 78: Install Blastwave Packages
Here we call a script that will install all the packages we specify on the command line using Blastwave's repository (NRPE in this case):
- #!/bin/sh
- PKGS=$*
- chroot /a /opt/csw/bin/pkgutil -y --install $PKGS
Lines 82 through 85: Setup Apache Log Rotation using logadm
A quick example on how to setup log rotation using Solaris tools.
Lines 87 through 90: Setup an RBAC Profile
Here I create an RBAC profile that contains the auths necessary to control the Webstack Apache service.
Lines 92 through 94: Setup the custom NDD SMF service
Here, I download the method and the manifest for our NDD SMF service from Subversion. Since the SMF repository hasn't been setup yet, we simply need to copy the xml file to the proper place, and it will be imported into the repository on first boot. Part of the service involves specifying a profile name, so the script knows which ndd tunings to perform. The profile is passed in as an argument to the script:
- #!/bin/sh
- PROFILE=$1
- export PROFILE
- cd /a/tmp
- ${SVNGET}SMF/trunk/ndd/ndd.xml
- ${SVNGET}SMF/trunk/ndd/site-ndd
- cp ndd.xml /a/var/svc/manifest/site/
- cp site-ndd /a/lib/svc/method/site-ndd
- chmod 755 /a/lib/svc/method/site-ndd
- perl -pi -e 's/^(.*propval name="profile" .*) value=""/$1 value="$ENV{PROFILE}"/' \
- /a/var/svc/manifest/site/ndd.xml
- cd
Lines 96 through 101: Modify /etc/system
Quick example of how to modify /etc/system.
Lines 103 through 105: Install pca (Patch Check Advanced) and apply updates
This subscript installs the latest stable pca script, mounts a shared patch server NFS mountpoint, and then installs all recommended and security patches applicable. The beauty of this script is that each jumpstart updates the NFS server with the latest patch files from Sun:
- #!/bin/sh
- mkdir -p /a/apps/patches
- cp ${SI_CONFIG_DIR}/scripts/pca /a/apps/patches
- chmod 755 /a/apps/patches/pca
- mkdir /tmp/patches
- mount -F nfs patches.mydomain.com:/export/home/patches /tmp/patches
- /a/apps/patches/pca -P /tmp/patches --nobackup=all --user=me@my.com --passwd=mysunpassword \
- -R /a --wget=/a/usr/sfw/bin/wget -i missingrs
Lines 107 through 108: Install site.xml
Here, I copy a site.xml that specifies which services I want enabled on bootup to the appropriate directory. For reference, here's what's in that site.xml file:
- <?xml version='1.0'?>
- <!DOCTYPE service_bundle SYSTEM '/usr/share/lib/xml/dtd/service_bundle.dtd.1'>
- <service_bundle type='profile' name='site'>
- <xi:include href='file:/var/svc/profile/name_service.xml' />
- <service name='network/ntp' type='service' version='0'>
- <instance name='default' enabled='true'/>
- </service>
- <service name='network/nagios/nrpe' type='service' version='0'>
- <instance name='default' enabled='true'/>
- </service>
- <service name='site/ndd' type='service' version='0'>
- <instance name='default' enabled='true'/>
- </service>
- <service name='network/http' type='service' version='0'>
- <instance name='sun-apache22' enabled='true'/>
- </service>
- </service_bundle>
Lines 110 through 125: Setup passwordless users with SSH access and RBAC Profiles
This for-do loop is pretty cool. It creates the users user1, user2, and user3. It sets their password to an impossible value, but the account is unlocked. It then sets up their authorized_keys file to allow access from the ${SI_CONFIG_DIR}/sshkeys/$u/sshpubkey.txt file on the jumpstart server. As part of the useradd, it gives the users the previously defined "WebServer Admin" RBAC profile.
Lines 127 through 129: Setup a zpool scrub cronjob
This is a good example of how to create a cronjob from within jumpstart:
- #!/bin/sh
- cp ${SI_CONFIG_DIR}/scripts/zpool_scrub.sh /a/apps/customscripts/
- echo "15 4 * * 1 /apps/customscripts/zpool_scrub.sh" >> /a/var/spool/cron/crontabs/root
- #!/bin/ksh
- #
- # this script will go through all pools and scrub them one at a time
- #
- ZPOOL=/usr/sbin/zpool
- TMPFILE=/tmp/scrub.sh.$$.$RANDOM
- scrub_in_progress() {
- if $ZPOOL status $1 | grep "scrub in progress" >/dev/null; then
- return 0
- else
- return 1
- fi
- }
- for pool in `$ZPOOL list -H -o name`; do
- $ZPOOL scrub $pool
- while scrub_in_progress $pool; do
- sleep 60
- done
- if ! $ZPOOL status $pool | grep "with 0 errors" >/dev/null; then
- $ZPOOL status $pool >>$TMPFILE
- fi
- done
- if [ -s $TMPFILE ]; then
- cat $TMPFILE
- fi
- rm -f $TMPFILE
Lines 131: Don't reboot.
Here is where we fire off a shell to prevent the automatic reboot. This line is commented out once everything has been tested and verified to be working:
- #!/bin/sh
- echo "Dropping you into a shell."
- echo "To complete the installation, and reboot this machine,"
- echo "simply 'exit' the shell."
- /bin/sh
Summary
As you can see by the length of this post, you can do just darn near anything when you have a jumpstart finish script available. This is by no means perfect, there's some code that could be modularized, bug-proofed, etc. I should note that an epiphany occured to me while setting all this up. The perfect approach would be to setup only the bare minimum required installation to get puppet working via Jumpstart, and then to let puppet do it's thing to get the server to it's proper state immediately after installation as described perfectly over at madstop.com. I've ordered my copy of "Pulling Strings with Puppet", and I hope the only changes I make this jumpstart config is to get puppet up and running on the target. If you're going to be doing this much with Jumpstart, I have to at least mention the Jumpstart Enterprise Toolkit which I probably would have used except that it requires the use of the Sun DHCP server.
This concludes the series of posts, I hope you enjoyed them! If you find any bugs, have any questions, or just have something to add, please do so in the comments!

Comments
Post new comment