본문으로 건너뛰기
5분 AI.
TRENDSHIFT DAILY #27 딥다이브 · 2026-10-23 분석

dumbtv 딥다이브
— TV의 제어권을 되찾는 두 겹의 설계

기기 관리 도구

빠른 판단

무엇
ADB·DNS 필터·자체 런처로 Fire TV의 서비스와 홈 화면을 조정하는 비루트 기기 정리 도구
이럴 때
Fire TV의 광고·추적 관련 기능을 줄이고 앱·HDMI 중심 홈 화면과 변경 복구 절차를 구성할 때
Fire TV의 광고·추적 서비스를 줄이는 Bash 도구와, 앱·입력·시계만 보여 주는 Java 홈 화면. 작은 저장소를 따라가며 기기 자동화, Android 시스템 연동, 복구 설계, 외부 장치 없는 테스트를 배웁니다. 소스 설명은 커밋 12c21f7 기준이며, 파일 경로는 모두 저장소 루트 기준입니다.
목차 · 코드를 따라 읽는 11단계
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

TV를 새로 만드는 대신, 기존 운영체제의 동작과 첫 화면을 조정합니다

dumbtv는 ADB로 Fire TV의 불필요한 서비스를 비활성화하고 DNS 필터와 자체 홈 화면을 연결하는, 루트 권한 없는 기기 정리 도구입니다. Fire OS를 지우거나 다른 운영체제를 설치하지 않습니다. 앱 실행과 HDMI 입력은 기존 Android 기반 시스템의 기능을 이용합니다.

GitHub의 주 언어는 Java이지만 저장소 전체를 이해하려면 두 부분을 함께 봐야 합니다. 컴퓨터에서 실행되는 scripts/는 TV의 상태를 바꾸고, TV 안에서 실행되는 launcher/는 사용자가 보는 홈 화면을 그립니다. Java 서버나 웹 프론트엔드가 있는 프로젝트는 아닙니다.

이 글을 관통하는 비유

집을 허물지 않고, 관리 규칙과 현관을 바꾸기

Fire OS가 집이라면 Bash 스크립트는 어떤 설비를 켜 둘지 정하는 관리인입니다. DNS 필터는 특정 주소로 나가는 연락을 막는 교환원이고, Java 런처는 자주 쓰는 방으로 연결되는 간결한 현관입니다.

집의 기초인 부트로더와 /system은 그대로 둡니다. 그래서 이 프로젝트의 재미는 화면을 예쁘게 만드는 코드뿐 아니라, 남의 기기를 다룰 때 경계를 지키는 코드에도 있습니다.

1-1. 세 가지 수단이 서로 다른 문제를 맡습니다

수단실제 구현배울 질문
패키지·설정 조정scripts/debloat.sh, lockdown.sh삭제하지 않고 기능을 끌 수 있을까?
통신 제한scripts/dnsfilter.sh가 외부 필터 앱을 설치·설정꺼지지 않는 서비스는 어떻게 다룰까?
홈 화면 교체launcher/src/tv/dumbtv/home/시스템이 HOME을 쥐고 있으면 어떻게 연동할까?
먼저 알아둘 용어
ADB · Android Debug Bridge
컴퓨터에서 Android 기기에 명령을 보내는 개발 도구입니다. 화면을 원격으로 클릭하는 도구만을 뜻하지 않습니다. 패키지 상태 조회, 파일 전송, 앱 설치, 시스템 설정 조회·변경도 할 수 있습니다. 연결 승인은 TV를 가진 사람이 해야 합니다.

이 저장소의 핵심은 광고 제거 명령 하나가 아니라, 변경·검증·복구를 한 흐름으로 묶는 방법입니다.

읽는 방법
TV가 없어도 설계의 대부분을 공부할 수 있습니다

소스와 설정 파일을 읽으며 명령의 흐름을 종이 위에 그려 보세요. TV 없이도 사용자 선택에 따른 분기와 변경·검증·복구의 연결을 공부할 수 있습니다.

2왜 주목받는가

화제의 맥락과, 코드에서 확인할 수 있는 장점을 나누어 봅니다

2-1. 스마트 TV의 불편을 작은 공개 도구로 풀었습니다

README에서 저자는 느려진 Fire TV Edition TV를 ADB와 코딩 에이전트로 정리한 경험이 출발점이라고 설명합니다. 남은 메모리가 129 MB였다는 일화와 언론 보도 링크도 README에 있습니다. 이는 저자의 경험과 README에 기록된 배경이며, 다른 TV에서도 같은 성능 개선이 나타난다는 뜻은 아닙니다.

2026-10-23 TrendShift Daily #27에 선정되었으며, 2026-10-23 GitHub 기준 스타 1,312개, 포크 113개입니다. 생성일은 2026-10-18입니다. 이 수치만으로 스타의 증가 속도나 특정 보도가 인기를 끌어올렸는지는 알 수 없습니다.

코드 관점에서 눈에 띄는 조합은 세 가지입니다. 루트 없이 동작하는 도구, 원문을 읽을 수 있는 작은 Java 런처, 그리고 기기별 실패 사례를 YAML로 남기는 카탈로그입니다. 초보자는 기능과 운영 지식을 같은 저장소에 담는 방법을 볼 수 있습니다.

2-2. 비슷한 해결책과 무엇이 다른가

접근·예시주로 해결하는 문제dumbtv에서 더 볼 부분
홈 화면 교체
예: Projectivy
TV의 첫 화면과 앱 탐색을 바꿉니다. 자체 런처와 함께 패키지 정리·DNS 설정·결과 검증을 연결합니다. 다른 런처의 전체 기능 우열을 뜻하지 않습니다.
네트워크 DNS 필터 도메인 단위로 광고·추적 주소의 이름 해석을 막습니다. 필터를 TV 안에 둡니다. 라우터 설정을 바꾸지 않고 해당 TV에 적용하는 구성입니다.
수동 ADB 정리 사용자가 패키지와 설정을 하나씩 조정합니다. 기종별 목록, 유지 목록, 금지 목록, 복구 절차와 테스트를 함께 읽을 수 있습니다.
Android TV용 도구
예: cleanyourtv
Android TV·Google TV 계열을 정리하는 별도 프로젝트입니다. dumbtv README가 대안으로 안내합니다. dumbtv의 Fire OS 자동화와 지원 범위를 구별해야 합니다.

위 표는 각 접근이 해결하는 문제의 범위를 비교합니다. Projectivy 연동의 흔적은 launcher/install.sh의 OLD_GUARD에서도 확인됩니다. 설치 과정은 기존 Projectivy 접근성 서비스를 교체 대상으로 다루며, 일반적인 모든 런처와의 공존을 보장하지는 않습니다.

2-3. 작아서 더 잘 보이는 설계 선택

launcher/README.md는 APK 54 KB, 홈 화면 표시 중 메모리 약 60~70 MB, 영상 재생 중 약 37 MB를 제시합니다. 특정 1.4 GB RAM 기기에서의 저자 측정값입니다. APK 크기가 작다고 실행 중 메모리도 같은 크기가 되는 것은 아닙니다.

dumbtv의 교육적 장점은 작은 코드에서도 권한, 호환성, 복구, 테스트가 서로 연결되는 모습을 볼 수 있다는 점입니다.

효과를 읽을 때
런처의 네트워크 차단과 TV 전체의 추적 차단은 다릅니다

런처에 INTERNET 권한이 없다는 사실은 Manifest에서 확인됩니다. 다른 시스템 앱과 스트리밍 앱의 통신까지 사라진다는 뜻은 아닙니다. DNS 차단은 도메인 단위이며, 실제 앱 정상 동작과 차단 효과는 기기에서 확인해야 합니다.

3기술 스택 전체 지도

웹 서비스의 백엔드·프론트엔드 대신, 컴퓨터의 제어부와 TV의 실행부로 읽습니다

3-1. 백엔드에 해당하는 제어부: Bash와 ADB

package.json, Cargo.toml, pyproject.toml, go.mod, pom.xml, Gradle 설정, Dockerfile은 커밋 12c21f7의 파일 목록에 없습니다. 서버 프레임워크·DB·컨테이너 스택을 억지로 대응시키면 구조를 잘못 이해하게 됩니다.

구성 요소소스에서 확인한 값역할·주의점
Bashscripts/lib.sh: 3.2 호환 명시macOS 기본 Bash까지 고려합니다. 실제 실행 버전을 고정한 잠금 파일은 없습니다.
ADBADB=${ADB:-adb}실기기 제어 경계이자 테스트 대체 지점입니다. Platform-Tools 버전은 고정하지 않습니다.
텍스트 도구awk, sed, grep, curl패키지 목록과 명령 출력을 해석합니다. 각 도구의 버전 잠금은 없습니다.
curl 옵션fetch(): --retry-all-errors소스 주석은 curl 7.71+ 옵션임을 밝힙니다. Bash 호환성만으로 모든 환경이 충족되지는 않습니다.

3-2. 프론트엔드에 해당하는 TV 화면: Java와 Android Views

Home은 Activity, Guard는 AccessibilityService, Keeper는 Service를 확장합니다. React·웹뷰로 화면을 그리는 구성이 아닙니다. 화면은 Home.build() 등의 Java 코드에서 Android View를 조합합니다.

항목실제 설정초보자가 구별할 것
빌드 JDKCI: Temurin 17컴파일 도구를 실행하는 Java 버전입니다.
Java 컴파일javac --release 8Java 8 언어·라이브러리 기준을 사용합니다. Android API 호환성 검사는 별도로 필요합니다.
Android 플랫폼MIN_SDK=25, SDK의 platforms/android-25/android.jar기본 빌드는 API 25를 기준으로 컴파일하고 설치 최소 API도 25로 지정합니다.
대상 API--target-sdk-version 28최소 지원 API와 구별해야 합니다. 최신 API를 겨냥한 앱이라는 뜻은 아닙니다.
외부 Java 라이브러리별도 의존성 선언 없음AndroidX·Compose·Play Services 없이 Android 표준 API를 사용합니다.
런처 버전VERSION=1.2, VERSION_CODE=3launcher/version.env가 빌드·설치의 공통 기준입니다.
버전 숫자 읽기
JDK 버전 · Java 대상 버전 · Android API 수준
JDK 17은 작업대의 버전, Java 8은 만드는 코드의 기준, API 25는 사용할 Android 부품 목록이라고 생각하세요. --release 8 하나로 모든 Android 기종의 API 호환성이 보장되는 것은 아닙니다. 이 레포는 오래된 android.jar와 실제 기기 보고도 함께 사용합니다.

3-3. 인프라·빌드: 작은 APK의 제작 과정을 드러냅니다

launcher/build.sh의 호출 순서는 아래와 같습니다. CI는 build-tools;34.0.0을 설치하지만, 로컬 스크립트는 설치된 build-tools 디렉토리를 정렬해 마지막 버전을 고릅니다. 따라서 로컬 빌드 도구가 CI와 항상 같다고 단정할 수 없습니다.

launcher/res/ + launcher/AndroidManifest.xml
  -> aapt2 compile / link       # 리소스 묶기, R.java 생성
Java 소스 + Android SDK의 platforms/android-25/android.jar
  -> javac --release 8          # .class 생성, 경고를 오류로 처리
  -> d8 --min-api 25           # Android 실행 형식 classes.dex
  -> zip + zipalign            # APK 조립과 정렬
  -> apksigner sign / verify   # 서명하고 서명 유효성 확인
  -> launcher/build/dumbtv-home.apk

서명 키는 이후 업데이트에도 같아야 합니다. install.sh는 서명이 다르면 INSTALL_FAILED_UPDATE_INCOMPATIBLE을 별도로 안내합니다. 파일 해시와 APK 서명은 각각 파일 일치 여부와 설치·업데이트의 서명 관계를 다루는 서로 다른 장치입니다.

3-4. 외부 구성 요소와 재현성의 경계

의존 대상실제 고정값·정책확인 파일
personalDNSfilterF-Droid APK versionCode 1506103과 SHA-256 고정scripts/dnsfilter.sh
런처 릴리스 APKlauncher-v1.2 경로와 SHA-256 고정launcher/version.env, install.sh
DNS 기본 설정상류 master의 설정 파일을 다운로드CONF_URL: 커밋 미고정
HaGeZi 목록jsDelivr의 @latestHAGEZI_URL: 내용이 변할 수 있음
Python카탈로그 CI는 3.12, 테스트는 표준 unittest.github/workflows/validate.yml
PyYAMLpip install pyyaml, 버전 미고정카탈로그 검증용이며 TV 런타임 의존성이 아님
GitHub Actionscheckout@v4, setup-java@v4, setup-python@v5동일 워크플로에서 확인

실행 파일의 해시를 고정해도, 함께 가져오는 설정과 차단 목록까지 고정되는 것은 아닙니다. dnsfilter.sh는 설정 치환 후 필수 문자열을 검사해 상류 형식 변화 일부를 감지합니다. 이는 완전한 의존성 잠금과는 다릅니다.

4아키텍처 심화 분석

시스템 전체를 먼저 그리고, 네 가지 설계 패턴을 함수까지 따라갑니다

4-1. 컴퓨터·TV·외부 다운로드를 나누어 보기

[사용자의 컴퓨터]
  scripts/configure.sh ----> dumbtv.conf
  data/ ------------------> scripts/lib.sh
                              |
  scripts/all.sh              | ADB 명령·파일 전송
    preflight                 v
    debloat              [Fire TV / Fire OS]
    lockdown               PackageManager / Settings / AppOps
    dnsfilter              NetworkPolicy / Accessibility
    launcher                    |
    youtube (선택)              +-- tv.dumbtv.home
    verify                      |     Home / Catalog / Config
        |                       |     Guard (Fire OS 7·8)
        v                       |     Keeper (Fire OS 6)
  report/                       |     Input -> TvView -> HDMI
  설정 스냅샷·변경 기록         |
                                +-- personalDNSfilter (외부 앱)
                                      로컬 VPN -> DNS 필터링

[외부 배포처: 설치 시 컴퓨터가 접근]
  GitHub Releases -> 런처 APK / 선택한 SmartTube APK
  F-Droid --------> personalDNSfilter APK
  HaGeZi·상류 설정 -> 필터 목록·설정 -> 조합 후 TV로 전송

런처가 외부에서 목록을 받아 오는 구조가 아닙니다. APK와 필터 자료를 내려받는 일은 컴퓨터의 스크립트가 맡습니다. report/와 dumbtv.conf도 컴퓨터 쪽 파일입니다. TV의 런처 설정인 home.conf와 이름이 비슷하니 구별하세요.

4-2. 패턴 ① 데이터로 정책을 분리하고, 실행 경계에서 다시 검사하기

어떤 패키지를 끌지는 data/fireos/packages.txt, 절대 끄지 않을 대상은 never-disable.txt, 비활성화가 거부되는 대상은 protected.txt가 설명합니다. 스크립트에 패키지 이름을 길게 박아 넣는 대신 데이터와 실행 절차를 나눈 것입니다.

lib.sh의 fos()는 API 28 미만에서 Fire OS 6 전용 파일이 있으면 그 파일을 선택하고, 없으면 data/fireos/로 돌아갑니다. cleanup_list()는 사용자가 Alexa를 끄겠다고 했을 때만 추가 목록을 합칩니다.

# scripts/debloat.sh의 실제 루프 일부
for p in $(cleanup_list); do
  if protected_by_policy "$p"; then
    printf '  %-46s SKIP (never-disable list)\n' "$p"; continue
  fi
  if keep_pkg "$p"; then
    printf '  %-46s SKIP (keep list)\n' "$p"; continue
  fi
  if ! present "$p"; then
    printf '  %-46s absent\n' "$p"; absent=$((absent+1)); continue
  fi
  disable_pkg "$p"
done

이 루프에서 정책 금지, 사용자 유지, 실제 설치 여부를 차례로 검사합니다. protected_by_policy()는 첫 필드인 패키지 식별자만 비교합니다. 앞서 list_of()가 주석과 행 끝 공백, 빈 줄을 제거합니다.

이름이 비슷하지만 다릅니다
never-disable · protected · keep
never-disable은 프로젝트 정책상 건드리지 않을 핵심 기능, protected는 운영체제가 끄기를 거부하는 패키지, keep은 사용자가 유지하겠다고 선택한 대상입니다. 세 집합이 같은 뜻은 아닙니다.

disable_pkg()는 명령 출력에 따라 disabled, PROTECTED, system-disabled, SUSPECT를 나눕니다. lockdown.sh는 UID를 읽지 못하거나 UID가 10000 미만이면 패키지별 제한을 건너뜁니다. 낮은 시스템 UID를 제한하면 같은 UID를 공유하는 다른 구성 요소에도 영향을 줄 수 있기 때문입니다.

비유

작업 목록에 문 이름이 적혀 있어도, 공사 직전에 “비상구인가, 주인이 쓰는 방인가, 실제 존재하는 문인가”를 다시 보는 방식입니다. 목록을 신뢰하는 것과 실행 시 검사를 생략하는 것은 별개의 결정입니다.

4-3. 패턴 ② 단계별 실행과 보상 작업으로 복구 설계하기

all.sh는 preflight → configure(필요 시) → debloat → lockdown → dnsfilter → launcher → youtube(선택) → verify를 호출합니다. 각 주요 단계가 실패하면 || exit 1로 다음 단계를 멈춥니다. 하지만 앞에서 바꾼 내용이 자동으로 원상 복귀되는 DB 트랜잭션은 아닙니다.

변경복구 코드보존되는 정보의 범위
설정 값 조정preflight.sh 스냅샷 + restore_value()secure·global을 기록하되, 복구는 목록에 있는 키를 대상으로 합니다.
패키지 비활성화restore_list(), pm enable모든 패키지의 변경 전 enabled 상태를 저장하는 방식은 아닙니다.
HOME 연동launcher/install.sh --undoGuard·Keeper 경로를 해제하고 기본 홈을 다시 엽니다.
DNS 연동always-on 설정 제거, 필터 비활성화앱 자체를 삭제하지는 않습니다.

preflight.sh는 첫 스냅샷이 있으면 덮어쓰지 않습니다. 변경 뒤 값을 다시 “원래 값”으로 기록하면 복구 기준을 잃기 때문입니다. restore_value()는 스냅샷의 값이 비어 있으면 번들 기본값을 사용하므로, 원래 없던 키와 빈 값까지 완벽히 재현하는 복구는 아닙니다.

복구 순서도 중요합니다. restore.sh는 설정과 HOME 복구를 먼저 하고 DNS 필터 비활성화를 뒤로 미룹니다. VPN을 끄면 네트워크 ADB 연결이 끊어질 수 있기 때문입니다. 이후 adb_reconnect()로 연결을 회복하고 재부팅합니다.

reboot_wait()는 sys.boot_completed=1만 보지 않고 이전과 이후의 /proc/sys/kernel/random/boot_id를 비교합니다. 명령이 무시되어 TV가 계속 켜져 있는 상태를 새 부팅으로 오인하지 않도록 한 것입니다.

외부 기기를 바꾸는 자동화에서는 명령의 성공 문구보다, 다음에 읽은 실제 상태가 더 중요한 증거입니다.

4-4. 패턴 ③ 같은 목표에 다른 전략: Guard와 Keeper

목표는 HOME을 눌렀을 때 dumbtv 화면이 나타나게 하는 것입니다. 소스 주석과 기기 보고에 따르면 Fire OS는 Amazon 홈을 보호하고 HOME 키도 먼저 처리합니다. 따라서 런처가 단순히 HOME 키 이벤트를 가로채는 구조로 설명하면 틀립니다.

경로감지 방법실제 전환
Fire OS 7·8Guard.onAccessibilityEvent()가 특정 Amazon 홈 창 확인launchWhenCovered() → home() → 알람의 PendingIntent
Fire OS 6Keeper.check()·top()가 UsageStats 조회launch()가 기존 작업을 moveToFront(), 옮길 작업이 없거나 예외가 나면 알람
Fire OS 7·8
  창 변경 이벤트
    -> 패키지 + HomeActivity_vNext 확인
    -> 가림 화면 표시
    -> 첫 그리기 직전 콜백 또는 150 ms 대체 경로
    -> AlarmManager -> Home
    -> Home의 표시 콜백 -> 가림 화면 제거

Fire OS 6
  250 ms 간격 조회
    -> 최근 4초 UsageEvents에서 전면 앱·클래스 확인
    -> 런처의 기존 작업 앞으로 이동 시도
    -> 옮길 작업이 없거나 예외가 나면 알람으로 실행

Guard에는 반복 실행 제한 REPEAT_MS=1000, 가림 화면 상한 COVER_MAX_MS=1500, 그리기 콜백의 대체 지연 LAUNCH_FALLBACK_MS=150이 있습니다. install.sh의 set_futurity()는 알람 최소 대기 설정에 min_futurity=0을 넣습니다. 이는 Android 전체에 통하는 일반 권장값이 아니라 이 구현이 택한 시스템 연동입니다.

Keeper는 HandlerThread에서 POLL_MS=250 간격으로 조회하고, DEBOUNCE_MS=1500으로 잦은 재전환을 막습니다. 패키지뿐 아니라 홈 Activity 클래스까지 확인해 같은 패키지의 설정 화면을 빼앗지 않습니다. Keeper.start() 자체는 API 26 이상이면 시작하지 않는 방어 조건도 갖습니다.

guard.xml은 canRetrieveWindowContent="false"입니다. 접근성 서비스를 쓰지만 화면 본문을 읽는 기능은 요청하지 않고 창 변경 이벤트를 이용합니다. “권한이 없다”는 소개 문구와 “시스템이 접근성 서비스를 활성화한다”는 동작은 다른 층위의 이야기입니다.

4-5. 패턴 ④ 비동기 결과의 수명 관리와 오래된 응답 버리기

Home은 io와 colorIo라는 단일 작업자 Executor를 두고, UI 수정은 메인 Looper의 Handler로 보냅니다. 앱 목록을 갱신하는 loadApps()에는 요청 세대 번호가 있습니다.

// Home.loadApps(): 이미지 캐시 재사용 부분을 생략한 발췌
int generation = ++appsGeneration;
io.execute(() -> {
    List<Item> list = Catalog.apps(this);
    ui.post(() -> {
        if (generation != appsGeneration || isDestroyed()) return;
        // 기존 이미지 캐시를 옮긴 뒤 목록을 반영합니다.
        apps.all = list;
        render(apps);
    });
});

요청 A 뒤에 B가 생겼다면 A 결과는 이미 낡은 결과입니다. 화면이 파괴된 뒤에도 반영하지 않습니다. 현재 작업자는 순차 실행되지만, UI에 쌓인 이전 결과를 무시하는 데에도 이 검사가 의미가 있습니다. onDestroy()는 작업자를 종료하고 등록한 수신기도 해제합니다.

예외도 읽어야 합니다. 첫 화면의 firstApps()는 Future.get()으로 기다리고, 화면에 들어올 만큼의 배너를 즉시 읽습니다. 입력 행만 먼저 나타나는 깜빡임을 피하려는 선택입니다. “이미지는 전부 비동기”라고 요약하면 이 시작 지연과 완성된 첫 화면 사이의 절충을 놓칩니다.

코드 읽기 질문
정상 경로보다 늦게 도착하는 결과를 상상해 보세요

앱이 삭제되는 동안 목록 조회가 끝나거나, 배경 이미지 A를 읽는 중 사용자가 B를 선택하면 어떻게 될까요? appsGeneration, wallKey, isDestroyed()가 각각 무엇을 막는지 설명할 수 있으면 비동기 코드의 큰 줄기를 이해한 것입니다.

5디렉토리 구조 해부

파일 위치를 알면 실행 흐름과 책임의 경계를 함께 볼 수 있습니다
dumbtv/
|-- AGENTS.md                  에이전트의 실기기 작업 규칙
|-- README.md                  사용자 안내와 프로젝트 주장
|-- scripts/                   컴퓨터에서 실행하는 Bash 14개
|   |-- lib.sh                 ADB·설정·목록·재연결 공통 함수
|   |-- all.sh                 단계 순서와 실패 전파
|   |-- configure.sh           사용자 선택 -> dumbtv.conf
|   |-- preflight.sh           기기 조회와 최초 설정 스냅샷
|   |-- debloat.sh             패키지 비활성화·선택 앱 제거
|   |-- lockdown.sh            보호 패키지의 배경 실행·데이터 제한
|   |-- dnsfilter.sh           외부 필터 설치와 목록 구성
|   |-- launcher.sh            런처 설치기 연결
|   |-- verify.sh              실제 상태 확인
|   `-- restore.sh             복구 절차
|-- data/
|   |-- fireos/                Fire OS 7·8 정책과 공통 설정
|   |-- fireos6/               Fire OS 6 전용 목록
|   |-- androidtv/             수동 작업용 참고 목록
|   |-- blocklists/            추가·검증용 도메인 목록
|   `-- apps.txt               앱 이름과 패키지 식별자 대응
|-- launcher/
|   |-- src/tv/dumbtv/home/    Java 구현 13개
|   |-- res/                   아이콘·문자열·테마·접근성 XML
|   |-- tests/                 FocusColorTest.java
|   |-- AndroidManifest.xml    앱 구성·기능·권한 선언
|   |-- version.env            버전과 릴리스 APK 해시
|   |-- build.sh               Android SDK 도구 직접 호출
|   |-- install.sh             APK 검증·설치·HOME 연동
|   `-- release-check.sh       버전·해시 일관성 확인
|-- tests/                     Python 테스트와 가짜 ADB
|-- catalog/
|   |-- devices/               실제 보고 22개 + 템플릿
|   |-- schema.json            기기 보고 형식
|   `-- validate.py            YAML 해석과 일부 스키마 규칙 검사
|-- docs/                      원리·문제 해결·다른 TV 안내
`-- .github/workflows/
    `-- validate.yml           카탈로그·APK·스크립트 CI

트리는 핵심 경로를 추린 것입니다. discover.sh는 모르는 기기의 패키지를 조사하고, contribute.sh는 기기 보고 작성을 돕습니다. youtube.sh는 사용자 동의에 따른 별도 선택 경로입니다.

5-1. Java 파일은 이름 그대로 역할을 나눕니다

파일책임처음 볼 함수
Home.java화면 구성, 갱신, 포커스, 수명 관리onCreate(), render(), open()
Catalog.java앱·입력 조회, 노출 여부, 순서apps(), visible(), saveOrder()
Config.java파일 설정·기본값·배경 이미지 선택refresh(), save(), wallpaper()
Tile.java·Grid.java타일 그리기·배치Android의 그리기·측정 콜백
Guard.java·Keeper.java세대별 HOME 전환onAccessibilityEvent(), check()
Input.java입력 앱 선택·내장 HDMI 화면intent(), onStart(), onStop()
FocusColor.java아이콘에서 배경용 대표색 추출representative(), tint()
Options.java·Ui.java설정 화면·공통 시각 요소Config.Choice와 연결해서 읽기

5-2. 커밋 기준 코드 규모

대상파일 수물리 줄 수
Java 구현132,529
Java 테스트132
Bash 전체172,106 = scripts 1,744 + launcher 362
Python 전체111,052 = 테스트·가짜 장치 805 + 카탈로그 검증 247
XML6144
YAML·YML271,550

계수는 .git을 제외하고 해당 확장자의 파일을 읽어 빈 줄·주석을 포함해 센 값입니다. 커밋 12c21f7의 소스 파일 기준이며, 실행 코드만의 줄 수나 GitHub 언어 비율과는 다릅니다. Java 합계는 2,561줄이고, Git 추적 파일은 134개입니다.

기기 보고 22개 중 최상위 status 값은 tested 6개, partial 16개입니다. README에 이름이 나왔다고 모두 전체 자동화 검증을 통과한 기기는 아닙니다. 기기별 보고 상태가 다른 펌웨어의 호환성까지 보장하지는 않습니다.

6학습 포인트 (기술별)

기술 이름을 외우기보다, 구체적인 문제와 구현 선택을 연결합니다

6-1. Bash: 출력도 인터페이스입니다

configure.sh의 yn()은 질문을 표준 오류에, 답인 0 또는 1을 표준 출력에 씁니다. ALEXA=$(answer ...)가 설명 문장까지 값으로 받아 버리지 않도록 한 것입니다. 터미널에서 보기 좋은 출력과 프로그램이 읽기 좋은 출력을 나누는 간단한 사례입니다.

lib.sh의 dev()는 장치 명령을, acmd()는 dry-run을 고려한 변경 명령을 감쌉니다. dev_ok()는 명령이 성공 종료했더라도 출력에 특정 연결 오류 문자열이 있으면 중단합니다. all.sh의 run()은 Bash 3.2에서 빈 배열과 set -u가 만나는 예외까지 처리합니다.

# scripts/lib.sh의 변경 명령 경계
acmd() {
  if [[ $DRY_RUN == 1 ]]; then note "[dry-run] $*"; return 0; fi
  dev "$@"
}

실습 아이디어: “계획 출력”과 “기기 명령”을 서로 다른 색의 펜으로 표시하세요. 모든 변경이 이 함수 하나를 거치는지 찾아보면, dnsfilter.sh의 실제 설치·파일 전송처럼 별도 분기에서 dry-run을 검사하는 경로도 보입니다.

6-2. Android: 앱 검색과 입력 화면의 수명

Catalog.apps()는 TV용 CATEGORY_LEANBACK_LAUNCHER를 먼저 모으고 일반 CATEGORY_LAUNCHER를 추가합니다. LinkedHashMap의 패키지 키로 중복을 줄이고, Collator로 이름을 정렬합니다. shown()은 일반 앱의 기본 노출도 별도로 판단합니다.

Input.intent()는 입력 URI를 받을 앱과 설정 모드를 보고 TV의 앱에 넘길지, 내부 TvView를 사용할지 결정합니다. 내부 화면의 onStart()는 입력을 연결하고 오디오 포커스를 요청하며, onStop()는 view.reset()과 오디오 포커스 반납으로 자원을 놓습니다.

실습 아이디어: HDMI 화면에서 HOME 또는 BACK을 누른 순간을 그려 보세요. 화면을 숨기는 것만으로 충분한지, 입력 세션과 오디오를 함께 해제해야 하는지 설명해 보세요. 화면 수명과 하드웨어 자원 수명이 만나는 지점입니다.

6-3. 파일 설정: 작은 제품에도 저장 실패를 생각하기

Config는 home.conf의 key=value를 읽고 알 수 없는 키도 맵에 보존합니다. get(Choice)는 허용된 선택지인지 검사해 기본값으로 돌아갑니다. refresh()는 수정 시각과 파일 길이로 변경을 감지하므로 내용 전체 해시 비교는 아닙니다.

# TV 런처의 home.conf를 설명하기 위한 예시 데이터
accent=amber
layout=grid
clock=24h
background=black
inputs=on

save()는 home.conf.tmp에 먼저 쓰고 renameTo()로 교체합니다. 실제 설정 파일을 직접 덮어쓰다가 중간에 끊기는 문제를 줄이려는 방식입니다. 다만 디스크 동기화까지 보장하는 저널이나 완전한 충돌 복구 체계를 구현한 것은 아닙니다.

실습 아이디어: layout=unknown, 값이 빈 줄, 주석, 알 수 없는 키를 각각 넣었다고 가정하고 read()와 get()의 결과를 표로 적어 보세요. 컴퓨터의 dumbtv.conf는 Bash로 source하는 파일이라 이 파서와 다르다는 점도 확인하세요.

6-4. 이미지·색상: UI 문제를 작은 순수 계산으로 분리하기

FocusColor.from()은 Drawable 복사본을 48×48 픽셀로 그린 뒤 representative()에 넘깁니다. 알파가 96 미만인 픽셀은 제외하고, 12개 그룹 중 채도가 있는 색에 가중치를 더 줍니다. 밝기가 높으면 RGB 크기를 줄여 흰 글씨를 올릴 어두운 배경색을 만듭니다.

FocusColorTest.java는 흰 바탕 75개와 붉은색 25개로 구성한 아이콘에서도 붉은색이 선택되는지 확인합니다. 단순 전체 평균이면 바탕의 흰색에 끌려가기 쉬운 문제를 테스트 데이터로 표현한 것입니다.

실습 아이디어: “평균색”과 “대표색”의 차이를 네모 100개로 그려 보세요. tint()가 RGB는 유지하고 알파를 61로 바꾸는 이유도 계산해 보세요. 61/255는 약 24%이며, 채도와 투명도는 서로 다른 개념입니다.

6-5. DNS·네트워크: 차단과 정상 동작을 함께 검증하기

compose_blocklist()는 내려받은 목록, 추가 목록, 선택한 Alexa 목록을 합칩니다. PRIME=1이면 aiv-delivery, aiv-cdn, 특정 메시징 호스트를 제외합니다. expected_blocked()도 같은 사용자 선택에 맞춰 검증 대상을 바꿉니다.

verify.sh는 차단 대상의 ping 첫 줄에서 0.0.0.0 또는 127.0.0.1을 찾고, 허용 대상에는 잘못된 차단·이름 해석 실패가 없는지 검사합니다. ping의 응답 주소를 관찰하는 휴리스틱이며, 영상 재생·로그인·모든 통신 경로를 시험하는 것은 아닙니다.

차단 도구의 좋은 검증은 막혀야 할 주소뿐 아니라, 계속 열려 있어야 할 주소도 확인합니다.

실습 아이디어: Prime·Alexa 두 선택의 네 조합을 만들고 차단 목록과 검증 기대값이 함께 바뀌는지 추적하세요. 네트워크 VPN 필터는 이 구성에서 DNS를 걸러 주는 수단입니다. 외부 VPN 사업자가 익명성을 제공한다는 의미로 읽으면 안 됩니다.

6-6. 테스트·CI: 장치 대신 명령 경계를 바꾸기

tests/test_debloat.py는 환경변수 ADB에 tests/fake_tv.py 경로를 넣습니다. 가짜 장치는 JSON 상태를 읽고 받은 명령을 로그에 남깁니다. 스크립트 본체를 대대적으로 바꾸지 않고도 “무엇을 비활성화했는가”를 확인하는 구조입니다.

test_restore.py는 필터 종료 때 연결이 끊기는 상황과 재부팅 요청이 무시되는 상황을 다룹니다. test_verify.py는 접근성 서비스가 설정상 켜졌어도 실제 바인딩되지 않은 경우를 구분합니다. Python 테스트 파일 8개의 test_ 메서드는 정적으로 세어 51개입니다.

CI는 APK를 만든 뒤 권한 선언도 검사합니다. 모든 권한 항목에 maxSdkVersion=27이 있어야 하며, 항목이 3개를 넘거나 이 상한이 없는 항목이 있으면 실패합니다. 이 검사는 APK의 권한 선언을 검사하며, 실제 기기에서의 동작 검증은 별도로 필요합니다.

7하드웨어 / 시스템 요구사항

소스 읽기, TV 사용, 직접 빌드의 요구사항은 서로 다릅니다
목적필요한 것근거·범위
소스 학습텍스트 편집기, Git 체크아웃TV·GPU·API 키가 없어도 이 글의 코드 경로를 읽을 수 있습니다.
일반 사용컴퓨터, ADB, TV에 도달하는 네트워크README: macOS·Linux·Windows의 WSL 또는 Git Bash
자동 정리 대상Fire OS 6·7·8 기기need_supported_fireos(): 제조사 Amazon*, API 25 이상 검사
기기 승인ADB 디버깅 활성화와 TV 리모컨첫 연결 허용 창은 사람이 승인해야 합니다.
APK 직접 빌드JDK 17 권장, Android SDK API 25, build-toolsCI 기준 build-tools 34.0.0. 단순 릴리스 설치에는 빌드 환경이 보통 필요 없습니다.
카탈로그 개발Python, 전체 YAML 파싱에는 PyYAMLCI Python 3.12. PyYAML이 없으면 한 단계의 중첩까지만 읽는 제한된 파서 사용
추가 다운로드컴퓨터의 인터넷 연결선택한 APK, DNS 목록과 설정을 외부 배포처에서 받습니다.

need_supported_fireos()는 API 상한을 강제로 막지 않습니다. fireos_name()은 API 30 초과를 “Fire OS 8보다 새로움·미시험”으로 표시합니다. 코드가 실행을 허용하는 범위와 검증했다고 말할 수 있는 범위는 구별해야 합니다.

7-1. 플랫폼별 지원 범위를 먼저 확인하세요

플랫폼이 저장소가 안내하는 범위
Fire OS 6별도 패키지 목록, UsageStats Keeper 경로. API 25이며 세 가지 구형 권한 선언이 적용됩니다.
Fire OS 7·8접근성 Guard 경로. 구형 권한 세 개는 API 27 상한 때문에 적용되지 않습니다.
Fire OS 5 이하자동 정리 스크립트가 거부합니다. 루트 우회 절차를 제공하는 프로젝트가 아닙니다.
Android TV·Google TV참고 목록과 수동 보고가 있습니다. Fire OS 정리 스크립트의 자동 지원으로 해석하면 안 됩니다.
Samsung·LG·Roku 등docs/OTHER-TVS.md에서 별도로 다룹니다. Android ADB 절차를 그대로 적용할 대상이 아닙니다.

README는 최소 RAM·CPU·저장 공간의 수치 요구사항을 제시하지 않습니다. README의 메모리 실측은 성능 사례이지 최소 사양표가 아닙니다. HDMI 입력의 실제 제공 여부도 TV와 스트리밍 스틱이 다르므로 앱 목록처럼 모두에게 있다고 가정하면 안 됩니다.

자동화가 멈추는 실제 지점
Fire OS 8은 DNS 설정에서 리모컨 조작이 필요할 수 있습니다

dnsfilter.sh는 API 30 이상에서 앱 전용 데이터 폴더에 접근하지 못하면 Android/media로 목록을 보내고, 필터 앱에서 그 목록을 선택하라고 안내한 뒤 종료합니다. 이 분기에서는 일반 경로의 DNS 라우팅 설정을 모두 적용할 수 없다고 소스가 밝힙니다. “실행 한 번으로 모든 기기가 자동 설정된다”는 설명은 맞지 않습니다.

기기별 메뉴와 대화상자는 catalog/devices/를 먼저 읽으세요. 기본 목록은 OTA 업데이트 서버도 막으므로 업데이트 정책까지 함께 바뀐다는 점을 이해해야 합니다.

8직접 해볼 수 있는 실습 과제

TV 없이 가능한 과제 5개 · 제출물과 완료 기준을 명확히 정합니다

아래 과제는 읽기·계산·별도 모형 작성으로 진행할 수 있습니다. 원본 코드를 보존하고 실습 파일은 별도 연습 폴더에 두세요. 실제 ADB 대신 명령을 기록하는 모형을 사용하면 TV 없이도 실패 상황을 다룰 수 있습니다.

과제 1 · 실행 순서 읽기

사용자 선택을 흐름도로 바꾸기 입문

scripts/all.sh, configure.sh, lib.sh를 읽고 설정 파일 없음·기존 설정 있음·YOUTUBE=1의 세 경우를 그리세요.

제출물: 실행 단계 순서표와 기본 선택 여섯 가지. 완료 기준: Prime·Alexa·런처·DNS는 기본 1, YouTube는 0, 제거 앱은 없음으로 설명하고, 기존 설정이 있으면 질문을 다시 하지 않는 이유를 말할 수 있어야 합니다.

과제 2 · 목록 파싱

주석과 공백이 있어도 금지 목록 지키기 입문

list_of()와 protected_by_policy()를 읽고 아래 세 줄에서 비교할 식별자를 직접 적으세요. 이어서 별도 모형 함수로 “주석 제거 → 빈 줄 제거 → 첫 필드 선택”을 작성해 보세요.

com.example.home       # 홈 화면
# 설명만 있는 줄
com.example.input      # 입력 서비스

제출물: 결과 com.example.home, com.example.input과 모형 함수. 완료 기준: 행 전체 문자열 비교가 왜 실패할 수 있는지 설명하고, 빈 줄은 패키지로 취급하지 않아야 합니다.

과제 3 · 조건 조합과 검증

Prime·Alexa 정책의 진리표 만들기 중급

compose_blocklist(), expected_blocked(), tests/test_blocklist.py를 읽으세요. PRIME과 ALEXA의 네 조합에서 CDN 예시 호스트·Alexa 호스트·업데이트 호스트가 어떻게 달라지는지 정리하세요.

제출물: 4행 표와 허용 호스트 두 개를 잘못 막았을 때의 예상 실패 메시지. 완료 기준: Prime 예외가 모든 Amazon 주소를 허용하는 것은 아니며, 정책 변경과 검증 기대값 변경이 한 쌍임을 설명해야 합니다.

과제 4 · 실패를 재현하는 모형

VPN 종료 뒤 연결이 끊기는 가짜 장치 설계 중급

tests/fake_tv.py와 test_restore.py의 상태와 명령 로그를 읽으세요. 종이 또는 별도 프로그램으로 연결됨 → 필터 종료 → 연결 끊김 → 재연결의 상태 전이를 표현하세요.

제출물: 상태표와 올바른 복구 명령 순서에 대한 검증 조건 세 개. 완료 기준: 설정 쓰기가 필터 종료보다 먼저이며, 끊긴 연결에서 다음 명령을 성공 처리하지 않아야 합니다. 실제 ADB 대신 받은 명령을 기록하는 인터페이스만 사용하세요.

과제 5 · 복구 정확도 높이기

변경 전 상태를 보존하는 기록 형식 제안 고급

preflight.sh와 restore.sh의 차이를 바탕으로, 패키지 활성 상태와 설정의 존재 여부까지 저장하는 JSON 기록 형식을 설계하세요. 예를 들어 값 없음과 빈 문자열을 다른 상태로 표현해야 합니다.

{
  "package": "com.example.metrics",
  "enabled_before": false,
  "setting_before": {"exists": false, "value": null},
  "change_applied": true
}

제출물: 기록 형식, 적용·복구 의사코드, 중간 실패 사례 두 개. 완료 기준: 원래 꺼져 있던 패키지를 복구 과정에서 켜지 않고, 쓰기 성공이 확인된 작업만 보상하며, 복구를 두 번 요청해도 결과가 안정적이어야 합니다.

실습의 완료 기준을 “명령을 실행했다”가 아니라 “실패해도 지켜야 할 상태를 설명했다”로 잡아 보세요.

9관련 기술 심화 학습 로드맵

6주 동안 읽는 범위를 넓히되, 매주 설명 가능한 결과물을 남깁니다
주차주제·읽을 코드학습 결과물
1주차 Bash의 인자·출력·종료 코드. lib.sh의 dev(), acmd(), confirm() 조회 명령과 변경 명령을 분류한 표, dry-run의 동작 설명
2주차 정책 데이터와 외부 기기 상태. debloat.sh, data/fireos/, test_debloat.py 금지·유지·부재·보호·성공의 분기표, 각 분기의 입력 예시
3주차 Android Activity·Intent·View. Home, Catalog, Input 앱 실행과 입력 전환의 호출도, 시작·중지 시 자원 목록
4주차 스레드·콜백·포커스. loadApps(), loadArt(), focusBackground() 작업자와 UI 스레드의 시간축, 오래된 결과를 버리는 세 조건
5주차 DNS·VPN·관찰 가능한 상태. compose_blocklist(), dnsfilter.sh, verify.sh 정책 진리표, 차단 성공과 앱 정상 동작을 구별하는 검증 계획
6주차 복구·테스트 대역·배포 검증. restore.sh, fake_tv.py, CI, build.sh 실패 복구 설계서, APK 제작 단계 그림, 해시와 서명의 차이 설명

9-1. 읽은 코드를 일반 원리로 확장하기

1~2주차를 마치면 CLI의 입력·출력 계약과 정책 검사로 확장하세요. 3~4주차는 Android 공식 Activity 수명 문서를 옆에 두고, 콜백마다 언제 등록하고 해제하는지 표시하면 좋습니다. 5~6주차에는 “설정이 존재한다”와 “기능이 실제 동작한다” 사이에 어떤 관찰이 필요한지 적어 보세요.

학습 비유

레포를 읽는 일은 완성된 기계를 분해하는 것과 비슷합니다. 부품 이름만 외우면 다시 조립할 수 없습니다. 각 선이 왜 연결되어 있고, 연결이 끊어졌을 때 무엇으로 알아차리는지까지 알아야 합니다.

9-2. 다음 단계로 넘어갈 때의 질문

파일을 많이 읽는 것보다, 한 기능의 입력·변경·증거·복구 경로를 끝까지 연결하는 편이 더 깊은 학습이 됩니다.

10핵심 키워드 사전

설명과 함께, 이 저장소에서 그 용어가 나타나는 자리를 적었습니다
용어쉬운 설명
ADB컴퓨터와 Android 기기 사이의 명령 통로입니다. lib.sh의 dev()가 감쌉니다.
Fire OS이 레포가 주로 다루는 Amazon의 Android 기반 TV 시스템입니다. 세대별 API와 제한이 다릅니다.
패키지설치된 앱·서비스를 식별하는 단위입니다. com.amazon.tv.launcher 같은 이름으로 다룹니다.
disable-user지정 사용자에서 패키지를 꺼 두는 동작입니다. APK 파일을 시스템에서 삭제하는 것과 다릅니다.
루트 권한시스템 전체에 강한 변경 권한을 가진 사용자 권한입니다. dumbtv의 정상 경로는 이를 요구하지 않습니다.
dm-verity시스템 영역의 무결성을 확인하는 장치입니다. 레포는 /system을 수정하지 않는 경계를 둡니다.
AppOps앱의 특정 동작을 허용하거나 제한하는 Android 제어 장치입니다. 여기서는 배경 실행·VPN 활성화 등에 사용합니다.
UID앱이 실행되는 사용자 식별값입니다. 여러 구성 요소가 공유할 수 있어 네트워크 제한의 영향 범위를 살펴야 합니다.
DNS도메인 이름을 주소로 바꾸는 과정입니다. 이 레포는 목록에 있는 이름이 차단 주소를 받는지 확인합니다.
로컬 VPN기기 안의 가상 네트워크 경로입니다. 이 구성에서는 personalDNSfilter가 DNS를 다루는 데 사용합니다.
OTA네트워크를 통한 기기 업데이트입니다. 기본 DNS 정책에는 업데이트 서버 차단도 들어 있습니다.
ACR화면 내용의 특징을 이용해 시청 콘텐츠를 식별하는 기술입니다. README가 줄이려는 추적 기능 중 하나입니다.
런처앱을 선택하고 실행하는 홈 화면입니다. Home은 이 레포가 직접 만든 런처입니다.
Intent앱이나 시스템에 특정 화면·작업을 요청하는 메시지입니다. 앱 실행과 TV 입력 URI 전달에 쓰입니다.
ActivityAndroid에서 사용자 화면을 맡는 구성 요소입니다. 시작·일시 중지·종료 등의 수명이 있습니다.
접근성 서비스사용자 지원을 위한 Android 서비스입니다. Guard는 창 변경 이벤트를 HOME 연동에 이용합니다.
PendingIntent나중에 시스템이 실행할 수 있도록 준비한 요청입니다. Guard.home()은 알람에 이를 등록합니다.
UsageStats앱 사용 이벤트를 조회하는 Android 기능입니다. Keeper.top()이 전면 앱을 알아내는 데 씁니다.
TvViewTV 입력 서비스가 제공하는 영상을 표시하는 Android View입니다. Input의 내부 입력 화면에 사용합니다.
메인 스레드화면을 갱신하고 입력을 처리하는 실행 흐름입니다. 오래 걸리는 일을 맡기면 화면이 멈출 수 있습니다.
테스트 대역실제 외부 구성 요소를 대신하는 모형입니다. fake_tv.py는 ADB 대신 상태와 명령을 기록합니다.
스냅샷변경 전 상태의 기록입니다. 이 레포에서는 일부 설정 복구의 기준이며 TV 전체 백업은 아닙니다.
보상 작업이미 적용한 변경을 반대 동작으로 되돌리는 절차입니다. restore.sh에서 구체적으로 볼 수 있습니다.
멱등성같은 작업을 반복해도 결과가 더 달라지지 않는 성질입니다. 모든 스크립트가 완전히 이를 보장한다는 뜻은 아닙니다.
SHA-256파일 내용의 일치 여부를 비교하는 해시입니다. 신뢰하는 예상 해시와 비교해야 의미가 있습니다.
APK 서명Android가 앱 설치·업데이트의 서명 관계를 확인하는 장치입니다. 빌드 키가 달라지면 기존 앱 업데이트가 거부될 수 있습니다.
shallow clone최근 일부 이력만 가져온 Git 복제본입니다. 현재 파일은 읽을 수 있지만 전체 커밋 통계를 낼 수는 없습니다.

11참고 링크

변하는 메타데이터와 고정된 소스 증거를 따로 확인할 수 있게 모았습니다

11-1. 공식 저장소와 소스 파일

11-2. 관련 기술의 공식 자료

11-3. 현재 상태 (2026-10-23 확인)

저장소 메타데이터는 2026-10-23 GitHub API 기준입니다. 파일·설정·줄 수는 커밋 12c21f7 기준이며, 성능 수치는 README에 기록된 측정 사례입니다.

항목상태근거
스타 / 포크1,312 / 1132026-10-23 GitHub API 기준
생성일2026-10-182026-10-23 GitHub API 기준
주 언어Java2026-10-23 GitHub API 기준
최신 릴리스launcher-v1.2 · 2026-10-222026-10-23 GitHub API 기준. VERSION=1.2와 일치
최근 push2026-10-222026-10-23 GitHub API 기준
소스 기준 커밋12c21f7 · 2026-10-23Git 기록. dumbtv.org 링크를 문서·템플릿에 연결한 변경
코드 규모Java 2,561 / Bash 2,106 / Python 1,052줄빈 줄·주석·테스트 포함
파일·테스트추적 파일 134개 / Python 테스트 메서드 51개정적 계수. 실행 결과·커버리지 수치가 아님
기기 카탈로그22개 보고: tested 6 / partial 16템플릿 제외, YAML 최상위 상태 값 계수
API 라이선스NOASSERTION2026-10-23 GitHub API 기준
소스 라이선스코드·문서·런처 MIT / 카탈로그 CC BY-SA 4.0LICENSE, catalog/LICENSE의 명시 내용
수치 해석물리 줄 수·정적 테스트 개수성능·테스트 통과율·커버리지 수치와 구별

NOASSERTION은 라이선스 파일의 부재를 뜻하지 않습니다. 반대로 외부에서 받는 필터·목록까지 모두 MIT라고 하면 틀립니다. README는 personalDNSfilter GPL-2.0, HaGeZi 목록 GPL-3.0, SmartTube MIT를 별도로 안내합니다.

최근 push 날짜와 커밋에 기록된 날짜는 서로 다른 항목입니다. 소스 설명과 코드 규모는 명시한 커밋을 기준으로 읽으세요.

사용 전에 알아둘 한계
복구 가능성·차단 효과·기종 호환성을 각각 확인해야 합니다

복구는 완전한 기기 백업이 아닙니다. restore.sh는 대상 목록을 다시 켜며, 기존 패키지 활성 상태 전체를 복원하지 않습니다. 설정 스냅샷이 없거나 빈 값이면 기본값을 사용합니다. 삭제한 스토어 앱은 재설치해야 하고, 추가한 런처·필터·SmartTube는 설치된 채 남습니다. README의 “필터를 제거한다”는 요약과 달리 실제 코드는 필터를 비활성화합니다.

DNS는 모든 광고를 구분하지 못합니다. README는 YouTube 광고가 영상과 같은 서버를 써 DNS로 분리 차단할 수 없다고 설명합니다. 선택한 SmartTube 설치는 별도 접근입니다. 도메인 테스트 성공도 모든 앱 기능이나 모든 추적 통신이 사라졌다는 증거는 아닙니다.

설정과 실제 동작은 다릅니다. Fire OS 8의 수동 DNS 분기, Fire OS 6의 시각 동기화 예외, 접근성 서비스의 실제 바인딩을 기기별로 봐야 합니다. SYSTEM_DNS=1은 Fire OS 6에만 적용되며, 시스템 사용자로 실행되는 앱도 필터를 우회하게 합니다. 업데이트 서버 차단은 보안 업데이트 수신에도 영향을 줍니다.

실행 계획은 단계별로 확인해야 합니다. 사용자 선택은 설정 파일과 각 실행 단계에서 반영됩니다. preflight.sh의 계획 출력도 사용자 설정을 모두 반영하는 최종 실행 계획은 아닙니다. 실제 변경 대상은 설정을 읽은 debloat.sh의 분기까지 확인해야 합니다.

이 레포에서 가져갈 설계 습관
작은 자동화에도 사용자의 상태와 선택을 담기

목록을 코드에서 분리하고, 기기 명령을 한 경계로 감싸고, 실제 상태를 다시 읽고, 실패한 작업의 복구 순서를 설계하세요. dumbtv는 이런 결정을 Java와 Bash 몇 천 줄 안에서 구체적으로 살펴볼 수 있는 공부 자료입니다.