前言
经过对比之后,最终还是选择了Halo,因为Halo更加符合我的操作习惯,用起来更加顺手。Halo更新至Halo 2后,根据官方的介绍,已经是一个建站工具,不单单是个简单的博客,本次已搭建博客教程为主,更多功能大家可以参考官方资料,自己去摸索。
本次搭建的是Halo 2,因为 Halo 2.0 的底层架构变动,无法兼容 1.x 的数据,导致无法平滑升级,所以需要进行数据迁移。为此,官方提供了从 Halo 1.5 / 1.6 版本迁移的插件。官方提供了迁移教程:点击打开。在进行迁移之前,有几点注意事项和要求,如果你目前无法满足,建议先暂缓迁移。
Halo简介
相关
Github项目地址:https://github.com/halo-dev/halo
官方还提供在线体验:
用户名:
demo
密码:
P@ssw0rd123..
Halo 2介绍
DEMO
特性
代码开源——Halo 的项目代码开源在 GitHub 上且处于积极维护状态,截止目前已经发布了 109 个版本。你也可以在上面提交你的问题或者参与代码贡献。
易于部署——推荐使用 Docker 的方式部署 Halo,便于升级,同时避免了各种环境依赖的问题。统一管理在工作目录中的应用数据也能方便地进行备份和迁移。
插件机制——支持在插件运行时为系统添加新功能,同时保持 Halo 自身的简洁轻量。这种灵活的插件机制让用户根据自身需求自由扩展 Halo 的功能,帮助用户实现富有想象力的站点。
模板机制——支持自定义配置、主题预览、多语言等功能。这种灵活的模板系统让用户可以针对自己的需求进行自定义配置,为网站带来更加个性化的外观和交互体验。
附件管理——支持多种存储策略,并支持通过插件扩展外部存储位置,可以让用户更加灵活地地上传、查看和管理附件。
搜索引擎——内置全文搜索引擎,支持关键字搜索文章和页面内容。同时支持通过插件扩展外部搜索引擎,做到让用户按需选择、自由扩展。
安装Docker和Docker Compose
本次搭建的环境是Debian12系统,使用Docker Compose部署,数据库采用mysql。
准备好域名并做好托管解析,cloudfraer的话,直接开启小云朵。
部署Halo的VPS内存最好在2G或以上,不够的话开点Swap(教程),不然加载的时候容易卡死。
如果配置不够,装好或者重启启动的时候,需要加载一点时间才能进入博客界面,要等一会,具体时间看VPS的配置。
升级系统软件包
apt update -y
apt upgrade -y
安装docker
wget -qO- get.docker.com | bash
docker -v #查看 docker 版本
systemctl enable docker # 设置开机自动启动
安装 Docker-compose
sudo curl -L "https://github.com/docker/compose/releases/download/版本号/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
替换命令中的版本号
,打开Docker-compose查看最新版本,如最新版本为v2.35.1
,则把版本号改为v2.35.1,示例:sudo curl -L "https://github.com/docker/compose/releases/download/2.35.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version #查看 docker-compose 版本
部署Halo
创建文件夹
mkdir -p /root/data/docker_data/halo
cd /root/data/docker_data/halo
编辑Docker-compose配置文件
注意
目前 Halo 2 并未更新 Docker 的 latest 标签镜像,主要因为 Halo 2 不兼容 1.x 版本,防止使用者误操作。我们推荐使用固定版本的标签,比如 2.20 或者 2.20.0。
registry.fit2cloud.com/halo/halo:2:表示最新的 2.x 版本,即每次发布新版本都会更新此镜像。
registry.fit2cloud.com/halo/halo:2.20:表示最新的 2.20.x 版本,即每次发布 patch 版本都会同时更新此镜像。
registry.fit2cloud.com/halo/halo:2.20.0:表示一个具体的版本。
本次 registry.fit2cloud.com/halo/halo:2 为例。
nano docker-compose.yml
官方提供了docker-compose.yml的配置模板,我在官方模板模板的提前下修改了一些内容,更加合适我,但不一定合适你,建议使用官方的模板就好。
官方模板
version: "3"
services:
halo:
image: registry.fit2cloud.com/halo/halo:2
restart: on-failure:3
depends_on:
halodb:
condition: service_healthy
networks:
halo_network:
volumes:
- ./halo2:/root/.halo2
ports:
- "8090:8090"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8090/actuator/health/readiness"]
interval: 30s
timeout: 5s
retries: 5
start_period: 30s
environment:
# JVM 参数,默认为 -Xmx256m -Xms256m,可以根据实际情况做调整,置空表示不添加 JVM 参数
- JVM_OPTS=-Xmx256m -Xms256m
command:
- --spring.r2dbc.url=r2dbc:pool:mysql://halodb:3306/halo
- --spring.r2dbc.username=root
# MySQL 的密码,请保证与下方 MYSQL_ROOT_PASSWORD 的变量值一致。
- --spring.r2dbc.password=o#DwN&JSa56
- --spring.sql.init.platform=mysql
# 外部访问地址,请根据实际需要修改
- --halo.external-url=http://localhost:8090/
halodb:
image: mysql:8.1.0
restart: on-failure:3
networks:
halo_network:
command:
- --default-authentication-plugin=caching_sha2_password
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_general_ci
- --explicit_defaults_for_timestamp=true
volumes:
- ./mysql:/var/lib/mysql
- ./mysqlBackup:/data/mysqlBackup
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "--silent"]
interval: 3s
retries: 5
start_period: 30s
environment:
# 请修改此密码,并对应修改上方 Halo 服务的 SPRING_R2DBC_PASSWORD 变量值
- MYSQL_ROOT_PASSWORD=o#DwN&JSa56
- MYSQL_DATABASE=halo
networks:
halo_network:
根据自己的实际情况调整,不懂修改就直接按照官方的模板来,要修改
halo.external-url=http://localhost:8090/
,localhost
要改成你的域名spring.r2dbc.password
和MYSQL_ROOT_PASSWORD
的密码,一定要修改!!!不要和模板的一样,两个密码要一致。
我的模板
version: "3"
services:
halo:
image: registry.fit2cloud.com/halo/halo:2
container_name: halo
restart: unless-stopped
depends_on:
halodb:
condition: service_healthy
networks:
halo_network:
volumes:
- ./:/root/.halo2
ports:
- "8090:8090"
command:
- --spring.r2dbc.url=r2dbc:pool:mysql://halodb:3306/halo
- --spring.r2dbc.username=root
# MySQL 的密码,请修改,不要和这里一样,请保证与下方 MYSQL_ROOT_PASSWORD 的变量值一致。
- --spring.r2dbc.password=o#DwN&JSa56
- --spring.sql.init.platform=mysql
# 外部访问地址,请根据实际需要修改
- --halo.external-url=https://域名/
# 初始化的超级管理员用户名,不一定是admin,这只是示例
- --halo.security.initializer.superadminusername=admin
# 初始化的超级管理员密码,要修改,不要和这里一样
- --halo.security.initializer.superadminpassword=o#DwN&JSa56
halodb:
image: mysql:8.0.31
container_name: halodb
restart: unless-stopped
networks:
halo_network:
command:
- --default-authentication-plugin=mysql_native_password
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_general_ci
- --explicit_defaults_for_timestamp=true
volumes:
- ./mysql:/var/lib/mysql
- ./mysqlBackup:/data/mysqlBackup
ports:
- "3306"
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "--silent"]
interval: 3s
retries: 5
start_period: 30s
environment:
# 请修改此密码,并对应修改上方 Halo 服务的 SPRING_R2DBC_PASSWORD 变量值
- MYSQL_ROOT_PASSWORD=o#DwN&JSa56
- MYSQL_DATABASE=halo
networks:
halo_network:
修改的地方:
restart
:改成了unless-stopped
,重启开机后会自动启动。原本on-failure:3
意思是如果容器异常退出(比如崩溃、返回错误码不为 0),自动重启最多 3 次,如果连续 3 次都失败了,就停止尝试重启,需要你手动干预volumes
:我改成了合适我自己的路径,可以不用修改halo.external-url=https://域名/
:因为后续反代了,所以直接https://域名/增加了一个管理员账户,也可以不要,在第一次登录的时候配置就好
一定要记住要修改spring.r2dbc.password
和MYSQL_ROOT_PASSWORD
的密码,不要用默认的。否则被人删除/修改数据库,敲诈勒索就来不及了!
一定要记住要修改spring.r2dbc.password
和MYSQL_ROOT_PASSWORD
的密码,不要用默认的。否则被人删除/修改数据库,敲诈勒索就来不及了!
Ctrl+x
确认保存并退出。
打开服务器防火墙并访问网页(非必需)
打开防火墙的端口 8090
查看端口是否被占用(以8090
为例),输入:
lsof -i:8090 #查看 8090 端口是否被占用,如果被占用,重新自定义一个端口
如果啥也没出现,表示端口未被占用,我们可以继续下面的操作了~
如果出现:
-bash: lsof: command not found
运行:
apt install lsof #安装 lsof
如果端口没有被占用(被占用了就修改一下端口,比如改成 8091
,注意 docker 命令行里和防火墙都要改)
拉取Halo
cd /root/data/docker_data/halo
docker-compose up -d
跑完命令后,理论上就可以输入 http://ip:8090 访问了。
反代
Ningx、Caddy、Nginx Proxy Manager都可以。官方有Ningx、Caddy、Traefik反代配置:反向代理
此处为了操作简单且通俗易懂,使用Caddy进行反代。
安装Caddy
Caddy官网安装教程:点击打开,找到自己对应系统的安装方式,如Debian, Ubuntu, Raspbian适用的:
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/testing/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-testing-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/testing/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-testing.list
sudo apt update
sudo apt install caddy
等待安装完成,在浏览器输入你的ip,会显示:
到此Caddy安装成功。
配置Caddy
域名 {
reverse_proxy localhost:8090
}
把域名
修改成你的域名
打开Caddy的配置文件:
nano /etc/caddy/Caddyfile
把修改好的配置信息粘贴到最后去,然后Ctrl+x
确认保存并退出。
重启Caddy:
systemctl reload caddy
此时,直接在浏览器输入你的域名即可。
更新Halo
cd /root/data/docker_data/halo
docker-compose down
cp -r /root/data/docker_data/halo /root/data/docker_data/halo.archive # 万事先备份,以防万一
docker-compose pull
docker-compose up -d # 请不要使用 docker-compose stop 来停止容器,因为这么做需要额外的时间等待容器停止;docker-compose up -d 直接升级容器时会自动停止并立刻重建新的容器,完全没有必要浪费那些时间。
docker image prune # prune 命令用来删除不再使用的 docker 对象。删除所有未被 tag 标记和未被容器使用的镜像
提示:
WARNING! This will remove all dangling images.
Are you sure you want to continue? [y/N]
输入 y
卸载 Halo
cd /root/data/docker_data/halo
docker-compose down
rm -rf /root/data/docker_data/halo # 完全删除映射到本地的数据
用Docker Compose部署、更新、卸载都很方便。
备份
做好备份绝对没毛病,繁琐一点就繁琐一点。Halo上也有备份,但是需要再次安装好Halo之后才可以恢复。这次用的是Docker Compose部署,可以打包整个文件夹备份,迁移或者恢复都很方便。本次做两种备份方式:1是直接打包到本地,2是打包后传到别的VPS。传到网盘类的比较复杂,这里不做教程。
本地备份
备份脚本
采用一个简单的本地备份脚本就可以完成,脚本如下:
#!/bin/bash
# 定义备份文件的存储路径
BACKUP_DIR="/root/backups"
LOG_FILE="/root/backups/backup.log"
# 生成备份文件的名称(带时间戳)
BACKUP_FILE="$BACKUP_DIR/data-backup-$(date '+%Y%m%d%H%M%S').tar.gz"
# 创建备份
echo "$(date '+%Y-%m-%d %H:%M:%S') - 开始备份..." >> "$LOG_FILE"
tar -czf "$BACKUP_FILE" /root data
# 记录备份完成
echo "$(date '+%Y-%m-%d %H:%M:%S') - 备份完成:$BACKUP_FILE" >> "$LOG_FILE"
# 删除 7 天前的备份文件
echo "$(date '+%Y-%m-%d %H:%M:%S') - 删除超过 7 天的备份文件..." >> "$LOG_FILE"
find "$BACKUP_DIR" -type f -name "*.tar.gz" -mtime +7 -exec rm -f {} \;
# 记录清理完成
echo "$(date '+%Y-%m-%d %H:%M:%S') - 清理完成,已删除超过 7 天的备份文件" >> "$LOG_FILE"
脚本解读:
把data文件夹打包,生成一个tar.gz格式文件且带时间戳的备份文件名,备份到/root/backups文件夹,备份的日志在/root/backups/下,名字是backup.log。每次执行脚本,在备份的同时,也会检查超过7天的文件,如果备份文件超过7天,会把该超时的备份删除,防止大量备份占用磁盘资源。
路径、名称、清理备份的时间自己调整成合适自己的。
nano /root/backup_script.sh
然后把脚本粘贴进去,调整完毕后,ctrl+x
确认保存并退出。
赋予脚本权限
chmod +x /root/backup_script.sh
执行权限授予后,可以直接运行脚本来验证其是否正常工作
/bin/bash /root/backup_script.sh
自行去查看/root/backups文件夹查看是否备份成功,按照教程的话,正常会备份成功。
设置定时任务(cron)
此时每次备份我们还要手动去执行脚本,无法自动执行,为了让备份脚本自动定期执行,可以使用 cron 设置定时任务
编辑 cron 任务
crontab -e
第一次可能遇到以下提示,提示用哪个编辑器,按照个人习惯来,我选择1
拉到最后,添加以下行
0 3 * * * /bin/bash /root/backup_script.sh
这条规则表示每天凌晨 3 点执行 /root/backup_script.sh
脚本,ctrl+x
确认保存并退出。
完整流程总结
cron 定时任务:定期执行备份脚本,自动备份并清理过期文件。
日志记录:备份和清理操作会被记录在 /root/backups /backup.log,方便后续查看。
备份脚本:将 /root下的data 文件夹备份到 /root/backups 目录,并且清理超过 7 天的旧备份文件。
远程备份
这次说的是备份到别的VPS上,接收备份的VPS需要设置成SSH密钥登录,不然每次备份传输前都要输入密码。如果接收的VPS没有设置SSH密钥登录,那么要先设置SSH密钥登录
设置 SSH 密钥登录(只需做一次)
设置密钥
在 本地(发起备份的 VPS) 上运行
ssh-keygen -t rsa -b 4096 -C "[email protected]"
一直按回车,生成密钥对(默认位置:/root/.ssh/id_rsa
和 id_rsa.pub.pub
)
它会生成两个文件:
/root/.ssh/id_rsa
(私钥,自己保存好)/root/.ssh/id_rsa.pub
(公钥,发给远程服务器)
把【公钥】复制到【远程服务器】
用 ssh-copy-id
命令可以一键搞定:
ssh-copy-id your-username@your-server-ip
把username
和your-server-ip
换成另外一台VPS的登录账户名和IP,然后它会提示你输入一次密码(就这一次),之后就设置好了,以后脚本里用 scp
、ssh
都不用再输密码了!
ssh-copy-id命令的解释
执行
ssh-copy-id your-username@your-server-ip
后,传过去的公钥默认会存放在远程服务器的目标用户目录下的~/.ssh/id_rsa.pub
文件中。具体路径是/home/your-username/.ssh/id_rsa.pub
,如果你是以 root 用户身份执行的,它会存放在/root/.ssh/id_rsa.pub
文件里。ssh-copy-id
命令中的id
指的是 公钥文件,默认是~/.ssh/id_rsa.pub
,但它可以指向任何你希望复制到远程服务器的公钥文件。
默认操作
如果你没有指定其他公钥文件,ssh-copy-id
会自动使用 默认的公钥,即 ~/.ssh/id_rsa.pub
。你可以通过以下命令复制默认公钥到远程服务器:
ssh-copy-id your-username@your-server-ip
自定义公钥文件
如果你有多个公钥文件,或者公钥文件不叫 id_rsa.pub
,可以指定具体的公钥文件。例如my_custom_key.pub
ssh-copy-id -i ~/.ssh/my_custom_key.pub your-username@your-server-ip
这样,你就可以根据需要选择合适的公钥文件来进行复制。
测试
在本机输入
ssh your-username@your-server-ip
把username
和your-server-ip
换成另外一台VPS的登录账户名和IP,如果能直接登录进去而不要求输入密码,那就说明配置成功!
如果你的远程服务器不在默认 22 端口,还可以加上
-p 端口号
,例如:ssh-copy-id -p 22022 your-username@your-server-ip
最后提醒
千万别把私钥 (
id_rsa
) 给别人!!!如果是 root 用户,就直接在
/root/.ssh/
目录下面生成和保存。
远程备份脚本
#!/bin/bash
# ====== 配置区域 ======
DATA_DIR="/root/data"
BACKUP_DIR="/root/docker-backups"
REMOTE_USER="your-username" # 改成远程 VPS 用户名
REMOTE_IP="your-server-ip" # 改成远程 VPS IP
REMOTE_DIR="/remote/backup/path/" # 改成远程 VPS 存储路径
KEEP_DAYS=30
SSH_KEY="/root/.ssh/id_rsa" # 私钥路径
LOG_FILE="$BACKUP_DIR/backup.log" # 日志文件路径
# ====== 颜色配置(只用于终端,不用于日志) ======
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # 没有颜色
# ====== 函数定义(方便同时输出到终端和日志) ======
log() {
# 参数:$1=输出到终端的文字(带颜色) $2=输出到日志的文字(纯净)
echo -e "$1"
echo "$(date '+%Y-%m-%d %H:%M:%S') - $2" >> "$LOG_FILE"
}
# ====== 脚本执行区域 ======
# 时间戳
DATE=$(date +"%Y%m%d-%H%M%S")
BACKUP_FILE="$BACKUP_DIR/data-backup-$DATE.tar.gz"
# 创建备份目录
mkdir -p "$BACKUP_DIR"
log "${BLUE}🛠 开始备份流程,时间:$DATE${NC}" "开始备份流程,时间:$DATE"
# 打包整个 /root/data
log "${YELLOW}📦 正在打包 $DATA_DIR 到 $BACKUP_FILE...${NC}" "正在打包 $DATA_DIR 到 $BACKUP_FILE..."
if tar -zcvf "$BACKUP_FILE" -C /root data; then
log "${GREEN}✅ 本地打包成功${NC}" "本地打包成功"
else
log "${RED}❌ 打包失败,终止脚本${NC}" "打包失败,终止脚本"
exit 1
fi
# 上传到远程服务器
log "${YELLOW}☁️ 正在上传备份到远程服务器 $REMOTE_IP...${NC}" "正在上传备份到远程服务器 $REMOTE_IP..."
if scp -i "$SSH_KEY" "$BACKUP_FILE" "$REMOTE_USER@$REMOTE_IP:$REMOTE_DIR"; then
log "${GREEN}✅ 上传成功${NC}" "上传成功"
else
log "${RED}❌ 上传失败,终止脚本${NC}" "上传失败,终止脚本"
exit 1
fi
# 清理旧备份
log "${YELLOW}🧹 正在清理超过 $KEEP_DAYS 天的旧备份...${NC}" "正在清理超过 $KEEP_DAYS 天的旧备份..."
if find "$BACKUP_DIR" -type f -mtime +$KEEP_DAYS -name "*.tar.gz" -exec rm -f {} \;; then
log "${GREEN}✅ 清理完成${NC}" "清理完成"
else
log "${RED}⚠️ 清理时出现问题(通常不影响)${NC}" "清理时出现问题"
fi
log "${BLUE}🎉 全部完成!备份文件位置:$BACKUP_FILE${NC}" "全部完成!备份文件位置:$BACKUP_FILE"
exit 0
注意
把这几行换成你的实际信息
# 远程服务器信息
REMOTE_USER="your-username" #改成远程服务器登录用户名
REMOTE_IP="your-server-ip" #改成远程服务器IP
REMOTE_DIR="/remote/backup/path/" #改成远程服务器存储路径
#保留被备份的时间
KEEP_DAYS=30 #自己修改,默认30天,需要注意磁盘空间
确认你的密钥路径是
/root/.ssh/id_rsa
,如果不是,请改掉
创建脚本
nano /root/backup_script.sh
然后把脚本粘贴进去,调整完毕后,ctrl+x
确认保存并退出。
赋予脚本权限
chmod +x /root/backup_script.sh
测试
/bin/bash /root/backup_script.sh
自行去查看两台VPS是否备份成功,按照教程的话,正常会备份成功。
设置定时任务(cron)
此时每次备份我们还要手动去执行脚本,无法自动执行,为了让备份脚本自动定期执行,可以使用 cron 设置定时任务
编辑 cron 任务
crontab -e
第一次可能遇到以下提示,提示用哪个编辑器,按照个人习惯来,我选择1
拉到最后,添加以下行
0 3 * * * /bin/bash /root/backup_script.sh >> /root/docker-backups/cronjob.log 2>&1
这条规则表示每天凌晨 3 点执行 /root/backup_script.sh
脚本,上面对比多了输出cron的日志,把定时执行的输出也保存到 cronjob.log,包括标准输出和错误输出。
然后ctrl+x
确认保存并退出。
本地vps的文件架构
执行后的文件结构大概是这样的
/root/docker-backups/
├── backup.log # 脚本自己维护的详细日志
├── cronjob.log # crontab跑脚本时的输出记录(出错可以排查)
├── data-backup-20250430-020001.tar.gz # 每天新生成的备份包
├── data-backup-20250501-020001.tar.gz
├── ...
完整流程总结
cron 定时任务:定期执行备份脚本,自动备份并清理过期文件。
日志记录:备份和清理操作会被记录在 /root/backups /backup.log和cronjob.log,方便后续查看。
备份脚本:将 /root下的data 文件夹备份到 /root/backups 目录,并且清理超过 30 天的旧备份文件。
主题和插件
可访问 官方应用市场 或 awesome-halo 仓库 查看适用于 Halo 2.x 的主题和插件。
插件安装和更新方式可参考:https://docs.halo.run/user-guide/plugins
文章编辑器默认是没有Markdown,用到的话要添加插件。
总结
这次只是搭建博客,按照Halo官方的说法,现在的Halo 2不单单是博客这么简单,还有更多的功能,大家可以查看官网资料学习,Halo的文档是比较详细的,网上也有很多的教程。
Github项目地址:https://github.com/halo-dev/halo
评论区