使用 Hugo 框架
Hugo 是最流行的开源静态站点生成器之一。凭借其惊人的速度和灵活性,Hugo 让搭建网站再次变得有趣。
- Hugo 官网: https://gohugo.io
- Hugo 中文网: https://gohugo.cn
- Hugo 文档: https://gohugo.io/installation
- Hugo 命令手册: https://gohugo.io/commands/hugo
- Hugo Docker 第三方开源镜像: https://docker.hugomods.com/zh-hans/docs/tags/#std-base
安装 Hugo
配置 Hugo 主题
- Hugo 主题列表: https://themes.gohugo.io
- 配置方法:
- 下载 主题源代码 放到 站点 的 ./themes 文件夹下
- 然后修改 根目录 下的 站点配置 hugo.toml
- 启动站点
- 注意:
- 如果没有配置主题:
- 新建的站点会找不到页面文件,导致报错。
- blog 容器无法成功启动,nginx 容器会报 服务器内部错误。
- 如果没有配置主题:
使用 hugo-theme-cleanwhite 主题
- hugo-theme-cleanwhite 主题地址:
- 演示站点: https://www.zhaohuabing.com
- 演示站点代码地址: https://github.com/zhaohuabing/hugo-theme-cleanwhite/tree/master/exampleSite
- 演示站点的站点配置 hugo.toml: https://github.com/zhaohuabing/hugo-theme-cleanwhite/blob/master/exampleSite/hugo.toml
- 演示站点的静态资源:
- content 文件夹(存放 文章 和 页面 文件): https://github.com/zhaohuabing/hugo-theme-cleanwhite/tree/master/exampleSite/content
- static 文件夹(存放 静态资源): https://github.com/zhaohuabing/hugo-theme-cleanwhite/tree/master/exampleSite/static/img
- 演示站点代码地址: https://github.com/zhaohuabing/hugo-theme-cleanwhite/tree/master/exampleSite
站点配置 (参考主题演示站点)
- 站点配置 是参考 主题演示站点的站点配置 进行修改或复制。
- 注意:
- 根目录下的 content 文件夹是用来存放 文章 或 页面 文件:可参考 主题演示站点 的 写法
- 根目录下的 static 文件夹是用来存放 静态资源
主题默认版本
- 默认版本存在错误:
- 120行:add 后面的 空格 需要删除,否则启动会报错: unmarshal failed: toml: expected character ]
- 130行:add 后面的 空格 需要删除,否则启动会报错: unmarshal failed: toml: expected character ]
hugo.toml
1baseurl = "https://zhaohuabing.com"
2title = "Huabing Blog"
3theme = "hugo-theme-cleanwhite"
4languageCode = "en-us"
5preserveTaxonomyNames = true
6hasCJKLanguage = true
7
8[services]
9 # Enable comments by entering your Disqus shortname
10 [services.disqus]
11 shortname = ""
12 [services.googleAnalytics]
13 id = ""
14
15[pagination]
16 pagerSize = 5 # frontpage pagination
17
18[outputs]
19home = ["HTML", "RSS", "Algolia"]
20
21[params]
22 header_image = "img/home-bg-jeep.jpg"
23 SEOTitle = "赵化冰的博客 | Zhaohuabing Blog"
24 description = "赵化冰,程序员, 开源爱好者,生活探险家 | 这里是 赵化冰 的博客,与你一起发现更大的世界。"
25 keyword = "赵化冰, zhaohuabing, Zhaohuabing, , 赵化冰的网络日志, 赵化冰的博客, Zhaohuabing Blog, 博客, 个人网站, 互联网, Web, 云原生, PaaS, Istio, Kubernetes, 微服务, Microservice"
26 slogan = "路在脚下,心向远方"
27 upstreamAttribution = true
28
29 image_404 = "img/404-bg.jpg"
30 title_404 = "你来到了没有知识的荒原 :("
31 omit_categories = false
32
33 # leancloud storage for page view counter
34 page_view_counter = false
35 leancloud_app_id = ""
36 leancloud_app_key = ""
37
38 # algolia site search
39 algolia_search = true
40 algolia_appId = ""
41 algolia_indexName = ""
42 algolia_apiKey = ""
43
44 # Sidebar settings
45 sidebar_about_description = "Software Developer, Open Source Enthusiast and Life Adventurer"
46 #sidebar_avatar = "img/avatar-zhaohuabing.jpg" # use absolute URL, seeing it's used in both `/` and `/about/`
47 sidebar_avatar = "img/zhaohuabing.png" # use absolute URL, seeing it's used in both `/` and `/about/`
48
49 featured_tags = true
50 featured_condition_size = 1
51
52 # Baidu Analytics
53 ba_track_id = ""
54
55 # We need a proxy to access Disqus api in China
56 # Follow https://github.com/zhaohuabing/disqus-php-api to set up your own disqus proxy
57 disqus_proxy = ""
58 disqus_site = ""
59
60 # Twikoo comments
61 # Follow https://twikoo.js.org/ to set up your own env_id
62 twikoo_env_id = ""
63
64 # Artalk comments
65 # Follow https://artalk.js.org/ to set up your own server
66 artalk_enable = true
67 artalk_server = "https://xxx.xxx.com"
68 artalk_site = "xxx blog"
69
70 #Enable wechat pay & alipay to allow readers send reward money if they like the articles
71 reward = true
72 # reward_guide = "如果这篇文章对你有用,请随意打赏"
73
74 friends = true
75 bookmarks = false
76 about_me = true
77
78 # Include any custom CSS and/or JS files, url or relative to /static folder
79 #custom_css = ["css/lightbox.css", "https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.2/animate.min.css", "css/main.css"]
80 #custom_js = ["js/lightbox.js", "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js", "js/main.js"]
81
82 [params.social]
83 rss = true
84 email = "youremail@gmail.com"
85 #facebook = "full profile url in facebook"
86 #googleplus = "full profile url in googleplus"
87 #twitter = "full profile url in twitter"
88 linkedin = "https://www.linkedin.com/in/yourlinkedinid"
89 stackoverflow = "https://stackoverflow.com/users/yourstackoverflowid"
90 #instagram = "full profile url in instagram"
91 github = "https://github.com/yourgithub"
92 wechat = "your wechat qr code image"
93 #medium = "full profile url in medium"
94 #slack = "full workspace url in slack"
95 #pinterest = "full profile url in pinterest"
96 #reddit = "full profile url in reddit"
97 #gitlab = "full profile url in gitlab"
98 #mastodon = "full profile url in mastodon"
99 #keybase = "full profile url in keybase"
100 #xing = "https://www.xing.com/profile/yourxingid"
101 #git = "full profile url for git user"
102
103 [[params.friend_link]]
104 title = "Linda的博客"
105 href = "https://zhaozhihan.com"
106
107 [[params.bookmark_link]]
108 title = "Martin Fowler"
109 href = "https://martinfowler.com"
110 [[params.bookmark_link]]
111 title = "ServiceMesher"
112 href = "http://www.servicemesher.com"
113 [[params.bookmark_link]]
114 title = "Pxhere"
115 href = "https://pxhere.com"
116 [[params.bookmark_link]]
117 title = "unsplash"
118 href = "https://unsplash.com"
119
120 [[params.add itional_menus]]
121 title = "ARCHIVE"
122 href = "/archive/"
123 [[params.add itional_menus]]
124 title = "NOTES"
125 href = "/notes/"
126 [[params.additional_menus]]
127 title = "ABOUT"
128 href = "/about/"
129
130[outputFormats.Algolia]
131baseName = "algolia"
132isPlainText = true
133mediaType = "application/json"
134notAlternative = true
135
136[params.algolia]
137vars = ["title", "summary", "date", "publishdate", "expirydate", "permalink"]
138params = ["categories", "tags"]
139
140[markup]
141 [markup.tableOfContents]
142 endLevel = 2
143 startLevel = 1
144 [markup.highlight]
145 style = "dracula"
146 [markup.goldmark]
147 [markup.goldmark.renderer]
148 unsafe = true
初始化版本备份
hugo.toml
1baseURL = 'https://example.org/'
2languageCode = 'en-us'
3title = 'My New Hugo Site'
主题bug修复
- .\themes\hugo-theme-cleanwhite\layouts\partials\sidebar.html:
- 289 行: /tags/ 前面的 空格 要删除,否则生成的 tag 链接会出错,无法正确跳转。
- .\themes\hugo-theme-cleanwhite\layouts\partials\footer.html:
- fastClick.js cdn 大陆无法访问,它的作用是 消除移动设备上的300毫秒点击延迟,这里我们直接 下载,本地导入
1<!--fastClick.js -->
2<script>
3 // loadAsync("https://cdn.jsdelivr.net/npm/fastclick@1.0.6/lib/fastclick.min.js", function () {
4 // var $nav = document.querySelector("nav");
5 // if ($nav) FastClick.attach($nav);
6 // })
7 loadAsync('{{ "js/fastclick.min.js" | relURL }}', function () {
8 var $nav = document.querySelector("nav");
9 if ($nav) FastClick.attach($nav);
10 })
11</script>
Hugo 的 Front matter 模板
文章 Front matter
1---
2layout: single
3title: "Visual Studio Code 使用手册"
4description: "VS Code 下载安装,常用插件 等。"
5author: "谌中钱"
6date: "2025-01-14"
7image: "/img/temple_404_bg.jpg" # banner 背景图
8categories: [ "tech" ]
9tags:
10 - "tools"
11weight: 1 # 置顶,数值越小,权重越大
12---
页面 Front matter
1---
2layout: page
3title: "关于我"
4description: "Hi,我是谌中钱,本科主修计算机科学,全栈高级开发工程师,曾在 阿里云,腾讯,Coupang 从事开发工作。常用技术栈以 React,Node 等技术为主。"
5author: "谌中钱"
6date: "2025-01-14"
7---
Window 11 下
包管理器 方式安装
- Win + R 打开 运行 窗口,输入 cmd 打开 命令终端
- 操作命令: 参考 《Git 使用手册》
1# 查看本地是否安装 Hugo
2winget list | findstr Hugo
3# 更新包索引,确保包都是最新的
4winget update
5# 查询一下 Hugo 包列表
6winget search Hugo
7# 名称 ID 版本 匹配 源
8# --------------------------------------------------------------------------
9# HuGO - Hygiene und GO 9NCDTRC8TX5J Unknown msstore
10# Remember Passwords 9NXJM8NGHDG6 Unknown msstore
11# Hugo Hugo.Hugo 0.146.7 winget
12# Hugo (Deploy) Hugo.Hugo.Deploy 0.145.0 Command: hugo winget
13# Hugo (Extended) Hugo.Hugo.Extended 0.146.7 winget
14# 希沃集控 Seewo.SeewoHugo 1.4.5.57 winget
15# 掌上看班 Seewo.SeewoHugoKanban 1.4.5.68 winget
16
17# 安装 Hugo 标准版
18winget install Hugo.Hugo
19# 更新 Hugo 版本,有时候版本会有 bug,需要更新
20# winget upgrade --name Hugo --version 0.146.7
21# 卸载 Hugo
22# winget uninstall Hugo
23
24# 查看 Hugo 版本
25hugo version
26
27# 创建新站点,指定路径,命名为 blog
28hugo new site D:\workspace\blog && cd /d D:\workspace\blog
29
30# 下载主题 hugo-theme-cleanwhite
31# 记得删除 ./themes/hugo-theme-cleanwhite 下的 .git 文件夹,不然后续提交会报错
32git clone https://github.com/zhaohuabing/hugo-theme-cleanwhite.git ./themes/hugo-theme-cleanwhite
33
34# 复制 主题演示站点 的 站点配置文件 hugo.toml,content 文件夹,static 文件夹 到 新站点 下
35# 注意修改 hugo.toml 的错误,上面有描述
36copy /Y ".\themes\hugo-theme-cleanwhite\exampleSite\hugo.toml" "."
37xcopy ".\themes\hugo-theme-cleanwhite\exampleSite\content" ".\content" /E /Y
38xcopy ".\themes\hugo-theme-cleanwhite\exampleSite\static" ".\static" /E /Y
39
40# 启动测试服务器,访问站点 http://localhost:1313
41# --bind 0.0.0.0 是为了监听 IPv4 所有网络地址,让 手机 等设备可以通过 局域网 (同一 WiFi 下) 访问 电脑 的站点,进行移动端测试
42hugo server --bind 0.0.0.0
CentOS 9 下
图片批量动态压缩
- 采用 ImageMagick、bc、parallel 包 批量动态压缩图片资源,提高站点访问速度,会在 Docker 方式安装 时用到,操作命令如下:
1# 手动压缩图片资源(会覆盖源文件,注意保留源文件)
2# 压缩规则:
3# 1. 超过 500KB 的图片才会压缩
4# 2. 根据 图片大小 动态控制压缩比例,最后都控制在 300KB 左右
5# 3. 可压缩 PNG,JPG,JPEG,WEBP 的图片
6
7# 安装工具包
8dnf install -y ImageMagick-7.1.1.26-2.oc9 bc parallel
9# 配置ImageMagick策略文件
10vim /etc/ImageMagick-7/policy.xml
11
12# 按 i 键进入 插入模式
13# 复制下面的 配置内容 到里面:
14
15# <!-- 允许读写图片格式 -->
16# <policy domain="coder" rights="read|write" pattern="PNG,JPG,JPEG,WEBP" />
17# <!-- 提升资源限制 -->
18# <policy domain="resource" name="memory" value="1GiB"/>
19# <policy domain="resource" name="disk" value="4GiB"/>
20# <policy domain="resource" name="width" value="32KP"/>
21# <policy domain="resource" name="height" value="32KP"/>
22
23# 按 ESC 键退出 插入模式,输入 :wq 并按 Enter 来保存(write)并退出(quit)
24# 查看当前生效策略
25convert -list policy
26
27# 将脚本合成为一条命令
28find ./static/img/ \( -name "*.png" -o -name "*.jpg" -o -name "*.jpeg" -o -name "*.webp" \) -type f -print0 | parallel -0 -j 4 --bar 'f="{}";s=$(stat -c%s "$f");if [ $s -gt 512000 ];then q=$(echo "scale=0;60-30*l($s/512000)/l(10)" | bc -l | awk "{print int(\$1+0.5)}");q=$((q<10?10:q>75?75:q));case "${f##*.}" in png) p="-quality $((q-25)) -define png:compression-level=9 -colors 128" ;; jpg|jpeg) p="-quality $((q-10)) -sampling-factor 4:2:0" ;; webp) p="-quality $((q-20)) -define webp:method=6" ;; esac;mogrify $p "$f";fi'
- 下面是命令展开的脚本,用于分析:
1find ./static/img/ \( -name "*.png" -o ... \) -type f -print0 |
2parallel -0 -j4 --bar '
3f="{}";
4s=$(stat -c%s "$f");
5if [ $s -gt 512000 ];then
6 # 质量计算
7 q=$(echo "scale=0;60-30*l($s/512000)/l(10)" | bc -l | awk "{print int(\$1+0.5)}");
8 q=$((q<10?10:q>75?75:q));
9
10 # 格式处理分支
11 case "${f##*.}" in
12 png) p="-quality $((q-25)) -define png:compression-level=9 -colors 128";;
13 jpg|jpeg) p="-quality $((q-10)) -sampling-factor 4:2:0";;
14 webp) p="-quality $((q-20)) -define webp:method=6";;
15 esac;
16
17 # 执行压缩
18 mogrify $p "$f";
19fi'
Docker 方式安装
- 安装 Docker
- 进入云服务终端
- 安装 blog 容器:
1# 创建文件夹
2mkdir /usr/local/src/blog && cd /usr/local/src/blog
3# 新建 Dockerfile 文件
4vim Dockerfile
5
6# 按 i 键进入 插入模式
7# 复制下面的 Dockfile 文件 内容到里面
8# 按 ESC 键退出 插入模式,输入 :wq 并按 Enter 来保存(write)并退出(quit)
9
10# 构建镜像
11docker build -t blog .
12
13# 把需要 外部挂载 的文件,放到指定的 目录里:
14# ./themes : 存放 主题源代码
15# ./hugo.toml : 存放 主题站点配置
16# ./content : 存放 文章 和 页面 的 md 文件
17# ./static : 存放 静态资源
18# ./public : 存放 站点构建结果
19
20# 下载主题 hugo-theme-cleanwhite
21# 记得删除 ./themes/hugo-theme-cleanwhite 下的 .git 文件夹,不然后续提交会报错
22git clone https://github.com/zhaohuabing/hugo-theme-cleanwhite.git ./themes/hugo-theme-cleanwhite
23# 复制 主题演示站点 的 站点配置文件 hugo.toml,content 文件夹,static 文件夹 到 新站点 下
24# 注意用 vim 修改 hugo.toml 的错误,上面有描述
25cp -f ./themes/hugo-theme-cleanwhite/exampleSite/hugo.toml .
26cp -rf ./themes/hugo-theme-cleanwhite/exampleSite/content/* ./content/
27cp -rf ./themes/hugo-theme-cleanwhite/exampleSite/static/* ./static/
28
29
30# 手动压缩图片资源(会覆盖源文件,注意保留源文件)
31# 压缩规则:
32# 1. 超过 500KB 的图片才会压缩
33# 2. 根据 图片大小 动态控制压缩比例,最后都控制在 300KB 左右
34# 3. 可压缩 PNG,JPG,JPEG,WEBP 的图片
35
36# 安装工具包
37dnf install -y ImageMagick-7.1.1.26-2.oc9 bc parallel
38# 配置ImageMagick策略文件
39sed -i '/<policy domain="coder" rights="read|write"/!b;n;c\ \ <policy domain="coder" rights="read|write" pattern="PNG,JPG,JPEG,WEBP" />' /etc/ImageMagick-7/policy.xml
40sed -i '/<policy domain="resource" name="memory"/s/value=".*"/value="1GiB"/' /etc/ImageMagick-7/policy.xml
41sed -i '/<policy domain="resource" name="disk"/s/value=".*"/value="4GiB"/' /etc/ImageMagick-7/policy.xml
42sed -i '/<policy domain="resource" name="width"/s/value=".*"/value="32KP"/' /etc/ImageMagick-7/policy.xml
43sed -i '/<policy domain="resource" name="height"/s/value=".*"/value="32KP"/' /etc/ImageMagick-7/policy.xml
44# 查看当前生效策略
45convert -list policy
46
47# 将脚本合成为一条命令
48find ./static/img/ \( -name "*.png" -o -name "*.jpg" -o -name "*.jpeg" -o -name "*.webp" \) -type f -print0 | parallel -0 -j 4 --bar 'f="{}";s=$(stat -c%s "$f");if [ $s -gt 512000 ];then q=$(echo "scale=0;60-30*l($s/512000)/l(10)" | bc -l | awk "{print int(\$1+0.5)}");q=$((q<10?10:q>75?75:q));case "${f##*.}" in png) p="-quality $((q-25)) -define png:compression-level=9 -colors 128" ;; jpg|jpeg) p="-quality $((q-10)) -sampling-factor 4:2:0" ;; webp) p="-quality $((q-20)) -define webp:method=6" ;; esac;mogrify $p "$f";fi'
49
50
51# 创建并运行容器
52docker run -d --restart=always -p 81:80 \
53-v ./themes:/blog/themes \
54-v ./hugo.toml:/blog/hugo.toml \
55-v ./content:/blog/content \
56-v ./static:/blog/static \
57-v ./public:/blog/public \
58--name blog blog
59
60# 等待 5 秒,确保容器已启动
61sleep 5
62# 修复 RSS
63sed -i 's#http://212.64.16.86:80#https://blog.climbtw.com#g' ./public/index.xml
64sed -i 's#http://212.64.16.86:80#https://blog.climbtw.com#g' ./public/sitemap.xml
65# 修复站点的动态链接
66sed -i 's#http://212\.64\.16\.86:80#https://blog.climbtw.com#g' \
67./public/index.html \
68./public/categories/solutions/index.html \
69./public/categories/tech/index.html
70
71# 推送索引到 Algolia,下面的 使用 Algolia 会详细介绍(atomic-algolia 是基于 Node.js 的工具,需先安装 Node.js)
72# npm install atomic-algolia
73# npx atomic-algolia
- Dockfile 文件:
1# 注意更新 Hugo 版本,有时候版本会有 bug,需要更新
2FROM hugomods/hugo:std-base-0.146.7
3RUN hugo new site blog
4WORKDIR /blog
5
6# 创建索引文件
7RUN hugo
8
9# 212.64.16.86 为 云服务器 的 公网IP
10# 这里不能使用域名,否则 Hugo 生成的 动态链接 会变成 https://域名:80,导致 端口 重复,文章跳转会出错
11# --disableLiveReload:在生产环境中部署网站,不希望浏览器频繁刷新
12CMD ["hugo", "server", "--disableLiveReload", "--baseURL", "http://212.64.16.86", "-p", "80"]
-
搭建 Nginx 服务器 容器
- 参考 《Nginx 使用手册》
-
修改 nginx.conf 配置:
- 二级域名的生成 参考 《云服务器购买和使用手册》
1# 修改 nginx.conf 配置,让 二级域名 blog.climbtw.com 反向代理到 blog 容器映射的 宿主机端口 81:
2# 即 复制下面的 nginx.conf 文件 到 nginx 容器 配置文件的 挂载目录 /usr/local/src/nginx/conf/nginx.conf 里
3
4# 重启 nginx 容器
5docker restart nginx
6
7# 可以通过 二级域名 blog.climbtw.com 访问站点
- nginx.conf 文件:
1worker_processes 1;
2
3events {
4 worker_connections 1024;
5}
6
7http {
8 include mime.types;
9 default_type application/octet-stream;
10
11 sendfile on;
12
13 keepalive_timeout 65;
14
15 proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:100m inactive=60m max_size=1g use_temp_path=off;
16
17 # 开启gzip
18 gzip on;
19 gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
20 gzip_min_length 1024; # 设置最小压缩数据大小,小于该值的数据将不进行压缩
21 gzip_comp_level 5; # 设置压缩级别,1 为最快压缩,9 为最高压缩率(建议设置为 4~6)
22
23 gzip_buffers 16 8k; # 设置用于存储压缩数据的缓冲区数量和大小
24 gzip_http_version 1.1; # 仅对支持 HTTP/1.1 或更高版本的客户端启用 Gzip 压缩
25 gzip_vary on; # 启用 Vary 响应头,告知缓存代理服务器对不同编码方式进行缓存分离处理
26 gzip_static on; # 启用对预生成 .gz 文件的直接传输支持,减少服务器实时压缩负担
27 gzip_disable "msie6"; # 禁用对特定客户端(如 IE6)的 Gzip 支持,避免兼容性问题
28 gzip_proxied any; # 指定在代理场景下是否启用压缩(如 any 表示对所有请求启用压缩)
29
30
31 upstream blog_server {
32 ip_hash;
33 server 172.17.0.1:81 max_fails=3 fail_timeout=30s;
34
35 keepalive 32; # 保持连接数,减少每次请求的连接开销
36
37 # max_fails 服务器失败的最大次数
38 # fail_timeout 每台服务器失败的超时时间
39 }
40
41 upstream twikoo_server {
42 ip_hash;
43 server 172.17.0.1:82 max_fails=3 fail_timeout=30s;
44
45 keepalive 32;
46 }
47
48 server {
49 listen 80;
50 server_name blog.climbtw.com climbtw.com www.climbtw.com;
51 # rewrite ^(.*)$ https://$server_name$1 permanent; # permanent,301 永久重定向,更新 url
52 return 301 https://$server_name$request_uri; # 重定向使用 return 效率更高
53 }
54
55 # 通过 ip 访问的话,优先匹配 显式标记为 default_server 的 server,如果没有则 使用第一个 server
56 # 这里设置下,通过 ip 访问的话,跳到博客容器
57 server {
58 listen 80 default_server;
59 server_name blog.climbtw.com;
60 # rewrite ^(.*)$ https://$server_name$1 permanent; # permanent,301 永久重定向,更新 url
61 return 301 https://$server_name$request_uri; # 重定向使用 return 效率更高
62 }
63
64 server {
65 listen 443 ssl;
66 server_name blog.climbtw.com;
67
68 ssl_certificate /etc/nginx/certs/blog.climbtw.com_bundle.pem;
69 ssl_certificate_key /etc/nginx/certs/blog.climbtw.com.key;
70
71 ssl_session_cache shared:SSL:1m;
72
73 ssl_session_timeout 5m;
74 # 请按照以下协议配置
75 ssl_protocols TLSv1.2 TLSv1.3;
76 # 请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。
77 ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
78 ssl_prefer_server_ciphers on;
79
80 # 系统临时维护
81 # rewrite ^(.*)$ /maintainace.html break; # break,地址栏 url 不变
82 # location = /maintainace.html {
83 # root /etc/nginx/html/nginx/html;
84 # }
85
86 # location / {
87 # root /etc/nginx/html/blog/public;
88 # # try_files $uri $uri/ /index.html; # 解决单页应用 history 路由 404 的问题
89 # index index.html index.htm;
90 # }
91
92 # 反向代理
93 location / {
94 proxy_pass http://blog_server;
95
96 proxy_cache my_cache;
97 proxy_set_header Host $host;
98 proxy_set_header X-Real-IP $remote_addr;
99 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
100 proxy_set_header X-Forwarded-Proto $scheme;
101 }
102
103 error_page 500 502 503 504 /50x.html;
104 location = /50x.html {
105 root /etc/nginx/html/nginx/html;
106 }
107
108 # error_page 404 /404.html;
109 # location = /404.html {
110 # root /etc/nginx/html/blog/public;
111 # }
112 }
113
114 server {
115 listen 443 ssl;
116 server_name twikoo.climbtw.com;
117
118 ssl_certificate /etc/nginx/certs/twikoo.climbtw.com_bundle.pem;
119 ssl_certificate_key /etc/nginx/certs/twikoo.climbtw.com.key;
120
121 ssl_session_cache shared:SSL:1m;
122
123 ssl_session_timeout 5m;
124 # 请按照以下协议配置
125 ssl_protocols TLSv1.2 TLSv1.3;
126 # 请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。
127 ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
128 ssl_prefer_server_ciphers on;
129
130 # 系统临时维护
131 # rewrite ^(.*)$ /maintainace.html break; # break,地址栏 url 不变
132 # location = /maintainace.html {
133 # root /etc/nginx/html/nginx/html;
134 # }
135
136 # 反向代理
137 location / {
138 proxy_pass http://twikoo_server;
139
140 proxy_cache my_cache;
141 proxy_set_header Host $host;
142 proxy_set_header X-Real-IP $remote_addr;
143 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
144 proxy_set_header X-Forwarded-Proto $scheme;
145 }
146
147 error_page 500 502 503 504 /50x.html;
148 location = /50x.html {
149 root /etc/nginx/html/nginx/html;
150 }
151 }
152
153 server {
154 listen 443 ssl;
155 server_name climbtw.com www.climbtw.com;
156
157 ssl_certificate /etc/nginx/certs/climbtw.com_bundle.pem;
158 ssl_certificate_key /etc/nginx/certs/climbtw.com.key;
159
160 ssl_session_cache shared:SSL:1m;
161
162 ssl_session_timeout 5m;
163 #请按照以下协议配置
164 ssl_protocols TLSv1.2 TLSv1.3;
165 #请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。
166 ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
167 ssl_prefer_server_ciphers on;
168
169 # 系统临时维护
170 # rewrite ^(.*)$ /maintainace.html break; # break,地址栏 url 不变
171 # location = /maintainace.html {
172 # root /etc/nginx/html/nginx/html;
173 # }
174
175 location / {
176 root /etc/nginx/html/climbtw;
177 index index.html index.htm;
178 }
179
180 error_page 500 502 503 504 /50x.html;
181 location = /50x.html {
182 root /etc/nginx/html/nginx/html;
183 }
184 }
185}
使用 Twikoo 评论系统
一个简洁、安全、免费的静态网站评论系统。
- Twikoo 官网:https://twikoo.js.org
- GitHub: https://github.com/twikoojs/twikoo/releases
- 前端引用 twikoo.js 版本需要与 云函数版本 保持一致
云函数部署
Docker 方式
- 进入云服务终端
- 操作命令:
1# 创建文件夹,用来存放 数据文件
2mkdir /usr/local/src/twikoo && cd /usr/local/src/twikoo
3
4# 拉取 twikoo 镜像
5docker pull imaegoo/twikoo:1.6.42
6
7# 创建并运行容器
8docker run -d --restart=always -p 82:8080 -e TWIKOO_THROTTLE=1000 -v ./data:/app/data --name twikoo imaegoo/twikoo:1.6.42
9
10# 修改 nginx.conf 配置,让 二级域名 twikoo.climbtw.com 反向代理到 twikoo 容器映射的 宿主机端口 82,重启 nginx 容器
在 hugo-theme-cleanwhite 主题中使用
修改站点配置启用
- 修改 站点配置文件 hugo.toml
1# Twikoo comments
2# Follow https://twikoo.js.org/ to set up your own env_id
3twikoo_env_id = "https://twikoo.climbtw.com/" # 云函数部署的地址
- 修改 主题 源码
- 修改 前端引用的 twikoo.js,与 云函数版本 保持一致,这里用的 1.6.42:
- 静态引入的话,可以去上面 GitHub 地址 里下载 对应版本(注意 twikoo.all.min.js 中存在很多敏感信息,无法提交到 git)
.\themes\hugo-theme-cleanwhite\layouts\partials\comments.html
1<script src="https://registry.npmmirror.com/twikoo/1.6.42/files/dist/twikoo.all.min.js"></script>
2
3<!-- <script src='{{ "js/twikoo.all.min.js" | relURL }}'></script> -->
- 主题 的 bootstrap.min.css 对 上传图像 样式有覆盖,修改一下:
.\themes\hugo-theme-cleanwhite\static\css\bootstrap.min.css
1<style>
2.twikoo .tk-input-image {
3 display: none;
4}
5</style>
6<div id="twikoo-tcomment"></div>
Twikoo 设置
在 站点文章 下方会有 Twikoo 评论组件,组件右下侧有 设置 按钮,第一次打开 设置 需要 配置密码。
使用自定义头像
使用 Weavatar 服务
Weavatar,多端多元化的统一头像服务。
- Weavatar 官网: https://weavatar.com/
- 配置方法:
- 去 Weavatar 官网 用 邮箱 注册 和 上传 自定义头像。
- 在 Twikoo 设置 -> 配置管理 -> 通用,按如下配置:
GRAVATAR_CDN: weavatar.com
- 主题 hugo-theme-cleanwhite 的样式对 头像 div 有覆盖,需要修改:
.\themes\hugo-theme-cleanwhite\layouts\partials\comments.html
1<style>
2.twikoo img {
3 margin: 0;
4}
5</style>
6<div id="twikoo-tcomment"></div>
使用 Server酱 (ServerChan) 评论推送
Server酱,英文名 ServerChan,是一款 手机 和 服务器、智能设备 之间的通信软件。
- Server酱 官网: https://sct.ftqq.com
- 配置方法:
- 去 Server酱 官网 注册,获取 SendKey,然后在 通道配置 里面设置 通知方式,我使用的 方糖的微信服务号。
- 在 Twikoo 设置 -> 配置管理 -> 即时通知,按如下配置:
PUSHOO_CHANNEL: serverchan
PUSHOO_TOKEN: 填写获取的 SendKey
SC_MAIL_NOTIFY: false
- 注意:
- ServerChan 目前由于用户量比较大,免费的通知额度只有 5 条。
- 自己发布的评论 (按邮箱判断),不会通知。
使用 Algolia 站点搜索
Algolia 是为你的 APP 或者网站添加搜索的最佳方式。
- Algolia 官网:https://www.algolia.com
在 hugo-theme-cleanwhite 主题中使用
开启 Algolia
- 修改 站点配置文件 hugo.toml
1# algolia site search
2algolia_search = true
3algolia_appId = "***"
4algolia_apiKey = "***"
5algolia_indexName = "hugo-blog"
6
7[outputs]
8home = ["HTML", "RSS", "Algolia"]
9
10[outputFormats.Algolia]
11baseName = "algolia"
12isPlainText = true
13mediaType = "application/json"
14notAlternative = true
15
16[params.algolia]
17vars = ["title", "summary", "date", "publishdate", "expirydate", "permalink"]
18params = ["categories", "tags"]
本地生成索引文件
- 在站点项目下执行构建命令:
hugo
,会在 public 目录下新建一个名为 algolia.json 的索引文件,它的地址即ALGOLIA_INDEX_FILE
。
推送搜索引到 Algolia
- 去 Algolia 官网 注册一个账号,获取
Application ID
、Write API Key
。 - 然后点击左侧的 Search 按钮,创建一个新的索引,自定义取名,比如 “hugo-blog”,即
ALGOLIA_INDEX_NAME
。 - 在站点根目录下创建一个
.env
文件,写入上面获取到的ALGOLIA_INDEX_FILE
、Application ID
、Write API Key
、ALGOLIA_INDEX_NAME
.env
:
1ALGOLIA_INDEX_FILE=/public/algolia.json
2ALGOLIA_APP_ID=***
3ALGOLIA_ADMIN_KEY=***
4ALGOLIA_INDEX_NAME=hugo-blog
- 安装推送工具包
atomic-algolia
,推送索引到 Algolia
atomic-algolia
是基于 Node.js 的工具,需先安装 Node.js- 参考 《Node 使用手册》
1npm install atomic-algolia
2npx atomic-algolia
访问索引的配置
- 即在上面的 站点配置 hugo.toml 中,填入 Algolia 官网中的
Application ID
,Search API Key
, 以及ALGOLIA_INDEX_NAME
- 创建 搜索页面文件,即在 content 创建一个文件夹 search,构建一个空文件,命名
placeholder.md
,即可。
placeholder.md:
1---
2layout: page
3title: "搜索页面"
4description: "使用 Algolia 为 站点 提供 搜索功能。 "
5author: "谌中钱"
6date: "2025-01-14"
7ogurl: "https://blog.climbtw.com/search"
8---
嵌入视频
哔哩哔哩
使用方式:
{ {< bilibili BV1kZ4y137gv >}}
- 优点:不需要梯子。
- 缺点:站内只能观看 360P 内容。
Vimeo (需要梯子)
使用方式:
{ {< vimeo 146022717 >}}
- 优点:可在 站内 直接观看 1080p 内容。
- 缺点:需要梯子。
- 参考: 《梯子使用手册》
YouTube (需要梯子)
使用方式:
{ {< youtube cllc1ZGlhsQ >}}
- 优点:
- 可在 站内 直接观看 1080p 内容。
- 月活跃用户 26亿+(全球第一)
- 缺点:
- 需要梯子。
- 站内 不能全屏观看。
- 参考: 《梯子使用手册》
自动化部署
实现推送代码到 github 上的 main 分支时,会自动部署到 部署服务器。
- 自动化部署流程:
- 在 Github 站点仓库上,通过 Github Action,新建一个 Workflow 工作流,创建 .github/workflows/deploy.yml,在里面监听 代码上传,合并 等操作,触发 连接部署服务器 的动作,并在 部署服务器 上执行 站点仓库里的 部署脚本 ./deploy.sh
- 要连接部署服务器,需要先把 部署服务器 的 SSH公钥 id_rsa.pub 添加到其 ~/.ssh/authorized_keys 里,然后把 部署服务器 的 SSH私钥 设置到 GitHub Action 上,取个名字。
- 在 .github/workflows/deploy.yml 中使用 SSH私钥 去连接 部署服务器。
- 参考:
- 要连接部署服务器,需要先把 部署服务器 的 SSH公钥 id_rsa.pub 添加到其 ~/.ssh/authorized_keys 里,然后把 部署服务器 的 SSH私钥 设置到 GitHub Action 上,取个名字。
- 部署脚本 ./deploy.sh 里面,会拉取最新的代码,完成网站更新。
- 在 Github 站点仓库上,通过 Github Action,新建一个 Workflow 工作流,创建 .github/workflows/deploy.yml,在里面监听 代码上传,合并 等操作,触发 连接部署服务器 的动作,并在 部署服务器 上执行 站点仓库里的 部署脚本 ./deploy.sh
在部署服务器上生成SSH密钥
1# 在云服务器上生成 公钥(id_rsa.pub) 和 私钥(id_rsa)
2ssh-keygen -t rsa -b 4096 -C "templechan@126.com"
3# 将 公钥 添加到云服务器的 ~/.ssh/authorized_keys 文件中
4cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
5
6# 查看 私钥
7cat ~/.ssh/id_rsa
8# 登录 GitHub -> 站点下的 Settings -> Secrets and variables -> Actions -> New repository secret 中添加 3 个常量:
9# Name: CTW_SSH_PRIVATE_KEY
10# Value: 你的私钥内容(注意不要添加换行符,可以直接从文件复制)
11
12# Name: CTW_SERVER_IP
13# Value: 部署服务器的 IP
14
15# Name: CTW_USER
16# Value: 部署服务器的 user
17
18
19# 私钥 CTW_SSH_PRIVATE_KEY 会在 .github/workflows/deploy.yml 的 Secrets 中被用来连接 部署服务器
编写 GitHub Action 工作流
.github/workflows/deploy.yml:
1name: Deploy to Server
2
3on:
4 push:
5 branches:
6 - main
7
8jobs:
9 deploy:
10 runs-on: ubuntu-latest
11 steps:
12 - name: Checkout code
13 uses: actions/checkout@v2
14
15 - name: Install SSH key
16 uses: webfactory/ssh-agent@v0.5.3
17 with:
18 ssh-private-key: ${{ secrets.CTW_SSH_PRIVATE_KEY }}
19
20 - name: Adding Known Hosts
21 run: ssh-keyscan ${{ secrets.CTW_SERVER_IP }} >> ~/.ssh/known_hosts
22
23 - name: Deploy to Server
24 run: ssh ${{ secrets.CTW_USER }}@${{ secrets.CTW_SERVER_IP }} 'bash -s' < ./deploy.sh
站点仓库的部署脚本
./deploy.sh:
1cd /usr/local/src
2rm -rf /usr/local/src/blog
3
4if ! command -v git &> /dev/null; then
5 dnf install -y git
6 git config --global user.email "templechan@126.com"
7 git config --global user.name "templechan"
8 # 设置 GitHub 国内镜像源
9 # git config --global url."https://bgithub.xyz/".insteadOf https://github.com/
10fi
11
12# 如果失效,则删除旧的,设置的新的
13# git config --global --unset url."https://bgithub.xyz/".insteadOf https://github.com/
14# git config --global url."https://kkgithub.com/".insteadOf https://github.com/
15git clone -b main https://github.com/templechan/blog.git
16
17if [ -d /usr/local/src/blog ]; then
18 cd /usr/local/src/blog
19 if ! command -v mogrify &> /dev/null; then
20 # 安装图片压缩包 ImageMagick
21 dnf install -y ImageMagick-7.1.1.26-2.oc9 bc parallel
22 # 配置ImageMagick策略文件
23 sed -i '/<policy domain="coder" rights="read|write"/!b;n;c\ \ <policy domain="coder" rights="read|write" pattern="PNG,JPG,JPEG,WEBP" />' /etc/ImageMagick-7/policy.xml
24 sed -i '/<policy domain="resource" name="memory"/s/value=".*"/value="1GiB"/' /etc/ImageMagick-7/policy.xml
25 sed -i '/<policy domain="resource" name="disk"/s/value=".*"/value="4GiB"/' /etc/ImageMagick-7/policy.xml
26 sed -i '/<policy domain="resource" name="width"/s/value=".*"/value="32KP"/' /etc/ImageMagick-7/policy.xml
27 sed -i '/<policy domain="resource" name="height"/s/value=".*"/value="32KP"/' /etc/ImageMagick-7/policy.xml
28 fi
29
30 # 手动压缩图片资源(会覆盖源文件,注意保留源文件)
31 # 压缩规则:
32 # 1. 超过 500KB 的图片才会压缩
33 # 2. 根据 图片大小 动态控制压缩比例,最后都控制在 300KB 左右
34 # 3. 可压缩 PNG,JPG,JPEG,WEBP 的图片
35 find ./static/img/ \( -name "*.png" -o -name "*.jpg" -o -name "*.jpeg" -o -name "*.webp" \) -type f -print0 | parallel -0 -j 4 --bar 'f="{}";s=$(stat -c%s "$f");if [ $s -gt 512000 ];then q=$(echo "scale=0;60-30*l($s/512000)/l(10)" | bc -l | awk "{print int(\$1+0.5)}");q=$((q<10?10:q>75?75:q));case "${f##*.}" in png) p="-quality $((q-25)) -define png:compression-level=9 -colors 128" ;; jpg|jpeg) p="-quality $((q-10)) -sampling-factor 4:2:0" ;; webp) p="-quality $((q-20)) -define webp:method=6" ;; esac;mogrify $p "$f";fi'
36
37
38 if [ ! "$(docker ps -a -f "name=blog" --quiet)" ]; then
39 if [ ! "$(docker images -q blog)" ]; then
40 # 构建镜像
41 docker build -t blog .
42 fi
43 # 创建并运行容器
44 docker run -d --restart=always -p 81:80 -v ./themes:/blog/themes -v ./hugo.toml:/blog/hugo.toml -v ./content:/blog/content -v ./static:/blog/static -v ./public:/blog/public -v ./static/sitemap/:/blog/public/*.xml --name blog blog
45 else
46 docker restart blog
47 fi
48
49 # 等待 5 秒,确保容器已启动
50 sleep 5
51 # 修复 RSS
52 sed -i 's#http://212.64.16.86:80#https://blog.climbtw.com#g' ./public/index.xml
53 sed -i 's#http://212.64.16.86:80#https://blog.climbtw.com#g' ./public/sitemap.xml
54 # 修复站点的动态链接
55 sed -i 's#http://212\.64\.16\.86:80#https://blog.climbtw.com#g' \
56 ./public/index.html \
57 ./public/categories/solutions/index.html \
58 ./public/categories/tech/index.html
59
60 # 推送索引到 Algolia
61 # 检查 npm 是否存在 (注意 非交互式 Shell 不会自动加载 ~/.bashrc 或 ~/.zshrc,导致:nvm 命令找不到, npm/node 路径未正确设置)
62 # 所以尝试先加载一遍 nvm 环境
63 export NVM_DIR="$HOME/.nvm"
64 [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
65 [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
66
67 if ! command -v npm &> /dev/null; then
68 echo "npm 未安装,开始安装 nvm 并安装 npm..."
69
70 if ! command -v nvm &> /dev/null; then
71 echo "nvm 未安装,开始安装 nvm..."
72 # 下载并安装 nvm
73 curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
74 # 加载 nvm 环境
75 export NVM_DIR="$HOME/.nvm"
76 [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
77 [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
78
79 else
80 echo "nvm 已存在。"
81 fi
82
83 echo "使用 nvm 安装 node 和 npm"
84 nvm install 20.19.1
85 nvm use 20.19.1
86 nvm alias default 20.19.1
87 fi
88
89 # 检查 atomic-algolia 是否全局已安装
90 if ! npm list -g atomic-algolia --depth=0 &> /dev/null; then
91 echo "atomic-algolia 未安装,安装中..."
92 npm install -g atomic-algolia
93 else
94 echo "atomic-algolia 全局已安装。"
95 fi
96
97 # 执行 atomic-algolia 命令
98 echo "执行 atomic-algolia 命令..."
99 npx atomic-algolia
100
101 # Nginx 如果配置好了,可直接访问网站查看部署更新
102fi
SEO
搜索引擎收录
- 百度收录官网:https://ziyuan.baidu.com/linksubmit/index
- 百度的 sitemap 提交权限需要和客服去申请。
- 必应收录官网:https://www.bing.com/webmasters/sitemaps
- 谷歌收录官网:https://search.google.com/search-console/sitemaps
修复站点地图
- 本文使用
hugo server --disableLiveReload --baseURL http://212.64.16.86 -p 80
的方式 部署服务器站点, 这样 Hugo 生成的 动态链接 的头部会变成https://212.64.16.86:80
,导致在生成 站点地图 index.xml 和 sitmap.xml 时,谷歌会判定 非域名 的链接 无效。- 解决方案:
- 用 VSCode 手动批量替换 ip 为 域名 的 站点地图文件 index.xml 和 sitmap.xml:
- 比如:
http://212.64.16.86:80
替换为https://blog.climbtw.com
- 比如:
- 可以在 启动站点容器 的命令结束后,通过 命令 替换 站点地图文件中的
https://212.64.16.86:80
为https://blog.climbtw.com
- 同时也修复下站点的动态链接
- 修复命令:
- 用 VSCode 手动批量替换 ip 为 域名 的 站点地图文件 index.xml 和 sitmap.xml:
- 解决方案:
1# 等待 5 秒,确保容器已启动
2sleep 5
3# 修复 RSS
4sed -i 's#http://212.64.16.86:80#https://blog.climbtw.com#g' ./public/index.xml
5sed -i 's#http://212.64.16.86:80#https://blog.climbtw.com#g' ./public/sitemap.xml
6# 修复站点的动态链接
7sed -i 's#http://212\.64\.16\.86:80#https://blog.climbtw.com#g' \
8./public/index.html \
9./public/categories/solutions/index.html \
10./public/categories/tech/index.html
「 您的咖啡能让我写出少 Bug 的代码 ☕️ ~ 」
「 会出现在赞赏名单中哦 ~ 」

您的咖啡能让我写出少 Bug 的代码 ☕️ ~
使用 微信 扫描二维码完成支付
