[{"content":"Hugo 负责把markdown文章变成网页，虽然主题可以在themes中单独设置。\n但是一个站点如果想在 Stack 和 Blowfish 之间切换，很容易遇到一些问题：\n文章、图片和配置散落在不同仓库，修一篇文章要复制两份，换主题还会产生大量重复文件变更。 即使 使用一个themes文件夹组织不同的主题，layouts、shortcode、_index.md可能不同。 本文按实际仓库结构说明如何把文章集中管理，让两个主题读取同一份内容，同时保留各自的网站样式和配置。\n1. 三个仓库 这套多主题结构由三个 Git 仓库组成。同时最后推送到github.io仓库中，实际应该算是四个。\nYourName/you-blog-content 是内容仓库，保存文章 Markdown、文章图片和附件。它是文章的唯一源头。新增文章、修改正文或更新图片，都在这里完成。 YourName/you-blog-stack 是 Stack 站点源码，保存 Stack 主题、主题配置、站点布局、菜单、自定义样式和 Stack 专属页面。 YourName/you-blog-blowfish 是 Blowfish 站点源码，保存 Blowfish 主题、主题配置、站点布局、菜单、自定义样式和 Blowfish 专属页面。 两个站点源码仓库中都有一个名为 shared-content/ 的子模块目录。它是内容仓库在当前站点中的工作副本和版本指针，不是第二份需要维护的文章。真正的文章仍以 you-blog-content 的 main 分支为准。更新内容时先推送内容仓库，两个站点的部署工作流会读取它的最新版本。\n每个仓库的目录大致如下：\nyou-blog-content/ └── content/ └── blog/ └── my-post/ ├── index.md └── assets/image.png you-blog-stack/ 或 you-blog-blowfish/ ├── config/ # 当前主题和站点设置 ├── content/ # 当前主题专用页面 ├── layouts/ # 当前站点的模板和短代码 ├── themes/ # Stack 或 Blowfish 主题 └── shared-content/ # 子模块，指向内容仓库 这样拆分后，两个主题仓库仍可以独立调整外观。例如，Stack 可以使用自己的首页布局、菜单和文章卡片，Blowfish 可以保留另一套首页和主题选项。文章标题、正文、日期、分类、标签及附件则集中在内容仓库，不需要因为切换主题而复制一份。\n主题仓库里的 content/ 仍有用途，但它只保存和站点展示方式有关的页面。常见例子包括 content/_index.md 首页、content/search/index.md 搜索页、content/blog/_index.md 栏目介绍，以及只对某个主题有意义的说明页。两边这些文件可以不同，也可以有相同的路径；它们不会取代共享文章，除非文件路径完全相同。\n判断一个文件应该放在哪里，可以问自己一个简单问题：“换成另一个主题后，这个文件还应该原样使用吗？”如果答案是肯定的，通常适合放进内容仓库。例如文章正文、发布日期、分类、标签、引用资料和文章配图，都是读者关心的内容。如果答案是否定的，通常应留在主题仓库。例如首页欢迎语、某主题的导航菜单、搜索页模板、侧边栏配置、主题短代码和 CSS，都属于站点的呈现方式。\n栏目页需要根据用途决定归属。若栏目页只是给某一个主题设置标题、封面和简介，就留在对应站点的 content/\u0026lt;section\u0026gt;/_index.md。若两个站点必须使用完全相同的栏目文字，也可以把它放进内容仓库；但要先确认另一个站点没有同路径的本地文件覆盖它。刚开始拆分时，建议先让共享仓库只保存文章和附件，减少两个主题互相影响的机会。\n草稿也可以放在共享内容仓库。Hugo front matter 中的 draft = true 可以控制默认构建是否包含草稿，但两个站点的构建选项必须一致。若构建命令启用了草稿，文章可能意外进入发布结果。公开发布前应检查文章的 draft、发布日期、密码字段和图片链接；内容共享不会自动替文章完成审核。\n2. Hugo 如何把同一篇文章交给两个主题 在内容仓库中，每篇文章通常是一个页面目录，Markdown 文件名为 index.md。文章自己的图片和附件放在相邻目录中，形成 Hugo 的 page bundle。例如：\nyou-blog-content/content/blog/hugo-build/index.md you-blog-content/content/blog/hugo-build/assets/stack-menu.png 这篇文章在内容仓库里的完整路径是 content/blog/hugo-build/index.md。在网站源码中，它会通过 shared-content/content 出现。Hugo 仍按照内容目录生成 URL，所以文章网址是 /blog/hugo-build/，而不是包含 shared-content 的路径。图片也属于同一个页面包，可以从文章中按相对路径引用。\n两个站点都需要在 config/_default/hugo.toml 中配置内容挂载。先挂载站点自己的 content，再挂载共享内容：\n[[module.mounts]] source = \u0026#34;content\u0026#34; target = \u0026#34;content\u0026#34; [[module.mounts]] source = \u0026#34;shared-content/content\u0026#34; target = \u0026#34;content\u0026#34; Hugo 会把两个来源合并为一个虚拟的 content/。挂载顺序决定同名文件的优先级：本地 content/ 在前，所以站点专属首页或栏目说明优先；共享仓库提供其余文章。尽量避免在两边创建相同的文章路径，这样读者和维护者都能明确知道哪份文件会生效。\n在 Stack 源码仓库中，配置文件属于 you-blog-stack/config/_default/hugo.toml；在 Blowfish 源码仓库中，则属于 you-blog-blowfish/config/_default/hugo.toml。两份配置都要有上述挂载，但主题名称、评论、侧边栏、配色等设置各自维护。内容仓库不保存主题文件，也不决定当前网站使用哪个主题。\n保留挂载顺序很重要。Hugo 按照挂载层次创建统一的虚拟内容目录；如果共享文章和本地站点页面恰好使用同一路径，本地 content/ 中的文件会优先。比如两个来源都存在 blog/_index.md，页面最后显示的是本地版本。遇到“我明明改了文章，但页面没变化”时，先检查是否有同路径的本地文件遮住了共享版本，再检查子模块是否更新到了预期提交。\n正文尽量使用两边都支持的 Markdown。短代码则要格外留意：Stack 和 Blowfish 内置的短代码并不完全相同。如果文章使用了某个主题专有短代码，另一个主题构建时可能报“找不到短代码”，或者输出不符合预期。要让文章在两个主题中都能构建，可以优先使用标准 Markdown；确实需要自定义效果时，在两个站点都提供同名短代码，并分别检查渲染结果。\n每添加一个短代码，都应把“文章源码”和“短代码实现”分开考虑。文章源码属于共享内容，两个站点都要读它；短代码模板属于站点布局，应分别保存在两个站点仓库的 layouts/_shortcodes/。模板名称和参数约定要保持一致，否则一个主题可能把参数显示成普通文本，或无法处理图像、表格和折叠面板。可以先挑一篇同时使用该短代码的测试文章，在两个主题的本地预览中确认标题、样式、链接和移动端显示。\n封面字段也可能不同。例如，Stack 读取 image，Blowfish 读取 featureimage。如果同一张图片要作为两个主题的封面，可以在文章 front matter 中同时设置这两个字段：\nimage = \u0026#34;assets/cover.png\u0026#34; featureimage = \u0026#34;assets/cover.png\u0026#34; 每个主题只使用自己认识的字段，因此共存不会互相覆盖。若文章没有明确封面，就不要添加这两个字段；不要假设主题会从任意图片自动挑选封面。分类、标签、标题和日期通常可以共用，但新文章最好分别在本地预览两个主题，确认分类名称、封面比例、短代码和页面资源路径符合预期。\n图片建议使用相对路径并和文章一起提交，不要依赖个人电脑上的绝对路径。把图片放入该文章的 page bundle 后，文章和附件可以一起移动、备份和审查。对于远程图片，要考虑原站删除、访问限制或跨域问题；如果图片必须长期显示，优先保存有权使用的本地副本。两个主题的封面比例可能不同，即使共用同一张图片，也应分别检查裁切效果。\n3. 从写作到两个主题分别发布 第一次克隆任一站点仓库后，需要初始化主题和内容子模块。在站点仓库根目录执行：\ngit submodule update --init --recursive 这会下载 themes/ 中的主题子模块和 shared-content/ 内容子模块。内容仓库是私有仓库，因此本机 GitHub 账号需要有读取权限；GitHub Actions 使用的访问令牌也必须能读取它。缺少权限时，子模块初始化会提示认证失败，Hugo 后续则会因为找不到文章而少生成页面。\n写新文章前进入共享内容目录，并切换到内容仓库的 main 分支：\ncd shared-content git switch main git pull --ff-only origin main 然后创建文章目录，例如 content/blog/hugo-build/index.md，把图片放在同一个文章目录中。写完后，仍在 shared-content/ 内提交并推送：\ngit add content/blog/hugo-build git commit -m \u0026#34;Add Hugo build guide\u0026#34; git push origin main 到这里，文章只在内容仓库提交了一次。两个站点的 GitHub Actions 都配置为手动触发；每次构建时会先拉取内容仓库 main 的最新提交，再安装 Hugo、构建页面并运行加密脚本。发布提交会记录当次使用的内容版本，之后可以根据提交号确认网站由哪一版文章构建。\n如果希望本机的站点源码也固定到新内容版本，在回到主题仓库根目录后，可以更新并提交子模块指针：\ncd .. git add shared-content git commit -m \u0026#34;Update shared content reference\u0026#34; git push 这个指针记录该主题仓库默认关联的内容版本，便于其他人克隆时得到一致结果。Stack 和 Blowfish 可以分别更新各自的指针；即使暂时没有更新指针，线上手动部署仍会按工作流设置读取内容仓库的最新 main。\n这里有两个容易混淆的版本：子模块指针是本地站点仓库记录的默认内容版本；部署工作流拉取的则是运行时 main 分支最新版本。工作流明确更新到最新内容，因此即使子模块指针暂时落后，部署仍会使用新内容。若想让其他协作者克隆后得到相同的本地预览结果，应同时更新并提交两个站点仓库中的子模块指针。提交内容后看到 shared-content 显示修改，是 Git 在提醒子模块当前版本和站点仓库记录的版本不同，并不代表文章又复制了一份。\n日常编辑时可以按以下顺序操作：先在内容仓库 main 拉取最新改动，再编辑一篇文章及其附件；本地预览 Stack 和 Blowfish；确认没有缺图、短代码错误或不需要的封面后，提交并推送内容；最后分别手动运行两个主题的部署。若只想试用另一个主题，可以只运行它的部署，再把 GitHub Pages 的发布来源切到对应分支。内容仓库更新、主题部署和 Pages 分支切换是三个独立动作，不要把它们误认为同一次操作。\n如果只更新了文章，却没有重新运行部署工作流，线上页面不会自动变化，因为部署是手动触发的。如果 Stack 更新成功而 Blowfish 没有更新，检查是否也运行了 Blowfish 的工作流；如果工作流失败，先看日志中的共享内容拉取步骤是否通过，再看 Hugo 构建错误。如果两个站点显示的文章版本不同，可以比较发布提交信息里的内容提交号。这样能快速判断它们是否使用同一版内容，而不必比较两套生成后的 HTML 文件。\n要同时更新两个主题的发布结果，分别在 you-blog-stack 和 you-blog-blowfish 的 Actions 页面手动运行部署工作流。Stack 工作流把生成结果写入 YourName/you-blog-pages 的 stack 分支，Blowfish 工作流写入 blowfish 分支；GitHub Pages 当前选择哪个发布分支，就展示哪个主题。运行另一个主题的工作流只更新它自己的发布分支，不会自动切换 Pages 设置。切换主题时，再到 Pages 的发布来源设置中选择 stack 或 blowfish。\n最后要区分“内容源文件”和“发布后的加密页面”：加密发生在构建阶段，内容仓库中仍保存 Markdown 原文以及加密所需的信息。不要把私有内容仓库改为公开仓库，也不要把访问令牌写进文章或配置文件。令牌应放在 GitHub Secrets 中，并只授予读取内容仓库及写入发布仓库所需的权限。按这个分工，文章维护只有一个入口，主题源码各自独立，构建和切换过程也更容易检查。\n4. 子theme仓库固定版本 之前我使用的方法是fork一份永不更新，问过AI后发现有更好的做法，就是直接追theme仓库的指定提交。\n可以用下面这套流程检查，关键是看仓库记录的 提交 SHA。\n在 Stack 仓库根目录运行：\ngit submodule status themes/hugo-theme-stack shared-content git ls-tree HEAD themes/hugo-theme-stack shared-content 比如我的是：\n3e123a30... themes/hugo-theme-stack (v4.0.3) f3eb8007... shared-content 前面没有 +，表示本地子模块与站点仓库记录的版本一致。git ls-tree 会显示父仓库保存的 SHA；Stack 现在固定在 3e123a30...，恰好对应 v4.0.3。上游以后发布新版本，不会自动改变这个 SHA。普通的 git pull 和 git submodule update 也不会把 Stack 升级。\n只有明确更新 Stack 子模块、再提交新的子模块指针，版本才会改变。更新前可以先选定一个版本，例如 vX.Y.Z：\ngit -C themes/hugo-theme-stack fetch --tags git -C themes/hugo-theme-stack checkout vX.Y.Z git add themes/hugo-theme-stack git commit -m \u0026#34;Update Stack theme to vX.Y.Z\u0026#34; 而相对的 shared-content虽然也是追了固定 SHA，但部署工作流（workyml）里的 Use latest shared content 会额外拉取内容仓库 main，所以部署时使用最新内容。也就是说，主题不会自动升级，部署时的文章内容会追 main。\n","date":"2026-10-02T00:00:00Z","permalink":"/blog/multi-theme-hugo-content-guide/","title":"hugo切换多主题构建"},{"content":"将 基于GTID的一主两从复制集群 接入Prometheus和Grafana。\n具体的，在prometheus.yml的target中设置三个mysql实例的地址，通过/probe?target=127.0.0.1:3306~3308交给mysqld-exporter，让mysqld-exporter基于mysql协议，通过多目标模式（multi-target）采集这些mysql实例的status信息。\n地址 部署方式 一主两从 127.0.0.1:3306~3308 mysql二进制部署，添加service unit来使用systemctl管理 prometheus 19090 docker部署，host模式 mysqld-exporter 9104 docker部署，host模式\n从GTID一主三从中察取数据给prometheus监控管理。 grafana 13000 docker部署，host模式 compose相关准备 slow-query-monitor/ ├── README.md ├── .env.example # 端口与 Grafana 密码，复制成 .env 后使用 ├── .gitignore # 排除 .env 和真实的 my.cnf ├── docker-compose.yml # prometheus + mysqld-exporter + grafana ├── mysqld-exporter/ │ └── my.cnf.example # exporter 连 MySQL 的凭据，复制成 my.cnf 后填密码 ├── grafana/ │ └── provisioning/ │ └── datasources/ │ └── prometheus.yml # 数据源自动配置，启动即生效，不用在 UI 里手点 ├── prometheus/ │ ├── prometheus.yml # 多目标采集 + 自身采集 │ └── rules/ │ └── mysql-slow.yml # 慢查询 / 复制延迟 / 复制中断 / 采集失败 ├── sql/ │ ├── 01-create-exporter-user.sql # 在【当前主库】执行 │ └── 02-enable-slow-log.sql # 三个实例都要执行 └── scripts/ ├── 00-who-is-master.sh # 查当前主库（读 orchestrator API） └── check-metrics.sh # 核对指标名真的存在、连通性正常 整体docker-compose\n# 慢查询监控栈：Prometheus + mysqld-exporter + Grafana # # 三个服务全部使用 host 网络，原因见 README 第四节： # 1. 容器里的 127.0.0.1 就是宿主机，能直连 bind-address=0.0.0.0 的四个 mysqld； # 2. 源 IP 是 127.0.0.1，exporter 账号可以收紧成 \u0026#39;exporter\u0026#39;@\u0026#39;127.0.0.1\u0026#39;，不必开 \u0026#39;%\u0026#39;； # 3. 这台机器上跑着 mihomo（Clash），桥接网络里的容器会被 fake-ip DNS 坑到， # host 网络全程只用 IP，不触发 DNS 解析。 # # 副作用：端口不走 ports: 映射，而是由各服务命令行的监听参数决定。 name: mysql-slow-monitor services: # ---------------------------------------------------------------- exporter # 一个进程覆盖三个 MySQL 实例：靠 mysqld-exporter 的多目标模式， # Prometheus 用 /probe?target=127.0.0.1:3306 逐个探，凭据从挂载的 my.cnf 读。 mysqld-exporter: image: prom/mysqld-exporter:v0.20.0 container_name: mysqld-exporter restart: unless-stopped network_mode: host command: - --config.my-cnf=/etc/mysqld_exporter/my.cnf - --web.listen-address=:${EXPORTER_PORT:-9104} # 下面三个是默认就开的，显式写出来方便看出采集了什么 - --collect.global_status # Slow_queries 等 SHOW GLOBAL STATUS 计数器，慢查询监控的核心 - --collect.global_variables # long_query_time 等变量，用于确认配置真的生效了 - --collect.slave_status # 复制延迟/IO/SQL 线程状态，只在从库上有数据 # 按需打开的采集器 - --collect.engine_innodb_status # SHOW ENGINE INNODB STATUS：死锁、history list - --collect.binlog_size # binlog 体积，复制健康度 - --collect.perf_schema.eventsstatements # 慢 SQL digest 下钻，会查 performance_schema # - --collect.info_schema.innodb_metrics # 指标数量很大（数百个），默认不启用 volumes: # 注意：若 my.cnf 不存在，Docker 会把挂载源当成目录自动建出来， # 结果是一个名为 my.cnf 的目录，exporter 会以\u0026#34;读不到凭据\u0026#34;的怪错启动失败。 # 启动前必须先 cp my.cnf.example my.cnf。 - ./mysqld-exporter/my.cnf:/etc/mysqld_exporter/my.cnf:ro # -------------------------------------------------------------- prometheus prometheus: image: prom/prometheus:v3.15.0 container_name: prometheus restart: unless-stopped network_mode: host command: - --config.file=/etc/prometheus/prometheus.yml - --web.listen-address=:${PROMETHEUS_PORT:-9090} - --web.enable-lifecycle # 允许 POST /-/reload 热加载配置和规则 - --storage.tsdb.retention.time=15d volumes: - ./prometheus:/etc/prometheus:ro # 用命名卷而不是 bind mount：镜像里 /prometheus 的属主是 nobody(65534)， # 命名卷会继承镜像内目录的属主所以开箱可用；bind mount 到一个 root 属主的 # 宿主机目录则会以 \u0026#34;permission denied\u0026#34; 反复重启。 - prometheus-data:/prometheus # ----------------------------------------------------------------- grafana grafana: image: grafana/grafana:13.2.3 container_name: grafana restart: unless-stopped network_mode: host environment: # 3000 被 orchestrator.service 占用，这里必须用别的端口 - GF_SERVER_HTTP_PORT=${GRAFANA_PORT:-13000} - GF_SECURITY_ADMIN_PASSWORD=${GF_ADMIN_PASSWORD:-admin} # 传给 grafana/provisioning/datasources/prometheus.yml 里的 ${PROMETHEUS_PORT}， # 让数据源地址和 Prometheus 实际端口永远一致（Grafana 的 provisioning # 文件支持环境变量插值，所以这里是唯一需要维护端口的地方） - PROMETHEUS_PORT=${PROMETHEUS_PORT:-9090} - TZ=Asia/Shanghai volumes: - grafana-data:/var/lib/grafana # 数据源自动配置。只读挂载，防止容器往里写东西后宿主机上看不出来。 - ./grafana/provisioning:/etc/grafana/provisioning:ro volumes: prometheus-data: grafana-data: mysqld-exporter # my.cnf.exmaple # mysqld-exporter 的 MySQL 凭据。 # 复制成 my.cnf 后再改：cp mysqld-exporter/my.cnf.example mysqld-exporter/my.cnf # # 真实的 my.cnf 已被 .gitignore 排除，只有这个 .example 入库。 # # 为什么放在这里而不是容器环境变量： # 本项目用 mysqld-exporter 的\u0026#34;多目标模式\u0026#34;，一个 exporter 容器同时服务 3306/3307/3308。 # 多目标模式下凭据从本文件的 [client] 段读取，目标地址由 Prometheus 通过 # /probe?target=127.0.0.1:3306 传进来，因此不能再用单目标的 DATA_SOURCE_NAME 环境变量。 # # 密码必须与 sql/01-create-exporter-user.sql 里创建的账号一致。 [client] user = exporter password = CHANGE_ME prometheus prometheus/premetheus.yaml\n# Prometheus 配置：多目标模式采集三个 MySQL 实例 # # 两个硬性提醒： # 1. Prometheus 的配置文件不支持读环境变量。下面标注了「与 .env 保持一致」的 # 两处地址，改 .env 里的端口时必须同步改这里。 # 2. static_configs 里只写地址，绝不写 role: master / role: slave 这类标签 —— # 这台集群已经切过主（README 第一节），静态角色标签切完就是错的。 # 需要判断主从时，从指标反推：有 mysql_slave_status_* 的就是从库。 global: scrape_interval: 30s evaluation_interval: 30s external_labels: cluster: mysql-ms-01 rule_files: - /etc/prometheus/rules/*.yml # 没接 Alertmanager，告警只在 Prometheus 的 /alerts 页面可见。 # 要外发（飞书/钉钉/邮件）时再按 README 第十节扩展。 # alerting: # alertmanagers: # - static_configs: # - targets: [\u0026#39;127.0.0.1:9093\u0026#39;] scrape_configs: - job_name: prometheus static_configs: # 【必须与 .env 的 PROMETHEUS_PORT 一致】Prometheus 的配置文件不支持读环境变量， # 所以改端口时这里必须手改——这是本方案唯一需要\u0026#34;改两处\u0026#34;的地方。 # 当前 19090：本机 mihomo（Clash）占着 Prometheus 的出厂默认端口 9090。 # # 写错的症状很隐蔽，记一下：target 状态显示 down、lastError 是 # \u0026#34;server returned HTTP status 404 Not Found\u0026#34;——因为你其实采到 mihomo 上了， # 它的 external-controller 在 9090，/metrics 是 404。 # 这个 job 只采 Prometheus 自己的运行指标，挂了不影响 mysql 那三个 target， # 所以很容易一直没发现。 - targets: [\u0026#39;127.0.0.1:19090\u0026#39;] - job_name: mysql scrape_interval: 15s # 多目标模式：每个 target 都去 exporter 的 /probe 端点探，而不是各自的 /metrics metrics_path: /probe params: # 对应 mysqld-exporter 挂载的 my.cnf 里的 [client] 段； # 不写这个参数时默认值也是 client，写出来是为了自解释。 auth_module: [client] static_configs: - targets: - 127.0.0.1:3306 - 127.0.0.1:3307 - 127.0.0.1:3308 labels: cluster: mysql-ms-01 relabel_configs: # 把 target 地址搬到 /probe 的 target 查询参数里 - source_labels: [__address__] target_label: __param_target # 让 instance 标签显示成被监控的 MySQL 地址，而不是 exporter 的地址 - source_labels: [__param_target] target_label: instance # 真正的抓取地址统一指向 exporter 自己。 # 与 .env 的 EXPORTER_PORT 保持一致。 - target_label: __address__ replacement: 127.0.0.1:9104 rules: rules/mysql-slow.yml\n# MySQL 慢查询与复制告警规则 # # 指标名的出处（都已核实，不是凭印象写的）： # mysql_global_status_slow_queries 来自 SHOW GLOBAL STATUS，值是\u0026#34;本实例启动 # 至今的慢查询总数\u0026#34;，是个只增不减的计数器 # mysql_global_status_questions 同上，总查询数 # mysql_slave_status_\u0026lt;列名小写\u0026gt; 由 BuildFQName(\u0026#34;mysql\u0026#34;, \u0026#34;slave_status\u0026#34;, 列名) # 生成，即 SHOW SLAVE STATUS 的每一列 # mysql_global_variables_long_query_time 来自 SHOW GLOBAL VARIABLES，用来核对阈值 # # 两个容易踩的坑： # 1. Slow_queries 计数器与慢查询日志开关无关，超过 long_query_time 就递增。 # 如果这里一直是 0，先查 long_query_time 是不是还停在默认的 10 秒。 # 2. mysql_slave_status_* 只在【从库】上有序列（主库 SHOW SLAVE STATUS 返回空）。 # 副本停止复制时 Seconds_Behind_Master 是 NULL，该指标直接消失而不是变成 0， # 所以\u0026#34;复制断了\u0026#34;靠 IO/SQL 线程那两条规则抓，不要指望延迟规则能兜住。 groups: - name: mysql-slow-query rules: - alert: MySQL慢查询占比偏高 # 慢查询速率 / 总查询速率。绝对值会随业务量波动，占比更能说明\u0026#34;变慢了\u0026#34;， # 而不是\u0026#34;流量涨了\u0026#34;。clamp_min 防止 0 查询时除零。 expr: \u0026gt;- sum by (instance) (rate(mysql_global_status_slow_queries[5m])) / clamp_min(sum by (instance) (rate(mysql_global_status_questions[5m])), 1) \u0026gt; 0.01 for: 10m labels: severity: info annotations: summary: \u0026#39;实例 {{ $labels.instance }} 慢查询占比超过 1%\u0026#39; description: \u0026gt;- 最近 5 分钟慢查询占总查询的 {{ printf \u0026#34;%.2f\u0026#34; $value }}%， 通常说明执行计划或数据分布变了，而不是流量上涨。 - alert: MySQL实例采集失败 # up == 0 表示 Prometheus 这次没抓到：exporter 挂了 / MySQL 连不上 / 认证失效。 # 这条同时也是\u0026#34;实例本身挂了\u0026#34;的兜底。 expr: up{job=\u0026#34;mysql\u0026#34;} == 0 for: 2m labels: severity: critical annotations: summary: \u0026#39;MySQL 实例 {{ $labels.instance }} 指标采集失败\u0026#39; description: \u0026gt;- 连续 2 分钟抓不到 {{ $labels.instance }} 的指标。依次排查： mysqld-exporter 容器是否在跑、exporter 账号密码是否与 mysqld-exporter/my.cnf 里的一致、该 mysqld 是否还在监听。 MySQL慢查询占比偏高\n- name: mysql-slow-query rules: - alert: MySQL慢查询占比偏高 # 慢查询速率 / 总查询速率。绝对值会随业务量波动，占比更能说明\u0026#34;变慢了\u0026#34;， # 而不是\u0026#34;流量涨了\u0026#34;。clamp_min 防止 0 查询时除零。 expr: \u0026gt;- # ${value} 取值是 比较符号左侧的值。 # 设置3的原因是，默认低流量的情况下，1%就是默认慢查询数量。 sum by (instance) (rate(mysql_global_status_slow_queries[5m])) / clamp_min(sum by (instance) (rate(mysql_global_status_questions[5m])), 1) * 100 \u0026gt; 3 for: 10m labels: severity: info annotations: summary: \u0026#39;实例 {{ $labels.instance }} 慢查询占比超过 2%\u0026#39; description: \u0026gt;- 最近 5 分钟慢查询占总查询的 {{ printf \u0026#34;%.2f\u0026#34; $value }}%， 通常说明执行计划或数据分布变了，而不是流量上涨。 指标\nmysql_global_status_slow_queries，Mysql执行的慢查询条数 mysql_global_status_questions，mysql执行客户端语句数量 rate(mysql_global_status_slow_queries[5m]: 最近5min每秒增加多少慢查询 sum by (instance) (rate(mysql_global_status_slow_queries[5m])) 逐实例计算 clamp_min(sum by (instance) (rate(mysql_global_status_questions[5m])), 1) # 如果questions_rate\u0026lt;1, 强行当做1,， 进入mysql实例查看 SHOW GLOBAL STATUS LIKE \u0026#39;Slow_queries\u0026#39;; SHOW GLOBAL STATUS LIKE \u0026#39;Questions\u0026#39;; # 236 / 345600, # 1s 后 变成 239 / 345690 在无业务流量的情况下，主要的慢查询都是mysqld-exporter产生的，可以让mysqld-exporter产生的查询不计入慢查询。使用SET SESSION long_query_time = 60; 来调高记录慢查询的门限。 grafana/provisioning 数据源自动配置，启动即生效，不用在grafana的explore中手动输入promql\ndatasources/prometheus\n# Grafana 数据源自动配置（provisioning） # # 放在这个目录里，Grafana 启动时会自己读，不用在 UI 里手点。 # 相比手点的好处： # - 换台机器照搬这个目录就能复现，不依赖\u0026#34;我记得当时点过什么\u0026#34; # - URL 不会手抖打错（手点最容易把 http:// 写成 https:// 或者漏端口） # - timeInterval 这种容易被忽略但影响出图质量的设置，写死在文件里 # # 官方文档：https://grafana.com/docs/grafana/latest/administration/provisioning/ apiVersion: 1 datasources: - name: Prometheus type: prometheus # access: proxy = 由 Grafana 后端去请求 Prometheus（UI 里叫 Server access）。 # Prometheus 数据源只支持这一种。 access: proxy # host 网络下，容器里的 127.0.0.1 就是宿主机，所以这就是宿主机的 Prometheus。 # ${PROMETHEUS_PORT} 由 docker-compose.yml 里 grafana 服务的 environment 传进来， # 值来自 .env —— 改了端口不用动这个文件。 url: http://127.0.0.1:${PROMETHEUS_PORT} # 固定 uid。导入社区看板时，看板是按 uid 引用数据源的， # 不固定的话每次重建数据源 uid 都会变，看板就\u0026#34;找不到数据源\u0026#34;了。 uid: prometheus isDefault: true # 设 false 是为了防止有人在 UI 里改了设置，却以为 provisioning 文件还在生效 editable: false jsonData: # 必须和 prometheus.yml 里的 scrape_interval 一致（15s）。 # 不设的话 Grafana 可能按比采集间隔更细的步长去查询， # 结果是图上出现锯齿状或空洞——数据本身没问题，是查询步长的问题。 timeInterval: 15s httpMethod: POST prometheusType: Prometheus dashboards里面的可以自己写，这里我就不post了。\ndocker部署 设置三个mysql实例的别名，方便进入mysql client管理：\nalias mysql3306=\u0026#39;/usr/local/mysql/bin/mysql -uroot -h127.0.0.1 -P3306 -pb617@cumt\u0026#39; alias mysql3307=\u0026#39;/usr/local/mysql/bin/mysql -uroot -h127.0.0.1 -P3307 -pb617@cumt\u0026#39; alias mysql3308=\u0026#39;/usr/local/mysql/bin/mysql -uroot -h127.0.0.1 -P3308 -pb617@cumt\u0026#39; 1、在主库上创建exporter账号\n-- 01-create-exporter-user.sql -- 创建 mysqld-exporter 使用的监控账号 -- -- 在【当前主库】上执行，不要在每个实例上都建。 -- 3306/3307/3308 里有两个是 super_read_only，直接在只读实例上 CREATE USER 会被拒绝： -- ERROR 1290 (HY000): The MySQL server is running with the --super-read-only option -- 这个账号和授权会通过 GTID 复制自动下发到另外两个实例，无需手工同步。 -- -- 先执行 scripts/00-who-is-master.sh 确认当前主库是哪个端口 —— 这台集群切过主， -- 不要凭印象认定。当前是 3306： -- mysql -h127.0.0.1 -P3306 -uroot -p \u0026lt; sql/01-create-exporter-user.sql -- 密码要和 mysqld-exporter/my.cnf 里的 password 完全一致。 -- 用默认的 caching_sha2_password（MySQL 8.0 的默认插件）， -- mysqld-exporter v0.20.0 内置的 go-sql-driver 支持它，不必改回 mysql_native_password -- （那个插件在 8.0.34 起已废弃、8.4 起默认关闭）。 CREATE USER IF NOT EXISTS \u0026#39;exporter\u0026#39;@\u0026#39;127.0.0.1\u0026#39; IDENTIFIED BY \u0026#39;CHANGE_ME_Exporter@2026\u0026#39;; -- 三个权限各自的用途： -- PROCESS SHOW ENGINE INNODB STATUS / SHOW PROCESSLIST -- REPLICATION CLIENT SHOW REPLICA STATUS（复制延迟和线程状态） -- SELECT 读 performance_schema / information_schema 做下钻 -- SELECT ON *.* 已经覆盖 performance_schema，不需要再单独授权。 GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO \u0026#39;exporter\u0026#39;@\u0026#39;127.0.0.1\u0026#39;; FLUSH PRIVILEGES; -- 验证：确认账号存在且插件正确 SELECT user, host, plugin FROM mysql.user WHERE user = \u0026#39;exporter\u0026#39;; SHOW GRANTS FOR \u0026#39;exporter\u0026#39;@\u0026#39;127.0.0.1\u0026#39;; 创建成功\nmysql3306 \u0026lt; 01-create-exporter-user.sql mysql: [Warning] Using a password on the command line interface can be insecure. user host plugin exporter 127.0.0.1 caching_sha2_password Grants for exporter@127.0.0.1 GRANT SELECT, PROCESS, REPLICATION CLIENT ON *.* TO `exporter`@`127.0.0.1` 2、三个实例开慢查询\n使用set persist持久化到数据库中。\nfor p in 3306 3307 3308; do echo \u0026#34;===== $p =====\u0026#34; mysql -h127.0.0.1 -P$p -uroot -p \u0026lt; sql/02-enable-slow-log.sql done 三个实例都需要处理一遍\n不然从库上就没有自己的慢查询了。\nset persist 不会进binlog，所有不会复制到从库上。set persist写的是实例自己的datadir下面的mysqd-auto.cnf。\nsql/02-enable-slow-log.sql\n-- 打开慢查询统计 -- -- 【三个实例都要执行】—— 这是实例级参数，不会通过复制下发。 -- for p in 3306 3307 3308; do -- mysql -h127.0.0.1 -P$p -uroot -p \u0026lt; sql/02-enable-slow-log.sql -- done -- -- 为什么用 SET PERSIST 而不是 SET GLOBAL、也不是改 /etc/my*.cnf： -- -- SET GLOBAL 立即生效，但重启后丢失，回到默认的 10 秒。 -- 改 my.cnf 最规范，但要重启 mysqld。这台机器上 orchestrator 在跑， -- 重启主库会触发一次自动切换；而且 3306 的 cnf 里硬编码了 -- read_only=1，重启后它会以只读身份回来，集群会在没有可写主库的 -- 状态下等 orchestrator 救场。所以能不动重启就不动。 -- SET PERSIST 立即生效 + 写进 datadir 下的 mysqld-auto.cnf，重启不丢， -- 而且下次故障切换、被提升为主库的实例依然带着这套配置。 -- -- 关于只读实例：3306/3307/3308 里的从库是 super_read_only。服务器只读模式挡的是 -- 普通表的写入，SET PERSIST 写的是配置文件，不受影响；文档里那类 -- \u0026#34;ERROR 1238 ... is a read only variable\u0026#34; 说的是 version/log_bin 这种 -- 【只读变量】，和服务器只读模式是两回事。下面三个都是动态变量。 -- 慢查询判定阈值，单位秒，支持小数。默认 10 秒，对绝大多数业务来说太宽松了。 -- 这里取 0.1 秒（和 prometheus-examples 示例一致）。生产环境按自己的 SLA 定， -- 通常 0.1 ~ 1 秒。 SET PERSIST long_query_time = 0.1; -- 打开慢查询日志。 -- 注意：Slow_queries 这个计数器跟日志开关无关，超过 long_query_time 就递增； -- 开日志是为了能下钻看到【具体的 SQL 文本】。 SET PERSIST slow_query_log = ON; -- 输出到文件。不用 TABLE 的原因：往 mysql.slow_log 表里写，在只读从库上更别扭， -- 而且表写满还要自己清理；文件方式由 logrotate / purge 处理更省心。 SET PERSIST log_output = \u0026#39;FILE\u0026#39;; -- 保持关闭（默认就是 OFF）。打开后会把\u0026#34;没走索引\u0026#34;的查询也计入慢查询， -- 计数器会被灌爆，告警形同虚设。需要查这类问题时临时开、用完关。 -- SET PERSIST log_queries_not_using_indexes = OFF; -- 保持关闭（默认 OFF）。它控制的是\u0026#34;从库回放主库 binlog 的语句要不要写慢日志\u0026#34;， -- 关着才不会把主库已经记过的慢查询在从库上再记一遍。 -- SET PERSIST log_slow_replica_statements = OFF; -- ---------------------------------------------------------------- 验证 -- 1) 这三个变量当前值（期望 0.1 / ON / FILE） SHOW GLOBAL VARIABLES WHERE Variable_name IN (\u0026#39;long_query_time\u0026#39;, \u0026#39;slow_query_log\u0026#39;, \u0026#39;log_output\u0026#39;, \u0026#39;slow_query_log_file\u0026#39;); -- 2) 确认真的持久化了（应该在结果里看到上面三个变量） SELECT * FROM performance_schema.persisted_variables; -- 3) 计数器确实在动：跑一条必然超阈值的查询， -- 然后看 Slow_queries 是否 +1（这是 rate/increase 计算的原始指标） SELECT SLEEP(1); SHOW GLOBAL STATUS LIKE \u0026#39;Slow_queries\u0026#39;; 3、准备.env和my.cnf\ncp .env.example .env cp mysqld-exporter/my.cnf.example mysqld-exporter/my.cnf vi mysqld-exporter/my.cnf # 填 ③ 里的密码 vi .env # 填 GF_ADMIN_PASSWORD 4、拉取镜像\ndocker compose pull docker images | grep -E \u0026#39;prometheus|mysqld-exporter|grafana\u0026#39; # 三个都在了才算好 5、启动并验证\ndocker compose up -d bash scripts/check-metrics.sh # 核对指标名 + 三实例连通性 curl -s \u0026#39;http://127.0.0.1:9090/-/ready\u0026#39; 然后浏览器开 http://127.0.0.1:9090/targets，4 个 target 全 UP 就成了\n监控慢sql和主从延迟 总之看起来是比直接使用pt-query-digest或者直接看slow.log来的舒服，更不用提还可以配置自动告警了。\nPrometheus http://aios:19090/alerts\n警告时：\nGrafana 监控慢sql速率、增量、占比，QPS、主从up状态，主从延迟，最慢sql等等。\n写PromQL\n","date":"2026-09-29T22:01:19+08:00","permalink":"/blog/mysql-monitor/","title":"使用Prometheus和Grafana监控mysql主从延迟和慢查询"},{"content":"为什么需要高可用？ 规模越大、业务越关键，单点故障带来的代价越难以承受。单台数据库实例，无论硬件多可靠，故障是必然会发生的：磁盘损坏、内存故障、机房断电、误操作,这些都可能让这台唯一的主库瞬间不可用。\n如果不使用高可用架构，那么RPO和RTO都无法保证。必须要有自动化的、即时的高可用方案，来将「数据丢失」和「恢复时间」保证在可控范围。\n搭建结构 这里使用一个简单的高可用方案：\n角色 最少机器数量 高可用的说明 基于GTID异步复制的MySQL一主两从 至少 3台（每台一个MySQL实例） mysql1/2/3 分布在不同物理机/可用区，才能真正做到高可用 Orchestrator + 元数据库 建议独立 1台（或多台组集群） Orchestrator自身也建议3节点做Raft集群，避免它自己是单点 ProxySQL 建议独立 1-2台（多台做负载均衡） 作为流量入口，也要避免单点故障 总结 最低高可用配置：mysql3台，orch+orch元数据库 1台， ProxySQL1台，至少需要5台。 其中：\n基于GTID异步复制的MySQL一主两从：用GTID简化基于传统位点的主从复制，但是复制本身仍然是异步复制（主库不等从库确认就返回成功），所以RPO较大，没有被GTID解决。可以使用半同步方案来减少这一数据丢失风险。 Orchestrator 负责拓扑发现、故障检测、候选主库选举和自动切换。 ProxySQL 提供固定的读写、只读入口，并根据 Orchestrator API 和 MySQL read_only 状态更新路由。 在单机上实现，或者在腾讯云/阿里云的弹性ECS上按小时租，这里我在单机上实现：\nCompose 服务 管理地址 角色 mysql1 127.0.0.1:3306 master，可写 mysql2 127.0.0.1:3307 GTID slave 从库，只读 mysql3 127.0.0.1:3308 GTID slave 从库，只读 orchestrator-db 127.0.0.1:3309 Orchestrator 元数据库 orchestrator 127.0.0.1:3000 拓扑管理、自动故障切换主库 ，旧主库重启后不会回归 proxysql 127.0.0.1:6032(管理)/6033(读)/6034(写) 读写分离和连接路由 GTID异步复制的Mysql Replication拓扑 二进制安装几个mysql实例：单二进制脚本🔗link\nsudo python3 mysql_binary_install.py \\ --archive ~/mysql/Notes-Mysql/Mysql-Installer/mysql-8.0.37-linux-glibc2.28-x86_64.tar.xz \\ --md5sum 4ef87d5f5160a6565e767ec10002ce74 \\ --root-password \u0026#39;Your@password123\u0026#39; \\ --port 3306 \\ --bind-address 0.0.0.0 \\ --server-id 101 \\ --report-host 127.0.0.1 \\ --report-port 3306 sudo python3 mysql_binary_install.py \\ --archive ~/mysql/Notes-Mysql/Mysql-Installer/mysql-8.0.37-linux-glibc2.28-x86_64.tar.xz \\ --md5sum 4ef87d5f5160a6565e767ec10002ce74 \\ --root-password \u0026#39;Your@password123\u0026#39; \\ --port 3307 \\ --bind-address 0.0.0.0 \\ --server-id 102 \\ --report-host 127.0.0.1 \\ --report-port 3307 \\ --datadir /var/lib/mysql-3307 \\ --config /etc/my-3307.cnf sudo python3 mysql_binary_install.py \\ --archive ~/mysql/Notes-Mysql/Mysql-Installer/mysql-8.0.37-linux-glibc2.28-x86_64.tar.xz \\ --md5sum 4ef87d5f5160a6565e767ec10002ce74 \\ --root-password \u0026#39;Your@password123\u0026#39; \\ --port 3308 \\ --bind-address 0.0.0.0 \\ --server-id 103 \\ --report-host 127.0.0.1 \\ --report-port 3308 \\ --datadir /var/lib/mysql-3308 \\ --config /etc/my-3308.cnf 修改3307和3308上的slave配置：\n// etc/my-3307.cnf, etc/my-3308.cnf [mysqld] read_only = ON super_read_only = ON 在主库上添加replication账号：\nCREATE USER \u0026#39;repl\u0026#39;@\u0026#39;%\u0026#39; IDENTIFIED BY \u0026#39;Your@password123\u0026#39;; GRANT REPLICATION SLAVE ON *.* TO \u0026#39;repl\u0026#39;@\u0026#39;%\u0026#39;; 在从库上进行复制配置\n# 开启tls CHANGE REPLICATION SOURCE TO SOURCE_SSL = 1; # 设置主库 CHANGE REPLICATION SOURCE TO SOURCE_HOST = \u0026#39;127.0.0.1\u0026#39;, SOURCE_USER = \u0026#39;repl\u0026#39;, SOURCE_PASSWORD = \u0026#39;Your@password123\u0026#39;, SOURCE_PORT = 3306, SOURCE_AUTO_POSITION = 1; # 作为主从的 从节点 START REPLICA; # SHOW REPLICA STATUS\\G 此时主从搭建完成，等待主从同步结束，两个slave 3307和3308上面也有了replication的账号。\nOrchestrator 个人还是推荐docker安装，本地部署需要查不少手册，有点累。\n安装\nwget https://github.com/openark/orchestrator/releases/download/v3.2.6/orchestrator_3.2.6_amd64.deb dpkg -i ... dpkg -r orchestrator 安装目录\n安装目录：/usr/local/orchestrator /usr/local/orchestrator/ ├── orchestrator # 主程序二进制（19MB，2021-07-27） ├── orchestrator-sample.conf.json ├── orchestrator-sample-sqlite.conf.json └── resources/ ├── bin/orchestrator-client # 客户端脚本 ├── templates/ # Web UI 模板 ├── public/ # 前端 js/css/图片 ├── metrics/orchestrator-grafana.json └── pseudo-gtid/ 配置/etc/orchestrator.conf.json\n{ \u0026#34;Debug\u0026#34;: false, \u0026#34;ListenAddress\u0026#34;: \u0026#34;:3000\u0026#34;, \u0026#34;MySQLOrchestratorHost\u0026#34;: \u0026#34;127.0.0.1\u0026#34;, \u0026#34;MySQLOrchestratorPort\u0026#34;: 3306, \u0026#34;MySQLOrchestratorDatabase\u0026#34;: \u0026#34;orchestrator\u0026#34;, \u0026#34;MySQLOrchestratorUser\u0026#34;: \u0026#34;orch_backend\u0026#34;, \u0026#34;MySQLOrchestratorPassword\u0026#34;: \u0026#34;orch_backend_pass\u0026#34;, \u0026#34;MySQLTopologyUser\u0026#34;: \u0026#34;orch_topo\u0026#34;, \u0026#34;MySQLTopologyPassword\u0026#34;: \u0026#34;orch_topo_pass\u0026#34;, \u0026#34;InstancePollSeconds\u0026#34;: 5, \u0026#34;DiscoverByShowSlaveHosts\u0026#34;: true, \u0026#34;UnseenInstanceForgetHours\u0026#34;: 240, \u0026#34;RecoveryPeriodBlockSeconds\u0026#34;: 3600, \u0026#34;RecoverMasterClusterFilters\u0026#34;: [\u0026#34;*\u0026#34;], \u0026#34;PromotionIgnoreHostnameFilters\u0026#34;: [], } 创建orch元数据库\nsudo python3 mysql_binary_install.py \\ --archive ~/mysql/Notes-Mysql/Mysql-Installer/mysql-8.0.37-linux-glibc2.28-x86_64.tar.xz \\ --md5sum 4ef87d5f5160a6565e767ec10002ce74 \\ --root-password \u0026#39;Your@password123\u0026#39; \\ --port 3309 \\ --bind-address 0.0.0.0 \\ --server-id 104 \\ --report-host 127.0.0.1 \\ --report-port 3309 \\ --basedir /usr/local/mysql \\ --datadir /var/lib/mysql-3309 \\ --config /etc/my-3309.cnf alias mysql3309=\u0026#39;/usr/local/mysql/bin/mysql -uroot -h127.0.0.1 -P3309 -pYour@password123\u0026#39; 在主库上创建mysql用户，通过replication复制到从库上：\nCREATE USER \u0026#39;orch_topo\u0026#39;@\u0026#39;127.0.0.1\u0026#39; IDENTIFIED BY \u0026#39;orch_topo_pass\u0026#39;; GRANT PROCESS, REPLICATION SLAVE, REPLICATION CLIENT, RELOAD ON *.* TO \u0026#39;orch_topo\u0026#39;@\u0026#39;127.0.0.1\u0026#39;; GRANT REPLICATION_SLAVE_ADMIN, GROUP_REPLICATION_ADMIN ON *.* TO \u0026#39;orch_topo\u0026#39;@\u0026#39;127.0.0.1\u0026#39;; 在从库上select user, host, plugin from mysql.user;做验证。\n在orch元数据库上创建用户\nCREATE USER \u0026#39;orch_backend\u0026#39;@\u0026#39;127.0.0.1\u0026#39; IDENTIFIED BY \u0026#39;orch_backend_pass\u0026#39;; CREATE DATABASE IF NOT EXISTS orchestrator; GRANT ALL ON orchestrator.* TO \u0026#39;orch_backend\u0026#39;@\u0026#39;127.0.0.1\u0026#39;; 进行orch发现\ncurl -s \u0026#34;http://127.0.0.1:3000/api/discover/127.0.0.1/3306\u0026#34; 有：\n可以手动测试下故障切换\nsystemctl stop mysqld 停止3306后 3308自动提升为主库，自动切主的时候将新主库的两个read_only修改为false。\nmysql\u0026gt; SELECT VARIABLE_NAME, VARIABLE_VALUE -\u0026gt; FROM performance_schema.global_variables -\u0026gt; WHERE VARIABLE_NAME IN (\u0026#39;read_only\u0026#39;, \u0026#39;super_read_only\u0026#39;); +-----------------+----------------+ | VARIABLE_NAME | VARIABLE_VALUE | +-----------------+----------------+ | read_only | OFF | | super_read_only | OFF | +-----------------+----------------+ 2 rows in set (0.00 sec) mysql\u0026gt; SHOW global VARIABLES LIKE \u0026#39;%read_only\u0026#39;; +-----------------------+-------+ | Variable_name | Value | +-----------------------+-------+ | innodb_read_only | OFF | | read_only | OFF | | super_read_only | OFF | | transaction_read_only | OFF | +-----------------------+-------+ 4 rows in set (0.01 sec) proxySQL 用于分流的工具，提供固定的读写、只读入口，并根据 Orchestrator API 和 MySQL read_only 状态更新路由。\n在故障切换的时候，自动切换可写主库，在应用层我们只需要填写两个东西：读写url+用户名密码、只读url+用户名密码。\n需要在orchestrator 配置的json中添加钩子：\n{ \u0026#34;PostMasterFailoverProcesses\u0026#34;: [ \u0026#34;curl -fsS -X POST http://proxy-sync:8080/sync \u0026gt;/dev/null 2\u0026gt;\u0026amp;1 || true\u0026#34;, \u0026#34;echo \u0026#39;Master failover complete: {failedHost}:{failedPort} -\u0026gt; {successorHost}:{successorPort}\u0026#39; \u0026gt;\u0026gt; /tmp/recovery.log\u0026#34; ], \u0026#34;PostIntermediateMasterFailoverProcesses\u0026#34;: [ \u0026#34;curl -fsS -X POST http://proxy-sync:8080/sync \u0026gt;/dev/null 2\u0026gt;\u0026amp;1 || true\u0026#34;, \u0026#34;echo \u0026#39;Intermediate master failover complete: {failedHost}:{failedPort} -\u0026gt; {successorHost}:{successorPort}\u0026#39; \u0026gt;\u0026gt; /tmp/recovery.log\u0026#34; ], \u0026#34;PostGracefulTakeoverProcesses\u0026#34;: [ \u0026#34;curl -fsS -X POST http://proxy-sync:8080/sync \u0026gt;/dev/null 2\u0026gt;\u0026amp;1 || true\u0026#34;, \u0026#34;echo \u0026#39;Graceful takeover complete: {failedHost}:{failedPort} -\u0026gt; {successorHost}:{successorPort}\u0026#39; \u0026gt;\u0026gt; /tmp/recovery.log\u0026#34; ], } 这块要记录的内容比较多，有时间以后再写。\n","date":"2026-09-27T21:56:00+08:00","permalink":"/blog/mysql-s1mple-ha/","title":"Mysql简单高可用搭建: 异步主从复制+Orchestrator+ProxySQL"},{"content":"先亮fnos版本，在26年上半年的0day漏洞导致几十万台公网裸奔的飞牛变成肉鸡之后，我重装了飞牛版本到1.1.18，主要还是眼馋飞牛的美丽UI，最近在一次断电后到现在连续开机27天，某天闲的无聊看了下内存占用，常用进程+系统内存占用杂七杂八的占用2G+，然后一看使用内存4G+，于是有点纳闷剩下来的2G是谁在使用。\n使用htop查出来是一个叫nmbd的进程（这个名字是在很难绷啊🫥）SMB的一个子服务进程，负责 NetBIOS 名称服务和浏览(browsing)相关的功能。\n❯ ps -eo pid,user,rss,vsz,cmd | grep nmbd 1266 root 2112100 2261868 /usr/sbin/nmbd -D 实际内存2G，虚拟内存2G。\n重启nmbd即可\nsystemctl restart nmbd # htop 按 Mem排序 验证 fnOS 社区也有人反馈过 nmbd 产生大量日志、循环执行工作组相关操作的问题，link here：出现大量nmbd日志 - BUG反馈 飞牛私有云论坛 fnOS。使用journalctl每隔30s都是nmbd的无穷输出，即使你没有开启smb服务。\n这很难绷啊。虽然fnos目前仍然是个玩具，但是谁不希望你拥有和truenas一样的稳定性呢？老是出这个问题那个问题。\n","date":"2026-08-07T13:11:40+08:00","permalink":"/linux/nmbd/","title":"飞牛nmbd进程内存泄露"},{"content":"A 在实际开发中，有时需要临时修改第三方依赖的源码来定位问题或验证修复方案，不同语言生态的做法略有不同，总体上来说，java是最简单的，c++和go差不多，python是相对比较复杂的。\nJava：得益于类加载机制，只需在自己项目的 source path 下新建一个与目标依赖完全相同的包名（package）和类名，编译时该类会覆盖 classpath 中依赖包里的同名类，从而实现无需修改依赖本身即可调试的效果。Java的这点确实很方便，以前用这个方法定位过 easypoi 的问题。 Python：常见方式是用 pip install -e . 做可编辑安装，或者直接找到 site-packages（pip show \u0026lt;package\u0026gt; 可查看路径）里的源码进行修改；也可以把依赖源码整体拷贝到项目内，通过调整 sys.path 优先级或使用 PYTHONPATH 让本地版本优先加载。 C++ ：由于普遍采用源码 vendor 或静态/动态链接的方式管理依赖，通常做法是把第三方库源码拷贝到项目的 third_party 或 vendor 目录，通过 CMake 的 add_subdirectory（或 FetchContent 指向本地路径）引入本地修改版本，替代原本从包管理器或远程仓库拉取的版本。 B 接着说说Go：可以利用 go.mod 的 replace，将依赖指向本地拷贝的源码目录，下面详细说明具体步骤：\n先定位依赖源码的缓存位置，可通过 GOPATH 找到：\ngo env GOPATH echo $(go env GOPATH)/pkg/mod/github.com/example/awesome-pkg@v1.2.3/ 在当前项目下新建 third_party 目录，并将其加入 .gitignore（避免把第三方源码提交进仓库），然后把依赖源码拷贝进去：\ncp -r $(go env GOPATH)/pkg/mod/github.com/example/awesome-pkg@v1.2.3 ./third_party/awesome-pkg 修改 go.mod，通过 replace 指令让依赖指向本地目录：\n// 原来 // require github.com/example/awesome-pkg v1.2.3 replace github.com/example/awesome-pkg =\u0026gt; ./third_party/awesome-pkg // 最终类似如下 module my-project go 1.25 require ( github.com/example/awesome-pkg v1.2.3 ) replace github.com/example/awesome-pkg =\u0026gt; ./third_party/awesome-pkg 接着对本地源码目录赋予相应权限，方便编辑调试：\nchmod -R u+x ./third_party/awesome-pkg cd ./third_party/awesome-pkg nano xxx.go 修改完成后即可正常编译调试，go build/go run 会自动使用本地修改后的版本。\n调试结束后记得移除 replace 指令并删除 third_party 目录，避免影响正式构建。\n","date":"2026-08-04T16:21:45+08:00","permalink":"/go/go-dependency-debug/","title":"go mod依赖源码本地修改与调试"},{"content":"Pre 为什么要升级 新版本bitwarden client登录旧版本vaultwarden和web-vault的兼容性问题。\n我的vaultwarden版本是\n❯ docker logs vaultwarden | head -n 50 /--------------------------------------------------------------------\\ | Starting Vaultwarden | | Version 1.34.1 | |--------------------------------------------------------------------| ❯ docker exec vaultwarden /vaultwarden --version Vaultwarden 1.34.1 Web-Vault 2025.5.0 换用别的电脑/手机 安装bitwarden ios/android/windows/macos客户端 或者 浏览器插件 ，发现都在登录时出现意外错误。\n很难绷的一个问题，刚开始以为是本地服务器上的证书自动化工具certbot向let\u0026rsquo;s encrypt申请的的ssl证书缺少中间证书的问题，经过验证后发现fullchain中的确是两层证书。\n后续发现是vaultwarden服务端不适配最新bitwarden客户端的问题，解决方法有两种：一种是降级bitwarden官方客户端，另外一种是升级vaultwarden的服务端。\nA 客户端降级 可以在bitwarden的历史页面下载旧版本的客户端/插件\nhttps://github.com/bitwarden/clients/releases\ntags:browser 下载浏览器插件，解压后拖入浏览器的插件安装页面。 tags:desktop 桌面客户端 vaultwarden 1.34.1 可以适配\nbrowser v2025.10.1 desktop v2025.10.0 这些服务端上破坏性的变更导致之前的老版本客户端无法使用，这还是挺蛋疼的，可以选择留一份过去版本的客户端。\nbitwarden经常对「安卓客户端/macos客户端/windows客户端/浏览器插件」和「server服务端」作修改，导致新版本的bitwarden客户端完全连接不上旧版本的vaultwarden。对于旧版本的浏览器扩展，可以在~~~~crx4chrome~~~~中下载旧版。 对于旧版本的浏览器客户端或者扩展，可在bitwarden的历史页面下载。另外一种方式是 重新修改并部署新版本的vaultwarden。\nB 服务端升级 B.1 升级之前 升级之前确认\n原版本 vw的数据保存位置 上一版的docker-compose VaultWarden版本\n❯ docker exec vaultwarden /vaultwarden --version Vaultwarden 1.34.1 Web-Vault 2025.5.0 确认数据保存位置\ndocker inspect vaultwarden \\ --format \u0026#39;{{range .Mounts}}{{println .Type .Source \u0026#34;-\u0026gt;\u0026#34; .Destination}}{{end}}\u0026#39; 部署目录的tree显示：\n❯ pwd \u0026amp;\u0026amp; tree -L 2 /home/dockers/vaultwarden . ├── vw-compose.yml ├── vw-data │ ├── attachments │ ├── db.sqlite3 │ ├── db.sqlite3-shm │ ├── db.sqlite3-wal │ ├── icon_cache │ ├── rsa_key.pem │ ├── sends │ └── tmp docker-compose：\n❯ cat vw-compose.yml services: vaultwarden: image: vaultwarden/server:latest container_name: vaultwarden restart: always environment: DOMAIN: \u0026#34;https://yoursite\u0026#34; # 使用反向代理时必填；您的域名；Vaultwarden 需要知道它是 https 才能正确处理附件 SIGNUPS_ALLOWED: \u0026#34;false\u0026#34; # 创建账户后，使用 \u0026#34;false\u0026#34; 停用此选项，这样就不会有陌生人注册了 volumes: - ./vw-data:/data # : 前面的路径可以修改 ports: - 11001:80 # 您可以将 11001 替换为您喜欢的端口 B.2 升级 1、确认compose识别正常 2、停止vaultwarden 3、备份数据和配置 4、修改compose，固定版本，方便下次升级 5、拉取镜像、启动 1、确认compose识别正常\ncd /home/dockers/vaultwarden 确认 Compose 识别正常：\ndocker compose -f vw-compose.yml config 2、停止容器\nsqlite文件一览：\ndb.sqlite3 db.sqlite3-shm db.sqlite3-wal 说明 SQLite 正在使用 WAL 模式。为了获得一致的数据库备份，建议先停止容器：\ndocker compose -f vw-compose.yml stop vaultwarden 确认已停止：\ndocker ps -a --filter name=vaultwarden 状态应该是 Exited。\n3、备份数据和配置\n执行：\nmkdir -p backups sudo tar -czpf \\ \u0026#34;backups/vaultwarden-$(date +%Y%m%d-%H%M%S).tar.gz\u0026#34; \\ vw-compose.yml vw-data 检查备份：\nls -lh backups 再简单验证压缩包：\ntar -tzf \u0026#34;$(ls -t backups/vaultwarden-*.tar.gz | head -n 1)\u0026#34; | head -n 30 应该能够看到：\nvw-compose.yml vw-data/ vw-data/db.sqlite3 vw-data/attachments/ vw-data/sends/ vw-data/rsa_key.pem 为了保险，也可以生成校验值：\nsha256sum \u0026#34;$(ls -t backups/vaultwarden-*.tar.gz | head -n 1)\u0026#34; 4、修改Compose并固定版本\n不建议继续使用：\nimage: vaultwarden/server:latest 因为以后很难确认每次具体升级到了什么版本，也不方便回滚。\n修改为：\nservices: vaultwarden: image: vaultwarden/server:1.36.0 container_name: vaultwarden restart: always environment: DOMAIN: \u0026#34;https://yoursite\u0026#34; SIGNUPS_ALLOWED: \u0026#34;false\u0026#34; volumes: - ./vw-data:/data ports: - \u0026#34;11001:80\u0026#34; 可以直接执行：\nsed -i \\ \u0026#39;s#image: vaultwarden/server:latest#image: vaultwarden/server:1.36.0#\u0026#39; \\ vw-compose.yml 检查修改结果：\ncat vw-compose.yml 验证语法：\ndocker compose -f vw-compose.yml config 5、拉取并启动\n拉取镜像：\ndocker compose -f vw-compose.yml pull vaultwarden 重新创建容器：\ndocker compose -f vw-compose.yml up -d vaultwarden 注意，up -d 会替换旧容器，但继续挂载原来的：\n/home/dockers/vaultwarden/vw-data 因此数据不会被清空。\n6、验证安装\n查看日志\ndocker compose -f vw-compose.yml logs --tail=200 vaultwarden docker compose -f vw-compose.yml logs -f vaultwarden docker exec vaultwarden /vaultwarden --version 重新安装新版本后，就可以正常使用bitwarden的各种最新客户端了。\n一般vaultwarden都是追bitwarden更新的，如果新版官方客户端不能适配最新vaultwarden的话，可以降级客户端使用。\n","date":"2026-07-21T16:44:34+08:00","permalink":"/dockers/vw-bw-up-down-grade/","title":"VaultWarden升级/BW客户端降级: Bitwarden客户端/插件不能登录VaultWarden的几种解决方法"},{"content":"源自我的一个小项目hugo-blog-encrypt，主要是解决hugo加密的问题\n示例网站用的是hugo的stack主题，有两个例子：\n一个是局部加密：局部加密 另外一个是整篇加密：整篇加密 使用方式也比较简单，问问ai结合这个示例网站的仓库，然后整合到自己的hugo-blog中：hugo-encrypt-showcase。\n","date":"2026-06-21T00:00:00Z","permalink":"/blog/hugo-encrypt/","title":"hugo加密的另一种方法"},{"content":"基本啥小说都看，收录一点比较喜欢的小说。\n恋爱故事集 恋爱故事当然主要看恋爱，但是如果有别的非恋爱剧情更好，个人还是比较好这口不只有谈恋爱的。\n年纪上来了，爱看。\n书名 推荐指数 来源 书评 刀剑神域 4.5星 霓虹 推荐SAO第一卷，GGO篇，进击篇，第8卷。\n第一卷写的太出彩了，这本书基本上是我对类亚丝娜女主偏好的启蒙作。同样孤独的二人因为命运的缘分在剑与魔法的世界成为彼此的依靠，互相救赎。 异世界转生周回play 4星 霓虹，轻小说机翻机器人 剑与魔法，感情真挚，流星幻视亚丝娜，对这种细腻又带点小刁蛮的女主没有抵抗力。\n对比刀剑的优点是有女主视角，相对更美味。\n缺点是第二卷的是不同女主的前妻文学，但是这里的打分纯爱是加分，男主爱上多个女主是减分项，不管主动被动。 神秘物语 4.7星 国内 借鉴型月世界观的剑与魔法，缺点是太监（个人对烂尾的接受度较高，太监不等于烂尾，总比作者狗尾续貂好），优点是男女主很好磕，真纯爱。 逆流重启人生 3星 国内，起点 逆流90年代的故事，作为恋爱故事看不讨厌这种真挚的女孩，总之各种情节描写让我很喜欢这个女孩，不赖。\n后期Aside是事业线，Bside有部分和一些女配的纠葛。事业线很有趣，不喜欢和女配的情节，扣分。\n优点，不同于其他国内重生文Aside事业Bside恋爱双线，这本和女主的故事之前男主都在救赎自己的前世，和女主的故事线结束后才开始事业线，这点非常好评。 最长的一梦 3.5星 国内 只看前百章，倪裳和江澈，永远的意难平。后面和其他女孩的故事线都像在找倪裳的影子，喜欢前妻文学的有福了。\n作者是小鱼联盟，只写过这一本，好像在美国工作，有人说是半自传体性质的小说。 重生之心动 2.5星 国内，起点 并非纯爱，有非常恶心的Aside事业线。最好的两个女主的感情线仿佛是在诱骗两个未成年。\n我推荐的原因是对乡村生活的描写非常美好，时常能让我想起小时候那些美好的夏天，作者能少点女主，少点事业线我就喜欢看了。 八月长安故事集 4星 国内，晋江 女频我高中时期看的多，但是我记得的只有这个作者（还有荔枝？写小甜文的），女作者的文风和心思更加细腻，真挚不是工业糖精，缺点是女主得不到的男主老是遇到各种各样的天灾人祸，有点古早韩剧风？ 我的青春恋爱物语果然有问题 霓虹，gagaga文库 初中时期很喜欢的恋爱故事，一直更新到大学才算最终完结。很有青春情怀的一本小说，不打分了。\n唯一一本男女主就算只是勾勾小手都能让我心砰砰跳的作品。\n除了恋爱故事之外，日本的校园生活，东京千叶县的人文风情都是很值得一看的几点。 花与剑与法兰西 3.7星 国内，起点 女作者，但是是男频故事。非常华丽的书名与中世纪法国背景的故事，事业线和恋爱线都非常好，有病娇、青梅、妹妹、未婚妻，很喜欢。 喜欢爱情喜剧的主人公吗？ 3.5星 霓虹，轻小说机翻机器人 站内扛把子，作者现在在写冲击波，等大学篇吧。 败犬女主太多了 4星 霓虹 作为恋爱故事集，老八卷的天爱星让我有心动感，其他倒是略一般？\n作为对非传统后宫作品的解构视角起家的作品却越来越像后宫作品了？ 村上春树故事集 4星 霓虹 挪威的森林，1984，寻羊冒险记，我说村上的书和galgame是不是很像？ 更多的，朋友们可以参考机翻机器人论坛书友的帖子：现代恋爱故事集\n科幻/奇幻故事集 科幻\u0026amp;奇幻在国外貌似是不分家的，很多喜欢看三体的朋友如果去翻历届雨果还是星云奖，经常会发现许多奇幻故事也在里面。\n比较喜欢看科幻世界上的科幻小故事，但是记不得那么作者是谁了，和小时候看故事大王类似。\n更多的，b站上有一个非常专业科幻小说故事的up主：卡尔维诺不下树，朋友们可以看看他的推荐。\n简单地混着奇幻和科幻推荐几个喜欢的故事：\n书名 推荐指数 来源 书评 三体 5星 国内 夯，不用多说 地火 5星 国内 在相关专业的学校读过书，实际去过很多次现场，个人很喜欢这个故事。 诗云 5星 国内 ai时代下人类何去何从 永恒的终结 5星 国外 阿西莫夫，中篇小说，一本看完，在一次监考中看完，内心五味杂陈。 银河帝国 5星 国外 超长篇，没有看完，在高中图书馆借过前七本，庞大瑰丽的世界。 我们生活在南京 5星 国内 半夏，南京。去南京旅游的时候脑子里想的都是半夏。 黎明之剑 4.5星 国内，起点 剑与魔法，奇幻\u0026amp;科幻，种田流。大眼珠子几本小说中最能让人看进去的。 无职转生 4.5星 霓虹、轻小说 剑与魔法，正统日式奇幻流，我说他是日本的凡人修仙传有懂的吗？当然没那么凡人，虽然凡人里面的韩立也看不出来哪里凡人了。男主算是个神人（对很多人来说可能难以阅读，但是日式作品有的时候就是会把内心阴暗的部分描写的赤赤裸裸，换个角度想男主也只是个普通人而已，只是大部分小说不会把主角内心阴暗的部分写的这么详细罢了），3个女主我都很喜欢，感情线有人物弧光。 记录的地平线 4.5星 霓虹、轻小说 剑与魔法的日式mmorpg，放国内应该叫群像系统穿越流。打团本很好看。 领主 4星 国内、出版书 DNF世界观的作品，推荐从第4 or 5卷开始阅读。基于DNF所以设定更加具体。\n我说地龙铁火龙你才是女主吧。 社团故事集 社团，特指有共同目标的（甲子园、全国奖、等等等）日本高中社团故事。\n书名 推荐指数 来源 书评 吹响吧，上低音号 5星 霓虹 最好看的社团故事，好看好看好看非常好看。 一点碎碎念 作为描写社团故事集的小说很少，这很可惜。反倒是漫画/动画作品有一些相当出彩的社团故事集，比如排球少年、强风吹拂、白箱等等。 谜语人故事集 很有见地的知乎回答：谜语人、悬念、伏笔三者的区别是什么？ - 水月的回答 - 知乎。\n个人相当不能接受「规则怪谈」、「克系」的一大部分作品，核心问题就是谜语过多，叙事者扣押不给读者知道的信息太多，这种畸形的读者作者关系，会让作者在写作时更容易故弄玄虚以导致观感很差，但是仍然有许多相关受众读者。\n推荐了一些很好的小说，这里也举一些谜语人故事吧：\n书名 来源 书评 仙道尽头 起点 纯谜语人，开头的老婆和老牛也喜欢谜语，一谜语就是几百张。\n可以当最近几年起点的典型了，没有长段落，主要角色诙谐幽默，看完前百章等于看完400章。 苟在初圣魔门当人才 起点 精品谜语人小说。虽然谜语很多，但前几百章确实不错；\n人人都有身份，人人都有后手和不能直说的秘密，这你能绷住吗？\n后期套娃越来越多。 玄鉴仙族 起点 道诡异仙 起点 Re：从零开始的异世界生活 也算很谜语的一个系列，不过这个谜语卡在能接受和不能接受的范围边界上。\n叙事者的角色信息扣押会在下个轮回主角莫名知道别人的信息时看的比较爽，但是也就仅此而已了。 在网络小说中，\u0026ldquo;谜语人\u0026quot;几乎就是贬义词。谜语和解谜本身挺有意思，但很多小说把这个词用坏了：主角什么都不知道，故事却硬着头皮往下推；配角话说一半就不说了，读者被晾在那里干等答案。\n常见的借口是\u0026quot;主角实力低微，不该知道真相\u0026rdquo;，于是干脆让角色不说人话来搪塞——本该交代清楚的信息，偏偏说得含糊其辞，阅读体验大打折扣。\n这个毛病在修仙、东方玄幻、克苏鲁向、规则怪谈这几类小说里最明显。常常这些类型本身信息密度就高，有用的故事信息和可以被藏起来的谜语信息对半开。很多人把谜语人当悬念用，把伏笔埋成了谜语人。\n展开阅读：谜语人、悬念与伏笔的区别 谜语人、悬念、伏笔是有区别的，或者说服务于不同的叙事目的：\n谜语人：叙事者单方面扣着信息不给，制造\u0026quot;我知道你不知道\u0026quot;的优越感。读者的感受是被耍。 悬念：叙事者和读者站在同一条信息线上，一起面对未知。读者的感受是被拉着往前走。 伏笔：提前埋一个不起眼的细节，当下不要求任何反应，等后面被点出来才生效。读者的感受是\u0026quot;原来如此\u0026quot;。 区别的关键在于信息是否完整——谜语人是信息被藏起来读者根本拿不到；悬念是信息不全，但读者知道它不全，所以想继续看；伏笔是信息其实已经给全了，只是读者自己当时没意识到它的重要性。\n在阅读这些谜语人小说时间长了之后难免会生出疲倦，有的时候我们会反过来去阅读传统武侠、出版轻小说之类。\n传统出版小说是写完之后再面对读者的，哪怕连载，作者心里也大致清楚故事会走向哪里，后面发生什么、谁的秘密什么时候揭开，这些在动笔前或者写作过程中已经想得差不多了。金庸的有些小说甚至反复修订过多版，每一版都在往回补逻辑漏洞、把伏笔和结局对得更严。这意味着他扣住的每一个信息，都是真的知道答案的。 而轻小说的两大类：异世界和恋爱。霓虹异世界说实话里面有谜语人的我看的貌似不是很多，大部分主角当下接触到的就够用了，没有必要谜语，或者主角一开始就是这个世界的上限，谁还能给他说谜语啊，只有他说谜语当嘉豪的时候。恋爱系列小说呢，基本上要么在多个女生中反复拉扯水字数，要么就是纯爱铁杵磨成针、纯爱狗粮从头撒到尾。他根本没啥必要加什么谜语。 而大部分网文是边写边发，日更、按字数计费，作者自己也不知道三十万字后主角会遇到什么。这时候\u0026quot;谜语人\u0026quot;就变成了一种拖延策略——角色说\u0026quot;时机未到\u0026quot;，往往不是因为这个角色有不可说的理由，而是因为作者自己还没想好怎么说。读者其实分辨得出这两种沉默的质感差别，虽然说不清道理，但那种\u0026quot;这个悬念是装出来的\u0026quot;的感觉，就是从这里来的。很多所谓的“伏笔”，其实读得多的读者本能就会对这种所谓伏笔有预感，一眼望到头的感觉，我是来看你装杯的，不是来看你的滑稽谜语秀的。\n传统出版物的精致感不是文笔好坏的问题，而是作者在动笔之前是否已经想清楚了故事的全貌。一部有完整设计和大纲的小说，作者对每一处细节都心里有数，读者感受到的是一个已经被想透、写完、反复修订过的完整结构，即便是闲笔，读起来也带着新鲜感，因为它确实通向后面的某处。网文读者感受到的，很多时候是一个正在被实时决定、边写边想的过程——作者自己都不知道下一步要交代什么，谜语人便成了最省力的填充物。\n最后不是说谜语人不好，市面上至少在大陆还是有很多受众的，你写得好我们还是能接受你的谜语的，别管批评声音多不多，就说读者多不多吧，赚的money多不多吧，故事质量先放一边。\n","date":"2026-06-20T15:38:52+08:00","permalink":"/blog/novel-collection/","title":"小说Collection"},{"content":"前言 为什么需要使用SSL证书自动部署？\nSSL证书的时间比较短，之前可以使用1年证书的时候手动部署无所谓，但是最近两年（指从24年开始）免费证书的有效期都普遍变成了3个月。每三个月都得手动续期一下，我自己每三个月拉一次证书和私钥、然后传到服务器上的这个流程已经做了十几遍了，流汗了🤣😅。 现在的短有效期证书以后可能会更短，据说CA/Browser Forum通过的决议会在2029年让证书最长有效期会缩到47天。这种短周期会让倒闭证书的自动化部署流程，同时减少网站内容传输没有被SSL证书加密、而被劫持的风险。 现在的SSL证书本质上就是可以给你的网站内容传输做一次加密，实际上也是WebPKI信任模型的边界：证书不能帮你证明这是家合法合规公司，他只负责加密通道。 证书自动化的机制：\nACME协议把证书申请变成了可编程的API：通过DNS验证某个域名是你自己的，CA就会给你发证书。（Let's Encrypt真是能和cloudflare类比的大好人啊，我以前还以为证书都需要自己花钱买的。） 我的情况：\n我只需要更新SSL的cert和key，将cert和key替换原来的证书和密钥文件，然后重启nginx，现在需要做的就是实现这个流程的自动化。 之前我在ohttps上申请证书并部署到服务器上，使用fullchain.pem（证书和中间证书）和certkey.pem（私钥）两个文件。 现在使用certbot和python3-certbot-dns-cloudflare自动化该流程，向let's encrypt申请通配符证书到本地。 关于我为什么需要SSL证书：\nDamn的密码管理器VaultWarden强制需要SSL证书，不然不给连接。 CertBot使用 基本流程：\n1、安装 2、DNS 验证插件安装 3、使用CertBot申请证书 1、安装 两种方式： 1）使用snap安装\nsudo apt update sudo apt install snapd -y sudo snap install core sudo snap refresh core sudo snap install --classic certbot sudo ln -s /snap/bin/certbot /usr/bin/certbot 2）使用apt安装\napt install certbot 验证\ncertbot --version 2、DNS 验证插件安装 (1) 申请cloudflare的api token\n申请cloudflare的api token（注意不是global token）\n登录 Cloudflare：\n进入 → My Profile 选择 → API Tokens 点击 → Create Token 使用模板：Edit zone DNS，其中权限必须包含：Zone：DNS+Zobe Zone Resources 选择：Include → Specific zone → aoidayo.site 保存并复制完整 token。\n⚠️ 注意：不要多复制空格或换行。\n(2) DNS API自动验证\nDNS API自动验证：以Cloudflare为例：\n1、安装插件\nsudo apt install python3-certbot-dns-cloudflare 2、创建 API Token 文件\nsudo nano /root/.cloudflare.ini 写入：\ndns_cloudflare_api_token = NEywfvoLF012e5bvEQyqqntGL4V4L971YnTInGqd 设置权限：\nsudo chmod 600 /root/.cloudflare.ini 3、certbot使用 1、申请通配符证书\nsudo certbot certonly \\ --dns-cloudflare \\ --dns-cloudflare-credentials /root/.cloudflare.ini \\ -d aoidayo.site \\ -d \u0026#34;*.aoidayo.site\u0026#34; 这一步需要你添加通知邮箱和一些协议，我都默认yes了。\n❯ sudo certbot certonly \\ --dns-cloudflare \\ --dns-cloudflare-credentials /root/.cloudflare.ini \\ -d aoidayo.site \\ -d \u0026#34;*.aoidayo.site\u0026#34; Saving debug log to /var/log/letsencrypt/letsencrypt.log Requesting a certificate for aoidayo.site and *.aoidayo.site Waiting 10 seconds for DNS changes to propagate Successfully received certificate. Certificate is saved at: /etc/letsencrypt/live/aoidayo.site/fullchain.pem Key is saved at: /etc/letsencrypt/live/aoidayo.site/privkey.pem This certificate expires on 2026-05-31. These files will be updated when the certificate renews. Certbot has set up a scheduled task to automatically renew this certificate in the background. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - If you like Certbot, please consider supporting our work by: * Donating to ISRG / Let\u0026#39;s Encrypt: https://letsencrypt.org/donate * Donating to EFF: https://eff.org/donate-le - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 成功后证书在：\n/etc/letsencrypt/live/example.com/ 2、同时certbot会自动创建定时任务：\n查看：\nsystemctl list-timers | grep certbot # Tue 2026-03-03 09:46:37 CST 17h left n/a n/a certbot.timer certbot.service 3、测试续期：\nsudo certbot renew --dry-run # 测试续期 sudo certbot renew --force-renewal # 强制续期 4、添加续期后hook\n🤔 添加续期后hook，在复制证书后，自动reload nginx，最后写入nginx/ssl的op.log中。\nsudo nano /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh # sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh # 赋权 我的bash脚本如下：\n#!/bin/bash set -e # ============================== # 基本变量 # ============================== DOMAIN=\u0026#34;aoidayo.site\u0026#34; SRC_DIR=\u0026#34;/etc/letsencrypt/live/$DOMAIN\u0026#34; DST_DIR=\u0026#34;/home/dockers/nginx/ssl/aoidayo\u0026#34; LOG_FILE=\u0026#34;$DST_DIR/op.log\u0026#34; SRC_CERT=\u0026#34;$SRC_DIR/fullchain.pem\u0026#34; SRC_KEY=\u0026#34;$SRC_DIR/privkey.pem\u0026#34; DST_CERT=\u0026#34;$DST_DIR/fullchain.pem\u0026#34; DST_KEY=\u0026#34;$DST_DIR/certkey.pem\u0026#34; TIMESTAMP=$(date \u0026#34;+%Y-%m-%d %H:%M:%S\u0026#34;) # ============================== # 检查源文件是否存在 # ============================== if [[ ! -f \u0026#34;$SRC_CERT\u0026#34; || ! -f \u0026#34;$SRC_KEY\u0026#34; ]]; then echo \u0026#34;[$TIMESTAMP] ERROR: Source certificate files not found.\u0026#34; \u0026gt;\u0026gt; \u0026#34;$LOG_FILE\u0026#34; exit 1 fi # ============================== # 创建目标目录 # ============================== mkdir -p \u0026#34;$DST_DIR\u0026#34; # ============================== # 复制并覆盖 # ============================== cp -f \u0026#34;$SRC_CERT\u0026#34; \u0026#34;$DST_CERT\u0026#34; cp -f \u0026#34;$SRC_KEY\u0026#34; \u0026#34;$DST_KEY\u0026#34; # 设置合理权限 chmod 644 \u0026#34;$DST_CERT\u0026#34; chmod 600 \u0026#34;$DST_KEY\u0026#34; # ============================== # 重启 docker nginx # ============================== if docker restart nginx-server \u0026gt; /dev/null 2\u0026gt;\u0026amp;1; then echo \u0026#34;[$TIMESTAMP] SUCCESS: Certificate updated and nginx-server restarted.\u0026#34; \u0026gt;\u0026gt; \u0026#34;$LOG_FILE\u0026#34; else echo \u0026#34;[$TIMESTAMP] ERROR: Docker restart failed!\u0026#34; \u0026gt;\u0026gt; \u0026#34;$LOG_FILE\u0026#34; exit 1 fi exit 0 参考 最后给出本文参考。\n参考：\nHTTPS证书自动化部署方案求推荐，Let\u0026rsquo;s Encrypt靠谱吗？ - 杞鋂的回答 - 知乎 使用 Let‘s Encrypt 和 Certbot 为 Cloudflare 托管的域名申请 SSL 证书 - 指南 - ljbguanli - 博客园 ","date":"2026-03-02T19:58:04+08:00","permalink":"/blog/auto-ssl/","title":"CertBot自动化SSL证书部署"},{"content":"最近家里的联通光猫的3poe拨号上网的用户名和密码都变更了，导致路由器拿不到公网，因此家里的网络瘫痪了小一段时间。本来我是觉得可能是刷openwrt的路由坏了，因为这个路由器虽然配置不错（指AX3000），但其实是我在咸鱼上50r+淘的运营商定制路由器。当时春节前一天，打10010电话叫师傅过来的时候刚开始师傅脾气很冲（指师傅想跟我无限制格斗），后面耐心和他说明情况后倒没什么，发现就是路由器的3poe账号密码变了，同步修改路由和光猫两边的上网方式（从3poe改成dhcp）就可以了。\n由于家里的宽带本来也快到期了，续费时大概率还是哪家便宜选哪家，所以公网 IP 被收回对我来说几乎是注定的结果。只是这样一来，一个现实问题就摆在面前：如果不再拥有公网地址，我该如何在外网环境下访问家里的那台小服务器呢？\n这台小服务器配置还不错，功耗也低，常年开着也不心疼电费，完全闲置实在可惜。既然公网直连这条路可能走不通（其实公网直连也有坏处，年前飞牛爆出的0day漏洞让几十万台裸奔机变成了肉鸡，即使我将默认5666端口转发到了1024，也未能幸免），那就只能另辟蹊径。于是，我开始在网上查阅各种资料，系统了解“异地组网”相关的技术方案，希望找到一种既稳定又省心的远程访问方式。\n简单罗列一下的搜索到的远程访问方式：\nfrps、frpc：这是一种比较常见的内网穿透方式，平时开发用的也多，常见于将本地需要高配置才能运行的服务穿透到公网服务器上给甲方访问。但是我们这里需要访问的端口不止一个，而且鉴于安全，直接开公网端口显然不适合，所以这种方式不适用。 基于WireGuard的方案：VPN的一种，其实很多路由器的互联Mesh也是一种WireGuard，网络这块深入的我了解不太多。 WireGuard使用 WireGuard 教程：WireGuard 的工作原理 - 知乎 Tailscale + 使用云服务器自建中继Derper 的方案： Nebula WireGuard最大的问题，我是在回到学校后才发现的。这种固定 UDP 端口、固定握手格式、包长度特征明显、协议特征稳定的协议很容易被识别为WireGuard流量，在学校实验室的内网环境下会出现无法使用WireGuard协议从TailScale客户端登录到TailScale官网/自建HeadScale的情况。伪装性上不如被VPN广泛采用的Vless、XUDP、Hysteria2和Torjan。（不过最近Torjan已经很少看见了，被时代抛弃了吗😭）。 Zerotier 和TailScale一样的情况，无法在学校实验室的内网环境连接到Zerotier的Planet节点。 Syncthing：这个是中心服务器无关的，但是只能部署文件的同步，适合拿来同步eagle。 最终，我选择的是 Tailscale + 云服务器自建 DERP 中继 的方案。\n之所以做出这个选择，其实中间也经历了一些折腾。\n一开始在学院实验室内网环境下使用 Tailscale 时，怎么都连不上，节点始终无法正常建立连接，折腾得我相当头大。但奇怪的是，在宿舍的学生内网环境下却可以正常使用。两个内网之间甚至还能互相 ping 通，甚至可以说本质上就是一个内网，可偏偏 Tailscale 就是在实验室环境下水土不服。\n推测大概率是实验室网络的安全策略更严格，对 UDP 流量或打洞行为做了更细致的限制，而宿舍网络相对宽松。\n后来我又尝试了自建 Headscale 作为控制服务器，但新的问题又出现了：最新版本的 Tailscale 客户端几乎强制要求通过 HTTPS（SSL）访问 Headscale 服务器。相关报错可以在 ProgramData/Local/Tailscale 的日志里清晰看到。\n当时我是真的有点绷不住——原本只是想搭个轻量的控制器，结果还得额外折腾HTTPS。对于一个只是想打通内外网访问的人来说，复杂度明显开始上升。\n不过，后来我发现了一个曲线救国的办法：先用手机热点连接到 Tailscale 官方控制服务器，完成 STUN 打洞，让节点之间建立直连；随后再切回实验室内网环境。神奇的是，只要洞已经打通，后续的直连流量并不会被实验室网络拦截，节点之间依然可以正常互访。\n这下就简单了——既然已经能稳定打通，那就没必要继续深挖复杂部署了。与其在实验室网络环境里和各种策略，不如先在一个宽松环境下完成打洞，再切换回目标网络使用。于是我：直接采用官方控制服务器 + 自建 DERP 中继的组合，既保证稳定性，也保留一定程度的可控性。以后需要连接实验室网络时，先切手机流量打洞，再进内网，一切都简单得多。\n参考 | 自建Derper服务器\n关于自建Derper服务器中转：自建免备案防偷 Tailscale 国内中继（DERP）教程 - Sleepstars 的记录室 扩展 | 以后可以扩展实现的方案\nHeadScale配置Https：HeadScale如果配置好https应该也是能正常用的，只不过不想折腾了。 TailScale早期客户端：TailScale早期的客户端可以直连http，不过tailscale早期版本在Github上只有Code没有Release安装包。在TailScale官网上找到的最早安装包也是在1.8.x，不支持http连接。 ","date":"2026-03-02T19:45:56+08:00","permalink":"/blog/s2s-vpn/","title":"公网ip收回后：利用TailScale+自建中转Derp实现异地组网"},{"content":" Windows自带的Edge浏览器使用bing必应搜索的时候，会给你Rewards，如果你的Rewards积分积攒到一定程度（去年是17000+，现在是19000+）可以兑换肯德基/盒马生鲜等的百元代金券。这个积分主要通过你对必应的使用频率来给予积分，一般攒个半年就可以拿到这个大奖了，当然如果你还玩玩它的滑动拼图🧩小游戏（或者一些人喜欢叫它为数字华容道、八数码）的话，可能会攒的更快。\n我自己不喜欢攒淘宝的淘金币，因为它的小游戏实在是太复杂了，但是这个bing的简单小游戏我还挺喜欢的，日常完成有5积分，有时候有活动完成可以获得40+积分。\n手动解 参考：\n关于滑动九宫格拼图解法（无脑向） - 工匠物语2(SmithStory II)攻略 九宫格数字拼图（通用解法） - 知乎 你可以在这里在线开玩：在线数字华容道拼图\n手动解和编程解的方式完全不一样，人不可能记住许多状态，只能走一个相对更容易理解的路径。\n1、拼第一行 滑动九宫格可以看做若干个2*2的2宫格，每一步都是在一个独立的2*2宫格中移动。\n首先，我们可以很简单的将1移动到它应该在的左上角，在第一个2*2中做转动即可。\n接着，保持1不变，将2通过多个宫格移动到右上角。\n接下来，我们需要将3移动到蓝色方框中，这里可以分两种情况讨论\n（1）3在下面的2*3方格中，也即第二行到第三行这个范围内。\n第一种情况很简单，通过第二行和第三行两个2*2方格的顺逆时针交换即可将3移动到蓝色方框中。\n（2）3夹在1和2中间，也就是上图中8所占据的位置。\n这种情况如下图所示，3和2在一个2*2方格的相邻位置。我们需要将这种麻烦的情况转换为3在下两行的情况。\n我们首先需要将3和2换到一个2*2方格的对角位置，也即只在蓝色方框标注的两个2*2方格中变换\n首先顺时针交换326方格，改成263布局，给下面的8腾出位置，即转换263为2683\n接着，顺时针将2复原到右上角，此时3已经在下两行中，而8在1和2中间。\n接着我们顺时针移动，即可拼完第一行：\n2、转圈的准备工作 将6放在3的下面，8放在中间。\n目标状态：\n第一步是将6放在3的下面，这个如上在下两行的2*2中转圈圈即可。 8放在中间，其实就相当于将6和8放在一个2*2的顺序相邻位置“86” 我们这里只讨论顺序相邻位置为68的情况（请忽略这里的第一行）\n将6和8在一行中用第三个数隔开，然后再做交换。\n3、开转 逆时针转上两行，得到：\n接着，顺时针转3*3的大外圈，得到12368的正确顺序：\n4、左下方的2*2 接着左下方的2*2很好处理，顺逆时针转转就得到答案了。\n不存在解的情况 将这个「数字华容道 」从上到下，从左到右展开为一维序列后，如果其中的逆序对数量为偶数存在解，如果逆序对数量为奇数则不存在解。\n什么是逆序对呢？比如${ 6,1,2,3 }$ ，在前面位置的值比在后面位置的值大，比如 $6\u0026gt;{1,2,3}$，这个序列中则存在3个逆序对。\n编程解 如果使用编程解决，我们就不止需要找到一个解，而需要更进一步找到它的最优解，也就是可以解决八数码问题的最小步数。\nBFS 说到最小步数，很容易想到使用BFS解决。\n我们将起点（点Node对应的xy和数组str）加入Normal Queue，同时将「当前状态：数组str和对应步数」加入map（如果map中之前没有的话，也就是说我们第一次加入的时候就是最短step）。\n在Queue不为空的时候，不断poll访问和将周围四向点加入即可，如果poll访问的Node中的str状态就是我们需要的end状态，那么直接返回对应step。\n为了方便，将原来的二维矩阵转成字符串（一维矩阵）进行处理。\n这样带来的好处直接可以作为哈希 Key 使用，也可以很方便进行「二维坐标」与「一维下标」的转换。\n由于固定是 2∗3 的格子，因此任意的合法二维坐标 $(x,y)$ 和对应一维下标 $idx$ 可通过以下转换：\n$$idx=x∗3+y$$ $$x=idx/3,y=idx%3$$ 其余的就是常规的 BFS 过程了。\nclass Solution { int n = 2, m = 3; String e = \u0026#34;123450\u0026#34;; String s = \u0026#34;\u0026#34;; int x, y; int[] dx = {-1,0,0,1}; int[] dy = {0,-1,1,0}; /** * BFS解法 * @param board * @return */ public int slidingPuzzle(int[][] board) { // for(int i=0;i\u0026lt;n;i++) for(int j=0;j\u0026lt;m;j++){ s+=board[i][j]; if(board[i][j] == 0){ // 起点 x = i; y = j; } } int ans = bfs(); return ans; } class Node{ // 记录当前空格的位置 int x; int y; String s; public Node(int _x, int _y, String _s){ x = _x; y = _y; s = _s; } } public int bfs(){ // Queue\u0026lt;Node\u0026gt; q = new ArrayDeque\u0026lt;\u0026gt;(); // bfs必须的queue q.add(new Node(x, y, s)); HashMap\u0026lt;String, Integer\u0026gt; map = new HashMap\u0026lt;\u0026gt;(); // 记录某状态String的最小步数 map.put(s, 0); // q不空时 while(!q.isEmpty()){ Node poll = q.poll(); int old_x = poll.x, old_y = poll.y; int step = map.get(poll.s); // 如果poll的s为e，则直接返回step if(e.equals(poll.s)) return step; // 遍历四向 for(int i=0;i\u0026lt;4;i++){ // 四向合法性判断 int new_x = old_x+dx[i], new_y = old_y + dy[i]; if(new_x\u0026lt;0 || new_y\u0026lt;0 || new_x\u0026gt;=n || new_y\u0026gt;=m) continue; // 尝试更新 String update = update(poll.s, old_x, old_y, new_x, new_y); if(!map.containsKey(update)){ // 如果map包含，则说明之前已经有过，不做处理 map.put(update, step + 1); q.add(new Node(new_x, new_y, update)); } } } return -1; } public String update(String str, int old_x, int old_y, int new_x, int new_y){ // 更换old_x,old_y 和new_x,new_y 对应str位置的值 char[] chars = str.toCharArray(); int old_pos = old_x*m + old_y; int new_pos = new_x*m + new_y; char tmp = chars[old_pos]; chars[old_pos] = chars[new_pos]; chars[new_pos] = tmp; return String.valueOf(chars); } } A*搜索 1、什么是A*搜索\nA*（A-star）是一种启发式搜索算法，用于在图中找到从起点到目标的最短路径。它结合了：\ng(n)：从起点到当前节点 n 的实际代价（已经走过的步数） h(n)：从当前节点 n 到目标节点的估计代价（启发式函数） 公式：\n$$f(n)=g(n)+h(n)$$\nf(n)：A* 在优先队列中排序的依据 g(n)：已经走的步数 h(n)：预计剩余步数（越准确越快） 我们使用 数组转换的字符串 表示此时八数组的状态，比如终止状态就是end=”123450”。\n我们使用曼哈顿距离定义启发式函数h(n)，也就是当前节点与目标节点的二维坐标差的绝对值之和：\n$$h(pos_{new}, pos_{old}) = abs(new[0]-old[0]) + abs(new[1]-old[1])$$\n这表示着从当前坐标移动到目标坐标的最小估计代价。\n而从一个华容道状态 移动到 另外一个华容道状态，需要同时计算所有非0/非占位符 坐标的转换代价。\n2、A*搜索的流程\n使用基于启发式搜索代价f(n)的优先队列pq存储Node。\n每次在队列非空的时候，访问q中最小f(n)的Node\n如果node.s=end, 直接返回map中记录的step 否则，尝试添加node的四向节点 如果四向节点不在map中，添加到pq中 如果四向节点在map中，但是map中的四向节点对应状态的step\u0026gt;step+1, 则需要将代表新Node状态的加入q和map 3、代码\npublic class T0773_AStar { int n = 2, m = 3; String end = \u0026#34;123450\u0026#34;; String start = \u0026#34;\u0026#34;; int start_x, start_y; int[] dx = {-1,0,0,1}; int[] dy = {0,-1,1,0}; /** * BFS解法 * @param board * @return */ public int slidingPuzzle(int[][] board) { // for(int i=0;i\u0026lt;n;i++) for(int j=0;j\u0026lt;m;j++){ start +=board[i][j]; if(board[i][j] == 0){ // 起点 start_x = i; start_y = j; } } if(!nixu(start)) return -1; int ans = astar(); return ans; } class Node{ int x; int y; int f_val; // f = g + h; String str; // 当前节点对应状态的str public Node(int _x, int _y, int _f_val, String _str){ x = _x; y = _y; f_val = _f_val; str = _str; } } public int h(String str){ // 从str转移为end的最小开销 char[] from = str.toCharArray(); char[] to = end.toCharArray(); int res = 0; for(int i=0;i\u0026lt;n;i++){ for(int j=0;j\u0026lt;m;j++){ // if(from[i*m+j] == \u0026#39;0\u0026#39; || to[i*m+j] == \u0026#39;0\u0026#39;) continue; // 其实to[i*m+j]才是真正的起点，而from[i*m+j]是我们的目标点 int st = to[i*m+j], ed = from[i*m+j]; res += Math.abs( (st-1)/3 - (ed-1)/3 ) + Math.abs( (st-1)%3 - (ed-1)%3 ); } } return res; } public boolean nixu(String str){ // 逆序对数量为偶数时有解 char[] chars = str.toCharArray(); // 过滤\u0026#39;0\u0026#39;占位符 ArrayList\u0026lt;Integer\u0026gt; list = new ArrayList\u0026lt;\u0026gt;(); for(int i=0;i\u0026lt;chars.length;i++){ if(chars[i] != \u0026#39;0\u0026#39;) list.add(chars[i]-\u0026#39;0\u0026#39;); } // int res = 0; for(int i=0;i\u0026lt;list.size();i++){ for(int j=i+1; j\u0026lt;list.size();j++){ // if(list.get(i)\u0026gt;list.get(j)) res++; } } return res%2==0; } public int astar(){ // 按f值升序 PriorityQueue\u0026lt;Node\u0026gt; pq = new PriorityQueue\u0026lt;\u0026gt;((a,b)-\u0026gt;a.f_val-b.f_val); pq.add(new Node(start_x, start_y, 0+h(start),start)); HashMap\u0026lt;String, Integer\u0026gt; mp = new HashMap\u0026lt;\u0026gt;(); mp.put(start, 0); // while(!pq.isEmpty()){ // Node poll = pq.poll(); int step = mp.get(poll.str); if(poll.str.equals(end)) return step; // 添加poll的四向节点 int x = poll.x, y = poll.y; for(int k=0;k\u0026lt;4;k++){ int new_x = x+dx[k], new_y = y+dy[k]; if(new_x \u0026lt; 0 || new_y\u0026lt;0 || new_x\u0026gt;=n || new_y\u0026gt;=m) continue; // String update = update(poll.str, x, y, new_x, new_y); // mp.get(update)\u0026gt;step+1，在存在更优的step的时候，更新pq和mp if(!mp.containsKey(update) || mp.get(update)\u0026gt;step+1){ pq.add(new Node(new_x, new_y, step+1+h(update), update)); mp.put(update, step+1); } } } return -1; } public String update(String str, int old_x, int old_y, int new_x, int new_y){ // 更换old_x,old_y 和new_x,new_y 对应str位置的值 char[] chars = str.toCharArray(); int old_pos = old_x*m + old_y; int new_pos = new_x*m + new_y; char tmp = chars[old_pos]; chars[old_pos] = chars[new_pos]; chars[new_pos] = tmp; return String.valueOf(chars); } } ","date":"2026-02-20T16:57:55+08:00","permalink":"/blog/ms-rewards-puzzle/","title":"薅微软Rewards羊毛：更快的滑动拼图方法"},{"content":" 孩子们，本文100%由DeepSeek和GPT混合润色，阅读到其中的非人句子时保持心平气合，因为这压根就不是我写的。 chap1 如果说要给过去的2025年选一个总结的主题词的话，那我会把这一票投给 「时间的魔法」。\n这个名字来自于游戏「白色相簿2」的一首背景音乐「時の魔法」。相比人声版本，八音盒版本 这种仿佛来自童年的纯粹旋律更贴近人声版本歌词的主旨，一个只属于游戏的美好机制：「Save\u0026amp;Load」，我们知道很多游戏都有这个模式，关键处存档，后续推进游戏遇到棘手情况时读档重开。当然也有像杀戮尖塔这种只允许单把关卡存档的游戏，但不管怎么样，在游戏中我们可以针对一关甚至多关反复重开，通过求多次局部最优的方式达到我们心中的结果最优。 在网易云音乐中打开 在网易云音乐中打开 现实和游戏的最大区别就是无法「Save\u0026amp;Load」，也正因为这样，这首歌的名字才叫「时间的魔法」。游戏中选错选项可以读档，打不过可以重开，配卡不理想可以重新构筑，只要愿意投入时间，总能获得一个更好的结果。现实更像每把强制的铁人模式，每一个选择都会被永久写入存档，等意识到你某一步操作的影响时，游戏已经默默向前了一大步。\nchap2 2025对我来说，就是这样的一年。\n很多事情当时看都很普通：要不要多花一点时间在某件事上，要不要早点开始准备，要不要把身体的不适当回事。等到回头看，才发现这些当时随手做的决定，已经慢慢把这一年带到了现在这个样子。\n写年终的时候，很容易去想：如果当时换一个选项，或者再坚持一下，会不会结果不同？但现实没有「重来一次」的机会。错过的就是错过了，只能在现有的基础上继续往下走。\n明年是我的本命年。十二年前还在小学，那时候对时间没什么概念，一年就是一年。现在走到学生阶段的末尾，再回头看这十二年，才发现很多变化都是一点点发生的。你以为只是普通的一年，其实也许是某个长期故事阶段性章节的收尾。\n年末生病之后，不得不把小论文的撰写和投递往前赶，又一次寒假提前回家。本来安排好的节奏被打乱，人也感觉明显的状态下降。游戏里血量掉了可以补回来，现实里却不是这样。身体的消耗是慢慢累积的，不会因为休息几天就完全清零。\nchap3 如果游戏的机制是通过读档来反复尝试逼近最优解，那现实的机制，大概是通过承担后果，逐步修正决策。这或许才是「时间的魔法」的真正含义：\n不是回溯，而是累积。\n不是重开，而是继续。\nchap4 今年没有很多想说的话，过年的时候坐在家里闲的没事儿写一下这篇博客，以此纪念下2025，很多年之后再看会不会感觉很唏嘘呢？\n最后祝大家身体健康，吃嘛嘛香。身体才是支撑时间魔法的唯一本钱，大家平时忙的时候也要注意休息，压力大的时候多摆摆烂。毕竟我们不必事事努力，生活只需要健康的身体与继续下去的勇气。\n2025，存档（111真能存档吗，孩子们我想重来了）。\n2026，继续。\n","date":"2026-02-13T10:36:02+08:00","permalink":"/blog/25-sum/","title":"2025：时间的魔法"},{"content":"最近 GPT 和 Gemini 越来越强，于是萌生了办理境外借记卡的想法。之前也不太了解银行卡的类型，简单做个调研。\nA. 信用卡、借记卡 信用卡 - 维基百科，自由的百科全书 借记卡 - 维基百科，自由的百科全书 先说说借记卡（Debit Card），又称储蓄卡，主要特点是\n消费/转账/取现，都是扣卡中的活期账户余额 存取款、转账、代发工资、绑定微信/支付宝、缴费等 一般日常说的“工资卡、银行卡、储蓄卡”，都是借记卡 没有透支额度，必须有余额才能花钱 借记卡里面可以存定期，定期的利息一般比活期高点 随大学录取通知书寄过来的就是一些学校指定银行的借记卡，当年寄过来的是中国银行的借记卡。另外常有说的一二类账户卡指的是转账限额，因为这是我第一张中行的卡，所以在 app 里面看默认就是一类。（不过目前也没那么多钱）\n信用卡（Credit Card），也称贷记卡，这个一般用的比较少\n先花银行的钱：有授信额度（额度内透支消费）。刷卡时不从存款账户立刻扣钱，而是记到“欠银行的钱”（形成信用账款）。从会计/记账角度，这类卡对应“贷记/信用”体系，所以叫“贷记卡”。 账单日出账、还款日还款；按规则按时全额还款通常免息，逾期/最低还款会有利息/费用。 可能有年费、权益（里程、返现、积分等），也可能有分期。 取现通常成本更高（手续费/利息），不建议当“取现金工具”。 以前国内有过 “准贷记卡”（介于两者之间，要求先存款但也可小额透支），现在已经比较少见。日常可以办到的主流“信用卡”基本都是“贷记卡”。\nB. 信用卡组织 国际上主要的发卡组织有 Visa、万事达卡、美国运通、大来国际、JCB、中国银联及发现卡等。\n它们主要是卡组织/支付网络（payment network / scheme）：负责把刷卡的信息在“商户收单行 → 卡组织网络 → 发卡行（银行）”之间完成授权、清算、结算。银行是“发卡方”，发卡组织是“跨机构的支付高速公路”。\n银联（UnionPay）：中国的卡组织，国内覆盖最强；境外很多国家也能用（尤其亚洲、部分旅游商户/ATM 覆盖不错），但在欧美一些小商户的刷卡覆盖通常不如 Visa/万事达。 Visa / 万事达（Mastercard）：全球范围最通用的两家卡组织，海外线下刷卡、线上电商/订阅支付的兼容性普遍最好。 American Express（运通/Amex）：既是卡组织，很多情况下也更“自营/闭环”（和商户、持卡人关系更强），常见在高端权益、商旅场景更突出；但受理范围通常不如 Visa/万事达广。 C. 境外借记卡 关于境外借记卡，有几种不同的类型\n肉身出境办的卡，这就是纯纯的境外卡了，不过因为我在国内不能去国外，所以这个不考虑。 要去的话，貌似是美国办卡更加好一点。 U 卡 这个卡类特别神奇，U 卡里面存的是 USDT，通过服务商在支付时自动换算成法定货币。 本质是“加密资产余额 + 结算时换汇 + 卡组织支付”的组合产品。 风险在于 不确定 U 卡的服务商能撑几年，还有就是加密货币的风险性。 USDT 由 Tether 公司发行，USDC 由 Circle 发行，透明度更高。不过目前貌似在支付一些境外服务的时候，都是使用 USDT 结算的。 港卡 香港卡，听同学说近些年办港卡的政策在逐步收紧，例如 26 年 1 月 1 日起对于非港户口的新开用户，账户余额低于一万港币的需要收取每月 100 港元的管理费。 接下来说说国内常见的境外借记卡：\n1、第一个是中国银行的\n中行每天在一食堂都有个桌子用来给同学们办卡，实验室学弟最近在食堂申请中行的境外借记卡。 中行的境外借记卡一般对应长城跨境通国际借记卡，信用卡组织一般是万事达和 Visa。\n1.1、 关于费用 初始费用可以分为两类\n管理费（年费/账户管理费） 交易手续费 而跨境汇款的收费就比较多，这个我自己都搞不太清，以后真自己有了一张再去了解吧。 1.2、 这里的跨境借记卡参考：\n长城跨境通国际借记卡使用指南及交易限额 长城跨境通国际借记卡 1.3、\n外币卡，需要配合一张中行 I 类卡提前购汇（这个可以用学校发的银行卡） 冻结比例：预授权冻结 100%的金额，非美元是 102%，账户里没有对应货币则默认以美元结算 年费：没有年费 3DS：支持 3-D Secure 支付安全认证（简称 3DS），在支持 3DS 的平台上消费时会弹出手机验证码验证页面 币种支持：多币种（共 19 币种，美元、欧元、日元、港币、英镑、加拿大元、澳大利亚元、瑞士法朗、新加坡元、澳门元、韩国元、丹麦克朗、挪威克朗、新西兰元、菲律宾比索、俄罗斯卢布、瑞典克朗、泰国铢、南非兰特） 网络评价：总体较好 支持的商户会比运通多（大部分的商户一般都支持 VISA、万事达） 支持 HMV、日本亚马逊、Rakuten Books（※实体商品貌似不行）、日区 Play 商店、国区 PayPal、DLSite、TOWER RECORDS、honto、turgame（购买土区 Apple 礼品卡） 评价：本卡也算是国内外币借记卡的经典之作了，发行快十年，经久不衰，甚至中行最近几年还在对这卡做优化（比如提供免费短信通知、冻结比例减小），而中行的境外支付也是一如既往的稳定，唯一可惜的就是 VISA 这两款比较丑，风头被万事达系列的卡和自家冬奥卡盖过去了。 注：一般而言，无人民币户的外币借记卡并无设置非柜面限额的必要。但由于“断卡行动”、线下网点柜员业务不熟等客观原因，本卡在开卡、换卡及日后用卡可能会被网点/后台批量设置非柜面限额，占用的非柜面限额将会换算为外币等值人民币。非柜面限额影响包括但不限于：境外线上/线下支付、境外 ATM 取现、向其他账户转出外汇/跨境汇款等业务。若您被设置不满足日常使用额度的非柜面限额，请与开户网点进行妥善沟通处理。 2、招行万事达借记卡 又称万事网联普卡\n冻结比例：100% 网付：完整支持，需手动开启（APP 我的-银行卡-快捷支付-万事达卡跨境线上支付） 3DS：有 自动购汇：有（使用卡组织汇率） FTF：无 年费：无 优惠政策：境外消费笔笔 0.5%返现、境外 ATM 查询免无限笔、取现免每月前 1 笔（有效期至 2026 年 12 月 31 日） 发行情况：网申或线下办理均可。 币种支持：单币种（人民币）（人民币户可用于境内外正常消费） 网络评价：相当好 说到招行的万事达卡：今天是星期六，我上午做完调研后想去招行碰碰运气，顺便办一张境外借记卡。不过周末很多网点不一定能办这种业务，具体还得看网点安排。我花了 5 块打车到万达旁边的支行，结果果然办不了，心情一下就有点低落。原本想直接走回学校，6 公里路走了快 3 公里，天气太冷又没带帽子和口罩，实在扛不住，最后又花了 3 块打车回去了。得亏今天高德有活动，半价打车，不然血亏 😭。\nD. 其他 关于 5 万美元的结汇/购汇限制：\n这个貌似不是一刀卡死的，我估计自己也不弄股票和玩币，就是订阅软件/AI 服务，花不了那么多，且先记在这里 😂。 关于风控\n借记卡风控 买境外服务前需要先往卡里预存一点钱，以免绑定支付方式时平台扣费验证账号的时候没钱，导致卡被风控后面用不了。 平台风控 不要频繁绑定/解绑卡片，避免深夜用卡 Play 商店：绑卡时账单地址必须是账号所在区域对应的地址。如果绑卡失败，检查余额是否充足、尝试联系客服解决。 亚马逊：注册的新账号或者没有完成过一次订单的账号不要购买虚拟充值卡类产品（包括但不限于亚马逊礼品卡、Google Play 礼品卡、Apple 礼品卡等，用礼品卡下单也是一样），否则大概率会支付失败并导致账号被风控。 HMV（日本）：这个网站风控挺迷的。比如从购物车进入到结算界面，已经绑了卡，会先验证卡（冻结 1 日元），如果频繁进入这个界面（如下图）而不下单的话，会触发风控。另外，新账号也有几率触发风控 关于预授权\n商户先发起一笔“预授权”，银行/发卡机构会在账户里先冻结等额资金，确保有钱支付（但此时还没真正扣款）。 冻结比例，比如中行的“预授权冻结 100% / 非美元 102%” 使用美元交易，如果要付 100 美元，那么就会先冻结卡里的等额 100 美元。 如果使用欧元，比如 100 欧则需要先冻结 102 欧。 冻结比例越高，代表同样一笔交易会占用更多可用余额，资金流动性变差。体感上酒店/租车的押金可能会延迟好几天，另外当多笔预授权叠加，容易导致“明明有钱但可用余额不够”。 E. 参考 国际借记卡的使用体验和海淘的经验分享 - 哔哩哔哩 可能是全网最全的国内国际借记卡分析 ","date":"2025-12-27T23:31:36+08:00","permalink":"/blog/debit-card/","title":"境外借记卡简单调研"},{"content":"Olgitbridge Olgitbridge: Overleaf Git Bridge\n鉴于 Overleaf 开源版本无法集成 git，在 github 上逛了一圈发现还是olgitbridge最好用，本篇博客做部署和使用的相关介绍。\n😋github 上的介绍 / 机翻过来作为参考\n另一个 Overleaf git 桥。这个方法允许你通过 git 在 Overleaf 社区版（CE）或 Pro 服务器上拉取和推送更改，就像官方 Overleaf SaaS 宣布中展示的一样。只需使用 git clone http://your.server.invalid/ ，你的项目 ID 可以从你的 Overleaf 实例中查看。 这个 gitbridge 使用 Overleaf 的 web API 来同步更改。这意味着它不需要运行在与 Overleaf 相同的服务器上。实际上，建议在另一台服务器上运行它，以便可以直接使用 https 端口 443。 请注意，与云版本不同，这个桥不使用实时更改操作，任何通过 git 修改的文件都会在在线编辑器中导致“此文件已被外部修改”的中断。\nA.部署 使用 DockerFile 部署\n1、clone 代码 chazeon/olgitbridge: Overleaf git bridge, fork of https://gitlab.com/axkibe/olgitbridge with support for the Overleaf v4 frontend 2、修改 config.js，创建 data 文件夹 3、修改 dockerfile 与 src/server.js，构建 image 2、修改 config.js\n// overleaf server // 修改为我们的overleaf server ip + port config.olServer = \u0026#34;http://192.168.10.4:9999\u0026#34;; // used if all the working directories in one place // must be absolute path ending with \u0026#39;/\u0026#39; config.baseDir = \u0026#34;/var/olgitbridge/data/\u0026#34;; 创建 data 文件夹 /var/olgitbridge/data/，以便后续挂载\n3、修改 dockerfile，构建 image 3.1、dockerfile\nFROM node:18.14.2-slim WORKDIR /var/olgitbridge/ COPY . . RUN apt-get update \u0026amp;\u0026amp; apt-get upgrade -y \u0026amp;\u0026amp; apt-get install --no-install-recommends git ca-certificates -y \u0026amp;\u0026amp; apt-get clean RUN npm install RUN git config --global user.email \u0026#34;overleaf-git-bridge@system.changeme.invalid\u0026#34; \u0026amp;\u0026amp; git config --global user.name \u0026#34;Overleaf Git Bridge\u0026#34; RUN mkdir -p /var/olgitbridge/data EXPOSE 5000 ENTRYPOINT [ \u0026#34;node\u0026#34;, \u0026#34;src/server.js\u0026#34; ] 构建\ndocker build -t olgitbridge:latest -f Dockerfile . 值得注意的是，这里设置的 git user，将会被作为后续你 pull / push overleaf latex 时的 user 使用，这里推荐可以改成自己的\nRUN git config --global user.email \u0026#34;overleaf-git-bridge@system.changeme.invalid\u0026#34; \u0026amp;\u0026amp; git config --global user.name \u0026#34;Overleaf Git Bridge\u0026#34; 另外，如果使用自己的 user，可以顺手修改 src/server.js\n// await git.save( count, project.padDir, \u0026#39;synced by olgitbridge\u0026#39; ); await git.save(count, project.padDir, \u0026#34;synced with olgitbridge by your name\u0026#34;); 3.2、docker run v1，仅仅挂载 data 目录\ndocker run -d --name olgitbridge \\ -v ./data:/var/olgitbridge/data -p 5000:5000 \\ --restart unless-stopped \\ olgitbridge:latest docker run -d --name olgitbridge -v ./data:/var/olgitbridge/data -p 5000:5000 --restart unless-stopped olgitbridge:latest 3.3、docker run v2，挂载 olgitbridge 整个源码目录\n3.4、检查命令\ndocker logs -f olgitbridge # 看日志 docker ps # 看是否在跑 docker exec -it olgitbridge sh # 进容器 B.使用 1、直接使用 git 拉取\ngit clone http://localhost:5000/\u0026lt;project-id\u0026gt; project-id 在 overleaf 对应文档的网页后缀中可得 2、拉取完成后 origin 仓库\n➜ git remote -v origin http://localhost:5000/694b4ac659ee1b0088f18f2d (fetch) origin http://localhost:5000/694b4ac659ee1b0088f18f2d (push) 3、添加 github 仓库\ngit remote add gh xxx # 可得 ➜ git remote -v gh git@github.com:Username/xxx.git (fetch) gh git@github.com:Username/xxx.git (push) 4、使用\n# 与overleaf互相同步 git pull git push # 相应的、与gh同步 git pull gh git push gh ","date":"2025-12-26T23:44:14+08:00","permalink":"/blog/olgit/","title":"Overleaf集成Git\u0026Github"},{"content":"A\u0026amp;B 场景：A 和 B 同时维护一个项目，B 电脑出问题了，在 A 电脑上改了代码后需要 push，但是 A 不想 B 用他的账号提交，所以需要添加新的 git 用户。\n1、添加新用户的 sshkey\n假设第一个 key 是\n~/.ssh/id_rsa 现在创建第二个 key，用不同的名字（比如 id_rsa_work）：\nssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa_B -C \u0026#34;B@B.com\u0026#34; 这里的~为用户目录，可以使用 powershell 的 cd ~ 查看\n# powershell cd ~ pwd C:\\Users\\yourusername 2、编辑 code ~/.ssh/config 如果没有则新建，添加如下内容\n# 默认 GitHub 账号（id_rsa） Host github.com HostName github.com User git IdentityFile ~/.ssh/id_rsa # 第二个 GitHub 账号（id_rsa_B） Host B HostName github.com User git IdentityFile ~/.ssh/id_rsa_B 测试是否正常工作\nssh -T github.com ssh -T B 返回如下则正常工作\nHi another-user! You\u0026#39;ve successfully authenticated... 3、修改远程仓库地址 然后：\n对于第一个账号克隆的仓库仍用 git@github.com:xxx/repo.git 对于第二个账号克隆仓库时： git@B:xxx/repo.git 这样 Git 会根据 Host 自动选择 SSH key。 git remote -v # 这里显示 origin git@github:USERNAME/REPO.git (fetch) # 你需要替换github为第二个账号对应的 Host git remote set-url origin git@B:USERNAME/REPO.git # 验证 git remote -v 4、修改需要推送的仓库局部 B 的 git 用户名和邮箱\n接着 在你需要推送的仓库中修改用户名和 email\ngit config --local user.name B git config --local user.email B@B.com 最后就可以让 B 在 A 的电脑上提交 commits 了。\nAppendix 如果 B 使用 A 的 git 账号提交了怎么办？\nAppendix.1、修改指定 A 提交为 B 提交 例如你想修改 commit hash 77f5a0 的作者A： 1、启动交互式 rebase（从要改的 commit 前一个开始）\ngit rebase -i 77f5a0^ 2、编辑器修改：\npick 77f5a0 B的提交信息 改成：\nedit 77f5a0 B的提交信息 保存退出。\n3、此时 Git 会停在该提交，执行：\ngit commit --amend --author=\u0026#34;B \u0026lt;B@B.com\u0026gt;\u0026#34; 4、继续 rebase：\ngit rebase --continue 完成后该 commit 的作者就被修改了。\nAppendix.2、修改所有 A 提交为 B 提交 使用 git filter-branch，将某个人的所有提交统一改成另外一个身份 ⚠️：filter-branch 会彻底重写历史，请先备份仓库！\ngit filter-branch --env-filter \u0026#39; OLD_EMAIL=\u0026#34;A@A.com\u0026#34; CORRECT_NAME=\u0026#34;B\u0026#34; CORRECT_EMAIL=\u0026#34;B@B.com\u0026#34; if [ \u0026#34;$GIT_COMMITTER_EMAIL\u0026#34; = \u0026#34;$OLD_EMAIL\u0026#34; ] then export GIT_COMMITTER_NAME=\u0026#34;$CORRECT_NAME\u0026#34; export GIT_COMMITTER_EMAIL=\u0026#34;$CORRECT_EMAIL\u0026#34; fi if [ \u0026#34;$GIT_AUTHOR_EMAIL\u0026#34; = \u0026#34;$OLD_EMAIL\u0026#34; ] then export GIT_AUTHOR_NAME=\u0026#34;$CORRECT_NAME\u0026#34; export GIT_AUTHOR_EMAIL=\u0026#34;$CORRECT_EMAIL\u0026#34; fi \u0026#39; --tag-name-filter cat -- --all 检查+提交\ngit log # 强推 git push --force --all # git push -f git push --force --tags ","date":"2025-11-27T08:53:55+08:00","permalink":"/blog/git-a-b/","title":"单电脑git多用户配置"},{"content":"实验室的一套 Python 算法项目在部署阶段需要以容器/虚拟化方式运行，因此整理了整个 Docker 化流程，方便后续参考。\n背景 算法部分：包含 PyTorch、XGBoost 后端部分：使用 Flask 提供简单 API 服务 将算法后端打包成可直接部署的 Docker 镜像。\n算法容器化 / Now 1、构建 dockerfile 新建Dockerfile.alg\n# 🐋 基于指定的 PyTorch CUDA 镜像 FROM pytorch/pytorch:2.5.1-cuda12.4-cudnn9-devel # 📁 设置工作目录 WORKDIR /workspace # 📦 复制本地 alg 仓库到容器 COPY alg /workspace/alg # 🔧 进入 alg 目录 WORKDIR /workspace/alg # 🪞 配置 pip 使用清华源加速下载 RUN pip config set global.index-url https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple # 📚 安装依赖 RUN pip install --no-cache-dir -r requirements.txt # 🌐 暴露 Flask 服务的端口 EXPOSE 20010 # 🚀 启动容器时运行 Flask 服务，并将日志写入 output.log # ⚠️ 这里保留前台，以使docekr run -d不会直接退出 CMD [\u0026#34;bash\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;python main.py \u0026gt; output.log 2\u0026gt;\u0026amp;1\u0026#34;] 2、构建镜像 docker build -t alg:v1.0 -f Dockerfile.alg . 3、运行容器 # single docker run -d -p 20010:20010 -v /opt/code/alg:/workspace/mount --gpus all --name alg --restart=unless-stopped alg:v1.0 # multiple docker run -d \\ -p 20010:20010 \\ -v /opt/code/alg:/workspace/mount \\ --gpus all \\ --name alg \\ --restart=unless-stopped \\ alg:v1.0 4、查看容器运行状态 docekr ps -a docker logs -f alg 5、端口连通性测试 测试端口联通\ncurl -I http://localhost:20010 镜像保存与迁移 1、保存 image 为 tar\ndocker save -o alg.tar alg:v1.0 2、加载 image\n# 7z x alg.7z -o./ # 得到 alg.tar docker load -i alg.tar docker images 3、启动容器\ndocker run -d -p 20010:20010 -v /opt/code/alg:/workspace/mount --gpus all --name alg --restart=unless-stopped alg:v1.0 4、开防火墙\nufw allow 20010 5、API 测试\n# 测试 curl -X POST http://localhost:20010/predict # 返回如下输出即完成 { \u0026#34;code\u0026#34;: 415, \u0026#34;error\u0026#34;: \u0026#34;Internal Server Error\u0026#34;, \u0026#34;message\u0026#34;: \u0026#34;415 Unsupported Media Type: Did not attempt to load JSON data because the request Content-Type was not \u0026#39;application/json\u0026#39;.\u0026#34; } 非 docker 部署 / Before 配置 python 环境 conda/miniforge/mamba pip poetry 下载依赖 pip install -r requirements.txt conda 下载 pytorch 相关依赖 部署 nohup python main.py \u0026gt; output.log 2\u0026gt;\u0026amp;1，将算法的输出重定向至 output.log 中，同时每次向 predict 发送的请求存放至 log/json 中，以时间戳顺序排布。 ","date":"2025-11-24T17:13:35+08:00","permalink":"/blog/python-container/","title":"Python算法项目 容器化"},{"content":"1 / Windows 包管理 scoop scoop 多开发环境配置使用教程：Scoop 搭建 Windows 开发环境 | 潇然工作室 scoop 必不可少的一环：如何使用 (63 封私信 / 82 条消息) Scoop——也许是 Windows 平台最好用的软件（包）管理器 - 知乎 Scoop | Windows 下的软件包管理神器 - 苏青羽的小窝 官网：Scoop windows 的包管理器之一，使用它下载一些比较轻量的软件/命令行工具、以及一些开发环境（类似 Debian 的 apt / brew）。\n前置准备 Scoop 是一个 PowerShell 工具，需要：\nWindows 10 / 11 PowerShell 5.1 或更高（默认都有）,最好使用 PowerShell7 可联网（最好能访问 GitHub） 建议关闭 UAC（User Account Control） 弹窗（Scoop 默认装在用户目录，无需管理员权限） 安装 pre\nSet-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser （1）默认安装 （自动放在 C:\\Users\\\u0026lt;你\u0026gt;\\scoop） 直接打开 PowerShell（非管理员），执行：\niwr -useb get.scoop.sh | iex 执行后，你可以运行：\nscoop --version 确认安装成功。\n（2）自定义安装目录 （推荐） 如果你想把所有软件放在自定义目录下面比如 D:\\Apps\\Scoop，则需要添加环境变量\nsetx SCOOP \u0026#34;D:\\Apps\\Scoop\u0026#34; /M iwr -useb get.scoop.sh | iex 安装后，所有 Scoop 软件会放在：\nD:\\Apps\\Scoop\\apps\\\u0026lt;软件名\u0026gt;\\current\\ 非常干净，不污染系统盘。\n🔺setx 不加/m 表示用户变量，加上就是系统变量\n安装完成后，scoop 文件夹下面的结构\napps——所有通过 scoop 安装的软件都在里面。 buckets——管理软件的仓库，用于记录哪些软件可以安装、更新等信息，默认添加main仓库，主要包含无需 GUI 的软件，可手动添加其他仓库或自建仓库，具体在推荐软件仓库中介绍。 cache——软件下载后安装包暂存目录。 persit——用于储存一些用户数据，不会随软件更新而替换。 shims——用于软链接应用，使应用之间不会互相干扰，实际使用过程中无用户操作不必细究。 添加代理 设置 scoop 的代理\nscoop config proxy 127.0.0.1:7890 # clash verge rev是7897 或者直接找到 Scoop 配置文件，路径 C:\\Users\\username\\.config\\scoop\\config.json，然后直接修改里面的配置。\n取消代理\nscoop config rm proxy 配置 Aria2 下载 使用 Scoop 安装 Aria2 ，Scoop 会自动调用 Aria2 进行多线程加速下载。安装完会自动启用.\nscoop install aria2 scoop config aria2-warning-enabled false 与 Aria2 有关的设置选项：\naria2-enabled: 开启 Aria2 下载，默认true aria2-retry-wait: 重试等待秒数，默认2 aria2-split: 单任务最大连接数，默认5 aria2-max-connection-per-server: 单服务器最大连接数，默认5 ，最大16 aria2-min-split-size: 最小文件分片大小，默认5M 优化 Aria2 设置，单任务最大连接数设置为 32，单服务器最大连接数设置为 16，最小文件分片大小设置为 1M ### aria2 在 Scoop 中默认开启 scoop config aria2-enabled true ### 关于以下参数的作用，详见aria2的相关资料 scoop config aria2-retry-wait 4 scoop config aria2-split 16 scoop config aria2-max-connection-per-server 16 scoop config aria2-min-split-size 4M 因为 Scoop 默认的 TLS1.0/1.1 这两个协议已经被 github 禁用，这里需要配置现代 TLS 协议才能通过 HTTPS 连接下载。\n[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 配置 sudo 打开管理员模式 使用 Scoop 安装 sudo ，如果要用管理员运行命令,无需用管理员打开 cmd(or powershell) 直接在命令行前加一个 sudo 即可。\nscoop install sudo 测试：\nsudo setx testForSudo \u0026#34;C:/\u0026#34; $env:testForSudo sudo [Environment]::SetEnvironmentVariable(\u0026#39;testForSudo\u0026#39;, $null, \u0026#39;Machine\u0026#39;) $env:testForSudo 添加 buckets 添加 buckets 之前，需要有 git 和 7zip\nscoop install git # 这段命令会连带下载7zip 1、添加整合 buckets kkzzhizhou/scoop-apps: 使用 Github Action 每天自动合并其他 scoop 仓库的更新，仓库地址：https://github.com/kkzzhizhou/scoop-apps\nscoop bucket add apps https://github.com/kkzzhizhou/scoop-apps 2、添加dorado\nscoop bucket add dorado https://github.com/chawyehsu/dorado 所有的包管理器都会有相应的软件仓库 ，而 bucket 就是 Scoop 中的软件仓库。细心的你可能会发现 scoop 翻译为中文是 “舀”，而 bucket 是 “水桶”，所以安装软件可以理解为从水桶里舀水，似乎很形象的说。 Scoop 默认软件仓库（main bucket）软件数量是有限的，但是可以进行额外的添加。\nscoop bucket list 查看当前已经添加的 bucekt\n✗ scoop bucket list Name Source Updated Manifests ---- ------ ------- --------- main https://github.com/ScoopInstaller/Main.git 2025/10/16 8:38:16 1406 extras https://github.com/ScoopInstaller/Extras 2025/10/16 8:39:16 2197 apps https://github.com/kkzzhizhou/scoop-apps 2025/10/15 12:40:54 14810 基本使用 basic\nsearch # 搜索仓库中是否有相应软件。 install # 安装软件。 uninstall # 卸载软件。 update # 更新软件。可通过scoop update *更新所有已安装软件，或通过scoop update更新所有软件仓库资料及Scoop自身而不更新软件。 hold # 锁定软件阻止其更新。 info # 查询软件简要信息。 home # 打开浏览器进入软件官网。 help # 帮助 More\u0026hellip;\nalias Manage scoop aliases bucket Manage Scoop buckets cache Show or clear the download cache cat Show content of specified manifest. checkup Check for potential problems cleanup Cleanup apps by removing old versions config Get or set configuration values create Create a custom app manifest depends List dependencies for an app export Exports (an importable) list of installed apps help Show help for a command hold Hold an app to disable updates home Opens the app homepage info Display information about an app install Install apps list List installed apps prefix Returns the path to the specified app reset Reset an app to resolve conflicts search Search available apps status Show status and check for new app versions unhold Unhold an app to enable updates uninstall Uninstall an app update Update apps, or Scoop itself virustotal Look for app\u0026#39;s hash on virustotal.com which Locate a shim/executable (similar to \u0026#39;which\u0026#39; on Linux) 例子 以下载 hugo 为例\n# hugo信息 scoop info hugo # hugo 指定版本 scoop install hugo@0.140.2 # 当前已经安装的hugo版本，可能只会显示一个 scoop list hugo # 再次下载一个hugo scoop install hugo@0.151.1 scoop reset hugo@0.140.2 以文件夹符号链接的方式，引用当前使用的 hugo 版本： 删除旧版本软件，scoop 更新软件不会将旧版移除，只是将创建一个链接指向新版本。\n清理包缓存 Scoop 会保留下载的安装包，对于卸载后又想再安装的情况，不需要重复下载。但长期累积会占用大量的磁盘空间，如果用不到就成了垃圾。这时可以使用 scoop cache 命令来清理。\n`scoop cache show` - 显示安装包缓存 `scoop cache rm *` - 删除所有的安装包缓存 如果不希望安装和更新软件时保留安装包缓存，可以加上 -k\nscoop install -k \u0026lt;app\u0026gt; scoop update -k * 安装失败时的处理\n# 删除损坏的安装 scoop uninstall miniforge --purge # 管理员模式安装 sudo scoop install miniforge 如何卸载 scoop uninstall scoop 卸载后重新安装会出现问题\n\u0026gt; iwr -useb get.scoop.sh | iex Initializing... Scoop is already installed. Run \u0026#39;scoop update\u0026#39; to get the latest version 默认安装路径在 $env:userprofile 下面的 scoop 文件夹中，先删除这个文件夹\n环境配置 python | miniforge sudo scoop install miniforge 安装完成后的 Note：\nThis variant does not add the default venv into your PATH unless you activate it with \u0026#39;conda activate base\u0026#39;. You may choose to install the one from the official bucket if you do not care about python PATH pollution. Conda base env is not persisted by Scoop, packages installed to the base env will be removed when upgrading the app. We advice to setup \u0026#39;envs_dirs\u0026#39; to use a seperated path like \u0026#39;~/.conda/envs\u0026#39; and avoid using the base env. https://conda.io/projects/conda/en/latest/user-guide/configuration/use-condarc.html#specify-environment-directories-envs-dirs To enable conda tab-completion autoload, you can add the following code to your PowerShell $PROFILE: (\u0026amp; conda \u0026#39;shell.powershell\u0026#39; \u0026#39;hook\u0026#39;) | Out-String | Invoke-Expression 意思就是：\n没有添加 miniforge 的 env 到你的 path 中，但是因为 scoop/shims 已经将软链接 conda 添加，在使用 conda init 后可以直接使用 conda activate base 激活环境 自动补全开启：(\u0026amp; conda 'shell.powershell' 'hook') | Out-String | Invoke-Expression，把 conda hook 的输出加载进当前 PowerShell 环境。 condainit 后会向 profile 中写入脏东西，启动速度优化可以转向 PowerShell 升级。\napp list ➜ scoop list Installed apps: Name Version Source Updated Info ---- ------- ------ ------- ---- 7zip 25.01 main 2025-10-11 12:01:05 aria2 1.37.0-1 main 2025-10-16 09:54:49 colortool 1904.29002 main 2025-10-12 11:30:19 dark 3.14.1 main 2025-10-13 10:00:38 docker-desktop nightly-20251011 apps 2025-10-11 15:37:06 hugo 0.140.2 \u0026lt;auto-generated\u0026gt; 2025-10-16 10:17:26 neofetch 7.1.0 main 2025-10-17 10:36:27 powershell 7.5.3 apps 2025-10-11 16:41:03 powertoys 0.94.2 extras 2025-10-13 10:02:39 qqmusic 2025-10-11 15:40:07 Install failed qqmusic_akirco 21.92 apps 2025-10-11 15:40:41 starship 1.23.0 main 2025-10-12 11:41:57 sudo 0.2020.01.26 main 2025-10-16 10:00:15 tre-command 0.4.0 main 2025-10-11 11:45:48 scoop-list-all 的 powershell 函数 function Scoop-ListAll { param( [Parameter(Mandatory = $true)] [string]$App ) $appPath = \u0026#34;$env:scoop\\apps\\$App\u0026#34; if (-not (Test-Path $appPath)) { Write-Host \u0026#34;❌ 未找到 Scoop 应用 \u0026#39;$App\u0026#39;\u0026#34; -ForegroundColor Red return } $versions = Get-ChildItem -Path $appPath -Directory | Select-Object -ExpandProperty Name if ($versions.Count -eq 0) { Write-Host \u0026#34;⚠️ 未找到已安装的版本。\u0026#34; return } # 检查 current 链接 $currentLink = (Get-Item \u0026#34;$appPath\\current\u0026#34; -ErrorAction SilentlyContinue) $currentTarget = $null if ($currentLink -and $currentLink.LinkType -eq \u0026#34;SymbolicLink\u0026#34;) { $currentTarget = Split-Path $currentLink.Target -Leaf } Write-Host \u0026#34;📦 $App 已安装的版本：\u0026#34; -ForegroundColor Cyan foreach ($v in $versions) { if ($v -eq \u0026#34;current\u0026#34;) { continue } if ($v -eq $currentTarget) { Write-Host \u0026#34; → $v (当前启用)\u0026#34; -ForegroundColor Green } else { Write-Host \u0026#34; $v\u0026#34; } } } 打开 profile，将这个函数加进去，重启 ps 后直接使用\nnotepad $PROFILE 使用\n➜ scoop-listall hugo 📦 hugo 已安装的版本： 0.140.2 0.151.1 ➜ scoop list hugo Installed apps matching \u0026#39;hugo\u0026#39;: Name Version Source Updated Info ---- ------- ------ ------- ---- hugo 0.140.2 \u0026lt;auto-generated\u0026gt; 2025-10-16 10:17:26 2 / Windows 终端优化 Windows Terminal 在 github Windows Terminal 或者微软商店页面搜索下载即可。 Windows Terminal 有更加优雅的现代化界面和终端统一管理功能。\nPowerShell 升级 1、首先查看 powershell 的版本\n$Host.Version $psversiontable pwsh --version 得到\nMajor Minor Build Revision ----- ----- ----- -------- 5 1 19041 1320 分别是「主版本.次版本.构建版本.修订版本」\npowershell 的命令不区分大小写，驼峰式写法只是为了更可读。\n2、使用 scoop 升级 powershell\nscoop install powershell 3、启用 powershell 在启动处将其设置为默认配置文件\n启动速度优化 1、查看 PowerShell 配置文件 PowerShell 会在启动时按顺序加载不同配置文件： $PROFILE | Format-List * -Force 输出类似：\nAllUsersAllHosts : C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\profile.ps1 AllUsersCurrentHost : C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\Microsoft.PowerShell_profile.ps1 CurrentUserAllHosts : C:\\Users\\YourUser\\Documents\\PowerShell\\profile.ps1 CurrentUserCurrentHost : C:\\Users\\YourUser\\Documents\\PowerShell\\Microsoft.PowerShell_profile.ps1 💡 解释：\nAllUsersAllHosts：对所有用户和所有宿主有效 CurrentUserCurrentHost：只对当前用户和当前宿主（比如 Windows Terminal）有效 默认只要存在，PowerShell 就会尝试加载它们 你可以直接用 Notepad 打开它们：\nPS C:\\Users\\tohsaka rin\u0026gt; $PROFILE | Format-List * -Force AllUsersAllHosts : C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\profile.ps1 AllUsersCurrentHost : C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\Microsoft.PowerShell_profile.ps1 CurrentUserAllHosts : C:\\Users\\tohsaka rin\\Documents\\WindowsPowerShell\\profile.ps1 CurrentUserCurrentHost : C:\\Users\\tohsaka rin\\Documents\\WindowsPowerShell\\Microsoft.PowerShell_profile.ps1 Length : 81 PS C:\\Users\\tohsaka rin\u0026gt; notepad $profile.AllUsersAllHosts PS C:\\Users\\tohsaka rin\u0026gt; Test-Path $PROFILE.AllUsersCurrentHost False PS C:\\Users\\tohsaka rin\u0026gt; Test-Path $PROFILE.CurrentUserAllHosts PS C:\\Users\\tohsaka rin\u0026gt; Test-Path $PROFILE.CurrentUserAllHosts True 2、修改 conda 的配置 参考解决 conda init 后启动 powershell 速度慢的问题 - 知乎 conda init 后启动 powershell 速度慢的问题：\n在 PowerShell 中输入(路径替换为自己真实的路径)\n(\u0026amp; \u0026#34;C:\\path\\to\\conda\\Scripts\\conda.exe\u0026#34; \u0026#34;shell.powershell\u0026#34; \u0026#34;hook\u0026#34;) | Out-String 将输出的文本贴到 profile.ps1​ 中（将 username 替换为实际的用户名）\n$Env:CONDA_EXE = \u0026#34;C:\\Users\\username\\miniconda3\\Scripts\\conda.exe\u0026#34; $Env:_CONDA_ROOT = \u0026#34;C:\\Users\\username\\miniconda3\u0026#34; $Env:_CONDA_EXE = \u0026#34;C:\\Users\\username\\miniconda3\\Scripts\\conda.exe\u0026#34; $CondaModuleArgs = @{ChangePs1 = $True } Import-Module \u0026#34;$Env:_CONDA_ROOT\\shell\\condabin\\Conda.psm1\u0026#34; -ArgumentList $CondaModuleArgs conda activate base Remove-Variable CondaModuleArgs 最好删除 conda activate base​，并使用 conda config \u0026ndash;set auto_activate_base false​ 禁用自动激活 如果 conda config \u0026ndash;set auto_activate_base false​ 提示无权限，则直接去 C:\\Users\\username​（username 替换)下找到.condarc​ 文件加入如下代码\nauto_activate_base: false ‍\n自动补全 1、自动补全 这里输入 h，向右即可自动补全 2、开启补全配置\nSet-PSReadLineKeyHandler -Key Tab -Function MenuComplete Set-PSReadLineKeyHandler -Key Tab -Function Complete 这两个函数的作用分别是：\nMenuComplete：当有多个匹配项时，会显示一个交互式菜单列表。 Complete：直接补全到唯一的匹配项。 如果需要全局生效，则需要加入$profile中\n3、重新加载 PSReadLine 在 PowerShell 7.4 中，你可以通过以下步骤来重新加载 PSReadLine 模块：\n首先，你需要确保 PSReadLine 模块已经安装。你可以通过运行 Get-Module -ListAvailable PSReadLine 命令来检查 PSReadLine 模块是否已经安装。 如果 PSReadLine 模块已经安装，你可以通过运行 Remove-Module PSReadLine 命令来卸载当前加载的 PSReadLine 模块。 3，然后，你可以通过运行 Import-Module PSReadLine 命令来重新加载 PSReadLine 模块。 starship 美化 1、下载 nerd 字体\nnerd 字体推荐下载 jetbrainsmono nerd font nerd 字体 cheat sheet：Nerd Fonts - Iconic font aggregator, glyphs/icons collection, \u0026amp; fonts patcher 只安装 mono\n. ├── JetBrainsMonoNerdFontMono-Bold.ttf ├── JetBrainsMonoNerdFontMono-BoldItalic.ttf ├── JetBrainsMonoNerdFontMono-ExtraBold.ttf ├── JetBrainsMonoNerdFontMono-ExtraBoldItalic.ttf ├── JetBrainsMonoNerdFontMono-ExtraLight.ttf ├── JetBrainsMonoNerdFontMono-ExtraLightItalic.ttf ├── JetBrainsMonoNerdFontMono-Italic.ttf ├── JetBrainsMonoNerdFontMono-Light.ttf ├── JetBrainsMonoNerdFontMono-LightItalic.ttf ├── JetBrainsMonoNerdFontMono-Medium.ttf ├── JetBrainsMonoNerdFontMono-MediumItalic.ttf ├── JetBrainsMonoNerdFontMono-Regular.ttf ├── JetBrainsMonoNerdFontMono-SemiBold.ttf ├── JetBrainsMonoNerdFontMono-SemiBoldItalic.ttf ├── JetBrainsMonoNerdFontMono-Thin.ttf └── JetBrainsMonoNerdFontMono-ThinItalic.ttf 2、scoop 安装\nscoop install starship 3、初始化 （1）使用 powershell，运行 $PROFILE，记住这个路径，并进入这个路径（如果没有这个路径就创建） 在这个路径下的 Microsoft.PowerShell_profile.ps1 中加入\nInvoke-Expression (\u0026amp;starship init powershell) 重启终端即可\n（2）或者\nPS C:\\Users\\tohsaka rin\u0026gt; starship init powershell Invoke-Expression (\u0026amp; \u0026#39;D:\\software\\scoop\\shims\\starship.exe\u0026#39; init powershell --print-full-init | Out-String) PS C:\\Users\\tohsaka rin\u0026gt; notepad $profile 4、配置 config\n\u0026#34;$schema\u0026#34; = \u0026#39;https://starship.rs/config-schema.json\u0026#39; format = \u0026#34;\u0026#34;\u0026#34; [](color_orange)\\ $os\\ $username\\ $hostname\\ [](bg:color_yellow fg:color_orange)\\ $directory\\ [](fg:color_yellow bg:color_aqua)\\ $git_branch\\ $git_status\\ [](fg:color_aqua bg:color_blue)\\ $c\\ $cpp\\ $rust\\ $golang\\ $nodejs\\ $php\\ $java\\ $kotlin\\ $haskell\\ $python\\ [](fg:color_blue bg:color_bg3)\\ $docker_context\\ $conda\\ $pixi\\ [](fg:color_bg3 bg:color_bg1)\\ $time\\ [ ](fg:color_bg1)\\ $line_break$character\u0026#34;\u0026#34;\u0026#34; palette = \u0026#39;gruvbox_dark\u0026#39; [palettes.gruvbox_dark] color_fg0 = \u0026#39;#fbf1c7\u0026#39; color_bg1 = \u0026#39;#3c3836\u0026#39; color_bg3 = \u0026#39;#665c54\u0026#39; color_blue = \u0026#39;#458588\u0026#39; color_aqua = \u0026#39;#689d6a\u0026#39; color_green = \u0026#39;#98971a\u0026#39; color_orange = \u0026#39;#d65d0e\u0026#39; color_purple = \u0026#39;#b16286\u0026#39; color_red = \u0026#39;#cc241d\u0026#39; color_yellow = \u0026#39;#d79921\u0026#39; color_font = \u0026#39;#FBF1C7\u0026#39; color_font_app = \u0026#39;#FBF1C7\u0026#39; [palettes.sakura_night_stars] color_fg0 = \u0026#39;#fbf1c7\u0026#39; # 柔和温暖的白色（明亮前景色） color_bg1 = \u0026#39;#2e1a47\u0026#39; # 深紫色，像夜空 color_bg3 = \u0026#39;#3a2d56\u0026#39; # 深紫色的阴影 color_blue = \u0026#39;#7a4d97\u0026#39; # 柔和梦幻的蓝色，像星辰 color_aqua = \u0026#39;#b4e0d9\u0026#39; # 轻盈的水绿色，像月光的反射 color_green = \u0026#39;#a8d8a5\u0026#39; # 淡绿色，像樱花的叶子 color_orange = \u0026#39;#f8a89f\u0026#39; # 柔和的粉橙色，像樱花花瓣 color_purple = \u0026#39;#c5a0e2\u0026#39; # 浅紫色，像暮光的色调 color_red = \u0026#39;#e5856e\u0026#39; # 淡珊瑚红色，像黎明的第一缕阳光 color_yellow = \u0026#39;#f2d97e\u0026#39; # 浅黄色，像远处星星的微光 color_font = \u0026#39;#FBF1C7\u0026#39; # 明亮的白色字体，便于阅读 color_font_app = \u0026#39;#FBF1C7\u0026#39; # 应用程序中一致的明亮字体 [palettes.sakura_night_stars_pink_deep] color_fg0 = \u0026#39;#fbf1c7\u0026#39; # 明亮的白色字体，确保可读性 color_bg1 = \u0026#39;#1e0d2b\u0026#39; # 深紫黑色背景，增强对比度 color_bg3 = \u0026#39;#3e1f4b\u0026#39; # 深紫粉色，适合高对比度的环境 color_blue = \u0026#39;#7a4d97\u0026#39; # 梦幻蓝色，适合星辰的点缀 color_aqua = \u0026#39;#f1c8d1\u0026#39; # 粉蓝色，像月光 color_green = \u0026#39;#f0b8c4\u0026#39; # 淡粉绿，增强柔和感 color_orange = \u0026#39;#f4b7d6\u0026#39; # 柔和粉橙色，清新又不刺眼 color_purple = \u0026#39;#d08fc7\u0026#39; # 深粉紫色，提升深度和对比感 color_red = \u0026#39;#f56b8a\u0026#39; # 深粉红色，强调点缀 color_yellow = \u0026#39;#f2d39b\u0026#39; # 浅黄色，星光点缀 color_font = \u0026#39;#FBF1C7\u0026#39; # 明亮的白色字体，便于阅读 color_font_app = \u0026#39;#FBF1C7\u0026#39; # 一致的白色字体应用 [os] disabled = false style = \u0026#34;bg:color_orange fg:color_fg0\u0026#34; [os.symbols] Windows = \u0026#34;\u0026#34; Ubuntu = \u0026#34;󰕈\u0026#34; SUSE = \u0026#34;\u0026#34; Raspbian = \u0026#34;󰐿\u0026#34; Mint = \u0026#34;󰣭\u0026#34; Macos = \u0026#34;󰀵\u0026#34; Manjaro = \u0026#34;\u0026#34; Linux = \u0026#34;󰌽\u0026#34; Gentoo = \u0026#34;󰣨\u0026#34; Fedora = \u0026#34;󰣛\u0026#34; Alpine = \u0026#34;\u0026#34; Amazon = \u0026#34;\u0026#34; Android = \u0026#34;\u0026#34; Arch = \u0026#34;󰣇\u0026#34; Artix = \u0026#34;󰣇\u0026#34; EndeavourOS = \u0026#34;\u0026#34; CentOS = \u0026#34;\u0026#34; Debian = \u0026#34;󰣚\u0026#34; Redhat = \u0026#34;󱄛\u0026#34; RedHatEnterprise = \u0026#34;󱄛\u0026#34; Pop = \u0026#34;\u0026#34; [username] show_always = true style_user = \u0026#34;bg:color_orange fg:color_fg0\u0026#34; style_root = \u0026#34;bg:color_orange fg:color_fg0\u0026#34; format = \u0026#39;[ $user]($style)\u0026#39; [hostname] disabled = false ssh_only = false ssh_symbol = \u0026#34;🌏 \u0026#34; style = \u0026#34;bold green\u0026#34; # [[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style) # format = \u0026#34;[ @$hostname ]($style)\u0026#34; format = \u0026#34;[[@$hostname](fg:color_font bg:color_orange)]($style)\u0026#34; trim_at = \u0026#34;.companyname.com\u0026#34; [directory] style = \u0026#34;fg:color_fg0 bg:color_yellow\u0026#34; format = \u0026#34;[ $path ]($style)\u0026#34; truncation_length = 3 truncation_symbol = \u0026#34;…/\u0026#34; [directory.substitutions] \u0026#34;Documents\u0026#34; = \u0026#34;󰈙 \u0026#34; \u0026#34;Downloads\u0026#34; = \u0026#34; \u0026#34; \u0026#34;Music\u0026#34; = \u0026#34;󰝚 \u0026#34; \u0026#34;Pictures\u0026#34; = \u0026#34; \u0026#34; \u0026#34;Developer\u0026#34; = \u0026#34;󰲋 \u0026#34; [git_branch] symbol = \u0026#34;\u0026#34; style = \u0026#34;bg:color_aqua\u0026#34; format = \u0026#39;[[ $symbol $branch ](fg:color_fg0 bg:color_aqua)]($style)\u0026#39; [git_status] style = \u0026#34;bg:color_aqua\u0026#34; format = \u0026#39;[[($all_status$ahead_behind )](fg:color_fg0 bg:color_aqua)]($style)\u0026#39; [nodejs] symbol = \u0026#34;\u0026#34; style = \u0026#34;bg:color_blue\u0026#34; format = \u0026#39;[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)\u0026#39; [c] symbol = \u0026#34; \u0026#34; style = \u0026#34;bg:color_blue\u0026#34; format = \u0026#39;[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)\u0026#39; [cpp] symbol = \u0026#34; \u0026#34; style = \u0026#34;bg:color_blue\u0026#34; format = \u0026#39;[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)\u0026#39; [rust] symbol = \u0026#34;\u0026#34; style = \u0026#34;bg:color_blue\u0026#34; format = \u0026#39;[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)\u0026#39; [golang] symbol = \u0026#34;\u0026#34; style = \u0026#34;bg:color_blue\u0026#34; format = \u0026#39;[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)\u0026#39; [php] symbol = \u0026#34;\u0026#34; style = \u0026#34;bg:color_blue\u0026#34; format = \u0026#39;[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)\u0026#39; [java] symbol = \u0026#34;\u0026#34; style = \u0026#34;bg:color_blue\u0026#34; format = \u0026#39;[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)\u0026#39; [kotlin] symbol = \u0026#34;\u0026#34; style = \u0026#34;bg:color_blue\u0026#34; format = \u0026#39;[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)\u0026#39; [haskell] symbol = \u0026#34;\u0026#34; style = \u0026#34;bg:color_blue\u0026#34; format = \u0026#39;[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)\u0026#39; [python] symbol = \u0026#34;\u0026#34; style = \u0026#34;bg:color_blue\u0026#34; format = \u0026#39;[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)\u0026#39; [docker_context] symbol = \u0026#34;\u0026#34; style = \u0026#34;bg:color_bg3\u0026#34; format = \u0026#39;[[ $symbol( $context) ](fg:#83a598 bg:color_bg3)]($style)\u0026#39; [conda] # style = \u0026#34;bg:color_bg3\u0026#34; # format = \u0026#39;[[ $symbol( $environment) ](fg:#83a598 bg:color_bg3)]($style)\u0026#39; style = \u0026#34;green bold\u0026#34; # symbol = \u0026#34;󱔎\u0026#34; symbol = \u0026#34;env:\u0026#34; # \u0026#34;[[ @$hostname](fg:color_yellow bg:color_orange)]($style)\u0026#34; format = \u0026#39;[[ $symbol( $environment) ](fg:color_font_app bg:color_bg3)]($style)\u0026#39; ignore_base = false [pixi] style = \u0026#34;bg:color_bg3\u0026#34; format = \u0026#39;[[ $symbol( $version)( $environment) ](fg:color_fg0 bg:color_bg3)]($style)\u0026#39; [time] disabled = false time_format = \u0026#34;%R\u0026#34; style = \u0026#34;bg:color_bg1\u0026#34; format = \u0026#39;[[  $time ](fg:color_fg0 bg:color_bg1)]($style)\u0026#39; [line_break] disabled = false [character] disabled = false format = \u0026#34;$symbol \u0026#34; success_symbol = \u0026#39;[➜](bold fg:color_green)\u0026#39; error_symbol = \u0026#39;[✗](bold fg:color_red)\u0026#39; vimcmd_symbol = \u0026#39;[](bold fg:color_green)\u0026#39; vimcmd_replace_one_symbol = \u0026#39;[](bold fg:color_purple)\u0026#39; vimcmd_replace_symbol = \u0026#39;[](bold fg:color_purple)\u0026#39; vimcmd_visual_symbol = \u0026#39;[](bold fg:color_yellow)\u0026#39; # error_symbol = \u0026#34;[✗](fg:red)\u0026#34; # success_symbol = \u0026#34;[](fg:green)\u0026#34; 命令行增强 命令行之前，可以先在 Microsoft Learn 上认识一下基本的 windows 命令 Windows commands | Microsoft Learn cd | Microsoft Learn 以 cd 为例，需要切换驱动器需要加上 /d Learn 上推荐的一些 cl 工具 Sysinternals 实用工具 - Sysinternals | Microsoft Learn psping，替换 ping 命令 iperf，测试双向网速 没有被推荐的一些 cl 工具 tree 命令替换 dduan/tre: 改进版的 Tree 命令。 \u0026mdash; dduan/tre: Tree command, improved. tre -d -l 1 . -d 只显示目录，-l 限定深度 x-cmd x-cmd windows 下 1、安装\n[System.Text.Encoding]::GetEncoding(\u0026#34;utf-8\u0026#34;).GetString($(Invoke-WebRequest -Uri \u0026#34;https://get.x-cmd.com/x-cmd.ps1\u0026#34;).RawContentStream.ToArray()) | Invoke-Expression 2、使用\nx tping 192.168.10.4 23 wsl 下\neval \u0026#34;$(curl https://get.x-cmd.com)\u0026#34; 3 / wsl 配置 Windows SSH 配置 1、安装 openssh 1.1) Powershell 打开 PowerShell（管理员），输入：\nGet-WindowsCapability -Online | Where-Object Name -like \u0026#39;OpenSSH*\u0026#39; 输出中 OpenSSH\nName : OpenSSH.Client~~~~0.0.1.0 State : Installed Name : OpenSSH.Server~~~~0.0.1.0 State : NotPresent 如果 State 是 NotPresent → 还没安装 如果 Installed → 已安装 如果 OpenSSH.Server 没有安装，就安装：\nAdd-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0 Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 安装完成后，重启服务或电脑确保生效。\n启动并设置自启动\n# 启动 sshd 服务 Start-Service sshd # 设置开机自动启动 Set-Service -Name sshd -StartupType \u0026#39;Automatic\u0026#39; 这时再运行：\nnet start sshd 就不会报“服务名无效”的错误了。\n1.2) scoop\nscoop install openssh Notes\nNotes ----- Windows 10 or higher includes this build of OpenSSH in the system itself, hence it is recommended to use the system OpenSSH there. Run \u0026#39;sudo D:\\software\\scoop\\apps\\openssh\\current\\install-sshd.ps1\u0026#39; to install sshd and ssh-agent as a service. Run \u0026#39;sudo D:\\software\\scoop\\apps\\openssh\\current\\uninstall-sshd.ps1\u0026#39; to uninstall the services. 运行\nPS C:\\Users\\tohsaka rin\u0026gt; sudo D:\\software\\scoop\\apps\\openssh\\current\\install-sshd.ps1 \u0026gt; sudo net start sshd 输出\nPS C:\\Users\\tohsaka rin\u0026gt; sudo net start sshd OpenSSH SSH Server 服务正在启动 .. OpenSSH SSH Server 服务已经启动成功。 2、开启防火墙 3、连接测试\nPS C:\\Users\\aoi\u0026gt; x tping 192.168.10.4:22 - I|tping: Target -\u0026gt; [host=192.168.10.4] [port=22] 0 0.013 1.554 192.168.10.2 57700 192.168.10.4 22 1 0.011 1.595 192.168.10.2 57702 192.168.10.4 22 seq dns con local-ip:port remote-ip:port 1.595 ms ∈ [ 1 , 1 ] | AVG = 1.57 ± 0.02 FAIL= 0 用户名\n$env:username whoami # 计算机名/用户名，域/用户 设置 ssh 密码 / 不建议更改 Administrator 用户，可能没什么用\nSSH 登录使用的是 Windows 用户账户密码，不是 PIN、指纹或图形密码。 如果该用户没有设置密码，你可能无法通过 SSH 登录（Windows 默认不允许空密码远程登录）。 用户名需要和你在远程机器上看到的完全一致，包括空格，例如 \u0026quot;tohsaka rin\u0026quot;。 net user \u0026#34;tohsaka rin\u0026#34; net user \u0026#34;tohsaka rin\u0026#34; NewPassword123! 连接\nssh -l \u0026#34;tohsaka rin\u0026#34; 192.168.10.4 设置连接时自动打开 pwsh\n打开 管理员权限 PowerShell。 编辑 sshd_config 文件： notepad \u0026quot;C:\\ProgramData\\ssh\\sshd_config\u0026quot; 添加或修改以下行： # 设置登录用户默认 shell ForceCommand \u0026#34;C:\\Program Files\\PowerShell\\7\\pwsh.exe\u0026#34; 或者针对 所有用户：\n# 替换默认 shell Subsystem powershell C:\\Program Files\\PowerShell\\7\\pwsh.exe -sshs -NoLogo -NoProfile 注意路径必须写完整，并且如果路径中有空格，需要用双引号包裹。\n重启\nsudo net stop sshd sudo net start sshd Restart-Service sshd 局域网中的其他主机访问 wsl 值得注意的是，windows10 的 wsl 不支持 mirror 模式，也就是无法使用 localhost 直接访问 wsl（但是可以用 localhost 访问 docker 容器开出来的端口，合理怀疑 docker 开的端口是直接映射 windows 端口，而非 wsl 端口）。镜像模式直到 win11 的 23H2 版本才支持，win10 的 22H2 的最后一个 build 版本也不支持。\n（1）windows11 通过 电脑 A-SSH 登录 电脑 B-Windows，然后自动进入 WSL，而不是 电脑 B-Windows 的 cmd 或 pwsh。\n1、wsl / 设置 wsl 的 root 密码\npasswd root 2、wsl / 首先 wsl 添加 openssh-server\n# 更新并安装 sudo apt update sudo apt install -y openssh-server # 确保 /var/run/sshd 存在（某些发行版需要） sudo mkdir -p /var/run/sshd sudo chown root:root /var/run/sshd # 修改 sshd 配置，将 Port 22 改为 23 # 先备份 sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak 修改 sshd_config\nPort 23 ListenAddress 0.0.0.0 PermitRootLogin yes PasswordAuthentication yes 重启\nsudo service ssh restart 3、windows / 添加映射\nsudo netsh interface portproxy add v4tov4 listenport=23 listenaddress=0.0.0.0 connectport=23 connectaddress=localhost listenport 监听转发端口 listenaddress 设置为 0.0.0.0, 可以给任意网段连接 connectport 目标端口，wsl 开放的 ssh 端口 connectaddress 目标地址，就是 wsl mirrored 的 localhost 一些其他的命令\n# 删除代理 netsh interface portproxy delete v4tov4 listenport=23 listenaddress=0.0.0.0 # 展示所有v4代理 netsh interface portproxy show v4tov4 连接测试\nssh -p 23 root@192.168.10.4 （2）windows10 Success 下面的方法适合在 win10 上启用，我自己不推荐这种麻烦的做法，但如果你是 win10 的话，还是使用这种方法吧。\n如何在局域网的其他主机上中访问本机的 WSL2 - 知乎 如何在局域网的其他主机上中访问本机的 WSL2 - 知乎 Github 上的方法 HobaiRiku/wsl2-auto-portproxy：从 wsl2 linux 到 windows 的代理端口的 TCP 解决方法 \u0026mdash; HobaiRiku/wsl2-auto-portproxy: A TCP workaround tool for proxy port from wsl2 linux to windows 我们这里使用 wslpp 这个项目来将 wsl 的 ip 从 172 代理到 localhost\n工作原理 wslpp start an interval to get IP address of the nat interface and scan all ports listening at all network in the subsystem, then use golang\u0026rsquo;s net to start proxy direct to ports.\n间隔 3s 扫描 wsl 中的所有 ports，然后使用 golang 代理。\n1、启动 wslpp，配置文件在 %HOMEPATH%/.wslpp/config.json 中\ncmd /c wslpp.exe 2、使用 ssh 测试\n3、如果有问题，需要先关闭 23 代理的进程，然后重启 wsl\nnetstat -ano | findstr \u0026#34;:23\u0026#34; taskkill /f /pid \u0026lt;xxxid\u0026gt; More、添加计划任务 wsl 配置 miniforge wget \u0026#34;https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh\u0026#34; # 交互，最后默认no就是不执行初始化 # 如果选择yes就需要手动执行 conda config --set auto_activate_base false bash Miniforge3-$(uname)-$(uname -m).sh # 非交互，直接这个就行 # 然后跳转到下面的「✈️非交互后」 bash Miniforge3-$(uname)-$(uname -m).sh -b 安装配置参考：Miniforge：Conda solving enviroment 终结者 · Aoidayo 补充一些： miniforge linux 默认\n安装在 /root/minifortge3 下面 ✈️ 非交互后\neval \u0026#34;$(/root/miniforge3/bin/conda shell.bash hook)\u0026#34; # 配置写入bashrc conda init # 关闭 base自动激活 conda config --set auto_activate_base false pycharm 配置 ssh 两步\n1、settings\u0026gt;build,execution,deployment\u0026gt;添加 sftp 连接，配置 ssh 连接，最后 apply\n2、设置 Mappings，\n3、添加 Python Interpreter，选择 ssh，设置 rsync 的文件夹。\n编写一段简单的代码，运行即可。\n","date":"2025-10-17T12:42:24+08:00","permalink":"/blog/windows-use/","title":"Windows Scoop包管理，Terminal优化，WSL配置"},{"content":"本文对这篇魔改版 RTX 2060 12G 装驱动 解决 43 错误 - Shaw\u0026rsquo;s Blog 做更详细的傻瓜式补充。\n实验室的这块 GPU 我在咸鱼上看了一下市价，6G 普遍都是四五百，12G 都要七八百了，这类魔改显卡芯片都是笔记本的。去年的时候我看值得买，里面讨论的 4090m 魔改版功耗比 Desltop 版差了一倍多，实际性能只能和 4070ti 掰手腕，我手上的这块 2060m 跑甜甜圈功耗也就在 120 左右。总之垃圾佬可以选择性的尝试甜品显卡的魔改版，高端一点的显卡还是不建议买魔改版。\nGPU 是魔改自笔记本 RTX 2060M 核心的 RTX 2060 12G 版黄伟达 2060M 魔改卡体验 | 梨数码，直接安装官方的 2060 驱动会出现 43 错误。\n20 系魔改卡正确的安装流程为：\n先手动安装 GTX 1660 Ti with Max-Q 驱动（从公版驱动中解压得到，并从设备管理器中手动选择驱动文件进行安装）； 然后使用公版驱动（CUDA 驱动或 Game Ready 驱动等）覆盖一遍 1、在 NVIDIA GeForce 驱动程序 - N 卡驱动 | NVIDIA 搜索 2060 NoteBooks 驱动\n下方的驱动程序中随便选择一个 Game Ready 驱动\n2、在设备管理器中卸载当前已经安装的 Nvidia 驱动/或者使用 DDU 卸载已经有的驱动\n如果在管理中直接卸载可能会出现卸载完成后桌面分辨率过大、系统卡顿、卡死等情况。\n作为替换，我推荐使用 DDU 卸载，这个卸的干净而且使用非常方便。\n卸载完成后，设备管理器的显示适配器中将不会有和 2060、1660Ti 相关的设备显示。\n3、解压缩驱动\n我这里直接使用 7zip 解压驱动\n4、winx-设备管理器-显示适配器，从本地目录查找，选择解压目录下面Display.Driver里面的 nv_dispi.inf，在下方的驱动中选择 1660Ti With Max-Q Design 的驱动 点击下一步安装\n安装完成后重启应用驱动更改（也有的电脑不用重启，直接就应用成功了，这种情况就直接继续打 2060 驱动）\n重启后\n虽然但是，这个时候看 Nvidia-Smi 已经是 2060 的形状了\n5、接着，以管理员模式运行之前下载的 Nvidia 2060 Notebooks 驱动，等待安装完成\n","date":"2025-10-14T22:38:24+08:00","permalink":"/blog/2060m-extra/","title":"魔改版RTX 2060 12G装驱动 的傻瓜式补充"},{"content":"此文章包含加密内容。","date":"2025-05-22T15:38:16+08:00","permalink":"/blog/encrypt/","title":"Hugo 伪加密"},{"content":" 在网易云音乐中打开 PyG中的图神经网络中，scatter和gather运算（离散和聚集）都是非常常见的操作。\n我们以GAT为例，说明scatter在GAT中的应用：一是相似度系数的归一，二是 \\(h_i^{\u0026rsquo;}\\) 的加权邻域聚合。\n相似度系数归一\n$$ \\alpha_{i,j} = \\frac{\\exp(LeakyReLU(e_{i,j}))}{\\sum \\limits_{k \\in N(i)} \\exp(LeakyReLU(e_{i,k})) } $$\n其中，分母部分聚合节点 \\(i\\) 所有邻边相似度系数的运算可以用 scatter_add 简洁优雅的实现。\n\\(h_i^{\u0026rsquo;}\\) 的加权邻域聚合\n$$ h_{i}^{\u0026rsquo;} = \\sigma( \\sum \\limits_{k \\in N(i)} \\alpha_{i,k} h_{k} ) $$\n这里的邻域聚合的操作逻辑基本同上，同样可以使用 scatter_add。\nQ:为什么要用scatter？同样一个邻域聚合的操作，我们可以使用for循环，预处理出neighbor照样可以聚合，那么我们为什么需要使用scatter呢？ A:答案就是scatter支持pytorch的并行化，同时更加便于pytorch的梯度传播。（for循环本身的效率其实不低，因为一个node只有有限个neighbor）\nscatter self.scatter(dim, index, src) self: tensor张量 Args: dim: 沿dim轴索引 index(LongTensor): 索引 src: 写入self中的值张量，类型需要和self相同 作用：将src中的每个值，按index的指定索引，scatter至self中。 以3维张量为例，self中的值由如下公式决定： 同时self，index以及src需要满足如下条件：\nself、index、src的维度相同（即dim相同:self.dim()=index.dim()=src.dim()，非维度大小） 具体来说就是 index.shape (3,4,5), dim()=3,同理 src.shape=(4,5,6), dim()=3。因为需要执行如上公式的赋值，所以self、index、src的维度需要相同。 index每一个维度的大小 \u0026lt;= src每一个维度的大小, 即 \\(index.shape(i) \\leq src.shape(i),\\ i \\in [0,src.dim())\\) \\(index.shape(i) \\leq self.shape(i),\\ i \\in [0,src.dim()) \\ 且 i \\neq dim_{arg}\\), 即除开操作维度dim之外（指的是scatter的参数dim之外），其他维度的大小都必须小于self的对应维度大小。 理解起来同样很简单，因为其他维度需要索引self，而 index[i][j][k] 在操作维度之上无所谓，他只需要满足 index[i][j][k]\u0026lt;=self.dim(arg_dim) 即可。 张量index的数值大小约束：\nindex中的任意值，其大小需要在 [0, self.dim(arg_dim)-1] 的范围内。 index沿dim维度的一行，值必须唯一（弱约束，违反不报错，但是会产生无意义的重复scatter） 例1.index沿dim的一行数组，值不唯一，产生scatter的重复赋值 例2. index沿dim的一行数组，值唯一，scatter离散到不同的位置 scatter_add scatter_add的基本原理同scatter，但是self的同一位置可以多次add（即不需要满足上面数学约束的第五条，允许index沿dim轴有重复值），以GAT的邻域聚合为例：\n单头GAT 邻域相加，得到最后的结果，即 neighbor_sum[i] 为node_i的邻域聚合值。\n多头GAT 基本原理仍然和单头相似，具体如下图所示：\nreference 知乎 奔腾的黑猫 博客： PyTorch数据发散（scatter/scatter_add）与聚集（Gather）操作和二者关系 - 知乎 ","date":"2025-04-21T20:25:21+08:00","permalink":"/blog/scatter-in-gnn/","title":"GNN中的scatter离散运算"},{"content":"引言 conda在不启用libmamba时下载小包的速度就非常慢，一直 solving enviroment... 容易让人狂躁，更不用说用它来安装 pytorch。\n作为替换，可以在 阿里云的pytorch wheels镜像 上下载需要的pytorch，然后再使用 pip 安装，当然后续可能还需要安装 mkl 等依赖，虽然速度尚可，但是还需要手动解决依赖问题，还是有不小的麻烦的。\nconda也清楚自己的依赖解析问题，他们在最新版的conda中引入mamba项目的核心库——用c++重写的依赖项求解器libsolv，conda现在可以通过如下命令开启libmamba作为dependency solver, 支持conda包的高效依赖解析，并行下载与更快速的环境创建。同时，conda也支持直接下载mamba，作为当前环境中下包的一种替换方式。\nconda 启用libmamba作为依赖求解器 / 通过以下方法启用\n# 在base环境中安装conda-libmamba-solver conda install -n base conda-libmamba-solver conda config --set solver libmamba # or conda install numpy --solver=libmamba conda 启用mamba作为替代 / 通过以下方法启用\nconda config --set custom_channels.conda-forge https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/ conda install -n base -c conda-forge mamba # or conda install -c conda-forge mamba mamba install numpy 上面的方法不错，当然有一个问题就出现了，conda下mamba的时候又在 solving environment 了怎么办？这个问题太地狱了。\n当然有解决方法，可以使用 mamba/micromamba 这两个类似于 conda/miniconda 的包管理器作为替代，只有一个问题，就是纯血 mamaba 不支持 pycharm 调试。\n不过这个问题也有解决方法，就是使用 miniforge，这个仅仅只内置 conda-forge 源的最小 conda+mamba 发行版，将 mamba 完美继承到 conda 里面，可以 conda 和 mamba 命令互换，几乎0学习成本，而且安装也很方便。\n最后再提一嘴 mamba 原作者正在维护的 pixi，这个工具更加类似于 venv，一个项目创建一个虚拟环境，和 conda/mamba 不是一个赛道的，而且 pixi 换源/镜像非常麻烦，现在不是很成熟。另外作为 pip 的替代品 uv，现在已经相当好用了，可以用conda或者mamba直接安装，因为 pip 的速度尚可，所以关于 uv 的介绍以后有机会再写吧。\n安装 如果已经装了一些包管理器，先卸载\n如果已经安装了micromamba，则卸载需要注意: micromamba如果是用cmd安装的，默认会安装在 C:/User/YourName 下面, 需要同时删除可执行文件和所有envs.\n关于miniforge:\n安装 miniforge/conda-forge官网 conda-forge/miniforge: A conda-forge distribution. miniforge github repo页面 conda-forge | community-driven packaging for conda | conda-forge | community-driven packaging for conda guide，同时参考mamba和conda micromamba conda windows安装时需要注意：\n安装时 Install 选项 JustForMe, 否则会出现创建环境下载包时权限不够,无法写入文件 手动添加miniforge的path 换源 使用mamba时需要使用pytorch和nvidia仓库的镜像源来下载 pytorch-cuda及其依赖，前者的国内镜像仓库很多，后者的镜像仓库国内只有南方科技大学和cernet校园联合镜像站mirrorz提供，所以一旦这两个哥们不提供支持了，mamba在国内也就废了。\n# 添加 pytorch 镜像 conda config --set custom_channels.pytorch https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/ # 添加 nvidia 镜像 conda config --set custom_channels.nvidia https://mirrors.cernet.edu.cn/anaconda-extra/cloud/ .condarc\nwindows : C:\\Users\\ffs linux : ~/.condarc show_channel_urls: true channels: - defaults default_channels: - https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch/ - https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/menpo/ - https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/bioconda/ - https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/msys2/ - https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/ custom_channels: pytorch: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/ nvidia: https://mirrors.cernet.edu.cn/anaconda-extra/cloud/ conda-forge: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/ or\nchannels: - https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch/ - https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/menpo/ - https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/bioconda/ - https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/msys2/ - https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/ show_channel_urls: true custom_channels: pytorch: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/ nvidia: https://mirrors.cernet.edu.cn/anaconda-extra/cloud/ conda-forge: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/ linux使用命令写入\ncat \u0026lt;\u0026lt;\u0026#39;EOF\u0026#39; \u0026gt; ~/.condarc channels: - defaults show_channel_urls: true default_channels: - https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch/ - https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/menpo/ - https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/bioconda/ - https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/msys2/ - https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/ custom_channels: pytorch: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/ nvidia: https://mirrors.cernet.edu.cn/anaconda-extra/cloud/ conda-forge: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/ EOF 基本命令流 mamba create -n mamba python=3.9 conda search pytorch-cuda=11.8 # mamba install pytorch-cuda=11.8=\u0026lt;build_version\u0026gt; -c nvidia -c pytorch mamba install pytorch-cuda=11.8=h8dd9ede_2 -c nvidia -c pytorch # conda search pytorch=2.3.0 --info mamba install pytorch=2.3.0=py3.9_cuda11.8_cudnn8_0 ","date":"2025-03-19T22:00:16+08:00","permalink":"/blog/conda-or-mamba/","title":"Miniforge：Conda solving enviroment终结者"},{"content":"此文章包含加密内容。","date":"2025-03-18T07:00:16+08:00","permalink":"/blog/24-sum/","title":"24年年终总结"},{"content":"前言 最近使用 Git 管理 Unity 项目，误将 Unity 的Filo Cables包commit\u0026amp;push到仓库中了，折腾半天发现无法撤回，这个包没有导入到 Unity 之前就有12MB+, 对于一个 Git 仓库来说还是太大了，所以需要即时从.git中删除所有相关文件的历史提交记录，避免仓库越来越大，防止占用 Github 公共资源。\n工具 推荐使用git filter-repo，一个可以清理并重写仓库中历史记录的工具；自动清理本地仓库的引用与缓存；比 git 自带的git filter-branch更加高效，后者容易导致历史记录的混乱与重写；适合处理有高占用空间文件的仓库（比如 Unity 的 3d-fbx 文件，模型 pt 文件等等）。\ngit filter-repo：newren/git-filter-repo: Quickly rewrite git repository history (filter-branch replacement) 验证清理效果：git 自带命令 查看 git 仓库占用空间：git count-objects -vH 常用方法 A. filter-repo # 1. 文件夹 # 保留my_folder下面的所有文件, 删除其他所有文件 git filter-repo --path my_folder # --invert-paths 反转. 删除my_folder下面的所有文件 git filter-repo --path my_folder --invert-paths --force # 2. 文件 # --path-glob \u0026lt;file.name\u0026gt; 删除指定文件 git filter-repo --path-glob xxx.mp4 --invert-paths --force # 3. 占用空间 git filter-repo --strip-blobs-bigger-than 100K git filter-repo --strip-blobs-bigger-than 1M git filter-repo --strip-blobs-bigger-than 1G # 清理引用和缓存 git reflog expire --expire=now --all \u0026amp;\u0026amp; git gc --prune=now --aggressive # 可能清理完成之后，把remote也给清除了，这里重置 git remote add origin git@github.com:Aoidayo/xxx.git # push git push origin --force --all git push origin --force --tags B. 验证 git rev-list 查看高占用文件， 剩余文件大小分布 git count-objects -vH git log \u0026lt;file/dir\u0026gt; # 1. git rev-list # 查看高空间占用的文件 : 列出 Git 仓库中占用空间最大的 10 个文件（blob 对象） git rev-list --objects --all | grep -f \u0026lt;(git verify-pack -v .git/objects/pack/*.idx | grep blob | sort -k 3 -n | cut -f 1 -d \u0026#34; \u0026#34;|tail -10) # 剩余文件大小分布 (B) git rev-list --objects --all | awk \u0026#39;{print $1}\u0026#39; | git cat-file --batch-check=\u0026#39;%(objecttype) %(objectname) %(objectsize) %(rest)\u0026#39; | awk \u0026#39;/^blob/ {print $3, $4}\u0026#39; | sort -n # 2. git count-objects -vH \u0026#39;\u0026#39;\u0026#39; git count-objects -vH count: 19 size: 25.71 KiB in-pack: 283 packs: 1 size-pack: 9.30 MiB prune-packable: 0 garbage: 0 size-garbage: 0 bytes \u0026#39;\u0026#39;\u0026#39; # 3. git log \u0026lt;file/dir\u0026gt; $ git log RopeUpDown\\ # 没有输出表示已经清理干净了 # 输出 和当前文件或者目录有关的commit history \u0026#39;\u0026#39;\u0026#39; \u0026gt; commit c656025c6c761d5f3fc0be2989c34232cdaf5dfb (HEAD -\u0026gt; main) Author: aoi \u0026lt;2908496836@qq.com\u0026gt; Date: Fri Feb 28 21:17:38 2025 +0800 fix(remove filo): add ignore commit 6f0a3ffb2731698ec5ce981f9fa281ee8332ba13 Author: aoi \u0026lt;2908496836@qq.com\u0026gt; Date: Fri Feb 28 20:57:55 2025 +0800 Rope Up Down \u0026#39;\u0026#39;\u0026#39; 附录 参考：\nGitFAQ-如何删除提交记录中的大文件_哔哩哔哩_bilibili Git 清理.git 文件夹中的大文件及.gitignore 简单使用 - 知乎 DS ","date":"2025-02-28T22:15:00+08:00","permalink":"/blog/git-big/","title":"Git 清除.git仓库历史记录中的高存储占用文件"},{"content":"此文章包含加密内容。","date":"2025-01-07T15:38:16+08:00","permalink":"/blog/use-hugo/","title":"使用hugo"},{"content":"FrameWork Typecho 24/1/? wordpress的精简版本，用docker装容易产生nginx的user:www-data 权限问题，在使用时必须没写完一篇文章就sudo chmod -Rf 777 typecho/一次，其他没有缺点，主题也都不错，主要走简洁风（Handsome开心版）。\n因为权限问题弃用。\nHello Hexo 24/4/14 第一个博客在github.io上使用Hexo和GithubActions搭建。\nGithub原生支持的静态博客框架有Jekyll, Hexo, Hugo这三个，其中Hexo使用起来最麻烦，Hugo和Jekyll稍微简单一点。\n这类静态博客使用起来最大的槽点就是主题基本上没有一个比较详细的使用说明，当你 clone一个主题下来后，可以说是开箱不能用🤣，所有关乎到日常使用以及美观的配置都得你自己摸索（甚至直接在github上找别人的配置文件也不保证能100%复现），这也就导致了网上关于静态博客如何配置的教程也是最多的，最奇怪的一点就是为什么作者不能给一个开箱即用的 simple starter再搭配一个接入github评论的 mkdocs让大家来讨论，这就是前端的奇奇怪怪之处吧。\n我尝试过如下几种静博框架的不同主题：\nHexo Butterfly Hugo Blowfish Stack Jekyll tmaize-blog Hello Hugo 24/4/14 Hugo是我尝试过的三个静态博客框架中，视觉效果最为出色的一个。特别是其中的Blowfish和Stack主题，它们的设计简洁而不失优雅，色调和排版清新现代。Blowfish主题一种类似苹果的背景高斯模糊展示两种层次的背景，Stack有一种说不上来的直观、流畅。\n总之Hugo我很喜欢，但是配置和使用太麻烦了。\n顺带一提，Hugo虽然原生支持markdown，但是如果你要在页面上显示目录的话，只能使用二级及以下的目录，不能使用一级目录撰文。\nHello Wordpress 24/5/? Wordpress的Sakurairo很好，但是太重了，我的小带宽承受不了。\nHello Halo 24/10/12 争议性最大的，彼时的Halo还没有开始收费，其在12月份宣布启用专业版收费，价格相当高，960买断才能使用应用市场中的插件。\n抛开争议话题不谈，Halo本身做的还是很优秀的，较低的内存占用和较快的响应速度、不算丑的外观、简单的docker部署、两键将备份打包迁移到新网站，这些优点是许多用户选择它的理由，对比停更几年的Typecho和高占用的Wp，让人很难不选择Halo。\nDeployMent 旅游地图 24/11/1 偶尔看看部署的这个小玩具，可以想起旅游的快乐时光。\nNas导航 24/11/1 copy一个学弟的导航页，用来重定向到我的nas服务。\n","date":"2025-01-04T15:38:16+08:00","permalink":"/blog/blog-history/","title":"Blog History"}]
