#!/bin/sh # Served from https://mono.bolte.sh. Build embeds immutable payload metadata. set -eu mono_release='7a29def18428cd0cf8d3' mono_sha256='7a29def18428cd0cf8d3d0687a245c9f9072c9eabd18ee91c7c61ed6c31684d2' mono_base='https://mono.bolte.sh' mono_runtime_consent=0 mono_no_input=0 mono_dry_run=0 mono_target_home=${HOME:-} mono_home_next=0 for mono_argument in "$@"; do if [ "$mono_home_next" = 1 ]; then mono_target_home=$mono_argument mono_home_next=0 fi case "$mono_argument" in --home) mono_home_next=1 ;; --home=*) mono_target_home=${mono_argument#--home=} ;; --bootstrap-python) mono_runtime_consent=1 ;; --no-input|--json) mono_no_input=1 ;; --dry-run|-n) mono_dry_run=1 ;; --help|-h) cat <<'HELP' Mono — a familiar environment for each machine. curl -fsSL https://mono.bolte.sh | bash curl -fsSL https://mono.bolte.sh -o /tmp/mono-install.sh sh /tmp/mono-install.sh --features shell --dry-run sh /tmp/mono-install.sh --features shell,git,vim,tmux --backup-conflicts --yes Choose individual features; start with shell and add only what you need. Compose with --features shell,git; customize with --with / --without FEATURE. Features: shell, git, vim, tmux, terminal, dev, mono, wiki, codex, packages. --features FEATURES Exact selection; repeat or comma-separate (none clears) --feature FEATURE Alias for --features --dry-run Preview files and configuration; make no home changes --yes --no-input Apply without prompts --backup-conflicts Preserve existing files before replacing them --migrate-from PATH Replace links into a legacy dotfiles checkout --home PATH Use a different installation home --json / --no-color Machine output / plain terminal output --plain Use the numbered text menu instead of the visual picker --run-hook Explicitly run your private ~/.mono/post-install.sh --skip-hook Skip the optional private-hook prompt --bootstrap-python Explicitly allow a private runtime if Python is absent Python 3.9+ is sufficient. Without Python, an interactive prompt can install uv and Python privately under ~/.mono/runtime. No sudo or shell edits are used. After installation: mono-dotfiles status, install, list, update-runtime, update-codex, uninstall. Coordinated home migration: mono-dotfiles rebase-home --dry-run. HELP exit 0 ;; esac done if [ -z "$mono_target_home" ]; then printf '%s\n' 'No home directory is configured. Set HOME or pass --home PATH.' >&2 exit 1 fi case "$mono_release" in @*) printf '%s\n' 'This is the source template. Run ./dotfiles/install from the repository.' >&2 exit 1 ;; esac mono_temp=$(mktemp -d "${TMPDIR:-/tmp}/mono-install.XXXXXXXX") trap 'rm -rf "$mono_temp"' EXIT HUP INT TERM mono_python='' for mono_candidate in "${MONO_PYTHON:-python3}" python3.13 python3.12 python3.11 "$mono_target_home"/.mono/runtime/python/*/bin/python3; do if command -v "$mono_candidate" >/dev/null 2>&1 && "$mono_candidate" -c 'import sys; sys.exit(sys.version_info < (3, 9))' 2>/dev/null; then mono_python=$(command -v "$mono_candidate") break fi done if [ -z "$mono_python" ]; then if [ "$mono_dry_run" = 1 ]; then printf '%s\n' 'Python 3.9+ is needed for this preview. No runtime has been installed.' >&2 exit 1 fi if [ "$mono_runtime_consent" = 0 ] && [ "$mono_no_input" = 0 ] && ( : /dev/null; then printf '%s\n' 'Python is missing. Install uv and Python privately in ~/.mono/runtime?' 'This downloads from astral.sh and GitHub, uses no sudo, and leaves shell startup unchanged.' >&2 printf '%s' 'Install private runtime? [y/N] ' >&2 read -r mono_answer &2 exit 1 fi if [ -L "$mono_target_home/.mono" ] || [ -L "$mono_target_home/.mono/runtime" ] || [ -L "$mono_target_home/.mono/runtime/bin" ] || [ -L "$mono_target_home/.mono/runtime/python" ] || [ -L "$mono_target_home/.mono/runtime/cache" ]; then printf '%s\n' 'Refusing a symlinked ~/.mono runtime directory.' >&2 exit 1 fi umask 077 mkdir -p "$mono_target_home/.mono/runtime/bin" curl --proto '=https' --tlsv1.2 --fail --show-error --silent --location --connect-timeout 15 --max-time 120 https://astral.sh/uv/install.sh -o "$mono_temp/uv-install.sh" UV_NO_MODIFY_PATH=1 UV_UNMANAGED_INSTALL="$mono_target_home/.mono/runtime/bin" sh "$mono_temp/uv-install.sh" PATH="$mono_target_home/.mono/runtime/bin:$PATH" UV_PYTHON_INSTALL_DIR="$mono_target_home/.mono/runtime/python" UV_PYTHON_BIN_DIR="$mono_target_home/.mono/runtime/bin" UV_CACHE_DIR="$mono_target_home/.mono/runtime/cache" "$mono_target_home/.mono/runtime/bin/uv" python install 3.12 mono_python=$(UV_PYTHON_INSTALL_DIR="$mono_target_home/.mono/runtime/python" UV_CACHE_DIR="$mono_target_home/.mono/runtime/cache" "$mono_target_home/.mono/runtime/bin/uv" python find --managed-python 3.12) fi printf '%s\n' 'Downloading the verified Mono shell environment…' >&2 curl --proto '=https' --tlsv1.2 --fail --show-error --silent --location --connect-timeout 15 --max-time 120 "$mono_base/releases/$mono_release/dotfiles.tar.gz" -o "$mono_temp/dotfiles.tar.gz" "$mono_python" - "$mono_temp" "$mono_sha256" <<'PY' import hashlib from pathlib import Path import sys import tarfile root = Path(sys.argv[1]) archive = root / 'dotfiles.tar.gz' if hashlib.sha256(archive.read_bytes()).hexdigest() != sys.argv[2]: raise SystemExit('Payload checksum mismatch; nothing has been installed. Try downloading the installer again.') with tarfile.open(archive) as bundle: members = bundle.getmembers() for member in members: if not member.isfile() or member.name.startswith('/') or '..' in Path(member.name).parts: raise SystemExit('Unexpected payload entry; installation stopped.') if sys.version_info >= (3, 12): bundle.extractall(root / 'payload', members=members, filter='data') else: bundle.extractall(root / 'payload', members=members) PY # curl | bash consumes stdin; hand only the downloaded installer a terminal when one exists. # --no-input/--json intentionally keep stdin untouched for automation. if [ "$mono_no_input" = 0 ] && [ ! -t 0 ] && ( : /dev/null; then MONO_PYTHON="$mono_python" sh "$mono_temp/payload/install" "$@"