Viewing entries tagged
pkg

Capturing Package Files with PkgKeeper

2 Comments

Capturing Package Files with PkgKeeper

Deploying software via Munki is an excellent asset to sites managing fleets of Macs. Sometimes however, a package will not be listed directly on Apple's Support website and also may not be taking advantage of OS X Server's Caching Service. This is why I created the script PkgKeeper. The script works by monitoring filesystem access and if a pkg or dmg file is detected a hard link of the file is created on the user’s desktop.

At this point you may be asking yourself “what is a hard link?” Every unique file on a Unix (the foundation of OS X) filesystem has an inode (index node). One of the attributes of an inode is ‘link count.’ The link count is the number of hard links to a file.

Normally a file has a link count of just one, but when a new hard link is created that link count is incremented by one. Naturally, removing a file decrements the link count by one. It is not until the link count reaches zero that the inode is removed and the space is marked as available for use.

Under normal circumstances once an update package is installed and the package is removed the file's link count goes from one to zero. However, PkgKeeper creates another hard link of the file while it is still in use setting the file's link count to two. This stops the file from hitting a link count of zero and being completely removed.

 

Using The Script

Open Terminal and paste the following to download the script:

curl -O https://raw.githubusercontent.com/Error-freeIT/PkgKeeper/master/pkgkeeper.sh

Make the script executable:

chmod +x pkgkeeper.sh

Run the script:

sudo ./pkgkeeper.sh

Start downloading an update and watch as the script captures the package file.

Note for OS X 10.11 users: El Capitan's System Integrity Protection prevents this script from working. To temporally disable SIP boot into a recovery partition or 10.11 USB installer, open Terminal and type 'csrutil enable --without dtrace'.

Bonus Tips

In Terminal you can view a file's link count with the command:

stat -f '%l' FILE_NAME

The inode also contains the User ID, Group ID and file mode attributes of the file. Therefore all hard links will have the same user, group ownership and access permissions.

Once the update is installed the original process deletes its hard link to the file. This means it is no longer accessing the file and we are safe to edit the file's ownership. The easiest way to do this is by editing the 'Sharing & Permissions' section in the 'Get Info' window.

2 Comments

Creating OS X Package Files (.pkg) In Terminal

6 Comments

Creating OS X Package Files (.pkg) In Terminal

2018 Update

Everything below still works, but for the past few years I have been using munkipkg for the generation of all my packages. Although Munki is in the name, it is just a nice command-line tool by Greg Neagle for creating packages.

OS X includes a handy Terminal tool called ‘pkgbuild’ for compiling package installer files.

To demonstrate pkgbuild I will use my 'Enable ARD' (Apple Remote Desktop) package.

This package places an enableard.sh script into /Library/Scripts/Enable ARD/ and creates a LaunchDaemon to run that script on boot. The enableard.sh script simply removes all users from Remote Management (ARD) and then gives full access to a specified username, ensuring that user has remote access.

Structure

To keep things organised when creating a package, I create a directory consisting of the following files and folders:

build.sh: Includes the package name, versioning information and is used to compile the package.

complied: Is where the product of build.sh is written.

files: Contains the files the package will copy into place.

scripts: Can contain preinstall and/or postinstall scripts. A preinstall script is run before the contents of files is copied into place, whereas postinstall is after. It is important not to give these script files an extension.

Contents of build.sh:

#!/bin/bash

# Name of the package.
NAME="enableard"

# Once installed the identifier is used as the filename for a receipt files in /var/db/receipts/.
IDENTIFIER="au.com.errorfreeit.$NAME"

# Package version number.
VERSION="1.0"

# The location to copy the contents of files.
INSTALL_LOCATION="/Library/Scripts/Enable ARD"


# Remove any unwanted .DS_Store files.
find files/ -name '*.DS_Store' -type f -delete

# Set full read, write, execute permissions for owner and just read and execute permissions for group and other.
/bin/chmod -R 755 files

# Remove any extended attributes (ACEs).
/usr/bin/xattr -rc files

# Build package.
/usr/bin/pkgbuild \
    --root files/ \
    --install-location "$INSTALL_LOCATION" \
    --scripts scripts/ \
    --identifier "$IDENTIFIER" \
    --version "$VERSION" \
    "compiled/$NAME-$VERSION.pkg"

 

Contents of scripts/postinstall:

#!/bin/bash

# Filename of script.
NAME="enableard.sh"

# Path to script.
PATH="/Library/Scripts/Enable ARD"

# Script identifier (same as package identifier).
IDENTIFIER="au.com.errorfreeit.enableard"


LAUNCH_DAEMON_PLIST="/Library/LaunchDaemons/$IDENTIFIER.plist"

# Write LaunchDaemon plist file.
echo '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>'$IDENTIFIER'</string>
    <key>ProgramArguments</key>
    <array>
        <string>'$PATH/$NAME'</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
</dict>
</plist>' > "$LAUNCH_DAEMON_PLIST"

# Load the new LaunchDaemon.
/bin/launchctl load "$LAUNCH_DAEMON_PLIST"

# Check LaunchDaemon is loaded.
STATUS=`/bin/launchctl list | /usr/bin/grep $IDENTIFIER | /usr/bin/awk '{print $3}'`

if [ "$STATUS" = "$IDENTIFIER" ]
then
        echo "Success: LaunchDaemon loaded."
        exit 0      
else
        echo "Error: LaunchDaemon not loaded."      
        exit 1
fi

 

Contents of files/enableard.sh:

#!/bin/bash

# Username of account used for ARD access.
USER="ardadmin"

# Revoke ARD access for all users.
/System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -activate -configure -access -off

# Assign full privileges to an ARD Administrator account.
/System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -activate -configure -access -on -users $USER -privs -all -restart -agent -menu

# Set Remote Management to only accept the user specified.
/System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -configure -allowAccessFor -specifiedUsers

Ready To Compile

To compile a package:

Open Terminal,

type 'cd ' (change directory),

drag the folder containing build.sh into Terminal and hit return,

type 'chmod +x build.sh' to make it executable,

lastly type ‘./build.sh’ and hit return.

You should now have a new package file in the compiled directory.

Bonus Tips

Use Full Paths

When writing scripts it is important to include the full path to any binaries used. You can easily find the full path of a binary with the ‘which’ command.

Check Your Packages

Sometimes I want to know what a package would do without running it. This is where the Finder QuickLook plug-in called ‘Suspicious Package’ comes in handy, as it shows where files would be copied and the contents of any preinstall and postinstall scripts. I also like to use Suspicious Package on my own package files to ensure files are being copied into the correct directory and any scripts are present.

Read The Manual

My example build.sh script does not take advantage of pkgbuild’s complete feature-set, for example packages can also be signed to prove authenticity. If you want to know more about pkgbuild simply read the man (manual) file, by typing ‘man pkgbuild’ into Terminal.

6 Comments