Mac equivalent of ninite

jwojda
Valued Contributor II

Saw this on reddit today- mac apps.link

8 REPLIES 8

RaulSantos
Contributor

http://www.getmacapps.com/

rcorbin
Contributor II

There was another thread opened on this today as well.

https://jamfnation.jamfsoftware.com/discussion.html?id=15382

It all looks interesting but it looks super risky.

jarednichols
Honored Contributor

Sure, let's use installers from untrusted sources. What could possibly go wrong?

mark_mahabir
Valued Contributor

I used to find Google Pack pretty useful.

alexjdale
Valued Contributor III

Ugh, I don't know who would choose to save a little time by introducing risk this way. Nobody here, I hope. :-)

tlarkin
Honored Contributor

Hi Everyone,

I just wanted to chime and state the obvious here. Pipping a curl or wget command into a shell can be very dangerous. Even though you may not be running the command as root or with sudo privileges there have been exploits out there like root pipe that can escalate themselves to root. This is a pretty good way to get your Mac rooted, maybe a Trojan Horse installed, etc.

I actually looked at this yesterday. I used curl to download the Chrome installer script in memory on my VM and looked at it, here it is (I had tossed it in paste bin):

#/bin/bash
clear && rm -rf ~/macapps && mkdir ~/macapps > /dev/null && cd ~/macapps

###############################
#    Print script header      #
###############################
echo $"

███╗   ███╗ █████╗  ██████╗ █████╗ ██████╗ ██████╗ ███████╗
████╗ ████║██╔══██╗██╔════╝██╔══██╗██╔══██╗██╔══██╗██╔════╝
██╔████╔██║███████║██║     ███████║██████╔╝██████╔╝███████╗
██║╚██╔╝██║██╔══██║██║     ██╔══██║██╔═══╝ ██╔═══╝ ╚════██║
██║ ╚═╝ ██║██║  ██║╚██████╗██║  ██║██║     ██║     ███████║╔═════════╗
╚═╝     ╚═╝╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝╚═╝     ╚═╝     ╚══════╝╚═ .link ═╝
"


###############################
#    Define worker functions  #
###############################
versionChecker() {
        local v1=$1; local v2=$2;
        while [ `echo $v1 | egrep -c [^0123456789.]` -gt 0 ]; do
                char=`echo $v1 | sed 's/.*([^0123456789.]).*/1/'`; char_dec=`echo -n "$char" | od -b | head -1 | awk {'print $2'}`; v1=`echo $v1 | sed "s/$char/.$char_dec/g"`; done
        while [ `echo $v2 | egrep -c [^0123456789.]` -gt 0 ]; do
                char=`echo $v2 | sed 's/.*([^0123456789.]).*/1/'`; char_dec=`echo -n "$char" | od -b | head -1 | awk {'print $2'}`; v2=`echo $v2 | sed "s/$char/.$char_dec/g"`; done
        v1=`echo $v1 | sed 's/../.0/g'`; v2=`echo $v2 | sed 's/../.0/g'`;
        checkVersion "$v1" "$v2"
}

checkVersion() {
        [ "$1" == "$2" ] && return 1
        v1f=`echo $1 | cut -d "." -f -1`;v1b=`echo $1 | cut -d "." -f 2-`;v2f=`echo $2 | cut -d "." -f -1`;v2b=`echo $2 | cut -d "." -f 2-`;
        if [[ "$v1f" != "$1" ]] || [[ "$v2f" != "$2" ]]; then [[ "$v1f" -gt "$v2f" ]] && return 1; [[ "$v1f" -lt "$v2f" ]] && return 0;
                [[ "$v1f" == "$1" ]] || [[ -z "$v1b" ]] && v1b=0; [[ "$v2f" == "$2" ]] || [[ -z "$v2b" ]] && v2b=0; checkVersion "$v1b" "$v2b"; return $?
        else [ "$1" -gt "$2" ] && return 1 || return 0; fi
}

appStatus() {
  if [ ! -d "/Applications/$1" ]; then echo "uninstalled"; else
    if [[ $5 == "build" ]]; then BUNDLE="CFBundleVersion"; else BUNDLE="CFBundleShortVersionString"; fi
    INSTALLED=`/usr/libexec/plistbuddy -c Print:$BUNDLE: "/Applications/$1/Contents/Info.plist"`
      if [ $4 == "dmg" ]; then COMPARETO=`/usr/libexec/plistbuddy -c Print:$BUNDLE: "/Volumes/$2/$1/Contents/Info.plist"`;
      elif [[ $4 == "zip" || $4 == "tar" ]]; then COMPARETO=`/usr/libexec/plistbuddy -c Print:$BUNDLE: "$3$1/Contents/Info.plist"`; fi
    checkVersion "$INSTALLED" "$COMPARETO"; UPDATED=$?;
    if [[ $UPDATED == 1 ]]; then echo "updated"; else echo "outdated"; fi; fi
}
installApp() {
  echo $'360237214200  - ['$2'] Downloading app...'
  if [ $1 == "dmg" ]; then curl -s -L -o "$2.dmg" $4; yes | hdiutil mount -nobrowse "$2.dmg" -mountpoint "/Volumes/$2" > /dev/null;
    if [[ $(appStatus "$3" "$2" "" "dmg" "$7") == "updated" ]]; then echo $'342235214  - ['$2'] Skipped because it was already up to date!
';
    elif [[ $(appStatus "$3" "$2" "" "dmg" "$7") == "outdated" && $6 != "noupdate" ]]; then ditto "/Volumes/$2/$3" "/Applications/$3"; echo $'360237214216  - ['$2'] Successfully updated!
'
    elif [[ $(appStatus "$3" "$2" "" "dmg" "$7") == "outdated" && $6 == "noupdate" ]]; then echo $'342235214  - ['$2'] This app cant be updated!
'
    elif [[ $(appStatus "$3" "$2" "" "dmg" "$7") == "uninstalled" ]]; then cp -R "/Volumes/$2/$3" /Applications; echo $'360237221215  - ['$2'] Succesfully installed!
'; fi
    hdiutil unmount "/Volumes/$2" > /dev/null && rm "$2.dmg"
  elif [ $1 == "zip" ]; then curl -s -L -o "$2.zip" $4; unzip -qq "$2.zip";
    if [[ $(appStatus "$3" "" "$5" "zip" "$7") == "updated" ]]; then echo $'342235214  - ['$2'] Skipped because it was already up to date!
';
    elif [[ $(appStatus "$3" "" "$5" "zip" "$7") == "outdated" && $6 != "noupdate" ]]; then ditto "$5$3" "/Applications/$3"; echo $'360237214216  - ['$2'] Successfully updated!
'
    elif [[ $(appStatus "$3" "" "$5" "zip" "$7") == "outdated" && $6 == "noupdate" ]]; then echo $'342235214  - ['$2'] This app cant be updated!
'
    elif [[ $(appStatus "$3" "" "$5" "zip" "$7") == "uninstalled" ]]; then mv "$5$3" /Applications; echo $'360237221215  - ['$2'] Succesfully installed!
'; fi;
    rm -rf "$2.zip" && rm -rf "$5" && rm -rf "$3"
  elif [ $1 == "tar" ]; then curl -s -L -o "$2.tar.bz2" $4; tar -zxf "$2.tar.bz2" > /dev/null;
    if [[ $(appStatus "$3" "" "$5" "tar" "$7") == "updated" ]]; then echo $'342235214  - ['$2'] Skipped because it was already up to date!
';
    elif [[ $(appStatus "$3" "" "$5" "tar" "$7") == "outdated" && $6 != "noupdate" ]]; then ditto "$3" "/Applications/$3"; echo $'360237214216  - ['$2'] Successfully updated!
';
    elif [[ $(appStatus "$3" "" "$5" "tar" "$7") == "outdated" && $6 == "noupdate" ]]; then echo $'342235214  - ['$2'] This app cant be updated!
'
    elif [[ $(appStatus "$3" "" "$5" "tar" "$7") == "uninstalled" ]]; then mv "$5$3" /Applications; echo $'360237221215  - ['$2'] Succesfully installed!
'; fi
    rm -rf "$2.tar.bz2" && rm -rf "$3"; fi
}

###############################
#    Install selected apps    #
###############################
installApp "dmg" "Chrome" "Google Chrome.app" "https://dl.google.com/chrome/mac/stable/GGRO/googlechrome.dmg" "" "" ""

###############################
#    Print script footer      #
###############################
echo $'------------------------------------------------------------------------------'
echo $'360237222254  - Thank you for using macapps.link! Liked it? Recommend us to your friends!'
echo $'------------------------------------------------------------------------------
'

So the author of the script is basically passing 7 arguments to a function called installApp(). As far as I can tell it is nothing out of the ordinary and the script is literally just using curl to download the Chrome DMG, mount it, and file copy it over to the /Applications folder.

I strongly suggest that if you want to use a method like this you vet and parse the scripts that this site relies on, and you read all the code. Then simply use it to write your own methods. There looks to be a bunch of logic in that function to look for tar.gz files, PKG installers, or DMG disk images, and I am willing to take a guess that the author reuses that same function in all the scripts but just changes the arguments passed to them and the URL of where to download the App from.

There are other methods to automate package updates like AutoPKG which is not the same exact workflow as just pipping script into the shell you download from the Internet, but the community at least vets all the AutoPKG recipes.

So bottom line is, be extremely careful with things like this, and if you want to go this route I suggest writing your own code that you know is not malicious.

Cheers
Tom

mm2270
Legendary Contributor III
So bottom line is, be extremely careful with things like this, and if you want to go this route I suggest writing your own code that you know is not malicious.

Yes, this is more or less what I have done with one of my scripts. Although the code above from this tool looks very different from mine, the general idea is similar in practice. At least since I wrote it, I know whats in it and what its doing.
Also, all those relative home paths (~) in the script aren't going to work too well if you put this script into a Casper Suite policy.

In the end, better to use Autopkg/Autopkgr, or roll your own solution if that's more your style.

tlarkin
Honored Contributor
Yes, this is more or less what I have done with one of my scripts. Although the code above from this tool looks very different from mine, the general idea is similar in practice. At least since I wrote it, I know whats in it and what its doing. Also, all those relative home paths (~) in the script aren't going to work too well if you put this script into a Casper Suite policy. In the end, better to use Autopkg/Autopkgr, or roll your own solution if that's more your style.

That is a problem I find common with bash. Everyone has their own writing style and there are lots of things you can do with bash. The author of this script wrote what looks to me, as a reusable function to pass arguments to, so they can recycle code to download and install Apps. I definitely would not have written it that way either because my coding style/preference is different.

There is also no redundancy in any of those scripts, if something fails it doesn't retry, or even verify that the download completed and installed. At least with a AutoPKG workflow you have a tangible package that you can test and verify and you know it will work. Plus if it fails for whatever reason you can just have it reinstall the PKG pretty easily with Casper or Self Service. With this workflow I feel that a number of failure points are introduced and they will be impossible to track or easily remedy if say curl fails, or the DMG fails to mount, or the end user lets their device go to sleep or shuts the lid on their laptop, etc.