腾讯云已加入建站黑名单,请不要使用腾讯云,因为平台会不明原因地自动扣费,且平台无法给出解释。 - 20230909
一部分的网站框架都是动态的,以著名的 WordPass (PHP),Typecho (PHP),Python Django 等,这些动态每年都有大量的 CVE(Common Vulnerabilities & Exposures)漏洞,需要时刻保持更新。一些 0Day 级别的漏洞防不胜防。
另一部分网站框架是静态的,比方说 Hexo (Node JS),Gridea (React JS),VuePress (Vue JS) 等,在反复切换后,选择了最原始的 Hexo 作为博客框架。Hexo 会将 Markdown 文件转换为 html,css,js,jpg 等静态文件,在访问网站时,省了生成的这个过程,速度更快。
一句话概括,动态网站有 登录,编辑,发布,评论 等功能,而静态网站只有 阅读。在 Javascript 的加持下,可以通过和“动态” 框架进行联动,实现评论等功能。
静态网站的缺陷是——难以实现准确的权限管理。
章节 简介 静态网站生成器 关于如何配置 Hexo 静态网站生成器,以及一些插件配置 静态网站部署 如何部署静态网站到服务商上,主要用 Actions 进行自动化部署 静态页面托管 如何部署静态网站到服务器,使用免费的服务
懒得看一大长串记录,可以直接跳转到最后面的 Actions 配置文件合辑中查看:
环境参考 本地主机 使用 Linux/Mac 没有区别,仅习惯
89元/年:云主机 在 WSL(Windows Sub Linux) 中也可以
200元/年(没有钱可以用 Pages 服务提供的子域名)
预算 240元/年,包年 184元/年,建议做好流量预算(没有钱可以用 Pages 服务)
静态网站生成器 Hexo 是运行于 Nodejs 上的静态网站生成器,换句话说,就是 Chromium 内核家族下的又一大得力干将。Node.js (nodejs.org)
静态网站生成初体验 熟悉命令行可以直接跳过本节
考虑到我是用过很久的生成器,后面的东西都很熟悉,但一开始接触静态网站生成的肯定会很懵逼,为什么要用 Docker 环境,为什么又一步跳转到 Github Action 里面了,一会儿又手动的,很乱。
实际上这一点也不乱,从一开始,无论是 Docker 还是 Action 都是手动在 Linux 命令行下配置的,Dockerfile 和 Action yaml 都是对命令行操作的极大简化,因此现有手动,后又自动。
这里给出一个简化的 Blog 搭建流程,以减轻理解负担。
WSL 安装 适合 Windows 10 以上系统,Windows 11 系统可正常运行
Win
+ X
打开“终端管理员”,执行以下命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatformEnable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux wsl --set-default-version 2 wsl --install Ubuntu wsl
命令行环境准备 首先可以在 Git 的提供网站上注册账户,当然可以自己搭建 Git 服务器
操作系统是 Ubuntu ,命令在 Debian 家族下都是兼容的,推荐使用 WSL 进行操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 sed -i "s|archive.ubuntu.com|mirrors.cloud.tencent.com|g" /etc/apt/sources.list sudo apt-get install git-core curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - sudo apt-get install -y nodejs npm config set registry http://mirrors.cloud.tencent.com/npm/ npm install hexo-cli -g hexo -v
在 Window 下也能运行,稍微复杂一点,因此推荐使用 Linux
看到提示一堆版本信息就代表成功了:
初始化 Blog 接下来是初始化 Blog:
1 2 3 4 5 6 7 8 9 10 mkdir blog && cd bloghexo init hexo generate hexo server
至此,打开 http://localhost:4000/ 就能在浏览器中预览博客的样子了。 输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 > hexo init INFO Cloning hexo-starter https://github.com/hexojs/hexo-starter.git INFO Install dependencies INFO Start blogging with Hexo! > hexo generate INFO Validating config INFO Start processing INFO Files loaded in 95 ms INFO Generated: archives/index.html INFO Generated: index.html INFO Generated: archives/2023/08/index.html INFO Generated: fancybox/jquery.fancybox.min.css INFO Generated: js/script.js INFO Generated: css/style.css INFO Generated: archives/2023/index.html INFO Generated: fancybox/jquery.fancybox.min.js INFO Generated: js/jquery-3.6.4.min.js INFO Generated: css/images/banner.jpg INFO Generated: 2023/08/05/hello-world/index.html INFO 11 files generated in 110 ms > hexo server INFO Validating config INFO Start processing INFO Hexo is running at http://localhost:4000/ . Press Ctrl+C to stop.
新建文章 比方说需要新加一篇文章:
输出:
1 2 3 > hexo new new_post INFO Validating config INFO Created: /blog/source/_posts/new-post.md
新建的文件就在 /blog/source/_posts/new-post.md
,新手建议使用 nano
来编辑,即:
1 nano /blog/source/_posts/new-post.md
一般在 WSL 里面自带了 VScode,只需要在主机安装 Visual Studio Code 就能直接在主机编辑:
1 code /blog/source/_posts/new-post.md
保存后预览文章:
使用 git 仓库 1 2 3 4 5 6 7 8 9 10 11 12 git config --global user.email "you@example.com" git config --global user.name "Your Name" git init git add . git commit -m "My blog."
完毕,整个流程就这么简单。
Hexo-Docker 保留备忘,是在写 Actions 文件中留下的,是是个中间过程。
不会用 Docker 建议跳过本节,没有影响。
准备 Docker 环境 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 sudo apt update sudo apt install docker.io -y sudo service docker start
视网络情况而定,有时会特别久。
一般推荐直接安装 Docker Desktop :Download Docker Desktop | Docker
容器构建 Dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 FROM node:lts-bookwormRUN sed -i 's|deb.debian.org|mirrors.cloud.tencent.com|g' /etc/apt/sources.list && \ apt-get update && \ apt-get install -y fish vim && \ chsh -s /usr/bin/fish RUN npm config set registry http://mirrors.cloud.tencent.com/npm/ && \ npm install hexo -g && \ npm install hexo-admin -g && \ npm install hexo-theme-butterfly -g RUN mkdir /root/blog && \ cd /root/blog && \ hexo init . && \ git init . RUN mkdir ~/.ssh/ && \ echo 'Host github.com' >> ~/.ssh/config && \ echo ' HostName ssh.github.com' >> ~/.ssh/config && \ echo ' User git' >> ~/.ssh/config && \ echo ' Port 443' >> ~/.ssh/config && \ echo "set fileencodings=utf-8,ucs-bom,gb18030,gbk,gb2312,cp936" >> ~/.vimrc && \ echo "set termencoding=utf-8" >> ~/.vimrc && \ echo "set encoding=utf-8" >> ~/.vimrc && \ git clone --depth=1 https://github.com/amix/vimrc.git ~/.vim_runtime && \ sh ~/.vim_runtime/install_awesome_vimrc.sh WORKDIR /root/blog/ RUN npm install hexo-renderer-pug --save && \ npm install hexo-renderer-stylus --save && \ npm uninstall hexo-renderer-marked && \ npm uninstall hexo-renderer-kramed && \ npm install hexo-renderer-markdown-it --save && \ npm install katex @renbaoshuo/markdown-it-katex --save &&\ npm install hexo-generator-search --save && \ npm install hexo-wordcount --save && \ npm install hexo-filter-nofollow --save && \ npm install hexo-generator-feed --save && \ npm install hexo-memorial-day --save && \ npm install hexo-images-watermark --save RUN rm -f _config.landscape.yml && \ git clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly && \ cp themes/butterfly/_config.yml _config.butterfly.yml &&\ sed -i 's|landscape|butterfly|g' _config.yml CMD ["fish" ]
然后构建镜像,这里的命令既可以在 PowerShell (Windows) 中执行,也能在 Shell (Linux) 下执行:
1 2 3 4 5 6 7 8 docker build -t hexo . docker run -it \ --name =hexo \ -p 4000 :4000 \ -v $ (pwd )/blog/:/root/blog/ hexo
构建镜像比较久,如果网络比较差,可以去服务器上构建。
可选,这里是可选的,都用了镜像了,网速不慢都很快。
1 2 3 4 5 6 7 8 9 10 11 docker save hexo:latest | gzip > hexo_latest.tar.gz scp root@<remote_ip>:~/hexo_latest.tar.gz ~/hexo_latest.tar.gz docker load < hexo_latest.tar.gz
服务器一般不限速,并且距离镜像站比较近(或许只有十几米)。
外网 IDC 上构建需要去掉国内镜像,构建速度会更快。
总之,构建完后,需要将将整个镜像拉到本地。
构建环境配置的步骤记录 建议跳过,是上面 Dockerfile 的解释,细节比较繁琐。 如果不喜欢使用 Docker ,或者完全不熟悉,手动安装环境,也不会态复杂:
一开始是手动配置的,习惯性边配置边记录,免得容器配置炸了,可以快速恢复。
配置镜像,准备环境:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 sed -i 's|deb.debian.org|mirrors.ustc.edu.cn|g' /etc/apt/sources.list apt update apt install -y fish vim chsh -s /usr/bin/fish fish npm config set registry http://mirrors.cloud.tencent.com/npm/ npm config get registry npm install hexo -g npm install hexo-admin -g npm install hexo-theme-butterfly -g npm install hexo-renderer-pug -g npm install hexo-renderer-stylus -g cd /roothexo init blog cd bloggit init cat > ~/.ssh/config <<EOF Host github.com HostName ssh.github.com User git Port 443 EOF cat > ~/.vimrc <<EOF set fileencodings=utf-8,ucs-bom,gb18030,gbk,gb2312,cp936 set termencoding=utf-8 set encoding=utf-8 EOF ls vi _config.yml rm _config.landscape.ymlgit clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly cp themes/butterfly/_config.yml _config.butterfly.yml
这里有个很折磨人的问题,明明 ssh-key
配置对的,可就是报错 kex_exchange_identification: Connection closed by remote host
直到查了祖传笔记,才知道是端口问题,解决方法在上面的脚本里。
配置公钥 在 容器内执行命令:
1 2 ssh-keygen cat /root/.ssh/id_rsa.pub
或者把外面的密钥复制进去也行:
文件 位置 用途 公钥 cat ~/.ssh/id_rsa.pub
本地保存,远程验证 私钥 cat ~/.ssh/id_rsa
本地保存,不可泄露
然后提交到对应的地方:
总之,本地的公钥和私钥要配置对,远端的公钥和私钥也需要填对。
拷贝进入 Linux
需要注意密钥权限问题,比方说遇上 Permissions 0640 for 'xxx/id_rsa' are too open
,就需要修正权限:
1 2 chmod 600 ~/.ssh/id_rsachmod 600 ~/.ssh/id_rsa.pub
推送到仓库 首先新建仓库:
然后执行命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 git config --global user.email "git@0xac.cn" git config --global user.name "hxac" git add . git commit -m "First commit." git branch -M master git remote add origin git@github.com:hxac/blog.git git push -u origin master
注意这里的 user.email
和 user.name
需要改成自己的。
如果代理软件配置的不正确,就会出现 kex_exchange_identification: Connection closed by remote host
的错误,这个时候就需要调整 ssh
的配置:
1 2 3 4 5 6 cat >> ~/.ssh/config <<EOF Host github.com HostName ssh.github.com User git Port 443 EOF
安装 VSCode 可跳过,一般没必要
由于一开始都是那 vim 配置的,考虑到使用 vscode 确实会方便很多,虽然所可以直接用 ssh 直连容器,但这里还是贴上(水)vscode 的安装方法。
直接按照 VSCOE,适合 WSL2,以及带 GUI 环境。
1 2 3 4 5 6 7 8 9 10 11 wget -q https://packages.microsoft.com/keys/microsoft.asc -O- | sudo apt-key add - echo "deb https://packages.microsoft.com/repos/vscode stable main" >> / etc/apt/sources.listsudo apt update sudo apt install code
VScode Server,网页版的 VScode,性能稍微差,适合用在不开放 SSH 端口,并且改不了 SSH 端口的环境。
1 2 3 4 5 curl -fsSL https://code-server.dev/install.sh | sh
VSCode 通过 ssh 挂到远端 只要有 GUI 系统的,都可以,运行 vscode:
左下角蓝色 ><
标识了当前 vscode 运行的地方,如果是远端,请在本地命令行执行 code .
。
Ctrl + Shift + P 打开命令栏 输入 Remote-SSH: Add New SSH Host ...
,新建 ssh,这里需要联网下载插件 选择 C:\User\<your_name>\.ssh\config
输入远端地址 <user_name>@<ip_address>
Ctrl + Shift + P 打开命令栏 输入 Remote-SSH: Connect to Host ...
,打开远端 ssh 选择 Linux
,这里依然需要联网,在远端下载对应服务 vscode 不适合在无法联网的环境使用
Hexo 配置 目录结构 Hexo 的目录结构并不复杂,最好在理解 nodejs
原理下,会好理解很多:
1 2 3 4 5 6 7 8 9 10 blog/ |- _config.butterfly.yml // 主题配置(优先级比 _config.yml 高) |- _config.yml // 网站全局配置 |- node_modules/ // npm 环境(自动) |- package-lock.json // npm 环境配置(自动) |- package.json // npm 环境配置(自动) |- public/ // 生成的静态文件 |- source/ // 文章/页面 |- themes/ // 主题 |- yarn.lock // yarn 环境配置(自动)
只用记住一条: 这里的 source/
目录下的所有文件,除了一些排除项,以及 .md
和 .markdown
结尾的文件会被 hexo
解析为网页,其他的都会原封不动 地复制到 public/
目录下。
1 2 3 4 source/ |- about // 创建的 pages |- img // 会自动复制到 public |- _posts // post 文章
这里的 public/
也就是静态网站的访问目录,换句话说就是需要上传的储存桶的文件。
开始配置 这里可以从 终端命令行 或者 vscode 进行编辑。
Linux 连接到容器进行配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 docker exec -it hexo /usr/bin/fish
GUI 挂靠到容器进行配置
1 2 code . docker run -it hexo /usr/bin/fish
一般会提示安装 Docker 插件,直接附加到容器上去即可
配置网站 配置 _config.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 title: '' subtitle: '' description: '' keywords: author: hxac language: zh-cn timezone: 'Asia/Shanghai' theme: butterfly
配置主题 配置 _config.butterfly.yml
里面的说明非常详细
具体配置可以参考官方文档:Butterfly 安裝文檔(三) 主題配置-1 | Butterfly Butterfly 安裝文檔(四) 主題配置-2 | Butterfly
使用 hexo s
命令就能在 http://localhost:4000/
上实时预览博客:
1 2 3 4 5 6 7 8 9 10 11 12 13 INFO Validating config INFO =================================================================== 4.9.0 =================================================================== INFO Start processing INFO Hexo is running at http://localhost:4000/ . Press Ctrl+C to stop.
创建页面 具体页面创建在官方文档里面有Butterfly 安裝文檔(五) 主題問答 | Butterfly
基础操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 hexo new 'hello world' hexo new page about hexo g hexo s hexo clean
一些插件 哀悼日专用 1 npm install hexo-memorial-day --save
添加到 _config.yml
1 2 3 memorial_day: enable: true day: 12 -13
压缩博客 1 npm install hexo-neat --save
水印 1 npm install hexo-images-watermark --save
添加到 _config.yml
1 2 3 4 5 watermark: enable: true textEnable: true gravity: southeast text: '0xac.cn'
插件汇总 插件必须在 blog 的目录下安装,同时所有插件必须带 --save
后缀,确保渲染时会被包含。
大部分报错都是因为没有加 --save
后缀。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 # hexo 所必须的渲染器 npm install hexo-renderer-pug --save npm install hexo-renderer-stylus --save # KaTex 不兼容的渲染器 npm uninstall hexo-renderer-marked npm uninstall hexo-renderer-kramed # KaTex 需要的渲染器 npm install hexo-renderer-markdown-it --save npm install katex @renbaoshuo/markdown-it-katex --save # 本地搜索 npm install hexo-generator-search --save # 字数统计 npm install hexo-wordcount --save # SEO 优化,防止外链权重流失 #npm install hexo-filter-nofollow --save # SSR feeds 订阅生成 npm install hexo-generator-feed --save # 外链转内链,同 SEO 优化 npm install hexo-filter-links --save # 水印 npm install hexo-images-watermark --save # SEO 优化 npm install hexo-submit-urls-to-search-engine --save
换句话说,就是在 _config.yml
一个目录下,执行这些命令。
npm 自带包管理,文件夹下的包,优先服从上级文件夹的依赖。
依赖报错 依赖容易出现各种报错,只有能正常生成文件,就不用管。
落要卸载掉对应的插件,请使用 npm list
查看包名后,用 npm uninstall <package_name>
卸载对应包。
可以参考如下配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 > npm list hexo-site@0.0.0 /home/aero/blog ├── @renbaoshuo/markdown-it-katex@2.0.2 ├── hexo-filter-links@1.0.7 ├── hexo-generator-archive@2.0.0 ├── hexo-generator-category@2.0.0 ├── hexo-generator-feed@3.0.0 ├── hexo-generator-index@3.0.0 ├── hexo-generator-search@2.4.3 ├── hexo-generator-tag@2.0.0 ├── hexo-images-watermark@2.3.0 ├── hexo-renderer-ejs@2.0.0 ├── hexo-renderer-markdown-it@7.1.0 ├── hexo-renderer-pug@3.0.0 ├── hexo-renderer-stylus@3.0.0 ├── hexo-server@3.0.0 ├── hexo-wordcount@6.0.1 ├── hexo@6.3.0 └── katex@0.16.8
常用命令
1 2 3 4 5 6 7 8 9 10 11 sudo npm install -g hexo-cli npm install --save npm update --save npm list
预加载 预加载提升了用户的体验,但在开发过程中,也带来了非常多奇奇怪怪的 BUG。
过程大概是:用户鼠标放到链接上,浏览器会自动预拉取数据,在用户点击后,直接渲染页面。
Hexo Butterfly 采用的是 MoOx/pjax 框架进行预加载,可以在配置文件中开启:
开启预加载:
1 2 3 4 5 6 7 8 pjax: enable: true exclude:
开启加载动画:
1 2 3 4 5 6 preloader: enable: true source: 2
开启预加载应当开启加载动画,否则在网络比较差的时候,容易被误认为网页卡死了。
框架注入 现有的魔改主题方案基本上是直接修改源码,这种效果自然是立竿见影的,但一旦 博客更新,或者需要应用到别的框架中,就会变得非常复杂。
一部分框架支持 inject
注入,可以实现方便地改变主题。
CCS 注入 比方说想要给导航栏和页脚,加上磨砂玻璃效果,可以通过框架的注入选项,注入对应的 js
和 css
流程如下:
1 2 3 4 5 6 7 8 9 10 11 cd blogmkdir source /jsmkdir source /csstouch source /css/custom.csstouch source /js/custom.js
vi _config.butterfly.yml
1 2 3 4 5 6 7 inject: head: - <link rel="stylesheet" href="/css/custom.css"> bottom: - <script src="/js/custom.js"></script>
注意这里的 /css/custom.css
和 /js/custom.js
不要填错,或者拼错。
调试可以用 浏览器的 F12 开发者模式 -> 审查元素 进行编辑,编辑完成后直接加入 css
即可。
比方说这里,给导航栏和页脚增加毛玻璃效果。
vi custom.css
1 2 3 4 5 6 7 8 9 10 #footer { background : unset; } #page-header .nav-fixed #nav { background : rgba (255 , 255 , 255 , 0.4 ); }
这有一点需要注意的是,注入后如果不想要某种属性,直接删掉是没用的,因为你是注入,东西原本还在哪儿,只能使用 unset
和 none
等标记来去掉对应元素。
比方说:
或者是:
Javascript 注入 对于更复杂的效果,需要结合 Javascript
进行注入,比方说想要在不修改主题代码情况下,附加上一些功能,完全可以通过各种函数来实现:
函数 用途 document.getElementById()
元素查找 document.createElement()
元素创建 xxx.appendChild()
元素追加 xxx.remove()
元素删除 xxx.removeChild()
子元素删除
注意,这里还有两个问题:
搜索引擎的站长验证不能注入,因为搜索引擎因为安全问题,都不会渲染 Javascript
,采用 Javascript 进行的站长验证是无效的。 若采用了 Pjax
框架实现的页面局部刷新,导致被注入的元素被修改了,但是注入只有一次,因此引入元素后注入就失效了。 对于采用了 Pjax
框架的博客,需要在注入时加 data-pjax
标签,让每个页面在刷新的时候,都重新加载注入一遍 Javascript
。
_config.yml
1 2 3 4 5 6 7 inject: head: - <link data-pjax rel="stylesheet" href="/css/custom.css"> bottom: - <script data-pjax src="/js/custom.js"></script>
网页标题注入 比方有时候浏览博客时,只要一切换页面,就会弹出提示:
一切回来就变成了:
这是通过事件监听 addEventListener
函数实现的:
1 2 3 document .addEventListener ('visibilitychange' , func ());window .addEventListener ('blur' , ()func);window .addEventListener ('focus' , ()func);
事件 用途 visibilitychange
元素可见性发生变化 blur
元素失去焦点 focus
元素获得焦点
采用 blur
和 focus
给 window
挂上 addEventListener
事件监听,理论上没问题。
当然,若是使用 visibilitychange
还得配合 document.hidden
才能准确检测当前页面状态。
本来两个事件监听的方法都没什么区别,但遇上预加载框架 Pjax
前者就会出现非常的见鬼的 BUG,参考了前人的实践,用后一个方法都会好些。
当然改变标题这段动作非常简单,也就是修改 document.title
元素,这里采用了 setTimeout()
函数,实现延迟修改:
1 2 3 4 setTimeout (() => { document .title = '~(>_<)~ 网页崩溃了!' ; }, 3000 );
若采用 Pjax
框架,必须启用 data-pjax
标签,否则修改标题可能出现异常。
核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const OriginTitile = document .title ; let backTime, leaveTime;document .addEventListener ('visibilitychange' , () => { if (document .hidden ) { document .title = '~(>_<)~ 网页崩溃了!' ; } else { clearTimeout (leaveTime); document .title = '(≧▽≦) 骗你的,又好了。' ; backTime = setTimeout (() => { document .title = OriginTitile ; }, 1500 ); } });
考虑到用户体验,加了一点点功能:
反复离开进入标签页三次,就会停止触发彩蛋。 除非用户主动清除浏览器缓存,否则计数器不清零,彩蛋不会重复触发。 只要设置了 globalMourn = true;
,在严肃场合会自动停止彩蛋。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 var globalMourn = false ; const titleMaxDisplayed = 3 ; var titleDisplayedCount = 0 ; const OriginTitile = document .title ; let backTime, leaveTime;document .addEventListener ('visibilitychange' , () => { if (globalMourn == true ) { return ; } if (localStorage .getItem ('titleDisplayedCount' ) == null || localStorage .getItem ('titleDisplayedCount' ) < titleMaxDisplayed) { if (document .hidden ) { clearTimeout (backTime); leaveTime = setTimeout (() => { document .title = '~(>_<)~ 网页崩溃了!' ; }, 3000 ); } else { clearTimeout (leaveTime); document .title = '(≧▽≦) 骗你的,又好了。' ; backTime = setTimeout (() => { document .title = OriginTitile ; }, 1500 ); localStorage .setItem ('titleDisplayedCount' , titleDisplayedCount++); } } });
星空背景注入 给主页加星空效果,通过给主页的 page-header
注入 canvas
然后在这个 canvas
上进行绘画,实现稍微复杂。
核心是利用 createElement("canvas")
创建 canvas
层,借助一系列函数进行绘制:
用途 函数 创建 canvas
元素 const starLightCanvas = document.createElement("canvas");
创建 2d 绘画面 const ctx = starLightCanvas.getContext("2d");
设置起始路径 ctx.beginPath();
绘画圆形路径 ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
填充颜色 ctx.fillStyle = rgba(211, 211, 211, ${this.opacity});
填充颜色到路径内 ctx.fill();
调用浏览器 API requestAnimationFrame(drawMain);
vi custom.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 (function ( ) { const starLightCanvas = document .createElement ("canvas" ); const page_header = document .getElementById ('page-header' ) page_header.appendChild (starLightCanvas); const ctx = starLightCanvas.getContext ("2d" ); starLightCanvas.width = page_header.getBoundingClientRect ().width ; starLightCanvas.height = page_header.getBoundingClientRect ().height ; function handleResize ( ) { starLightCanvas.width = page_header.getBoundingClientRect ().width ; starLightCanvas.height = page_header.getBoundingClientRect ().height ; } window .addEventListener ('resize' , handleResize); class starLightStar { constructor ( ) { this .x = 0 ; this .y = 0 ; this .radius = Math .random () * 1 + 1 ; this .speed = Math .random () * 0.02 + 0.01 ; this .opacity = 0 ; this .opacityDirection = 1 ; } draw ( ) { ctx.beginPath (); ctx.arc (this .x , this .y , this .radius , 0 , Math .PI * 2 ); ctx.fillStyle = `rgba(211, 211, 211, ${this .opacity} )` ; ctx.fill (); } move ( ) { this .x += (this .x - starLightCanvas.width / 2 ) * 0.004 ; this .y += (this .y - starLightCanvas.height / 2 ) * 0.004 ; const radian = 0.001 ; const RotateX = this .x * Math .cos (radian) - this .y * Math .sin (radian); const RotateY = this .x * Math .sin (radian) + this .y * Math .cos (radian); this .x = RotateX ; this .y = RotateY ; var opacity = this .opacity + this .opacityDirection * this .speed ; this .opacity = Math .max (0 , Math .min (1 , opacity)); this .opacityDirection = this .opacity === 1 ? -1 : 1 ; this .draw (); if (this .x < 0 || this .x > starLightCanvas.width || this .y < 0 || this .y > starLightCanvas.height ) { this .x = Math .random () * starLightCanvas.width ; this .y = Math .random () * starLightCanvas.height ; this .radius = Math .random () * 1 + 1 ; this .speed = Math .random () * 0.002 + 0.001 ; } } } const stars = []; for (let i = 0 ; i < 1024 ; i++) { stars.push (new starLightStar ()); } function drawMain ( ) { ctx.clearRect (0 , 0 , starLightCanvas.width , starLightCanvas.height ); for (let i = 0 ; i < stars.length ; i++) { stars[i].move (); } requestAnimationFrame (() => { setTimeout (drawMain, 20 ); }); } drawMain ();})();
这个由于采用纯计算实现绘图,会占用浏览器 CPU 资源。
横幅注入 同时注入 css
和 Javascript
实现一个消息提示的横幅。
虽然说主题已经内置消息提示框了,但缺一个横幅啊,就是那种挂在学校门口,单位楼上的那种,又红又土的横幅土就是战斗力,越土战斗力越强 。
vi custom.css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #banner { position : fixed; top : 0 ; left : 0 ; width : 100% ; opacity : 0 ; transform : translateY (-100px ); transition : opacity 0.5s ease, transform 0.5s ease; display : flex; align-items : center; justify-content : center; font-size : 18px ; transition : opacity 0.5s ease-in-out; } #bannerAction { margin-left : 10px ; cursor : pointer; font-size : 18px ; color :#d2b42c ; } @keyframes bounce-down { 0% { opacity : 0 ; transform : translateY (-100px ); } 70% { opacity : 1 ; transform : translateY (20px ); } 100% { opacity : 1 ; transform : translateY (0 ); } } @keyframes bounce-up { 0% { opacity : 1 ; transform : translateY (0 ); } 70% { opacity : 1 ; transform : translateY (20px ); } 100% { opacity : 0 ; transform : translateY (-100px ); } }
vi custom.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 class Banner { constructor (id ) { if (document .getElementById (id) === null ) { return ; } this .parentElement = document .getElementById (id); this .banner = document .createElement ("div" ); this .banner .id = "banner" ; this .bannerMessage = document .createElement ("span" ); this .bannerMessage .id = "bannerMessage" ; this .bannerAction = document .createElement ("span" ); this .bannerAction .id = "bannerAction" ; this .banner .appendChild (this .bannerMessage ); this .banner .appendChild (this .bannerAction ); this .parentElement .appendChild (this .banner ); } setElement ( ) { this .bannerMessage .innerHTML = this .text ; this .bannerAction .innerHTML = this .actionText ; this .banner .style .background = `linear-gradient(to top, ${this .background} , ${this .background} )` ; this .banner .style .color = this .color ; this .banner .style .height = this .height + 'px' ; } isShown ( ) { const storedData = localStorage .getItem ("banner_shown" ); if (storedData == null ) { return false ; } const parsedData = JSON .parse (storedData); if (parsedData === undefined ) { return false ; } const currentTime = Date .now (); const expirationTime = parsedData.time + parsedData.expire * 1000 ; if (currentTime < expirationTime) { if (parsedData.data == '1' ) { return true ; } } return false ; } setShown ( ) { localStorage .setItem ("banner_shown" , JSON .stringify ({ data : 1 , time : Date .now (), expire : 24 * 3600 , }) ); } isnotDate ( ) { const currentDate = new Date (); const currentMonth = currentDate.getMonth () + 1 ; const currentDay = currentDate.getDate (); const dateParts = this .date .split ('-' ); const targetMonth = parseInt (dateParts[0 ]); const targetDay = parseInt (dateParts[1 ]); if (currentMonth === targetMonth && currentDay === targetDay) { return false ; } return true ; } displayBanner ( ) { this .banner .style .animation = "bounce-down 0.5s ease forwards" ; setTimeout (() => { this .banner .style .animation = "unset;" ; }, 500 ); } hiddenBanner ( ) { this .banner .style .animation = "bounce-up 0.5s ease forwards" ; setTimeout (() => { this .banner .style .animation = "unset;" ; }, 500 ); } applyFilter ( ) { const htmlElement = document .querySelector ("html" ); htmlElement.style .mozFilter = "grayscale(100%)" ; htmlElement.style .msFilter = "grayscale(100%)" ; htmlElement.style .oFilter = "grayscale(100%)" ; htmlElement.style .filter = "grayscale(100%)" ; } resetFilter ( ) { const htmlElement = document .querySelector ("html" ); htmlElement.style .mozFilter = "grayscale(0%)" ; htmlElement.style .msFilter = "grayscale(0%)" ; htmlElement.style .oFilter = "grayscale(0%)" ; htmlElement.style .filter = "grayscale(0%)" ; } show (options ) { this .text = options.text ; this .actionText = !(options.actionText === undefined ) ? options.actionText : '✖' ; this .onAction = !(options.onAction === undefined ) ? options.onAction : false ; this .background = !(options.background === undefined ) ? options.background : '#c04851' ; this .color = !(options.color === undefined ) ? options.color : '#d8e3e7' ; this .height = !(options.height === undefined ) ? options.height : 50 ; this .delay = !(options.delay === undefined ) ? options.delay : 4000 ; this .date = !(options.date === undefined ) ? options.date : false ; this .once = !(options.once === undefined ) ? options.once : false ; this .mourn = !(options.mourn === undefined ) ? options.mourn : false ; if (this .once && this .isShown ()) { return ; } if (this .date && this .isnotDate ()) { return ; } if (this .mourn ) { globalMourn = true ; this .applyFilter (); } this .setElement (); this .displayBanner (); if (this .delay != false ) { setTimeout (() => { this .hiddenBanner (); }, this .delay ); } this .bannerAction .addEventListener ("click" , () => { if (this .onAction == false ) { this .hiddenBanner (); } else { this .onAction (); this .hiddenBanner (); } }); if (this .once ) { this .setShown (); } } }
用起来非常简单:
1 2 3 4 5 var banner = new Banner ("id" );document .addEventListener ("DOMContentLoaded" , banner.show ({ text : "这是动态消息内容" , }) );
务必注意,text
和 actionText
绝对不能交给用户,需要处理注入问题。
条目 用途 默认 true false 说明 text 显示标题消息 - - - actionText 关闭按钮 X X - onAction 关闭按钮 true 关闭 - 自定义动作 background 背景颜色 红 - - color 文字颜色 白 - - height 横幅高 40px - - delay 显示时长 ms 4000ms - 不关闭 date 显示日期 不开启 - - 仅支持 日月 09-08 once 一天一次 一天一次 一次 每次 mourn 全屏灰色默哀 禁用 启用 关闭
二次展开 二次展开可以省略很多代码,由于这三个文件的存在,可以准确地帮助 nodejs
配置好 hexo
所需要的依赖:
文件名 用途 package-lock.json
锁定各个包的版本号 package.json
项目数据挖掘 db.json
hexo 框架配置
这几个是不需要的目录,默认在 .gitignore
进行了忽略。
目录 用途 node_modules/
npm
包缓存theme/
主题缓存,构建时从 git 拉去 public/
构建结果,hexo s
构建即可
快速展开环境 这里默认配置好了 git
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 git clone git@github.com:hxac/blog.git sudo npm install hexo-cli -g git clone https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly npm install hexo clean hexo generate hexo server
推送优化 SEO - Search Engine Optimization 搜索引擎优化,理论上可以提高搜索引擎的索引排名。
调整 url 1 2 3 4 5 6 7 8 url: https://0xac.cn permalink: :year/:month/:title/ permalink_defaults: pretty_urls: trailing_index: false trailing_html: false
这里的 url
需要填对,避免在生成页面时产生大量点不开的死链接。url
的默认是 yoursite
。
RSS - Really Simple Syndication(信息聚合)
1 npm install hexo-generator-feed --save
_config.yml
1 2 3 4 5 6 7 8 9 10 11 feed: type: atom type: - atom - rss2 path: - atom.xml - rss2.xml
Sitemap 这个 sitemap.xml
是用来指导搜索引擎可爬取页面。
1 2 3 4 npm install hexo-generator-sitemap --save npm install hexo-generator-baidu-sitemap --save
设置 robots.txt 这个 robots.txt
是用来指导网络爬虫工作的文件,一些搜索引擎可能不会遵循这个文件。
1 2 3 4 5 6 7 8 User-agent: * Allow: / Disallow: /js/ Disallow: /css/ Disallow: /img/ Disallow: /tags/ Sitemap: https://0xac.cn/sitemap.xml
这里的 https://0xac.cn/sitemap.xml
需要填写为自己实际的网址
链接优化 一般需要在链接添加 rel="external nofollow"
标识,防止权重流失:
1 npm install hexo-filter-links --save
1 2 3 4 5 6 7 links: enable: true field: "site" exclude:
静态网站部署 域名准备 没有钱可以用 Pages 服务,不需要准备域名都行,建议直接跳过本节。
常见域名后缀 后缀 价格 RMB 注释 .cn 39 便宜 .com 89 中等 .co 169 贵 .org 98 组织,无法备案 .me 120 只对五眼联盟 英美加澳 四国学生免费 .io 259 巨贵
域名注册商比较多,这里随便列举一部分:
免费域名 目前只有 https://www.freenom.com/ 提供的 5 个免费后缀
二级域名有一定风险。
证书准备 推荐采用 Let’s Encrypt 的证书 https://letsencrypt.org/ 是免费的。
这里采用 DNS 验证,简单快捷,采用服务器验证经常遇到各种奇怪的故障(比方说折腾一个下午,发现是防火墙端口没用放行,而且是云服务商和主机两道防火墙都没用放行)。
准备好域名 <you_domain>
,以及一台 Linux 主机:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 git clone https://github.com/acmesh-official/acme.sh cd acme.sh./acme.sh --set-default-ca --server letsencrypt export RQ_DOMAIN=0xac.cn./acme.sh --issue --dns -d ${RQ_DOMAIN} ./acme.sh --issue --dns -d $RQ_DOMAIN --yes-I-know-dns-manual-mode-enough-go-ahead-please dig +nocmd txt +noall +answer _acme-challenge.$RQ_DOMAIN ./acme.sh --renew -d $RQ_DOMAIN --yes-I-know-dns-manual-mode-enough-go-ahead-please cat "${HOME} /.acme.sh/${RQ_DOMAIN} _ecc/${RQ_DOMAIN} .cer" cat "${HOME} /.acme.sh/${RQ_DOMAIN} _ecc/fullchain.cer" cat "${HOME} /.acme.sh/${RQ_DOMAIN} _ecc/${RQ_DOMAIN} .key" ./acme.sh --issue -d $RQ_DOMAIN --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please
API 请求会方便一些,脚本会全自动进行一切必要验证,详细可见官方文档:dnsapi · acmesh-official/acme.sh Wiki (github.com)
比方说采用 DNSPod 的话,可以在 DNSPod(不是腾讯云)上申请 API 密钥,然后填入执行
1 2 3 4 5 6 7 8 9 10 11 12 export DP_Id="id" export DP_Key="key" acme.sh --issue --dns dns_dp -d <you_domain> acme.sh --issue --renew -d <you_domain> --dns acme.sh --issue -d <you_domain> --dns
注意这里的重新签发并不会吊销旧证书。
虽然说 DNSPod 是腾讯旗下的产品,在 腾讯云管理 中也能配置 DNS,但 API 密钥却得去 DNSPod 申请。
一般 云服务器管理 中都有界面上传密钥,可以手动操作。后面会采用 Actions 全自动申请推送密钥。
完整配置可见后面的 [Actions 合辑
请务必在本地验证成功后,再采用 Actions 配置,证书申请有次数限制。
CDN 准备 CDN 按照流量计费,一般而言 CDN 流量都有免费额度,普遍在 50GB 以下是免费的。
流量计费很贵,建议买流量包(新站流量不大,不用买
对象储存 准备 对象储存流入是免费的,但流出需要计费,一般接在 CDN 后端,都只用流出一次给 CDN 缓存,只要网站更新得不是很频繁,消耗流量都非常低。
下面的 对象储存 的流量价格按照 10GB,10GB CDN 流出,每年计费计算。
实际上域名可以直接解析到 对象储存上,但 对象储存 的流出流量非常贵。
Action 自动构建 如果对自己的代码能力有充分信心,可以用以下 Github Action
脚本进行自动化构建。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 name: Deploy Blog on: push: branches: [ "master" ] workflow_dispatch: env: TZ: Asia/Shanghai jobs: generate-pages: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Use Node.js 18 uses: actions/setup-node@v3 with: node-version: 18 cache: 'npm' - name: Cache node modules uses: actions/cache@v3 id: cache with: path: node_modules key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- - name: Prepare Hexo run: | npm install hexo-cli -g - name: Prepare Themes run: | mkdir -p blog/themes git clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly - name: Prepare Blog if: steps.cache.outputs.cache-hit != 'true' run: npm install - name: Generate Blog run: | hexo clean hexo generate - name: Upload generated public pages uses: actions/upload-artifact@v3 with: name: public_pages path: ./public
当然,这里只是生成了构建文件,需要送到 对象存储筒 上,需要用到一些命令行工具。
完整配置可见后面的 [Actions 合辑](#Actions 合辑)
对象储存上传 首先需要在云服务商申请密钥,确保有对储存桶的全读写权限:
后续会有全自动证书配置,因此也建议一并申请这些权限:
权限 说明 存储桶全读写 上传静态网页到存储桶 CDN ,刷新、配置 配置证书,上传证书 DNS 的新增,删除解析 申请证书时的 DNS 验证
特别注意,对于腾讯云 DNS,不是在控制台申请,而是在 DNSPOD 上申请。
1 2 3 4 COS_SECRET_ID=yourSecretId COS_SECRET_KEY=yourSecretKey COS_BUCKET=yourBucket COS_REGION=yourRegion
注意这里必须使用 Secerts 以防止密钥泄露,绝对不能用 Variables,Variables 会将变量暴露在命令行膻中,非常危险。
暴露密钥基本上等同于送免费网站给别人,特别是已备案网站,还可能被利用做一些违法的事情。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 upload-store: runs-on: ubuntu-latest needs: compress-pages steps: - uses: actions/checkout@v3 - name: Download compressed public pages uses: actions/download-artifact@v3 with: name: public_pages path: ./public - name: Install COS Tool run: | wget https://github.com/tencentyun/coscli/releases/download/v0.13.0-beta/coscli-linux mv coscli-linux coscli chmod 755 coscli ./coscli --version touch ~/.cos.yaml - name: Depoly blog run: | ./coscli config add -b ${{ secrets.COS_BUCKET }} -r ${{ secrets.TENCENTCLOUD_REGION }} -a public-page ./coscli config set --secret_key ${{ secrets.TENCENTCLOUD_SECRET_KEY }} --secret_id ${{ secrets.TENCENTCLOUD_SECRET_ID }} ./coscli rm cos://public-page/ -rf ./coscli cp public/ cos://public-page/ ./coscli du cos://public-page/
tar 压缩文件的解压需要服务商的支持,部分服务商不支持
服务商都有自己的对象储存上传工具,后面给出了国内主流服务商的参考命令。
这里采用的是删掉旧文件,重新上传新文件,避免旧文件不删除引发安全问题。
完整配置可见后面的 [Actions 合辑](#Actions 合辑)
腾讯云对象储存 对象存储 COSCLI - 腾讯云 (tencent.com)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 wget https://github.com/tencentyun/coscli/releases/download/v0.13.0-beta/coscli-linux mv coscli-linux cosclichmod 755 coscli./coscli --version touch ~/.cos.yaml ./coscli config add -b ${COS_BUCKET} -r ${COS_REGION} -a public-page ./coscli config set --secret_key ${COS_SECRET_KEY} --secret_id ${secrets.COS_SECRET_ID} ./coscli rm cos://public-page/ -rf ./coscli cp public/ cos://public-page/ -r ./coscli du cos://public-page/
华为云对象储存 这些命令没有验证过
对象存储服务 OBS_obsutil_华为云 (huaweicloud.com)
1 2 3 4 5 6 7 8 9 10 11 12 wget https://obs-community.obs.cn-north-1.myhuaweicloud.com/obsutil/current/obsutil_linux_amd64.tar.gz tar -xzvf obsutil_linux_amd64.tar.gz cp obsutil_linux_amd64_*/obsutil obsutilchmod 755 obsutil./obsutil version ./obsutil config -i=${OBS_AK} -k=${OBS_SK} -e=${OBS_ENDPOINT} ./obsutil rm obs://${OBS_BUCKET_NAME} / -r -f ./obsutil cp public/ obs://${OBS_BUCKET_NAME} / -r -f -flat ./obsutil stat obs://${OBS_BUCKET_NAME} /
阿里云对象储存 这些命令也没有验证过
对象存储服务 OOS - 安装 ossutil (aliyun.com)
1 2 3 4 5 6 7 8 9 10 11 12 13 wget https://github.com/aliyun/ossutil/releases/download/v1.7.16/ossutil-v1.7.16-linux-amd64.zip unzip ossutil-*.zip cp ossutil-*/ossutil-*/ossutil64 ossutil64chmod 755 ossutil64./ossutil64 update ./ossutil64 config -i=${OOS_AK} -k=${OOS_SK} -e=${OOS_ENDPOINT} ./obsutil rm oss://${OOS_BUCKET_NAME} / -r -f ./ossutil64 cp -r public/ oss://${OOS_BUCKET_NAME} / ./ossutil64 du oss://${OOS_BUCKET_NAME} /
末端部署全自动 截至目前,只实现了网页生成和推送的全自动化。而证书生成,和 CDN 预热刷新都需要手动进行。
下面将会配合各大厂商的 cli
工具,彻底实现真正的全自动方案。
各个云服务厂商的接口不统一,特别是某些厂商,DNS 和 CDN 解析的 API 要到两个子站上申请,特别麻烦。某些厂商 API 文档写得跟捉迷藏似的,非常难找。
这里列举了一些厂商的命令行工具进行自动化操作:
由于成本问题,实现全自动化的只有腾讯云,理论上 其他厂商,都能实现全自动化部署崩了别打我,找文档 。
阿里云全自动化 未验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 export RQ_DOMAIN="0xac.cn" export Ali_Key="<key>" export Ali_Secret="<secret>" export REGION="ap-guangzhou " git clone https://github.com/acmesh-official/acme.sh ./acme.sh --set-default-ca --server letsencrypt ./acme.sh --issue -d "${RQ_DOMAIN} " -d "*.${RQ_DOMAIN} " --dns dns_ali cd acme.shpublic_key="$(cat $HOME/.acme.sh/${RQ_DOMAIN}_ecc/fullchain.cer) " private_key="$(cat $HOME/.acme.sh/${RQ_DOMAIN}_ecc/${RQ_DOMAIN}.key) " private_name="$(cat $HOME/.acme.sh/${RQ_DOMAIN}_ecc/fullchain.cer | md5sum) " wget https://github.com/aliyun/aliyun-cli/releases/download/v3.0.170/aliyun-cli-linux-3.0.170-amd64.tgz tar xzvf aliyun-cli-linux-*-amd64.tgz chmod 755 aliyunaliyun configure set --profile akProfile --region "${REGION} " --access-key-id "${Ali_Secret} " --access-key-secret "${Ali_Key} " aliyun cas UploadUserCertificate --region cn-hangzhou --Cert "${public_key} " --Key "${private_key} " --Name "${private_name} " --version 2020-04-07 --force aliyun cdn SetCdnDomainSSLCertificate --region "${REGION} " --DomainName "${RQ_DOMAIN} " --SSLProtocol on --CertName "${private_name} " aliyun dcdn RefreshDcdnObjectCaches --region "${REGION} " --ObjectPath 'https://${RQ_DOMAIN}/'
需要调试 API 可以在:阿里云OpenAPI开发者门户 (aliyun.com)
没有验证,就不给出对应的 Actions 工作流配置了。
华为云全自动化 未验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 export RQ_DOMAIN="0xac.cn" export HUAWEICLOUD_Username="<Your IAM Username>" export HUAWEICLOUD_Password="<Your Password>" export HUAWEICLOUD_DomainName="<Your DomainName>" export HW_TOKEN=<cli-x-auth-token>export REGION="cn-north-4" git clone https://github.com/acmesh-official/acme.sh ./acme.sh --set-default-ca --server letsencrypt ./acme.sh --issue -d "${RQ_DOMAIN} " -d "*.${RQ_DOMAIN} " --dns dns_huaweicloud cd acme.shpublic_key="$(cat $HOME/.acme.sh/${RQ_DOMAIN}_ecc/fullchain.cer) " private_key="$(cat $HOME/.acme.sh/${RQ_DOMAIN}_ecc/${RQ_DOMAIN}.key) " cert_name="$(cat $HOME/.acme.sh/${RQ_DOMAIN}_ecc/fullchain.cer | md5sum) " wget https://github.com/aliyun/aliyun-cli/releases/download/v3.0.170/aliyun-cli-linux-3.0.170-amd64.tgz tar xzvf aliyun-cli-linux-*-amd64.tgz chmod 755 aliyunhcloud configure set --cli-profile=hwToken --cli-mode=token --cli-region="${REGION} " --cli-x-auth-token="${HW_TOKEN} " --cli-domain-id="${HUAWEICLOUD_DomainName} " hcloud SCM ImportCertificate/v3 --cli-region="${REGION} " --name="${private_name} " --certificate="${public_key} " --private_key="${private_key} " hcloud CDN UpdateDomainMultiCertificates/v1 --cli-region="${REGION} " --https.domain_name="${RQ_DOMAIN} " --https.cert_name="${cert_name} " --https.https_switch=1 --https.force_redirect_config.switch=1 hcloud CDN CreatePreheatingTasks/v1 --cli-region="${REGION} " --preheating_task.urls.1="https://${RQ_DOMAIN} /"
需要调试 API 可以在:API Explorer (huaweicloud.com)
腾讯云全自动化 这里是在本地部署的脚本,作为参考:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 export RQ_DOMAIN="0xac.cn" export DP_Id="123456" export DP_Key="8921sdfjo237o4274h9387xxxxx" export TENCENTCLOUD_SECRET_ID="xxxxxxxxxx" export TENCENTCLOUD_SECRET_KEY="xxxxxxxxxx" export TENCENTCLOUD_REGION="ap-xxxxxxxxx" git clone https://github.com/acmesh-official/acme.sh ./acme.sh --set-default-ca --server letsencrypt ./acme.sh --issue -d "${RQ_DOMAIN} " -d "*.${RQ_DOMAIN} " --dns dns_dp PUBLIC_KEY="$(cat $HOME/.acme.sh/${RQ_DOMAIN}_ecc/fullchain.cer) " PRIVATE_KEY="$(cat $HOME/.acme.sh/${RQ_DOMAIN}_ecc/${RQ_DOMAIN}.key) " echo export PUBLIC_KEY="\"$(cat ${HOME}/.acme.sh/${RQ_DOMAIN}_ecc/${RQ_DOMAIN}.key) \"" >.env echo export PRIVATE_KEY="\"$(cat $HOME/.acme.sh/${RQ_DOMAIN}_ecc/${RQ_DOMAIN}.key) \"" >>.env pip install tccli echo export TENCENTCLOUD_SECRET_ID="\"***\"" >> .env echo export TENCENTCLOUD_SECRET_KEY="\"***\"" >> .env echo export TENCENTCLOUD_REGION="\"***\"" >> .env source .env echo export CERT_ID=$(tccli ssl UploadCertificate \--cli-unfold-argument \ --CertificatePublicKey "${PUBLIC_KEY} " \ --CertificatePrivateKey "${PRIVATE_KEY} " \ --filter CertificateId) >> .env source .env tccli cdn UpdateDomainConfig \ --cli-unfold-argument \ --Domain "${RQ_DOMAIN} " \ --Https.Switch on \ --Https.CertInfo.CertId "${CERT_ID} " tccli cdn PurgePathCache \ --cli-unfold-argument \ --Paths "https://${RQ_DOMAIN} /" \ --FlushType flush
需要调试 API 可以在:API Explorer - 云 API - 控制台 (tencent.com)
然后在本地可以正常运行上述脚本时,转写成 Actions 工作流。这里分为两个部分,一个是刷新 CDN 预热
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 reflush-cdn: runs-on: ubuntu-latest needs: upload-store steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: '3.9' - name: Install Cloud Service Cli run: | pip install tccli sleep 20 - name: Load Tencent Cloud API run: | echo export TENCENTCLOUD_SECRET_ID="\"${{ secrets.TENCENTCLOUD_SECRET_ID }}\"" > .env echo export TENCENTCLOUD_SECRET_KEY="\"${{ secrets.TENCENTCLOUD_SECRET_KEY }}\"" >> .env echo export TENCENTCLOUD_REGION="\"${{ secrets.TENCENTCLOUD_REGION }}\"" >> .env - name: reflush CDN run: | source .env tccli cdn PurgePathCache --cli-unfold-argument --Paths "https://${{ secrets.RQ_DOMAIN }}/" --FlushType flush
另一部分是证书自动申请,和提交,配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 name: Generate Certificates on: push: branches-ignore: [master ] schedule: - cron: "1 1 1 * 1" workflow_dispatch: jobs: generate-Key: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 - name: Clone ACME.SH run: | curl https://get.acme.sh | sh - name: Config ACME.sh if: ${{ env.ACME == 1 }} run: | echo "DEFAULT_ACME_SERVER='https://acme-v02.api.letsencrypt.org/directory'" > /home/runner/.acme.sh/account.conf echo "SAVED_DP_Id='${{ secrets.DP_ID }}'" >> /home/runner/.acme.sh/account.conf echo "SAVED_DP_Key='${{ secrets.DP_KEY }}'" >> /home/runner/.acme.sh/account.conf - name: Request Let's Encrypt for issue certificates if: ${{ env.ACME == 1 }} run: | /home/runner/.acme.sh/acme.sh --issue -d "${{ secrets.RQ_DOMAIN }}" -d "*.${{ secrets.RQ_DOMAIN }}" --dns dns_dp - name: Load certificates and key from ACME.SH if: ${{ env.ACME == 1 }} run: | echo export PUBLIC_KEY="\"$(cat /home/runner/.acme.sh/${RQ_DOMAIN}_ecc/${RQ_DOMAIN}.key)\"" > .env echo export PUBLIC_KEY="\"$(cat /home/runner/.acme.sh/${RQ_DOMAIN}_ecc/${RQ_DOMAIN}.key)\"" >> .env - name: Load certificates and key from ENV if: ${{ env.ACME == 0 }} run: | echo PUBLIC_KEY="\"${{ secrets.PUBLIC_KEY }}\"" > .env echo PRIVATE_KEY="\"${{ secrets.PRIVATE_KEY }}\"" >> .env - name: Install Tencent Cloud CLi run: | pip install tccli - name: Load Tencent Cloud API run: | echo export TENCENTCLOUD_SECRET_ID="\"${{ secrets.TENCENTCLOUD_SECRET_ID }}\"" >> .env echo export TENCENTCLOUD_SECRET_KEY="\"${{ secrets.TENCENTCLOUD_SECRET_KEY }}\"" >> .env echo export TENCENTCLOUD_REGION="\"${{ secrets.TENCENTCLOUD_REGION }}\"" >> .env - name: Upload public key and private key run: | source .env echo export CERT_ID="\"$(tccli ssl UploadCertificate --cli-unfold-argument --CertificatePublicKey "${PUBLIC_KEY}" --CertificatePrivateKey "${PRIVATE_KEY}" --filter CertificateId)"\" >> .env - name: Update public key and private key to CDN run: | source .env tccli cdn UpdateDomainConfig --cli-unfold-argument --Domain "${{ secrets.RQ_DOMAIN }}" --Https.Switch on --Https.CertInfo.CertId "${CERT_ID}" tccli cdn UpdateDomainConfig --cli-unfold-argument --Domain "api.${{ secrets.RQ_DOMAIN }}" --Https.Switch on --Https.CertInfo.CertId "${CERT_ID}"
完整配置可见后面的 [Actions 合辑](#Actions 合辑)
压缩网页 压缩网页放到最后,是因为用到的包 gulp
和 hexo
不互相构成依赖关系,为了避免对原来的开发环境造成破坏,gulp
压缩放到后面进行。
流程如下
1 2 3 4 5 6 7 8 9 10 11 12 13 mkdir gulp_dir && gulp_dirnpm install gulp gulp-clean-css gulp-html-minifier-terser gulp-htmlclean gulp-fontmin gulp-terser --save npm install gulp-cli -g cp ./blog/pubilc ./pubilcgulp
依赖包的版本如下:
1 2 3 4 5 6 7 8 > npm list gulp@ /home/aero/blog/gulp_dir ├── gulp-clean-css@4.3.0 ├── gulp-fontmin@0.7.4 ├── gulp-html-minifier-terser@7.1.0 ├── gulp-htmlclean@2.7.22 ├── gulp-terser@2.1.0 └── gulp@4.0.2
各个包的用途如下:
项目 用途 gulp-clean-css
压缩 css gulp-html-minifier-terser
压缩 HTML gulp-htmlclean
压缩 html gulp-uglify
压缩 js gulp-fontmin
压缩字体
目前没有找到更方便的 gulpfile.js
配置文件,沿用的是这里的:
1 [使用gulp压缩博客静态资源 | Akilar の糖果屋](https :
效果嘛,不能说是不尽人意,只能是没有尽到一点意义,可能是文件量太小的缘故吧。
压缩前:
1 2 > du -s public 1848 public
压缩后:
1 2 > du -s public 1864 public
但凡量纲大些都看不出来差别在哪儿。
搜索引擎优化 采用的是这个 Github 项目,可以实现自动推送链接cjh0613/hexo-submit-urls-to-search-engine
1 npm install hexo-submit-urls-to-search-engine --save
详细可见其官方文档:hexo-submit-urls-to-search-engine 中文文档
进行站长验证 申请好验证标记,三家都能申请
申请好的结果是这样的:
1 2 3 4 5 <meta name ="google-site-verification" content ="MXalOlJ3giNXLmtfFmg3nqhepdzSTZIx2amET1sJ90M" /> <meta name ="msvalidate.01" content ="MXalOlJ3giNXLmtfFmg3nqhepdzSTZIx2amET1sJ90M" /> <meta name ="baidu-site-verification" content ="codeva-YhIH7IUWWW" />
然后编辑主题文件 _config.butterfly.yml
分别填入对应的位置
1 2 3 4 5 6 7 site_verification: - name: google-site-verification content: MXalOlJ3giNXLmtfFmg3nqhepdzSTZIx2amET1sJ90M - name: baidu-site-verification content: codeva-YhIH7IUWWW - msvalidate.01 content: MXalOlJ3giNXLmtfFmg3nqhepdzSTZIx2amET1sJ90M
构建并发布网站后,就可以一个个去点击验证了。
这里的密钥泄露没关系,网站验证密钥仅用于验证。后面的推送密钥绝对不能泄露。
设置搜索引擎推送 在主题配置站点配置 _config.yml
中添加:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 deploy: - type: cjh_google_url_submitter - type: cjh_bing_url_submitter - type: cjh_baidu_url_submitter hexo_submit_urls_to_search_engine: submit_condition: count count: 10 period: 900 google: 1 bing: 1 baidu: 1 txt_path: submit_urls.txt baidu_host: https://0xac.cn baidu_token: BAIDU_TOKEN bing_host: https://0xac.cn bing_token: BING_TOKEN google_host: https://0xac.cn google_key_file: project.json
特别注意,这里的推送文件的密钥绝对不能公开,尤其是放到公共仓库中。 保不准谁拿着密钥干一些奇奇怪怪的事情
Actions 配置 如果采用的是 Actions,具体步骤可见后面的 Action 自动构建,可以这样子设置 Action:
1 2 3 4 5 6 7 8 9 - name: Prepare SEO run: | sed -i "s|BAIDU_TOKENS|${{ secrets.BAIDU_TOKEN }}|g" _config.yml sed -i "s|BING_TOKENS|${{ secrets.BING_TOKEN }}|g" _config.yml echo -E "${{ secrets.GOOGLE_JSON }}" > project.json - name: Push to search engine run: | hexo deploy
然后在 Actions 环境变量里面设置:
1 2 3 4 5 6 7 8 BAIDU_TOKEN=xxxxxxxxxxx BING_TOKEN=xxxxxxxxxxxxx GOOGLE_JSON= { "type" : "service_account" , xxxxxx }
GOOGLE_JSON
的密钥有多行,上面的 echo -E
设置了避免写入时转义,避免读取出错
完整配置可见后面的 [Actions 合辑](#Actions 合辑)
优化的其他问题 Google Search Console 的 DNS 验证有些问题,本地已经查得到解析了,可依然提示验证不通过。
1 dig +nocmd google-site-verification.0xac.cn txt +noall +answer
Google Search Console 必须采样 .json 文件作为验证密钥,实际使用不容易安全导入到公开仓库。
评论区 评论区可以采用免费的 Valine 或需要自己搭建服务器的 Artalk,二者都比较简单,并且支持评论审核,可有效避免意外。
静网页托管 方案 特点 难度 价格 SEO 对象储存+CDN+DNS 自主可控 复杂 贵 完整对搜索引擎优化 Pages + DNS 证书由 Pages 服务提供 稍复杂 只付域名 能做 纯 Pages 无自定义域名 简单 无 稍微能做
讲实话,真要追求推广,应该去写公众号
各大 Pages 服务 Pages 服务放到最后进行讲解,这里只会将 Github Pages 的使用,其他的都差不多。由于 Cloudflare 经常被用于奇怪的用途,导致服务非常不稳定,建议不要用。
部署到 CDN 一般只有国内才需要这样子搞,步骤比较多:
在云服务商买一个最便宜,带 IP 地址的云服务器,因为备案必须带 IP 在云服务商里面申请备案,备案有一定限制,可以在服务商上了解 等云服务商提交信息到管理局,填短信验证 ICP/IP地址/域名信息备案管理系统 (miit.gov.cn) 如果没收到,可以申请重发 等备案号下来 创建对象存储桶,选择对应区域 创建 CDN 并配置好域名 设置 DNS 解析 虽然 DNS 可以解析到存储桶上,但这样流量费会非常贵。
每个厂商的流程都一定区别,并且某些厂商一直在变,还有直接取消已有服务的情况,不好具体说明,详细可见各厂商提供的文档。
备案域名需要更换,请直接申请新域名备案,备案下来后注销旧域名即可。不要听客服引导直接注销备案,那样子会产生空壳主体,很麻烦。
设置境内加速的 CDN 在境外是无法访问的
国内运营商的境内流量包比较便宜,境外的比较贵。
部署到网页自动生成服务 比较简单,这里推荐用 Qexo,带网页 GUI,可以实现全程白嫖,只需要申请一系列密钥,然后就能开始部署,全程不涉及命令行操作,非常简单。
Github 是 git 存储仓库,可以有之前的博客 Vercel 是 pages 服务 reCAPTCHA 是网页端登录验证 然后点击以下链接一键部署https://vercel.com/new/clone?repository-url=https://github.com/am-abudu/Qexo 数据库选 PostgreSQL。
不明白可以看官方文档,写的有点复杂:Qexo | Qexo (oplog.cn)
其中 Vercel 的 Project ID 需要在这里获得:
1 https://vercel.com/<your_name>/<you_project>/settings
Actions 合辑 博客的核心是采用 GitHub Actions 进行自动化构建,整个结构如下:
这里简要介绍下 GitHub Actions, Actions 主要由一下几个部分构成:
构成 解释 意义 workflow 工作流 一个 .yml
一个工作流 job 任务 workflow 由一个或多个 jobs 构成,任务直接互相独立 step 步骤 job 由 step 构成,是上下文衔接传递的,共享环境,工作目录固定 action 动作 执行的命令,相当于在命令行顺序执行
仓库目录下的 .github/workflows/xxxxxx.yml
就是一个工作流,在 .github/workflows
下的任何 .yml
结尾的文件,都是一个工作流,只要推送到仓库,就会自动识别 .yml
里面的配置,并构建。
只需要记住以下几点,就能避免掉很多令人困惑的情况:
workflow 之间完全隔离,无法互联。 job 之间互相隔离,job 之间传输文件,必需采用“工件传递”的方法。 step 之间环境变量隔离,step 之间传递环境变量需要额外配置。 actions 之间共享环境变量。 注意,无论是哪一种配置,都需要在本地搭建好博客,然后推送到 git 仓库中去,在云端构建。
直接在云端写入 Actions 控制文件是行不通的,容易出错
Actions 部署前准备 目前的方案是 Github 的私有仓库 + 公开仓库 pages + 腾讯云 CDN 链条,使用云端本地双备份,境内境外双路线。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 git clone git@github.com:hxac/blog.git cd blogsudo npm install hexo-cli -g git clone https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly npm install
请务必在本地测试好 hexo generate
和 hexo server
,确保可以看到非空白的网页。
这一步的核心是确保生成对应的 package.json
和 package-lock.json
文件,以便构建的时候可以命中构建缓存,加快构建速度。
然后新建文件夹,放入 Actions 配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 mkdir .githubmkdir .github/workflowstouch .github/workflows/onDaily.ymltouch .github/workflows/onMonthly.ymltouch .github/workflows/onPush.yml
部署到 COS 并刷新 CDN 环境变量设置 需要准备的环境变量:
Google Search 的密钥不好配置,建议在私有库中配置。
大部分厂商的配置大同小异,就不一一列举了
配置其他厂商,可以参考前面的代码自己写 Actions,但请确保脚本可以在本地正常运行后,再写成 Actions 文件
不会写 Actions?跟着完整的流程下来就能写了,不难就是掉亿点点头发
全部的环境变量如下,在仓库设置页面的 Settings
-> Actions
-> Secrets
里面逐个添加即可。
1 2 3 4 5 6 7 TENCENTCLOUD_SECRET_ID TENCENTCLOUD_SECRET_KEY TENCENTCLOUD_REGION COS_BUCKET RQ_DOMAIN BAIDU_TOKENS BING_TOKENS
Actions 文件 完整的 Actions 配置文件如下,一般是在仓库里面的 blog/.github/workflows/my_workflow.yml
。比方说这里的是 blog
仓库,my_workflow.yml
文件名任意取,只要是 .yml
结尾的,在 ./.github/workflows
下的都会被执行。
默认为有 push
到 Github 就会自动执行。
注意,这里的 branches
是 master
,而新版本的 Github 默认分支是 main
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 name: Deploy Blog to CDN on: push: branches-ignore: [master ] schedule: - cron: "3 3 * * *" workflow_dispatch: env: TZ: Asia/Shanghai jobs: generate-pages: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Use Node.js 18 uses: actions/setup-node@v3 with: node-version: 18 cache: 'npm' - name: Cache node modules uses: actions/cache@v3 id: cache with: path: node_modules key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- - name: Prepare Hexo run: | npm install hexo-cli -g - name: Prepare Themes run: | mkdir -p blog/themes git clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly - name: Prepare Blog if: steps.cache.outputs.cache-hit != 'true' run: npm install - name: Generate Blog run: | hexo clean hexo generate - name: Prepare SEO run: | sed -i "s|BAIDU_TOKENS|${{ secrets.BAIDU_TOKEN }}|g" _config.yml sed -i "s|BING_TOKENS|${{ secrets.BING_TOKEN }}|g" _config.yml - name: Push to search engine run: | hexo deploy - name: Upload generated public pages uses: actions/upload-artifact@v3 with: name: public_pages path: ./public compress-pages: runs-on: ubuntu-latest needs: generate-pages steps: - uses: actions/checkout@v3 - name: Use Node.js 18 uses: actions/setup-node@v3 with: node-version: 18 cache: 'npm' - name: Download generated public pages uses: actions/download-artifact@v3 with: name: public_pages path: ./public - name: Install Gulp run: | npm install gulp gulp-clean-css gulp-html-minifier-terser gulp-htmlclean gulp-terser --save npm install gulp-cli -g - name: Compress Pages run: | gulp - name: Upload compressed public pages uses: actions/upload-artifact@v3 with: name: public_pages path: ./public upload-store: runs-on: ubuntu-latest needs: compress-pages steps: - uses: actions/checkout@v3 - name: Download compressed public pages uses: actions/download-artifact@v3 with: name: public_pages path: ./public - name: Install COS Tool run: | wget https://github.com/tencentyun/coscli/releases/download/v0.13.0-beta/coscli-linux mv coscli-linux coscli chmod 755 coscli ./coscli --version touch ~/.cos.yaml - name: Depoly blog run: | ./coscli config add -b ${{ secrets.COS_BUCKET }} -r ${{ secrets.TENCENTCLOUD_REGION }} -a public-page ./coscli config set --secret_key ${{ secrets.TENCENTCLOUD_SECRET_KEY }} --secret_id ${{ secrets.TENCENTCLOUD_SECRET_ID }} ./coscli rm cos://public-page/ -rf ./coscli cp public/ cos://public-page/ -r ./coscli du cos://public-page/ reflush-cdn: runs-on: ubuntu-latest needs: upload-store steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: '3.9' - name: Install Cloud Service Cli run: | pip install tccli - name: Load Tencent Cloud API run: | echo export TENCENTCLOUD_SECRET_ID="\"${{ secrets.TENCENTCLOUD_SECRET_ID }}\"" > .env echo export TENCENTCLOUD_SECRET_KEY="\"${{ secrets.TENCENTCLOUD_SECRET_KEY }}\"" >> .env echo export TENCENTCLOUD_REGION="\"${{ secrets.TENCENTCLOUD_REGION }}\"" >> .env - name: reflush CDN run: | source .env tccli cdn PurgePathCache --cli-unfold-argument --Paths "https://${{ secrets.RQ_DOMAIN }}/" --FlushType flush
申请证书并提交到 CDN 请确保有独立的一级域名,否则可能申请不了证书
环境变量设置 需要准备的环境变量:
环境变量 说明 申请链接 TENCENTCLOUD_SECRET_ID
腾讯云 API ID 信息中心 - 云 API TENCENTCLOUD_SECRET_KEY
腾讯云 API key 信息中心 - 云 API TENCENTCLOUD_REGION
云服务区域 COS_BUCKET
存储桶所在区域 ID 存储桶列表 - 对象存储 RQ_DOMAIN
CDN 加速的域名 你的域名 0xac.cn
DP_ID
DNSPod ID API 密钥 DP_KEY
DNSPod Key
这里的 RQ_DOMAIN
建议是一级域名 0xac.cn
不要是 blog.0xac.cn
。因为申请证书默认申请 0xac.cn
和 *.0xac.cn
泛域名的。
大部分厂商的配置大同小异,需要自行修改 Actions 文件配置。
配置其他厂商,可以参考前面的代码自己写 Actions,但请确保脚本可以在本地正常运行后,再写成 Actions 文件。
Let’sencrypt 证书申请有请求频率限制,确保能够在本地正常使用,再上云。
全部的环境变量如下,在 Settings
-> Actions
-> Secrets
里面逐个添加即可。
1 2 3 4 5 6 7 TENCENTCLOUD_SECRET_ID TENCENTCLOUD_SECRET_KEY TENCENTCLOUD_REGION COS_BUCKET RQ_DOMAIN DP_ID DP_KEY
和上一篇不同的是多了 DNSPod 的 API 密钥:
定时任务设置 这里设置为定时执行 cron: "1 1 1 1/2 1"
。
根据文档:Events that trigger workflows - GitHub Docs
1 2 3 4 5 6 7 8 9 ┌───────────── minute (0 - 59) │ ┌───────────── hour (0 - 23) │ │ ┌───────────── day of the month (1 - 31) │ │ │ ┌───────────── month (1 - 12 or JAN-DEC) │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ * * * * *
也就是说 corn
的语法是由空格分割,每个位置如下:
填入数字有范围限制,也就是是:
位数 含义 范围 1 分钟 0-59 2 小时 0-23 3 日 1-31 4 月 1-12 5 周 0-6
只有到指定日期时间,cron
才会触发任务运行。
同时一些特殊字符表示:
符号 表示 例子 *
任何时间 15 * * * *
每小时的 xx:15 执行一次,
多个时间 2,10 * * * *
每小时的 xx:02 和 xx:10 执行一次-
时间范围 10 4-6 * * *
每天的 4-6 的第十分钟执行一次 ,即 04:10, 05:10: 06:10 执行一次/
时间周期 5/15 * * * *
每小时的 5 分钟后,每 15 分钟执行一次,即每小时的 xx:05, xx:20, xx:35, xx:50 分钟执行一次
换句话说 1 1 1 1/2 1
表示在每隔 2 月的 1 分,1小时, 1 日,第 1 周,执行一次,也就是会在以下时间执行:
1 2 3 4 5 6 1 月 1 日 - 第 1 周 - 01:01 3 月 1 日 - 第 1 周 - 01:01 5 月 1 日 - 第 1 周 - 01:01 7 月 1 日 - 第 1 周 - 01:01 9 月 1 日 - 第 1 周 - 01:01 11 月 1 日 - 第 1 周 - 01:01
Let’sencrypt 证书 有效期为三个月,为了避免提前失效,可以两个月申请一次。
Actions 文件 这里的 branches-ignore: [master]
忽略分支需要设置正确,否则一旦有 push
请求到仓库就会被执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 name: Generate Certificates on: push: branches-ignore: [master ] schedule: - cron: "1 1 1 1/2 1" workflow_dispatch: jobs: generate-Key: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 - name: Clone ACME.SH run: | curl https://get.acme.sh | sh - name: Config ACME.sh if: ${{ env.ACME == 1 }} run: | echo "DEFAULT_ACME_SERVER='https://acme-v02.api.letsencrypt.org/directory'" > /home/runner/.acme.sh/account.conf echo "SAVED_DP_Id='${{ secrets.DP_ID }}'" >> /home/runner/.acme.sh/account.conf echo "SAVED_DP_Key='${{ secrets.DP_KEY }}'" >> /home/runner/.acme.sh/account.conf - name: Request Let's Encrypt for issue certificates if: ${{ env.ACME == 1 }} run: | /home/runner/.acme.sh/acme.sh --issue -d "${{ secrets.RQ_DOMAIN }}" -d "*.${{ secrets.RQ_DOMAIN }}" --dns dns_dp - name: Load certificates and key from ACME.SH if: ${{ env.ACME == 1 }} run: | echo export PUBLIC_KEY="\"$(cat /home/runner/.acme.sh/${RQ_DOMAIN}_ecc/${RQ_DOMAIN}.key)\"" > .env echo export PUBLIC_KEY="\"$(cat /home/runner/.acme.sh/${RQ_DOMAIN}_ecc/${RQ_DOMAIN}.key)\"" >> .env - name: Load certificates and key from ENV if: ${{ env.ACME == 0 }} run: | echo PUBLIC_KEY="\"${{ secrets.PUBLIC_KEY }}\"" > .env echo PRIVATE_KEY="\"${{ secrets.PRIVATE_KEY }}\"" >> .env - name: Install Tencent Cloud CLi run: | pip install tccli - name: Load Tencent Cloud API run: | echo export TENCENTCLOUD_SECRET_ID="\"${{ secrets.TENCENTCLOUD_SECRET_ID }}\"" >> .env echo export TENCENTCLOUD_SECRET_KEY="\"${{ secrets.TENCENTCLOUD_SECRET_KEY }}\"" >> .env echo export TENCENTCLOUD_REGION="\"${{ secrets.TENCENTCLOUD_REGION }}\"" >> .env - name: Upload public key and private key run: | source .env echo export CERT_ID="\"$(tccli ssl UploadCertificate --cli-unfold-argument --CertificatePublicKey "${PUBLIC_KEY}" --CertificatePrivateKey "${PRIVATE_KEY}" --filter CertificateId)"\" >> .env - name: Update public key and private key to CDN run: | source .env tccli cdn UpdateDomainConfig --cli-unfold-argument --Domain "${{ secrets.RQ_DOMAIN }}" --Https.Switch on --Https.CertInfo.CertId "${CERT_ID}" tccli cdn UpdateDomainConfig --cli-unfold-argument --Domain "api.${{ secrets.RQ_DOMAIN }}" --Https.Switch on --Https.CertInfo.CertId "${CERT_ID}"
直接部署到 pages 页面 新建一个和用户名一样的仓库 hxac/hxac.github.io
,比方说用户名是 hxac
,那么仓库就是 hxac.github.io
。 需要在仓库里设置 Settings -> Pages -> Source,将 branch 改为 gh-pages
确保 pages 的显示页面是正确的。 需要在仓库里设置 Settings -> Deploy keys,里面添加。 这里由于已经有了域名+CDN 了,做这个新页面只是用来备份,因此有个切换博客域名的操作:
1 2 3 - name: Change Domain run: | sed -i 's|^url: https:\/\/0xac\.cn|url: https:\/\/hxac.github.io|g' _config.yml
环境变量 添加 ssh-key
在私有仓库的环境变量 里面,然后推送到公开仓库中去。
仓库 可见性 用途 blog
私有 私有文件,构建原文件,Actions 文件 username.github.io
公开 pages 页面展示
有备案的建议移除备案,github.io
子域名是无法备案的/
然后设置好环境变量
Actions 文件 注意,这里的:
1 2 3 4 - name: Change Domain run: | sed -i 's|^url: https:\/\/0xac\.cn|url: https:\/\/hxac.github.io|g' _config.yml sed -i '/custom_text/d' _config.butterfly.yml
需要根据自己的情况配置,注意 sed
命令,是需要转义的。
如果不采用国内 + 国外双模式,可以直接删掉这一行 actions。
双模式可以加快国内和国外的访问速度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 name: Deploy Blog to pages on: push: branches: [ "master" ] workflow_dispatch: env: TZ: Asia/Shanghai jobs: generate-pages: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Use Node.js 18 uses: actions/setup-node@v3 with: node-version: 18 cache: 'npm' - name: Cache node modules uses: actions/cache@v3 id: cache with: path: node_modules key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- - name: Prepare Hexo run: | npm install hexo-cli -g - name: Prepare Themes run: | mkdir -p blog/themes git clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly - name: Prepare Blog if: steps.cache.outputs.cache-hit != 'true' run: npm install - name: Change Domain run: | sed -i 's|^url: https:\/\/0xac\.cn|url: https:\/\/hxac.github.io|g' _config.yml sed -i '/custom_text/d' _config.butterfly.yml - name: Generate Blog run: | hexo clean hexo generate - name: Upload compressed public pages uses: actions/upload-artifact@v3 with: name: public_pages path: ./public deploy-pages: runs-on: ubuntu-latest needs: generate-pages steps: - uses: actions/checkout@v3 - name: Download compressed public pages uses: actions/download-artifact@v3 with: name: public_pages path: ./public/ - name: No jekyll run: | touch .nojekyll - name: Deploy to gh-pages uses: s0/git-publish-subdir-action@develop env: REPO: git@github.com:hxac/hxac.github.io.git BRANCH: gh-pages FOLDER: ./public/ SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_PRIVATE_KEY }}
参考资料 【1】:actions/setup-python: Set up your GitHub Actions workflow with a specific version of Python 【2】:Platform-CUF/use-gulp: gulp资料收集 (github.com)