's'
This commit is contained in:
280
android13/README.md
Normal file
280
android13/README.md
Normal file
@@ -0,0 +1,280 @@
|
||||
# redroid 13 Control Kit
|
||||
|
||||
This folder contains the local control entrypoint for the remote redroid 13 environment.
|
||||
The goal is to keep one script and one workflow that can:
|
||||
|
||||
- build the image on the server
|
||||
- launch one or many redroid instances
|
||||
- bootstrap Magisk and Google framework settings
|
||||
- check whether a running instance is actually usable
|
||||
- expose a web UI for Docker management
|
||||
|
||||
## Architecture
|
||||
|
||||
The repository is split into two layers:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Local workstation<br/>Windows / Git Bash"] --> B["redroid13.sh<br/>Paramiko over SSH"]
|
||||
B --> C["Remote host<br/>/home/eric/redroid-android13"]
|
||||
C --> D["Docker / Podman"]
|
||||
D --> E["redroid container"]
|
||||
E --> F["Android 13 system image"]
|
||||
F --> G["Google framework"]
|
||||
F --> H["Magisk / root"]
|
||||
B --> I["Portainer web panel"]
|
||||
```
|
||||
|
||||
### Local side
|
||||
|
||||
`redroid13.sh` is the only file you usually run from this folder. It:
|
||||
|
||||
- asks for the SSH password once
|
||||
- connects to the server with `paramiko`
|
||||
- executes build, launch, bootstrap, and health-check commands remotely
|
||||
- resolves the best image for the server architecture
|
||||
|
||||
### Remote side
|
||||
|
||||
The server repo, `/home/eric/redroid-android13`, contains the actual Android build and bootstrap code:
|
||||
|
||||
- `redroid.py` builds the image layers
|
||||
- `run_redroid.py` launches the container
|
||||
- `bootstrap_redroid.py` applies the post-boot hardening
|
||||
- `stuff/magisk.py` packages Magisk
|
||||
- `stuff/mindthegapps.py` packages Google apps/framework
|
||||
|
||||
## Compatibility Model
|
||||
|
||||
The script is designed to work with both x86_64 and arm64 hosts.
|
||||
|
||||
### Image selection
|
||||
|
||||
The script resolves the image automatically unless you override it.
|
||||
|
||||
- x86_64 host: `redroid/redroid:13.0.0_mindthegapps_houdini_magisk`
|
||||
- arm64 host: `redroid/redroid:13.0.0_mindthegapps_magisk`
|
||||
|
||||
You can still override the image explicitly with `REDROID_IMAGE`.
|
||||
If your arm64 build uses a different tag, set `REDROID_IMAGE_ARM64` to match it.
|
||||
|
||||
### Runtime bridge
|
||||
|
||||
`run_redroid.py` already resolves bridge mode from the image tag when `--bridge auto` is used.
|
||||
|
||||
- `houdini` images use native bridge translation for x86 apps on x86 hosts
|
||||
- arm64 images can stay on the default bridge path
|
||||
|
||||
### Root handling
|
||||
|
||||
`bootstrap_redroid.py` now probes root using both of the common Magisk styles:
|
||||
|
||||
- `su root id`
|
||||
- `su 0 id`
|
||||
|
||||
That avoids the old `su -mm -c` mismatch and keeps Magisk-compatible bootstrapping stable across the current image variants.
|
||||
|
||||
## What The Script Does
|
||||
|
||||
The shell entrypoint supports:
|
||||
|
||||
- building the Android 13 image
|
||||
- starting one instance
|
||||
- starting multiple instances
|
||||
- bootstrapping a single container
|
||||
- bootstrapping all running redroid containers
|
||||
- listing containers
|
||||
- checking a running instance
|
||||
- starting Portainer
|
||||
- showing runtime info
|
||||
|
||||
It also resolves:
|
||||
|
||||
- the server architecture
|
||||
- the appropriate image tag
|
||||
- the right ADB / Magisk bootstrap path
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Local machine:
|
||||
|
||||
- Bash
|
||||
- Python 3
|
||||
- `paramiko` installed for Python 3
|
||||
|
||||
Remote server:
|
||||
|
||||
- Docker or Podman
|
||||
- access to `/home/eric/redroid-android13`
|
||||
- working ADB server
|
||||
- a redroid-compatible Linux host
|
||||
|
||||
## Quick Start
|
||||
|
||||
Run the script from this folder:
|
||||
|
||||
```bash
|
||||
./redroid13.sh
|
||||
```
|
||||
|
||||
The menu is interactive and covers the common workflow:
|
||||
|
||||
1. build the image
|
||||
2. start one instance
|
||||
3. start a batch of instances
|
||||
4. bootstrap an instance
|
||||
5. list containers
|
||||
6. check a running instance
|
||||
7. start Portainer
|
||||
8. stop an instance
|
||||
9. bootstrap all redroid containers
|
||||
10. show runtime info
|
||||
11. exit
|
||||
|
||||
## Non-Interactive Usage
|
||||
|
||||
The same script also supports command-style usage:
|
||||
|
||||
```bash
|
||||
./redroid13.sh build
|
||||
./redroid13.sh start
|
||||
./redroid13.sh batch
|
||||
./redroid13.sh bootstrap redroid13-1
|
||||
./redroid13.sh bootstrap-all
|
||||
./redroid13.sh list
|
||||
./redroid13.sh redroid-list
|
||||
./redroid13.sh check
|
||||
./redroid13.sh portainer
|
||||
./redroid13.sh info
|
||||
./redroid13.sh stop redroid13-1
|
||||
```
|
||||
|
||||
## Recommended Defaults
|
||||
|
||||
These defaults are tuned for a stable Android 13 desktop-style setup:
|
||||
|
||||
- `REDROID_GPU_MODE=guest`
|
||||
- `REDROID_ADB_INSECURE=1`
|
||||
- `REDROID_DEVICE_PROFILE=pixel-7-pro`
|
||||
- `REDROID_ROOT_TIMEOUT=60`
|
||||
- `REDROID_BOOT_TIMEOUT=120`
|
||||
- `REDROID_WAIT_TCP=120`
|
||||
|
||||
That combination prioritizes boot reliability over host-GPU speed.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
You can override the behavior without editing the script.
|
||||
|
||||
| Variable | Purpose | Default |
|
||||
| --- | --- | --- |
|
||||
| `REDROID_SSH_HOST` | Remote server address | `192.168.2.179` |
|
||||
| `REDROID_SSH_USER` | SSH user | `eric` |
|
||||
| `REDROID_REMOTE_REPO` | Remote repo path | `/home/eric/redroid-android13` |
|
||||
| `REDROID_IMAGE` | Force a specific image | auto-select by architecture |
|
||||
| `REDROID_IMAGE_X86` | x86_64 image | `redroid/redroid:13.0.0_mindthegapps_houdini_magisk` |
|
||||
| `REDROID_IMAGE_ARM64` | arm64 image | `redroid/redroid:13.0.0_mindthegapps_magisk` |
|
||||
| `REDROID_ARCH_PROFILE` | `auto`, `x86_64`, or `arm64` | `auto` |
|
||||
| `REDROID_DEVICE_PROFILE` | Device fingerprint preset | `pixel-7-pro` |
|
||||
| `REDROID_GPU_MODE` | `guest`, `host`, or `auto` | `guest` |
|
||||
| `REDROID_ADB_INSECURE` | Inject insecure adb boot props | `1` |
|
||||
| `REDROID_BOOT_TIMEOUT` | Seconds to wait for Android boot | `120` |
|
||||
| `REDROID_ROOT_TIMEOUT` | Seconds to wait for Magisk root | `60` |
|
||||
| `REDROID_WAIT_TCP` | Seconds to wait for ADB TCP | `120` |
|
||||
| `REDROID_DEFAULT_PORT` | First ADB port | `5570` |
|
||||
| `REDROID_PORT_STEP` | Port step in batch mode | `1` |
|
||||
| `REDROID_INSTANCE_PREFIX` | Instance name prefix | `redroid13` |
|
||||
| `REDROID_DEFAULT_COUNT` | Batch size prompt default | `3` |
|
||||
| `REDROID_PORTAINER_PORT` | Portainer port | `9000` |
|
||||
| `REDROID_DATA_PREFIX` | Host data directory prefix | `/home/eric/data-redroid13` |
|
||||
|
||||
## Multi-Instance Layout
|
||||
|
||||
The script uses one host data directory per instance:
|
||||
|
||||
```text
|
||||
/home/eric/data-redroid13-redroid13-1
|
||||
/home/eric/data-redroid13-redroid13-2
|
||||
/home/eric/data-redroid13-redroid13-3
|
||||
```
|
||||
|
||||
Ports are assigned sequentially:
|
||||
|
||||
- `5570`
|
||||
- `5571`
|
||||
- `5572`
|
||||
|
||||
This keeps each container isolated and makes it easy to reboot or replace a single instance.
|
||||
|
||||
## Bootstrap Flow
|
||||
|
||||
The bootstrap phase does the important post-boot cleanup:
|
||||
|
||||
- waits for Android boot completion
|
||||
- waits for Magisk root when root-only actions are required
|
||||
- sets provisioning flags
|
||||
- disables setup wizard if the package exists
|
||||
- sets mock location for shell
|
||||
- applies the selected device profile through `resetprop`
|
||||
- applies proxy settings
|
||||
- installs the optional extreme script when requested
|
||||
|
||||
That bootstrap logic is the part that keeps Google Play and Magisk behavior stable after first boot.
|
||||
|
||||
## Portainer
|
||||
|
||||
The script can start Portainer on the remote host.
|
||||
|
||||
Once it is running:
|
||||
|
||||
- open `http://<server-ip>:9000`
|
||||
- use it to inspect containers, logs, volumes, and restart policies
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### ADB connects but shell fails
|
||||
|
||||
Check whether the instance actually reached a usable boot state:
|
||||
|
||||
```bash
|
||||
./redroid13.sh check
|
||||
```
|
||||
|
||||
If `sys.boot_completed` is not `1`, the container may still be booting or the guest may have stalled.
|
||||
|
||||
### Magisk root is unavailable
|
||||
|
||||
The bootstrap logic now retries root with the currently supported `su` syntax.
|
||||
|
||||
If you still do not see root:
|
||||
|
||||
- check the container logs on the remote host
|
||||
- verify the image tag you selected
|
||||
- make sure the container was started from the current repository revision
|
||||
|
||||
### Google Play login looks broken
|
||||
|
||||
That usually means one of these is still wrong:
|
||||
|
||||
- device fingerprint
|
||||
- setup wizard state
|
||||
- provisioning state
|
||||
- Google Play Services package state
|
||||
|
||||
Use `./redroid13.sh check` to inspect all of those in one shot.
|
||||
|
||||
### Host GPU causes boot issues
|
||||
|
||||
If a container never becomes reachable over ADB, switch back to guest GPU mode:
|
||||
|
||||
```bash
|
||||
REDROID_GPU_MODE=guest ./redroid13.sh start
|
||||
```
|
||||
|
||||
Host GPU is faster when it works, but guest GPU is the safe default for headless servers.
|
||||
|
||||
## Notes
|
||||
|
||||
- This folder is a control layer, not the Android build itself.
|
||||
- The actual server-side fixes live in the remote repository at `/home/eric/redroid-android13`.
|
||||
- The goal is stability first: Magisk root, Google framework, and repeatable multi-instance startup.
|
||||
143
android13/patch_bootstrap.py
Normal file
143
android13/patch_bootstrap.py
Normal file
@@ -0,0 +1,143 @@
|
||||
from pathlib import Path
|
||||
path = Path('/home/eric/redroid-android13/bootstrap_redroid.py')
|
||||
text = path.read_text()
|
||||
old = ''' def connect(self):
|
||||
if self.mode == "adb" and ":" in self.serial:
|
||||
run(["adb", "connect", self.serial], check=False)
|
||||
|
||||
def has_root(self):
|
||||
if self.mode == "container":
|
||||
return True
|
||||
if not self._root_checked or not self._root_available:
|
||||
result = adb(
|
||||
self.serial,
|
||||
["shell", "su", "-mm", "-c", "id"],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
)
|
||||
self._root_available = result.returncode == 0 and "uid=0(" in result.stdout
|
||||
self._root_checked = self._root_available
|
||||
return self._root_available
|
||||
|
||||
def wait_for_root(self, timeout):
|
||||
if self.mode == "container":
|
||||
return True
|
||||
deadline = time.time() + timeout
|
||||
while time.time() < deadline:
|
||||
if self.has_root():
|
||||
return True
|
||||
time.sleep(2)
|
||||
return False
|
||||
|
||||
def exec_shell(self, command, check=True, capture_output=False, root=False):
|
||||
if self.mode == "container":
|
||||
return run(
|
||||
[
|
||||
self.container_runtime,
|
||||
"exec",
|
||||
self.container_name,
|
||||
"/system/bin/sh",
|
||||
"-c",
|
||||
command,
|
||||
],
|
||||
check=check,
|
||||
capture_output=capture_output,
|
||||
)
|
||||
if root and self.has_root():
|
||||
return adb(
|
||||
self.serial,
|
||||
["shell", "su", "-mm", "-c", command],
|
||||
check=check,
|
||||
capture_output=capture_output,
|
||||
)
|
||||
return adb(
|
||||
self.serial,
|
||||
["shell", command],
|
||||
check=check,
|
||||
capture_output=capture_output,
|
||||
)
|
||||
'''
|
||||
new = ''' def connect(self):
|
||||
if self.mode == "adb" and ":" in self.serial:
|
||||
run(["adb", "connect", self.serial], check=False)
|
||||
|
||||
def _root_probe_args(self):
|
||||
return [
|
||||
["shell", "su", "root", "id"],
|
||||
["shell", "su", "0", "id"],
|
||||
]
|
||||
|
||||
def _root_shell_args(self, command):
|
||||
return [
|
||||
["shell", "su", "root", "sh", "-c", command],
|
||||
["shell", "su", "0", "sh", "-c", command],
|
||||
]
|
||||
|
||||
def has_root(self):
|
||||
if self.mode == "container":
|
||||
return True
|
||||
if not self._root_checked or not self._root_available:
|
||||
for args in self._root_probe_args():
|
||||
result = adb(
|
||||
self.serial,
|
||||
args,
|
||||
check=False,
|
||||
capture_output=True,
|
||||
)
|
||||
if result.returncode == 0 and "uid=0(" in result.stdout:
|
||||
self._root_available = True
|
||||
self._root_checked = True
|
||||
return True
|
||||
self._root_available = False
|
||||
self._root_checked = False
|
||||
return self._root_available
|
||||
|
||||
def wait_for_root(self, timeout):
|
||||
if self.mode == "container":
|
||||
return True
|
||||
deadline = time.time() + timeout
|
||||
while time.time() < deadline:
|
||||
if self.has_root():
|
||||
return True
|
||||
time.sleep(2)
|
||||
return False
|
||||
|
||||
def exec_shell(self, command, check=True, capture_output=False, root=False):
|
||||
if self.mode == "container":
|
||||
return run(
|
||||
[
|
||||
self.container_runtime,
|
||||
"exec",
|
||||
self.container_name,
|
||||
"/system/bin/sh",
|
||||
"-c",
|
||||
command,
|
||||
],
|
||||
check=check,
|
||||
capture_output=capture_output,
|
||||
)
|
||||
if root and self.has_root():
|
||||
last_result = None
|
||||
for args in self._root_shell_args(command):
|
||||
result = adb(
|
||||
self.serial,
|
||||
args,
|
||||
check=False,
|
||||
capture_output=capture_output,
|
||||
)
|
||||
last_result = result
|
||||
if result.returncode == 0:
|
||||
return result
|
||||
if check and last_result is not None:
|
||||
last_result.check_returncode()
|
||||
return last_result
|
||||
return adb(
|
||||
self.serial,
|
||||
["shell", command],
|
||||
check=check,
|
||||
capture_output=capture_output,
|
||||
)
|
||||
'''
|
||||
if old not in text:
|
||||
raise SystemExit('expected bootstrap_redroid.py block not found')
|
||||
path.write_text(text.replace(old, new))
|
||||
20
android13/patch_bootstrap_setupwizard.py
Normal file
20
android13/patch_bootstrap_setupwizard.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from pathlib import Path
|
||||
path = Path('/home/eric/redroid-android13/bootstrap_redroid.py')
|
||||
text = path.read_text()
|
||||
old = ''' try_shell(backend, "pm disable-user --user 0 com.android.setupwizard")
|
||||
try_shell(backend, "pm disable-user --user 0 com.google.android.setupwizard")
|
||||
try_shell(backend, "appops set com.android.shell android:mock_location allow")
|
||||
'''
|
||||
new = ''' for package in ("com.android.setupwizard", "com.google.android.setupwizard"):
|
||||
packages = backend.exec_shell(
|
||||
"pm list packages --user 0",
|
||||
check=False,
|
||||
capture_output=True,
|
||||
).stdout
|
||||
if package in packages:
|
||||
try_shell(backend, "pm disable-user --user 0 {}".format(shell_quote(package)))
|
||||
try_shell(backend, "appops set com.android.shell android:mock_location allow")
|
||||
'''
|
||||
if old not in text:
|
||||
raise SystemExit('expected setupwizard block not found')
|
||||
path.write_text(text.replace(old, new))
|
||||
427
android13/redroid13.sh
Normal file
427
android13/redroid13.sh
Normal file
@@ -0,0 +1,427 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user