lots of change - all to start my 3rd redo

This commit is contained in:
2026-09-26 14:31:52 -04:00
parent 7f4d388e19
commit bceb52e8ee
1046 changed files with 4476 additions and 0 deletions

View File

@@ -0,0 +1,126 @@
/**
* tree-permissions.ts — make a copied tree readable by whoever owns the workspace.
*
* Files captured from a task run can arrive owned by another user, or with a
* directory missing the permission needed to walk into it. Packaging then fails
* with `Cannot stat: Permission denied`. This repairs both.
*
* Grants owner rwX only, never group or other. Never throws, and never hands
* files to root. Set `RACCOON_SKIP_PERMISSION_REPAIR=1` to turn it off.
*/
import { chmodSync, chownSync, lstatSync, readdirSync, statSync } from 'fs';
import { join } from 'path';
export interface NormalizeReport {
/** Paths whose owner was changed. */
ownerFixed: string[];
/** Paths whose mode gained owner rwX. */
modeFixed: string[];
/** Paths we wanted to change but could not, with the errno. */
failures: { path: string; reason: string }[];
/** Resolved target owner, or null if it couldn't be determined. */
target: { uid: number; gid: number } | null;
/** Set when disabled via RACCOON_SKIP_PERMISSION_REPAIR. */
skipped?: boolean;
}
/** Owner a workspace tree should have: whoever owns `ownerRef`. */
export function resolveWorkspaceOwner(ownerRef: string): { uid: number; gid: number } | null {
try {
const st = statSync(ownerRef);
return { uid: st.uid, gid: st.gid };
} catch {
return null;
}
}
/**
* Owner-rwX mode, preserving every other bit. Dirs also need the search bit.
*
* `stranded` means the file stays root-owned because we have no non-root owner to
* give it to. Owner bits then help nobody — whoever has to read it is a different
* user — so read and search are granted more widely. Never write, never +x on files.
*/
function withOwnerAccess(mode: number, isDir: boolean, stranded: boolean): number {
const owner = isDir ? 0o700 : 0o600;
return mode | owner | (stranded ? (isDir ? 0o055 : 0o044) : 0);
}
/**
* Give every entry under `root` to the workspace owner and make sure that owner
* can read and traverse it. Symlinks are skipped. Repairs what it can and
* reports what it couldn't; it never throws and never blocks its caller.
*/
export function normalizeTreePermissions(
root: string,
options: { ownerRef?: string } = {}
): NormalizeReport {
if (process.env.RACCOON_SKIP_PERMISSION_REPAIR === '1') {
return { ownerFixed: [], modeFixed: [], failures: [], target: null, skipped: true };
}
const target = resolveWorkspaceOwner(options.ownerRef ?? process.cwd());
const report: NormalizeReport = { ownerFixed: [], modeFixed: [], failures: [], target };
// Never hand files to root — that would lock the owner out rather than help.
const chownTarget = target && target.uid !== 0 ? target : null;
try {
const stack: string[] = [root];
while (stack.length > 0) {
const path = stack.pop() as string;
let st;
try {
st = lstatSync(path);
} catch (err) {
report.failures.push({ path, reason: (err as NodeJS.ErrnoException).code ?? 'ELSTAT' });
continue;
}
if (st.isSymbolicLink()) continue;
const isDir = st.isDirectory();
// Mode first: a directory we can't search is one we can't descend into.
const wanted = withOwnerAccess(st.mode, isDir, chownTarget === null && st.uid === 0);
if (wanted !== st.mode) {
try {
chmodSync(path, wanted);
report.modeFixed.push(path);
} catch (err) {
report.failures.push({ path, reason: (err as NodeJS.ErrnoException).code ?? 'ECHMOD' });
}
}
if (chownTarget && (st.uid !== chownTarget.uid || st.gid !== chownTarget.gid)) {
try {
chownSync(path, chownTarget.uid, chownTarget.gid);
report.ownerFixed.push(path);
} catch (err) {
report.failures.push({ path, reason: (err as NodeJS.ErrnoException).code ?? 'ECHOWN' });
}
}
if (!isDir) continue;
try {
for (const entry of readdirSync(path)) stack.push(join(path, entry));
} catch (err) {
report.failures.push({ path, reason: (err as NodeJS.ErrnoException).code ?? 'EREADDIR' });
}
}
} catch (err) {
report.failures.push({ path: root, reason: (err as NodeJS.ErrnoException).code ?? 'EWALK' });
}
return report;
}
/** True when something was actually repaired. */
export function didRepair(report: NormalizeReport): boolean {
return report.ownerFixed.length > 0 || report.modeFixed.length > 0;
}
/** The command to run on your host if we couldn't fix it ourselves. */
export function manualRepairHint(path: string): string {
return `sudo chown -R "$(id -un):$(id -gn)" ${path} && chmod -R u+rwX ${path}`;
}