这次搬迁的原因是看 MoScenix 的新博客主题太丝滑了,自己也想弄一个。不查不知道,astro框架下有不少开箱即用的博客,这个博客用了 Mizuki Next Theme。
安装依赖
astro 是一个 js 框架,所以需要先保证 nodejs 的环境,另外需要 pnpm,如果是用了 homebrew 的 mac 那么只需要
brew install nodenpm install -g pnpm装完之后检查一下
node -vnpm -vpnpm -v本地调试
直接 clone 主题的模板,然后安装需要的包
git clone https://github.com/matsuzaka-yuki/mizuki.gitcd Mizukipnpm install直接
pnpm dev就可以跑起来测试页了,默认端口是 4321,访问 http://localhost:4321 即可预览。
如果想构建用于在生产环境部署的版本需要
pnpm run build默认会输出到 ./dist 目录下,预览构建出的静态网页
pnpm preview如果需要直接部署到服务器上,把 ./dist 目录复制到 /var/www,用 nginx 或者 apache2 部署均可。
配置
改 src/config.ts 中的配置,官方有中文文档,很详细。
我配置的过程中遇到了一些迷惑的问题
| 问题 | 情况 |
|---|---|
| toc 怎么设置好像都没有,很奇怪 | 实际上是因为我的浏览器窗口不够宽,toc 被挤到右边屏幕外了 |
| Twikoo 评论区的 IP 地址莫名其妙都在美国 | 很诡异 |
迁移文章
主要是这个文件头,下面是官方给的规范(然后我就发现了个问题,这个表格的有边框没有贴合😨,强迫症又要犯了。
| Attribute | Description |
|---|---|
title | The title of the post. |
published | The date the post was published. |
pinned | Whether this post is pinned to the top of the post list. |
priority | The priority of the pinned post. Smaller value means higher priority (0, 1, 2…). |
description | A short description of the post. Displayed on index page. |
image | The cover image path of the post. 1. Start with http:// or https://: Use web image2. Start with /: For image in public dir3. With none of the prefixes: Relative to the markdown file |
tags | The tags of the post. |
category | The category of the post. |
licenseName | The license name for the post content. |
author | The author of the post. |
sourceLink | The source link or reference for the post content. |
draft | If this post is still a draft, which won’t be displayed. |
我要从 Hugo,根据这个表,需要动一下 Frontmatter,主要需要调整
date->published,并删除引号和冗余的详细时间categories->category,只保留一个类别
另外文章内容也要微调一下
- 内部 a 标签
/p/xxx->/posts/xxx/,/tags/xxx->/archive/?tag=xxx… - 内部公式需要统一改成
$ 我是行内公式 $,$$ 我是大公式 $$,不能用\( \)和\[ \]组合
于是我让 GPT 生成了一个脚本处理了 Frontmatter,然后手动处理了一下公式
from pathlib import Pathfrom shutil import copy2, rmtreeimport reimport ast
input_dir = Path.cwd() / "post"output_dir = Path.cwd() / "output"
def extract_date(date_str: str) -> str: """ 从 2025-03-02T15:09:48+0000 或 2025-07-26T17:11:04+08:00 提取 2025-03-02 """ return date_str[:10]
def parse_yaml_front_matter(text: str): """ 解析 --- YAML ---,返回 (meta_dict, body) """ parts = text.split("---", 2) if len(parts) < 3: return None, text
raw = parts[1].strip() body = parts[2].lstrip()
meta = {} for line in raw.splitlines(): if ":" not in line: continue k, v = line.split(":", 1) meta[k.strip()] = ast.literal_eval(v.strip()) if v.strip().startswith(("[", "'", '"')) else v.strip()
return meta, body
def safe_eval(value: str): """ 尝试 literal_eval,失败则作为普通字符串 """ value = value.strip() try: return ast.literal_eval(value) except Exception: return value.strip("'\"") # 去掉可能的引号
def parse_ini_front_matter(text: str): """ 解析 +++ INI/TOML 风格 +++ """ parts = text.split("+++", 2) if len(parts) < 3: return None, text
raw = parts[1].strip() body = parts[2].lstrip()
meta = {} for line in raw.splitlines(): line = line.strip() if not line or line.startswith("#"): continue if "=" not in line: continue
k, v = line.split("=", 1) meta[k.strip()] = safe_eval(v)
return meta, body
def needs_quotes(s: str) -> bool: return any(c in s for c in [":", "#", "\n", "\"", "'"])
def to_yaml_front_matter(meta: dict) -> str: lines = ["---"] for k, v in meta.items(): if isinstance(v, list): lines.append(f"{k}: {v}") elif isinstance(v, bool): lines.append(f"{k}: {'true' if v else 'false'}") elif isinstance(v, (int, float)): lines.append(f"{k}: {v}") elif isinstance(v, str): if needs_quotes(v): lines.append(f'{k}: "{v}"') else: lines.append(f"{k}: {v}") else: lines.append(f"{k}: {v}") lines.append("---\n") return "\n".join(lines)
def choose_category(title: str, categories: list[str]) -> str: print("\n⚠️ 检测到多个 categories") print(f"文章标题:{title}") print("可选分类:")
for i, c in enumerate(categories, 1): print(f" {i}. {c}")
while True: try: choice = int(input("请选择一个分类编号: ")) if 1 <= choice <= len(categories): return categories[choice - 1] except ValueError: pass print("输入无效,请重新输入。")
def normalize_meta(meta: dict, src_path: Path | None = None) -> dict: new_meta = {}
title = meta.get("title") if not title and src_path: title = src_path.stem
for k, v in meta.items(): if k == "date": new_meta["published"] = extract_date(str(v))
elif k == "draft": continue
elif k == "categories": if isinstance(v, list): if len(v) == 1: new_meta["category"] = v[0] elif len(v) > 1: chosen = choose_category(title or "(无标题)", v) new_meta["category"] = chosen else: new_meta["category"] = v
else: new_meta[k] = v
return new_meta
def process_md(src: Path, dst: Path): text = src.read_text(encoding="utf-8")
if text.startswith("---"): meta, body = parse_yaml_front_matter(text) elif text.startswith("+++"): meta, body = parse_ini_front_matter(text) else: dst.write_text(text, encoding="utf-8") return
if meta is None: dst.write_text(text, encoding="utf-8") return
meta = normalize_meta(meta, src) yaml_block = to_yaml_front_matter(meta) dst.write_text(yaml_block + body, encoding="utf-8")
def main(): if output_dir.exists(): rmtree(output_dir) output_dir.mkdir(parents=True)
for src in input_dir.rglob("*"): rel = src.relative_to(input_dir) dst = output_dir / rel
if src.is_dir(): dst.mkdir(parents=True, exist_ok=True) continue
if src.suffix == ".md": dst.parent.mkdir(parents=True, exist_ok=True) process_md(src, dst) else: dst.parent.mkdir(parents=True, exist_ok=True) copy2(src, dst)
if __name__ == "__main__": main()最近腾讯云有一个可以白嫖的 Pages,但是国内服务器 + 国内域名需要备案,等备完案之后更新下一篇。
部署到 nginx
因为备案域名需要买服务器,所以我就 79R 买了个 2G + 2G 服务器,作为临时方案,我尝试先部署到服务器的 nginx 上。我本来想按照标准的流程先给服务器配个 Github 的 ssh,然后 clone 项目。发现他这个丐版服务器根本跑不了 pnpm run build,然后我突然灵机一动,我在本地构建一下然后挪到服务器上不就行了,于是喂给 GPT,就有了下面这个脚本。
#!/usr/bin/env bashset -e
# ===== 配置区 =====SERVER_USER=youruserSERVER_HOST=your.server.ipSERVER_DIR=/var/www/htmlARCHIVE=dist.tar.gz# ==================
echo "===> Building..."pnpm build
echo "===> Packing dist/"rm -f $ARCHIVEtar -zcvf $ARCHIVE dist
echo "===> Uploading to server..."scp $ARCHIVE ${SERVER_USER}@${SERVER_HOST}:/tmp/
echo "===> Deploying on server..."ssh ${SERVER_USER}@${SERVER_HOST} << 'EOF'set -ecd /tmp
# 备份旧站点if [ -d /var/www/html ]; then sudo rm -rf /var/www/html.bak sudo mv /var/www/html /var/www/html.bakfi
# 解压并部署tar -zxvf dist.tar.gzsudo mv dist /var/www/html
# 权限(按需)sudo chown -R www-data:www-data /var/www/html || trueEOF
echo "===> Done!"
# 把包删了rm -f $ARCHIVE当然,如果还没成功备案,想把他解析到域名,那可能会

部分信息可能已经过时







