初始化:私有维护版 Gemini-in-Chrome 安装脚本

从 appsail/Gemini-in-Chrome (MIT) fork 并重写 install.sh / install.ps1:
- 原脚本用正则/sed 做“仅替换已存在字段”,字段本不存在时静默不生效
- 改为 JSON 感知读写(jq 优先,探测/自动装失败时降级为 sed+awk 零依赖兜底)
- 修复过程中实测发现并修掉的坑:BSD sed 不支持 GNU 的 0,/re/ 插入语法、
  正则不容忍冒号后空格导致插入重复 key、macOS 系统自带 bash 3.2 在脚本文件模式下
  中文紧跟未加花括号的 $VAR 会解析错位丢字节
- 新增 test-fixtures/ 下完全人工构造(非真实数据脱敏)的多 profile 测试样本
- README 改为中文,下载链接指向本仓库,补充 MIT LICENSE 及上游致谢
This commit is contained in:
SkyJourney
2026-08-16 13:30:42 +08:00
commit d90d2ecd03
7 changed files with 662 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
# macOS
.DS_Store
# Backup files
*.bak
# Editor
.vscode/
.idea/
*.swp
*.swo
+28
View File
@@ -0,0 +1,28 @@
MIT License
Copyright (c) 2026 Shunzhi Jiang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
This project is a fork/rewrite of appsail/Gemini-in-Chrome
(https://github.com/appsail/Gemini-in-Chrome), which is stated by its README
to be MIT licensed (no LICENSE file was present in the upstream source at the
time of forking).
+92
View File
@@ -0,0 +1,92 @@
# Gemini in Chrome
> Fork 自:[appsail/Gemini-in-Chrome](https://github.com/appsail/Gemini-in-Chrome)MIT License
一键脚本,用于强制启用 Chrome 内置的 Gemini AI 功能(绕过非美区的资格限制)。
基于上述原项目重写,修复了原脚本"字段不存在时静默不生效"的问题,改为 JSON 感知读写
`jq` 优先,装不上时自动降级为 `sed`+`awk` 零依赖兜底方案),并支持同一台机器上的
多个 Chrome 账号(profile)。
## 快速开始
### macOS / Linux
```bash
curl -fsSL https://gitea.rk-health.com/jiangshunzhi/Gemini-in-Chrome/raw/branch/main/install.sh | bash
```
### Windows
打开 PowerShell 执行:
```powershell
irm https://gitea.rk-health.com/jiangshunzhi/Gemini-in-Chrome/raw/branch/main/install.ps1 | iex
```
## 脚本做了什么
1. ✅ 检查 Chrome 是否在运行(在运行的话会提示你先完全退出——Local State 会在 Chrome 退出时被覆写,不退出改了也白改)
2. 💾 备份原配置文件(`Local State.bak`
3. 🔧 修补以下字段(存在则替换、不存在则插入,而不是原脚本那种"只替换已存在字段"的逻辑):
- 顶层 `variations_country``"us"`
- 顶层 `variations_permanent_consistency_country``[当前 Chrome 版本号, "us"]`
- `profile.info_cache` 下**每一个** profile 的 `is_glic_eligible``true`(不只是已存在该字段的那个)
4. ✓ 写回前后都做合法性校验,`sed`+`awk` 兜底路径下用括号配平检查兜底;校验不过就自动放弃改动,不会覆盖原文件
5. ✓ 汇报本次实际用的是 `jq` 路径还是 `sed`+`awk` 兜底路径,以及各字段是否生效
## 还原原始配置
**macOS**
```bash
mv ~/Library/Application\ Support/Google/Chrome/Local\ State.bak \
~/Library/Application\ Support/Google/Chrome/Local\ State
```
**Linux**
```bash
mv ~/.config/google-chrome/Local\ State.bak \
~/.config/google-chrome/Local\ State
```
**Windows PowerShell**
```powershell
Move-Item -Path "$env:LOCALAPPDATA\Google\Chrome\User Data\Local State.bak" `
-Destination "$env:LOCALAPPDATA\Google\Chrome\User Data\Local State" -Force
```
## 安全性
- 改动前一定会先备份原配置
- 只改本地 Chrome 配置,不上传任何数据、不访问网络(除了下载脚本本身,以及 `jq` 缺失时按需自动安装)
- 改动前后都做合法性校验,写坏了会自动回滚、不覆盖原文件
- 随时可还原
- 非官方脚本,仅供个人研究/自用,风险自负
## jq 依赖说明
macOS 上如果检测到 `jq` 就直接用;没有的话,检测到 Homebrew 会尝试 `brew install jq`
没有 Homebrew 就直接走零依赖兜底,不会替你装 Homebrew。
Linux 上会按顺序探测 `apt-get`/`dnf`/`yum`/`pacman`/`apk` 并尝试自动安装(可能会弹 `sudo`
密码提示);装不上(比如设备网络受限)同样自动降级为 `sed`+`awk` 方案。
`install.ps1`Windows)用 PowerShell 原生的 `ConvertFrom-Json`/`ConvertTo-Json`,本身就不
依赖 `jq`
## 配置文件路径
| 系统 | 路径 |
|----|------|
| macOS | `~/Library/Application Support/Google/Chrome/Local State` |
| Linux | `~/.config/google-chrome/Local State` |
| Windows | `%LOCALAPPDATA%\Google\Chrome\User Data\Local State` |
## 测试
`test-fixtures/` 下有一份完全人工构造(非真实账号脱敏)的多 profile 测试样本,
覆盖了"字段缺失 / 已是 false / 已是 true"三种场景,用法见该目录下的 README。
## License
MIT,见 [LICENSE](./LICENSE)。基于 [appsail/Gemini-in-Chrome](https://github.com/appsail/Gemini-in-Chrome) 重写。
+137
View File
@@ -0,0 +1,137 @@
# 强制启用 Chrome 中的 GeminiProject Glic(PowerShell 版本)
# 通过修补 Chrome 的 Local State 配置文件,绕过地区资格限制
# 支持系统:Windows
#
# PowerShell 原生自带 ConvertFrom-Json / ConvertTo-Json,天然是 JSON 感知的,
# 不需要像 macOS/Linux 那边为了避免依赖 jq 而手写括号深度扫描兜底方案。
Write-Host ""
Write-Host "🚀 Gemini in Chrome Enabler" -ForegroundColor Cyan
Write-Host ""
# Chrome 配置文件路径
$chromeStatePath = "$env:LOCALAPPDATA\Google\Chrome\User Data\Local State"
# 检查 Chrome 是否在运行;Local State 会在 Chrome 退出时被覆写,
# 所以修改前必须确保 Chrome 完全退出
$chromeProcesses = Get-Process -Name "chrome" -ErrorAction SilentlyContinue
if ($chromeProcesses) {
Write-Host "⚠️ Chrome 正在运行,请先完全退出再继续。" -ForegroundColor Yellow
Read-Host "关闭 Chrome 后按回车继续"
$chromeProcesses = Get-Process -Name "chrome" -ErrorAction SilentlyContinue
if ($chromeProcesses) {
Write-Host "❌ Chrome 仍在运行,请退出后重试。" -ForegroundColor Red
exit 1
}
}
# 检查配置文件是否存在
if (-not (Test-Path $chromeStatePath)) {
Write-Host "❌ 未找到 Chrome 配置文件:$chromeStatePath" -ForegroundColor Red
exit 1
}
# 备份原文件
$backupPath = "$chromeStatePath.bak"
Copy-Item -Path $chromeStatePath -Destination $backupPath -Force
Write-Host "✓ 已备份:Local State.bak" -ForegroundColor Green
# 探测本机安装的 Chrome 版本号(写入 variations_permanent_consistency_country 用)
function Get-ChromeVersion {
$candidates = @(
"$env:ProgramFiles\Google\Chrome\Application\chrome.exe",
"${env:ProgramFiles(x86)}\Google\Chrome\Application\chrome.exe",
"$env:LOCALAPPDATA\Google\Chrome\Application\chrome.exe"
)
foreach ($path in $candidates) {
if (Test-Path $path) {
return (Get-Item $path).VersionInfo.ProductVersion
}
}
return ""
}
$chromeVersion = Get-ChromeVersion
if (-not $chromeVersion) {
Write-Host "⚠️ 未能自动探测 Chrome 版本号,variations_permanent_consistency_country 的版本位会留空" -ForegroundColor Yellow
}
# 读取并解析 JSON(-Depth 需要给够,避免嵌套结构在读取阶段被截断)
$json = Get-Content -Path $chromeStatePath -Raw -Encoding UTF8 | ConvertFrom-Json -Depth 100
# 对可能不存在的属性做"存在则改、不存在则插入"的统一封装,
# 因为 PSCustomObject 对不存在的属性不能直接用 `=` 赋值
function Set-JsonProperty {
param(
[Parameter(Mandatory)] $Object,
[Parameter(Mandatory)] [string] $Name,
[Parameter(Mandatory)] $Value
)
if ($Object.PSObject.Properties.Name -contains $Name) {
$Object.$Name = $Value
} else {
$Object | Add-Member -NotePropertyName $Name -NotePropertyValue $Value -Force
}
}
Set-JsonProperty -Object $json -Name "variations_country" -Value "us"
Set-JsonProperty -Object $json -Name "variations_permanent_consistency_country" -Value @($chromeVersion, "us")
# profile.info_cache 下每个 profile 都补 is_glic_eligible,而不仅仅是已存在该字段的 profile
if (-not ($json.PSObject.Properties.Name -contains "profile")) {
$json | Add-Member -NotePropertyName "profile" -NotePropertyValue ([PSCustomObject]@{}) -Force
}
if (-not ($json.profile.PSObject.Properties.Name -contains "info_cache")) {
$json.profile | Add-Member -NotePropertyName "info_cache" -NotePropertyValue ([PSCustomObject]@{}) -Force
}
foreach ($entry in $json.profile.info_cache.PSObject.Properties) {
Set-JsonProperty -Object $entry.Value -Name "is_glic_eligible" -Value $true
}
# 写回:UTF-8 无 BOMChrome 要求),-Depth 同样要给够——
# 默认的 -Depth 2 会把深层嵌套对象压成 "System.Object[]" 之类的字符串,直接写坏配置
$newContent = $json | ConvertTo-Json -Depth 100 -Compress
$utf8NoBom = New-Object System.Text.UTF8Encoding $false
$tmpPath = "$chromeStatePath.tmp"
[System.IO.File]::WriteAllText($tmpPath, $newContent, $utf8NoBom)
# 校验写回的内容仍是合法 JSON,且目标字段确实生效;不合法就回滚、不覆盖原文件
try {
$verify = Get-Content -Path $tmpPath -Raw -Encoding UTF8 | ConvertFrom-Json -Depth 100
if ($verify.variations_country -ne "us") {
throw "variations_country 未生效"
}
Move-Item -Path $tmpPath -Destination $chromeStatePath -Force
} catch {
Write-Host "❌ 校验失败,已放弃本次修改(原文件未被触碰):$_" -ForegroundColor Red
Remove-Item -Path $tmpPath -ErrorAction SilentlyContinue
exit 1
}
# 汇报结果
Write-Host ""
$errors = 0
$final = Get-Content -Path $chromeStatePath -Raw -Encoding UTF8 | ConvertFrom-Json -Depth 100
if ($final.variations_country -eq "us") {
Write-Host "✓ variations_country 已设为 us" -ForegroundColor Green
} else {
Write-Host "⚠️ variations_country 未生效" -ForegroundColor Yellow
$errors++
}
$allEligible = $true
foreach ($entry in $final.profile.info_cache.PSObject.Properties) {
if ($entry.Value.is_glic_eligible -ne $true) { $allEligible = $false }
}
if ($allEligible) {
Write-Host "✓ is_glic_eligible 已对所有 profile 启用" -ForegroundColor Green
} else {
Write-Host "⚠️ 部分 profile 的 is_glic_eligible 未生效" -ForegroundColor Yellow
$errors++
}
Write-Host ""
if ($errors -eq 0) {
Write-Host "✅ 完成!请完全重启 Chrome 使改动生效。" -ForegroundColor Green
} else {
Write-Host "⚠️ 部分改动可能未生效,请检查 Chrome 版本。" -ForegroundColor Yellow
}
Executable
+358
View File
@@ -0,0 +1,358 @@
#!/bin/bash
# 强制启用 Chrome 中的 GeminiProject Glic
# 通过修补 Chrome 的 Local State 配置文件,绕过地区资格限制
# 支持系统:macOS、Linux
#
# 设计说明:
# - 优先用 jq 做 JSON 级别的读写(准确、可读性好);
# - 没有 jq 时按系统类型自动尝试安装(macOS 用 brew,Linux 探测常见包管理器);
# - 自动安装失败(例如设备网络受限)时,降级为 sed(处理两个顶层标量/数组字段)
# + awk(对 profile.info_cache 做带引号转义感知的括号深度扫描,逐个 profile 插入字段)
# 的零依赖兜底方案,只重写 info_cache 这一小段字节范围,其余内容原样透传。
#
# 编码注意事项(实测踩过的坑):
# macOS 自带 /bin/bash 是 3.2.57(苹果因 GPLv3 许可问题多年未升级)。以脚本文件方式
# 运行时,一旦某个多字节 UTF-8 字符(中文、全角标点等)紧跟一个**未加花括号**的
# `$VAR`bash 3.2 的分词会解析错位、丢字节,输出会出现乱码或字段被吞掉——交互式敲同样
# 的命令反而不会复现。本文件里所有紧跟中文/全角字符之后的变量引用一律写成 `${VAR}`。
set -e
echo ""
echo "🚀 Gemini in Chrome Enabler"
echo ""
# 探测系统类型,确定配置文件路径
OS_TYPE=$(uname -s)
case "$OS_TYPE" in
Darwin)
CHROME_STATE=~/Library/Application\ Support/Google/Chrome/Local\ State
CHROME_PROCESS="Google Chrome"
;;
Linux)
CHROME_STATE=~/.config/google-chrome/Local\ State
CHROME_PROCESS="chrome"
;;
*)
echo "💡 Windows 用户请直接运行同目录下的 install.ps1"
exit 1
;;
esac
# 跨 BSD sedmacOS/ GNU sedLinux)的原地编辑封装
sed_inplace() {
if [[ "$OS_TYPE" == "Darwin" ]]; then
sed -i '' "$@"
else
sed -i "$@"
fi
}
# 检查 Chrome 是否在运行;Local State 会在 Chrome 退出时被覆写,
# 所以修改前必须确保 Chrome 完全退出
check_chrome_running() {
pgrep -x "$CHROME_PROCESS" > /dev/null 2>&1
}
if check_chrome_running; then
echo "⚠️ Chrome 正在运行!"
if [[ "$OS_TYPE" == "Darwin" ]]; then
echo "📌 请先完全退出 ChromeCmd+Q)再继续"
else
echo "📌 请先完全退出 Chrome 再继续"
fi
echo ""
read -p "关闭 Chrome 后按回车继续... " -r
echo ""
if check_chrome_running; then
echo "❌ Chrome 仍在运行,请退出后重试。"
exit 1
fi
fi
# 检查配置文件是否存在
if [ ! -f "$CHROME_STATE" ]; then
echo "❌ 未找到 Chrome 配置文件:${CHROME_STATE}"
exit 1
fi
# 备份原文件
cp "$CHROME_STATE" "$CHROME_STATE.bak"
echo "✓ 已备份:Local State.bak"
# 探测本机安装的 Chrome 版本号(写入 variations_permanent_consistency_country 用)
detect_chrome_version() {
local version=""
if [[ "$OS_TYPE" == "Darwin" ]]; then
version=$(mdls -name kMDItemVersion -raw "/Applications/Google Chrome.app" 2>/dev/null || true)
if [[ -z "$version" || "$version" == "(null)" ]]; then
version=$(defaults read "/Applications/Google Chrome.app/Contents/Info" CFBundleShortVersionString 2>/dev/null || true)
fi
else
if command -v google-chrome >/dev/null 2>&1; then
version=$(google-chrome --version 2>/dev/null | grep -oE '[0-9]+(\.[0-9]+){2,3}' | head -1 || true)
elif command -v google-chrome-stable >/dev/null 2>&1; then
version=$(google-chrome-stable --version 2>/dev/null | grep -oE '[0-9]+(\.[0-9]+){2,3}' | head -1 || true)
fi
fi
echo "$version"
}
CHROME_VERSION=$(detect_chrome_version)
if [[ -z "$CHROME_VERSION" ]]; then
echo "⚠️ 未能自动探测 Chrome 版本号,variations_permanent_consistency_country 的版本位会留空"
fi
# 探测 jq;没有则按系统类型自动尝试安装(绝不自动安装 Homebrew 本身)
ensure_jq() {
if command -v jq >/dev/null 2>&1; then
return 0
fi
echo "ℹ️ 未检测到 jq,尝试自动安装..."
case "$OS_TYPE" in
Darwin)
if command -v brew >/dev/null 2>&1; then
brew install jq || true
else
echo "⚠️ 未检测到 Homebrew,跳过自动安装(不会自动装 Homebrew"
fi
;;
Linux)
if command -v apt-get >/dev/null 2>&1; then
sudo apt-get update -y && sudo apt-get install -y jq || true
elif command -v dnf >/dev/null 2>&1; then
sudo dnf install -y jq || true
elif command -v yum >/dev/null 2>&1; then
sudo yum install -y jq || true
elif command -v pacman >/dev/null 2>&1; then
sudo pacman -Sy --noconfirm jq || true
elif command -v apk >/dev/null 2>&1; then
sudo apk add jq || true
else
echo "⚠️ 未找到已知的包管理器,跳过自动安装"
fi
;;
esac
if command -v jq >/dev/null 2>&1; then
echo "✓ jq 安装成功"
return 0
else
echo "⚠️ jq 安装失败或被跳过,降级为 sed+awk 零依赖方案"
return 1
fi
}
TMP_FILE="${CHROME_STATE}.tmp.$$"
PATCH_METHOD=""
if ensure_jq; then
PATCH_METHOD="jq"
# -c 保持压缩单行输出:一是跟 Chrome 自己写出来的格式一致、改动面最小,
# 二是脚本末尾的校验用 grep 做字面量匹配,没有 -c 的话 jq 会输出带缩进的多行
# JSON,字段其实生效了,但 grep 会因为格式不匹配而误报"未生效"——实测踩过这个坑。
jq -c --arg ver "$CHROME_VERSION" '
.variations_country = "us"
| .variations_permanent_consistency_country = [$ver, "us"]
| .profile.info_cache = ((.profile.info_cache // {}) | with_entries(.value.is_glic_eligible = true))
' "$CHROME_STATE" > "$TMP_FILE"
else
PATCH_METHOD="sed+awk"
cp "$CHROME_STATE" "$TMP_FILE"
# 顶层字段:存在则替换,不存在则插入到根对象的第一个 '{' 之后。
# 注意:这里用 `1s/{/.../ `POSIX 通用写法,Local State 本身就是单行 JSON
# 而不是 `0,/{/s//.../ ` —— 后者是 GNU sed 的扩展语法,在 macOS 自带的 BSD sed
# 上会静默不生效(退出码仍是 0,但完全没有插入),实测踩过这个坑。
# 冒号两侧允许出现空白再匹配——真实 Chrome 写出的 Local State 是压缩无空格的单行
# JSON,理论上用不到这个容错,但如果“判断是否存在”和“实际替换”的正则宽松度不一致,
# 一旦真的遇到带空格的输入,会把“判断为不存在”和“已存在但没匹配上替换”这两种情况
# 搞混,插入一个重复 key(JSON 语法上合法,但解析器通常只认最后一个,等于新值被
# 静默吞掉)。这里两处判断都用同一个宽松正则,避免这种不一致。
if grep -Eq '"variations_country"[[:space:]]*:[[:space:]]*"[^"]*"' "$TMP_FILE"; then
sed_inplace -E 's/"variations_country"[[:space:]]*:[[:space:]]*"[^"]*"/"variations_country":"us"/' "$TMP_FILE"
else
sed_inplace "1s/{/{\"variations_country\":\"us\",/" "$TMP_FILE"
fi
if grep -Eq '"variations_permanent_consistency_country"[[:space:]]*:[[:space:]]*\[' "$TMP_FILE"; then
sed_inplace -E "s/\"variations_permanent_consistency_country\"[[:space:]]*:[[:space:]]*\[[^]]*\]/\"variations_permanent_consistency_country\":[\"${CHROME_VERSION}\",\"us\"]/" "$TMP_FILE"
else
sed_inplace "1s/{/{\"variations_permanent_consistency_country\":[\"${CHROME_VERSION}\",\"us\"],/" "$TMP_FILE"
fi
# 已存在 is_glic_eligible:false 的 profile 直接原地翻转为 true——
# 这个字段是布尔标量,全文只会以 profile 的直接子字段形式出现,
# 用简单正则翻转是安全的,不需要结构感知
sed_inplace -e 's/"is_glic_eligible":[[:space:]]*false/"is_glic_eligible":true/g' "$TMP_FILE"
# profile.info_cache 下完全没有 is_glic_eligible 字段的 profile 补插入:需要结构感知,
# 用 awk 做带引号转义感知的括号深度扫描,只重写 info_cache 这一小段
AWK_SCRIPT="${TMP_FILE}.awk"
cat > "$AWK_SCRIPT" <<'AWK_EOF'
{
if (NR == 1) content = $0
else content = content "\n" $0
}
END {
# 定位 "info_cache" 键之后的 '{',冒号两侧允许空白(跟顶层字段那两处保持同一容错水平,
# 避免"字符串精确匹配"这个原脚本的老问题在这里换了个地方重新出现)
key = "\"info_cache\""
keyPos = index(content, key)
if (keyPos == 0) {
# 没有 info_cache(理论上不该发生,Chrome 至少会有一个 profile),原样输出
printf "%s\n", content
exit
}
p = keyPos + length(key)
n0 = length(content)
while (p <= n0 && substr(content, p, 1) ~ /[ \t\r\n]/) p++
if (substr(content, p, 1) != ":") {
printf "%s\n", content
exit
}
p++
while (p <= n0 && substr(content, p, 1) ~ /[ \t\r\n]/) p++
if (substr(content, p, 1) != "{") {
printf "%s\n", content
exit
}
objStart = p
# 第一遍扫描:定位 info_cache 对象的匹配右括号
depth = 0
inStr = 0
n = length(content)
i = objStart
objEnd = 0
while (i <= n) {
c = substr(content, i, 1)
if (inStr) {
if (c == "\\") { i += 2; continue }
else if (c == "\"") { inStr = 0 }
} else {
if (c == "\"") { inStr = 1 }
else if (c == "{" || c == "[") { depth++ }
else if (c == "}" || c == "]") {
depth--
if (depth == 0) { objEnd = i; break }
}
}
i++
}
if (objEnd == 0) {
# 括号没配平,说明文件本身有问题,不做任何改动,原样输出并报错
printf "%s\n", content > "/dev/stderr"
print "AWK_BRACE_MISMATCH" > "/dev/stderr"
exit 1
}
span = substr(content, objStart, objEnd - objStart + 1)
# 第二遍扫描:只在 info_cache 的直接子对象(每个 profile)里检查/插入 is_glic_eligible
out = ""
depth2 = 0
inStr2 = 0
childStart = 0
m = length(span)
j = 1
while (j <= m) {
c = substr(span, j, 1)
out = out c
if (inStr2) {
if (c == "\\") {
j++
if (j <= m) out = out substr(span, j, 1)
} else if (c == "\"") {
inStr2 = 0
}
} else if (c == "\"") {
inStr2 = 1
} else if (c == "{" || c == "[") {
depth2++
if (depth2 == 2 && c == "{") childStart = length(out)
} else if (c == "}" || c == "]") {
if (depth2 == 2 && c == "}") {
childSlice = substr(out, childStart + 1, length(out) - childStart - 1)
if (index(childSlice, "\"is_glic_eligible\"") == 0) {
out = substr(out, 1, length(out) - 1) ",\"is_glic_eligible\":true}"
}
}
depth2--
}
j++
}
newContent = substr(content, 1, objStart - 1) out substr(content, objEnd + 1)
printf "%s\n", newContent
}
AWK_EOF
awk -f "$AWK_SCRIPT" "$TMP_FILE" > "${TMP_FILE}.out"
mv "${TMP_FILE}.out" "$TMP_FILE"
rm -f "$AWK_SCRIPT"
# 零依赖场景下没有真正的 JSON 解析器可用,做一个括号配平 + 首尾定界符的
# 保守合法性检查兜底(不是完整 JSON 校验,但能拦住most常见的写坏场景)
balance_check() {
awk '
{
if (NR == 1) content = $0
else content = content "\n" $0
}
END {
depth = 0
inStr = 0
n = length(content)
for (i = 1; i <= n; i++) {
c = substr(content, i, 1)
if (inStr) {
if (c == "\\") { i++; continue }
else if (c == "\"") inStr = 0
} else {
if (c == "\"") inStr = 1
else if (c == "{" || c == "[") depth++
else if (c == "}" || c == "]") depth--
}
}
exit (depth == 0) ? 0 : 1
}' "$1"
}
if ! balance_check "$TMP_FILE"; then
echo "❌ 兜底方案生成的内容括号不配平,判定为写坏,已放弃本次修改(原文件未被触碰)"
rm -f "$TMP_FILE"
exit 1
fi
fi
mv "$TMP_FILE" "$CHROME_STATE"
# 校验结果
echo ""
echo "(本次修补方式:${PATCH_METHOD}"
errors=0
if grep -q '"variations_country":"us"' "$CHROME_STATE"; then
echo "✓ variations_country 已设为 us"
else
echo "⚠️ variations_country 未生效"
((errors++)) || true
fi
if grep -q '"is_glic_eligible":true' "$CHROME_STATE"; then
echo "✓ is_glic_eligible 已启用"
else
echo "⚠️ is_glic_eligible 未生效"
((errors++)) || true
fi
echo ""
if [ $errors -eq 0 ]; then
echo "✅ 完成!请完全重启 Chrome 使改动生效。"
else
echo "⚠️ 部分改动可能未生效,请检查 Chrome 版本或改用 --force-glic-eligible 之类的实验开关。"
fi
+1
View File
@@ -0,0 +1 @@
{"autofill":{"ablation_seed":"AAAAAAAAAAA="},"breadcrumbs":{"enabled":false,"enabled_time":"13400000000000000"},"browser":{"first_run_finished":true,"last_whats_new_version":151,"whats_new":{"enabled_order":["VerticalTabsLaunch"]}},"demographics_birth_year_offset":0,"enterprise_reporting":{"saas_usage":{"last_trigger_time":"13400000000000000"}},"hardware_acceleration_mode_previous":true,"legacy":{"profile":{"name":{"migrated":true}}},"management":{"platform":{"enterprise_mdm_mac":0,"last_log_time":"13400000000000000"}},"network_time":{"network_time_mapping":{"local":1700000000000.0,"network":1700000000000.0,"ticks":90000000000.0,"uncertainty":1000000.0}},"optimization_guide":{"model_cache_key_mapping":{"TESTKEY000000001":"TESTMODEL0000001"},"model_store_metadata":{"1":{"TESTMODEL0000001":{"et":"13400000000000000","kbvd":true,"mbd":"1/TESTKEY000000001/TESTBLOB00000001","v":"1"}}},"on_device":{"last_version":"151.0.7922.138","model_crash_count":0,"performance_class":5,"performance_class_version":"151.0.7922.138","vram_mb":"8192"},"predictionmodelfetcher":{"last_fetch_attempt":"13400000000000000","last_fetch_success":"13400000000000000"}},"password_manager":{"had_biometrics_available":false},"performance_intervention":{"last_daily_sample":"13400000000000000"},"performance_tuning":{"last_battery_use":{"timestamp":"13400000000000000"}},"policy":{"last_statistics_update":"13400000000000000"},"profile":{"info_cache":{"Default":{"active_time":1700000000.0,"ai_subscription_tier":1,"avatar_icon":"chrome://theme/IDR_PROFILE_AVATAR_26","background_apps":false,"default_avatar_fill_color":-14868944,"default_avatar_stroke_color":-3816227,"enterprise_label":"","force_signin_profile_locked":false,"gaia_given_name":"Test","gaia_id":"100000000000000000001","gaia_name":"Test User One","gaia_picture_file_name":"Google Profile Picture.png","hosted_domain":"NO_HOSTED_DOMAIN","is_consented_primary_account":true,"is_ephemeral":false,"is_managed":0,"is_using_default_avatar":true,"is_using_default_name":false,"last_downloaded_gaia_picture_url_with_size":"https://example.com/fake-avatar-1.png","managed_user_id":"","metrics_bucket_index":1,"name":"Test","profile_color_seed":-16775169,"profile_highlight_color":-14868944,"signin.with_credential_provider":false,"user_accepted_account_management":false,"user_name":"test.user.one@example.com"},"Profile 1":{"active_time":1700000001.0,"ai_subscription_tier":0,"avatar_icon":"chrome://theme/IDR_PROFILE_AVATAR_5","gaia_given_name":"Work","gaia_id":"100000000000000000002","gaia_name":"Test User Two","hosted_domain":"NO_HOSTED_DOMAIN","is_glic_eligible":false,"is_using_default_avatar":false,"name":"Work","user_name":"test.user.two@example.com"},"Profile 2":{"active_time":1700000002.0,"ai_subscription_tier":1,"avatar_icon":"chrome://theme/IDR_PROFILE_AVATAR_9","gaia_given_name":"Personal","gaia_id":"100000000000000000003","gaia_name":"Test User Three","hosted_domain":"NO_HOSTED_DOMAIN","is_glic_eligible":true,"is_using_default_avatar":true,"name":"Personal","user_name":"test.user.three@example.com"}},"last_active_profiles":["Profile 1","Profile 2"],"metrics":{"next_bucket_index":4},"profile_counts_reported":"13400000000000000","profiles_order":["Default","Profile 1","Profile 2"]},"restart":{"last":{"session":{"on":{"shutdown":false}}}},"segmentation_platform":{"ukm_most_recent_allowed_time_key":"13400000000000000"},"session_id_generator_last_value":"1000000000","signin":{"active_accounts":{"FAKEHASH0000000000000000000000000000000001=":"13400000000000000"},"active_accounts_last_emitted":"13400000000000000","active_accounts_managed":{"FAKEHASH0000000000000000000000000000000001=":false}},"subresource_filter":{"ruleset_version":{"checksum":0,"content":"9.70.0","format":38}},"tab_stats":{"discards_external":0,"discards_frozen":0,"discards_proactive":0,"discards_suggested":0,"discards_urgent":0,"last_daily_sample":"13400000000000000","max_tabs_per_window":1,"reloads_external":0,"reloads_frozen":0,"reloads_proactive":0,"reloads_suggested":0,"reloads_urgent":0,"total_tab_count_max":1,"window_count_max":1},"toast":{"non_milestone_update_toast_version":"151.0.7922.138"},"ukm":{"client_id":"1000000000000000000","persisted_logs":[{"data":"FAKEBASE64DATA0000000000000000000000000000","hash":"FAKEHASH00000000000000000000=","signature":"FAKESIGNATURE0000000000000000000000000000=","timestamp":"1700000000","type":0}],"session_id":1},"uninstall_metrics":{"installation_date2":"1700000000"},"updateclientdata":{"apps":{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa":{"cohort":"1:0000:","cohortname":"Stable","dlrc":1,"fp":"","installdate":1,"max_pv":"0.0.0.0","pf":"00000000-0000-0000-0000-000000000000","pv":"1.0.0.0"}}},"was":{"restarted":false}}
+35
View File
@@ -0,0 +1,35 @@
# 测试用 Local State fixture
`Local State.sample.json` 是**完全人工构造**的样本文件,不是从任何真实 Chrome 安装脱敏而来——
所有 `gaia_id`、姓名、邮箱、头像 URL 等字段都是占位符(`test.user.one@example.com` 之类),
不包含任何真实账号信息,可以放心提交到仓库、拿去别的机器测试。
结构上覆盖了 `profile.info_cache` 的三种场景,用来验证 `install.sh` / `install.ps1`
对每个 profile 的处理是否正确:
| profile | 初始状态 | 用来验证什么 |
|---|---|---|
| `Default` | 完全没有 `is_glic_eligible` 字段 | 插入逻辑 |
| `Profile 1` | `is_glic_eligible: false` | 翻转逻辑 |
| `Profile 2` | `is_glic_eligible: true` | 已生效时保持不变、不产生重复 key |
顶层也没有 `variations_country` / `variations_permanent_consistency_country`,用来验证插入逻辑。
## 在 Windows 上测试 install.ps1
1. 先备份你自己真实的 Local State(如果要在真实 Chrome 目录下测):
```powershell
Copy-Item "$env:LOCALAPPDATA\Google\Chrome\User Data\Local State" "$env:LOCALAPPDATA\Google\Chrome\User Data\Local State.real.bak"
```
2. 把这份 fixture 拷贝过去顶替(**记得测完要恢复**):
```powershell
Copy-Item ".\test-fixtures\Local State.sample.json" "$env:LOCALAPPDATA\Google\Chrome\User Data\Local State"
```
3. 运行 `..\install.ps1`,检查输出里三个 profile 是否都提示 `is_glic_eligible` 已启用。
4. 测完恢复真实文件:
```powershell
Copy-Item "$env:LOCALAPPDATA\Google\Chrome\User Data\Local State.real.bak" "$env:LOCALAPPDATA\Google\Chrome\User Data\Local State" -Force
```
也可以不碰真实路径,直接改一份 `install.ps1` 的副本把 `$chromeStatePath` 硬编码指向这份
fixture,跑完直接看输出文件内容,更安全、不依赖"记得恢复"这一步。