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:
admin committed 2026-09-15 06:36:20 +08:00
1 parent ed0c3c0ad5
commit cf8b7b1f5c
19 files changed
+29 -2690

No files matched your search

-150
View File
@@ -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
View File
@@ -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 },