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:

  1. #!/bin/sh
  2. BASE=/a
  3. export BASE
  4. MNT=${BASE}/mnt
  5. export MNT
  6. ADMIN_FILE=/a/tmp/admin
  7. export ADMIN_FILE
  8. SVNGET="${BASE}/usr/sfw/bin/wget -q --no-check-certificate --http-user=xxx \
  9. --http-password=yyy https%3A%2F%2Fsvn.domain.com/repos/"
  10. export SVNGET
  11.  
  12.  
  13. #Setup a working mnttab in the /a root
  14. /sbin/mount -F mntfs mnttab ${BASE}/etc/mnttab
  15.  
  16. #Setup a working proc in the /a root
  17. mount -F lofs /proc ${BASE}/proc
  18.  
  19. # Add static routes to appservers that don't go through the lb
  20. echo "Setting up static routes"
  21. for i in 12 13 15 23; do
  22.   echo "192.168.1.${i} 192.168.0.1" >> ${BASE}/etc/inet/static_routes
  23. done
  24.  
  25. # Create defaultdomain
  26. echo "Setting /etc/defaultdomain"
  27. ${SI_CONFIG_DIR}/scripts/create_defaultdomain.sh
  28.  
  29. #Setup /etc/hosts
  30. echo "Setting /etc/hosts"
  31. ${SI_CONFIG_DIR}/scripts/create_etc_hosts.sh loghost
  32.  
  33. # Setup sendmail aliases
  34. echo "Setting up Sendmail"
  35. ${SI_CONFIG_DIR}/scripts/setup_sendmail.sh
  36.  
  37. # Setup NTP
  38. echo "Setting up NTP"
  39. ${SI_CONFIG_DIR}/scripts/setup_ntp.sh prod
  40.  
  41. #Setup /apps dataset
  42. echo "Setting up /apps and /apps/apache2.2/logs datasets"
  43. zfs create -o mountpoint=/apps -o atime=off rpool/apps
  44. zfs create -o mountpoint=/apps/apache2.2/logs -o atime=off rpool/apachelogs
  45. mkdir /a/apps/customscripts
  46.  
  47. #Set quota on home dirs
  48. zfs set quota=2G rpool/export/home
  49.  
  50. #Install webstack
  51. echo "Installing Glassfish Webstack"
  52. ${SI_CONFIG_DIR}/scripts/setup_webstack.sh
  53.  
  54.  
  55. #Install companion cd packages
  56. echo "Installing Companion CD Packages"
  57. ${SI_CONFIG_DIR}/scripts/install_comp_cd_pkgs.sh SFWrsync SFWcoreu \
  58.     SFWcurl SFWtop
  59.  
  60. #Put misbehaving packages in this loop to install via chroot
  61. for p in ESUscponly; do
  62.         chroot /a /usr/sbin/pkgadd -n -a /tmp/admin \
  63.            -d /mnt/Solaris_sparc/Packages ${p} > /dev/null
  64. done
  65.  
  66. #Build symlinks to certain binaries
  67. cd ${BASE}/usr/bin/
  68. for b in rsync top; do
  69.   ln -s ../../opt/sfw/bin/${b} ${b}
  70. done
  71.  
  72. #Install Blastwave support
  73. echo "Installing Blastwave support"
  74. ${SI_CONFIG_DIR}/scripts/install_blastwave.sh
  75.  
  76. #Install NRPE
  77. echo "Installing NRPE from Blastwave"
  78. #${SI_CONFIG_DIR}/scripts/pkgutil_install.sh nrpe
  79.  
  80. #Setup SNMPd
  81.  
  82. #Setup Apache Log Rotation
  83. echo "Configuring Apache Log Rotation"
  84. chroot ${BASE} /usr/sbin/logadm -w Apache22Logs -p1d -C4 \
  85.  -c -t '/apps/apache2.2/logs/archives/$basename.$N.gz' '/apps/apache2.2/logs/*.log'
  86.  
  87. echo "Creating \"WebServer Admin\" RBAC Profile"
  88. AUTHS="solaris.smf.manage.http/sun-apache22,solaris.smf.value.http/sun-apache22"
  89. echo "WebServer Admin:::Manage Apache and MediaClient:auths=$AUTHS" \
  90.    >> /a/etc/security/prof_attr
  91.  
  92. #Setup ndd SMF
  93. echo "Downloading and installing ndd service"
  94. ${SI_CONFIG_DIR}/scripts/setup_ndd.sh webserver
  95.  
  96. #Setup /etc/system
  97. echo "Adding entries to /etc/system"
  98. echo "set sq_max_size=0" >> /a/etc/system
  99. echo "set rlim_fd_max=2000000" >> /a/etc/system
  100. echo "set rlim_fd_cur=2000000" >> /a/etc/system
  101. echo "set maxphys=1048576" >> /a/etc/system
  102.  
  103. #Install pca
  104. echo "Installing Patch Check Advanced and installing all missing/recommended patches"
  105. ${SI_CONFIG_DIR}/scripts/install_pca.sh
  106.  
  107. #Install site.xml bundle
  108. cp ${SI_CONFIG_DIR}/webserver-site.xml /a/var/svc/profile/site.xml
  109.  
  110. #Setup Users, SSH Keys
  111. echo "Setting up users"
  112. for u in user1 user2 user3; do
  113.     # Add to webservd group
  114.     # Install ~/.ssh/authorized_keys
  115.     # Generate a random password
  116.     chroot /a /usr/sbin/useradd -P "WebServer Admin" -G webservd -d /export/home/$u -m $u
  117.     chroot /a /usr/bin/passwd -N $u > /dev/null
  118.     mkdir /a/export/home/$u/.ssh/
  119.     touch /a/export/home/$u/.ssh/authorized_keys
  120.     if [ -f ${SI_CONFIG_DIR}/sshkeys/${u}/sshpubkey.txt ]; then
  121.       cp ${SI_CONFIG_DIR}/sshkeys/$u/sshpubkey.txt /a/export/home/$u/.ssh/authorized_keys
  122.     fi
  123.     chmod 700 /a/export/home/$u/.ssh/
  124.     chroot /a /usr/bin/chown -R $u /export/home/$u/.ssh
  125. done
  126.  
  127. #Setup a weekly zpool scrub
  128. echo "Setting up weekly zpool scrub"
  129. ${SI_CONFIG_DIR}/scripts/setup_weekly_zpool_scrub.sh
  130.  
  131. ${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:

  1. #!/bin/sh
  2.  
  3. DN=`grep domain /a/etc/resolv.conf | awk '{ print $2 }'`
  4. if [ -n "$DN" ]; then
  5.   echo $DN > /a/etc/defaultdomain
  6.   /usr/bin/domainname $DN
  7. 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:

  1. #!/bin/sh
  2.  
  3. ALIASES=$*
  4.  
  5. HOSTNAME=`/usr/bin/hostname`
  6. DOMAINNAME=`/usr/bin/domainname`
  7. FQDN="${HOSTNAME}.${DOMAINNAME}"
  8.  
  9. IP=`getent hosts $FQDN | awk '{ print $1 }' | head -1`
  10.  
  11. cat <<EOD > /a/etc/inet/hosts
  12. #
  13. # Internet host table
  14. #
  15. ::1     localhost      
  16. 127.0.0.1       localhost      
  17. EOD
  18.  
  19. 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':

  1. #!/bin/sh
  2.  
  3. cat <<EOD >> /a/etc/mail/aliases
  4. user1:root
  5. user2:root
  6. root:admin@my.com
  7. EOD
  8.  
  9. 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:

  1. #!/bin/sh
  2.  
  3. case $1 in
  4.         "prod")
  5.         cat <<EOD > /a/etc/inet/ntp.conf
  6. server 192.168.1.1
  7. server 10.0.0.7
  8. server us.pool.ntp.org
  9. driftfile /var/ntp/ntp.drift
  10. EOD
  11.         ;;
  12.         *)
  13.         cat <<EOD > /a/etc/inet/ntp.conf
  14. server 10.0.0.7
  15. server 192.168.1.1
  16. server us.pool.ntp.org
  17. driftfile /var/ntp/ntp.drift
  18. EOD
  19.         ;;
  20. 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:

  1. #!/bin/sh
  2.  
  3. cp -RPp ${SI_CONFIG_DIR}/webstack ${BASE}/var/tmp/
  4. chroot ${BASE} /var/tmp/webstack/install apache && \
  5.   rm -rf ${BASE}/var/tmp/webstack
  6.  
  7. echo "Adding RBAC Authorizations to manage Apache"
  8. echo "solaris.smf.manage.http/sun-apache22:::Apache2.2 Server management::" \
  9.   >> ${BASE}/etc/security/auth_attr
  10. echo "solaris.smf.value.http/sun-apache22:::Apache2.2 Server SMF property management::" \
  11.   >> ${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:

  1. #!/bin/sh
  2.  
  3. PKGS=$*
  4.  
  5. mount -f nfs nfsserver.mydomain.com:/export/home/jumpstart/install/s10u7_companion_cd ${MNT}
  6. cat >${ADMIN_FILE} <<DONT_ASK
  7. mail=
  8. instance=overwrite
  9. partial=nocheck
  10. runlevel=nocheck
  11. idepend=nocheck
  12. rdepend=nocheck
  13. space=nocheck
  14. setuid=nocheck
  15. conflict=nocheck
  16. action=nocheck
  17. basedir=default
  18. DONT_ASK
  19.  
  20. for p in $PKGS; do
  21.         /usr/sbin/pkgadd -n -a ${ADMIN_FILE} -d ${MNT}/Solaris_sparc/Packages -R ${BASE} ${p} > /dev/null
  22. done
The ESUscponly package is a 3rd party package that doesn't respect the -R flag to pkgadd, so it's installed by running pkgadd from within the chroot environment.

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:

  1. #!/bin/sh
  2.  
  3. cd /a/tmp
  4. ${BASE}/usr/sfw/bin/wget -q <a href="http://blastwave.network.com/csw/pkgutil_sparc.pkg<br />
  5. /usr/sbin/pkgadd" title="http://blastwave.network.com/csw/pkgutil_sparc.pkg<br />
  6. /usr/sbin/pkgadd">http://blastwave.network.com/csw/pkgutil_sparc.pkg<br />
  7. /usr/sbin/pkgadd</a> -n -a ${ADMIN_FILE} -d /a/tmp/pkgutil_sparc.pkg -R ${BASE} CSWpkgutil
  8. chroot /a /opt/csw/bin/pkgutil -y --catalog
  9. 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):

  1. #!/bin/sh
  2.  
  3. PKGS=$*
  4.  
  5. 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:

  1. #!/bin/sh
  2.  
  3. PROFILE=$1
  4. export PROFILE
  5.  
  6. cd /a/tmp
  7. ${SVNGET}SMF/trunk/ndd/ndd.xml
  8. ${SVNGET}SMF/trunk/ndd/site-ndd
  9. cp ndd.xml /a/var/svc/manifest/site/
  10. cp site-ndd /a/lib/svc/method/site-ndd
  11. chmod 755 /a/lib/svc/method/site-ndd
  12. perl -pi -e 's/^(.*propval name="profile" .*) value=""/$1 value="$ENV{PROFILE}"/' \
  13.   /a/var/svc/manifest/site/ndd.xml
  14. 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:

  1. #!/bin/sh
  2.  
  3. mkdir -p /a/apps/patches
  4. cp ${SI_CONFIG_DIR}/scripts/pca /a/apps/patches
  5. chmod 755 /a/apps/patches/pca
  6.  
  7. mkdir /tmp/patches
  8. mount -F nfs patches.mydomain.com:/export/home/patches /tmp/patches
  9. /a/apps/patches/pca -P /tmp/patches --nobackup=all --user=me@my.com --passwd=mysunpassword \
  10.  -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:

  1. <?xml version='1.0'?>
  2. <!DOCTYPE service_bundle SYSTEM '/usr/share/lib/xml/dtd/service_bundle.dtd.1'>
  3. <service_bundle type='profile' name='site'>
  4.   <xi:include href='file:/var/svc/profile/name_service.xml' />
  5.   <service name='network/ntp' type='service' version='0'>
  6.     <instance name='default' enabled='true'/>
  7.   </service>
  8.   <service name='network/nagios/nrpe' type='service' version='0'>
  9.     <instance name='default' enabled='true'/>
  10.   </service>
  11.   <service name='site/ndd' type='service' version='0'>
  12.     <instance name='default' enabled='true'/>
  13.   </service>
  14.   <service name='network/http' type='service' version='0'>
  15.     <instance name='sun-apache22' enabled='true'/>
  16.   </service>
  17. </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:

  1. #!/bin/sh
  2.  
  3. cp ${SI_CONFIG_DIR}/scripts/zpool_scrub.sh /a/apps/customscripts/
  4. echo "15 4 * * 1 /apps/customscripts/zpool_scrub.sh" >> /a/var/spool/cron/crontabs/root
For reference, here's the scrubbing script:
  1. #!/bin/ksh
  2. #
  3. # this script will go through all pools and scrub them one at a time
  4. #
  5.  
  6. ZPOOL=/usr/sbin/zpool
  7. TMPFILE=/tmp/scrub.sh.$$.$RANDOM
  8.  
  9. scrub_in_progress() {
  10.         if $ZPOOL status $1 | grep "scrub in progress" >/dev/null; then
  11.                 return 0
  12.         else
  13.                 return 1
  14.         fi
  15. }
  16.  
  17. for pool in `$ZPOOL list -H -o name`; do
  18.         $ZPOOL scrub $pool
  19.  
  20.         while scrub_in_progress $pool; do
  21.                 sleep 60
  22.         done
  23.  
  24.         if ! $ZPOOL status $pool | grep "with 0 errors" >/dev/null; then
  25.                 $ZPOOL status $pool >>$TMPFILE
  26.         fi
  27. done
  28.  
  29. if [ -s $TMPFILE ]; then
  30.         cat $TMPFILE
  31. fi
  32.  
  33. 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:

  1. #!/bin/sh
  2.  
  3. echo "Dropping you into a shell."
  4. echo "To complete the installation, and reboot this machine,"
  5. echo "simply 'exit' the shell."
  6.  
  7. /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!

No votes yet

Comments

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <p> <span> <div> <h1> <h2> <h3> <h4> <h5> <h6> <img> <map> <area> <hr> <br> <br /> <ul> <ol> <li> <dl> <dt> <dd> <table> <tr> <td> <em> <b> <u> <i> <strong> <font> <del> <ins> <sub> <sup> <quote> <blockquote> <pre> <address> <code> <cite> <embed> <object> <param> <strike> <caption>
  • Lines and paragraphs break automatically.

More information about formatting options