Context
npm packages specify git dependencies using SSH URLs for GitHub authentication. Inside the proot environment, there is no SSH agent, no SSH keys, and no way to authenticate to GitHub over SSH. All git operations must use HTTPS (for public repos) or HTTPS with a token (for private repos).
The initial git wrapper used a single bash string substitution: ${URL/git@github.com:/https://github.com/}. This handled only the git@github.com:user/repo SCP-style format. In practice, npm and its transitive dependencies use at least three distinct URL formats:
git+ssh://git@github.com/user/repo.git-- the most common format in package.jsonssh://git@github.com/user/repo.git-- used by some older packagesgit@github.com:user/repo.git-- SCP-style, used by git submodules and some dependencies
A single sed command with multiple -e expressions handles all three in one pass. This is a LEGACY hack, but the URL rewriting logic was carried forward into the native migration's git configuration.
Implementation
The sed rewrite function, used inside the git wrapper (see hack 4 for the full argument parser):
#!/data/data/com.termux/files/usr/bin/bash
# Git wrapper — URL rewriting via sed
# Deployed to: $ROOTFS/usr/bin/git
REAL_GIT="/termux-prefix/bin/git"
rewrite_url() {
echo "$1" | sed \
-e 's|^git+ssh://git@github.com/|https://github.com/|' \
-e 's|^git+ssh://git@github\.com:|https://github.com/|' \
-e 's|^ssh://git@github.com/|https://github.com/|' \
-e 's|^git@github\.com:|https://github.com/|'
}The four sed expressions cover all observed patterns, including the rare variant where git+ssh:// uses a colon instead of a slash after the hostname.
For private repos, inject a GitHub personal access token into the URL:
rewrite_url_with_auth() {
local url="$1"
local token="${GITHUB_TOKEN:-}"
local rewritten
rewritten=$(echo "$url" | sed \
-e 's|^git+ssh://git@github.com/|https://github.com/|' \
-e 's|^git+ssh://git@github\.com:|https://github.com/|' \
-e 's|^ssh://git@github.com/|https://github.com/|' \
-e 's|^git@github\.com:|https://github.com/|')
if [ -n "$token" ]; then
rewritten=$(echo "$rewritten" | sed "s|https://github.com/|https://$token@github.com/|")
fi
echo "$rewritten"
}Integrated into the full wrapper with argument parsing (hack 4):
#!/data/data/com.termux/files/usr/bin/bash
REAL_GIT="/termux-prefix/bin/git"
if [ "$1" = "clone" ]; then
shift
URL="" ; DEST="" ; FLAGS=()
while [ $# -gt 0 ]; do
case "$1" in
--depth|--branch|-b|-c|--reference|--origin|-o)
FLAGS+=("$1" "$2"); shift 2 ;;
--mirror|--bare|--recursive|--no-checkout)
FLAGS+=("$1"); shift ;;
-*) FLAGS+=("$1"); shift ;;
*) if [ -z "$URL" ]; then URL="$1"; elif [ -z "$DEST" ]; then DEST="$1"; fi; shift ;;
esac
done
# Rewrite all SSH URL formats to HTTPS
URL=$(echo "$URL" | sed \
-e 's|^git+ssh://git@github.com/|https://github.com/|' \
-e 's|^git+ssh://git@github\.com:|https://github.com/|' \
-e 's|^ssh://git@github.com/|https://github.com/|' \
-e 's|^git@github\.com:|https://github.com/|')
if [ -n "$DEST" ]; then
exec "$REAL_GIT" clone "${FLAGS[@]}" "$URL" "$DEST"
else
exec "$REAL_GIT" clone "${FLAGS[@]}" "$URL"
fi
else
exec "$REAL_GIT" "$@"
fiDeploy into the proot rootfs:
ROOTFS="$PREFIX/var/lib/proot-distro/installed-rootfs/ubuntu"
# Write the script (use scp or cat > from Termux)
chmod +x "$ROOTFS/usr/bin/git"Verification
Test each URL format directly:
# From inside proot, or test the function in isolation
rewrite_url "git+ssh://git@github.com/user/repo.git"
# Output: https://github.com/user/repo.git
rewrite_url "ssh://git@github.com/user/repo.git"
# Output: https://github.com/user/repo.git
rewrite_url "git@github.com:user/repo.git"
# Output: https://github.com/user/repo.git
# HTTPS URLs pass through unchanged
rewrite_url "https://github.com/user/repo.git"
# Output: https://github.com/user/repo.gitEnd-to-end verification with npm:
# Inside proot, install a package known to have git dependencies
npm install some-package-with-git-deps 2>&1 | grep -i "clone"
# All clone URLs in the output should show https://Gotchas
- The
git+ssh://format sometimes uses a colon instead of a slash after the hostname:git+ssh://git@github.com:user/repo. This is technically invalid per RFC but appears in real package.json files. The second sed expression (\.com:) handles this edge case. - sed on Termux uses the busybox or GNU variant depending on what is installed. Stick to basic regex and
-eflags for each pattern. Extended regex (-E) works but is not needed here since all patterns are simple fixed prefixes. - The
.gitsuffix may or may not be present on the original URL. The sed rewrite does not add or remove it. Suffix normalization is handled separately in the argument parser (hack 4). - If a URL points to a non-GitHub host (e.g.,
git@gitlab.com:), these sed rules do not match and the URL passes through unchanged. This is correct behavior since only GitHub URLs need rewriting in this setup. - Order of sed
-eexpressions does not matter here because the four patterns are mutually exclusive (different prefixes). Only one can match per URL. - The rewrite happens in a subshell (
$(echo ... | sed ...)), which costs one fork. On proot where forks are expensive (ptrace overhead), this adds a few milliseconds per git clone. Acceptable since clones are infrequent.
Result
All three SSH URL formats are transparently rewritten to HTTPS. npm install succeeds for packages with git dependencies regardless of which URL format their maintainers chose. The rewriting is invisible to npm and to the git operations themselves. Combined with the argument parser (hack 4), this gives proot a fully functional git layer without any SSH infrastructure on the device.