[{"content":"Welcome to Hugo theme Stack. This is your first post. Edit or delete it, then start writing!\nFor more information about this theme, check the documentation: https://stack.jimmycai.com/\nWant a site like this? Check out hugo-theme-stack-stater\nPhoto by Pawel Czerwinski on Unsplash\n","date":"2022-03-06T00:00:00Z","image":"/p/hello-world/cover.jpg","permalink":"/p/hello-world/","title":"Hello World"},{"content":"这篇笔记完整记录了我把一个本地 Hugo 博客部署到 VPS 的全过程，包括：\n本地 Hugo 模板初始化与基础配置 GitHub 仓库与 Actions 自动部署 VPS 上的目录规划与 Nginx 配置 域名解析与 HTTPS 证书申请 常见问题排查 这个过程中经常使用的命令与含义 最终实现的效果是：本地写文章并提交到 GitHub 后，GitHub Actions 自动构建静态站点并发布到 VPS，网页内容自动更新。\n一、目标与整体思路 我想实现的目标非常明确：\n在本地维护 Hugo 博客内容 通过 git push 将更新提交到 GitHub GitHub Actions 自动构建 Hugo 站点 将生成后的静态文件自动同步到 VPS 由 Nginx 直接对外提供网页服务 配置 HTTPS，让 phyweicheng.com 和 www.phyweicheng.com 都可以安全访问 整个流程可以概括为：\n1 2 3 4 5 6 7 本地修改文章 -\u0026gt; git commit / git push -\u0026gt; GitHub Actions 自动构建 Hugo -\u0026gt; 通过 SSH/SCP 上传静态文件到 VPS -\u0026gt; VPS 切换 current 软链接到最新版本 -\u0026gt; Nginx 读取 current 目录 -\u0026gt; 网站自动更新 二、本地 Hugo 模板部署 1. 准备 Hugo 博客项目 首先需要在本地准备一个 Hugo 项目。我这里使用的是 Hugo Theme Stack 模板，并保留了标准的 Hugo 目录结构，例如：\n1 2 3 4 5 6 config/ content/ layouts/ assets/ themes/ .github/ 其中最关键的是：\ncontent/：文章内容 config/：站点配置 .github/workflows/：GitHub Actions 自动部署配置 2. 设置生产站点地址 在 Hugo 中，生产站点的域名需要通过 baseurl 指定。我在配置文件中设置为：\n1 baseurl = \u0026#34;https://www.phyweicheng.com/\u0026#34; 对应文件：\nconfig/_default/config.toml 这个值非常重要，因为 Hugo 在构建时会根据它生成页面中的链接、资源路径和 canonical URL。\n3. 保持 public/ 不提交到仓库 Hugo 构建后的静态文件默认输出到 public/。这部分不需要提交到 GitHub，而是交给 GitHub Actions 自动构建。\n因此 .gitignore 中应当忽略：\n1 public 这样可以避免把编译产物混入源码仓库。\n三、将博客与 GitHub 仓库连接 1. 初始化 Git 仓库并提交 如果项目还没有连接 GitHub，基本流程如下：\n1 2 3 4 5 6 git init git add . git commit -m \u0026#34;init hugo blog\u0026#34; git branch -M main git remote add origin \u0026lt;your-github-repo-url\u0026gt; git push -u origin main 完成后，本地博客项目就与 GitHub 仓库建立了连接。\n2. 为什么要用 GitHub Actions 这里没有选择“在 VPS 上直接 git pull 并构建 Hugo”，而是选择让 GitHub Actions 来完成构建，原因是：\nVPS 不需要安装 Hugo VPS 不需要保留完整源码仓库 构建过程统一由 GitHub 执行，结果更稳定 VPS 只保存最终静态文件，更适合静态站点部署 四、配置 GitHub Actions 自动部署 1. 自动部署工作流的目标 自动部署的核心思路是：\n当 main 分支收到新的提交 GitHub Actions 自动运行 Hugo 构建 将构建好的静态文件打包 通过 SSH 上传到 VPS 在 VPS 上解压并切换到最新版本 2. 工作流文件位置 自动部署工作流文件放在：\ndeploy-vps.yml 这个工作流主要做了下面几件事：\ncheckout 当前仓库 安装 Go 安装 Hugo Extended 执行 hugo --gc --minify 将 public/ 打成压缩包 使用 appleboy/scp-action 上传到 VPS 使用 appleboy/ssh-action 在 VPS 上切换部署版本 3. 为什么要用 releases 和 current 在 VPS 上，我采用了这样的目录结构：\n1 2 3 /var/www/phyweicheng-blog/ releases/ current -\u0026gt; /var/www/phyweicheng-blog/releases/\u0026lt;commit-sha\u0026gt; 每次发布都会：\n生成一个新的版本目录 把静态文件解压进去 将 current 软链接切换到新版本 这种方案的好处是：\n发布过程清晰 方便回滚 Nginx 永远读取固定路径 current 五、配置 GitHub Actions 所需 Secrets 在 GitHub 仓库中进入：\n1 Settings -\u0026gt; Secrets and variables -\u0026gt; Actions 然后新增或确认以下 Repository secrets：\n1. VPS_IP VPS 的公网 IP 地址。\n2. VPS_USER GitHub Actions 登录 VPS 时使用的 Linux 用户名。\n如果直接使用 root 登录，可以写：\n1 root 3. VPS_PORT SSH 登录端口。如果服务器 SSH 不是默认的 22，这里需要填实际端口，例如：\n1 6626 4. VPS_DEPLOY_PATH 站点部署目录，例如：\n1 /var/www/phyweicheng-blog 5. SSH_PRIVATE_KEY GitHub Actions 用于连接 VPS 的私钥内容。通常需要把本地或服务器生成的一对 SSH 密钥中的私钥完整粘贴到这里。\n六、VPS 上的目录准备 1. 创建部署目录 如果使用 root 部署，可以直接执行：\n1 2 sudo mkdir -p /var/www/phyweicheng-blog/releases sudo chown -R root:root /var/www/phyweicheng-blog 如果使用普通用户，例如 deploy，则应改为：\n1 sudo chown -R deploy:deploy /var/www/phyweicheng-blog 2. 检查 SSH 连通性 在正式自动部署前，最好先确认 GitHub 与 VPS 的 SSH 连通已经打通。\n如果 GitHub Actions 能顺利执行远程命令，说明：\nVPS_IP 正确 VPS_USER 正确 SSH_PRIVATE_KEY 正确 VPS_PORT 正确 七、配置 Nginx 站点 1. 站点配置文件 我的 VPS 没有使用 sites-available / sites-enabled 结构，因此采用了 conf.d 的方式。\n配置文件路径：\n1 /etc/nginx/conf.d/phyweicheng-blog.conf 2. 初始 HTTP 配置 在申请证书之前，先配置 HTTP 站点：\n1 2 3 4 5 6 7 8 9 10 11 server { listen 80; server_name phyweicheng.com www.phyweicheng.com; root /var/www/phyweicheng-blog/current; index index.html; location / { try_files $uri $uri/ =404; } } 保存后执行：\n1 2 sudo nginx -t sudo systemctl reload nginx 只要域名已经解析到 VPS，这时就应该能通过 HTTP 访问网页。\n八、域名解析配置 为了让两个域名都能访问，需要在域名服务商后台配置 DNS 解析。\n推荐至少配置两条 A 记录：\n1 2 A @ -\u0026gt; VPS公网IP A www -\u0026gt; VPS公网IP 含义如下：\n@ 表示裸域 phyweicheng.com www 表示 www.phyweicheng.com 如果只配置了 www，那么通常只有 www.phyweicheng.com 可以访问，而 phyweicheng.com 无法访问。\n九、申请 HTTPS 证书 1. 安装 Certbot 我的系统是 Debian，因此安装方式如下：\n1 2 sudo apt update sudo apt install certbot python3-certbot-nginx -y 2. 申请两个域名的证书 执行：\n1 sudo certbot --nginx -d phyweicheng.com -d www.phyweicheng.com 这个命令会完成以下工作：\n申请 Let’s Encrypt 证书 自动修改 Nginx 配置 为 phyweicheng.com 和 www.phyweicheng.com 绑定证书 可选开启 HTTP 到 HTTPS 的自动跳转 3. 证书申请后检查 申请完成后，可以执行：\n1 2 3 4 sudo nginx -t sudo systemctl reload nginx sudo ls -la /etc/letsencrypt/live/ sudo ls -la /etc/letsencrypt/live/phyweicheng.com/ 如果能看到：\nfullchain.pem privkey.pem 说明证书已经生成成功。\n十、HTTPS 访问失败时的一个真实问题 在本次部署过程中，曾经出现过一个很典型的问题：\n证书申请成功 Nginx 配置正常 但浏览器访问 https://www.phyweicheng.com 仍然失败 最后排查发现，并不是证书有问题，而是：\n1 443 端口被 xray 占用了 通过以下命令可以检查是谁占用了 443：\n1 sudo ss -tlnp | grep 443 如果看到是 xray 而不是 nginx，就说明 HTTPS 请求没有交给 Nginx 处理。\n解决方法是：\n修改 xray 配置，将其监听端口从 443 改到其他端口，例如 8443 在客户端同步修改 xray 端口 放行新的端口，例如 8443 重新启动 xray 和 nginx 相关命令例如：\n1 2 3 4 sudo systemctl restart xray sudo systemctl restart nginx sudo ufw allow 8443/tcp sudo ufw reload 十一、自动续期是否会生效 只要使用的是 Debian 上标准安装的 certbot，通常会自动配置续期机制。\n可以通过以下命令检查：\n1 2 3 systemctl list-timers | grep certbot sudo systemctl status certbot.timer sudo certbot renew --dry-run 它们的作用分别是：\n查看是否存在 Certbot 定时任务 查看续期定时器是否已启用 模拟一次证书续期，确认自动续期链路没问题 十二、首次上线后的完整发布流程 当以上步骤全部完成后，后续每次更新博客的流程就非常简单：\n1. 在本地修改文章或页面 例如新建文章、修改配置、调整样式等。\n2. 本地测试 Hugo 1 hugo server 或者正式构建检查：\n1 hugo --gc --minify 3. 提交到 GitHub 1 2 3 git add . git commit -m \u0026#34;update blog post\u0026#34; git push origin main 4. GitHub Actions 自动执行部署 推送成功后，GitHub 会自动：\n构建 Hugo 站点 上传 public/ 产物到 VPS 切换 current 到最新版本 5. Nginx 自动提供新内容 因为 Nginx 的 root 始终指向：\n1 /var/www/phyweicheng-blog/current 所以网站内容会自动更新，不需要登录 VPS 手动 git pull 或手动复制文件。\n十三、常见问题总结 1. GitHub Actions checkout 失败 可能原因：\n主题目录存在残留 submodule 信息 仓库里有 gitlink，但没有 .gitmodules 解决思路：\n不依赖错误的 submodule checkout 改用 Hugo module 的方式引入主题 2. phyweicheng.com 能否访问取决于 DNS 如果只有 www.phyweicheng.com 能访问，通常是因为只解析了 www，没有解析裸域 @。\n3. https:// 无法访问但 http:// 正常 重点检查：\nNginx 是否监听 443 443 是否被其他程序占用 防火墙是否放行 443 云厂商安全组是否放行 443 4. xray 改端口后仍然无法连接 重点检查：\nxray 是否真的监听了新端口 ufw 和云安全组是否放行新端口 客户端端口是否同步修改 十四、这个过程中常用的命令汇总 下面把整个部署过程中最常用的命令整理一下。\n1. Git 相关 1 git add . 将当前修改加入暂存区。\n1 git commit -m \u0026#34;message\u0026#34; 生成一次本地提交记录。\n1 git push origin main 将本地提交推送到 GitHub 的 main 分支，并触发自动部署。\n2. Hugo 相关 1 hugo server 在本地启动开发服务器，方便预览博客效果。\n1 hugo --gc --minify 执行正式构建：\n--gc：清理无用缓存和资源 --minify：压缩输出内容 3. Nginx 相关 1 sudo nginx -t 检查 Nginx 配置语法是否正确。\n1 sudo systemctl reload nginx 平滑重载 Nginx 配置，不中断服务。\n1 sudo systemctl restart nginx 重启 Nginx 服务，适合在重大配置变化后使用。\n4. Certbot 相关 1 sudo certbot --nginx -d phyweicheng.com -d www.phyweicheng.com 为两个域名申请证书，并自动写入 Nginx HTTPS 配置。\n1 sudo certbot renew --dry-run 模拟续期，验证自动续期逻辑是否正常。\n5. 端口与服务排查 1 sudo ss -tlnp | grep 443 检查谁正在监听 443 端口。\n1 sudo systemctl status nginx 查看 Nginx 当前状态。\n1 sudo systemctl status xray 查看 xray 当前状态。\n1 sudo journalctl -u xray -n 50 --no-pager 查看 xray 最近的日志，便于定位启动失败和配置错误。\n6. 防火墙相关 1 sudo ufw status 查看当前防火墙规则。\n1 2 3 sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw allow 8443/tcp 分别放行 HTTP、HTTPS 和 xray 的自定义端口。\n1 sudo ufw reload 重新加载防火墙规则。\n7. 证书文件检查 1 2 sudo ls -la /etc/letsencrypt/live/ sudo ls -la /etc/letsencrypt/live/phyweicheng.com/ 确认证书目录和文件是否存在。\n十五、总结 这次部署的关键点并不只是“把网页放上去”，而是把整套链路打通：\n本地负责内容维护 GitHub 负责自动构建与自动发布 VPS 负责稳定托管静态文件 Nginx 负责网站访问 Certbot 负责 HTTPS 当这套流程跑通以后，博客维护就会轻松很多。以后更新文章时，只需要：\n1 2 3 git add . git commit -m \u0026#34;update note\u0026#34; git push origin main 剩下的构建、上传、切换版本、网页更新，都会自动完成。\n如果以后继续扩展，还可以在这套基础上增加：\n评论系统 访问统计 备份与回滚脚本 多环境部署 自动化健康检查 但对于一个 Hugo 静态博客来说，这样的部署方案已经足够稳定、清晰，也非常适合长期维护。\n","date":"2026-04-28T00:00:00Z","image":"/p/hugo-vps-auto-deploy/cover.png","permalink":"/p/hugo-vps-auto-deploy/","title":"Hugo 博客自动部署到 VPS 的完整流程记录"},{"content":"This article offers a sample of basic Markdown syntax that can be used in Hugo content files, also it shows whether basic HTML elements are decorated with CSS in a Hugo theme.\nHeadings The following HTML \u0026lt;h1\u0026gt;—\u0026lt;h6\u0026gt; elements represent six levels of section headings. \u0026lt;h1\u0026gt; is the highest section level while \u0026lt;h6\u0026gt; is the lowest.\nH1 H2 H3 H4 H5 H6 Paragraph Xerum, quo qui aut unt expliquam qui dolut labo. Aque venitatiusda cum, voluptionse latur sitiae dolessi aut parist aut dollo enim qui voluptate ma dolestendit peritin re plis aut quas inctum laceat est volestemque commosa as cus endigna tectur, offic to cor sequas etum rerum idem sintibus eiur? Quianimin porecus evelectur, cum que nis nust voloribus ratem aut omnimi, sitatur? Quiatem. Nam, omnis sum am facea corem alique molestrunt et eos evelece arcillit ut aut eos eos nus, sin conecerem erum fuga. Ri oditatquam, ad quibus unda veliamenimin cusam et facea ipsamus es exerum sitate dolores editium rerore eost, temped molorro ratiae volorro te reribus dolorer sperchicium faceata tiustia prat.\nItatur? Quiatae cullecum rem ent aut odis in re eossequodi nonsequ idebis ne sapicia is sinveli squiatum, core et que aut hariosam ex eat.\nBlockquotes The blockquote element represents content that is quoted from another source, optionally with a citation which must be within a footer or cite element, and optionally with in-line changes such as annotations and abbreviations.\nBlockquote without attribution Tiam, ad mint andaepu dandae nostion secatur sequo quae. Note that you can use Markdown syntax within a blockquote.\nBlockquote with attribution Don\u0026rsquo;t communicate by sharing memory, share memory by communicating.\n— Rob Pike1\nTables Tables aren\u0026rsquo;t part of the core Markdown spec, but Hugo supports supports them out-of-the-box.\nName Age Bob 27 Alice 23 Inline Markdown within tables Italics Bold Code italics bold code A B C D E F Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus ultricies, sapien non euismod aliquam, dui ligula tincidunt odio, at accumsan nulla sapien eget ex. Proin eleifend dictum ipsum, non euismod ipsum pulvinar et. Vivamus sollicitudin, quam in pulvinar aliquam, metus elit pretium purus Proin sit amet velit nec enim imperdiet vehicula. Ut bibendum vestibulum quam, eu egestas turpis gravida nec Sed scelerisque nec turpis vel viverra. Vivamus vitae pretium sapien Code Blocks Code block with backticks 1 2 3 4 5 6 7 8 9 10 \u0026lt;!doctype html\u0026gt; \u0026lt;html lang=\u0026#34;en\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;utf-8\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Example HTML5 Document\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p\u0026gt;Test\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; Code block indented with four spaces \u0026lt;!doctype html\u0026gt; \u0026lt;html lang=\u0026quot;en\u0026quot;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;utf-8\u0026quot;\u0026gt; \u0026lt;title\u0026gt;Example HTML5 Document\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p\u0026gt;Test\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; Diff code block 1 2 3 4 5 [dependencies.bevy] git = \u0026#34;https://github.com/bevyengine/bevy\u0026#34; rev = \u0026#34;11f52b8c72fc3a568e8bb4a4cd1f3eb025ac2e13\u0026#34; - features = [\u0026#34;dynamic\u0026#34;] + features = [\u0026#34;jpeg\u0026#34;, \u0026#34;dynamic\u0026#34;] One line code block 1 \u0026lt;p\u0026gt;A paragraph\u0026lt;/p\u0026gt; List Types Ordered List First item Second item Third item Unordered List List item Another item And another item Nested list Fruit Apple Orange Banana Dairy Milk Cheese Other Elements — abbr, sub, sup, kbd, mark GIF is a bitmap image format.\nH2O\nXn + Yn = Zn\nPress CTRL + ALT + Delete to end the session.\nMost salamanders are nocturnal, and hunt for insects, worms, and other small creatures.\nThe above quote is excerpted from Rob Pike\u0026rsquo;s talk during Gopherfest, November 18, 2015.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2023-09-07T00:00:00Z","permalink":"/p/markdown-syntax-guide/","title":"Markdown Syntax Guide"},{"content":"Hugo theme Stack supports the creation of interactive image galleries using Markdown. It\u0026rsquo;s powered by PhotoSwipe and its syntax was inspired by Typlog.\nTo use this feature, the image must be in the same directory as the Markdown file, as it uses Hugo\u0026rsquo;s page bundle feature to read the dimensions of the image. External images are not supported.\nSyntax 1 ![Image 1](1.jpg) ![Image 2](2.jpg) Result Photo by mymind and Luke Chesser on Unsplash\n","date":"2023-08-26T00:00:00Z","image":"/p/image-gallery/2.jpg","permalink":"/p/image-gallery/","title":"Image gallery"},{"content":"For more details, check out the documentation.\nBilibili video Tencent video YouTube video Generic video file Your browser doesn't support HTML5 video. Here is a link to the video instead. GitLab Quote Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n― A famous person, The book they wrote Photo by Codioful on Unsplash\n","date":"2023-08-25T00:00:00Z","image":"/p/shortcodes/cover.jpg","permalink":"/p/shortcodes/","title":"Shortcodes"},{"content":"Stack has built-in support for math typesetting using KaTeX.\nIt\u0026rsquo;s not enabled by default side-wide, but you can enable it for individual posts by adding math: true to the front matter. Or you can enable it side-wide by adding math = true to the params.article section in config.toml.\nInline math This is an inline mathematical expression: $\\varphi = \\dfrac{1+\\sqrt5}{2}= 1.6180339887…$\n1 $\\varphi = \\dfrac{1+\\sqrt5}{2}= 1.6180339887…$ Block math $$ \\varphi = 1+\\frac{1} {1+\\frac{1} {1+\\frac{1} {1+\\cdots} } } $$ 1 2 3 $$ \\varphi = 1+\\frac{1} {1+\\frac{1} {1+\\frac{1} {1+\\cdots} } } $$ $$ f(x) = \\int_{-\\infty}^\\infty\\hat f(\\xi)\\,e^{2 \\pi i \\xi x}\\,d\\xi $$ 1 2 3 $$ f(x) = \\int_{-\\infty}^\\infty\\hat f(\\xi)\\,e^{2 \\pi i \\xi x}\\,d\\xi $$ ","date":"2023-08-24T00:00:00Z","permalink":"/p/math-typesetting/","title":"Math Typesetting"}]