Files
gitlab-instance-0a899031_sh/android13/redroid13.sh
2026-04-09 21:01:21 -05:00

428 lines
11 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
SSH_HOST="${REDROID_SSH_HOST:-192.168.2.179}"
SSH_USER="${REDROID_SSH_USER:-eric}"
REMOTE_REPO="${REDROID_REMOTE_REPO:-/home/eric/redroid-android13}"
REDROID_IMAGE_OVERRIDE="${REDROID_IMAGE:-}"
REDROID_IMAGE_X86="${REDROID_IMAGE_X86:-redroid/redroid:13.0.0_mindthegapps_houdini_magisk}"
REDROID_IMAGE_ARM64="${REDROID_IMAGE_ARM64:-redroid/redroid:13.0.0_mindthegapps_magisk}"
REDROID_ARCH_PROFILE="${REDROID_ARCH_PROFILE:-auto}"
DEVICE_PROFILE="${REDROID_DEVICE_PROFILE:-pixel-7-pro}"
PORTAINER_PORT="${REDROID_PORTAINER_PORT:-9000}"
DEFAULT_PORT="${REDROID_DEFAULT_PORT:-5570}"
DATA_PREFIX="${REDROID_DATA_PREFIX:-/home/eric/data-redroid13}"
PORT_STEP="${REDROID_PORT_STEP:-1}"
INSTANCE_PREFIX="${REDROID_INSTANCE_PREFIX:-redroid13}"
DEFAULT_COUNT="${REDROID_DEFAULT_COUNT:-3}"
GPU_MODE="${REDROID_GPU_MODE:-guest}"
ADB_INSECURE="${REDROID_ADB_INSECURE:-1}"
BOOT_TIMEOUT="${REDROID_BOOT_TIMEOUT:-120}"
ROOT_TIMEOUT="${REDROID_ROOT_TIMEOUT:-60}"
WAIT_TCP="${REDROID_WAIT_TCP:-120}"
RESOLVED_IMAGE=""
RESOLVED_ARCH=""
if ! command -v python3 >/dev/null 2>&1; then
echo "python3 is required." >&2
exit 1
fi
if ! python3 - <<'PY' >/dev/null 2>&1
import importlib.util
raise SystemExit(0 if importlib.util.find_spec("paramiko") else 1)
PY
then
echo "python3 paramiko is required." >&2
exit 1
fi
read -r -s -p "SSH password for ${SSH_USER}@${SSH_HOST}: " SSH_PASSWORD
echo
sq() {
printf '%q' "$1"
}
remote_exec() {
local remote_cmd="$1"
SSH_HOST="$SSH_HOST" \
SSH_USER="$SSH_USER" \
SSH_PASSWORD="$SSH_PASSWORD" \
SSH_CMD="$remote_cmd" \
python3 - <<'PY'
import os
import sys
import paramiko
host = os.environ["SSH_HOST"]
user = os.environ["SSH_USER"]
password = os.environ["SSH_PASSWORD"]
cmd = os.environ["SSH_CMD"]
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(
hostname=host,
username=user,
password=password,
timeout=20,
banner_timeout=20,
auth_timeout=20,
)
stdin, stdout, stderr = client.exec_command(cmd, get_pty=True)
out = stdout.read().decode(errors="replace")
err = stderr.read().decode(errors="replace")
if out:
sys.stdout.write(out)
if err:
sys.stderr.write(err)
status = stdout.channel.recv_exit_status()
client.close()
sys.exit(status)
PY
}
remote_repo_cmd() {
local inner="$1"
remote_exec "cd $(sq "$REMOTE_REPO") && ${inner}"
}
normalize_arch() {
case "${1,,}" in
x86_64|amd64)
printf '%s' "x86_64"
;;
aarch64|arm64)
printf '%s' "arm64"
;;
*)
printf '%s' "unknown"
;;
esac
}
detect_remote_arch() {
remote_exec "uname -m | tr -d '\r'"
}
resolve_image() {
if [[ -n "$RESOLVED_IMAGE" ]]; then
printf '%s' "$RESOLVED_IMAGE"
return 0
fi
if [[ -n "$REDROID_IMAGE_OVERRIDE" ]]; then
RESOLVED_IMAGE="$REDROID_IMAGE_OVERRIDE"
RESOLVED_ARCH="custom"
printf '%s' "$RESOLVED_IMAGE"
return 0
fi
local arch="${REDROID_ARCH_PROFILE}"
if [[ "$arch" == "auto" ]]; then
arch="$(normalize_arch "$(detect_remote_arch)")"
else
arch="$(normalize_arch "$arch")"
fi
case "$arch" in
x86_64)
RESOLVED_IMAGE="$REDROID_IMAGE_X86"
RESOLVED_ARCH="x86_64"
;;
arm64)
RESOLVED_IMAGE="$REDROID_IMAGE_ARM64"
RESOLVED_ARCH="arm64"
;;
*)
RESOLVED_IMAGE="$REDROID_IMAGE_X86"
RESOLVED_ARCH="x86_64"
;;
esac
printf '%s' "$RESOLVED_IMAGE"
}
resolve_arch() {
if [[ -z "$RESOLVED_IMAGE" ]]; then
resolve_image >/dev/null
fi
printf '%s' "${RESOLVED_ARCH:-x86_64}"
}
remote_docker_ps() {
remote_exec "docker ps --format 'table {{.Names}}\\t{{.Image}}\\t{{.Ports}}\\t{{.Status}}'"
}
remote_redroid_ps() {
local image
image="$(resolve_image)"
remote_exec "docker ps --format '{{.Names}} {{.Image}} {{.Ports}} {{.Status}}' | awk -v image=$(sq "$image") '\$2 == image || index(\$2, \"redroid/redroid:\") == 1 { print }'"
}
remote_redroid_names() {
local image
image="$(resolve_image)"
remote_exec "docker ps --format '{{.Names}} {{.Image}}' | awk -v image=$(sq "$image") '\$2 == image || index(\$2, \"redroid/redroid:\") == 1 {print \$1}'"
}
show_runtime_info() {
local image arch
image="$(resolve_image)"
arch="$(resolve_arch)"
printf 'Remote arch profile: %s\n' "$arch"
printf 'Resolved image: %s\n' "$image"
printf 'Device profile: %s\n' "$DEVICE_PROFILE"
printf 'GPU mode: %s\n' "$GPU_MODE"
printf 'ADB insecure: %s\n' "$ADB_INSECURE"
}
build_image() {
remote_repo_cmd "env -u REDROID_MAGISK_APK -u REDROID_MAGISK_URL REDROID_OFFLINE=1 REDROID_MAGISK_BOOT_COMPLETE=1 python3 redroid.py -a 13.0.0 -mtg -i -m --offline --no-compat-check"
}
make_instance_name() {
local prefix="$1"
local index="$2"
printf '%s-%s' "$prefix" "$index"
}
make_data_dir() {
local prefix="$1"
local name="$2"
printf '%s-%s' "$prefix" "$name"
}
start_single_instance() {
local name="$1"
local port="$2"
local data_dir="$3"
local image arch
local adb_arg=""
if [[ "$ADB_INSECURE" != "0" ]]; then
adb_arg="--adb-insecure"
fi
image="$(resolve_image)"
arch="$(resolve_arch)"
remote_repo_cmd "python3 run_redroid.py --image $(sq "$image") --bridge auto --device-profile $(sq "$DEVICE_PROFILE") --gpu-mode $(sq "$GPU_MODE") --data-dir $(sq "$data_dir") --port $(sq "$port") --name $(sq "$name") --replace --keep-container --wait-tcp $(sq "$WAIT_TCP") ${adb_arg}"
bootstrap_instance "$name"
echo "Local ADB connect:"
echo " adb connect ${SSH_HOST}:${port}"
echo "Resolved arch/image: ${arch} / ${image}"
}
start_instance() {
local name port data_dir
read -r -p "Instance name [${INSTANCE_PREFIX}-1]: " name
name="${name:-${INSTANCE_PREFIX}-1}"
read -r -p "ADB port [${DEFAULT_PORT}]: " port
port="${port:-$DEFAULT_PORT}"
read -r -p "Data dir [${DATA_PREFIX}-${name}]: " data_dir
data_dir="${data_dir:-${DATA_PREFIX}-${name}}"
start_single_instance "$name" "$port" "$data_dir"
}
start_batch_instances() {
local count start_port prefix data_prefix
read -r -p "How many instances [${DEFAULT_COUNT}]: " count
count="${count:-$DEFAULT_COUNT}"
read -r -p "Start ADB port [${DEFAULT_PORT}]: " start_port
start_port="${start_port:-$DEFAULT_PORT}"
read -r -p "Name prefix [${INSTANCE_PREFIX}]: " prefix
prefix="${prefix:-$INSTANCE_PREFIX}"
read -r -p "Data prefix [${DATA_PREFIX}]: " data_prefix
data_prefix="${data_prefix:-$DATA_PREFIX}"
local i name port data_dir
for ((i = 1; i <= count; i++)); do
name="$(make_instance_name "$prefix" "$i")"
port="$((start_port + (i - 1) * PORT_STEP))"
data_dir="$(make_data_dir "$data_prefix" "$name")"
echo "Starting ${name} on port ${port}..."
start_single_instance "$name" "$port" "$data_dir"
echo
done
local open_panel
read -r -p "Start Portainer web panel now? [Y/n]: " open_panel
case "${open_panel:-Y}" in
n|N|no|NO|No) ;;
*) start_portainer ;;
esac
}
bootstrap_instance() {
local name="${1:?instance name required}"
remote_repo_cmd "python3 bootstrap_redroid.py --container-name $(sq "$name") --container-runtime docker --device-profile $(sq "$DEVICE_PROFILE") --proxy-host none --timeout $(sq "$BOOT_TIMEOUT") --root-timeout $(sq "$ROOT_TIMEOUT")"
}
bootstrap_all_instances() {
local names
names="$(remote_redroid_names)"
if [[ -z "$names" ]]; then
echo "No running redroid containers found."
return 0
fi
local name
while IFS= read -r name; do
[[ -z "$name" ]] && continue
echo "Bootstrapping ${name}..."
bootstrap_instance "$name"
echo
done <<< "$names"
}
check_instance() {
local port
read -r -p "ADB port [${DEFAULT_PORT}]: " port
port="${port:-$DEFAULT_PORT}"
remote_exec "
set +e
adb connect 127.0.0.1:${port} >/dev/null 2>&1
adb -s 127.0.0.1:${port} shell getprop sys.boot_completed
adb -s 127.0.0.1:${port} shell settings get global device_provisioned
adb -s 127.0.0.1:${port} shell settings get secure user_setup_complete
adb -s 127.0.0.1:${port} shell settings get global http_proxy
adb -s 127.0.0.1:${port} shell getprop ro.build.fingerprint
adb -s 127.0.0.1:${port} shell getprop ro.product.model
adb -s 127.0.0.1:${port} shell su root id 2>/dev/null || adb -s 127.0.0.1:${port} shell su 0 id 2>/dev/null
adb -s 127.0.0.1:${port} shell cmd package resolve-activity --brief -a android.intent.action.MAIN -c android.intent.category.LAUNCHER io.github.huskydg.magisk 2>/dev/null | tail -n 2 || true
adb -s 127.0.0.1:${port} shell pm path io.github.huskydg.magisk 2>/dev/null || true
adb -s 127.0.0.1:${port} shell pm list packages 2>/dev/null | grep -E 'com.google.android.gms|com.google.android.setupwizard|io.github.huskydg.magisk' | sort || true
adb -s 127.0.0.1:${port} shell dumpsys package com.google.android.gms 2>/dev/null | grep -E 'versionName=|pkg=|enabled=' | head -n 10 || true
"
}
start_portainer() {
remote_exec "
docker volume create portainer_data >/dev/null
docker rm -f portainer >/dev/null 2>&1 || true
docker run -d \
--name portainer \
--restart unless-stopped \
-p ${PORTAINER_PORT}:9000 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
portainer/portainer-ce:latest
"
echo "Portainer: http://${SSH_HOST}:${PORTAINER_PORT}"
}
stop_instance() {
local name
read -r -p "Container name [redroid13-1]: " name
name="${name:-redroid13-1}"
remote_exec "docker rm -f $(sq "$name")"
}
menu() {
while true; do
cat <<EOF
redroid13 menu
1) Build Android 13 image
2) Start redroid instance
3) Start multiple instances
4) Bootstrap instance
5) List containers
6) Check instance health
7) Start Portainer web panel
8) Stop instance
9) Bootstrap all redroid containers
10) Show runtime info
11) Exit
EOF
read -r -p "Choose: " choice
case "$choice" in
1) build_image ;;
2) start_instance ;;
3) start_batch_instances ;;
4)
local name
read -r -p "Container name [${INSTANCE_PREFIX}-1]: " name
name="${name:-${INSTANCE_PREFIX}-1}"
bootstrap_instance "$name"
;;
5) remote_docker_ps ;;
6) check_instance ;;
7) start_portainer ;;
8) stop_instance ;;
9) bootstrap_all_instances ;;
10) show_runtime_info ;;
11) exit 0 ;;
*) echo "Invalid choice." ;;
esac
echo
done
}
case "${1:-}" in
build)
build_image
;;
start)
start_instance
;;
batch|multi)
start_batch_instances
;;
bootstrap)
if [[ $# -lt 2 ]]; then
echo "Usage: $0 bootstrap <container-name>" >&2
exit 1
fi
bootstrap_instance "$2"
;;
list)
remote_docker_ps
;;
redroid-list|rlist)
remote_redroid_ps
;;
info|runtime)
show_runtime_info
;;
check)
check_instance
;;
status)
check_instance
;;
portainer)
start_portainer
;;
panel|web)
start_portainer
;;
stop)
if [[ $# -lt 2 ]]; then
echo "Usage: $0 stop <container-name>" >&2
exit 1
fi
remote_exec "docker rm -f $(sq "$2")"
;;
bootstrap-all|ball)
bootstrap_all_instances
;;
""|menu)
menu
;;
*)
cat <<EOF
Usage: $0 [build|start|batch|multi|bootstrap <name>|bootstrap-all|ball|list|redroid-list|check|status|portainer|info|runtime|stop <name>]
EOF
exit 1
;;
esac