|
| 1 | += scripts/ |
| 2 | +:toc: preamble |
| 3 | + |
| 4 | +Estate-wide bash automation. Every script is built on |
| 5 | +`lib/common.sh`, which provides safe-by-default strict mode, structured |
| 6 | +logging, dry-run support, bounded parallelism, single-instance locking, |
| 7 | +signal-safe cleanup, GH CLI rate-limit + retry, snapshot-based rollback, |
| 8 | +and repo-iteration helpers. |
| 9 | + |
| 10 | +== Layout |
| 11 | + |
| 12 | +The directory tree is *grouped by purpose* via symlinks. The actual |
| 13 | +scripts continue to live at `scripts/` root (the Elixir TUI hardcodes |
| 14 | +those paths). |
| 15 | + |
| 16 | +[cols="1,3", options="header"] |
| 17 | +|=== |
| 18 | +| Group | Purpose |
| 19 | + |
| 20 | +| `lib/` | Shared library (`common.sh`); source it from every script. |
| 21 | +| `audit/` | Read-only inspection: contractiles, wiki, About metadata, sync, secrets+deps. |
| 22 | +| `fix/` | Mutating remediations: unwrap, innerHTML, branch protection, composite `all.sh`. |
| 23 | +| `sync/` | Estate-wide sync: update-repos, README standardisation, mirror-policy verification. |
| 24 | +| `health/` | Diagnostics: `gh-doctor`, `estate-report` aggregator. |
| 25 | +|=== |
| 26 | + |
| 27 | +== Common flags |
| 28 | + |
| 29 | +Every script that uses `lib/common.sh` understands: |
| 30 | + |
| 31 | +[cols="1,3", options="header"] |
| 32 | +|=== |
| 33 | +| Flag | Effect |
| 34 | + |
| 35 | +| `-n` / `--dry-run` | Read-only; destructive ops are logged, not executed. |
| 36 | +| `-y` / `--yes` | Skip confirmation prompts (CI mode). |
| 37 | +| `-j` / `--jobs N` | Bounded parallelism (default 4). |
| 38 | +| `-v` / `--verbose` | Debug-level logging. |
| 39 | +| `-q` / `--quiet` | Warnings/errors only. |
| 40 | +| `-h` / `--help` | Per-script help text. |
| 41 | +|=== |
| 42 | + |
| 43 | +== Common environment |
| 44 | + |
| 45 | +[cols="1,3", options="header"] |
| 46 | +|=== |
| 47 | +| Var | Default |
| 48 | + |
| 49 | +| `GS_REPOS_DIR` | `/var/mnt/eclipse/repos` |
| 50 | +| `GS_LOG_LEVEL` | `info` |
| 51 | +| `GS_DRY_RUN` | `0` |
| 52 | +| `GS_PARALLEL` | `4` |
| 53 | +| `GS_BACKUP_DIR` | `~/.cache/git-scripts/backups` |
| 54 | +| `GS_REPORT_DIR` | `~/.cache/git-scripts/reports` |
| 55 | +| `GS_LOCK_DIR` | `${TMPDIR}/git-scripts.locks` |
| 56 | +| `GS_REPO_LIST` | (optional) one-repo-per-line filter list |
| 57 | +| `NO_COLOR` | non-empty disables ANSI colours |
| 58 | +|=== |
| 59 | + |
| 60 | +== audit/ |
| 61 | + |
| 62 | +[cols="1,3", options="header"] |
| 63 | +|=== |
| 64 | +| Script | Reports |
| 65 | + |
| 66 | +| `audit/contractiles.sh` | 6 canonical verbs (`intend trust must bust adjust dust`), K9 SVC location, accessibility hooks/docs. Drift vs missing vs ok. |
| 67 | +| `audit/wiki.sh` | Wiki enabled/disabled + page count + page list. |
| 68 | +| `audit/project-tabs.sh` | GitHub repo About: description, homepage, mandatory topics. |
| 69 | +| `audit/sync-status.sh` | Local HEAD vs origin/<branch> table. Read-only (no fetch). |
| 70 | +| `audit/secrets-and-deps.sh`| gitleaks scan + open-Dependabot-alert count. |
| 71 | +|=== |
| 72 | + |
| 73 | +== fix/ |
| 74 | + |
| 75 | +[cols="1,3", options="header"] |
| 76 | +|=== |
| 77 | +| Script | Effect |
| 78 | + |
| 79 | +| `fix/unwrap.sh` | Reports bare `.unwrap()` in non-test Rust. Rewrite is opt-in via `--apply-expect` (anti-pattern; see memory). |
| 80 | +| `fix/innerhtml.sh` | Annotates / rewrites XSS-prone DOM writes. Snapshot+rollback per file. |
| 81 | +| `fix/branch-protection.sh` | Applies the canonical 'Base' ruleset to every non-archived repo. Updates pre-existing rulesets in place. |
| 82 | +| `fix/all.sh` | Runs every fixer in sequence against one repo. Replaces legacy `fix-security-issues.sh`. |
| 83 | +|=== |
| 84 | + |
| 85 | +== sync/ |
| 86 | + |
| 87 | +[cols="1,3", options="header"] |
| 88 | +|=== |
| 89 | +| Script | Effect |
| 90 | + |
| 91 | +| `sync/update-repos.sh` | Fetch + safe-rebase + force-with-lease push across the configured set. ff-only safety; per-repo failure capture. |
| 92 | +| `sync/standardize-readmes.sh`| README.md -> README.adoc via pandoc; snapshots saved. |
| 93 | +| `sync/mirror-check.sh` | Verifies the GitHub-only-push policy. Flags non-github origins, multi-forge drift, suspicious `pushurl` redirects. Honours estate-wide exceptions (`007`, `bitfuckit`). |
| 94 | +|=== |
| 95 | + |
| 96 | +== health/ |
| 97 | + |
| 98 | +[cols="1,3", options="header"] |
| 99 | +|=== |
| 100 | +| Script | Effect |
| 101 | + |
| 102 | +| `health/gh-doctor.sh` | Validates gh installed, authenticated, scoped (`repo`, `workflow`, `read:org`), rate-limit headroom, jq present, git credential helper sane. Replaces legacy `USE-GH-CLI.sh`. |
| 103 | +| `health/estate-report.sh`| Runs every read-only audit and aggregates into a single Markdown report. |
| 104 | +|=== |
| 105 | + |
| 106 | +== Removed / deprecated |
| 107 | + |
| 108 | +* `ci-integration-example.sh` -> moved to `docs/ci-integration-example.adoc` (it was prose, not a script). |
| 109 | +* `md_to_adoc_converter.sh` -> deprecation stub forwarding to `standardize_readmes.sh` (original sed regexes were broken). |
| 110 | +* `fix-security-issues.sh` -> deprecation stub forwarding to `fix/all.sh`. |
| 111 | +* `USE-GH-CLI.sh` -> deprecation stub forwarding to `health/gh-doctor.sh`. |
| 112 | + |
| 113 | +The deprecation stubs still resolve, so the Elixir TUI's hardcoded names |
| 114 | +keep working — they just print a notice and exec the replacement. |
| 115 | + |
| 116 | +== Writing a new script |
| 117 | + |
| 118 | +[source,bash] |
| 119 | +---- |
| 120 | +#!/usr/bin/env bash |
| 121 | +# SPDX-License-Identifier: PMPL-1.0-or-later |
| 122 | +set -uo pipefail |
| 123 | +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" |
| 124 | +. "${SCRIPT_DIR}/lib/common.sh" # or ../lib/common.sh from a subgroup |
| 125 | +
|
| 126 | +GS_SCRIPT_NAME="my-thing" |
| 127 | +GS_HELP_TEXT="Usage: my-thing.sh [--dry-run] [--help]" |
| 128 | +gs::strict |
| 129 | +gs::install_trap |
| 130 | +gs::install_trap_summary |
| 131 | +gs::lock my-thing # single-instance protection |
| 132 | +gs::need gh jq # fail fast on missing tools |
| 133 | +gs::gh_check # auth + rate-limit headroom |
| 134 | +
|
| 135 | +while (( $# > 0 )); do |
| 136 | + case "$1" in |
| 137 | + -n|--dry-run) GS_DRY_RUN=1 ;; |
| 138 | + -h|--help) printf '%s\n' "${GS_HELP_TEXT}"; exit 0 ;; |
| 139 | + *) gs::die "unknown flag: $1" ;; |
| 140 | + esac |
| 141 | + shift |
| 142 | +done |
| 143 | +
|
| 144 | +while IFS= read -r repo; do |
| 145 | + gs::info "scanning $(basename "${repo}")..." |
| 146 | + gs::do touch "${repo}/.scanned" # honours --dry-run |
| 147 | +done < <(gs::repos) |
| 148 | +---- |
0 commit comments