환경: Android → F-Droid → Termux + Termux:API → proot-distro Debian → Claude Code
특징: Debian 안에서termux-notification등 Android API를 직접 호출 가능
Play Store 대신 F-Droid를 통해 Termux를 설치해야 합니다.
(Play Store 버전은 API 제한으로 제대로 동작하지 않습니다.)
- 브라우저에서 https://f-droid.org 접속 → APK 다운로드 후 설치
- F-Droid 앱 실행 후 저장소 업데이트
F-Droid에서 두 앱을 설치합니다:
| 앱 | 역할 |
|---|---|
| Termux | Android 위의 Linux 터미널 환경 |
| Termux:API | 알림·클립보드·TTS 등 Android API 브릿지 |
Termux:API 앱이 반드시 설치되어 있어야
termux-notification등이 동작합니다.
# 패키지 업데이트
pkg update && pkg upgrade -y
# 필수 패키지
pkg install -y proot-distro termux-api git curl jq zsh
# Debian 배포판 설치
proot-distro install debian~/debian-login.sh 로 저장합니다.
# 일반 진입 — 래퍼가 없을 때만 생성
./debian-login.sh
# 래퍼 강제 재생성 — 깨진 래퍼 복구 시
./debian-login.sh --forceproot-distro Debian 안에서는 Termux/Android 바이너리를 직접 실행할 때 권한 문제가 발생합니다:
/system/bin/am: proot 환경에서 읽기 권한이 없어Operation not permitted오류 발생amuser 문제:termux-am이getCurrentUser()호출 시INTERACT_ACROSS_USERS권한 부족으로 user-2로 fallback →SecurityException발생.--user 0자동 삽입으로 우회- Termux API 스크립트: 자체 shebang이 있으므로
exec bash script없이 직접 실행합니다
#!/bin/zsh
# debian-login.sh — Termux API 브릿지 포함 proot-distro Debian 진입 스크립트
# 사용법: ./debian-login.sh [--force]
# --force: 기존 래퍼를 모두 삭제하고 다시 생성
FORCE=0
[[ "$1" == "--force" ]] && FORCE=1
TERMUX_BIN="/data/data/com.termux/files/usr/bin"
TERMUX_APIS=(termux-audio-info termux-battery-status termux-brightness termux-call-log termux-camera-info termux-camera-photo termux-clipboard-get termux-clipboard-set termux-contact-list termux-dialog termux-download termux-file-editor termux-fingerprint termux-info termux-infrared-frequencies termux-infrared-transmit termux-job-scheduler termux-keystore termux-location termux-media-player termux-media-scan termux-microphone-record termux-notification termux-notification-list termux-notification-remove termux-open termux-open-url termux-reload-settings termux-sensor termux-share termux-sms-inbox termux-sms-list termux-sms-send termux-speech-to-text termux-storage-get termux-telephony-call termux-telephony-cellinfo termux-telephony-deviceinfo termux-toast termux-torch termux-tts-engines termux-tts-speak termux-url-opener termux-vibrate termux-volume termux-wake-lock termux-wake-unlock termux-wallpaper termux-wifi-enable termux-wifi-scaninfo termux-wifi-connectioninfo)
INSTALL_SCRIPT="#!/bin/sh
TERMUX_BIN='${TERMUX_BIN}'
FORCE=${FORCE}
"
# termux-* API 래퍼
for api in "${TERMUX_APIS[@]}"; do
INSTALL_SCRIPT+="
if [ \"\$FORCE\" = 1 ] || [ ! -f /usr/local/bin/${api} ]; then
printf '#!/bin/sh\nexec %s/%s \"\$@\"\n' \"\$TERMUX_BIN\" '${api}' > /usr/local/bin/${api}
chmod +x /usr/local/bin/${api}
fi"
done
INSTALL_SCRIPT+='
# xdg-open — URL은 termux-open-url, 파일은 termux-open
if [ "$FORCE" = 1 ] || [ ! -f /usr/local/bin/xdg-open ]; then
cat > /usr/local/bin/xdg-open << '\''XEOF'\''
#!/bin/sh
case "$1" in
http://*|https://*|mailto:*|ftp://*)
exec /usr/local/bin/termux-open-url "$@" ;;
*)
exec /usr/local/bin/termux-open "$@" ;;
esac
XEOF
chmod +x /usr/local/bin/xdg-open
fi
# xclip
if [ "$FORCE" = 1 ] || [ ! -f /usr/local/bin/xclip ]; then
cat > /usr/local/bin/xclip << '\''XCLIPEOF'\''
#!/bin/sh
if echo "$@" | grep -q -- -o; then
exec /usr/local/bin/termux-clipboard-get
else
exec /usr/local/bin/termux-clipboard-set
fi
XCLIPEOF
chmod +x /usr/local/bin/xclip
fi
# xsel
if [ "$FORCE" = 1 ] || [ ! -f /usr/local/bin/xsel ]; then
cat > /usr/local/bin/xsel << '\''XSELEOF'\''
#!/bin/sh
if echo "$@" | grep -qE -- "-o|--output|-p|--primary"; then
exec /usr/local/bin/termux-clipboard-get
else
exec /usr/local/bin/termux-clipboard-set
fi
XSELEOF
chmod +x /usr/local/bin/xsel
fi
# notify-send
if [ "$FORCE" = 1 ] || [ ! -f /usr/local/bin/notify-send ]; then
cat > /usr/local/bin/notify-send << '\''NOTIFYEOF'\''
#!/bin/sh
TITLE="${1:-Notification}"
BODY="${2:-}"
exec /usr/local/bin/termux-notification --title "$TITLE" --content "$BODY"
NOTIFYEOF
chmod +x /usr/local/bin/notify-send
fi
# espeak / espeak-ng
if [ "$FORCE" = 1 ] || [ ! -f /usr/local/bin/espeak ]; then
printf "#!/bin/sh\nexec /usr/local/bin/termux-tts-speak \"\$@\"\n" > /usr/local/bin/espeak
chmod +x /usr/local/bin/espeak
fi
[ "$FORCE" = 1 ] && rm -f /usr/local/bin/espeak-ng
[ ! -f /usr/local/bin/espeak-ng ] && ln -sf /usr/local/bin/espeak /usr/local/bin/espeak-ng
# pbcopy / pbpaste
if [ "$FORCE" = 1 ] || [ ! -f /usr/local/bin/pbcopy ]; then
printf "#!/bin/sh\nexec /usr/local/bin/termux-clipboard-set \"\$@\"\n" > /usr/local/bin/pbcopy
chmod +x /usr/local/bin/pbcopy
fi
if [ "$FORCE" = 1 ] || [ ! -f /usr/local/bin/pbpaste ]; then
printf "#!/bin/sh\nexec /usr/local/bin/termux-clipboard-get \"\$@\"\n" > /usr/local/bin/pbpaste
chmod +x /usr/local/bin/pbpaste
fi
# am — termux-am 경유, --user 0 자동 삽입
if [ "$FORCE" = 1 ] || [ ! -f /usr/local/bin/am ]; then
cat > /usr/local/bin/am << '\''AMEOF'\''
#!/bin/sh
# proot 환경에서 termux-am이 getCurrentUser() 호출 시
# INTERACT_ACROSS_USERS 권한 부족으로 user -2 fallback → SecurityException 발생.
# --user 미지정 시 자동으로 --user 0 을 삽입하여 우회.
case "$1" in
start|start-activity|startservice|start-service|stopservice|stop-service|broadcast)
# --user 가 이미 지정되어 있으면 그대로 통과
for arg in "$@"; do
case "$arg" in --user) exec /data/data/com.termux/files/usr/bin/am "$@" ;; esac
done
# --user 없으면 서브커맨드 뒤에 삽입
subcmd="$1"; shift
exec /data/data/com.termux/files/usr/bin/am "$subcmd" --user 0 "$@"
;;
*)
exec /data/data/com.termux/files/usr/bin/am "$@"
;;
esac
AMEOF
chmod +x /usr/local/bin/am
fi
if [ "$FORCE" = 1 ]; then
echo "[debian-login] Termux API wrappers force-reinstalled."
else
echo "[debian-login] Termux API wrappers ready."
fi
if ! command -v zsh > /dev/null 2>&1; then
apt-get install -y zsh > /dev/null 2>&1
fi
if ! grep -q "PROMPT=.*🐧" ~/.zshrc 2>/dev/null; then
echo "PROMPT='"'"'🐧 %~ %# '"'"'" >> ~/.zshrc
fi
'
proot-distro login debian --shared-tmp --no-arch-warning --env PROOT_NO_SECCOMP=1 -- bash -c "$INSTALL_SCRIPT"
proot-distro login debian --shared-tmp --no-arch-warning --env PROOT_NO_SECCOMP=1 -- bash -c '
clear
echo "🐧 Welcome to Debian on Android"
exec zsh
'chmod +x ~/debian-login.shDebian 진입 후:
# Node.js 설치 (nvm 권장)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
source ~/.zshrc
nvm install --lts
# Claude Code 설치
npm install -g @anthropic-ai/claude-code
# GitHub CLI (Gist/PR 연동용, 선택)
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | \
dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] \
https://cli.github.com/packages stable main" > /etc/apt/sources.list.d/github-cli.list
apt update && apt install -y gh{
"hooks": {
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "jq -r '.message // \"응답이 준비됐습니다\"' | { read -r msg; termux-notification --id 8270 --title \"Claude Code: $(basename $PWD)\" --content \"$msg\" --channel claude-code --priority high --vibrate 300 --sound --action \"am start --activity-single-top com.termux/.app.TermuxActivity\"; } 2>/dev/null || true"
}
]
}
]
}
}동작: Claude가 작업을 완료하면 Android 알림이 뜨고, 탭하면 Termux 앱으로 포커스 이동
claude-code 채널은 첫 알림 발송 시 자동 생성됩니다. 아래 명령으로 즉시 생성할 수 있습니다:
termux-notification \
--id 8270 \
--title "Claude Code" \
--content "알림 채널이 생성됐습니다." \
--channel claude-code \
--priority high \
--sound이후 Android 설정 → 앱 → Termux → 알림 → claude-code 에서 채널 이름·소리·진동 등을 커스터마이즈할 수 있습니다.
Android OS
├─ /system/bin/am ← 읽기 권한 없음 (proot에서 EPERM)
├─ /system/bin/app_process64 ← 실행 가능
│
└─ Termux (F-Droid)
├─ Termux:API ───────────────────────────────────────────┐
│ │ Android API
├─ termux-am (am.apk + app_process) │ (소켓 브릿지)
│ └─ /system/bin/am 의 권한 문제를 우회 │
│ │
├─ proot-distro │
│ └─ Debian (proot) │
│ │ │
│ ├─ /usr/local/bin/termux-* ◄────────────────────┘
│ │ (exec → Termux bin 직접 호출)
│ │
│ ├─ /usr/local/bin/am
│ │ └─ exec → termux-am → app_process → am.apk
│ │ (/system/bin/am 을 거치지 않고 동작)
│ │
│ ├─ 호환 shim
│ │ ├─ xdg-open → termux-open / termux-open-url
│ │ ├─ pbcopy → termux-clipboard-set
│ │ ├─ pbpaste → termux-clipboard-get
│ │ ├─ xclip → termux-clipboard-*
│ │ ├─ notify-send → termux-notification
│ │ └─ espeak → termux-tts-speak
│ │
│ └─ Claude Code
│ └─ Notification Hook
│ └─ termux-notification → Android 알림
│ └─ --action "am start ..." (탭 시 Termux 복귀)
│
└─ ~/debian-login.sh (진입 스크립트, 래퍼 자동 설치)
proot 환경은 호스트(Android)와 네트워크 네임스페이스를 공유합니다.
Debian 안에서 띄운 서버에 Android 브라우저에서 바로 접속할 수 있습니다.
# Debian 안에서 서버 실행
python3 -m http.server 8080
# 또는
npx serve .
# 또는
flask run --port 5000Android 브라우저 주소창에 http://localhost:8080 입력하면 바로 접속됩니다.
xdg-open shim 덕분에 터미널에서 URL을 열면 Android 기본 브라우저가 실행됩니다:
xdg-open http://localhost:8080
# 또는
xdg-open https://github.com개발 서버 실행 후 자동으로 브라우저를 여는 도구(Vite, Next.js 등)도 그대로 동작합니다:
# Next.js — 빌드 후 브라우저 자동 오픈
npx next dev --open
# Vite
npx vite --open
0.0.0.0바인딩 불가: proot 환경에서는 권한 문제로0.0.0.0에 바인딩이 안 됩니다.
반드시127.0.0.1(localhost) 로 바인딩해야 합니다.# 잘못된 예 — 실패 python3 -m http.server 8080 # 기본값이 0.0.0.0 # 올바른 예 python3 -m http.server 8080 --bind 127.0.0.1 flask run --host=127.0.0.1 --port=5000 node server.js # listen('127.0.0.1', 8080) 으로 지정포트 충돌 주의: Android에서 이미 사용 중인 포트가 있을 수 있습니다.
ss -tlnp | grep <포트>로 확인 후 사용하세요.
debian-login.sh 가 설치하는 Linux/macOS 호환 래퍼입니다. 기존 스크립트나 도구가 수정 없이 동작합니다.
| 커맨드 | 실제 동작 | 용도 |
|---|---|---|
am |
termux-am (via app_process) |
Android Activity Manager — proot에서 /system/bin/am 접근 불가 문제 우회 |
pbcopy |
termux-clipboard-set |
macOS 호환 — 표준입력을 클립보드로 복사 |
pbpaste |
termux-clipboard-get |
macOS 호환 — 클립보드 내용을 출력 |
xclip -i / xclip -o |
clipboard set / get | X11 클립보드 도구 호환 |
xsel --input / xsel --output |
clipboard set / get | X11 클립보드 도구 호환 |
xdg-open <url> |
termux-open-url |
브라우저 등 외부 앱으로 열기 |
xdg-open <file> |
termux-open |
파일을 연관 앱으로 열기 |
notify-send <title> <body> |
termux-notification |
데스크톱 알림 도구 호환 |
espeak / espeak-ng |
termux-tts-speak |
TTS 음성 합성 |
사용 예시:
# 클립보드
echo "hello" | pbcopy
pbpaste
# 알림
notify-send "빌드 완료" "테스트 통과"
# 브라우저로 열기
xdg-open https://github.com
# TTS
espeak "작업이 완료됐습니다"
# Activity Manager (proot에서도 동작)
am start -n com.termux/.app.TermuxActivityproot 내부에서는 두 가지 권한 문제가 발생합니다:
/system/bin/am접근 불가 — 읽기 권한 없음 (EPERM)termux-am의 user 문제 —getCurrentUser()가INTERACT_ACROSS_USERS권한 부족으로 user-2로 fallback →SecurityException
debian-login.sh의 am 래퍼는 두 문제를 모두 해결합니다:
/system/bin/am대신termux-am호출 (app_process + am.apk)--user미지정 시 자동으로--user 0삽입
기존 (실패): am start ... → /system/bin/am (EPERM)
am start ... → termux-am → getCurrentUser() → user -2 → SecurityException
수정 (동작): am start ... → am 래퍼 → termux-am start --user 0 ... → 정상 동작
래퍼가 깨졌다면 ./debian-login.sh --force로 재생성하거나 수동으로 생성합니다:
cat > /usr/local/bin/am << 'EOF'
#!/bin/sh
case "$1" in
start|start-activity|startservice|start-service|stopservice|stop-service|broadcast)
for arg in "$@"; do
case "$arg" in --user) exec /data/data/com.termux/files/usr/bin/am "$@" ;; esac
done
subcmd="$1"; shift
exec /data/data/com.termux/files/usr/bin/am "$subcmd" --user 0 "$@"
;;
*)
exec /data/data/com.termux/files/usr/bin/am "$@"
;;
esac
EOF
chmod +x /usr/local/bin/am- Termux:API 앱이 설치되어 있는지 확인 (F-Droid에서 설치)
--shared-tmp옵션으로 proot에 진입했는지 확인 (소켓 통신에 필요)- Termux:API 앱의 배터리 최적화를 해제했는지 확인
Debian /usr/local/bin/ 에 자동 설치되는 Android API 커맨드:
termux-audio-info termux-battery-status termux-brightness
termux-call-log termux-camera-info termux-camera-photo
termux-clipboard-get termux-clipboard-set termux-contact-list
termux-dialog termux-download termux-file-editor
termux-fingerprint termux-info termux-location
termux-media-player termux-media-scan termux-microphone-record
termux-notification termux-notification-list termux-notification-remove
termux-open termux-open-url termux-sensor
termux-share termux-sms-send termux-speech-to-text
termux-toast termux-torch termux-tts-speak
termux-vibrate termux-volume termux-wake-lock
termux-wallpaper termux-wifi-enable termux-wifi-connectioninfo
proot 환경에서 termux-am은 getCurrentUser() 호출 시 INTERACT_ACROSS_USERS 권한이 없어 user -2로 fallback되고, SecurityException이 발생합니다.
SecurityException: Permission Denial: startActivityAsUser asks to run as user -2
but is calling from uid u0a744; this requires
android.permission.INTERACT_ACROSS_USERS_FULL
/usr/local/bin/am 래퍼가 --user가 지정되지 않은 경우 자동으로 --user 0을 삽입하여 우회합니다:
#!/bin/sh
case "$1" in
start|start-activity|startservice|start-service|stopservice|stop-service|broadcast)
# --user 가 이미 지정되어 있으면 그대로 통과
for arg in "$@"; do
case "$arg" in --user) exec /data/data/com.termux/files/usr/bin/am "$@" ;; esac
done
# --user 없으면 서브커맨드 뒤에 삽입
subcmd="$1"; shift
exec /data/data/com.termux/files/usr/bin/am "$subcmd" --user 0 "$@"
;;
*)
exec /data/data/com.termux/files/usr/bin/am "$@"
;;
esac수정 전: am start -n com.termux/.app.TermuxActivity
→ getCurrentUser() → user -2 → SecurityException
수정 후: am start --user 0 -n com.termux/.app.TermuxActivity
→ user 0 명시 → 정상 동작
F-Droid에서 Termux:Tasker 플러그인을 설치하면 Tasker에서 Termux 스크립트를 실행할 수 있습니다.
# Termux (proot 밖)에서 실행
mkdir -p ~/.termux/tasker/~/.termux/tasker/run-in-debian.sh:
#!/data/data/com.termux/files/usr/bin/bash
# Tasker에서 Debian proot 안의 명령을 실행하는 브릿지 스크립트
# Tasker Plugin → Termux:Tasker → run-in-debian.sh
# Arguments: 실행할 명령어
CMD="${1:-echo 'No command specified'}"
proot-distro login debian \
--shared-tmp \
--no-arch-warning \
--env PROOT_NO_SECCOMP=1 \
-- bash -c "$CMD"~/.termux/tasker/get-claude-token.sh:
토큰을 읽고, 만료되었으면 claude -p를 실행하여 자동 갱신합니다.
#!/data/data/com.termux/files/usr/bin/bash
# Claude Code OAuth 토큰을 stdout으로 출력
# 만료 시 claude -p 를 한번 실행하여 토큰 자동 갱신 후 재시도
CRED="/data/data/com.termux/files/usr/var/lib/proot-distro/installed-rootfs/debian/root/.claude/.credentials.json"
TOKEN=$(jq -r '.claudeAiOauth.accessToken' "$CRED" 2>/dev/null)
if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
echo "ERROR: no token"
exit 1
fi
# 토큰 유효성 검사 (usage API 호출)
HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' \
-H "Authorization: Bearer $TOKEN" \
-H "User-Agent: claude-code/2.0.31" \
-H "anthropic-beta: oauth-2025-04-20" \
https://api.anthropic.com/api/oauth/usage)
if [ "$HTTP_CODE" != "200" ]; then
# 토큰 만료 → claude -p 로 갱신
proot-distro login debian --shared-tmp --no-arch-warning --env PROOT_NO_SECCOMP=1 \
-- bash -lc '/root/.local/bin/claude -p "hi" --max-turns 1 2>/dev/null' >/dev/null 2>&1
TOKEN=$(jq -r '.claudeAiOauth.accessToken' "$CRED" 2>/dev/null)
fi
echo "$TOKEN"chmod +x ~/.termux/tasker/run-in-debian.sh
chmod +x ~/.termux/tasker/get-claude-token.sh- Task 생성 → Plugin → Termux:Tasker
- Configuration → Executable 선택 (예:
get-claude-token.sh) - 실행 후 결과는
%stdout변수에 저장됨 - 필요 시
Variable Set으로%ClaudeToken = %stdout매핑
| Tasker 트리거 | 실행 스크립트 | 용도 |
|---|---|---|
| 시간 기반 (매일 새벽) | run-in-debian.sh "npm run build" |
자동 빌드 |
| Wi-Fi 연결 시 | get-claude-token.sh |
토큰 갱신 확인 |
| 알림 클릭 | run-in-debian.sh "git status" |
상태 확인 |
proot-distro login시--shared-tmp필수 — 소켓 통신에 필요--env PROOT_NO_SECCOMP=1권장 — 일부 시스템 호출 차단 방지- Termux:API 앱이 백그라운드에서 살아있어야 API 호출 가능 (배터리 최적화 해제 권장)
- Termux:Tasker 스크립트는 반드시
~/.termux/tasker/안에 위치해야 함 - Claude Code 알림 채널
claude-code는 첫 실행 후 Android 설정에서 커스터마이즈 가능 proot환경이므로 systemd 불가 → 서비스는 직접 포그라운드 실행