#!/usr/bin/env bash
set -uo pipefail
# github_forensics_dump.sh
# Usage: ./github_forensics_dump.sh
# Requires: gh, git, jq, curl, tar, unzip
# Must have gh authenticated (gh auth status) OR GHTOKEN exported
DATE=$(date -u +%Y%m%dT%H%M%SZ)
OUTROOT="$HOME/github_forensics_$DATE"
LOG="$OUTROOT/collection.log"
CHECKPOINT="$OUTROOT/.checkpoint"
mkdir -p "$OUTROOT"
exec 3>&1 1>>"$LOG" 2>&1

log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" >&3; echo "$*"; }
safe_run() { if ! "$@"; then echo "COMMAND FAILED: $*"; fi }

# Resume helper
checkpoint_set() { echo "$1" > "$CHECKPOINT"; }
checkpoint_get() { if [ -f "$CHECKPOINT" ]; then cat "$CHECKPOINT"; fi }

log "Starting GitHub forensic dump to $OUTROOT"
log "Checkpoint was: $(checkpoint_get || true)"

# Validate tools
for cmd in gh git jq curl tar unzip; do
  if ! command -v "$cmd" >/dev/null 2>&1; then
    log "ERROR: required tool missing: $cmd. Install it and re-run."
    exit 2
  fi
done

# Validate GH auth
if ! gh auth status >/dev/null 2>&1; then
  if [ -z "${GHTOKEN:-}" ]; then
    log "gh not authenticated and GHTOKEN not set. Run 'gh auth login' or export GHTOKEN."
    exit 3
  else
    log "Using GHTOKEN for curl fallback; gh may still require auth for some operations."
  fi
fi

# Basic profile dump
if [ "$(checkpoint_get)" != "profile" ]; then
  log "Collecting user profile, emails, keys, gpg keys"
  mkdir -p "$OUTROOT/profile"
  gh api user > "$OUTROOT/profile/user.json" || curl -s -H "$GHHDR" https://api.github.com/user > "$OUTROOT/profile/user.json"
  gh api user/emails > "$OUTROOT/profile/emails.json" || curl -s -H "$GHHDR" https://api.github.com/user/emails > "$OUTROOT/profile/emails.json"
  gh api user/keys > "$OUTROOT/profile/keys.json" || curl -s -H "$GHHDR" https://api.github.com/user/keys > "$OUTROOT/profile/keys.json"
  gh api user/gpg_keys > "$OUTROOT/profile/gpg_keys.json" || curl -s -H "$GHHDR" https://api.github.com/user/gpg_keys > "$OUTROOT/profile/gpg_keys.json"
  checkpoint_set profile
fi

# Repo enumeration
if [ "$(checkpoint_get)" != "repos" ]; then
  log "Listing repos accessible by user"
  mkdir -p "$OUTROOT/repos"
  gh repo list SpaceDudem --limit 1000 --json name,nameWithOwner,isPrivate,sshUrl,updatedAt,viewerPermission  > "$OUTROOT/repos/repos.json" || \
    curl -s -H "$GHHDR" "https://api.github.com/user/repos?per_page=100" | jq . > "$OUTROOT/repos/repos.json"
  # also list org repos you may be able to see via org list if public/private
  checkpoint_set repos
fi

# Parse repo names into array
REPOS_FILE="$OUTROOT/repos/repos.json"
REPOS=()
if [ -s "$REPOS_FILE" ]; then
  mapfile -t REPOS < <(jq -r '.[] | .nameWithOwner' "$REPOS_FILE")
fi
log "Found ${#REPOS[@]} repos to process"

# Per-repo collection loop
for repo_full in "${REPOS[@]}"; do
  repo_dir="$OUTROOT/repos/$(echo "$repo_full" | tr '/' '__')"
  mkdir -p "$repo_dir"
  marker="$repo_dir/.done"
  if [ -f "$marker" ]; then
    log "Skipping $repo_full (already done)"
    continue
  fi
  log "Processing $repo_full"

  # collaborators
  gh api "repos/$repo_full/collaborators?per_page=100" > "$repo_dir/collaborators.json" || \
    curl -s -H "$GHHDR" "https://api.github.com/repos/$repo_full/collaborators?per_page=100" > "$repo_dir/collaborators.json" || true

  # deploy keys
  gh api "repos/$repo_full/keys?per_page=100" > "$repo_dir/deploy_keys.json" || \
    curl -s -H "$GHHDR" "https://api.github.com/repos/$repo_full/keys?per_page=100" > "$repo_dir/deploy_keys.json" || true

  # webhooks
  gh api "repos/$repo_full/hooks?per_page=100" > "$repo_dir/hooks.json" || \
    curl -s -H "$GHHDR" "https://api.github.com/repos/$repo_full/hooks?per_page=100" > "$repo_dir/hooks.json" || true

  # secrets (names only)
  gh api "repos/$repo_full/actions/secrets" > "$repo_dir/secrets.json" || \
    curl -s -H "$GHHDR" "https://api.github.com/repos/$repo_full/actions/secrets" > "$repo_dir/secrets.json" || true

  # actions runs (list)
  gh api "repos/$repo_full/actions/runs?per_page=100" > "$repo_dir/actions_runs.json" || \
    curl -s -H "$GHHDR" "https://api.github.com/repos/$repo_full/actions/runs?per_page=100" > "$repo_dir/actions_runs.json" || true

  # Download logs for each run (if any)
  runs_count=$(jq -r '.total_count // 0' "$repo_dir/actions_runs.json" 2>/dev/null || echo 0)
  if [ "$runs_count" -gt 0 ]; then
    mkdir -p "$repo_dir/actions_logs"
    jq -r '.workflow_runs[].id' "$repo_dir/actions_runs.json" 2>/dev/null | while read -r runid; do
      outzip="$repo_dir/actions_logs/run_${runid}_logs.zip"
      if [ -f "$outzip" ]; then
        log "Run $runid logs already downloaded"
      else
        log "Downloading logs for run $runid"
        gh api "repos/$repo_full/actions/runs/$runid/logs" --output "$outzip" || \
          curl -s -H "$GHHDR" -L "https://api.github.com/repos/$repo_full/actions/runs/$runid/logs" -o "$outzip" || true
      fi
    done
  fi

  # Artifacts list & download
  gh api "repos/$repo_full/actions/artifacts?per_page=100" > "$repo_dir/artifacts.json" || \
    curl -s -H "$GHHDR" "https://api.github.com/repos/$repo_full/actions/artifacts?per_page=100" > "$repo_dir/artifacts.json" || true
  jq -r '.artifacts[]?.id' "$repo_dir/artifacts.json" 2>/dev/null | while read -r aid; do
    artifact_zip="$repo_dir/artifact_${aid}.zip"
    if [ -f "$artifact_zip" ]; then
      log "Artifact $aid already downloaded"
    else
      log "Downloading artifact $aid"
      # endpoint to download artifact archive:
      gh api "repos/$repo_full/actions/artifacts/$aid/zip" --output "$artifact_zip" || \
        curl -s -H "$GHHDR" -L "https://api.github.com/repos/$repo_full/actions/artifacts/$aid/zip" -o "$artifact_zip" || true
    fi
  done

  # Mirror clone via SSH (non-destructive)
  git_mirror_dir="$repo_dir/mirror.git"
  if [ -d "$git_mirror_dir" ]; then
    log "Mirror already exists for $repo_full"
  else
    log "Attempting mirror clone of $repo_full"
    # prefer sshUrl if present in repos.json
    sshurl=$(jq -r --arg r "$repo_full" '.[] | select(.fullName==$r) | .sshUrl' "$REPOS_FILE" 2>/dev/null || echo "")
    if [ -n "$sshurl" ] && [[ "$sshurl" != "null" ]]; then
      git clone --mirror "$sshurl" "$git_mirror_dir" || { log "Mirror clone via SSH failed, trying HTTPS"; git clone --mirror "https://github.com/$repo_full.git" "$git_mirror_dir" || true; }
    else
      git clone --mirror "https://github.com/$repo_full.git" "$git_mirror_dir" || true
    fi
  fi

  # Search within repo via API for attacker email / tokens
  gh api "search/commits?q=author-email:hoetinn@outlook.com+repo:$repo_full" -H "Accept: application/vnd.github.cloak-preview" > "$repo_dir/commits_hoetinn.json" 2>/dev/null || true
  gh api "search/code?q=hoetinn@outlook.com+repo:$repo_full" > "$repo_dir/code_hoetinn.json" 2>/dev/null || true
  gh api "search/code?q=ghp_+repo:$repo_full" > "$repo_dir/code_ghp_tokens.json" 2>/dev/null || true

  # Mark done
  touch "$marker"
  log "Finished $repo_full"
done

# Org audit log (if user is org admin over any orgs)
if [ "$(checkpoint_get)" != "org_audit" ]; then
  log "Collecting list of orgs for user"
  gh api user/orgs --paginate > "$OUTROOT/orgs.json" || curl -s -H "$GHHDR" https://api.github.com/user/orgs > "$OUTROOT/orgs.json" || true
  jq -r '.[].login' "$OUTROOT/orgs.json" 2>/dev/null | while read -r org; do
    log "Collecting audit log for org: $org"
    # audit log requires auth and org scope; will fail if not permitted
    gh api "orgs/$org/audit-log" --paginate > "$OUTROOT/org_${org}_audit_log.json" 2>/dev/null || \
      curl -s -H "$GHHDR" "https://api.github.com/orgs/$org/audit-log" > "$OUTROOT/org_${org}_audit_log.json" 2>/dev/null || true
  done
  checkpoint_set org_audit
fi

# Installations / apps
if [ "$(checkpoint_get)" != "apps" ]; then
  log "Collecting user installations and app installs"
  gh api user/installations --paginate > "$OUTROOT/user_installations.json" || curl -s -H "$GHHDR" https://api.github.com/user/installations > "$OUTROOT/user_installations.json" || true
  gh api user/tokens --paginate > "$OUTROOT/user_tokens.json" 2>/dev/null || true
  checkpoint_set apps
fi

# Global searches across all accessible repos (may hit rate limits)
if [ "$(checkpoint_get)" != "searches" ]; then
  log "Running global search for attacker patterns (email / ghp_)"
  mkdir -p "$OUTROOT/searches"
  curl -s -H "$GHHDR" "https://api.github.com/search/code?q=\"hoetinn@outlook.com\"&per_page=100" > "$OUTROOT/searches/code_hoetinn_global.json" || true
  curl -s -H "$GHHDR" "https://api.github.com/search/commits?q=author-email:hoetinn@outlook.com&per_page=100" -H "Accept: application/vnd.github.cloak-preview" > "$OUTROOT/searches/commits_hoetinn_global.json" || true
  curl -s -H "$GHHDR" "https://api.github.com/search/code?q=ghp_&per_page=100" > "$OUTROOT/searches/code_ghp_global.json" || true
  checkpoint_set searches
fi

# Collect GH CLI config and hosts
gh config list > "$OUTROOT/gh_config.txt" 2>&1 || true
cat ~/.config/gh/hosts.yml > "$OUTROOT/gh_hosts.yml" 2>/dev/null || true

# Finalize: checksums & compress
log "Generating checksums and packaging evidence"
( cd "$OUTROOT" && find . -type f -not -name '*.tar.gz' -print0 | xargs -0 sha256sum > sha256sums.txt ) || true
tarball="${OUTROOT}.tar.gz"
tar -czf "$tarball" -C "$(dirname "$OUTROOT")" "$(basename "$OUTROOT")"
sha256sum "$tarball" > "${tarball}.sha256"
log "Evidence tarball created: $tarball"
log "SHA256: $(cat ${tarball}.sha256)"

# Produce quick suspicious-report
log "Generating quick suspicious report"
jq -s '[ .[] | .[]? ]' "$OUTROOT/repos/repos.json" 2>/dev/null > /dev/null || true
# find unknown deploy keys / hooks / collaborators heuristic
echo "SUSPICIOUS_ITEMS" > "$OUTROOT/suspicious_summary.txt"
grep -R "\"title\"" "$OUTROOT" | head -n 200 > "$OUTROOT/suspicious_summary.txt" || true

log "Collection complete. Review $tarball and $OUTROOT/suspicious_summary.txt"
echo "DONE"

