Skip to content

Commit 9f39105

Browse files
auto-detect personal SSH identity for mirror setup
- detect personal GitHub account from ~/.ssh/config hosts - auto-construct SSH mirror URL (no HTTPS tokens needed) - add timeout + BatchMode to SSH probes (won't hang on bad hosts) - handle empty repos after clone (create initial commit if no HEAD) - extract fix_https_auth() as reusable function - mask credentials in displayed URLs - add 3-option repair menu on rerun (change URL / fix auth / skip) - test push after setting remote in no-origin branch - push HEAD instead of hardcoded main/master in sync
1 parent 563f218 commit 9f39105

File tree

2 files changed

+216
-55
lines changed

2 files changed

+216
-55
lines changed

setup.sh

Lines changed: 215 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,102 @@ ok() { echo " [ok] $*" >&2; }
3535
warn() { echo " [!] $*" >&2; }
3636
fail() { echo " [✗] $*" >&2; }
3737

38+
# Mask credentials in URLs (https://TOKEN@github.com/... -> https://****@github.com/...)
39+
mask_url() {
40+
echo "$1" | sed 's|https://[^@]*@|https://****@|'
41+
}
42+
43+
# Detect personal SSH host and GitHub username.
44+
# Parses ~/.ssh/config for GitHub hosts, runs ssh -T on each,
45+
# returns the one that ISN'T the work account.
46+
# Sets: PERSONAL_SSH_HOST, PERSONAL_GH_USER
47+
detect_personal_ssh() {
48+
local work_user="${1:-}" work_host="${2:-}"
49+
PERSONAL_SSH_HOST=""
50+
PERSONAL_GH_USER=""
51+
52+
[[ ! -f "$HOME/.ssh/config" ]] && return 1
53+
54+
# Extract all GitHub host aliases from SSH config
55+
local hosts=()
56+
while IFS= read -r line; do
57+
# Match "Host github*" or "Host *github*" lines, extract the alias
58+
local h
59+
h="$(echo "$line" | sed -n 's/^[Hh]ost[[:space:]][[:space:]]*//p' | tr -d ' ')"
60+
[[ -n "$h" && "$h" != "*" ]] && hosts+=("$h")
61+
done < <(grep -i "^[[:space:]]*host[[:space:]].*github" "$HOME/.ssh/config" 2>/dev/null | grep -iv "hostname")
62+
63+
[[ ${#hosts[@]} -lt 2 ]] && return 1
64+
65+
for h in "${hosts[@]}"; do
66+
# Skip the known work host
67+
[[ "$h" == "$work_host" ]] && continue
68+
69+
# Test SSH and extract username
70+
local ssh_output
71+
ssh_output="$(ssh -T -o BatchMode=yes -o ConnectTimeout=5 "git@$h" 2>&1 || true)"
72+
local gh_user
73+
gh_user="$(echo "$ssh_output" | sed -n 's/.*Hi \([^!]*\)!.*/\1/p' | head -1)"
74+
75+
if [[ -n "$gh_user" && "$gh_user" != "$work_user" ]]; then
76+
PERSONAL_SSH_HOST="$h"
77+
PERSONAL_GH_USER="$gh_user"
78+
return 0
79+
fi
80+
done
81+
return 1
82+
}
83+
84+
# Prompt for HTTPS token and embed in mirror remote URL.
85+
# Usage: fix_https_auth <mirror_dir> <current_url>
86+
fix_https_auth() {
87+
local mdir="$1" url="$2" mirror_token=""
88+
info "HTTPS repos need authentication to push."
89+
echo ""
90+
info "Create a PAT on your PERSONAL GitHub account (not work):"
91+
info " https://github.com/settings/tokens/new"
92+
info " Scope: [x] repo"
93+
echo ""
94+
if confirm "Do you have a personal GitHub token for push access?"; then
95+
printf " Paste token (hidden): " >&2
96+
read -rs mirror_token
97+
echo "" >&2
98+
else
99+
echo ""
100+
info "How to create one:"
101+
info " 1. Go to: https://github.com/settings/tokens/new"
102+
info " (Make sure you're logged into your PERSONAL GitHub, not work)"
103+
info " 2. Note: greens-push"
104+
info " 3. Expiration: 90 days"
105+
info " 4. Select scopes: [x] repo"
106+
info " 5. Click 'Generate token' and copy it"
107+
echo ""
108+
if confirm "Ready to paste the token?"; then
109+
printf " Paste token (hidden): " >&2
110+
read -rs mirror_token
111+
echo "" >&2
112+
else
113+
mirror_token=""
114+
fi
115+
fi
116+
if [[ -n "${mirror_token:-}" ]]; then
117+
local authed_url
118+
authed_url="$(echo "$url" | sed "s|https://[^@]*@|https://|; s|https://|https://${mirror_token}@|")"
119+
git -C "$mdir" remote set-url origin "$authed_url"
120+
if git -C "$mdir" push origin HEAD 2>/dev/null; then
121+
ok "Push access works now"
122+
return 0
123+
else
124+
warn "Still can't push. Check that the token has repo scope and the repo exists."
125+
return 1
126+
fi
127+
else
128+
warn "Skipping — commits won't push to GitHub until auth is configured."
129+
info "Fix later: greens --setup"
130+
return 1
131+
fi
132+
}
133+
38134
# ─────────────────────────────────────────────────────────────────────────────
39135
# Prerequisites check
40136
# ─────────────────────────────────────────────────────────────────────────────
@@ -596,13 +692,53 @@ info "It can be public or private. If private, enable 'Private contributions'"
596692
info "in GitHub Settings > Profile so the green squares show on your profile."
597693
echo ""
598694
if [[ ! -d "$mirror_dir/.git" ]]; then
599-
# Offer to create via gh if available
600-
if command -v gh &>/dev/null && gh auth status &>/dev/null 2>&1; then
695+
mirror_repo_name="$(prompt "Repo name" "work-contributions-mirror")"
696+
697+
# Try to auto-detect personal SSH identity for the mirror
698+
personal_host=""
699+
personal_user=""
700+
if [[ "$auth_method" == "ssh" ]]; then
701+
info "Detecting your personal GitHub account from SSH config..."
702+
if detect_personal_ssh "${github_username:-}" "${detected_ssh_host:-}"; then
703+
personal_host="$PERSONAL_SSH_HOST"
704+
personal_user="$PERSONAL_GH_USER"
705+
ok "Found personal account: $personal_user (SSH host: $personal_host)"
706+
fi
707+
fi
708+
709+
if [[ -n "$personal_user" && -n "$personal_host" ]]; then
710+
# SSH multi-account: auto-construct mirror URL using personal identity
711+
mirror_url="git@${personal_host}:${personal_user}/${mirror_repo_name}.git"
712+
info "Mirror URL: $mirror_url"
713+
echo ""
714+
715+
# Try to create the repo on GitHub
716+
repo_created=0
717+
if command -v gh &>/dev/null && gh auth status &>/dev/null 2>&1; then
718+
gh_user="$(gh api user -q .login 2>/dev/null || echo "")"
719+
if [[ "$gh_user" == "$personal_user" ]]; then
720+
# gh is on the personal account, create directly
721+
if gh repo create "$mirror_repo_name" --public --description "Mirror of private work contributions" 2>/dev/null; then
722+
ok "Created github.com/$personal_user/$mirror_repo_name"
723+
repo_created=1
724+
fi
725+
fi
726+
fi
727+
728+
if [[ "$repo_created" -eq 0 ]]; then
729+
echo ""
730+
info "Create this repo on your personal GitHub before continuing:"
731+
info " https://github.com/new?name=$mirror_repo_name"
732+
echo ""
733+
info "Press Enter when done..."
734+
read -r
735+
fi
736+
elif command -v gh &>/dev/null && gh auth status &>/dev/null 2>&1; then
737+
# Fallback: use gh CLI (single account or HTTPS)
601738
if confirm "Create a new public mirror repo on GitHub?"; then
602-
mirror_repo_name="$(prompt "Repo name" "work-contributions-mirror")"
603739
gh_user="$(gh api user -q .login 2>/dev/null || echo "")"
604740
if [[ -n "$gh_user" ]]; then
605-
# Check if gh is authenticated as work account (matches org or work username)
741+
# Check if gh is authenticated as work account
606742
is_work_account=0
607743
if [[ -n "${org_name:-}" && "$gh_user" == "$org_name" ]]; then
608744
is_work_account=1
@@ -621,7 +757,6 @@ if [[ ! -d "$mirror_dir/.git" ]]; then
621757
skip_gh_create=1
622758
fi
623759
else
624-
# Always confirm the target account before creating
625760
echo ""
626761
info "gh is authenticated as '$gh_user'."
627762
if ! confirm "Create repo under $gh_user?"; then
@@ -637,7 +772,6 @@ if [[ ! -d "$mirror_dir/.git" ]]; then
637772
if [[ -z "${mirror_url:-}" && -z "${skip_gh_create:-}" ]]; then
638773
if gh repo create "$mirror_repo_name" --public --description "Mirror of private work contributions" 2>/dev/null; then
639774
ok "Created repo on GitHub"
640-
# Get clone URL matching auth method
641775
if [[ "$auth_method" == "ssh" ]]; then
642776
mirror_url="$(gh repo view "$mirror_repo_name" --json sshUrl -q .sshUrl 2>/dev/null)"
643777
else
@@ -664,8 +798,11 @@ if [[ ! -d "$mirror_dir/.git" ]]; then
664798
mkdir -p "$mirror_dir"
665799
git -C "$mirror_dir" init --quiet
666800
git -C "$mirror_dir" remote add origin "$mirror_url"
667-
git -C "$mirror_dir" commit --allow-empty -m "init" --quiet
668801
}
802+
# Ensure at least one commit exists (empty repos have no HEAD)
803+
if ! git -C "$mirror_dir" rev-parse --verify HEAD &>/dev/null; then
804+
git -C "$mirror_dir" commit --allow-empty -m "init" --quiet
805+
fi
669806
ok "Mirror repo ready at $mirror_dir"
670807

671808
# Test push access
@@ -677,58 +814,84 @@ if [[ ! -d "$mirror_dir/.git" ]]; then
677814
warn "Can't push to mirror repo. Commits will be created locally but won't appear on GitHub."
678815
echo ""
679816
if [[ "$mirror_url" == https://* ]]; then
680-
info "HTTPS repos need authentication to push. Options:"
681-
echo ""
682-
info " Option 1: Embed a Personal Access Token in the URL"
683-
info " ─────────────────────────────────────────────────"
684-
info " Create a PAT on your PERSONAL GitHub account (not work):"
685-
info " https://github.com/settings/tokens/new"
686-
info " Scope: [x] repo"
687-
echo ""
688-
if confirm "Do you have a personal GitHub token for push access?"; then
689-
printf " Paste token (hidden): " >&2
690-
read -rs mirror_token
691-
echo "" >&2
692-
else
693-
echo ""
694-
info "No worries — here's how to create one:"
695-
echo ""
696-
info " 1. Go to: https://github.com/settings/tokens/new"
697-
info " (Make sure you're logged into your PERSONAL GitHub, not work)"
698-
info " 2. Note: greens-push"
699-
info " 3. Expiration: 90 days (or custom)"
700-
info " 4. Select scopes:"
701-
info " [x] repo"
702-
info " 5. Click 'Generate token' and copy it"
703-
echo ""
704-
if confirm "Ready to paste the token?"; then
705-
printf " Paste token (hidden): " >&2
706-
read -rs mirror_token
707-
echo "" >&2
708-
else
709-
mirror_token=""
710-
fi
711-
fi
712-
if [[ -n "${mirror_token:-}" ]]; then
713-
# Embed token in remote URL: https://TOKEN@github.com/user/repo.git
714-
authed_url="$(echo "$mirror_url" | sed "s|https://|https://${mirror_token}@|")"
715-
git -C "$mirror_dir" remote set-url origin "$authed_url"
716-
if git -C "$mirror_dir" push origin HEAD 2>/dev/null; then
717-
ok "Push access works now"
718-
else
719-
warn "Still can't push. Check that the token has repo scope and the repo exists on GitHub."
720-
fi
721-
else
722-
warn "Skipping — your sync will run but commits won't push to GitHub."
723-
info "Fix later: greens --setup"
724-
fi
817+
fix_https_auth "$mirror_dir" "$mirror_url"
725818
else
726819
info "Check that your SSH key has access to push to this repo."
727820
fi
728821
fi
729822
fi
730823
else
731824
ok "Mirror repo already exists at $mirror_dir"
825+
current_remote="$(git -C "$mirror_dir" remote get-url origin 2>/dev/null || echo "")"
826+
if [[ -n "$current_remote" ]]; then
827+
info "Current remote: $(mask_url "$current_remote")"
828+
echo ""
829+
830+
# Test push access
831+
info "Testing push access..."
832+
if git -C "$mirror_dir" push origin HEAD 2>/dev/null; then
833+
ok "Push access works"
834+
else
835+
warn "Can't push to mirror repo."
836+
echo ""
837+
echo " What would you like to do?" >&2
838+
echo " 1) Change the remote URL (wrong repo or deleted repo)" >&2
839+
echo " 2) Fix push authentication (HTTPS token)" >&2
840+
echo " 3) Skip (fix later)" >&2
841+
printf " Choice [1]: " >&2
842+
read -r push_fix_choice
843+
push_fix_choice="${push_fix_choice:-1}"
844+
845+
case "$push_fix_choice" in
846+
1)
847+
new_mirror_url="$(prompt "New mirror repo URL" "")"
848+
if [[ -n "$new_mirror_url" ]]; then
849+
git -C "$mirror_dir" remote set-url origin "$new_mirror_url"
850+
ok "Remote updated to $(mask_url "$new_mirror_url")"
851+
# Test push with new URL
852+
info "Testing push access..."
853+
if git -C "$mirror_dir" push origin HEAD 2>/dev/null; then
854+
ok "Push access works"
855+
elif [[ "$new_mirror_url" == https://* ]]; then
856+
warn "Can't push with new URL."
857+
fix_https_auth "$mirror_dir" "$new_mirror_url"
858+
else
859+
warn "Still can't push. Check SSH access to this repo."
860+
fi
861+
fi
862+
;;
863+
2)
864+
if [[ "$current_remote" == https://* ]]; then
865+
fix_https_auth "$mirror_dir" "$current_remote"
866+
else
867+
info "Current remote uses SSH. Check that your SSH key has push access."
868+
info "Test: ssh -T git@github.com"
869+
fi
870+
;;
871+
*)
872+
info "Skipping. Fix later: greens --setup"
873+
;;
874+
esac
875+
fi
876+
else
877+
warn "No remote URL configured for mirror repo."
878+
new_mirror_url="$(prompt "Mirror repo URL" "")"
879+
if [[ -n "$new_mirror_url" ]]; then
880+
git -C "$mirror_dir" remote add origin "$new_mirror_url" 2>/dev/null || \
881+
git -C "$mirror_dir" remote set-url origin "$new_mirror_url"
882+
ok "Remote set to $(mask_url "$new_mirror_url")"
883+
# Test push access right away
884+
info "Testing push access..."
885+
if git -C "$mirror_dir" push origin HEAD 2>/dev/null; then
886+
ok "Push access works"
887+
elif [[ "$new_mirror_url" == https://* ]]; then
888+
warn "Can't push."
889+
fix_https_auth "$mirror_dir" "$new_mirror_url"
890+
else
891+
warn "Can't push. Check SSH access to this repo."
892+
fi
893+
fi
894+
fi
732895
fi
733896

734897
# ─────────────────────────────────────────────────────────────────────────────

sync.sh

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -790,9 +790,7 @@ log ""
790790
log "Step 5/5: Pushing to GitHub (making your contribution graph green)"
791791

792792
push_output=""
793-
if push_output="$(git push origin main 2>&1)"; then
794-
log "Mirror pushed."
795-
elif push_output="$(git push origin master 2>&1)"; then
793+
if push_output="$(git -C "$MIRROR_DIR" push origin HEAD 2>&1)"; then
796794
log "Mirror pushed."
797795
else
798796
log "ERROR: Push to mirror repo failed."

0 commit comments

Comments
 (0)