293 lines
12 KiB
Bash
Executable File
293 lines
12 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
|
||
# ============================================
|
||
# Vaultwarden 一键部署脚本
|
||
# 自动安装 Docker + Nginx + SSL + Vaultwarden
|
||
# ============================================
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
cd "$SCRIPT_DIR"
|
||
|
||
# ===== 加载公共基础函数 =====
|
||
BASE_DIR="$(cd "$SCRIPT_DIR/../base" 2>/dev/null && pwd)" || true
|
||
if [ -z "$BASE_DIR" ] || [ ! -f "$BASE_DIR/setup.sh" ]; then
|
||
echo "[ERROR] base/setup.sh 未找到" >&2
|
||
echo "请确保目录结构如下:" >&2
|
||
echo " /opt/base/setup.sh" >&2
|
||
echo " /opt/vaultwarden/deploy.sh (当前脚本)" >&2
|
||
exit 1
|
||
fi
|
||
source "$BASE_DIR/setup.sh"
|
||
|
||
# =============================================================
|
||
# Vaultwarden 专用函数
|
||
# =============================================================
|
||
|
||
GITHUB_REPO="dani-garcia/vaultwarden"
|
||
|
||
# 生成随机密码
|
||
generate_password() {
|
||
openssl rand -base64 32 | tr -d '/+=' | head -c 32
|
||
}
|
||
|
||
# 查询 GitHub 上的最新 release 版本号(不含前缀 v),失败返回非零
|
||
resolve_latest_version() {
|
||
local ver
|
||
ver=$(curl -sf --max-time 20 \
|
||
"https://api.github.com/repos/${GITHUB_REPO}/releases/latest" \
|
||
| grep -o '"tag_name"[[:space:]]*:[[:space:]]*"[^"]*"' \
|
||
| head -1 | sed 's/.*"\([^"]*\)"$/\1/' | sed 's/^v//') || true
|
||
[ -n "$ver" ] || return 1
|
||
echo "$ver"
|
||
}
|
||
|
||
# 把镜像固定到具体版本号。
|
||
# 必须同时更新 .env 和已导出的环境变量:init_env 里 `set -a; source .env` 已把旧值
|
||
# 导出到本进程,而 docker compose 对环境变量的优先级高于 .env 文件,只改文件不生效。
|
||
pin_image_version() {
|
||
local ref="$1"
|
||
if grep -q '^VAULTWARDEN_IMAGE=' .env; then
|
||
sed -i "s|^VAULTWARDEN_IMAGE=.*|VAULTWARDEN_IMAGE=${ref}|" .env
|
||
else
|
||
printf '\nVAULTWARDEN_IMAGE=%s\n' "$ref" >> .env
|
||
fi
|
||
export VAULTWARDEN_IMAGE="$ref"
|
||
}
|
||
|
||
# 确定本次部署使用的镜像版本。
|
||
# 不使用 latest 标签的原因:它只在 docker compose pull 时才重新解析,容器不重建就会
|
||
# 一直跑旧版本,且从配置上看不出实际运行的是哪一版。
|
||
setup_image_version() {
|
||
local current="${VAULTWARDEN_IMAGE:-}"
|
||
|
||
# 已固定到具体版本 → 尊重现有配置,升级交给 upgrade.sh
|
||
if [ -n "$current" ] && [[ "$current" != *":latest" ]]; then
|
||
log " 镜像: ${current}"
|
||
return 0
|
||
fi
|
||
|
||
# 已有实例却仍在用 latest:此时固定版本等同于一次升级,
|
||
# 而升级必须走 upgrade.sh(带冷备份、完整性校验、自动回滚),这里不擅自处理。
|
||
if docker compose ps -a -q vaultwarden 2>/dev/null | grep -q .; then
|
||
warn "检测到已存在的实例,且 .env 仍在使用 latest 标签"
|
||
warn "latest 不会自动更新,也看不出实际运行版本,建议固定到具体版本号:"
|
||
warn " bash upgrade.sh # 安全升级到最新版,并自动写回版本号"
|
||
warn "本次部署不改动镜像配置"
|
||
return 0
|
||
fi
|
||
|
||
log "正在查询 Vaultwarden 最新版本..."
|
||
local ver
|
||
if ! ver="$(resolve_latest_version)"; then
|
||
warn "无法从 GitHub 获取最新版本号(网络问题?),本次回退使用 latest 标签"
|
||
warn "部署完成后建议手动固定: VAULTWARDEN_IMAGE=vaultwarden/server:<版本号>"
|
||
[ -n "$current" ] || pin_image_version "vaultwarden/server:latest"
|
||
return 0
|
||
fi
|
||
|
||
pin_image_version "vaultwarden/server:${ver}"
|
||
log " 镜像: vaultwarden/server:${ver}(当前最新 release,已写入 .env)"
|
||
}
|
||
|
||
init_env() {
|
||
step "初始化 Vaultwarden 配置"
|
||
|
||
if [ ! -f .env ]; then
|
||
if [ ! -f .env.example ]; then
|
||
error "缺少 .env.example 模板文件"
|
||
exit 1
|
||
fi
|
||
cp .env.example .env
|
||
|
||
# 自动生成 ADMIN_TOKEN
|
||
local admin_token
|
||
admin_token=$(generate_password)
|
||
sed -i "s/^ADMIN_TOKEN=$/ADMIN_TOKEN=${admin_token}/" .env
|
||
log "已自动生成 ADMIN_TOKEN"
|
||
|
||
log "已生成 .env 文件"
|
||
echo ""
|
||
warn "┌──────────────────────────────────────────────────────┐"
|
||
warn "│ 请编辑 .env 文件,至少修改以下配置: │"
|
||
warn "│ │"
|
||
warn "│ VAULTWARDEN_DOMAIN=vault.yourdomain.com │"
|
||
warn "│ CERTBOT_EMAIL=you@yourdomain.com │"
|
||
warn "│ │"
|
||
warn "│ ADMIN_TOKEN 已自动生成,请妥善保存 │"
|
||
warn "│ │"
|
||
warn "│ 编辑命令: vi $SCRIPT_DIR/.env │"
|
||
warn "│ 编辑完成后重新运行: bash deploy.sh │"
|
||
warn "└──────────────────────────────────────────────────────┘"
|
||
exit 0
|
||
fi
|
||
|
||
fix_crlf .env
|
||
set -a; source .env; set +a
|
||
|
||
local has_error=0
|
||
if [[ -z "${VAULTWARDEN_DOMAIN:-}" ]] || [[ "${VAULTWARDEN_DOMAIN}" == "vault.example.com" ]]; then
|
||
error "请在 .env 中将 VAULTWARDEN_DOMAIN 修改为你的实际域名"
|
||
has_error=1
|
||
fi
|
||
if [[ -z "${CERTBOT_EMAIL:-}" ]] || [[ "${CERTBOT_EMAIL}" == "admin@example.com" ]]; then
|
||
error "请在 .env 中将 CERTBOT_EMAIL 修改为你的实际邮箱"
|
||
has_error=1
|
||
fi
|
||
if [[ -z "${ADMIN_TOKEN:-}" ]]; then
|
||
error "ADMIN_TOKEN 未设置"
|
||
has_error=1
|
||
fi
|
||
[ "$has_error" -eq 1 ] && { error "请修改 .env 后重新运行"; exit 1; }
|
||
|
||
log "配置检查通过"
|
||
log " 域名: ${VAULTWARDEN_DOMAIN}"
|
||
log " 邮箱: ${CERTBOT_EMAIL}"
|
||
log " 注册: ${SIGNUPS_ALLOWED:-true}"
|
||
|
||
setup_image_version
|
||
}
|
||
|
||
create_dirs() {
|
||
step "创建数据目录"
|
||
local data_dir="${VAULTWARDEN_DATA_DIR:-/var/lib/vaultwarden}"
|
||
local backup_dir="${BACKUP_DIR:-/var/backups/vaultwarden}"
|
||
|
||
mkdir -p "$data_dir" "$backup_dir"
|
||
log "数据目录: $data_dir"
|
||
log "备份目录: $backup_dir"
|
||
}
|
||
|
||
# 确认容器真的跑在配置指定的镜像上,避免「配置改了但容器没重建」这类静默失败
|
||
verify_running_version() {
|
||
local cid actual expect="${VAULTWARDEN_IMAGE:-}"
|
||
cid=$(docker compose ps -a -q vaultwarden 2>/dev/null | head -1)
|
||
[ -n "$cid" ] || return 0
|
||
|
||
actual=$(docker inspect "$cid" --format '{{.Config.Image}}' 2>/dev/null || true)
|
||
if [ -n "$expect" ] && [ "$actual" != "$expect" ]; then
|
||
warn "容器实际镜像 (${actual}) 与配置 (${expect}) 不一致"
|
||
warn "请检查: docker compose up -d --force-recreate"
|
||
return 0
|
||
fi
|
||
log " 运行镜像: ${actual}"
|
||
}
|
||
|
||
start_services() {
|
||
step "启动 Vaultwarden 服务"
|
||
|
||
log "正在拉取镜像..."
|
||
docker compose pull
|
||
|
||
# --force-recreate:容器处于 stopped 状态时,up -d 只会把原容器重新 start,
|
||
# 不会因镜像或环境变量变更而重建,导致「配置改了但没生效」。
|
||
log "正在启动容器..."
|
||
docker compose up -d --force-recreate
|
||
|
||
local port="${VAULTWARDEN_PORT:-8080}"
|
||
log "等待 Vaultwarden 就绪..."
|
||
local max_wait=30
|
||
for _ in $(seq 1 "$max_wait"); do
|
||
if curl -sf "http://127.0.0.1:${port}/alive" &> /dev/null; then
|
||
log "Vaultwarden 启动成功!"
|
||
verify_running_version
|
||
return
|
||
fi
|
||
sleep 2
|
||
done
|
||
warn "Vaultwarden 可能仍在启动中,请稍后检查: docker compose logs -f"
|
||
}
|
||
|
||
show_info() {
|
||
set -a; source .env; set +a
|
||
|
||
echo ""
|
||
echo -e "${GREEN}╔══════════════════════════════════════════════════════════╗${NC}"
|
||
echo -e "${GREEN}║ Vaultwarden 部署完成! ║${NC}"
|
||
echo -e "${GREEN}╠══════════════════════════════════════════════════════════╣${NC}"
|
||
echo -e "${GREEN}║${NC}"
|
||
echo -e "${GREEN}║${NC} Web 访问: ${CYAN}https://${VAULTWARDEN_DOMAIN}${NC}"
|
||
echo -e "${GREEN}║${NC} 管理后台: ${CYAN}https://${VAULTWARDEN_DOMAIN}/admin${NC}"
|
||
echo -e "${GREEN}║${NC}"
|
||
echo -e "${GREEN}║${NC} 数据目录: ${VAULTWARDEN_DATA_DIR:-/var/lib/vaultwarden}"
|
||
echo -e "${GREEN}║${NC} 备份目录: ${BACKUP_DIR:-/var/backups/vaultwarden}"
|
||
echo -e "${GREEN}║${NC}"
|
||
echo -e "${GREEN}║${NC} ${YELLOW}管理员令牌保存在 .env 文件的 ADMIN_TOKEN 中${NC}"
|
||
echo -e "${GREEN}║${NC}"
|
||
echo -e "${GREEN}║${NC} 客户端下载:"
|
||
echo -e "${GREEN}║${NC} 浏览器插件: ${CYAN}https://bitwarden.com/download/${NC}"
|
||
echo -e "${GREEN}║${NC} 桌面客户端: ${CYAN}https://bitwarden.com/download/${NC}"
|
||
echo -e "${GREEN}║${NC} 移动端: ${CYAN}https://bitwarden.com/download/${NC}"
|
||
echo -e "${GREEN}║${NC}"
|
||
echo -e "${GREEN}║${NC} ${RED}⚠ 客户端连接时,服务器 URL 填写:${NC}"
|
||
echo -e "${GREEN}║${NC} ${CYAN} https://${VAULTWARDEN_DOMAIN}${NC}"
|
||
echo -e "${GREEN}║${NC}"
|
||
if [[ "${SIGNUPS_ALLOWED:-true}" == "true" ]]; then
|
||
echo -e "${GREEN}║${NC} ${YELLOW}⚠ 注册功能已开启,注册完账号后建议关闭:${NC}"
|
||
echo -e "${GREEN}║${NC} ${YELLOW} 修改 .env 中 SIGNUPS_ALLOWED=false${NC}"
|
||
echo -e "${GREEN}║${NC} ${YELLOW} 然后 docker compose up -d(不是 restart)${NC}"
|
||
fi
|
||
echo -e "${GREEN}║${NC}"
|
||
echo -e "${GREEN}╚══════════════════════════════════════════════════════════╝${NC}"
|
||
echo ""
|
||
echo "常用命令:"
|
||
echo " 查看日志: cd $SCRIPT_DIR && docker compose logs -f"
|
||
echo " 重启服务: cd $SCRIPT_DIR && docker compose restart"
|
||
echo " 停止服务: cd $SCRIPT_DIR && docker compose down"
|
||
echo " 备份数据: cd $SCRIPT_DIR && bash backup.sh"
|
||
echo ""
|
||
}
|
||
|
||
# =============================================================
|
||
# 主流程
|
||
# =============================================================
|
||
main() {
|
||
echo -e "${CYAN}"
|
||
echo " __ __ _ _"
|
||
echo " \\ \\ / /_ _ _ _| | |___ ____ _ _ __ ____| | ___ _ __"
|
||
echo " \\ \\ / / _\` | | | | | __\\ \\ /\\ / / _\` | '__/ _ | |/ _ \\ '_ \\"
|
||
echo " \\ V / (_| | |_| | | |_ \\ V V / (_| | | | (_| | __/ | | |"
|
||
echo " \\_/ \\__,_|\\__,_|_|\\__| \\_/\\_/ \\__,_|_| \\__,_|\\___|_| |_|"
|
||
echo -e "${NC}"
|
||
echo ""
|
||
|
||
check_root
|
||
load_base_env "$BASE_DIR"
|
||
|
||
# Step 1: 系统初始化
|
||
init_system
|
||
|
||
# Step 2: 安装 Docker
|
||
install_docker
|
||
|
||
# Step 3: 安装 Nginx
|
||
install_nginx
|
||
|
||
# Step 4: 初始化配置
|
||
init_env
|
||
|
||
# Step 5: 配置 Docker 镜像加速
|
||
configure_docker_mirrors
|
||
|
||
# Step 6: 创建数据目录
|
||
create_dirs
|
||
|
||
# Step 7: 配置防火墙
|
||
setup_firewall_base
|
||
|
||
# Step 8: 配置 SSL 证书
|
||
setup_ssl_cert "${VAULTWARDEN_DOMAIN}" "${CERTBOT_EMAIL}" "vaultwarden"
|
||
|
||
# Step 9: 部署 Nginx 反向代理
|
||
deploy_nginx_conf "$SCRIPT_DIR/nginx/vaultwarden.conf" "${VAULTWARDEN_DOMAIN}" "vaultwarden"
|
||
|
||
# Step 10: 启动服务
|
||
start_services
|
||
|
||
# 显示部署信息
|
||
show_info
|
||
log "===== Vaultwarden 部署完成 ====="
|
||
}
|
||
|
||
main "$@"
|