172 lines
5.6 KiB
Bash
172 lines
5.6 KiB
Bash
#!/bin/bash
|
||
set -Eeuo pipefail
|
||
|
||
echo "======================================"
|
||
echo " ZeroTier AIO 离线快速部署脚本 v2"
|
||
echo " 支持备份恢复 / 新服务器迁移"
|
||
echo "======================================"
|
||
|
||
[ "$EUID" -ne 0 ] && { echo "请用 root 执行"; exit 1; }
|
||
|
||
INSTALL_DIR="/opt/zerotier-aio"
|
||
IMAGE_TAR="$INSTALL_DIR/zerotier-aio-zh.tar"
|
||
IMAGE_REF="niliaerith/zerotier-aio-zh:latest"
|
||
CONTAINER_NAME="zerotier-aio"
|
||
FORCE_REGEN_MOON="${FORCE_REGEN_MOON:-0}"
|
||
|
||
if ! command -v apt >/dev/null 2>&1; then
|
||
echo "当前脚本仅支持 Debian/Ubuntu(需要 apt)"
|
||
exit 1
|
||
fi
|
||
|
||
# 安装依赖(离线环境至少要有 docker + compose + curl + ip)
|
||
if ! command -v docker >/dev/null 2>&1; then
|
||
apt update -y
|
||
apt install -y docker.io docker-compose-plugin curl iproute2
|
||
systemctl enable --now docker
|
||
fi
|
||
|
||
if ! docker compose version >/dev/null 2>&1; then
|
||
apt update -y
|
||
apt install -y docker-compose-plugin
|
||
fi
|
||
|
||
command -v curl >/dev/null 2>&1 || apt install -y curl
|
||
command -v ip >/dev/null 2>&1 || apt install -y iproute2
|
||
|
||
# 检查 TUN
|
||
modprobe tun 2>/dev/null || true
|
||
[ -c /dev/net/tun ] || { echo "TUN 不可用"; exit 1; }
|
||
|
||
# 获取公网 IP
|
||
PUBLIC_IP=$(curl -fsS --connect-timeout 2 --max-time 5 http://100.100.100.200/latest/meta-data/eipv4 || true)
|
||
[ -z "$PUBLIC_IP" ] && PUBLIC_IP=$(ip route get 1.1.1.1 2>/dev/null | awk '/src/ {for(i=1;i<=NF;i++) if($i=="src"){print $(i+1); exit}}')
|
||
[ -z "$PUBLIC_IP" ] && read -r -p "无法自动获取公网IP,请手动输入: " PUBLIC_IP
|
||
[ -n "$PUBLIC_IP" ] || { echo "未获取到公网 IP,退出"; exit 1; }
|
||
echo "公网 IP: $PUBLIC_IP"
|
||
|
||
mkdir -p "$INSTALL_DIR"
|
||
cd "$INSTALL_DIR"
|
||
|
||
# 如果有备份包,优先恢复
|
||
if ls zerotier-aio-backup*.tar.gz 1>/dev/null 2>&1; then
|
||
BACKUP_FILE=$(ls -t zerotier-aio-backup*.tar.gz | head -n1)
|
||
echo "检测到备份包,正在恢复: $BACKUP_FILE"
|
||
tar -xzf "$BACKUP_FILE" -C /opt
|
||
elif ls zerotier-aio-essential*.tar.gz 1>/dev/null 2>&1; then
|
||
BACKUP_FILE=$(ls -t zerotier-aio-essential*.tar.gz | head -n1)
|
||
echo "检测到 essential 包,正在恢复: $BACKUP_FILE"
|
||
tar -xzf "$BACKUP_FILE" -C /opt
|
||
elif ls zerotier-aio-[0-9]*.tar.gz 1>/dev/null 2>&1; then
|
||
# 兼容旧版文档中的备份命名
|
||
BACKUP_FILE=$(ls -t zerotier-aio-[0-9]*.tar.gz | head -n1)
|
||
echo "检测到旧版命名备份,正在恢复: $BACKUP_FILE"
|
||
tar -xzf "$BACKUP_FILE" -C /opt
|
||
fi
|
||
|
||
# 加载镜像(离线强依赖)
|
||
[ -f "$IMAGE_TAR" ] || {
|
||
echo "未找到离线镜像包: $IMAGE_TAR"
|
||
echo "请先上传 zerotier-aio-zh.tar 到 $INSTALL_DIR"
|
||
exit 1
|
||
}
|
||
|
||
echo "加载本地镜像..."
|
||
docker load -i "$IMAGE_TAR"
|
||
docker image inspect "$IMAGE_REF" >/dev/null 2>&1 || {
|
||
echo "镜像加载后未找到: $IMAGE_REF"
|
||
echo "请确认 tar 内镜像标签与 docker-compose.yml 一致"
|
||
exit 1
|
||
}
|
||
|
||
# 生成或使用 docker-compose.yml
|
||
cat > docker-compose.yml <<EOF
|
||
services:
|
||
zerotier-aio:
|
||
image: $IMAGE_REF
|
||
pull_policy: never
|
||
container_name: $CONTAINER_NAME
|
||
restart: unless-stopped
|
||
cap_add: [ALL]
|
||
devices: [/dev/net/tun]
|
||
network_mode: host
|
||
volumes:
|
||
- ./etc:/opt/key-networks/ztncui/etc
|
||
- ./zerotier-one:/var/lib/zerotier-one
|
||
- ./zt-mkworld:/etc/zt-mkworld
|
||
environment:
|
||
- TZ=Asia/Shanghai
|
||
- AUTOGEN_PLANET=0
|
||
- NODE_ENV=production
|
||
- HTTP_PORT=3000
|
||
- HTTP_ALL_INTERFACES=yes
|
||
- ZTNCUI_PASSWD=admin123
|
||
- MYADDR=$PUBLIC_IP
|
||
privileged: true
|
||
EOF
|
||
|
||
# 启动
|
||
docker compose up -d
|
||
echo "等待容器初始化..."
|
||
for _ in $(seq 1 30); do
|
||
if docker exec "$CONTAINER_NAME" test -f /var/lib/zerotier-one/identity.public >/dev/null 2>&1; then
|
||
break
|
||
fi
|
||
sleep 1
|
||
done
|
||
docker exec "$CONTAINER_NAME" test -f /var/lib/zerotier-one/identity.public >/dev/null 2>&1 || {
|
||
echo "容器未完成初始化,未找到 identity.public"
|
||
exit 1
|
||
}
|
||
|
||
# 检查 Moon(如果 moons.d 为空则生成)
|
||
MOONS_DIR="./zerotier-one/moons.d"
|
||
|
||
if [ "$FORCE_REGEN_MOON" = "1" ]; then
|
||
rm -f "$MOONS_DIR"/*.moon 2>/dev/null || true
|
||
fi
|
||
|
||
if [ ! -d "$MOONS_DIR" ] || [ -z "$(find "$MOONS_DIR" -maxdepth 1 -name '*.moon' -print -quit 2>/dev/null)" ]; then
|
||
echo "生成 Moon..."
|
||
docker exec "$CONTAINER_NAME" sh -c "zerotier-idtool initmoon /var/lib/zerotier-one/identity.public > /tmp/moon.json"
|
||
docker exec "$CONTAINER_NAME" sh -c "sed -i 's|\"stableEndpoints\": \\[\\]|\"stableEndpoints\": [\"$PUBLIC_IP/9993\"]|' /tmp/moon.json"
|
||
docker exec "$CONTAINER_NAME" sh -c "cd /tmp && zerotier-idtool genmoon moon.json"
|
||
MOON_FILE=$(docker exec "$CONTAINER_NAME" sh -c "find /tmp -maxdepth 1 -name '*.moon' | head -n1")
|
||
[ -n "$MOON_FILE" ] || { echo "Moon 生成失败"; exit 1; }
|
||
mkdir -p "$MOONS_DIR"
|
||
docker cp "$CONTAINER_NAME:$MOON_FILE" "$MOONS_DIR/"
|
||
MOON_ID=$(basename "$MOON_FILE" .moon)
|
||
docker restart "$CONTAINER_NAME" >/dev/null
|
||
else
|
||
MOON_ID=$(find "$MOONS_DIR" -maxdepth 1 -name '*.moon' | head -n1 | xargs basename | cut -d. -f1)
|
||
fi
|
||
|
||
# 防火墙(ufw)
|
||
command -v ufw &>/dev/null && {
|
||
ufw allow 9993/udp || true
|
||
ufw allow 3000/tcp || true
|
||
ufw allow 3443/tcp || true
|
||
ufw reload || true
|
||
}
|
||
|
||
cat <<EOF
|
||
|
||
======================================
|
||
部署完成!
|
||
======================================
|
||
|
||
Moon ID: $MOON_ID
|
||
Orbit 命令: sudo zerotier-cli orbit $MOON_ID $MOON_ID
|
||
|
||
Web 界面: http://$PUBLIC_IP:3000
|
||
用户: admin 密码: admin123 (立即修改!)
|
||
|
||
安全组需放行: 9993/udp 3000/tcp 3443/tcp
|
||
|
||
备份建议: tar -czf zerotier-aio-backup-$(date +%Y%m%d).tar.gz /opt/zerotier-aio
|
||
|
||
调试: docker logs zerotier-aio
|
||
docker exec -it zerotier-aio bash
|
||
======================================
|
||
EOF
|