#!/usr/bin/env node
/**
* backfill-export-ok — mark every EXISTING repo public.
*
* Step R1 of the read-gate rollout (forge #138). The gate defaults to DENY:
* a repo is readable only if it carries a `pear-git-export-ok` marker. Every
* repo predating the marker has none, so enabling enforcement without this
* backfill would un-serve the entire fleet at once — 142 bares on tabertron
* alone.
*
* ORDER MATTERS. This must run, everywhere, BEFORE PEAR_GIT_READ_GATE=enforce
* is set anywhere. An offline mirror that misses the backfill and later
* receives the enforcing build would un-serve its whole repo set.
*
* The marker is deliberately LOCAL and not replicated — "do I serve this?" is
* a per-peer property, consistent with #135 making byte removal a per-peer
* operator step. So this must be run ON EACH PEER; it cannot be done once
* centrally.
*
* Idempotent: a repo that already has the marker is left alone.
*
* node scripts/backfill-export-ok.js --dry-run # report only
* node scripts/backfill-export-ok.js # write markers
* node scripts/backfill-export-ok.js --repos <dir> # explicit dir
*
* Default repos dir: $PEAR_GIT_REPOS, else ~/git-repos.
*/
import fs from 'fs'
import path from 'path'
import os from 'os'
// Imported, NOT re-declared. If this script and the gate disagreed on the
// filename the backfill would write a file the gate never reads, and the whole
// fleet would go dark the moment enforcement turned on. Sharing the constant
// makes that failure impossible rather than merely unlikely.
import { PUBLIC_MARKER } from '../lib/repo-access.js'
function parseArgs (argv) {
const out = { dryRun: false, reposDir: null, help: false }
for (let i = 0; i < argv.length; i++) {
if (argv[i] === '--dry-run' || argv[i] === '-n') out.dryRun = true
else if (argv[i] === '--repos') out.reposDir = argv[++i]
else if (argv[i] === '--help' || argv[i] === '-h') out.help = true
}
return out
}
function main () {
const args = parseArgs(process.argv.slice(2))
if (args.help) {
console.log('usage: backfill-export-ok.js [--dry-run] [--repos <dir>]')
process.exit(0)
}
const reposDir = args.reposDir ||
process.env.PEAR_GIT_REPOS ||
path.join(os.homedir(), 'git-repos')
if (!fs.existsSync(reposDir)) {
console.error('repos dir not found: ' + reposDir)
process.exit(1)
}
let entries
try {
entries = fs.readdirSync(reposDir)
} catch (err) {
console.error('cannot read ' + reposDir + ': ' + err.message)
process.exit(1)
}
const bares = entries.filter((d) => d.endsWith('.git'))
let marked = 0
let already = 0
let failed = 0
for (const d of bares) {
const repoDir = path.join(reposDir, d)
// Only real directories — a stray file named *.git is not a repo.
try {
if (!fs.statSync(repoDir).isDirectory()) continue
} catch { continue }
const marker = path.join(repoDir, PUBLIC_MARKER)
if (fs.existsSync(marker)) { already++; continue }
if (args.dryRun) {
console.log(' would mark public: ' + d)
marked++
continue
}
try {
fs.writeFileSync(marker, '')
marked++
} catch (err) {
console.error(' FAILED ' + d + ': ' + err.message)
failed++
}
}
console.log('')
console.log('marker: ' + PUBLIC_MARKER)
console.log('repos dir: ' + reposDir)
console.log('bare repos: ' + bares.length)
console.log(args.dryRun ? 'would mark: ' + marked : 'marked public: ' + marked)
console.log('already marked: ' + already)
if (failed) console.log('FAILED: ' + failed)
console.log('')
if (args.dryRun) {
console.log('dry run — nothing written. Re-run without --dry-run to apply.')
} else {
console.log('Run this on EVERY peer before enabling PEAR_GIT_READ_GATE=enforce.')
}
process.exit(failed ? 1 : 0)
}
main()