Add the GitHub and Gitea steps and split the script into library files
Dry run by default: the script reads from both hosts (tokens, owners, names, license, SSH) and prints a plan; --apply creates the repositories and the Gitea -> GitHub push mirror after a final yes. Choosing GitHub applies the AGPL-3.0 license to the Gitea repository. A failed step is reported with what exists and how to continue; nothing is ever deleted. create-project.sh is now the entry point; the work lives in src/lib/, one responsibility per file. .gitignore gets !src/lib (the Python template ignores any lib/ folder). Tests grow to 599 checks, with a stub curl and ssh, and guards for the file structure. Task: MIL-002#1 Task: MIL-002#2 Task: MIL-002#3 Task: MIL-002#4 Task: MIL-002#5 Task: MIL-002#6 Refs #9 Refs #10 Refs #11 Refs #12 Refs #13 Refs #14 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
# shellcheck shell=bash
|
||||
# shellcheck disable=SC2004,SC2034,SC2154 # shared state and arrays are declared in constants.sh
|
||||
# preflight.sh - Read-only checks of both hosts before anything is created.
|
||||
#
|
||||
# Part of create-project.sh: sourced by it, never run on its own.
|
||||
#
|
||||
# Provides: check_gitea_organization, check_gitea_license, inspect_repository, preflight_gitea, check_github_organization, preflight_github, test_gitea_ssh, decide_existing_repositories, run_preflight
|
||||
|
||||
check_gitea_organization() {
|
||||
local org="$1" login="$2"
|
||||
api_call gitea GET "/orgs/$org"
|
||||
if [[ $HTTP_STATUS == 404 ]]; then
|
||||
die "Gitea owner '$org' is neither your account ($login) nor an organization the token can see"
|
||||
fi
|
||||
expect_status "cannot look up the Gitea organization '$org'" 200
|
||||
api_call gitea GET "/users/$login/orgs/$org/permissions"
|
||||
expect_status "cannot read your permissions in the Gitea organization '$org'" 200
|
||||
if [[ $(json_get "$HTTP_BODY_FILE" can_create_repository) != true ]]; then
|
||||
die "you may not create repositories in the Gitea organization '$org'"
|
||||
fi
|
||||
}
|
||||
|
||||
check_gitea_license() {
|
||||
api_call gitea GET /licenses
|
||||
expect_status "cannot list the licenses of the Gitea server" 200
|
||||
json_has_value "$HTTP_BODY_FILE" key "$AGPL_LICENSE_KEY" ||
|
||||
die "the Gitea server does not offer the $AGPL_LICENSE_KEY license"
|
||||
}
|
||||
|
||||
# inspect_repository HOST: record in STATE[HOST_repo] whether the repository
|
||||
# is free (does not exist), empty, license_only or not_empty.
|
||||
inspect_repository() {
|
||||
local host="$1" owner name names
|
||||
owner="$(repo_owner "$host")"
|
||||
name="${PROJECT[name]}"
|
||||
api_call "$host" GET "/repos/$owner/$name"
|
||||
if [[ $HTTP_STATUS == 404 ]]; then
|
||||
STATE[${host}_repo]="free"
|
||||
return 0
|
||||
fi
|
||||
expect_status "cannot look up the $(host_label "$host") repository $owner/$name" 200
|
||||
api_call "$host" GET "/repos/$owner/$name/contents"
|
||||
if [[ $HTTP_STATUS == 404 ]]; then
|
||||
STATE[${host}_repo]="empty"
|
||||
return 0
|
||||
fi
|
||||
expect_status "cannot read the contents of the $(host_label "$host") repository $owner/$name" 200
|
||||
names="$(json_values "$HTTP_BODY_FILE" name)"
|
||||
case "$names" in
|
||||
"") STATE[${host}_repo]="empty" ;;
|
||||
LICENSE) STATE[${host}_repo]="license_only" ;;
|
||||
*) STATE[${host}_repo]="not_empty" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
preflight_gitea() {
|
||||
local owner="${PROJECT[gitea_owner]}" login
|
||||
api_call gitea GET /user
|
||||
expect_status "Gitea rejected the token" 200
|
||||
login="$(json_get "$HTTP_BODY_FILE" login)"
|
||||
[[ -n $login ]] || die "Gitea did not say which account the token belongs to"
|
||||
STATE[gitea_login]="$login"
|
||||
if is_same_name "$owner" "$login"; then
|
||||
STATE[gitea_owner_kind]="user"
|
||||
else
|
||||
check_gitea_organization "$owner" "$login"
|
||||
STATE[gitea_owner_kind]="organization"
|
||||
fi
|
||||
if ((PROJECT[has_github])); then
|
||||
check_gitea_license
|
||||
fi
|
||||
inspect_repository gitea
|
||||
}
|
||||
|
||||
check_github_organization() {
|
||||
local org="$1" login="$2"
|
||||
api_call github GET "/user/memberships/orgs/$org"
|
||||
if [[ $HTTP_STATUS == 404 ]]; then
|
||||
die "GitHub owner '$org' is neither your account ($login) nor an organization you belong to (or the token lacks the read:org scope)"
|
||||
fi
|
||||
expect_status "cannot read your membership of the GitHub organization '$org'" 200
|
||||
if [[ $(json_get "$HTTP_BODY_FILE" state) != active ]]; then
|
||||
die "your membership of the GitHub organization '$org' is not active"
|
||||
fi
|
||||
}
|
||||
|
||||
preflight_github() {
|
||||
local owner="${PROJECT[github_owner]}" configured login
|
||||
configured="${CREDENTIALS[GITHUB_USER]:-}"
|
||||
api_call github GET /user
|
||||
expect_status "GitHub rejected the token" 200
|
||||
login="$(json_get "$HTTP_BODY_FILE" login)"
|
||||
[[ -n $login ]] || die "GitHub did not say which account the token belongs to"
|
||||
STATE[github_login]="$login"
|
||||
if [[ -n $configured ]] && ! is_same_name "$configured" "$login"; then
|
||||
warn "GITHUB_USER is '$configured' but the token belongs to '$login'; the mirror will use '$login'"
|
||||
fi
|
||||
if is_same_name "$owner" "$login"; then
|
||||
STATE[github_owner_kind]="user"
|
||||
else
|
||||
check_github_organization "$owner" "$login"
|
||||
STATE[github_owner_kind]="organization"
|
||||
fi
|
||||
inspect_repository github
|
||||
}
|
||||
|
||||
# The SSH result decides later whether origin uses SSH or HTTPS. Without ssh
|
||||
# or without access it is simply "not passed"; it never stops the run.
|
||||
test_gitea_ssh() {
|
||||
local host port output status=0
|
||||
host="${CONFIG[GITEA_URL]#https://}"
|
||||
host="${host%%/*}"
|
||||
host="${host%%:*}"
|
||||
port="${CONFIG[GITEA_SSH_PORT]}"
|
||||
STATE[is_ssh_ok]=0
|
||||
STATE[ssh_note]="failed (check your SSH key and that $host:$port is reachable)"
|
||||
if ! command -v ssh >/dev/null 2>&1; then
|
||||
STATE[ssh_note]="not tested (ssh is not installed)"
|
||||
return 0
|
||||
fi
|
||||
output="$(ssh -p "$port" -o BatchMode=yes -o ConnectTimeout=5 \
|
||||
-o StrictHostKeyChecking=yes -T "git@$host" 2>&1)" || status=$?
|
||||
if ((status == 0)) || [[ $output == *"successfully authenticated"* ]]; then
|
||||
STATE[is_ssh_ok]=1
|
||||
STATE[ssh_note]="passed"
|
||||
fi
|
||||
}
|
||||
|
||||
# decide_existing_repositories: a repository that already exists may only be
|
||||
# reused when it is empty (Gitea: or holds just the license this script adds).
|
||||
decide_existing_repositories() {
|
||||
local host owner state
|
||||
for host in gitea github; do
|
||||
STATE[reuse_$host]=0
|
||||
if [[ $host == github ]] && ! ((PROJECT[has_github])); then
|
||||
continue
|
||||
fi
|
||||
owner="$(repo_owner "$host")"
|
||||
state="${STATE[${host}_repo]}"
|
||||
case "$state" in
|
||||
free) ;;
|
||||
empty) STATE[reuse_$host]=1 ;;
|
||||
license_only)
|
||||
if [[ $host == gitea ]] && ((PROJECT[has_github])); then
|
||||
STATE[reuse_$host]=1
|
||||
else
|
||||
die "the $(host_label "$host") repository $owner/${PROJECT[name]} already exists and has content; choose another name or remove it first"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
die "the $(host_label "$host") repository $owner/${PROJECT[name]} already exists and has content; choose another name or remove it first"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
run_preflight() {
|
||||
say ""
|
||||
say "Checking the hosts (read-only requests)..."
|
||||
preflight_gitea
|
||||
if ((PROJECT[has_github])); then
|
||||
preflight_github
|
||||
fi
|
||||
test_gitea_ssh
|
||||
decide_existing_repositories
|
||||
say "All checks passed."
|
||||
}
|
||||
Reference in New Issue
Block a user