chore(k8s): 下线 K8s 后端形态,移除依赖 @kubernetes/client-node
生产形态是单机 local(DEFAULT_DEPLOY_MODE=local,env 未覆盖)⇒ K8s 分支
在 local 下本来不可达;且该形态与官方 dsh 基座、插件体系均无关
=> 整体下线,并移除该形态唯一的第三方依赖。
移除(备份在 D:/github/_dsh_shenxian_K8s后端备份_20260915/,含还原命令与
「集群化方案要复用的模板清单」):
src/supervisor/{k8s-spawner,leader,reconcile}.ts
src/fs/k8s-user-fs.ts · src/tcp-bridge.ts · src/web/file-service.ts
test/{k8s-spawner,leader}.test.mjs · scripts/smoke-file-service.mjs
改写调用方:cli.ts(5 条 import / 选主块 / file-service 与 tcp-bridge 两个
子命令 / dispatch / HELP)、web/server.ts(改为 fail-loud 守卫 + 恒用
LocalSpawner)、fs/provider.ts(只留 LocalUserFs)、package.json(测试与
smoke 入口),外加 3 处指向已删类型的悬空 JSDoc。
保留(集群化方案列为未来可选):deploy/ · poc/01-04 · Dockerfile.dsh ·
docs/k8s*.md(已加「代码已下线」状态横幅)· config.ts 的 K8s 配置字段与
DeployMode 联合类型。
验证:tsc --noEmit exit 0;npm test 36 测试 / 35 通过 / 0 失败 / 1 跳过;
npm run verify exit 0;依赖与被删符号全仓 0 命中。
This commit is contained in:
1 parent
ed0c3c0ad5
commit
cf8b7b1f5c
19 files changed
+29
-2690
No files matched your search
@@ -1,150 +0,0 @@
|
||||
/**
|
||||
* The per-user file sidecar (docs/k8s.md §4.10).
|
||||
*
|
||||
* Runs inside the user's own Pod as the user's uid, so it *can* read and write
|
||||
* their `0700` directory — the thing the control plane (uid 65532, no volume)
|
||||
* cannot do. It serves exactly one user: the root is fixed at startup, and no
|
||||
* request carries a user id.
|
||||
*
|
||||
* **No authentication.** The boundary is the NetworkPolicy that lets only the
|
||||
* control plane reach port 8082, the same argument that lets the socat sidecar
|
||||
* bridge 8081 unauthenticated (docs/k8s.md §6.1 item 4). If that policy is ever
|
||||
* widened, this service needs a token check *first*.
|
||||
* @module dshs/web/file-service
|
||||
*/
|
||||
|
||||
import Fastify, { type FastifyInstance, type FastifyReply } from 'fastify'
|
||||
import { LocalUserFs } from '../fs/local-user-fs.js'
|
||||
import { UserFsError } from '../fs/user-fs.js'
|
||||
|
||||
/** Port the sidecar binds; the per-user Service targets it directly. */
|
||||
export const FILE_SERVICE_PORT = 8082
|
||||
|
||||
/** Env var carrying the single user root this sidecar serves. */
|
||||
export const USER_ROOT_ENV = 'DSHS_USER_ROOT'
|
||||
|
||||
/** The sidecar serves one user, so the id crossing {@link UserFs} is a constant. */
|
||||
const SOLE_USER = 'self'
|
||||
|
||||
const pathSchema = {
|
||||
body: {
|
||||
type: 'object',
|
||||
required: ['path'],
|
||||
additionalProperties: false,
|
||||
properties: { path: { type: 'string', maxLength: 512 } },
|
||||
},
|
||||
} as const
|
||||
|
||||
const entrySchema = {
|
||||
body: {
|
||||
type: 'object',
|
||||
required: ['path', 'name', 'type'],
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
path: { type: 'string', maxLength: 512 },
|
||||
name: { type: 'string', maxLength: 255 },
|
||||
type: { type: 'string', enum: ['file', 'dir'] },
|
||||
},
|
||||
},
|
||||
} as const
|
||||
|
||||
const uploadSchema = {
|
||||
body: {
|
||||
type: 'object',
|
||||
required: ['path', 'name', 'data'],
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
path: { type: 'string', maxLength: 512 },
|
||||
name: { type: 'string', maxLength: 255 },
|
||||
data: { type: 'string' },
|
||||
},
|
||||
},
|
||||
} as const
|
||||
|
||||
const handoffSchema = {
|
||||
body: {
|
||||
type: 'object',
|
||||
required: ['content'],
|
||||
additionalProperties: false,
|
||||
properties: { content: { type: 'string', maxLength: 8192 } },
|
||||
},
|
||||
} as const
|
||||
|
||||
/** Reply with the seam's own wire form so the client can rebuild the error. */
|
||||
function fail(reply: FastifyReply, err: unknown): FastifyReply {
|
||||
if (err instanceof UserFsError) return reply.code(err.status).send({ error: err.code })
|
||||
throw err
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the sidecar's Fastify instance. Does not listen; the caller binds.
|
||||
* @param root - the user's data root inside the Pod.
|
||||
* @param options - `bodyLimit` must exceed the control plane's upload cap.
|
||||
*/
|
||||
export function buildFileService(root: string, options: { bodyLimit: number; logLevel: string }): FastifyInstance {
|
||||
const fs = new LocalUserFs(() => root)
|
||||
const app = Fastify({ logger: { level: options.logLevel }, bodyLimit: options.bodyLimit })
|
||||
|
||||
app.get('/healthz', async () => ({ ok: true }))
|
||||
|
||||
app.get('/fs/tree', async (request, reply) => {
|
||||
const { path = '' } = request.query as { path?: string }
|
||||
try {
|
||||
return { entries: await fs.listDir(SOLE_USER, path) }
|
||||
} catch (err) {
|
||||
return fail(reply, err)
|
||||
}
|
||||
})
|
||||
|
||||
app.get('/fs/stat', async (request, reply) => {
|
||||
const { path = '' } = request.query as { path?: string }
|
||||
try {
|
||||
return { isDirectory: await fs.isDirectory(SOLE_USER, path) }
|
||||
} catch (err) {
|
||||
return fail(reply, err)
|
||||
}
|
||||
})
|
||||
|
||||
app.get('/fs/plugins', async () => ({ plugins: await fs.listInstalledPlugins(SOLE_USER) }))
|
||||
|
||||
app.post('/fs/init', async () => {
|
||||
await fs.initUserRoot(SOLE_USER)
|
||||
return { ok: true }
|
||||
})
|
||||
|
||||
app.post('/fs/mkdir', { schema: pathSchema }, async (request, reply) => {
|
||||
const { path } = request.body as { path: string }
|
||||
try {
|
||||
await fs.mkdir(SOLE_USER, path)
|
||||
return { ok: true }
|
||||
} catch (err) {
|
||||
return fail(reply, err)
|
||||
}
|
||||
})
|
||||
|
||||
app.post('/fs/create', { schema: entrySchema }, async (request, reply) => {
|
||||
const { path, name, type } = request.body as { path: string; name: string; type: 'file' | 'dir' }
|
||||
try {
|
||||
return { name: await fs.createEntry(SOLE_USER, path, name, type) }
|
||||
} catch (err) {
|
||||
return fail(reply, err)
|
||||
}
|
||||
})
|
||||
|
||||
app.post('/fs/upload', { schema: uploadSchema }, async (request, reply) => {
|
||||
const { path, name, data } = request.body as { path: string; name: string; data: string }
|
||||
try {
|
||||
return { name: await fs.upload(SOLE_USER, path, name, Buffer.from(data, 'base64')) }
|
||||
} catch (err) {
|
||||
return fail(reply, err)
|
||||
}
|
||||
})
|
||||
|
||||
app.post('/fs/handoff', { schema: handoffSchema }, async (request) => {
|
||||
const { content } = request.body as { content: string }
|
||||
await fs.writeHandoff(SOLE_USER, content)
|
||||
return { ok: true }
|
||||
})
|
||||
|
||||
return app
|
||||
}
|
||||
+5
-6
@@ -17,7 +17,6 @@ import type { UserFs } from '../fs/user-fs.js'
|
||||
import { decrypt, deriveKey } from '../crypto.js'
|
||||
import { hashUid } from '../isolation.js'
|
||||
import { LocalSpawner } from '../supervisor/orchestrator.js'
|
||||
import { K8sSpawner } from '../supervisor/k8s-spawner.js'
|
||||
import { registerDshProxy } from '../supervisor/proxy.js'
|
||||
import type { Spawner } from '../supervisor/spawner.js'
|
||||
import {
|
||||
@@ -257,11 +256,11 @@ export async function buildServer(config: ServerConfig): Promise<FastifyInstance
|
||||
const user = await db.findUserById(userId)
|
||||
return user?.uid ?? hashUid(userId, config.baseUid)
|
||||
}
|
||||
const supervisor: Spawner =
|
||||
config.deployMode === 'k8s'
|
||||
? new K8sSpawner(config, db, resolveApiKey, resolveUid)
|
||||
: new LocalSpawner(config, resolveApiKey, resolveUid)
|
||||
const userFs = createUserFs(config, (userId) => supervisor.ensureFileService(userId))
|
||||
if (config.deployMode === 'k8s') {
|
||||
throw new Error('deployMode "k8s" is not supported by this build: only the single-machine backend ships')
|
||||
}
|
||||
const supervisor: Spawner = new LocalSpawner(config, resolveApiKey, resolveUid)
|
||||
const userFs = createUserFs(config)
|
||||
|
||||
const app = Fastify({
|
||||
logger: { level: config.logLevel },
|
||||
|
||||
Reference in new issue
Block a user