大家都应该遇到过,国内网络访问ghcr.io的超时问题,经过网上学习加AI沟通,如下是本次处理的总结记录,希望可以帮到有需要的人。 ps:国内大厂不代理加速ghcr.io,说是因为流量和审核问题,所以基本上都是临时的,不知道是不是真这个理由。 一、 问题背景与痛点 在国内折腾 Homelab 或 NAS(如飞牛 OS)时,很多开源项目(例如 wg-easy )的 Docker 镜像都逐步迁移到了 GitHub 的容器注册表 ghcr.io (GitHub Container Registry)。 由于国内特殊的网络环境,直接在 NAS 上拉取 ghcr.io 镜像极易出现两种错误: 网络直接超时 :完全无法连接官方源。 公共加速站失效 :使用第三方或公益代拉加速站(如已经关停的域名),会遭遇 dial tcp: lookup xxx: no such host 报错。 为了彻底、稳定地解决这一“最后一公里”的分发难题,最完美的方案是利用自己拥有的 海外原生网络服务器(如美国 VPS) ,通过 1Panel 搭建一个专属的私有镜像反代通道。 二、 踩坑历程与底层原理分析 在折腾自建反代的过程中,通常会经历三个阶段的报错,其背后的底层网络逻辑非常值得记录: 坑 1:直接反代返回 Whoa there! Not Found 错误 现象 :浏览器访问或 Docker 拉取时返回 GitHub 标准的 404 网页。 原因 :Nginx 默认的反代配置非常老实,在转发时将请求头中的 Host 保持为了用户自己的域名。而 GitHub API 服务器极为严格,发现 Host 不是 ghcr.io 时,会直接将请求重定向到网页端的 404 错误页。 坑 2:拉取时遭遇 TLS handshake timeout 认证超时 现象 :修改了 Host 后,Docker 客户端在连接时依然卡死,报错提示 Get "https://ghcr.io/token?scope=..." net/http: TLS handshake timeout 。 原因 :Docker 注册表有一套特殊的 令牌(Token)鉴权机制 。当 NAS 找反代服务器下载镜像时,GitHub 会要求客户端先去拿一个临时 Token。因为没有拦截重定向,GitHub 直接把官方的 ghcr.io/token 地址甩给了国内的 NAS。结果 NAS 绕过了中转站,自己“肉身”去连官方拿 Token ,直接被大墙拦截导致超时。 三、 终极解决方案 步骤 1:域名解析准备 在域名服务商(如 Cloudflare)中添加一条 A 记录(例如 ghcr.xxx.com ),解析到你的海外服务器真实公网 IP。 注意 :如果使用 Cloudflare, 必须关闭小黄云(改为仅限 DNS / 灰色云朵) 。因为 Cloudflare 的 CDN 节点 IP 在国内经常遭到大面积封锁或干扰,开启会导致国内 NAS 报 no such host 错误。 步骤 2:在 1Panel 中配置高级反代 在 1Panel 中创建一个反向代理网站,主域名填写你的域名,后端代理地址填写 https://ghcr.io 。 申请并强制开启 HTTPS / SSL 证书 (Docker 客户端强制要求安全连接)。 打开该网站的 配置文件(Nginx 源码) ,将 location / 块完全替换为以下“瞒天过海”终极版配置: Nginx location / { proxy_pass https://ghcr.io; # 核心修复 1:强制将 Host 设为后端目标域名,欺骗 GitHub 放行 proxy_set_header Host ghcr.io; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 核心修复 2:拦截并改写 GitHub 返回的鉴权重定向 Location 头 # 强制让 Token 请求也走你自己的中转域名,闭环解决国内超时问题 proxy_redirect https://ghcr.io/ https://ghcr.xxx.com/; # 隐藏可能暴露源站域名的特定 Link 响应头 proxy_hide_header Link; # 穿透 Docker 认证头(Authorization) proxy_set_header Authorization $http_authorization; proxy_pass_header Authorization; # 核心修复 3:关闭缓冲区,开启流式传输,防止大容量镜像拉取时断开或 504 超时 proxy_buffering off; proxy_read_timeout 900s; proxy_send_timeout 900s; } 步骤 3:国内 NAS 客户端测试与使用 配置保存重载后,回到国内飞牛 OS 的 SSH 终端,直接在原镜像名前加上你的专属域名即可丝滑下载: Bash sudo docker pull ghcr.xxx.com/wg-easy/wg-easy:latest 四、 进阶安全建议 由于公开的反代接口极易被全网扫描器扫到并疯狂盗刷流量,建议在 1Panel 中为该反代站点开启 Basic Auth(用户密码认证) 或限制访问 IP。开启后,在 NAS 终端执行一次 docker login ghcr.xxx.com 输入账号密码,即可安全、专享地白嫖自己服务器的流量,不用担心月流量被黑客一夜薅秃。 2 个帖子 - 2 位参与者 阅读完整话题
最近在做一个 PyInstaller 打包 + tkinter GUI 的项目,踩了不少坑,整理出来给佬们避雷。 背景 项目结构大致是:GUI 主程序负责界面和交互,动态执行一个打包进去的 Python 脚本, 脚本的 sys.stdout / sys.stderr 重定向到 GUI 的文本框。 看起来简单,但联调过程中几乎每一个环节都出了幺蛾子。 Q1 PermissionError / RuntimeError:无法获取脚本内容 现象 :打包后运行报 PermissionError: Permission denied 或 RuntimeError: 无法获取脚本内容 。 原因 :PyInstaller onefile 模式下, _MEIPASS 是只读内存映射, 用 open() 直接读取其中的文件会失败。 解法 :改用嵌入脚本方案,把脚本内容写成一个 Python 模块 ( embedded_script.py ),在打包时作为普通模块引入,不在运行时读文件。 Q2 退出码 -1,但没有任何错误详情 现象 :脚本执行失败,捕获到退出码 -1 ,日志里没有任何异常信息。 原因 : sys.exit() 抛出的是 SystemExit ,它继承自 BaseException 而非 Exception ,普通的 except Exception 捕获不到,异常被静默丢弃。 解法 : try: exec(script_code) except BaseException as e: if isinstance(e, SystemExit): error_msg = f"[SystemExit] sys.exit({e.code})" else: error_msg = traceback.format_exc() Q3 logging 报 AttributeError:‘GuiWriter’ 没有 isatty 现象 : AttributeError: 'GuiWriter' object has no attribute 'isatty' 原因 : logging.StreamHandler 内部会调用 stream.isatty() 判断是否为终端, 自定义的 GuiWriter 没有实现这个方法。 解法 :在 GuiWriter 类里补一个: def isatty(self): return False Q4 TypeError:string argument expected, got ‘bytes’ 现象 :脚本运行时报类型错误,看起来跟字节/字符串混用有关。 原因 :脚本里有一段 Windows UTF-8 兼容处理: if sys.platform == 'win32' and hasattr(sys.stdout, 'buffer'): sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8') 这段代码把 sys.stdout 包了一层 TextIOWrapper ,但外层 GuiWriter 已经接管了 sys.stdout ,两者的类型不一致,写入时就炸了。 解法 :在 generate_embedded_script.py 中,生成脚本内容时把这段替换掉: _win_utf8_block = """if sys.platform == 'win32' and hasattr(sys.stdout, 'buffer'): sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8') """ content = content.replace(_win_utf8_block, "") Q5 batch 脚本报错:‘ansion’ 不是内部或外部命令 现象 :PowerShell 执行 batch 文件时,某条命令被截断, 报类似 'ansion' 不是内部或外部命令 的错误( expansion 被截成了 ansion )。 原因 :batch 里用了 call set "VAR=%%VAR:--onedir=%%" 做字符串替换, 遇到参数中含有分号 ; 时会截断;加上 PowerShell 对 !VAR! 有特殊解析, 变量扩展结果与预期不一致。 解法 :放弃变量拼接,直接在 batch 中写出完整参数列表,不做运行时字符串替换。 Q6 Excel 文件路径 ./exam_info_df.xlsx 找不到 现象 :打包后运行,脚本找不到 Excel 文件,但开发环境没问题。 原因 :冻结环境下 Path.cwd() 指向 exe 所在目录,不是项目源码目录, 相对路径失效。 解法 :区分冻结环境和开发环境,优先从 _MEIPASS 取文件: import sys from pathlib import Path if getattr(sys, 'frozen', False): base = Path(sys._MEIPASS) else: base = Path(__file__).parent excel_path = base / "exam_info_df.xlsx" 或者通过 CLI 参数 --excel-path 传入绝对路径,更灵活。 Q7 WebDriver 版本不匹配 现象 : SessionNotCreatedException ,WebDriver 与浏览器版本不兼容。 原因 :Edge 浏览器静默自动更新,项目里打包的 msedgedriver.exe 版本落后了。 解法 :去官方下载页面下载与当前浏览器版本匹配的驱动, 替换 edgedriver_win64/msedgedriver.exe 。 下载地址: Microsoft Edge WebDriver | Microsoft Edge Developer 速查表 现象 根因 方案 PermissionError 读脚本 _MEIPASS 只读 改用嵌入脚本模块 退出码 -1 无详情 SystemExit 不被 except Exception 捕获 改用 except BaseException isatty() 属性错误 StreamHandler 调用了 isatty() GuiWriter 中补实现 stdout 类型冲突 TextIOWrapper 与 GuiWriter 冲突 生成脚本时移除包装代码 batch 命令截断 分号与 !VAR! 的特殊解析 不做变量拼接,直接写完整参数 Excel 路径找不到 cwd() ≠ 项目目录 用 _MEIPASS 或绝对路径参数 WebDriver 不兼容 浏览器自动更新后驱动落后 匹配版本重新下载驱动 踩完这些坑最大的感受是: PyInstaller 打包后,一切"理所当然"的运行时假设都要重新验证 —— 工作目录、文件读写权限、标准流对象的接口、异常捕获的范围,一个都不能漏。 佬们如果遇到类似问题欢迎评论区交流 1 个帖子 - 1 位参与者 阅读完整话题
Antigravity Electron39 崩溃排查与修复全记录 谷糕 antigravity从刚出就开始用了,刚出的时候配合学生订阅疯狂白嫖,帮我做完了一整个R的生信项目,效果很好,但是不知道从哪个弱智版本开始,electron就开始崩溃,倒是也几乎不影响你正常使用,但是通知疯狂报崩溃真的很几把恶心,在网上也没有找到好的解决方案,无非是禁用sandbox和gpu加速,实际上也不好使,想着干脆算了,等更新吧,等到年后了问题依旧,今天掏一上午来跟这个干上了,所幸解决了,AI真强啊,好时代来临力(喜 环境信息 OS: Arch Linux 6.19.11-zen1-1-zen GPU: NVIDIA (驱动 580.142) 软件: antigravity 1.19.6-1 (pacman), 使用 electron39 现象: antigravity 频繁崩溃 (SIGSEGV / SIGTRAP) 排查思路 第一阶段:定位崩溃来源 假设 : Electron 在 Linux 上的 GPU 加速/沙箱与 NVIDIA 驱动不兼容(这是最常见的 Electron 崩溃原因) 操作 : coredumpctl list — 查看崩溃记录,确认是 electron39 进程 读取 /usr/bin/antigravity 启动脚本,发现它会读取 ~/.config/electron39-flags.conf 依次尝试: --disable-gpu-sandbox → 仍然崩溃 --disable-gpu + --no-sandbox → 仍然崩溃 关键观察 : --no-sandbox 生效了(崩溃命令行里 --enable-sandbox 消失了),但进程还是 SIGSEGV。说明 不是 GPU/沙箱问题 ,假设被证伪。 第二阶段:从 coredump 精确定位崩溃模块(正确方向) 核心思路 : coredump 里有完整的内存映射,可以把崩溃的虚拟地址映射回具体的 .so/.node 文件 步骤 1: 获取崩溃地址 从 coredumpctl info 输出中: Stack trace of thread 277673: #0 0x00007fed67594775 n/a (n/a + 0x0) 崩溃地址是 0x00007fed67594775 ,但符号缺失( n/a ),无法直接知道是哪个函数。 步骤 2: 获取内存映射表 coredumpctl debug <PID> # 进入 gdb (gdb) info proc mappings 这会输出进程中所有加载的共享库及其虚拟地址范围,例如: 0x00007fed67554000 0x00007fed67571000 .../watcher.node 0x00007fed67571000 0x00007fed675bf000 .../watcher.node 0x00007fed675bf000 0x00007fed675cc000 .../watcher.node 0x00007fed675cc000 0x00007fed675ce000 .../watcher.node 0x00007fed675ce000 0x00007fed675d0000 .../watcher.node 步骤 3: 匹配地址到文件 崩溃地址 0x7fed67594775 落在 0x7fed67554000 - 0x7fed675ce000 区间内,对应文件: /usr/lib/antigravity/node_modules/@parcel/watcher/build/Release/watcher.node 而不是 electron39 本身! 步骤 4: 计算文件内偏移 偏移 = 崩溃地址 - 模块基址 = 0x7fed67594775 - 0x7fed67554000 = 0x40775 步骤 5: 用 nm 找到偏移对应的函数 nm watcher.node | sort | awk '{val=strtonum("0x"$1); if(val<=0x40775) last=$0; else {print last; exit}}' 结果: 00000000000406f0 W _ZNSt19_Sp_counted_deleterIP7DirTree14DirTreeDeleterSaIvELN9__gnu_cxx12_Lock_policyE2EE10_M_disposeEv Demangling: std::_Sp_counted_deleter<DirTree*, DirTreeDeleter>::_M_dispose() 结论 : @parcel/watcher 2.5.1 的 C++ 代码在释放 DirTree 对象时访问了无效内存,导致 SIGSEGV。这是该模块自身的 bug,与 Electron GPU/沙箱无关。 第三阶段:修复 方案 : 用 npm 编译更新版本的 @parcel/watcher 替换掉有 bug 的 2.5.1 # 1. 安装 + 编译最新版 mkdir -p /tmp/parcel-watcher-rebuild && cd /tmp/parcel-watcher-rebuild npm init -y npm install @parcel/watcher # 下载源码 npx node-gyp rebuild --directory=node_modules/@parcel/watcher # 编译 # 2. 备份 + 替换 sudo cp /usr/lib/antigravity/node_modules/@parcel/watcher/build/Release/watcher.node{,.bak} sudo cp /tmp/parcel-watcher-rebuild/node_modules/@parcel/watcher/build/Release/watcher.node \ /usr/lib/antigravity/node_modules/@parcel/watcher/build/Release/ # 3. 清理 electron flags(之前误加的) rm ~/.config/electron39-flags.conf N-API 模块 ABI 稳定,不受 Node 版本限制,可以安全用系统 node 编译后给 electron 使用。 关键学到的 Electeron 崩溃 ≠ Electron 的锅 : 崩溃进程名是 electron,但可能是它加载的任何 .so/.node 出的问题 coredump + info proc mappings 是定位共享库崩溃的根本方法 : 虚拟地址 → 地址范围 → 具体文件 → 偏移 → 符号表 N-API (node_api) 模块可以跨 Node 版本使用 : 不同于传统的 V8 Addon,N-API 模块 ABI 稳定 先证伪再换方向 : GPU/沙箱是最常见原因,但验证无效后果断换了方向,没有死磕 修复后维护 备份文件: /usr/lib/antigravity/node_modules/@parcel/watcher/build/Release/watcher.node.bak 新编译文件: ~/Downloads/watcher.node antigravity 更新后如果又崩溃,检查版本并替换: # 检查版本 cat /usr/lib/antigravity/node_modules/@parcel/watcher/package.json | python3 -c "import sys,json; print(json.load(sys.stdin)['version'])" # 如果还是 2.5.1,替换回来 sudo cp ~/Downloads/watcher.node /usr/lib/antigravity/node_modules/@parcel/watcher/build/Release/watcher.node 照理说watcher崩了之后,反重力的自动补全应该也是挂掉的状态,但是实际是可以正常使用的 2 个帖子 - 2 位参与者 阅读完整话题