Skip to content

Instantly share code, notes, and snippets.

@sng2c
Last active April 11, 2026 18:37
Show Gist options
  • Select an option

  • Save sng2c/3f886946543f4d5cc037abdb94790999 to your computer and use it in GitHub Desktop.

Select an option

Save sng2c/3f886946543f4d5cc037abdb94790999 to your computer and use it in GitHub Desktop.
Claude Code on Android: Termux + proot-distro Debian 환경 구성 가이드

Claude Code on Android

Termux + proot-distro Debian 완전 구성 가이드

환경: Android → F-Droid → Termux + Termux:API → proot-distro Debian → Claude Code
특징: Debian 안에서 termux-notification 등 Android API를 직접 호출 가능


1단계. F-Droid 설치

Play Store 대신 F-Droid를 통해 Termux를 설치해야 합니다.
(Play Store 버전은 API 제한으로 제대로 동작하지 않습니다.)

  1. 브라우저에서 https://f-droid.org 접속 → APK 다운로드 후 설치
  2. F-Droid 앱 실행 후 저장소 업데이트

2단계. Termux + Termux:API 설치

F-Droid에서 두 앱을 설치합니다:

역할
Termux Android 위의 Linux 터미널 환경
Termux:API 알림·클립보드·TTS 등 Android API 브릿지

Termux:API 앱이 반드시 설치되어 있어야 termux-notification 등이 동작합니다.


3단계. Termux 기본 세팅

# 패키지 업데이트
pkg update && pkg upgrade -y

# 필수 패키지
pkg install -y proot-distro termux-api git curl jq zsh

# Debian 배포판 설치
proot-distro install debian

4단계. debian-login.sh — Termux API 브릿지 포함 진입 스크립트

~/debian-login.sh 로 저장합니다.

사용법

# 일반 진입 — 래퍼가 없을 때만 생성
./debian-login.sh

# 래퍼 강제 재생성 — 깨진 래퍼 복구 시
./debian-login.sh --force

proot 내부 권한 문제와 래퍼 설계

proot-distro Debian 안에서는 Termux/Android 바이너리를 직접 실행할 때 권한 문제가 발생합니다:

  • /system/bin/am: proot 환경에서 읽기 권한이 없어 Operation not permitted 오류 발생
  • am user 문제: termux-amgetCurrentUser() 호출 시 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.sh

5단계. Debian 안에서 Claude Code 설치

Debian 진입 후:

# 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

6단계. Claude Code 설정 — Notification 훅

~/.claude/settings.json

{
  "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 앱으로 포커스 이동

알림 채널 생성 (최초 1회)

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 5000

Android 브라우저 주소창에 http://localhost:8080 입력하면 바로 접속됩니다.

xdg-open으로 브라우저 자동 실행

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 <포트> 로 확인 후 사용하세요.


호환 shim 커맨드

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.TermuxActivity

트러블슈팅: proot 내부 권한 문제

am 명령이 Operation not permitted 또는 SecurityException으로 실패

proot 내부에서는 두 가지 권한 문제가 발생합니다:

  1. /system/bin/am 접근 불가 — 읽기 권한 없음 (EPERM)
  2. termux-am의 user 문제getCurrentUser()INTERACT_ACROSS_USERS 권한 부족으로 user -2로 fallback → SecurityException

debian-login.sham 래퍼는 두 문제를 모두 해결합니다:

  • /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-* 명령이 동작하지 않을 때

  1. Termux:API 앱이 설치되어 있는지 확인 (F-Droid에서 설치)
  2. --shared-tmp 옵션으로 proot에 진입했는지 확인 (소켓 통신에 필요)
  3. 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

am 래퍼: --user 0 자동 삽입

proot 환경에서 termux-amgetCurrentUser() 호출 시 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 명시 → 정상 동작

Termux:Tasker 연동

F-Droid에서 Termux:Tasker 플러그인을 설치하면 Tasker에서 Termux 스크립트를 실행할 수 있습니다.

사전 준비

# Termux (proot 밖)에서 실행
mkdir -p ~/.termux/tasker/

스크립트 1: Debian 안에서 명령 실행

~/.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"

스크립트 2: Claude Code 토큰 가져오기

~/.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

Tasker 설정

  1. Task 생성Plugin → Termux:Tasker
  2. Configuration → Executable 선택 (예: get-claude-token.sh)
  3. 실행 후 결과는 %stdout 변수에 저장됨
  4. 필요 시 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 불가 → 서비스는 직접 포그라운드 실행
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment