#!/bin/sh # macMAKE per-user Linux installer. # Copyright © 2026 MPLS LLC. All rights reserved. # The approved v0.1.1 archive is pinned below by its immutable release identity and SHA-256. set -eu umask 077 PROGRAM=${0##*/} DEFAULT_VERSION=0.1.1 RELEASE_BASE_URL='https://github.com/mplsllc/macMAKE/releases/download' PUBLIC_RELEASE_ACTIVE='yes' UMAMI_WEBSITE_ID='5947c982-135a-415d-a7b5-7bbf6317a2b1' UMAMI_SEND_URL='https://stats.mp.ls/api/send' usage() { cat <<'EOF' Install macMAKE for the current Linux user. Usage: sh install.sh [--version VERSION] [--prefix PATH] [--with-sdk] [--non-interactive] [--no-analytics] sh install.sh --help Options: --version VERSION Install an explicitly selected approved version. --prefix PATH Installation prefix (default: ~/.local). --with-sdk Also fetch the versioned minimal PowerPC C SDK subset; use only if authorized. --non-interactive Run without prompts (the installer does not prompt). --no-analytics Do not send a successful-install event to Umami at stats.mp.ls. -h, --help Show this help. The installer supports Linux x86-64 only. It downloads a versioned release over HTTPS and verifies the archive against a checksum pinned in this script. The selected archive is downloaded from its versioned GitHub Release and checked against the SHA-256 pinned in this script before extraction or installation. By default, the installer installs macMAKE only. `--with-sdk` explicitly fetches the versioned minimal PowerPC C SDK subset through `macmake sdk fetch --minimal`, then places the MSL archives beside the bundled first-app example. The fetched files are separate historical SDK material, not part of the macMAKE archive. The SDK is not automatically registered; the installer prints the exact `macmake sdk add` command after installation. After a successful installation, the installer sends one best-effort `installer-completed` event to the Umami service at stats.mp.ls with the macMAKE version. The service receives ordinary request metadata such as the connecting IP address and User-Agent. The event does not add a distinct ID, username, installer machine hostname, installation path, project files, or SDK contents. Repeated installs count as repeated events. Set MACMAKE_DISABLE_ANALYTICS=1 or pass --no-analytics to opt out. The event is sent only after installation succeeds, and reporting failures are ignored. EOF } fail() { printf '%s: error: %s\n' "$PROGRAM" "$*" >&2 exit 1 } version=$DEFAULT_VERSION prefix=${HOME:-}/.local non_interactive=no with_sdk=no analytics_enabled=yes if [ "${MACMAKE_DISABLE_ANALYTICS:-0}" = 1 ]; then analytics_enabled=no fi while [ "$#" -gt 0 ]; do case "$1" in --version) [ "$#" -ge 2 ] || fail '--version requires a version; see --help' version=$2 shift 2 ;; --prefix) [ "$#" -ge 2 ] || fail '--prefix requires a path; see --help' prefix=$2 shift 2 ;; --non-interactive) non_interactive=yes shift ;; --with-sdk) with_sdk=yes shift ;; --no-analytics) analytics_enabled=no shift ;; -h|--help) usage exit 0 ;; --) shift [ "$#" -eq 0 ] || fail "unexpected argument: $1" ;; -* ) fail "unknown option: $1; see --help" ;; * ) fail "unexpected argument: $1; see --help" ;; esac done : "${non_interactive:-no}" case "$version" in *[!A-Za-z0-9.+-]*|'') fail "invalid version: $version" ;; esac release_tag= expected_sha256= case "$version" in 0.1.1) release_tag='v0.1.1' # Frozen public v0.1.1 archive SHA-256. expected_sha256='d83c173d4184a67738df3bad7d0cadb2b6eab45fa2cc06df89f9f7eff4bd0656' ;; 0.1.0) release_tag='v0.1.0' # Frozen public v0.1.0 archive SHA-256. expected_sha256='3c9da84e53022d16a07208d0591a9964b8399dbe043b3628e227373a6aa6e980' ;; *) fail "version $version is not present in this installer's approved release map" ;; esac if [ -z "$expected_sha256" ]; then fail "macMAKE $version has no approved archive checksum; no files were changed. See --help" fi case "$expected_sha256" in *[!0-9a-f]*|'') fail 'installer release map has an invalid pinned SHA-256 value' ;; esac [ "${#expected_sha256}" -eq 64 ] || fail 'installer release map has an invalid pinned SHA-256 length' os_name=$(uname -s 2>/dev/null || true) machine=$(uname -m 2>/dev/null || true) [ "$os_name" = Linux ] || fail "unsupported operating system '$os_name'; macMAKE v0.1 supports Linux x86-64" case "$machine" in x86_64|amd64) ;; *) fail "unsupported CPU architecture '$machine'; macMAKE v0.1 supports Linux x86-64" ;; esac [ "$PUBLIC_RELEASE_ACTIVE" = yes ] || fail "the one-command installer is temporarily disabled pending release activation; no files were changed. Use the verified manual archive from the Downloads page." for utility in curl sha256sum python3 mktemp mv ln readlink grep sed stat id mkdir rm chmod cat; do command -v "$utility" >/dev/null 2>&1 || fail "required utility '$utility' was not found; install it with your Linux distribution's package manager and retry" done if [ "$with_sdk" = yes ]; then for utility in git bash install; do command -v "$utility" >/dev/null 2>&1 || fail "--with-sdk requires '$utility'; install it with your Linux distribution's package manager and retry" done fi python3 -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 8) else 1)' || fail 'Python 3.8 or newer is required for safe archive validation and extraction' [ -n "$prefix" ] || fail '--prefix must not be empty' case "$prefix" in *' '*) fail '--prefix must not contain a newline' ;; esac case "$prefix" in /*) ;; *) prefix=$PWD/$prefix ;; esac archive_name=macmake-v${version}-linux-x86_64.tar.xz archive_url=$RELEASE_BASE_URL/$release_tag/$archive_name work_dir=$(mktemp -d "${TMPDIR:-/tmp}/macmake-install.XXXXXXXX") || fail 'could not create a private temporary directory' stage_dir= new_release=no current_changed=no transaction_complete=no data_dir= current_path= release_path= wrapper_tmp= current_tmp= libs_tmp= analytics_state_tmp= analytics_state_path= analytics_marker_created=no sdk_root= universal_interfaces_path= old_current_exists=no old_current_target= # The cleanup function is invoked indirectly by the EXIT trap. # shellcheck disable=SC2317 cleanup() { cleanup_status=$? trap - EXIT HUP INT TERM if [ "$cleanup_status" -ne 0 ] && [ "$transaction_complete" != yes ]; then if [ "$current_changed" = yes ] && [ -n "$current_path" ]; then if [ "$old_current_exists" = yes ]; then rollback_link=$(mktemp "$data_dir/.current-rollback.XXXXXXXX" 2>/dev/null || true) if [ -n "$rollback_link" ]; then rm -f "$rollback_link" 2>/dev/null || : if ln -s "$old_current_target" "$rollback_link" 2>/dev/null; then mv -Tf "$rollback_link" "$current_path" 2>/dev/null || : fi rm -f "$rollback_link" 2>/dev/null || : fi else rm -f "$current_path" 2>/dev/null || : fi fi if [ "$analytics_marker_created" = yes ] && [ -n "$analytics_state_path" ]; then rm -f "$analytics_state_path" 2>/dev/null || : fi if [ "$new_release" = yes ] && [ -n "$release_path" ] && [ -f "$release_path/.macmake-install" ]; then if grep -Fqx "version=$version" "$release_path/.macmake-install" && grep -Fqx "sha256=$expected_sha256" "$release_path/.macmake-install"; then rm -rf "$release_path" 2>/dev/null || : fi fi fi if [ -n "$stage_dir" ]; then rm -rf "$stage_dir" 2>/dev/null || :; fi if [ -n "$work_dir" ]; then rm -rf "$work_dir" 2>/dev/null || :; fi if [ -n "$wrapper_tmp" ]; then rm -f "$wrapper_tmp" 2>/dev/null || :; fi if [ -n "$current_tmp" ]; then rm -f "$current_tmp" 2>/dev/null || :; fi if [ -n "$libs_tmp" ]; then rm -rf "$libs_tmp" 2>/dev/null || :; fi if [ -n "$analytics_state_tmp" ]; then rm -f "$analytics_state_tmp" 2>/dev/null || :; fi exit "$cleanup_status" } trap cleanup EXIT trap 'exit 129' HUP trap 'exit 130' INT trap 'exit 143' TERM archive_path=$work_dir/$archive_name printf 'Downloading macMAKE %s for Linux x86-64...\n' "$version" curl --fail --location --silent --show-error \ --proto '=https' --proto-redir '=https' --tlsv1.2 \ --output "$archive_path" "$archive_url" || fail 'download failed or was interrupted; no installed version was changed' printf '%s %s\n' "$expected_sha256" "$archive_path" > "$work_dir/expected.sha256" (cd "$work_dir" && sha256sum --check --status expected.sha256) || fail 'download checksum does not match the pinned release checksum; archive rejected' mkdir -p "$prefix" || fail "cannot create installation prefix: $prefix" prefix=$(cd -P "$prefix" && pwd) || fail 'cannot resolve installation prefix' [ -w "$prefix" ] || fail "installation prefix is not writable by this user: $prefix" prefix_owner=$(stat -c '%u' "$prefix") || fail 'cannot inspect installation prefix ownership' current_uid=$(id -u) || fail 'cannot determine current user id' [ "$prefix_owner" = "$current_uid" ] || fail "installation prefix must be owned by the current user: $prefix" bin_dir=$prefix/bin data_dir=$prefix/share/macmake releases_dir=$data_dir/releases current_path=$data_dir/current analytics_state_path=$data_dir/analytics-disabled release_path=$releases_dir/$version [ ! -L "$bin_dir" ] || fail "refusing to install through a symlinked bin directory: $bin_dir" [ ! -L "$prefix/share" ] || fail "refusing to install through a symlinked share directory: $prefix/share" [ ! -L "$data_dir" ] || fail "refusing to install through a symlinked data directory: $data_dir" mkdir -p "$bin_dir" "$data_dir" || fail "cannot create macMAKE directories under $prefix" if [ -e "$analytics_state_path" ] || [ -L "$analytics_state_path" ]; then [ -f "$analytics_state_path" ] && [ ! -L "$analytics_state_path" ] \ || fail "analytics preference path is not a regular file: $analytics_state_path" fi if [ -f "$analytics_state_path" ]; then analytics_enabled=no fi if [ "$analytics_enabled" = yes ]; then printf 'After success, an installer-completed event with the release version is sent to Umami at stats.mp.ls; normal HTTP IP and User-Agent metadata are visible to the service. Use --no-analytics or MACMAKE_DISABLE_ANALYTICS=1 to opt out.\n' else printf 'Installer completion statistics are disabled.\n' fi for managed_dir in "$bin_dir" "$prefix/share" "$data_dir"; do if [ ! -d "$managed_dir" ] || [ -L "$managed_dir" ]; then fail "not a regular installation directory: $managed_dir" fi managed_owner=$(stat -c '%u' "$managed_dir") || fail "cannot inspect directory ownership: $managed_dir" [ "$managed_owner" = "$current_uid" ] || fail "installation directory must be owned by the current user: $managed_dir" if [ ! -w "$managed_dir" ] || [ ! -x "$managed_dir" ]; then fail "installation directory is not writable by this user: $managed_dir" fi done [ ! -L "$releases_dir" ] || fail "refusing to install through a symlinked releases directory: $releases_dir" mkdir -p "$releases_dir" || fail "cannot create release directory: $releases_dir" releases_owner=$(stat -c '%u' "$releases_dir") || fail 'cannot inspect releases directory ownership' [ "$releases_owner" = "$current_uid" ] || fail 'releases directory must be owned by the current user' wrapper_path=$bin_dir/macmake if [ -e "$wrapper_path" ] || [ -L "$wrapper_path" ]; then if [ ! -f "$wrapper_path" ] || [ -L "$wrapper_path" ]; then fail "refusing to replace a non-regular PATH entry: $wrapper_path" fi wrapper_marker=$(sed -n '2p' "$wrapper_path") [ "$wrapper_marker" = '# macMAKE-managed-wrapper-v1' ] || fail "refusing to replace an unmanaged file at $wrapper_path" fi if [ -e "$current_path" ] || [ -L "$current_path" ]; then [ -L "$current_path" ] || fail "refusing to replace a non-symlink active path: $current_path" old_current_target=$(readlink "$current_path") || fail "cannot read active release link: $current_path" case "$old_current_target" in releases/*) ;; *) fail "active release link is not a macMAKE-managed relative path: $old_current_target" ;; esac old_current_version=${old_current_target#releases/} case "$old_current_version" in ''|*[!A-Za-z0-9.+-]*) fail "active release link has an invalid version path: $old_current_target" ;; esac old_release=$data_dir/$old_current_target if [ ! -d "$old_release" ] || [ -L "$old_release" ]; then fail 'the current release link does not point to a regular installed release' fi if [ ! -f "$old_release/.macmake-install" ] || [ -L "$old_release/.macmake-install" ]; then fail 'the current release has no regular macMAKE installer provenance marker' fi old_current_exists=yes fi if [ -e "$release_path" ] || [ -L "$release_path" ]; then if [ ! -d "$release_path" ] || [ -L "$release_path" ]; then fail "release path exists and is not a regular directory: $release_path" fi release_owner=$(stat -c '%u' "$release_path") || fail 'cannot inspect installed release ownership' [ "$release_owner" = "$current_uid" ] || fail 'installed release directory must be owned by the current user' if [ ! -f "$release_path/.macmake-install" ] || [ -L "$release_path/.macmake-install" ]; then fail "release directory already exists without installer provenance: $release_path" fi grep -Fqx "version=$version" "$release_path/.macmake-install" || fail "installed version directory has a different version identity: $release_path" grep -Fqx "sha256=$expected_sha256" "$release_path/.macmake-install" || fail "version $version is already installed from a different archive; refusing to replace immutable version contents" root=$release_path else stage_dir=$(mktemp -d "$releases_dir/.stage-${version}.XXXXXXXX") || fail 'cannot create release staging directory' expected_root=macmake-v$version python3 - "$archive_path" "$stage_dir" "$expected_root" <<'PY' import os import posixpath import shutil import sys import tarfile from pathlib import PurePosixPath archive, destination, expected_root = sys.argv[1:] max_members = 200000 max_expanded = 1024 * 1024 * 1024 def reject(message): print("macmake installer: unsafe or invalid archive: " + message, file=sys.stderr) raise SystemExit(1) try: with tarfile.open(archive, mode="r:xz") as tf: members = tf.getmembers() if not members or len(members) > max_members: reject("empty archive or too many entries") seen = set() total = 0 normalized = [] for member in members: name = member.name if not name or name.startswith("/") or "\\" in name or "\x00" in name: reject("absolute, empty, or non-portable member path") raw_path = name[:-1] if name.endswith("/") else name raw_parts = raw_path.split("/") if any(part in ("", ".", "..") for part in raw_parts): reject("path traversal or non-canonical member path: " + name) path = PurePosixPath(name) parts = path.parts if any(part in ("", ".", "..") for part in parts): reject("path traversal or non-canonical member path: " + name) if not parts or parts[0] != expected_root or len(parts) < 1: reject("member is outside expected root " + expected_root + ": " + name) canonical = "/".join(parts) if canonical in seen: reject("duplicate member path: " + canonical) seen.add(canonical) if member.mode & 0o7000: reject("set-id or sticky mode is not permitted: " + canonical) if member.isfile(): total += member.size if member.size < 0 or total > max_expanded: reject("expanded archive exceeds size limit") elif member.isdir(): pass elif member.issym(): target = member.linkname if not target or target.startswith("/") or "\\" in target or "\x00" in target: reject("absolute or invalid symbolic link: " + canonical) resolved = posixpath.normpath(posixpath.join(posixpath.dirname(canonical), target)) if resolved != expected_root and not resolved.startswith(expected_root + "/"): reject("symbolic link escapes archive root: " + canonical) else: reject("special files and hard links are not permitted: " + canonical) normalized.append((member, canonical)) for member, canonical in normalized: parent = posixpath.dirname(canonical) while parent: if parent in seen: ancestor = next((m for m, n in normalized if n == parent), None) if ancestor is not None and not ancestor.isdir(): reject("a non-directory entry is used as a parent: " + parent) parent = posixpath.dirname(parent) root_path = os.path.join(destination, expected_root) os.mkdir(root_path, 0o700) directories = {expected_root} for member, canonical in normalized: if member.isdir(): directories.add(canonical) parent = posixpath.dirname(canonical) while parent: directories.add(parent) parent = posixpath.dirname(parent) for directory in sorted(directories, key=lambda p: (p.count("/"), p)): target = os.path.join(destination, *PurePosixPath(directory).parts) if directory != expected_root: os.mkdir(target, 0o700) symlinks = [] for member, canonical in normalized: output = os.path.join(destination, *PurePosixPath(canonical).parts) if member.isdir(): continue if member.issym(): symlinks.append((member, canonical, output)) continue os.makedirs(os.path.dirname(output), exist_ok=True) flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL if hasattr(os, "O_NOFOLLOW"): flags |= os.O_NOFOLLOW fd = os.open(output, flags, member.mode & 0o755 & ~0o6000) try: stream = tf.extractfile(member) if stream is None: reject("cannot read archive member: " + canonical) with stream, os.fdopen(fd, "wb", closefd=False) as out: shutil.copyfileobj(stream, out) finally: os.close(fd) os.chmod(output, member.mode & 0o755 & ~0o6000) for member, canonical, output in symlinks: if any(canonical.startswith(link_name + "/") for link_name in (n for m, n in normalized if m.issym())): reject("symbolic link is a parent of another entry: " + canonical) os.symlink(member.linkname, output) for directory in sorted(directories, key=lambda p: (-p.count("/"), p)): member = next((m for m, n in normalized if n == directory and m.isdir()), None) mode = (member.mode if member else 0o755) & 0o755 & ~0o6000 os.chmod(os.path.join(destination, *PurePosixPath(directory).parts), mode) required = ( "bin/macmake", "toolchain/bin/powerpc-unknown-macmake-gcc", "toolchain/bin/powerpc-unknown-macmake-as", "INSTALL-DEPENDENCIES.txt", "LICENSE-MACMAKE.txt", "licenses/THIRD-PARTY-NOTICES.md", ) executables = list(required[:3]) if expected_root == "macmake-v0.1.1": required += ( "toolchain/m68k/bin/m68k-unknown-elf-gcc", "toolchain/m68k/bin/m68k-unknown-elf-as", "toolchain/m68k/bin/m68k-unknown-elf-ld", "toolchain/m68k/bin/m68k-unknown-elf-objcopy", "toolchain/m68k/bin/m68k-unknown-elf-readelf", "toolchain/m68k/libexec/gcc/m68k-unknown-elf/12.2.0/cc1", "toolchain/m68k/lib/gcc/m68k-unknown-elf/12.2.0/libgcc.a", "toolchain/m68k/m68k-unknown-elf/bin/as", ) executables += ( "toolchain/m68k/bin/m68k-unknown-elf-gcc", "toolchain/m68k/bin/m68k-unknown-elf-as", "toolchain/m68k/bin/m68k-unknown-elf-ld", "toolchain/m68k/bin/m68k-unknown-elf-objcopy", "toolchain/m68k/bin/m68k-unknown-elf-readelf", "toolchain/m68k/libexec/gcc/m68k-unknown-elf/12.2.0/cc1", "toolchain/m68k/m68k-unknown-elf/bin/as", ) for relative in required: candidate = os.path.join(root_path, *PurePosixPath(relative).parts) if not os.path.isfile(candidate): reject("required release file is missing: " + relative) for relative in executables: candidate = os.path.join(root_path, *PurePosixPath(relative).parts) if not os.access(candidate, os.X_OK): reject("required executable is not executable: " + relative) resolved = os.path.realpath(candidate) if os.path.commonpath((os.path.realpath(root_path), resolved)) != os.path.realpath(root_path): reject("required executable resolves outside release root: " + relative) except (tarfile.TarError, OSError, ValueError) as exc: reject(str(exc)) PY [ -x "$stage_dir/$expected_root/bin/macmake" ] || fail 'release archive did not contain an executable macMAKE CLI' if [ -e "$stage_dir/$expected_root/.macmake-install" ] || [ -L "$stage_dir/$expected_root/.macmake-install" ]; then fail 'release archive uses a reserved installer metadata path' fi printf 'version=%s\nsha256=%s\n' "$version" "$expected_sha256" > "$stage_dir/$expected_root/.macmake-install" chmod 0644 "$stage_dir/$expected_root/.macmake-install" root=$stage_dir/$expected_root fi example_root=$root/examples/first-app [ -d "$example_root" ] || fail 'release archive is missing the packaged examples/first-app project' if [ "$with_sdk" = yes ]; then sdk_root=$data_dir/macMAKELibs [ ! -L "$sdk_root" ] || fail "refusing to fetch SDK files through a symlinked path: $sdk_root" MACMAKE_TOOLCHAIN_ROOT="$root/toolchain" \ "$root/bin/macmake" sdk fetch --minimal --destination "$sdk_root" \ || fail 'minimal SDK fetch failed; macMAKE installation was not switched' "$example_root/setup-user-msl.sh" "$sdk_root" \ || fail 'MSL archive setup failed; macMAKE installation was not switched' universal_interfaces_path=$sdk_root/Universal fi if [ "$root" != "$release_path" ]; then if [ -e "$release_path" ] || [ -L "$release_path" ]; then fail "refusing to replace existing release directory: $release_path" fi mv "$root" "$release_path" || fail 'could not atomically install the verified version directory' new_release=yes root=$release_path fi verify_output=$(MACMAKE_TOOLCHAIN_ROOT="$root/toolchain" "$root/bin/macmake" --version 2>&1) || fail "installed CLI failed its version check: $verify_output" [ "$verify_output" = "macMAKE $version" ] || fail "installed CLI reported an unexpected version: $verify_output" [ -x "$root/toolchain/bin/powerpc-unknown-macmake-gcc" ] || fail 'bundled compiler is missing or not executable' [ -x "$root/toolchain/bin/powerpc-unknown-macmake-as" ] || fail 'bundled assembler is missing or not executable' wrapper_tmp=$(mktemp "$bin_dir/.macmake-wrapper.XXXXXXXX") || fail 'cannot create a temporary CLI launcher' cat > "$wrapper_tmp" <<'EOF' #!/bin/sh # macMAKE-managed-wrapper-v1 set -eu bin_dir=$(CDPATH= cd -P -- "$(dirname -- "$0")" && pwd) prefix=$(CDPATH= cd -P -- "$bin_dir/.." && pwd) active="$prefix/share/macmake/current" if [ -f "$prefix/share/macmake/analytics-disabled" ]; then MACMAKE_DISABLE_ANALYTICS=1 export MACMAKE_DISABLE_ANALYTICS fi if [ ! -x "$active/bin/macmake" ]; then printf '%s\n' 'macmake: active installation is missing; rerun the macMAKE installer' >&2 exit 1 fi if [ -z "${MACMAKE_TOOLCHAIN_ROOT:-}" ]; then MACMAKE_TOOLCHAIN_ROOT="$active/toolchain" export MACMAKE_TOOLCHAIN_ROOT fi if [ -z "${MACMAKE_M68K_TOOLCHAIN_ROOT:-}" ] && [ -x "$active/toolchain/m68k/bin/m68k-unknown-elf-gcc" ]; then MACMAKE_M68K_TOOLCHAIN_ROOT="$active/toolchain/m68k" export MACMAKE_M68K_TOOLCHAIN_ROOT fi exec "$active/bin/macmake" "$@" EOF chmod 0755 "$wrapper_tmp" || fail 'cannot mark the CLI launcher executable' if [ "$analytics_enabled" = no ] && [ ! -e "$analytics_state_path" ]; then analytics_state_tmp=$(mktemp "$data_dir/.analytics-disabled.XXXXXXXX") || fail 'cannot create the analytics preference marker' printf 'disabled\n' > "$analytics_state_tmp" || fail 'cannot write the analytics preference marker' chmod 0600 "$analytics_state_tmp" || fail 'cannot protect the analytics preference marker' mv -T "$analytics_state_tmp" "$analytics_state_path" || fail 'cannot save the analytics preference marker' analytics_state_tmp= analytics_marker_created=yes fi if [ -e "$current_path" ] || [ -L "$current_path" ]; then [ -L "$current_path" ] || fail "refusing to replace a non-symlink active path: $current_path" fi current_tmp=$(mktemp "$data_dir/.current.XXXXXXXX") || fail 'cannot create the active-version link' rm -f "$current_tmp" ln -s "releases/$version" "$current_tmp" || fail 'cannot create the active-version link' mv -Tf "$current_tmp" "$current_path" || fail 'cannot switch the active macMAKE version' current_changed=yes mv -Tf "$wrapper_tmp" "$wrapper_path" || fail "cannot install the CLI launcher at $wrapper_path" transaction_complete=yes send_install_event() { [ "$analytics_enabled" = yes ] || return 0 event_payload=$(printf '{"payload":{"hostname":"macmake.dev","url":"/install.sh","website":"%s","name":"installer-completed","data":{"version":"%s"}},"type":"event"}' "$UMAMI_WEBSITE_ID" "$version") curl --fail --silent --show-error --max-time 2 --connect-timeout 1 \ --proto '=https' --proto-redir '=https' --tlsv1.2 \ --request POST \ --header 'Content-Type: application/json' \ --header "User-Agent: macMAKE-installer/$version (+https://macmake.dev)" \ --data-binary "$event_payload" "$UMAMI_SEND_URL" >/dev/null 2>&1 || : } send_install_event printf 'Installed macMAKE %s under %s\n' "$version" "$prefix" if [ "$with_sdk" = yes ]; then printf 'Fetched minimal SDK inputs at %s\n' "$sdk_root" printf 'First-app MSL archives: %s/examples/first-app/sdk\n' "$root" printf 'Register Universal Interfaces with:\n %s/macmake sdk add "%s" --id macos9-sdk\n' "$bin_dir" "$universal_interfaces_path" else printf 'To fetch the authorized minimal C/PowerPC SDK inputs, run:\n %s/macmake sdk fetch --minimal\n' "$bin_dir" printf 'Then register Universal Interfaces and copy MSL archives using examples/first-app/README.md.\n' fi case ":${PATH:-}:" in *":$bin_dir:"*) ;; *) printf 'Add this directory to your PATH if it is not already present:\n %s\n' "$bin_dir" ;; esac printf 'Verify the installation with:\n %s/macmake --version\n %s/macmake doctor PROJECT --target TARGET\n' "$bin_dir" "$bin_dir" exit 0