Let's say you've got a package foo.pkg and you'd like to extract it to /tmp/foo to find out what's inside, you could do this:

# delete /tmp/foo, if it exists
rm -rf /tmp/foo

# extract foo.pkg
pkgutil --expand-full ~/Downloads/foo.pkg /tmp/foo/

The secret of --expand-full is that it will also decompress Payload files within the .pkg.

Rationale

It makes me nervous when a program that I want to use requires sudo (Admin) privileges to install.

Simple user programs (as opposed to Kernel drivers) should NOT require admin access to my computer.

In particular, I found that both OBS Advanced Scene Switcher (which is unsigned) and gpg require admin privileges to install, and that just goes against all reason - so I wanted to open up the pkg to figure out what kind of kool-aid they were trying to get me to drink - but it turns out neither need all the hoopla.

They, like most programs, can install into user space just fine.

Note: My kool-aid free install script for gpg is up on https://webinstall.dev/gpg.

Example GnuPG

Here's what's inside of the GnuPG package for macOS;

rm -rf /tmp/GnuPG
pkgutil --expand-full /Volumes/GnuPG\ 2.2.33/Install.pkg /tmp/GnuPG
lsd --tree /tmp/GnuPG
GnuPG
├── Distribution
└── GnuPG.pkg
   ├── Bom
   ├── PackageInfo
   ├── Payload
   │  ├── bin
   │  │  ├── convert-keyring
   │  │  ├── dirmngr
   │  │  ├── dirmngr-client
   │  │  ├── dumpsexp
   │  │  ├── gpg ⇒ gpg2
   │  │  ├── gpg-agent
   │  │  ├── gpg-connect-agent
   │  │  ├── gpg-error
   │  │  ├── gpg2
   │  │  ├── gpgconf
   │  │  ├── gpgme-json
   │  │  ├── gpgparsemail
   │  │  ├── gpgsm
   │  │  ├── gpgtar
   │  │  ├── gpgv2
   │  │  ├── hmac256
   │  │  ├── kbxutil
   │  │  ├── mpicalc
   │  │  ├── pinentry-mac.app
   │  │  │  └── Contents
   │  │  │     ├── Info.plist
   │  │  │     ├── MacOS
   │  │  │     │  └── pinentry-mac
   │  │  │     └── Resources
   │  │  │        ├── de.lproj
   │  │  │        │  └── Localizable.strings
   │  │  │        ├── en.lproj
   │  │  │        │  └── Localizable.strings
   │  │  │        ├── Icon.icns
   │  │  │        ├── Main.nib
   │  │  │        └── Pinentry.nib
   │  │  └── watchgnupg
   │  ├── etc
   │  ├── lib
   │  │  ├── libasprintf.0.dylib
   │  │  ├── libasprintf.dylib ⇒ libasprintf.0.dylib
   │  │  ├── libassuan.0.dylib
   │  │  ├── libassuan.dylib ⇒ libassuan.0.dylib
   │  │  ├── libcharset.1.dylib
   │  │  ├── libcharset.dylib ⇒ libcharset.1.dylib
   │  │  ├── libgcrypt.20.dylib
   │  │  ├── libgcrypt.dylib ⇒ libgcrypt.20.dylib
   │  │  ├── libgpg-error.0.dylib
   │  │  ├── libgpg-error.dylib ⇒ libgpg-error.0.dylib
   │  │  ├── libgpgme.11.dylib
   │  │  ├── libgpgme.dylib ⇒ libgpgme.11.dylib
   │  │  ├── libgpgmepp.6.dylib
   │  │  ├── libgpgmepp.dylib ⇒ libgpgmepp.6.dylib
   │  │  ├── libhistory.8.1.dylib
   │  │  ├── libhistory.8.dylib ⇒ libhistory.8.1.dylib
   │  │  ├── libhistory.dylib ⇒ libhistory.8.1.dylib
   │  │  ├── libiconv.2.dylib
   │  │  ├── libiconv.dylib ⇒ libiconv.2.dylib
   │  │  ├── libintl.8.dylib
   │  │  ├── libintl.dylib ⇒ libintl.8.dylib
   │  │  ├── libksba.8.dylib
   │  │  ├── libksba.dylib ⇒ libksba.8.dylib
   │  │  ├── libnpth.0.dylib
   │  │  ├── libnpth.dylib ⇒ libnpth.0.dylib
   │  │  ├── libntbtls.0.dylib
   │  │  ├── libntbtls.dylib ⇒ libntbtls.0.dylib
   │  │  ├── libreadline.8.1.dylib
   │  │  ├── libreadline.8.dylib ⇒ libreadline.8.1.dylib
   │  │  ├── libreadline.dylib ⇒ libreadline.8.1.dylib
   │  │  ├── libsqlite3.0.dylib
   │  │  ├── libsqlite3.dylib ⇒ libsqlite3.0.dylib
   │  │  ├── libtextstyle.0.dylib
   │  │  └── libtextstyle.dylib ⇒ libtextstyle.0.dylib
   │  ├── libexec
   │  │  ├── dirmngr_ldap
   │  │  ├── gpg-check-pattern
   │  │  ├── gpg-preset-passphrase
   │  │  ├── gpg-protect-tool
   │  │  ├── gpg-wks-client
   │  │  └── scdaemon
   │  └── share
   │     ├── gnupg
   │     │  ├── distsigkey.gpg
   │     │  │  └── distsigkey.gpg
   │     │  ├── help.be.txt
   │     │  ├── help.ca.txt
   │     │  ├── help.cs.txt
   │     │  ├── help.da.txt
   │     │  ├── help.de.txt
   │     │  ├── help.el.txt
   │     │  ├── help.eo.txt
   │     │  ├── help.es.txt
   │     │  ├── help.et.txt
   │     │  ├── help.fi.txt
   │     │  ├── help.fr.txt
   │     │  ├── help.gl.txt
   │     │  ├── help.hu.txt
   │     │  ├── help.id.txt
   │     │  ├── help.it.txt
   │     │  ├── help.ja.txt
   │     │  ├── help.nb.txt
   │     │  ├── help.pl.txt
   │     │  ├── help.pt.txt
   │     │  ├── help.pt_BR.txt
   │     │  ├── help.ro.txt
   │     │  ├── help.ru.txt
   │     │  ├── help.sk.txt
   │     │  ├── help.sv.txt
   │     │  ├── help.tr.txt
   │     │  ├── help.txt
   │     │  ├── help.zh_CN.txt
   │     │  ├── help.zh_TW.txt
   │     │  └── sks-keyservers.netCA.pem
   │     └── man
   │        ├── man1
   │        │  ├── autopoint.1
   │        │  ├── dirmngr-client.1
   │        │  ├── envsubst.1
   │        │  ├── gettext.1
   │        │  ├── gettextize.1
   │        │  ├── gpg-agent.1
   │        │  ├── gpg-check-pattern.1
   │        │  ├── gpg-connect-agent.1
   │        │  ├── gpg-preset-passphrase.1
   │        │  ├── gpg-wks-client.1
   │        │  ├── gpg-wks-server.1
   │        │  ├── gpg.1
   │        │  ├── gpgconf.1
   │        │  ├── gpgparsemail.1
   │        │  ├── gpgrt-config.1
   │        │  ├── gpgsm.1
   │        │  ├── gpgtar.1
   │        │  ├── gpgv.1
   │        │  ├── hmac256.1
   │        │  ├── iconv.1
   │        │  ├── msgattrib.1
   │        │  ├── msgcat.1
   │        │  ├── msgcmp.1
   │        │  ├── msgcomm.1
   │        │  ├── msgconv.1
   │        │  ├── msgen.1
   │        │  ├── msgexec.1
   │        │  ├── msgfilter.1
   │        │  ├── msgfmt.1
   │        │  ├── msggrep.1
   │        │  ├── msginit.1
   │        │  ├── msgmerge.1
   │        │  ├── msgunfmt.1
   │        │  ├── msguniq.1
   │        │  ├── ngettext.1
   │        │  ├── recode-sr-latin.1
   │        │  ├── scdaemon.1
   │        │  ├── sqlite3.1
   │        │  ├── watchgnupg.1
   │        │  └── xgettext.1
   │        ├── man3
   │        │  ├── bind_textdomain_codeset.3
   │        │  ├── bindtextdomain.3
   │        │  ├── dcgettext.3
   │        │  ├── dcngettext.3
   │        │  ├── dgettext.3
   │        │  ├── dngettext.3
   │        │  ├── gettext.3
   │        │  ├── history.3
   │        │  ├── iconv.3
   │        │  ├── iconv_close.3
   │        │  ├── iconv_open.3
   │        │  ├── iconv_open_into.3
   │        │  ├── iconvctl.3
   │        │  ├── ngettext.3
   │        │  ├── readline.3
   │        │  └── textdomain.3
   │        ├── man7
   │        │  └── gnupg.7
   │        └── man8
   │           ├── addgnupghome.8
   │           ├── applygnupgdefaults.8
   │           └── dirmngr.8
   └── Scripts
      ├── postinstall
      └── preinstall

Rather than installing to a system location, this can now be installed to ~/.local/opt/gnupg as it ought to be:

mkdir -p ~/.local/opt/gnupg/
rsync -avhP /tmp/GnuPG/GnuPG.pkg/Payload/ ~/.local/opt/gnupg/

Note: since the some assumptions about the location of certain files have been baked in at compile-time (rather than being read relative at run-time), some paths need to be added manually to ~/.gnupg/gpg-agent.conf and ~/Library/LaunchAgents/gpg-agent.plist.

That's covered in my other article at https://coolaj86.com/archive/.

Example: Scene Switcher

Here's what's the inside of SceneSwitcher.pkg (the OBS Advanced Scene Switcher (github) plugin):

rm -rf /tmp/SceneSwitcher
pkgutil --expand-full ~/Downloads/SceneSwitcher/MacOs/SceneSwitcher.pkg /tmp/SceneSwitcher
lsd --tree /tmp/SceneSwitcher
SceneSwitcher
├── Bom
├── PackageInfo
├── Payload
│  └── Library
│     └── Application Support
│        └── obs-studio
│           └── plugins
│              └── advanced-scene-switcher
│                 ├── bin
│                 │  ├── advanced-scene-switcher.so
│                 │  ├── libopencv_calib3d.4.5.3.dylib
│                 │  ├── libopencv_calib3d.4.5.dylib ⇒ libopencv_calib3d.4.5.3.dylib
│                 │  ├── libopencv_calib3d.dylib ⇒ libopencv_calib3d.4.5.dylib
│                 │  ├── libopencv_core.4.5.3.dylib
│                 │  ├── libopencv_core.4.5.dylib ⇒ libopencv_core.4.5.3.dylib
│                 │  ├── libopencv_core.dylib ⇒ libopencv_core.4.5.dylib
│                 │  ├── libopencv_features2d.4.5.3.dylib
│                 │  ├── libopencv_features2d.4.5.dylib ⇒ libopencv_features2d.4.5.3.dylib
│                 │  ├── libopencv_features2d.dylib ⇒ libopencv_features2d.4.5.dylib
│                 │  ├── libopencv_flann.4.5.3.dylib
│                 │  ├── libopencv_flann.4.5.dylib ⇒ libopencv_flann.4.5.3.dylib
│                 │  ├── libopencv_flann.dylib ⇒ libopencv_flann.4.5.dylib
│                 │  ├── libopencv_imgproc.4.5.3.dylib
│                 │  ├── libopencv_imgproc.4.5.dylib ⇒ libopencv_imgproc.4.5.3.dylib
│                 │  ├── libopencv_imgproc.dylib ⇒ libopencv_imgproc.4.5.dylib
│                 │  ├── libopencv_objdetect.4.5.3.dylib
│                 │  ├── libopencv_objdetect.4.5.dylib ⇒ libopencv_objdetect.4.5.3.dylib
│                 │  └── libopencv_objdetect.dylib ⇒ libopencv_objdetect.4.5.dylib
│                 └── data
│                    ├── locale
│                    │  ├── de-DE.ini
│                    │  ├── en-US.ini
│                    │  ├── ru-RU.ini
│                    │  └── zh-CN.ini
│                    └── res
│                       ├── cascadeClassifiers
│                       │  ├── haarcascade_eye.xml
│                       │  ├── haarcascade_eye_tree_eyeglasses.xml
│                       │  ├── haarcascade_frontalface_alt.xml
│                       │  ├── haarcascade_frontalface_alt2.xml
│                       │  ├── haarcascade_frontalface_alt_tree.xml
│                       │  ├── haarcascade_frontalface_default.xml
│                       │  ├── haarcascade_fullbody.xml
│                       │  ├── haarcascade_lefteye_2splits.xml
│                       │  ├── haarcascade_lowerbody.xml
│                       │  ├── haarcascade_profileface.xml
│                       │  ├── haarcascade_righteye_2splits.xml
│                       │  └── haarcascade_upperbody.xml
│                       └── time.svg
└── Scripts

Now, rather than require admin privileges, I can just copy the plugin into the plugins folder:

mkdir -p ~/Library/Application\ Support/obs-studio/plugins/
rsync -avhP \
    /tmp/SceneSwitcher/Payload/Library/Application\ Support/obs-studio/plugins/advanced-scene-switcher/ \
    ~/Library/Application\ Support/obs-studio/plugins/advanced-scene-switcher/

By AJ ONeal

If you loved this and want more like it, sign up!


Did I make your day?
Buy me a coffeeBuy me a coffee  

(you can learn about the bigger picture I'm working towards on my patreon page )