阿里云部署Hugo与备案实践

如需更优质的阅读体验,欢迎访问个人站点。原文地址

前言

想了老半天终于还是把博客搬到国内服务器上了,简单的记录一下搬迁过程。主要记录了证书申请&管理,nginx部署,顺便附带一些服务器管理和防爆破的小技巧。

部署实践

环境准备

服务器

从阿里云购买云服务器一台,配置如下:

  • 实例规格: ecs.e-c1m1.large
  • CPU: 2vCpu
  • 内存: 2GiB
  • 带宽: 3Mbps
  • 存储: 40GiB
  • 操作系统: Alibaba Cloud Linux 3.2104 LTS 64位 UEFI版

我在安装操作系统时选了禁止root登录,所以需要知道root和ecs-user密码,其中root密码用于执行sudo命令。

因为是需要作为网站的入口使用,所以需要开放80和443端口。具体步骤为 实例详情页-进入安全组tab-管理规则,在规则管理页面,添加入方向的80和443规则,开放给所有IP。可以点击快速添加,勾选80和443。

执行命令hostnamectl set-hostname ${HOST_NAME},其中HOST_NAME更换为需要更改的主机名 修改后可通过 hostname 验证,并重启验证是否永久生效

Nginx

使用nginx作为站点的HTTP容器。

登录服务器后,执行 sudo yum install nginx 进行安装,遇到提示后按y表示同意安装,如果需要输入root密码则输入root密码。

输入命令 nginx -v 来验证nginx是否安装完成,输出版本即为安装完成,比如我安装的是 1.20.1 ,则输出为 nginx version: nginx/1.20.1

输入命令sudo nginx,来启动nginx。可以通过 ps -ef|grep nginx 命令来验证进程是否启动。如正常启动,可以看到一个root用户启动的master进程和若干个nginx用户启动的worker进程。(数量取决于核心数,由配置项 worker_processes auto; 控制) 如果需要在浏览器中验证页面是否可以访问,需要在阿里云的安全组中放行80端口。可以访问的话可以在浏览器中看到一个经典的Nginx小白页面。

Hugo部署

首先,需要规划一下我们网站部署的目录,以便修改nginx的配置。这里简单介绍一下nginx如何配置静态网站。我们的网站都被打包成静态文件放在服务器的某个目录里,而nginx可以通过指定root到这个目录来实现访问,网站的入口就是这个目录的index.html 。在Hugo中,这个目录就是运行hugo server后生成的 public/ 目录。所以,部署只需要把 public/ 目录放到服务器上,然后修改nginx的配置文件就好,网站的更新也只需要更新这个目录,而不需要修改nginx配置。 但是考虑到增量部署的场景,不断在目录中更新是也可以解决文件的新增和修改,无法删除文件。所以,在我的规划中,我每一次的部署都会放一个将打包出来的内容放到一个新的目录中。为了不修改nginx的配置,并保证目录切换的时间足够短(提供高的可访问性,虽然必要性不大),我对比了三个方案,最终选择在自动部署方案中采用了软链接的方式。

  • 先删除后上传:上传时间较长,会有比较长时间的一段无法提供服务的时间

  • 先上传后删除:一个相当比较好的方案,具体步骤为 上传新版本-删除老版本-重命名新版本,但是上一个版本删除后无法回滚,不过一般也用不到,再部署修复即可。(这里也可以把删除老版本改为重命名,但是依然需要做历史版本的管理)

  • 软链接:具体步骤为 上传新版本-修改软链接-删除老版本。在更新了软连接好就可用了。

    其实使用git更新也是一个非常好的方案,维护成本也不高。只是长期使用git的历史堆积会使得文件夹变得比较大,需要定期清理历史版本信息来控制文件大小。

    最初方案下图所示:

    /website/website/data/website/data/blog_xxx 的属主为ecs-user,权限为755,需要保证nginx的读和部署用户ecs-user的读写。

nginx配置修改

按照规划,更改nginx的配置,在 /etc/nginx/default.d/ 下新建文件 blog.conf 输入以下内容:

conf 复制代码
server_name ${YOUR_DOMAIN} # 替换为自己的域名
location / { 
	root /website/blog/;
	index index.html;
}

这段配置用于将root指向 /website/blog 目录,同时,需要删除原本 nginx.conf 中的内容,注释即可,可参考以下代码。

conf 复制代码
listen       80;
listen       [::]:80;
#server_name  _;
#root         /usr/share/nginx/html;

# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;

#error_page 404 /404.html;
#    location = /40x.html {
#}

#error_page 500 502 503 504 /50x.html;
#    location = /50x.html {
#}
}

还需要更改 nginx.conf 中的user为 /website/blog 目录的属主。

手动部署

手动部署不做赘述,将本地的 public/ 拉到服务器的 /website/blog 目录下就可以看到网站了(注意,是要用 hugo serve 重新生成过的,不是开发的直接拿上去)。

自动部署(CI/CD集成)

自动部署要做的事情其实不也复杂,就是在Github Action中添加一步,把 public/ 部署到服务器上,然调用服务器上的脚本就可以了。 先来写一个脚本,用于实现软链接的创建/更新和历史版本数量的管理,脚本位于服务器的某个目录就可以了,我放在了 ~/scripts/ 下。

bash 复制代码
#!/bin/bash

# 指定目录路径
DIRECTORY="/website/data/blog"
# 软连接路径
SOFT_LINK="/website/blog"

# 获取所有符合 blog_xxxx 格式的目录,并按创建时间排序
directories=($(ls -d "$DIRECTORY"/blog_* | xargs -I {} stat --format '%Y {}' {} | sort -n | cut -d' ' -f2-))

# 获取目录数量
num_directories=${#directories[@]}

# 如果存在目录,则更新或新建软连接到最新创建的目录
if [ $num_directories -gt 0 ]; then
    latest_directory=${directories[-1]}
	echo "将使用目录 $latest_directory 作为工作目录"
    # 如果软连接不存在,则新建软连接
    if [ ! -L "$SOFT_LINK" ]; then
        ln -s "$latest_directory" "$SOFT_LINK"
    else
        # 如果软连接已存在,则更新软连接
        ln -sfn "$latest_directory" "$SOFT_LINK"
    fi
fi

# 如果目录数量大于2,则删除最早创建的目录,直到只剩下2个
while [ $num_directories -gt 2 ]; do
    # 删除最早创建的目录
	echo "删除目录 ${directories[0]}"
    rm -rf "${directories[0]}"
    # 更新目录列表
    directories=("${directories[@]:1}")
    num_directories=$((num_directories-1))
done

# 如果目录数量少于或等于2,不需要进一步操作
if [ $num_directories -le 2 ]; then
    echo "目录数量符合要求,无需进一步操作。"
else
    echo "操作完成,现在目录数量为 $num_directories。"
fi

chmod -R 755 "$DIRECTORY"/blog_*

写好部署脚本之后,需要添加执行权限 chmod +x deploy_blog.sh ,然后,更新一下Github Action的流水线定义文件,添加了两个步骤。

yaml 复制代码
# Sample workflow for building and deploying a Hugo site to GitHub Pages
name: Deploy Hugo site to Pages

on:
  # Runs on pushes targeting the default branch
  push:
    branches: ["main"]

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
  contents: read
  pages: write
  id-token: write

# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
  group: "pages"
  cancel-in-progress: false

# Default to bash
defaults:
  run:
    shell: bash

jobs:
  # Build job
  build:
    runs-on: ubuntu-latest
    env:
      HUGO_VERSION: 0.139.3
    steps:
      - name: Get current date
        id: gen-id
        run: echo "::set-output name=time::$(date +'%Y%m%d%H%M%s')"
      - name: Install Hugo CLI
        run: |
          wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \
          && sudo dpkg -i ${{ runner.temp }}/hugo.deb
      - name: Install Dart Sass
        run: sudo snap install dart-sass
      - name: Checkout
        uses: actions/checkout@v4
        with:
          submodules: recursive
      - name: Install Node.js dependencies
        run: "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true"
      - name: Build with Hugo
        env:
          HUGO_CACHEDIR: ${{ runner.temp }}/hugo_cache
          HUGO_ENVIRONMENT: production
        run:
          hugo --baseURL=https://www.zanks.link/
      - name: Deploy Pages
        uses: peaceiris/actions-gh-pages@v3
        with:
            PERSONAL_TOKEN: ${{ secrets.PERSONAL_TOKEN }}
            EXTERNAL_REPOSITORY: ZaNksC/ZaNksC.github.io
            PUBLISH_BRANCH: main
            PUBLISH_DIR: ./public
            commit_message: ${{ github.event.head_commit.message }}
      - name: scp ssh pipelines
        uses: cross-the-world/ssh-scp-ssh-pipelines@latest
        with:
          host: ${{ secrets.HOST }}
          port: ${{ secrets.PORT }}
          user: ${{ secrets.USER }}
          key: ${{ secrets.PWD }}
          scp: |
            ./public/* => /website/data/blog/blog_${{ steps.gen-id.outputs.time }}
          last_ssh: |
            /home/ecs-user/scripts/deploy_blog.sh

${{ secret.HOST }} 等三个变量,则需要在Github代码仓 - settings -Secrets And variables - Actions 中添加,分别表示 IP地址、ssh端口(如果改过)、部署用户和部署用户登陆凭证(密码用 pass: ${{ VAR }} , 密钥用 key: ${{ VAR }}, 推荐用密钥)。

SSL证书(可选)

觉得没有小绿锁没有格调,又觉得有了小绿锁续签太过麻烦?没关系,接下来就教你从申请到配置再到自动续签的全过程。

SSL证书将通过acme.sh客户端向Let's Encrypt申请(免费!!!)。

acme.sh 是一个优秀的证书管理客户端。可以非常简单的做到申请与续签。

登录服务器,找一个目录,安装 acme.sh ,我是在 /home/ecs-user/scripts 下,输入以下命令(国内版)进行安装。[email protected] 替换为自己的邮箱(没有git的话,sudo yum install git 来安装)。

bash 复制代码
git clone https://gitee.com/neilpang/acme.sh.git
cd acme.sh
./acme.sh --install -m [email protected]

输出OK表示已经安装完成了。当然,也可以输入 ./acme.sh version 来查看版本来验证。

在安装过程,acme完成了三件事情

  • ~/.acme.sh/ 目录下完成了安装
  • ~/.bashrc 中新增了一行,用于配置别名,可以在任意目录输入 acme.sh 命令(需要 source ~/.bashrc 或重新登录生效)
  • 创建了一个crontab的任务,用于检测即将过期的证书来实现自动续签,可以通过命令 crontab -l 查看

安装完成之后,因为acme的默认证书签发机构不是Let's Encrypt所以需要手动配置一下。输入命令 acme.sh --set-default-ca --server letsencrypt 来完成配置。当然,也可以在申请命令中添加 --server letsencrypt 来指定机构。

接下来就是主题了,开始申请证书。在申请证书的过程中,机构会验证我们是否为域名的主人,在泛域名证书 *.zanks.link (以下命令中替换为自己的域名)的申请中,只能使用DNS方式来验证。输入以下命令来使用dns验证方式申请证书。

bash 复制代码
acme.sh  --issue  --dns -d *.zanks.link \
 --yes-I-know-dns-manual-mode-enough-go-ahead-please

执行完成之后,会提示需要添加一条TXT的解析记录。添加完成之后,根据DNS解析时间等待一段时间(NameSilo的解析实在是太慢了,改用腾讯之后体验非常好,秒解析),建议等待半小时,然后执行以下命令。

bash 复制代码
acme.sh  --renew   -d *.zanks.link \
  --yes-I-know-dns-manual-mode-enough-go-ahead-please

申请证书也可以通过域名商API的方式,参考 【官方文档】 (我试了一下没成功,感觉是网络问题) 申请成功后会出现4个文件,分别是 domainname.cerdomainname.keyca.cerfullchain.cer。主要使用的是 fullchain.cer 文件和 domainname.key 文件。fullchain.cer 提供了完整的证书链,而 domainname.key 是用于解密由客户端发送的加密数据的私钥。

证书生成之后,安装到nginx,不要手动去拷贝,使用以下命令。

bash 复制代码
acme.sh --install-cert -d *.zanks.link \
--key-file       /home/ecs-user/.acme.sh/*.zanks.link/*.zanks.link.key  \
--fullchain-file /home/ecs-user/.acme.sh/*.zanks.link/fullchain.cer \
--reloadcmd     "service nginx force-reload"

其中 --key-file 的参数修改为 domainname.key 的文件路径,--fullchain-file 的参数修改为 fullchain.cer 的文件路径。

之后就是修改nginx的配置开启443端口,代码如下:

conf 复制代码
    server {
        listen       443 ssl http2;
        listen       [::]:443 ssl http2;
        #server_name  _;
        #root         /usr/share/nginx/html;

        ssl_certificate "/path/to/fullchain.cer";
        ssl_certificate_key "/path/to/domainname.key";
        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout  10m;
        ssl_ciphers PROFILE=SYSTEM;
        ssl_prefer_server_ciphers on;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        #error_page 404 /404.html;
        #    location = /40x.html {
        #}

        #error_page 500 502 503 504 /50x.html;
        #    location = /50x.html {
        #}
    }

保持了跟http一样的配置,只是新增了一些ssl相关的配置,只需要将 fullchain.cerdomainname.key 的文件路径替换成之前申请出来的即可。

{{}} 配置好https之后,如果希望只能通过https访问,那么可以通过重定向配合HSTS来告诉浏览器使用https。需要修改 /etc/nginx/nginx.conf 来实现。

conf 复制代码
server {
	listen       80;
	listen       [::]:80;
	server_name  example.com www.example.com;
	return 301 https://$server_name$request_uri;
}


server {
	listen       443 ssl http2;
	listen       [::]:443 ssl http2;

	ssl_certificate "/path/to/fullchain.cer";
	ssl_certificate_key "/path/to/example.key";
	ssl_session_cache shared:SSL:1m;
	ssl_session_timeout  10m;
	ssl_ciphers PROFILE=SYSTEM;
	ssl_prefer_server_ciphers on;
	
	add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

	include /etc/nginx/default.d/*.conf;
}

{{}}

最后,就是在DNS解析中添加一条A记录,用于指向服务器。

DNS解析实践

由于服务器是没有IPv6地址的,为了使得网站支持IPv6,我在域名的解析规则中保留了Github Pages的IPv6站点。 最终,我的DNS解析规则(最后发现提交不了那么多条,腾讯云免费版的可解析条目有限,按需选择了)就是

subdomain domain Type Value
zanks.link A 我的服务器ip
www zanks.link A 我的服务器ip
zanks.link AAAA 2606:50c0:8000::153
www zanks.link AAAA 2606:50c0:8000::153
zanks.link AAAA 2606:50c0:8001::153
www zanks.link AAAA 2606:50c0:8001::153
zanks.link AAAA 2606:50c0:8002::153
www zanks.link AAAA 2606:50c0:8002::153
zanks.link AAAA 2606:50c0:8003::153
www zanks.link AAAA 2606:50c0:8003::153

通过 nslookup www.zanks.link 可以看到返回1个IPv4地址(自己的服务器地址)和多个IPv6地址(Github Pages)。

同时,分别通过 curl www.zanks.linkcurl -6 www.zanks.link 可以验证IPv4和IPv6的访问。

域名备案

当我们的服务器是国内IP时,要求域名进行备案,否则会无法使用。

国外注册商无法通过备案,需要转到国内

域名转移

我是在NameSilo上买的域名,已转入腾讯云。

首先,需要在NameSilo上解锁域名+获取转移码。转移码的获取可以通过点击 Send Email ,而且需要等到60天之后才可以转移。(为此拖延了好几个月才备案)

域名备案

ICP备案

ICP备案需要在服务器所在的云服务商处发起备份,比如我的域名在腾讯云管理,服务器却在阿里云,我是从阿里云发起的ICP备案。发起是比较简单的,按照规范填好订单直接发起即可,发起后会有专员审核,帮你修改掉简单的错误就提交到管理局了(有一个比较麻烦的是需要拍一个读保证书的视频)。如果有无法通过的则会退回来,比如之前是在NameSilo买的域名没办法备案。提交申请快的话一天就可以,提交之后会收到一个管理局的短信去阿里云备案流程里面输入一下,然后等审批就好。

备案第一次失败了,退回原因为,该网站备案在审核通过前网站已开通,把DNS的解析关了之后重新提交。

公安备案

公安备案需要在ICP备案完成后的30天内进行(不知道不备案会有什么后果,查了一下可能会切断dns解析)。按照阿里云给出的文档,在全国互联网安全管理平台申请主体(需要下载一个app做人脸认证),需要准备 身份证正面、身份证反面、手持身份证照片、网站域名证书等材料。

公安的备案相当麻烦,需要关闭评论等功能,提交之后会当地负责网安的民警来协助你,需要你提交材料,如实回答,如实禀报即可。(就是评论功能这块,需要验证能发送的人每个人都实名有点为难个人博客了)

番外:服务器安全加固

fail2ban

用于防止暴力密码破解,在高位端口被扫到后,黑客会进行暴力密码破解,即便是IP代理池进行破解,代理池中的IP也是有限的,可以通过暴力拆解多次失败后将IP加入黑名单来提高黑客的破解成本,毕竟小破服务器也没什么值得人家大张旗鼓破解的必要,别人也只想要个肉鸡。所以这个操作可以有效防止黑客的暴力密码破解。

安装

输入命令进行安装

bash 复制代码
sudo yum install fail2ban

通过以下命令管理fail2ban

bash 复制代码
sudo systemctl status fail2ban # 查看状态
sudo systemctl start fail2ban # 启动服务
sudo systemctl stop fail2ban # 停止服务
sudo systemctl restart fail2ban # 重启服务

配置

fail2ban的配置位于 /etc/fail2ban 目录下,通常自定配置写在 /etc/fail2ban/jail.local 中,使用命令复制 jail.conf 来自定义配置,进行微调。

bash 复制代码
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

可以使用命令 sudo fail2ban-client status 来检查fail2ban的配置

编辑 jail.local ,找到 [sshd] 模块进行修改。其中port如果改过,需要设置为配置的端口。

ini 复制代码
enabled = true 
port = 22222 
filter = sshd 
logpath = /var/log/secure 
maxretry = 5 
findtime = 5m 
bantime = 4h

修改后重启服务,并查看配置可以看到有一条生效的。

yaml 复制代码
sudo fail2ban-client status Status 
|- Number of jail: 1 
`- Jail list: sshd

登录选项设置

设置ssh登录方式为禁止root登录。由于机器需要使用Github Action进行部署,暂时无法禁用密码登录(实际上是可以的)

修改 /etc/ssh/sshd_config 中的 PermitRootLogin yesPermitRootLogin no 。 重启ssh服务 sudo systemctl restart sshd

高位端口

将ssh端口设置为高位端口,可以预防大部分的常规扫描。

修改 /etc/ssh/sshd_config 中的 #port 22port 22222 或其他端口。 重启ssh服务 sudo systemctl restart sshd

检查登录日志

查看ssh登录失败日志

bash 复制代码
sudo cat /var/log/secure | grep "Failed"

查看failed2ban的日志

bash 复制代码
sudo cat /var/log/fail2ban.log
相关推荐
十年编程老舅3 小时前
二本计算机,毕业=失业?
c++·程序员·编程·秋招·c++项目·春招·qt项目
远洋录9 天前
Ethan独立开发产品日报 | 2025-04-29
人工智能·程序员·副业·独立开发·赚钱
袁煦丞10 天前
Mdserver-web让服务器自由飞翔!:cpolar内网穿透实验室第590个成功挑战
前端·程序员·远程工作
考虑考虑10 天前
go使用gorilla/websocket实现websocket
后端·程序员·go
袁煦丞10 天前
每天省2小时!这个网盘神器让我告别云存储混乱(附内网穿透神操作)
前端·程序员·远程工作
袁煦丞10 天前
AI视频生成神器Wan 2.1:cpolar内网穿透实验室第596个成功挑战
人工智能·程序员·远程工作
程序员马晓博10 天前
还是聊聊吧:"大龄"程序员失业的一年
前端·程序员
京东云开发者10 天前
云交易技术对接全景
程序员
京东云开发者10 天前
自己写插件-实现时间戳自由
程序员
渭雨轻尘_学习计算机ing11 天前
二叉树构建算法全解析
算法·程序员