{"version":"https://jsonfeed.org/version/1","title":"yupanzi","home_page_url":"https://yupanzi.com/","feed_url":"https://yupanzi.com/feed.json","description":"个人博客 / 技术笔记","icon":"https://yupanzi.com/og/welcome.png","author":{"name":"yupanzi","url":"https://yupanzi.com/"},"items":[{"id":"https://yupanzi.com/posts/codex-config/","content_html":"如果你使用支持 OpenAI API 的工具（如 Codex），需要设置以下配置来连接到中转服务。\n\n---\n\n## Codex 配置文件\n\n在 `~/.codex/config.toml` 文件开头添加以下配置：\n\n```toml\nmodel_provider = \"myai\"\nmodel = \"gpt-5-codex\"\nmodel_reasoning_effort = \"high\"\ndisable_response_storage = true\npreferred_auth_method = \"apikey\"\n\n[model_providers.myai]\nname = \"myai\"\nbase_url = \"你的服务地址/openai\"\nwire_api = \"responses\"\nrequires_openai_auth = true\nenv_key = \"MY_API_KEY\"\n```\n\n在 `~/.codex/auth.json` 文件中配置API密钥：\n\n```json\n{\n  \"OPENAI_API_KEY\": null\n}\n```\n\n💡 将 OPENAI_API_KEY 设置为 null，然后设置环境变量 MY_API_KEY 为你的 API 密钥（格式如 sk-xxxxxxxxxx）。\n\n---\n\n## Windows 教程\n\n### 环境变量设置方法\n\n**CMD 临时设置：**\n\n```cmd\nset MY_API_KEY=sk-xxxxxxxxxx\n```\n\n**PowerShell 临时设置：**\n\n```powershell\n$env:MY_API_KEY = \"sk-xxxxxxxxxx\"\n```\n\n**PowerShell 永久设置（用户级）：**\n\n```powershell\n[System.Environment]::SetEnvironmentVariable(\"MY_API_KEY\", \"sk-xxxxxxxxxx\", [System.EnvironmentVariableTarget]::User)\n```\n\n💡 设置后需要重新打开 PowerShell 窗口才能生效。\n\n### 验证环境变量\n\n在 PowerShell 中验证：\n\n```powershell\necho $env:MY_API_KEY\n```\n\n在 CMD 中验证：\n\n```cmd\necho %MY_API_KEY%\n```\n\n---\n\n## macOS 教程\n\n### 环境变量设置方法\n\n**临时设置：**\n\n```bash\nexport MY_API_KEY=sk-xxxxxxxxxx\n```\n\n**Shell 配置文件（持久保存）：**\n\n添加到你的 shell 配置文件中：\n\n```bash\n# 对于 zsh (默认)\necho \"export MY_API_KEY=sk-xxxxxxxxxx\" >> ~/.zshrc\nsource ~/.zshrc\n\n# 对于 bash\necho \"export MY_API_KEY=sk-xxxxxxxxxx\" >> ~/.bash_profile\nsource ~/.bash_profile\n```\n\n### 验证环境变量\n\n在 Terminal 中验证：\n\n```bash\necho $MY_API_KEY\n```\n\n---\n\n## Linux / WSL2 教程\n\n### 环境变量设置方法\n\n**临时设置：**\n\n```bash\nexport MY_API_KEY=sk-xxxxxxxxxx\n```\n\n**Shell 配置文件（持久保存）：**\n\n添加到你的 shell 配置文件中：\n\n```bash\n# 对于 bash (默认)\necho \"export MY_API_KEY=sk-xxxxxxxxxx\" >> ~/.bashrc\nsource ~/.bashrc\n\n# 对于 zsh\necho \"export MY_API_KEY=sk-xxxxxxxxxx\" >> ~/.zshrc\nsource ~/.zshrc\n```\n\n### 验证环境变量\n\n在终端中验证：\n\n```bash\necho $MY_API_KEY\n```","url":"https://yupanzi.com/posts/codex-config/","title":"Codex 配置指南","summary":"Codex（OpenAI CLI）工具的配置教程，覆盖 Windows、macOS、Linux 三平台","date_modified":"2025-12-22T21:18:37.000Z","author":{"name":"yupanzi"},"tags":["AI","CLI","OpenAI"]},{"id":"https://yupanzi.com/posts/claude-code-config/","content_html":"跟着这个教程，你可以轻松在自己的电脑上安装并使用 Claude Code。\n\n---\n\n## Windows 教程\n\n### 1. 安装 Node.js 环境\n\nClaude Code 需要 Node.js 环境才能运行。\n\n#### Windows 安装方法\n\n**方法一：官网下载（推荐）**\n\n1. 打开浏览器访问 `https://nodejs.org/`\n2. 点击 \"LTS\" 版本进行下载（推荐长期支持版本）\n3. 下载完成后双击 `.msi` 文件\n4. 按照安装向导完成安装，保持默认设置即可\n\n**方法二：使用包管理器**\n\n如果你安装了 Chocolatey 或 Scoop，可以使用命令行安装：\n\n```bash\n# 使用 Chocolatey\nchoco install nodejs\n\n# 或使用 Scoop\nscoop install nodejs\n```\n\n> **Windows 注意事项**\n> - 建议使用 PowerShell 而不是 CMD\n> - 如果遇到权限问题，尝试以管理员身份运行\n> - 某些杀毒软件可能会误报，需要添加白名单\n\n#### 验证安装是否成功\n\n安装完成后，打开 PowerShell 或 CMD，输入以下命令：\n\n```bash\nnode --version\nnpm --version\n```\n\n如果显示版本号，说明安装成功了！\n\n---\n\n### 2. 安装 Claude Code\n\n打开 PowerShell 或 CMD，运行以下命令：\n\n```bash\n# 全局安装 Claude Code\nnpm install -g @anthropic-ai/claude-code\n```\n\n这个命令会从 npm 官方仓库下载并安装最新版本的 Claude Code。\n\n> **提示**\n> - 建议使用 PowerShell 而不是 CMD，功能更强大\n> - 如果遇到权限问题，以管理员身份运行 PowerShell\n\n#### 验证 Claude Code 安装\n\n安装完成后，输入以下命令检查是否安装成功：\n\n```bash\nclaude --version\n```\n\n如果显示版本号，恭喜你！Claude Code 已经成功安装了。\n\n---\n\n### 3. 设置环境变量\n\n为了让 Claude Code 连接到你的中转服务，需要设置两个环境变量：\n\n#### 方法一：PowerShell 临时设置（当前会话）\n\n在 PowerShell 中运行以下命令：\n\n```powershell\n$env:ANTHROPIC_BASE_URL = \"你的服务地址/api\"\n$env:ANTHROPIC_AUTH_TOKEN = \"你的API密钥\"\n```\n\n💡 记得将 \"你的API密钥\" 替换为在上方 \"API Keys\" 标签页中创建的实际密钥。\n\n#### 方法二：PowerShell 永久设置（用户级）\n\n在 PowerShell 中运行以下命令设置用户级环境变量：\n\n```powershell\n# 设置用户级环境变量（永久生效）\n[System.Environment]::SetEnvironmentVariable(\"ANTHROPIC_BASE_URL\", \"你的服务地址/api\", [System.EnvironmentVariableTarget]::User)\n[System.Environment]::SetEnvironmentVariable(\"ANTHROPIC_AUTH_TOKEN\", \"你的API密钥\", [System.EnvironmentVariableTarget]::User)\n```\n\n查看已设置的环境变量：\n\n```powershell\n# 查看用户级环境变量\n[System.Environment]::GetEnvironmentVariable(\"ANTHROPIC_BASE_URL\", [System.EnvironmentVariableTarget]::User)\n[System.Environment]::GetEnvironmentVariable(\"ANTHROPIC_AUTH_TOKEN\", [System.EnvironmentVariableTarget]::User)\n```\n\n💡 设置后需要重新打开 PowerShell 窗口才能生效。\n\n#### VSCode Claude 插件配置\n\n如果使用 VSCode 的 Claude 插件，需要在配置文件中进行设置：\n\n**配置文件位置：** `C:\\Users\\你的用户名\\.claude\\config.json`\n\n💡 如果该文件不存在，请手动创建。\n\n```json\n{\n  \"primaryApiKey\": \"myai\"\n}\n```\n\n#### 验证环境变量设置\n\n设置完环境变量后，可以通过以下命令验证是否设置成功：\n\n**在 PowerShell 中验证：**\n\n```powershell\necho $env:ANTHROPIC_BASE_URL\necho $env:ANTHROPIC_AUTH_TOKEN\n```\n\n**在 CMD 中验证：**\n\n```cmd\necho %ANTHROPIC_BASE_URL%\necho %ANTHROPIC_AUTH_TOKEN%\n```\n\n**预期输出示例：**\n\n```\n你的服务地址/api\nsk-xxxxxxxxxxxxxxxxxx\n```\n\n💡 如果输出为空或显示变量名本身，说明环境变量设置失败，请重新设置。\n\n---\n\n### 4. 开始使用 Claude Code\n\n现在你可以开始使用 Claude Code 了！\n\n**启动 Claude Code**\n\n```bash\nclaude\n```\n\n**在特定项目中使用**\n\n```bash\n# 进入你的项目目录\ncd C:\\path\\to\\your\\project\n\n# 启动 Claude Code\nclaude\n```\n\n---\n\n### Windows 常见问题解决\n\n<details>\n<summary>安装时提示 \"permission denied\" 错误</summary>\n\n这通常是权限问题，尝试以下解决方法：\n- 以管理员身份运行 PowerShell\n- 或者配置 npm 使用用户目录：`npm config set prefix %APPDATA%\\npm`\n\n</details>\n\n<details>\n<summary>PowerShell 执行策略错误</summary>\n\n如果遇到执行策略限制，运行：\n\n```powershell\nSet-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser\n```\n\n</details>\n\n<details>\n<summary>环境变量设置后不生效</summary>\n\n设置永久环境变量后需要：\n- 重新启动 PowerShell 或 CMD\n- 或者注销并重新登录 Windows\n- 验证设置：`echo $env:ANTHROPIC_BASE_URL`\n\n</details>\n\n---\n\n## macOS 教程\n\n### 1. 安装 Node.js 环境\n\nClaude Code 需要 Node.js 环境才能运行。\n\n#### macOS 安装方法\n\n**方法一：使用 Homebrew（推荐）**\n\n如果你已经安装了 Homebrew，使用它安装 Node.js 会更方便：\n\n```bash\n# 更新 Homebrew\nbrew update\n\n# 安装 Node.js\nbrew install node\n```\n\n**方法二：官网下载**\n\n1. 访问 `https://nodejs.org/`\n2. 下载适合 macOS 的 LTS 版本\n3. 打开下载的 `.pkg` 文件\n4. 按照安装程序指引完成安装\n\n> **macOS 注意事项**\n> - 如果遇到权限问题，可能需要使用 `sudo`\n> - 首次运行可能需要在系统偏好设置中允许\n> - 建议使用 Terminal 或 iTerm2\n\n#### 验证安装是否成功\n\n安装完成后，打开 Terminal，输入以下命令：\n\n```bash\nnode --version\nnpm --version\n```\n\n如果显示版本号，说明安装成功了！\n\n---\n\n### 2. 安装 Claude Code\n\n打开 Terminal，运行以下命令：\n\n```bash\n# 全局安装 Claude Code\nnpm install -g @anthropic-ai/claude-code\n```\n\n如果遇到权限问题，可以使用 sudo：\n\n```bash\nsudo npm install -g @anthropic-ai/claude-code\n```\n\n#### 验证 Claude Code 安装\n\n安装完成后，输入以下命令检查是否安装成功：\n\n```bash\nclaude --version\n```\n\n如果显示版本号，恭喜你！Claude Code 已经成功安装了。\n\n---\n\n### 3. 设置环境变量\n\n为了让 Claude Code 连接到你的中转服务，需要设置两个环境变量：\n\n#### 方法一：临时设置（当前会话）\n\n在 Terminal 中运行以下命令：\n\n```bash\nexport ANTHROPIC_BASE_URL=\"你的服务地址/api\"\nexport ANTHROPIC_AUTH_TOKEN=\"你的API密钥\"\n```\n\n💡 记得将 \"你的API密钥\" 替换为在上方 \"API Keys\" 标签页中创建的实际密钥。\n\n#### 方法二：永久设置\n\n编辑你的 shell 配置文件（根据你使用的 shell）：\n\n```bash\n# 对于 zsh (默认)\necho 'export ANTHROPIC_BASE_URL=\"你的服务地址/api\"' >> ~/.zshrc\necho 'export ANTHROPIC_AUTH_TOKEN=\"你的API密钥\"' >> ~/.zshrc\nsource ~/.zshrc\n```\n\n```bash\n# 对于 bash\necho 'export ANTHROPIC_BASE_URL=\"你的服务地址/api\"' >> ~/.bash_profile\necho 'export ANTHROPIC_AUTH_TOKEN=\"你的API密钥\"' >> ~/.bash_profile\nsource ~/.bash_profile\n```\n\n#### VSCode Claude 插件配置\n\n如果使用 VSCode 的 Claude 插件，需要在配置文件中进行设置：\n\n**配置文件位置：** `~/.claude/config.json`\n\n💡 如果该文件不存在，请手动创建。\n\n```json\n{\n  \"primaryApiKey\": \"myai\"\n}\n```\n\n---\n\n### 4. 开始使用 Claude Code\n\n现在你可以开始使用 Claude Code 了！\n\n**启动 Claude Code**\n\n```bash\nclaude\n```\n\n**在特定项目中使用**\n\n```bash\n# 进入你的项目目录\ncd /path/to/your/project\n\n# 启动 Claude Code\nclaude\n```\n\n---\n\n### macOS 常见问题解决\n\n<details>\n<summary>安装时提示权限错误</summary>\n\n尝试以下解决方法：\n- 使用 sudo 安装：`sudo npm install -g @anthropic-ai/claude-code`\n- 或者配置 npm 使用用户目录：`npm config set prefix ~/.npm-global`\n\n</details>\n\n<details>\n<summary>macOS 安全设置阻止运行</summary>\n\n如果系统阻止运行 Claude Code：\n- 打开\"系统偏好设置\" → \"安全性与隐私\"\n- 点击\"仍要打开\"或\"允许\"\n- 或者在 Terminal 中运行：`sudo spctl --master-disable`\n\n</details>\n\n<details>\n<summary>环境变量不生效</summary>\n\n检查以下几点：\n- 确认修改了正确的配置文件（.zshrc 或 .bash_profile）\n- 重新启动 Terminal\n- 验证设置：`echo $ANTHROPIC_BASE_URL`\n\n</details>\n\n---\n\n## Linux / WSL2 教程\n\n### 1. 安装 Node.js 环境\n\nClaude Code 需要 Node.js 环境才能运行。\n\n#### Linux 安装方法\n\n**方法一：使用官方仓库（推荐）**\n\n```bash\n# 添加 NodeSource 仓库\ncurl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -\n\n# 安装 Node.js\nsudo apt-get install -y nodejs\n```\n\n**方法二：使用系统包管理器**\n\n虽然版本可能不是最新的，但对于基本使用已经足够：\n\n```bash\n# Ubuntu/Debian\nsudo apt update\nsudo apt install nodejs npm\n\n# CentOS/RHEL/Fedora\nsudo dnf install nodejs npm\n```\n\n> **Linux 注意事项**\n> - 某些发行版可能需要安装额外的依赖\n> - 如果遇到权限问题，使用 `sudo`\n> - 确保你的用户在 npm 的全局目录有写权限\n\n#### 验证安装是否成功\n\n安装完成后，打开终端，输入以下命令：\n\n```bash\nnode --version\nnpm --version\n```\n\n如果显示版本号，说明安装成功了！\n\n---\n\n### 2. 安装 Claude Code\n\n打开终端，运行以下命令：\n\n```bash\n# 全局安装 Claude Code\nnpm install -g @anthropic-ai/claude-code\n```\n\n如果遇到权限问题，可以使用 sudo：\n\n```bash\nsudo npm install -g @anthropic-ai/claude-code\n```\n\n#### 验证 Claude Code 安装\n\n安装完成后，输入以下命令检查是否安装成功：\n\n```bash\nclaude --version\n```\n\n如果显示版本号，恭喜你！Claude Code 已经成功安装了。\n\n---\n\n### 3. 设置环境变量\n\n为了让 Claude Code 连接到你的中转服务，需要设置两个环境变量：\n\n#### 方法一：临时设置（当前会话）\n\n在终端中运行以下命令：\n\n```bash\nexport ANTHROPIC_BASE_URL=\"你的服务地址/api\"\nexport ANTHROPIC_AUTH_TOKEN=\"你的API密钥\"\n```\n\n💡 记得将 \"你的API密钥\" 替换为在上方 \"API Keys\" 标签页中创建的实际密钥。\n\n#### 方法二：永久设置\n\n编辑你的 shell 配置文件：\n\n```bash\n# 对于 bash (默认)\necho 'export ANTHROPIC_BASE_URL=\"你的服务地址/api\"' >> ~/.bashrc\necho 'export ANTHROPIC_AUTH_TOKEN=\"你的API密钥\"' >> ~/.bashrc\nsource ~/.bashrc\n```\n\n```bash\n# 对于 zsh\necho 'export ANTHROPIC_BASE_URL=\"你的服务地址/api\"' >> ~/.zshrc\necho 'export ANTHROPIC_AUTH_TOKEN=\"你的API密钥\"' >> ~/.zshrc\nsource ~/.zshrc\n```\n\n#### VSCode Claude 插件配置\n\n如果使用 VSCode 的 Claude 插件，需要在配置文件中进行设置：\n\n**配置文件位置：** `~/.claude/config.json`\n\n💡 如果该文件不存在，请手动创建。\n\n```json\n{\n  \"primaryApiKey\": \"myai\"\n}\n```\n\n---\n\n### 4. 开始使用 Claude Code\n\n现在你可以开始使用 Claude Code 了！\n\n**启动 Claude Code**\n\n```bash\nclaude\n```\n\n**在特定项目中使用**\n\n```bash\n# 进入你的项目目录\ncd /path/to/your/project\n\n# 启动 Claude Code\nclaude\n```\n\n---\n\n### Linux 常见问题解决\n\n<details>\n<summary>安装时提示权限错误</summary>\n\n尝试以下解决方法：\n- 使用 sudo 安装：`sudo npm install -g @anthropic-ai/claude-code`\n- 或者配置 npm 使用用户目录：`npm config set prefix ~/.npm-global`\n- 然后添加到 PATH：`export PATH=~/.npm-global/bin:$PATH`\n\n</details>\n\n<details>\n<summary>缺少依赖库</summary>\n\n某些 Linux 发行版需要安装额外依赖：\n\n```bash\n# Ubuntu/Debian\nsudo apt install build-essential\n\n# CentOS/RHEL\nsudo dnf groupinstall \"Development Tools\"\n```\n\n</details>\n\n<details>\n<summary>环境变量不生效</summary>\n\n检查以下几点：\n- 确认修改了正确的配置文件（.bashrc 或 .zshrc）\n- 重新启动终端或运行 `source ~/.bashrc`\n- 验证设置：`echo $ANTHROPIC_BASE_URL`\n\n</details>","url":"https://yupanzi.com/posts/claude-code-config/","title":"Claude Code 配置指南","summary":"Claude Code 命令行工具的安装与配置教程，覆盖 Windows、macOS、Linux 三平台","date_modified":"2025-12-22T21:18:36.000Z","author":{"name":"yupanzi"},"tags":["AI","CLI","Claude"]},{"id":"https://yupanzi.com/posts/gemini-cli-config/","content_html":"如果你使用 Gemini CLI，需要设置以下环境变量来连接到中转服务。\n\n---\n\n## Windows 教程\n\n### PowerShell 临时设置（当前会话）\n\n在 PowerShell 中运行以下命令：\n\n```powershell\n$env:GOOGLE_GEMINI_BASE_URL = \"你的服务地址/gemini\"\n$env:GEMINI_API_KEY = \"你的API密钥\"\n$env:GEMINI_MODEL = \"gemini-2.5-pro\"\n```\n\n💡 使用与 Claude Code 相同的 API 密钥即可。\n\n### PowerShell 永久设置（用户级）\n\n在 PowerShell 中运行以下命令：\n\n```powershell\n# 设置用户级环境变量（永久生效）\n[System.Environment]::SetEnvironmentVariable(\"GOOGLE_GEMINI_BASE_URL\", \"你的服务地址/gemini\", [System.EnvironmentVariableTarget]::User)\n[System.Environment]::SetEnvironmentVariable(\"GEMINI_API_KEY\", \"你的API密钥\", [System.EnvironmentVariableTarget]::User)\n[System.Environment]::SetEnvironmentVariable(\"GEMINI_MODEL\", \"gemini-2.5-pro\", [System.EnvironmentVariableTarget]::User)\n```\n\n💡 设置后需要重新打开 PowerShell 窗口才能生效。\n\n### 验证 Gemini CLI 环境变量\n\n在 PowerShell 中验证：\n\n```powershell\necho $env:GOOGLE_GEMINI_BASE_URL\necho $env:GEMINI_API_KEY\necho $env:GEMINI_MODEL\n```\n\n---\n\n## macOS 教程\n\n### 临时设置（当前会话）\n\n在 Terminal 中运行以下命令：\n\n```bash\nexport GOOGLE_GEMINI_BASE_URL=\"你的服务地址/gemini\"\nexport GEMINI_API_KEY=\"你的API密钥\"\nexport GEMINI_MODEL=\"gemini-2.5-pro\"\n```\n\n💡 使用与 Claude Code 相同的 API 密钥即可。\n\n### 永久设置\n\n添加到你的 shell 配置文件：\n\n```bash\n# 对于 zsh (默认)\necho 'export GOOGLE_GEMINI_BASE_URL=\"你的服务地址/gemini\"' >> ~/.zshrc\necho 'export GEMINI_API_KEY=\"你的API密钥\"' >> ~/.zshrc\necho 'export GEMINI_MODEL=\"gemini-2.5-pro\"' >> ~/.zshrc\nsource ~/.zshrc\n```\n\n```bash\n# 对于 bash\necho 'export GOOGLE_GEMINI_BASE_URL=\"你的服务地址/gemini\"' >> ~/.bash_profile\necho 'export GEMINI_API_KEY=\"你的API密钥\"' >> ~/.bash_profile\necho 'export GEMINI_MODEL=\"gemini-2.5-pro\"' >> ~/.bash_profile\nsource ~/.bash_profile\n```\n\n### 验证 Gemini CLI 环境变量\n\n在 Terminal 中验证：\n\n```bash\necho $GOOGLE_GEMINI_BASE_URL\necho $GEMINI_API_KEY\necho $GEMINI_MODEL\n```\n\n---\n\n## Linux / WSL2 教程\n\n### 临时设置（当前会话）\n\n在终端中运行以下命令：\n\n```bash\nexport GOOGLE_GEMINI_BASE_URL=\"你的服务地址/gemini\"\nexport GEMINI_API_KEY=\"你的API密钥\"\nexport GEMINI_MODEL=\"gemini-2.5-pro\"\n```\n\n💡 使用与 Claude Code 相同的 API 密钥即可。\n\n### 永久设置\n\n添加到你的 shell 配置文件：\n\n```bash\n# 对于 bash (默认)\necho 'export GOOGLE_GEMINI_BASE_URL=\"你的服务地址/gemini\"' >> ~/.bashrc\necho 'export GEMINI_API_KEY=\"你的API密钥\"' >> ~/.bashrc\necho 'export GEMINI_MODEL=\"gemini-2.5-pro\"' >> ~/.bashrc\nsource ~/.bashrc\n```\n\n```bash\n# 对于 zsh\necho 'export GOOGLE_GEMINI_BASE_URL=\"你的服务地址/gemini\"' >> ~/.zshrc\necho 'export GEMINI_API_KEY=\"你的API密钥\"' >> ~/.zshrc\necho 'export GEMINI_MODEL=\"gemini-2.5-pro\"' >> ~/.zshrc\nsource ~/.zshrc\n```\n\n### 验证 Gemini CLI 环境变量\n\n在终端中验证：\n\n```bash\necho $GOOGLE_GEMINI_BASE_URL\necho $GEMINI_API_KEY\necho $GEMINI_MODEL\n```","url":"https://yupanzi.com/posts/gemini-cli-config/","title":"Gemini CLI 配置指南","summary":"Gemini CLI 命令行工具的环境变量配置教程，覆盖 Windows、macOS、Linux 三平台","date_modified":"2025-12-22T21:18:36.000Z","author":{"name":"yupanzi"},"tags":["AI","CLI","Gemini"]},{"id":"https://yupanzi.com/posts/openspec-translation-extension/","content_html":"OpenSpec 是个很棒的规范驱动开发框架，但只支持英文文档。对非英语团队来说，这挺麻烦的。好在 Claude Code 支持自定义命令，我们可以自己动手补上这个功能。\n\n## 快速上手\n\n一行命令搞定翻译：\n\n```bash\n# 翻译成中文\n/openspec:translate chinese\n\n# 也可以用中文输入\n/openspec:translate 中文\n\n# 支持多种语言\n/openspec:translate japanese   # 日文\n/openspec:translate español    # 西班牙语\n```\n\n## 核心功能\n\n### 支持的语言\n\n| 语言 | 英文名称 | 本地化名称 |\n|------|---------|-----------|\n| 中文 | chinese | 中文 |\n| 日文 | japanese | 日本語 |\n| 西班牙语 | spanish | español |\n| 韩语 | korean | 한국어 |\n| 法语 | french | français |\n\n### 智能保护机制\n\n翻译时自动保护技术内容：\n\n- ✅ 代码块原样保留\n- ✅ 文件路径不翻译\n- ✅ 命令示例保持英文\n- ✅ Markdown 结构不破坏\n- ✅ 特殊标记（如 `<!-- OPENSPEC:START -->`）保持原样\n\n### 自动更新配置\n\n翻译完成后，命令会自动更新项目配置文件 `CLAUDE.md` 和 `AGENTS.md`，添加或更新语言指令。\n\n## 翻译范围\n\n命令会处理这些文件：\n\n**核心文档**：\n- `openspec/project.md`\n- `openspec/AGENTS.md`\n\n**命令文档**：\n- `.claude/commands/openspec/proposal.md`\n- `.claude/commands/openspec/apply.md`\n- `.claude/commands/openspec/archive.md`\n\n**全局配置**（仅 OpenSpec 相关部分）：\n- `AGENTS.md`\n- `CLAUDE.md`\n\n**排除**：`.claude/commands/openspec/translate.md`（命令本身保持英文）\n\n## 实现方式\n\n### 命令文件结构\n\n在项目中创建 `.claude/commands/openspec/translate.md`：\n\n```markdown\n---\nname: OpenSpec: Translate\ndescription: Translate OpenSpec documentation to any target language\ncategory: OpenSpec\ntags: [openspec, translation, i18n]\n---\n\n<!-- 这里写命令的详细执行指令 -->\n```\n\n### 工作原理\n\n```mermaid\ngraph LR\n    A[用户输入命令] --> B[Claude 读取命令文件]\n    B --> C[解析语言参数]\n    C --> D[读取目标文件]\n    D --> E[执行翻译]\n    E --> F[写回文件]\n    F --> G[更新配置]\n```\n\n执行流程：\n\n1. 提取目标语言（如 `chinese` → `中文`）\n2. 读取每个目标文件\n3. 翻译文本内容，保护技术部分\n4. 写回原文件\n5. 更新语言指令到配置文件\n\n### 语言映射实现\n\n```javascript\n// 映射表（伪代码）\nconst languageMap = {\n  'chinese': '中文',\n  '中文': '中文',\n  'japanese': '日本語',\n  '日本語': '日本語'\n}\n\n// 智能识别\nconst input = 'chinese'  // 或 '中文'\nconst target = languageMap[input.toLowerCase()]  // '中文'\n```\n\n## 完整命令代码\n\n```markdown\n---\nname: OpenSpec: Translate\ndescription: Translate OpenSpec documentation to any target language\ncategory: OpenSpec\ntags: [openspec, translation, i18n]\n---\n<!-- OPENSPEC:START -->\n**Overview**\nThis command translates OpenSpec-related documentation from English to a specified target language while preserving technical accuracy and document structure.\n\n**Usage**\n`/openspec:translate <language>`\n\n\n**Examples**\n- `/openspec:translate chinese` or `/openspec:translate 中文`\n- `/openspec:translate japanese` or `/openspec:translate 日本語`\n- `/openspec:translate spanish` or `/openspec:translate español`\n- `/openspec:translate korean` or `/openspec:translate 한국어`\n- `/openspec:translate french` or `/openspec:translate français`\n\n**Target Files**\nThe following files will be translated:\n- `openspec/project.md`\n- `openspec/AGENTS.md`\n- `.claude/commands/openspec/proposal.md`\n- `.claude/commands/openspec/apply.md`\n- `.claude/commands/openspec/archive.md`\n- `AGENTS.md` (OpenSpec section only)\n- `CLAUDE.md` (OpenSpec section only)\n\n**Excluded Files**\nThe following files will NOT be translated and always remain in English:\n- `.claude/commands/openspec/translate.md` (this command file itself)\n\n**Language Name Mapping**\nThe system maps English language names to localized names for the language instruction:\n- `chinese` or `中文` → \"中文\"\n- `japanese` or `日本語` → \"日本語\"\n- `spanish` or `español` → \"español\"\n- `korean` or `한국어` → \"한국어\"\n- `french` or `français` → \"français\"\n\n**Process**\n1. Extract target language from command arguments\n2. Determine the localized language name for the language instruction\n3. Read each target file using the Read tool (skip `.claude/commands/openspec/translate.md`)\n4. Translate text content to the specified language while preserving:\n   - Frontmatter (YAML headers)\n   - Code blocks and command examples\n   - Markdown structure (headings, lists, links, tables)\n   - Special markers (e.g., `<!-- OPENSPEC:START -->`)\n   - English commands, file paths, and technical identifiers\n5. Write translated content back to original files using the Write tool\n6. After translation, append or update language instruction in `CLAUDE.md` and `AGENTS.md`:\n   - Check if file ends with \"**Always respond in [language]**\"\n   - If exists, replace with new target language\n   - If not exists, append new instruction: \"Always respond in [localized language name]\"\n7. Provide translation progress and completion feedback\n\n**Translation Principles**\n- Maintain technical terminology accuracy\n- Use natural, idiomatic expressions in the target language\n- Preserve all English commands, code, and file paths\n- Keep the original document's tone and style\n- Support both English language names (e.g., \"chinese\") and native names (e.g., \"中文\")\n\n**WARNING**\n- Do not translate the command file itself `.claude/commands/openspec/translate.md`\n<!-- OPENSPEC:END -->\n```\n\n## 扩展应用\n\n### 更多自定义命令示例\n\n**代码审查**：\n\n```markdown\n---\nname: Code Review\ndescription: Generate a comprehensive code review checklist\n---\nReview the code with focus on:\n1. Security vulnerabilities\n2. Performance issues\n3. Code style consistency\n4. Test coverage\n```\n\n**文档生成**：\n\n```markdown\n---\nname: Generate API Docs\ndescription: Auto-generate API documentation from code comments\n---\nScan all files in `src/api/` and generate:\n- Endpoint descriptions\n- Request/response schemas\n- Example usage\n```\n\n### 命令设计原则\n\n| 原则 | 好的做法 | 不好的做法 |\n|------|---------|-----------|\n| **清晰性** | `/openspec:translate chinese` | `/t cn` |\n| **健壮性** | 支持大小写、多种格式 | 只认固定格式 |\n| **文档性** | 详细的用法示例和参数说明 | 缺少说明文档 |\n\n### 项目组织\n\n建议的目录结构：\n\n```\n.claude/\n└── commands/\n    ├── openspec/        # OpenSpec 相关\n    │   ├── translate.md\n    │   ├── proposal.md\n    │   └── apply.md\n    ├── git/             # Git 相关\n    │   ├── commit-template.md\n    │   └── pr-template.md\n    └── docs/            # 文档相关\n        ├── api-docs.md\n        └── changelog.md\n```\n\n### 团队协作\n\n把自定义命令加入版本控制：\n\n```bash\ngit add .claude/commands/\ngit commit -m \"feat: add OpenSpec translation command\"\n```\n\n团队成员克隆代码后自动获得所有命令。\n\n## 总结\n\n通过自定义斜杠命令，为 OpenSpec 补上了多语言支持。\n\n**简单易用**：\n- 一个 Markdown 文件就能定义命令\n- 用自然语言描述指令，无需编程\n- 可以调用所有 Claude Code 工具\n\n**实用价值**：\n- 补充工具缺失的功能\n- 打造团队专属工作流\n- 沉淀最佳实践\n\n别被工具限制住——通过自定义命令，让 AI 助手真正为你所用。\n\n---\n\n**相关资源**：\n- [OpenSpec 官方文档](https://github.com/Fission-AI/OpenSpec)\n- [Claude Code 命令文档](https://docs.claude.com/en/docs/claude-code)","url":"https://yupanzi.com/posts/openspec-translation-extension/","title":"为 OpenSpec 添加多语言支持","summary":"通过 Claude Code 自定义命令实现 OpenSpec 文档多语言翻译","date_modified":"2025-10-25T14:11:38.000Z","author":{"name":"yupanzi"},"tags":["AI","OpenSpec","Claude"]},{"id":"https://yupanzi.com/posts/claude-code-usage-guide/","content_html":"本文整合了 AI 编程工具的选择对比和 Claude Code 的进阶使用技巧。\n\n## 第一梯队：Cursor vs Claude Code\n\n| 特性 | Cursor | Claude Code |\n|------|--------|-------------|\n| **类型** | AI 编辑器（基于 VSCode） | CLI 命令行工具 |\n| **安装** | 下载安装包 | `npm install -g @anthropic-ai/claude-code` |\n| **核心优势** | IDE 体验、多模型、智能补全 | 终端原生、深度代码理解、Git 集成 |\n| **适用场景** | 日常编码、大型项目 | 快速调试、代码审查、自动化 |\n\n---\n\n## 编辑器方案对比\n\n### 工具层级\n\n```mermaid\ngraph LR\n    A[Cursor<br/>旗舰版] -->|廉价版| B[VSCode + Copilot]\n    A -->|替代方案| C[VSCode + KiloCode]\n    C -->|简化版| D[VSCode + Cline]\n```\n\n### 详细对比\n\n| 工具 | 类型 | 核心特性 | 推荐度 |\n|------|------|----------|--------|\n| **Cursor** | AI 编辑器 | Plan Mode、多模型切换、Agent CLI | ⭐⭐⭐⭐⭐ |\n| **VSCode + Copilot** | 编辑器 + 官方插件 | GitHub 官方、Agent 模式 | ⭐⭐⭐⭐ |\n| **VSCode + KiloCode** | 编辑器 + 开源插件 | 自动补全、Memory Bank、团队协作 | ⭐⭐⭐⭐⭐ |\n| **VSCode + Cline** | 编辑器 + 开源插件 | 完全开源、多 API、自主编辑 | ⭐⭐⭐⭐ |\n\n### 选择建议\n\n| 预算 | 推荐方案 |\n|------|----------|\n| 充足 | Cursor |\n| 有限 | VSCode + Copilot Free 或 Cline |\n| 团队 | VSCode + KiloCode |\n\n---\n\n## API 代理服务\n\n| 服务 | 用途 | 特点 |\n|------|------|------|\n| **OpenRouter** | 统一 API 网关 | 400+ 模型、自动降级 |\n| **Z.ai（智谱）** | Claude 替代 | Claude 协议兼容、价格 1/7 |\n\n### 配置示例\n\n**OpenRouter（Cline/KiloCode）：**\n\n```json\n{\n  \"apiProvider\": \"OpenRouter\",\n  \"apiKey\": \"sk-or-v1-xxx\",\n  \"baseURL\": \"https://openrouter.ai/api/v1\"\n}\n```\n\n**智谱（Claude Code 替代）：**\n\n```bash\n# 国际版\nexport ANTHROPIC_BASE_URL=\"https://api.z.ai/api/paas/v4\"\nexport ANTHROPIC_API_KEY=\"your-key\"\n\n# 国内版\nexport ANTHROPIC_BASE_URL=\"https://open.bigmodel.cn/api/paas/v4\"\nexport ANTHROPIC_API_KEY=\"your-key\"\n```\n\n---\n\n## Claude Code 进阶：MCP 生态\n\nModel Context Protocol (MCP) 生态提供了强大的扩展能力。\n\n### Zen MCP Server\n\n多模型协作 MCP 服务器，支持 Gemini、OpenAI、OpenRouter 等。\n\n**核心功能：**\n- **多模型智能调度**：不同模型发挥各自优势\n- **对话线程**：跨工具上下文延续\n- **CLI 子代理**：任务分层处理\n\n**配置示例：**\n\n```json\n{\n  \"mcpServers\": {\n    \"zen\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@beehiveinnovations/zen-mcp-server\"],\n      \"env\": {\n        \"OPENAI_API_KEY\": \"your-key\",\n        \"GEMINI_API_KEY\": \"your-key\"\n      }\n    }\n  }\n}\n```\n\n**模型选择建议：**\n\n| 场景 | 推荐模型 | 原因 |\n|------|----------|------|\n| 大型代码库 | Gemini 2.5 Pro | 100 万 token 上下文 |\n| 算法优化 | GPT-5 | 强推理能力 |\n| 日常编码 | Claude 4.5 | 平衡、快速响应 |\n\n---\n\n### Claude Code Router\n\n智能代理工具，将 Claude Code 请求路由到其他 AI 提供商。\n\n**功能：**\n- 请求拦截与路由\n- 多提供商支持（OpenRouter、DeepSeek、Ollama）\n- 动态模型切换\n\n```bash\n# 切换模型\n/model gpt-5\n/model deepseek-coder\n/model ollama:qwen2.5-coder\n```\n\n**替代方案：**\n\n| 项目 | 特点 |\n|------|------|\n| ccproxy | 基于 LiteLLM，最大兼容性 |\n| y-router | Cloudflare Worker，云端方案 |\n\n---\n\n### ZCF (Zero-Config Code Flow)\n\n零配置 AI 编码工作流工具。\n\n```bash\n# 安装使用\nnpm install -g zcf\nnpx zcf\n# 选择 \"4. Configure Codex MCP services\"\n```\n\n**特点：**\n- 零配置启动\n- Claude Code 和 Codex 共享 MCP 配置\n- 内置搜索、Git、文件系统服务\n\n---\n\n### OpenSpec\n\n规范驱动开发框架：\"先定义规格，再编写代码\"。\n\n```bash\n# 工作流\nopenspec init feature-auth\nopenspec review feature-auth\nopenspec generate feature-auth\n```\n\n---\n\n## 方案对比\n\n| 维度 | 方案 A (Zen + OpenSpec) | 方案 B (Router + ZCF) |\n|------|-------------------------|------------------------|\n| 配置难度 | 需要多个 API Key | 零配置 |\n| 灵活性 | 完全自定义 | 预设方案 |\n| 成本 | 多个 API 订阅 | 可用免费模型 |\n| 离线能力 | ❌ | ✅ Ollama |\n| 适合人群 | 高级用户、团队 | 新手、个人 |\n\n### 选择建议\n\n**选择方案 A 如果：**\n- 需要多模型协作\n- 重视开发流程规范化\n- 处理复杂大型项目\n\n**选择方案 B 如果：**\n- AI 工具新手\n- 希望快速上手\n- 预算有限或需离线\n\n---\n\n## 团队协作配置\n\n以下工具支持配置文件提交到仓库：\n\n### Cursor\n\n```bash\n.cursor/\n  ├── settings.json\n  ├── rules.json\n  └── prompts/\n```\n\n### KiloCode\n\n```bash\n.kilocode/\n  ├── config.json\n  ├── models.json\n  └── memory/\n```\n\n### Claude Code\n\n```bash\n.claude/\n  ├── config.json\n  ├── commands/\n  └── hooks/\n```\n\n**注意**：⚠️ 不要将 API Key 提交到仓库。\n\n---\n\n## 快速上手\n\n### 新手路径\n\n```bash\n# 1. ZCF 零配置（10 分钟）\nnpm install -g zcf\nnpx zcf\n\n# 2. Claude Code Router（20 分钟）\nnpm install -g claude-code-router\nexport OPENROUTER_API_KEY=your-key\nclaude-code-router start\n```\n\n### 高级路径\n\n```bash\n# 1. 部署 Zen MCP Server\n# 配置 ~/.config/claude/claude_desktop_config.json\n\n# 2. 引入 OpenSpec\nnpm install -g @fission-ai/openspec\nopenspec init my-feature\n```\n\n---\n\n## 总结\n\n| 工具 | 核心价值 | 适合场景 |\n|------|----------|----------|\n| **Zen MCP Server** | 多模型协作 | 复杂项目、团队协作 |\n| **OpenSpec** | 规范化开发 | 团队协作、知识沉淀 |\n| **Claude Code Router** | 低成本/离线 | 预算有限、本地开发 |\n| **ZCF** | 零配置启动 | 新手入门、快速上手 |\n\n**建议：**\n- 5 分钟：试试 ZCF\n- 1 小时：部署 Router + ZCF\n- 深度探索：学习 Zen MCP + OpenSpec\n\n---\n\n## 参考链接\n\n- [Zen MCP Server](https://github.com/BeehiveInnovations/zen-mcp-server)\n- [OpenSpec](https://github.com/Fission-AI/OpenSpec)\n- [Claude Code Router](https://github.com/musistudio/claude-code-router)\n- [ZCF](https://github.com/UfoMiao/zcf)\n- [Model Context Protocol](https://github.com/modelcontextprotocol)","url":"https://yupanzi.com/posts/claude-code-usage-guide/","title":"AI 编程工具使用指南","summary":"AI 编程工具对比选择、Claude Code 进阶技巧、MCP 多模型协作完全指南","date_modified":"2025-10-25T11:25:05.000Z","author":{"name":"yupanzi"},"tags":["AI","Claude","MCP","Cursor"]},{"id":"https://yupanzi.com/posts/notion-auto-daily-todo-button/","content_html":"每天手动创建 TODO 清单太麻烦？用 Notion 的按钮功能，点一下就能自动生成包含日期、待办和笔记的模板。\n\n## 配置按钮\n\n### 第 1 步：创建按钮\n\n在 Notion 页面输入 `/button`，选择\"Button\"。\n\n### 第 2 步：设置触发\n\n- **按钮名称**：\"每日工作\"（可自定义）\n- **When**：`Button is clicked`\n\n### 第 3 步：配置内容\n\n**Do** 区域选择：`Insert blocks` → `below button`\n\n在编辑框输入：\n\n```\n@Today\n\nTODO\n[ ] To-do\n\nNOTE\n```\n\n![Notion 按钮配置界面](./Snipaste_2025-10-13_09-06-45.png)\n\n**说明**：\n- `@Today` - 自动显示当天日期\n- `[ ] To-do` - 创建可勾选的待办项（Notion 中输入 `/todo` 创建）\n- 空行分隔内容区域\n\n点击 **Done** 保存。\n\n## 自定义模板\n\n### 多个待办项\n\n```\nTODO\n[ ] 处理邮件\n[ ] 代码审查\n[ ] 团队会议\n```\n\n### 完整工作日模板\n\n```\n@Today\n\n## 今日目标\n[ ]\n\n## 工作进展\n[ ]\n\n## 会议记录\n\n## 备注\n```\n\n### 日期变量\n\n| 变量 | 说明 |\n|------|------|\n| `@Today` | 当天日期 |\n| `@Tomorrow` | 明天 |\n| `@Yesterday` | 昨天 |\n| `@Now` | 当前时间 |\n\n## 进阶功能\n\n### 结合数据库\n\n如果 TODO 使用数据库管理，可以在按钮中：\n- **Add pages to** - 向数据库添加新页面\n- **Edit pages** - 批量更新页面属性\n\n### 组合操作\n\n**Do** 区域点击 `Add action` 可添加多个动作：\n1. 插入今日模板\n2. 发送提醒通知\n3. 更新数据库字段\n\n### 美化按钮\n\n点击按钮右上角 `⋮` 可以：\n- 修改颜色\n- 添加图标\n- 调整大小\n\n## 注意事项\n\n- 每次点击都会新增内容，建议每天只点击一次\n- 已插入的内容可直接编辑，不影响按钮模板\n- 在 Notion 中输入 `/todo` 可创建待办复选框","url":"https://yupanzi.com/posts/notion-auto-daily-todo-button/","title":"Notion 自动按钮生成每日 TODO","summary":"用 Notion 按钮一键生成每日待办清单","date_modified":"2025-10-13T09:11:38.000Z","author":{"name":"yupanzi"},"tags":["Notion","Productivity","Automation"]},{"id":"https://yupanzi.com/posts/ai-autonomous-driving-drainage-ditch/","content_html":"AI 编程很像自动驾驶，自动驾驶分为 L0 到 L5 六个级别，AI 编程也差不多：\n\n## AI 编程的六个级别\n\n| 级别 | 自动驾驶 | AI 编程 | 监控要求 | 特征 |\n|------|---------|---------|---------|------|\n| L0 | 无自动化 | 古法编程 | 人工全程 | 纯手写代码，啥都没有 |\n| L1 | 驾驶辅助 | IDE 补全 | 人工全程 | 基础语法提示 |\n| L2 | 部分自动化 | Tab 补全 | 人工全程 | Copilot 那种代码生成 |\n| L3 | 有条件自动化 | Agent 使用 | 人需待命 | Claude Code 这类工具 |\n| L4 | 高度自动化 | 规划编程 | 限定场景免监控 | AI 自己搞定大部分需求 |\n| L5 | 完全自动化 | 完全自主 | 无需监控 | 任何场景都能编程 |\n\n**重点来了**：\n- **L0-L2**：辅助级别，你得全程盯着\n- **L3-L5**：自动化级别，系统能自己干活\n\n现在大概在 **L3 阶段**：特定场景下 AI 能搞定复杂任务，但碰到没见过的情况还得你接手。\n\n## Claude Code 的真实表现\n\n我最近让 Claude Code 提供算法进行实现，它上来就给了个 O(n⁴) 的暴力解：\n\n```javascript\n// Claude 最初的方案：四层循环暴力搜\nfor (i in positions)\n  for (j in positions)\n    for (k in positions)\n      for (l in positions)\n        检查是否对称矩形\n```\n\n能跑，但慢得要死。我跟它说了下我的优化算法方案，它才恍然大悟：\n\n```javascript\n// 我提示后的方案：分组优化\n按数字值分组\nfor 每个数字 value1:\n  for 该数字的每对位置:\n    for 每个其他数字 value2:\n      for 该数字的每对位置:\n        检查是否对称矩形\n```\n\n看看差距：\n\n| 指标 | Claude 最初方案 | 我提示后 | 提升 |\n|------|----------------|---------|------|\n| 算法复杂度 | O(n⁴) | O(k × m²) | - |\n| 比较次数（30数字）| 81 万次 | 112 次 | 7200 倍 |\n| 平均速度 | 75.87 种子/秒 | 273.49 种子/秒 | 3.6 倍 ⚡ |\n| 总耗时 | 19 分钟 | 5.3 分钟 | 3.6 倍 |\n| 准确性 | ✅ 通过 | ✅ 通过 | 一致 |\n\n发现问题了吗？AI 能写出能跑的代码，但不一定是最优解。这就是 L3 的典型特征——常规场景没问题，碰到需要动脑筋的地方还得你来。\n\n## AI 编程的\"排水渠过弯\"\n\n还记得《头文字D》里的经典操作吗？在秋名山下坡弯道，利用排水渠的特殊地形，一侧轮胎压入排水渠，突破物理极限实现超车。\n\n**自动驾驶的困境**：\n- 系统知道最优行车路线的数据和参数\n- 但它不知道\"排水渠过弯\"这种非常规技巧\n- 这需要对真实场景的深刻理解和创造性思维\n\nAI 编程也一样\n\n### 啥时候你得接手？\n\n**算法优化场景**（就是上面那个例子）\n- AI 知道常规算法（标准路线）\n- 但不知道\"分组\"这种巧妙优化（排水渠过弯）\n- 你得告诉它这个思路\n- 然后它能飞快实现细节\n\n**没见过的领域**\n- AI 训练数据里没这玩意儿\n- 你得详细说清楚要干啥\n- 来回沟通几轮才能搞定\n\n**业务特殊情况**\n- 行业规则、边界条件\n- 性能要求、取舍权衡\n- 这些你比 AI 清楚\n\n**需要创新的地方**\n- 得跳出常规思路\n- 组合多个技术解决问题\n- AI 能帮忙，但方向得你指\n\n## 怎么更好地\"驾驶\" AI\n\n这次优化给我几个感受：\n\n**别满足于\"能跑\"**\n- 代码能运行不代表写得好\n- 多想想有没有更优解法\n- AI 容易陷入训练数据的惯性\n\n**提供关键思路就够了**\n- 不用写完整代码\n- 提供关键思路\n- AI 会自己把细节补全\n\n**用数据说话**\n- 跑实际测试对比性能\n- 确保优化没搞坏功能\n- 数字比感觉靠谱\n\n**来回迭代很正常**\n- AI 给方案 → 你看看 → 提改进 → AI 优化\n- 这就是 L3 的工作模式\n\n## 离 L5 还有多远？\n\n**L3（现在）**：AI 是副驾驶，你开车\n- 大部分常规任务能搞定\n- 关键决策得你拍板\n- 碰到没见过的必须你接手\n\n**L4（几年后？）**：特定领域自己搞定\n- 像 CRUD 这种可能不用人管了\n- 复杂系统还是得人盯着\n\n**L5（遥远的未来）**：啥场景都能搞\n- 理解任何需求\n- 自己选最优方案\n- 你就当乘客就行\n\n现在的 AI 编程感觉就是 L3 自动驾驶：**大部分路段很稳，但得随时准备接手**。\n\n碰到需要\"排水渠过弯\"的时候——那些需要突破常规的场景——你的经验和创造力还是不可替代的。AI 知道参数和数据，但**只有你知道真实场景里还藏着排水渠这条隐藏赛道**。","url":"https://yupanzi.com/posts/ai-autonomous-driving-drainage-ditch/","title":"AI 编程与自动驾驶到排水渠过弯","summary":"AI 编程有些类似于自动驾驶，但目前自动驾驶还不知道排水渠过弯","date_modified":"2025-10-12T00:45:06.000Z","author":{"name":"yupanzi"},"tags":["AI","Programming","Claude"]},{"id":"https://yupanzi.com/posts/hexo-mermaid-integration/","content_html":"想在技术博客中画流程图、时序图？Mermaid 可以让我们用代码直接绘制各种图表，无需额外的绘图工具。\n\n## 问题背景\n\nHexo 默认的 Markdown 渲染器会把所有代码块（包括 `mermaid`）渲染成带行号的 `<figure><table>` 结构，而 Mermaid.js 只能识别简单的 `<pre class=\"mermaid\">` 标签。我们需要一个轻量的解决方案。\n\n## 实现方案\n\n本文基于 **Hexo Frame 主题**，但方案也适用于其他主题（需调整对应的配置文件路径）。\n\n采用原生配置 + 脚本转换的方式，**无需安装任何 npm 插件**。\n\n### 1. 添加主题配置\n\n**Frame 主题**：编辑 `themes/frame/_config.yml`\n\n**其他主题**：编辑对应主题的 `_config.yml`（如 `themes/next/_config.yml`）\n\n添加 Mermaid 配置：\n\n```yaml\n# mermaid diagram setting\nmermaid_enable: true\nmermaid_version: \"11\"  # mermaid 版本\n```\n\n### 2. 加载 Mermaid.js\n\n**Frame 主题**：编辑 `themes/frame/layout/partials/head.ejs`\n\n**其他主题**：找到主题的 head 模板文件（通常在 `layout/_partial/head.ejs` 或 `layout/partials/head.ejs`）\n\n在 `</head>` 前添加：\n\n```ejs\n<%# mermaid diagram support %>\n<% if(theme.mermaid_enable){ %>\n    <% var mermaidVersion = theme.mermaid_version || '11'; %>\n    <script type=\"module\">\n        import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@<%= mermaidVersion %>/dist/mermaid.esm.min.mjs';\n        mermaid.initialize({\n            startOnLoad: true,\n            theme: 'default'\n        });\n    </script>\n<% } %>\n```\n\n### 3. 添加 HTML 转换脚本\n\n创建 `scripts/mermaid-renderer.js`（**通用方案，适用于所有主题**）：\n\n```javascript\n/**\n * Mermaid renderer for Hexo\n * Converts mermaid code blocks from <figure> to <pre class=\"mermaid\">\n */\n\nhexo.extend.filter.register('after_post_render', function(data) {\n  // Only process if mermaid is enabled\n  if (!hexo.theme.config.mermaid_enable) {\n    return data;\n  }\n\n  // Replace <figure class=\"highlight plaintext\"> containing mermaid code\n  data.content = data.content.replace(\n    /<figure class=\"highlight plaintext\"><table><tr><td class=\"gutter\">[\\s\\S]*?<\\/td><td class=\"code\"><pre>([\\s\\S]*?)<\\/pre><\\/td><\\/tr><\\/table><\\/figure>/g,\n    function(match, code) {\n      // Extract plain text from the code\n      const plainCode = code\n        .replace(/<span class=\"line\">/g, '')\n        .replace(/<\\/span>/g, '')\n        .replace(/<br>/g, '\\n')\n        .trim();\n\n      // Check if it looks like mermaid code\n      const mermaidKeywords = ['graph', 'sequenceDiagram', 'classDiagram', 'stateDiagram', 'gantt', 'pie', 'flowchart', 'erDiagram', 'journey'];\n      const isMermaid = mermaidKeywords.some(keyword => plainCode.startsWith(keyword));\n\n      if (isMermaid) {\n        return '<pre class=\"mermaid\">' + plainCode + '</pre>';\n      }\n\n      return match;\n    }\n  );\n\n  return data;\n});\n```\n\n## 使用示例\n\n### 流程图\n\n```mermaid\ngraph TD\n    A[开始] --> B{需要图表?}\n    B -->|是| C[使用 Mermaid]\n    B -->|否| D[纯文本描述]\n    C --> E[完成]\n    D --> E\n```\n\n### 时序图\n\n```mermaid\nsequenceDiagram\n    participant 客户端\n    participant 服务器\n    participant 数据库\n\n    客户端->>服务器: 发起请求\n    服务器->>数据库: 查询数据\n    数据库-->>服务器: 返回结果\n    服务器-->>客户端: 响应数据\n```\n\n### 甘特图\n\n```mermaid\ngantt\n    title 项目计划\n    dateFormat YYYY-MM-DD\n    section 开发\n    功能开发    :2025-01-01, 5d\n    代码审查    :2025-01-06, 2d\n    section 测试\n    测试验证    :2025-01-08, 3d\n```\n\n## 配置说明\n\n| 配置项 | 说明 | 默认值 |\n|--------|------|--------|\n| `mermaid_enable` | 是否启用 Mermaid | `false` |\n| `mermaid_version` | Mermaid 版本号 | `\"11\"` |\n\n**版本配置示例**：\n- `\"11\"` - 使用 11.x 最新版\n- `\"10.9.0\"` - 使用指定版本\n\n## 主题适配说明\n\n本方案由三部分组成：\n\n1. **主题配置**：在主题的 `_config.yml` 中添加开关和版本配置\n2. **脚本加载**：在主题的 `head` 模板中添加 CDN 脚本（**需要适配主题**）\n3. **HTML 转换**：`scripts/mermaid-renderer.js` 脚本（**通用，无需修改**）\n\n如果你使用的不是 Frame 主题，只需要调整步骤 1 和步骤 2 的文件路径，步骤 3 的脚本完全通用。\n\n## 注意事项\n\n- Hexo 脚本修改后需重启 `hexo server`\n- Mermaid 代码块会自动识别关键字（`graph`、`sequenceDiagram` 等）\n- 如需关闭渲染，设置 `mermaid_enable: false`\n\n更多图表语法参考 [Mermaid 官方文档](https://mermaid.js.org/)。","url":"https://yupanzi.com/posts/hexo-mermaid-integration/","title":"Hexo 添加 Mermaid 图表支持","summary":"用最小修改在 Hexo 中集成 Mermaid 图表渲染","date_modified":"2025-10-11T20:03:05.000Z","author":{"name":"yupanzi"},"tags":["Hexo","Mermaid","Visualization"]},{"id":"https://yupanzi.com/posts/claude-code-configuration/","content_html":"本文整合了 Claude Code、Codex、Gemini 等 AI 编程工具的配置方法，包括命令行工具和 VS Code 插件。\n\n## Claude Code 命令行\n\n### 安装\n\n```bash\nnpm install -g @anthropic-ai/claude-code\n```\n\n### 配置文件\n\n| 系统 | 路径 |\n|------|------|\n| Windows | `%USERPROFILE%\\.claude\\settings.json` |\n| macOS/Linux | `~/.claude/settings.json` |\n\n编辑配置文件：\n\n```bash\n# Windows\nnotepad $env:USERPROFILE\\.claude\\settings.json\n\n# macOS/Linux\nvim ~/.claude/settings.json\n```\n\n写入配置：\n\n```json\n{\n  \"env\": {\n    \"ANTHROPIC_BASE_URL\": \"https://api.example.com/claude\",\n    \"ANTHROPIC_AUTH_TOKEN\": \"sk-xxxx\"\n  }\n}\n```\n\n配置完成后直接运行 `claude` 即可。\n\n---\n\n## Claude Code VS Code 插件\n\n如果你更喜欢在 VS Code 中使用 Claude Code，可以安装插件并配置自定义 API。\n\n### 安装插件\n\n从 VS Code 扩展市场安装 [Claude Code 插件](https://marketplace.visualstudio.com/items?itemName=anthropic.claude-code)。\n\n### 配置 API 地址\n\n1. 打开 VS Code 设置（`Ctrl+,` 或 `Cmd+,`）\n2. 搜索 `claude-code.environmentVariables`\n3. 点击 **Edit in settings.json**\n4. 添加配置：\n\n```json\n{\n  \"claude-code.environmentVariables\": [\n    {\n      \"name\": \"ANTHROPIC_BASE_URL\",\n      \"value\": \"https://api.example.com\"\n    }\n  ]\n}\n```\n\n### 配置 API Key\n\n插件从 `~/.claude/config.json` 读取 API 密钥：\n\n**macOS / Linux:**\n\n```bash\nmkdir -p ~/.claude\necho '{\"primaryApiKey\": \"your-api-key\"}' > ~/.claude/config.json\n```\n\n**Windows:**\n\n```powershell\nNew-Item -ItemType Directory -Force -Path \"$HOME\\.claude\"\nSet-Content -Path \"$HOME\\.claude\\config.json\" -Value '{\"primaryApiKey\": \"your-api-key\"}'\n```\n\n重启 VS Code 后，点击活动栏的 Claude 图标即可开始使用。\n\n---\n\n## Codex CLI\n\n### 安装\n\n```bash\nnpm install -g @openai/codex\n```\n\n### 配置文件\n\n| 系统 | 路径 |\n|------|------|\n| Windows | `%USERPROFILE%\\.codex\\config.toml` |\n| macOS/Linux | `~/.codex/config.toml` |\n\n写入配置：\n\n```toml\nmodel = \"gpt-5\"\nmodel_provider = \"openai-chat-completions\"\n\n[model_providers.openai-chat-completions]\nname = \"PROXY\"\nbase_url = \"https://api.example.com/v1\"\nenv_key = \"PROXY_OPENAI_API_KEY\"\nwire_api = \"chat\"\n```\n\n### 设置环境变量\n\nCodex 需要额外配置环境变量：\n\n**Windows:**\n\n```powershell\n# 临时\n$env:PROXY_OPENAI_API_KEY = \"sk-xxxx\"\n\n# 永久\n[System.Environment]::SetEnvironmentVariable('PROXY_OPENAI_API_KEY', 'sk-xxxx', 'User')\n```\n\n**macOS/Linux:**\n\n```bash\n# 临时\nexport PROXY_OPENAI_API_KEY=\"sk-xxxx\"\n\n# 永久\necho 'export PROXY_OPENAI_API_KEY=\"sk-xxxx\"' >> ~/.bashrc  # 或 ~/.zshrc\nsource ~/.bashrc\n```\n\n---\n\n## Gemini CLI\n\n### 安装\n\n```bash\nnpm install -g @google/gemini-cli\n```\n\n### 配置方式\n\nGemini 没有专门的配置文件，使用环境变量或 `.env` 文件。\n\n**方法一：.env 文件**\n\n```bash\n# Windows: notepad $env:USERPROFILE\\.env\n# macOS/Linux: vim ~/.env\n\nGOOGLE_GEMINI_BASE_URL=https://api.example.com/gemini\nGEMINI_API_KEY=sk-xxxx\nGEMINI_MODEL=gemini-2.5-pro\n```\n\n**方法二：环境变量**\n\nWindows:\n\n```powershell\n[System.Environment]::SetEnvironmentVariable('GOOGLE_GEMINI_BASE_URL', 'https://api.example.com/gemini', 'User')\n[System.Environment]::SetEnvironmentVariable('GEMINI_API_KEY', 'sk-xxxx', 'User')\n[System.Environment]::SetEnvironmentVariable('GEMINI_MODEL', 'gemini-2.5-pro', 'User')\n```\n\nmacOS/Linux:\n\n```bash\ncat >> ~/.bashrc << 'EOF'\nexport GOOGLE_GEMINI_BASE_URL=https://api.example.com/gemini\nexport GEMINI_API_KEY=sk-xxxx\nexport GEMINI_MODEL=gemini-2.5-pro\nEOF\n\nsource ~/.bashrc\n```\n\n---\n\n## 工具对比\n\n| 工具 | 配置方式 | 环境变量 | 难度 |\n|------|---------|---------|------|\n| Claude Code CLI | JSON 配置文件 | 不需要 | 简单 |\n| Claude Code 插件 | VS Code 设置 + JSON | 不需要 | 简单 |\n| Codex | TOML 配置 + 环境变量 | 需要 | 中等 |\n| Gemini CLI | 环境变量或 .env | 需要 | 简单 |\n\n---\n\n## 注意事项\n\n### API Key 安全\n\n把配置文件加到 `.gitignore`：\n\n```\n.env\nsettings.json\nconfig.toml\n```\n\n### 文件权限\n\n**macOS/Linux:**\n\n```bash\nchmod 600 ~/.claude/settings.json\nchmod 600 ~/.codex/config.toml\nchmod 600 ~/.env\n```\n\n**Windows:**\n\n```powershell\nicacls \"$env:USERPROFILE\\.claude\\settings.json\" /inheritance:r /grant:r \"$env:USERNAME:F\"\n```\n\n### 环境变量持久化\n\n| 系统 | 配置文件 |\n|------|----------|\n| Windows | 系统环境变量或 `$PROFILE` |\n| macOS | `~/.zshrc`（默认 Zsh） |\n| Linux | `~/.bashrc` 或 `~/.zshrc` |\n\n### 常见问题\n\n| 问题 | 解决方案 |\n|------|----------|\n| 找不到配置目录 | 手动创建：`mkdir ~/.claude` |\n| 环境变量不生效 | Windows 重启终端，macOS/Linux 执行 `source ~/.bashrc` |\n| 切换不同 API | Codex 可配多个 `model_providers`，修改 `model_provider` 字段 |\n\n---\n\n## 使用建议\n\n- **推荐**：命令行版 Claude Code 比 VS Code 插件更好用\n- 如果使用官方服务，直接登录更方便，不需要这些自定义配置\n- Codex 支持多 provider 切换，适合需要在不同 API 间切换的场景\n\n## 参考资料\n\n- [Claude Code 官方文档](https://docs.anthropic.com/en/docs/claude-code)\n- [CSDN - Claude Code 配置教程](https://blog.csdn.net/weixin_49869937/article/details/152317666)","url":"https://yupanzi.com/posts/claude-code-configuration/","title":"AI 编程工具配置指南","summary":"Claude Code、Codex、Gemini 命令行工具和 VS Code 插件的自定义 API 配置指南","date_modified":"2025-10-10T14:30:37.000Z","author":{"name":"yupanzi"},"tags":["AI","Claude","VSCode","Codex","Gemini"]},{"id":"https://yupanzi.com/posts/nginx-reverse-proxy-ssl-expired-k8s/","content_html":"## 背景\n\n遇到第三方 API 的 SSL 证书过期了，直接访问会报证书错误。可以用 Nginx 反向代理来绕过这个问题。\n\n## Nginx 配置\n\n在 `values.yaml` 里添加自定义的 server 配置：\n\n```yaml\nserverBlock: |\n  server {\n    listen 8080;\n\n    location / {\n      proxy_pass https://api.foobar.com/;\n      proxy_ssl_verify off;  # 关键：忽略 SSL 证书验证\n\n      proxy_set_header Host api.foobar.com;\n      proxy_set_header X-Real-IP $remote_addr;\n      proxy_set_header X-Forwarded-for $proxy_add_x_forwarded_for;\n    }\n  }\n```\n\n## 部署\n\n```bash\nhelm repo add bitnami https://charts.bitnami.com/bitnami\nhelm install my-nginx bitnami/nginx -f values.yaml\n```\n\n## 测试\n\n直接访问 HTTPS（会失败）：\n\n```bash\n# 会报证书错误\ncurl https://api.foobar.com/endpoint\n```\n\n通过 Nginx 代理访问（成功）：\n\n```bash\n# 通过代理访问\ncurl http://my-nginx:8080/endpoint\n```\n\n如果想直接用 curl 忽略证书验证：\n\n```bash\n# -k 参数忽略 SSL 证书\ncurl -k https://api.foobar.com/endpoint\n\n# 或者使用 --insecure\ncurl --insecure https://api.foobar.com/endpoint\n```\n\n## 注意\n\n- 关闭 SSL 验证有安全风险，仅适合内网环境\n- 生产环境还是建议让对方更新证书","url":"https://yupanzi.com/posts/nginx-reverse-proxy-ssl-expired-k8s/","title":"Nginx 反向代理 SSL 过期的网站并部署在 Kubernetes 上","summary":"使用 Nginx 反向代理 SSL 证书过期的网站，并通过 Helm 部署在 Kubernetes 集群上","date_modified":"2025-10-10T10:18:12.000Z","author":{"name":"yupanzi"},"tags":["Helm","Kubernetes","Network","Nginx"]},{"id":"https://yupanzi.com/posts/how-to-start-database-proxy/","content_html":"使用 Nginx Stream 模块代理 TCP 连接，实现数据库端口转发和负载均衡。\n\n## 应用场景\n\n- **端口映射**：本地 33078 → 远程 MySQL 3306\n- **负载均衡**：多个数据库实例分流\n- **连接日志**：记录所有数据库连接信息\n- **安全加固**：隐藏真实数据库地址\n\n## Nginx Stream 配置\n\n### 完整配置示例\n\n`/etc/nginx/nginx.conf`：\n\n```nginx\nuser www-data;\nworker_processes auto;\npid /run/nginx.pid;\ninclude /etc/nginx/modules-enabled/*.conf;\n\n# 增加文件描述符限制\nworker_rlimit_nofile 65535;\n\nevents {\n    worker_connections 10240;\n}\n\n# HTTP 配置（Web 服务）\nhttp {\n    # ... HTTP 配置保留 ...\n    include /etc/nginx/sites-enabled/*;\n}\n\n# Stream 配置（TCP/UDP 代理）\nstream {\n    # 日志格式\n    log_format proxy '$remote_addr [$time_local] '\n                     '$protocol $status $bytes_sent $bytes_received '\n                     '$session_time \"$upstream_addr\" '\n                     '\"$upstream_bytes_sent\" \"$upstream_bytes_received\" \"$upstream_connect_time\"';\n\n    access_log /var/log/nginx/tcp-access.log proxy;\n    error_log /var/log/nginx/tcp-error.log;\n    open_log_file_cache off;\n\n    # MySQL 代理\n    server {\n        listen 33078;\n        proxy_pass mysql.example.com:3306;\n        proxy_timeout 300m;\n        proxy_connect_timeout 10s;\n    }\n\n    # PostgreSQL 代理\n    server {\n        listen 54320;\n        proxy_pass postgres.example.com:5432;\n        proxy_timeout 300m;\n    }\n\n    # Redis 代理\n    server {\n        listen 63790;\n        proxy_pass redis.example.com:6379;\n        proxy_timeout 600s;\n    }\n\n    # MongoDB 代理\n    server {\n        listen 27018;\n        proxy_pass mongodb.example.com:27017;\n        proxy_timeout 300m;\n    }\n}\n```\n\n## 配置说明\n\n### 日志字段\n\n| 字段 | 说明 |\n|------|------|\n| `$remote_addr` | 客户端 IP |\n| `$time_local` | 本地时间 |\n| `$protocol` | 协议（TCP/UDP） |\n| `$status` | 连接状态 |\n| `$bytes_sent` | 发送字节数 |\n| `$bytes_received` | 接收字节数 |\n| `$session_time` | 会话时长 |\n| `$upstream_addr` | 后端服务器地址 |\n| `$upstream_connect_time` | 连接时间 |\n\n### 超时设置\n\n```nginx\nproxy_timeout 300m;           # 会话超时（5 小时）\nproxy_connect_timeout 10s;    # 连接超时（10 秒）\n```\n\n## 负载均衡配置\n\n### 多个 MySQL 实例\n\n```nginx\nstream {\n    # 定义后端服务器组\n    upstream mysql_backend {\n        # 负载均衡策略：least_conn（最少连接）\n        least_conn;\n\n        server mysql1.example.com:3306 weight=2 max_fails=3 fail_timeout=30s;\n        server mysql2.example.com:3306 weight=1 max_fails=3 fail_timeout=30s;\n        server mysql3.example.com:3306 backup;  # 备用服务器\n    }\n\n    server {\n        listen 33078;\n        proxy_pass mysql_backend;\n        proxy_timeout 300m;\n    }\n}\n```\n\n### 负载均衡策略\n\n| 策略 | 说明 |\n|------|------|\n| `round-robin` | 轮询（默认） |\n| `least_conn` | 最少连接数 |\n| `hash $remote_addr` | IP 哈希（会话保持） |\n| `random` | 随机 |\n\n### IP Hash（会话保持）\n\n```nginx\nupstream mysql_backend {\n    hash $remote_addr consistent;\n    server mysql1.example.com:3306;\n    server mysql2.example.com:3306;\n}\n```\n\n## SSL/TLS 加密\n\n### 添加 SSL 层\n\n```nginx\nstream {\n    server {\n        listen 33078 ssl;\n        proxy_pass mysql.example.com:3306;\n\n        ssl_certificate /etc/nginx/ssl/server.crt;\n        ssl_certificate_key /etc/nginx/ssl/server.key;\n        ssl_protocols TLSv1.2 TLSv1.3;\n        ssl_ciphers HIGH:!aNULL:!MD5;\n    }\n}\n```\n\n## 访问控制\n\n### IP 白名单\n\n```nginx\nstream {\n    # 定义允许的 IP\n    geo $allowed_ip {\n        default 0;\n        192.168.1.0/24 1;\n        10.0.0.0/8 1;\n    }\n\n    server {\n        listen 33078;\n\n        # 检查 IP\n        if ($allowed_ip = 0) {\n            return 403;\n        }\n\n        proxy_pass mysql.example.com:3306;\n    }\n}\n```\n\n## 使用示例\n\n### 连接 MySQL\n\n```bash\n# 直接连接（原始）\nmysql -h mysql.example.com -P 3306 -u user -p\n\n# 通过代理连接\nmysql -h proxy.example.com -P 33078 -u user -p\n```\n\n### 连接 PostgreSQL\n\n```bash\npsql -h proxy.example.com -p 54320 -U user -d database\n```\n\n### 连接 Redis\n\n```bash\nredis-cli -h proxy.example.com -p 63790\n```\n\n## 查看连接日志\n\n```bash\n# 实时查看\ntail -f /var/log/nginx/tcp-access.log\n\n# 示例输出：\n# 192.168.1.100 [29/Sep/2024:15:34:32 +0800] TCP 200 1024 2048 300.123 \"mysql.example.com:3306\" \"512\" \"1536\" \"0.003\"\n```\n\n## 性能优化\n\n### 连接池配置\n\n```nginx\nworker_rlimit_nofile 65535;  # 增加文件描述符\n\nevents {\n    worker_connections 10240;  # 增加连接数\n    use epoll;  # Linux 使用 epoll\n}\n```\n\n### 缓冲区调优\n\n```nginx\nstream {\n    server {\n        listen 33078;\n        proxy_pass mysql.example.com:3306;\n\n        proxy_buffer_size 16k;\n        proxy_upload_rate 0;\n        proxy_download_rate 0;\n    }\n}\n```\n\n## 健康检查\n\n需要 Nginx Plus 或第三方模块：\n\n```nginx\nupstream mysql_backend {\n    server mysql1.example.com:3306;\n\n    # Nginx Plus 健康检查\n    health_check interval=5s fails=3 passes=2;\n}\n```\n\n开源版替代方案：使用外部脚本定期检测。\n\n## 注意事项\n\n- Stream 模块默认编译，无需额外安装\n- 与 HTTP 配置在同一个 `nginx.conf` 文件中\n- 日志文件单独配置，避免与 HTTP 日志混淆\n- 超时时间根据实际业务调整（长连接可设置更大值）\n- 生产环境建议启用 SSL/TLS\n- 定期清理日志文件，避免磁盘占满\n\n## 参考资料\n\n- [Nginx Stream 模块文档](https://nginx.org/en/docs/stream/ngx_stream_core_module.html)\n- [Nginx TCP 负载均衡](https://nginx.org/en/docs/stream/ngx_stream_upstream_module.html)","url":"https://yupanzi.com/posts/how-to-start-database-proxy/","title":"使用 Nginx Stream 代理数据库连接","summary":"Nginx Stream 模块实现 MySQL、PostgreSQL 等 TCP 服务代理","date_modified":"2024-09-29T15:34:32.000Z","author":{"name":"yupanzi"},"tags":["Database","Network","Nginx"]},{"id":"https://yupanzi.com/posts/docker-private-services/","content_html":"本文整合了常用私有服务的 Docker 部署配置，包括制品仓库、CI/CD、身份认证和密码管理。\n\n## Nexus 制品仓库\n\nNexus 支持 Docker、Helm、npm、PyPI、Maven 等多种格式的制品管理。\n\n### Docker 部署\n\n```yaml\nversion: '2.0'\n\nservices:\n  nexus:\n    image: sonatype/nexus3\n    container_name: nexus\n    restart: always\n    ports:\n      - \"127.0.0.1:8081:8081\"  # Web UI\n      - \"127.0.0.1:5000:5000\"  # Docker Registry\n    volumes:\n      - ${HOME}/nexus-data:/nexus-data\n```\n\n```bash\n# 获取初始密码\ndocker exec nexus cat /nexus-data/admin.password\n```\n\n### Nginx 反向代理\n\n```nginx\nserver {\n    listen 443 ssl;\n    server_name repo.example.com;\n\n    ssl_certificate /etc/letsencrypt/live/repo.example.com/fullchain.pem;\n    ssl_certificate_key /etc/letsencrypt/live/repo.example.com/privkey.pem;\n\n    client_max_body_size 10G;  # Docker 镜像可能很大\n\n    location / {\n        proxy_pass http://localhost:8081/;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto \"https\";\n    }\n}\n```\n\n### 使用示例\n\n```bash\n# Docker Registry\ndocker login docker.example.com\ndocker tag myapp:latest docker.example.com/myapp:latest\ndocker push docker.example.com/myapp:latest\n\n# Helm 仓库\nhelm repo add myrepo https://repo.example.com/repository/helm-hosted/ \\\n  --username admin --password password\n\n# npm 仓库\nnpm config set registry https://repo.example.com/repository/npm-group/\n```\n\n---\n\n## Jenkins CI/CD\n\n### Docker 部署\n\n```yaml\nversion: '3'\nservices:\n  jenkins:\n    image: jenkins/jenkins:lts\n    user: root\n    restart: always\n    container_name: jenkins\n    ports:\n      - \"127.0.0.1:8080:8080\"\n      - \"127.0.0.1:50000:50000\"  # Agent 连接\n    volumes:\n      - ${HOME}/jenkins/jenkins_home:/var/jenkins_home\n      - /var/run/docker.sock:/var/run/docker.sock  # Docker in Docker\n      - ${HOME}/.ssh:/root/.ssh\n    environment:\n      - JENKINS_OPTS=--sessionTimeout=43200\n```\n\n```bash\n# 查看初始密码\ndocker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword\n```\n\n### Nginx 反向代理（含 WebSocket）\n\n```nginx\nmap $http_upgrade $connection_upgrade {\n  default upgrade;\n  '' close;\n}\n\nupstream jenkins {\n  keepalive 32;\n  server 127.0.0.1:8080;\n}\n\nserver {\n  listen 443 ssl;\n  server_name jenkins.example.com;\n\n  ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;\n  ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;\n\n  ignore_invalid_headers off;\n\n  location / {\n    sendfile off;\n    proxy_pass http://jenkins;\n    proxy_http_version 1.1;\n\n    # WebSocket 支持\n    proxy_set_header Connection $connection_upgrade;\n    proxy_set_header Upgrade $http_upgrade;\n\n    proxy_set_header Host $host;\n    proxy_set_header X-Real-IP $remote_addr;\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    proxy_set_header X-Forwarded-Proto $scheme;\n\n    proxy_buffering off;\n    proxy_request_buffering off;\n  }\n}\n```\n\n### Pipeline 示例\n\n```groovy\npipeline {\n  agent any\n  environment {\n    DOCKER_REGISTRY = 'docker.example.com'\n    IMAGE_NAME = 'myapp'\n  }\n  stages {\n    stage('Build') {\n      steps {\n        sh 'docker build -t ${DOCKER_REGISTRY}/${IMAGE_NAME}:${BUILD_NUMBER} .'\n      }\n    }\n    stage('Push') {\n      steps {\n        sh 'docker push ${DOCKER_REGISTRY}/${IMAGE_NAME}:${BUILD_NUMBER}'\n      }\n    }\n  }\n}\n```\n\n---\n\n## OpenLDAP 身份认证\n\n### Docker 部署\n\n```yaml\nversion: '3.8'\n\nservices:\n  openldap:\n    image: osixia/openldap:1.5.0\n    restart: always\n    container_name: ldap\n    hostname: ldap.example.com\n    ports:\n      - \"389:389\"\n      - \"636:636\"\n    environment:\n      - LDAP_ORGANISATION=MyCompany\n      - LDAP_DOMAIN=example.com\n      - LDAP_ADMIN_PASSWORD=adminPassword\n    volumes:\n      - ${PWD}/ldap:/var/lib/ldap\n      - ${PWD}/slapd.d:/etc/ldap/slapd.d\n\n  phpldapadmin:\n    image: osixia/phpldapadmin:latest\n    container_name: phpldapadmin\n    ports:\n      - \"8080:80\"\n    environment:\n      - PHPLDAPADMIN_LDAP_HOSTS=ldap\n      - PHPLDAPADMIN_HTTPS=false\n    depends_on:\n      - openldap\n```\n\n### 添加用户\n\n`add-user.ldif`:\n\n```ldif\ndn: uid=john,ou=Users,dc=example,dc=com\nobjectClass: inetOrgPerson\nobjectClass: posixAccount\nuid: john\ncn: John Doe\nsn: Doe\nmail: john@example.com\nuserPassword: {SSHA}xxxxx\nuidNumber: 10000\ngidNumber: 10000\nhomeDirectory: /home/john\n```\n\n```bash\n# 生成密码\ndocker exec ldap slappasswd\n\n# 添加用户\ndocker exec ldap ldapadd -x -D \"cn=admin,dc=example,dc=com\" -w adminPassword -f /tmp/add-user.ldif\n\n# 搜索用户\nldapsearch -x -H ldap://localhost:389 -D \"cn=admin,dc=example,dc=com\" -w adminPassword \\\n  -b \"dc=example,dc=com\" \"(uid=john)\"\n```\n\n### 应用集成示例\n\n**Grafana:**\n\n```toml\n[[servers]]\nhost = \"ldap.example.com\"\nport = 389\nbind_dn = \"cn=admin,dc=example,dc=com\"\nbind_password = \"adminPassword\"\nsearch_filter = \"(uid=%s)\"\nsearch_base_dns = [\"ou=Users,dc=example,dc=com\"]\n```\n\n---\n\n## Vaultwarden 密码管理\n\nVaultwarden 是 Bitwarden 的轻量级开源替代，资源占用极低。\n\n### Docker 部署\n\n```yaml\nversion: \"3.9\"\n\nservices:\n  vaultwarden:\n    image: vaultwarden/server:latest\n    container_name: vaultwarden\n    restart: unless-stopped\n    environment:\n      - WEBSOCKET_ENABLED=true\n      - SIGNUPS_ALLOWED=false\n      - ADMIN_TOKEN=your-secure-random-token\n      - DOMAIN=https://vault.example.com\n    volumes:\n      - ${HOME}/vaultwarden:/data\n    ports:\n      - \"127.0.0.1:8080:80\"\n      - \"127.0.0.1:3012:3012\"  # WebSocket\n```\n\n```bash\n# 生成管理员 Token\nopenssl rand -base64 48\n```\n\n### Nginx 反向代理\n\n```nginx\nserver {\n  listen 443 ssl http2;\n  server_name vault.example.com;\n\n  ssl_certificate /etc/letsencrypt/live/vault.example.com/fullchain.pem;\n  ssl_certificate_key /etc/letsencrypt/live/vault.example.com/privkey.pem;\n\n  client_max_body_size 128M;\n\n  # Web UI\n  location / {\n    proxy_pass http://localhost:8080;\n    proxy_set_header Host $host;\n    proxy_set_header X-Real-IP $remote_addr;\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    proxy_set_header X-Forwarded-Proto $scheme;\n  }\n\n  # WebSocket\n  location /notifications/hub {\n    proxy_pass http://localhost:3012;\n    proxy_set_header Upgrade $http_upgrade;\n    proxy_set_header Connection \"upgrade\";\n  }\n\n  location /notifications/hub/negotiate {\n    proxy_pass http://localhost:8080;\n  }\n}\n```\n\n### 客户端使用\n\n1. 安装 Bitwarden 浏览器扩展/App\n2. 设置 → 自托管环境\n3. 服务器 URL：`https://vault.example.com`\n4. 登录使用\n\n---\n\n## 服务对比\n\n| 服务 | 用途 | 端口 | 资源占用 |\n|------|------|------|----------|\n| **Nexus** | 制品仓库 | 8081, 5000 | 中等 |\n| **Jenkins** | CI/CD | 8080, 50000 | 较高 |\n| **OpenLDAP** | 身份认证 | 389, 636 | 低 |\n| **Vaultwarden** | 密码管理 | 80, 3012 | 极低 |\n\n---\n\n## 通用注意事项\n\n### 安全配置\n\n- **HTTPS**：所有服务通过 Nginx 反向代理启用 HTTPS\n- **防火墙**：只开放必要端口，内部服务绑定 127.0.0.1\n- **密码**：使用强密码，定期更换\n\n### 备份策略\n\n```bash\n# 通用备份脚本\n#!/bin/bash\nBACKUP_DIR=~/backups\nDATE=$(date +%Y%m%d)\n\n# Nexus\ntar -czf $BACKUP_DIR/nexus-$DATE.tar.gz ~/nexus-data\n\n# Jenkins\ntar -czf $BACKUP_DIR/jenkins-$DATE.tar.gz ~/jenkins/jenkins_home\n\n# LDAP\ndocker exec ldap slapcat > $BACKUP_DIR/ldap-$DATE.ldif\n\n# Vaultwarden\ntar -czf $BACKUP_DIR/vault-$DATE.tar.gz ~/vaultwarden\n\n# 清理 30 天前的备份\nfind $BACKUP_DIR -name \"*.tar.gz\" -mtime +30 -delete\n```\n\n### SSL 证书\n\n使用 Let's Encrypt 自动证书：\n\n```bash\ncertbot certonly --nginx -d repo.example.com -d jenkins.example.com -d vault.example.com\n```\n\n---\n\n## 参考资料\n\n- [Nexus 官方文档](https://help.sonatype.com/)\n- [Jenkins 反向代理配置](https://www.jenkins.io/doc/book/system-administration/reverse-proxy-configuration-nginx/)\n- [OpenLDAP Docker 镜像](https://github.com/osixia/docker-openldap)\n- [Vaultwarden Wiki](https://github.com/dani-garcia/vaultwarden/wiki)","url":"https://yupanzi.com/posts/docker-private-services/","title":"Docker 私有服务部署指南","summary":"Nexus、Jenkins、LDAP、Vaultwarden 等私有服务的 Docker 部署指南","date_modified":"2024-09-29T15:11:40.000Z","author":{"name":"yupanzi"},"tags":["Docker","CI/CD","DevOps"]},{"id":"https://yupanzi.com/posts/authentik-oauth-integration/","content_html":"本文整合了多个常用应用集成 Authentik OAuth 认证的配置方法，包括 Airflow、Grafana 和 JupyterHub。\n\n## 通用 Authentik 配置\n\n在 Authentik 中创建 OAuth2/OpenID 应用时，需要配置以下端点：\n\n| 端点 | URL 格式 |\n|------|----------|\n| 授权 | `https://authentik.example.com/application/o/authorize/` |\n| Token | `https://authentik.example.com/application/o/token/` |\n| UserInfo | `https://authentik.example.com/application/o/userinfo/` |\n| 元数据 | `https://authentik.example.com/application/o/{app}/.well-known/openid-configuration` |\n\n**重要提示**：`userinfo` 端点的路径必须带斜杠 `/userinfo/`，否则会报错。\n\n---\n\n## Airflow 集成\n\n### 版本信息\n- App Version: 2.8.3\n- Chart Version: 1.13.1\n\n### OAuth 认证配置\n\n`webserver_config.py`:\n\n```python\nfrom flask_appbuilder.security.manager import AUTH_OAUTH\nfrom airflow.auth.managers.fab.security_manager.override import FabAirflowSecurityManagerOverride\n\nAUTH_TYPE = AUTH_OAUTH\nAUTH_USER_REGISTRATION = True\nAUTH_USER_REGISTRATION_ROLE = \"User\"  # 默认角色\n\nOAUTH_PROVIDERS = [\n    {\n        \"name\": \"OAuth\",\n        \"icon\": \"fa-circle-o\",\n        \"token_key\": \"access_token\",\n        \"remote_app\": {\n            \"client_id\": \"your-client-id\",\n            \"api_base_url\": \"https://authentik.example.com/application/o/\",\n            \"client_secret\": \"your-client-secret\",\n            \"client_kwargs\": {\"scope\": \"openid email profile\"},\n            \"server_metadata_url\": \"https://authentik.example.com/application/o/oauth/.well-known/openid-configuration\",\n        },\n    }\n]\n\nPERMANENT_SESSION_LIFETIME = 259200  # 3 天\n\n\nclass CustomSecurityManager(FabAirflowSecurityManagerOverride):\n    def get_oauth_user_info(self, provider, resp):\n        if provider != \"OAuth\":\n            return {}\n\n        # 关键：必须是 userinfo/ 而不是 userinfo\n        me = self.appbuilder.sm.oauth_remotes[provider].get(\"userinfo/\")\n        data = me.json()\n\n        return {\n            \"email\": data[\"email\"],\n            \"username\": data.get(\"name\", \"\"),\n            \"first_name\": data.get(\"given_name\", \"\"),\n            \"role_keys\": data.get(\"groups\", []),  # 支持组映射\n        }\n\n\nSECURITY_MANAGER_CLASS = CustomSecurityManager\n```\n\n### LDAP 认证配置（备选）\n\n```python\nfrom flask_appbuilder.security.manager import AUTH_LDAP\n\nAUTH_TYPE = AUTH_LDAP\nAUTH_LDAP_SERVER = \"ldap://ldap.example.com\"\nAUTH_LDAP_USE_TLS = False\n\nAUTH_USER_REGISTRATION = True\nAUTH_USER_REGISTRATION_ROLE = \"Admin\"\n\nAUTH_LDAP_LASTNAME_FIELD = \"cn\"\nAUTH_LDAP_EMAIL_FIELD = \"mail\"\nAUTH_LDAP_SEARCH = \"ou=foo,ou=People,dc=example,dc=com\"\nAUTH_LDAP_UID_FIELD = \"uid\"\nAUTH_LDAP_BIND_USER = \"cn=admin,dc=example,dc=com\"\nAUTH_LDAP_BIND_PASSWORD = \"adminPassword\"\n\nAUTH_ROLES_MAPPING = {\n    \"cn=g-admin,ou=Group,dc=example,dc=com\": [\"Admin\"],\n}\n\nAUTH_LDAP_GROUP_FIELD = \"memberOf\"\nAUTH_ROLES_SYNC_AT_LOGIN = True\nPERMANENT_SESSION_LIFETIME = 86400\n```\n\n### API 认证\n\n```yaml\nconfig:\n  api:\n    auth_backends: \"airflow.api.auth.backend.basic_auth\"\n```\n\n---\n\n## Grafana 集成\n\n### OAuth 认证配置\n\n`grafana.ini`:\n\n```yaml\ngrafana:\n  grafana.ini:\n    auth:\n      oauth_allow_insecure_email_lookup: true\n\n    auth.generic_oauth:\n      enabled: true\n      name: \"OAuth\"\n      client_id: \"your-client-id\"\n      client_secret: \"your-client-secret\"\n      scopes: \"openid email profile offline_access\"\n      auth_url: \"https://authentik.example.com/application/o/authorize/\"\n      token_url: \"https://authentik.example.com/application/o/token/\"\n      api_url: \"https://authentik.example.com/application/o/userinfo/\"\n      use_pkce: true\n      use_refresh_token: true\n      allow_sign_up: false\n      allow_assign_grafana_admin: true\n      auto_login: true\n      skip_org_role_sync: true\n\n    server:\n      root_url: \"https://grafana.example.com/\"  # 必须正确配置\n```\n\n### 关键配置说明\n\n| 配置项 | 说明 | 注意事项 |\n|--------|------|----------|\n| `root_url` | Grafana 访问地址 | **必须与实际域名一致**，否则会重定向失败 |\n| `oauth_allow_insecure_email_lookup` | 邮箱查找 | 使用邮箱匹配用户时需设为 `true` |\n| `use_pkce` | PKCE 支持 | 增强安全性 |\n\n### LDAP 认证配置（备选）\n\n```yaml\ngrafana:\n  ldap:\n    enabled: true\n    config: |\n      verbose_logging = true\n\n      [[servers]]\n      host = \"ldap.example.com\"\n      port = 389\n      use_ssl = false\n      bind_dn = \"cn=admin,dc=example,dc=com\"\n      bind_password = \"examplePassword\"\n      search_filter = \"(uid=%s)\"\n      search_base_dns = [\"ou=foo,dc=example,dc=com\"]\n\n      [servers.attributes]\n      name = \"cn\"\n      surname = \"sn\"\n      username = \"uid\"\n      email = \"mail\"\n\n      [[servers.group_mappings]]\n      group_dn = \"cn=g-admin,ou=Group,dc=example,dc=com\"\n      org_role = \"Editor\"\n```\n\n---\n\n## JupyterHub 集成\n\n### OAuth 认证配置\n\n`values.yaml`:\n\n```yaml\nhub:\n  config:\n    Authenticator:\n      admin_users:\n        - \"admin\"\n      allow_all: true\n      auto_login: true\n\n    GenericOAuthenticator:\n      client_id: \"your-client-id\"\n      client_secret: \"your-client-secret\"\n      login_service: \"OAuth\"\n      authorize_url: \"https://authentik.example.com/application/o/authorize/\"\n      token_url: \"https://authentik.example.com/application/o/token/\"\n      userdata_url: \"https://authentik.example.com/application/o/userinfo/\"\n      oauth_callback_url: \"https://jupyter.example.com/hub/oauth_callback\"\n      scope:\n        - \"openid\"\n        - \"email\"\n        - \"profile\"\n      username_claim: \"sub\"\n\n    JupyterHub:\n      authenticator_class: \"generic-oauth\"\n      admin_access: true\n```\n\n### 配置说明\n\n| 配置项 | 说明 | 可选值 |\n|--------|------|--------|\n| `allow_all` | 是否允许所有用户 | `true` / `false` |\n| `username_claim` | 用户名字段 | `sub` / `email` / `preferred_username` |\n| `oauth_callback_url` | 回调地址 | 必须在 Authentik 中配置 |\n\n### LDAP 认证配置（备选）\n\n```yaml\nhub:\n  config:\n    LDAPAuthenticator:\n      server_address: \"ldap.example.com\"\n      use_ssl: true\n      bind_dn_template:\n        - \"uid={username},ou=foo,ou=People,dc=example,dc=com\"\n      escape_userdn: true\n\n    JupyterHub:\n      authenticator_class: \"ldapauthenticator.LDAPAuthenticator\"\n```\n\n---\n\n## OAuth vs LDAP 对比\n\n| 特性 | OAuth | LDAP |\n|------|-------|------|\n| 配置复杂度 | 简单 | 中等 |\n| 统一登录（SSO） | ✅ 支持 | ❌ 不支持 |\n| 离线访问 | ✅ Refresh Token | ❌ 需在线验证 |\n| 安全性 | ✅ PKCE 增强 | 一般 |\n| 适用场景 | 现代 SSO 架构 | 传统 LDAP 环境 |\n\n---\n\n## 常见问题\n\n### userinfo 路径问题\n\nAuthentik 的 userinfo 端点必须是 `/userinfo/`（带斜杠），否则会报错：\n```\nERROR - OAUTH userinfo does not have username or email {}\n```\n\n### 重定向 URI 不匹配\n\nGrafana 报错 `redirect_uri_mismatch`：确保 `root_url` 与实际访问域名一致。\n\n### 回调地址配置\n\nJupyterHub 的 `oauth_callback_url` 必须在 Authentik 应用的\"重定向 URI\"中添加。\n\n---\n\n## 参考文档\n\n- [Airflow OAuth 文档](https://airflow.apache.org/docs/apache-airflow/2.8.3/security/webserver.html)\n- [Grafana OAuth 配置](https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/generic-oauth/)\n- [JupyterHub OAuth 认证](https://z2jh.jupyter.org/en/stable/administrator/authentication.html)","url":"https://yupanzi.com/posts/authentik-oauth-integration/","title":"应用集成 Authentik OAuth 认证指南","summary":"Airflow、Grafana、JupyterHub 等应用集成 Authentik OAuth2 认证的完整配置指南","date_modified":"2024-05-31T14:22:19.000Z","author":{"name":"yupanzi"},"tags":["Authentication","OAuth2","Authentik"]},{"id":"https://yupanzi.com/posts/sentry-k8s-deployment/","content_html":"在 Kubernetes 集群中部署 Sentry 错误监控系统。官方没有提供 K8s 部署文档，但我们可以使用社区的 [Helm Chart](https://github.com/sentry-kubernetes/charts)。\n\n## 版本信息\n\n- App Version: 24.5.0\n- Chart Version: 23.1.0\n\n## 核心配置\n\n### values.yaml\n\n```yaml\n# 文件存储配置\nfilestore:\n  backend: filesystem\n  filesystem:\n    path: /var/lib/sentry/files\n    persistence:\n      accessMode: ReadWriteMany  # 需要 ReadWriteMany 存储\n      enabled: true\n      persistentWorkers: true\n      size: 10Gi\n      storageClass: \"efs\"  # 使用支持 RWX 的存储类\n\n# Ingress 配置\nnginx:\n  ingress:\n    enabled: true\n    hostname: sentry.company.com\n    ingressClassName: \"alb\"  # 使用 AWS ALB\n    path: /\n    pathType: Prefix\n\n# 数据库配置\npostgresql:\n  postgresqlPassword: \"examplePassword\"\n  postgresqlPostgresPassword: \"examplePassword\"\n\n# Source Maps 支持\nsourcemaps:\n  enabled: true\n\n# 初始管理员用户\nuser:\n  create: true\n  email: example@company.com\n  password: examplePassword\n\n# Zookeeper 数据清理\nzookeeper:\n  autopurge:\n    purgeInterval: 3\n    snapRetainCount: 3\n```\n\n### 配置说明\n\n| 配置项 | 说明 | 注意事项 |\n|--------|------|----------|\n| filestore | 文件存储 | 必须使用 ReadWriteMany 存储类 |\n| postgresql | 数据库密码 | 防止启动报错，[详见 issue](https://github.com/sentry-kubernetes/charts/issues/571#issuecomment-1039616281) |\n| nginx.ingress | 入口配置 | 根据实际 Ingress Controller 调整 |\n| zookeeper.autopurge | 自动清理 | 避免数据过大 |\n\n## ClickHouse 报错修复\n\n### 问题现象\n\n部署后 `sentry-clickhouse` Pod 一直处于 `CrashLoopBackOff` 状态：\n\n```bash\nsnuba.clickhouse.errors.ClickhouseWriterError: Method write is not supported\nby storage Distributed with more than one shard and no sharding key provided\n```\n\n### 原因分析\n\n分布式表 `metrics_raw_v2_dist` 缺少 sharding key 配置。相关 issue：\n- [snuba#4897](https://github.com/getsentry/snuba/issues/4897)\n- [charts#1272](https://github.com/sentry-kubernetes/charts/issues/1272)\n- [charts#1042](https://github.com/sentry-kubernetes/charts/issues/1042)\n\n### 解决步骤\n\n进入 ClickHouse 容器重建表：\n\n```bash\n# 进入容器\nkubectl exec -it sentry-clickhouse-0 -- bash\n\n# 连接 ClickHouse\nclickhouse-client -h sentry-clickhouse\n\n# 删除旧表\nDROP TABLE default.metrics_raw_v2_dist ON CLUSTER 'sentry-clickhouse' SYNC;\n\n# 创建新表（带 sharding key）\nCREATE TABLE default.metrics_raw_v2_dist ON CLUSTER 'sentry-clickhouse'\n(\n    `use_case_id` LowCardinality(String),\n    `org_id` UInt64,\n    `project_id` UInt64,\n    `metric_id` UInt64,\n    `timestamp` DateTime,\n    `tags.key` Array(UInt64),\n    `tags.value` Array(UInt64),\n    `metric_type` LowCardinality(String),\n    `set_values` Array(UInt64),\n    `count_value` Float64,\n    `distribution_values` Array(Float64),\n    `materialization_version` UInt8,\n    `retention_days` UInt16,\n    `partition` UInt16,\n    `offset` UInt64,\n    `timeseries_id` UInt32\n)\nENGINE = Distributed('sentry-clickhouse', 'default', 'metrics_raw_v2_local',\n                     sipHash64('timeseries_id'));  -- 关键：添加 sharding key\n```\n\n执行后 Pod 会恢复正常。\n\n## 替代方案\n\n如果 Sentry 过于复杂，可以考虑 [GlitchTip](https://glitchtip.com/) - 一个轻量级的 Sentry 替代品。\n\n## 注意事项\n\n- 存储类必须支持 **ReadWriteMany**（如 EFS、NFS）\n- PostgreSQL 密码务必提前配置，否则可能导致初始化失败\n- ClickHouse 问题需要手动修复，Chart 未自动处理","url":"https://yupanzi.com/posts/sentry-k8s-deployment/","title":"Sentry 在 K8s 中的部署与问题解决","summary":"使用 Helm 在 K8s 部署 Sentry，解决 ClickHouse 分布式表问题","date_modified":"2024-05-31T09:58:29.000Z","author":{"name":"yupanzi"},"tags":["Database","Helm","Kubernetes"]},{"id":"https://yupanzi.com/posts/fibonacci-sequence-time-complexity/","content_html":"计算斐波那契数列的第 n 个数有多种方法，不同方法的时间复杂度差异很大。我们来看看常见的几种实现方式。\n\n## 什么是斐波那契数列\n\n除了第一个和第二个数外，任意一个数都等于前两个数之和：\n\n```\n0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...\n```\n\n## 四种实现方法对比\n\n| 方法 | 时间复杂度 | 空间复杂度 | 特点 |\n|------|-----------|-----------|------|\n| 递归 | O(2^n) | O(n) | 简单但效率低 |\n| 动态规划 | O(n) | O(n) | 避免重复计算 |\n| 矩阵快速幂 | O(log n) | O(1) | 最快的精确方法 |\n| Binet 公式 | O(1) | O(1) | 有精度限制 |\n\n## 递归方法\n\n最直观的实现，但效率很低：\n\n```python\ndef fibonacci_recursive(n):\n    if n <= 1:\n        return n\n    # 每次都会重复计算大量子问题\n    return fibonacci_recursive(n-1) + fibonacci_recursive(n-2)\n```\n\n**问题**：计算 `f(5)` 时，`f(3)` 会被计算 2 次，`f(2)` 会被计算 3 次。\n\n## 动态规划\n\n用数组存储已计算的值，避免重复计算：\n\n```python\ndef fibonacci_dp(n):\n    if n <= 1:\n        return n\n    # 存储所有中间结果\n    fib = [0] * (n+1)\n    fib[1] = 1\n    for i in range(2, n+1):\n        fib[i] = fib[i-1] + fib[i-2]\n    return fib[n]\n```\n\n**优化**：只需要保存前两个数，空间复杂度可以降到 O(1)。\n\n## 矩阵快速幂\n\n利用矩阵乘法性质，通过快速幂算法加速：\n\n```python\ndef matrix_multiply(a, b):\n    return [[sum(x * y for x, y in zip(row, col)) for col in zip(*b)] for row in a]\n\ndef matrix_power(matrix, n):\n    result = [[1, 0], [0, 1]]  # 单位矩阵\n    while n > 0:\n        if n % 2 == 1:\n            result = matrix_multiply(result, matrix)\n        matrix = matrix_multiply(matrix, matrix)\n        n //= 2\n    return result\n\ndef fibonacci_matrix(n):\n    if n <= 1:\n        return n\n    matrix = [[1, 1], [1, 0]]\n    powered_matrix = matrix_power(matrix, n-1)\n    return powered_matrix[0][0]\n```\n\n**原理**：利用 `[[1,1],[1,0]]^n` 的性质快速计算。\n\n## Binet 公式\n\n使用黄金分割比直接计算：\n\n```python\nimport math\n\ndef fibonacci_binet(n):\n    sqrt_5 = math.sqrt(5)\n    phi = (1 + sqrt_5) / 2  # 黄金分割比\n    return round((phi**n - (-1/phi)**n) / sqrt_5)\n```\n\n## 注意事项\n\n- **递归方法**仅适合小数值，`n > 40` 时会非常慢\n- **动态规划**是实际工作中最常用的方法，平衡了效率和易读性\n- **矩阵快速幂**适合需要计算超大数值的场景\n- **Binet 公式**在 `n` 很大时会因浮点数精度问题产生误差","url":"https://yupanzi.com/posts/fibonacci-sequence-time-complexity/","title":"斐波那契数列的时间复杂度分析","summary":"对比递归、动态规划、矩阵快速幂等方法的时间复杂度和实现","date_modified":"2024-05-16T16:34:22.000Z","author":{"name":"yupanzi"},"tags":["Algorithm","Python"]},{"id":"https://yupanzi.com/posts/base64-create-different/","content_html":"## 问题\n\n为什么 Shell 中的 `echo 'abc' | base64` 和 Python 中的结果不一样？\n\n## 原因\n\nShell 的 `echo` 命令默认会在输出末尾添加换行符 `\\n`，这个换行符也会被 base64 编码。\n\n## 解决方法\n\n### Shell 中不添加换行符\n\n使用 `echo -n` 选项：\n\n```bash\necho -n 'abc' | base64\n```\n\n### Python 中不添加换行符\n\n```python\nimport base64\n\ndata = 'abc'\nencoded_data = base64.b64encode(data.encode('utf-8'))\nprint(encoded_data.decode('utf-8'))\n```\n\n现在两者结果一致！\n\n### Python 中添加换行符（模拟默认 echo）\n\n```python\nimport base64\n\ndata = 'abc\\n'  # 添加换行符\nencoded_data = base64.b64encode(data.encode('utf-8'))\nprint(encoded_data.decode('utf-8'))\n```\n\n## Python Base64 常用方法\n\n### 标准编码/解码\n\n```python\nimport base64\n\n# 原始数据\ndata = \"Python Base64 编码测试！\"\n\n# 编码\ndata_bytes = data.encode('utf-8')\nencoded_data = base64.b64encode(data_bytes)\nprint(\"编码后:\", encoded_data)\n\n# 解码\ndecoded_data = base64.b64decode(encoded_data)\nprint(\"解码后:\", decoded_data.decode('utf-8'))\n```\n\n### URL 安全编码/解码\n\n将 `+` 和 `/` 替换为 `-` 和 `_`，适用于 URL 参数：\n\n```python\n# URL 安全编码\nurlsafe_encoded = base64.urlsafe_b64encode(data_bytes)\nprint(\"URL安全编码:\", urlsafe_encoded)\n\n# URL 安全解码\nurlsafe_decoded = base64.urlsafe_b64decode(urlsafe_encoded)\nprint(\"URL安全解码:\", urlsafe_decoded.decode('utf-8'))\n```\n\n## 常用方法对比\n\n| 方法 | 说明 |\n|------|------|\n| `b64encode(s)` | 标准 Base64 编码 |\n| `b64decode(s)` | 标准 Base64 解码 |\n| `urlsafe_b64encode(s)` | URL 安全编码（替换 +/） |\n| `urlsafe_b64decode(s)` | URL 安全解码 |\n\n## 注意事项\n\n- Base64 是编码方式，不是加密方法\n- 不应该用于保护敏感信息\n- Shell 和 Python 编码差异主要源于换行符","url":"https://yupanzi.com/posts/base64-create-different/","title":"Base64 编码差异问题","summary":"Shell 和 Python 中 Base64 编码结果不同的原因及解决方法","date_modified":"2024-05-16T16:23:13.000Z","author":{"name":"yupanzi"},"tags":["Algorithm","Python","Shell"]},{"id":"https://yupanzi.com/posts/git-cicd-tips/","content_html":"Git 版本控制和 CI/CD 流水线的常用技巧和最佳实践。\n\n## Pull vs Fetch\n\n`git pull` 和 `git fetch` 的区别：\n\n```\ngit pull = git fetch + merge to local\n```\n\n| 命令 | 作用 |\n|------|------|\n| `git fetch` | 只下载远程更新到本地仓库，不合并 |\n| `git pull` | 下载远程更新并自动合并到当前分支 |\n\n**建议**：先 `fetch` 查看变更，再决定是否 `merge` 或 `rebase`。\n\n> 参考: [Ruby China - git pull 和 git fetch 有什么区别](https://ruby-china.org/topics/15729)\n\n---\n\n## Rebase vs Merge\n\n### Merge（合并）\n\n- 创建新的合并提交\n- 保留非线性历史\n- 保持完整的分支记录\n- 适合公共分支间合并\n\n### Rebase（变基）\n\n- 重写提交历史\n- 创建线性历史\n- 看起来像顺序开发\n- 适合特性分支更新\n\n### 使用原则\n\n| 原则 | 说明 |\n|------|------|\n| 公共分支用 merge | 不要在公共分支（main/develop）上 rebase |\n| 特性分支用 rebase | 更新特性分支或准备合并到主分支 |\n| 团队统一规范 | 确保团队理解并遵守相同的工作流程 |\n| 操作前备份 | rebase 前创建备份分支或推送到远程 |\n\n---\n\n## 切换默认分支（main → master）\n\n### 1. 创建并推送 master 分支\n\n```bash\n# 从 main 创建 master\ngit branch master main\n\n# 推送到远程\ngit push origin master\n```\n\n### 2. 在远程仓库更改默认分支\n\n- **GitHub**: Settings → Branches → Default branch → 选择 master\n- **GitLab**: Settings → Repository → Default Branch → 选择 master\n- **Bitbucket**: Repository settings → Branches → Main branch → 选择 master\n\n### 3. 删除旧的 main 分支（可选）\n\n```bash\n# 切换到 master\ngit checkout master\n\n# 删除本地 main\ngit branch -d main\n\n# 删除远程 main\ngit push origin --delete main\n```\n\n**注意**：更改默认分支会影响其他开发者和 CI/CD，需提前通知团队。\n\n---\n\n## 分支同步（develop → master）\n\n### 方法一：Reset（重置，丢弃所有修改）\n\n```bash\n# 拉取最新 master\ngit checkout master\ngit pull origin master\n\n# 重置 develop 到 master\ngit checkout develop\ngit reset --hard origin/master\n```\n\n**警告**：会丢弃 develop 上所有不在 master 的提交。\n\n### 方法二：Merge（合并，保留历史）\n\n```bash\ngit checkout develop\ngit merge master\n```\n\n保留 develop 的提交历史，将 master 的更改合并进来。\n\n### 方法三：Rebase（变基，线性历史）\n\n```bash\ngit checkout develop\ngit rebase master\n```\n\n将 develop 的更改重新应用到 master 之上，可能产生冲突。\n\n### 方法四：重建分支（彻底同步）\n\n```bash\ngit checkout master\ngit branch -D develop           # 删除旧 develop\ngit checkout -b develop         # 基于 master 创建新 develop\ngit push -f origin develop      # 强制推送\n```\n\n**警告**：强制推送会重写远程历史，需与团队沟通。\n\n### 安全建议\n\n操作前创建备份：\n\n```bash\ngit checkout develop\ngit branch backup_develop  # 创建备份分支\n```\n\n出现问题时可用备份恢复。\n\n---\n\n## GitLab CI/CD 并行执行\n\n### 并行执行原理\n\nGitLab CI/CD 中，同一个 stage 的多个 job 默认会并行执行（前提是有足够的 Runner）。\n\n### 方法一：同一 Stage 多个 Job\n\n```yaml\nstages:\n  - build\n  - test\n\njob1:\n  stage: build\n  script:\n    - echo \"This is job1\"\n\njob2:\n  stage: build\n  script:\n    - echo \"This is job2\"\n\ntest1:\n  stage: test\n  script:\n    - echo \"This is test1\"\n\ntest2:\n  stage: test\n  script:\n    - echo \"This is test2\"\n```\n\n- `job1` 和 `job2` 在 `build` 阶段并行执行\n- `test1` 和 `test2` 在 `test` 阶段并行执行\n\n### 方法二：使用 parallel 关键字\n\n将单个作业分成多个并行实例：\n\n```yaml\ntest:\n  stage: test\n  script:\n    - echo \"This is a parallel test\"\n  parallel: 3\n```\n\n这会启动 3 个并行的 `test` 作业实例。\n\n### Runner 并发配置\n\n确保 GitLab Runner 支持并发执行。\n\n在 Runner 配置文件中设置 `concurrent` 值：\n\n```toml\nconcurrent = 10\n```\n\n如果并发数设置过低，作业会排队等待，无法真正并行。\n\n### 注意事项\n\n- 并行执行需要足够的可用 Runner\n- Runner 的 `concurrent` 值决定了最大并发作业数\n- 不同 stage 之间是串行执行的\n- 使用 `parallel` 关键字可以水平扩展单个作业","url":"https://yupanzi.com/posts/git-cicd-tips/","title":"Git 使用技巧与 CI/CD 实践","summary":"Git 常用操作技巧和 GitLab CI/CD 并行执行实践","date_modified":"2024-05-16T16:17:56.000Z","author":{"name":"yupanzi"},"tags":["Git","CI/CD","GitLab"]},{"id":"https://yupanzi.com/posts/authentik-use-dingtalk-login/","content_html":"## 背景\n\nAuthentik 是一个开源的身份验证和授权服务，支持多种身份验证方式。钉钉提供了 OAuth2 授权机制，但其接口是非标准的，需要通过自定义转换服务来适配。\n\n本文介绍如何使用 Authentik 的 OAuth2 提供程序集成钉钉登录，让用户可以使用钉钉账户登录应用。\n\n参考文档：[Authentik OAuth Sources](https://docs.goauthentik.io/integrations/sources/oauth/)\n\n## 实现步骤\n\n### 1. 创建钉钉应用\n\n参考以下文档创建钉钉应用：\n- [GitLab DingTalk 集成](https://docs.gitlab.com/ee/integration/ding_talk.html)\n- [Casdoor DingTalk OAuth](https://casdoor.org/zh/docs/provider/oauth/DingTalk/)\n\n#### 1.1 钉钉 OAuth 接口说明\n\n官方文档：[获取登录用户的访问凭证](https://open.dingtalk.com/document/orgapp/obtain-identity-credentials)\n\n**OAuth2 流程（钉钉版本）：**\n\n<details>\n<summary>接口示例</summary>\n\n```text\n钉钉 OAuth2 协议流程\n\n1. 授权请求\nGET https://login.dingtalk.com/oauth2/auth?\nredirect_uri=https%3A%2F%2Fwww.example.com%2F&response_type=code&client_id=dingyourclientid&scope=openid&prompt=consent\n\n2. 回调地址格式\nhttps://www.example.com/?authCode=6b427e8bfab83e93bedd13f16a430702\n\n3. 获取 Token\nPOST https://api.dingtalk.com/v1.0/oauth2/userAccessToken\nContent-Type: application/json\n\n{\n  \"clientId\": \"ding your id\",\n  \"clientSecret\": \"your secret\",\n  \"code\": \"6b427e8bfab83e93bedd13f16a430702\",\n  \"grantType\": \"authorization_code\"\n}\n\n响应：\n{\n  \"expireIn\": 7200,\n  \"accessToken\": \"a8f4e3215a703ce9a7164e91dbab53c0\",\n  \"refreshToken\": \"b13e5a61b421342d95d86c9e64c275c6\"\n}\n\n4. 获取用户信息\nGET https://api.dingtalk.com/v1.0/contact/users/me\nx-acs-dingtalk-access-token: a8f4e3215a703ce9a7164e91dbab53c0\nContent-Type: application/json\n\n响应：\n{\n  \"nick\": \"AWIS ME\",\n  \"unionId\": \"D578iS5hxxxx\",\n  \"avatarUrl\": \"https://static-legacy.dingtalk.com/media/lADPGT5i9m5ZyXDNA4LNAtA_720.jpg\",\n  \"openId\": \"WySPOpXqxE\",\n  \"mobile\": \"1350xxxxxxxx\",\n  \"stateCode\": \"86\",\n  \"email\": \"xxxu@xxx.com\"\n}\n```\n\n</details>\n\n**参考实现：**\n- [Directus 讨论示例](https://github.com/directus/directus/discussions/11881)\n- [apiproxy 实现](https://github.com/xu4wang/apiproxy/blob/main/src/handlers/oauth_dingtalk.ts)\n- [Kratos 实现](https://github.com/ory/kratos/blob/eb67bed1f26d2c7ff10e5481b679b2213b44676d/selfservice/strategy/oidc/provider_dingtalk.go)\n\n### 2. 配置 Authentik\n\n参考：[Twitch 集成文档](https://docs.goauthentik.io/integrations/sources/twitch/)\n\n**关键配置项：**\n\n| 配置项 | 值 |\n|--------|-----|\n| 身份验证类型 | OpenID Connect |\n| Scopes | `openid` |\n| Authorization URL | `https://login.dingtalk.com/oauth2/auth?prompt=consent` |\n| Token URL | 自定义转换服务 URL（见下文） |\n| User Info URL | 自定义转换服务 URL（见下文） |\n\n> **注意**：钉钉的 OAuth2 接口是非标准的（命名方法和参数格式有差异），需要自己实现转换服务。参考：[知乎文章](https://zhuanlan.zhihu.com/p/666423994)\n\n### 3. 实现 OAuth2 转换服务\n\n使用 AWS Serverless（Lambda）实现，将钉钉接口转换为标准 OAuth2 格式。\n\n#### 3.1 Token 接口\n\n<details>\n<summary>📄 /auth/dingtalk/token</summary>\n\n```python\nimport requests\nimport json\nfrom base64 import b64decode\nfrom urllib.parse import parse_qs\n\nTOKEN_URL = 'https://api.dingtalk.com/v1.0/oauth2/userAccessToken'\n\ndef parse_form_data_to_json(form_data):\n    parsed_data = parse_qs(form_data)\n    result = {k: v[0] for k, v in parsed_data.items()}\n    return result\n\ndef main(event, context):\n    print(f\"event:\\n{event}\")\n    s = event.get(\"body\")\n    if event.get(\"isBase64Encoded\") and s:\n        s = b64decode(s).decode(\"utf-8\")\n    body = parse_form_data_to_json(s)\n\n    headers = {\"Content-Type\": \"application/json\"}\n    response = requests.post(TOKEN_URL, json={\n        'clientId': body.get('client_id'),\n        'clientSecret': body.get('client_secret'),\n        'code': body.get('code'),\n        'grantType': body.get('grant_type'),\n    }, headers=headers)\n    response.raise_for_status()\n    res = response.json()\n\n    result = {\n        # 'refresh_token': res.get('refreshToken'),\n        'access_token': res.get('accessToken'),\n        'expires_in': res.get('expiresIn'),\n        'token_type': 'Bearer',\n    }\n\n    return {\"statusCode\": 200, \"body\": json.dumps(result)}\n```\n\n</details>\n\n#### 3.2 用户信息接口\n\n<details>\n<summary>📄 /auth/dingtalk/profile</summary>\n\n```python\nimport requests\nimport json\n\nURL = 'https://api.dingtalk.com/v1.0/contact/users/me'\n\ndef main(event, context):\n    print(f\"event:\\n{event}\")\n    access_token = event.get('headers', {}).get('authorization', '')\n    access_token = access_token.replace('Bearer ', '')\n    print(access_token)\n\n    headers = {\n        \"Content-Type\": \"application/json\",\n        'x-acs-dingtalk-access-token': access_token,\n    }\n    response = requests.get(URL, headers=headers)\n    response.raise_for_status()\n    user_info = response.json()\n    print(user_info)\n\n    result = {\n        # 'issuer': userInfoURL,\n        # 'picture': user_info.get('avatarUrl'),\n        'sub': user_info['openId'],  # 关键字段，必须有\n        'nickname': user_info['nick'],\n        'name': user_info['nick'],\n        'email': user_info['email']\n    }\n    return {\"statusCode\": 200, \"body\": json.dumps(result)}\n```\n\n</details>\n\n## 常见问题\n\n### 错误：Could not determine id.\n\n**原因**：返回的用户信息中缺少 `sub` 字段。\n\n**解决**：确保转换服务返回包含 `sub` 字段的 JSON，`sub` 通常对应钉钉的 `openId`。\n\n**相关源码：**\n- [OAuth 类型定义](https://github.com/goauthentik/authentik/blob/main/authentik/sources/oauth/types/oidc.py)\n- [回调处理逻辑](https://github.com/goauthentik/authentik/blob/main/authentik/sources/oauth/views/callback.py#L59)\n\n**错误日志示例：**\n\n```json\n{\n  \"auth_via\": \"unauthenticated\",\n  \"domain_url\": \"example.com\",\n  \"event\": \"Authentication Failure\",\n  \"host\": \"example.com\",\n  \"level\": \"warning\",\n  \"logger\": \"authentik.sources.oauth.views.callback\",\n  \"pid\": 4721,\n  \"reason\": \"Could not determine id.\",\n  \"request_id\": \"28a8d8818c63441da41051455c32d437\",\n  \"schema_name\": \"public\",\n  \"timestamp\": \"2024-04-09T10:30:48.464283\"\n}\n```\n\n## 总结\n\n通过自定义转换服务，成功将钉钉的非标准 OAuth2 接口适配为 Authentik 可识别的标准格式，实现了钉钉登录集成。核心要点：\n\n1. 钉钉接口参数命名与标准 OAuth2 不同（如 `clientId` vs `client_id`）\n2. 必须在用户信息中返回 `sub` 字段\n3. 使用 Serverless 服务作为中间层进行协议转换","url":"https://yupanzi.com/posts/authentik-use-dingtalk-login/","title":"Authentik 集成钉钉 OAuth2 登录","summary":"通过 AWS Serverless 实现 Authentik 集成钉钉 OAuth2 登录的完整方案","date_modified":"2024-04-10T13:44:53.000Z","author":{"name":"yupanzi"},"tags":["AWS","Authentication","OAuth2","Python"]},{"id":"https://yupanzi.com/posts/chatgpt-help-s3-move-object/","content_html":"## 需求\n\n将 S3 中的文件路径从：\n```\ns3://foobar/expense/2023/12/02 00:00:00/2023-12-02 00:00:00.json\n```\n\n移动为：\n```\ns3://foobar/expense/2023/12/02/2023-12-02.json\n```\n\n需要处理从某个日期到今天的所有文件。\n\n## 实现方式\n\nS3 没有直接的\"移动\"操作，需要先复制（COPY）再删除（DELETE）原对象。\n\n### 安装依赖\n\n```bash\npip install boto3\n```\n\n### Python 脚本\n\n```python\nimport boto3\nimport datetime\n\n# 初始化 S3 客户端\ns3_client = boto3.client('s3')\n\n# S3 桶名称\nbucket_name = 'foobar'\n\n# 设定日期范围\nstart_date = datetime.datetime(2023, 12, 1)\nend_date = datetime.datetime.now()\n\n# 遍历日期范围\ncurrent_date = start_date\nwhile current_date <= end_date:\n    # 构建原始和目标键\n    original_key = f'expense/{current_date.year}/{current_date.month:02d}/{current_date.day:02d} 00:00:00/{current_date.strftime(\"%Y-%m-%d\")} 00:00:00.json'\n    new_key = f'expense/{current_date.year}/{current_date.month:02d}/{current_date.day:02d}/{current_date.strftime(\"%Y-%m-%d\")}.json'\n\n    # 复制对象\n    copy_source = {\n        'Bucket': bucket_name,\n        'Key': original_key\n    }\n    try:\n        s3_client.copy(copy_source, bucket_name, new_key)\n        print(f'Copied: {original_key} to {new_key}')\n\n        # 删除原始对象\n        s3_client.delete_object(Bucket=bucket_name, Key=original_key)\n        print(f'Deleted: {original_key}')\n    except s3_client.exceptions.NoSuchKey:\n        print(f'No such key: {original_key}')\n    except Exception as e:\n        print(f'Error: {e}')\n\n    # 移至下一天\n    current_date += datetime.timedelta(days=1)\n```\n\n## 注意事项\n\n- 确保 AWS 凭证已配置（环境变量或 `.aws/credentials` 文件）\n- 需要有足够的 S3 操作权限（复制和删除）\n- 运行前建议备份重要数据\n- 大量文件时可能需要处理 S3 分页响应","url":"https://yupanzi.com/posts/chatgpt-help-s3-move-object/","title":"Python 批量移动 S3 对象","summary":"使用 Python 和 boto3 批量重命名移动 S3 对象","date_modified":"2024-03-21T14:46:34.000Z","author":{"name":"yupanzi"},"tags":["AWS","Cloud","Python","S3"]},{"id":"https://yupanzi.com/posts/kubernetes-ops-guide/","content_html":"Kubernetes 集群运维的实用技巧和最佳实践。\n\n## Kubectl 常用技巧\n\n### 批量删除 Job\n\n删除所有以 `foo-bar` 开头的 Job：\n\n```bash\nkubectl get jobs -o name | grep 'foo-bar' | xargs kubectl delete\n```\n\n**命令说明：**\n\n| 部分 | 作用 |\n|------|------|\n| `kubectl get jobs -o name` | 列出所有 Job 名称 |\n| `grep 'foo-bar'` | 过滤以 `foo-bar` 开头的 Job |\n| `xargs kubectl delete` | 批量删除 |\n\n**预览删除列表（不执行删除）：**\n\n```bash\nkubectl get jobs -o name | grep 'foo-bar'\n```\n\n**指定命名空间：**\n\n```bash\nkubectl get jobs -n your-namespace -o name | grep 'foo-bar' | xargs kubectl delete -n your-namespace\n```\n\n### 设置默认 StorageClass\n\n**1. 查看现有 StorageClass**\n\n```bash\nkubectl get storageclass\n```\n\n**2. 设置默认 StorageClass**\n\n将 `my-storage-class` 设置为默认：\n\n```bash\nkubectl patch storageclass my-storage-class -p '{\"metadata\": {\"annotations\":{\"storageclass.kubernetes.io/is-default-class\":\"true\"}}}'\n```\n\n**3. 移除其他默认标记**\n\n如果之前有其他默认 StorageClass，需要移除：\n\n```bash\nkubectl patch storageclass old-default-storage-class -p '{\"metadata\": {\"annotations\":{\"storageclass.kubernetes.io/is-default-class\":\"false\"}}}'\n```\n\n**4. 验证设置**\n\n```bash\nkubectl get storageclass\n```\n\n查看 `my-storage-class` 的注解是否为 `true`。\n\n---\n\n## 镜像管理\n\n### 查询特定镜像仓库的所有镜像\n\n查询 Kubernetes 集群上所有以 `docker.foobar.com` 开头的 Pod 镜像，并去重排序：\n\n```bash\nkubectl get pods --all-namespaces -o jsonpath=\"{..image}\" | tr -s '[[:space:]]' '\\n' | grep '^docker\\.foobar\\.com' | sort -u\n```\n\n**命令说明：**\n\n| 命令部分 | 作用 |\n|---------|------|\n| `kubectl get pods --all-namespaces -o jsonpath=\"{..image}\"` | 获取所有命名空间中 Pod 的镜像信息 |\n| `tr -s '[[:space:]]' '\\n'` | 将空格转换为换行符，每个镜像占一行 |\n| `grep '^docker\\.foobar\\.com'` | 过滤以 `docker.foobar.com` 开头的镜像 |\n| `sort -u` | 排序并去重（`-u` 参数表示唯一） |\n\n**脚本方式：**\n\n```bash\n#!/bin/bash\n\n# 查询并去重排序镜像\nkubectl get pods --all-namespaces -o jsonpath=\"{..image}\" | \\\n  tr -s '[[:space:]]' '\\n' | \\\n  grep '^docker\\.foobar\\.com' | \\\n  sort -u\n```\n\n保存为 `get-images.sh` 后执行：\n\n```bash\nchmod +x get-images.sh\n./get-images.sh\n```\n\n**注意：**\n- 需要有查询所有命名空间 Pod 的权限\n- 确保 kubectl 配置正确并能访问集群\n\n---\n\n## JupyterHub 在 K8s 上的配置\n\n### LDAP 非加密端口支持\n\n**问题**：JupyterHub 的 LDAP 认证默认只支持 LDAPS（加密端口），对于使用非加密 LDAP 端口的场景无法正常工作。\n\n**原因**：`jupyterhub-ldapauthenticator` 包中的 `use_ssl` 参数逻辑有问题，导致非加密连接时使用了错误的绑定模式。\n\n**解决方案**：通过 Dockerfile 修改认证器源码，修复 `use_ssl` 逻辑：\n\n```Dockerfile\nFROM jupyterhub/k8s-hub:latest\n\nUSER root\n# 修复 LDAP authenticator use_ssl 问题\nRUN FILEPATH=`python -c \"import pkg_resources; import os; print(os.path.join(pkg_resources.get_distribution('jupyterhub-ldapauthenticator').location, 'ldapauthenticator', 'ldapauthenticator.py'))\"` && \\\n    sed -i 's/ldap3.AUTO_BIND_NO_TLS if self.use_ssl else ldap3.AUTO_BIND_TLS_BEFORE_BIND/ldap3.AUTO_BIND_NO_TLS if not self.use_ssl else ldap3.AUTO_BIND_TLS_BEFORE_BIND/g' ${FILEPATH}\n\nUSER jovyan\n```\n\n这个修复将原来的 `if self.use_ssl` 改为 `if not self.use_ssl`，确保：\n- 非加密连接时使用 `AUTO_BIND_NO_TLS`\n- 加密连接时使用 `AUTO_BIND_TLS_BEFORE_BIND`\n\n---\n\n### ProfileList 配置\n\n在 K8s 上部署 JupyterHub 时，可以让用户在启动 Notebook 时选择不同的资源配置（如 4G/8G 内存，不同节点类型）。\n\n**配置方式**：通过 `KubeSpawner.profile_list` 配置多个资源配置选项。\n\n**values.yaml 配置示例**：\n\n```yaml\nhub:\n  extraConfig:\n    profileList: |\n      c.KubeSpawner.profile_list = [\n          {\n              'display_name': '小型环境 (4G 内存)',\n              'description': '适合轻量级数据分析任务',\n              'kubespawner_override': {\n                  'cpu_limit': 1,\n                  'cpu_guarantee': 0.5,\n                  'mem_limit': '4G',\n                  'mem_guarantee': '2G',\n                  'node_selector': {\n                      'disktype': 'ssd',\n                      'workload': 'general'\n                  }\n              }\n          },\n          {\n              'display_name': '大型环境 (8G 内存)',\n              'description': '适合大规模数据处理和模型训练',\n              'kubespawner_override': {\n                  'cpu_limit': 2,\n                  'cpu_guarantee': 1,\n                  'mem_limit': '8G',\n                  'mem_guarantee': '4G',\n                  'node_selector': {\n                      'disktype': 'ssd',\n                      'workload': 'compute'\n                  }\n              }\n          },\n          {\n              'display_name': 'GPU 环境',\n              'description': '配备 GPU 用于深度学习',\n              'kubespawner_override': {\n                  'cpu_limit': 4,\n                  'mem_limit': '16G',\n                  'extra_resource_limits': {\n                      'nvidia.com/gpu': '1'\n                  },\n                  'node_selector': {\n                      'accelerator': 'nvidia-tesla-v100'\n                  }\n              }\n          }\n      ]\n```\n\n**配置项说明**：\n\n| 配置项 | 说明 | 示例 |\n|--------|------|------|\n| `cpu_limit` | CPU 上限 | `2` (2 核) |\n| `cpu_guarantee` | CPU 保证值 | `1` (1 核) |\n| `mem_limit` | 内存上限 | `'8G'` |\n| `mem_guarantee` | 内存保证值 | `'4G'` |\n\n**节点选择**：\n\n```python\n'node_selector': {\n    'disktype': 'ssd',        # 磁盘类型\n    'workload': 'compute',    # 工作负载类型\n    'zone': 'us-east-1a'      # 可用区\n}\n```\n\n**Tolerations（容忍度）**：\n\n```python\n'tolerations': [\n    {\n        'key': 'gpu',\n        'operator': 'Equal',\n        'value': 'true',\n        'effect': 'NoSchedule'\n    }\n]\n```\n\n**部署配置**：\n\n```bash\nRELEASE=jhub\nNAMESPACE=jhub\n\nhelm upgrade --install $RELEASE jupyterhub/jupyterhub \\\n    --namespace $NAMESPACE \\\n    --create-namespace \\\n    --version=3.0.0 \\\n    --values config.yaml\n```\n\n---\n\n## 注意事项\n\n- 批量删除操作不可逆，执行前务必确认\n- 同一时间只能有一个默认 StorageClass\n- 修改 StorageClass 需要相应的集群权限\n- 节点必须提前打好对应的标签：`kubectl label nodes node1 disktype=ssd`\n- `cpu_limit` 和 `mem_limit` 会限制 Pod 的最大资源使用\n- GPU 资源需要先安装 NVIDIA Device Plugin\n\n## 参考文档\n\n- [JupyterHub 配置参考](https://z2jh.jupyter.org/en/latest/resources/reference.html)\n- [KubeSpawner profile_list](https://jupyterhub-kubespawner.readthedocs.io/en/latest/spawner.html#kubespawner.KubeSpawner.profile_list)","url":"https://yupanzi.com/posts/kubernetes-ops-guide/","title":"Kubernetes 运维实践指南","summary":"Kubernetes 运维实践，包括 kubectl 技巧、JupyterHub 配置等","date_modified":"2024-03-21T09:25:03.000Z","author":{"name":"yupanzi"},"tags":["Kubernetes","JupyterHub","LDAP"]},{"id":"https://yupanzi.com/posts/mobile-and-pc/","content_html":"最近在手机和电脑间切换，发现手机主要用于娱乐，使用上主要是方便，而电脑则是用于工作，使用上主要是偏向效率。\n\n这两点的根本不同不能过于颠倒，比如你说用手机工作，用电脑娱乐。这种总是有些拧巴，为什么？\n\n手机当前主要是触摸屏幕，所以对于容错性比较高的场景适用，而且最好是没有输入键盘的操作\n\n电脑由于大部分不是触屏，所以对于精确效率的场景比较合适，尤其是键盘这种输入设备的操作\n\n手机主要在于便携和多传感器，可以认为是一个采集终端\n\n电脑主要在于物理设备和硬件，可以看作是一个处理中心\n\n仔细品，看看现在这两个设备上的应用有哪些不同，以及流行的分别是什么就知道了，同时在开发对应的应用的同时也要按照这种设计顺应不同的方向，别搞反了，除非你是主打“差异化”","url":"https://yupanzi.com/posts/mobile-and-pc/","title":"手机和电脑","summary":"手机偏娱乐与便携、电脑偏效率与处理，聊聊两者使用场景的根本差异。","date_modified":"2024-02-08T11:27:48.000Z","author":{"name":"yupanzi"},"tags":["thoughts","productivity"]},{"id":"https://yupanzi.com/posts/network-diagnostic-tips/","content_html":"网络运维中常用的诊断和调试技巧。\n\n## HTTP 状态码\n\nHTTP 状态码用来表示请求是否成功，以及出现了什么错误。\n\n### 状态码分类\n\n| 范围 | 类型 | 说明 |\n|------|------|------|\n| 1xx (100-199) | 信息性 | 请求正在处理 |\n| 2xx (200-299) | 成功 | 请求正常处理完毕 |\n| 3xx (300-399) | 重定向 | 需要进一步操作 |\n| 4xx (400-499) | 客户端错误 | 请求有问题或无法实现 |\n| 5xx (500-599) | 服务器错误 | 服务器处理时出错 |\n\n### 不常见的状态码\n\n| 状态码 | 说明 | 备注 |\n|--------|------|------|\n| 101 | 切换协议 | 服务器同意切换到请求的协议，常用于 WebSocket |\n| 104 | 连接重置 | Nginx 定义，客户端取消了请求 |\n| 429 | 请求过多 | 触发了频率限制 |\n| 499 | 客户端关闭连接 | Nginx 定义，客户端在服务器响应前关闭了连接 |\n\n### 104 连接重置示例\n\n```text\n('Connection aborted.', ConnectionResetError(104, 'Connection reset by peer'))\n```\n\n常见场景：AWS NAT Gateway 会在 30 秒内关闭空闲连接，导致客户端收到 104 错误。\n\n参考：[AWS NAT Gateway 文档](https://docs.aws.amazon.com/vpc/latest/userguide/vpc-nat-gateway.html)\n\n### 499 客户端关闭连接\n\n这是 Nginx 自定义的状态码，表示客户端在服务器准备响应前就关闭了连接，通常发生在用户取消请求时。\n\n---\n\n## TCP 端口连通性测试\n\n需要测试远程服务器的某个端口（如 MySQL 的 3306）是否可访问。介绍几种常用的测试方法。\n\n### 快速测试方法\n\n**使用 nc (推荐)**\n\n最简洁的方式：\n\n```bash\nnc -zv 192.168.1.100 3306\n```\n\n输出：\n\n```\nConnection to 192.168.1.100 3306 port [tcp/mysql] succeeded!\n```\n\n**参数说明**：\n- `-z`：扫描模式，不发送数据\n- `-v`：显示详细信息\n\n**使用 telnet**\n\n传统方式：\n\n```bash\ntelnet 192.168.1.100 3306\n```\n\n成功时会显示：\n\n```\nTrying 192.168.1.100...\nConnected to 192.168.1.100.\n```\n\n**退出方式**：按 `Ctrl + ]`，然后输入 `quit`\n\n### 批量测试端口\n\n**nc 批量扫描**\n\n```bash\n# 测试多个端口\nfor port in 3306 80 443 22; do\n    nc -zv -w 3 192.168.1.100 $port\ndone\n```\n\n**扫描端口范围**\n\n```bash\n# 扫描 3000-3010 端口\nnc -zv 192.168.1.100 3000-3010\n```\n\n### 高级测试方法\n\n**使用 nmap**\n\n功能最强大的扫描工具：\n\n```bash\n# 扫描单个端口\nnmap -p 3306 192.168.1.100\n\n# 扫描多个端口\nnmap -p 3306,80,443 192.168.1.100\n\n# 扫描端口范围\nnmap -p 3000-4000 192.168.1.100\n\n# 快速扫描（跳过主机发现）\nnmap -Pn -p 3306 192.168.1.100\n```\n\n输出示例：\n\n```\nPORT     STATE SERVICE\n3306/tcp open  mysql\n```\n\n**使用 timeout 避免卡住**\n\n```bash\n# 3 秒超时\ntimeout 3 bash -c \"cat < /dev/null > /dev/tcp/192.168.1.100/3306\"\n\n# 检查返回值\nif [ $? -eq 0 ]; then\n    echo \"端口 3306 开放\"\nelse\n    echo \"端口 3306 关闭或超时\"\nfi\n```\n\n**使用 /dev/tcp (内置方法)**\n\n不需要额外工具：\n\n```bash\n# Bash 内置 TCP 测试\n(echo > /dev/tcp/192.168.1.100/3306) &>/dev/null && echo \"开放\" || echo \"关闭\"\n```\n\n### 编写测试脚本\n\n```bash\n#!/bin/bash\n\nhost=\"192.168.1.100\"\nport=3306\ntimeout=3\n\n# 测试端口\nif timeout $timeout bash -c \"cat < /dev/null > /dev/tcp/$host/$port\" 2>/dev/null; then\n    echo \"✓ $host:$port 可访问\"\n    exit 0\nelse\n    echo \"✗ $host:$port 不可访问\"\n    exit 1\nfi\n```\n\n使用：\n\n```bash\nchmod +x test-port.sh\n./test-port.sh\n```\n\n### 方法对比\n\n| 方法 | 优点 | 缺点 | 适用场景 |\n|------|------|------|----------|\n| `nc` | 简单快速 | 需要安装 | 日常测试 |\n| `telnet` | 通用性好 | 难以脚本化 | 交互式测试 |\n| `nmap` | 功能强大 | 较慢，需要安装 | 批量扫描 |\n| `/dev/tcp` | 无需工具 | 仅 Bash 支持 | 脚本中使用 |\n| `timeout + /dev/tcp` | 内置 + 超时控制 | 语法复杂 | 生产脚本 |\n\n### 安装工具\n\n**CentOS/RHEL**\n\n```bash\nyum install nc nmap telnet\n```\n\n**Ubuntu/Debian**\n\n```bash\napt-get install netcat nmap telnet\n```\n\n**macOS**\n\n```bash\nbrew install netcat nmap telnet\n```\n\n### 注意事项\n\n- `telnet` 连接成功后需要手动退出，不适合脚本\n- `nc` 的 `-w` 参数可以设置超时时间\n- `nmap` 扫描大量端口可能触发防火墙告警\n- `/dev/tcp` 方法在某些受限环境下可能不可用\n- 使用 `timeout` 命令避免无限等待","url":"https://yupanzi.com/posts/network-diagnostic-tips/","title":"网络诊断与运维技巧","summary":"网络运维常用技巧，包括 HTTP 状态码、TCP 端口测试等","date_modified":"2024-01-03T18:13:47.000Z","author":{"name":"yupanzi"},"tags":["Network","Shell","Troubleshooting"]},{"id":"https://yupanzi.com/posts/chatgpt-one-year/","content_html":"2022 年 11 月 30 日，ChatGPT 全球发布，开创了大语言模型的新时代。\n\n从最开始的毫不在乎到现在的离不开，感受到的不只是震撼。\n\n以前使用 Siri 总是觉着很蠢听不懂人话，而现在的 ChatGPT 从直观反馈来说，应该是不仅仅能听懂还能理解（所谓的人类理解），就算你中文夹杂着英文也是可以的，感觉语言的巴别塔好像被攻破了。\n\n但是我觉着，如果将 ChatGPT 作为“好用”工具，主要体现在两点，理解需求和知识集合，这两点缺一不可，同时也相辅相成。\n\n### 一、理解需求\n\n很多人凭着新鲜感试用了下，感觉回答的也就很一般，不好用，甚至觉着它不明白我要问什么。可是真的是这样吗？\n\n首先，很多人连需求都没有理清楚，而且有时候说话是有上下文的，想想看，如果一个陌生人突然问你“吃饭了吗”，你知道他究竟是什么意思吗？\n\nGPT4 出来的时候我问了问题，它会将你的需求描述一下，按照它的理解方式去执行，这一点就很厉害了，因为现实中很多人都做不到。\n\n当然，缺点也很明显， token 长度，就如同人的记忆，当然还是越大越好，就好像你的朋友一样，越熟悉越明白。\n\n### 二、知识集合\n\nChatGPT 如果只是能理解你说的话虽然是进步但是也不会特别好用，主要是 OpenAI 这个公司整理了很多高质量数据，互联网海洋般的数据。\n\n这一点才是我主要付费的原因，得益于互联网开放的精神，可能也恰恰是这些高质量数据才能通过量变引起质变。\n\n现在 ChatGPT 就像一个特别好的老师，拥有整个互联网知识，而如何向老师学习在于你是如何使用的。\n\n你可以问问题，你也可以将你理解的问题找他对答案，着实有趣。\n\n当然，还是有一些专有知识无从获取，这点它就不如人意了，会出现一本正经的胡说八道了，这点也是广为诟病的问题。\n\n所以它可以辅助你，但你最好是个专家能够辨别出 1% 这种级别的错误。对于编程倒是很好确定，执行下就行了，但是对于无法验证的领域就有些危险了。\n\n\n### 预测推理\n\n预测是我使用后最明显的感受，打字机效果的输出。通过我粗浅的分析，感觉人类也只不过是做了一些高级的预测，就好像说话一样——你真的懂你为什么说出下一个字吗？是不是脱口而出呢？\n\n而推理如同思考一样，人脑也是，如果想一想再回答，那么效果可能会相对好些，也许 GPT 这条路可能会对人类的模拟更加进了一步！\n\n### 未来展望\n\n语言使人类产生文明，那么以后的人工智能时代是不是能够产生新的文明呢？\n\n目前还不得而知，但是有一点即将发生，那就是改变生产关系。","url":"https://yupanzi.com/posts/chatgpt-one-year/","title":"ChatGPT 发布一周年感想","summary":"这是最好的时代，也是最坏的时代。","date_modified":"2023-11-30T12:00:00.000Z","author":{"name":"yupanzi"},"tags":["AI"]},{"id":"https://yupanzi.com/posts/start-hexo-blog/","content_html":"## 又换博客系统了\n\n说起来有点惭愧，这个博客从 WordPress 折腾到 Org-mode，现在又换成了 Hexo。\n\n之前在 [部署博客主机](/2017/04/28/deploy-blog-server/) 那篇文章里说过，从虚拟主机的 WordPress 到 VPS 的 WordPress 再到 Org-mode，中间几次折腾把文章颠簸的不剩什么了。那时候还说\"需要不再折腾\"，结果现在还是换了。\n\n不过这次真的是最后一次了（大概）。\n\n## 为什么不继续用 Org-mode\n\nOrg-mode 确实强大，Emacs 用户的最爱。但是我遇到几个问题：\n\n1. **生态太小**：相比 Hexo/Hugo，Org-mode 的主题插件太少了\n2. **工具链重**：需要 Emacs + Org-mode + org-publish，配置复杂\n3. **协作困难**：如果想在其他机器写文章，还得配置一套 Emacs 环境\n\n说白了，我只是想写点技术笔记，不需要 Org-mode 那么强大的功能。\n\n## 为什么选择 Hexo\n\n其实之前就考虑过 Hexo，这次终于下决心了。主要原因：\n\n- **简单**：Node.js 生态，npm 一键安装，不需要复杂配置\n- **快速**：生成速度快，本地预览秒开\n- **生态好**：主题插件丰富，社区活跃\n- **纯 Markdown**：不需要学 Org 语法，任何编辑器都能写\n\n## 基本使用\n\nHexo 的常用命令就几个，记住了基本够用：\n\n```bash\n# 创建新文章\nhexo new \"文章标题\"\n\n# 本地预览（支持热更新）\nhexo server\n\n# 生成静态文件\nhexo generate  # 或简写 hexo g\n\n# 部署到远程\nhexo deploy    # 或简写 hexo d\n\n# 一键生成并部署\nhexo g -d\n```\n\n本地预览会在 `http://localhost:4000` 启动服务，修改文章后自动刷新，体验很流畅。\n\n## 迁移过程\n\n从 Org-mode 迁移到 Hexo 还挺顺利：\n\n1. **文章转换**：Org 文件转 Markdown，手动改了下格式\n2. **图片资源**：都是静态文件，直接复制过来就行\n3. **域名部署**：还是用之前的 VPS，Nginx 配置都不用改\n\n唯一麻烦的是历史文章不多了，之前几次折腾已经丢了不少。不过也好，算是一次清理，留下的都是还有价值的。\n\n## 使用感受\n\n用了几个月，真香。\n\n最大的感受就是**简单**。写文章就在本地编辑器写，想用 VS Code 用 VS Code，想用 Vim 用 Vim。写完 `hexo g -d` 发布，整个流程顺畅。\n\n所有文章就是 Markdown 文件，用 Git 管理，想改历史文章直接改文件，想回滚直接 `git revert`。不像 WordPress 还要在数据库里捣鼓，也不像 Org-mode 需要配置一堆 elisp。\n\n而且静态网站部署简单，之前配好的 Nginx + Let's Encrypt 继续用，不需要数据库，不需要 PHP，省心。\n\n## 给折腾党的建议\n\n如果你也在各种博客系统之间纠结：\n\n- **别花太多时间选主题**：先用个简洁的开始写，主题以后可以换\n- **Git 管理很重要**：文章都是本地文件，一定要推到远程仓库备份\n- **够用就好**：别像我一样折腾，Hexo 就挺好的，别再换了\n\n最后，静态博客不是万能的，需要评论、搜索这些功能要用第三方服务。但如果只是想写点技术笔记，Hexo 够了。\n\n这次应该不会再换了（真的）。\n\n更多信息查看 [Hexo 官方文档](https://hexo.io/docs/)","url":"https://yupanzi.com/posts/start-hexo-blog/","title":"兜兜转转，还是回到了 Hexo","summary":"从 WordPress 到 Org-mode 再到 Hexo，折腾了一圈终于找到合适的","date_modified":"2023-11-30T08:00:00.000Z","author":{"name":"yupanzi"},"tags":["Hexo","Blog"]},{"id":"https://yupanzi.com/posts/aws-upload-file-size-limit/","content_html":"## AWS 上传文件大小 5GB 限制\n\n在使用 AWS 上传大文件时,可能会遇到 5GB 的限制问题。以下是两种解决方案:\n\n### 1. 在 boto3 中使用 copy 而不是 copy_object\n\n参考: [GitHub Issue #1715](https://github.com/boto/boto3/issues/1715)\n\n### 2. 使用 aws-cli 的 --expected-size 参数\n\n参考: [GitHub Issue #1090](https://github.com/aws/aws-cli/issues/1090)","url":"https://yupanzi.com/posts/aws-upload-file-size-limit/","title":"AWS 上传文件 5GB 限制问题","summary":"解决 AWS 上传文件时 5GB 大小限制的方法","date_modified":"2018-10-10T10:00:00.000Z","author":{"name":"yupanzi"},"tags":["AWS","boto3"]},{"id":"https://yupanzi.com/posts/macos-system-tips/","content_html":"macOS 有一些系统级的配置技巧，可以提升日常使用和开发体验。\n\n## 显示隐藏文件\n\nmacOS 默认隐藏所有以 `.` 开头的文件（dotfiles），比如 `.gitignore`、`.env` 等配置文件。\n\n### 方法对比\n\n| 方法 | 适用场景 | 持久性 |\n|------|---------|--------|\n| 快捷键 | Finder、文件对话框 | 临时 |\n| 终端命令 | 全局设置 | 永久 |\n| ls 命令 | 仅终端查看 | 临时 |\n\n### 快捷键切换（推荐）\n\n在 Finder 或文件对话框中按 **Command + Shift + .** 即可快速切换显示/隐藏：\n\n- 再按一次恢复隐藏\n- 立即生效，无需重启\n- 最常用的方法\n\n### 终端全局设置\n\n**显示所有隐藏文件**：\n\n```bash\n# 修改 Finder 设置\ndefaults write com.apple.finder AppleShowAllFiles -bool true\n\n# 重启 Finder 使设置生效\nkillall Finder\n```\n\n**恢复隐藏**：\n\n```bash\n# 恢复默认设置\ndefaults write com.apple.finder AppleShowAllFiles -bool false\n\n# 重启 Finder\nkillall Finder\n```\n\n### 终端查看隐藏文件\n\n如果只需要在终端查看，用 `ls` 命令即可：\n\n```bash\n# 显示所有文件（包括隐藏文件）\nls -la\n\n# 或者不显示 . 和 ..\nls -A\n```\n\n### 让单个文件永久可见\n\n**情况 1：文件名以 . 开头**\n\n这种文件只能通过**重命名**去掉开头的点：\n\n```bash\n# 例如让 .env 文件可见\nmv .env env\n```\n\n> **注意**：这是 Unix 规则，`chflags` 命令无法改变这类文件的可见性。\n\n**情况 2：被隐藏标记隐藏**\n\n用 `chflags` 命令取消隐藏标记：\n\n```bash\n# 取消隐藏（-R 表示递归处理文件夹）\nchflags -R nohidden /path/to/file\n\n# 重新隐藏\nchflags hidden /path/to/file\n```\n\n### 注意事项\n\n开启全局显示后，会看到很多系统和应用的内部文件（如 `.DS_Store`、`.Trash` 等），**操作时注意别误删或修改这些文件**。\n\n---\n\n## 禁用 .DS_Store 文件\n\n### 什么是 .DS_Store\n\n.DS_Store (Desktop Services Store) 是 macOS 系统用来存储文件夹的自定义属性的隐藏文件，比如文件图标的位置、文件夹视图设置或背景图片。这个文件在本地使用没什么问题，但在网络共享、Git 仓库或 USB 设备中可能会造成困扰。\n\n详细信息请参考: [Wikipedia - .DS_Store](https://en.wikipedia.org/wiki/.DS_Store)\n\n### 查询当前设置状态\n\n在修改设置前，我们可以先查看当前的配置状态：\n\n```bash\n# 查询网络卷的 .DS_Store 禁用状态\ndefaults read com.apple.desktopservices DSDontWriteNetworkStores\n\n# 查询 USB 设备的 .DS_Store 禁用状态\ndefaults read com.apple.desktopservices DSDontWriteUSBStores\n\n# 如果返回 1 表示已禁用，0 或错误提示表示未禁用（默认）\n```\n\n### 禁用 .DS_Store 文件创建\n\n**禁用网络卷上的 .DS_Store**\n\n在网络共享文件夹（如 SMB、AFP、NFS）上禁用：\n\n```bash\ndefaults write com.apple.desktopservices DSDontWriteNetworkStores -bool true\n```\n\n**禁用 USB 设备上的 .DS_Store**\n\n在外接 USB 存储设备上禁用：\n\n```bash\ndefaults write com.apple.desktopservices DSDontWriteUSBStores -bool true\n```\n\n**让设置生效**\n\n修改设置后需要重启 Finder：\n\n```bash\nkillall Finder\n```\n\n**注意**：macOS 目前**无法完全禁用本地磁盘**上的 .DS_Store 文件创建，这是系统 Finder 正常工作所必需的。上述命令只对网络卷和 USB 设备有效。\n\n### 清理现有的 .DS_Store 文件\n\n**清理特定目录**：\n\n```bash\n# 清理当前目录及子目录\nfind . -name \".DS_Store\" -type f -delete\n\n# 清理指定目录（将 /path/to/dir 替换为实际路径）\nfind /path/to/dir -name \".DS_Store\" -type f -delete\n```\n\n**清理整个系统（需谨慎）**：\n\n```bash\n# 清理整个用户目录\nfind ~ -name \".DS_Store\" -type f -delete\n\n# 清理整个系统（需要 sudo 权限，执行前请三思）\nsudo find / -name \".DS_Store\" -depth -exec rm {} \\;\n```\n\n### Git 仓库处理\n\n如果你在使用 Git，建议将 .DS_Store 加入 `.gitignore`：\n\n```bash\necho \".DS_Store\" >> .gitignore\n```\n\n已经提交到仓库的 .DS_Store 可以这样移除：\n\n```bash\n# 从 Git 索引中删除，但保留本地文件\nfind . -name .DS_Store -print0 | xargs -0 git rm -f --ignore-unmatch --cached\n\n# 提交更改\ngit commit -m \"Remove .DS_Store files\"\n```\n\n### 恢复默认设置\n\n如果想恢复 macOS 的默认行为（允许创建 .DS_Store）：\n\n```bash\n# 恢复网络卷设置\ndefaults delete com.apple.desktopservices DSDontWriteNetworkStores\n\n# 恢复 USB 设备设置\ndefaults delete com.apple.desktopservices DSDontWriteUSBStores\n\n# 重启 Finder 使设置生效\nkillall Finder\n```\n\n### 适用版本\n\n以上命令在 macOS 10.12 (Sierra) 及更高版本（包括最新的 macOS Sequoia 15.x）上均可正常使用。`defaults` 命令是 macOS 系统的标准配置工具，向后兼容性良好。\n\n## 参考链接\n\n- [Wikipedia - .DS_Store](https://en.wikipedia.org/wiki/.DS_Store)\n- [How to disable the creation of .DS_Store files for Mac users folders](https://www.techrepublic.com/article/how-to-disable-the-creation-of-dsstore-files-for-mac-users-folders/)","url":"https://yupanzi.com/posts/macos-system-tips/","title":"macOS 系统配置与技巧","summary":"macOS 系统常用配置技巧，包括显示隐藏文件、禁用 .DS_Store 等","date_modified":"2018-10-10T10:00:00.000Z","author":{"name":"yupanzi"},"tags":["macOS","Terminal"]},{"id":"https://yupanzi.com/posts/ssh-complete-guide/","content_html":"本文整合了 SSH 的常用配置，包括密钥生成、免密登录和跳板机代理。\n\n## 密钥生成\n\n### 生成 RSA 密钥对\n\n```bash\nssh-keygen -t rsa -C \"your-email@example.com\"\n```\n\n生成后的文件：\n- 私钥：`~/.ssh/id_rsa`\n- 公钥：`~/.ssh/id_rsa.pub`\n\n### 生成 Ed25519 密钥（推荐）\n\nEd25519 更安全、更快：\n\n```bash\nssh-keygen -t ed25519 -C \"your-email@example.com\"\n```\n\n---\n\n## 免密登录配置\n\n### 方法一：ssh-copy-id\n\n```bash\nssh-copy-id user@remote-host\n```\n\n### 方法二：手动复制公钥\n\n```bash\n# 查看公钥\ncat ~/.ssh/id_rsa.pub\n\n# 复制到远程服务器的 ~/.ssh/authorized_keys\n```\n\n### 权限设置\n\nSSH 对权限有严格要求，权限过于宽松会导致认证失败：\n\n```bash\nchmod 700 ~/.ssh\nchmod 600 ~/.ssh/*\n```\n\n| 文件/目录 | 推荐权限 |\n|-----------|----------|\n| `~/.ssh/` | 700 |\n| `~/.ssh/id_rsa` | 600 |\n| `~/.ssh/id_rsa.pub` | 644 |\n| `~/.ssh/authorized_keys` | 600 |\n| `~/.ssh/config` | 600 |\n\n---\n\n## SSH 配置文件\n\n`~/.ssh/config` 可以简化 SSH 连接命令。\n\n### 基本配置\n\n```\nHost myserver\n    HostName 192.168.1.100\n    User admin\n    Port 22\n    IdentityFile ~/.ssh/id_rsa\n```\n\n使用：\n\n```bash\n# 不需要记 IP 和用户名\nssh myserver\n```\n\n### 多个服务器配置\n\n```\n# 开发服务器\nHost dev\n    HostName dev.example.com\n    User developer\n    IdentityFile ~/.ssh/id_rsa_dev\n\n# 生产服务器\nHost prod\n    HostName prod.example.com\n    User admin\n    IdentityFile ~/.ssh/id_rsa_prod\n    Port 2222\n```\n\n---\n\n## 跳板机配置\n\n### 场景\n\n需要通过跳板机（堡垒机）访问内网服务器。\n\n```\n本地 → 跳板机(Jump Host) → 目标服务器\n```\n\n### ProxyCommand 配置\n\n在 `~/.ssh/config` 中：\n\n```\n# 跳板机配置\nHost jump\n    HostName jump.example.com\n    User jumpuser\n    IdentityFile ~/.ssh/id_rsa\n\n# 内网服务器（通过跳板机）\nHost internal\n    HostName 10.0.0.100\n    User admin\n    IdentityFile ~/.ssh/id_rsa\n    ProxyCommand ssh -q -W %h:%p jump\n```\n\n使用：\n\n```bash\n# 自动通过 jump 跳转到 internal\nssh internal\n```\n\n### ProxyJump（更简洁）\n\nOpenSSH 7.3+ 支持更简洁的语法：\n\n```\nHost internal\n    HostName 10.0.0.100\n    User admin\n    ProxyJump jump\n```\n\n### 参数说明\n\n| 参数 | 说明 |\n|------|------|\n| `-q` | 静默模式，不输出警告 |\n| `-W %h:%p` | 转发 stdin/stdout 到目标主机和端口 |\n| `%h` | 目标主机名 |\n| `%p` | 目标端口 |\n\n### 多级跳转\n\n```\nHost target\n    HostName 10.0.0.200\n    User admin\n    ProxyJump jump1,jump2\n```\n\n---\n\n## 常见问题\n\n### sign_and_send_pubkey: signing failed\n\n通常是权限问题，检查 `~/.ssh` 目录权限：\n\n```bash\nchmod 700 ~/.ssh\nchmod 600 ~/.ssh/*\n```\n\n### 连接超时\n\n添加 Keep Alive 设置：\n\n```\nHost *\n    ServerAliveInterval 60\n    ServerAliveCountMax 3\n```\n\n### 首次连接确认\n\n跳过主机指纹确认（仅限受信任环境）：\n\n```\nHost *\n    StrictHostKeyChecking no\n    UserKnownHostsFile /dev/null\n```\n\n---\n\n## 实用配置示例\n\n```\n# 全局设置\nHost *\n    ServerAliveInterval 60\n    AddKeysToAgent yes\n\n# 跳板机\nHost bastion\n    HostName bastion.example.com\n    User ops\n    IdentityFile ~/.ssh/id_ed25519\n\n# 内网开发服务器\nHost dev-*\n    User developer\n    ProxyJump bastion\n\nHost dev-web\n    HostName 10.0.1.10\n\nHost dev-db\n    HostName 10.0.1.20\n\n# GitHub\nHost github.com\n    HostName github.com\n    User git\n    IdentityFile ~/.ssh/github_key\n```\n\n---\n\n## 参考资料\n\n- [SSH 原理与运用](http://www.ruanyifeng.com/blog/2011/12/ssh_remote_login.html)\n- [SSH ProxyCommand 详解](http://mingxinglai.com/cn/2015/07/ssh-proxycommand/)\n- [OpenSSH Config 手册](https://man.openbsd.org/ssh_config)","url":"https://yupanzi.com/posts/ssh-complete-guide/","title":"SSH 完全配置指南","summary":"SSH 密钥生成、免密登录、跳板机配置的完整指南","date_modified":"2018-06-19T10:00:00.000Z","author":{"name":"yupanzi"},"tags":["SSH","Linux","Network"]},{"id":"https://yupanzi.com/posts/ubuntu-system-tips/","content_html":"Ubuntu 系统日常运维中的一些常用配置技巧。\n\n## 更改主机名\n\n在 Ubuntu 系统中,可以通过以下步骤更改主机名:\n\n### 方法一：修改配置文件\n\n1. 编辑 hostname 文件:\n\n```sh\nsudo vi /etc/hostname\n```\n\n2. 编辑 hosts 文件:\n\n```sh\nsudo vi /etc/hosts\n```\n\n在 hosts 文件中修改或添加:\n\n```\n127.0.1.1 [hostname]\n```\n\n### 方法二：使用 hostnamectl 命令\n\n```sh\nsudo hostnamectl set-hostname [hostname]\n```\n\n使用 `hostnamectl` 命令是最简单和推荐的方法,它会自动更新所有相关的配置文件。\n\n### 验证更改\n\n重启系统后,可以使用以下命令验证主机名是否更改成功:\n\n```sh\nhostname\n# 或\nhostnamectl\n```\n\n---\n\n## 时钟同步设置\n\n### 方法一：使用 timedatectl\n\n**查看同步状态**：\n\n```bash\ntimedatectl status\n```\n\n**启用 NTP 同步**：\n\n```bash\nsudo timedatectl set-ntp true\n```\n\n这会启用 `systemd-timesyncd` 服务，自动与外部 NTP 服务器同步时间。\n\n### 方法二：安装 NTP 服务\n\n适用于需要更精细控制的场景。\n\n**1. 安装 NTP**\n\n```bash\nsudo apt-get update\nsudo apt-get install ntp\n```\n\n**2. 配置 NTP 服务器**\n\n编辑配置文件：\n\n```bash\nsudo nano /etc/ntp.conf\n```\n\n添加或修改 NTP 服务器：\n\n```conf\nserver 0.ubuntu.pool.ntp.org\nserver 1.ubuntu.pool.ntp.org\nserver 2.ubuntu.pool.ntp.org\nserver 3.ubuntu.pool.ntp.org\n```\n\n**3. 重启服务**\n\n```bash\nsudo systemctl restart ntp\n```\n\n**4. 查看同步状态**\n\n```bash\nntpq -p\n```\n\n### 两种方法对比\n\n| 方法 | 优点 | 缺点 |\n|------|------|------|\n| timedatectl | 系统自带，简单快速 | 功能相对简单 |\n| NTP 服务 | 功能强大，可精细配置 | 需要额外安装 |\n\n### 注意事项\n\n- 确保防火墙允许 UDP 123 端口（NTP 端口）\n- 受限网络环境需配置特定 NTP 服务器\n- 两种方法不要同时使用，选择其一即可","url":"https://yupanzi.com/posts/ubuntu-system-tips/","title":"Ubuntu 系统配置技巧","summary":"Ubuntu 系统常用配置技巧，包括更改主机名、时钟同步等","date_modified":"2018-06-19T10:00:00.000Z","author":{"name":"yupanzi"},"tags":["Ubuntu","Linux"]},{"id":"https://yupanzi.com/posts/container-troubleshooting/","content_html":"本文整合了 Docker 和 Kubernetes 常见故障的排查方法。\n\n## Docker X11 显示问题\n\n在 Docker 容器中运行图形界面应用时，可能遇到：\n\n```\nError: cannot open display localhost:11.0\n```\n\n### 解决方案\n\n在 `docker run` 时添加 `--net=host` 参数：\n\n```bash\ndocker run --net=host -e DISPLAY=$DISPLAY your-image\n```\n\n---\n\n## EKS 节点丢失\n\n### 问题现象\n\n- EKS 控制台看不到节点\n- `kubectl get nodes` 可以正常显示\n- Pod 调度正常\n\n### 解决方法\n\n在 `aws-auth` ConfigMap 中添加节点的 IAM Role 映射：\n\n```bash\nkubectl edit configmap aws-auth -n kube-system\n```\n\n添加配置：\n\n```yaml\ndata:\n  mapRoles: |\n    - groups:\n      - system:bootstrappers\n      - system:nodes\n      rolearn: arn:aws:iam::111122223333:role/my-node-role\n      username: system:node:{{EC2PrivateDNSName}}\n```\n\n### 获取节点 IAM Role\n\n```bash\n# 获取实例 ID\nkubectl get nodes -o wide\n\n# 查询 IAM Role\naws ec2 describe-instances --instance-ids i-xxxxx \\\n  --query 'Reservations[0].Instances[0].IamInstanceProfile.Arn'\n```\n\n---\n\n## Helm Release 找不到\n\n### 问题现象\n\n```bash\n$ helm list -n jupyter\n# 没有 foobar\n\n$ helm install foobar jupyterhub/jupyterhub -n jupyter\nError: release foobar already exists\n```\n\n### 原因分析\n\nRelease 可能处于失败或 pending 状态。\n\n### 解决方法\n\n**1. 查看所有状态的 Release**\n\n```bash\nhelm list --all-namespaces --all\n```\n\n**2. 处理不同状态**\n\n| 状态 | 处理方法 |\n|------|----------|\n| `failed` | `helm rollback` 或 `helm delete` |\n| `pending-install` | `helm delete` 然后重新安装 |\n| `pending-upgrade` | `helm rollback` 或强制升级 |\n\n**3. 手动清理**\n\nHelm 3 将 release 信息存储在 Secret 中：\n\n```bash\n# 查找\nkubectl get secret -n jupyter | grep foobar\n\n# 删除\nkubectl delete secret sh.helm.release.v1.foobar.v1 -n jupyter\n```\n\n**4. 重新安装**\n\n```bash\nhelm install foobar jupyterhub/jupyterhub -n jupyter\n```\n\n### 预防措施\n\n```bash\n# 使用 --atomic，失败自动回滚\nhelm install foobar chart -n jupyter --atomic\n\n# 设置超时\nhelm install foobar chart -n jupyter --timeout 5m\n```\n\n---\n\n## 完整排查流程\n\n```bash\n# 1. 查看所有 release\nhelm list -n namespace --all\n\n# 2. 查看详细状态\nhelm status release-name -n namespace\n\n# 3. 查看历史\nhelm history release-name -n namespace\n\n# 4. 尝试回滚\nhelm rollback release-name 1 -n namespace\n\n# 5. 如果失败，手动清理\nkubectl get secret -n namespace | grep release-name\nkubectl delete secret sh.helm.release.v1.release-name.v1 -n namespace\n\n# 6. 重新安装\nhelm install release-name chart -n namespace\n```\n\n---\n\n## 参考链接\n\n- [Docker X11 Display Issue](https://stackoverflow.com/questions/38249629)\n- [EKS aws-auth ConfigMap](https://docs.aws.amazon.com/eks/latest/userguide/auth-configmap.html)\n- [Helm Troubleshooting](https://helm.sh/docs/faq/troubleshooting/)","url":"https://yupanzi.com/posts/container-troubleshooting/","title":"容器与 K8s 故障排查指南","summary":"Docker X11 显示、EKS 节点丢失、Helm Release 问题的解决方案","date_modified":"2017-10-10T10:00:00.000Z","author":{"name":"yupanzi"},"tags":["Docker","Kubernetes","Troubleshooting"]},{"id":"https://yupanzi.com/posts/emacs-tips/","content_html":"## Buffer 操作\n\n### 从终端 Buffer 切换到其他 Buffer\n\n在终端模式下,C-x 键绑定变成了 C-c。\n\n参考: [How to switch to a different buffer from a terminal buffer](https://stackoverflow.com/questions/2173356/how-to-switch-to-a-different-buffer-from-a-terminal-buffer)\n\n### Buffer 关闭操作\n\n- `C-x k` - 关闭当前 buffer\n- `M-x kill-some-buffers` - 选择性关闭 buffers\n- `M-x kill-matching-buffers` - 关闭匹配的 buffers\n\n## Term-mode 配置\n\n### 禁用 evil-mode\n\n```elisp\n(evil-set-initial-state 'term-mode 'emacs)\n```\n\n参考:\n- [After-advice for disabling evil-mode in ansi-term has no effect](https://emacs.stackexchange.com/questions/32234/after-advice-for-disabling-evil-mode-in-ansi-term-has-no-effect)\n- [Disabling a package in emacs term-mode](https://stackoverflow.com/questions/19623545/disabling-a-package-in-emacs-term-mode)\n- [How to use terminal in Emacs effectively](http://blog.binchen.org/posts/how-to-use-terminal-in-emacs-effectively.html)\n\n使用 `C-z` 可以在 evil-mode 和 term-mode 之间切换。\n\n### 禁用行号显示\n\n```elisp\n(add-hook 'term-mode-hook (lambda () (linum-mode -1)))\n```\n\n参考: [How to disable global-linum-mode for certain mode](https://emacs.stackexchange.com/questions/17333/how-to-disable-global-linum-mode-for-certain-mode)\n\n## JSON 格式化\n\n### JSON reformat\n\n- `C-c C-f` - 使用 json-reformat 格式化选中区域或整个 buffer\n\n相关包:\n- [json-reformat](https://github.com/gongo/json-reformat)\n- [json-mode](https://github.com/joshwnj/json-mode)\n\njson-mode 用于 .json 文件的编辑。","url":"https://yupanzi.com/posts/emacs-tips/","title":"Emacs 使用技巧","summary":"Emacs 编辑器的实用技巧和配置","date_modified":"2017-10-10T10:00:00.000Z","author":{"name":"yupanzi"},"tags":["Emacs","Editor"]}]}