Files
kindle-calendar/bootstrap-new-kindle.sh
2026-03-21 18:44:12 +08:00

599 lines
19 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 打通后的自动化收尾;默认要求设备上已存在 prepare-storage 预置的 dashboard 基础运行时
选项:
-v, --volume <path> Kindle 挂载目录,默认 /Volumes/Kindle
-k, --kindle <host> Kindle SSH 主机名,默认 kindle
-t, --theme <theme-id> SSH 阶段切换到指定主题;默认使用 themes.json 的默认主题
-o, --orientation <value> SSH 阶段切换到指定方向;默认使用 themes.json 的默认方向
--kterm-package <path> 指定 KTerm 安装包;官方 release 用 .zip也兼容外部 .bin
--download-kterm 在 Mac 侧联网下载 KTerm 到 dash/staging/kterm/,再预置到 Kindle
--kterm-version <tag> 下载指定 KTerm 版本;默认 latest
--no-background SSH 阶段同步后不立即切主题出图
--start-dashboard SSH 阶段额外后台启动 dashboard 主循环;默认要求设备上已有完整 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”。
关于 post-ssh
1. 它当前会同步 dashboard 的 shell 脚本、KUAL 菜单和主题包。
2. 它不会回补 prepare-storage 阶段预置的原生二进制,例如 next-wakeup、xh。
3. 因此最稳的用法仍然是:先跑 prepare-storage完成设备侧步骤后再跑 post-ssh。
推荐操作顺序:
阶段 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 zipextensions/ 里会被解压出 kterm/
- 如果提供了外部 KTerm binmrpackages/ 里会出现对应文件
5. 安全弹出 Kindle。
阶段 B在 Kindle 上完成 WatchThis 和越狱
1. 恢复出厂。
2. 语言只选 English (United Kingdom)。
3. 到 Wi-Fi 页面时,不要真的联网;先按 WatchThis 文档进入 demo mode。
4. 搜索 ;demo进入 demo menu -> Sideload Content。
5. 到这个页面后再接 USB因为阶段 A 已经预置好 .demo 内容,这里只需确认文件存在。
6. 弹出 Kindle在设备上点 Done退出 demo menu。
7. 进入 ;dsts -> Help & User Guides -> Get Started。
8. 按设备提示继续完成 register this demo -> Skip -> standard。
9. 重启后如果看到 Configure Device用隐藏手势回到主页。
10. 搜索 ;uzb再接 USB把 Update_hotfix_watchthis_custom.bin 放到根目录。
11. 弹出设备后,进入 ;dsts -> Device Options -> Update Your Kindle。
阶段 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
阶段 ESSH 打通后自动同步并出图
1. 执行:
sh bootstrap-new-kindle.sh post-ssh -t simple -o portrait
2. 预期结果:
- 自动同步 dashboard shell 脚本、KUAL 菜单和主题包
- Kindle 立即切到 simple / portrait
- 屏幕马上显示背景和时钟
3. 如果还希望后台常驻 dashboard 主循环,再执行:
sh bootstrap-new-kindle.sh post-ssh -t simple -o portrait --start-dashboard
这一步默认建立在 prepare-storage 已经把完整 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. `Get Started` 只负责触发越狱脚本;真正退出 demo 状态还要再执行一次 `Update Your Kindle` 安装 custom hotfix。
2. `Update Your Kindle` 之前,先确认根目录里是真正的 `Update_hotfix_watchthis_custom.bin`,不要把 `._Update_hotfix_watchthis_custom.bin` 误当成正确文件。
3. 首次 SSH 最稳的入口是 KTerm不是 USB 直连盲试。
4. post-ssh 阶段如果不带 -t/-o会退回 themes.json 里的默认主题和方向。
5. 如果本轮没有预置 KTerm 包,阶段 C 结束后仍需你手工补装 KTerm。
6. post-ssh 不是“从零补齐 dashboard”的入口next-wakeup、xh 这类原生二进制仍来自 prepare-storage。
示例:
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 <<EOF
$release_info
EOF
if [ -z "$downloaded_name" ] || [ -z "$downloaded_url" ]; then
echo "无法解析 KTerm 下载地址。" >&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 <<EOF
$resolved_output
EOF
if [ -z "$RESOLVED_THEME_ID" ] || [ -z "$RESOLVED_ORIENTATION" ]; then
echo "无法解析主题和方向。" >&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 </dev/null &"
}
print_post_ssh_summary() {
load_theme_selection
cat <<EOF
SSH 阶段已完成:
- 主机:$HOST_TARGET
- 已同步 dashboard shell 脚本、KUAL 菜单与主题包
- 当前用于出图的主题:$RESOLVED_THEME_ID / $RESOLVED_ORIENTATION
如果你要继续验证:
- SSH 登录ssh $HOST_TARGET
- 立即切主题ssh $HOST_TARGET '/mnt/us/dashboard/switch-theme.sh $RESOLVED_THEME_ID $RESOLVED_ORIENTATION'
- 抓图到本地sh snapshot.sh -t $RESOLVED_THEME_ID -o $RESOLVED_ORIENTATION
EOF
}
post_ssh() {
if ! ssh_reachable; then
echo "当前还无法通过 SSH 连接 ${HOST_TARGET}" >&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 shell 脚本、KUAL 菜单和主题包到 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