DevOS v1 + Phase 2: unified resource model, infra ops, website deployment, AI chat ops

- Monorepo: NestJS API (Fastify) + React web (antd/ProComponents) + shared packages
- PostgreSQL/Prisma (unified resources, relations graph, audit, events, jobs)
- Auth/RBAC/workspace isolation, credential vault (AES-256-GCM)
- Servers/SSH, Docker discovery & lifecycle, Job Center
- Websites & deployments (git provider credential clone, env, health verification)
- Domains/DNS (Cloudflare adapter), certificates (TLS probe), monitoring
- Automation (cron/event/webhook HMAC + notifications), AI Gateway, MCP (29 tools)
- AI chat window (LLM function-calling + rules fallback, SSE streaming, approvals)
- Notes: Vditor WYSIWYG markdown editor, non-blocking autosave
- Tests: 35 unit/security tests; UI audit clean (desktop+mobile, deep links)
This commit is contained in:
phptits
2026-09-10 06:35:55 +00:00
commit 0d6cd37579
209 changed files with 22783 additions and 0 deletions
@@ -0,0 +1,76 @@
/**
* # 凭据保险库
* AES-256-GCM 信封式加密:
* Application Master Key (ENCRYPTION_KEY, 32B) → 加密凭据载荷。
* - AAD 绑定 workspaceId,防止跨租户密文移植;
* - 明文只在内存中短暂存在,绝不入库、绝不入日志(审计层另有脱敏);
* - API 层不提供任何“读取明文”端点。
*/
import { Injectable } from "@nestjs/common";
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
import { ConfigService } from "../../common/config/config.service";
import { ApiError } from "../../common/errors/api-error";
/** # 密文载荷结构(JSON 序列化后存储) **/
interface EncryptedPayload {
v: 1;
iv: string;
tag: string;
data: string;
}
@Injectable()
export class CredentialVaultService {
/** # 主密钥 **/
private readonly key: Buffer;
constructor(config: ConfigService) {
this.key = Buffer.from(config.encryptionKey, "hex");
}
/** # 加密
* @param workspaceId 租户 ID(作为 AAD 绑定)
* @param plaintext 明文(如私钥/密码/API Token)
*/
encrypt(workspaceId: string, plaintext: string): string {
const iv = randomBytes(12);
const cipher = createCipheriv("aes-256-gcm", this.key, iv, { authTagLength: 16 });
cipher.setAAD(Buffer.from(workspaceId, "utf8"));
const data = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
const payload: EncryptedPayload = {
v: 1,
iv: iv.toString("base64"),
tag: cipher.getAuthTag().toString("base64"),
data: data.toString("base64"),
};
return JSON.stringify(payload);
}
/** # 解密(仅限服务端内部使用)
* @throws 完整性校验失败(密文被篡改或跨租户使用)时抛出
*/
decrypt(workspaceId: string, encrypted: string): string {
let payload: EncryptedPayload;
try {
payload = JSON.parse(encrypted) as EncryptedPayload;
} catch {
throw ApiError.forbidden("Credential payload corrupted");
}
try {
const decipher = createDecipheriv(
"aes-256-gcm",
this.key,
Buffer.from(payload.iv, "base64"),
{ authTagLength: 16 },
);
decipher.setAAD(Buffer.from(workspaceId, "utf8"));
decipher.setAuthTag(Buffer.from(payload.tag, "base64"));
return Buffer.concat([
decipher.update(Buffer.from(payload.data, "base64")),
decipher.final(),
]).toString("utf8");
} catch {
throw ApiError.forbidden("Credential integrity check failed");
}
}
}