-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathandroidtv-app-sync.sh
More file actions
executable file
·137 lines (124 loc) · 4.63 KB
/
Copy pathandroidtv-app-sync.sh
File metadata and controls
executable file
·137 lines (124 loc) · 4.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#!/usr/bin/env bash
# Update pinned Android TV APKs when a device is reachable over network ADB.
set -euo pipefail
readonly ADB_CONTAINER="${ADB_CONTAINER:-androidtv-adb}"
usage() {
cat >&2 <<EOF
Usage: $0 <device-host> <manifest.tsv>
Manifest columns (tab-separated): package, minimum versionCode, source, SHA-256.
Use source "managed" and checksum "-" to audit a Play-managed package without
installing it. Otherwise source must be an APK URL with its pinned checksum.
Blank lines and lines beginning with # are ignored. Updates and downgrades must
be represented by a new pinned version and checksum; downgrades are refused.
EOF
exit 2
}
[[ $# -eq 2 ]] || usage
readonly DEVICE_TARGET="$1:5555"
readonly MANIFEST="$2"
[[ -r "$MANIFEST" ]] || {
printf 'ERROR: manifest is not readable: %s\n' "$MANIFEST" >&2
exit 1
}
declare -a adb_cmd
using_container=false
if command -v docker >/dev/null 2>&1 \
&& docker ps --format '{{.Names}}' 2>/dev/null | grep -Fxq "$ADB_CONTAINER"; then
adb_cmd=(docker exec "$ADB_CONTAINER" adb)
using_container=true
elif command -v adb >/dev/null 2>&1; then
adb_cmd=(adb)
else
printf 'ERROR: no running ADB container and no host adb executable\n' >&2
exit 1
fi
run_adb() {
"${adb_cmd[@]}" "$@"
}
tmp_dir="$(mktemp -d)"
declare -a container_apks=()
cleanup() {
run_adb disconnect "$DEVICE_TARGET" >/dev/null 2>&1 || true
if [[ "$using_container" == true && ${#container_apks[@]} -gt 0 ]]; then
docker exec "$ADB_CONTAINER" rm -f "${container_apks[@]}" >/dev/null 2>&1 || true
fi
rm -rf "$tmp_dir"
}
trap cleanup EXIT
if ! timeout 15s "${adb_cmd[@]}" connect "$DEVICE_TARGET" >/dev/null; then
printf 'Device is offline or ADB is unavailable: %s\n' "$DEVICE_TARGET" >&2
exit 75
fi
device_state="$(run_adb -s "$DEVICE_TARGET" get-state 2>&1 || true)"
if [[ "$device_state" == *unauthorized* ]]; then
printf 'ERROR: ADB authorization is missing; approve the persistent client key on the TV\n' >&2
exit 77
fi
if ! timeout 15s "${adb_cmd[@]}" -s "$DEVICE_TARGET" wait-for-device; then
printf 'ERROR: timed out waiting for authorized ADB device %s\n' "$DEVICE_TARGET" >&2
exit 75
fi
device_state="$(run_adb -s "$DEVICE_TARGET" get-state 2>&1 || true)"
[[ "$device_state" == device ]] || {
printf 'ERROR: unexpected ADB state for %s: %s\n' "$DEVICE_TARGET" "$device_state" >&2
exit 1
}
updates=0
audit_failures=0
while IFS=$'\t' read -r package target_version source expected_sha extra; do
[[ -z "$package" || "$package" == \#* ]] && continue
[[ -z "${extra:-}" && "$target_version" =~ ^[0-9]+$ ]] || {
printf 'ERROR: invalid manifest row for %s\n' "$package" >&2
exit 1
}
installed_version="$(run_adb -s "$DEVICE_TARGET" shell dumpsys package "$package" 2>/dev/null \
| sed -n 's/.*versionCode=\([0-9][0-9]*\).*/\1/p' | head -n 1)"
installed_version="${installed_version:-0}"
if [[ "$source" == managed ]]; then
[[ "$expected_sha" == - ]] || {
printf 'ERROR: managed package %s must use checksum "-"\n' "$package" >&2
exit 1
}
if ((installed_version >= target_version && installed_version > 0)); then
printf 'Managed/current: %s (%s)\n' "$package" "$installed_version"
else
printf 'MANAGED ACTION NEEDED: %s is missing or below %s (installed: %s)\n' \
"$package" "$target_version" "$installed_version" >&2
audit_failures=$((audit_failures + 1))
fi
continue
fi
[[ "$expected_sha" =~ ^[[:xdigit:]]{64}$ ]] || {
printf 'ERROR: invalid SHA-256 for %s\n' "$package" >&2
exit 1
}
if ((installed_version == target_version)); then
printf 'Current: %s (%s)\n' "$package" "$target_version"
continue
fi
if ((installed_version > target_version)); then
printf 'ERROR: refusing downgrade of %s from %s to %s\n' \
"$package" "$installed_version" "$target_version" >&2
exit 1
fi
apk_path="$tmp_dir/${package}.apk"
curl --fail --location --silent --show-error "$source" --output "$apk_path"
printf '%s %s\n' "${expected_sha,,}" "$apk_path" | sha256sum --check --status || {
printf 'ERROR: checksum mismatch for %s\n' "$package" >&2
exit 1
}
printf 'Updating: %s (%s -> %s)\n' "$package" "$installed_version" "$target_version"
install_path="$apk_path"
if [[ "$using_container" == true ]]; then
install_path="/tmp/${package}-$$.apk"
docker cp "$apk_path" "$ADB_CONTAINER:$install_path"
container_apks+=("$install_path")
fi
run_adb -s "$DEVICE_TARGET" install -r -g "$install_path"
updates=$((updates + 1))
done <"$MANIFEST"
printf 'Sync complete: %s update(s) installed\n' "$updates"
if ((audit_failures > 0)); then
printf 'Managed package audit: %s action(s) needed\n' "$audit_failures" >&2
exit 3
fi