博客迁移:从 Jekyll Chirpy 到 Hugo PaperMod

背景 这个博客从 2013 年底开始,使用 Jekyll 托管在 GitHub Pages 上,经历了几次主题变更。最近一次使用的是 jekyll-theme-chirpy 7.1.1。 积累了 55 篇文章后,决定重新审视技术栈选型,期望找到一个简单、优雅、有技术范的方案。 2026 年静态博客生态 在选型之前,整理了当前主流的静态站点生成器(SSG)对比: SSG 语言 构建速度 特点 Jekyll Ruby 中等 GitHub Pages 原生支持,生态最成熟 Hugo Go 极快 (<1ms/页) 单二进制,零依赖 Astro JS/TS 快 默认零 JS 输出,Island Architecture 11ty JS 快 灵活,多模板引擎 Zola Rust 极快 单二进制,内置 Sass/语法高亮 为什么选 Hugo + PaperMod 经过评估,最终选择 Hugo + PaperMod: 构建速度 — 54 篇文章构建时间 <100ms,Jekyll 需要数秒 PaperMod 主题 — 极简优雅,专注内容,暗色模式完美 零 Ruby 依赖 — hugo 单个二进制文件,装完即用 多语言原生支持 — Hugo 内置 i18n 机制,对中英双语友好 活跃维护 — PaperMod 社区活跃,2026 年仍在持续更新 迁移过程 1. 项目结构变化 1 2 3 4 5 6 7 8 # Jekyll 结构 # Hugo 结构 ├── _config.yml ├── hugo.yaml ├── _posts/ ├── content/posts/ ├── _data/ ├── static/ ├── _plugins/ ├── themes/PaperMod/ ├── _tabs/ ├── archetypes/ ├── Gemfile └── layouts/ └── assets/ 2. Front Matter 标准化 Jekyll 时期 front matter 格式不统一,迁移时统一为: ...

2026年6月26日 · 2 分钟 · haoxiqiang

Build AOSP for Pixel 3 XL

AOSP 的构建流程已经比较清晰,大致分为:同步代码、添加对应设备的驱动和内核、构建目标镜像。之前尝试构建 AOSP 来排查一些问题,但 Pixel 3 XL 的官方适配只到 Android 12。最近在 Chromium 开发中需要测试 WebView,因此使用 LineageOS 21 的适配来方便构建。 前提条件 默认已安装 AOSP 构建环境: AOSP 环境准备 Codenames, Tags, and Build Numbers 同步 AOSP 源码 1 2 3 4 mkdir ~/aosp cd ~/aosp repo init --partial-clone -b android-12.0.0_r34 -u https://android.googlesource.com/platform/manifest repo sync -c -j8 获取驱动 在 Codenames, Tags, and Build Numbers 页面搜索 Pixel 3 XL(代号 crosshatch),获取最新 Build ID,示例:SP1A.210812.016.C2 在 Google Drivers 页面下载对应 Build ID 的驱动 1 2 3 4 5 6 7 8 9 mkdir vendor_backup && cd vendor_backup wget https://dl.google.com/dl/android/aosp/google_devices-crosshatch-sp1a.210812.016.c2-a4e274b7.tgz wget https://dl.google.com/dl/android/aosp/qcom-crosshatch-sp1a.210812.016.c2-00a7f1f3.tgz tar xvf qcom-crosshatch-*.tgz tar xvf google_devices-crosshatch-*.tgz ./extract-google_devices-crosshatch.sh ./extract-qcom-crosshatch.sh mv vendor/ ../ 构建并刷机 参考 Building AOSP 文档: ...

2022年8月15日 · 1 分钟 · haoxiqiang

OpenSSL RSA 加密的一个认知盲区

原本对 RSA 不同加密方式的区别和使用场景还是很清楚的。今天在实现一个 RSA 加密功能时,发现公钥加密结果每次都不一样,这个现象触发了我的盲区。查阅相关资料后,记录一下背后的原理。 问题现象 以下代码使用 OpenSSL 的 EVP API 进行 RSA 加密,没有显式传入随机值,但每次运行结果都不相同: 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 unsigned char *encode_by_rsa(const char *public_key, unsigned const char *input) { int key_len = (int) strlen(public_key); BIO *bio = BIO_new_mem_buf(public_key, key_len); EVP_PKEY *pKey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL); BIO_free_all(bio); EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pKey, NULL); EVP_PKEY_encrypt_init(ctx); EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING); EVP_PKEY_CTX_set_rsa_oaep_md(ctx, EVP_sha256()); EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, EVP_sha256()); size_t rsa_len = (int) RSA_LENGTH; unsigned char *encrypted_data = malloc(rsa_len + 1); memset(encrypted_data, 0, rsa_len + 1); if (encrypted_data == NULL) { return NULL; } int input_len = (int) strlen((const char *) input); EVP_PKEY_encrypt(ctx, encrypted_data, &rsa_len, input, input_len); encrypted_data[rsa_len] = '\0'; EVP_PKEY_CTX_free(ctx); EVP_PKEY_free(pKey); return encrypted_data; } 原因:随机填充 不管是 RSA 私钥签名还是公钥加密,操作中都需要对待处理数据先进行填充,再对填充后的数据进行加密运算。 填充过程中引入了伪随机数,所以即使相同的输入和密钥,每次输出都不同。 ...

2022年8月8日 · 2 分钟 · haoxiqiang

Jenkins 配置记录

最近在处理一个海外应用,打包机原本在上海。由于一些特殊原因需要迁移到海外,顺便记录 Jenkins 的迁移和配置过程。 前置条件:安装 JDK 11 这里使用了 jenv 工具管理多版本 Java: 1 2 3 4 5 6 7 8 9 # 安装 jenv git clone https://github.com/jenv/jenv.git ~/.jenv echo 'export PATH="$HOME/.jenv/bin:$PATH"' >> ~/.bashrc echo 'eval "$(jenv init -)"' >> ~/.bashrc source ~/.bashrc # 重启会话后启用 jenv export 插件 jenv enable-plugin export exec $SHELL -l CentOS 上安装 Jenkins 1 2 3 4 5 6 # 添加 Jenkins 仓库和 GPG 密钥 sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key # 安装 yum install jenkins 配置 systemd 服务 1 2 3 4 sudo vi /etc/systemd/system/jenkins.service sudo systemctl enable /etc/systemd/system/jenkins.service sudo systemctl start jenkins systemctl status jenkins 1 2 3 4 5 6 7 8 9 10 11 12 13 [Unit] Description=jenkins service After=network.target [Service] Type=simple LimitNOFILE=65536 ExecStart=/usr/bin/jenkins User=work Environment="JENKINS_PORT=8082" [Install] WantedBy=multi-user.target 配置 Android SDK 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 wget https://dl.google.com/android/repository/commandlinetools-linux-8512546_latest.zip unzip commandlinetools-linux-8512546_latest.zip chmod a+x cmdline-tools/ cd cmdline-tools/bin # 使用 sdkmanager 安装所需组件 ./sdkmanager --sdk_root=/home/work/workspace/android_sdk --list ./sdkmanager --install 'platforms;android-33' --sdk_root=/home/work/workspace/android_sdk ./sdkmanager --install 'build-tools;33.0.0' --sdk_root=/home/work/workspace/android_sdk ./sdkmanager --install 'cmdline-tools;7.0' --sdk_root=/home/work/workspace/android_sdk ./sdkmanager --install 'build-tools;32.0.0' --sdk_root=/home/work/workspace/android_sdk ./sdkmanager --install 'build-tools;31.0.0' --sdk_root=/home/work/workspace/android_sdk ./sdkmanager --install 'ndk;25.1.8937393' --sdk_root=/home/work/workspace/android_sdk ./sdkmanager --install 'platforms;android-28' --sdk_root=/home/work/workspace/android_sdk ./sdkmanager --install 'platforms;android-29' --sdk_root=/home/work/workspace/android_sdk ./sdkmanager --install 'platforms;android-30' --sdk_root=/home/work/workspace/android_sdk ./sdkmanager --install 'platforms;android-31' --sdk_root=/home/work/workspace/android_sdk ./sdkmanager --install 'cmake;3.22.1' --sdk_root=/home/work/workspace/android_sdk ./sdkmanager --install 'cmake;3.10.2.4988404' --sdk_root=/home/work/workspace/android_sdk 解决 SSH 密钥权限问题 如果遇到 Permissions 0664 for '/home/work/.ssh/jenkins_id_rsa' are too open 错误: ...

2022年6月6日 · 2 分钟 · haoxiqiang

ShadowSocks Rust 的配置与优化

五一期间重新整理了家里的网络,目标是看 4K 流媒体不卡顿。既然服务器要重新配置,干脆将旧方案迁移到新的 shadowsocks-rust 上。 系统更新 1 sudo apt update && sudo apt upgrade 安装并配置 SS 方案一:通过 Cargo 编译安装 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # 安装 Rust 工具链 curl https://sh.rustup.rs -sSf | sh # 配置 Cargo 环境变量(写入 .profile / .bash_profile 等) # CARGO_HOME 指定 Cargo 安装路径 # target-cpu=native 让 rustc 为目标 CPU 生成优化代码 CARGO_HOME=/root/cargo RUSTFLAGS="-C target-cpu=native" source .profile # 安装编译依赖 sudo apt install build-essential # 安装 shadowsocks-rust cargo install shadowsocks-rust 方案二:直接下载预编译二进制 1 2 3 4 wget https://github.com/shadowsocks/shadowsocks-rust/releases/download/v1.14.3/shadowsocks-v1.14.3.x86_64-unknown-linux-gnu.tar.xz tar -xf shadowsocks-v1.14.3.x86_64-unknown-linux-gnu.tar.xz cp ssserver /usr/local/bin chmod a+x /usr/local/bin/ssserver 配置文件 1 2 mkdir /etc/shadowsocks vi /etc/shadowsocks/config.json 1 2 3 4 5 6 { "server": "::", "server_port": 8888, "method": "aes-256-gcm", "password": "pw" } 测试运行: ...

2022年5月6日 · 2 分钟 · haoxiqiang

利用自建 Nexus 仓库优化 Android 构建

在 Android 项目的依赖管理中,通常需要配置多个远程仓库,如 jcenter、jitpack、google() 等。一些大型项目(如"最右")甚至依赖超过 10 个仓库。当首次初始化项目、依赖发生变化或网络出现问题时,构建过程的排査会变得相当困难。 很早之前就发现了这个问题,但一直因为懒没有处理。本文记录 Nexus 的搭建与配置过程。 安装并配置 Nexus 前置条件:JDK 8+。 1 2 3 4 5 6 7 8 9 10 11 12 # 下载并解压 Nexus mkdir /app && cd /app wget -O nexus.tar.gz https://download.sonatype.com/nexus/3/latest-unix.tar.gz tar -xvf nexus.tar.gz mv nexus-3* nexus # 创建专用用户 adduser nexus # 修改目录权限 chown -R nexus:nexus /app/nexus chown -R nexus:nexus /app/sonatype-work 配置运行用户: 1 2 3 vi /app/nexus/bin/nexus.rc # 添加以下内容 run_as_user="nexus" 如需修改存储路径等,编辑 JVM 参数: ...

2019年12月31日 · 1 分钟 · haoxiqiang

Shadowsocks 的配置与优化

最近办公室网络波动影响工作,在 VPS 上重新搭建了一套 Shadowsocks 用来拉取源码。以下步骤适用于大多数 Linux 发行版,已在 Ubuntu 16.04 和 18.04 上测试通过。 2024 更新说明: 本文使用的 Python 版 shadowsocks 已停止维护。推荐使用 shadowsocks-rust,它是当前官方活跃维护的实现,性能更好且支持现代加密协议。如从零开始搭建,建议直接参考 shadowsocks-rust 官方文档。下文仍保留 Python 版步骤供参考。 基础环境准备 1 apt update && apt upgrade -y 安装并配置 Shadowsocks (Python 版) 安装 1 2 apt install python3-pip -y pip3 install https://github.com/shadowsocks/shadowsocks/archive/master.zip 配置文件 1 2 mkdir /etc/shadowsocks vi /etc/shadowsocks/config.json 1 2 3 4 5 6 7 8 9 10 { "server":"::", "server_port":8888, "local_address": "127.0.0.1", "local_port":1080, "password":"your-password", "timeout":300, "method":"aes-256-cfb", "fast_open": true } 防火墙配置 1 2 3 4 5 iptables -I INPUT -p tcp --dport 8888 -j ACCEPT iptables -I INPUT -p udp --dport 8888 -j ACCEPT # 如果使用 UFW,则执行: ufw allow 8888 测试运行 1 ssserver -c /etc/shadowsocks/config.json 启用 BBR 加速 BBR (Bottleneck Bandwidth and Round-trip propagation time) 是 Google 开发的 TCP 拥塞控制算法,能显著提升网络吞吐量。 ...

2019年12月31日 · 3 分钟 · haoxiqiang

问题整理三

三个小问题的记录:DialogFragment 返回键处理、chmod 权限速查、SSL 域名中的下划线问题。 DialogFragment 返回键处理 DialogFragment 没有直接复写返回键的方法,有两种方式可以实现。 方式一:在 onCreateDialog 中复写 1 2 3 4 5 6 7 8 9 @Override public Dialog onCreateDialog(Bundle savedInstanceState) { return new Dialog(getActivity(), getTheme()){ @Override public void onBackPressed() { // 在这里处理返回键逻辑 } }; } 方式二:通过 onKeyListener 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Override public void onResume() { super.onResume(); Dialog dialog = getDialog(); if (dialog != null) { dialog.setOnKeyListener(this); } } @Override public void onPause() { super.onPause(); Dialog dialog = getDialog(); if (dialog != null) { dialog.setOnKeyListener(null); } } 现代方案:OnBackPressedDispatcher(AndroidX) 如果需要更高版本支持,推荐使用 AndroidX 的 OnBackPressedDispatcher。从 Fragment 1.6.1 开始,DialogFragment 默认返回 ComponentDialog,它自带独立的 OnBackPressedDispatcher,可以更优雅地处理返回键: ...

2017年4月5日 · 2 分钟 · haoxiqiang

Android 的 MediaStore

最近在写一个类似微信的相册功能,需要读取照片和视频,支持多文件夹切换,且速度要比微信快。调研后发现基于 MediaStore 的方案最为合适。以前用得不多,特此记录。 ContentResolver 对 GROUP BY 的特殊处理 ContentResolver.query() 没有提供 groupBy 参数(与 SQLiteQueryBuilder.query() 不同),但可以通过在 selection 参数中嵌入 GROUP BY 来实现类似效果。 原理是 ContentResolver 会在编译 SQL 时给 selection 自动加上括号包裹,形成 WHERE ( ... )。利用这一点,可以在 selection 中提前闭合括号,然后追加 GROUP BY 子句。 1 2 3 4 5 6 // 常规写法 — selection 会被包装成 WHERE (mime_type IS NOT NULL) MediaStore.Images.ImageColumns.MIME_TYPE + " IS NOT NULL " // Hack 写法 — 利用闭合括号注入 GROUP BY MediaStore.Images.ImageColumns.MIME_TYPE + " IS NOT NULL " + ") GROUP BY (" + MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME; 生成的 SQL 变为: ...

2017年3月31日 · 2 分钟 · haoxiqiang

HTTPS 相关记录

使用 HTTPS 建议先阅读 Android 官方 Training: Security with SSL。很多公司已经全站 HTTPS,但有些用法并不正确。这里简单记录一下自己遇到的问题。 ...

2016年1月20日 · 3 分钟 · haoxiqiang