#!/usr/bin/env bash set -Eeuo pipefail REPOSITORY_REF="${REPOSITORY_REF:-main}" INSTALL_DIR="${INSTALL_DIR:-/opt/open-ai-canvas}" CANVAS_HTTP_PORT="${CANVAS_HTTP_PORT:-3000}" REQUESTED_IMAGE_TAG="${CANVAS_IMAGE_TAG:-}" CANVAS_IMAGE_TAG="${REQUESTED_IMAGE_TAG:-latest}" CANVAS_IMAGE_TAG="${CANVAS_IMAGE_TAG#v}" COMPOSE_FILE="docker-compose.deploy.yml" COMPOSE_URL="${COMPOSE_URL:-https://raw.githubusercontent.com/ddcat-ai/open-ai-canvas/${REPOSITORY_REF}/${COMPOSE_FILE}}" UPDATER_INSTALL_URL="${UPDATER_INSTALL_URL:-https://raw.githubusercontent.com/ddcat-ai/open-ai-canvas/${REPOSITORY_REF}/scripts/install-host-updater.sh}" step() { printf '\n==> %s\n' "$1" } fail() { printf '\n安装失败:%s\n' "$1" >&2 exit 1 } require_root() { if [[ "${EUID}" -ne 0 ]]; then fail "请使用 README 中带 sudo 的一键安装命令" fi [[ "$(uname -s)" == "Linux" ]] || fail "一键部署脚本仅支持 Linux 服务器" [[ "$CANVAS_HTTP_PORT" =~ ^[0-9]+$ ]] || fail "CANVAS_HTTP_PORT 必须是 1 到 65535 的数字" ((CANVAS_HTTP_PORT >= 1 && CANVAS_HTTP_PORT <= 65535)) || fail "CANVAS_HTTP_PORT 必须是 1 到 65535 的数字" [[ "$CANVAS_IMAGE_TAG" =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$ ]] || fail "CANVAS_IMAGE_TAG 不是有效的 Docker 镜像标签" } install_packages() { local packages=(ca-certificates curl openssl) if command -v apt-get >/dev/null 2>&1; then apt-get update DEBIAN_FRONTEND=noninteractive apt-get install -y "${packages[@]}" elif command -v dnf >/dev/null 2>&1; then dnf install -y "${packages[@]}" elif command -v yum >/dev/null 2>&1; then yum install -y "${packages[@]}" else fail "暂不支持当前 Linux 发行版,请先手动安装 Docker、curl 和 OpenSSL" fi } install_docker() { if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then return fi step "安装 Docker 和 Docker Compose" local installer installer="$(mktemp)" curl -fsSL https://get.docker.com -o "$installer" sh "$installer" rm -f "$installer" if command -v systemctl >/dev/null 2>&1; then systemctl enable --now docker elif command -v service >/dev/null 2>&1; then service docker start fi docker compose version >/dev/null 2>&1 || fail "Docker Compose 安装失败" } login_ghcr() { if [[ -n "${GHCR_USERNAME:-}" || -n "${GHCR_TOKEN:-}" ]]; then [[ -n "${GHCR_USERNAME:-}" && -n "${GHCR_TOKEN:-}" ]] || fail "GHCR_USERNAME 和 GHCR_TOKEN 必须同时配置" step "登录 GitHub Container Registry" # token 只通过 stdin 交给 Docker,避免出现在命令参数和进程列表中。 printf '%s' "$GHCR_TOKEN" | docker login ghcr.io --username "$GHCR_USERNAME" --password-stdin fi } prepare_environment() { mkdir -p "$INSTALL_DIR" cd "$INSTALL_DIR" if [[ -f .env ]]; then grep -Eq '^POSTGRES_PASSWORD=.+$' .env || fail "现有 .env 缺少 POSTGRES_PASSWORD" grep -Eq '^DATABASE_URL=.+$' .env || fail "现有 .env 缺少 DATABASE_URL" local configured_http_port configured_http_port="$(sed -n 's/^CANVAS_HTTP_PORT=//p' .env | tail -n 1)" if [[ -n "$configured_http_port" ]]; then [[ "$configured_http_port" =~ ^[0-9]+$ ]] || fail ".env 中的 CANVAS_HTTP_PORT 无效" ((configured_http_port >= 1 && configured_http_port <= 65535)) || fail ".env 中的 CANVAS_HTTP_PORT 无效" CANVAS_HTTP_PORT="$configured_http_port" fi local configured_image_tag configured_image_tag="$(sed -n 's/^CANVAS_IMAGE_TAG=//p' .env | tail -n 1)" if [[ -n "$REQUESTED_IMAGE_TAG" ]]; then local temporary_env temporary_env="$(mktemp "${INSTALL_DIR}/.env.XXXXXX")" awk -v image_tag="$CANVAS_IMAGE_TAG" ' BEGIN { updated=0 } /^CANVAS_IMAGE_TAG=/ { print "CANVAS_IMAGE_TAG=" image_tag; updated=1; next } { print } END { if (!updated) print "CANVAS_IMAGE_TAG=" image_tag } ' .env > "$temporary_env" chmod --reference=.env "$temporary_env" mv "$temporary_env" .env elif [[ -n "$configured_image_tag" ]]; then [[ "$configured_image_tag" =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$ ]] || fail ".env 中的 CANVAS_IMAGE_TAG 无效" CANVAS_IMAGE_TAG="${configured_image_tag#v}" fi return fi step "生成 PostgreSQL 随机密码和部署配置" local database_password database_password="$(openssl rand -hex 32)" umask 077 cat >.env <