#!/usr/bin/env bash # firstboot.sh - 由 weknora-firstboot.service 在新实例首次开机时自动执行。 # 任务: 生成随机密钥写入 .env -> 启动容器 -> 输出凭证 -> 标记完成 + 自禁用。 # # 幂等性策略: # 1) 一上来先写 ${MARKER} 标记 "已生成过密钥", # 之后即便 docker compose 失败导致脚本中断, 重启后由 unit 的 # ConditionPathExists=!${MARKER} 拦住, 不会再生成新密钥覆盖 .env。 # (旧密钥已经写进 postgres 数据卷, 二次生成会导致数据库永久无法登录) # 2) 失败时 unit 标记 failed, 用户可手动 docker compose up -d 恢复; # 凭证仍可在 ${ENV_FILE} 中查到。 set -euo pipefail WEKNORA_DIR="${WEKNORA_DIR:-/opt/WeKnora}" ENV_FILE="${WEKNORA_DIR}/.env" CRED_FILE="/root/weknora-credentials.txt" LOG_FILE="/var/log/weknora-firstboot.log" MARKER="${WEKNORA_DIR}/.firstboot.done" exec >>"${LOG_FILE}" 2>&1 echo "==== firstboot started at $(date -Iseconds) ====" if [[ -f "${MARKER}" ]]; then echo "marker ${MARKER} exists, skip (already initialized)" exit 0 fi if [[ ! -f "${ENV_FILE}" ]]; then echo "ERROR: ${ENV_FILE} not found" exit 1 fi DOCKER_BIN="$(command -v docker || true)" if [[ -z "${DOCKER_BIN}" ]]; then echo "ERROR: docker binary not found in PATH" exit 1 fi # 生成 32 字节强随机字符串(用于 AES-256 key, 必须刚好 32 字节) gen32() { LC_ALL=C tr -dc 'A-Za-z0-9' >"${ENV_FILE}" fi } replace DB_PASSWORD "${DB_PWD}" replace REDIS_PASSWORD "${REDIS_PWD}" replace JWT_SECRET "${JWT}" replace SYSTEM_AES_KEY "${SYS_AES}" replace TENANT_AES_KEY "${TENANT_AES}" replace GIN_MODE "release" # 关键: .env 改完立刻写 marker。 # 这之后即便 docker compose up 失败, 重启也不会再次重写 .env, # 防止与 postgres 已经持久化的密码不一致。 umask 077 touch "${MARKER}" chmod 0600 "${MARKER}" echo "env updated, marker written, starting docker compose..." cd "${WEKNORA_DIR}" "${DOCKER_BIN}" compose up -d # 尝试拿公网 IP, 失败就用内网 PUB_IP=$(curl -fsS --max-time 5 https://ifconfig.me 2>/dev/null \ || curl -fsS --max-time 5 https://api.ipify.org 2>/dev/null \ || hostname -I | awk '{print $1}') cat >"${CRED_FILE}" <