mirror of
https://github.com/wnlen/clash-for-linux.git
synced 2026-10-10 04:03:04 +08:00
Add strict offline asset bundles
This commit is contained in:
+31
-22
@@ -1,22 +1,31 @@
|
|||||||
# IDE
|
# IDE
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
# Local env
|
# Local env
|
||||||
.env
|
.env
|
||||||
|
|
||||||
# Runtime
|
# Runtime
|
||||||
runtime/
|
runtime/
|
||||||
|
|
||||||
# Local dashboard unpacked files
|
# Optional offline assets
|
||||||
resources/dashboard/dist/
|
resources/bin/mihomo/
|
||||||
|
resources/bin/clash/
|
||||||
# Keep packaged dashboard asset
|
resources/bin/yq/
|
||||||
!resources/dashboard/dist.zip
|
resources/bin/subconverter/
|
||||||
|
resources/geo/
|
||||||
# Local test scripts
|
resources/asset-manifest.env
|
||||||
test.sh
|
resources/SHA256SUMS
|
||||||
|
|
||||||
# OpenClaw control-plane local scaffolding
|
# Local dashboard unpacked files
|
||||||
ai/
|
resources/dashboard/dist/
|
||||||
.openclaw-control-plane/
|
|
||||||
|
# Keep packaged dashboard asset
|
||||||
|
!resources/dashboard/dist.zip
|
||||||
|
|
||||||
|
# Local test scripts
|
||||||
|
test.sh
|
||||||
|
|
||||||
|
# OpenClaw control-plane local scaffolding
|
||||||
|
ai/
|
||||||
|
.openclaw-control-plane/
|
||||||
|
|||||||
@@ -312,13 +312,14 @@ MIXED_PORT=7890
|
|||||||
EXTERNAL_CONTROLLER=0.0.0.0:9090
|
EXTERNAL_CONTROLLER=0.0.0.0:9090
|
||||||
CLASH_CONTROLLER_SECRET=your-secret
|
CLASH_CONTROLLER_SECRET=your-secret
|
||||||
CLASH_SUBSCRIPTION_URL=https://example.com/sub
|
CLASH_SUBSCRIPTION_URL=https://example.com/sub
|
||||||
MIHOMO_VERSION=latest
|
MIHOMO_VERSION=v1.19.23
|
||||||
CLASH_VERSION=latest
|
CLASH_VERSION=v1.18.0
|
||||||
YQ_VERSION=v4.44.3
|
YQ_VERSION=v4.52.4
|
||||||
SUBCONVERTER_VERSION=v0.9.9
|
SUBCONVERTER_VERSION=v0.9.9
|
||||||
MIHOMO_DOWNLOAD_BASE=https://github.com/MetaCubeX/mihomo/releases/download
|
MIHOMO_DOWNLOAD_BASE=https://github.com/MetaCubeX/mihomo/releases/download
|
||||||
CLASH_DOWNLOAD_BASE=https://github.com/WindSpiritSR/clash/releases/download
|
CLASH_DOWNLOAD_BASE=https://github.com/WindSpiritSR/clash/releases/download
|
||||||
CLASH_BUNDLED_ASSET_ENABLED=true
|
CLASH_BUNDLED_ASSET_ENABLED=true
|
||||||
|
CLASH_OFFLINE=false
|
||||||
CLASH_SHELL_AUTO_RESTORE_PROXY=true
|
CLASH_SHELL_AUTO_RESTORE_PROXY=true
|
||||||
CLASH_PREDOWNLOAD_GEO=true
|
CLASH_PREDOWNLOAD_GEO=true
|
||||||
```
|
```
|
||||||
@@ -349,9 +350,7 @@ CLASH_GH_PROXY=https://ghfast.top
|
|||||||
|
|
||||||
当前正式支持的架构为 `amd64`、`arm64`、`armv7`。超出这三种架构时会明确失败,不会伪装成已支持。
|
当前正式支持的架构为 `amd64`、`arm64`、`armv7`。超出这三种架构时会明确失败,不会伪装成已支持。
|
||||||
|
|
||||||
如果安装环境访问 GitHub 很慢,可以把 Mihomo、yq、subconverter 的刚需文件跟随项目一起分发。安装和 `clash upgrade` 会优先读取 `resources/bin` 中与当前版本、架构对应的精确文件名;本地没有对应文件时,会回退到原来的远程下载逻辑,不影响后续升级内核。
|
如果安装环境访问 GitHub 很慢,可以把 Mihomo/Clash、yq、subconverter 的刚需文件跟随项目一起分发。安装和 `clash upgrade` 会优先读取 `resources/bin` 中与当前版本、架构对应的精确文件名;普通在线模式下,本地没有对应文件时会回退到远程下载。
|
||||||
|
|
||||||
Clash 仅作为兼容内核处理,固定走远程下载,不会命中 `resources/bin` 中的本地资源。
|
|
||||||
|
|
||||||
推荐路径直接放在分类目录下:
|
推荐路径直接放在分类目录下:
|
||||||
|
|
||||||
@@ -359,6 +358,9 @@ Clash 仅作为兼容内核处理,固定走远程下载,不会命中 `resour
|
|||||||
resources/bin/mihomo/mihomo-linux-amd64-compatible-v1.19.23.gz
|
resources/bin/mihomo/mihomo-linux-amd64-compatible-v1.19.23.gz
|
||||||
resources/bin/mihomo/mihomo-linux-arm64-v1.19.23.gz
|
resources/bin/mihomo/mihomo-linux-arm64-v1.19.23.gz
|
||||||
resources/bin/mihomo/mihomo-linux-armv7-v1.19.23.gz
|
resources/bin/mihomo/mihomo-linux-armv7-v1.19.23.gz
|
||||||
|
resources/bin/clash/clash-linux-amd64-v1.18.0.gz
|
||||||
|
resources/bin/clash/clash-linux-arm64-v1.18.0.gz
|
||||||
|
resources/bin/clash/clash-linux-armv7-v1.18.0.gz
|
||||||
resources/bin/yq/yq_linux_amd64.tar.gz
|
resources/bin/yq/yq_linux_amd64.tar.gz
|
||||||
resources/bin/yq/yq_linux_arm64.tar.gz
|
resources/bin/yq/yq_linux_arm64.tar.gz
|
||||||
resources/bin/yq/yq_linux_arm.tar.gz
|
resources/bin/yq/yq_linux_arm.tar.gz
|
||||||
@@ -372,6 +374,39 @@ resources/geo/Country.mmdb
|
|||||||
|
|
||||||
也可以设置 `CLASH_BUNDLED_ASSET_ENABLED=false` 强制跳过内置文件,或用 `CLASH_BUNDLED_ASSET_DIR=/path/to/assets` 指向项目外的资源目录。Mihomo、yq、subconverter 兼容旧路径 `resources/bin/<category>/<version>/<file>`。
|
也可以设置 `CLASH_BUNDLED_ASSET_ENABLED=false` 强制跳过内置文件,或用 `CLASH_BUNDLED_ASSET_DIR=/path/to/assets` 指向项目外的资源目录。Mihomo、yq、subconverter 兼容旧路径 `resources/bin/<category>/<version>/<file>`。
|
||||||
|
|
||||||
|
#### 离线资源包
|
||||||
|
|
||||||
|
推荐在有代理或网络较好的设备上,按目标主机架构生成资源包:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看将要下载的文件,不产生网络请求
|
||||||
|
bash scripts/prefetch-assets.sh --arch amd64 --dry-run
|
||||||
|
|
||||||
|
# 生成 amd64 + Mihomo 资源包
|
||||||
|
bash scripts/prefetch-assets.sh \
|
||||||
|
--arch amd64 \
|
||||||
|
--kernel mihomo \
|
||||||
|
--output clash-assets-mihomo-amd64.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
支持的目标架构为 `amd64`、`arm64`、`armv7`。脚本默认包含内核、yq、subconverter 和五个 GEO 文件,只打包 `resources/` 静态资源,不会把本机的订阅、日志、密钥或其他 `runtime/` 状态带入资源包。压缩包同时包含版本/架构清单与 `SHA256SUMS`。
|
||||||
|
|
||||||
|
把项目和资源包复制到目标主机,在项目根目录执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tar -xzf clash-assets-mihomo-amd64.tar.gz
|
||||||
|
bash install.sh --offline
|
||||||
|
```
|
||||||
|
|
||||||
|
`--offline` 等价于本次安装设置 `CLASH_OFFLINE=true`。安装脚本会先集中检查架构、版本、校验和及所有必需文件;有任何缺失都会明确列出并停止,不会静默回退到 GitHub。也可以和安装范围一起使用:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash install.sh system --offline
|
||||||
|
bash install.sh user --offline
|
||||||
|
```
|
||||||
|
|
||||||
|
严格离线安装时,订阅也不能使用远程 URL。可暂时不配置订阅,或者提前准备 Clash YAML 并通过 `file:///绝对路径/config.yaml` 导入。使用 `--no-geo` 生成精简资源包时,目标项目还需要设置 `CLASH_PREDOWNLOAD_GEO=false`;若最终配置使用 `GEOIP`,仍须提供 `Country.mmdb`。
|
||||||
|
|
||||||
### `runtime/mixin.yaml`(兼容 `config/mixin.yaml`)
|
### `runtime/mixin.yaml`(兼容 `config/mixin.yaml`)
|
||||||
|
|
||||||
用于对最终运行配置做补丁:
|
用于对最终运行配置做补丁:
|
||||||
|
|||||||
+13
-2
@@ -12,10 +12,20 @@ source "$PROJECT_DIR/scripts/init/systemd-user.sh"
|
|||||||
source "$PROJECT_DIR/scripts/init/script.sh"
|
source "$PROJECT_DIR/scripts/init/script.sh"
|
||||||
|
|
||||||
init_project_context "$PROJECT_DIR"
|
init_project_context "$PROJECT_DIR"
|
||||||
guard_unsafe_sudo_auto_install "${1:-}"
|
parse_install_options "$@"
|
||||||
|
|
||||||
|
if [ "$INSTALL_SHOW_HELP" = "true" ]; then
|
||||||
|
print_install_usage
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
guard_unsafe_sudo_auto_install "$INSTALL_REQUESTED_SCOPE"
|
||||||
load_env_if_exists
|
load_env_if_exists
|
||||||
|
if [ "$INSTALL_OFFLINE_REQUESTED" = "true" ]; then
|
||||||
|
export CLASH_OFFLINE="true"
|
||||||
|
fi
|
||||||
migrate_env_legacy_compat_fields "$PROJECT_DIR/.env"
|
migrate_env_legacy_compat_fields "$PROJECT_DIR/.env"
|
||||||
detect_install_scope "${1:-auto}"
|
detect_install_scope "$INSTALL_REQUESTED_SCOPE"
|
||||||
ensure_project_not_wsl_windows_mount
|
ensure_project_not_wsl_windows_mount
|
||||||
|
|
||||||
ensure_openwrt_install_supported
|
ensure_openwrt_install_supported
|
||||||
@@ -23,6 +33,7 @@ ensure_required_commands
|
|||||||
|
|
||||||
init_layout
|
init_layout
|
||||||
ensure_dashboard_deploy_prerequisites
|
ensure_dashboard_deploy_prerequisites
|
||||||
|
preflight_offline_assets
|
||||||
|
|
||||||
resolve_geo_assets
|
resolve_geo_assets
|
||||||
|
|
||||||
|
|||||||
+46
-36
@@ -1,36 +1,46 @@
|
|||||||
# 内置运行依赖
|
# 内置运行依赖
|
||||||
|
|
||||||
如果安装时 GitHub 下载太慢,可以把 Mihomo、yq、subconverter 提前放到本目录。安装和 `clash upgrade` 会优先读取这里的文件;没有匹配文件时,仍会回退到原来的下载逻辑。
|
如果安装时 GitHub 下载太慢,可以把 Mihomo/Clash、yq、subconverter 提前放到本目录。安装和 `clash upgrade` 会优先读取这里的文件;普通在线模式下,没有匹配文件时仍会回退到原来的下载逻辑。
|
||||||
|
|
||||||
Clash 是兼容内核,固定走远程下载,不使用本目录中的本地资源。
|
默认目录结构:
|
||||||
|
|
||||||
默认目录结构:
|
```text
|
||||||
|
resources/bin/
|
||||||
```text
|
mihomo/
|
||||||
resources/bin/
|
mihomo-linux-amd64-compatible-v1.19.23.gz
|
||||||
mihomo/
|
mihomo-linux-arm64-v1.19.23.gz
|
||||||
mihomo-linux-amd64-compatible-v1.19.23.gz
|
mihomo-linux-armv7-v1.19.23.gz
|
||||||
mihomo-linux-arm64-v1.19.23.gz
|
clash/
|
||||||
mihomo-linux-armv7-v1.19.23.gz
|
clash-linux-amd64-v1.18.0.gz
|
||||||
yq/
|
clash-linux-arm64-v1.18.0.gz
|
||||||
yq_linux_amd64.tar.gz
|
clash-linux-armv7-v1.18.0.gz
|
||||||
yq_linux_arm64.tar.gz
|
yq/
|
||||||
yq_linux_arm.tar.gz
|
yq_linux_amd64.tar.gz
|
||||||
subconverter/
|
yq_linux_arm64.tar.gz
|
||||||
subconverter_linux64.tar.gz
|
yq_linux_arm.tar.gz
|
||||||
subconverter_aarch64.tar.gz
|
subconverter/
|
||||||
subconverter_armv7.tar.gz
|
subconverter_linux64.tar.gz
|
||||||
```
|
subconverter_aarch64.tar.gz
|
||||||
|
subconverter_armv7.tar.gz
|
||||||
当前正式支持 `amd64`、`arm64`、`armv7`。版本号要和 `.env` 里的 `MIHOMO_VERSION`、`YQ_VERSION`、`SUBCONVERTER_VERSION` 对应。脚本只按当前目标文件名精确命中,不会扫描目录,也不会自动选择最高版本;如果本地没有对应文件,会继续联网下载。
|
```
|
||||||
|
|
||||||
Mihomo、yq、subconverter 兼容旧路径 `resources/bin/<category>/<version>/<file>`,但默认推荐直接使用 `resources/bin/<category>/<file>`。
|
当前正式支持 `amd64`、`arm64`、`armv7`。版本号要和 `.env` 里的 `MIHOMO_VERSION`、`YQ_VERSION`、`SUBCONVERTER_VERSION` 对应。脚本只按当前目标文件名精确命中,不会扫描目录,也不会自动选择最高版本;如果本地没有对应文件,会继续联网下载。
|
||||||
|
|
||||||
可选开关:
|
Mihomo、yq、subconverter 兼容旧路径 `resources/bin/<category>/<version>/<file>`,但默认推荐直接使用 `resources/bin/<category>/<file>`。
|
||||||
|
|
||||||
```bash
|
可选开关:
|
||||||
export CLASH_BUNDLED_ASSET_ENABLED=false
|
|
||||||
export CLASH_BUNDLED_ASSET_DIR=/path/to/assets
|
```bash
|
||||||
```
|
export CLASH_BUNDLED_ASSET_ENABLED=false
|
||||||
|
export CLASH_BUNDLED_ASSET_DIR=/path/to/assets
|
||||||
`Country.mmdb` 仍放在 `resources/geo/Country.mmdb` 或 `resources/geo/country.mmdb`。
|
```
|
||||||
|
|
||||||
|
`Country.mmdb` 仍放在 `resources/geo/Country.mmdb` 或 `resources/geo/country.mmdb`。
|
||||||
|
|
||||||
|
推荐通过项目提供的脚本生成完整资源包:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash scripts/prefetch-assets.sh --arch amd64 --kernel mihomo
|
||||||
|
```
|
||||||
|
|
||||||
|
将生成的压缩包复制到目标主机并解压到项目根目录后,可执行 `bash install.sh --offline`。严格离线模式会在安装前检查全部资源,缺失时直接失败,不会回退联网下载。资源包只包含 `resources/`,不会包含可能带有订阅、日志或密钥的 `runtime/`。
|
||||||
|
|||||||
+147
-10
@@ -428,16 +428,75 @@ bundled_asset_root() {
|
|||||||
echo "$RESOURCE_DIR/bin"
|
echo "$RESOURCE_DIR/bin"
|
||||||
}
|
}
|
||||||
|
|
||||||
copy_bundled_asset() {
|
bundled_asset_metadata_root() {
|
||||||
|
if [ -n "${CLASH_BUNDLED_ASSET_DIR:-}" ]; then
|
||||||
|
echo "$CLASH_BUNDLED_ASSET_DIR"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ -n "${RESOURCE_DIR:-}" ] || return 1
|
||||||
|
echo "$RESOURCE_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
bundled_asset_manifest_file() {
|
||||||
|
local root
|
||||||
|
|
||||||
|
root="$(bundled_asset_metadata_root 2>/dev/null || true)"
|
||||||
|
[ -n "${root:-}" ] || return 1
|
||||||
|
echo "$root/asset-manifest.env"
|
||||||
|
}
|
||||||
|
|
||||||
|
bundled_asset_checksums_file() {
|
||||||
|
local root
|
||||||
|
|
||||||
|
root="$(bundled_asset_metadata_root 2>/dev/null || true)"
|
||||||
|
[ -n "${root:-}" ] || return 1
|
||||||
|
echo "$root/SHA256SUMS"
|
||||||
|
}
|
||||||
|
|
||||||
|
read_bundled_asset_manifest_value() {
|
||||||
|
local key="$1"
|
||||||
|
local file
|
||||||
|
|
||||||
|
file="$(bundled_asset_manifest_file 2>/dev/null || true)"
|
||||||
|
[ -n "${file:-}" ] && [ -f "$file" ] || return 1
|
||||||
|
|
||||||
|
sed -nE "s/^[[:space:]]*${key}=['\"]?([^'\"]*)['\"]?$/\1/p" "$file" | head -n 1
|
||||||
|
}
|
||||||
|
|
||||||
|
bundled_asset_manifest_compatible() {
|
||||||
|
local category="$1"
|
||||||
|
local version="$2"
|
||||||
|
local file bundle_arch bundle_version version_key
|
||||||
|
|
||||||
|
file="$(bundled_asset_manifest_file 2>/dev/null || true)"
|
||||||
|
[ -n "${file:-}" ] && [ -f "$file" ] || return 0
|
||||||
|
|
||||||
|
bundle_arch="$(read_bundled_asset_manifest_value "BUNDLE_ARCH" 2>/dev/null || true)"
|
||||||
|
if [ -n "${bundle_arch:-}" ] && [ "$bundle_arch" != "$(get_arch)" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$category" in
|
||||||
|
mihomo) version_key="MIHOMO_VERSION" ;;
|
||||||
|
clash) version_key="CLASH_VERSION" ;;
|
||||||
|
yq) version_key="YQ_VERSION" ;;
|
||||||
|
subconverter) version_key="SUBCONVERTER_VERSION" ;;
|
||||||
|
*) return 0 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
bundle_version="$(read_bundled_asset_manifest_value "$version_key" 2>/dev/null || true)"
|
||||||
|
[ -z "${bundle_version:-}" ] || [ "$bundle_version" = "$version" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
find_bundled_asset() {
|
||||||
local category="$1"
|
local category="$1"
|
||||||
local version="$2"
|
local version="$2"
|
||||||
local file="$3"
|
local file="$3"
|
||||||
local out="$4"
|
|
||||||
local asset_name="${5:-$file}"
|
|
||||||
local root candidate
|
local root candidate
|
||||||
|
|
||||||
[ "$category" != "clash" ] || return 1
|
|
||||||
bundled_asset_enabled || return 1
|
bundled_asset_enabled || return 1
|
||||||
|
bundled_asset_manifest_compatible "$category" "$version" || return 1
|
||||||
|
|
||||||
root="$(bundled_asset_root 2>/dev/null || true)"
|
root="$(bundled_asset_root 2>/dev/null || true)"
|
||||||
[ -n "${root:-}" ] || return 1
|
[ -n "${root:-}" ] || return 1
|
||||||
@@ -448,18 +507,48 @@ copy_bundled_asset() {
|
|||||||
"$root/$file" \
|
"$root/$file" \
|
||||||
"$RESOURCE_DIR/$category/$file"; do
|
"$RESOURCE_DIR/$category/$file"; do
|
||||||
[ -s "$candidate" ] || continue
|
[ -s "$candidate" ] || continue
|
||||||
|
echo "$candidate"
|
||||||
mkdir -p "$(dirname "$out")"
|
|
||||||
if [ "$candidate" != "$out" ]; then
|
|
||||||
cp -f "$candidate" "$out"
|
|
||||||
fi
|
|
||||||
success "${asset_name} 已使用内置资源:$candidate"
|
|
||||||
return 0
|
return 0
|
||||||
done
|
done
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
copy_bundled_asset() {
|
||||||
|
local category="$1"
|
||||||
|
local version="$2"
|
||||||
|
local file="$3"
|
||||||
|
local out="$4"
|
||||||
|
local asset_name="${5:-$file}"
|
||||||
|
local candidate
|
||||||
|
|
||||||
|
candidate="$(find_bundled_asset "$category" "$version" "$file" 2>/dev/null || true)"
|
||||||
|
[ -n "${candidate:-}" ] || return 1
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$out")"
|
||||||
|
if [ "$candidate" != "$out" ]; then
|
||||||
|
cp -f "$candidate" "$out"
|
||||||
|
fi
|
||||||
|
success "${asset_name} 已使用内置资源:$candidate"
|
||||||
|
}
|
||||||
|
|
||||||
|
offline_mode_enabled() {
|
||||||
|
case "${CLASH_OFFLINE:-false}" in
|
||||||
|
true|1|yes|on|TRUE|YES|ON) return 0 ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
offline_download_blocked() {
|
||||||
|
local asset_name="$1"
|
||||||
|
local url="${2:-}"
|
||||||
|
local url_hint=""
|
||||||
|
|
||||||
|
[ -n "${url:-}" ] && url_hint=";远程地址:$url"
|
||||||
|
die_state "离线模式禁止下载:${asset_name}${url_hint}" \
|
||||||
|
"请补齐离线资源包后重试,或移除 --offline / CLASH_OFFLINE=true 允许联网下载"
|
||||||
|
}
|
||||||
|
|
||||||
download_connect_timeout() {
|
download_connect_timeout() {
|
||||||
echo "${CLASH_DOWNLOAD_CONNECT_TIMEOUT:-8}"
|
echo "${CLASH_DOWNLOAD_CONNECT_TIMEOUT:-8}"
|
||||||
}
|
}
|
||||||
@@ -934,6 +1023,10 @@ download_file() {
|
|||||||
local tried_urls=""
|
local tried_urls=""
|
||||||
local fetch_tmp
|
local fetch_tmp
|
||||||
|
|
||||||
|
if offline_mode_enabled; then
|
||||||
|
offline_download_blocked "$asset_name" "$url"
|
||||||
|
fi
|
||||||
|
|
||||||
mkdir -p "$(dirname "$out")"
|
mkdir -p "$(dirname "$out")"
|
||||||
rm -f "$out" 2>/dev/null || true
|
rm -f "$out" 2>/dev/null || true
|
||||||
|
|
||||||
@@ -1031,6 +1124,10 @@ download_http_fetch_to_file() {
|
|||||||
local max_time="${5:-300}"
|
local max_time="${5:-300}"
|
||||||
local progress_arg="--progress-bar"
|
local progress_arg="--progress-bar"
|
||||||
|
|
||||||
|
if offline_mode_enabled; then
|
||||||
|
offline_download_blocked "$(basename "${url%%\?*}")" "$url"
|
||||||
|
fi
|
||||||
|
|
||||||
curl_download \
|
curl_download \
|
||||||
"$progress_arg" \
|
"$progress_arg" \
|
||||||
--show-error \
|
--show-error \
|
||||||
@@ -1171,6 +1268,46 @@ guard_unsafe_sudo_auto_install() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parse_install_options() {
|
||||||
|
local arg
|
||||||
|
|
||||||
|
INSTALL_REQUESTED_SCOPE="auto"
|
||||||
|
INSTALL_OFFLINE_REQUESTED="false"
|
||||||
|
INSTALL_SHOW_HELP="false"
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
system|user|auto)
|
||||||
|
if [ "$INSTALL_REQUESTED_SCOPE" != "auto" ] && [ "$INSTALL_REQUESTED_SCOPE" != "$arg" ]; then
|
||||||
|
die_usage "安装范围不能同时指定为 $INSTALL_REQUESTED_SCOPE 和 $arg" \
|
||||||
|
"用法:bash install.sh [system|user|auto] [--offline]"
|
||||||
|
fi
|
||||||
|
INSTALL_REQUESTED_SCOPE="$arg"
|
||||||
|
;;
|
||||||
|
--offline)
|
||||||
|
INSTALL_OFFLINE_REQUESTED="true"
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
INSTALL_SHOW_HELP="true"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die_usage "未知安装参数:$arg" \
|
||||||
|
"用法:bash install.sh [system|user|auto] [--offline]"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
print_install_usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
用法:
|
||||||
|
bash install.sh [system|user|auto] [--offline]
|
||||||
|
|
||||||
|
选项:
|
||||||
|
--offline 严格离线安装;缺少本地资源时直接失败,不回退联网下载
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
detect_install_scope() {
|
detect_install_scope() {
|
||||||
local requested="${1:-auto}"
|
local requested="${1:-auto}"
|
||||||
|
|
||||||
|
|||||||
+261
-47
@@ -13,10 +13,256 @@ GEO_ASSET_DOWNLOADS=(
|
|||||||
"resources/geo/GeoSite.dat https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat"
|
"resources/geo/GeoSite.dat https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat"
|
||||||
)
|
)
|
||||||
|
|
||||||
resolve_geo_assets() {
|
geo_predownload_enabled() {
|
||||||
case "${CLASH_PREDOWNLOAD_GEO:-true}" in
|
case "${CLASH_PREDOWNLOAD_GEO:-true}" in
|
||||||
0|false|no|off|disable|disabled|FALSE|NO|OFF) return 0 ;;
|
0|false|no|off|disable|disabled|FALSE|NO|OFF) return 1 ;;
|
||||||
|
*) return 0 ;;
|
||||||
esac
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
yq_asset_file() {
|
||||||
|
case "$1" in
|
||||||
|
amd64) echo "yq_linux_amd64.tar.gz" ;;
|
||||||
|
arm64) echo "yq_linux_arm64.tar.gz" ;;
|
||||||
|
armv7) echo "yq_linux_arm.tar.gz" ;;
|
||||||
|
*) die "暂不支持的 yq 架构:$1" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
mihomo_asset_file() {
|
||||||
|
local arch="$1"
|
||||||
|
local version="$2"
|
||||||
|
|
||||||
|
case "$arch" in
|
||||||
|
amd64) echo "mihomo-linux-amd64-compatible-${version}.gz" ;;
|
||||||
|
arm64) echo "mihomo-linux-arm64-${version}.gz" ;;
|
||||||
|
armv7) echo "mihomo-linux-armv7-${version}.gz" ;;
|
||||||
|
*) die "暂不支持的 Mihomo 架构:$arch" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
subconverter_asset_file() {
|
||||||
|
case "$1" in
|
||||||
|
amd64) echo "subconverter_linux64.tar.gz" ;;
|
||||||
|
arm64) echo "subconverter_aarch64.tar.gz" ;;
|
||||||
|
armv7) echo "subconverter_armv7.tar.gz" ;;
|
||||||
|
*) die "暂不支持的 subconverter 架构:$1" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
clash_asset_candidates() {
|
||||||
|
local arch="$1"
|
||||||
|
local version="$2"
|
||||||
|
local version_no_v="${version#v}"
|
||||||
|
|
||||||
|
case "$arch" in
|
||||||
|
amd64)
|
||||||
|
printf '%s\n' \
|
||||||
|
"clash-linux-amd64-${version}.gz" \
|
||||||
|
"clash-linux-amd64-v${version_no_v}.gz"
|
||||||
|
;;
|
||||||
|
arm64)
|
||||||
|
printf '%s\n' \
|
||||||
|
"clash-linux-arm64-${version}.gz" \
|
||||||
|
"clash-linux-arm64-v${version_no_v}.gz" \
|
||||||
|
"clash-linux-armv8-${version}.gz" \
|
||||||
|
"clash-linux-armv8-v${version_no_v}.gz"
|
||||||
|
;;
|
||||||
|
armv7)
|
||||||
|
printf '%s\n' \
|
||||||
|
"clash-linux-armv7-${version}.gz" \
|
||||||
|
"clash-linux-armv7-v${version_no_v}.gz"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die "暂不支持的 Clash 架构:$arch"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_offline_asset_bundle_metadata() {
|
||||||
|
local manifest checksums metadata_root actual expected kernel
|
||||||
|
local failed="false"
|
||||||
|
|
||||||
|
manifest="$(bundled_asset_manifest_file 2>/dev/null || true)"
|
||||||
|
checksums="$(bundled_asset_checksums_file 2>/dev/null || true)"
|
||||||
|
metadata_root="$(bundled_asset_metadata_root 2>/dev/null || true)"
|
||||||
|
|
||||||
|
if [ -n "${manifest:-}" ] && [ -f "$manifest" ]; then
|
||||||
|
actual="$(read_bundled_asset_manifest_value "BUNDLE_ARCH" 2>/dev/null || true)"
|
||||||
|
expected="$(get_arch)"
|
||||||
|
if [ -n "${actual:-}" ] && [ "$actual" != "$expected" ]; then
|
||||||
|
error "离线资源包架构不匹配:资源包=$actual,目标=$expected"
|
||||||
|
failed="true"
|
||||||
|
fi
|
||||||
|
|
||||||
|
actual="$(read_bundled_asset_manifest_value "YQ_VERSION" 2>/dev/null || true)"
|
||||||
|
expected="${YQ_VERSION:-$DEFAULT_YQ_VERSION}"
|
||||||
|
if [ -n "${actual:-}" ] && [ "$actual" != "$expected" ]; then
|
||||||
|
error "离线资源包 yq 版本不匹配:资源包=$actual,目标=$expected"
|
||||||
|
failed="true"
|
||||||
|
fi
|
||||||
|
|
||||||
|
actual="$(read_bundled_asset_manifest_value "SUBCONVERTER_VERSION" 2>/dev/null || true)"
|
||||||
|
expected="${SUBCONVERTER_VERSION:-$DEFAULT_SUBCONVERTER_VERSION}"
|
||||||
|
if [ -n "${actual:-}" ] && [ "$actual" != "$expected" ]; then
|
||||||
|
error "离线资源包 subconverter 版本不匹配:资源包=$actual,目标=$expected"
|
||||||
|
failed="true"
|
||||||
|
fi
|
||||||
|
|
||||||
|
kernel="$(runtime_kernel_type)"
|
||||||
|
case "$kernel" in
|
||||||
|
mihomo)
|
||||||
|
actual="$(read_bundled_asset_manifest_value "MIHOMO_VERSION" 2>/dev/null || true)"
|
||||||
|
expected="${MIHOMO_VERSION:-$DEFAULT_MIHOMO_VERSION}"
|
||||||
|
;;
|
||||||
|
clash)
|
||||||
|
actual="$(read_bundled_asset_manifest_value "CLASH_VERSION" 2>/dev/null || true)"
|
||||||
|
expected="${CLASH_VERSION:-$DEFAULT_CLASH_VERSION}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
actual=""
|
||||||
|
expected=""
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -n "${actual:-}" ] && [ "$actual" != "$expected" ]; then
|
||||||
|
error "离线资源包 $kernel 版本不匹配:资源包=$actual,目标=$expected"
|
||||||
|
failed="true"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "${checksums:-}" ] && [ -f "$checksums" ]; then
|
||||||
|
if command -v sha256sum >/dev/null 2>&1; then
|
||||||
|
if ! (cd "$metadata_root" && sha256sum -c "$(basename "$checksums")" >/dev/null); then
|
||||||
|
error "离线资源包 SHA256 校验失败:$checksums"
|
||||||
|
failed="true"
|
||||||
|
fi
|
||||||
|
elif command -v shasum >/dev/null 2>&1; then
|
||||||
|
if ! (cd "$metadata_root" && shasum -a 256 -c "$(basename "$checksums")" >/dev/null); then
|
||||||
|
error "离线资源包 SHA256 校验失败:$checksums"
|
||||||
|
failed="true"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "系统缺少 sha256sum/shasum,已跳过离线资源包完整性校验"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ "$failed" = "false" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
offline_asset_missing_text() {
|
||||||
|
local category="$1"
|
||||||
|
local version="$2"
|
||||||
|
local file="$3"
|
||||||
|
local label="$4"
|
||||||
|
|
||||||
|
if [ -n "${CLASH_BUNDLED_ASSET_DIR:-}" ]; then
|
||||||
|
printf '%s:%s/%s/%s(版本 %s)' \
|
||||||
|
"$label" "${CLASH_BUNDLED_ASSET_DIR%/}" "$category" "$file" "$version"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$category" in
|
||||||
|
geo)
|
||||||
|
printf '%s:resources/geo/%s' "$label" "$file"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf '%s:resources/bin/%s/%s(版本 %s)' "$label" "$category" "$file" "$version"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
preflight_offline_assets() {
|
||||||
|
offline_mode_enabled || return 0
|
||||||
|
|
||||||
|
local arch kernel version file item path
|
||||||
|
local missing=()
|
||||||
|
local candidate_found="false"
|
||||||
|
|
||||||
|
arch="$(get_arch)"
|
||||||
|
kernel="$(runtime_kernel_type)"
|
||||||
|
|
||||||
|
ui_section "离线资源预检"
|
||||||
|
ui_kv "🧩" "目标架构" "$arch"
|
||||||
|
ui_kv "🚀" "目标内核" "$kernel"
|
||||||
|
|
||||||
|
if ! verify_offline_asset_bundle_metadata; then
|
||||||
|
die_state "离线资源包元数据或完整性校验失败" \
|
||||||
|
"请重新生成与当前架构、版本匹配的资源包"
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$kernel" in
|
||||||
|
mihomo)
|
||||||
|
version="${MIHOMO_VERSION:-$DEFAULT_MIHOMO_VERSION}"
|
||||||
|
file="$(mihomo_asset_file "$arch" "$version")"
|
||||||
|
if [ ! -x "$(mihomo_bin)" ] \
|
||||||
|
&& ! find_bundled_asset "mihomo" "$version" "$file" >/dev/null 2>&1; then
|
||||||
|
missing+=("$(offline_asset_missing_text "mihomo" "$version" "$file" "Mihomo")")
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
clash)
|
||||||
|
version="${CLASH_VERSION:-$DEFAULT_CLASH_VERSION}"
|
||||||
|
if [ ! -x "$(clash_bin)" ]; then
|
||||||
|
candidate_found="false"
|
||||||
|
while IFS= read -r file; do
|
||||||
|
[ -n "${file:-}" ] || continue
|
||||||
|
if find_bundled_asset "clash" "$version" "$file" >/dev/null 2>&1; then
|
||||||
|
candidate_found="true"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done <<EOF
|
||||||
|
$(clash_asset_candidates "$arch" "$version")
|
||||||
|
EOF
|
||||||
|
if [ "$candidate_found" != "true" ]; then
|
||||||
|
file="$(clash_asset_candidates "$arch" "$version" | head -n 1)"
|
||||||
|
missing+=("$(offline_asset_missing_text "clash" "$version" "$file" "Clash")")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
missing+=("未知内核类型:$kernel")
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
version="${YQ_VERSION:-$DEFAULT_YQ_VERSION}"
|
||||||
|
file="$(yq_asset_file "$arch")"
|
||||||
|
if [ ! -x "$(yq_bin)" ] \
|
||||||
|
&& ! find_bundled_asset "yq" "$version" "$file" >/dev/null 2>&1; then
|
||||||
|
missing+=("$(offline_asset_missing_text "yq" "$version" "$file" "yq")")
|
||||||
|
fi
|
||||||
|
|
||||||
|
version="${SUBCONVERTER_VERSION:-$DEFAULT_SUBCONVERTER_VERSION}"
|
||||||
|
file="$(subconverter_asset_file "$arch")"
|
||||||
|
if ! { [ -x "$(subconverter_bin)" ] \
|
||||||
|
&& [ "$(subconverter_version_file_value)" = "$version" ] \
|
||||||
|
&& subconverter_runtime_layout_ready "$(subconverter_home)"; } \
|
||||||
|
&& ! find_bundled_asset "subconverter" "$version" "$file" >/dev/null 2>&1; then
|
||||||
|
missing+=("$(offline_asset_missing_text "subconverter" "$version" "$file" "subconverter")")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if geo_predownload_enabled; then
|
||||||
|
for item in "${GEO_ASSET_DOWNLOADS[@]}"; do
|
||||||
|
path="${item%% *}"
|
||||||
|
file="$(basename "$path")"
|
||||||
|
if [ ! -s "$RUNTIME_DIR/$file" ] \
|
||||||
|
&& ! find_bundled_asset "geo" "latest" "$file" >/dev/null 2>&1; then
|
||||||
|
missing+=("$(offline_asset_missing_text "geo" "latest" "$file" "$file")")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${#missing[@]}" -gt 0 ]; then
|
||||||
|
error "离线安装缺少以下资源:"
|
||||||
|
printf ' - %s\n' "${missing[@]}" >&2
|
||||||
|
die_state "离线资源预检失败,共缺少 ${#missing[@]} 项" \
|
||||||
|
"在联网设备执行 scripts/prefetch-assets.sh --arch $arch,再把资源包复制到目标主机并解压到项目根目录"
|
||||||
|
fi
|
||||||
|
|
||||||
|
success "离线资源预检通过"
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_geo_assets() {
|
||||||
|
geo_predownload_enabled || return 0
|
||||||
|
|
||||||
[ -n "${PROJECT_DIR:-}" ] || return 0
|
[ -n "${PROJECT_DIR:-}" ] || return 0
|
||||||
|
|
||||||
@@ -44,12 +290,7 @@ resolve_yq() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
case "$arch" in
|
file="$(yq_asset_file "$arch")"
|
||||||
amd64) file="yq_linux_amd64.tar.gz" ;;
|
|
||||||
arm64) file="yq_linux_arm64.tar.gz" ;;
|
|
||||||
armv7) file="yq_linux_arm.tar.gz" ;;
|
|
||||||
*) die "暂不支持的 yq 架构:$arch" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
url="https://github.com/mikefarah/yq/releases/download/${version}/${file}"
|
url="https://github.com/mikefarah/yq/releases/download/${version}/${file}"
|
||||||
tmp_dir="$(mktemp -d)"
|
tmp_dir="$(mktemp -d)"
|
||||||
@@ -86,12 +327,7 @@ resolve_mihomo() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
case "$arch" in
|
file="$(mihomo_asset_file "$arch" "$version")"
|
||||||
amd64) file="mihomo-linux-amd64-compatible-${version}.gz" ;;
|
|
||||||
arm64) file="mihomo-linux-arm64-${version}.gz" ;;
|
|
||||||
armv7) file="mihomo-linux-armv7-${version}.gz" ;;
|
|
||||||
*) die "暂不支持的 Mihomo 架构:$arch" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ -n "${custom_url:-}" ]; then
|
if [ -n "${custom_url:-}" ]; then
|
||||||
url="$custom_url"
|
url="$custom_url"
|
||||||
@@ -109,12 +345,11 @@ resolve_mihomo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resolve_clash() {
|
resolve_clash() {
|
||||||
local arch version version_no_v url_base tmp_file url downloaded="false"
|
local arch version url_base tmp_file url downloaded="false"
|
||||||
local candidates file
|
local candidates file
|
||||||
|
|
||||||
arch="$(get_arch)"
|
arch="$(get_arch)"
|
||||||
version="${CLASH_VERSION:-$DEFAULT_CLASH_VERSION}"
|
version="${CLASH_VERSION:-$DEFAULT_CLASH_VERSION}"
|
||||||
version_no_v="${version#v}"
|
|
||||||
url_base="${CLASH_DOWNLOAD_BASE:-https://github.com/WindSpiritSR/clash/releases/download}"
|
url_base="${CLASH_DOWNLOAD_BASE:-https://github.com/WindSpiritSR/clash/releases/download}"
|
||||||
|
|
||||||
if [ -x "$(clash_bin)" ] \
|
if [ -x "$(clash_bin)" ] \
|
||||||
@@ -123,36 +358,20 @@ resolve_clash() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
case "$arch" in
|
candidates="$(clash_asset_candidates "$arch" "$version")"
|
||||||
amd64)
|
|
||||||
candidates="
|
|
||||||
clash-linux-amd64-${version}.gz
|
|
||||||
clash-linux-amd64-v${version_no_v}.gz
|
|
||||||
"
|
|
||||||
;;
|
|
||||||
arm64)
|
|
||||||
candidates="
|
|
||||||
clash-linux-arm64-${version}.gz
|
|
||||||
clash-linux-arm64-v${version_no_v}.gz
|
|
||||||
clash-linux-armv8-${version}.gz
|
|
||||||
clash-linux-armv8-v${version_no_v}.gz
|
|
||||||
"
|
|
||||||
;;
|
|
||||||
armv7)
|
|
||||||
candidates="
|
|
||||||
clash-linux-armv7-${version}.gz
|
|
||||||
clash-linux-armv7-v${version_no_v}.gz
|
|
||||||
"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
die "暂不支持的 Clash 架构:$arch"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
tmp_file="$(mktemp)"
|
tmp_file="$(mktemp)"
|
||||||
rm -f "$tmp_file"
|
rm -f "$tmp_file"
|
||||||
|
|
||||||
for file in $candidates; do
|
for file in $candidates; do
|
||||||
|
if copy_bundled_asset "clash" "$version" "$file" "$tmp_file" "clash"; then
|
||||||
|
downloaded="true"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
for file in $candidates; do
|
||||||
|
[ "$downloaded" = "false" ] || break
|
||||||
url="${url_base}/${version}/${file}"
|
url="${url_base}/${version}/${file}"
|
||||||
|
|
||||||
if download_file "$url" "$tmp_file" "clash"; then
|
if download_file "$url" "$tmp_file" "clash"; then
|
||||||
@@ -273,12 +492,7 @@ resolve_subconverter() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
case "$arch" in
|
file="$(subconverter_asset_file "$arch")"
|
||||||
amd64) file="subconverter_linux64.tar.gz" ;;
|
|
||||||
arm64) file="subconverter_aarch64.tar.gz" ;;
|
|
||||||
armv7) file="subconverter_armv7.tar.gz" ;;
|
|
||||||
*) die "暂不支持的 subconverter 架构:$arch" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
url="https://github.com/asdlokj1qpi233/subconverter/releases/download/${version}/${file}"
|
url="https://github.com/asdlokj1qpi233/subconverter/releases/download/${version}/${file}"
|
||||||
tmp_dir="$(mktemp -d)"
|
tmp_dir="$(mktemp -d)"
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
|
||||||
|
# shellcheck source=scripts/core/runtime.sh
|
||||||
|
source "$PROJECT_DIR/scripts/core/runtime.sh"
|
||||||
|
|
||||||
|
tmp_dir="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$tmp_dir"' EXIT
|
||||||
|
|
||||||
|
fixture_project="$tmp_dir/project"
|
||||||
|
RUNTIME_DIR="$fixture_project/runtime"
|
||||||
|
RESOURCE_DIR="$fixture_project/resources"
|
||||||
|
BIN_DIR="$RUNTIME_DIR/bin"
|
||||||
|
LOG_DIR="$RUNTIME_DIR/logs"
|
||||||
|
|
||||||
|
mkdir -p \
|
||||||
|
"$RESOURCE_DIR/bin/mihomo" \
|
||||||
|
"$RESOURCE_DIR/bin/yq" \
|
||||||
|
"$RESOURCE_DIR/bin/subconverter" \
|
||||||
|
"$RESOURCE_DIR/geo" \
|
||||||
|
"$BIN_DIR" \
|
||||||
|
"$LOG_DIR"
|
||||||
|
|
||||||
|
export CLASH_TEST_UNAME_M="amd64"
|
||||||
|
export KERNEL_TYPE="mihomo"
|
||||||
|
export CLASH_OFFLINE="true"
|
||||||
|
export CLASH_PREDOWNLOAD_GEO="true"
|
||||||
|
unset CLASH_BUNDLED_ASSET_DIR
|
||||||
|
unset CLASH_BUNDLED_ASSET_ENABLED
|
||||||
|
|
||||||
|
mihomo_version="${MIHOMO_VERSION:-$DEFAULT_MIHOMO_VERSION}"
|
||||||
|
yq_version="${YQ_VERSION:-$DEFAULT_YQ_VERSION}"
|
||||||
|
subconverter_version="${SUBCONVERTER_VERSION:-$DEFAULT_SUBCONVERTER_VERSION}"
|
||||||
|
|
||||||
|
mihomo_file="$(mihomo_asset_file amd64 "$mihomo_version")"
|
||||||
|
yq_file="$(yq_asset_file amd64)"
|
||||||
|
subconverter_file="$(subconverter_asset_file amd64)"
|
||||||
|
|
||||||
|
printf 'mock\n' > "$RESOURCE_DIR/bin/mihomo/$mihomo_file"
|
||||||
|
printf 'mock\n' > "$RESOURCE_DIR/bin/yq/$yq_file"
|
||||||
|
printf 'mock\n' > "$RESOURCE_DIR/bin/subconverter/$subconverter_file"
|
||||||
|
|
||||||
|
for item in "${GEO_ASSET_DOWNLOADS[@]}"; do
|
||||||
|
geo_path="${item%% *}"
|
||||||
|
printf 'mock\n' > "$RESOURCE_DIR/geo/$(basename "$geo_path")"
|
||||||
|
done
|
||||||
|
|
||||||
|
preflight_offline_assets >/dev/null
|
||||||
|
echo "ok - complete offline asset set passes preflight"
|
||||||
|
|
||||||
|
rm -f "$RESOURCE_DIR/bin/mihomo/$mihomo_file"
|
||||||
|
missing_output="$tmp_dir/missing-output"
|
||||||
|
if (preflight_offline_assets) >"$missing_output" 2>&1; then
|
||||||
|
echo "not ok - missing Mihomo asset unexpectedly passed preflight" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! grep -Fq "Mihomo" "$missing_output"; then
|
||||||
|
echo "not ok - missing asset output did not identify Mihomo" >&2
|
||||||
|
cat "$missing_output" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "ok - missing offline asset fails with an actionable name"
|
||||||
|
|
||||||
|
blocked_output="$tmp_dir/blocked-output"
|
||||||
|
if (download_file "https://example.com/file" "$tmp_dir/downloaded" "fixture") >"$blocked_output" 2>&1; then
|
||||||
|
echo "not ok - offline download unexpectedly succeeded" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -e "$tmp_dir/downloaded" ]; then
|
||||||
|
echo "not ok - offline download created an output file" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! grep -Fq "离线模式禁止下载" "$blocked_output"; then
|
||||||
|
echo "not ok - offline download failure was not explicit" >&2
|
||||||
|
cat "$blocked_output" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "ok - offline mode blocks remote fallback before curl"
|
||||||
|
|
||||||
|
parse_install_options user --offline
|
||||||
|
if [ "$INSTALL_REQUESTED_SCOPE" != "user" ] \
|
||||||
|
|| [ "$INSTALL_OFFLINE_REQUESTED" != "true" ]; then
|
||||||
|
echo "not ok - install option parser lost scope or offline flag" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "ok - install options accept scope and --offline together"
|
||||||
|
|
||||||
|
dry_run_output="$tmp_dir/dry-run-output"
|
||||||
|
bash "$PROJECT_DIR/scripts/prefetch-assets.sh" \
|
||||||
|
--arch arm64 \
|
||||||
|
--kernel mihomo \
|
||||||
|
--dry-run >"$dry_run_output"
|
||||||
|
|
||||||
|
if ! grep -Fq "mihomo-linux-arm64-" "$dry_run_output" \
|
||||||
|
|| ! grep -Fq "subconverter_aarch64.tar.gz" "$dry_run_output" \
|
||||||
|
|| ! grep -Fq "未生成资源包" "$dry_run_output"; then
|
||||||
|
echo "not ok - prefetch dry run did not describe the arm64 bundle" >&2
|
||||||
|
cat "$dry_run_output" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "ok - prefetch dry run resolves target architecture without downloading"
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
|
||||||
|
# shellcheck source=scripts/core/runtime.sh
|
||||||
|
source "$PROJECT_DIR/scripts/core/runtime.sh"
|
||||||
|
|
||||||
|
prefetch_usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
用法:
|
||||||
|
bash scripts/prefetch-assets.sh [选项]
|
||||||
|
|
||||||
|
选项:
|
||||||
|
--arch amd64|arm64|armv7 目标主机架构;默认使用当前主机架构
|
||||||
|
--kernel mihomo|clash 要打包的代理内核;默认读取 .env 或使用 mihomo
|
||||||
|
--output FILE 输出文件;默认 clash-assets-<kernel>-<arch>.tar.gz
|
||||||
|
--no-geo 不包含安装期 GEO 资源
|
||||||
|
--dry-run 只显示下载计划,不下载或生成压缩包
|
||||||
|
-h, --help 显示帮助
|
||||||
|
|
||||||
|
资源包复制到目标主机后,在项目根目录执行:
|
||||||
|
tar -xzf clash-assets-<kernel>-<arch>.tar.gz
|
||||||
|
bash install.sh --offline
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
normalize_prefetch_arch() {
|
||||||
|
case "$1" in
|
||||||
|
x86_64|amd64) echo "amd64" ;;
|
||||||
|
aarch64|arm64) echo "arm64" ;;
|
||||||
|
armv7l|armv7) echo "armv7" ;;
|
||||||
|
*) die_usage "不支持的目标架构:$1" "可选值:amd64、arm64、armv7" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
prefetch_sha256_line() {
|
||||||
|
local file="$1"
|
||||||
|
|
||||||
|
if command -v sha256sum >/dev/null 2>&1; then
|
||||||
|
sha256sum "$file"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v shasum >/dev/null 2>&1; then
|
||||||
|
shasum -a 256 "$file"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
die "生成资源包需要 sha256sum 或 shasum"
|
||||||
|
}
|
||||||
|
|
||||||
|
prefetch_download() {
|
||||||
|
local url="$1"
|
||||||
|
local out="$2"
|
||||||
|
local label="$3"
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$out")"
|
||||||
|
download_file "$url" "$out" "$label"
|
||||||
|
}
|
||||||
|
|
||||||
|
target_arch=""
|
||||||
|
target_kernel=""
|
||||||
|
output_file=""
|
||||||
|
include_geo="true"
|
||||||
|
dry_run="false"
|
||||||
|
|
||||||
|
while [ "$#" -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--arch)
|
||||||
|
[ "$#" -ge 2 ] || die_usage "--arch 缺少参数" "可选值:amd64、arm64、armv7"
|
||||||
|
target_arch="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--kernel)
|
||||||
|
[ "$#" -ge 2 ] || die_usage "--kernel 缺少参数" "可选值:mihomo、clash"
|
||||||
|
target_kernel="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--output)
|
||||||
|
[ "$#" -ge 2 ] || die_usage "--output 缺少文件路径" "例如:--output /tmp/clash-assets-amd64.tar.gz"
|
||||||
|
output_file="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--no-geo)
|
||||||
|
include_geo="false"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--dry-run)
|
||||||
|
dry_run="true"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
prefetch_usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die_usage "未知参数:$1" "执行 bash scripts/prefetch-assets.sh --help 查看用法"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
init_project_context "$PROJECT_DIR"
|
||||||
|
load_env_if_exists
|
||||||
|
|
||||||
|
target_arch="$(normalize_prefetch_arch "${target_arch:-$(get_arch)}")"
|
||||||
|
target_kernel="$(normalize_kernel_type "${target_kernel:-${KERNEL_TYPE:-mihomo}}")"
|
||||||
|
|
||||||
|
case "$target_kernel" in
|
||||||
|
mihomo|clash) ;;
|
||||||
|
*) die_usage "不支持的目标内核:$target_kernel" "可选值:mihomo、clash" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
export CLASH_TEST_UNAME_M="$target_arch"
|
||||||
|
export CLASH_OFFLINE="false"
|
||||||
|
|
||||||
|
mihomo_version="${MIHOMO_VERSION:-$DEFAULT_MIHOMO_VERSION}"
|
||||||
|
clash_version="${CLASH_VERSION:-$DEFAULT_CLASH_VERSION}"
|
||||||
|
yq_version="${YQ_VERSION:-$DEFAULT_YQ_VERSION}"
|
||||||
|
subconverter_version="${SUBCONVERTER_VERSION:-$DEFAULT_SUBCONVERTER_VERSION}"
|
||||||
|
|
||||||
|
yq_file="$(yq_asset_file "$target_arch")"
|
||||||
|
subconverter_file="$(subconverter_asset_file "$target_arch")"
|
||||||
|
yq_url="https://github.com/mikefarah/yq/releases/download/${yq_version}/${yq_file}"
|
||||||
|
subconverter_url="https://github.com/asdlokj1qpi233/subconverter/releases/download/${subconverter_version}/${subconverter_file}"
|
||||||
|
|
||||||
|
case "$target_kernel" in
|
||||||
|
mihomo)
|
||||||
|
kernel_file="$(mihomo_asset_file "$target_arch" "$mihomo_version")"
|
||||||
|
if [ -n "${MIHOMO_DOWNLOAD_URL:-}" ]; then
|
||||||
|
kernel_url="$MIHOMO_DOWNLOAD_URL"
|
||||||
|
else
|
||||||
|
kernel_url="${MIHOMO_DOWNLOAD_BASE:-https://github.com/MetaCubeX/mihomo/releases/download}"
|
||||||
|
kernel_url="${kernel_url%/}/${mihomo_version}/${kernel_file}"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
clash)
|
||||||
|
kernel_file="$(clash_asset_candidates "$target_arch" "$clash_version" | head -n 1)"
|
||||||
|
kernel_url="${CLASH_DOWNLOAD_BASE:-https://github.com/WindSpiritSR/clash/releases/download}"
|
||||||
|
kernel_url="${kernel_url%/}/${clash_version}/${kernel_file}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
ui_section "资源包计划"
|
||||||
|
ui_kv "🧩" "目标架构" "$target_arch"
|
||||||
|
ui_kv "🚀" "目标内核" "$target_kernel"
|
||||||
|
printf ' - %s\n' "$kernel_url"
|
||||||
|
printf ' - %s\n' "$yq_url"
|
||||||
|
printf ' - %s\n' "$subconverter_url"
|
||||||
|
|
||||||
|
if [ "$include_geo" = "true" ]; then
|
||||||
|
for item in "${GEO_ASSET_DOWNLOADS[@]}"; do
|
||||||
|
printf ' - %s\n' "${item#* }"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$dry_run" = "true" ]; then
|
||||||
|
success "仅显示下载计划,未生成资源包"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
command -v curl >/dev/null 2>&1 || die "当前系统缺少 curl"
|
||||||
|
command -v tar >/dev/null 2>&1 || die "当前系统缺少 tar"
|
||||||
|
|
||||||
|
stage_dir="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$stage_dir"' EXIT
|
||||||
|
|
||||||
|
RESOURCE_DIR="$stage_dir/resources"
|
||||||
|
RUNTIME_DIR="$stage_dir/runtime"
|
||||||
|
BIN_DIR="$RUNTIME_DIR/bin"
|
||||||
|
LOG_DIR="$RUNTIME_DIR/logs"
|
||||||
|
mkdir -p "$RESOURCE_DIR/bin" "$RESOURCE_DIR/geo" "$RUNTIME_DIR"
|
||||||
|
|
||||||
|
prefetch_download "$kernel_url" "$RESOURCE_DIR/bin/$target_kernel/$kernel_file" "$target_kernel"
|
||||||
|
prefetch_download "$yq_url" "$RESOURCE_DIR/bin/yq/$yq_file" "yq"
|
||||||
|
prefetch_download \
|
||||||
|
"$subconverter_url" \
|
||||||
|
"$RESOURCE_DIR/bin/subconverter/$subconverter_file" \
|
||||||
|
"subconverter"
|
||||||
|
|
||||||
|
if [ "$include_geo" = "true" ]; then
|
||||||
|
for item in "${GEO_ASSET_DOWNLOADS[@]}"; do
|
||||||
|
geo_path="${item%% *}"
|
||||||
|
geo_url="${item#* }"
|
||||||
|
geo_file="$(basename "$geo_path")"
|
||||||
|
prefetch_download "$geo_url" "$RESOURCE_DIR/geo/$geo_file" "$geo_file"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
source_commit="$(git -C "$PROJECT_DIR" rev-parse --verify HEAD 2>/dev/null || echo unknown)"
|
||||||
|
cat > "$RESOURCE_DIR/asset-manifest.env" <<EOF
|
||||||
|
BUNDLE_FORMAT="1"
|
||||||
|
BUNDLE_ARCH="$target_arch"
|
||||||
|
KERNEL_TYPE="$target_kernel"
|
||||||
|
MIHOMO_VERSION="$mihomo_version"
|
||||||
|
CLASH_VERSION="$clash_version"
|
||||||
|
YQ_VERSION="$yq_version"
|
||||||
|
SUBCONVERTER_VERSION="$subconverter_version"
|
||||||
|
GEO_INCLUDED="$include_geo"
|
||||||
|
SOURCE_COMMIT="$source_commit"
|
||||||
|
GENERATED_AT="$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$RESOURCE_DIR"
|
||||||
|
while IFS= read -r asset_file; do
|
||||||
|
prefetch_sha256_line "$asset_file"
|
||||||
|
done < <(
|
||||||
|
find bin geo -type f -print
|
||||||
|
printf '%s\n' "asset-manifest.env"
|
||||||
|
) | LC_ALL=C sort -k2
|
||||||
|
) > "$RESOURCE_DIR/SHA256SUMS"
|
||||||
|
|
||||||
|
if [ -z "${output_file:-}" ]; then
|
||||||
|
output_file="$PWD/clash-assets-${target_kernel}-${target_arch}.tar.gz"
|
||||||
|
else
|
||||||
|
output_dir="$(dirname "$output_file")"
|
||||||
|
output_name="$(basename "$output_file")"
|
||||||
|
mkdir -p "$output_dir"
|
||||||
|
output_file="$(cd "$output_dir" && pwd)/$output_name"
|
||||||
|
fi
|
||||||
|
|
||||||
|
tar -czf "$output_file" -C "$stage_dir" resources
|
||||||
|
|
||||||
|
success "资源包已生成:$output_file"
|
||||||
|
ui_next "复制到目标主机的项目目录,解压后执行:bash install.sh --offline"
|
||||||
Reference in New Issue
Block a user