[PVE]在Proxmox Virtual Environment中给虚拟机直通PCIE SATA控制器

我在PVE中跑了个TrueNAS系统,不过之前一直是通过软SCSI控制器把硬盘一块块映射到虚拟机里面去的,虽然这样能用,但这样一个问题是有性能开销,truenas也不能直接对硬盘进行最直接的控制,另一个问题是换盘的时候都要重新执行映射指令,很麻烦。

于是我想着还是得实现真正的直通才行,要实现对虚拟机来说能让硬盘即插即用最干脆的方法还是直接把整个SATA控制器映射进去,我刚好有个插在主板m.2插槽上的PCIE转SATA扩展卡,扩出来5个SATA口。

注意主板上的SATA控制器是不可以映射到虚拟机里的,一个原因是其iommu分组一定会绑定主板芯片组里其它兼职的部分无法分开,另一个原因是如果你的PVE系统盘在主板上的SATA接口上或者在通过芯片组的m.2接口上(通常是远离CPU的那个m.2接口),会导致pve启动不了。

这篇文章的主要部分就是以上我踩的坑,分享出来给同样遇到问题的人参考一下,下面是跟着AI的指导操作的步骤。

编辑PVE的配置文件,设置在PVE主机中屏蔽需要直通硬件的驱动:

  1. 编辑 /etc/default/grub,在 GRUB_CMDLINE_LINUX_DEFAULT 中添加 intel_iommu=on(Intel)或 amd_iommu=on(AMD)以及 iommu=pt
  2. 如果你发现扩展卡和别的设备被捆绑在一个组里,可以添加 pcie_acs_override=downstream,multifunction
  3. 我这个AMD平台的示例:
    GRUB_CMDLINE_LINUX_DEFAULT="quiet amd_iommu=on iommu=pt pcie_acs_override=downstream,multifunction
  4. 编辑 /etc/modules,确保里面包含:
    vfio
    vfio_iommu_type1
    vfio_pci
    vfio_virqfd
  5. 执行proxmox-boot-tool refresh,然后重启PVE。
  6. 重启后执行以下命令检查 IOMMU 分组
    for d in /sys/kernel/iommu_groups/*/devices/*; do n=${d#*/iommu_groups/*}; n=${n%%/*}; printf '%s\t%s\n' "$n" "$(lspci -nns ${d##*/})"; done | sort -n

    如果此时你要直通的设备还是和其他设备共用一个组号,那么要考虑更换直通CPU的插槽,换插槽也没用就是没有办法了。

  7. 在刚才命令执行后你还会获得一个PCI地址,就在你设备所在行开头的那一串,类似:26:00.0,然后就要执行lspci -n -s 26:00.0查看你的设备ID(这里面的PCI地址替换成你自己设备的那个),我的设备id返回的是这个样式的:197b:0585
  8. 接下来要用vfio驱动接管你的设备,防止设备被ahci驱动接管。
    1. /etc/modprobe.d/vfio.conf中添加以下内容:options vfio-pci ids=你的设备ID
    2. /etc/modprobe.d/pve-blacklist.conf中添加:softdep ahci pre: vfio-pci
    3. 执行update-initramfs -u -k all
    4. 重启 PVE,重启后执行lspci -k -s 你的设备PCI地址,检查是否有Kernel driver in use: vfio-pci,有的话就是驱动成功被接管了。
  9. 然后就可以在虚拟机设置里添加PCIE设备了,编辑PCIE设备时选择“原始设备”,在里面选择你的SATA控制器,“所有功能”的选项不用勾,然后勾上”Rom-Bar”和”PCI-Express”保存即可。

完成以上步骤直通SATA控制器就设置好了,然后可以打开虚拟机。

[PostgreSQL]pgroonga扩展关闭强制前缀搜索

被这个破问题困扰了好几天,明明我没有在搜索语法中使用前缀搜索,但搜索结果就硬是会出现只有前缀匹配的结果,自己的测试结果和groonga文档里的结果都不一样,搞得自己怀疑是不是我的pgsql服务坏了。

挣扎了三天才发现pgroonga扩展打开了强制前缀搜索,要修改这个参数:

SET pgroonga.match_escalation_threshold = -1;

这个参数默认值为0,表示“如果主关键字的命中数为 0,则升级为松散前缀搜索”,设置为-1之后即可关闭这个功能。

 

 

PostgreSQL Docker从17版本升级到18版本

PostgreSQL Docker从17升级到18版本,有一些大的变动,因此升级过程需要进行一些额外的操作。

首先,17版本默认是不开启数据检验和的,因此需要先把17版本的数据库启用校验和,才能正确迁移升级到18版本。

  1. 开启校验和的第一步,一定要把数据库服务停止
  2. 然后执行:
    • docker run --rm -it \
        --mount "type=bind,src=17版本数据库目录路径,dst=/var/lib/postgresql/data" \
        postgres:17 \
        pg_checksums --enable --verbose --progress -D /var/lib/postgresql/data

      如果出现类似这样的错误:`pg_checksums: error: invalid segment number 0 in file name “/var/lib/postgresql/data/base/24699/pgrn.conf”`,可能是插件生成的文件此工具无法识别,暂时把它们移走,等执行完以上命令之后再移动回来。

  3. 完成后应该看到类似这样的结果提示:
    • Checksum operation completed
      Files scanned:   1991
      Blocks scanned:  7209262
      Files written:  1674
      Blocks written: 7209185
      pg_checksums: syncing data directory
      pg_checksums: updating control file
      Data checksum version: 1
      Checksums enabled in cluster

现在数据库的校验和就开启完成了,然后就是改变数据库的目录结构,17版本以及之前的数据目录在`/var/lib/postgresql/data`中,但18版本开始,数据目录需要在以大版本号命名的子目录中,使用以下流程进行迁移:

  1. 保持数据库服务处于停止的状态
  2. 首先你需要把原来的17版本数据库目录中的内容从`pgsql数据目录/data`中移动到`pgsql数据目录/17/docker`中(部分17版本已经把数据移动到了此目录中,视情况操作),也就是数据不再存放在”data”目录中,而是放在”版本号/docker”目录中。之后18版本的数据库将被生成在`pgsql数据目录/18/docker`目录中。
  3. 创建一个新的18版本空数据库,可以使用你自己的dockerfile或者compose文件创建,把17版本对应的配置文件复制一份改一下版本就行,以下是注意点:
    1. 注意使用”tianon/postgres-upgrade”工具需要确保新老版本的超级用户名一致,否则会报”FATAL: role “xxx” does not exist”的错误,而且我测试出来它没法通过环境变量指定新创建数据库的用户名和密码,只能使用”PGUSER”环境变量指定执行迁移时使用的用户,因此才有这一节独立创建空库的步骤,如果你原本数据库的超级用户是”postgres”,则可以尝试直接跳到下一个大步骤。
    2. 新建一个临时目录”pgsql数据目录/tmp”映射到将启动容器的”/var/lib/postgresql/”,创建完成后再把生成的”18″目录移动出来。(如果不使用临时目录,在创建新的容器时检测到数据目录中有其它包含数据库文件的目录它会拒绝启动)
    3. 创建空数据库示例:
        1. 确保你没有数据库容器在运行。
        2. 启动一个18版本的临时容器来创建数据库文件
          docker run --name temp-pg-18 \
            --mount "type=bind,src=pgsql数据目录/tmp,dst=/var/lib/postgresql/" \
            -e POSTGRES_USER=超级用户名 \
            -e POSTGRES_PASSWORD=超级用户密码 \
            -e POSTGRES_DB=postgres \
            -d postgres:18

          容器成功运行后等十几秒钟,检查”pgsql数据目录/tmp”目录中是否已经生成了数据库文件,有了的话执行命令清除临时容器:

          docker stop temp-pg-18 && docker rm temp-pg-18
        3. 最后把生成的目录从临时目录移出来(如果你刚刚已经手动创建了18目录,就把它先删除,再执行):
          mv tmp/18 ./18
          rm -r tmp
  4. 开始迁移,注意,如果你的17版本数据库使用了额外的扩展库,那么在迁移时你需要确保当前迁移环境中也有对应的扩展库,否则会提示”Your installation references loadable libraries that are missing from the new installation. You can add these libraries to the new installation, or remove the functions using them from the old installation.”。运行以下工具容器进行迁移:
    • docker run --rm \
        --mount "type=bind,src=pgsql数据目录,dst=/var/lib/postgresql" \
        --env "PGDATAOLD=/var/lib/postgresql/17/docker" \
        --env "PGDATANEW=/var/lib/postgresql/18/docker" \
        --env "PGUSER=超级用户名" \
        tianon/postgres-upgrade:17-to-18 \
          /bin/bash -c "apt update && \ #在这里执行你的依赖安装命令,如果不需要安装依赖的话可以把这个apt命令也去掉,仅占位示例用
          /usr/local/bin/docker-upgrade --link"

      不算安装依赖的时间,迁移过程应该很快会完成,并且给出如下提示:

      Upgrade Complete
      ----------------
      Some statistics are not transferred by pg_upgrade.
      Once you start the new server, consider running these two commands:
          /usr/lib/postgresql/18/bin/vacuumdb --all --analyze-in-stages --missing-stats-only
          /usr/lib/postgresql/18/bin/vacuumdb --all --analyze-only
      Running this script will delete the old cluster's data files:
          ./delete_old_cluster.sh
  5. 迁移完成之后使用18版本的数据库容器检查迁移状态:
    • 启动18版本服务之前:
      •  检查校验和(可选,如果是前面17版本刚刚生成的校验和则不用执行):
        docker run --rm -it \
          --mount "type=bind,src=pgsql数据目录,dst=/var/lib/postgresql/data" \
          postgres:18 \
          pg_checksums --check -D /var/lib/postgresql/data/18/docker
    • 启动18版本的服务
      • 重建统计信息,进入容器执行: vacuumdb -U 超级用户名 --all --analyze-in-stages

 

这样基本上就完成了迁移工作。

 

bgm-controller 背景音乐控制器

为了在订婚宴上更好地控制背景音乐,用AI连夜写了个背景音乐控制器,然后这两天又好好鞭笞AI修掉了大部分bug,现在分享出来;

项目地址:https://github.com/JiaJiaJiang/bgm-controller

在现示例:https://pages.luojia.me/bgm-controller/dist/ (示例中的音乐可不是我订婚宴用的音乐哦,是正好在我静态文件存储里有的音乐)

安卓应用脱壳逆向笔记

写一篇小笔记,记录一下第一次脱壳并逆向修改安卓应用的过程,为了避免麻烦,这里不会有被我逆向应用的信息。

为了把一个应用使用的一个库换成opencv,我这个连java都不会写的人硬着头皮消耗了一个月,甚至整个春节假期在做这个事情。

首先需要的一个工具是apktool [https://github.com/iBotPeaches/Apktool],它的作用是把应用安装包apk文件中的各个部分提取出来,变成一个apktool项目目录,且修改完之后我们也需要用它打包回apk。

以下是用ai生成的一个apktool反编译后目录的结构,另外此次逆向修改过程中也大量使用了ai,不然以我这第一次认识这些东西的知识储备,还得更久才能弄好。

your_app/
├── AndroidManifest.xml        # 应用主配置文件,声明组件、权限、包名等
├── apktool.yml                # APKTool 元数据,记录 SDK 版本、资源 ID 偏移、压缩规则等,用于重打包
├── smali/                     # 主 DEX 反编译出的 Dalvik 字节码(.smali),对应 Java/Kotlin 逻辑,如果没有选择反编译资源的话,则不会有这个目录,取而代之的是classes.dex文件
│   └── com/example/...        # 按原始包名组织的类文件
├── smali_classes2/            # (若存在)第二个 DEX 文件的 smali 代码,用于 multidex 应用
├── smali_classes3/            # (若存在)第三个 DEX,依此类推
├── res/                       # 编译后的资源目录,已还原为可读 XML
│   ├── layout/                # 界面布局文件(.xml)
│   ├── values/                # 字符串(strings.xml)、颜色(colors.xml)、样式(styles.xml)等
│   └── drawable-*/            # 图片资源,按屏幕密度分类(如 drawable-xhdpi)
├── assets/                    # (可选)原始资产目录,内容不被编译,通过 AssetManager 访问(如 .json、.db)
├── lib/                       # (可选)原生库目录,包含 .so 文件
│   ├── arm64-v8a/             # 64 位 ARM 架构原生库
│   └── armeabi-v7a/           # 32 位 ARM 架构原生库(其他架构如 x86 等也可能存在)
├── original/                  # (可选)原始 APK 中的 META-INF 目录,含签名文件(重打包时通常会被替换)
└── [resources.arsc]           # (注:此文件在反编译后不直接出现,其内容已解析并融入 res/ 目录中)

要修改应用的逻辑,则需要修改各个smali目录下的smali文件,这些是反编译出来的字节码文件。 继续阅读安卓应用脱壳逆向笔记

frida调试安卓应用时重载loadLibrary导致无法从应用库目录载入库文件

我让ai给我写了段调试安卓应用的frida脚本,里面有一段类似这样的代码:

var SystemClass = Java.use("java.lang.System");
SystemClass.loadLibrary.overload("java.lang.String").implementation = function (libname) {
	console.log("[库加载] System.loadLibrary被调用: " + libname);

	// 所有库都调用原始方法,不做任何拦截
	this.loadLibrary(libname);
};

然后调试应用的时候就发现应用总是只从”/system/lib/”和” /vendor/lib/”中寻找库文件了,结果当时是加载失败,踩了个坑,记录一下。

似乎一旦重载了这种方法,就会影响应用加载库的搜索路径,目前没找到什么好办法解决。

[nodejs]require位于SMB的js文件非常缓慢的问题

一个小工具里需要require位于SMB存储设备的js文件作为配置文件,以前配置文件位于本地硬盘的时候是没有任何问题的,但自从把这个配置移动到SMB共享的服务器上了之后,程序执行就相当慢。

开了个调试发现是require执行了40多秒,但我如果直接用fs.readFileSync去读取那个配置文件的话是瞬间返回的,于是就奇怪了,并不是文件io慢,那怎么卡成这样。

接着开始在程序卡住的时候点开发者工具里的暂停,看程序卡完以后会停在什么地方,于是发现了在停下的位置之前是以下内容:

if (StringPrototypeEndsWith(filename, '.cjs')) {
  format = 'commonjs';
} else if (StringPrototypeEndsWith(filename, '.mjs')) {
  format = 'module';
} else if (StringPrototypeEndsWith(filename, '.js')) {
  pkg = packageJsonReader.getNearestParentPackageJSON(filename);
  const typeFromPjson = pkg?.data.type;
  if (typeFromPjson === 'module' || typeFromPjson === 'commonjs' || !typeFromPjson) {
    format = typeFromPjson;
  }
}

我的配置文件是.js后缀的,从代码上来看,这段代码只有可能卡在`packageJsonReader.getNearestParentPackageJSON`上,那么就首先来排除是不是这里出了问题,只要绕开这个分支,比如换成`.cjs`后缀试一试。

果然换成`.cjs`后缀以后,`require`瞬间就返回了。

那么造成卡顿的原因终于找到了,是require一个js文件时,他需要去一层层往上找到一个`package.json`文件,目的是看看这个js文件到底是模块(module)类型的还是cjs(commonjs)类型的,目前由于另外一个未知原因导致这个过程非常慢,但我也没时间去细究咋回事了。

如果要解决这个问题,又不想改原来的文件后缀,那么就只要在该文件的目录里再新建一个`package.json`文件,并在里面写上需要的`type`就行了,比如:

{
  "type": "commonjs"
}

 

[HomeAssistant]初始化时修改网关

如果网络中的默认网关没有走梯子的话,HomeAssistant的安装初始化过程在中国是几乎无法完成的,此时需要通过命令行设置旁路代理网关。

首先使用net info查看网卡名称,找到你的网卡项目,其中interface那条就是网卡名称,然后使用以下命令修改设置即可(由于此设置只能在静态地址模式下使用,所以需要把其它项目也一起设置掉)。

net update 网卡名称 --ipv4-address ipv4地址/掩码 --ipv4-gateway ipv4网关 --ipv4-nameserver 223.5.5.5 --ipv4-method static --ipv6-method disabled

此命令中顺便把ipv6暂时禁用了一下,避免一些请求解析为ipv6地址后依然从ipv6路由走掉而不走代理,如果有需要可以在初始化完成之后再启用。

如果网络没有变化,可以用以下命令重启一下网络。

net reload