montana/_internal-private/Apps/macOS/install-anywhere.sh

211 lines
9.4 KiB
Bash
Raw Normal View History

2026-05-18 18:05:32 +03:00
#!/bin/bash
# Montana — единый installer для любого Mac (fresh OR recovery).
#
# Использование:
#
# FRESH (новый identity, генерируется 24 слова):
# bash install-anywhere.sh
#
# RECOVERY (восстановить identity из backup на втором Mac):
# INSTALL_MNEMONIC_OR_SEED='word1 word2 ... word24' bash install-anywhere.sh
# либо
# INSTALL_MNEMONIC_OR_SEED='<64 hex символа master_seed>' bash install-anywhere.sh
#
# Кастомные пути:
# INSTALL_DIR=/custom/path DATA_DIR=/custom/path/data bash install-anywhere.sh
#
# Что делает:
# 1. Сборка montana-node release (Rust, ~1-2 мин cold)
# 2. Установка binary в ~/Applications/Montana/montana-node
# 3. Identity: init заново ИЛИ recovery из переданного mnemonic/seed
# 4. launchd-агент org.montana.node (auto-restart, переживает logout)
# 5. Сборка Montana.app (SwiftUI, ~10 сек)
# 6. Установка Montana.app в /Applications/Montana.app
# 7. Регистрация в LaunchServices (Dock-иконка)
# 8. Запуск Montana.app
#
# Mac должен иметь: Xcode Command Line Tools (xcode-select --install).
# Rust toolchain установится автоматически если нет.
set -euo pipefail
SCRIPT_PATH="${BASH_SOURCE[0]}"
if [ -L "$SCRIPT_PATH" ]; then SCRIPT_PATH="$(readlink "$SCRIPT_PATH")"; fi
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
PROTOCOL_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
CODE_DIR="$PROTOCOL_ROOT/Code"
APP_SRC_DIR="$SCRIPT_DIR/Montana"
INSTALL_DIR="${INSTALL_DIR:-$HOME/Applications/Montana}"
DATA_DIR="${DATA_DIR:-$INSTALL_DIR/data}"
LOGS_DIR="$DATA_DIR/logs"
LAUNCH_AGENTS_DIR="$HOME/Library/LaunchAgents"
PLIST_PATH="$LAUNCH_AGENTS_DIR/org.montana.node.plist"
SERVICE_LABEL="org.montana.node"
APP_DEST="/Applications/Montana.app"
log() { printf '\033[1;32m[montana]\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m[montana]\033[0m %s\n' "$*" >&2; }
die() { printf '\033[1;31m[montana] ОШИБКА:\033[0m %s\n' "$*" >&2; exit 1; }
# --- 1. платформа + tooling ---
[ "$(uname -s)" = "Darwin" ] || die "только macOS"
log "macOS $(sw_vers -productVersion 2>/dev/null), arch $(uname -m)"
log "Montana-Protocol: $PROTOCOL_ROOT"
[ -f "$CODE_DIR/Cargo.toml" ] || die "не найден $CODE_DIR/Cargo.toml"
[ -d "$APP_SRC_DIR" ] || die "не найден $APP_SRC_DIR"
if ! xcode-select -p >/dev/null 2>&1; then
warn "Xcode Command Line Tools не установлены — открываю установщик"
xcode-select --install 2>/dev/null || true
die "после установки CLT повторите команду"
fi
if ! command -v cargo >/dev/null 2>&1; then
log "ставлю Rust toolchain (rustup minimal)..."
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal --no-modify-path
fi
export PATH="$HOME/.cargo/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"
command -v cargo >/dev/null 2>&1 || die "cargo не доступен"
command -v swift >/dev/null 2>&1 || die "swift не доступен (нужен Xcode CLT)"
log "Rust: $(cargo --version) · Swift: $(swift --version | head -1)"
# --- 1.5. остановка running Montana.app (singleton enforce) ---
log "останавливаю running Montana.app (если запущена)..."
pkill -f "Montana.app/Contents/MacOS/Montana" 2>/dev/null || true
sleep 1
# --- 2. остановка старого узла ---
if launchctl list "$SERVICE_LABEL" >/dev/null 2>&1; then
log "останавливаю текущий узел перед обновлением..."
launchctl unload "$PLIST_PATH" 2>/dev/null || true
fi
# --- 3. сборка montana-node ---
log "собираю montana-node release..."
cargo build --release --manifest-path "$CODE_DIR/Cargo.toml" -p montana-node 2>&1 | tail -3
# --- 4. install binary + commands ---
mkdir -p "$INSTALL_DIR" "$DATA_DIR" "$LOGS_DIR" "$LAUNCH_AGENTS_DIR"
cp -f "$CODE_DIR/target/release/montana-node" "$INSTALL_DIR/montana-node"
chmod 0755 "$INSTALL_DIR/montana-node"
cp -f "$CODE_DIR/dist/macOS/Montana"/*.command "$INSTALL_DIR/" 2>/dev/null || true
cp -f "$CODE_DIR/dist/macOS/Montana/README.txt" "$INSTALL_DIR/" 2>/dev/null || true
chmod 0755 "$INSTALL_DIR"/*.command 2>/dev/null || true
xattr -dr com.apple.quarantine "$INSTALL_DIR" 2>/dev/null || true
# --- 5. identity — fresh OR recovery ---
RECOVERY="${INSTALL_MNEMONIC_OR_SEED:-}"
if [ -n "$RECOVERY" ]; then
WORD_COUNT=$(echo "$RECOVERY" | tr -s ' ' '\n' | grep -c .)
HEX_LEN=${#RECOVERY}
if [ "$WORD_COUNT" -eq 24 ]; then
log "RECOVERY: identity из 24-словной мнемоники"
"$INSTALL_DIR/montana-node" init --data-dir "$DATA_DIR" --mnemonic "$RECOVERY" --force
elif [ "$HEX_LEN" -eq 64 ] && echo "$RECOVERY" | grep -qiE '^[0-9a-f]+$'; then
log "RECOVERY: identity из 64-hex master_seed"
"$INSTALL_DIR/montana-node" init --data-dir "$DATA_DIR" --entropy "$RECOVERY" --force
else
die "INSTALL_MNEMONIC_OR_SEED должно быть либо 24 слова через пробел, либо 64 hex; получено: $WORD_COUNT слов / $HEX_LEN символов"
fi
elif [ ! -f "$DATA_DIR/identity.bin" ]; then
log ""
log "================================================================"
log " ГЕНЕРАЦИЯ IDENTITY — запишите 24 слова мнемоники!"
log "================================================================"
log ""
"$INSTALL_DIR/montana-node" init --data-dir "$DATA_DIR"
log ""
log "Мнемоника также в логе установщика. Скопируйте из Terminal scrollback."
log "Для recovery на втором Mac:"
log " INSTALL_MNEMONIC_OR_SEED='<24 слова>' bash install-anywhere.sh"
log "================================================================"
else
log "identity уже существует — пропускаю init (для recovery передайте INSTALL_MNEMONIC_OR_SEED)"
fi
# --- 6. launchd plist ---
log "устанавливаю LaunchAgent $PLIST_PATH..."
cat > "$PLIST_PATH" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>$SERVICE_LABEL</string>
<key>ProgramArguments</key>
<array>
<string>$INSTALL_DIR/montana-node</string>
<string>start</string>
<string>--data-dir</string>
<string>$DATA_DIR</string>
</array>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key>
<dict><key>SuccessfulExit</key><false/><key>Crashed</key><true/></dict>
<key>ThrottleInterval</key><integer>10</integer>
<key>StandardOutPath</key><string>$LOGS_DIR/montana.log</string>
<key>StandardErrorPath</key><string>$LOGS_DIR/montana.err.log</string>
<key>WorkingDirectory</key><string>$DATA_DIR</string>
<key>EnvironmentVariables</key>
<dict><key>PATH</key><string>/usr/local/bin:/usr/bin:/bin</string></dict>
<key>ProcessType</key><string>Standard</string>
</dict>
</plist>
PLIST
chmod 0644 "$PLIST_PATH"
# --- 7. сборка Montana.app ---
log "собираю Montana.app..."
bash "$APP_SRC_DIR/build.sh" 2>&1 | tail -4
APP_BUILT="$APP_SRC_DIR/build/Montana.app"
[ -d "$APP_BUILT" ] || die "Montana.app не собрался: $APP_BUILT"
# --- 8. установка Montana.app в /Applications/ ---
log "копирую Montana.app в /Applications/..."
if [ -d "$APP_DEST" ]; then
rm -rf "$APP_DEST"
fi
cp -R "$APP_BUILT" "$APP_DEST"
xattr -dr com.apple.quarantine "$APP_DEST" 2>/dev/null || true
touch "$APP_DEST"
/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -f "$APP_DEST" 2>/dev/null || true
# --- 8.5. чистка dev build (singleton enforce — каноничен только /Applications/Montana.app) ---
if [ -d "$APP_SRC_DIR/build/Montana.app" ]; then
rm -rf "$APP_SRC_DIR/build/Montana.app"
log "удалил dev-копию $APP_SRC_DIR/build/Montana.app"
fi
# --- 9. запуск узла ---
log "запускаю узел через launchctl load..."
launchctl load -w "$PLIST_PATH"
sleep 2
if launchctl list "$SERVICE_LABEL" 2>/dev/null | grep -q "$SERVICE_LABEL"; then
STATE=$(launchctl list "$SERVICE_LABEL" | awk '/PID/ {print $3}')
log "узел запущен, PID=$STATE"
else
warn "не удалось загрузить службу — проверьте $LOGS_DIR/montana.err.log"
fi
# --- 10. запуск приложения ---
log "запускаю Montana.app..."
open "$APP_DEST" || warn "не смог открыть приложение автоматически"
# --- 11. финальный отчёт ---
log ""
log "================================================================"
log " MONTANA УСТАНОВЛЕНА И ЗАПУЩЕНА"
log "================================================================"
log ""
log "Узел: $INSTALL_DIR/montana-node (launchd: $SERVICE_LABEL)"
log "Данные: $DATA_DIR"
log "Логи: $LOGS_DIR/montana.log"
log "Приложение: $APP_DEST"
log ""
log "Identity:"
"$INSTALL_DIR/montana-node" inspect --data-dir "$DATA_DIR" 2>&1 | grep -E "^(account_id|node_id|libp2p_peer_id|master_seed_fp)" | sed 's/^/ /'
log ""
log "Backup и второй Mac: см. Apps/macOS/INSTALL.md"
log ""