OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / scripts / backfill-export-ok.js

121 lines · javascript ref:
#!/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()