Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a9dc056506 | |||
| 9ef58b5724 | |||
| c9067179ff | |||
| 5e3b5e83a4 | |||
| d4e62b1fb1 |
@@ -1 +1 @@
|
|||||||
0.4.8
|
0.4.11
|
||||||
|
|||||||
@@ -1,91 +1,128 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
# CloudSearch 一键部署
|
||||||
cd "$SCRIPT_DIR"
|
# curl -sS https://gitea.timxx.cn/admin/CloudSearch/raw/branch/master/source_clean/deploy.sh | bash
|
||||||
|
#
|
||||||
|
# 环境变量(可选):
|
||||||
|
# CORS_ORIGIN - 域名 (默认自动检测公网IP)
|
||||||
|
# ADMIN_PASSWORD - 管理员密码 (留空自动生成)
|
||||||
|
# JWT_SECRET - JWT密钥 (留空自动生成)
|
||||||
|
# DEPLOY_DIR - 部署目录 (默认 /opt/cloudsearch)
|
||||||
|
|
||||||
# 如果 docker-compose.yml 不存在,自动下载
|
REPO_URL="https://gitea.timxx.cn/admin/CloudSearch.git"
|
||||||
if [ ! -f docker-compose.yml ]; then
|
DEPLOY_DIR="${DEPLOY_DIR:-/opt/cloudsearch}"
|
||||||
echo "📥 下载 docker-compose.yml..."
|
|
||||||
wget -q https://gitea.timxx.cn/admin/CloudSearch/raw/branch/master/source_clean/docker-compose.yml || {
|
info() { echo "[INFO] $*"; }
|
||||||
echo "❌ 下载失败,请检查网络"
|
warn() { echo "[WARN] $*"; }
|
||||||
exit 1
|
err() { echo "[ERROR] $*"; }
|
||||||
}
|
|
||||||
|
command -v docker &>/dev/null || { err "Docker未安装"; exit 1; }
|
||||||
|
|
||||||
|
# -- CORS_ORIGIN --
|
||||||
|
if [ -z "$CORS_ORIGIN" ]; then
|
||||||
|
PUBLIC_IP=$(curl -s --connect-timeout 3 ifconfig.me 2>/dev/null || curl -s --connect-timeout 3 ip.sb 2>/dev/null || echo "")
|
||||||
|
if [ -n "$PUBLIC_IP" ]; then
|
||||||
|
CORS_ORIGIN="http://${PUBLIC_IP}:9527"
|
||||||
|
else
|
||||||
|
CORS_ORIGIN="http://localhost:9527"
|
||||||
|
fi
|
||||||
|
warn "CORS_ORIGIN=$CORS_ORIGIN (自动检测)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "🔍 检测 Redis..."
|
# -- 拉取仓库 --
|
||||||
|
info "拉取仓库..."
|
||||||
EXISTING_REDIS=$(docker ps --format '{{.Names}}' | grep -i redis | head -1)
|
if [ -d "$DEPLOY_DIR/.git" ]; then
|
||||||
|
cd "$DEPLOY_DIR" && git pull --ff-only origin master 2>/dev/null || true
|
||||||
if [ -n "$EXISTING_REDIS" ]; then
|
|
||||||
if docker network inspect cloudsearch-net --format '{{range .Containers}}{{.Name}} {{end}}' 2>/dev/null | grep -qw "$EXISTING_REDIS"; then
|
|
||||||
echo "✅ 已有 Redis: $EXISTING_REDIS (已加入 cloudsearch-net),跳过创建"
|
|
||||||
else
|
|
||||||
echo "✅ 已有 Redis: $EXISTING_REDIS,正在加入网络..."
|
|
||||||
docker network connect cloudsearch-net "$EXISTING_REDIS" 2>/dev/null || true
|
|
||||||
echo " ✅ 已加入 cloudsearch-net"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 检测 Redis 密码 (多种来源)
|
|
||||||
REDIS_PASS=""
|
|
||||||
|
|
||||||
# 方法1: 从命令行参数 --requirepass
|
|
||||||
PASS_FROM_CMD=$(docker inspect "$EXISTING_REDIS" --format '{{range .Config.Cmd}}{{println .}}{{end}}' 2>/dev/null | grep -A1 'requirepass' | tail -1 | tr -d '[:space:]' || true)
|
|
||||||
if [ -n "$PASS_FROM_CMD" ] && [ "$PASS_FROM_CMD" != "redis-server" ] && [ "$PASS_FROM_CMD" != "/etc/redis/redis.conf" ]; then
|
|
||||||
REDIS_PASS="$PASS_FROM_CMD"
|
|
||||||
echo " 🔑 从启动参数检测到 Redis 密码"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 方法2: 从 redis.conf 读取 (1Panel 常见配置方式)
|
|
||||||
if [ -z "$REDIS_PASS" ]; then
|
|
||||||
PASS_FROM_CONF=$(docker exec "$EXISTING_REDIS" cat /etc/redis/redis.conf 2>/dev/null | grep '^requirepass ' | awk '{print $2}' | tr -d '"' || true)
|
|
||||||
if [ -n "$PASS_FROM_CONF" ]; then
|
|
||||||
REDIS_PASS="$PASS_FROM_CONF"
|
|
||||||
echo " 🔑 从 redis.conf 检测到 Redis 密码"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 方法3: 尝试无密码连接测试
|
|
||||||
if [ -z "$REDIS_PASS" ]; then
|
|
||||||
if docker exec "$EXISTING_REDIS" redis-cli ping 2>/dev/null | grep -q PONG; then
|
|
||||||
echo " ℹ️ Redis 无密码(已通过连接测试验证)"
|
|
||||||
else
|
|
||||||
# 有密码但检测不到 — 尝试从 redis-cli 的错误消息中提取
|
|
||||||
AUTH_ERR=$(docker exec "$EXISTING_REDIS" redis-cli ping 2>&1 || true)
|
|
||||||
if echo "$AUTH_ERR" | grep -q "NOAUTH\|AUTH"; then
|
|
||||||
echo " ⚠️ Redis 需要密码但无法自动检测,请手动设置 REDIS_URL"
|
|
||||||
echo " 提示: 在 .env 中设置 REDIS_URL=redis://:密码@${EXISTING_REDIS}:6379"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$REDIS_PASS" ]; then
|
|
||||||
# 对密码中的特殊字符进行URL编码
|
|
||||||
ENCODED_PASS=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$REDIS_PASS', safe=''))" 2>/dev/null || echo "$REDIS_PASS")
|
|
||||||
REDIS_URL="redis://:${ENCODED_PASS}@${EXISTING_REDIS}:6379"
|
|
||||||
else
|
|
||||||
REDIS_URL="redis://${EXISTING_REDIS}:6379"
|
|
||||||
fi
|
|
||||||
PROFILE=""
|
|
||||||
else
|
else
|
||||||
echo "📦 未检测到 Redis,将自动创建..."
|
rm -rf "$DEPLOY_DIR"
|
||||||
REDIS_URL="redis://CloudSearch_Redis:6379"
|
git clone --depth 1 "$REPO_URL" "$DEPLOY_DIR"
|
||||||
PROFILE="--profile full"
|
fi
|
||||||
|
cd "$DEPLOY_DIR/source_clean"
|
||||||
|
|
||||||
|
# -- 切换镜像模式 --
|
||||||
|
if grep -q '^ build:' docker-compose.yml 2>/dev/null; then
|
||||||
|
info "切换镜像模式..."
|
||||||
|
sed -i 's/^ build:/ # build:/' docker-compose.yml
|
||||||
|
sed -i 's/^ context:/ # context:/' docker-compose.yml
|
||||||
|
sed -i 's/^ dockerfile:/ # dockerfile:/' docker-compose.yml
|
||||||
|
sed -i 's|^ # image: gitea.timxx.cn/admin/cloudsearch:latest| image: gitea.timxx.cn/admin/cloudsearch:latest|' docker-compose.yml
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 生成 .env
|
# -- 检测 Redis --
|
||||||
cat > .env <<EOF
|
info "检测Redis..."
|
||||||
|
EXISTING_REDIS=$(docker ps --format '{{.Names}}' 2>/dev/null | grep -i redis | grep -v CloudSearch | head -1)
|
||||||
|
if [ -n "$EXISTING_REDIS" ]; then
|
||||||
|
REDIS_URL="redis://${EXISTING_REDIS}:6379"
|
||||||
|
info "复用已有Redis: $EXISTING_REDIS"
|
||||||
|
else
|
||||||
|
REDIS_URL="redis://redis:6379"
|
||||||
|
info "使用Compose自带Redis"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -- 生成密钥 --
|
||||||
|
JWT_SECRET="${JWT_SECRET:-$(openssl rand -hex 32)}"
|
||||||
|
ADMIN_PASSWORD="${ADMIN_PASSWORD:-$(openssl rand -base64 12 | tr -d '=+/' | head -c 16)}"
|
||||||
|
|
||||||
|
# -- 生成 .env --
|
||||||
|
cat > .env <<ENVEOF
|
||||||
|
CORS_ORIGIN=${CORS_ORIGIN}
|
||||||
|
JWT_SECRET=${JWT_SECRET}
|
||||||
|
ADMIN_PASSWORD=${ADMIN_PASSWORD}
|
||||||
REDIS_URL=${REDIS_URL}
|
REDIS_URL=${REDIS_URL}
|
||||||
CORS_ORIGIN=https://zy.hk.timxx.cn
|
LOG_LEVEL=${LOG_LEVEL:-info}
|
||||||
JWT_SECRET=cloudsearch-jwt-secret-2024
|
ENVEOF
|
||||||
ADMIN_PASSWORD=0nL5kLhMIJ1121PYmQb25A
|
|
||||||
LOG_LEVEL=info
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo ""
|
info "管理员: admin / ${ADMIN_PASSWORD}"
|
||||||
echo "🚀 启动服务..."
|
|
||||||
docker compose $PROFILE up -d
|
|
||||||
|
|
||||||
echo ""
|
# -- 部署 --
|
||||||
echo "✅ 部署完成"
|
info "拉取镜像..."
|
||||||
docker compose ps
|
docker compose pull app 2>/dev/null || true
|
||||||
|
|
||||||
|
info "停止旧服务..."
|
||||||
|
docker compose down --remove-orphans 2>/dev/null || true
|
||||||
|
|
||||||
|
info "启动服务..."
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# -- 等待就绪 --
|
||||||
|
info "等待服务就绪..."
|
||||||
|
for i in $(seq 1 20); do
|
||||||
|
if curl -s -o /dev/null -w '%{http_code}' http://localhost:9527/health 2>/dev/null | grep -q '200'; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
# -- 强制写入管理员密码 --
|
||||||
|
info "同步管理员密码..."
|
||||||
|
sleep 3
|
||||||
|
docker exec CloudSearch_App node -e '
|
||||||
|
var bcrypt = require("bcryptjs");
|
||||||
|
var Database = require("better-sqlite3");
|
||||||
|
var db = new Database("/data/database.sqlite");
|
||||||
|
var hash = bcrypt.hashSync("'"${ADMIN_PASSWORD}"'", 10);
|
||||||
|
var existing = db.prepare("SELECT id FROM admins WHERE username = ?").get("admin");
|
||||||
|
if (existing) {
|
||||||
|
db.prepare("UPDATE admins SET password_hash = ? WHERE username = ?").run(hash, "admin");
|
||||||
|
} else {
|
||||||
|
db.prepare("INSERT INTO admins (username, password_hash) VALUES (?, ?)").run("admin", hash);
|
||||||
|
}
|
||||||
|
db.close();
|
||||||
|
' 2>/dev/null && info "密码已同步" || warn "密码同步失败,请稍后重试"
|
||||||
|
|
||||||
|
# -- 验证 --
|
||||||
|
if docker compose ps 2>/dev/null | grep -q 'Up'; then
|
||||||
|
echo ""
|
||||||
|
echo "=============================================="
|
||||||
|
echo " CloudSearch 部署完成"
|
||||||
|
echo "=============================================="
|
||||||
|
docker compose ps
|
||||||
|
echo ""
|
||||||
|
echo " 管理后台: ${CORS_ORIGIN}/admin/login"
|
||||||
|
echo " 用户名: admin"
|
||||||
|
echo " 密码: ${ADMIN_PASSWORD}"
|
||||||
|
else
|
||||||
|
err "启动失败: docker compose logs"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ function decryptCookie(encrypted: string): string {
|
|||||||
|
|
||||||
function extractCookieUid(cookie: string): string {
|
function extractCookieUid(cookie: string): string {
|
||||||
if (!cookie) return '';
|
if (!cookie) return '';
|
||||||
let m = cookie.match(/__uid=([a-zA-Z0-9+/=_-]+)/);
|
let m = cookie.match(/__uid=([^;]+)/);
|
||||||
if (m) return m[1];
|
if (m) return m[1];
|
||||||
m = cookie.match(/b-user-id=([a-zA-Z0-9-]+)/);
|
m = cookie.match(/b-user-id=([^;]+)/);
|
||||||
if (m) return m[1];
|
if (m) return m[1];
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ function runMigrations(db: Database.Database): void {
|
|||||||
cloud_type TEXT NOT NULL,
|
cloud_type TEXT NOT NULL,
|
||||||
cookie TEXT,
|
cookie TEXT,
|
||||||
cloud_type_uid TEXT DEFAULT NULL,
|
cloud_type_uid TEXT DEFAULT NULL,
|
||||||
|
cookie_uid TEXT DEFAULT NULL,
|
||||||
nickname TEXT,
|
nickname TEXT,
|
||||||
is_active INTEGER NOT NULL DEFAULT 1,
|
is_active INTEGER NOT NULL DEFAULT 1,
|
||||||
storage_used TEXT,
|
storage_used TEXT,
|
||||||
@@ -108,6 +109,12 @@ function runMigrations(db: Database.Database): void {
|
|||||||
value TEXT NOT NULL DEFAULT '',
|
value TEXT NOT NULL DEFAULT '',
|
||||||
description TEXT,
|
description TEXT,
|
||||||
updated_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
|
updated_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
|
||||||
|
CREATE TABLE IF NOT EXISTS push_users (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
account TEXT NOT NULL UNIQUE,
|
||||||
|
notify_config TEXT,
|
||||||
|
created_at TEXT NOT NULL DEFAULT (datetime('now','localtime')),
|
||||||
|
updated_at TEXT NOT NULL DEFAULT (datetime('now','localtime'))
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS content_cache (
|
CREATE TABLE IF NOT EXISTS content_cache (
|
||||||
@@ -149,6 +156,8 @@ function migrateSaveRecords(db: Database.Database): void {
|
|||||||
{ col: 'request_url', def: 'TEXT' },
|
{ col: 'request_url', def: 'TEXT' },
|
||||||
{ col: 'ip_location', def: 'TEXT' },
|
{ col: 'ip_location', def: 'TEXT' },
|
||||||
{ col: 'original_folder_name', def: 'TEXT' },
|
{ col: 'original_folder_name', def: 'TEXT' },
|
||||||
|
{ col: 'config_id', def: 'INTEGER' },
|
||||||
|
{ col: 'promotion_account', def: 'TEXT' },
|
||||||
];
|
];
|
||||||
for (const { col, def } of newCols) {
|
for (const { col, def } of newCols) {
|
||||||
try {
|
try {
|
||||||
@@ -207,6 +216,7 @@ function migrateCloudConfigs(db: Database.Database): void {
|
|||||||
cookie TEXT,
|
cookie TEXT,
|
||||||
cloud_type_uid TEXT DEFAULT NULL,
|
cloud_type_uid TEXT DEFAULT NULL,
|
||||||
nickname TEXT,
|
nickname TEXT,
|
||||||
|
cookie_uid TEXT DEFAULT NULL,
|
||||||
is_active INTEGER NOT NULL DEFAULT 1,
|
is_active INTEGER NOT NULL DEFAULT 1,
|
||||||
storage_used TEXT,
|
storage_used TEXT,
|
||||||
storage_total TEXT,
|
storage_total TEXT,
|
||||||
|
|||||||
Reference in New Issue
Block a user