Skip to content

Commit 31fe019

Browse files
committed
Bootstrap private runner repo when missing
1 parent 4328ebe commit 31fe019

1 file changed

Lines changed: 175 additions & 11 deletions

File tree

run-browserbox.sh

Lines changed: 175 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,24 @@ Launch the BrowserBox GitHub Actions runner workflow and print the live link.
99
1010
Options:
1111
--license-key KEY Set/update the repo secret BBX_LICENSE_KEY before running.
12-
--repo OWNER/NAME Repository to run the workflow in.
12+
--repo OWNER/NAME Repository to run the workflow in, creating it if needed.
13+
--repo-name NAME Repo name for auto-create. Defaults to <directory>-runner.
1314
--ref REF Branch/ref to run. Defaults to the current branch or main.
1415
--timeout MINUTES Session timeout. Defaults to 10.
1516
--workflow NAME Workflow name or file. Defaults to "Live Smoke Run".
17+
--public Create the bootstrap repo as public.
18+
--private Create the bootstrap repo as private. This is the default.
19+
--no-create-repo Fail instead of creating/bootstrapping a repo.
20+
--remote NAME Local git remote name for bootstrap pushes. Defaults to browserbox-runner.
1621
--no-open Do not open the tracking issue in a browser.
1722
-h, --help Show this help.
1823
1924
Environment:
2025
BBX_LICENSE_KEY, BROWSERBOX_LICENSE_KEY, or BROWSERBOX_ACTION_LICENSE_KEY
2126
Used when --license-key is not supplied.
2227
BROWSERBOX_RUN_REPO Default repository, in owner/name form.
28+
BROWSERBOX_RUN_REPO_NAME
29+
Repo name for auto-create.
2330
EOF
2431
}
2532

@@ -29,7 +36,7 @@ die() {
2936
}
3037

3138
log() {
32-
printf '[browserbox] %s\n' "$*"
39+
printf '[browserbox] %s\n' "$*" >&2
3340
}
3441

3542
remote_repo() {
@@ -69,14 +76,133 @@ workflow_exists() {
6976
gh workflow view "$workflow" --repo "$repo" >/dev/null 2>&1
7077
}
7178

79+
repo_exists() {
80+
local repo="$1"
81+
gh repo view "$repo" >/dev/null 2>&1
82+
}
83+
84+
authenticated_owner() {
85+
gh api user --jq .login
86+
}
87+
88+
repo_clone_url() {
89+
local repo="$1"
90+
local protocol
91+
92+
protocol="$(gh config get git_protocol -h github.com 2>/dev/null || true)"
93+
if [[ "$protocol" == "ssh" ]]; then
94+
gh repo view "$repo" --json sshUrl --jq .sshUrl
95+
else
96+
gh repo view "$repo" --json url --jq .url
97+
fi
98+
}
99+
100+
sanitize_repo_name() {
101+
local raw="$1"
102+
local clean
103+
104+
clean="$(printf '%s' "$raw" | tr ' ' '-' | tr -cd 'A-Za-z0-9._-')"
105+
clean="${clean#.}"
106+
clean="${clean%-}"
107+
printf '%s\n' "${clean:-browserbox-runner}"
108+
}
109+
110+
default_auto_repo() {
111+
local owner="$1"
112+
local name
113+
local root
114+
115+
root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
116+
name="${BROWSERBOX_RUN_REPO_NAME:-${repo_name_arg:-}}"
117+
if [[ -z "$name" ]]; then
118+
name="$(basename "$root")-runner"
119+
fi
120+
121+
printf '%s/%s\n' "$owner" "$(sanitize_repo_name "$name")"
122+
}
123+
124+
ensure_git_worktree() {
125+
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || die "repo bootstrap requires running from this git checkout"
126+
}
127+
128+
warn_uncommitted_bootstrap_changes() {
129+
local dirty
130+
131+
dirty="$(git status --porcelain -- .github/workflows/smoke-run.yml action.yml scripts/run-browserbox.sh run-browserbox.sh 2>/dev/null || true)"
132+
if [[ -n "$dirty" ]]; then
133+
log "warning: workflow/action helper files have uncommitted changes; only committed HEAD will be pushed"
134+
fi
135+
}
136+
137+
ensure_remote() {
138+
local remote="$1"
139+
local repo="$2"
140+
local url
141+
142+
url="$(repo_clone_url "$repo")"
143+
if git remote get-url "$remote" >/dev/null 2>&1; then
144+
if [[ "$(git remote get-url "$remote")" != "$url" ]]; then
145+
git remote set-url "$remote" "$url"
146+
fi
147+
else
148+
git remote add "$remote" "$url"
149+
fi
150+
}
151+
152+
wait_for_workflow() {
153+
local repo="$1"
154+
local workflow="$2"
155+
156+
for _ in $(seq 1 60); do
157+
workflow_exists "$repo" "$workflow" && return 0
158+
sleep 2
159+
done
160+
161+
return 1
162+
}
163+
164+
bootstrap_repo() {
165+
local repo="$1"
166+
local workflow="$2"
167+
local ref="$3"
168+
169+
[[ "$create_repo" == "true" ]] || die "no repo with workflow \"${workflow}\" found; pass --repo OWNER/NAME or remove --no-create-repo"
170+
ensure_git_worktree
171+
warn_uncommitted_bootstrap_changes
172+
173+
if repo_exists "$repo"; then
174+
log "bootstrap repo exists: ${repo}"
175+
else
176+
log "creating ${repo_visibility} repo: ${repo}"
177+
gh repo create "$repo" "--${repo_visibility}" --disable-wiki \
178+
--description "Private BrowserBox GitHub Actions runner"
179+
fi
180+
181+
ensure_remote "$bootstrap_remote" "$repo"
182+
183+
log "pushing current HEAD to ${repo}:${ref}"
184+
if ! git push "$bootstrap_remote" "HEAD:refs/heads/${ref}"; then
185+
die "failed to push current HEAD to ${repo}:${ref}; resolve the git push error or pass a different --repo"
186+
fi
187+
188+
log "waiting for workflow \"${workflow}\" to become available"
189+
wait_for_workflow "$repo" "$workflow" || die "workflow \"${workflow}\" was not found in ${repo} after pushing ${ref}"
190+
}
191+
72192
resolve_repo() {
73193
local workflow="$1"
74-
local repo="${BROWSERBOX_RUN_REPO:-}"
194+
local ref="$2"
195+
local explicit_repo="${repo_arg:-${BROWSERBOX_RUN_REPO:-}}"
75196
local candidate
76197
local candidates=()
77198

78-
if [[ -n "$repo" ]]; then
79-
printf '%s\n' "$repo"
199+
if [[ -n "$explicit_repo" ]]; then
200+
if workflow_exists "$explicit_repo" "$workflow"; then
201+
printf '%s\n' "$explicit_repo"
202+
return 0
203+
fi
204+
bootstrap_repo "$explicit_repo" "$workflow" "$ref"
205+
printf '%s\n' "$explicit_repo"
80206
return 0
81207
fi
82208

@@ -95,8 +221,10 @@ resolve_repo() {
95221
fi
96222
done
97223

98-
[[ "${#candidates[@]}" -gt 0 ]] || die "could not infer a GitHub repo; pass --repo OWNER/NAME"
99-
printf '%s\n' "${candidates[0]}"
224+
candidate="$(default_auto_repo "$(authenticated_owner)")"
225+
log "no existing candidate repo has workflow \"${workflow}\""
226+
bootstrap_repo "$candidate" "$workflow" "$ref"
227+
printf '%s\n' "$candidate"
100228
}
101229

102230
open_url() {
@@ -216,11 +344,15 @@ wait_for_login_link() {
216344

217345
license_key="${BBX_LICENSE_KEY:-${BROWSERBOX_LICENSE_KEY:-${BROWSERBOX_ACTION_LICENSE_KEY:-}}}"
218346
repo_arg=""
347+
repo_name_arg=""
219348
ref="$(git branch --show-current 2>/dev/null || true)"
220349
ref="${ref:-main}"
221350
timeout_mins="10"
222351
workflow="Live Smoke Run"
223352
open_issue="true"
353+
create_repo="true"
354+
repo_visibility="private"
355+
bootstrap_remote="browserbox-runner"
224356

225357
while [[ $# -gt 0 ]]; do
226358
case "$1" in
@@ -242,6 +374,15 @@ while [[ $# -gt 0 ]]; do
242374
repo_arg="${1#*=}"
243375
shift
244376
;;
377+
--repo-name)
378+
[[ $# -ge 2 ]] || die "--repo-name requires a value"
379+
repo_name_arg="$2"
380+
shift 2
381+
;;
382+
--repo-name=*)
383+
repo_name_arg="${1#*=}"
384+
shift
385+
;;
245386
--ref)
246387
[[ $# -ge 2 ]] || die "--ref requires a value"
247388
ref="$2"
@@ -269,6 +410,27 @@ while [[ $# -gt 0 ]]; do
269410
workflow="${1#*=}"
270411
shift
271412
;;
413+
--public)
414+
repo_visibility="public"
415+
shift
416+
;;
417+
--private)
418+
repo_visibility="private"
419+
shift
420+
;;
421+
--no-create-repo)
422+
create_repo="false"
423+
shift
424+
;;
425+
--remote)
426+
[[ $# -ge 2 ]] || die "--remote requires a value"
427+
bootstrap_remote="$2"
428+
shift 2
429+
;;
430+
--remote=*)
431+
bootstrap_remote="${1#*=}"
432+
shift
433+
;;
272434
--no-open)
273435
open_issue="false"
274436
shift
@@ -287,14 +449,16 @@ while [[ $# -gt 0 ]]; do
287449
esac
288450
done
289451

290-
command -v gh >/dev/null 2>&1 || die "gh is required"
291-
gh auth status >/dev/null 2>&1 || die "gh is not authenticated"
452+
command -v gh >/dev/null 2>&1 || die "gh is required. Install GitHub CLI: https://cli.github.com/"
453+
gh auth status >/dev/null 2>&1 || die "gh is not authenticated. Run: gh auth login --scopes repo"
292454
[[ "$timeout_mins" =~ ^[0-9]+$ ]] || die "--timeout must be an integer number of minutes"
455+
[[ "$repo_visibility" == "private" || "$repo_visibility" == "public" ]] || die "repo visibility must be private or public"
456+
[[ -n "$bootstrap_remote" ]] || die "--remote cannot be empty"
293457

294458
if [[ -n "$repo_arg" ]]; then
295-
repo="$repo_arg"
459+
repo="$(resolve_repo "$workflow" "$ref")"
296460
else
297-
repo="$(resolve_repo "$workflow")"
461+
repo="$(resolve_repo "$workflow" "$ref")"
298462
fi
299463

300464
log "repo: ${repo}"

0 commit comments

Comments
 (0)