Files
dsh_ai1net_server/归档/poc-dsh-local/session-gc.cjs
T
admin ce8e6ceed9 chore(工作区): 纳入版本控制基线(回收 411 MB 过程产物)
回收 411 MB(470 M → 58.8 M),全部经回收站,可恢复:
- 待清理/(146.2 M,含 relay 分片 128 M 与 42 项过程目录)
- tmp/(32.4 M,按接续棒命名的过程临时区)
- .workbuddy/tmp/(39.5 M)
- 4 份 workbuddy.db 冗余副本(101 M,09-23 事故的坏副本 / 抢救产物)
- tmp/im16/gw/centrifugo 二进制(63.9 M,可重下)+ 缓存残留

入库范围:常驻规则(CODEBUDDY.md / README.md / state.py)、在途接续入口与
接续包、docs/、交付物/、交接单/、归档/、scripts/、.codebuddy/、
.workbuddy/memory/;共 398 件,其中 >60 KB 的 26 件全为文档。

排除(.gitignore):tmp/、待清理/、运行态日志与缓存、*.db 与 DB 备份整目录、
打包二进制(*.tar.gz / *.tgz)、记忆修复前备份。
2026-09-24 07:51:03 +08:00

80 lines
4.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* session-gc.cjs —— 会话记录保留期回收(档案 14 §H3 / 档案 28)
* 规则(用户 2026-09-11 定):**每个用户的 sessions 达到阈值才触发**,清理 **超过 N 天** 的会话目录。
* 结构:<home>/sessions/<workspace-slug>/<session-id>/session.jsonl.zstd
* 安全:默认 dry-run;--apply 时 `mv` 到 <userRoot>/trash/<日期>-session-gc/(保留 30 天)。
* 说明:storages/session_projcache 体积很小(KB 级),本脚本不动它(留作后续细化)。
*
* 用法:node session-gc.cjs [--apply] [--threshold 500] [--days 90] [--user-id <uuid>]
*/
const { execFileSync } = require('node:child_process')
const { existsSync, mkdirSync, readdirSync, statSync } = require('node:fs')
const { join } = require('node:path')
const Database = require('/opt/dshs/node_modules/better-sqlite3')
const argv = process.argv.slice(2)
const APPLY = argv.includes('--apply')
const num = (flag, dflt) => { const i = argv.indexOf(flag); const v = i >= 0 ? Number(argv[i + 1]) : NaN; return Number.isFinite(v) ? v : dflt }
const THRESHOLD_MB = num('--threshold', 500)
const DAYS = num('--days', 90)
const idx = argv.indexOf('--user-id')
const ONLY = idx >= 0 ? argv[idx + 1] : ''
const STAMP = new Date().toISOString().slice(0, 10)
const CUTOFF = Date.now() - DAYS * 86400_000
const duKB = (p) => { try { return Number(execFileSync('du', ['-sk', p], { encoding: 'utf8' }).split(/\s+/)[0]) * 1024 } catch { return 0 } }
const fmt = (b) => (b >= 1048576 ? (b / 1048576).toFixed(1) + ' MB' : (b / 1024).toFixed(0) + ' KB')
const db = new Database('/var/lib/dshs/dshs.db', { readonly: true })
const users = db.prepare('SELECT id, username, uid, home_dir FROM users').all().filter((u) => ONLY === '' || u.id === ONLY)
for (const u of users) {
const sessions = join(u.home_dir, 'sessions')
if (!existsSync(sessions)) { console.log(` ${u.username}: NO_SESSIONS`); continue }
const total = duKB(sessions)
const over = total >= THRESHOLD_MB * 1048576
const stale = []
for (const slug of readdirSync(sessions)) {
const slugDir = join(sessions, slug)
let st; try { st = statSync(slugDir) } catch { continue }
if (!st.isDirectory()) continue
for (const sid of readdirSync(slugDir)) {
const sd = join(slugDir, sid)
let sst; try { sst = statSync(sd) } catch { continue }
if (!sst.isDirectory()) continue
const f = join(sd, 'session.jsonl.zstd')
let mtime = sst.mtimeMs
try { if (existsSync(f)) mtime = statSync(f).mtimeMs } catch {}
if (mtime < CUTOFF) stale.push({ p: sd, size: duKB(sd), mtime: new Date(mtime) })
}
}
const staleB = stale.reduce((a, c) => a + c.size, 0)
console.log(` ${u.username}: sessions=${fmt(total)} (${over ? '≥' : '<'} 阈值 ${THRESHOLD_MB}MB) | 超 ${DAYS} 天会话=${stale.length} 个 / ${fmt(staleB)}`)
for (const c of stale) console.log(` ${c.p.split('/').slice(-2).join('/')} ${fmt(c.size)} (${c.mtime.toISOString().slice(0, 10)})`)
if (APPLY && over && stale.length > 0) {
const trash = join(u.home_dir, '..', 'trash', `${STAMP}-session-gc`)
mkdirSync(trash, { recursive: true })
for (const c of stale) {
const dest = join(trash, c.p.split('/').slice(-2).join('__'))
try { execFileSync('mv', [c.p, dest]); execFileSync('chown', ['-R', `${u.uid}:${u.uid}`, dest]) }
catch (e) { console.log(` ! 失败 ${c.p}: ${String(e.message).split('\n')[0]}`) }
}
// 同步回收投影缓存:storages/session_projcache/sessions/<session-id>.json(按 id 精确匹配)
const pcDir = join(u.home_dir, 'storages', 'session_projcache', 'sessions')
let pcMoved = 0
if (existsSync(pcDir)) {
for (const c of stale) {
const sid = c.p.split('/').pop()
const pc = join(pcDir, `${sid}.json`)
if (existsSync(pc)) {
try { execFileSync('mv', [pc, join(trash, `projcache__${sid}.json`)]); pcMoved += 1 } catch {}
}
}
}
console.log(` → 已移 ${stale.length} 个会话 / ${fmt(staleB)}${pcMoved > 0 ? `(+ ${pcMoved} 个投影缓存)` : ''} → trash/${STAMP}-session-gc(保留 30 天)`)
} else if (APPLY && !over) console.log(' (未超阈值,跳过)')
}
db.close()
console.log(APPLY ? 'done(已按阈值执行)' : 'done(dry-run,未改动)')