feat: 初始化 vps-manager 资产管理系统(Asset/VPSDetail/DomainDetail/AIAccount 模型 + FastAPI 骨架)
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.venv/
|
||||||
|
data/
|
||||||
|
.env
|
||||||
|
.DS_Store
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""vps-manager 资产管理系统"""
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
"""SQLite 数据库连接与初始化"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from sqlmodel import SQLModel, create_engine
|
||||||
|
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
DATA_DIR = BASE_DIR / "data"
|
||||||
|
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
DB_URL = f"sqlite:///{DATA_DIR / 'vps_manager.db'}"
|
||||||
|
|
||||||
|
engine = create_engine(DB_URL, echo=False, connect_args={"check_same_thread": False})
|
||||||
|
|
||||||
|
|
||||||
|
def init_db() -> None:
|
||||||
|
"""创建所有表(幂等,可重复调用)"""
|
||||||
|
# 显式导入模型,确保其注册到 SQLModel.metadata
|
||||||
|
from app.models.asset import AIAccount, Asset, DomainDetail, VPSDetail # noqa: F401
|
||||||
|
|
||||||
|
SQLModel.metadata.create_all(engine)
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
"""vps-manager 后端入口"""
|
||||||
|
|
||||||
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
from app.database import init_db
|
||||||
|
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def lifespan(_: FastAPI):
|
||||||
|
"""应用启动时自动建表"""
|
||||||
|
init_db()
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
app = FastAPI(title="VPS 资产管理系统", version="0.1.0", lifespan=lifespan)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
def health_check() -> dict:
|
||||||
|
"""健康检查"""
|
||||||
|
return {"status": "ok", "app": "vps-manager", "version": "0.1.0"}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""数据库模型包"""
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
"""资产数据库模型
|
||||||
|
|
||||||
|
包含四种核心资产模型:
|
||||||
|
- Asset: 资产主表(统一登记 VPS/域名/AI 账号/Cloudflare 等一切资产)
|
||||||
|
- VPSDetail: VPS 资产详情(与 Asset 一对一关联)
|
||||||
|
- DomainDetail: 域名资产详情(与 Asset 一对一关联)
|
||||||
|
- AIAccount: AI Agent 账号详情(与 Asset 一对一关联)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from datetime import date, datetime
|
||||||
|
from enum import Enum
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from sqlmodel import Field, SQLModel
|
||||||
|
|
||||||
|
|
||||||
|
class AssetType(str, Enum):
|
||||||
|
"""资产类型"""
|
||||||
|
|
||||||
|
VPS = "vps" # 云服务器
|
||||||
|
DOMAIN = "domain" # 域名
|
||||||
|
AI_AGENT = "ai_agent" # AI Agent 账号(OpenAI/Kimi/DeepSeek 等)
|
||||||
|
CLOUDFLARE = "cloudflare" # Cloudflare 账号/子资产(DNS/Workers/R2/Tunnel)
|
||||||
|
OTHER = "other" # 其他
|
||||||
|
|
||||||
|
|
||||||
|
class AssetStatus(str, Enum):
|
||||||
|
"""资产运行状态"""
|
||||||
|
|
||||||
|
ACTIVE = "active" # 正常使用中
|
||||||
|
EXPIRED = "expired" # 已过期
|
||||||
|
STOPPED = "stopped" # 已停止(不再使用)
|
||||||
|
CANCELLED = "cancelled" # 已注销
|
||||||
|
UNKNOWN = "unknown" # 未知(待确认)
|
||||||
|
|
||||||
|
|
||||||
|
class Asset(SQLModel, table=True):
|
||||||
|
"""资产主表:所有资产在此统一登记,用于续费提醒与状态总览"""
|
||||||
|
|
||||||
|
__tablename__ = "assets"
|
||||||
|
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
name: str = Field(index=True, description="资产名称,如:博客主站 VPS")
|
||||||
|
asset_type: AssetType = Field(index=True, description="资产类型")
|
||||||
|
provider: str = Field(index=True, description="服务商,如 aliyun/vultr/namesilo/openai")
|
||||||
|
account: Optional[str] = Field(
|
||||||
|
default=None, description="所属账号标识(如 Cloudflare 账号邮箱)"
|
||||||
|
)
|
||||||
|
expiry_date: Optional[date] = Field(
|
||||||
|
default=None, index=True, description="到期/续费日期,用于续费提醒"
|
||||||
|
)
|
||||||
|
auto_renew: bool = Field(default=False, description="是否自动续费")
|
||||||
|
cost: float = Field(default=0, description="单次续费费用")
|
||||||
|
currency: str = Field(default="USD", description="币种(USD/CNY/JPY)")
|
||||||
|
status: AssetStatus = Field(default=AssetStatus.ACTIVE, index=True, description="运行状态")
|
||||||
|
is_archived: bool = Field(
|
||||||
|
default=False, description="已归档:不再使用但可能仍在续费,需重点排查"
|
||||||
|
)
|
||||||
|
remark: Optional[str] = Field(default=None, description="备注")
|
||||||
|
created_at: datetime = Field(default_factory=datetime.utcnow, description="创建时间")
|
||||||
|
updated_at: datetime = Field(
|
||||||
|
default_factory=datetime.utcnow,
|
||||||
|
sa_column_kwargs={"onupdate": datetime.utcnow},
|
||||||
|
description="更新时间",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class VPSDetail(SQLModel, table=True):
|
||||||
|
"""VPS 资产详情(一对一关联 Asset)"""
|
||||||
|
|
||||||
|
__tablename__ = "vps_details"
|
||||||
|
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
asset_id: int = Field(
|
||||||
|
foreign_key="assets.id", unique=True, index=True, description="关联资产 ID"
|
||||||
|
)
|
||||||
|
ip_address: str = Field(index=True, description="公网 IP")
|
||||||
|
tailscale_ip: Optional[str] = Field(default=None, description="Tailscale 内网 IP")
|
||||||
|
region: Optional[str] = Field(default=None, description="机房区域,如 Japan/US West")
|
||||||
|
os: Optional[str] = Field(default=None, description="操作系统,如 Debian 12")
|
||||||
|
cpu_cores: int = Field(default=1, description="CPU 核数")
|
||||||
|
memory_gb: float = Field(default=1, description="内存大小(GB)")
|
||||||
|
disk_gb: int = Field(default=20, description="磁盘大小(GB)")
|
||||||
|
bandwidth_gb: Optional[int] = Field(default=None, description="月流量配额(GB)")
|
||||||
|
ssh_port: int = Field(default=22, description="SSH 端口")
|
||||||
|
panel_url: Optional[str] = Field(default=None, description="服务商管理面板 URL")
|
||||||
|
|
||||||
|
|
||||||
|
class DomainDetail(SQLModel, table=True):
|
||||||
|
"""域名资产详情(一对一关联 Asset)"""
|
||||||
|
|
||||||
|
__tablename__ = "domain_details"
|
||||||
|
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
asset_id: int = Field(
|
||||||
|
foreign_key="assets.id", unique=True, index=True, description="关联资产 ID"
|
||||||
|
)
|
||||||
|
domain_name: str = Field(index=True, description="域名,如 example.com")
|
||||||
|
registrar: Optional[str] = Field(
|
||||||
|
default=None, description="注册商:namesilo/新网/腾讯/阿里/dynadot/cloudflare"
|
||||||
|
)
|
||||||
|
dns_provider: Optional[str] = Field(
|
||||||
|
default=None, description="DNS 托管商:cloudflare/阿里云/注册商默认"
|
||||||
|
)
|
||||||
|
cloudflare_account: Optional[str] = Field(
|
||||||
|
default=None, description="归属的 Cloudflare 账号标识"
|
||||||
|
)
|
||||||
|
is_using: bool = Field(default=True, description="是否仍在实际使用")
|
||||||
|
redirect_target: Optional[str] = Field(
|
||||||
|
default=None, description="若为跳转用途域名,填写目标地址"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AIAccount(SQLModel, table=True):
|
||||||
|
"""AI Agent 账号资产详情(一对一关联 Asset)"""
|
||||||
|
|
||||||
|
__tablename__ = "ai_accounts"
|
||||||
|
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
asset_id: int = Field(
|
||||||
|
foreign_key="assets.id", unique=True, index=True, description="关联资产 ID"
|
||||||
|
)
|
||||||
|
provider: str = Field(
|
||||||
|
index=True, description="服务商:openai/minimax/kimi/agnes/deepseek"
|
||||||
|
)
|
||||||
|
api_key: Optional[str] = Field(
|
||||||
|
default=None, description="API Key(后续版本将改为加密存储)"
|
||||||
|
)
|
||||||
|
plan: Optional[str] = Field(default=None, description="订阅计划,如 ChatGPT Plus")
|
||||||
|
balance: Optional[float] = Field(default=None, description="当前余额")
|
||||||
|
currency: str = Field(default="USD", description="币种")
|
||||||
|
monthly_usage: Optional[float] = Field(default=None, description="本月用量")
|
||||||
|
monthly_limit: Optional[float] = Field(default=None, description="月度限额")
|
||||||
|
last_synced_at: Optional[datetime] = Field(
|
||||||
|
default=None, description="最近一次自动同步余额/用量的时间"
|
||||||
|
)
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# vps-manager systemd 服务配置
|
||||||
|
# 部署位置:/etc/systemd/system/vps-manager.service
|
||||||
|
# 使用方法:systemctl daemon-reload && systemctl enable --now vps-manager
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=VPS Asset Manager (vps-manager)
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
WorkingDirectory=/opt/vps-manager
|
||||||
|
ExecStart=/opt/vps-manager/.venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
fastapi>=0.115
|
||||||
|
uvicorn[standard]>=0.30
|
||||||
|
sqlmodel>=0.0.22
|
||||||
|
pydantic>=2.7
|
||||||
|
python-dotenv>=1.0
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# vps-manager 启动脚本(部署于 Tailscale cc1 节点 100.89.0.11)
|
||||||
|
# 用法:./run.sh # 前台启动
|
||||||
|
# ./run.sh --reload # 开发模式(热重载)
|
||||||
|
|
||||||
|
cd "$(dirname "$0")" || exit 1
|
||||||
|
|
||||||
|
if [ ! -d ".venv" ]; then
|
||||||
|
echo "[init] 创建虚拟环境并安装依赖..."
|
||||||
|
python3 -m venv .venv
|
||||||
|
.venv/bin/pip install -r requirements.txt
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec .venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000 "$@"
|
||||||
Reference in New Issue
Block a user