๐๏ธ ็ณป็ปๆถๆ่ฎพ่ฎก โ
voidVM ้็จๅๅ็ซฏๅ็ฆป็็ฐไปฃๅๆถๆ๏ผ้่ฟๅพฎๆๅก่ฎพ่ฎกๅฎ็ฐ้ซๅฏ็จใๅฏๆฉๅฑ็่ๆๆบ็ฎก็ๅนณๅฐใ
ๆดไฝๆถๆๅพ โ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
โ Web Browser โ โ Mobile App โ โ Desktop App โ
โ (Vue) โ โ (Future) โ โ (Future) โ
โโโโโโโโโโโฌโโโโโโโโ โโโโโโโโโโโฌโโโโโโโโ โโโโโโโโโโโฌโโโโโโโโ
โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโ
โ Load Balancer โ
โ (Nginx) โ
โโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโ
โ โ โ
โโโโโโโโโโโดโโโโโโโโ โโโโโโโโโโโดโโโโโโโโ โโโโโโโโโโโดโโโโโโโโ
โ API Gateway โ โ Static Files โ โ WebSocket โ
โ (Express.js) โ โ (Assets) โ โ (Socket.io) โ
โโโโโโโโโโโฌโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโฌโโโโโโโโ
โ โ
โโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโ
โ Backend Services โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โ
โ โ VM โ โ Auth โ โ Monitoring โ โ
โ โ Service โ โ Service โ โ Service โ โ
โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โ
โ โ Image โ โ Network โ โ Storage โ โ
โ โ Service โ โ Service โ โ Service โ โ
โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโ
โ โ โ
โโโโโโโโโโโดโโโโโโโโ โโโโโโโโโโโดโโโโโโโโ โโโโโโโโโโโดโโโโโโโโ
โ Supabase โ โ QEMU โ โ Host OS โ
โ (Database) โ โ Hypervisor โ โ Resources โ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
ๅ็ซฏๆถๆ (Vue) โ
Frontend Architecture
โโโ src/
โ โโโ components/ # ๅฏๅค็จ็ปไปถ
โ โ โโโ common/ # ้็จ็ปไปถ
โ โ โ โโโ Loading.vue
โ โ โ โโโ Modal.vue
โ โ โ โโโ Toast.vue
โ โ โโโ vm/ # ่ๆๆบ็ธๅ
ณ็ปไปถ
โ โ โ โโโ VMCard.vue
โ โ โ โโโ VMConsole.vue
โ โ โ โโโ VMMetrics.vue
โ โ โ โโโ VMSettings.vue
โ โ โโโ layout/ # ๅธๅฑ็ปไปถ
โ โ โโโ Header.vue
โ โ โโโ Sidebar.vue
โ โ โโโ Footer.vue
โ โโโ views/ # ้กต้ข่งๅพ
โ โ โโโ Dashboard.vue
โ โ โโโ VMList.vue
โ โ โโโ VMDetail.vue
โ โ โโโ Settings.vue
โ โโโ stores/ # Pinia ็ถๆ็ฎก็
โ โ โโโ auth.js
โ โ โโโ vm.js
โ โ โโโ ui.js
โ โโโ composables/ # ็ปๅๅผๅฝๆฐ
โ โ โโโ useAuth.js
โ โ โโโ useVM.js
โ โ โโโ useWebSocket.js
โ โโโ services/ # API ๆๅกๅฑ
โ โ โโโ api.js
โ โ โโโ vmService.js
โ โ โโโ authService.js
โ โโโ utils/ # ๅทฅๅ
ทๅฝๆฐ
โ โโโ constants.js
โ โโโ helpers.js
โ โโโ validators.js
ๅ็ซฏๆๆฏ้ๅ:
- Vue: ้็จ Composition API๏ผๆไพๆดๅฅฝ็้ป่พๅค็จๅ็ฑปๅๆจๆญ
- Vue Router: ๅ้กต้ขๅบ็จ่ทฏ็ฑ็ฎก็
- Pinia: ็ฐไปฃๅ็ถๆ็ฎก็๏ผๆฟไปฃ Vuex
- Vite: ๅฟซ้็ๆๅปบๅทฅๅ ทๅๅผๅๆๅกๅจ
- BootStrap: UI ็ปไปถๅบ
- Socket.io Client: ๅฎๆถ้ไฟก
ๅ็ซฏๆถๆ (Node.js) โ
Backend Architecture
โโโ server/
โ โโโ routes/ # ่ทฏ็ฑๅฑ
โ โ โโโ auth.js # ่ฎค่ฏ่ทฏ็ฑ
โ โ โโโ vms.js # ่ๆๆบ่ทฏ็ฑ
โ โ โโโ images.js # ้ๅ่ทฏ็ฑ
โ โ โโโ monitoring.js # ็ๆง่ทฏ็ฑ
โ โโโ controllers/ # ๆงๅถๅจๅฑ
โ โ โโโ AuthController.js
โ โ โโโ VMController.js
โ โ โโโ ImageController.js
โ โ โโโ MonitoringController.js
โ โโโ services/ # ไธๅก้ป่พๅฑ
โ โ โโโ VMService.js # ่ๆๆบๆๅก
โ โ โโโ QEMUService.js # QEMU ็ฎก็ๆๅก
โ โ โโโ AuthService.js # ่ฎค่ฏๆๅก
โ โ โโโ ImageService.js # ้ๅ็ฎก็ๆๅก
โ โ โโโ NetworkService.js # ็ฝ็ป็ฎก็ๆๅก
โ โ โโโ StorageService.js # ๅญๅจ็ฎก็ๆๅก
โ โโโ middleware/ # ไธญ้ดไปถ
โ โ โโโ auth.js # ่ฎค่ฏไธญ้ดไปถ
โ โ โโโ validation.js # ๅๆฐ้ช่ฏ
โ โ โโโ rateLimiter.js # ้ๆตไธญ้ดไปถ
โ โ โโโ errorHandler.js # ้่ฏฏๅค็
โ โโโ models/ # ๆฐๆฎๆจกๅ
โ โ โโโ VM.js
โ โ โโโ User.js
โ โ โโโ Image.js
โ โโโ utils/ # ๅทฅๅ
ท็ฑป
โ โ โโโ qemuWrapper.js # QEMU ๅฝไปคๅฐ่ฃ
โ โ โโโ logger.js # ๆฅๅฟๅทฅๅ
ท
โ โ โโโ validators.js # ้ช่ฏๅทฅๅ
ท
โ โโโ config/ # ้
็ฝฎๆไปถ
โ โโโ database.js # ๆฐๆฎๅบ้
็ฝฎ
โ โโโ qemu.js # QEMU ้
็ฝฎ
โ โโโ supabase.js # Supabase ้
็ฝฎ
ๅ็ซฏๆๆฏ้ๅ:
- Express.js: Web ๆกๆถ
- Socket.io: WebSocket ๅฎๆถ้ไฟก
- Joi: ๅๆฐ้ช่ฏ
- Winston: ๆฅๅฟ่ฎฐๅฝ
- Node-cron: ๅฎๆถไปปๅก
- Multer: ๆไปถไธไผ ๅค็
ๆฐๆฎๅบ่ฎพ่ฎก (Supabase/PostgreSQL) โ
sql
-- ็จๆท่กจ
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
email VARCHAR(255) UNIQUE NOT NULL,
username VARCHAR(100) UNIQUE NOT NULL,
avatar_url TEXT,
role VARCHAR(20) DEFAULT 'user',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- ่ๆๆบ่กจ
CREATE TABLE virtual_machines (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
description TEXT,
status VARCHAR(20) DEFAULT 'stopped', -- stopped, running, paused, error
cpu_cores INTEGER DEFAULT 1,
memory_mb INTEGER DEFAULT 1024,
disk_size_gb INTEGER DEFAULT 20,
os_type VARCHAR(50),
image_id UUID REFERENCES vm_images(id),
network_config JSONB,
qemu_config JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- ่ๆๆบ้ๅ่กจ
CREATE TABLE vm_images (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(100) NOT NULL,
description TEXT,
os_type VARCHAR(50),
version VARCHAR(50),
file_path TEXT NOT NULL,
file_size_bytes BIGINT,
checksum VARCHAR(64),
is_public BOOLEAN DEFAULT false,
created_by UUID REFERENCES users(id),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- ่ๆๆบ็ๆงๆฐๆฎ่กจ
CREATE TABLE vm_metrics (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
vm_id UUID REFERENCES virtual_machines(id) ON DELETE CASCADE,
cpu_usage DECIMAL(5,2),
memory_usage DECIMAL(5,2),
disk_usage DECIMAL(5,2),
network_in_bytes BIGINT,
network_out_bytes BIGINT,
timestamp TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- ๆไฝๆฅๅฟ่กจ
CREATE TABLE operation_logs (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES users(id),
vm_id UUID REFERENCES virtual_machines(id),
operation VARCHAR(50), -- create, start, stop, delete, etc.
status VARCHAR(20), -- success, failed, pending
details JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
QEMU ้ๆๆถๆ โ
javascript
// QEMU ๆๅกๆฝ่ฑกๅฑ
class QEMUService {
constructor() {
this.runningVMs = new Map() // ่ฟ่กไธญ็่ๆๆบๅฎไพ
this.qmpSockets = new Map() // QMP ็ๆงๅฅๆฅๅญ
}
// ๅๅปบ่ๆๆบ
async createVM(vmConfig) {
const qemuArgs = this.buildQEMUArgs(vmConfig)
const vmProcess = spawn('qemu-system-x86_64', qemuArgs)
this.runningVMs.set(vmConfig.id, vmProcess)
this.setupQMPConnection(vmConfig.id)
return vmProcess
}
// ๆๅปบ QEMU ๅๆฐ
buildQEMUArgs(config) {
return [
'-m',
`${config.memory}M`,
'-smp',
`cores=${config.cpu}`,
'-hda',
config.diskPath,
'-netdev',
'user,id=net0',
'-device',
'e1000,netdev=net0',
'-vnc',
`:${config.vncPort}`,
'-qmp',
`unix:${config.qmpSocket},server,nowait`,
config.cdrom ? ['-cdrom', config.cdrom] : [],
]
.flat()
.filter(Boolean)
}
// QMP ็ๆง่ฟๆฅ
setupQMPConnection(vmId) {
// ้่ฟ QMP ๅ่ฎฎ็ๆง่ๆๆบ็ถๆ
// ่ทๅ CPUใๅ
ๅญใ็ฝ็ปไฝฟ็จๆ
ๅต
}
}
ๅฎๆถ้ไฟกๆถๆ โ
javascript
// WebSocket ๆๅก
class WebSocketService {
constructor(server) {
this.io = socketIO(server, {
cors: { origin: '*' },
})
this.setupEventHandlers()
}
setupEventHandlers() {
this.io.on('connection', socket => {
// ็จๆท่ฎค่ฏ
socket.on('authenticate', async token => {
const user = await this.verifyToken(token)
socket.userId = user.id
socket.join(`user:${user.id}`)
})
// ่ฎข้
่ๆๆบ็ถๆ
socket.on('subscribe:vm', vmId => {
socket.join(`vm:${vmId}`)
})
// ่ๆๆบๆไฝ
socket.on('vm:start', async vmId => {
try {
await VMService.startVM(vmId)
this.io.to(`vm:${vmId}`).emit('vm:status', {
vmId,
status: 'starting',
})
} catch (error) {
socket.emit('error', error.message)
}
})
})
}
// ๅนฟๆญ่ๆๆบ็ถๆๆดๆฐ
broadcastVMStatus(vmId, status, metrics) {
this.io.to(`vm:${vmId}`).emit('vm:status', {
vmId,
status,
metrics,
timestamp: new Date().toISOString(),
})
}
}
ๅฎๅ จๆถๆ โ
javascript
// ๅฎๅ
จไธญ้ดไปถ
const securityMiddleware = {
// JWT ่ฎค่ฏ
authenticate: async (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1]
const user = await supabase.auth.getUser(token)
req.user = user
next()
},
// ๆ้ๆฃๆฅ
authorize: permissions => {
return (req, res, next) => {
if (!req.user.permissions.includes(permissions)) {
return res.status(403).json({ error: 'Forbidden' })
}
next()
}
},
// ่ตๆบ่ฎฟ้ฎๆงๅถ
checkVMOwnership: async (req, res, next) => {
const vmId = req.params.id
const vm = await VMService.getVM(vmId)
if (vm.user_id !== req.user.id && req.user.role !== 'admin') {
return res.status(403).json({ error: 'Access denied' })
}
next()
},
// ้ๆต
rateLimit: rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP',
}),
// ่พๅ
ฅ้ช่ฏๅๆธ
็
validateInput: schema => {
return (req, res, next) => {
const { error, value } = schema.validate(req.body)
if (error) {
return res.status(400).json({ error: error.details[0].message })
}
req.body = value
next()
}
},
// CSRF ไฟๆค
csrfProtection: csrf({
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
},
}),
}
็ๆงไธๆฅๅฟๆถๆ โ
javascript
// ็ๆงๆๅก
class MonitoringService {
constructor() {
this.metrics = new Map()
this.startMetricsCollection()
}
// ๆถ้็ณป็ปๆๆ
async collectSystemMetrics() {
return {
cpu: await this.getCPUUsage(),
memory: await this.getMemoryUsage(),
disk: await this.getDiskUsage(),
network: await this.getNetworkStats(),
timestamp: new Date().toISOString(),
}
}
// ๆถ้่ๆๆบๆๆ
async collectVMMetrics(vmId) {
const vm = this.runningVMs.get(vmId)
if (!vm) return null
try {
// ้่ฟ QMP ๅ่ฎฎ่ทๅ่ๆๆบๆๆ
const qmpClient = this.qmpSockets.get(vmId)
const metrics = await qmpClient.query({
execute: 'query-status',
})
return {
vmId,
status: metrics.status,
cpu: await this.getVMCPUUsage(vmId),
memory: await this.getVMMemoryUsage(vmId),
disk: await this.getVMDiskUsage(vmId),
network: await this.getVMNetworkStats(vmId),
timestamp: new Date().toISOString(),
}
} catch (error) {
logger.error(`Failed to collect metrics for VM ${vmId}:`, error)
return null
}
}
// ๅฏๅจๆๆ ๆถ้ๅฎๆถไปปๅก
startMetricsCollection() {
// ๆฏ30็งๆถ้ไธๆฌก็ณป็ปๆๆ
cron.schedule('*/30 * * * * *', async () => {
const metrics = await this.collectSystemMetrics()
await this.storeMetrics('system', metrics)
})
// ๆฏ15็งๆถ้ไธๆฌก่ๆๆบๆๆ
cron.schedule('*/15 * * * * *', async () => {
for (const vmId of this.runningVMs.keys()) {
const metrics = await this.collectVMMetrics(vmId)
if (metrics) {
await this.storeMetrics('vm', metrics)
// ้่ฟ WebSocket ๅนฟๆญๅฎๆถๆๆ
wsService.broadcastVMMetrics(vmId, metrics)
}
}
})
}
// ๅญๅจๆๆ ๅฐๆฐๆฎๅบ
async storeMetrics(type, metrics) {
try {
if (type === 'vm') {
await supabase.from('vm_metrics').insert({
vm_id: metrics.vmId,
cpu_usage: metrics.cpu,
memory_usage: metrics.memory,
disk_usage: metrics.disk,
network_in_bytes: metrics.network.in,
network_out_bytes: metrics.network.out,
timestamp: metrics.timestamp,
})
}
} catch (error) {
logger.error('Failed to store metrics:', error)
}
}
}
// ๆฅๅฟๆๅก
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: { service: 'voidVM' },
transports: [
// ้่ฏฏๆฅๅฟๅๅ
ฅๆไปถ
new winston.transports.File({
filename: 'logs/error.log',
level: 'error',
}),
// ๆๆๆฅๅฟๅๅ
ฅๆไปถ
new winston.transports.File({
filename: 'logs/combined.log',
}),
// ๅผๅ็ฏๅข่พๅบๅฐๆงๅถๅฐ
...(process.env.NODE_ENV !== 'production'
? [
new winston.transports.Console({
format: winston.format.simple(),
}),
]
: []),
],
})
็ผๅญๆถๆ โ
javascript
// Redis ็ผๅญๆๅก
class CacheService {
constructor() {
this.redis = new Redis({
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379,
password: process.env.REDIS_PASSWORD,
db: 0,
})
}
// ็ผๅญ่ๆๆบ็ถๆ
async cacheVMStatus(vmId, status, ttl = 300) {
const key = `vm:status:${vmId}`
await this.redis.setex(key, ttl, JSON.stringify(status))
}
// ่ทๅ็ผๅญ็่ๆๆบ็ถๆ
async getCachedVMStatus(vmId) {
const key = `vm:status:${vmId}`
const cached = await this.redis.get(key)
return cached ? JSON.parse(cached) : null
}
// ็ผๅญ็จๆทไผ่ฏ
async cacheUserSession(userId, sessionData, ttl = 3600) {
const key = `session:${userId}`
await this.redis.setex(key, ttl, JSON.stringify(sessionData))
}
// ๆธ
้ค่ๆๆบ็ธๅ
ณ็ผๅญ
async clearVMCache(vmId) {
const pattern = `vm:*:${vmId}`
const keys = await this.redis.keys(pattern)
if (keys.length > 0) {
await this.redis.del(...keys)
}
}
}
้จ็ฝฒๆถๆ โ
yaml
# docker-compose.yml
version: '3.8'
services:
# ๅ็ซฏๆๅก
frontend:
build:
context: ./client
dockerfile: Dockerfile
ports:
- '3000:80'
environment:
- VITE_API_URL=http://localhost:5000
- VITE_SUPABASE_URL=${SUPABASE_URL}
- VITE_SUPABASE_ANON_KEY=${SUPABASE_ANON_KEY}
depends_on:
- backend
# ๅ็ซฏๆๅก
backend:
build:
context: ./server
dockerfile: Dockerfile
ports:
- '5000:5000'
environment:
- NODE_ENV=production
- SUPABASE_URL=${SUPABASE_URL}
- SUPABASE_SERVICE_KEY=${SUPABASE_SERVICE_KEY}
- REDIS_HOST=redis
volumes:
- /var/lib/libvirt:/var/lib/libvirt
- /dev/kvm:/dev/kvm
privileged: true
depends_on:
- redis
- postgres
# Redis ็ผๅญ
redis:
image: redis:7-alpine
ports:
- '6379:6379'
volumes:
- redis_data:/data
command: redis-server --appendonly yes
# PostgreSQL (ๅค็จ๏ผไธป่ฆไฝฟ็จ Supabase)
postgres:
image: postgres:15
environment:
- POSTGRES_DB=voidvm
- POSTGRES_USER=${DB_USER}
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- '5432:5432'
# Nginx ๅๅไปฃ็
nginx:
image: nginx:alpine
ports:
- '80:80'
- '443:443'
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- frontend
- backend
volumes:
redis_data:
postgres_data:
ๆง่ฝไผๅๆถๆ โ
javascript
// ๆง่ฝไผๅๆๅก
class PerformanceService {
constructor() {
this.vmQueue = new Queue('vm-operations', {
redis: {
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT,
},
})
this.setupQueueProcessors()
}
// ่ฎพ็ฝฎ้ๅๅค็ๅจ
setupQueueProcessors() {
// ่ๆๆบๅฏๅจ้ๅ
this.vmQueue.process('start-vm', 5, async job => {
const { vmId, userId } = job.data
return await VMService.startVM(vmId, userId)
})
// ่ๆๆบๅๆญข้ๅ
this.vmQueue.process('stop-vm', 10, async job => {
const { vmId, userId } = job.data
return await VMService.stopVM(vmId, userId)
})
// ้ๅๆๅปบ้ๅ
this.vmQueue.process('build-image', 2, async job => {
const { imageConfig } = job.data
return await ImageService.buildImage(imageConfig)
})
}
// ๅผๆญฅๆง่ก่ๆๆบๆไฝ
async queueVMOperation(operation, vmId, userId) {
const job = await this.vmQueue.add(
`${operation}-vm`,
{
vmId,
userId,
timestamp: new Date().toISOString(),
},
{
attempts: 3,
backoff: {
type: 'exponential',
delay: 2000,
},
}
)
return job.id
}
// ่ฟๆฅๆฑ ็ฎก็
setupConnectionPool() {
// ๆฐๆฎๅบ่ฟๆฅๆฑ
this.dbPool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
})
// QEMU ่ฟ็จๆฑ
this.qemuPool = new Map()
}
}
็พ้พๆขๅคๆถๆ โ
javascript
// ๅคไปฝๆขๅคๆๅก
class BackupService {
constructor() {
this.setupScheduledBackups()
}
// ่ๆๆบๅฟซ็
ง
async createVMSnapshot(vmId, snapshotName) {
try {
logger.info(`Creating snapshot ${snapshotName} for VM ${vmId}`)
const vm = await VMService.getVM(vmId)
const qmpClient = this.qmpSockets.get(vmId)
// ๅๅปบๅ
ๅญๅฟซ็
ง
await qmpClient.execute('savevm', {
name: snapshotName,
})
// ๅๅปบ็ฃ็ๅฟซ็
ง
await this.createDiskSnapshot(vm.disk_path, snapshotName)
// ่ฎฐๅฝๅฟซ็
งไฟกๆฏ
await supabase.from('vm_snapshots').insert({
vm_id: vmId,
name: snapshotName,
type: 'full',
created_at: new Date().toISOString(),
})
logger.info(`Snapshot ${snapshotName} created successfully`)
return true
} catch (error) {
logger.error(`Failed to create snapshot: ${error.message}`)
throw error
}
}
// ๆขๅค่ๆๆบๅฟซ็
ง
async restoreVMSnapshot(vmId, snapshotName) {
try {
logger.info(`Restoring snapshot ${snapshotName} for VM ${vmId}`)
// ๅๆญข่ๆๆบ
await VMService.stopVM(vmId)
// ๆขๅค็ฃ็
await this.restoreDiskSnapshot(vmId, snapshotName)
// ๅฏๅจ่ๆๆบๅนถๅ ่ฝฝๅ
ๅญๅฟซ็
ง
await VMService.startVM(vmId)
const qmpClient = this.qmpSockets.get(vmId)
await qmpClient.execute('loadvm', {
name: snapshotName,
})
logger.info(`Snapshot ${snapshotName} restored successfully`)
return true
} catch (error) {
logger.error(`Failed to restore snapshot: ${error.message}`)
throw error
}
}
// ๅฎๆถๅคไปฝ
setupScheduledBackups() {
// ๆฏๅคฉๅๆจ2็นๆง่ก่ชๅจๅคไปฝ
cron.schedule('0 2 * * *', async () => {
const runningVMs = await VMService.getRunningVMs()
for (const vm of runningVMs) {
try {
const snapshotName = `auto_${new Date().toISOString().split('T')[0]}`
await this.createVMSnapshot(vm.id, snapshotName)
} catch (error) {
logger.error(`Auto backup failed for VM ${vm.id}: ${error.message}`)
}
}
})
}
}
API ่ฎพ่ฎก่ง่ โ
javascript
// RESTful API ่ฎพ่ฎก
const apiRoutes = {
// ่ๆๆบ็ฎก็
'GET /api/v1/vms': 'List all VMs for authenticated user',
'POST /api/v1/vms': 'Create a new VM',
'GET /api/v1/vms/:id': 'Get VM details',
'PUT /api/v1/vms/:id': 'Update VM configuration',
'DELETE /api/v1/vms/:id': 'Delete VM',
// ่ๆๆบๆไฝ
'POST /api/v1/vms/:id/start': 'Start VM',
'POST /api/v1/vms/:id/stop': 'Stop VM',
'POST /api/v1/vms/:id/pause': 'Pause VM',
'POST /api/v1/vms/:id/resume': 'Resume VM',
'POST /api/v1/vms/:id/reset': 'Reset VM',
// ๅฟซ็
ง็ฎก็
'GET /api/v1/vms/:id/snapshots': 'List VM snapshots',
'POST /api/v1/vms/:id/snapshots': 'Create VM snapshot',
'POST /api/v1/vms/:id/snapshots/:name/restore': 'Restore snapshot',
'DELETE /api/v1/vms/:id/snapshots/:name
'DELETE /api/v1/vms/:id/snapshots/:name': 'Delete snapshot',
// ็ๆงๅๆๆ
'GET /api/v1/vms/:id/metrics': 'Get VM performance metrics',
'GET /api/v1/vms/:id/console': 'Get VM console access',
'GET /api/v1/vms/:id/logs': 'Get VM operation logs',
// ้ๅ็ฎก็
'GET /api/v1/images': 'List available VM images',
'POST /api/v1/images': 'Upload new VM image',
'GET /api/v1/images/:id': 'Get image details',
'DELETE /api/v1/images/:id': 'Delete VM image',
// ็ฝ็ป็ฎก็
'GET /api/v1/networks': 'List virtual networks',
'POST /api/v1/networks': 'Create virtual network',
'GET /api/v1/networks/:id': 'Get network details',
'PUT /api/v1/networks/:id': 'Update network configuration',
'DELETE /api/v1/networks/:id': 'Delete virtual network',
// ๅญๅจ็ฎก็
'GET /api/v1/storage': 'List storage pools',
'POST /api/v1/storage': 'Create storage pool',
'GET /api/v1/storage/:id/volumes': 'List volumes in storage pool',
'POST /api/v1/storage/:id/volumes': 'Create new volume',
// ็จๆท็ฎก็
'GET /api/v1/users/profile': 'Get user profile',
'PUT /api/v1/users/profile': 'Update user profile',
'GET /api/v1/users/usage': 'Get resource usage statistics',
// ็ณป็ป็ฎก็
'GET /api/v1/system/stats': 'Get system statistics',
'GET /api/v1/system/health': 'Health check endpoint',
'GET /api/v1/system/version': 'Get system version info'
};
// API ๅๅบๆ ผๅผๆ ๅ
const ApiResponse = {
success: {
code: 200,
message: 'Success',
data: {},
timestamp: new Date().toISOString()
},
error: {
code: 400,
message: 'Error message',
error: 'Detailed error information',
timestamp: new Date().toISOString()
}
};
ๆฉๅฑๆงๆถๆ โ
javascript
// ๅพฎๆๅกๆถๆๆฉๅฑ
class MicroserviceArchitecture {
constructor() {
this.services = new Map()
this.serviceRegistry = new ServiceRegistry()
this.loadBalancer = new LoadBalancer()
}
// ๆๅกๆณจๅ
registerService(serviceName, serviceInstance) {
this.services.set(serviceName, serviceInstance)
this.serviceRegistry.register(serviceName, serviceInstance)
}
// ๆๅกๅ็ฐ
async discoverService(serviceName) {
return await this.serviceRegistry.discover(serviceName)
}
// ่ด่ฝฝๅ่กก
async routeRequest(serviceName, request) {
const serviceInstances = await this.discoverService(serviceName)
const selectedInstance = this.loadBalancer.select(serviceInstances)
return await selectedInstance.handleRequest(request)
}
}
// ๆๅกๆณจๅไธญๅฟ
class ServiceRegistry {
constructor() {
this.services = new Map()
this.healthChecker = new HealthChecker()
}
register(serviceName, instance) {
if (!this.services.has(serviceName)) {
this.services.set(serviceName, [])
}
this.services.get(serviceName).push(instance)
// ๅฏๅจๅฅๅบทๆฃๆฅ
this.healthChecker.monitor(instance)
}
async discover(serviceName) {
const instances = this.services.get(serviceName) || []
// ่ฟๅๅฅๅบท็ๆๅกๅฎไพ
return instances.filter(instance => instance.isHealthy)
}
}
// ๅฎนๅจๅๆฉๅฑ
class ContainerOrchestration {
constructor() {
this.docker = new Docker()
this.kubernetes = new KubernetesClient()
}
// Docker ๅฎนๅจ็ฎก็
async deployService(serviceName, config) {
const container = await this.docker.createContainer({
Image: config.image,
name: serviceName,
Env: config.environment,
PortBindings: config.ports,
RestartPolicy: { Name: 'unless-stopped' },
})
await container.start()
return container
}
// Kubernetes ้จ็ฝฒ
async deployToK8s(serviceName, manifest) {
await this.kubernetes.apply(manifest)
return await this.kubernetes.waitForDeployment(serviceName)
}
}
ๅฎๅ จๅ ๅบๆถๆ โ
javascript
// ๅฎๅ
จๆๅก
class SecurityService {
constructor() {
this.setupSecurityPolicies()
this.auditLogger = new AuditLogger()
}
// ่ฎพ็ฝฎๅฎๅ
จ็ญ็ฅ
setupSecurityPolicies() {
// ๅฏ็ ็ญ็ฅ
this.passwordPolicy = {
minLength: 8,
requireUppercase: true,
requireLowercase: true,
requireNumbers: true,
requireSpecialChars: true,
maxAge: 90, // days
}
// ไผ่ฏ็ญ็ฅ
this.sessionPolicy = {
maxAge: 3600, // 1 hour
maxConcurrentSessions: 5,
requireMFA: false,
}
// ่ฎฟ้ฎๆงๅถ็ญ็ฅ
this.accessPolicy = {
maxFailedAttempts: 5,
lockoutDuration: 1800, // 30 minutes
allowedIPs: [], // empty = allow all
blockedIPs: [],
}
}
// ๅคๅ ็ด ่ฎค่ฏ
async setupMFA(userId, method = 'totp') {
const secret = authenticator.generateSecret()
const qrCode = await QRCode.toDataURL(authenticator.keyuri(userId, 'voidVM', secret))
await supabase.from('user_mfa').insert({
user_id: userId,
method,
secret: this.encrypt(secret),
enabled: false,
})
return { secret, qrCode }
}
// ้ช่ฏ MFA
async verifyMFA(userId, token) {
const mfaRecord = await supabase
.from('user_mfa')
.select('secret')
.eq('user_id', userId)
.single()
if (!mfaRecord) {
throw new Error('MFA not configured')
}
const secret = this.decrypt(mfaRecord.secret)
return authenticator.verify({ token, secret })
}
// ๅฎก่ฎกๆฅๅฟ
async logSecurityEvent(event) {
await this.auditLogger.log({
type: 'security',
event: event.type,
userId: event.userId,
ip: event.ip,
userAgent: event.userAgent,
details: event.details,
timestamp: new Date().toISOString(),
})
}
// ๅ ๅฏ่งฃๅฏ
encrypt(text) {
const cipher = crypto.createCipher('aes-256-cbc', process.env.ENCRYPTION_KEY)
let encrypted = cipher.update(text, 'utf8', 'hex')
encrypted += cipher.final('hex')
return encrypted
}
decrypt(encryptedText) {
const decipher = crypto.createDecipher('aes-256-cbc', process.env.ENCRYPTION_KEY)
let decrypted = decipher.update(encryptedText, 'hex', 'utf8')
decrypted += decipher.final('utf8')
return decrypted
}
}
ๆต่ฏๆถๆ โ
javascript
// ๆต่ฏๆกๆถ
describe('voidVM Test Suite', () => {
// ๅๅ
ๆต่ฏ
describe('Unit Tests', () => {
describe('VM Service', () => {
it('should create a new VM', async () => {
const vmConfig = {
name: 'test-vm',
cpu: 2,
memory: 2048,
disk: 20,
}
const vm = await VMService.createVM(vmConfig)
expect(vm).to.have.property('id')
expect(vm.name).to.equal('test-vm')
})
it('should start a VM', async () => {
const vmId = 'test-vm-id'
const result = await VMService.startVM(vmId)
expect(result).to.be.true
})
})
describe('QEMU Service', () => {
it('should build correct QEMU arguments', () => {
const config = {
memory: 2048,
cpu: 2,
diskPath: '/path/to/disk.qcow2',
}
const args = QEMUService.buildQEMUArgs(config)
expect(args).to.include('-m')
expect(args).to.include('2048M')
})
})
})
// ้ๆๆต่ฏ
describe('Integration Tests', () => {
describe('API Endpoints', () => {
it('should create VM via API', async () => {
const response = await request(app)
.post('/api/v1/vms')
.set('Authorization', `Bearer ${testToken}`)
.send({
name: 'integration-test-vm',
cpu: 1,
memory: 1024,
})
expect(response.status).to.equal(201)
expect(response.body.data).to.have.property('id')
})
})
describe('Database Operations', () => {
it('should store VM configuration', async () => {
const vm = await VMService.createVM(testVMConfig)
const stored = await supabase.from('virtual_machines').select('*').eq('id', vm.id).single()
expect(stored.data).to.not.be.null
})
})
})
// ็ซฏๅฐ็ซฏๆต่ฏ
describe('E2E Tests', () => {
describe('VM Lifecycle', () => {
it('should complete full VM lifecycle', async () => {
// ๅๅปบ่ๆๆบ
const vm = await VMService.createVM(testVMConfig)
// ๅฏๅจ่ๆๆบ
await VMService.startVM(vm.id)
expect(await VMService.getVMStatus(vm.id)).to.equal('running')
// ๅๆญข่ๆๆบ
await VMService.stopVM(vm.id)
expect(await VMService.getVMStatus(vm.id)).to.equal('stopped')
// ๅ ้ค่ๆๆบ
await VMService.deleteVM(vm.id)
const deleted = await VMService.getVM(vm.id)
expect(deleted).to.be.null
})
})
})
// ๆง่ฝๆต่ฏ
describe('Performance Tests', () => {
it('should handle concurrent VM operations', async () => {
const promises = []
const vmCount = 10
for (let i = 0; i < vmCount; i++) {
promises.push(
VMService.createVM({
name: `perf-test-vm-${i}`,
cpu: 1,
memory: 512,
})
)
}
const results = await Promise.all(promises)
expect(results).to.have.length(vmCount)
})
it('should respond to API requests within acceptable time', async () => {
const start = Date.now()
await request(app).get('/api/v1/vms').set('Authorization', `Bearer ${testToken}`)
const duration = Date.now() - start
expect(duration).to.be.below(1000) // less than 1 second
})
})
})
้ ็ฝฎ็ฎก็ๆถๆ โ
javascript
// ้
็ฝฎ็ฎก็
class ConfigManager {
constructor() {
this.configs = new Map()
this.loadConfigurations()
}
loadConfigurations() {
// ๅบ็จ้
็ฝฎ
this.configs.set('app', {
name: 'voidVM',
version: process.env.APP_VERSION || '1.0.0',
environment: process.env.NODE_ENV || 'development',
port: process.env.PORT || 5000,
host: process.env.HOST || 'localhost',
})
// ๆฐๆฎๅบ้
็ฝฎ
this.configs.set('database', {
supabase: {
url: process.env.SUPABASE_URL,
anonKey: process.env.SUPABASE_ANON_KEY,
serviceKey: process.env.SUPABASE_SERVICE_KEY,
},
redis: {
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379,
password: process.env.REDIS_PASSWORD,
},
})
// QEMU ้
็ฝฎ
this.configs.set('qemu', {
binaryPath: process.env.QEMU_BINARY || '/usr/bin/qemu-system-x86_64',
imagePath: process.env.VM_IMAGE_PATH || '/var/lib/voidvm/images',
maxConcurrentVMs: process.env.MAX_CONCURRENT_VMS || 10,
defaultMemory: 1024,
defaultCPU: 1,
vncPortRange: {
start: 5900,
end: 5999,
},
})
// ๅฎๅ
จ้
็ฝฎ
this.configs.set('security', {
jwtSecret: process.env.JWT_SECRET,
encryptionKey: process.env.ENCRYPTION_KEY,
sessionTimeout: 3600,
maxLoginAttempts: 5,
lockoutDuration: 1800,
})
// ็ๆง้
็ฝฎ
this.configs.set('monitoring', {
metricsInterval: 30000, // 30 seconds
logLevel: process.env.LOG_LEVEL || 'info',
enableMetrics: process.env.ENABLE_METRICS !== 'false',
alertingEnabled: process.env.ALERTING_ENABLED === 'true',
})
}
get(configName, key = null) {
const config = this.configs.get(configName)
if (!config) {
throw new Error(`Configuration '${configName}' not found`)
}
return key ? config[key] : config
}
validate() {
const required = [
'SUPABASE_URL',
'SUPABASE_ANON_KEY',
'SUPABASE_SERVICE_KEY',
'JWT_SECRET',
'ENCRYPTION_KEY',
]
const missing = required.filter(key => !process.env[key])
if (missing.length > 0) {
throw new Error(`Missing required environment variables: ${missing.join(', ')}`)
}
// ้ช่ฏ QEMU ไบ่ฟๅถๆไปถๅญๅจ
if (!fs.existsSync(this.get('qemu', 'binaryPath'))) {
throw new Error('QEMU binary not found at specified path')
}
// ้ช่ฏ้ๅ็ฎๅฝๅญๅจๆๅๅปบ
const imagePath = this.get('qemu', 'imagePath')
if (!fs.existsSync(imagePath)) {
fs.mkdirSync(imagePath, { recursive: true })
}
return true
}
}
ๆฐๆฎๆตๆถๆ โ
็นๅปๆพๅคง
้่ฏฏๅค็ๆถๆ โ
javascript
// ็ปไธ้่ฏฏๅค็
class ErrorHandler {
constructor() {
this.errorTypes = {
VALIDATION_ERROR: 'ValidationError',
AUTHENTICATION_ERROR: 'AuthenticationError',
AUTHORIZATION_ERROR: 'AuthorizationError',
RESOURCE_NOT_FOUND: 'ResourceNotFoundError',
QEMU_ERROR: 'QEMUError',
DATABASE_ERROR: 'DatabaseError',
NETWORK_ERROR: 'NetworkError',
SYSTEM_ERROR: 'SystemError',
}
}
// ๅๅปบ่ชๅฎไน้่ฏฏ็ฑป
createError(type, message, details = {}) {
const error = new Error(message)
error.name = type
error.details = details
error.timestamp = new Date().toISOString()
return error
}
// Express ้่ฏฏๅค็ไธญ้ดไปถ
expressErrorHandler() {
return (err, req, res, next) => {
// ่ฎฐๅฝ้่ฏฏ
logger.error({
error: err.message,
stack: err.stack,
url: req.url,
method: req.method,
userId: req.user?.id,
timestamp: new Date().toISOString(),
})
// ๆ นๆฎ้่ฏฏ็ฑปๅ่ฟๅไธๅ็ๅๅบ
let statusCode = 500
let message = 'Internal Server Error'
switch (err.name) {
case this.errorTypes.VALIDATION_ERROR:
statusCode = 400
message = err.message
break
case this.errorTypes.AUTHENTICATION_ERROR:
statusCode = 401
message = 'Authentication required'
break
case this.errorTypes.AUTHORIZATION_ERROR:
statusCode = 403
message = 'Access denied'
break
case this.errorTypes.RESOURCE_NOT_FOUND:
statusCode = 404
message = err.message || 'Resource not found'
break
case this.errorTypes.QEMU_ERROR:
statusCode = 500
message = 'Virtual machine operation failed'
break
}
res.status(statusCode).json({
error: true,
message,
code: err.name,
details: process.env.NODE_ENV === 'development' ? err.details : undefined,
timestamp: new Date().toISOString(),
})
}
}
// ๅผๆญฅๆไฝ้่ฏฏๅค็
asyncWrapper(fn) {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next)
}
}
// QEMU ้่ฏฏๅค็
handleQEMUError(error, vmId) {
const qemuError = this.createError(
this.errorTypes.QEMU_ERROR,
`QEMU operation failed for VM ${vmId}`,
{
vmId,
originalError: error.message,
stderr: error.stderr,
}
)
logger.error('QEMU Error:', qemuError)
return qemuError
}
}