#!/bin/sh
# The scmtr DSC Signer installer for macOS, published at downloads.scmtr.io/install-mac.sh and run
# as the one-liner on https://www.scmtr.io/download:
#
#   curl -fsSL https://downloads.scmtr.io/install-mac.sh | sh
#   curl -fsSL https://downloads.scmtr.io/install-mac.sh | sh -s -- ~/dsc.p12   # and finish setup
#
# ## Why a script rather than "download and double-click"
#
# A browser download is tagged com.apple.quarantine, Archive Utility passes that tag to everything
# it extracts, and Gatekeeper then refuses an unsigned binary outright. curl sets no tag and tar
# propagates none, so this route never meets the problem. The tarball is unsigned and un-notarised
# and will stay that way until there is an Apple Developer ID — a Certum code-signing certificate
# signs Authenticode only, so it does nothing for macOS.
#
# ## Why it verifies before it installs
#
# Anything fetched over the network and then executed has to be checked against something fetched
# separately, or the check is theatre. SHA256SUMS is published beside the tarballs and read here
# before a single byte is unpacked.
#
# POSIX sh, not bash: this is piped into whatever /bin/sh is, and macOS's bash is ancient.
set -eu

BASE="${SCMTR_DOWNLOAD_BASE:-https://downloads.scmtr.io}"
VERSION="${SCMTR_SIGNER_VERSION:-1.1.1}"
# Matches what 0.1.0 published, and the override with it. Changing it would leave anyone who
# already ran the old one-liner with a stale binary in ~/.local/bin, still first on their PATH,
# while the new one sat somewhere they would never look.
DEST="${SCMTR_SIGN_DEST:-$HOME/.local/bin}"
P12="${1:-}"

die() { printf '\nscmtr-sign: %s\n' "$1" >&2; exit 1; }

# uname -m is the machine this shell runs on. Rosetta can make an Intel shell report x86_64 on an
# Apple Silicon Mac, which installs the Intel build — slower, but it runs, where the reverse would
# not. Guessing the other way would hand an arm64 binary to a real Intel Mac and fail outright.
case "$(uname -m)" in
  arm64) ARCH=mac-arm64 ;;
  x86_64) ARCH=mac-x64 ;;
  *) die "unsupported Mac architecture $(uname -m)" ;;
esac

TARBALL="scmtr-dsc-signer-${VERSION}-${ARCH}.tar.gz"
TMP="$(mktemp -d)"
# Leaving a half-downloaded binary in /tmp after a failed install is how someone ends up running
# one later by hand.
trap 'rm -rf "$TMP"' EXIT INT TERM

printf 'scmtr DSC Signer %s — downloading %s\n' "$VERSION" "$ARCH"
curl -fsSL "$BASE/$TARBALL" -o "$TMP/$TARBALL" || die "could not download $BASE/$TARBALL"
curl -fsSL "$BASE/SHA256SUMS" -o "$TMP/SHA256SUMS" || die "could not download $BASE/SHA256SUMS"

printf 'verifying checksum\n'
EXPECTED="$(awk -v f="$TARBALL" '$2 == f || $2 == "*" f { print $1 }' "$TMP/SHA256SUMS")"
[ -n "$EXPECTED" ] || die "$TARBALL is not listed in SHA256SUMS — do not use this download"
ACTUAL="$(shasum -a 256 "$TMP/$TARBALL" | awk '{print $1}')"
[ "$EXPECTED" = "$ACTUAL" ] || die "checksum mismatch for $TARBALL
  expected $EXPECTED
  actual   $ACTUAL
Do not use this download."

printf 'installing to %s\n' "$DEST"
mkdir -p "$DEST"
# tar, never Archive Utility — see the quarantine note above.
tar -xzf "$TMP/$TARBALL" -C "$TMP"
[ -f "$TMP/scmtr-sign" ] || die "archive did not contain scmtr-sign"
# The mode is set here rather than trusted from the archive: the release is built on Windows, where
# the executable bit is not a thing the filesystem has to lose in the first place.
chmod 0755 "$TMP/scmtr-sign"
mv -f "$TMP/scmtr-sign" "$DEST/scmtr-sign"

# The signer needs a DSC to serve. On macOS that is a PKCS#12 file (a Mac has no Windows
# certificate store), and only the filer knows where theirs is — so without one the LaunchAgent is
# deliberately NOT installed. Writing a plist that points at a placeholder and loading it would
# give KeepAlive a process that exits immediately, i.e. a crash loop at every login, reported
# nowhere the filer looks.
if [ -z "$P12" ]; then
  cat <<EOF

Installed: $DEST/scmtr-sign

It is not running yet — it needs your DSC. Export it as a .p12, then run:

  curl -fsSL $BASE/install-mac.sh | sh -s -- /path/to/your-dsc.p12

or start it by hand:

  $DEST/scmtr-sign -pkcs12 /path/to/your-dsc.p12
EOF
  exit 0
fi

[ -f "$P12" ] || die "no such file: $P12"

# Resolve to an absolute path before it goes anywhere near the plist. The test above passes for a
# relative path because it resolves against the shell's working directory; launchd's is not that
# one, so a plist holding "dsc.p12" points at nothing. It loads without complaint -- launchd does
# not check the path -- and then KeepAlive restarts the failure at every login forever.
P12="$(cd "$(dirname "$P12")" && pwd)/$(basename "$P12")"

# A DSC exported for macOS is password-protected: the CCA-India tooling will not export one
# without a password, so the empty default is wrong for every real filer. Without it the signer
# exits on "decrypt: incorrect password" the moment launchd starts it, KeepAlive restarts it, and
# the crash loop is invisible -- this script would have printed "Installed and running" over it.
#
# Read from the terminal, not taken as an argument, so it stays out of shell history and the
# process list. `< /dev/tty` because stdin here is the pipe from curl.
printf 'Password for %s (leave blank if it has none): ' "$(basename "$P12")"
stty -echo 2>/dev/null || true
read -r P12PASS < /dev/tty || P12PASS=""
stty echo 2>/dev/null || true
printf '\n'

PLIST="$HOME/Library/LaunchAgents/io.scmtr.sign.plist"
mkdir -p "$HOME/Library/LaunchAgents"

# Escaped, because these are paths a filer chose. An ampersand in a folder name -- "Documents &
# Downloads" -- is enough to produce malformed XML that launchctl refuses, and the failure names
# the LaunchAgent rather than the character.
xml_escape() {
	printf '%s' "$1" | sed -e 's/&/\&amp;/g' -e 's/</\&lt;/g' -e 's/>/\&gt;/g' -e 's/"/\&quot;/g' -e "s/'/\&apos;/g"
}

cat > "$PLIST" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
  <key>Label</key><string>io.scmtr.sign</string>
  <key>ProgramArguments</key>
  <array>
    <string>$(xml_escape "$DEST/scmtr-sign")</string>
    <string>-pkcs12</string>
    <string>$(xml_escape "$P12")</string>
  </array>
  <key>EnvironmentVariables</key>
  <dict>
    <key>SCMTR_SIGN_P12_PASSWORD</key><string>$(xml_escape "$P12PASS")</string>
  </dict>
  <key>RunAtLoad</key><true/>
  <key>KeepAlive</key><true/>
  <key>StandardErrorPath</key><string>/tmp/scmtr-sign.err.log</string>
</dict>
</plist>
EOF
# The plist now holds the DSC password, so it is readable only by its owner. launchd does not care
# about the mode; every other process on the machine does.
chmod 600 "$PLIST"
# unload first so re-running this upgrades rather than failing on an already-loaded label.
launchctl unload "$PLIST" 2>/dev/null || true
launchctl load "$PLIST" || die "could not load the LaunchAgent; see /tmp/scmtr-sign.err.log"

# Confirm it is actually serving rather than announcing that it is. KeepAlive makes a signer that
# cannot read its DSC indistinguishable from a working one at this point -- launchctl load succeeds
# either way, and the process restarts too fast to be obviously absent -- so the only honest check
# is to ask it. A wrong password, an unreadable file or a missing key all end up here.
printf 'Checking it started'
i=0
while [ $i -lt 10 ]; do
	if curl -fsS --max-time 2 http://127.0.0.1:13913/health 2>/dev/null | grep -q '"host":"scmtr-sign"'; then
		printf '\n\nInstalled and running. Log: /tmp/scmtr-sign.err.log\n'
		exit 0
	fi
	printf '.'
	sleep 1
	i=$((i + 1))
done

printf '\n'
die "installed, but it is not answering on 127.0.0.1:13913.
The usual cause is a wrong password for $(basename "$P12").
Last lines of /tmp/scmtr-sign.err.log:
$(tail -n 3 /tmp/scmtr-sign.err.log 2>/dev/null || echo '  (no log yet)')

Stop the restart loop and try again with:
  launchctl unload $PLIST"
