Current directory assumption
You are assuming that the script is executed in the correct user's home directory. If not, then you might be writing to inappropriate .bash_profile
file.
It's good practice to write temporary files to $TMPDIR
rather than to the current directory.
Error handling
If an error occurs (e.g. "unknown OS version" or download failure), the script blithely attempts to continue.
If a checksum failure occurs, then it exits silently with status code 0.
OS version switch
Instead of inferring the OS version based on the Darwin kernel release number, it would be simpler to read the version of Mac OS using sw_vers -productVersion
.
Note that for versions ≤ 10.5, the MacPorts installer is distributed as a .dmg rather than a .pkg. You probably shouldn't try to support those ancient releases.
I am dissatisfied with the entire strategy for determining the URL from which to download the package:
- You download
common.inc
, which contains PHP code.
- Then, you "parse" the PHP using Bash to extract the MacPorts release number (
mpvers
).
- To construct the package URL, you reproduce the logic of
common.inc
in Bash. In particular, you hard-code strings like uridist=https://distfiles.macports.org/MacPorts
and mppkg=MacPorts-${mpvers}-${osvers}-${osname}
.
In my opinion, this is a fragile approach. Why not use PHP to interpret common.inc
? Mac OS comes installed with a PHP interpreter.
To be fair, executing common.inc
as PHP code also carries a risk: we would be making an assumption that it has no nasty side-effects. By convention, the .inc
extension implies that it should be side-effect free. In general, executing network code is a bad idea, but it's mitigated by the fact that it is served over HTTPS, and that you intend to trust the MacPorts developers anyway.
Code organization
For scripts of this complexity, it is a good idea to define functions. You would be better able to see the big picture of what the code does without wading through the whole thing from top to bottom. I've done it in my PHP solution below, but the same principle would apply to Bash.
Suggested solution
Consider this alternate implementation in PHP. It's longer than yours, but much of the excess is due to error handling, which your script lacked.
#!/usr/bin/php -d allow_url_include=1 -d error_reporting=~EWARNING
<?php
# Need to set allow_url_include=1 to allow this include over the network.
# Need to set error_reporting=~EWARNING to suppress a warning when common.inc
# tries to read a non-existant file /var/www/script_data.
require 'https://svn.macports.org/repository/macports/trunk/www/includes/common.inc';
function pkgUrl() {
$osVersion = `sw_vers -productVersion`;
switch(preg_replace('/\.\d+\s*$/', '', $osVersion)) {
# Uses globals from common.inc
case '10.6': return $GLOBALS['snowleopard_pkg'];
case '10.7': return $GLOBALS['lion_pkg'];
case '10.9': return $GLOBALS['mavericks_pkg'];
case '10.10': return $GLOBALS['yosemite_pkg'];
case '10.11': return $GLOBALS['elcapitan_pkg'];
default: throw new Exception("Unsupported OS version $osVersion");
}
}
function download($url) {
$filename = $_ENV['TMPDIR'] . '/' . basename(parse_url($url, PHP_URL_PATH));
if (FALSE === file_put_contents($filename, file_get_contents($url))) {
throw new Exception("Failed to download $url");
}
return $filename;
}
function verifyChecksum($filename) {
# Uses $checksums from common.inc
$correctChecksums = file_get_contents($GLOBALS['checksums']);
$sha256 = hash_file('sha256', $filename);
if (FALSE === strstr($correctChecksums, $sha256)) {
throw new Exception("Incorrect checksum for $filename");
}
}
function appendIfNoSuchLine($filename, $line) {
if (FALSE !== strstr($line . "\n", file_get_contents($filename))) {
return;
}
if (!($fp = fopen($filename, 'a'))) {
throw new Exception("Could not write to $filename");
}
fwrite($fp, $line . "\n");
fclose($fp);
}
try {
$pkg = download(pkgUrl());
verifyChecksum($pkg);
system(
sprintf('sudo installer -verboseR -allowUntrusted -pkg %s -target /', escapeshellarg($pkg)),
$status
);
if (0 != $status) {
throw new Exception("Installation failure");
}
unlink($filename);
appendIfNoSuchLine(
$_ENV['HOME'] . '/.bash_profile',
'export PATH="/opt/local/bin:/opt/local/sbin:$PATH"'
);
} catch (Exception $e) {
echo $e->getMessage(), "\n";
return 1;
}
-allowUntrusted
tosudo installer
. That creates a security hole and is completely unnecessary. The MacPorts installers are signed and do not require this flag. \$\endgroup\$