周玉环 hace 23 horas
padre
commit
daf141c72c

+ 131 - 0
QUICKSTART.md

@@ -0,0 +1,131 @@
+# Cloudpods 快速开始
+
+## 新同事开发环境搭建(5 分钟)
+
+### 1. 克隆代码
+
+```bash
+git clone <repository-url>
+cd Cloudpods
+```
+
+### 2. 安装 MySQL
+
+确保 MySQL 已安装并启动:
+
+```bash
+# macOS
+brew install mysql
+brew services start mysql
+
+# Ubuntu/Debian
+sudo apt install mysql-server
+sudo systemctl start mysql
+
+# Windows (WSL)
+sudo apt install mysql-server
+sudo service mysql start
+```
+
+### 3. 配置数据库
+
+复制并编辑配置文件:
+
+```bash
+# 配置文件已存在,直接编辑
+vim .env.local
+```
+
+修改数据库密码(如果有):
+```bash
+DB_HOST=localhost
+DB_PORT=3306
+DB_USER=root
+DB_PASSWORD=你的MySQL密码  # 没有密码就留空
+```
+
+### 4. 初始化数据库
+
+```bash
+bash backend/dev-scripts/init-database.sh
+```
+
+### 5. 编译后端
+
+```bash
+cd backend
+make
+```
+
+### 6. 准备 etcd(非 macOS 用户)
+
+**macOS 用户跳过此步骤**
+
+**Linux 用户**:
+```bash
+cd backend/bin
+curl -L https://github.com/etcd-io/etcd/releases/download/v3.6.10/etcd-v3.6.10-linux-amd64.tar.gz -o etcd.tar.gz
+tar xzvf etcd.tar.gz
+cp etcd-v3.6.10-linux-amd64/etcd* linux/
+rm -rf etcd.tar.gz etcd-v3.6.10-linux-amd64
+cd ../..
+```
+
+**Windows 用户**:建议使用 WSL,然后按 Linux 步骤操作
+
+### 7. 启动后端服务
+
+```bash
+bash backend/dev-scripts/start-services.sh start
+```
+
+等待 30 秒,所有服务启动完成。
+
+### 8. 启动前端(可选)
+
+```bash
+cd frontend
+pnpm install
+pnpm run dev
+```
+
+### 9. 访问系统
+
+打开浏览器访问:http://localhost:8080
+
+**登录信息**:
+- 用户名:`sysadmin`
+- 密码:`sysadmin`
+- 域:`Default`
+
+## 常用命令
+
+```bash
+# 查看服务状态
+bash backend/dev-scripts/start-services.sh status
+
+# 停止服务
+bash backend/dev-scripts/start-services.sh stop
+
+# 重启服务
+bash backend/dev-scripts/start-services.sh restart
+
+# 查看日志
+tail -f backend/logs/keystone.log
+tail -f backend/logs/region.log
+tail -f backend/logs/apigateway.log
+```
+
+## 遇到问题?
+
+查看详细文档:[backend/dev-scripts/README.md](backend/dev-scripts/README.md)
+
+## 服务端口
+
+- 前端:http://localhost:8080
+- API Gateway:http://localhost:30300
+- Keystone:http://localhost:35357
+- Region:http://localhost:30888
+- Glance:http://localhost:9292
+- Yunionconf:http://localhost:30889
+- etcd:http://localhost:2379

+ 7 - 0
backend/.gitignore

@@ -37,6 +37,13 @@ GRTAGS
 GTAGS
 pkg/generated
 
+# Runtime data
+data/etcd/
+data/glance/
+data/torrents/
+logs/*.log
+logs/*.pid
+
 *.orig
 
 # gotext extracted

+ 220 - 0
backend/dev-scripts/README.md

@@ -0,0 +1,220 @@
+# Cloudpods 开发环境搭建指南
+
+本指南帮助开发者快速搭建 Cloudpods 本地开发环境。
+
+## 前置要求
+
+### 1. 数据库
+- MySQL 5.7+ 或 MariaDB 10.3+
+- 确保 MySQL 服务已启动
+
+### 2. Go 环境
+- Go 1.19+
+- 用于编译后端服务
+
+### 3. Node.js 环境(前端开发)
+- Node.js 16+
+- pnpm 或 npm
+
+### 4. 操作系统
+- macOS / Linux / Windows (推荐使用 WSL)
+
+## 快速开始
+
+### 第一步:配置数据库连接
+
+编辑项目根目录的 `.env.local` 文件:
+
+```bash
+# 数据库配置
+DB_HOST=localhost
+DB_PORT=3306
+DB_USER=root
+DB_PASSWORD=你的密码
+```
+
+**注意:** 如果 MySQL 没有密码,`DB_PASSWORD` 留空即可。
+
+### 第二步:初始化数据库
+
+```bash
+# 创建数据库和初始用户
+bash backend/dev-scripts/init-database.sh
+```
+
+这个脚本会:
+- 创建 `yunioncloud` 数据库
+- 创建初始管理员用户 `sysadmin`(密码:sysadmin)
+- 设置数据库字符集为 utf8mb4
+
+### 第三步:编译后端服务
+
+```bash
+cd backend
+
+# 编译所有服务
+make
+
+# 或者只编译需要的服务
+make cmd/keystone
+make cmd/region
+make cmd/glance
+make cmd/apigateway
+make cmd/yunionconf
+make cmd/scheduledtask
+```
+
+编译后的二进制文件在 `backend/_output/bin/` 目录。
+
+### 第四步:准备 etcd
+
+etcd 二进制文件按平台分目录存放在 `backend/bin/` 下:
+
+- **macOS 用户**:已包含,无需额外操作
+- **Linux 用户**:参考 `backend/bin/README.md` 下载 Linux 版本
+- **Windows 用户**:建议使用 WSL,然后下载 Linux 版本
+
+### 第五步:启动所有服务
+
+```bash
+# 一键启动所有服务
+bash backend/dev-scripts/start-services.sh start
+
+# 查看服务状态
+bash backend/dev-scripts/start-services.sh status
+
+# 停止所有服务
+bash backend/dev-scripts/start-services.sh stop
+
+# 重启所有服务
+bash backend/dev-scripts/start-services.sh restart
+```
+
+启动脚本会自动:
+1. 启动 etcd
+2. 启动 keystone(认证服务)
+3. 初始化 RBAC 权限策略
+4. 注册服务到 Service Catalog
+5. 启动其他业务服务(region、glance、yunionconf 等)
+6. 启动 API Gateway
+
+### 第六步:启动前端(可选)
+
+```bash
+cd frontend
+
+# 安装依赖
+pnpm install
+
+# 启动开发服务器
+pnpm run dev
+```
+
+前端默认运行在 http://localhost:8080
+
+## 访问系统
+
+- **前端地址**:http://localhost:8080
+- **API Gateway**:http://localhost:30300
+- **Keystone**:http://localhost:35357
+
+**默认账号**:
+- 用户名:`sysadmin`
+- 密码:`sysadmin`
+- 域:`Default`
+
+## 常见问题
+
+### 1. 数据库连接失败
+
+检查:
+- MySQL 服务是否启动
+- `.env.local` 中的数据库配置是否正确
+- 数据库用户是否有权限
+
+### 2. 服务启动失败
+
+查看日志:
+```bash
+tail -f backend/logs/<service>.log
+```
+
+常见原因:
+- 端口被占用
+- 数据库连接失败
+- etcd 未启动
+
+### 3. etcd 启动失败
+
+检查:
+- 是否下载了对应平台的 etcd
+- 端口 2379 和 2380 是否被占用
+- 查看日志:`tail -f backend/logs/etcd.log`
+
+### 4. 前端无法访问后端 API
+
+检查:
+- API Gateway 是否启动(端口 30300)
+- 前端代理配置是否正确
+- 浏览器控制台是否有错误
+
+## 开发工作流
+
+### 修改后端代码后
+
+```bash
+# 重新编译
+cd backend
+make cmd/<service-name>
+
+# 重启服务
+bash dev-scripts/start-services.sh restart
+```
+
+### 修改前端代码后
+
+前端开发服务器会自动热重载,无需手动重启。
+
+### 清理环境
+
+```bash
+# 停止所有服务
+bash backend/dev-scripts/start-services.sh stop
+
+# 清理数据(可选,会删除所有数据)
+rm -rf backend/data/etcd
+rm -rf backend/data/glance
+mysql -u root -p -e "DROP DATABASE yunioncloud;"
+```
+
+## 脚本说明
+
+- `start-services.sh` - 主启动脚本,管理所有服务
+- `start-etcd.sh` - 启动 etcd 服务
+- `init-database.sh` - 初始化数据库
+- `init-admin-policy.sh` - 初始化 RBAC 策略
+- `register-services.sh` - 注册服务到 Service Catalog
+- `start-mock-monitor.py` - Mock 监控服务(可选)
+
+## 目录结构
+
+```
+backend/
+├── _output/bin/        # 编译后的二进制文件
+├── bin/                # 外部依赖(etcd)
+│   ├── darwin/         # macOS 版本
+│   ├── linux/          # Linux 版本
+│   └── windows/        # Windows 版本
+├── data/               # 运行时数据
+│   ├── etcd/           # etcd 数据
+│   └── glance/         # 镜像存储
+├── logs/               # 服务日志
+└── dev-scripts/        # 开发脚本
+```
+
+## 获取帮助
+
+如有问题,请:
+1. 查看服务日志:`backend/logs/<service>.log`
+2. 检查服务状态:`bash backend/dev-scripts/start-services.sh status`
+3. 联系团队成员

+ 109 - 0
backend/dev-scripts/init-admin-policy.sh

@@ -0,0 +1,109 @@
+#!/bin/bash
+
+# 初始化系统管理员策略
+# Initialize system administrator policy
+
+set -e
+
+# 加载数据库配置
+if [ -f "../.env.local" ]; then
+    source ../.env.local
+elif [ -f ".env.local" ]; then
+    source .env.local
+fi
+
+DB_HOST=${DB_HOST:-localhost}
+DB_PORT=${DB_PORT:-3306}
+DB_USER=${DB_USER:-root}
+DB_PASSWORD=${DB_PASSWORD}
+DB_NAME="yunioncloud"
+
+echo "=== 初始化系统管理员策略 ==="
+
+# 检查策略是否已存在
+POLICY_COUNT=$(mysql -h $DB_HOST -P $DB_PORT -u $DB_USER -p$DB_PASSWORD $DB_NAME -sN -e "SELECT COUNT(*) FROM policy WHERE name='system-admin-allow-all';")
+
+if [ "$POLICY_COUNT" -gt 0 ]; then
+    echo "✓ 系统管理员策略已存在"
+    exit 0
+fi
+
+echo "创建系统管理员策略..."
+
+# 创建策略SQL
+mysql -h $DB_HOST -P $DB_PORT -u $DB_USER -p$DB_PASSWORD $DB_NAME << 'EOF'
+-- 生成策略ID
+SET @policy_id = REPLACE(UUID(), '-', '');
+SET @admin_role_id = 'f86e3d4191ae4a6283fc9a9d6b65fe6f';
+SET @system_project_id = 'cb66410213744b9a857376e529797a18';
+
+-- 插入策略
+INSERT INTO policy (
+    id,
+    name,
+    type,
+    description,
+    enabled,
+    scope,
+    is_public,
+    public_scope,
+    domain_id,
+    is_system,
+    `blob`,
+    created_at,
+    updated_at,
+    update_version,
+    deleted
+) VALUES (
+    @policy_id,
+    'system-admin-allow-all',
+    'system-admin-allow-all',
+    'System administrator policy - allows all operations',
+    1,
+    'system',
+    1,
+    'system',
+    'default',
+    1,
+    '{"policy":{"*":{"*":{"*":"allow"}}}}',
+    NOW(),
+    NOW(),
+    0,
+    0
+);
+
+-- 绑定策略到admin角色和system项目
+INSERT INTO rolepolicy_tbl (
+    role_id,
+    project_id,
+    policy_id,
+    auth,
+    created_at,
+    updated_at,
+    update_version,
+    deleted
+) VALUES (
+    @admin_role_id,
+    @system_project_id,
+    @policy_id,
+    1,
+    NOW(),
+    NOW(),
+    0,
+    0
+);
+
+-- 启用sysadmin用户的web控制台访问
+UPDATE user SET allow_web_console = 1 WHERE name = 'sysadmin';
+
+SELECT 'Policy created and bound successfully' as status;
+EOF
+
+echo "✓ 系统管理员策略创建成功"
+echo ""
+echo "策略详情:"
+echo "  - 名称: system-admin-allow-all"
+echo "  - 范围: system"
+echo "  - 权限: 允许所有操作 (*/*/*)"
+echo "  - 绑定: admin角色 + system项目"
+echo "  - Web访问: 已启用"

+ 81 - 0
backend/dev-scripts/init-database.sh

@@ -0,0 +1,81 @@
+#!/bin/bash
+
+# 数据库初始化脚本
+
+set -e
+
+# 颜色定义
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m'
+
+# 脚本目录
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
+ENV_FILE="$PROJECT_ROOT/.env.local"
+
+echo -e "${GREEN}=== Cloudpods 数据库初始化 ===${NC}"
+echo ""
+
+# 加载环境变量
+if [ ! -f "$ENV_FILE" ]; then
+    echo -e "${RED}错误: 找不到环境变量文件 $ENV_FILE${NC}"
+    exit 1
+fi
+
+source "$ENV_FILE"
+
+# 检查必要的环境变量
+if [ -z "$DB_HOST" ] || [ -z "$DB_PORT" ] || [ -z "$DB_USER" ]; then
+    echo -e "${RED}错误: 数据库配置不完整${NC}"
+    exit 1
+fi
+
+echo -e "${GREEN}数据库配置:${NC}"
+echo "  Host: $DB_HOST:$DB_PORT"
+echo "  User: $DB_USER"
+echo "  Database: yunioncloud"
+echo ""
+
+# 检查 MySQL 连接
+echo -e "${YELLOW}检查数据库连接...${NC}"
+if [ -z "$DB_PASSWORD" ]; then
+    mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -e "SELECT 1;" > /dev/null 2>&1
+else
+    mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASSWORD" -e "SELECT 1;" > /dev/null 2>&1
+fi
+
+if [ $? -ne 0 ]; then
+    echo -e "${RED}✗ 无法连接到数据库${NC}"
+    echo -e "${YELLOW}请确保:${NC}"
+    echo "  1. MySQL 服务正在运行"
+    echo "  2. .env.local 中的数据库配置正确"
+    echo "  3. 数据库用户有足够的权限"
+    exit 1
+fi
+
+echo -e "${GREEN}✓ 数据库连接成功${NC}"
+echo ""
+
+# 创建数据库
+echo -e "${YELLOW}创建数据库 yunioncloud...${NC}"
+if [ -z "$DB_PASSWORD" ]; then
+    mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -e "CREATE DATABASE IF NOT EXISTS yunioncloud CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" 2>/dev/null
+else
+    mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASSWORD" -e "CREATE DATABASE IF NOT EXISTS yunioncloud CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" 2>/dev/null
+fi
+
+if [ $? -eq 0 ]; then
+    echo -e "${GREEN}✓ 数据库创建成功${NC}"
+else
+    echo -e "${RED}✗ 数据库创建失败${NC}"
+    exit 1
+fi
+
+echo ""
+echo -e "${GREEN}=== 数据库初始化完成 ===${NC}"
+echo ""
+echo -e "${YELLOW}注意: 服务首次启动时会自动同步数据库表结构${NC}"
+echo -e "${YELLOW}如果遇到 'database schema not in sync' 错误,${NC}"
+echo -e "${YELLOW}请在启动命令中添加 --auto-sync-table 参数${NC}"

+ 112 - 0
backend/dev-scripts/register-services.sh

@@ -0,0 +1,112 @@
+#!/bin/bash
+
+# 手动注册服务到Keystone service catalog
+# 用于在没有region服务的情况下注册基础服务
+
+set -e
+
+# 加载数据库配置
+if [ -f "../.env.local" ]; then
+    source ../.env.local
+elif [ -f ".env.local" ]; then
+    source .env.local
+fi
+
+DB_HOST=${DB_HOST:-localhost}
+DB_PORT=${DB_PORT:-3306}
+DB_USER=${DB_USER:-root}
+DB_PASSWORD=${DB_PASSWORD}
+DB_NAME="yunioncloud"
+
+echo "=== 注册服务到Keystone Service Catalog ==="
+
+# 注册服务和端点
+mysql -h $DB_HOST -P $DB_PORT -u $DB_USER -p$DB_PASSWORD $DB_NAME << 'EOF'
+
+-- 清理可能存在的旧数据
+DELETE FROM endpoint;
+DELETE FROM service;
+
+-- 1. 注册 identity 服务 (keystone)
+INSERT INTO service (id, name, type, enabled, created_at, updated_at, update_version, deleted, is_emulated, config_version)
+VALUES ('identity-service-id', 'keystone', 'identity', 1, NOW(), NOW(), 0, 0, 0, 0);
+
+INSERT INTO endpoint (id, service_id, region_id, interface, url, enabled, created_at, updated_at, update_version, deleted, name)
+VALUES 
+('identity-endpoint-public', 'identity-service-id', 'default', 'public', 'http://localhost:35357/v3', 1, NOW(), NOW(), 0, 0, 'keystone'),
+('identity-endpoint-internal', 'identity-service-id', 'default', 'internal', 'http://localhost:35357/v3', 1, NOW(), NOW(), 0, 0, 'keystone'),
+('identity-endpoint-admin', 'identity-service-id', 'default', 'admin', 'http://localhost:35357/v3', 1, NOW(), NOW(), 0, 0, 'keystone');
+
+-- 2. 注册 image 服务 (glance)
+INSERT INTO service (id, name, type, enabled, created_at, updated_at, update_version, deleted, is_emulated, config_version)
+VALUES ('image-service-id', 'glance', 'image', 1, NOW(), NOW(), 0, 0, 0, 0);
+
+INSERT INTO endpoint (id, service_id, region_id, interface, url, enabled, created_at, updated_at, update_version, deleted, name)
+VALUES 
+('image-endpoint-public', 'image-service-id', 'default', 'public', 'http://localhost:9292', 1, NOW(), NOW(), 0, 0, 'glance'),
+('image-endpoint-internal', 'image-service-id', 'default', 'internal', 'http://localhost:9292', 1, NOW(), NOW(), 0, 0, 'glance'),
+('image-endpoint-admin', 'image-service-id', 'default', 'admin', 'http://localhost:9292', 1, NOW(), NOW(), 0, 0, 'glance');
+
+-- 3. 注册 yunionconf 服务
+INSERT INTO service (id, name, type, enabled, created_at, updated_at, update_version, deleted, is_emulated, config_version)
+VALUES ('yunionconf-service-id', 'yunionconf', 'yunionconf', 1, NOW(), NOW(), 0, 0, 0, 0);
+
+INSERT INTO endpoint (id, service_id, region_id, interface, url, enabled, created_at, updated_at, update_version, deleted, name)
+VALUES 
+('yunionconf-endpoint-public', 'yunionconf-service-id', 'default', 'public', 'http://localhost:30889', 1, NOW(), NOW(), 0, 0, 'yunionconf'),
+('yunionconf-endpoint-internal', 'yunionconf-service-id', 'default', 'internal', 'http://localhost:30889', 1, NOW(), NOW(), 0, 0, 'yunionconf'),
+('yunionconf-endpoint-admin', 'yunionconf-service-id', 'default', 'admin', 'http://localhost:30889', 1, NOW(), NOW(), 0, 0, 'yunionconf');
+
+-- 4. 注册 monitor 服务
+INSERT INTO service (id, name, type, enabled, created_at, updated_at, update_version, deleted, is_emulated, config_version)
+VALUES ('monitor-service-id', 'monitor', 'monitor', 1, NOW(), NOW(), 0, 0, 0, 0);
+
+INSERT INTO endpoint (id, service_id, region_id, interface, url, enabled, created_at, updated_at, update_version, deleted, name)
+VALUES 
+('monitor-endpoint-public', 'monitor-service-id', 'default', 'public', 'http://localhost:30093', 1, NOW(), NOW(), 0, 0, 'monitor'),
+('monitor-endpoint-internal', 'monitor-service-id', 'default', 'internal', 'http://localhost:30093', 1, NOW(), NOW(), 0, 0, 'monitor'),
+('monitor-endpoint-admin', 'monitor-service-id', 'default', 'admin', 'http://localhost:30093', 1, NOW(), NOW(), 0, 0, 'monitor');
+
+-- 5. 注册 scheduledtask 服务
+INSERT INTO service (id, name, type, enabled, created_at, updated_at, update_version, deleted, is_emulated, config_version)
+VALUES ('scheduledtask-service-id', 'scheduledtask', 'scheduledtask', 1, NOW(), NOW(), 0, 0, 0, 0);
+
+INSERT INTO endpoint (id, service_id, region_id, interface, url, enabled, created_at, updated_at, update_version, deleted, name)
+VALUES 
+('scheduledtask-endpoint-public', 'scheduledtask-service-id', 'default', 'public', 'http://localhost:30891', 1, NOW(), NOW(), 0, 0, 'scheduledtask'),
+('scheduledtask-endpoint-internal', 'scheduledtask-service-id', 'default', 'internal', 'http://localhost:30891', 1, NOW(), NOW(), 0, 0, 'scheduledtask'),
+('scheduledtask-endpoint-admin', 'scheduledtask-service-id', 'default', 'admin', 'http://localhost:30891', 1, NOW(), NOW(), 0, 0, 'scheduledtask');
+
+-- 6. 注册 compute 服务 (region) - 使用compute_v2类型
+INSERT INTO service (id, name, type, enabled, created_at, updated_at, update_version, deleted, is_emulated, config_version)
+VALUES ('compute-service-id', 'region', 'compute_v2', 1, NOW(), NOW(), 0, 0, 0, 0);
+
+INSERT INTO endpoint (id, service_id, region_id, interface, url, enabled, created_at, updated_at, update_version, deleted, name)
+VALUES 
+('compute-endpoint-public', 'compute-service-id', 'default', 'public', 'http://localhost:30888', 1, NOW(), NOW(), 0, 0, 'region'),
+('compute-endpoint-internal', 'compute-service-id', 'default', 'internal', 'http://localhost:30888', 1, NOW(), NOW(), 0, 0, 'region'),
+('compute-endpoint-admin', 'compute-service-id', 'default', 'admin', 'http://localhost:30888', 1, NOW(), NOW(), 0, 0, 'region');
+
+-- 7. 注册 region 表(即使region服务未运行,也需要有region记录)
+INSERT INTO region (id, name, created_at, updated_at, update_version, deleted)
+VALUES ('default', 'default', NOW(), NOW(), 0, 0)
+ON DUPLICATE KEY UPDATE updated_at=NOW();
+
+SELECT 'Services registered successfully' as status;
+SELECT COUNT(*) as service_count FROM service;
+SELECT COUNT(*) as endpoint_count FROM endpoint;
+
+EOF
+
+echo ""
+echo "✓ 服务注册完成"
+echo ""
+echo "已注册的服务:"
+echo "  - identity (keystone) - http://localhost:35357/v3"
+echo "  - compute (region) - http://localhost:30888"
+echo "  - image (glance) - http://localhost:9292"
+echo "  - yunionconf - http://localhost:30889"
+echo "  - monitor - http://localhost:30093"
+echo "  - scheduledtask - http://localhost:30891"
+echo ""
+echo "注意: 需要重启服务以加载新的service catalog"

+ 83 - 0
backend/dev-scripts/start-etcd.sh

@@ -0,0 +1,83 @@
+#!/bin/bash
+
+# 启动etcd服务
+
+set -e
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+BACKEND_DIR="$(dirname "$SCRIPT_DIR")"
+
+# 检测操作系统
+OS_TYPE="$(uname -s)"
+case "$OS_TYPE" in
+    Darwin*) PLATFORM="darwin" ;;
+    Linux*) PLATFORM="linux" ;;
+    CYGWIN*|MINGW*|MSYS*) PLATFORM="windows" ;;
+    *) echo "不支持的操作系统: $OS_TYPE"; exit 1 ;;
+esac
+
+# 根据平台选择 etcd 二进制文件
+ETCD_BIN="$BACKEND_DIR/bin/$PLATFORM/etcd"
+if [ "$PLATFORM" = "windows" ]; then
+    ETCD_BIN="$BACKEND_DIR/bin/$PLATFORM/etcd.exe"
+fi
+
+ETCD_DATA_DIR="$BACKEND_DIR/data/etcd"
+ETCD_LOG="$BACKEND_DIR/logs/etcd.log"
+ETCD_PID="$BACKEND_DIR/logs/etcd.pid"
+
+# 检查 etcd 是否已安装
+if [ ! -f "$ETCD_BIN" ]; then
+    echo "错误: 找不到 $PLATFORM 平台的 etcd 二进制文件"
+    echo "路径: $ETCD_BIN"
+    echo ""
+    echo "请下载对应平台的 etcd 并放置到正确位置:"
+    echo "  macOS:   backend/bin/darwin/etcd"
+    echo "  Linux:   backend/bin/linux/etcd"
+    echo "  Windows: backend/bin/windows/etcd.exe"
+    echo ""
+    echo "下载地址: https://github.com/etcd-io/etcd/releases/tag/v3.6.10"
+    exit 1
+fi
+
+# 检查etcd是否已运行
+if [ -f "$ETCD_PID" ]; then
+    PID=$(cat "$ETCD_PID")
+    if ps -p "$PID" > /dev/null 2>&1; then
+        echo "✓ etcd 已在运行 (PID: $PID)"
+        exit 0
+    fi
+fi
+
+# 创建数据目录
+mkdir -p "$ETCD_DATA_DIR"
+mkdir -p "$(dirname "$ETCD_LOG")"
+
+echo "启动 etcd..."
+
+# 启动etcd
+nohup "$ETCD_BIN" \
+    --name cloudpods-etcd \
+    --data-dir "$ETCD_DATA_DIR" \
+    --listen-client-urls http://localhost:2379 \
+    --advertise-client-urls http://localhost:2379 \
+    --listen-peer-urls http://localhost:2380 \
+    --initial-advertise-peer-urls http://localhost:2380 \
+    --initial-cluster cloudpods-etcd=http://localhost:2380 \
+    > "$ETCD_LOG" 2>&1 &
+
+PID=$!
+echo $PID > "$ETCD_PID"
+
+# 等待etcd启动
+sleep 2
+
+if ps -p "$PID" > /dev/null 2>&1; then
+    echo "✓ etcd 启动成功 (PID: $PID)"
+    echo "  - Client URL: http://localhost:2379"
+    echo "  - Data Dir: $ETCD_DATA_DIR"
+else
+    echo "✗ etcd 启动失败"
+    tail -10 "$ETCD_LOG"
+    exit 1
+fi

+ 41 - 0
backend/dev-scripts/start-mock-monitor.py

@@ -0,0 +1,41 @@
+#!/usr/bin/env python3
+"""Mock Monitor服务 - 返回空数据避免前端报错"""
+
+import http.server
+import socketserver
+import json
+from urllib.parse import urlparse
+
+PORT = 30093
+
+class MockMonitorHandler(http.server.BaseHTTPRequestHandler):
+    def do_GET(self):
+        # 返回空的监控数据
+        response = {"data": [], "total": 0}
+        
+        self.send_response(200)
+        self.send_header('Content-type', 'application/json')
+        self.send_header('Access-Control-Allow-Origin', '*')
+        self.end_headers()
+        self.wfile.write(json.dumps(response).encode())
+    
+    def do_POST(self):
+        self.do_GET()
+    
+    def do_OPTIONS(self):
+        self.send_response(200)
+        self.send_header('Access-Control-Allow-Origin', '*')
+        self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
+        self.send_header('Access-Control-Allow-Headers', '*')
+        self.end_headers()
+    
+    def log_message(self, format, *args):
+        pass  # 静默日志
+
+if __name__ == '__main__':
+    with socketserver.TCPServer(("127.0.0.1", PORT), MockMonitorHandler) as httpd:
+        print(f"✓ Mock Monitor服务运行在 http://127.0.0.1:{PORT}")
+        try:
+            httpd.serve_forever()
+        except KeyboardInterrupt:
+            print("\n✓ Mock Monitor服务已停止")

+ 404 - 0
backend/dev-scripts/start-services.sh

@@ -0,0 +1,404 @@
+#!/bin/bash
+
+# Cloudpods 一键启动脚本 V2
+# 分阶段启动,先启动keystone,再启动其他服务
+
+set -e
+
+# 颜色定义
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m'
+
+# 检测操作系统
+OS_TYPE="$(uname -s)"
+case "$OS_TYPE" in
+    Darwin*) OS="macos" ;;
+    Linux*) OS="linux" ;;
+    CYGWIN*|MINGW*|MSYS*) 
+        echo -e "${RED}检测到 Windows 系统${NC}"
+        echo -e "${YELLOW}建议使用 WSL (Windows Subsystem for Linux) 运行此项目${NC}"
+        echo ""
+        echo "如果您在 WSL 中运行,请忽略此消息"
+        echo "如果您在原生 Windows 中运行,请:"
+        echo "  1. 安装 WSL: https://docs.microsoft.com/zh-cn/windows/wsl/install"
+        echo "  2. 在 WSL 中重新运行此脚本"
+        exit 1
+        ;;
+    *) echo -e "${RED}不支持的操作系统: $OS_TYPE${NC}"; exit 1 ;;
+esac
+
+# 脚本目录
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+BACKEND_DIR="$(dirname "$SCRIPT_DIR")"
+PROJECT_ROOT="$(dirname "$BACKEND_DIR")"
+BIN_DIR="$BACKEND_DIR/_output/bin"
+LOG_DIR="$BACKEND_DIR/logs"
+PID_DIR="$BACKEND_DIR/logs"
+ENV_FILE="$PROJECT_ROOT/.env.local"
+
+echo -e "${GREEN}=== Cloudpods 服务启动脚本 V2 ===${NC}"
+echo ""
+
+# 加载环境变量
+if [ ! -f "$ENV_FILE" ]; then
+    echo -e "${RED}错误: 找不到环境变量文件 $ENV_FILE${NC}"
+    exit 1
+fi
+
+source "$ENV_FILE"
+
+# 检查必要的环境变量
+if [ -z "$DB_HOST" ] || [ -z "$DB_PORT" ] || [ -z "$DB_USER" ]; then
+    echo -e "${RED}错误: 数据库配置不完整${NC}"
+    exit 1
+fi
+
+# 构建数据库连接字符串
+if [ -z "$DB_PASSWORD" ]; then
+    SQL_CONNECTION="mysql+pymysql://${DB_USER}@${DB_HOST}:${DB_PORT}/yunioncloud?charset=utf8"
+else
+    SQL_CONNECTION="mysql+pymysql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/yunioncloud?charset=utf8"
+fi
+
+# Keystone认证URL
+AUTH_URL="http://localhost:35357/v3"
+
+echo -e "${GREEN}配置信息:${NC}"
+echo "  数据库: $DB_HOST:$DB_PORT"
+echo "  认证URL: $AUTH_URL"
+echo "  系统: $OS"
+echo ""
+
+# 创建必要的目录
+mkdir -p "$LOG_DIR" "$PID_DIR"
+
+# 检查服务是否运行
+is_service_running() {
+    local service_name=$1
+    local pid_file="$PID_DIR/${service_name}.pid"
+    
+    if [ -f "$pid_file" ]; then
+        local pid=$(cat "$pid_file")
+        if ps -p "$pid" > /dev/null 2>&1; then
+            return 0
+        fi
+    fi
+    return 1
+}
+
+# 启动keystone服务
+start_keystone() {
+    local service_name="keystone"
+    local port=35357
+    local bin_path="$BIN_DIR/$service_name"
+    local log_file="$LOG_DIR/${service_name}.log"
+    local pid_file="$PID_DIR/${service_name}.pid"
+    
+    if is_service_running "$service_name"; then
+        echo -e "${YELLOW}Keystone 已在运行${NC}"
+        return 0
+    fi
+    
+    echo -e "${GREEN}启动 Keystone (认证服务)...${NC}"
+    
+    > "$log_file"
+    
+    nohup "$bin_path" \
+        --sql-connection "$SQL_CONNECTION" \
+        --port $port \
+        --admin-port 35358 \
+        --auto-sync-table \
+        --bootstrap-admin-user-password sysadmin \
+        --region region0 \
+        --enable-default-policy \
+        --no-policy-violation-check \
+        > "$log_file" 2>&1 &
+    
+    local pid=$!
+    echo $pid > "$pid_file"
+    
+    # 等待keystone启动
+    local max_wait=20
+    local waited=0
+    
+    while [ $waited -lt $max_wait ]; do
+        sleep 1
+        waited=$((waited + 1))
+        
+        if ! ps -p "$pid" > /dev/null 2>&1; then
+            echo -e "${RED}✗ Keystone 启动失败${NC}"
+            tail -10 "$log_file"
+            return 1
+        fi
+        
+        if grep -q "Start listen on" "$log_file" 2>/dev/null; then
+            echo -e "${GREEN}✓ Keystone 启动成功 (PID: $pid)${NC}"
+            return 0
+        fi
+        
+        if grep -q "\[fatal\]" "$log_file" 2>/dev/null; then
+            echo -e "${RED}✗ Keystone 启动失败${NC}"
+            grep "\[fatal\]" "$log_file" | tail -1
+            return 1
+        fi
+    done
+    
+    echo -e "${YELLOW}⚠ Keystone 启动超时${NC}"
+    return 1
+}
+
+# 启动其他服务
+start_service() {
+    local service_name=$1
+    local port=$2
+    local need_auth=${3:-true}
+    local need_db=${4:-true}
+    
+    local bin_path="$BIN_DIR/$service_name"
+    local log_file="$LOG_DIR/${service_name}.log"
+    local pid_file="$PID_DIR/${service_name}.pid"
+    
+    if is_service_running "$service_name"; then
+        echo -e "${YELLOW}  $service_name 已在运行${NC}"
+        return 0
+    fi
+    
+    echo -e "${GREEN}启动 $service_name (端口: $port)...${NC}"
+    
+    > "$log_file"
+    
+    # 构建启动命令
+    local cmd="$bin_path --port $port"
+    
+    if [ "$need_db" = "true" ]; then
+        cmd="$cmd --sql-connection \"$SQL_CONNECTION\" --auto-sync-table"
+    fi
+    
+    if [ "$need_auth" = "true" ]; then
+        cmd="$cmd --auth-url $AUTH_URL --admin-user sysadmin --admin-password sysadmin --admin-project system"
+    fi
+    
+    # glance特殊配置
+    if [ "$service_name" = "glance" ]; then
+        mkdir -p "$BACKEND_DIR/data/glance"
+        cmd="$cmd --filesystem-store-datadir $BACKEND_DIR/data/glance"
+    fi
+    
+    # region特殊配置 (需要etcd)
+    if [ "$service_name" = "region" ]; then
+        cmd="$cmd --etcd-endpoints http://localhost:2379"
+    fi
+    
+    nohup bash -c "$cmd" > "$log_file" 2>&1 &
+    
+    local pid=$!
+    echo $pid > "$pid_file"
+    
+    # 等待服务启动
+    local max_wait=15
+    local waited=0
+    
+    while [ $waited -lt $max_wait ]; do
+        sleep 1
+        waited=$((waited + 1))
+        
+        if ! ps -p "$pid" > /dev/null 2>&1; then
+            echo -e "${RED}  ✗ $service_name 启动失败${NC}"
+            echo -e "${YELLOW}  错误信息:${NC}"
+            tail -3 "$log_file" | sed 's/^/    /'
+            return 1
+        fi
+        
+        if grep -q "Start listen on" "$log_file" 2>/dev/null; then
+            echo -e "${GREEN}  ✓ $service_name 启动成功 (PID: $pid)${NC}"
+            return 0
+        fi
+        
+        if grep -q "\[fatal\]" "$log_file" 2>/dev/null; then
+            echo -e "${RED}  ✗ $service_name 启动失败${NC}"
+            grep "\[fatal\]" "$log_file" | tail -1 | sed 's/^/    /'
+            return 1
+        fi
+    done
+    
+    echo -e "${YELLOW}  ⚠ $service_name 可能正在启动 (PID: $pid)${NC}"
+    return 0
+}
+
+# 停止服务
+stop_service() {
+    local service_name=$1
+    local pid_file="$PID_DIR/${service_name}.pid"
+    
+    if [ -f "$pid_file" ]; then
+        local pid=$(cat "$pid_file")
+        if ps -p "$pid" > /dev/null 2>&1; then
+            echo -e "${YELLOW}停止 $service_name (PID: $pid)...${NC}"
+            kill "$pid" 2>/dev/null || true
+            sleep 1
+            if ps -p "$pid" > /dev/null 2>&1; then
+                kill -9 "$pid" 2>/dev/null || true
+            fi
+            rm -f "$pid_file"
+        else
+            rm -f "$pid_file"
+        fi
+    fi
+}
+
+# 启动所有服务
+start_all() {
+    echo -e "${GREEN}=== 启动所有服务 ===${NC}"
+    echo ""
+    
+    # 第0阶段:启动etcd
+    echo -e "${YELLOW}阶段 0: 启动etcd${NC}"
+    if [ -f "$SCRIPT_DIR/start-etcd.sh" ]; then
+        bash "$SCRIPT_DIR/start-etcd.sh" || {
+            echo -e "${RED}etcd 启动失败${NC}"
+            exit 1
+        }
+    else
+        echo -e "${YELLOW}未找到 start-etcd.sh,跳过etcd启动${NC}"
+    fi
+    echo ""
+    
+    # 第一阶段:启动keystone
+    echo -e "${YELLOW}阶段 1: 启动认证服务${NC}"
+    if ! start_keystone; then
+        echo -e "${RED}Keystone 启动失败,无法继续${NC}"
+        exit 1
+    fi
+    echo ""
+    
+    # 等待keystone完全就绪
+    echo -e "${YELLOW}等待 Keystone 完全就绪...${NC}"
+    sleep 3
+    echo ""
+    
+    # 初始化RBAC策略
+    echo -e "${YELLOW}初始化RBAC策略...${NC}"
+    if [ -f "$SCRIPT_DIR/init-admin-policy.sh" ]; then
+        bash "$SCRIPT_DIR/init-admin-policy.sh" 2>&1 | grep -v "Warning" || true
+    fi
+    echo ""
+    
+    # 注册服务到service catalog
+    echo -e "${YELLOW}注册服务到Service Catalog...${NC}"
+    if [ -f "$SCRIPT_DIR/register-services.sh" ]; then
+        bash "$SCRIPT_DIR/register-services.sh" 2>&1 | grep -v "Warning" | grep -E "✓|已注册" || true
+    fi
+    echo ""
+    
+    # 第二阶段:启动业务服务
+    echo -e "${YELLOW}阶段 2: 启动业务服务${NC}"
+    
+    # region: 计算服务 (需要认证、数据库和etcd)
+    start_service "region" 30888 true true
+    
+    # glance: 镜像服务 (需要认证和数据库)
+    start_service "glance" 9292 true true
+    
+    # yunionconf: 配置服务 (需要认证和数据库)
+    start_service "yunionconf" 30889 true true
+    
+    # monitor: 监控服务 (需要认证和数据库) - 暂时跳过,需要influxdb
+    # start_service "monitor" 30093 true true
+    echo -e "${YELLOW}  跳过 monitor (需要 influxdb/victoria-metrics)${NC}"
+    
+    # scheduledtask: 定时任务服务 (需要认证和数据库)
+    start_service "scheduledtask" 30891 true true
+    
+    echo ""
+    
+    # 第三阶段:启动API网关 (需要认证,不需要数据库)
+    echo -e "${YELLOW}阶段 3: 启动API网关${NC}"
+    start_service "apigateway" 30300 true false
+    
+    echo ""
+    echo -e "${GREEN}=== 服务启动完成 ===${NC}"
+    echo ""
+    echo -e "${GREEN}访问地址:${NC}"
+    echo "  API Gateway: http://localhost:30300"
+    echo "  Keystone:    http://localhost:35357"
+    echo ""
+    echo -e "${YELLOW}查看日志: tail -f $LOG_DIR/<service>.log${NC}"
+}
+
+# 停止所有服务
+stop_all() {
+    echo -e "${YELLOW}=== 停止所有服务 ===${NC}"
+    echo ""
+    
+    local services=("scheduledtask" "monitor" "yunionconf" "scheduler" "apigateway" "glance" "region" "keystone")
+    
+    for service in "${services[@]}"; do
+        stop_service "$service"
+    done
+    
+    echo ""
+    echo -e "${GREEN}=== 所有服务已停止 ===${NC}"
+}
+
+# 查看服务状态
+status_all() {
+    echo -e "${GREEN}=== 服务状态 ===${NC}"
+    echo ""
+    
+    # 检查etcd
+    if [ -f "$BACKEND_DIR/logs/etcd.pid" ]; then
+        local pid=$(cat "$BACKEND_DIR/logs/etcd.pid")
+        if ps -p "$pid" > /dev/null 2>&1; then
+            echo -e "${GREEN}  ✓ etcd${NC} (PID: $pid, Port: 2379)"
+        else
+            echo -e "${RED}  ✗ etcd${NC} (未运行)"
+        fi
+    else
+        echo -e "${RED}  ✗ etcd${NC} (未运行)"
+    fi
+    
+    local services=("keystone:35357" "region:30888" "glance:9292" "apigateway:30300" "scheduler:30888" "yunionconf:30889" "monitor:30093" "scheduledtask:30891")
+    
+    for service_info in "${services[@]}"; do
+        local service_name="${service_info%%:*}"
+        local port="${service_info##*:}"
+        
+        if is_service_running "$service_name"; then
+            local pid=$(cat "$PID_DIR/${service_name}.pid")
+            echo -e "${GREEN}  ✓ $service_name${NC} (PID: $pid, Port: $port)"
+        else
+            echo -e "${RED}  ✗ $service_name${NC} (未运行)"
+        fi
+    done
+    echo ""
+}
+
+# 主函数
+case "${1:-}" in
+    start)
+        start_all
+        ;;
+    stop)
+        stop_all
+        ;;
+    restart)
+        stop_all
+        sleep 2
+        start_all
+        ;;
+    status)
+        status_all
+        ;;
+    *)
+        echo "用法: $0 {start|stop|restart|status}"
+        echo ""
+        echo "命令:"
+        echo "  start   - 启动所有服务"
+        echo "  stop    - 停止所有服务"
+        echo "  restart - 重启所有服务"
+        echo "  status  - 查看服务状态"
+        exit 1
+        ;;
+esac

+ 3 - 2
frontend/scope/layouts/Navbar/index.vue

@@ -267,13 +267,14 @@ export default {
     },
     globalSettingSetupKeys () {
       const { globalSetting } = this.$store.state
-      if (globalSetting && globalSetting.value) {
+      if (globalSetting && globalSetting.value && globalSetting.value.setupKeys) {
         return globalSetting.value.setupKeys
       }
       return []
     },
     supportLanguages () {
-      const languages = this.globalSettingSetupKeys.filter(item => ['zh-CN', 'en', 'ja-JP'].includes(item))
+      const setupKeys = this.globalSettingSetupKeys || []
+      const languages = setupKeys.filter(item => ['zh-CN', 'en', 'ja-JP'].includes(item))
       return languages
     },
   },

+ 1 - 1
frontend/vue.config.js

@@ -181,7 +181,7 @@ module.exports = {
     port: 8080,
     proxy: {
       '/api': {
-        target: 'https://127.0.0.1:3000',
+        target: 'http://localhost:30300',
         ws: true,
         changeOrigin: true,
         proxyTimeout: PROXY_TIMEOUT,