#!/bin/csh
# miniclone.csh
# script to clone a lightweight bootable mac os x system onto a flash drive
# by peter bartoli <downtime@slagheap.net>

# this script comes without warranty or support and is licensed under the terms
# of the FSF GPL version 2.0 - see http://www.gnu.org/licenses/gpl-2.0.html

# version 1.3 last revised 6/20/2010
set version = "1.3"
set updated = "6/17/2010"

# version history
# v0.1 (09/07/08): first initial version
# v0.2 (09/14/08): clones still not yet booting, but first bugfixes
# v0.3 (09/22/08): added in disk utility suites and validated PGP functionality
# v0.4 (10/21/08): added in rEFIt tools
# v0.5 (12/28/08): added in TextEdit
# v0.6 (03/03/09): added in vsdbutil to make sure ownership is on
# v0.7 (03/04/09): added iteration on subdirs of /Users
# v0.8 (09/19/09): updated Parallels links, naming target, added newfs_hfs
# v1.0 (09/20/09): optimizing, name change to miniclone, first public release
#      (09/20/09): happy birthday, mom. miss you!
# v1.1 (11/28/09): added SQLite Database Browser and sundry other edits
# v1.2 (01/09/10): stated project goal of under 16gb, removed some apps
# v1.3 (06/20/10): added clonesync command option to not wipe target

# find binary locations
foreach x ( \
	df \
	grep \
	uname \
	whoami \
	cut \
	basename \
	diskutil \
	newfs_hfs \
	vsdbutil \
	mkdir \
	chown \
	chmod \
	chgrp \
	ditto \
	mkdir \
	rm \
	cp \
	ln \
	find \
	bless \
	SetFile \
	update_dyld_shared_cache \
	)
  if (-e /usr/bin/$x) then
     set $x = "/usr/bin/$x"
     continue
    endif
  if (-e /bin/$x) then
     set $x = "/bin/$x"
     continue
    endif
  if (-e /usr/sbin/$x) then
     set $x = "/usr/sbin/$x"
     continue
    endif
  if (-e /sbin/$x) then
     set $x = "/sbin/$x"
     continue
    endif
  # workarounds for missing binaries can be inserted here
  echo "miniclone warning: cannot find $x"
  set $x = "none"
  continue
 end # foreach

## set up variables
# check if we're cloning or syncing
@ sync = 1
if ("`basename ${0}`" == "miniclone") @ sync = 0
if ("`basename ${0}`" == "miniclone.csh") @ sync = 0

# check number of arguments
if ($#argv > 1) then
  echo "usage: [miniclone|clonesync] [volume path]"
  exit 1
 endif
set target = "/Volumes/miniclone"
if ($#argv == 1) set target = "${1}"

# sanity checks
if (! -e $target) then
  echo "$target not found"
  exit
 endif
if (`$df | $grep -qw $target\$`) then
  echo "$target not a mount point"
  exit
 endif
if ("`$uname`" != "Darwin") then
  echo "miniclone only works on a mac"
  exit
 endif
if ("`$whoami`" != "root") then
  echo "miniclone must be run either with sudo or as root"
  exit
 endif

# banner
echo "miniclone version $version ($updated)"
echo "by peter bartoli <downtime@slagheap.net>"
echo ""

# prepare target unless syncing
if ($sync) goto skipprep
echo -n "preparing target "
# get special device file
set special = "`$df | $grep -w ${target}$ | $cut -d\  -f 1`"
set disk = "`$basename $special`"
set volume = "`$basename $target`"
echo -n "$disk ... "
# initialize filesystem
if ("`$diskutil umount $disk`" != "Volume $volume on $disk unmounted") then
  echo "unable to umount $target"
  exit
 endif
$newfs_hfs -v $volume $special >&! /dev/null
if ($status) then
  echo "failed to initialize target volume"
  exit
 endif
if ("`$diskutil mount $disk`" != "Volume $volume on $disk mounted") then
  echo "unable to remount $target"
  exit
 endif
# turn on file ownership
$vsdbutil -a $target
$chown -R root:wheel $target
echo "done."
skipprep:

# /Applications logic
if (! $sync) then
  echo -n "making /Applications tree (root:admin, 775) ... "
  $mkdir -p $target/Applications/Utilities
  $chown -R root:admin $target/Applications
  $chmod -R 775 $target/Applications
  echo "done."
 endif
echo "kloning /Applications ..."
cd /Applications
foreach x ( \
	0xED.app \
	Address\ Book.app \
	Adeona \
	AppleScript \
	Automator.app \
	Calculator.app \
	Check\ Point\ VPN-1\ SecureClient \
	Chicken\ of\ the\ VNC.app \
	ClamXav.app \
	CoolTerm.app \
	CoRD.app \
	DMGConverter.app \
	Dashboard.app \
	Dictionary.app \
	DiskWarrior* \
	Drive\ Genius* \
	Expose.app \
	Firefox.app \
	Font* \
	GPG* \
	Internet\ Explorer.app \
	KeyViewer.app \
	Keyspan\ Serial\ Assistant.app \
	KisMAC* \
	Lingon.app \
	Little\ Snitch\ Configuration.app \
	Mactracker.app \
	Microsoft\ Office* \
	MkConsole* \
	Network\ Connect.app \
	PGP* \
	Preview.app \
	QuickTime\ Player.app \
	Remote\ Desktop\ Connection.app \
	Retrospect* \
	Safari.app \
	SecureClient \
	Server \
	Shimo.app \
	SlingPlayer.app \
	Spaces.app \
	Stickies.app \
	StuffIt \
	System\ Preferences.app \
	TechTool* \
	TextEdit.app \
	Time\ Machine.app \
	TrueCrypt.app \
	VLC.app \
	VMware* \
	VPN* \
	VZAccess\ Manager.app \
	WaterRoof.app \
	ZTerm.app \
	iCal.app \
	Utilities/Activity\ Monitor.app \
	Utilities/Audit\ Log\ Viewer.app \
	Utilities/Bluetooth\ File\ Exchange.app \
	Utilities/Boot\ Camp\ Assistant.app \
	Utilities/Console.app \
	Utilities/Disk\ Utility.app \
	Utilities/Firmware\ Password\ Utility.app \
	Utilities/Java* \
	Utilities/Keychain\ Access.app \
	Utilities/Network\ Utility.app \
	Utilities/PackageMaker.app \
	Utilities/Partition\ Inspector.app \
	Utilities/Property\ List\ Editor.app \
	Utilities/Reset\ Password.app \
	Utilities/Terminal.app \
	Utilities/X11.app \
	)
  if ($sync) $rm -rf "$target/Applications/$x"
  $ditto -vX "/Applications/$x" "$target/Applications/$x"
 end # Applications

# /Library logic
if (! $sync) then
  echo -n "making /Library tree ... "
  $mkdir -p $target/Library/{Application\ Support,Printers,StartupItems}
  $chown -R root:admin $target/Library
  $chmod -R 775 $target/Library
  $chown root:wheel $target/Library/StartupItems
  $chmod 755 $target/Library/StartupItems
  echo "done."
 endif
echo "kloning /Library ..."
cd /Library
foreach x ( \
	Application\ Support/iStat\ local \
	Application\ Support/Macromedia \
	Application\ Support/Microsoft \
	Application\ Support/Objective\ Development \
	Application\ Support/PGP \
	Application\ Support/Sandisk \
	Application\ Support/Shimo \
	Application\ Support/SlingPlayer \
	Application\ Support/VMware* \
	Application\ Support/VPN* \
	Application\ Support/Verizon \
	Application\ Support/hp \
	Caches \
	CFMSupport \
	Color* \
	Contextual\ Menu\ Items \
	Dictionaries \
	Documentation \
	Extensions \
	FileSystems \
	Font* \
	Frameworks \
	Graphics \
	Input* \
	Internet\ Plug-Ins \
	Java \
	Keychains \
	LaunchAgents \
	LaunchDaemons \
	Little\ Snitch \
	Logs \
	Modem\ Scripts \
	Mozilla \
	PDF\ Services \
	Perl \
	Preference* \
	Printers/PPD* \
	Python \
	QuickLook \
	QuickTime \
	Ruby \
	Screen\ Savers \
	ScriptingAdditions \
	Scripts \
	Security \
	Spotlight \
	StartupItems/Parallels* \
	StartupItems/rEFItBlesser \
	StartupItems/RetroRun \
	User\ Pictures \
	Widgets \
	)
  if ($sync) $rm -rf "$target/Library/$x"
  $ditto -vX "/Library/$x" "$target/Library/$x"
 end # Library

# /System logic
if (! $sync) then
  echo -n "making /System tree (root:wheel, 755) ... "
  $mkdir -m 755 -p $target/System/Library
  $chown -R root:wheel $target/System
  echo "done."
 endif
echo "kloning /System ..."
cd /System/Library 
foreach x ( * )
  # skip text-to-speeech to save most of a gigabyte
  if ("$x" == "Speech") continue
  if ($sync) $rm -rf "$target/System/Library/$x"
  $ditto -vX "/System/Library/$x" "$target/System/Library/$x"
 end # System

# /Users logic
if (! $sync) then
  echo -n "making /Users tree ... "
  $mkdir $target/Users
  $chown root:admin $target/Users
  $chmod 755 $target/Users
  echo "done."
 endif
cd /Users
foreach homedir (*)
  # take care of Shared
  if ("$homedir" == "Shared") then
    if ($sync) $rm -rf $target/Users/Shared
    $ditto -vX /Users/Shared $target/Users/Shared
    continue
   endif
  # everything else - we assume shortname equal to directory name
  echo "kloning /Users/$homedir ..."
  if ($sync) $rm -rf $target/Users/$homedir
  $mkdir -p $target/Users/$homedir/Library/{Application\ Support,Caches} \
    $target/Users/$homedir/{Desktop,Documents,Pictures}
  $chown -R ${homedir}:staff $target/Users/$homedir
  $chmod 700 $target/Users/$homedir
  cd /Users/$homedir 
  foreach x ( \
	.DS_Store \
	.cshrc \
	.cups \
	.fontconfig \
	.fonts.cache-1 \
	.gnupg \
	.hidden \
	.login \
	.parallels* \
	.plan \
	.profile \
	.rdesktop \
	.rnd \
	.ssh \
	.telnetrc \
	.wgetrc \
	.xinitrc \
	.zshrc \
	Documents/PGP \
	Documents/passwd.txt.gpg \
	Documents/wordlists \
	Library/Application\ Support/AddressBook \
	Library/Application\ Support/Juniper\ Networks \
	Library/Application\ Support/Little\ Snitch \
	Library/Application\ Support/Macromedia \
	Library/Application\ Support/Mactracker \
	Library/Application\ Support/Terminal \
	Library/Application\ Support/TrueCrypt \
	Library/Application\ Support/VMware* \
	Library/Application\ Support/iCal \
	Library/Caches/com.apple.{dashboard,help,preference}* \
	Library/Contextual\ Menu\ Items \
	Library/Favorites \
	Library/Fonts \
	Library/Icons \
	Library/Keychains \
	Library/Preference* \
	Library/Printers \
	Library/Screen\ Savers \
	Library/Sounds \
	Library/StickiesDatabase \
	Library/VirtualBox \
	Library/Widgets \
	Pictures/self \
	bin \
	skel \
	john \
	)
    if (-e "$x") $ditto -vX "/Users/$homedir/$x" "$target/Users/$homedir/$x"
   end # $homedir loop
 end # /Users

# /private logic
if (! $sync) then
  echo -n "making /private tree (root:admin, 755) ... "
  $mkdir -m 755 -p $target/private/var/{audit,empty,vm}
  $mkdir -m 775 $target/private/tftpboot $target/private/var/run
  $mkdir -m 1777 -p $target/private/{tmp,var/tmp}
  $chown -R root:wheel $target/private
  $chown root:daemon $target/private/var/run
  if ("$SetFile" != "none") $SetFile -a V $target/private
  echo "done." 
 endif
echo "kloning /private ..."
cd /private
foreach x ( \
	etc \
	opt \
	var/at \
	var/db \
	var/folders \
	var/launchd \
	var/log \
	var/root \
	var/samba \
	var/spool \
	)
  if ($sync) $rm -rf "$target/private/$x"
  if (-e "$x") $ditto -vX "/private/$x" "$target/private/$x"
 end # private

# root level logic
if (! $sync) then
  $mkdir -m 0755 $target/{dev,Network}
  $mkdir -m 1777 $target/{Volumes,cores}
  if ("$SetFile" != "none") $SetFile -a V $target/{dev,cores,Network,Volumes}
 endif
cd /
foreach x ( \
	.DS_Store \
	bin \
	efi \
	mach* \
	sbin \
	usr \
	)
  if ($sync) $rm -rf "$target/$x"
  $ditto -vX "/$x" "$target/$x"
 end # root

# symlinks if not syncingA
if (! $sync) then
  echo -n "making symlinks for etc, opt, tmp, and var ... "
  $ln -s private/etc $target/etc
  $ln -s private/opt $target/opt
  $ln -s private/tmp $target/tmp
  $ln -s private/var $target/var
  if ("$SetFile" != "none") $SetFile -P -a V $target/{etc,opt,tmp,var}
  echo "done."
 endif

# handle fink tree, if it's there
if (-e /sw) then
  echo "kloning fink tree ... "
  $rm -rf "$target/sw"
  $ditto -vX /sw "$target/sw"
  if ("$SetFile" != "none") $SetFile -a V "$target/sw"
 endif

# bless the miniclone
echo -n "blessing cloned system folder ... "
$bless --folder $target/System/Library/CoreServices --bootefi
echo "done."

# clean up startup services
echo -n "cleaning up startup configs for faster booting ... "
$rm -f \
  $target/System/Library/LaunchAgents/com.apple.dotmac*.plist \
  $target/System/Library/LaunchAgents/com.apple.familycontrols.useragent.plist \
  $target/System/Library/LaunchAgents/com.apple.metadata.mdwrite.plist \
  $target/System/Library/LaunchAgents/com.apple.RemoteUI.plist \
  $target/System/Library/LaunchAgents/com.apple.ScreenSharing.plist \
  $target/System/Library/LaunchAgents/com.apple.Spotlight.plist \
  $target/System/Library/LaunchDaemons/com.apple.distccdConfig.plist \
  $target/System/Library/LaunchDaemons/com.apple.familycontrols.plist \
  $target/System/Library/LaunchDaemons/com.apple.InternetSharing.plist \
  $target/System/Library/LaunchDaemons/com.apple.iPhoneConfigService.plist \
  $target/System/Library/LaunchDaemons/com.apple.periodic-*.plist \
  $target/System/Library/LaunchDaemons/com.apple.webfilter.plist \
  $target/System/Library/LaunchDaemons/com.apple.xgrid*.plist \
  $target/System/Library/LaunchDaemons/org.apache.httpd.plist \
  $target/System/Library/LaunchDaemons/org.net-snmp.snmpd.plist \
  $target/System/Library/LaunchDaemons/org.postfix.master.plist \
  $target/System/Library/LaunchDaemons/{distccd,exec,finger,ftp,login}.plist \
  $target/System/Library/LaunchDaemons/{ntalk,shell,ssh,telnet}.plist \
  $target/Library/LaunchAgents/com.netopia.timbuktu.pro.host.plist
echo "done."

# truncate logs on miniclone
echo -n "truncating cloned syslogs ... "
$find $target/private/var/log -type f -exec /bin/cp /dev/null {} \;
echo "done."

# update prebinding on miniclonoe
echo -n "optimizing system performance on clone ... "
$update_dyld_shared_cache -root $target -force >&! /dev/null
echo "done."

## local customizations
if (-e /Users/peter) then
  # link /home
  $ln -s Users/peter $target/home
  if ("$SetFile" != "none") $SetFile -P -a V $target/home
  # remove login items
  $cp -p /Users/peter/src/scripts/miniclone/*login*.plist \
    $target/Users/peter/Library/Preferences
 endif

exit

