Latest Learning

Stuff I learned recently.

配置方法

在所有的 git 全局配置中,配置:

# Configure Git to ensure line endings in files you checkout are correct for macOS
git config --global core.autocrlf input

或者在对应的 ~/.gitconfig 中配置:

[core]
	autocrlf = input

但是 github 推荐在 windows 中将这个选项设置为 true,我查了下觉得在三端均采用 input 是非常合理的。

配置说明

配置值含义
true双向转换:
 • 输入(commit)时:CRLF → LF
 • 输出(checkout)时:LF → CRLF
input仅输入时转换:
 • 输入(commit)时:CRLF → LF
 • 输出(checkout)时:不做转换(保留 LF)
false完全不转换

参考链接:https://docs.github.com/en/get-started/git-basics/configuring-git-to-handle-line-endings?platform=linux

CR

“CR” 在电池型号中是标准命名:

  1. C = 锂 - 二氧化锰(Lithium-Manganese Dioxide)
  2. R 代表形状:圆形

后面的数字代表尺寸:

  1. 前两位(20):直径,单位是毫米(mm)→ 直径 20mm
  2. 后两位(32):厚度,单位是 0.1mm → 3.2mm 厚

结论

  1. CR2032 = 锂锰电池,圆形,20mm 直径,3.2mm 厚
  2. CR2025 = 锂锰电池,圆形,20mm 直径,2.5mm 厚

Unleash 是一个企业级的 Feature Toggle 管理平台,核心价值是解耦部署与发布

解决的问题

  1. 部署与发布分离:代码可以部署到生产环境但默认关闭,通过控制台按需开启
  2. 主干开发:避免长期分支的合并冲突,半成品代码可安全合并到主分支
  3. 灰度发布:支持渐进式流量释放(1% -> 10% -> 100%),降低发布风险
  4. A/B 测试:基于用户 ID 的分组分流,为产品决策提供数据支持
  5. 零停机修复:发现问题可秒级关闭开关,无需代码回滚

架构特点

Unleash 采用本地求值架构:

  • Server 只推送规则策略,不接触用户数据
  • SDK 在应用本地内存中判断开关状态
  • 即使 Server 宕机,应用仍可正常工作(缓存策略)
  • 用户隐私数据(GDPR)不离开服务器

核心策略

  • Standard:全量或关闭
  • Gradual Rollout:渐进式释放百分比
  • UserID:针对特定用户白名单
  • Flexible Rollout:A/B 分组测试

今天学到了可以在对应的 pr 中添加 .diff 直接查看 diff, 然后可以将链接直接丢给在线的 ai 大模型给你 review

比如 https://github.com/zhaochunqi/git-open/pull/24 可以使用 https://github.com/zhaochunqi/git-open/pull/24.diff, 然后提交给大模型说:review https://github.com/zhaochunqi/git-open/pull/24.diff 即可,我看了下纯文本,感觉复制粘贴都行。

参考链接:Get an AI code review in 10 seconds

traefik 中生成的证书如果已经不再使用 (表现其实就是过期了) 的证书,经查询发现有人写了相关的脚本来处理,稍作修改直接使用 uv 来管理避免需要手动安装依赖。

更简单的使用方法 (heredoc):

uv run - <<EOF                                                          
# /// script
# requires-python = ">=3.12"
# dependencies = [
#     "cryptography",
# ]
# ///
import json
import sys
import base64
from datetime import datetime, timedelta, timezone
from cryptography import x509
import os

DEBUG = os.environ.get("DEBUG", False)


def cleanup_certs(acme_file="acme.json"):
    """
    Deletes all expired certificates from acme.json.
    """
    try:
        with open(acme_file, "r") as f:
            data = json.load(f)
    except FileNotFoundError:
        print(f"Error: {acme_file} not found.")
        return

    now = datetime.now()
    if DEBUG:
        print(f"DEBUG: current time: {now}")

    cleaned_count = 0

    def process_certificates(certificates, version):
        nonlocal cleaned_count
        if not certificates:
            return [], 0

        original_cert_count = len(certificates)
        certs_to_keep = []
        removed_certs = []
        for cert in certificates:
            cert_b64 = ""
            domain = "N/A"
            try:
                if version == 1:
                    cert_b64 = cert.get("Certificate", "")
                    domain = cert.get("Domain", {}).get("Main", "N/A")
                elif version in [2, 3]:
                    cert_b64 = cert.get("certificate", "")
                    domain = cert.get("domain", {}).get("main", "N/A")

                if cert_b64:
                    cert_b64_decoded = cert_b64.replace("-", "+").replace("_", "/")
                    padding_needed = len(cert_b64_decoded) % 4
                    if padding_needed != 0:
                        cert_b64_decoded += "=" * (4 - padding_needed)

                    cert_pem_bytes = base64.b64decode(cert_b64_decoded)
                    x509_cert = x509.load_pem_x509_certificate(cert_pem_bytes)
                    not_after_date = x509_cert.not_valid_after_utc

                    if DEBUG:
                        print(
                            f"DEBUG: Processing domain {domain}, not_valid_after: {not_after_date}"
                        )

                    if not_after_date > datetime.now(timezone.utc):
                        certs_to_keep.append(cert)
                    else:
                        removed_certs.append(domain)
                else:
                    certs_to_keep.append(cert)
            except Exception as e:
                print(f"Could not process certificate for domain {domain}: {e}")
                certs_to_keep.append(cert)

        removed_count = original_cert_count - len(certs_to_keep)

        # We only want to add to the cleaned_count if we are in a v2/v3 file,
        # because the v1 file has only one list of certs and we will get the
        # count from the return value of this function.
        if version in [2, 3]:
            cleaned_count += removed_count

        for domain_name in removed_certs:
            print(f"Removed certificate for: {domain_name}")

        return certs_to_keep, removed_count

    # Detect acme.json version
    version = None
    if (
        isinstance(data, dict)
        and "Certificates" in data
        and isinstance(data.get("Certificates"), list)
    ):
        version = 1
    elif isinstance(data, dict):
        for key, value in data.items():
            if isinstance(value, dict) and "Certificates" in value:
                version = 2  # Treat as v2/v3 generic
                break

    if DEBUG:
        print(f"DEBUG: Detected version: {version}")

    if version == 1:
        certs_to_keep, removed_count = process_certificates(
            data["Certificates"], version
        )
        if removed_count > 0:
            data["Certificates"] = certs_to_keep
            cleaned_count = removed_count
    elif version == 2:
        for resolver, resolver_data in data.items():
            if (
                isinstance(resolver_data, dict)
                and "Certificates" in resolver_data
                and resolver_data["Certificates"] is not None
            ):
                if DEBUG:
                    print(f"DEBUG: Processing resolver: '{resolver}'")
                certs_to_keep, _ = process_certificates(
                    resolver_data["Certificates"], version
                )
                resolver_data["Certificates"] = certs_to_keep
            else:
                if DEBUG:
                    print(f"DEBUG: Resolver '{resolver}': No certificates to process.")

    else:
        print("Could not determine the structure of the acme.json file.")
        return

    if cleaned_count == 0:
        print("No expired certificates to remove.")
    else:
        try:
            with open(acme_file, "w") as f:
                json.dump(data, f, indent=4)
            print(
                f"Successfully cleaned up {cleaned_count} certificates in {acme_file}."
            )
        except Exception as e:
            print(f"Error writing to {acme_file}: {e}")


if __name__ == "__main__":
    help_message = """
Usage: python3 cleanup_certs.py [--help] [acme_file]

Deletes all expired certificates from an acme.json file.
The script attempts to auto-detect the acme.json file version (v1 or v2/v3).

Arguments:
  acme_file   Optional. Path to the acme.json file. Defaults to "acme.json".
  --help      Display this help message and exit.
"""

    if "--help" in sys.argv:
        print(help_message)
        sys.exit(0)

    if len(sys.argv) > 1 and sys.argv[1] != "--help":
        file_path = sys.argv[1]
    else:
        file_path = "acme.json"

    cleanup_certs(file_path)
EOF

当你打开网络下载的 App 遇到提示 “应用已损坏,打不开”“无法验证开发者” 时,通常是因为 macOS 的 Gatekeeper 安全机制拦截了该应用。

1. 解决方案 (知其然)

打开终端,执行以下命令即可修复:

# 语法:xattr -cr /Applications/你的应用名称.app
xattr -cr /Applications/jmcomic-downloader.app

提示:输入 xattr -cr 后加一个空格,然后直接把 App 从“应用程序”文件夹拖入终端,路径会自动生成。


2. 原理详解 (知其所以然)

macOS 使用 扩展文件属性 (Extended File Attributes) 来标记文件的来源和元数据。

  • 问题根源 (com.apple.quarantine): 当你使用浏览器(如 Chrome, Safari)下载文件时,macOS 会自动给文件加上一个名为 com.apple.quarantine (隔离) 的扩展属性。Gatekeeper 会检查这个属性,如果没有有效的开发者签名,系统就会拒绝运行并提示“已损坏”。
  • 命令拆解 (xattr)
  • xattr:管理文件扩展属性的工具。
  • -c (Clear):清除所有扩展属性(包括隔离标记)。
  • -r (Recursive):递归处理,应用到 .app 包内的所有文件。

3. 进阶:精准移除

如果你只想移除隔离属性,而不影响其他元数据(如 Finder 的颜色标签等),可以使用更精准的 -d (Delete) 参数:

# 仅移除 quarantine 属性,保留其他属性
xattr -rd com.apple.quarantine /Applications/jmcomic-downloader.app

TL,DR: 在环境中添加 export RBW_TTY=$(tty) 即可解决

在 macos 下使用 rbw 来获取密钥时,经常会遇到需要至少 10s 以上的情况,但是在 linux 下并没有此现象,经过一番研究发现是 macos 系统机制问题导致的:rbw 在系统中会调用 ttyname(), 这个机制在 linux 下和 macos 下不同,在 linux 下只需要读取 /proc/self/fd/0 即可,但是在 macos 下,需要遍历,这可能要调用 /dev 目录下数百个设备文件。rbw 提供了一个机制,可以通过设定 RBW_TTY 这个环境变量跳过此检测。

相关:direnv 配合 rbw 一起使用