阅读视图

发现新文章,点击刷新页面。
🔲 ☆

在VS Code配置Obsidian風格Markdown編輯環境

photo of triangle shape digital wallpaper

本文整理一套在VS Code中配置Markdown工作流的方法,使其在wikilink、附件管理與多媒體預覽上接近Obsidian。

未有旨在複製Obsidian的界面。重點在於讓編輯、貼圖與預覽行為盡量一致。

插件組合

建議安裝以下3類插件:

  • Foam:提供wikilink解析、反向連結與檔案重新命名時的連結同步。
  • Markdown Preview Enhanced:提供較完整的Markdown預覽、wikilink支援與HTML5多媒體嵌入。
  • 貼圖插件:本文示例使用Markdown Image。Paste Image也能完成本地貼圖,只是設定鍵不同;如果你已經習慣Paste Image,可自行替換對應配置。

配置原則

這套做法分成兩層:

  • 工作區層:放進.vscode/settings.json.vscode/obsidian-preview.css,讓整個專案的Markdown行為保持一致。
  • 使用者層:只保留個人偏好,例如Markdown Preview Enhanced採用哪個預覽主題。這類設定不建議寫進工作區,否則容易和不同人的深色或淺色主題衝突。

工作區預覽樣式

.vscode目錄建立obsidian-preview.css。這份樣式只處理版面與可讀性,顏色直接讀取VS Code主題變數,避免在深色模式下出現亮底預覽。

:root {
  --obsidian-text: var(--vscode-editor-foreground, #2a2a2a);
  --obsidian-bg: var(--vscode-editor-background, #ffffff);
  --obsidian-muted: var(--vscode-descriptionForeground, #6b7280);
  --obsidian-link: var(--vscode-textLink-foreground, #5b7db1);
  --obsidian-border: var(--vscode-panel-border, #d7dce3);
  --obsidian-code-bg: var(--vscode-textCodeBlock-background, #f5f7fa);
}

body {
  margin: 0 auto;
  max-width: 860px;
  background: var(--obsidian-bg);
  color: var(--obsidian-text);
  font-family: "Source Han Sans", "Source Han Sans HC", "Source Han Sans TC",
    "Source Han Sans JP", sans-serif;
  line-height: 1.75;
}

code,
pre,
kbd,
samp {
  font-family: Consolas, "Cascadia Code", monospace;
}

a {
  color: var(--obsidian-link);
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

img,
video,
audio {
  max-width: 100%;
}

blockquote {
  margin-left: 0;
  padding-left: 1rem;
  border-left: 3px solid var(--obsidian-border);
  color: var(--obsidian-muted);
}

pre,
code {
  background: var(--obsidian-code-bg);
}

pre {
  padding: 0.9rem 1rem;
  border-radius: 8px;
}

table {
  border-collapse: collapse;
}

th,
td {
  border: 1px solid var(--obsidian-border);
  padding: 0.5rem 0.75rem;
}

如果你不想綁定預覽字體,可以刪除body中的font-family一行。

這份CSS主要服務VS Code內建Markdown預覽,讓它在不同主題下都維持穩定的可讀性。

工作區settings.json

以下示例只保留和Markdown工作流直接相關的設定。

{
  "markdown.updateLinksOnFileMove.enabled": "always",
  "markdown.copyFiles.destination": {
    "/**/*.md": "_resources/"
  },
  "markdown.preview.scrollEditorWithPreview": true,
  "markdown.preview.scrollPreviewWithEditor": true,
  "markdown.styles": [
    ".vscode/obsidian-preview.css"
  ],
  "foam.files.exclude": [
    ".venv/**/*",
    ".vscode/**/*",
    ".github/**/*",
    ".MWebMetaData/**/*",
    ".git/**/*",
    "**/sync.ffs_db",
    "**/sync.ffs_lock",
    "**/desktop.ini",
    "desktop.ini"
  ],
  "foam.files.newNotePath": "currentDir",
  "foam.templates.folder": "Templates",
  "foam.links.sync.enable": true,
  "foam.links.hover.enable": true,
  "markdown-preview-enhanced.enableWikiLinkSyntax": true,
  "markdown-preview-enhanced.useGitHubStylePipedLink": false,
  "markdown-preview-enhanced.wikiLinkTargetFileExtension": ".md",
  "markdown-preview-enhanced.scrollSync": true,
  "markdown-preview-enhanced.automaticallyShowPreviewOfMarkdownBeingEdited": true,
  "markdown-preview-enhanced.enableHTML5Embed": true,
  "markdown-preview-enhanced.HTML5EmbedUseImageSyntax": true,
  "markdown-image.base.uploadMethod": "Local",
  "markdown-image.local.path": "_resources",
  "markdown-image.local.referencePath": "",
  "[markdown]": {
    "editor.wordWrap": "on",
    "files.trimTrailingWhitespace": false
  }
}

這裡有兩點需要特別說明:

  • markdown.copyFiles.destination控制拖放或複製附件時,VS Code內建Markdown功能使用的附件目錄。
  • markdown-image.local.path控制Markdown Image貼圖時的儲存位置。本文使用_resources,表示圖片儲存在當前Markdown檔同級的_resources目錄。若你想改成工作區根目錄的統一附件倉,應寫成/_resources

可選:修正Markdown Preview Enhanced中的代碼塊背景

如果你在Markdown Preview Enhanced中採用none.css之類較少干預的預覽主題,有時會遇到fenced code block背景不一致,出現整塊底色與局部小底色混在一起的情況。這時可在工作區根目錄建立.crossnote/style.less

.markdown-preview.markdown-preview pre {
  background: var(--vscode-textCodeBlock-background, #1f252d) !important;
}

.markdown-preview.markdown-preview pre code,
.markdown-preview.markdown-preview pre code *,
.markdown-preview.markdown-preview pre tt,
.markdown-preview.markdown-preview pre tt * {
  background: transparent !important;
  box-shadow: none !important;
}

這份樣式只影響pre中的程式碼,不會改動行內code

可選:保留Markdown Preview Enhanced的主題選擇在使用者層

若你希望Markdown Preview Enhanced正文不額外套主題,而是盡量跟隨VS Code本身的明暗風格,可以把這類偏好放在使用者自己的settings.json中,而不要寫進工作區:

{
  "markdown-preview-enhanced.previewTheme": "none.css",
  "markdown-preview-enhanced.codeBlockTheme": "github-dark.css"
}

這屬於個人閱讀偏好,不屬於專案必需配置。

需按自己情況調整的項目

  • 附件目錄:如果你的Obsidian附件規則不是當前檔案同級的_resources,就同步修改markdown.copyFiles.destinationmarkdown-image.local.path
  • 模板目錄:如果你的模板資料夾不是Templates,就調整foam.templates.folder
  • 忽略規則:foam.files.exclude應按自己的vault與工作區結構增刪,不必照抄。
  • 貼圖插件:如果你使用Paste Image,就把文中的markdown-image.*配置換成對應的pasteImage.*鍵;若已切到Markdown Image,記得清理舊的pasteImage.*殘留設定。
  • 預覽字體:如果你不想指定字體,可刪除obsidian-preview.css中的font-family

啟用方式

  1. 在命令面板執行Developer: Reload Window
  2. 打開任意Markdown檔案,使用Markdown Preview Enhanced: Open Preview to the Side作為主要預覽窗格。
  3. 測試一次wikilink跳轉、圖片貼上與多媒體引用,確認附件路徑與預覽結果符合自己的Obsidian習慣。

完成後,VS Code即可在編輯、預覽與附件管理上提供一套接近Obsidian的Markdown工作流,同時保留VS Code本身的擴充能力與由Git驅動的版本管理環境。

🔲 ☆

在Windows通过LM Studio使用Zotero MCP

LM Studio是一款允许用户在本地下载、运行和管理大语言模型 (LLM) 的桌面应用程序,支持在Windows、macOS 和Linux上同AI对话、编写代码,无庸依赖云端。

本文介绍如何在Windows环境(笔者用的是Python 3.13 Microsoft Store版)安装Zotero MCP并配置给LM Studio使用。

前置准备

需要安装以下软件:

  • Zotero 7+
  • LM Studio 0.3.17+
  • Python 3.10+

开启Zotero本地API

Zotero默认关闭此功能,需手动开启。

  1. 打开Zotero,进入 Edit > Preferences
  2. Advanced 标签页下,勾选 “Allow other applications on this computer to communicate with Zotero”。

安装Zotero MCP

在终端运行安装命令。

使用uv(官方推荐):

# 如果尚未安装 uv
pip install uv

# 安装 zotero-mcp
uv tool install "git+https://github.com/54yyyu/zotero-mcp.git"

或使用pip:

pip install -U "git+https://github.com/54yyyu/zotero-mcp.git"

安装后若提示"The script zotero-mcp.exe is installed in ... which is not on PATH",说明系统无法直接识别命令。复制提示信息中zotero-mcp.exe的完整路径(例如:C:\Users\[用户名]\AppData\...\Scripts\zotero-mcp.exe),后续配置需要用到。

配置LM Studio

打开LM Studio,点击+ Install以打开mcp.json。在 mcpServers 字段中加入以下配置:

{
  "mcpServers": {
    "zotero": {
      "command": "C:\\Users\\[你的用户名]\\AppData\\...\\Scripts\\zotero-mcp.exe",
      "args": [],
      "env": {
        "ZOTERO_LOCAL": "true"
      }
    }
  }
}

  • command:填入上一步获取的zotero-mcp.exe绝对路径。JSON中路径的反斜杠需写作双斜杠\\
  • envZOTERO_LOCAL设为true以启用本地全文检索支持。

验证与使用

  1. 在LM Studio加载支持Tool Use的模型(如Llama 3.1 8B Instruct或Qwen 2.5)。
  2. 确认MCP装载。
  3. 输入测试指令,如:“Find the most recent paper I added to my library.”

启用语义搜索(可选)

默认仅支持关键词搜索。如需语义检索,需手动建立索引。

在命令提示符使用完整路径运行update-db命令(路径请按实际情况替换):

C:\Users\[你的用户名]\AppData\...\Scripts\zotero-mcp.exe update-db

🔲 ☆

WordPress服务器权限与所有权配置详解

a person using a laptop

核心概念:所有权 > 权限

在Linux中,权限(755/644)必须配合正确的所有权(User:Group)才能生效。换言之,PHP进程(即WordPress)必须是文件/目录的“所有者”,才能在755/644权限下进行写入(上传图片、升级插件、生成缓存)。

标准化操作流程

确保位于WordPress根目录(通常为/var/www/htmlpublic_html)下执行。

第一步:修正所有权

对当前目录下所有文件的所有者和组,行统一之设定。兹以33 (www-data) 为例(后文同此)。

chown -R 33:33 .

第二步:批量重置目录权限

按:使用+模式代替\;可显著提高执行效率。

find . -type d -exec chmod 755 {} +

第三步:批量重置文件权限

find . -type f -exec chmod 644 {} +

第四步:关键文件安全加固

针对敏感文件设置更严格的只读权限。

# wp-config.php 包含数据库密码,生产环境应设为 440 或 400
# 前提:文件所有者必须是 PHP 运行用户,否则设为 400 后 PHP 无法读取将导致网站无法访问
chmod 440 wp-config.php

# .htaccess 控制伪静态,通常设为 644,若不频繁修改可设为 604
chmod 644 .htaccess

针对部分具体插件的特殊分析

按,任何要求设置777权限的插件通常都存在设计缺陷与安全隐患。

但在应用上述“严格权限”时,以下插件模块可能会遇到权限阻碍:

缓存类:Super Cache (超级缓存)

该插件需要向wp-config.php写入开启缓存的定义代码,并修改.htaccess。当wp-config.php被设置为440时,插件后台更新设置会报错或白屏。是故,作为运维策略:

  1. 配置/更新插件前:chmod 644 wp-config.php
  2. 完成配置后:chmod 440 wp-config.php

按:日常运行中,插件生成的静态文件在wp-content/cache,该目录保持755即可正常读写。

翻译与本地化:Loco Translate

插件直接写入wp-content/languages目录生成.po/.mo文件。

只要第一步的chown -R 33:33 .执行正确,标准755目录权限即可满足需求。如果无法保存翻译,通常是所有者变成了别的(例如:在www-data才OK的情况下,所有者却变为了root)。

联邦宇宙与导入类 (ActivityPub, Import/Export)

  • 依赖:wp-content/uploads
  • 注意:导入大型XML/CSV或处理ActivityPub媒体流时,会产生临时文件。
  • 排查:如果导入失败或头像不显示,通过ls -ld wp-content/uploads检查权限是否为drwxr-xr-x (755)且所有者为www-data

另外,像WP File Manager这样的插件,虽然对于共享主机用户来说非常地便利,但其风险其实极高。如果该插件存在0-day漏洞,攻击者可绕过WordPress权限体系,直接以www-data身份操作服务器文件(上传Webshell),此时440644设置将失效。对于自托管用户来说,如已经拥有SSH命令行权限,该插件会增加不必要的攻击面。不如卸载。

常见故障排查清单

  1. 错误:440导致某些环境白屏

    如果服务器使用的不是PHP-FPM (User: www-data) 而是以CGI模式运行(User: 系统账户),将wp-config.php设为440且所有者为www-data(或33)可能导致文件无法被读取,引发HTTP 500错误。

    • 验证:执行完命令后,如果网站正常访问,说明配置正确。
  2. 插件更新失败 / 无法创建目录

    • 现象:后台提示“无法创建目录”。
    • 原因:通常不是权限问题(755是对的),而是所有权问题。
    • 复查:运行ls -ld wp-content/plugins,确保输出包含www-data www-data(或33 33)。如果显示的是root root,插件将无法更新。
  3. Git/开发环境

    如使用Git管理代码,.git目录的权限不要设置为755/644,且不应允许Web访问。宜在Nginx/Apache配置中显式禁止访问.git目录。

推荐操作脚本

确认在WordPress根目录后,按顺序执行:

# 1. 修正所有权 (兹以www-data为例,即id 33)
chown -R 33:33 .

# 2. 修正目录权限
find . -type d -exec chmod 755 {} +

# 3. 修正文件权限
find . -type f -exec chmod 644 {} +

# 4. 加固配置文件 (注意:配置 Super Cache 插件时可能需临时改为 640/644)
chmod 440 wp-config.php

# 5. 确保 .htaccess 对 WP 可写但安全
chmod 644 .htaccess

延伸阅读

🔲 ☆

禁用WordPress中Jetpack的AI助手按钮

自托管WordPress中安装了Jetpack插件后,会见到如牛皮癣般在文本写作的各个环节内嵌入于界面的“AI助手”。对于AI的态度本文不赘述——但是Jetpack的AI助手只会给每个新建的WordPress站点免费用那么几次。免费次数用光后,就需要购买增值服务。为产品付费无可厚非。但是如果不买,大剌剌的充值广告也仍然霸占网站后台界面,非常难看,横亘在那儿也不便于使用其他WordPress功能。

看到这儿,如果你也希望为站点内所有用户彻底禁用Jetpack的所有AI功能,可以将以下代码添加到主题的functions.php文件或使用Code Snippets插件:

add_filter( 'jetpack_ai_enabled', '__return_false' );

此代码通过调用Jetpack AI钩子直接关闭该模块。

又及:如何看待Jetpack?

征引一则Reddit回帖:

Comment
byu/PettyNiwa from discussion
inWordPress

如果你不是开发者,而且你没有那么多经验,那么Jetpack就很棒。如果你有经验,那么你可能不需要Jetpack…

我不认为Jetpack是坏事或好事,它只是为某些用户准备的,而对其他人来说,它是不需要的。

pimplyteen@Reddit
🔲 ☆

WordPress/MCP Adapter安装与维护指南

此指南适用于开发环境或通过Git管理的生产环境。Docker环境可用。

核心概念

MCP Adapter既可以作为Composer库被其他插件引用(推荐),也可以作为独立插件运行。

  • 依赖管理:该插件不包含vendor目录(第三方库),必须在本地通过Composer生成。
  • 运行环境:需要PHP 7.4+和WordPress 6.8+。

首次安装流程

步骤A:下载源码

进入WordPress的插件目录并克隆仓库:

cd /path/to/wp-content/plugins/
git clone https://github.com/WordPress/mcp-adapter.git
cd mcp-adapter

步骤B:安装依赖

此步骤用于生成vendor/autoload.php文件。若跳过此步,插件将无法运行。

Docker环境下的命令:

docker run --rm \
    -v $(pwd):/app \
    composer install

会下载wordpress/abilities-api等核心依赖并生成自动加载映射。

步骤C:激活插件

# 使用 WP-CLI 或在后台激活
wp plugin activate mcp-adapter

HTTPS协议一致性

配置WP_API_URL时,必须严格匹配站点的实际协议

  • ❌ 错误做法:站点强制 HTTPS,但填写http://yousite.com/...
  • ✅ 正确做法:填写https://yousite.com/...

如果站点强制使用 HTTPS(生产环境标准配置),而在配置中填写了http://,WordPress或服务器(Nginx/Apache)会返回一个301 Redirect跳转到HTTPS。在这个跳转过程中,出于安全规范,客户端发送的Authorization标头(包含密码)通常会被丢弃。这将导致请求虽然到达了最终地址,但因丢失凭据而报401 Unauthorized错误。

后续更新流程

由于插件通过Git安装,使用WordPress后台的“更新”按钮,以免覆盖Git配置。应按照以下步骤进行更新:

第一步:同步代码

获取最新的功能和修复:

cd /path/to/wp-content/plugins/mcp-adapter
git pull origin trunk

第二步:同步依赖

代码更新可能包含依赖包版本的变化(composer.lock变更)。每次git pull后运行:

docker run --rm \
    -v $(pwd):/app \
    composer install

如果跳过此步,可能会因为缺少新引入的类文件导致网站出现Fatal Error。

常见问题

  • Plugin could not be activated (Fatal Error):
    • 原因:忘记执行composer install,导致vendor/autoload.php缺失。
    • 解决:重新执行安装流程中的步骤B。
  • 类名冲突:
    • 原因:如果有多个插件都使用了MCP Adapter。
    • 解决:文档建议使用Jetpack Autoloader来解决多版本冲突(composer require automattic/jetpack-autoloader),但在单插件模式下非必须。
  • API 404 错误:
    • 解决:确保WordPress的固定链接(Permalinks)设置不是“朴素 (Plain)”,需要设置为“文章名 (Post name)”或其他结构。
  • API 401 错误 (Unauthorized):
    • 现象MCP error -32603: WordPress API error (401),且确认密码无误。
    • 解决:检查mcp.json中的WP_API_URL是否误写为了http://。将其修改为https://以避免重定向导致的凭据丢失。

延伸阅读

🔲 ☆

在Windows上為GnuCash啟用線上報價 (Finance::Quote)

GnuCash內置了「線上報價」(Finance::Quote) 功能,允許軟體自動從網路獲取股票、基金和貨幣的最新價格。

但在Windows系統上,安裝此功能時常因缺少依賴項或權限設定而導致失敗。本教程將說明完整的安裝步驟,以及如何修復常見的依賴安裝錯誤,以確保功能正常運作。

先決條件

  1. 已經在Windows 上安裝了GnuCash(安裝包內含Strawberry Perl環境)。
  2. 擁有電腦的管理員權限

第1步:以管理員身份開啟PowerShell

安裝過程需要使用PowerShell操作。

  1. 點擊「開始」選單。
  2. 輸入PowerShell
  3. 在搜尋結果中,找到 “Windows PowerShell”。
  4. 右鍵點擊它,選擇「以管理員身份執行」。
  5. 開啟後應會看到標示為管理員的PowerShell視窗。

第2步:更改PowerShell執行策略

Windows預設可能會限制腳本執行。需要先為當前的PowerShell視窗解除限制。

  • 在藍色窗口中,複製並貼上以下指令,然後按Enter鍵:

    Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process
    
  • 如出現提示,請輸入Y並按Enter鍵確認。

第3步:切換至GnuCash目錄並執行安裝

接下來執行GnuCash提供的安裝腳本。

  1. 切換到GnuCash的程式目錄。複製並貼上以下指令,然後按Enter(一般來說是此路徑,但還是要確認下自己電腦內實際的安裝路徑):

    cd "c:\Program Files (x86)\gnucash\bin"
    
  2. 執行.ps1安裝腳本(注意是.ps1文件,不是.cmd):

    .\install-fq-mods.ps1
    

第4步:處理依賴項安裝失敗的問題

執行上述腳本時,日誌中可能會出現Result: FAILNOT OK的錯誤訊息。這是因為Finance::Quote依賴許多Perl模組,若其中任何一個測試失敗,安裝過程就會受到影響。

即使安裝腳本最後顯示:

>> Installation succeeded <<

Press Enter to continue...

若前方的日誌中有報錯,代表並未真正安裝成功。必須手動修復這些失敗的依賴項。

例1:修復Date::Simple失敗

如果日誌顯示類似以下的錯誤:

Result: FAIL Failed 1/3 test programs. 1/233 subtests failed. gmake: *** [makefile:1034: test_dynamic] Error 1 IZUT/Date-Simple-3.03.tar.gz C:\STRAWB~1\c\bin\gmake.exe test -- NOT OK Stopping: 'install' failed for 'Date::Simple'.

這表示Date::Simple模組因測試未通過而中止安裝。

修復方法:

  1. 同一個管理員PowerShell視窗中,輸入以下指令啟動Perl的CPAN安裝環境(一般來說是此路徑,但還是要確認下自己電腦內實際的安裝路徑):
    c:\Strawberry\perl\bin\cpan.bat
    
  2. 提示符會變為cpan[1]>
  3. 輸入force install指令強制安裝該模組(跳過測試):
    force install Date::Simple
    
  4. 等待安裝完成。
  5. 輸入exit並按Enter鍵,退回到PowerShell提示符。

例2:修復Module::CPANTS::Analyse失敗

修復完前一個模組後,必須重新執行安裝腳本:

.\install-fq-mods.ps1

此時可能會發現另一個模組失敗。例如:

Result: FAIL [中略] Stopping: 'install' failed for 'I/IS/ISHIGAKI/Module-CPANTS-Analyse-1.02.tar.gz'.

修復方法:

  1. 再次啟動CPAN(一般來說是此路徑,但還是要確認下自己電腦內實際的安裝路徑):
    c:\Strawberry\perl\bin\cpan.bat
    
  2. 強制安裝這個失敗的模組:
    force install Module::CPANTS::Analyse
    
  3. 安裝完成後,輸入exit退出。

重複此過程

重複上述步驟,即:

  1. 執行.\install-fq-mods.ps1
  2. 檢查日誌是否有... NOT OK的錯誤。
  3. 若有,進入cpan.bat使用force install [模組名]進行修復。
  4. 直到最後一次執行安裝腳本(.\install-fq-mods.ps1)時,日誌顯示所有測試均為PASSOK,並顯示:

BPSCHUCK/Finance-Quote-1.67.tar.gz C:\STRAWB~1\c\bin\gmake.exe install UNINST=1 -- OK >> Installation succeeded <<

此時才代表安裝真正成功。

第5步:驗證安裝

安裝完成後,需驗證GnuCash能否正確載入模組。

  1. 關閉目前的管理員PowerShell視窗。
  2. 以一般使用者身份開啟一個的「命令提示字元」 (CMD) 視窗。
  3. 輸入以下指令進行測試(一般來說是此路徑,但還是要確認下自己電腦內實際的安裝路徑):
    "c:\Program Files (x86)\gnucash\bin\gnucash-cli.exe" --quotes info
    
  4. 判斷結果:
    • 如果出現Failed to initialize... missing_modules,表示安裝仍未成功,需回頭檢查依賴項。
    • 如果出現一條警告(WARN),如下所示,則代表安裝成功
    * 20:09:31  WARN <gnc.price-quotes> [GncFQQuoteSource::set_api_key()] No Alpha Vantage API key set...
    

Alpha Vantage API key警告屬於正常現象,僅提示未設定特定數據源的API金鑰,但證明GnuCash已成功載入Finance::Quote模組。

總結

  1. 開啟GnuCash。
  2. 進入工具 (Tools) -> 證券編輯器 (Security Editor)
  3. 選擇一個證券(如股票),點擊「編輯」。
  4. 勾選「獲取在線報價(G)」。
  5. 此時原本顯示「Finance::Quote 未正確安裝」的警告訊息應已消失。

注意,Finance::Quote適用於獲取股票和主要貨幣匯率,但通常無法自動獲取——比方說中國的場外基金淨值等。對於此類基金,大概還是得定期前往 工具 -> 價格編輯器 (Price Editor) 手動更新淨值。

🔲 ☆

为WordPress Syndication Links插件添加新的站点与图标的实现方法

在实际使用Syndication Links插件的过程中,如果经常发布到一些插件默认不支持的网站,就会遇到无法显示对应站点图标和名称的问题。本文记录了如何通过不直接修改插件源文件的方式,为该插件增加新的站点映射与图标,以避免更新时丢失修改。

插件的站点映射机制

Syndication Links插件在includes/class-syn-link-domain-icon-map.php中维护了一个私有静态数组$map,用于将域名映射到对应的图标名。例如:

private static $map = array(
    'twitter.com'  => 'twitter',
    'facebook.com' => 'facebook',
    // ...
);

当页面需要显示某个链接的图标时,插件会:

  1. 从URL提取域名
  2. 检查$map数组是否有该域名的映射
  3. 如果有,则按映射名去svgs目录中加载对应的SVG图标文件
  4. 同时从includes/simple-icons.phpsimpleicons_syn_get_names()函数获取该映射名对应的显示名称

如果$map中没有对应域名,插件会尝试根据域名的中间部分去找图标文件;如果仍找不到,就会显示一个默认的website图标。

要添加新的站点映射,需要改动三个地方

  1. 域名映射表$map中添加新域名及其图标名,例如:

    'sanguok.com'         => 'wordpress',
    'chatan.cc'         => 'iceshrimp',
    'library.chatan.cc'         => 'bookwyrm',
    
  2. 图标名称映射includes/simple-icons.php中的simpleicons_syn_get_names()函数中,添加新的图标名与显示名称,例如:

    'bookwyrm' => 'Bookwyrm',
    
  3. SVG图标文件 在插件的svgs目录下放置与图标名一致的.svg文件,例如bookwyrm.svg。 文件内容需要是有效的SVG代码,来源可以是自己绘制的,或从SVG Repo等网站下载并调整。

避免插件更新覆盖修改

如果直接编辑插件的源文件,在更新插件后修改会丢失。 解决办法是使用Code Snippets插件,在运行时通过钩子动态扩展映射和图标名。

示例:通过Code Snippets扩展映射

<?php
// 扩展域名映射
add_filter('syn_link_mapping', function($mapped, $url) {
    $custom_map = array(
        'sanguok.com'         => 'wordpress',
        'chatan.cc'         => 'iceshrimp',
        'library.chatan.cc'         => 'bookwyrm',
    );

    $host = str_replace('www.', '', parse_url($url, PHP_URL_HOST));
    if (isset($custom_map[$host])) {
        return $custom_map[$host];
    }
    return $mapped;
}, 10, 2);

// 扩展图标名称映射
add_filter('simpleicons_syn_get_names', function($icons) {
    $icons['bookwyrm'] = 'Bookwyrm';
    return $icons;
});

说明:

  • syn_link_mapping过滤器允许在插件完成映射后,动态修改映射结果
  • simpleicons_syn_get_names是一个我们假设添加的过滤器,如果插件没有,需要通过其他方式覆盖函数返回值(可能需要额外包装)

关于SVG文件

  • SVG文件必须放在插件svgs目录下,否则插件找不到。
  • 更新插件时,svgs目录里的文件也可能被覆盖或删除,建议在更新前备份。

实际操作步骤

  1. 准备好新的域名与图标名映射表
  2. 准备对应的SVG文件
  3. 将SVG文件放到插件的svgs目录中
  4. 在WordPress后台Code Snippets插件中创建一个新的Snippet,将上面的PHP代码粘贴进去
  5. 保存并启用Snippet
  6. 刷新前台页面验证是否生效

总结

  • 核心流程是域名 → 映射名 → SVG 图标 → 显示名称
  • 不直接改插件源码,使用钩子和Code Snippets是比较安全的做法
  • SVG文件仍然需要放在插件目录中,但要注意更新时备份
  • 如果插件未来提供更灵活的API(例如允许自定义SVG路径),则可以完全不改动插件目录
🔲 ☆

俾Docker容器中的应用访问宿主机上的数据库服务

在Docker Compose中,默认情况下,服务之间会在一个自定义的网络中通信。因此,容器会通过这个网络进行互相通信,而无法直接访问宿主机的 IP 地址。

这便是为何当服务尝试连接宿主机上的数据库服务器地址(兹以PostgreSQL这一数据库服务以及192.168.1.1:5432这一服务器位址为例)时,会出现连接超时的问题。

为了解决这个问题,可以有几种方案来让Docker容器中的应用访问宿主机上的数据库,而无需使用Docker自己创建的网络。

方案 1: 使用 host.docker.internal

在Docker for Linux中,可以使用host.docker.internal来指向宿主机的IP地址。这是Docker提供的一个内置DNS名称,允许容器访问宿主机的网络资源。可以将数据库的HOST设置为host.docker.internal,这样容器内部会自动解析到宿主机的IP。

  1. 修改 settings.py.env 文件中的数据库配置,将 HOST 设置为 host.docker.internal

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql',
            'NAME': 'database',              # 改成自己的
            'USER': 'database',              # 改成自己的
            'PASSWORD': 'your_password',     # 改成自己的
            'HOST': 'host.docker.internal',  # 使用 host.docker.internal 而不是 192.168.1.1
            'PORT': '5432',
        }
    }
    
  2. 运行docker compose命令,尝试把容器架起来;容器会自动解析这个特殊的DNS名称,并连接到宿主机的PostgreSQL服务。

方案 2: 使用 network_mode: "host"

如果不想让Docker为容器创建单独的网络,可以使用network_mode: "host"配置项。这会让容器直接使用宿主机的网络堆栈,而不是通过Docker创建的网络。这意味着容器可以直接访问宿主机的网络接口。

  1. 修改 docker-compose.yml 文件,在 web 服务中添加 network_mode: "host"

    services:
      web:
        (从略)
        network_mode: "host"  # 添加这个选项
        (从略)
        ports:
          - "(所期望的容器外端口):8000"
    
  2. 重新运行docker compose up,此时Docker容器将直接使用宿主机的网络,因此它可以直接访问192.168.1.1:5432上的PostgreSQL。

方案 3: 自定义网络配置,桥接到宿主机网络

如果仍然想使用Docker自定义网络,但让容器能够访问宿主机的网络,可以使用extra_hosts来为容器提供宿主机 IP 的映射。

  1. docker-compose.yml中,使用extra_hosts指定宿主机的IP:

    services:
      web:
        (从略)
        ports:
          - "(所期望的容器外端口):8000"
        extra_hosts:
          - "host.docker.internal:192.168.1.1"  # 手动映射宿主机的 IP
    
  2. 运行docker-compose,这会将host.docker.internal映射到宿主机IP地址。

总结

  • 使用host.docker.internal是最简便的方法,这个DNS名会自动解析到宿主机的IP地址。
  • 如若希望容器直接使用宿主机网络,可以使用network_mode: "host",让容器与宿主机共享网络堆栈。
  • 如若欲继续使用Docker自定义的网络,但仍然访问宿主机服务,可以使用extra_hosts来手动指定宿主机 IP。

根据需求选择合适的方案,修改配置后应该可以解决Docker容器无法连接宿主机上数据库服务的问题。

🔲 ☆

Bookwyrm由0.7.5升级至Production(e217a17)完整过程及疑难解答

引言

本教程旨在帮助Bookwyrm用户如何将其部署从v0.7.5升级到(笔者执笔时)最新的production分支(具体到e217a17版本)。截至该版本提交(commit)的诸多代码带来了许多改进,例如对搜索词汇权重的优化设计(能更精准地根据标题、作者、系列等信息进行排序),以及数据导出功能的增强(排除已删除内容)等,这些都会提升用户体验和系统稳定性。

在升级过程中,可能会遇到一些并非普遍存在,但对于特定部署环境至关重要的特殊情况。如果您符合以下条件——

  1. 使用外部(包括宿主机上的)数据库服务,并依赖Docker的host网络模式进行通信;同时,
  2. 通过容器外的服务进行HTTPS管理(而非在容器内部自签)

——那么本教程中的“特殊情况”部分将提供相应的解决方案。

如果您没有这些特殊需求,通常可以直接遵循Bookwyrm官方的升级指南,那将是更简便的路径。

特殊情况概述

本次升级案例中的主要挑战源于以下几个相互关联的部署配置:

  1. 外部数据库与host网络模式: 为了与宿主机上运行的外部PostgreSQL数据库服务进行通信,笔者的Docker Compose配置中将db服务注释掉了,并将webcelery等服务设置为 network_mode: "host"

    • 影响:host网络模式下,Docker容器直接共享宿主机的网络堆栈,容器之间无法通过服务名称进行内部解析。它们必须通过宿主机的IP地址(通常宿主机实际的局域网IP;或者都是host了,干脆127.0.0.1亦可)进行通信。这直接影响了nginx与Web服务之间的通信配置。
  2. Cloudflare Zero Trust与HTTPS管理: 笔者用 Cloudflare Zero Trust来处理HTTPS加密。这意味着服务器端无需强制进行HTTPS,而是由Cloudflare在边缘网络提供SSL/TLS保护。

    • 影响: 如果在服务器端启用自签名或其他免费的HTTPS证书,并与Cloudflare的“严格”HTTPS策略冲突,可能导致Cloudflare阻止流量,认为存在不安全的端到端连接。因此,我们的nginx配置必须避免在服务器端强制HTTPS,而是通过HTTP监听,并由Cloudflare Zero Trust负责转发和加密。这与Bookwyrm官方版本(无论是截至v0.7.5抑是截至本文所提之“最新”版)中可能集成的Certbot自动化HTTPS方案有所冲突,需要进行定制化。
  3. nginx端口冲突: 由于采用host网络模式,nginx容器会尝试直接监听宿主机的端口。在升级过程中,我们发现宿主机的80端口已被一个别的(就诸如nginx这样的)Web服务器占用,导致nginx无法启动。这需要沉下心诊断并解决端口冲突问题。

这些特殊因素叠加,使得简单的拉取(pull)或者复制粘贴新版本docker-compose.yml配置变得不可行,需要对网络、nginx代理和端口管理进行精细调整。

升级教程:从0.7.5到Production (e217a17)

本教程假设您已经有了一个运行正常的Bookwyrm 0.7.5部署,并且熟悉基本的Git和Docker Compose命令。

步骤1:备份数据和配置

关键:在进行任何重大升级之前,请务必备份所有重要数据。

  1. 备份数据库: 如果数据库运行在Docker容器内(假设容器名为bookwyrm-db-1),可以使用docker exec命令进行备份:

    docker exec -t bookwyrm-db-1 pg_dump -U your_db_user your_db_name > db_backup.sql
    

    如果数据库是外部服务,则使用其提供的备份机制。

  2. 备份 Docker Compose 文件和自定义配置: 复制当前的docker-compose.yml文件和所有自定义的配置文件(例如.env文件、nginx配置目录 nginx/ 等)。

    cp docker-compose.yml docker-compose.yml.bak
    cp .env .env.bak
    cp -r nginx/ nginx.bak/
    # 确保备份所有可能包含定制化信息的目录和文件
    

步骤2:更新Bookwyrm代码库

  1. 停止当前运行的Bookwyrm服务:

    docker compose down
    
  2. 拉取最新的production分支代码并回滚:

    (请确保您明白本节在说什么之后,再继续操作!如于不熟悉Git之情况下贸然照搬,会导致可怕后果😱)

    由于过去站点的运行可能会生成很多不需要的变更甚至提交(例如,nginx静态文件目录的权限变更,或者是Windows/Linux之间的换行符差异,皆会被Git一一记录);故此,先回滚到干净的远程production状态,然后只应用您所需的修改。

    git fetch origin                   # 获取远程仓库的最新状态
    git reset --hard origin/production # 强制重置本地分支到远程 production 的最新提交
    

    这将清除您本地所有未推送到origin/production的修改。(或者可能是upstream/production,根据实际情况来。)

步骤3:应用有价值的修改

(本节同样也是,希望您明白下述文字在说什么以后,再考虑要否采纳。)

现在,在干净的origin/production基础上,重新应用希望保留修改:

  1. 重命名docker-compose.ymldocker-compose.example.yml 如果origin/production包含docker-compose.yml文件,执行:

    mv docker-compose.yml docker-compose.example.yml
    

    兹解释一下笔者为什么会需要这么做。笔者以为官方Git库直接提供docker-compose.yml并于每回释出新版本时累次将变更付诸此文件是不优雅的。这样做没有考虑到(包括笔者在内)魔改docker-compose.yml以取消掉部分原生服务(例如容器内的db)的可能性——每次拉取官方更新,若不备份好自己的docker-compose.yml,就会被官方发布的.yml一整个覆盖掉。

    笔者以为,较为优雅的实践,是官方提供一个docker-compose.example.yml,并指导用户去:

    cp docker-compose.example.yml docker-compose.yml
    

    岂不美哉?不过,反馈给官方后,项目的积极维护者所分享的考量也不无道理——

    对于像BookWyrm这样(笔者瞎按:由于处于内测阶段所以会急遽更迭)的项目来说,使用标准并有助于保持一致的环境是非常典型的。一般来说,自定义应该是环境变量。

    (笔者中译)

    官方倒也有提供docker-compose.override.yml这样的维护方式。记入在docker-compose.override.yml的配置如与docker-compose.yml冲突,优先执行的是override的。且override.yml也在.gitignore内,就不会在每次更新时被覆盖了。但是,笔者这种需要注释掉部分服务的做法,override支持不到了

    所以,截至本文执笔时,还是只能自行重命名。可以考虑fork一份Git库,然后把重命名操作应用过去。在这种情况下,我们就可以将upstream指定为官方Git库,origin指定为自己的Git库。

  2. .gitignore中增加临时文件的忽略规则: 打开本地的.gitignore文件,并在文件末尾或其他合适位置添加增加临时文件的忽略规则(笔者例)。如果您像笔者一样,宿主机用的是魔改版的Linux系统,而该系统恰会以自己的方式为一些图片文件生成临时缩略图的话,就需要忽略之。否则,nginx静态文件目录(./static)下面会出现很多临时缩略图待提交——或者在哪次直接不慎提交掉了。

  3. 提交这些更改:

    git add docker-compose.example.yml .gitignore # 根据实际重命名情况调整
    git commit -m "Rename docker-compose.yml to docker-compose.example.yml and add temp files ignore to .gitignore"
    

步骤4:调整docker-compose.yml和nginx配置以适应特殊情况

这是本次升级教程的核心和关键,将根据特殊需求定制docker-compose.yml和nginx配置。

  1. 手动更新 docker-compose.yml 基于docker-compose.example.ymlproduction提供的docker-compose.yml文件,进行以下修改:

    • nginx, web, celery_worker, celery_beat等实际有启用的服务设置 network_mode: "host"

      services:
        nginx:
          # ...
          network_mode: "host"
          # ...
        web:
          # ...
          network_mode: "host"
          # ...
        celery_worker:
          # ...
          network_mode: "host"
          # ...
        celery_beat:
          # ...
          network_mode: "host"
          # ...
      
    • nginx服务volumes调整 (核心): 笔者将放弃官方的default.conf.template模板机制,而是直接挂载一个包含所有自定义nginx配置的文件,并让它直接覆盖容器内nginx的默认配置。

      首先,在本地./nginx/目录中,准备一个名为my-bookwyrm-nginx.conf的文件(或者选择用https.conf也可以),将所有自定的nginx配置内容都放入其中。

      nginx服务volumes部分应类似于:

          volumes:
            # 直接将你的主Nginx配置文件挂载到容器的 /etc/nginx/conf.d/default.conf
            # 确保这个文件包含了所有你需要的Nginx指令,并且是最终版本
            - ./nginx/my-bookwyrm-nginx.conf:/etc/nginx/conf.d/default.conf
      
            # 如果你的 server_config, server_name, locations 是独立的Nginx include文件
            # 并且你希望它们继续被加载,你仍然可以挂载它们。但要确保它们不包含监听指令,
            # 而是被 my-bookwyrm-nginx.conf 文件通过 `include` 指令引用。
            # 示例:
            # - ./nginx/locations:/etc/nginx/conf.d/locations
            # - ./nginx/server_config:/etc/nginx/conf.d/server_config
            # - ./nginx/server_name:/etc/nginx/conf.d/server_name
      
            # 99-autoreload.sh 脚本的挂载
            - ./nginx/99-autoreload.sh:/docker-entrypoint.d/99-autoreload.sh
      
            # 静态文件和媒体文件卷 (改为直接挂载本地目录)
            - ./static:/app/static
            - ./images:/app/images
            - ./exports:/app/exports # 用于导出数据
      

      注意:docker-compose.yml中,如果Nginx服务仍然有ports部分,请确保将其移除或注释掉。network_mode: "host"会让Nginx直接使用宿主机网络,ports映射是多余的。

    • 按需添加redis_activityredis_brokerflower(用于监控Celery)服务。(笔者用了外部的服务,就没开这些个。)

    • web服务的depends_on调整:

      web:
        # ...
        depends_on:
          celery_worker:
            condition: service_started
          redis_activity:
            condition: service_started
          # 以及其他你实际有开启的
      
    • 定义具名卷 (volumes) 在docker-compose.yml底部: 所有在服务中使用的具名卷(例如 redis_activity_dataredis_broker_datapgdatabackups)都需要在这里定义。

      volumes:
        pgdata:
        backups:
        static_volume: # 如果你用具名卷而不是直接挂载目录
        media_volume: # 如果你用具名卷而不是直接挂载目录
        exports_volume:
        redis_broker_data:
        redis_activity_data:
      

      重要: 如果你的webcelerynginx已经使用network_mode: "host"并直接挂载本地目录static, images, exports,那么static_volume, media_volume等具名卷就不需要了。保持一致性,如果挂载本地目录,就不要定义具名卷,反之亦然。

  2. 创建或更新nginx主配置文件 (./nginx/my-bookwyrm-nginx.conf): 这个文件将是nginx容器实际加载的/etc/nginx/conf.d/default.conf

    • 确保upstream web指向127.0.0.1:8000

      upstream web {
          server 127.0.0.1:8000; # 因为web容器在宿主机上也监听这个地址
      }
      
    • 配置nginx监听的端口 (关键步骤): 由于Apache占用80端口,且打算通过Cloudflare Zero Trust处理HTTPS,Nginx应该监听一个未被占用的端口(例如8001),并只提供HTTP服务。

      # 定义一个 Nginx 缓存区域 (可选,但推荐)
      proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=bookwyrm_cache:10m inactive=60m;
      
      server {
          listen [::]:8001; # Nginx 监听宿主机的 8001 端口
          listen 8001;
      
          server_name sanguok.com; # 示例。换成你的域名。下同
      
          # 最大请求体大小,根据需要调整
          client_max_body_size 10M;
      
          # 确保只通过主域名访问
          if ($host != "sanguok.com") {
              return 301 $scheme://sanguok.com$request_uri;
          }
      
          # 主要的反向代理到 web 服务
          location / {
              proxy_pass http://web; # upstream web 已经指向了 127.0.0.1:8000
              proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
              proxy_set_header Host $host;
              proxy_redirect off;
      
              # 缓存设置 (与上面 proxy_cache_path 配合使用)
              proxy_cache bookwyrm_cache;
              proxy_cache_valid any 1m;
              add_header X-Cache-Status $upstream_cache_status;
              proxy_ignore_headers Cache-Control Set-Cookie Expires;
              proxy_cache_methods GET HEAD;
              proxy_no_cache $cookie_sessionid;
              proxy_cache_bypass $cookie_sessionid;
          }
      
          # 专门处理登录、密码重置等,通常有限制请求
          location ~ ^/(login[^-/]|password-reset|resend-link|2fa-check) {
              # limit_req zone=loginlimit; # 如果定义了限速区域
              proxy_pass http://web;
          }
      
          # 静态文件服务
          location /static/ {
              root /app; # 对应 docker-compose.yml 中的 ./static:/app/static
              try_files $uri =404;
              add_header X-Cache-Status STATIC;
              access_log off;
          }
      
          # 图像文件服务
          location /images/ {
              location ~ \.(bmp|ico|jpg|jpeg|png|svg|tif|tiff|webp)$ {
                  root /app; # 对应 docker-compose.yml 中的 ./images:/app/images
                  try_files $uri =404;
                  add_header X-Cache-Status STATIC;
                  access_log off;
              }
              return 403; # 阻止非图片文件访问
          }
      
          # favicon.ico 处理
          location /favicon.ico {
              root /app/images/logos; # 假设你的 favicon 在这个路径
              try_files /IMG_5682.ico =404; # 确保指向实际的图标文件
          }
      
          # Flower 监控界面 (如果启用了 flower 服务)
          location /flower/ {
              proxy_pass http://127.0.0.1:8888; # 如果flower也在host模式下监听,并且监听8888端口
              proxy_cache_bypass 1;
          }
      
          # 其他常用的 Nginx 优化设置
          sendfile on;
          tcp_nopush on;
          tcp_nodelay on;
          keepalive_timeout 65;
          types_hash_max_size 2048;
          gzip on;
          gzip_disable "msie6";
          proxy_read_timeout 1800s;
          chunked_transfer_encoding on;
      
          # 错误日志
          error_log /var/log/nginx/error.log debug;
          access_log /var/log/nginx/access.log; # 可以改为 cache_log
      }
      
      # 如果有其他 Nginx 配置 include,确保其内容正确且不会监听 80 端口
      # 例如:
      # include /etc/nginx/conf.d/server_config;
      # include /etc/nginx/conf.d/server_name;
      # include /etc/nginx/conf.d/locations;
      
    • 关于99-autoreload.sh的权限: 再次确认nginx/99-autoreload.sh文件具有执行权限 (chmod +x ./nginx/99-autoreload.sh)。

步骤5:更新.env文件

Bookwyrm和Redis服务会引入新的环境变量。打开.env文件并更新:

  • 数据库连接信息: 确保与你的外部数据库服务匹配。
  • Redis密码和端口:redis_activityredis_broker设置强密码和端口(与你在docker-compose.yml中定义的Redis命令相符)。
    # Redis for Celery Broker
    REDIS_BROKER_PASSWORD=your_broker_redis_password
    REDIS_BROKER_PORT=6379 # 或者你指定的端口
    
    # Redis for Activity Stream/Cache
    REDIS_ACTIVITY_PASSWORD=your_activity_redis_password
    REDIS_ACTIVITY_PORT=6380 # 或者你指定的端口
    
    # Flower (Celery 监控) 用户名和密码
    FLOWER_USER=your_flower_username
    FLOWER_PASSWORD=your_flower_password
    
    # Nginx 相关的环境变量 (如果你的Nginx配置依赖它们)
    DOMAIN=sanguok.com
    # NGINX_SETUP=https # 如果你打算用我的my-bookwyrm-nginx.conf,这一行可以不设置或删除
    
    根据实际的docker-compose.ymlREDIS_BROKER_PORTREDIS_ACTIVITY_PORT的配置来填写端口。

步骤6:构建和启动服务

  1. 构建Docker镜像:

    docker compose build
    

    这会根据更新的Dockerfiledocker-compose.yml来构建所有服务的镜像。

  2. 启动所有服务:

    docker compose up -d
    

    这将以后台模式启动所有容器。

  3. 检查日志以确认服务是否正常运行:

    docker compose logs -f
    

    密切关注nginx、Web和Celery服务的日志,确保它们没有错误并且正常监听。

步骤7:执行数据库迁移和初始化

首次启动新版本时,可能需要执行数据库迁移。

  1. 执行数据库迁移:

    docker compose exec web python manage.py migrate
    
  2. 创建超级用户 (如果还没有):

    docker compose exec web python manage.py createsuperuser
    
  3. 收集静态文件:

    docker compose exec web python manage.py collectstatic --noinput
    

步骤8:配置nginx和Cloudflare Zero Trust

  1. 确保宿主机上没有其他服务占用nginx计划监听的端口。 检查你的宿主机,确保其他服务没有占用8001端口 (如果你按照本教程将其配置为nginx的监听端口)。

    sudo lsof -i :8001 # 检查 8001 端口是否被占用
    

    如果被占用,需要停止占用8001端口的服务,或者将nginx配置为监听另一个空闲端口。

  2. Cloudflare Zero Trust配置: 在Cloudflare Zero Trust控制台,为域名(按前面的示例就是sanguok.com)配置应用程序,使其通过HTTP转发到你的服务器IP地址的8001端口。Cloudflare将负责从客户端到Cloudflare的HTTPS连接。

恭喜!

经过这些详细的步骤和针对特殊情况的调整,您的Bookwyrm实例应该能够成功从0.7.5升级到production分支的e217a17版本,并且在特定的Docker Host网络模式和Cloudflare Zero Trust环境下正常运行。

如果在升级过程中遇到问题,请检查服务日志,并对照本教程中的步骤进行排查。祝君顺利!

🔲 ☆

批量处理ArchiveBox所有未成功抓取的链接

使用archivebox update --status unarchived命令,可以批量处理所有未成功抓取的链接。

工序

  1. 进入Docker容器
docker exec -it archivebox /bin/bash
  1. 切换到数据目录
cd /data

切换到非管理员操作:

su archivebox

否则会有安全性报错:

[!] ArchiveBox should never be run as root!
    For more information, see the security overview documentation:
        https://github.com/ArchiveBox/ArchiveBox/wiki/Security-Overview#do-not-run-as-root
  1. 执行命令

使用以下命令重新抓取所有未成功抓取的链接:

archivebox update --status unarchived
  1. 退出容器
exit

示例

以下是能在终端中看到的示例输出:

root@b4444bfff46f:~# cd /data
root@b4444bfff46f:/data# archivebox update --status unarchived
[i] [2024-06-23 08:00:00] ArchiveBox v0.6.2: archivebox update --status unarchived
    > /data
[i] [2024-06-23 08:00:00] Updating all snapshots with status: unarchived...
[i] [2024-06-23 08:00:01] Snapshot URL: http://example.com (1/1)
[i] [2024-06-23 08:00:02] Archiving snapshot using wget...
...

其他选项

  • 指定时间段:可以使用--before--after选项来指定一个时间段,例如:
archivebox update --status unarchived --after "2023-01-01"
  • 特定的抓取工具:可以使用--extract选项来指定特定的抓取工具,例如:
archivebox update --status unarchived --extract wget

这些选项可以根据需求进行组合使用。

补充:倘遇到TypeError

有时执行本文所介绍之命令,会遇到中断的情况,并返回日志如下(以鄙站为例):

[+] [2025-07-06 04:31:51] "sanguok.com"
(中略)
archive_methods.save_readability(Link(url=https://sanguok.com))

Traceback (most recent call last):
  File "/app/archivebox/extractors/__init__.py", line 114, in archive_link
    log_archive_method_finished(result)
  File "/app/archivebox/logging_util.py", line 435, in log_archive_method_finished
    hints = hints if isinstance(hints, (list, tuple)) else hints.split('\n')
TypeError: a bytes-like object is required, not 'str'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/local/bin/archivebox", line 33, in <module>
    sys.exit(load_entry_point('archivebox', 'console_scripts', 'archivebox')())
  File "/app/archivebox/cli/__init__.py", line 140, in main
    run_subcommand(
  File "/app/archivebox/cli/__init__.py", line 80, in run_subcommand
    module.main(args=subcommand_args, stdin=stdin, pwd=pwd)    # type: ignore
  File "/app/archivebox/cli/archivebox_update.py", line 119, in main
    update(
  File "/app/archivebox/util.py", line 114, in typechecked_function
    return func(*args, **kwargs)
  File "/app/archivebox/main.py", line 783, in update
    archive_links(to_archive, overwrite=overwrite, **archive_kwargs)
  File "/app/archivebox/util.py", line 114, in typechecked_function
    return func(*args, **kwargs)
  File "/app/archivebox/extractors/__init__.py", line 181, in archive_links
    archive_link(to_archive, overwrite=overwrite, methods=methods, out_dir=Path(link.link_dir))
  File "/app/archivebox/util.py", line 114, in typechecked_function
    return func(*args, **kwargs)
  File "/app/archivebox/extractors/__init__.py", line 130, in archive_link
    raise Exception('Exception in archive_methods.save_{}(Link(url={}))'.format(
Exception: Exception in archive_methods.save_readability(Link(url=https://sanguok.com))

笔者是在ArchiveBox v0.6.2遇到的此情况。这个错误信息TypeError: a bytes-like object is required, not 'str'表明在archivebox/logging_util.py文件的log_archive_method_finished函数中,有一个预期的字节类型对象被传递了一个字符串类型对象。

这个错误通常发生在Python 3环境中处理字节和字符串混合的情况。ArchiveBox在尝试记录归档方法的完成状态时,可能某个内部函数或外部工具返回了字符串,而它期望的是字节数据,或者反之。从堆栈跟踪来看,错误发生在archive_methods.save_readability(Link(url=https://sanguok.com))这一行,这暗示问题可能与readability提取器在处理特定链接时有关。readability提取器负责将网页内容转换为更易读的格式。

为此,一个值得期待的解决方案是更新至新版本,看看该bug有无被解决掉。抑或者就是跳过有问题的链接(下述方法1.)或提取器(下述方法2.,笔者推荐这条)。

  1. 跳过特定链接(如果只影响少数链接): 目前ArchiveBox没有直接的“跳过”特定URL的功能。但可以尝试在更新时排除这个链接。不过,对于update --status unarchived这种全局操作,这不太可行。

  2. 暂时禁用 readability 提取器: 那么,可以尝试在update命令中排除readability提取器,看看是否能绕过这个问题。 首先,需要知道当前启用了哪些提取器。可以在ArchiveBox的配置中查看,或者查看默认启用的提取器列表。假设默认启用了wget, readability, dom, screenshot, git, media, favicon,可以尝试这样:

    archivebox update --status unarchived --extract wget,dom,screenshot,git,media,favicon
    

    这将指示ArchiveBox在归档时跳过readability模块。

辨析

还有一种俾ArichiveBox重新抓取的方法,是使用--resume参数;但这需要一个时间戳作为其值,指定从哪个时间点开始恢复。

倘是希望重新抓取所有未成功的链接,就可以使用本文介绍的--status unarchived选项,这个选项会处理所有未成功(即状态为unarchived)的链接。

🔲 ☆

Bandizip編輯檔案後未提示更新?莫慌!幫您秒速找回臨時存檔

您是否曾經歷過這樣的驚魂時刻——

透過Bandizip直接打開壓縮檔裡的Word或Excel文件,埋頭苦幹了半天,按下儲存並關閉檔案後,卻沒有像往常一樣跳出「是否要將變更更新至壓縮檔?」的提示。Bandizip靜悄悄的,彷彿什麼事也沒發生。這時候,一陣冷汗冒上背脊——難道剛剛編輯的內容全都消失了嗎?

——先別慌張!您辛苦的成果,極有可能還安全地躺在電腦的某個角落。這篇文章,將帶您了解背後的原理;並提供清晰的步驟:助君迅速找回那個「失蹤」的臨時檔案。

何以如此?
解密Bandizip的「幕後工作」

首先,我們需要明白,任何壓縮軟體(包括Bandizip)都無法直接修改壓縮檔內的檔案。當您對著壓縮檔裡的文件點擊兩下時,實際發生了以下事情:

  1. 解壓縮副本:Bandizip會在背景將該檔案的一個「副本」解壓縮到一個系統指定的「臨時資料夾 (Temporary Folder)」中。
  2. 編輯副本:您所使用的編輯軟體(如Microsoft Office)其實是打開了這個臨時副本。您所有的編輯、儲存動作,都是針對這個副本進行的。
  3. 偵測與更新:在理想情況下,當您關閉編輯軟體後,Bandizip會偵測到這個副本被修改了,於是跳出提示,詢問您是否要用這個修改過的副本去覆蓋壓縮檔裡的原檔案。

然而有時候,或因軟體的衝突,或權限的問題,或不明原因的程式小錯誤;最後這個「偵測與更新」的步驟失敗了,導致提示視窗沒有出現。

救援步驟
——找回臨時存檔

當您發現更新提示沒有出現時,請依照以下步驟操作。最關鍵的一點是:盡量不要關閉Bandizip,並立即行動!因為有些暫存檔可能會在程式關閉後被自動清理。

第一步
——打開「執行」對話框

同時按下鍵盤上的Windows標誌鍵⊞ + R鍵。這個組合鍵會快速叫出系統的「執行」視窗。

第二步
——輸入指令%temp%

在「執行」視窗的輸入框中,準確輸入以下內容(包含兩個百分比符號),然後按下「確定」或Enter鍵:

%temp%

這個指令是一個系統捷徑,會立刻打開目前使用者的臨時資料夾,省去您在複雜的C:\路徑中尋找的麻煩。

第三步
——在臨時資料夾中找尋您的檔案

臨時資料夾裡通常會存放大量的暫存檔,看起來可能有些混亂。別擔心,使用以下技巧可以快速定位目標:

  1. 排序檔案。在資料夾空白處點擊右鍵,選擇「排序方式」,然後選擇「修改日期」。這樣,最近被修改過的檔案就會排列在最頂端。
  2. 辨識檔案:
    • 剛剛編輯的檔案(例如我的報告.docx)很可能就直接出現在列表的最上方。
    • 有時候,Bandizip會建立一個專屬的暫存資料夾來存放解壓縮的檔案。這個資料夾的名稱通常以BNZ_bz開頭。如果您沒直接看到檔案,可以找找看有沒有類似名稱的資料夾,您的檔案就在裡面。

第四步
——立刻備份檔案!

一旦找到了您編輯過的檔案,請立刻將它複製(不要剪下)到一個安全的位置,例如桌面、您的文件資料夾等。這樣可以確保您的心血結晶被安全地保存下來。

最佳實踐
——避免未來再次發生

雖然上述方法是有效的救援手段,但最好的策略還是預防。如果您需要對壓縮檔內的檔案進行重要或長時間的編輯,建議採用更穩健的作法:

  1. 先手動將檔案從壓縮檔中解壓縮出來。
  2. 在您電腦的普通資料夾(如桌面)中打開並編輯這個解壓縮出來的檔案。
  3. 編輯完成並儲存後,再手動將這個修改過的檔案拉回Bandizip視窗中,以更新壓縮檔。

此方法雖然步驟稍多,但能確保檔案正確儲存,避免因暫存機制異常而導致資料遺失。

🔲 ☆

亂彈文忠公宴番仔事

傳說,我們的文忠公林則徐野。番仔不服氣,請林則徐喫酒

酒席上,番仔示意下人端出一道菜,只見盤中幾粒丸仔,大小跟連江魚丸一樣,但是是彩色的,而且還冒氣,看起來很燙。

林則徐心想,哇哈,這紅毛的丸仔,怎麼都是舀好的,沒有湯呢?

算了,做人客的隨東家的便,林則徐一瓢羹舀起丸仔,接到碗裏,吹了又吹,然後小心的咬下一口。

沒想到!這番丸仔凍冰冰的,寒意直鑽骨頭,感覺臉都要了。林則徐凍得寒寒戰瓢羹連著丸仔都打碗裏。

原來,這是番仔的一道小食,叫做「冰其冷」🍨!

番仔看見林則徐狼狽的樣子,蛤蛤大笑。林則徐肝火都棕了,感覺很沒面,心想,番仔解數真多!

😡

林則徐開動老腦筋,決定以其人之道還治其人之身!

💡

過了幾日,林則徐遣人給番仔講:

「中國有句老話,禮尚往來,上次諸位以泰西名點宴我,這回林某人也備了幾道本洋小菜,還請諸位賞光啊。」

番仔便洋洋得意地赴宴去了。

幾盤菜後,林則徐命下人端上幾碗小食。只見碗中填滿了很像「冰其冷」🍨的東西,還灑了寶圓、花生、芝麻,而且並不冒煙。

番仔心裏想,哎呀這個遠東之地莫不服我王化啊,連喫食上面也沐浴西風了。

林則徐說,上回嚐到了這個冰、冰、冰其冷,哎呀ho-lieh e-go-do,delicious!讓我想起鄙鄉一碗小菜,不知會否合諸公的口呀。

實在是一番美意,不得不領受之啊,番仔拿起瓢羹,舀起一大勺,就往嘴裏送。闔嘴瞬間,被燙得哇哇大叫。

林則徐這纔啞啞笑道:「哎呀淒慘啊,諸君做什麼喫那麼快呢?介紹一下,這是鄙鄉一碗小菜,叫做芋泥。」

番仔們仰天朝地,有的被燙得直捂喉嚨,有的吐得及時,直將手掌朝著嘴巴扇氣,還有的頭埋到桌子下面咳嗽。

眾人無不讚嘆我們文忠公林則徐的智慧😎!(完)

🔲 ⭐

进入不断重启的Docker容器的命令行之方法

停止自动重启

如果容器正在使用docker-compose.yml设置重启策略,临时将restart: always改为restart: no,然后执行docker-compose down停止它。

启动容器进入临时shell

手动启动容器并覆盖其默认命令,以便进入容器并设置权限。运行:

docker run -it --rm --entrypoint /bin/sh your_image_name

退出

做完想做的事后,退出容器命令行:

exit
🔲 ⭐

使用net use命令来临时挂载网络目录

在Windows CMD中,如果无法直接cd到一个网络目录(专门些的名字叫——通用命名约定〈UNC〉路径),可以使用net use命令来临时挂载网络目录。以下是具体步骤1 2

  1. 使用net use命令挂载网络目录:
net use Z: \\network_path\shared_folder /user:username password
  • 其中:
    • Z: 是你希望分配给网络目录的本地驱动器号。
    • \\network_path\shared_folder是网络目录的路径。
    • /user:username password是访问网络目录所需的用户名和密码。
    • 挂载完毕后,不仅在CMD内,图形界面的“文件资源管理器”下“此电脑”中也可以看到该挂载目录。
  1. 访问挂载的网络目录:
Z: # 直接输入`Z:`便可切换至该挂载盘符
cd shared_folder # 然后用`cd`命令跳转
  1. 完成操作后卸载网络目录:
net use Z: /delete

补充:pushd命令

在Windows CMD中,pushd命令也可以用来临时挂载网络目录并切换到该目录(而且该方式还更常见一些)。具体步骤如下:

  1. 使用pushd命令挂载网络目录3
set unc_path=\\network_path\shared_folder\b\c
pushd %unc_path%
  1. 执行想要的代码;
  1. 完成操作后使用popd命令卸载网络目录:
popd

使用pushd命令时,它会将网络路径\\network_path\shared_folder挂载到一个可用的盘符(例如Z:),并将工作目录更改为Z:\b\c
但是,这种方法有一个致命的缺点:如果用户对\\network_path\shared_folder\b\c有权限,但对\\network_path\shared_folder没有权限,那么所有在此目录下的命令都会报错:Access is denied4

参考资料

  1. rwv. 在 Windows CMD 中挂载网络路径至本地盘符. rwv 的博客. ↩︎
  2. Windows掛載共享磁碟/網路磁碟(CIFS)指令. 平凡的幸福. ↩︎
  3. pushd. Microsoft Learn. ↩︎
  4. rwv. 在 Windows CMD 中挂载网络路径至本地盘符. rwv 的博客. ↩︎
🔲 ⭐

检查PostgreSQL数据库服务器是否真的已经启动

导入

执行启动命令后,可以通过几种方式来检查 PostgreSQL 数据库服务器是否真的已经启动。

使用命令行工具检查

可以使用 pg_isready 命令来检查数据库服务器的运行状态。在终端中执行以下命令:

pg_isready -p 5432

如果数据库服务器正在运行并监听指定的端口(在这里是5432),命令会返回 “accepting connections”,表示服务器已经启动并可以接受连接。

查看日志文件

启动数据库服务器后,可以查看日志文件以获取更多详细信息。例如,假设日志文件的位置是 /var/log/postgresql/postgresql-12-main.log。则可以使用 tail 命令查看最后几行日志:

tail /var/log/postgresql/postgresql-12-main.log

如果服务器成功启动,日志文件中应该会显示相应的信息。

尝试连接到数据库

最直接的方式是尝试连接到数据库并执行一些查询。可以使用 PostgreSQL 的客户端工具(如 psql)连接到数据库并执行一些简单的查询语句。在终端中执行以下命令:

psql -h localhost -p 5432 -U postgres

这将连接到本地主机上的 PostgreSQL 数据库(假设数据库服务器在本地主机上),端口为 5432,并使用 “postgres” 用户身份登录。如果成功连接并登录到数据库,表示数据库服务器已经启动。

总结

通过以上方法之一,可以验证 PostgreSQL 数据库服务器是否已经成功启动。

🔲 ☆

辰年再訪神保町

今日,逛神保町

2019年第60回東京名物神田古本まつり 神保町交差点会場の様子
二〇一九年第六十回東京名物神田古本祭,神保町交差点會場的樣子(みそがい,CC BY-SA 4.0)

神保町這邊,素以舊書店之多聞。上次認認真真逛神保町的舊書店,已經是大疫以前的事情了。前兩年重返日京,也去了兩回神保町左近,但都是赴飯局。東京舊書街的話,本鄉正門前一帶、白山京華路逛過幾回,早稻田高馬沿途則是過門未入——畢竟現在未在彼處讀書,沒法以前那樣通學途中順路翻兩頁


今日由九段下車站落車,六號出口出站。於是一路向北。

先到了玉川堂。這是一家書道相關的店鋪。門口的玻璃櫥窗內,擺了一尊《九成宮醴泉銘》碑的縮小件。店內售有日文書道、寺社朱印帳、筆、墨等。前日謁櫻神宮時,已經「重金」求得了朱印帳,否則可能還真考慮在這裏買一冊。——不過據說,最好分別以兩本不同的朱印帳,去收集神道神社和佛教寺院的御朱印,因部分神職人員不會寫混有兩種御朱印的朱印帳。這樣的話,似乎再買一冊,亦是一擇。

《九成宮醴泉銘》碑(示意)
四國八十八所朱印(示意,Dokudami,CC BY-SA 4.0)

山本書店,內有大量中國文、史關聯書籍。至其帙冊之語言,有日語,也有中文。這家中文書店,倒是我以前印象較淺的,算是今日收穫了。淺淺翻閱了幾本書:

  • 近代中国の思想と文学》(近代中國之思想與文學)。一看編者,居然是東大中文研。這本書,一九六七年出版,是部論文集,劈頭從嚴復開始,洎於聞一多。嚴復那邊是說幾道先生對西洋思想、著作的譯介;聞一多這邊,篇名是「聞一多與唐詩」。
  • 北京好日》,林語堂作,佐藤亮一譯。河出書房一九五一年出版,屬於《市民文庫》。譯者佐藤亮一,畢業於慶應義塾大學法學部政治學科。一九三二年,加入時事新報社,並於三六年成爲《每日新聞》的記者。在福島支局工作後,作爲軍隊記者報導中國戰線。因涉嫌持有資料而被逮捕,從四六年到次年,在北京的戰俘收容所中。回到日本後,到每日新聞東京本社工作,並開始進行翻譯活動。該部譯作,最初是在五〇年由ジープ社出版,後來由河出書房刊行之。
  • 中國民俗方言謠諺叢刊》,八冊,江蘇廣陵古籍刻印社以一九八九年五月出版。今天逛的時候,店內標價凡五千日圓。內容其實是對古代方言謠諺類書的影印。方言謠諺,有向群眾嘴裏求,和向鄉邦文獻裏求,兩種求法。個中利弊,從一九二零年代歌謠徵集運動開始,就每有爭論。按說既然是謠諺了,是群眾的真感情、真語言,無問百姓,反倒去翻古書做訓詁,有點反直覺,有種求禮於野,只爲把經註的慣性感。然而真的開始想要蒐集謠諺了,和做這些事情的人接觸了,就難免悲哀地發現大家最終還是陷入到一種歷史的漩渦裏,恐慌於俗世生活、俗世語言的不厚重,而要從古籍找東西爲自己背書,證明其有來有自,傳承有序,焉乃正確。甚者攘除異己,扶出一個正來,然後守著正在那邊創新,我則不知道該說什麼好了。
  • 廣東語詞典》,臺灣總督府編。當日看到的是國書刊行會在一九九三年十一月復刻的版本。觀編者單位即知,是日據時期的著作。標題說的「廣東語」,其實指的是臺灣客家話。翻開詞典,也很容易看到客家話的一個特徵詞「个」(音「gai」,意即「的」,原書用假名注音)。當時有司統計族裔,客家人就被登記爲「廣東人」——雖然客家人不盡然都來自廣東。(順帶一提,臺灣客語已有在線詞典語料庫,熱心有志,居功厥偉。)
  • 《現代支那語の科學》(現代漢語的科學)。我乍一看以爲是研究當時漢語裏的科學術語的(其實若然,標題不能這麼寫),然後從中可能可以看出一些近代東亞細亞詞彙與知識的流動的真趣來;但實際上是研究現代漢語(這個「現代」其實是民國大陸時期了)的。有講述北京音打敗南京音成爲正統的故事,也有對「現代」漢語的語法學描述。
  • 宣教師漢文小説の研究》(傳教士漢文小說研究),宋莉華著,鈴木陽一監譯,青木萌譯。這本的中文原著,我滿七世求不得,今天反而讓我撞見日譯本了。居然有日譯本,大概是日本研究界也把注意力放過來了?我能聯想到的相關研究,除了近代的交涉史這種顯而易見的關鍵詞,大概就是文類的輸入、語詞與知識的流動、特定文體對外來思想、價值的收編,這樣的了。另外一提「監譯」,這也是日本特色,華語世界少見。最後買了這本,售價是碼洋的一半左右。

接著去了波多野書店。店內則與前一家大不同,賣的是日本相關書籍了——歷史方面等等。在這裏買了:

  • 《蹇蹇錄》,政治家陸奧宗光的自傳回憶錄。《》:「王臣蹇蹇,匪躬之故」。書名自焉。意即大臣爲了國君,不避艱險。《錄》中有提到朝鮮東學黨起義、甲午戰爭、馬關條約、三國干涉還遼。初於一八九六年刊行。
  • 《北槎聞略》,桂川甫周著。昔大黑屋光太夫口述俄國風土,甫周聽而記之,厥有此帙。一七九四年編成。凡正文十有一卷,附錄一卷,衣類器什圖等二軸,地圖十頁。

凡兩本。皆屬岩波文庫。都是高中時候就聽聞,想著有日能一睹的書了。因爲實在是對這個曾經同樣通行漢文的國家,如何面對近代化浪潮的,留下了哪些時代的肉筆,而感到興趣。

長島書店。店內文學的書佔不少,大概可分爲日本文學,以及華語文學(的日譯,及其在日本、以日語寫就的研究著作)。

這種街邊而立的書棚,也是神保町的風景了。書棚上一般擺的是定價便宜——一兩百日元左右——的書,當然也有寫重量級全集擺在櫥櫃上。過路行人可以隨意駐足,翻閱,有興趣的話則被「誘」至店內。到了大雨時節,店員慌忙從店內擁出,爲書棚套上塑料膜,或者拉上防水帘,也是一種在地的風物詩了

文華堂。滿櫥子都是戰爭相關的書籍。近代以來、二戰爲止的書又佔了絕大部分——例如滿蒙問題舊日軍等。在此之前的,例如剛步入近代時的削藩,也有見到相關書籍。

中川書店。這本書基本可以說是迎合日本大部分中年男人趣味的書店了——大人的教養,曾經的日本有多好,日本該往何處去之類。

南海堂書店。以日本史的書籍爲主,其中大量近代史內容。

神田古書中心導覽牌

之後進到了神田古書センター(神田古書中心)。是一棟大樓,一到五層都是舊書店。一層是書肆高山本店。店中書類,跨度很大,趣味性質很強。一半是日本傳統戲曲,剩下一半是廚藝、武術(日本武道、中國武術——少林拳法、氣功等)、書道字帖之類。

二層爲漫畫店——夢野書房。大部分都是上了年頭的漫畫單行本、雜誌,紙面都有些泛黃。然而有一本《鬼滅之刃》,堂而皇之立在中間,竟毫無違和感。

三層爲鳥海書房,主打自然主題,動植物、本草、釣魚相關。四層爲梓書房,教育關聯。

みわ書房門口

五層爲みわ書房,都是舊童書。有過道通往薰風花乃堂,爲古物、礦物關聯,牆上也掛了一些植物畫。

古書中心旁邊,是神保町ブックセンター(神保町圖書中心)。一層爲書店+咖啡店,可以在店內消費飲食,一邊喝咖啡一邊從壁櫥上取書看;也可以只買書;也可以二者兼有。

二層是秦川堂書店。店內裝潢頗爲考究。店內的古書,有百年前的教科書、老文獻等。此外有老地圖、經年但無字跡的美術明信片、舊觀光手冊、舊溫泉攻略、舊書籤等古物,即便不是專門的收藏家,拾其一二,以爲裝點,也頗雅緻。

神保町圖書中心牆上的地圖

神保町一帶不止舊書店、藝術品店,一路逛下來,還有看到家古著店。

街角拐彎後,到了大雲堂書店。售書以日本史爲主,也有書道的內容。

明倫館書店,屬於自然科學系(雖然門口裝有文學賞的應徵廣告),內爲國內外的自然科學、建築、風景園林讀物。想到以前朋友來日本看望我,其中有學習風景園林者,我也有帶他們來這裏逛。

一誠堂書店。賣日本文學的書,也有賣日本近代以來出版事情的書。對於出版宅、書物宅(有這類宅的吧?)來說,應會感到興奮。看到書店的棕色櫃檯,我就想起一段往事。那時候我第一次來神保町,到這邊興奮地挑書,結帳的時候發現帶的錢不夠,還差一兩百日元。店頭的老爺爺問我是不是學生,從哪裏來,在哪裏唸書,教我務必勤勉於學習,然後居然把這不足的錢給免了。這實在是讓我很意外,我都做好割愛的準備了。想來實在暖心。

澤口書店的書,則與以下界隈相關:鐵道、電影、思想。當然也有一些日本文學作品(例如宮澤賢治)的初版。

不遠處,小宮山書店令我十分震撼。這家不純賣書。一層就是舊電影海報、畫集、寫真集、單幅畫作、單幅寫真等,陳列於櫥櫃,掛於壁上待沽。但如是則已矣。登到二層,簡直是三島樂園。牆上貼著三島由紀夫的照片(其中有與川端康成同框的照片),賣有三島文學研究的著作、三島由紀夫的真跡。其中一幅,是縱書「三島由紀夫」五字。一幅寫著:

鍊武

又一幅寫著:

處卋若大夢

太白。讀之慨然。

三層則是一些前衛藝術。四層未去,震撼而離店。

最後從神保町車站上車,結束了一日的覯書之旅。


當然,神保町一帶的書店,遠不止這些。有些人統計爲四百家,有些統計則爲130家。今天隨便走走逛逛,已感到有些累。要全部逛個遍,相信需要時間。

今天所逛過的書店,也並沒有盡記在上,只是挑了些特點殘留在腦海中的寫了。其實神保町有兩家在愛書人士當中相當有名的中文書店——東方書店內山書店

一直幻想在這樣的舊書店中打工的樣子。整理這些老書,與不同時代、地域的思想們對話,與懂行的顧客暢聊,與乘興而來的路人介紹自己的所愛。翻書,對客,個中趣事還可與網上書友分享。多事一件美事。以前關注過一位在這樣的書店裏打工的博主,後來他回中國任教了。往後沒在網上再看到相關的分享了。

🔲 ☆

PHP-FPM站点池配置调优以解决WordPress过度占用系统资源之问题

white vintage typewriter with paper

众所周知,WordPress,简直电老虎来的。

从笔者实际运营经验来看,查看服务器状态,就会经常看到php-fpm: pool (站点域名)这一进程,对鄙服务器CPU资源的占用,其最低线稳定于⅔,动辄则99%,甚至于满格。

为此可能需要调整pm.max_childrenpm.start_serverspm.min_spare_serverspm.max_spare_servers这些参数。

参数配置之位置

PHP-FPM站点池的配置文件通常位于/etc/php/(版本号)/fpm/pool.d/www.conf。当然,有时根据实际情况,修改的对象会是/etc/php/(版本号)/fpm/pool.d/(具体域名).conf

在一些环境下,上述文件可能不支持直接修改,或者修改后也不会永久性保存。这时则根据实际情况来找寻配置位置,例如网站根目录下的.user.ini(记得配置好正确的文件权限)。

参数之修改

在许多PHP-FPM默认配置文件里,pm模式会被设置为ondemand,而pm.max_children的值被设置为4。在ondemand模式下,PHP-FPM只会在请求到达时启动新的子进程,并在子进程空闲一段时间后关闭它们。这种模式对于低流量的网站是非常有效的,但对于高流量的网站可能会导致额外的CPU消耗,因为需要频繁地创建和销毁子进程。以下是一些可能的优化建议:

  1. 修改进程管理模式

    • 考虑将pm模式从ondemand更改为dynamic。在dynamic模式下,PHP-FPM将保持一定数量的空闲进程,以便能够更快地响应新的请求。
      pm = dynamic
      
  2. 增加最大子进程数

    • pm.max_children的值决定了同时能处理多少请求。如果服务器资源允许,你可能想要增加这个值以处理更多的并发请求。
      pm.max_children = 16  ; 或更高的值,视服务器资源而定
      
  3. 设置服务器进程数

    • dynamic模式下,你还可以设置pm.start_serverspm.min_spare_serverspm.max_spare_servers参数来控制空闲进程的数量。例如:
      pm.start_servers = 2
      pm.min_spare_servers = 2
      pm.max_spare_servers = 4
      
  4. 优化最大请求数

    • pm.max_requests参数决定了每个子进程在重新生成之前可以处理多少请求。这可以防止可能的内存泄漏。你可以根据贵站的具体情况调整这个值。
      pm.max_requests = 5000  ; 或其他适合你的网站的值,4000可能够用了
      
  5. 调整空闲进程超时

    • pm.process_idle_timeout参数决定了一个子进程在被关闭之前可以保持空闲多长时间。你可以根据需要调整这个值。
      pm.process_idle_timeout = 20s  ; 或其他适合你的网站的值
      

以上建议可以帮助你优化PHP-FPM的配置,以更好地处理高流量并减少CPU的消耗。每次修改配置后,确保重启PHP-FPM服务以使更改生效。同时,建议在一个测试环境中尝试这些更改,以确保它们不会影响贵站的正常运行。

PHP的其他调优方向

  1. PHP版本

    • 确保正在使用的是最新的PHP版本。新版本的PHP通常会提供更好的性能和错误修复。
  2. 慢日志

    • 启用PHP和MySQL的慢日志功能,以便识别和优化执行缓慢的代码和查询。

PHP之外的其他因素

WordPress网站报错和服务器资源过度使用,也可能是由多种因素造成。

本文仅聚焦于PHP配置,只不过若果按照本文方式修改,仍有问题,也可以考虑下如下方向:

  1. 插件问题

    • 有些插件可能会消耗大量的资源。尝试禁用所有插件,然后逐个重新启用,以确定是哪个插件造成的问题。
  2. 主题问题

    • 有些主题可能编写不佳或包含不必要的功能,从而消耗大量资源。尝试切换到一个简单的主题,如WordPress的默认主题,看看问题是否得到解决。
  3. 代码优化

    • 确保你的WordPress网站代码优化良好。这包括简化代码,删除不必要的代码,以及确保代码没有错误。
  4. 数据库优化

    • 数据库查询可能会消耗大量资源。使用数据库优化插件,或手动清理和优化数据库。
  5. 缓存

    • 安装和配置缓存插件,以减少服务器的负载。例如,你可以使用W3 Total Cache或WP Super Cache插件。
  6. CDN服务

    • 使用内容分发网络(CDN)来减轻服务器的负担,并加速网站加载速度。
  7. 服务器配置

    • 检查服务器配置,特别是PHP和MySQL的配置。可能需要调整某些设置以改善性能。例如,可能需要增加PHP内存限制或优化PHP-FPM配置。
  8. 服务器资源

    • 考虑升级到一个更强大的服务器,或者增加更多的CPU和内存资源。
  9. 定期监控

    • 使用服务器监控工具来跟踪资源使用情况,以便在问题发生时快速识别和解决。
🔲 ☆

1951年《華東軍政委員會頒佈發展農業生產十大政策》

福建省人民政府農林廳印。2018年11月,偶於網間拾得此文件,公諸維基文庫


福建省人民政府農林廳所印版本
福建省人民政府農林廳所印版本

華東軍政委員會頒佈發展農業生產十大政策佈告。全文如下:

查獎勵勞動,發展農業生產,是我人民政府的旣定政策,也是土地改革的直接目的。現華東土地改革卽將基本完成,為發展農業生產,保護勞動所得,保護勞動人民在土地改革中的勝利果實,特頒佈下列十大政策:

(一)保護農民已分得的土地及財產,不得侵犯。

(二)保護農民的勞動所得及合法利得,不得侵犯。

(三)獎勵勞動模範,普及農業科學知識,提倡農業科學研究,獎勵發明創造。

(四)貫澈中央獎勵生產的負担政策,凡同等的土地,因勤勞耕作、善於經營,其收穫量超過常年應產量者,仍照常年應產量計算,不多計;因怠於耕作,其收穫量不及常年應產量者,亦照常年應產量計算,不少計。

(五)根據自願和等償交換的原則,因地制宜,發展農村中的勞動互助;並按自願互利原則,發展合作供銷事業。

(六)借貸自由,有借有還,利息由雙方自行議定,並獎勵城市資金下鄉,保護其合法經營及利得。

(七)允許富農經濟發展,勞動僱傭自由,工資待遇應根據兩利原則和政府法令雙方協議。

(八)做好烈、軍屬代耕工作,鼓勵烈、軍屬及榮軍,積極參加生產。

(九)督促地主、懶漢積極參加生產,不准荒廢土地,嚴懲地主匪特破壞生產的不法行為。

(十)在土地改革尚未實行地區,仍應繼續貫澈減租,保障佃權及誰種誰收等旣定的生產政策。

望各級人民政府對上述十大政策,廣泛進行宣傳解釋,務使家喻户曉,一體遵行,為要。

福建省人民政府農林廳印

🔲 ⭐

1959年《閩東農業小手冊》

福建省福安市农业局编印。

2018年11月,偶于网间拾得,公诸维基文库。按原文献字形录入,原文用简化字的就录简化字,用传统字形的也录传统字形。

原题《閩东农业小手册》。


三、阳历推算和节气

阳历推算歌

阳历眞方便,人人会推算。一年十二月,三百六十天。

月大三十一,月小三十天,七前单月大,八后单月小。

二月二十八,閏年加一天。何时始逢閏,四除公历年。

农家阳历二十四节氣歌

春雨惊春清谷天,夏滿芒夏暑相連;

秋处露秋寒霜降,冬雪雪冬小大寒;

本月两节日期定,年年如此少变更;

上半年来六廿一,下半年来八廿三;

人人熟記节气歌,按时耕种过豐年。

四、氣象資料

地名平均初、終霜間日数初日終日統計年份
平均初日

(月、日)

历史最早初日

(年、月、日)

平均終日

(月、日)

历史最迟終日

(年、月、日)

寿宁

(鰲阳)

136日11月3日1958年10月27日3月1?日1957年4月4日1956—1958年
福鼎

(桐山)

104日12月2日1954年11月19日3月16日1956年4月4日1953—1957年
福安

(城关)

75日12月13日1942年10月23日2月?3日1943年4月9日1937—1958年
宁德

(三都)

9日1月25日1954年12月13日2月3日1956年2月20日1953—1957年
罗源

(桂林)

92日12月12日1958年11月23日3月14日1957年4月4日1956—1958年
长乐

(鶴上)

36日1月4日1955年12月1日2月8日1955年2月22日1951—1958年
各地历年平均霜期

(阙如)

名称氮磷鉀含量%每百斤折合每百斤折合每百斤折合
硫酸銨斤数人粪尿斤数过磷酸鈣斤数蒸骨粉斤数硫酸鉀斤数草木灰斤数
2.1333.71400.06.65.94.442.?
1.3430.41260.05.85.22.82?.?
1.4022.1920.012.411.12.928.0
1.235.3220.01.81.72.624.0
1.3027.91660.015.013.42.72?.0
1.9024.01000.010.09.04.038.0
0.9716.4680.08.27.32.019.4
1.781.2470.015.013.53.73.5
1.3017.3720.06.55.82.726.0
1.079.6400.03.63.22.221.4
0.889.7400.01.21.01.817.6
人粪尿0.500.100.302.4100.00.50.40.66.0
其中:人粪1.000.300.404.8200.0105.01.30.88.0
人尿0.500.050.202.4100.00.20.20.44.0
猪粪0.600.450.502.9120.02.22.01.010.0
猪尿0.300.130.201.460.00.60.60.44.0
牛粪0.300.250.101.460.01.21.10.22.0
牛尿0.801.403.8160.02.928.0
馬粪0.500.350.302.4100.01.81.60.66.0
馬尿1.201.505.8240.03.130.0
羊粪0.750.600.303.6150.03.02.70.66.0
羊尿1.400.052.206.7280.00.20.24.644.0
鳮粪1.631.540.807.8326.07.76.91.817.0
鴨粪1.001.400.624.8200.07.06.31.312.4
鵝粪0.550.540.952.6110.02.72.42.019.0
粪砒0.7023.003.4140.0115.0103.0
蚕沙1.450.250.118.0290.01.21.10.22.2
粪尿肥对幾种常用肥料析合率
❌