Initial Commit
This commit is contained in:
276
scripts/setup.sh
Executable file
276
scripts/setup.sh
Executable file
@@ -0,0 +1,276 @@
|
||||
#!/usr/bin/env bash
|
||||
# shellcheck shell=bash
|
||||
#
|
||||
# Interactive onboarding driver. Sequences doctor, init-env,
|
||||
# check-gitea, login, check-access, and clone-orchestrator, prompting
|
||||
# only when a decision requires a human.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||
# shellcheck disable=SC1091
|
||||
. "$here/common.sh"
|
||||
|
||||
root="$(repo_root)"
|
||||
cd "$root"
|
||||
|
||||
headless=0
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Usage: just setup [--headless|--no-browser]
|
||||
|
||||
Options:
|
||||
--headless Do not open the browser during login. Prints the
|
||||
--no-browser authorisation URL to stderr instead; paste it into
|
||||
any browser that can reach the loopback callback
|
||||
port (typically via SSH port-forward, see README).
|
||||
-h, --help Show this message.
|
||||
|
||||
Environment:
|
||||
FORGE_SETUP_YES=1 Accept every default; do not prompt. Safe only when
|
||||
FORGE_GITEA_USERNAME is already set in .env.
|
||||
USAGE
|
||||
}
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--headless|--no-browser) headless=1; shift;;
|
||||
-h|--help) usage; exit 0;;
|
||||
--) shift; break;;
|
||||
-*) die "unknown option: $1 (try 'just setup --help')";;
|
||||
*) die "unexpected positional argument: $1 (try 'just setup --help')";;
|
||||
esac
|
||||
done
|
||||
|
||||
# info/ok/warn/err/step/note/die: see scripts/common.sh.
|
||||
|
||||
# Stdout = chosen reply; stderr = prompt/echo.
|
||||
prompt_choice() {
|
||||
local msg="$1" default="$2" reply
|
||||
if [ "${FORGE_SETUP_YES:-0}" = "1" ] || [ ! -t 0 ]; then
|
||||
printf '%s %s [%s] (auto: %s)\n' \
|
||||
"$(_fc_tag "$_FC_MAGENTA" '?')" "$msg" "$default" "$default" >&2
|
||||
reply="$default"
|
||||
else
|
||||
printf '%s %s [%s] ' \
|
||||
"$(_fc_tag "$_FC_MAGENTA" '?')" "$msg" "$default" >/dev/tty
|
||||
IFS= read -r reply </dev/tty || reply=""
|
||||
[ -z "$reply" ] && reply="$default"
|
||||
fi
|
||||
printf '%s' "$reply"
|
||||
}
|
||||
|
||||
prompt_line() {
|
||||
local msg="$1" reply
|
||||
if [ "${FORGE_SETUP_YES:-0}" = "1" ] || [ ! -t 0 ]; then
|
||||
die "non-interactive mode requested ($msg) but no default is safe; run 'just setup' in a terminal"
|
||||
fi
|
||||
printf '%s %s: ' "$(_fc_tag "$_FC_MAGENTA" '?')" "$msg" >/dev/tty
|
||||
IFS= read -r reply </dev/tty || reply=""
|
||||
printf '%s' "$reply"
|
||||
}
|
||||
|
||||
step "1/7 checking prerequisites (just doctor)"
|
||||
bash "$here/doctor.sh"
|
||||
|
||||
step "2/7 preparing .env"
|
||||
if [ ! -f "$root/.env" ]; then
|
||||
cp "$root/.env.example" "$root/.env"
|
||||
ok "wrote $root/.env from template"
|
||||
else
|
||||
ok ".env already present"
|
||||
fi
|
||||
|
||||
load_env
|
||||
|
||||
if [ -z "${FORGE_GITEA_USERNAME:-}" ]; then
|
||||
note "FORGE_GITEA_USERNAME is blank. Enter your Gitea username exactly as it appears in your profile URL."
|
||||
new_user="$(prompt_line "Gitea username")"
|
||||
if [ -z "$new_user" ]; then
|
||||
die "no username entered: aborting. Edit .env by hand and re-run 'just setup'."
|
||||
fi
|
||||
python3 - "$root/.env" "$new_user" <<'PY'
|
||||
import pathlib, sys
|
||||
path = pathlib.Path(sys.argv[1])
|
||||
value = sys.argv[2]
|
||||
lines = path.read_text(encoding="utf-8").splitlines()
|
||||
touched = False
|
||||
for i, line in enumerate(lines):
|
||||
stripped = line.lstrip()
|
||||
if stripped.startswith("FORGE_GITEA_USERNAME=") or stripped.startswith("export FORGE_GITEA_USERNAME="):
|
||||
prefix = "export " if stripped.startswith("export ") else ""
|
||||
lines[i] = f'{prefix}FORGE_GITEA_USERNAME="{value}"'
|
||||
touched = True
|
||||
break
|
||||
if not touched:
|
||||
lines.append(f'FORGE_GITEA_USERNAME="{value}"')
|
||||
tmp = path.with_suffix(path.suffix + ".tmp")
|
||||
tmp.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
tmp.replace(path)
|
||||
PY
|
||||
export FORGE_GITEA_USERNAME="$new_user"
|
||||
ok "set FORGE_GITEA_USERNAME=$new_user in .env"
|
||||
else
|
||||
ok "FORGE_GITEA_USERNAME=$FORGE_GITEA_USERNAME"
|
||||
fi
|
||||
|
||||
step "3/7 checking Gitea reachability (just check-gitea)"
|
||||
curl -fsS --max-time 10 ${FORGE_INSECURE_TLS:+-k} \
|
||||
"${FORGE_GITEA_URL}/api/v1/version" \
|
||||
| python3 -c 'import json,sys; d=json.load(sys.stdin); print("[check-gitea] Gitea version:", d.get("version","?"))'
|
||||
ok "reached $FORGE_GITEA_URL"
|
||||
|
||||
step "4/7 authenticating with Gitea (just login)"
|
||||
existing_user=""
|
||||
have_live=0
|
||||
have_stored=0
|
||||
if [ -f "$FORGE_AUTH_FILE" ]; then
|
||||
have_stored=1
|
||||
existing_user="$(python3 -c '
|
||||
import json, sys
|
||||
try:
|
||||
d = json.load(open(sys.argv[1]))
|
||||
print(d.get("username") or "")
|
||||
except Exception:
|
||||
print("")
|
||||
' "$FORGE_AUTH_FILE" 2>/dev/null || true)"
|
||||
if python3 "$here/forge_auth.py" status >/dev/null 2>&1; then
|
||||
have_live=1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$have_stored" = "1" ] && [ "$have_live" = "0" ] \
|
||||
&& [ -n "$existing_user" ] && [ -n "${FORGE_GITEA_USERNAME:-}" ] \
|
||||
&& [ "${existing_user,,}" = "${FORGE_GITEA_USERNAME,,}" ]; then
|
||||
note "stored session for '$existing_user' is not live; attempting silent refresh..."
|
||||
if python3 "$here/forge_auth.py" refresh --force >/dev/null 2>&1; then
|
||||
ok "refreshed stored session without a browser"
|
||||
have_live=1
|
||||
else
|
||||
note "refresh failed (refresh token likely expired); a fresh login is required"
|
||||
fi
|
||||
fi
|
||||
|
||||
run_login=1
|
||||
if [ "$have_live" = "1" ] && [ -n "$existing_user" ]; then
|
||||
if [ -n "${FORGE_GITEA_USERNAME:-}" ] \
|
||||
&& [ "${existing_user,,}" != "${FORGE_GITEA_USERNAME,,}" ]; then
|
||||
note "stored token is for '$existing_user' but .env configures '$FORGE_GITEA_USERNAME'"
|
||||
reply="$(prompt_choice "Log out and sign in as '$FORGE_GITEA_USERNAME'? [Y/n]" "Y")"
|
||||
case "$reply" in
|
||||
[Nn]*) run_login=0; note "keeping the existing '$existing_user' session (forge_login may still reject it)";;
|
||||
*) python3 "$here/forge_auth.py" logout >/dev/null; ok "cleared stored Gitea tokens";;
|
||||
esac
|
||||
else
|
||||
ok "stored token is live for '$existing_user'"
|
||||
reply="$(prompt_choice "Reuse this session, or log out and re-authenticate? [R]euse / [L]ogout [R]" "R")"
|
||||
case "$reply" in
|
||||
[Ll]*) python3 "$here/forge_auth.py" logout >/dev/null; ok "cleared stored Gitea tokens";;
|
||||
*) run_login=0; ok "reusing the stored session";;
|
||||
esac
|
||||
fi
|
||||
elif [ "$have_stored" = "1" ] && [ -n "$existing_user" ]; then
|
||||
note "stored session for '$existing_user' is stale and could not be refreshed silently; re-authenticating."
|
||||
fi
|
||||
|
||||
if [ "$run_login" = "1" ]; then
|
||||
if [ "$headless" = "1" ] && [ "${FORGE_SETUP_YES:-0}" = "1" ]; then
|
||||
die "cannot complete a fresh login under --headless + FORGE_SETUP_YES=1:
|
||||
a browser-based OAuth step is required but both flags
|
||||
forbid any interaction. Choose one:
|
||||
1. Drop FORGE_SETUP_YES and re-run 'just setup --headless'
|
||||
(setup will print the authorisation URL and wait
|
||||
while you paste it into a browser).
|
||||
2. Run 'just login' once on a machine with a browser
|
||||
to populate the stored refresh token, then re-run
|
||||
'just setup --headless' from the headless host.
|
||||
3. Run 'just setup' (with a browser available) instead."
|
||||
fi
|
||||
if [ "$headless" = "1" ]; then
|
||||
bash "$here/forge_login.sh" --no-browser
|
||||
else
|
||||
bash "$here/forge_login.sh"
|
||||
fi
|
||||
else
|
||||
bash "$here/install-git-credential-helper.sh" >/dev/null
|
||||
fi
|
||||
|
||||
step "5/7 confirming silent git access (just check-access)"
|
||||
GIT_TERMINAL_PROMPT=0 GCM_INTERACTIVE=Never \
|
||||
GIT_ASKPASS="" SSH_ASKPASS="" \
|
||||
VSCODE_GIT_ASKPASS_MAIN="" VSCODE_GIT_ASKPASS_NODE="" \
|
||||
VSCODE_GIT_ASKPASS_EXTRA_ARGS="" VSCODE_GIT_IPC_HANDLE="" \
|
||||
DISPLAY="" WAYLAND_DISPLAY="" \
|
||||
git ls-remote "$FORGE_ORCHESTRATOR_REPO_URL" HEAD >/dev/null 2>&1 \
|
||||
|| die "git ls-remote failed. Your account may not yet be in the org, or FORGE_ORCHESTRATOR_REPO_URL is wrong."
|
||||
ok "silent clone access confirmed"
|
||||
|
||||
step "6/7 cloning the orchestrator (just clone-orchestrator)"
|
||||
workspace_root="${FORGE_WORKSPACE_ROOT:-.}"
|
||||
repo_name="$(basename "$FORGE_ORCHESTRATOR_REPO_URL" .git)"
|
||||
dest="$workspace_root/$repo_name"
|
||||
|
||||
if [ -d "$dest/.git" ]; then
|
||||
note "orchestrator already present at $dest"
|
||||
reply="$(prompt_choice "Reuse existing checkout, or wipe and re-clone? [R]euse / [W]ipe [R]" "R")"
|
||||
case "$reply" in
|
||||
[Ww]*)
|
||||
note "removing $dest"
|
||||
rm -rf "$dest"
|
||||
;;
|
||||
*)
|
||||
ok "keeping existing checkout"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [ ! -d "$dest/.git" ]; then
|
||||
mkdir -p "$workspace_root"
|
||||
export GIT_TERMINAL_PROMPT=0 GCM_INTERACTIVE=Never
|
||||
unset GIT_ASKPASS SSH_ASKPASS \
|
||||
VSCODE_GIT_ASKPASS_MAIN VSCODE_GIT_ASKPASS_NODE \
|
||||
VSCODE_GIT_ASKPASS_EXTRA_ARGS VSCODE_GIT_IPC_HANDLE \
|
||||
DISPLAY WAYLAND_DISPLAY
|
||||
if [ -n "${FORGE_ORCHESTRATOR_BRANCH:-}" ]; then
|
||||
git clone --branch "$FORGE_ORCHESTRATOR_BRANCH" \
|
||||
"$FORGE_ORCHESTRATOR_REPO_URL" "$dest"
|
||||
else
|
||||
git clone "$FORGE_ORCHESTRATOR_REPO_URL" "$dest"
|
||||
fi
|
||||
ok "cloned into $dest"
|
||||
fi
|
||||
|
||||
step "7/7 next steps (from the orchestrator's manifest)"
|
||||
manifest_path="$dest/scripts/contributor_setup_steps.json"
|
||||
if [ -f "$manifest_path" ]; then
|
||||
# Authoritative step list: $manifest_path in the orchestrator checkout.
|
||||
# This welcome repo never hardcodes the sequence.
|
||||
bash "$here/next_steps.sh" || true
|
||||
|
||||
# Offer to run the plan now (respect --headless and FORGE_SETUP_YES).
|
||||
forward=()
|
||||
[ "$headless" = "1" ] && forward+=("--headless")
|
||||
[ "${FORGE_SETUP_YES:-0}" = "1" ] && forward+=("--yes")
|
||||
|
||||
reply="$(prompt_choice "Run the orchestrator's contributor-setup now? [y/N]" "N")"
|
||||
case "$reply" in
|
||||
[Yy]*)
|
||||
step "handing off to the orchestrator"
|
||||
exec bash "$here/next_steps.sh" --run "${forward[@]}"
|
||||
;;
|
||||
*)
|
||||
note "skipping auto-run. To execute later:"
|
||||
if [ "${#forward[@]}" -gt 0 ]; then
|
||||
note " just run-next-steps ${forward[*]}"
|
||||
else
|
||||
note " just run-next-steps"
|
||||
fi
|
||||
note "or, inside the orchestrator checkout:"
|
||||
note " cd $dest && just contributor-setup"
|
||||
;;
|
||||
esac
|
||||
else
|
||||
warn "orchestrator at $dest does not ship contributor_setup_steps.json"
|
||||
warn "(older checkout?). See the orchestrator's README for onboarding:"
|
||||
note " $dest/README.md"
|
||||
fi
|
||||
Reference in New Issue
Block a user