sycnnj
发布于 2026-02-07 / 37 阅读
0
0

群晖NAS Docker环境Halo + PostgreSQL自动化部署脚本(26.02.12更新)

Keyw:群晖,NAS, Halo 2.0, Docker 自动化, Macvlan 网络, 双网口群晖, 独立 IP, 运维脚本, 端口冲突解决

摘要:在群晖 (Synology) NAS 上部署 Halo 博客时,原生 Docker 的端口映射模式常导致 80/443 端口被系统占用,且 Web Station 的反向代理配置繁琐且易出错。本文提供了一套“一键式” Shell 自动化部署脚本,能够智能识别群晖的双网口及虚拟化网卡(eth0/eth1/ovs_eth*),自动创建 Macvlan 网络并分配独立 IP。通过本教程,你将彻底告别端口冲突,实现 Halo 的极速、纯净部署。


一、 引言:为什么群晖 Docker 原生部署这么“痛”?

对于热爱折腾 NAS 的玩家来说,Halo 是搭建个人博客的首选。但在群晖 DSM 系统中部署 Halo,往往会遭遇“三座大山”:

1. 端口冲突的死循环

群晖 DSM 系统自身保留了大量的常用端口。

  • 80/443:被系统 Nginx 占用,用于 Web Station 和系统重定向。

  • 5000/5001:DSM 管理界面占用。

    当你尝试使用 docker run -p 80:8090 ... 部署 Halo 时,Docker 会直接报错。你被迫使用 809018090 这种带“小尾巴”的端口访问博客,既不美观也不利于搜索引擎(SEO)抓取。

2. Web Station 的配置黑洞

为了去掉端口号,官方推荐使用 Web Station 进行反向代理。但实际操作中痛点极多:

  • 配置繁琐:每次部署都需要手动设置 WebSocket、HSTS、HTTP 版本。

  • 性能损耗:流量需要经过 路由器 -> 群晖主 Nginx -> Docker 代理 -> 容器 多层转发。

  • 排错困难:一旦出现 502 Bad Gateway,很难分清是群晖的问题还是容器的问题。

3. 面板功能的缺失

群晖自带的 Container Manager(原 Docker 套件)虽然界面好看,但功能由于阉割严重,无法在创建容器时指定固定 IP。一旦 NAS 重启,容器内部 IP 变化,之前做的反向代理规则瞬间失效。

二、 破局方案:Macvlan + 自动化脚本

什么是 Macvlan?

简单来说,Macvlan 技术允许我们在群晖的一个物理网口上,虚拟出多个拥有独立 MAC 地址和 IP 地址的“虚拟网卡”。

  • 传统模式:Halo 寄宿在群晖 IP 下,靠端口区分。

  • Macvlan 模式:Halo 拥有和群晖同级的局域网 IP(例如群晖是 192.168.1.10,Halo 是 192.168.1.11)。

脚本优势

针对群晖环境,我编写了一套全自动部署脚本。相比手动操作,它解决了以下核心问题:

  1. 双网口智能识别:很多高端群晖(如 DS920+, DS1621+)拥有双网口(eth0, eth1)。如果选错网卡,容器将无法联网。本脚本能自动检测流量出口。

  2. 虚拟化环境兼容:如果你开启了 VMM (虚拟机),物理网卡会被接管为 ovs_eth0,脚本能自动处理这种情况。

  3. 一键全自动:从创建文件夹、授权、网络配置到容器启动,一段代码全部搞定。


三、 部署前的准备工作

在执行脚本前,你需要确认以下信息(请记录在记事本上):

  1. 子网掩码 (Subnet):通常为 192.168.x.0/24(例如 192.168.1.0/24)。

  2. 网关地址 (Gateway):你的路由器 IP(例如 192.168.1.1)。

  3. 目标 IP (Target IP):你想给 Halo 分配的 IP,必须是局域网内空闲的(例如 192.168.1.11)。

  4. SSH 权限:确保群晖控制面板中已开启 SSH 功能。


四、 实战:一键部署 Halo 2.x最新版

请通过 SSH 终端(如 PuTTY、macOS Terminal)登录群晖,并切换到 root 权限:

Bash

sudo -i
# 输入密码后回车

复制下方的完整代码块,将其粘贴到 SSH 窗口中。

⚠️ 注意:粘贴前,请务必修改代码最上方的 配置区域,使其匹配你的家庭网络环境!

Bash

cat << 'EOF' > deploy_halo_pg.sh
#!/bin/bash

# ================= 配置区域 =================
# 目标 IP 地址
TARGET_IP="192.168.1.11"
GATEWAY="192.168.1.1"
SUBNET="192.168.1.0/24"

# 数据挂载根目录
BASE_DIR="/volume1/docker/halo-pro"

# 随机生成数据库凭据 (保证安全性)
DB_USER="halo_user_$(tr -dc 'a-z0-9' < /dev/urandom | head -c 4)"
DB_PASS="$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 16)"
DB_NAME="halo"
# ===========================================

echo ">>> 1. 环境预检与目录创建..."

# 网卡检测 (兼容群晖 VMM/虚拟交换机)
if ip link show "ovs_eth0" > /dev/null 2>&1; then
    INTERFACE="ovs_eth0"
else
    INTERFACE="eth0"
fi
echo ">>> 检测到网卡: $INTERFACE"

mkdir -p "$BASE_DIR/data" "$BASE_DIR/db"
chmod -R 777 "$BASE_DIR"

# >>> 2. Macvlan 网络处理
if ! docker network inspect macvlan_net >/dev/null 2>&1; then
    echo ">>> 正在创建 Macvlan 网络..."
    docker network create -d macvlan \
      --subnet=$SUBNET \
      --gateway=$GATEWAY \
      -o parent=$INTERFACE \
      macvlan_net
else
    echo ">>> Macvlan 网络已存在,跳过创建。"
fi

# >>> 3. 生成修正后的 Docker Compose 配置文件
cat << YAML > "$BASE_DIR/docker-compose.yaml"
version: '3'

services:
  db:
    image: postgres:15-alpine
    container_name: halo_db
    restart: always
    networks:
      macvlan_net:
        ipv4_address: $TARGET_IP
    volumes:
      - ./db:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=$DB_NAME
      - POSTGRES_USER=$DB_USER
      - POSTGRES_PASSWORD=$DB_PASS
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $DB_USER -d $DB_NAME"]
      interval: 10s
      timeout: 5s
      retries: 5

  halo:
    image: halohub/halo:2.22
    container_name: halo_app
    restart: always
    # 核心:让 Halo 共享 db 容器的网络栈,实现同 IP 不同端口
    network_mode: "service:db"
    volumes:
      - ./data:/root/.halo2
    environment:
      - SPRING_R2DBC_URL=r2dbc:postgresql://127.0.0.1:5432/$DB_NAME
      - SPRING_R2DBC_USERNAME=$DB_USER
      - SPRING_R2DBC_PASSWORD=$DB_PASS
      - SPRING_SQL_INIT_PLATFORM=postgresql
      - HALO_EXTERNAL_URL=http://$TARGET_IP:8090
    depends_on:
      db:
        condition: service_healthy

networks:
  macvlan_net:
    external: true
YAML

# >>> 4. 执行部署
echo ">>> 5. 正在启动服务 (同 IP 模式)..."
cd "$BASE_DIR"
docker-compose down > /dev/null 2>&1
docker-compose up -d

echo "========================================================"
echo "✅ 部署指令已发出!"
echo "--------------------------------------------------------"
echo "管理地址: http://$TARGET_IP:8090"
echo "数据库库名: $DB_NAME"
echo "数据库用户: $DB_USER"
echo "数据库密码: $DB_PASS"
echo "提示: 首次启动需约 30-60 秒进行数据库初始化。"
echo "========================================================"

# 自我清理脚本文件
rm -- "$0"
EOF

# 赋予权限并执行
chmod +x deploy_halo_pg.sh && ./deploy_halo_pg.sh

群晖NAS Docker环境Halo + PostgreSQL自动化部署脚本运行过程

🔄 重建、更新与升级 (确保数据安全)

在 Docker Compose 架构下,升级项目并保持数据不丢失的核心逻辑是:“镜像更新 + 容器重建 + 挂载点保留”。由于你的数据已持久化在 /volume1/docker/halo-pro 目录下,操作非常安全。

1. 简易更新流程

如果你想将镜像同步到最新版本(或当前指定的 2.22.13 补丁版):

Bash

cd /volume1/docker/halo-pro
# 第一步:拉取最新镜像
docker-compose pull
# 第二步:一键重建并后台运行
docker-compose up -d

2. 彻底重建环境

如果你修改了 docker-compose.yaml(例如修改了环境变量或 IP),建议执行:

Bash

cd /volume1/docker/halo-pro
# 停止并删除容器,不影响持久化数据
docker-compose down
# 重新构建并启动
docker-compose up -d

五、 脚本核心逻辑深度解析

为了让大家用得放心,这里对脚本中几个关键的升级逻辑进行详细解读。

1. 智能网卡侦测 (The Auto-Detection)

Bash

DETECTED_INTERFACE=$(ip route show | grep default | awk '{print $5}' | head -n1)

这是本脚本的灵魂。在双网口群晖(eth0 + eth1)或虚拟化环境(VMM 导致生成 ovs_eth0)中,手动指定网卡非常容易出错。

  • ip route show: 查看系统路由表。

  • grep default: 找到默认网关(即通往互联网的出口)。

  • awk '{print $5}': 提取对应的网络接口名称。

    这意味着,无论你的群晖网线插在网口1还是网口2,或者你是否开启了虚拟机,脚本都能精准找到当前正在工作的那个网卡,将其作为 Macvlan 的父接口。

2. 全量 EOF 写入

使用了 cat << 'EOF' > filename 结构。这是一种标准的 Shell 编程技巧。它允许我们将多行内容(也就是脚本正文)作为一个整体写入文件。这样做的好处是防止复制粘贴过程中的格式错乱,并且可以实现“一条命令完成写入+执行”的流畅体验。

3. 幂等性设计 (Idempotency)

脚本在创建网络和文件夹时,都加入了 if 判断。

  • 网络复用:如果 macvlan_net 已经存在(比如你之前部署过 1Panel 或 AdGuard Home),脚本不会报错退出,而是直接复用现有网络,这极大地提高了脚本的兼容性。

  • 旧容器清洗:脚本会自动 docker rm -f halo,这意味着你可以反复执行此脚本来重置 Halo,非常适合调试。


六、 常见问题与避坑指南 (Troubleshooting)

Q1: 部署成功了,但无法访问 http://IP:8090

  1. 检查 IP 冲突:请确保你填写的 TARGET_IP 没有被局域网内的手机、电脑或电视占用。

  2. 防火墙拦截:检查群晖控制面板 -> 安全性 -> 防火墙,是否放行了该网段或关闭了防火墙。

  3. 网关错误:脚本配置区的 GATEWAY 必须填写正确,否则 Halo 无法回包给你的电脑。

Q2: 为什么群晖主机无法 Ping 通 Halo 的 IP?

这是一个经典的 Macvlan Shim 问题。出于安全设计,Linux 内核禁止父接口(群晖)直接与子接口(Halo)通信。

  • 现象:电脑能访问 Halo,但群晖 SSH 里 Ping 不通 Halo。

  • 影响:如果你的 Halo 需要连接群晖本机的 MySQL 数据库,填群晖 IP 会连接超时。

  • 解决:建议使用 Docker 部署 MySQL 并加入同一 Macvlan 网络,或者使用第三方网桥脚本打通(不建议新手操作,容易搞崩网络)。

Q3: 我想用 80 端口直接访问,不要 :8090 怎么办?

既然已经拥有了独立 IP,你可以直接在路由器侧做端口转发,或者结合 Nginx Proxy Manager

但最简单的方案是:使用 Cloudflare Tunnel 或 Frp 进行内网穿透,直接映射到 TARGET_IP:8090,对外展示为标准的 HTTPS (443) 端口。

博客需要发布到公网,配置内网穿透请参照教程Cloudflare Tunnel 内网穿透终极指南一文


七、 总结

通过这段经过严密逻辑设计的脚本,我们在群晖上实现了“企业级”的部署体验:

  1. 独立 IP:像管理物理机一样管理容器。

  2. 资源隔离:彻底摆脱 Web Station 的束缚。

  3. 自动化:从识别硬件到部署上线,仅需一次粘贴。

希望这篇教程能帮你从繁琐的运维中解脱出来,专注于 Halo 博客的内容创作。


附录:速查参数表

🛠️ Halo + PostgreSQL 运维速查手册

功能描述

具体命令

说明

查看容器状态

docker-compose ps

显示 Halo 和 DB 的运行状态及端口映射

查看登录地址

grep "TARGET_IP" docker-compose.yaml

快速确认 Macvlan 分配的独立 IP

查看实时日志

docker-compose logs -f halo

排查 Halo 启动或运行错误的关键命令

重启所有容器

docker-compose restart

仅重启进程,不会触发配置重新加载

停止所有容器

docker-compose stop

停止运行,但保留容器实例

启动所有容器

docker-compose up -d

根据 YAML 配置文件在后台启动所有服务

删除所有容器

docker-compose down

停止并移除容器及网络 数据卷 dbdata 保留

查看数据库连接

docker exec -it halo_db pg_isready

检查 PostgreSQL 是否处于可连接状态

参数名

含义

典型值

TARGET_IP

Halo 容器的独立 IP

192.168.1.xxx

GATEWAY

路由器地址

192.168.1.1

SUBNET

局域网子网

192.168.1.0/24

INTERFACE

物理网卡名称

脚本自动检测 (eth0/ovs_eth0)


本文首发于E路领航 (blog.oool.cc),转载请注明出处


评论