米米的博客

做了一点微小的工作

家里的电信光猫使用路由模式,由光猫完成 PPPoE 拨号,下游再连接自己的路由器。这样的配置方式 IPv4 使用起来没什么问题,IPv6 也可以拿到地址,但是实际使用时却发现了一个问题:光猫已经从电信获得了一个/60 IPv6 前缀,直连光猫的设备也有 IPv6,但下游 OpenWrt 只能在 WAN 口获得一个/64地址,LAN 侧始终没有公网 IPv6 前缀。换句话说,光猫默认没有启用下游 IPv6 PD 前缀分发。这对于 Tailscale 之类需要打洞的场景不友好:OpenWrt 的 LAN 侧只能使用 ULA、NAT66 或 IPv6 Relay,也失去了利用原生 IPv6 进行 Tailscale 直连的条件。

把光猫改成桥接、由主路由拨号当然可以解决 IPv6 前缀分配问题,但 XG-140G-TF 本身的转发性能足够,而主路由是软路由,PPPoE 本身也非常吃单核性能,所以我还是希望保留光猫拨号。进一步检查后发现,XG-140G-TF 的固件中其实包含下游 DHCPv6-PD Server,只是电信配置默认没有开启对应的产品级开关。

本文记录如何在保留光猫拨号的情况下,开启 XG-140G-TF 的 IPv6 PD 前缀分发。光猫的管理员登录、Telnet 开启和 root 密码获取方法,请先参考配置 XG-140G-TF 光猫

问题现象

光猫的 IPv6 状态显示,WAN 连接已经通过 Prefix Delegation 从电信取得了/60前缀,例如:

1
2001:db8:1234:690::/60

一个/60可以划分为 16 个/64子网。在这个例子中,范围是:

1
2
3
4
2001:db8:1234:690::/64
2001:db8:1234:691::/64
……
2001:db8:1234:69f::/64

光猫使用第一个690::/64作为自己的 LAN 网段,通过 RA 广播给直连设备。但在修改前,下游 OpenWrt 的 WAN6 状态只有一个地址:

1
2001:db8:1234:690:6c59:4eff:fecb:61f4/64

容易产生误解的是,这只是 OpenWrt WAN 接口通过 SLAAC 获得的一个 IPv6 地址,并不是可供 OpenWrt 继续划分给 LAN 的前缀。检查 WAN6 状态时可以看到:

1
"ipv6-prefix": []

由于没有获得 IA_PD,OpenWrt 的 LAN 只能继续使用 ULA 地址,日志中还会出现:

1
A default route is present but there is no public prefix on lan
阅读全文 »

家里开通了电信 IPTV,但套餐只包含了一台机顶盒,家中有多台电视时使用起来并不方便。很早以前我就知道可以通过 igmpproxy 转发组播,再配合 udpxy 将组播转换为普通 HTTP 单播,但此前一直没有配置成功。

最近重新梳理了一遍光猫、机顶盒和组播之间的关系,终于实现了以下目标:

  • 原装 IPTV 机顶盒继续正常使用,直播和点播均不受影响;
  • 局域网中的其他设备(电视、电脑、手机等)可以收看直播。

本文记录的是我在湖北武汉电信网络中的实际配置。不同地区的 VLAN 和认证方式都可能不同,请不要直接照抄参数。修改光猫前,建议先备份配置,并记录每一处原始设置。

原始网络结构

光猫中原本有两项主要业务:

  1. 上网业务,采用路由模式,本文不讨论;
  2. iTV 业务,采用桥接模式,仅绑定到光猫的 LAN2 口。

iTV 桥接连接的关键参数如下:

项目原始设置
业务类型OTHER
连接模式桥接
IP 模式IPv4
业务 VLAN ID3542
端口绑定LAN2(iTV 口)

在这一结构下,电信机顶盒连接 LAN2,并由机顶盒自己进行 PPPoE 拨号。机顶盒的网络设置中可以看到 IPTV 账号,但密码通常不会直接显示。

这里有一个容易踩的坑:IPTV 密码可能由机房或装维系统下发。即使通过 10000 客服重置或查询到了一个密码,也不一定能在现网环境中成功拨号。如果确认账号无误但始终认证失败,最好直接联系当地装维人员,获取当前线路实际使用的 IPTV 密码。

失败的尝试:让两个设备分别拨号

我最初的思路很直接:在光猫的 iTV 桥接连接中,除 LAN2 外再绑定一个 LAN3,让原装机顶盒连接 LAN2,另一个路由器或小主机连接 LAN3。两台设备分别使用同一组 IPTV 账号、密码拨号,小主机负责运行 igmpproxy,就可以实现局域网组播。

实际测试发现,这种方式行不通:只要其中一台设备已经拨号成功,第二台设备的拨号就会被拒绝。至少在我的线路上,同一个 IPTV 账号无法同时建立两个 PPPoE 会话。这意味着如果光猫桥接,让下游设备拨号,原装机顶盒和独立组播代理只能二选一。

关键调整:改由光猫拨号

进一步查看机顶盒设置时,我发现它除了 PPPoE,还支持 DHCP 联网。由此可以换一种思路:

不再让机顶盒直接拨号,而是让光猫建立 IPTV PPPoE 连接,再通过 DHCP 给 LAN2、LAN3 下游设备分配地址。

我将光猫中的 iTV 连接从桥接模式改为路由模式,填写 IPTV PPPoE 账号和密码,并同时绑定 LAN2、LAN3。

修改后,光猫成功获得了运营商 IPTV 网络中的 10.x.x.x 地址,机顶盒改为 DHCP 后也能取得光猫下发的局域网地址。

此时测试结果如下:

  • IPTV 平台登录正常;
  • 菜单和业务页面正常;
  • 点播节目可以播放;
  • 直播频道仍然黑屏

这个现象很有价值。它说明 PPPoE 认证、DHCP 和普通 IP 通信已经正常,机顶盒也没有被限制为必须自行拨号;但直播所需的组播链路尚未打通。点播通常走普通单播,而直播依赖 IGMP 和运营商的组播 VLAN,因此两者完全可能出现一个正常、另一个黑屏的情况。

阅读全文 »

过去几年里,大模型把语音助手的想象空间重新打开了。我们已经习惯了在手机、网页和电脑上和 AI 对话,但家里的智能音箱却常常还停留在比较固定的指令式体验里:问天气、放音乐、控制设备可以,但想要更自然的连续对话、更灵活的工具调用,或者接入自己选择的大模型服务,就没那么顺手了。

xiaoai-agent 想解决的正是这个问题:让小爱音箱不只是一个云端语音入口,而是变成一台可以常驻运行的端侧语音 Agent 设备。

项目地址:github.com/stevenjoezhang/xiaoai-agent

背景:小爱音箱还有很多潜力

小爱音箱本身是一类很适合承载语音 Agent 的设备。它有麦克风阵列,有扬声器,有唤醒能力,也有成熟的音频播放和 TTS 链路。换句话说,它的硬件形态已经非常接近「家里的 AI 入口」。

但现实使用中会遇到几个限制:

  • 原生小爱同学的对话能力和可扩展性有限,很难按自己的偏好接入新的大模型、ASR 或工具。
  • 一些改造方案需要额外部署服务器或桥接服务,链路变长,维护成本也会变高。
  • 原生小爱同学仍然可能监听麦克风,出现抢麦、抢答,甚至触发小米云端控制的情况。
  • 音频体验很难做好,比如唤醒、录音、回声消除、连续对话、中途打断等细节,任何一个环节不稳都会影响日常可用性。

xiaoai-agent 的方向,是尽量把这些能力收回到音箱端侧,让音箱自己完成唤醒、录音、识别、思考、工具调用和回复。

现有方案与不足

xiaoai-agent 之前,已经有不少项目尝试把小爱音箱和大模型连接起来。这些探索证明了小爱音箱的可玩性,也把很多底层路径跑通了。

比如原生小爱同学提供了稳定的唤醒、TTS、设备控制和生态接入能力,适合普通用户日常使用。但它的对话能力、模型选择和工具扩展都比较固定,用户很难把自己的 ASR、大模型或智能家居工作流接进去。

Open-XiaoAI 这类项目则打开了更底层的设备改造路径,让音箱具备 SSH、启动脚本和自定义部署的可能。它解决的是「能不能进入设备、能不能运行自己的东西」的问题,为后续折腾提供了基础。

MiGPT 等桥接类方案则更进一步,把大模型对话接入到小爱音箱体验里,让用户可以用自然语言和 AI 聊天。这类方案的优势是思路清晰、上手相对友好,但通常需要额外服务端或桥接层来承载 Agent 逻辑,音箱本身更像前端入口。

这些方案各有价值,但也留下了一些共同痛点:语音链路复杂、部署组件偏多、原生小爱可能抢麦或抢答、云端控制可能被误触发,音频细节也容易受多层转发影响。xiaoai-agent 正是在这些探索之上,尝试把 Agent 主流程进一步收回到音箱端侧。

它解决了什么痛点

xiaoai-agent 最核心的价值,是把小爱音箱改造成一个更开放、更可控的语音 Agent 载体。

首先,它避免了「两个助手同时工作」的混乱。项目会让原生小爱同学的麦克风输入静音,由 xiaoai-agent 接管真实麦克风音频。这样可以减少抢麦、抢答,以及误触发小米云端控制的问题。

其次,它降低了长期运行的复杂度。传统方案常常需要在局域网里额外跑一个服务,音箱和服务之间再做消息转发。xiaoai-agent 选择直接在音箱端侧运行 Agent,部署链路更短,也更像一台独立设备。

第三,它复用了音箱原本已经做好的音频能力。唤醒、VPM 音频回调、TTS 播报、播放控制等能力都来自设备原生系统,日常交互会更接近「真正的音箱体验」,而不是把音箱当作一个简单的外设。

最后,它给用户留下了足够多的选择空间。ASR 可以使用兼容 OpenAI POST /v1/audio/transcriptions 的服务,大模型也可以接入 OpenAI-compatible API。你可以按自己的预算、延迟和隐私偏好选择服务,而不是被固定在单一路径里。

主要特性

xiaoai-agent 是一个非官方技术研究项目,核心 Agent 使用 Rust 编写,运行在小爱音箱端侧。目前项目已经在 Xiaomi 智能音箱 Pro(OH2P)固件 1.62.2 上测试成功。

它目前已经具备一套完整的语音 Agent 闭环:

  • 端侧常驻运行:Agent 直接运行在音箱上,不需要额外部署专门服务端。
  • 完整语音流程:支持唤醒、录音、ASR、大模型对话、工具调用和 TTS 回复。
  • 更自然的交互:支持连续对话、VAD、中途打断、回声消除和播放时录音。
  • 开放模型接入:ASR 和 LLM 均可配置为兼容 OpenAI API 的服务。
  • 工具与设备控制:内置时间、天气、音乐播放等工具,并可通过 Home Assistant MCP 控制智能家居。
  • 保留部分系统能力:语音对话由 Agent 接管,但蓝牙网关等非语音服务不应受到影响。
  • 可自定义体验:可以调整唤醒响应、LED 状态、音乐服务和运行参数。

项目仓库中也包含补丁固件制作、刷机辅助、示例配置和部署说明,方便用户从设备准备一路走到 Agent 常驻运行。需要特别说明的是,它不是官方项目,也不是面向所有型号的一键通用方案;现阶段更适合愿意折腾、理解刷机风险,并且手上有对应设备的用户尝试。

这套能力组合起来,小爱音箱就不再只是一个固定功能的智能音箱,而更像是家里可以听、可以说、可以调用工具的 AI 节点。

阅读全文 »

背景

笔者很久以前在 Mac 上通过 Boot Camp 安装了 Windows 系统,最近计划将这个系统迁移到 ESXi 虚拟机中使用。笔者首先使用同一机器上 macOS 系统安装的 VMware Fusion 来将 Boot Camp 的 Windows 转化为虚拟机,这一步转化是成功的,虚拟机可以启动。但是进一步导出 OVF 模板并在 ESXi 上导入时,结果发现虚拟机无法启动。经过分析,发现问题的根源在于磁盘扇区大小不匹配。

Mac 上通过 Boot Camp 安装的 Windows,磁盘默认使用 4Kn(4K Native) 扇区格式——物理扇区和逻辑扇区均为 4096 字节。这种格式的磁盘在正常使用时是无感的,但是在需要迁移系统到其他磁盘(比如转成 ESXi 虚拟机)时,就会碰到麻烦:大多数虚拟化平台的虚拟磁盘只支持 512 字节扇区(512n 或 512e)。

这个问题其实并不好解决。扇区大小不匹配,那么直接克隆必然失败,很多常见的硬盘分区、数据恢复工具,包括声称支持不同扇区大小之间的克隆转换的 Clonezilla,都无法解决这个问题。

真正可行的方案:DISM

经过测试,微软自带的 DISM(Deployment Image Servicing and Management) 工具可以完美解决这个问题。

原因很简单——DISM 的工作方式是文件级别的:

  • 备份时:把系统分区的所有文件打包成一个 .wim 镜像
  • 还原时:把 .wim 镜像中的文件逐个释放到目标分区

整个过程完全不涉及扇区结构。它不关心源盘是 4Kn 还是 512,也不关心目标盘是什么格式。扇区大小的差异在文件级操作面前根本不是问题。

大致流程:

  1. 在 WinPE 环境下,用 DISM 将 Boot Camp 的 Windows 系统捕获为 .wim 文件
  2. 在虚拟机中创建一个 512 扇区的虚拟磁盘,手动建好 ESP 分区和系统分区
  3. 用 DISM 将 .wim 释放到虚拟磁盘的系统分区
  4. 写入引导,完成

Dism++ 同样可以

如果你不喜欢敲命令行,Dism++ 是 DISM 的图形化前端,底层调用的是同样的 API,同样是文件级操作。用它来备份和还原效果完全一样,只是操作更直观。

唯一需要注意的是:无论用 DISM 还是 Dism++,都需要你事先在目标磁盘上手动建好分区(包括 ESP 分区),它们只负责释放文件和写入引导,不会自动帮你分区。

小结

在 4Kn 到 512 的系统迁移场景下,凡是涉及扇区级操作的工具都可能翻车。而 DISM / Dism++ 因为是纯文件级的备份还原,天然绕过了扇区大小的问题,是目前最简单可靠的方案。

本文记录了我在忘记 VMware Workstation 虚拟机加密密码后,通过 PowerShell 调用 Windows 凭据管理器 API 成功恢复密码的完整过程。如果你也遇到了同样的问题,希望这篇文章能帮到你。

起因

前几天我需要迁移一台之前创建的 VMware 加密虚拟机,选择导出 OVF,结果弹出了密码输入框——我完全不记得当时设置了什么密码。

说实话,这个密码根本不是我主动设置的。VMware Workstation 在某些操作(比如启用加密、克隆加密虚拟机)时会引导你设置加密密码,同时提供一个「Remember the password」(记住密码)选项。当时我随手勾选了这个选项,之后每次打开虚拟机都是自动解锁的。

直到需要迁移虚拟机时,它需要用户主动输入密码了。于是我开始研究恢复方案。

原理

经过一番搜索,我了解到以下关键信息:

  1. VMware Workstation 在你勾选「Remember the password」时,会将加密密码存储在 Windows 凭据管理器(Credential Manager) 中。
  2. 每台加密虚拟机都有一个唯一的 GUID,VMware 用这个 GUID 作为凭据的名称(Target Name)来存储密码。
  3. 虽然你可以在 Windows 的「凭据管理器」界面中看到这些条目,但界面上不会显示密码明文
  4. 好消息是,我们可以通过 Windows 的 Win32 CredReadW API 直接读取凭据中存储的密码。

换句话说,密码一直都在你的电脑里,只是 VMware 没有提供一个「显示密码」的按钮而已。

恢复步骤

找到虚拟机的加密 GUID

用文本编辑器打开虚拟机的 .vmx 配置文件。这个文件通常位于虚拟机的存储目录下,例如:

1
C:\Users\<用户名>\Documents\Virtual Machines\<虚拟机名>\<虚拟机名>.vmx

在文件中搜索 encryptedVM.guid,你会找到类似这样的一行:

1
encryptedVM.guid = "{833AB4F5-587E-4B11-9260-4DB13742FA7F}"

记下这个 GUID(包括大括号),后面会用到。

在 PowerShell 中加载 Win32 API

打开 PowerShell,将以下代码完整复制粘贴进去并回车执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Add-Type @"
using System;
using System.Runtime.InteropServices;

public class Win32Cred
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct CREDENTIAL
{
public int Flags;
public int Type;
public IntPtr TargetName;
public IntPtr Comment;
public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
public int CredentialBlobSize;
public IntPtr CredentialBlob;
public int Persist;
public int AttributeCount;
public IntPtr Attributes;
public IntPtr TargetAlias;
public IntPtr UserName;
}

[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CredReadW(
string target, int type, int reservedFlag, out IntPtr credentialPtr);

[DllImport("advapi32.dll", SetLastError = true)]
public static extern void CredFree(IntPtr buffer);

public static string CredRead(string targetName, int type = 1)
{
IntPtr credPtr;
if (CredReadW(targetName, type, 0, out credPtr))
{
CREDENTIAL cred = (CREDENTIAL)Marshal.PtrToStructure(
credPtr, typeof(CREDENTIAL));
string pass = Marshal.PtrToStringAnsi(
cred.CredentialBlob, cred.CredentialBlobSize);
CredFree(credPtr);
return pass;
}
throw new System.ComponentModel.Win32Exception(
Marshal.GetLastWin32Error());
}
}
"@

这段代码通过 C# 的 P/Invoke 机制定义了对 advapi32.dllCredReadWCredFree 函数的调用接口,并封装了一个简洁的 CredRead 方法。

读取密码

继续在 PowerShell 中执行以下命令,将 GUID 替换为你在第一步中找到的值:

1
[Win32Cred]::CredRead("{833AB4F5-587E-4B11-9260-4DB13742FA7F}")

如果一切顺利,密码会直接输出到屏幕上:

1
2
PS C:\Users\Andrew> [Win32Cred]::CredRead("{833AB4F5-587E-4B11-9260-4DB13742FA7F}")
MySecretPassword

就是这么简单。 拿到密码后,回到 VMware 输入即可解锁虚拟机。

总结

整个恢复过程可以概括为三步:

步骤操作关键点
1打开 .vmx 文件找到 encryptedVM.guid
2在 PowerShell 中加载 Win32 API复制粘贴 Add-Type 代码块
3调用 CredRead 方法传入 GUID,密码直接输出

VMware 的加密密码其实一直安静地躺在 Windows 凭据管理器里,只是没有提供直观的查看方式。通过一小段 PowerShell + C# 互操作代码,就能把它读出来。

希望这篇文章能帮助到同样被 VMware 加密密码困扰的你。


参考文章:andshrew 的 Gist

0%