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

 

[ESPHOME]自定义开发

esphome提供的框架偏向于让我们尽量在yaml配置文件中定义逻辑,它提供了很多基本的功能,这样可以减少自己需要写的代码,也可以减少出问题的概率。

如果框架提供的部分满足不了自己的全部需求的话,就需要自己写c++代码。另外再强调一下,esphome基于platformIO工具链,再底层可以选Arduino,也可以是esp-idf,在开发时要考虑兼容性的话(比如在esp8266和esp32上都可以运行),就尽量使用esphome和arduino的api。

esphome提供了几种方式混入外部的代码:

  1. 在配置文件中使用”includes”选项直接一同编译指定的文件。
  2. 在配置文件中使用”libraries”选项引入外部的库。
  3. 使用外部组件,外部组件支持直接指定仓库地址,它在编译时会去把仓库克隆下来。当指定的是本地路径时,也就是把本地的一个目录当成一个组件库来处理。

要自定义代码我认为最好的方式就是在项目目录里定义一个本地的外部组件,虽然使用外部组件需要写一个python初始化脚本(这是我极其讨厌的,还好有AI帮我写),但这种组件的形式可以更容易地嵌入和调用我们的代码(因为如果你不用这种形式,就要禁用main.cpp自动生成,然后自己去手写入口文件,否则你的代码就没有地方可以调用。esphome框架默认会生成一份它自己的入口和loop,你就不能再在全局定义你的了。)。

继续阅读[ESPHOME]自定义开发

[ESPHOME]本地部署开发环境

这是一篇笔记,记录我开始尝试使用esphome做一些小工具的过程,之后可能还会有一些相关的笔记。
如果你要问为什么我不用home assistant里的esphome builder插件的话,原因是我需要插入我自己的项目代码,如果使用esphome builder 我就必须先把代码上传到某处然后把部件的地址写在配置文件里才能下载并编译,会很麻烦,所以需要一个本地开发环境用于快速测试更改。

安装环境

使用esphome主要是为了接入home assistant,esphome依赖platformIO(以下都简称为pio)作为底层框架,因此需要先安装它。在windows平台,我们可以直接使用vscode安装platformIO插件,这个插件将会帮我们把pio安装好,在安装pio的时候需要确保网络爬墙,否则大概率会安装失败,在实际使用时我们并不需要直接去用这个插件,之后的步骤都将靠esphome的命令完成。

然后是安装esphome工具链,这其实是一个python包,按照官网的方式安装:https://esphome.io/guides/installing_esphome

pip3 install wheel
pip3 install esphome

然后使用`esphome version`命令检查能否正确显示版本号即可。

项目初始化

接下来进行一个基础的固件编译测试,首先确定要使用的esp8266或者esp32的模块,然后用以下命令来初始化一个配置文件:

esphome wizard 配置文件名字.yaml

执行后将会引导你选择板子以及进行一些设置,这里就不赘述了,完成后会生成一个配置文件。

我在写这篇笔记时使用的测试板是一个esp8266的开发板,选择的是’nodemcuv2’。

固件编译

有了配置文件之后就可以使用以下命令来进行编译,编译出来的固件会在当前目录下的”.esphome\build”文件夹里,这里面还有其它的缓存之类的东西,如果之后的编译出现了奇怪的错误,可以尝试删除这个文件夹。

esphome compile 配置文件名字.yaml

固件烧录

编译好固件之后就可以用以下命令上传你的固件,第一次上传需要使用串口连接,以后的固件就可以通过网络OTA的方式进行更新了(也是这个命令,执行时会让你选择)。

esphome upload 配置文件名字.yaml

固件写完之后重启开发板,它将会开始连接到wifi,此时同一局域网里的home assistant应该就会提示发现了新的设备,问是否要添加,添加时把配置里的api密钥填进去连接即可。

额外测试

接下来配置一个简单的控制开关用来点亮开发板上的led,在配置文件里加入以下顶层项目:

# LED控制配置
output:
- platform: gpio
pin: D4
id: led_gpio
inverted: true #led在引脚低电平时是亮,所以要反转一下

switch:
- platform: output
name: "开发板LED"
output: led_gpio

然后再编译并上传,设备联网后在home assistant的该设备页面上应该会自动多出来一个控制开关,现在就可以通过这个开关控制led的亮灭了。


继续阅读[ESPHOME]本地部署开发环境