#!/usr/bin/env sh set -eu ROOT_DIR="$(CDPATH= cd -- "$(dirname "$0")" && pwd)" WATCHTHIS_DIR="$ROOT_DIR/dash/staging/watchthis" POST_JAILBREAK_ROOT="$ROOT_DIR/dash/staging/post-jailbreak-root" KTERM_STAGING_DIR="$ROOT_DIR/dash/staging/kterm" SSH_HELPERS_DIR="$ROOT_DIR/scripts/kindle" SYNC_SCRIPT="$ROOT_DIR/scripts/sync-layered-clock-to-kindle.sh" THEMES_JSON="$ROOT_DIR/calendar/config/themes.json" KTERM_GITHUB_REPO="bfabiszewski/kterm" MODE="all" VOLUME_PATH="/Volumes/Kindle" HOST_TARGET="kindle" THEME_ID="" ORIENTATION="" KTERM_PACKAGE="" DOWNLOAD_KTERM=false KTERM_VERSION="latest" SHOW_BACKGROUND=true START_DASHBOARD=false print_usage() { cat <<'EOF' 用法: sh bootstrap-new-kindle.sh [模式] [选项] 模式: all 默认。能预置 USB 存储就先预置;能连 SSH 就继续做 SSH 后半段 prepare-storage 只做 USB 存储预置 post-ssh 只做 SSH 打通后的自动化收尾 选项: -v, --volume Kindle 挂载目录,默认 /Volumes/Kindle -k, --kindle Kindle SSH 主机名,默认 kindle -t, --theme SSH 阶段切换到指定主题;默认使用 themes.json 的默认主题 -o, --orientation SSH 阶段切换到指定方向;默认使用 themes.json 的默认方向 --kterm-package 指定 KTerm 安装包;官方 release 用 .zip,也兼容外部 .bin --download-kterm 在 Mac 侧联网下载 KTerm 到 dash/staging/kterm/,再预置到 Kindle --kterm-version 下载指定 KTerm 版本;默认 latest --no-background SSH 阶段不同步后立即切主题出图 --start-dashboard SSH 阶段额外后台启动 dashboard 主循环 -h, --help 查看帮助 说明: 这不是 100% 零交互刷机脚本。 受 Kindle 本机流程限制,下面这些步骤仍然必须人工完成: 1. 进入 demo mode / WatchThis 流程 2. 在正确时机执行 Sideload Content 3. 触发 Get Started 完成越狱 4. 搜索 ;log mrpi 安装 KUAL / MRPI / USBNetwork 5. 首次进 KTerm 执行 sh /mnt/us/ssh-force-dropbear-22.sh 关于 KTerm: 1. 当前仓库默认不自带 KTerm 安装包。 2. 官方 KTerm release 使用 .zip,解压后是 kterm/ 扩展目录。 3. 如果你手头已经有 KTerm 安装包,可以: - 放到 dash/staging/kterm/ 目录下,脚本会自动尝试拾取 - 或执行时显式传:--kterm-package /绝对路径/kterm-kindle-*.zip 4. 如果你希望脚本直接从网上拉,可以执行: --download-kterm --kterm-version latest 或: --download-kterm --kterm-version v2.6 5. 下载发生在 Mac 侧,文件会缓存到 dash/staging/kterm/,然后再解压或复制到 Kindle。 6. 对这台 Voyage 5.13.6,脚本默认优先选择“不带 armhf 后缀”的 zip。 7. 如果脚本没有检测到 KTerm 包,会明确提示“这一步仍需手工补装 KTerm”。 推荐操作顺序: 阶段 A:先做 USB 预置 1. 用 USB 线把 Kindle 接到 Mac。 2. 确认 Finder 里已经出现 Kindle,默认挂载目录是 /Volumes/Kindle。 3. 执行: sh bootstrap-new-kindle.sh prepare-storage 4. 预期结果: - Kindle 根目录出现 Update_hotfix_watchthis_custom.bin - Kindle 根目录出现 ssh-force-dropbear-22.sh 等脚本 - Kindle 根目录出现 dashboard/、extensions/、mrpackages/ - Kindle 根目录出现 .demo/KV-5.13.6.zip、.demo/demo.json、.demo/goodreads/ - 如果提供了 KTerm zip,extensions/ 里会被解压出 kterm/ - 如果提供了外部 KTerm bin,mrpackages/ 里会出现对应文件 5. 安全弹出 Kindle。 阶段 B:在 Kindle 上完成 WatchThis 和越狱 1. 恢复出厂。 2. 语言只选 English (United Kingdom)。 3. 到 Wi-Fi 页面时,不要真的联网;先按 WatchThis 文档进入 demo mode。 4. 第一次出现 Add Content / Sideload Content 时,只点 Done,不要接 USB。 5. 遇到 misconfiguration / Configure Device 时,执行隐藏手势回到可操作界面。 6. 再次进入 ;demo -> Sideload Content,这一次才是真正导入 payload 的时机。 因为阶段 A 已经预置好 .demo 内容,这里不需要再从 Mac 手工拷文件。 7. 退出 demo menu 后,进入 Help & User Guides -> Get Started 触发越狱。 8. 越狱成功后,Kindle 根目录应出现 mkk、libkh、rp。 阶段 C:安装 KUAL / MRPI / USBNetwork / dashboard 1. 在 Kindle 首页搜索: ;log mrpi 2. 等 MRPI 安装完成。 3. 预期结果: - 首页出现 KUAL - KUAL 菜单里有 Rename OTA Binaries - KUAL 菜单里有 kindle-dash - 如果本次预置了 KTerm 安装包,首页或搜索里应能找到 KTerm 4. 先在 KUAL 中执行: Rename OTA Binaries -> Rename 阶段 D:打通 Wi-Fi 和 SSH 1. 让 Kindle 连到和 Mac 同一个主 Wi-Fi,不要用 Guest 网络。 2. 打开 KTerm。 3. 在 KTerm 执行: sh /mnt/us/ssh-force-dropbear-22.sh 4. 回到 Mac,先测试: ssh kindle 5. 如果这一步还不通,不要继续盲试,先回看: dash/docs/kindle-voyage-5.13.6-dual-ssh-playbook-zh.md 阶段 E:SSH 打通后自动同步并出图 1. 执行: sh bootstrap-new-kindle.sh post-ssh -t simple -o portrait 2. 预期结果: - 自动同步 dashboard 运行时和主题包 - Kindle 立即切到 simple / portrait - 屏幕马上显示背景和时钟 3. 如果还希望后台常驻 dashboard 主循环,再执行: sh bootstrap-new-kindle.sh post-ssh -t simple -o portrait --start-dashboard 最省事的直接用法: 1. USB 挂载时先运行: sh bootstrap-new-kindle.sh all -t simple -o portrait 这会先做完能做的 USB 预置。 2. 等你在 Kindle 上完成越狱、MRPI、Wi-Fi、KTerm 拉起 SSH 后, 再重新运行同一条命令: sh bootstrap-new-kindle.sh all -t simple -o portrait 这时它会自动继续做 SSH 后半段。 最容易做错的地方: 1. 第一次 Add Content / Sideload Content 只能点 Done,不能在这一步导 payload。 2. 真正导 payload 的时机,是隐藏手势返回后,再次进入 ;demo -> Sideload Content。 3. 首次 SSH 最稳的入口是 KTerm,不是 USB 直连盲试。 4. post-ssh 阶段如果不带 -t/-o,会退回 themes.json 里的默认主题和方向。 5. 如果本轮没有预置 KTerm 包,阶段 C 结束后仍需你手工补装 KTerm。 示例: sh bootstrap-new-kindle.sh prepare-storage sh bootstrap-new-kindle.sh prepare-storage --kterm-package ~/Downloads/kterm-kindle-2.6.zip sh bootstrap-new-kindle.sh prepare-storage --download-kterm --kterm-version latest sh bootstrap-new-kindle.sh all -t simple -o portrait sh bootstrap-new-kindle.sh post-ssh -k kindle --start-dashboard EOF } log_step() { printf '\n[%s] %s\n' "$1" "$2" } require_path() { target_path=$1 label=$2 if [ ! -e "$target_path" ]; then echo "缺少${label}: $target_path" >&2 exit 1 fi } detect_kterm_package() { if [ -n "$KTERM_PACKAGE" ]; then if [ ! -f "$KTERM_PACKAGE" ]; then echo "指定的 KTerm 安装包不存在: $KTERM_PACKAGE" >&2 exit 1 fi printf '%s\n' "$KTERM_PACKAGE" return 0 fi if [ ! -d "$KTERM_STAGING_DIR" ]; then return 1 fi set +e set -- "$KTERM_STAGING_DIR"/*.zip "$KTERM_STAGING_DIR"/*.bin status=$? set -e if [ "$status" -ne 0 ] || [ ! -e "$1" ]; then return 1 fi printf '%s\n' "$1" } download_kterm_if_requested() { if [ "$DOWNLOAD_KTERM" != true ]; then return fi mkdir -p "$KTERM_STAGING_DIR" metadata_json="$KTERM_STAGING_DIR/kterm-release-${KTERM_VERSION}.json" case "$KTERM_VERSION" in latest) release_api_url="https://api.github.com/repos/$KTERM_GITHUB_REPO/releases/latest" ;; *) release_api_url="https://api.github.com/repos/$KTERM_GITHUB_REPO/releases/tags/$KTERM_VERSION" ;; esac echo "正在从 GitHub 获取 KTerm release 元数据: $release_api_url" curl -fsSL "$release_api_url" -o "$metadata_json" release_info="$(python3 - "$metadata_json" <<'PY' import json import pathlib import sys path = pathlib.Path(sys.argv[1]) data = json.loads(path.read_text()) assets = data.get("assets", []) preferred = None fallback = None for asset in assets: name = asset.get("name", "") url = asset.get("browser_download_url", "") if not name.endswith(".zip"): continue if fallback is None: fallback = (name, url) if "armhf" not in name.lower(): preferred = (name, url) break selected = preferred or fallback if not selected: raise SystemExit("未找到可用的 KTerm zip 资产") tag = data.get("tag_name", "unknown") print(f"TAG={tag}") print(f"NAME={selected[0]}") print(f"URL={selected[1]}") PY )" downloaded_tag="" downloaded_name="" downloaded_url="" while IFS='=' read -r key value; do case "$key" in TAG) downloaded_tag=$value ;; NAME) downloaded_name=$value ;; URL) downloaded_url=$value ;; esac done <&2 exit 1 fi downloaded_path="$KTERM_STAGING_DIR/$downloaded_name" echo "下载 KTerm: tag=$downloaded_tag asset=$downloaded_name" curl -fL "$downloaded_url" -o "$downloaded_path" KTERM_PACKAGE="$downloaded_path" } kindle_volume_available() { [ -d "$VOLUME_PATH" ] } ssh_reachable() { ssh -o BatchMode=yes -o ConnectTimeout=5 "$HOST_TARGET" true >/dev/null 2>&1 } resolve_theme_selection() { python3 - "$THEMES_JSON" "$THEME_ID" "$ORIENTATION" <<'PY' import json import pathlib import sys path = pathlib.Path(sys.argv[1]) requested_theme = sys.argv[2] requested_orientation = sys.argv[3] data = json.loads(path.read_text()) themes = {theme["id"]: theme for theme in data.get("themes", [])} theme_id = requested_theme or data.get("defaultThemeId", "") if theme_id not in themes: raise SystemExit(f"未知主题: {theme_id}") orientations = list(themes[theme_id].get("variants", {}).keys()) if not orientations: raise SystemExit(f"主题 {theme_id} 没有任何方向配置") orientation = requested_orientation or data.get("defaultOrientation", "") if orientation not in orientations: orientation = orientations[0] print(f"THEME_ID={theme_id}") print(f"ORIENTATION={orientation}") PY } load_theme_selection() { resolved_output="$(resolve_theme_selection)" RESOLVED_THEME_ID="" RESOLVED_ORIENTATION="" while IFS='=' read -r key value; do case "$key" in THEME_ID) RESOLVED_THEME_ID=$value ;; ORIENTATION) RESOLVED_ORIENTATION=$value ;; esac done <&2 exit 1 fi } copy_watchthis_payload() { require_path "$WATCHTHIS_DIR/KV-5.13.6/KV-5.13.6.zip" "WatchThis payload" require_path "$WATCHTHIS_DIR/KV-5.13.6/demo.json" "WatchThis demo.json" require_path "$WATCHTHIS_DIR/Update_hotfix_watchthis_custom.bin" "WatchThis hotfix" mkdir -p "$VOLUME_PATH/.demo/goodreads" cp "$WATCHTHIS_DIR/KV-5.13.6/KV-5.13.6.zip" "$VOLUME_PATH/.demo/" cp "$WATCHTHIS_DIR/KV-5.13.6/demo.json" "$VOLUME_PATH/.demo/" cp "$WATCHTHIS_DIR/Update_hotfix_watchthis_custom.bin" "$VOLUME_PATH/" } copy_post_jailbreak_bundle() { require_path "$POST_JAILBREAK_ROOT/extensions" "post-jailbreak extensions" require_path "$POST_JAILBREAK_ROOT/mrpackages" "post-jailbreak mrpackages" require_path "$POST_JAILBREAK_ROOT/dashboard" "post-jailbreak dashboard" mkdir -p "$VOLUME_PATH/extensions" "$VOLUME_PATH/mrpackages" "$VOLUME_PATH/dashboard" rsync -av --no-o --no-g "$POST_JAILBREAK_ROOT/extensions/" "$VOLUME_PATH/extensions/" rsync -av --no-o --no-g "$POST_JAILBREAK_ROOT/mrpackages/" "$VOLUME_PATH/mrpackages/" rsync -av --no-o --no-g "$POST_JAILBREAK_ROOT/dashboard/" "$VOLUME_PATH/dashboard/" } install_kterm_package_if_available() { detected_kterm_package=$(detect_kterm_package || true) if [ -z "$detected_kterm_package" ]; then echo "未检测到 KTerm 安装包;本轮只预置 SSH 恢复脚本,KTerm 仍需手工补装。" return fi case "$detected_kterm_package" in *.zip) mkdir -p "$VOLUME_PATH/extensions" unzip -oq "$detected_kterm_package" -d "$VOLUME_PATH/extensions" echo "已预置 KTerm 扩展包并解压到 extensions/: $(basename "$detected_kterm_package")" ;; *.bin) mkdir -p "$VOLUME_PATH/mrpackages" cp "$detected_kterm_package" "$VOLUME_PATH/mrpackages/" echo "已预置 KTerm bin 到 mrpackages/: $(basename "$detected_kterm_package")" ;; *) echo "不支持的 KTerm 安装包格式: $detected_kterm_package" >&2 exit 1 ;; esac } copy_ssh_helpers() { require_path "$SSH_HELPERS_DIR/ssh-force-dropbear-22.sh" "SSH helper scripts" rsync -av --no-o --no-g "$SSH_HELPERS_DIR/" "$VOLUME_PATH/" } print_storage_next_steps() { cat <<'EOF' 下一步请在 Kindle 上继续: 1. 按 [dash/docs/kindle-voyage-5.13.6-watchthis-zh.md] 的流程进入 demo mode。 2. 走到真正的 Sideload Content 时,脚本已预置好: - .demo/KV-5.13.6.zip - .demo/demo.json - .demo/goodreads/ 3. 触发 Get Started 完成越狱。 4. 搜索 `;log mrpi` 安装 KUAL / MRPI / USBNetwork / dashboard。 5. 先在 KUAL 里执行 `Rename OTA Binaries -> Rename`。 6. 连上 Wi-Fi。 7. 打开 KTerm,执行: sh /mnt/us/ssh-force-dropbear-22.sh 8. 回到 Mac 后,再运行: sh bootstrap-new-kindle.sh post-ssh EOF } prepare_storage() { if ! kindle_volume_available; then echo "未找到 Kindle 挂载目录: $VOLUME_PATH" >&2 exit 1 fi log_step "USB" "检查并下载 KTerm 安装包" download_kterm_if_requested log_step "USB" "预置 WatchThis payload" copy_watchthis_payload log_step "USB" "预置越狱后安装包" copy_post_jailbreak_bundle log_step "USB" "检查并预置 KTerm 安装包" install_kterm_package_if_available log_step "USB" "预置 SSH 恢复脚本" copy_ssh_helpers log_step "完成" "USB 存储预置已完成:$VOLUME_PATH" print_storage_next_steps } prepare_remote_helpers() { ssh "$HOST_TARGET" "chmod +x /mnt/us/ssh-collect.sh /mnt/us/ssh-fix-all-keys.sh /mnt/us/ssh-force-dropbear-22.sh /mnt/us/ssh-stop-all.sh 2>/dev/null || true" ssh "$HOST_TARGET" "if [ -f /mnt/us/ssh-fix-all-keys.sh ]; then sh /mnt/us/ssh-fix-all-keys.sh; fi" } sync_dashboard_runtime() { if [ -n "$THEME_ID" ]; then if [ -n "$ORIENTATION" ]; then sh "$SYNC_SCRIPT" "$HOST_TARGET" --theme "$THEME_ID" --orientation "$ORIENTATION" else sh "$SYNC_SCRIPT" "$HOST_TARGET" --theme "$THEME_ID" fi else sh "$SYNC_SCRIPT" "$HOST_TARGET" fi } show_background_once() { load_theme_selection ssh "$HOST_TARGET" "/mnt/us/dashboard/switch-theme.sh '$RESOLVED_THEME_ID' '$RESOLVED_ORIENTATION'" } start_dashboard_loop() { # 当前仓库里最稳的是 SSH 触发启动;这里后台拉起,便于 bootstrap 脚本直接返回。 ssh "$HOST_TARGET" "mkdir -p /mnt/us/dashboard/logs && cd /mnt/us/dashboard && nohup ./start.sh >/mnt/us/dashboard/logs/bootstrap-start.log 2>&1 &2 echo "请先在 Kindle 上连上 Wi-Fi,并在 KTerm 执行 sh /mnt/us/ssh-force-dropbear-22.sh" >&2 exit 1 fi log_step "SSH" "修复设备侧 SSH 辅助脚本权限与 authorized_keys" prepare_remote_helpers log_step "同步" "同步 dashboard 运行时和主题包到 Kindle" sync_dashboard_runtime if [ "$SHOW_BACKGROUND" = true ]; then log_step "显示" "立即切到当前主题并出图" show_background_once fi if [ "$START_DASHBOARD" = true ]; then log_step "启动" "后台启动 dashboard 主循环" start_dashboard_loop fi log_step "完成" "SSH 阶段自动化已完成" print_post_ssh_summary } while [ "$#" -gt 0 ]; do case "$1" in all|prepare-storage|post-ssh) MODE=$1 ;; -v|--volume) shift VOLUME_PATH=${1:?"missing volume path"} ;; -k|--kindle) shift HOST_TARGET=${1:?"missing kindle host"} ;; -t|--theme) shift THEME_ID=${1:?"missing theme id"} ;; -o|--orientation) shift ORIENTATION=${1:?"missing orientation"} ;; --kterm-package) shift KTERM_PACKAGE=${1:?"missing KTerm package path"} ;; --download-kterm) DOWNLOAD_KTERM=true ;; --kterm-version) shift KTERM_VERSION=${1:?"missing KTerm version"} ;; --no-background) SHOW_BACKGROUND=false ;; --start-dashboard) START_DASHBOARD=true ;; -h|--help) print_usage exit 0 ;; *) echo "未知参数: $1" >&2 print_usage >&2 exit 1 ;; esac shift done require_path "$THEMES_JSON" "themes.json" require_path "$SYNC_SCRIPT" "sync-layered-clock-to-kindle.sh" case "$MODE" in prepare-storage) prepare_storage ;; post-ssh) post_ssh ;; all) did_anything=false if kindle_volume_available; then prepare_storage did_anything=true else log_step "跳过" "未检测到 Kindle 存储挂载:$VOLUME_PATH" fi if ssh_reachable; then post_ssh did_anything=true else log_step "等待" "SSH 还未打通;等 Kindle 连上 Wi-Fi 并在 KTerm 执行 sh /mnt/us/ssh-force-dropbear-22.sh 后,再重跑本脚本或执行 post-ssh。" fi if [ "$did_anything" != true ]; then echo "既没有检测到 Kindle 存储挂载,也没有检测到可用 SSH。" >&2 echo "请先通过 USB 挂载 Kindle,或先恢复 SSH 后再执行。" >&2 exit 1 fi ;; esac