Files
notes/resource/系统/构建 HUGO 博客的完整指南:从搭建到部署.md
T
2026-03-01 01:43:46 +08:00

569 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Hugo 简介
Hugo 是一个开源的静态站点生成器,支持通过 Markdown 文件快速生成静态 HTML 页面。其主要优势包括:
1. **构建速度快**:Hugo 采用 Go 语言开发,单线程构建速度极快,适合处理大规模内容。
2. **灵活性强**:支持多种主题和模块化设计,用户可根据需求定制站点外观与功能。
3. **易于部署**:生成的静态文件可直接托管于任意 Web 服务器,无需复杂的后端支持。
4. **学术友好性**Hugo 支持 Markdown 格式,与学术写作常用的编辑工具(如 Obsidian)无缝衔接,便于内容管理和版本控制。
# 官网导航
[官方网站](https://gohugo.io/)
[官方论坛](https://discourse.gohugo.io/)
[中文官网](https://hugo.opendocs.io/)
# 流程概览
Obsidian (创作) → Hugo (本地预览) → Git (推送至 GitHub) → GitHub Actions (自动编译部署) → Nginx (静态服务) → Cloudflare (全球加速与HTTPS) → 全球用户访问
# 站点初始化
```shell
# 创建一个新的 Hugo 站点,命名为 404-blog
hugo new site 404-blog
# 进入新创建的站点目录
cd 404-blog
# 初始化 Git 仓库,用于版本管理和代码托管
git init
# 设置 Git 的提交用户信息
git config user.name "404"
git config user.email "404@example.com"
# 暂存所有文件,准备提交
git add .
# 提交更改,记录初始化的站点结构
git commit -m "Initial commit"
# 添加 Hugo PaperMod 主题作为 Git 子模块,方便管理和更新主题
git submodule add https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod
# 在配置文件 hugo.toml 中设置主题为 PaperMod
echo "theme = 'PaperMod'" >> hugo.toml
# (此处手动添加 .gitignore 文件,用于忽略不必要的文件,如 node_modules 或临时文件)
# 暂存所有更改,包括主题和 .gitignore 文件
git add .
# 提交更改,记录主题和忽略文件的添加
git commit -m "Add PaperMod theme and .gitignore file"
# 启动 Hugo 本地服务器,预览站点效果
hugo server
```
其中 `.gitignore` 可参考 `PaperMod` 作者提供的文件。
```
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
/public
.DS_Store
.hugo_build.lock
resources/_gen/
```
# 自动化部署与服务器配置
要实现每次推送到 GitHub 仓库时,自动构建 Hugo 博客并部署到云服务器的 Nginx 下,需要完成以下步骤:
## 1. 准备云服务器
首先,确保您的云服务器已安装 Nginx:
```bash
# Ubuntu/Debian
sudo apt update && sudo apt full-upgrade -y
sudo apt install nginx
```
## 2. 配置 Nginx
修改 Nginx 主配置文件:
```nginx
# --- 基本运行环境配置 ---
user www-data; # 指定 Nginx 运行的用户,确保权限安全
worker_processes auto; # 自动匹配 CPU 核心数,优化并发处理
worker_cpu_affinity auto; # 自动绑定 CPU 核心,提升缓存命中率
pid /run/nginx.pid; # 定义主进程 ID 文件路径
error_log /var/log/nginx/error.log warn; # 设置错误日志路径及警告级别
include /etc/nginx/modules-enabled/*.conf; # 引入额外的模块配置文件
# --- 事件处理模块配置 ---
events {
worker_connections 1024; # 每个进程最大连接数,适配中型流量
multi_accept on; # 启用多连接同时接受,提升并发效率
use epoll; # 使用 epoll 事件模型,优化高并发性能
}
# --- HTTP 服务核心配置 ---
http {
# 性能优化参数
sendfile on; # 启用高效文件传输,适合静态文件
tcp_nopush on; # 优化数据包传输,减少报文数量
tcp_nodelay on; # 禁用 Nagle 算法,降低传输延迟
types_hash_max_size 2048; # 优化 MIME 类型哈希表大小
server_tokens off; # 隐藏 Nginx 版本信息,增强安全性
server_names_hash_bucket_size 64; # 域名哈希桶大小,支持长域名解析
# MIME 类型定义
include /etc/nginx/mime.types; # 引入标准 MIME 类型配置
default_type application/octet-stream; # 默认文件类型为二进制流
# SSL/TLS 安全优化
ssl_protocols TLSv1.2 TLSv1.3; # 启用安全协议,优先 TLS 1.3
ssl_prefer_server_ciphers on; # 优先使用服务器指定的加密套件
ssl_ciphers EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH; # 高安全加密套件
ssl_session_timeout 1d; # SSL 会话缓存有效期,减少握手开销
ssl_session_cache shared:SSL:10m; # 共享 SSL 会话缓存,适配中型流量
ssl_session_tickets off; # 禁用会话票据,增强安全性
ssl_stapling on; # 启用 OCSP 装订,加速证书验证
ssl_stapling_verify on; # 验证 OCSP 响应,确保安全
resolver 8.8.8.8 8.8.4.4 valid=300s; # 指定 DNS 服务器,用于 OCSP 查询
resolver_timeout 5s; # 设置 DNS 解析超时时间
# 日志记录配置
log_format main # 定义标准日志格式,记录请求详情
'$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main buffer=32k flush=5s; # 访问日志,优化写入性能
# Gzip 压缩优化
gzip on; # 启用内容压缩,减少传输数据量
gzip_vary on; # 添加 Vary 头,适配代理缓存
gzip_proxied any; # 对所有代理请求启用压缩
gzip_comp_level 6; # 压缩级别 6,平衡性能与效果
gzip_buffers 16 8k; # 设置压缩缓冲区,优化内存使用
gzip_http_version 1.1; # 最低支持 HTTP 1.1 进行压缩
gzip_min_length 256; # 最小压缩文件长度,避免小文件开销
gzip_types text/plain # 定义支持压缩的文件类型
text/css
application/json
application/javascript
text/xml
application/xml
application/xml+rss
text/javascript
application/x-font-ttf
font/opentype
image/svg+xml;
# 客户端请求限制
client_max_body_size 10m; # 限制请求体大小,适配静态站点
client_body_buffer_size 128k; # 请求体缓冲区,优化上传性能
# 文件缓存优化
open_file_cache max=2000 inactive=20s; # 缓存文件句柄,加速静态文件访问
open_file_cache_valid 30s; # 文件缓存有效性检查周期
open_file_cache_min_uses 2; # 文件至少访问 2 次才缓存
open_file_cache_errors on; # 缓存错误信息,减少重复检查
# Cloudflare 真实 IP 获取
set_real_ip_from 173.245.48.0/20; # Cloudflare IP 范围,用于获取真实 IP
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;
real_ip_header CF-Connecting-IP; # 使用 Cloudflare 传递的真实客户端 IP
# 引入其他配置文件
include /etc/nginx/conf.d/*.conf; # 加载额外的配置目录
include /etc/nginx/sites-enabled/*; # 加载启用的站点配置文件
}
```
创建一个站点配置文件:
```bash
sudo vim /etc/nginx/sites-available/404-blog
```
添加以下配置(根据需要调整):
```nginx
# --- 非 www 域名 HTTP 重定向配置 ---
server {
listen 80; # 监听 HTTP 80 端口
server_name 404blog.org; # 匹配非 www 域名
access_log /var/log/nginx/404blog_redirect_access.log
main
buffer=16k; # 访问日志记录,设置缓冲
error_log /var/log/nginx/404blog_redirect_error.log warn; # 错误日志记录,警告级别
return 301 https://www.404blog.org$request_uri; # 永久重定向到 HTTPS 的 www 域名
}
# --- 非 www 域名 HTTPS 重定向配置 ---
server {
listen 443 ssl; # 监听 HTTPS 443 端口并启用 SSL
http2 on; # 启用 HTTP/2 协议,提升性能
server_name 404blog.org; # 匹配非 www 域名
include /etc/nginx/snippets/ssl-404blog.conf; # 引入预配置的 SSL 设置
access_log /var/log/nginx/404blog_ssl_redirect_access.log
main
buffer=16k; # 访问日志记录
error_log /var/log/nginx/404blog_ssl_redirect_error.log warn; # 错误日志记录
return 301 https://www.404blog.org$request_uri; # 永久重定向到 HTTPS 的 www 域名
}
# --- 主域名 www 的 HTTP 重定向配置 ---
server {
listen 80; # 监听 HTTP 80 端口
server_name www.404blog.org; # 匹配 www 域名
access_log /var/log/nginx/404blog_www_http_access.log main buffer=16k; # 访问日志记录
error_log /var/log/nginx/404blog_www_http_error.log warn; # 错误日志记录
return 301 https://$host$request_uri; # 重定向到 HTTPS,保留主机名和路径
}
# --- 主域名 www 的 HTTPS 服务配置 ---
server {
listen 443 ssl; # 监听 HTTPS 443 端口并启用 SSL
http2 on; # 启用 HTTP/2 协议,加速传输
server_name www.404blog.org; # 匹配 www 域名
include /etc/nginx/snippets/ssl-404blog.conf; # 引入 SSL 相关配置片段
access_log /var/log/nginx/404blog_www_https_access.log
main
buffer=32k
flush=5s; # 访问日志,设置更大缓冲和刷新时间
error_log /var/log/nginx/404blog_www_https_error.log warn; # 错误日志记录
# 安全相关的 HTTP 响应头配置
add_header X-Content-Type-Options "nosniff" always; # 防止浏览器嗅探 MIME 类型
add_header X-Frame-Options "DENY" always; # 禁止页面被嵌入到 iframe 中
add_header X-XSS-Protection "1; mode=block" always; # 启用 XSS 防护机制
add_header Referrer-Policy
"strict-origin-when-cross-origin"
always; # 限制跨域时的 Referrer 信息
add_header Content-Security-Policy
"default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; img-src 'self' data: https:; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; font-src 'self' https://cdn.jsdelivr.net; connect-src 'self'; frame-src 'none'; object-src 'none'; base-uri 'self'; form-action 'self';"
always; # CSP 策略,增强安全性
add_header Strict-Transport-Security
"max-age=63072000; includeSubDomains; preload"
always; # HSTS 强制 HTTPS 连接
add_header Permissions-Policy
"geolocation=(), microphone=(), camera=()"
always; # 限制浏览器敏感权限
root /var/www/404-blog; # 设置网站根目录,指向静态文件
index index.html; # 默认首页文件
if ($request_method !~ ^(GET|HEAD)$) { # 限制请求方法,仅允许 GET 和 HEAD
return 405; # 其他方法一律返回 405 错误
}
# 基本路由配置
location / {
try_files $uri $uri/ =404; # 尝试匹配文件或目录,无匹配则返回 404
}
# 特殊文件(如 robots.txt 和 favicon.ico)的处理
location ~ ^/(robots\.txt|favicon\.ico)$ {
access_log off; # 关闭访问日志,减少磁盘 I/O
log_not_found off; # 关闭未找到文件的日志记录
expires 30d; # 设置 30 天缓存过期时间
add_header Cache-Control "public, immutable"; # 设置公开且不可变缓存
}
# RSS 和 Sitemap 文件缓存策略
location ~* \.(xml|json)$ {
expires 12h; # 缓存时间设为 12 小时
add_header Cache-Control "public, must-revalidate"; # 公开缓存但需验证
}
# 图片等静态资源的缓存策略
location ~* \.(jpg|jpeg|png|gif|ico|svg|webp)$ {
expires 30d; # 图片资源缓存 30 天
add_header Cache-Control "public, immutable"; # 不可变缓存,适合 CDN
access_log off; # 关闭访问日志
}
# CSS 和 JS 文件缓存策略
location ~* \.(css|js)$ {
expires 7d; # 缓存时间为 7 天
add_header Cache-Control "public, immutable"; # 不可变缓存
access_log off; # 关闭访问日志
}
# 字体文件缓存策略
location ~* \.(woff|woff2|ttf|eot|otf)$ {
expires 90d; # 字体资源缓存 90 天
add_header Cache-Control "public, immutable"; # 不可变缓存
access_log off; # 关闭访问日志
}
# HTML 文件缓存策略
location ~* \.html$ {
expires 1h; # HTML 文件缓存 1 小时
add_header Cache-Control "public, must-revalidate"; # 公开缓存但需验证
}
# 禁止访问隐藏文件和敏感目录
location ~ /\. {
deny all; # 拒绝访问以 . 开头的文件或目录
access_log off; # 关闭访问日志
log_not_found off; # 关闭未找到日志
}
# 自定义错误页面配置
error_page 404 /404.html; # 404 错误页面指向自定义文件
error_page 500 502 503 504 /50x.html; # 5xx 错误页面指向自定义文件
location = /404.html {
root /var/www/404-blog; # 404 页面文件所在根目录
internal; # 仅限内部访问
}
location = /50x.html {
root /var/www/404-blog; # 5xx 页面文件所在根目录
internal; # 仅限内部访问
}
}
```
启用站点并创建部署目录:
```bash
sudo ln -s /etc/nginx/sites-available/your-blog /etc/nginx/sites-enabled/
sudo mkdir -p /var/www/your-blog
sudo chown -R $USER:$USER /var/www/your-blog # 确保部署用户有权限
sudo nginx -t # 测试配置
sudo systemctl reload nginx
```
## 3. 准备 SSH 密钥对
为了让 GitHub Actions 能够安全地连接到您的服务器,创建一个专用的 SSH 密钥:
```bash
ssh-keygen -t rsa -b 4096 -C "github-actions-deploy" -f ~/.ssh/github-actions
```
在您的服务器上,将公钥添加到 authorized_keys
```bash
cat ~/.ssh/github-actions.pub >> ~/.ssh/authorized_keys
```
## 4. 配置 GitHub 仓库 Secrets
将私钥和其他必要信息添加到 GitHub 仓库的 Secrets 中:
1. 在 GitHub 仓库页面,点击 "Settings" → "Secrets and variables" → "Actions" → "New repository secret"
2. 添加以下 Secrets:
- `SSH_PRIVATE_KEY`: 您生成的私钥内容(整个文件内容)
- `SERVER_HOST`: 您服务器的 IP 地址或域名
- `SERVER_USERNAME`: SSH 登录用户名
- `SERVER_PORT`: SSH 端口(通常是 22
- `SERVER_DEPLOY_PATH`: 部署路径(如 /var/www/your-blog
## 5. 创建 GitHub Actions 工作流
在您的 Hugo 项目中创建 `.github/workflows/deploy.yml` 文件:
```yaml
name: Deploy Hugo site to Server
on:
push:
branches:
- master
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: true
fetch-depth: 0
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: "latest"
extended: true
- name: Build
run: hugo --minify
- name: Install SSH Key
uses: shimataro/ssh-key-action@v2
with:
key: ${{ secrets.SSH_PRIVATE_KEY }}
known_hosts: "just-a-placeholder"
- name: Adding Known Hosts
run: ssh-keyscan -H -p ${{ secrets.SERVER_PORT }} ${{ secrets.SERVER_HOST }} >> ~/.ssh/known_hosts
- name: Deploy with rsync
run: |
rsync -avz --delete -e "ssh -p ${{ secrets.SERVER_PORT }}" \
./public/ \
${{ secrets.SERVER_USERNAME }}@${{ secrets.SERVER_HOST }}:${{ secrets.SERVER_DEPLOY_PATH }}
```
上述工作流在每次推送到主分支时触发,执行代码检出、Hugo 构建及静态文件部署等步骤,最终通过 `rsync` 工具将 `public` 目录同步到服务器。
# 图片管理策略
因为我这边更期望在 Obsidian 中进行编辑,所以只研究了两种图片管理方式:
## 1. 本地存储方案
此方案来源于 Hugo 论坛的 jmooring 最佳解决方案:
**Obsidian 配置**
```json
{
"attachmentFolderPath": "attachments",
"useMarkdownLinks": true,
"newLinkFormat": "absolute"
}
```
对应了设置中的如下配置,我图片存储在了 images 下:
![](https://assets.404blog.org/assets/attachments/images/image-20250428211114872.png)
**Hugo 配置**
```toml
[markup.goldmark.renderHooks.image]
enableDefault = true
[markup.goldmark.renderHooks.link]
enableDefault = true
[[module.mounts]]
source = 'assets'
target = 'assets'
[[module.mounts]]
source = 'attachments'
target = 'assets/attachments'
```
**目录结构**
```
attachments/
├── documents/
│ ├── a.pdf
│ └── b.pdf
└── images/
├── kitten-a.jpg
└── kitten-b.jpg
```
## 2. 图床存储方案
此时可以使用 Obsidian 的插件 Image Upload Toolkit。特别感谢作者,当我想使用此方案时,暂时还不支持 R2 上传,作者了解后,很快进行了适配。
我的配置如下:
![](https://assets.404blog.org/assets/attachments/images/image-20250603213730484.png)
上图重 `Attachment location` 需为 `/`,否则不适配插件会有找不到图片的异常信息。
![](https://assets.404blog.org/assets/attachments/images/image-20250603213835016.png)
上图所需要的配置在 R2 配置界面均可以找到:
![](https://assets.404blog.org/assets/attachments/images/image-20250428212524005.png)
存储桶设置中,推荐自定义域,当然前提时你需要在 Cloud flare 进行域名托管。才可以使用子域。
![](https://assets.404blog.org/assets/attachments/images/image-20250428212652549.png)
# 缓存加速
本设计主要聚焦于 Ng 和 Cloudflare 的配置,操作简单,基本通过点击即可完成。重点内容通过截图展示,一目了然。如果您想深入学习更专业的使用方法,建议参考相关教程。
## 1. Ng 加速配置
使用上述 Ng 配置已经处理好了大多数缓存配置和安全策略。
## 2. Cloud flare 缓存
将域名托管到 Cf 后,可以新增 Cache Rules。
![](https://assets.404blog.org/assets/attachments/images/image-20250428213040868.png)
具体规则如下:
![](https://assets.404blog.org/assets/attachments/images/image-20250428213148844.png)
![](https://assets.404blog.org/assets/attachments/images/image-20250428213205719.png)
## 3. SSL/TLS 加密
![](https://assets.404blog.org/assets/attachments/images/image-20250428213426039.png)
![](https://assets.404blog.org/assets/attachments/images/image-20250428213528347.png)
![](https://assets.404blog.org/assets/attachments/images/image-20250428213552615.png)
CloudFlare 为用户提供的源服务器证书是由 Cloudflare 签名的免费 TLS 证书,该域名证书属于泛域名证书,最长支持 15 年,主要用于源服务器和 Cloudflare 之间的流量加密。但是这个证书属于自签名证书,证书链不完整,缺少根证书。
使用如下网址下载 CloudFlare 的根证书/证书链文件,并上传到您的源 Web 服务器。请注意, CloudFlare 提供了 ECC 和 RSA 版本两个文件,具体下载哪一个参考上图,根据自己申请源服务器证书时选择的“私钥类型”来决定。
[Cloud flare 根证书下载](https://developers.cloudflare.com/ssl/origin-configuration/origin-ca/#cloudflare-origin-ca-root-certificate)
[RSA 下载](https://developers.cloudflare.com/ssl/static/origin_ca_rsa_root.pem)
根证书下载上传后 Ng 需要对应的配置,我是放到了 snippets 配置片段下,进行统一的引用。