gc: retain vscode cached vsix downloads

This commit is contained in:
Codex
2026-06-11 19:59:27 +00:00
parent 494a3b8342
commit 4b8d624b05
3 changed files with 88 additions and 8 deletions
+79 -4
View File
@@ -18,6 +18,7 @@ type GcItemKind =
| "tool-cache-delete"
| "vscode-server-delete"
| "vscode-extension-delete"
| "vscode-cached-vsix-delete"
| "baidu-staging-file-delete"
| "state-artifact-file-delete"
| "state-artifact-dir-delete"
@@ -44,6 +45,8 @@ interface GcOptions {
vscodeKeepServers: number;
vscodeStaleExtensions: boolean;
vscodeKeepExtensionVersions: number;
vscodeCachedVsix: boolean;
vscodeCachedVsixKeepDays: number;
baiduStaging: boolean;
baiduStagingKeepDays: number;
stateArtifacts: boolean;
@@ -185,6 +188,8 @@ const DEFAULT_OPTIONS: GcOptions = {
vscodeKeepServers: 2,
vscodeStaleExtensions: false,
vscodeKeepExtensionVersions: 1,
vscodeCachedVsix: false,
vscodeCachedVsixKeepDays: 7,
baiduStaging: false,
baiduStagingKeepDays: 10,
stateArtifacts: false,
@@ -305,6 +310,7 @@ const TOOL_CACHE_ALLOWLIST = [
const VSCODE_SERVER_ROOT = "/root/.vscode-server/cli/servers";
const VSCODE_EXTENSION_ROOT = "/root/.vscode-server/extensions";
const VSCODE_CACHED_VSIX_ROOT = "/root/.vscode-server/data/CachedExtensionVSIXs";
const BAIDU_STAGING_RELATIVE_ROOT = [".state", "baidu-netdisk", "staging"];
const STATE_ARTIFACT_FILE_ROOTS = [
{ id: "e2e", relativeRoot: [".state", "e2e"] },
@@ -473,6 +479,20 @@ export function gcPlan(config: UniDeskConfig, options: GcOptions = DEFAULT_OPTIO
});
}
}
if (options.vscodeCachedVsix) {
candidates.push(...collectVscodeCachedVsixCandidates(options, observedAt));
} else {
const cachedVsixSize = safePathSize(VSCODE_CACHED_VSIX_ROOT);
if (cachedVsixSize > 0) {
protectedItems.push({
kind: "vscode-cached-vsix-cache",
risk: "blocked",
ref: VSCODE_CACHED_VSIX_ROOT,
sizeBytes: cachedVsixSize,
reason: "VS Code CachedExtensionVSIXs download cache is not removed by default; rerun with --include-vscode-cached-vsix to remove stale cached VSIX files only.",
});
}
}
if (options.baiduStaging) {
candidates.push(...collectBaiduStagingCandidates(options, observedAt));
}
@@ -523,7 +543,7 @@ export function gcPlan(config: UniDeskConfig, options: GcOptions = DEFAULT_OPTIO
notes: [
"gc run only executes listed one-time cleanup actions after --confirm.",
options.full ? "Full candidate output requested." : `Default output is capped to ${options.limit} candidates; use --full or --limit N for broader disclosure.`,
"Tool caches, stale /tmp direct children, stale VS Code server versions and stale VS Code extension versions are opt-in and require explicit include flags.",
"Tool caches, stale /tmp direct children, stale VS Code server versions, stale VS Code extension versions and stale VS Code cached VSIX downloads are opt-in and require explicit include flags.",
"Baidu Netdisk staging cleanup is opt-in and only selects old PGDATA backup tarballs under server-data/unidesk-pg-data.",
"State artifact retention is opt-in for manual plan/run; --include-state-artifacts selects only stale files under .state/e2e, .state/validation, .state/jobs and .state/codex-queue/output-archive plus stale direct directories under .state/deploy/exports and .state/deploy/resolve.",
"VPN diagnostic pcap cleanup is opt-in and only selects stale hy2 ring pcap files; active pcap files and evidence JSONL are protected.",
@@ -634,6 +654,12 @@ function parseGcOptions(args: string[]): GcOptions {
const value = parseNonNegativeNumber(arg, args[++index]);
if (!Number.isInteger(value) || value <= 0) throw new Error("--vscode-keep-extension-versions must be a positive integer");
options.vscodeKeepExtensionVersions = Math.min(value, 20);
} else if (arg === "--include-vscode-cached-vsix") {
options.vscodeCachedVsix = true;
} else if (arg === "--no-vscode-cached-vsix") {
options.vscodeCachedVsix = false;
} else if (arg === "--vscode-cached-vsix-keep-days") {
options.vscodeCachedVsixKeepDays = parsePositiveIntegerOption(arg, args[++index], 365);
} else if (arg === "--target-use-percent") {
options.targetUsePercent = parseUsePercentOption(arg, args[++index]);
} else if (arg === "--include-baidu-staging") {
@@ -794,6 +820,8 @@ function publicOptions(options: GcOptions): Record<string, unknown> {
vscodeKeepServers: options.vscodeKeepServers,
vscodeStaleExtensions: options.vscodeStaleExtensions,
vscodeKeepExtensionVersions: options.vscodeKeepExtensionVersions,
vscodeCachedVsix: options.vscodeCachedVsix,
vscodeCachedVsixKeepDays: options.vscodeCachedVsixKeepDays,
baiduStaging: options.baiduStaging,
baiduStagingKeepDays: options.baiduStagingKeepDays,
stateArtifacts: options.stateArtifacts,
@@ -1108,6 +1136,35 @@ function vscodeExtensionIdFromDirectory(name: string): string | null {
return match?.[1] ?? null;
}
function collectVscodeCachedVsixCandidates(options: GcOptions, observedAt: string): GcCandidate[] {
if (!existsSync(VSCODE_CACHED_VSIX_ROOT)) return [];
const cutoffMs = new Date(observedAt).getTime() - options.vscodeCachedVsixKeepDays * 24 * 60 * 60 * 1000;
const result: GcCandidate[] = [];
for (const entry of readdirSync(VSCODE_CACHED_VSIX_ROOT, { withFileTypes: true })) {
if (!entry.isFile()) continue;
if (vscodeExtensionIdFromDirectory(entry.name) === null) continue;
const path = join(VSCODE_CACHED_VSIX_ROOT, entry.name);
let stat;
try {
stat = lstatSync(path);
} catch {
continue;
}
if (!stat.isFile() || stat.isSymbolicLink() || stat.mtimeMs >= cutoffMs || stat.size <= 0) continue;
result.push({
id: `vscode-cached-vsix:${entry.name}`,
kind: "vscode-cached-vsix-delete",
risk: "medium",
description: `Delete stale VS Code CachedExtensionVSIXs download older than ${options.vscodeCachedVsixKeepDays} days`,
path,
sizeBytes: stat.size,
estimatedReclaimBytes: stat.size,
action: { op: "unlink", allowlist: "vscode-cached-vsix", keepDays: options.vscodeCachedVsixKeepDays, activeCheck: "fuser-before-delete" },
});
}
return result.sort((left, right) => right.estimatedReclaimBytes - left.estimatedReclaimBytes);
}
function collectBaiduStagingCandidates(options: GcOptions, observedAt: string): GcCandidate[] {
const root = rootPath(...BAIDU_STAGING_RELATIVE_ROOT);
const pgdataRoot = join(root, "server-data", "unidesk-pg-data");
@@ -1486,8 +1543,8 @@ function gcPolicyPlan(options: GcPolicyOptions): unknown {
policy: {
safeScope: [
"systemd journal is capped at 512MiB",
"daily timer runs file-log, Docker json logs, 24h BuildKit cache, allowlisted /tmp gc, 24h VPN diagnostic pcap retention and 14-day UniDesk .state artifact retention",
"timer does not touch PostgreSQL PGDATA, Docker images, Docker volumes, tool caches, VS Code servers/extensions or Baidu Netdisk staging",
"daily timer runs file-log, Docker json logs, 24h BuildKit cache, allowlisted /tmp gc, 24h VPN diagnostic pcap retention, 14-day UniDesk .state artifact retention and 7d VS Code CachedExtensionVSIXs retention",
"timer does not touch PostgreSQL PGDATA, Docker images, Docker volumes, tool caches, installed VS Code servers/extensions, VS Code user data or Baidu Netdisk staging",
"timer output is redirected under .state/gc and capped by gc --result-limit",
],
manualDbRetention: "gc db-trace remains explicit maintenance and is not scheduled automatically.",
@@ -1538,7 +1595,7 @@ function gcPolicyInstall(options: GcPolicyOptions): unknown {
function gcPolicyFiles(): Record<string, { path: string; content: string }> {
const gcStateDir = rootPath(".state", "gc");
const bunPath = bunExecutablePath();
const gcScript = `cd ${shellQuote(repoRoot)} && mkdir -p ${shellQuote(gcStateDir)} && ${shellQuote(bunPath)} scripts/cli.ts gc run --confirm --no-db-summary --no-journal --build-cache-until 24h --include-vpn-diagnostic-logs --vpn-diagnostic-log-keep-hours 24 --include-state-artifacts --state-artifact-keep-days 14 --limit 5000 --result-limit 25 > ${shellQuote(join(gcStateDir, "last-run.json"))} 2> ${shellQuote(join(gcStateDir, "last-run.stderr"))}`;
const gcScript = `cd ${shellQuote(repoRoot)} && mkdir -p ${shellQuote(gcStateDir)} && ${shellQuote(bunPath)} scripts/cli.ts gc run --confirm --no-db-summary --no-journal --build-cache-until 24h --include-vpn-diagnostic-logs --vpn-diagnostic-log-keep-hours 24 --include-state-artifacts --state-artifact-keep-days 14 --include-vscode-cached-vsix --vscode-cached-vsix-keep-days 7 --limit 5000 --result-limit 25 > ${shellQuote(join(gcStateDir, "last-run.json"))} 2> ${shellQuote(join(gcStateDir, "last-run.stderr"))}`;
return {
journald: {
path: "/etc/systemd/journald.conf.d/unidesk-gc.conf",
@@ -1678,6 +1735,13 @@ function executeCandidate(candidate: GcCandidate, options: GcOptions): { reclaim
rmSync(candidate.path, { recursive: true, force: true });
return { reclaimedBytes: before };
}
if (candidate.kind === "vscode-cached-vsix-delete" && candidate.path !== undefined) {
assertVscodeCachedVsixCandidatePath(candidate.path);
assertPathNotOpen(candidate.path);
const before = safeFileSize(candidate.path);
unlinkSync(candidate.path);
return { reclaimedBytes: before };
}
if (candidate.kind === "baidu-staging-file-delete" && candidate.path !== undefined) {
assertBaiduStagingCandidatePath(candidate.path);
const before = safeFileSize(candidate.path);
@@ -1791,6 +1855,17 @@ function assertVscodeExtensionCandidatePath(path: string): void {
if (vscodeExtensionIdFromDirectory(basename(resolved)) === null) throw new Error(`refusing to remove unexpected VS Code extension directory: ${path}`);
}
function assertVscodeCachedVsixCandidatePath(path: string): void {
const resolved = resolve(path);
const root = resolve(VSCODE_CACHED_VSIX_ROOT);
if (!resolved.startsWith(`${root}/`)) throw new Error(`refusing to remove VS Code cached VSIX outside cache root: ${path}`);
const relativePath = resolved.slice(root.length + 1);
if (relativePath.includes("/")) throw new Error(`refusing to remove nested VS Code cached VSIX path: ${path}`);
if (vscodeExtensionIdFromDirectory(basename(resolved)) === null) throw new Error(`refusing to remove unexpected VS Code cached VSIX name: ${path}`);
const stat = lstatSync(resolved);
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`refusing to remove non-regular VS Code cached VSIX: ${path}`);
}
function assertBaiduStagingCandidatePath(path: string): void {
const resolved = resolve(path);
const root = resolve(rootPath(...BAIDU_STAGING_RELATIVE_ROOT, "server-data", "unidesk-pg-data"));