#!/usr/bin/env sh set -eu umask 077 branch=${GITHUB_BRANCH:-} infra_dir=${INFRA_DIR:-/volume2/docker/infra} proxy_url=${GITHUB_PROXY_URL:-http://192.168.0.4:7890} compose_file=$infra_dir/docker-compose.yaml state_dir=$infra_dir/git-bridge/state state_repo=$state_dir/import.git candidate=$state_dir/import-candidate gitea_token_file=$infra_dir/git-bridge/secrets/gitea_token github_token_file=$infra_dir/git-bridge/secrets/github_token api_base=http://127.0.0.1:13000/api/v1 temp_dir= if [ "$(id -u)" -ne 0 ]; then echo "refusing import: run with sudo" >&2 exit 2 fi case "$branch" in codex/*|grok/*|cloud/*) ;; *) echo "refusing import: GITHUB_BRANCH must use codex/, grok/, or cloud/ prefix" >&2 exit 2 ;; esac case "$branch" in *[!A-Za-z0-9._/-]*|*..*|*/.|/*|*/|*//*|main) echo "refusing import: unsafe branch name" >&2 exit 2 ;; esac for command_name in docker curl python3; do command -v "$command_name" >/dev/null 2>&1 || { echo "$command_name is required" >&2 exit 2 } done docker info >/dev/null [ -f "$compose_file" ] || { echo "canonical Infra Compose is missing" >&2 exit 2 } [ ! -e "$infra_dir/compose.yaml" ] || { echo "legacy compose.yaml must not exist" >&2 exit 2 } for secret_file in "$gitea_token_file" "$github_token_file"; do [ -s "$secret_file" ] || { echo "bridge secret is missing: $secret_file" >&2 exit 2 } [ "$(stat -c '%a' "$secret_file")" = "600" ] || { echo "bridge secret permissions must be 0600: $secret_file" >&2 exit 2 } done [ ! -e "$candidate" ] || { echo "stale candidate worktree exists: $candidate" >&2 exit 2 } temp_dir=$(mktemp -d /tmp/infra-branch-import.XXXXXX) askpass_file=$temp_dir/askpass.sh curl_config=$temp_dir/gitea-curl.conf response_file=$temp_dir/response.json request_file=$temp_dir/request.json cleanup() { status=$? if [ -d "$candidate" ]; then git_run --git-dir=/infra/git-bridge/state/import.git worktree remove --force \ /infra/git-bridge/state/import-candidate >/dev/null 2>&1 || true fi if [ -n "$temp_dir" ]; then rm -f "$askpass_file" "$curl_config" "$response_file" "$request_file" \ "$temp_dir/tracked.zlist" rmdir "$temp_dir" 2>/dev/null || true fi exit "$status" } trap cleanup EXIT trap 'exit 130' INT TERM cat >"$askpass_file" <<'ASKPASS' #!/bin/sh case "$1" in *Username*github.com*|*github.com*Username*) printf '%s\n' 'RX-Penguin' ;; *github.com*) cat /run/secrets/github_token ;; *Username*gitea*|*gitea*Username*) printf '%s\n' 'git-bridge' ;; *gitea*) cat /run/secrets/gitea_token ;; *) exit 1 ;; esac ASKPASS chmod 0500 "$askpass_file" gitea_token=$(cat "$gitea_token_file") printf 'header = "Authorization: token %s"\n' "$gitea_token" >"$curl_config" chmod 0600 "$curl_config" gitea_token= gitea_image=$(docker container inspect --format '{{.Config.Image}}' gitea) git_run() { docker run --rm \ --user 0:0 \ --network git_forge \ --entrypoint git \ -e GIT_TERMINAL_PROMPT=0 \ -e GIT_ASKPASS=/run/secrets/askpass.sh \ -e HTTP_PROXY="$proxy_url" \ -e HTTPS_PROXY="$proxy_url" \ -e ALL_PROXY="$proxy_url" \ -e NO_PROXY=localhost,127.0.0.1,::1,gitea,postgres,litellm \ -v "$infra_dir:/infra" \ -v "$gitea_token_file:/run/secrets/gitea_token:ro" \ -v "$github_token_file:/run/secrets/github_token:ro" \ -v "$askpass_file:/run/secrets/askpass.sh:ro" \ "$gitea_image" \ -c safe.directory='*' "$@" } git_run check-ref-format --branch "$branch" >/dev/null install -d -m 0700 "$state_dir" if [ ! -d "$state_repo" ]; then git_run init --bare /infra/git-bridge/state/import.git >/dev/null fi if git_run --git-dir=/infra/git-bridge/state/import.git remote get-url gitea >/dev/null 2>&1; then [ "$(git_run --git-dir=/infra/git-bridge/state/import.git remote get-url gitea)" = \ "http://gitea:3000/slh/infra.git" ] || { echo "gitea remote mismatch" >&2 exit 2 } else git_run --git-dir=/infra/git-bridge/state/import.git remote add gitea \ http://gitea:3000/slh/infra.git fi if git_run --git-dir=/infra/git-bridge/state/import.git remote get-url github >/dev/null 2>&1; then [ "$(git_run --git-dir=/infra/git-bridge/state/import.git remote get-url github)" = \ "https://github.com/RX-Penguin/infra.git" ] || { echo "github remote mismatch" >&2 exit 2 } else git_run --git-dir=/infra/git-bridge/state/import.git remote add github \ https://github.com/RX-Penguin/infra.git fi git_run --git-dir=/infra/git-bridge/state/import.git fetch --no-tags gitea \ '+refs/heads/main:refs/remotes/gitea/main' git_run --git-dir=/infra/git-bridge/state/import.git fetch --no-tags github \ "+refs/heads/main:refs/remotes/github/main" \ "+refs/heads/$branch:refs/remotes/github/$branch" gitea_main=$(git_run --git-dir=/infra/git-bridge/state/import.git rev-parse refs/remotes/gitea/main) github_main=$(git_run --git-dir=/infra/git-bridge/state/import.git rev-parse refs/remotes/github/main) candidate_sha=$(git_run --git-dir=/infra/git-bridge/state/import.git rev-parse "refs/remotes/github/$branch") [ "$gitea_main" = "$github_main" ] || { echo "import refused: GitHub Main differs from Gitea Main; run forward sync first" >&2 exit 42 } git_run --git-dir=/infra/git-bridge/state/import.git merge-base --is-ancestor \ "$gitea_main" "$candidate_sha" || { echo "import refused: candidate branch does not descend from current Gitea Main" >&2 exit 42 } [ "$candidate_sha" != "$gitea_main" ] || { echo "import refused: candidate branch contains no change from Gitea Main" >&2 exit 42 } if git_run --git-dir=/infra/git-bridge/state/import.git ls-remote --exit-code \ --heads gitea "refs/heads/$branch" >/dev/null 2>&1; then git_run --git-dir=/infra/git-bridge/state/import.git fetch --no-tags gitea \ "+refs/heads/$branch:refs/remotes/gitea/$branch" current_sha=$(git_run --git-dir=/infra/git-bridge/state/import.git \ rev-parse "refs/remotes/gitea/$branch") git_run --git-dir=/infra/git-bridge/state/import.git merge-base --is-ancestor \ "$current_sha" "$candidate_sha" || { echo "import refused: existing Gitea branch cannot fast-forward" >&2 exit 42 } fi git_run --git-dir=/infra/git-bridge/state/import.git worktree add --detach \ /infra/git-bridge/state/import-candidate "$candidate_sha" >/dev/null [ -f "$candidate/docker-compose.yaml" ] || { echo "candidate test failed: docker-compose.yaml is missing" >&2 exit 1 } [ ! -e "$candidate/compose.yaml" ] || { echo "candidate test failed: a second Compose entrypoint exists" >&2 exit 1 } docker compose -f "$candidate/docker-compose.yaml" config -q find "$candidate" -type f -name '*.sh' -exec sh -n '{}' ';' git_run -C /infra/git-bridge/state/import-candidate ls-files -z >"$temp_dir/tracked.zlist" python3 - "$temp_dir/tracked.zlist" <<'PY' import pathlib import sys items = pathlib.Path(sys.argv[1]).read_bytes().split(b"\0") for raw in items: if not raw: continue path = raw.decode("utf-8", "strict") parts = path.split("/") unsafe = ( path == ".env" or path.endswith("/.env") or "secrets" in parts or path.endswith("auth.json") or path.startswith("git-bridge/state/") or path.startswith("gitea/") or path.startswith("postgres/data/") or path.startswith("litellm/chatgpt/") or path.startswith("litellm/xai_oauth/") ) if unsafe: raise SystemExit(f"candidate test failed: runtime or secret path is tracked: {path}") PY # This is deliberately a normal push. Existing branches must fast-forward; # Git rejects divergence and the protected Main branch is never a destination. git_run --git-dir=/infra/git-bridge/state/import.git push gitea \ "refs/remotes/github/$branch:refs/heads/$branch" verified_sha=$(git_run --git-dir=/infra/git-bridge/state/import.git ls-remote gitea \ "refs/heads/$branch" | awk 'NR == 1 { print $1 }') [ "$verified_sha" = "$candidate_sha" ] || { echo "import verification failed" >&2 exit 1 } status=$(curl -sS --config "$curl_config" -o "$response_file" -w '%{http_code}' \ --get \ --data-urlencode 'state=open' \ --data-urlencode 'base=main' \ --data-urlencode "head=$branch" \ "$api_base/repos/slh/infra/pulls") [ "$status" = "200" ] || { echo "failed to inspect existing Gitea pull requests (HTTP $status)" >&2 exit 1 } pr_url=$(python3 - "$response_file" "$branch" <<'PY' import json import sys for item in json.load(open(sys.argv[1], encoding="utf-8")): head = item.get("head") or {} if head.get("ref") == sys.argv[2]: print(item.get("html_url") or "") break PY ) if [ -z "$pr_url" ]; then python3 - "$request_file" "$branch" <<'PY' import json import sys branch = sys.argv[2] payload = { "base": "main", "head": branch, "title": f"Import GitHub branch: {branch}", "body": ( "Imported by git-bridge after branch ancestry, canonical Compose, " "Compose render, shell syntax, and tracked-secret boundary checks. " "No automatic merge or deployment was performed." ), } with open(sys.argv[1], "w", encoding="utf-8") as handle: json.dump(payload, handle) PY status=$(curl -sS --config "$curl_config" -o "$response_file" -w '%{http_code}' \ -H 'Content-Type: application/json' \ -X POST "$api_base/repos/slh/infra/pulls" \ --data-binary "@$request_file") [ "$status" = "201" ] || { echo "branch imported but Gitea PR creation failed (HTTP $status)" >&2 exit 1 } pr_url=$(python3 -c \ 'import json,sys; print(json.load(open(sys.argv[1])).get("html_url", ""))' \ "$response_file") fi [ -n "$pr_url" ] || { echo "Gitea PR URL is unavailable" >&2 exit 1 } git_run --git-dir=/infra/git-bridge/state/import.git worktree remove --force \ /infra/git-bridge/state/import-candidate >/dev/null rm -f "$askpass_file" "$curl_config" "$response_file" "$request_file" \ "$temp_dir/tracked.zlist" rmdir "$temp_dir" temp_dir= trap - EXIT INT TERM echo "GitHub branch import completed" echo "branch=$branch" echo "commit=$candidate_sha" echo "gitea_pull_request=$pr_url" echo "tests=passed" echo "force_push=false" echo "main_modified=false" echo "production_started=false"