Files
2026-06-20 14:26:29 +08:00

976 lines
25 KiB
TypeScript

import 'dotenv/config'
// @ts-ignore - bcryptjs has its own types
import bcrypt from 'bcryptjs'
import * as db from '../src/db/index.js'
import { prisma } from '../src/db/prisma.js'
type SeedRole = 'admin' | 'user'
type HostStatus = 'online' | 'offline' | 'maintenance'
type HostArchitecture = 'x86_64' | 'aarch64'
type HostInstanceType = 'container' | 'vm' | 'both'
type PackageInstanceType = 'container' | 'vm'
type NetworkMode = 'nat' | 'nat_ipv6' | 'nat_ipv6_nat' | 'ipv6_only' | 'ipv6_nat'
type IoLimitMode = 'throughput' | 'iops'
type SeedCounter = {
created: number
updated: number
}
type SeedStats = {
users: SeedCounter
hosts: SeedCounter
packages: SeedCounter
plans: SeedCounter
}
type SeedUserSpec = {
username: string
email: string
role: SeedRole
password?: string
quota?: {
hostLimit: number
packageLimit: number
}
}
type SeedHostSpec = {
ownerUsername: string
name: string
url: string
location: string
countryCode: string
architecture: HostArchitecture
instanceType: HostInstanceType
status: HostStatus
tags: string[]
natPublicIp: string
natPortStart: number
natPortEnd: number
ipAddress: string
storageDriver: string
storageType: string
storageSize: number
enableApi: boolean
isInstalled: boolean
cpuAllowanceMax: number
memoryMax: number
}
type SeedPlanSpec = {
name: string
description: string
cpu: number
memory: number
disk: number
portLimit: number
snapshotLimit: number
backupLimit: number
siteLimit: number
swapSize: number
trafficLimit: bigint
trafficLimitSpeed: string
price: number
billingCycle: number
setupFee: number
isActive: boolean
sortOrder: number
slaGuarantee: number | null
}
type SeedPackageSpec = {
ownerUsername: string
name: string
description: string
hostNames: string[]
cpuMax: number
memoryMax: number
diskMax: number
bandwidthMax: number | null
networkMode: NetworkMode
instanceType: PackageInstanceType
privileged: boolean
nested: boolean
active: boolean
portLimit: number
snapshotLimit: number
backupLimit: number
siteLimit: number
monthlyTrafficLimit: bigint | null
ioLimitMode: IoLimitMode
limitsRead: string
limitsWrite: string
limitsReadIops: number
limitsWriteIops: number
limitsIngress: string
limitsEgress: string
limitsProcesses: number
limitsCpuPriority: number
bootAutostart: boolean
bootAutostartPriority: number
bootAutostartDelay: number
bootHostShutdownTimeout: number
globalShared: boolean
globalQuotaMultiplier: number | null
globalMaxInstances: number | null
allowInstanceDeletion: boolean
plans: SeedPlanSpec[]
}
const LOCAL_PASSWORD_SALT = '$2a$10$1234567890123456789012'
const stats: SeedStats = {
users: { created: 0, updated: 0 },
hosts: { created: 0, updated: 0 },
packages: { created: 0, updated: 0 },
plans: { created: 0, updated: 0 }
}
const userIds = new Map<string, number>()
const hostIds = new Map<string, number>()
function bytesFromGb(value: number): bigint {
return BigInt(value) * 1024n * 1024n * 1024n
}
function hostKey(ownerUsername: string, hostName: string): string {
return `${ownerUsername}:${hostName}`
}
function countPlans(packages: SeedPackageSpec[]): number {
return packages.reduce((total, pkg) => total + pkg.plans.length, 0)
}
function getUserId(username: string): number {
const userId = userIds.get(username)
if (!userId) {
throw new Error(`Missing seeded user: ${username}`)
}
return userId
}
function getHostId(ownerUsername: string, hostName: string): number {
const id = hostIds.get(hostKey(ownerUsername, hostName))
if (!id) {
throw new Error(`Missing seeded host: ${ownerUsername}/${hostName}`)
}
return id
}
async function buildPasswordHash(password: string): Promise<string> {
return bcrypt.hash(password, LOCAL_PASSWORD_SALT)
}
async function ensureUser(spec: SeedUserSpec): Promise<number> {
const existing = await db.findUserByUsername(spec.username)
const passwordHash = spec.password ? await buildPasswordHash(spec.password) : undefined
if (!existing) {
if (!passwordHash) {
throw new Error(`Cannot create user without password: ${spec.username}`)
}
const userId = await db.createUser(spec.username, spec.email, passwordHash, spec.role)
if (spec.quota) {
await db.updateUserQuotaLimits(userId, {
hostLimit: spec.quota.hostLimit,
packageLimit: spec.quota.packageLimit
})
}
stats.users.created += 1
userIds.set(spec.username, userId)
return userId
}
// 幂等处理:账号存在时只同步本脚本关心的字段,不重复创建脏数据。
await db.updateUser(existing.id, {
email: spec.email,
role: spec.role,
status: 'active',
...(passwordHash ? { passwordHash } : {})
})
const quota = await db.getUserQuota(existing.id)
if (!quota) {
await db.createUserQuota(existing.id, {
hostLimit: spec.quota?.hostLimit ?? 8,
packageLimit: spec.quota?.packageLimit ?? 8
})
} else if (spec.quota) {
await db.updateUserQuotaLimits(existing.id, {
hostLimit: spec.quota.hostLimit,
packageLimit: spec.quota.packageLimit
})
}
stats.users.updated += 1
userIds.set(spec.username, existing.id)
return existing.id
}
async function ensureHost(spec: SeedHostSpec): Promise<number> {
const ownerId = getUserId(spec.ownerUsername)
const existing = await db.getHostByUserAndName(ownerId, spec.name)
if (!existing) {
const hostId = await db.createHost({
userId: ownerId,
name: spec.name,
url: spec.url,
location: spec.location,
countryCode: spec.countryCode,
architecture: spec.architecture,
instanceType: spec.instanceType,
tags: spec.tags,
natPublicIp: spec.natPublicIp,
natPortStart: spec.natPortStart,
natPortEnd: spec.natPortEnd,
ipAddress: spec.ipAddress,
storageDriver: spec.storageDriver,
storageType: spec.storageType,
storageSize: spec.storageSize,
enableApi: spec.enableApi,
isInstalled: spec.isInstalled,
cpuAllowanceMax: spec.cpuAllowanceMax,
memoryMax: spec.memoryMax
})
await db.updateHostStatus(hostId, spec.status)
await db.updateHostResources(hostId, {
architecture: spec.architecture,
instanceType: spec.instanceType,
cpuUsed: 0,
memoryUsed: 0,
diskUsed: 0,
cpuAllowanceMax: spec.cpuAllowanceMax,
memoryMax: spec.memoryMax
})
stats.hosts.created += 1
hostIds.set(hostKey(spec.ownerUsername, spec.name), hostId)
return hostId
}
// 关键流程:优先复用现有 helper 同步节点展示信息、状态和配额上限。
await db.updateHost(existing.id, {
url: spec.url,
location: spec.location,
countryCode: spec.countryCode,
tags: spec.tags,
natPublicIp: spec.natPublicIp,
natPortStart: spec.natPortStart,
natPortEnd: spec.natPortEnd
})
await db.updateHostStatus(existing.id, spec.status)
await db.updateHostResources(existing.id, {
architecture: spec.architecture,
instanceType: spec.instanceType,
cpuUsed: 0,
memoryUsed: 0,
diskUsed: 0,
cpuAllowanceMax: spec.cpuAllowanceMax,
memoryMax: spec.memoryMax
})
stats.hosts.updated += 1
hostIds.set(hostKey(spec.ownerUsername, spec.name), existing.id)
return existing.id
}
async function ensurePackage(spec: SeedPackageSpec): Promise<number> {
const ownerId = getUserId(spec.ownerUsername)
const owner = await db.findUserByUsername(spec.ownerUsername)
if (!owner) {
throw new Error(`Missing package owner: ${spec.ownerUsername}`)
}
const hostIdsForPackage = spec.hostNames.map(hostName => getHostId(spec.ownerUsername, hostName))
const existing = await db.getPackageByUserAndName(ownerId, spec.name)
const packageData = {
name: spec.name,
description: spec.description,
cpuMax: spec.cpuMax,
memoryMax: spec.memoryMax,
diskMax: spec.diskMax,
bandwidthMax: spec.bandwidthMax,
networkMode: spec.networkMode,
instanceType: spec.instanceType,
hostIds: hostIdsForPackage,
privileged: spec.privileged,
nested: spec.nested,
active: spec.active,
portLimit: spec.portLimit,
snapshotLimit: spec.snapshotLimit,
backupLimit: spec.backupLimit,
siteLimit: spec.siteLimit,
monthlyTrafficLimit: spec.monthlyTrafficLimit,
ioLimitMode: spec.ioLimitMode,
limitsRead: spec.limitsRead,
limitsWrite: spec.limitsWrite,
limitsReadIops: spec.limitsReadIops,
limitsWriteIops: spec.limitsWriteIops,
limitsIngress: spec.limitsIngress,
limitsEgress: spec.limitsEgress,
limitsProcesses: spec.limitsProcesses,
limitsCpuPriority: spec.limitsCpuPriority,
bootAutostart: spec.bootAutostart,
bootAutostartPriority: spec.bootAutostartPriority,
bootAutostartDelay: spec.bootAutostartDelay,
bootHostShutdownTimeout: spec.bootHostShutdownTimeout,
globalShared: spec.globalShared,
globalQuotaMultiplier: spec.globalQuotaMultiplier,
globalMaxInstances: spec.globalMaxInstances,
allowInstanceDeletion: spec.allowInstanceDeletion
}
if (!existing) {
const packageId = await db.createPackage(
{
userId: ownerId,
...packageData
},
owner.role === 'admin'
)
stats.packages.created += 1
return packageId
}
await db.updatePackage(existing.id, packageData, owner.role === 'admin')
stats.packages.updated += 1
return existing.id
}
async function ensurePlans(packageId: number, plans: SeedPlanSpec[]): Promise<void> {
const existingPlans = await db.getPlansByPackageId(packageId)
const existingByName = new Map(existingPlans.map(plan => [plan.name, plan]))
const expectedNames = new Set<string>()
// 关键流程:方案使用 packageId + name 做唯一识别,重复执行时只更新不重复插入。
for (const plan of plans) {
expectedNames.add(plan.name)
const existing = existingByName.get(plan.name)
if (!existing) {
await db.createPlan({
packageId,
name: plan.name,
description: plan.description,
cpu: plan.cpu,
memory: plan.memory,
disk: plan.disk,
portLimit: plan.portLimit,
snapshotLimit: plan.snapshotLimit,
backupLimit: plan.backupLimit,
siteLimit: plan.siteLimit,
swapSize: plan.swapSize,
trafficLimit: plan.trafficLimit,
trafficLimitSpeed: plan.trafficLimitSpeed,
price: plan.price,
billingCycle: plan.billingCycle,
setupFee: plan.setupFee,
isActive: plan.isActive,
sortOrder: plan.sortOrder,
slaGuarantee: plan.slaGuarantee
})
stats.plans.created += 1
continue
}
await db.updatePlan(existing.id, {
description: plan.description,
cpu: plan.cpu,
memory: plan.memory,
disk: plan.disk,
portLimit: plan.portLimit,
snapshotLimit: plan.snapshotLimit,
backupLimit: plan.backupLimit,
siteLimit: plan.siteLimit,
swapSize: plan.swapSize,
trafficLimit: plan.trafficLimit,
trafficLimitSpeed: plan.trafficLimitSpeed,
price: plan.price,
billingCycle: plan.billingCycle,
setupFee: plan.setupFee,
isActive: plan.isActive,
sortOrder: plan.sortOrder,
slaGuarantee: plan.slaGuarantee
})
stats.plans.updated += 1
}
for (const existing of existingPlans) {
if (!expectedNames.has(existing.name) && existing.isActive) {
await db.updatePlan(existing.id, {
isActive: false
})
stats.plans.updated += 1
}
}
}
const seedUsers: SeedUserSpec[] = [
{
username: 'admin',
email: 'admin@incudal.local',
role: 'admin'
},
{
username: 'seed-market-alpha',
email: 'seed-market-alpha@incudal.local',
role: 'user',
password: 'SeedAlpha123!',
quota: {
hostLimit: 8,
packageLimit: 8
}
},
{
username: 'seed-market-beta',
email: 'seed-market-beta@incudal.local',
role: 'user',
password: 'SeedBeta123!',
quota: {
hostLimit: 8,
packageLimit: 8
}
}
]
const seedHosts: SeedHostSpec[] = [
{
ownerUsername: 'admin',
name: 'seed-official-us-west',
url: 'https://seed-official-us-west.local',
location: 'Los Angeles, US',
countryCode: 'us',
architecture: 'x86_64',
instanceType: 'both',
status: 'online',
tags: ['seed', 'official', 'us-west'],
natPublicIp: '198.51.100.10',
natPortStart: 20000,
natPortEnd: 24999,
ipAddress: '10.10.0.10',
storageDriver: 'zfs',
storageType: 'loop',
storageSize: 400,
enableApi: true,
isInstalled: true,
cpuAllowanceMax: 3200,
memoryMax: 131072
},
{
ownerUsername: 'admin',
name: 'seed-official-eu-vm',
url: 'https://seed-official-eu-vm.local',
location: 'Frankfurt, DE',
countryCode: 'de',
architecture: 'x86_64',
instanceType: 'vm',
status: 'online',
tags: ['seed', 'official', 'eu-central'],
natPublicIp: '198.51.100.11',
natPortStart: 25000,
natPortEnd: 29999,
ipAddress: '10.10.0.11',
storageDriver: 'zfs',
storageType: 'loop',
storageSize: 600,
enableApi: true,
isInstalled: true,
cpuAllowanceMax: 4800,
memoryMax: 196608
},
{
ownerUsername: 'admin',
name: 'seed-official-asia-arm',
url: 'https://seed-official-asia-arm.local',
location: 'Singapore, SG',
countryCode: 'sg',
architecture: 'aarch64',
instanceType: 'container',
status: 'online',
tags: ['seed', 'official', 'apac'],
natPublicIp: '198.51.100.12',
natPortStart: 30000,
natPortEnd: 34999,
ipAddress: '10.10.0.12',
storageDriver: 'zfs',
storageType: 'loop',
storageSize: 320,
enableApi: true,
isInstalled: true,
cpuAllowanceMax: 2400,
memoryMax: 98304
},
{
ownerUsername: 'seed-market-alpha',
name: 'seed-market-jp-tokyo',
url: 'https://seed-market-jp-tokyo.local',
location: 'Tokyo, JP',
countryCode: 'jp',
architecture: 'x86_64',
instanceType: 'container',
status: 'online',
tags: ['seed', 'market', 'tokyo'],
natPublicIp: '203.0.113.20',
natPortStart: 35000,
natPortEnd: 38999,
ipAddress: '10.20.0.20',
storageDriver: 'zfs',
storageType: 'loop',
storageSize: 240,
enableApi: true,
isInstalled: true,
cpuAllowanceMax: 2000,
memoryMax: 65536
},
{
ownerUsername: 'seed-market-alpha',
name: 'seed-market-sg-latency',
url: 'https://seed-market-sg-latency.local',
location: 'Singapore, SG',
countryCode: 'sg',
architecture: 'x86_64',
instanceType: 'container',
status: 'online',
tags: ['seed', 'market', 'singapore'],
natPublicIp: '203.0.113.21',
natPortStart: 39000,
natPortEnd: 42999,
ipAddress: '10.20.0.21',
storageDriver: 'zfs',
storageType: 'loop',
storageSize: 220,
enableApi: true,
isInstalled: true,
cpuAllowanceMax: 1800,
memoryMax: 49152
},
{
ownerUsername: 'seed-market-beta',
name: 'seed-market-us-east',
url: 'https://seed-market-us-east.local',
location: 'Ashburn, US',
countryCode: 'us',
architecture: 'x86_64',
instanceType: 'both',
status: 'online',
tags: ['seed', 'market', 'us-east'],
natPublicIp: '203.0.113.22',
natPortStart: 43000,
natPortEnd: 46999,
ipAddress: '10.30.0.22',
storageDriver: 'zfs',
storageType: 'loop',
storageSize: 260,
enableApi: true,
isInstalled: true,
cpuAllowanceMax: 2200,
memoryMax: 65536
}
]
const seedPackages: SeedPackageSpec[] = [
{
ownerUsername: 'admin',
name: 'SEED Official Edge Container',
description: 'Public official container package for local smoke testing across US and APAC.',
hostNames: ['seed-official-us-west', 'seed-official-asia-arm'],
cpuMax: 300,
memoryMax: 8192,
diskMax: 102400,
bandwidthMax: 2000,
networkMode: 'nat_ipv6',
instanceType: 'container',
privileged: true,
nested: true,
active: true,
portLimit: 40,
snapshotLimit: 8,
backupLimit: 4,
siteLimit: 12,
monthlyTrafficLimit: bytesFromGb(800),
ioLimitMode: 'throughput',
limitsRead: '250MB',
limitsWrite: '250MB',
limitsReadIops: 1200,
limitsWriteIops: 1200,
limitsIngress: '1Gbit',
limitsEgress: '1Gbit',
limitsProcesses: 1200,
limitsCpuPriority: 10,
bootAutostart: true,
bootAutostartPriority: 20,
bootAutostartDelay: 15,
bootHostShutdownTimeout: 45,
globalShared: true,
globalQuotaMultiplier: 2,
globalMaxInstances: 6,
allowInstanceDeletion: true,
plans: [
{
name: 'Mini',
description: '25% CPU / 512MB memory / 15GB disk',
cpu: 25,
memory: 512,
disk: 15360,
portLimit: 10,
snapshotLimit: 3,
backupLimit: 1,
siteLimit: 2,
swapSize: 0,
trafficLimit: bytesFromGb(120),
trafficLimitSpeed: '3Mbit',
price: 690,
billingCycle: 1,
setupFee: 0,
isActive: true,
sortOrder: 10,
slaGuarantee: 99.5
},
{
name: 'Standard',
description: '60% CPU / 2GB memory / 40GB disk',
cpu: 60,
memory: 2048,
disk: 40960,
portLimit: 20,
snapshotLimit: 5,
backupLimit: 2,
siteLimit: 5,
swapSize: 512,
trafficLimit: bytesFromGb(260),
trafficLimitSpeed: '5Mbit',
price: 1990,
billingCycle: 1,
setupFee: 0,
isActive: true,
sortOrder: 20,
slaGuarantee: 99.9
},
{
name: 'Compute',
description: '120% CPU / 4GB memory / 80GB disk',
cpu: 120,
memory: 4096,
disk: 81920,
portLimit: 35,
snapshotLimit: 8,
backupLimit: 4,
siteLimit: 8,
swapSize: 1024,
trafficLimit: bytesFromGb(520),
trafficLimitSpeed: '10Mbit',
price: 3990,
billingCycle: 1,
setupFee: 0,
isActive: true,
sortOrder: 30,
slaGuarantee: 99.95
}
]
},
{
ownerUsername: 'admin',
name: 'SEED Official EU VM',
description: 'Public official VM package for local billing and scheduler testing.',
hostNames: ['seed-official-eu-vm'],
cpuMax: 600,
memoryMax: 16384,
diskMax: 204800,
bandwidthMax: 3000,
networkMode: 'nat_ipv6',
instanceType: 'vm',
privileged: false,
nested: false,
active: true,
portLimit: 20,
snapshotLimit: 6,
backupLimit: 4,
siteLimit: 2,
monthlyTrafficLimit: bytesFromGb(1000),
ioLimitMode: 'iops',
limitsRead: '300MB',
limitsWrite: '300MB',
limitsReadIops: 2000,
limitsWriteIops: 2000,
limitsIngress: '1Gbit',
limitsEgress: '1Gbit',
limitsProcesses: 600,
limitsCpuPriority: 8,
bootAutostart: true,
bootAutostartPriority: 15,
bootAutostartDelay: 20,
bootHostShutdownTimeout: 60,
globalShared: true,
globalQuotaMultiplier: 1.5,
globalMaxInstances: 4,
allowInstanceDeletion: true,
plans: [
{
name: 'VM Starter',
description: '100% CPU / 2GB memory / 40GB disk',
cpu: 100,
memory: 2048,
disk: 40960,
portLimit: 6,
snapshotLimit: 2,
backupLimit: 1,
siteLimit: 0,
swapSize: 0,
trafficLimit: bytesFromGb(200),
trafficLimitSpeed: '5Mbit',
price: 2990,
billingCycle: 1,
setupFee: 500,
isActive: true,
sortOrder: 10,
slaGuarantee: 99.9
},
{
name: 'VM Business',
description: '200% CPU / 4GB memory / 80GB disk',
cpu: 200,
memory: 4096,
disk: 81920,
portLimit: 12,
snapshotLimit: 4,
backupLimit: 2,
siteLimit: 1,
swapSize: 0,
trafficLimit: bytesFromGb(400),
trafficLimitSpeed: '10Mbit',
price: 5990,
billingCycle: 1,
setupFee: 1000,
isActive: true,
sortOrder: 20,
slaGuarantee: 99.95
}
]
},
{
ownerUsername: 'seed-market-alpha',
name: 'SEED Market APAC Burst',
description: 'Hosted public package for local market testing in Tokyo and Singapore.',
hostNames: ['seed-market-jp-tokyo', 'seed-market-sg-latency'],
cpuMax: 240,
memoryMax: 6144,
diskMax: 81920,
bandwidthMax: 1500,
networkMode: 'nat_ipv6_nat',
instanceType: 'container',
privileged: true,
nested: true,
active: true,
portLimit: 32,
snapshotLimit: 6,
backupLimit: 3,
siteLimit: 6,
monthlyTrafficLimit: bytesFromGb(500),
ioLimitMode: 'throughput',
limitsRead: '180MB',
limitsWrite: '180MB',
limitsReadIops: 1000,
limitsWriteIops: 1000,
limitsIngress: '600Mbit',
limitsEgress: '600Mbit',
limitsProcesses: 900,
limitsCpuPriority: 9,
bootAutostart: true,
bootAutostartPriority: 25,
bootAutostartDelay: 10,
bootHostShutdownTimeout: 40,
globalShared: true,
globalQuotaMultiplier: 1.5,
globalMaxInstances: 5,
allowInstanceDeletion: true,
plans: [
{
name: 'Burst Nano',
description: '30% CPU / 768MB memory / 20GB disk',
cpu: 30,
memory: 768,
disk: 20480,
portLimit: 8,
snapshotLimit: 2,
backupLimit: 1,
siteLimit: 1,
swapSize: 0,
trafficLimit: bytesFromGb(80),
trafficLimitSpeed: '2Mbit',
price: 880,
billingCycle: 1,
setupFee: 0,
isActive: true,
sortOrder: 10,
slaGuarantee: 99.5
},
{
name: 'Burst Plus',
description: '80% CPU / 2GB memory / 45GB disk',
cpu: 80,
memory: 2048,
disk: 46080,
portLimit: 16,
snapshotLimit: 4,
backupLimit: 2,
siteLimit: 3,
swapSize: 512,
trafficLimit: bytesFromGb(180),
trafficLimitSpeed: '5Mbit',
price: 2280,
billingCycle: 1,
setupFee: 0,
isActive: true,
sortOrder: 20,
slaGuarantee: 99.8
},
{
name: 'Burst Pro',
description: '140% CPU / 4GB memory / 70GB disk',
cpu: 140,
memory: 4096,
disk: 71680,
portLimit: 24,
snapshotLimit: 6,
backupLimit: 3,
siteLimit: 5,
swapSize: 1024,
trafficLimit: bytesFromGb(320),
trafficLimitSpeed: '8Mbit',
price: 3680,
billingCycle: 1,
setupFee: 0,
isActive: true,
sortOrder: 30,
slaGuarantee: 99.9
}
]
},
{
ownerUsername: 'seed-market-beta',
name: 'SEED Market US Balanced',
description: 'Hosted public package for local market testing in the US East region.',
hostNames: ['seed-market-us-east'],
cpuMax: 260,
memoryMax: 8192,
diskMax: 102400,
bandwidthMax: 1800,
networkMode: 'nat',
instanceType: 'container',
privileged: true,
nested: true,
active: true,
portLimit: 28,
snapshotLimit: 5,
backupLimit: 3,
siteLimit: 4,
monthlyTrafficLimit: bytesFromGb(420),
ioLimitMode: 'throughput',
limitsRead: '200MB',
limitsWrite: '200MB',
limitsReadIops: 1100,
limitsWriteIops: 1100,
limitsIngress: '700Mbit',
limitsEgress: '700Mbit',
limitsProcesses: 950,
limitsCpuPriority: 9,
bootAutostart: true,
bootAutostartPriority: 18,
bootAutostartDelay: 12,
bootHostShutdownTimeout: 40,
globalShared: true,
globalQuotaMultiplier: 1.2,
globalMaxInstances: 4,
allowInstanceDeletion: true,
plans: [
{
name: 'Balanced S',
description: '40% CPU / 1GB memory / 25GB disk',
cpu: 40,
memory: 1024,
disk: 25600,
portLimit: 10,
snapshotLimit: 2,
backupLimit: 1,
siteLimit: 1,
swapSize: 0,
trafficLimit: bytesFromGb(100),
trafficLimitSpeed: '3Mbit',
price: 1090,
billingCycle: 1,
setupFee: 0,
isActive: true,
sortOrder: 10,
slaGuarantee: 99.6
},
{
name: 'Balanced M',
description: '90% CPU / 3GB memory / 60GB disk',
cpu: 90,
memory: 3072,
disk: 61440,
portLimit: 20,
snapshotLimit: 4,
backupLimit: 2,
siteLimit: 2,
swapSize: 512,
trafficLimit: bytesFromGb(220),
trafficLimitSpeed: '6Mbit',
price: 2490,
billingCycle: 1,
setupFee: 0,
isActive: true,
sortOrder: 20,
slaGuarantee: 99.85
}
]
}
]
async function main(): Promise<void> {
await db.initPrismaDatabase()
// 先准备账号,再按账号归属写入节点、公开套餐和套餐方案。
for (const user of seedUsers) {
await ensureUser(user)
}
for (const host of seedHosts) {
await ensureHost(host)
}
for (const pkg of seedPackages) {
const packageId = await ensurePackage(pkg)
await ensurePlans(packageId, pkg.plans)
}
console.log('[seed:test-data] completed')
console.log(
`[seed:test-data] users created=${stats.users.created} updated=${stats.users.updated}, ` +
`hosts created=${stats.hosts.created} updated=${stats.hosts.updated}, ` +
`packages created=${stats.packages.created} updated=${stats.packages.updated}, ` +
`plans created=${stats.plans.created} updated=${stats.plans.updated}`
)
console.log(
`[seed:test-data] seeded targets users=${seedUsers.length}, hosts=${seedHosts.length}, ` +
`packages=${seedPackages.length}, plans=${countPlans(seedPackages)}`
)
}
void main().catch(async error => {
console.error('[seed:test-data] failed:', error)
process.exitCode = 1
}).finally(async () => {
await prisma.$disconnect()
})