TweetFollow Us on Twitter

MacEnterprise: Managing Software Installs with Munki-Part 4

Volume Number: 27
Issue Number: 01
Column Tag: MacEnterprise

MacEnterprise: Managing Software Installs with Munki-Part 4

Crafting pkginfo files for munki

By Greg Neagle, MacEnterprise.org

Previously in MacEnterprise

Over the past several months, we've been looking at munki, a set of open-source tools that can manage software installation and removal on Mac OS X machines. Munki is available for download at http://code.google.com/p/munki. Munki can install software packaged in Apple's Installer package format, software delivered for "drag-and-drop" installs on disk images, and Adobe CS3, CS4 and CS5 products and updates using Adobe's supported enterprise deployment tools.

We set up a demonstration munki server on a Mac OS X "client" machine, and used that server along with the munki client tools to install, update, and remove some software packages on a client machine. The munki server is simply a web server, containing three types of information:

Installer items: these are packages or disk images containing the software to be installed. In many cases, you can use a package or disk image provided by the software vendor without having to repackage or convert the installer package. For example, munki can install Firefox directly from the disk image that you download from http://www.mozilla.com - you do not have to "repackage" Firefox in order to install it with munki.

Catalogs: these are lists of available software, containing metadata about the installer items. The munki administrator builds these catalogs using tools provided with munki.

Manifests: A manifest is a list of what software should be installed on or removed from a given machine. You can have a different manifest for every machine, or one manifest for all of your machines. Manifests can include the contents of other manifests, allowing you to group software for easy addition to client manifests. For example, you could create a manifest listing all of the software every machine in your organization must have. The manifest for a specific client could then include the "common software" manifest, and additionally have software unique to that client.

There is a fourth class of data that is commonly stored on the munki server as well, but munki clients do not access it directly. This data is the "pkginfo" files - typically one per installer item. These contain the metadata for each installer item. Munki clients do not access these files directly; instead they use the catalogs, which are themselves built from the pkginfo files.

Pkginfo files provide metadata about each installer item or package, information that either cannot be determined from the package itself, or info that would be too slow to get from every package each time munki runs. Much of the learning curve around munki involves crafting pkginfo files. Therefore, this month we will look at this topic in detail.

Creating pkginfo files

Fortunately, munki provides two tools to help with creating pkginfo files. The first we used in the previous two columns: munkiimport. munkiimport helps with importing new packages into the munki repository. It in turn calls a second tool to do most of the initial pkginfo generation. This tool is called, strangely enough, makepkginfo, and can be found at /usr/local/munki/makepkginfo. You can call it directly if you'd like. In its most common use, you give makepkginfo a package or disk image, and it outputs pkginfo. Here's an example:

% cd /usr/local/munki
% ./makepkginfo ~/Downloads/GoogleSketchUpMEN.dmg 
<?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>autoremove</key>
   <false/>
   <key>catalogs</key>
   <array>
      <string>testing</string>
   </array>
   <key>description</key>
   <string></string>
   <key>display_name</key>
   <string>Google SketchUp 8.0 (English)</string>
   <key>installed_size</key>
   <integer>105008</integer>
   <key>installer_item_hash</key>
   <string>d079e58569142aa5ac6c60617146b398a974be1b6d0aec3c270bfa3f70dbc07d</string>
   <key>installer_item_location</key>
   <string>GoogleSketchUpMEN.dmg</string>
   <key>installer_item_size</key>
   <integer>39800</integer>
   <key>minimum_os_version</key>
   <string>10.4.0</string>
   <key>name</key>
   <string>Google SketchUp 8.0 Installer</string>
   <key>receipts</key>
   <array>
      <dict>
         <key>filename</key>
         <string>Google_SU8_EN_SketchUp_Application.pkg</string>
         <key>installed_size</key>
         <integer>87812</integer>
         <key>name</key>
         <string>Google SketchUp Application</string>
         <key>packageid</key>
         <string>com.google.sketchup8.sketchup.application</string>
         <key>version</key>
         <string>8.0.3161.0.0</string>
      </dict>
      <dict>
         <key>filename</key>
         <string>Google_SU8_EN_SketchUp_Support.pkg</string>
         <key>installed_size</key>
         <integer>17192</integer>
         <key>name</key>
         <string>Google SketchUp Support</string>
         <key>packageid</key>
         <string>com.google.sketchup8.sketchup.support</string>
         <key>version</key>
         <string>8.0.3161.0.0</string>
      </dict>
      <dict>
         <key>filename</key>
         <string>Google_SU8_EN_SketchUp_Free.pkg</string>
         <key>installed_size</key>
         <integer>4</integer>
         <key>name</key>
         <string>Google SketchUp 8.0 (English) Add-Ons</string>
         <key>packageid</key>
         <string>com.google.sketchup8.sketchup_free.addons</string>
         <key>version</key>
         <string>3.0.3161.0.0</string>
      </dict>
   </array>
   <key>uninstall_method</key>
   <string>removepackages</string>
   <key>uninstallable</key>
   <true/>
   <key>version</key>
   <string>3.0.3161.0.0</string>
</dict>
</plist>

You could copy and paste this into a text editor for editing, or use shell redirection to create a text file directly; something like:

makepkginfo GoogleSketchUpMEN.dmg > GoogleSketchUp.plist

In either case, the pkginfo is probably not ideal as-is, and will need to be edited further. Let's look at some common edits.

Name

   <key>name</key>
   <string>Google SketchUp 8.0 Installer</string>

name is the most important key in a pkginfo file. It is this name that is used in manifest files to specify a package to be installed. In order for munki to find the latest version of a package, all the versions of a package must have the same name in their pkginfo item.

While you could use the name that makepkginfo pulled out of the Installer package, you probably want to remove the version number and simplify it:

   <key>name</key>
   <string>GoogleSketchUp</string>

(I remove the spaces from the name - but there's really no requirement to do this; it's just an old habit. It does make it easier to remember the name without having to check if it's "Google SketchUp" or "Google Sketch Up"…) If you later download a newer version of Google SketchUp, you'd want to make sure the name for its pkginfo was the same as the name you chose for this one).

Version

   <key>version</key>
   <string>3.0.3161.0.0</string>

version is the next most important key. This is how munki finds the most recent version (or a specific version) of a package. You'll see that the version that makepkginfo generated seems to be wrong. This is because the version of the SketchUp installer package is 3.0.3161.0.0. (Note - this is not common. Usually you'll find the installer package version more accurately reflects the version of the software it installs). You'll probably want to edit the version string to match the version of the Google SketchUp application:

   
   <key>version</key>
   <string>8.0.3161.0.0</string>

Catalogs

   <key>catalogs</key>
   <array>
      <string>testing</string>
   </array>

By default, makepkginfo puts newly generated items into a "testing" catalog. This is a useful default. You can configure a subset of your munki clients to look first in the testing catalog for package information. By placing new versions of software in the testing catalog, only your "testing" subset of machines will install the latest version. Later, when you are confident a new version won't cause issues for you, you can move it to a "production" or "release" catalog. All of your machines would be configured to check the production catalog.

   <key>catalogs</key>
   <array>
      <string>production</string>
   </array>

Note that the value of the catalogs key is an array - you can place an item in multiple catalogs. This is not used often, but one reason to do this would be to create multiple testing groups. Perhaps you are testing a new version of Firefox. You have a group of testers (for example, a group of web developers) that shouldn't get every new application, but do want newer versions of Firefox sooner than the general population. You can make this happen by creating a "firefox-testing" catalog. You create a munki catalog simply by defining at least one item as being in that catalog. So by adding "firefox-testing" to the list of catalogs for the latest version of Firefox, you cause a "firefox-testing" catalog to be created with at least one item.

   <key>catalogs</key>
   <array>
      <string>testing</string>
      <string>firefox-testing</string>
   </array>

You would also then list the "firefox-testing" catalog in the list of catalogs for your Firefox testers' manifest(s):

   <key>catalogs</key>
   <array>
      <string>firefox-testing</string>
      <string>production</string>
   </array>

This causes munki to look for items in the firefox-testing catalog first, looking in the production catalog only if no matching package is found in firefox-testing.

name and version are the most important keys, and so you should always check these and edit if needed. If you are using a testing catalog for new packages, you can often leave the catalogs key as-is, but if you want to move a package directly into production or have a special configuration, you may need to edit this key as well.

Display Name and Description

   <key>description</key>
   <string></string>
   <key>display_name</key>
   <string>Google SketchUp 8.0 (English)</string>

The display_name and description keys are optional, but help provide a better end-user experience. If provided, the display_name is used instead of the name when information is displayed to the user in Managed Software Update.app. The description, if it exists, is displayed below when a user selects an item in Managed Software Update.app.

   <key>description</key>
   <string>3D design software from Google.</string>
   <key>display_name</key>
   <string>Google SketchUp</string>

If I edit the two keys as shown above, it appears in Managed Software Update.app as in Figure 1.


Figure 1 - Google SketchUp displayed in Managed Software Update.app

For the display name, I removed the version number, since that is displayed elsewhere. The description can be as long as you'd like.

Installs Key

The pkginfo for Google SketchUp we've worked on so far is perfectly useable and functional. But there are more munki features that can be controlled by additional pkginfo edits.

Receipts vs. Installs

Our Google SketchUp pkginfo has a receipts key, listing the receipts for the Apple packages that are installed. This info can be used in two different ways. The first use for the receipts information is when removing an item. Munki uses the receipts to determine what files were installed, and compares that against all the other receipts on the machine. Any files listed in the item's receipts that are not in any other receipts are removed.

The second use for receipts is as a method to determine whether or not an item is installed, and therefore if munki should attempt to install the item. If munki has no other information, it will look for the existence of the receipts that are listed under the receipts key. If any of these are missing or an older version, the item will be installed.

But there are problems with this double duty for receipts. It is not uncommon for metapackages (packages that contain other packages) to install only a subset of the available packages, depending on the OS version and other installed software. This means that the list of receipts generated by makepkginfo may contain receipts for packages that don't actually get installed, or worse, are installed on some machines, but not others. If any receipt is missing, munki attempts to install the package. But if installing the package does not leave every receipt munki has on file, munki will continue to attempt to install the package again and again.

There are two ways to deal with this problem. The first is to edit the pkginfo, deleting any receipt for subpackages that are not actually installed. If the subpackages that don't get installed are only installed on Tiger machines, for example, and you don't have any Tiger machines, this solution may work. But if the subpackages are installed on some machines you manage and not others (perhaps due to hardware differences, or the existence of other software on the machine), this solution causes another problem. If you delete a reference to a receipt that does get installed, munki will not be able to accurately remove the item, as you have removed some of the information it needs to determine what needs to be removed.

A better solution to this problem is to provide munki with alternate information it can use to decide whether or not to install the item, leaving the receipts key to be used only for removing the item. Pkginfo files can contain an installs key, which lists items that are installed by the item. This key must exist (and is generated by default) for installer items that aren't Apple packages (like drag-n-drop disk images), as these items don't leave receipts. makepkginfo cannot (currently) automatically generate an installs key for Apple packages, but you can do so manually.

As an example of an automatically generated installs key, we can review the pkginfo items created for Firefox and Google Chrome in previous installments, as these items are distributed as drag-n-drop disk images. Here's the installs key for Firefox 3.6.13:

   <key>installs</key>
   <array>
      <dict>
         <key>CFBundleIdentifier</key>
         <string>org.mozilla.firefox</string>
         <key>CFBundleName</key>
         <string>Firefox</string>
         <key>CFBundleShortVersionString</key>
         <string>3.6.13</string>
         <key>path</key>
         <string>/Applications/Firefox.app</string>
         <key>type</key>
         <string>application</string>
      </dict>
   </array>
Here's the same key for a recent version of Google Chrome:
   <key>installs</key>
   <array>
      <dict>
         <key>CFBundleIdentifier</key>
         <string>com.google.Chrome</string>
         <key>CFBundleName</key>
         <string>Chrome</string>
         <key>CFBundleShortVersionString</key>
         <string>8.0.552.215</string>
         <key>minosversion</key>
         <string>10.5.0</string>
         <key>path</key>
         <string>/Applications/Google Chrome.app</string>
         <key>type</key>
         <string>application</string>
      </dict>
   </array>

There's a fair amount of information there. Fortunately, you don't have to generate these items by hand; makepkginfo can help you. Let's create one for Google SketchUp. First we must install Google SketchUp on a machine, so the application is at its correct path. We can then use makepkginfo with the -f flag to generate an item for the installs list:

% cd /usr/local/munki/
% ./makepkginfo -f /Applications/Google\ SketchUp\ 8/SketchUp.app
<?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>installs</key>
   <array>
      <dict>
         <key>CFBundleIdentifier</key>
         <string>com.google.sketchupfree8</string>
         <key>CFBundleName</key>
         <string>SketchUp</string>
         <key>CFBundleShortVersionString</key>
         <string>8.0.3161</string>
         <key>path</key>
         <string>/Applications/Google SketchUp 8/SketchUp.app</string>
         <key>type</key>
         <string>application</string>
      </dict>
   </array>
</dict>
</plist>

All we need is the actual installs section, which we can copy and paste into the Google SketchUp pkginfo:

   
   <key>installs</key>
   <array>
      <dict>
         <key>CFBundleIdentifier</key>
         <string>com.google.sketchupfree8</string>
         <key>CFBundleName</key>
         <string>SketchUp</string>
         <key>CFBundleShortVersionString</key>
         <string>8.0.3161</string>
         <key>path</key>
         <string>/Applications/Google SketchUp 8/SketchUp.app</string>
         <key>type</key>
         <string>application</string>
      </dict>
   </array>

This tells munki to check for the SketchUp.app inside the /Applications/Google SketchUp 8 folder, and if it exists, it must be version 8.0.3161 or later. If it doesn't exist, or is older, munki will attempt to install Google SketchUp 8.0.3161.

You may have multiple items in the installs list, and these items are not limited to applications. You can check for other bundle types, like Internet plug-ins or System Preferences panes, or Info.plist files at specific paths. These can all be compared to a specific version. You may also check for the existence of directories or files. With individual files, a checksum is generated - if the file on disk exists but its checksum doesn't match the one in the installs key, the item will be installed. For all of these file types, you can use makepkginfo -f to generate installs info.

The installs key provides a flexible and powerful way to help munki decide if a given item should be installed. It also provides for a certain amount of "self-repair". Using our prior example where we provide an installs key for our Google SketchUp item that lists the SketchUp.app application, if a privileged user were to delete the app or its entire enclosing folder, on its next run, munki would once again schedule an install of Google SketchUp.

Package Dependencies

Updates

Munki supports two kinds of package dependencies. Both require certain keys in pkginfo files. The first type of dependency allows you to mark a given package as an update for another package. We'll use Microsoft Office 2011 as an example. Our initial package is the disk image for the Office 2011 installer. Its pkginfo name is "Office2011" and its version is "14.0.0.0.0". Later, Microsoft releases an update for Office 2011. We create a new pkginfo item for the update, which we name "Office2011_Update", version "14.0.1.0.0". But we don't want to have to modify all the manifests that include Office 2011 and manually add the update to the list of managed_installs - instead, we want munki to discover and apply the update "automagically." We can do this by adding a new key - update_for - to the Office2011_Update pkginfo item.

   <key>update_for</key>
   <array>
      <string>Office2011</string>
   </array>

This key informs munki that this item is an update for Office2011. On any machine whose manifest contains "Office2011" in its managed_installs list, this update will also be considered for installation. When Microsoft releases the Office 2011 14.0.2 update, we can repeat the procedure for this update, and the next, and the next. In this way, all your managed machines with Office 2011 automatically find and install the updates.

You can also use the update_for key to mark other packages that aren't strictly updates so that they will be installed with (and removed with) another package. There are several pieces of software for which we need to install some additional files - licensing configuration files, or other site or organization-specific customizations. Instead of modifying the vendor's installer or repackaging the software, we package our additions and mark them as an update for the third-party software.

A good example of this is Firefox. Firefox is updated frequently, so it is convenient to be able to use the unmodified disk image as downloaded from Mozilla.com as the installation source. We also have some Firefox extensions we want everyone to have. By packaging these extensions separately, but marking them as updates for Firefox, any machine that gets Firefox also gets the extensions. When Firefox is removed, so are the extensions.

Requirements

Sometimes you need to ensure packages are installed in a certain order - for example, Adobe Acrobat 9 Pro has several updates that must be installed in the order they were released. If you name all the updates "AcrobatPro9Update" and you mark all them like so:

   <key>update_for</key>
   <array>
      <string>AcrobatPro9</string>
   </array>

munki will find the latest "AcrobatPro9Update" (as of this writing, 9.4.1) and try to install that. Unless the currently installed version of Acrobat Pro is 9.4.0, this is likely to fail. So you need to tell munki that before you install the 9.4.1 update, you must ensure the 9.4.0 update is installed. To do this, you use the requires key.

   <key>requires</key>
   <array>
      <string>AcrobatPro9Update-9.4.0.0.0</string>
   </array>

As it turns out, the 9.4.0 update requires the 9.3.4 update:

   <key>requires</key>
   <array>
      <string>AcrobatPro9Update-9.3.4.0.0</string>
   </array>

This update in turn requires the 9.3.3 update, which in turn requires the 9.3.2 update, and so on, all the way back to the 9.1.0 update. Manually installing all of these updates in the right order is a giant pain. Getting them all into munki and installing in the right order is also a pain, but once you've done it, munki can then do it on every machine that has or needs Acrobat Pro 9.

The requires key can also be used for other relationships: perhaps you have a tool to install that requires that the Xcode tools also be installed. You could add a requires key specifying that your tool requires Xcode, and so when someone tried to install your tool on a machine using munki, Xcode would be installed first.

More Optional pkginfo Keys

There are more optional pkginfo keys that can be useful in some situations. Here are a few.

   <key>minimum_os_version</key>
   <string>10.5.0</string>
   <key>maximum_os_version</key>
   <string>10.5.8</string>

You can use these keys to specify that a given version of a package should not be installed on an OS version lower than, say 10.5.0, or higher than, say, 10.5.8. (This is really useful with Xcode.)

   <key>supported_architectures</key>
   <array>
      <string>i386</string>
      <string>x86_64</string>
   </array>

This key can be used to limit the installation of a given version of a package to machines matching a certain processor architecture. In the above example, this would not install on a PowerPC-based machine.

Version 0.7.0 and later of munki support a few more interesting keys:

   <key>forced_install</key>
   <true/>
   <key>forced_uninstall</key>
   <true/>

These keys can be used to indicate that a package is safe to install and/or uninstall without the user's consent. A package marked with forced_install equal to true will be silently installed in the background without the user being notified. Use this carefully.

   <key>blocking_applications</key>
   <array>
        <string>Firefox</string>
      <string>Safari</string>
      <string>Opera</string>
   </array>

This key lists applications that may block the installation of a package. This key can come into effect in two circumstances. The first: a user is notified of updates to install and elects to install without logging out. In this scenario, if one or more of the applications in the blocking_applications list is open, the user is warned to quit the application(s) before being allowed to proceed.

The second scenario is in conjunction with the forced_install and forced_uninstall keys. If any application in the blocking_applications list is open, the forced operation will not be attempted for that package. The intent is to prevent munki from trying to update or remove open applications and potentially causing crashing and data loss.

Even More Keys

There are many more keys that may appear in a pkginfo item, but most of the ones not mentioned so far are automatically created by makepkginfo based on the contents of an installer item. Here are some additional keys:

installer_item_hash: a checksum of the installer item so we can verify the downloaded item matches the original item. Auto-generated.

installer_item_location: relative path to the installer item (package) inside the pkgs directory on the munki server. Auto-generated, but may need to be edited if you move or modify the path to the package.

RestartAction: does the package require a logout or restart? Auto-generated from Apple packages, but you can add or modify this and may need to for non-Apple packages. Set to "RequireLogout" if logout is needed, or "RequireRestart" if a restart is needed.

installer_type/uninstall_method: keys used by munki to determine the correct install and unsinstall methods. Auto-generated.

uninstallable: a Boolean that indicates whether or not the package is uninstallable. Auto-generated based on the installer type, but you may need to set it to false for items (especially Apple updates) that you know cannot be safely uninstalled.

Remember that when you edit a pkginfo file on the munki server, you must run makecatalogs /path/to/munki/repo to get your changes incorporated into the munki catalogs. A common mistake is to make changes and forget to run makecatalogs.

Conclusion

Whew - that's a lot of info to digest. Pkginfo files are easily the most complicated part of munki, but that's because they contain virtually all of the information munki needs to do its job. Pkginfo files contain both metadata extracted from the packages themselves, and additional information that only the munki admin can provide. munkiimport and makepkginfo can help create the pkginfo files, but you will sometimes need to manually edit these files to take advantage of all of munki's features.

That concludes (for now) our look at munki. We haven't exhausted all of munki's features, but hopefully we've covered enough for you to decide whether or not it's worth further investigation. If you'd like to continue your exploration of this set of tools, visit the munki website at http://code.google.com/p/munki, and read (or join) the munki-dev Google Group at http://groups.google.com/group/munki-dev.


Greg Neagle is a member of the steering committee of the Mac OS X Enterprise Project (macenterprise.org) and is a senior systems engineer at a large animation studio. Greg has been working with the Mac since 1984, and with OS X since its release. He can be reached at gregneagle@mac.com.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Tokkun Studio unveils alpha trailer for...
We are back on the MMORPG news train, and this time it comes from the sort of international developers Tokkun Studio. They are based in France and Japan, so it counts. Anyway, semantics aside, they have released an alpha trailer for the upcoming... | Read more »
Win a host of exclusive in-game Honor of...
To celebrate its latest Jujutsu Kaisen crossover event, Honor of Kings is offering a bounty of login and achievement rewards kicking off the holiday season early. [Read more] | Read more »
Miraibo GO comes out swinging hard as it...
Having just launched what feels like yesterday, Dreamcube Studio is wasting no time adding events to their open-world survival Miraibo GO. Abyssal Souls arrives relatively in time for the spooky season and brings with it horrifying new partners to... | Read more »
Ditch the heavy binders and high price t...
As fun as the real-world equivalent and the very old Game Boy version are, the Pokemon Trading Card games have historically been received poorly on mobile. It is a very strange and confusing trend, but one that The Pokemon Company is determined to... | Read more »
Peace amongst mobile gamers is now shatt...
Some of the crazy folk tales from gaming have undoubtedly come from the EVE universe. Stories of spying, betrayal, and epic battles have entered history, and now the franchise expands as CCP Games launches EVE Galaxy Conquest, a free-to-play 4x... | Read more »
Lord of Nazarick, the turn-based RPG bas...
Crunchyroll and A PLUS JAPAN have just confirmed that Lord of Nazarick, their turn-based RPG based on the popular OVERLORD anime, is now available for iOS and Android. Starting today at 2PM CET, fans can download the game from Google Play and the... | Read more »
Digital Extremes' recent Devstream...
If you are anything like me you are impatiently waiting for Warframe: 1999 whilst simultaneously cursing the fact Excalibur Prime is permanently Vault locked. To keep us fed during our wait, Digital Extremes hosted a Double Devstream to dish out a... | Read more »
The Frozen Canvas adds a splash of colou...
It is time to grab your gloves and layer up, as Torchlight: Infinite is diving into the frozen tundra in its sixth season. The Frozen Canvas is a colourful new update that brings a stylish flair to the Netherrealm and puts creativity in the... | Read more »
Back When AOL WAS the Internet – The Tou...
In Episode 606 of The TouchArcade Show we kick things off talking about my plans for this weekend, which has resulted in this week’s show being a bit shorter than normal. We also go over some more updates on our Patreon situation, which has been... | Read more »
Creative Assembly's latest mobile p...
The Total War series has been slowly trickling onto mobile, which is a fantastic thing because most, if not all, of them are incredibly great fun. Creative Assembly's latest to get the Feral Interactive treatment into portable form is Total War:... | Read more »

Price Scanner via MacPrices.net

Early Black Friday Deal: Apple’s newly upgrad...
Amazon has Apple 13″ MacBook Airs with M2 CPUs and 16GB of RAM on early Black Friday sale for $200 off MSRP, only $799. Their prices are the lowest currently available for these newly upgraded 13″ M2... Read more
13-inch 8GB M2 MacBook Airs for $749, $250 of...
Best Buy has Apple 13″ MacBook Airs with M2 CPUs and 8GB of RAM in stock and on sale on their online store for $250 off MSRP. Prices start at $749. Their prices are the lowest currently available for... Read more
Amazon is offering an early Black Friday $100...
Amazon is offering early Black Friday discounts on Apple’s new 2024 WiFi iPad minis ranging up to $100 off MSRP, each with free shipping. These are the lowest prices available for new minis anywhere... Read more
Price Drop! Clearance 14-inch M3 MacBook Pros...
Best Buy is offering a $500 discount on clearance 14″ M3 MacBook Pros on their online store this week with prices available starting at only $1099. Prices valid for online orders only, in-store... Read more
Apple AirPods Pro with USB-C on early Black F...
A couple of Apple retailers are offering $70 (28%) discounts on Apple’s AirPods Pro with USB-C (and hearing aid capabilities) this weekend. These are early AirPods Black Friday discounts if you’re... Read more
Price drop! 13-inch M3 MacBook Airs now avail...
With yesterday’s across-the-board MacBook Air upgrade to 16GB of RAM standard, Apple has dropped prices on clearance 13″ 8GB M3 MacBook Airs, Certified Refurbished, to a new low starting at only $829... Read more
Price drop! Apple 15-inch M3 MacBook Airs now...
With yesterday’s release of 15-inch M3 MacBook Airs with 16GB of RAM standard, Apple has dropped prices on clearance Certified Refurbished 15″ 8GB M3 MacBook Airs to a new low starting at only $999.... Read more
Apple has clearance 15-inch M2 MacBook Airs a...
Apple has clearance, Certified Refurbished, 15″ M2 MacBook Airs now available starting at $929 and ranging up to $410 off original MSRP. These are the cheapest 15″ MacBook Airs for sale today at... Read more
Apple drops prices on 13-inch M2 MacBook Airs...
Apple has dropped prices on 13″ M2 MacBook Airs to a new low of only $749 in their Certified Refurbished store. These are the cheapest M2-powered MacBooks for sale at Apple. Apple’s one-year warranty... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, now available for $679 for 8-Core CPU/7-Core GPU/256GB models. Apple’s one-year warranty is included, shipping is free, and each... Read more

Jobs Board

Seasonal Cashier - *Apple* Blossom Mall - J...
Seasonal Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Seasonal Fine Jewelry Commission Associate -...
…Fine Jewelry Commission Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) Read more
Seasonal Operations Associate - *Apple* Blo...
Seasonal Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Read more
Hair Stylist - *Apple* Blossom Mall - JCPen...
Hair Stylist - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.