mirror of
https://github.com/piabrota/mu.git
synced 2026-11-07 06:15:59 -10:00
* The repo scripts make repos on an operator's own account ("owner": "user")
Any operator can now make private or public repos on their own GitHub account with the same setup as
ouroboric-dev's. A config with "owner": "user" gets it, with the differences GitHub forces:
- create-repo.sh creates through /user/repos, after checking that the token belongs to the config's
user, so a repo never lands on the wrong account.
- setup-repo.sh writes each user repo's own Actions allowlist (lint.yml's pinned actions, nothing
else), since a user has no org to hold it. On a user's private repo it skips secret scanning, push
protection and private vulnerability reporting, which GitHub doesn't offer there, and says so.
- If GitHub refuses the ruleset (a user's private repo without GitHub Pro), setup-repo.sh fails and
says main is not protected, after every other setting is in place; new-repo.sh then reports the repo
as unprotected. The ruleset itself is unchanged.
An operator's config is theirs, not the company's: it lives in scripts/orgs/local/, which git ignores
except for its README, and --org reads it from there (a name in both folders is refused). Only
generic examples are committed, in scripts/orgs/examples/: a user config and a proprietary licence
text that names no one. Invariant 9 in AGENTS.md says never to commit an operator's config, and a test
fails if any committed config is neither an org's nor an example. The fakes learn GET /user,
POST /user/repos and refused writes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* check-repos.sh --check operator: no tracked file names the operator (invariant 9)
The check reads each repo's local clone and fails when a tracked file names the operator who runs it: their
login, their home folder's name, or a user account in scripts/orgs/local/, as whole words in any case. The
names come from the machine, so the check serves any operator and the script names no one;
CHECK_REPOS_OPERATOR replaces them. It prints the files, never the names, and fails closed on a missing
clone, an empty name list or a git error. The records of past work keep their paths as written: RECORDS
lists those files one by one, so a new plan beside them is still checked. It is a default check.
The local-qwen design, plan and how-to now give paths relative to the clones, and the design no longer
names the founder's folders. Invariant 9 also covers docs, plans and fixtures.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Bruno Guilhermo de Barros Valério <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
83 lines
4.1 KiB
Bash
Executable File
83 lines
4.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# shellcheck source-path=SCRIPTDIR
|
|
# Create new, empty repos in the org that a config names, or in a user's own account when the config
|
|
# has "owner": "user". Each repo has no commits and no settings beyond the visibility, so nothing in it
|
|
# is protected yet. new-repo.sh is the normal way to run this script: it adds the first commit and
|
|
# protects the repo. If any repo already exists, a name repeats or
|
|
# a description is bad, nothing is created. Stops at the first error. A description is one line, with
|
|
# no control characters and no leading or trailing spaces: new-repo.sh makes it the first paragraph of
|
|
# README.md.
|
|
# Founder-run: GH_TOKEN must hold the short-lived admin token, with Administration read and write
|
|
# (the pr-bot can't create repos). For a user config, the token is the user's own, and it must
|
|
# belong to the config's account, or nothing is created.
|
|
# GH_TOKEN=... create-repo.sh (--org <name>|--config <path>) [--private] [--check] <repo> "<description>" [<repo> "<description>"]...
|
|
# --check runs every check, creates nothing and prints nothing. --private makes private repos, such as
|
|
# huldra, the hidden benchmark suite.
|
|
set -euo pipefail
|
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
API="${CREATE_REPO_API:-https://api.github.com}"
|
|
die() { echo "create-repo: $*" >&2; exit 2; }
|
|
# shellcheck source=repo-config.sh
|
|
. "$HERE/repo-config.sh"
|
|
|
|
repo_config_args "$@"
|
|
repo_config_load
|
|
[ -n "${GH_TOKEN:-}" ] || die "GH_TOKEN must hold the short-lived admin token"
|
|
visibility=public check=''
|
|
set -- "${RC_REST[@]}"
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--private) visibility=private ;;
|
|
--check) check=1 ;;
|
|
-*) die "unknown option: $1" ;;
|
|
*) break ;;
|
|
esac
|
|
shift
|
|
done
|
|
[ $# -ge 2 ] && [ $(($# % 2)) = 0 ] || die 'usage: create-repo.sh (--org <name>|--config <path>) [--private] [--check] <repo> "<description>" [<repo> "<description>"]...'
|
|
args=("$@")
|
|
T="$(mktemp -d)"
|
|
trap 'rm -rf -- "${T:?}"' EXIT
|
|
|
|
request() { # request METHOD PATH [JSON]: prints the HTTP status; the reply goes to $T/reply
|
|
curl -sS -o "$T/reply" -w '%{http_code}' -X "$1" -H @<(printf 'Authorization: Bearer %s\n' "$GH_TOKEN") \
|
|
-H 'Accept: application/vnd.github+json' -H 'X-GitHub-Api-Version: 2022-11-28' \
|
|
${3:+--data "$3"} "$API$2" || die "$1 $2: request failed"
|
|
}
|
|
message() { jq -r '.message? // empty' "$T/reply" 2>/dev/null; }
|
|
|
|
for ((i = 0; i < ${#args[@]}; i += 2)); do
|
|
r="${args[i]}"
|
|
[[ "$r" =~ ^[A-Za-z0-9_][A-Za-z0-9._-]*$ ]] || die "bad repo name: $r"
|
|
[ -n "${args[i + 1]}" ] || die "$r: the description is empty"
|
|
# The description becomes the first paragraph of README.md: one line, no control characters, no
|
|
# leading or trailing spaces.
|
|
ok="$(jq -rn --arg d "${args[i + 1]}" '$d | (test("\\p{Cc}") or test("\\A\\s|\\s\\z")) | not')"
|
|
[ "$ok" = true ] || die "$r: the description must be one line, with no control characters and no leading or trailing spaces"
|
|
for ((j = 0; j < i; j += 2)); do [ "${args[j]}" != "$r" ] || die "$r is named twice"; done
|
|
done
|
|
# A user's repos are created on the token's own account, so the token must be the config's account.
|
|
create="/orgs/$RC_ORG/repos"
|
|
if [ "$RC_OWNER" = user ]; then
|
|
code="$(request GET /user)"
|
|
[ "$code" = 200 ] || die "can't tell whose token this is (HTTP $code); nothing was created"
|
|
login="$(jq -r '.login // empty' "$T/reply")"
|
|
[ "${login,,}" = "${RC_ORG,,}" ] || die "the token belongs to ${login:-no user}, not $RC_ORG; nothing was created"
|
|
create=/user/repos
|
|
fi
|
|
for ((i = 0; i < ${#args[@]}; i += 2)); do
|
|
r="${args[i]}"
|
|
code="$(request GET "/repos/$RC_ORG/$r")"
|
|
[ "$code" = 404 ] || die "$r: already exists or can't be checked (HTTP $code); nothing was created"
|
|
done
|
|
[ -z "$check" ] || exit 0
|
|
|
|
for ((i = 0; i < ${#args[@]}; i += 2)); do
|
|
r="${args[i]}"
|
|
body="$(jq -cn --arg n "$r" --arg d "${args[i + 1]}" --arg v "$visibility" \
|
|
'{name: $n, description: $d, visibility: $v, auto_init: false, has_issues: false, has_wiki: false, has_projects: false}')"
|
|
code="$(request POST "$create" "$body")"
|
|
[ "$code" = 201 ] || die "$r: GitHub refused to create it (HTTP $code: $(message))"
|
|
echo "created $r"
|
|
done
|