feat(system): add native Termux Android support#765
Conversation
- Add 'android' to supported operating systems in system detection. - Implement source compilation for 'engram' via 'go install' on Android to ensure compatibility with Bionic libc. - Fix path resolution by mapping '/tmp' to '$PREFIX/tmp' in TermuxResolver. - Update engram installation directory to use home-relative paths on Android. - Improve OS support error messages to include Android.
- Fix AddToUserPath docstring to reflect Termux PATH persistence - Handle os.ReadFile errors explicitly in persistPathTermux (ignore only NotExist) - Add HOME/fallback chain in engramInstallDir for Android edge case - Clarify engramAssetURL comment: android→linux mapping is URL-only, not runtime - Replace manual os.Setenv/defer with t.Setenv in resolver_test - Remove leftover TDD 'RED:' markers from committed tests - Fix strategy.go comment about PlatformProfile.OS on Termux - Correct docs/platforms.md: Android uses source compilation, not release binaries - Fix spec scenario: detection uses GOOS=android, not TERMUX_VERSION
…eanup - Remove Termux from Linux distro detection (detectLinuxDistro, resolvePlatformProfile) GOOS=android is now the exclusive Termux detection path, eliminating the half-detected state where OS=linux + LinuxDistro=termux bypassed downstream Android-specific logic (installViaGo, engramInstallDir, PIE flags) - Remove dead android→linux mapping in engramAssetURL DownloadLatestBinary returns early via installViaGo for OS=android, making the goos rewrite unreachable - Fix leading blank line in persistPathTermux for new rc files exportCmd no longer starts with \n when writing to an empty file - Extract withSudo helper in ResolveDependencyInstall Replaces 3 duplicate if-sudo-prepend blocks with a single function - Update tests to match: distro matrix expects unknown for ID=termux, PIE test uses OS=android instead of OS=linux
…uards - Set GORELEASER_OS='' for android (no release assets exist) - Add fatal guard in detect_install_method when OS=android and Go is missing - Add fatal guard when FORCE_METHOD=binary on Android (glibc incompatible) - Add safety net in install_binary for empty GORELEASER_OS (defense in depth) - Clean up contradictory priority comments into single coherent block
- Tighten TermuxResolver prefix matching to exact directory boundaries (/usr/ not /usrbin, /etc/ not /etcetera) with boundary-safe checks - Add shell-unsafe character guard in persistPathTermux to prevent rc file corruption from backticks, quotes, dollar signs, or newlines - Document engramInstallDir android branch as defensive (currently unused since installViaGo writes to GOBIN) - Include full args in goInstallUpgrade error message for easier debugging of PIE-related failures on Termux - Add engramInstallDir android test case and resolver boundary tests
Integra main actual, preserva la detección Android como frontera canónica y documenta size:exception para el PR existente.
There was a problem hiding this comment.
Pull request overview
This PR adds first-class Android/Termux support across detection, installation, path resolution, and upgrade flows, enabling gentle-ai (and related components like engram/GGA) to run natively in Termux where standard Linux assumptions (glibc binaries, /usr/* paths, sudo, etc.) do not hold.
Changes:
- Introduces
GOOS=androidas the canonical boundary for Termux support and marks Android as a supported OS in system detection/guards. - Adds Termux-aware path resolution and PATH persistence behavior (shell rc file updates) plus corresponding unit tests.
- Forces source-based installs/upgrades with Android PIE flags (
-ldflags=-extldflags=-pie) in the installer script and Go upgrade/component logic; adds docs/spec artifacts.
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/install.sh | Detect Android/Termux; force go install with PIE flags; guard against binary downloads on Android. |
| e2e/docker-test.sh | Adds early failure when Docker isn’t available (e.g., Termux), clarifying E2E requirements. |
| docs/platforms.md | Documents Android (Termux) as supported via source compilation + Termux-specific notes. |
| internal/system/detect.go | Adds android support and maps it to a Termux-like profile (LinuxDistroTermux, apt). |
| internal/system/detect_test.go | Tests Android support and ensures Termux isn’t inferred via Linux os-release. |
| internal/system/guard.go | Updates supported-OS error message to include Android. |
| internal/system/guard_test.go | Updates assertions for the Android-inclusive guard error message. |
| internal/system/resolver.go | Adds PathResolver abstraction and Termux prefix resolver keyed on profile.OS == "android". |
| internal/system/resolver_test.go | Adds coverage for default vs Termux prefix mapping behavior and boundary cases. |
| internal/system/path.go | Adds Termux PATH persistence to ~/.bashrc / ~/.zshrc and testable pathGOOS. |
| internal/system/path_test.go | Adds Termux PATH persistence tests and GOOS-boundary test for isTermux(). |
| internal/installcmd/resolver.go | Centralizes rootless/sudo handling; makes GGA temp dir Termux-prefix-aware. |
| internal/installcmd/resolver_test.go | Adds tests for Termux “no sudo” behavior and Termux-prefixed GGA temp dir. |
| internal/components/engram/download.go | Uses source go install @vX.Y.Z with PIE flags on Android; adds install-dir selection logic. |
| internal/components/engram/download_test.go | Tests Android source install command/flags and managed install destination behavior. |
| internal/update/upgrade/strategy.go | Passes platform profile into go-install upgrade path and injects PIE flags for Android. |
| internal/update/upgrade/strategy_test.go | Adds test ensuring Android go-install upgrade includes PIE flags. |
| internal/update/upgrade/executor_test.go | Adjusts GGA managed-path tests to use canonical gga path helpers. |
| openspec/specs/termux-support/spec.md | Adds a spec defining Termux detection, prefix path resolution, and PATH persistence requirements. |
| openspec/changes/archive/2026-04-11-termux-compatibility/tasks.md | Adds archived task breakdown for the Termux compatibility effort. |
| openspec/changes/archive/2026-04-11-termux-compatibility/proposal.md | Adds archived proposal describing scope, approach, and risks for Termux support. |
| openspec/changes/archive/2026-04-11-termux-compatibility/exploration.md | Adds archived exploration notes for Termux gaps and recommended approach. |
| openspec/changes/archive/2026-04-11-termux-compatibility/design.md | Adds archived design describing the resolver abstraction and testing strategy. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if path == "/usr" || strings.HasPrefix(path, "/usr/") { | ||
| return filepath.Join(r.Prefix, strings.TrimPrefix(path, "/usr")) | ||
| } | ||
| if path == "/bin" || strings.HasPrefix(path, "/bin/") { | ||
| return filepath.Join(r.Prefix, "bin", strings.TrimPrefix(path, "/bin")) | ||
| } | ||
| if path == "/etc" || strings.HasPrefix(path, "/etc/") { | ||
| return filepath.Join(r.Prefix, "etc", strings.TrimPrefix(path, "/etc")) | ||
| } | ||
| if path == "/tmp" || strings.HasPrefix(path, "/tmp/") { | ||
| return filepath.Join(r.Prefix, "tmp", strings.TrimPrefix(path, "/tmp")) | ||
| } |
| // escapePowerShellString escapes a string for safe use inside a PowerShell | ||
| // single-quoted string literal by replacing each ' with '' (PowerShell's escape | ||
| // single-quoted string literal by replacing each ' with ” (PowerShell's escape | ||
| // sequence for a literal single quote within single-quoted strings). |
e9d3c5c to
4c9fe72
Compare
4c9fe72 to
5fea865
Compare
5fea865 to
14aaf19
Compare
| if path == "/usr" || strings.HasPrefix(path, "/usr/") { | ||
| return filepath.Join(r.Prefix, strings.TrimPrefix(path, "/usr")) | ||
| } | ||
| if path == "/bin" || strings.HasPrefix(path, "/bin/") { | ||
| return filepath.Join(r.Prefix, "bin", strings.TrimPrefix(path, "/bin")) | ||
| } | ||
| if path == "/etc" || strings.HasPrefix(path, "/etc/") { | ||
| return filepath.Join(r.Prefix, "etc", strings.TrimPrefix(path, "/etc")) | ||
| } | ||
| if path == "/tmp" || strings.HasPrefix(path, "/tmp/") { | ||
| return filepath.Join(r.Prefix, "tmp", strings.TrimPrefix(path, "/tmp")) | ||
| } |
| // Priority 2: GOPATH/bin | ||
| if gopath := engramGetenv("GOPATH"); gopath != "" { | ||
| return filepath.Join(gopath, "bin"), nil, nil | ||
| } |
14aaf19 to
cdda85c
Compare
| if path == "/usr" || strings.HasPrefix(path, "/usr/") { | ||
| return filepath.Join(r.Prefix, strings.TrimPrefix(path, "/usr")) | ||
| } | ||
| if path == "/bin" || strings.HasPrefix(path, "/bin/") { | ||
| return filepath.Join(r.Prefix, "bin", strings.TrimPrefix(path, "/bin")) | ||
| } | ||
| if path == "/etc" || strings.HasPrefix(path, "/etc/") { | ||
| return filepath.Join(r.Prefix, "etc", strings.TrimPrefix(path, "/etc")) | ||
| } | ||
| if path == "/tmp" || strings.HasPrefix(path, "/tmp/") { | ||
| return filepath.Join(r.Prefix, "tmp", strings.TrimPrefix(path, "/tmp")) | ||
| } |
| // Priority 2: GOPATH/bin | ||
| if gopath := engramGetenv("GOPATH"); gopath != "" { | ||
| return filepath.Join(gopath, "bin"), nil, nil | ||
| } |
| home := t.TempDir() | ||
| oldHome := os.Getenv("HOME") | ||
| oldTermuxVersion := os.Getenv("TERMUX_VERSION") | ||
| oldShell := os.Getenv("SHELL") | ||
| oldGOOS := pathGOOS | ||
|
|
||
| t.Cleanup(func() { | ||
| os.Setenv("HOME", oldHome) | ||
| os.Setenv("TERMUX_VERSION", oldTermuxVersion) | ||
| os.Setenv("SHELL", oldShell) | ||
| pathGOOS = oldGOOS | ||
| }) |
|
Good direction, and I see the justification in the body: this supersedes #277 because maintainer edits were not available, and the PR also carries conflict/test hygiene after #257. That said, it still trips the cognitive-load gate at ~1.1k lines. Please either split this into reviewable slices, ideally platform detection/path handling, install/update behavior, then docs/e2e, or add an explicit Once that is resolved, this remains the Termux candidate to review. |
Linked Issue
Closes #276
PR Type
type:feature- New feature (non-breaking change that adds functionality)Summary
Supersedes #277 because the contributor fork does not allow maintainer edits. This preserves the Termux support work from #277 and adds maintainer conflict/test hygiene fixes after #257 landed.
GOOS=androidas the canonical platform boundary.Changes
internal/system/*internal/installcmd/*internal/components/engram/*go installwith PIE flags.internal/update/upgrade/*scripts/install.she2e/docker-test.shdocs/platforms.md,openspec/...Maintainer fixes added after #277
shellcheckSC2059 ine2e/docker-test.sh.go installlog line inscripts/install.sh.gentle-aiself-upgrade routing so it uses versionedgo installwith PIE flags instead of looking for non-existent Android release assets.Test Plan
bash -n scripts/install.sh e2e/docker-test.shshellcheck scripts/install.sh e2e/docker-test.shgo test ./internal/system ./internal/installcmd ./internal/components/engram ./internal/update/upgradego test ./...Contributor Checklist
Closes #276)type:*labelCo-Authored-Bytrailers