Compare commits
7 Commits
codex/offl
...
898238b3e0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
898238b3e0 | ||
|
|
f0bfb952e8 | ||
|
|
bf4a268526 | ||
|
|
0591362f8f | ||
|
|
2024daf22e | ||
|
|
97fe83cd3a | ||
|
|
08b532236d |
8
.gitignore
vendored
8
.gitignore
vendored
@@ -4,7 +4,7 @@ Dockerfile
|
|||||||
houdini/
|
houdini/
|
||||||
magisk/
|
magisk/
|
||||||
mindthegapps/
|
mindthegapps/
|
||||||
offline/downloads/*
|
# offline/downloads/*
|
||||||
!offline/downloads/.gitkeep
|
# !offline/downloads/.gitkeep
|
||||||
offline/images/*
|
# offline/images/*
|
||||||
!offline/images/.gitkeep
|
# !offline/images/.gitkeep
|
||||||
|
|||||||
@@ -26,6 +26,6 @@ python verify_offline_bundle.py
|
|||||||
|
|
||||||
1. Make sure your container runtime can load local image archives.
|
1. Make sure your container runtime can load local image archives.
|
||||||
2. Populate `downloads/` and `images/` with the files listed in `manifest.json`.
|
2. Populate `downloads/` and `images/` with the files listed in `manifest.json`.
|
||||||
3. Run `python redroid.py -mtg -i -m`.
|
3. Run `python redroid.py -mtg -i -m` (add `--offline` or set `REDROID_OFFLINE=1` to forbid any network use).
|
||||||
4. The build first tries to pull the base image and download dependency archives from their online sources.
|
4. The build prefers a local container image, then `images/redroid-redroid-13.0.0-latest.tar`, then a registry pull (skipped when offline-only).
|
||||||
5. If an online pull/download fails, the build falls back to the validated files in `downloads/` and `images/`.
|
5. Archives in `downloads/` are preferred when present and match the expected checksum; otherwise the build downloads from upstream (skipped when offline-only).
|
||||||
|
|||||||
BIN
offline/downloads/libhoudini.zip
Normal file
BIN
offline/downloads/libhoudini.zip
Normal file
Binary file not shown.
BIN
offline/downloads/libhoudini_hack.zip
Normal file
BIN
offline/downloads/libhoudini_hack.zip
Normal file
Binary file not shown.
BIN
offline/downloads/magisk.apk
Normal file
BIN
offline/downloads/magisk.apk
Normal file
Binary file not shown.
1
offline/downloads/magisk.version
Normal file
1
offline/downloads/magisk.version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
b149cd26
|
||||||
BIN
offline/downloads/mindthegapps.zip
Normal file
BIN
offline/downloads/mindthegapps.zip
Normal file
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
51
redroid.py
51
redroid.py
@@ -24,37 +24,48 @@ def ensure_base_image(container_runtime, android_version):
|
|||||||
)
|
)
|
||||||
local_present = inspect.returncode == 0
|
local_present = inspect.returncode == 0
|
||||||
|
|
||||||
helper.print_color(
|
tar_name = "redroid-redroid-{}-latest.tar".format(android_version)
|
||||||
"Trying to pull base image {} first ...".format(base_image),
|
tar_path = os.path.join(helper.get_image_dir(), tar_name)
|
||||||
helper.bcolors.GREEN,
|
tar_exists = os.path.isfile(tar_path)
|
||||||
)
|
offline_only = helper.is_offline_only()
|
||||||
pull = subprocess.run([container_runtime, "pull", base_image], check=False)
|
|
||||||
if pull.returncode == 0:
|
|
||||||
return "pulled"
|
|
||||||
|
|
||||||
if local_present:
|
if local_present:
|
||||||
helper.print_color(
|
helper.print_color(
|
||||||
"Online pull failed, falling back to local image cache ...",
|
"Using existing local base image {} ...".format(base_image),
|
||||||
helper.bcolors.YELLOW,
|
helper.bcolors.GREEN,
|
||||||
)
|
)
|
||||||
return "present"
|
return "present"
|
||||||
|
|
||||||
tar_name = "redroid-redroid-{}-latest.tar".format(android_version)
|
if tar_exists:
|
||||||
tar_path = os.path.join(helper.get_image_dir(), tar_name)
|
helper.print_color(
|
||||||
if not os.path.isfile(tar_path):
|
"Loading base image from offline archive {} ...".format(tar_path),
|
||||||
|
helper.bcolors.GREEN,
|
||||||
|
)
|
||||||
|
subprocess.run([container_runtime, "load", "-i", tar_path], check=True)
|
||||||
|
return tar_path
|
||||||
|
|
||||||
|
if offline_only:
|
||||||
raise FileNotFoundError(
|
raise FileNotFoundError(
|
||||||
"Online pull for {} failed, local image cache is missing, and offline archive {} was not found".format(
|
"REDROID_OFFLINE is set but {} is not in local storage and {} was not found".format(
|
||||||
base_image,
|
base_image,
|
||||||
tar_path,
|
tar_path,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
helper.print_color(
|
helper.print_color(
|
||||||
"Online pull failed, loading local image archive {} ...".format(tar_path),
|
"No local base image or offline .tar; pulling {} ...".format(base_image),
|
||||||
helper.bcolors.YELLOW,
|
helper.bcolors.YELLOW,
|
||||||
)
|
)
|
||||||
subprocess.run([container_runtime, "load", "-i", tar_path], check=True)
|
pull = subprocess.run([container_runtime, "pull", base_image], check=False)
|
||||||
return tar_path
|
if pull.returncode == 0:
|
||||||
|
return "pulled"
|
||||||
|
|
||||||
|
raise FileNotFoundError(
|
||||||
|
"Pull failed for {} and offline archive {} was not found".format(
|
||||||
|
base_image,
|
||||||
|
tar_path,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def build_layers(args):
|
def build_layers(args):
|
||||||
@@ -145,8 +156,16 @@ def main():
|
|||||||
choices=["docker", "podman"],
|
choices=["docker", "podman"],
|
||||||
help="Container runtime",
|
help="Container runtime",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--offline",
|
||||||
|
dest="offline",
|
||||||
|
action="store_true",
|
||||||
|
help="Do not pull images or download archives; require offline/downloads and offline/images",
|
||||||
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
if args.offline:
|
||||||
|
os.environ["REDROID_OFFLINE"] = "1"
|
||||||
base_image_source = ensure_base_image(args.container, args.android)
|
base_image_source = ensure_base_image(args.container, args.android)
|
||||||
dockerfile, tags = build_layers(args)
|
dockerfile, tags = build_layers(args)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import os
|
import os
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
from tools.helper import bcolors, download_file, file_md5, print_color
|
from tools.helper import bcolors, download_file, file_md5, is_offline_only, print_color
|
||||||
|
|
||||||
class General:
|
class General:
|
||||||
def _local_md5(self):
|
def _local_md5(self):
|
||||||
@@ -17,10 +17,24 @@ class General:
|
|||||||
temp_path = self.dl_file_name + ".tmp"
|
temp_path = self.dl_file_name + ".tmp"
|
||||||
local_valid = self._has_valid_local_file()
|
local_valid = self._has_valid_local_file()
|
||||||
|
|
||||||
|
if local_valid:
|
||||||
|
print_color(
|
||||||
|
"Using validated offline bundle {} ...".format(self.dl_file_name),
|
||||||
|
bcolors.GREEN,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if is_offline_only():
|
||||||
|
raise FileNotFoundError(
|
||||||
|
"REDROID_OFFLINE is set but offline file is missing or MD5 mismatch: {}".format(
|
||||||
|
self.dl_file_name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if os.path.isfile(temp_path):
|
if os.path.isfile(temp_path):
|
||||||
os.remove(temp_path)
|
os.remove(temp_path)
|
||||||
print_color("Trying online download first ...", bcolors.GREEN)
|
print_color("Local bundle not found or MD5 mismatch; downloading ...", bcolors.YELLOW)
|
||||||
remote_md5 = download_file(self.dl_link, temp_path)
|
remote_md5 = download_file(self.dl_link, temp_path)
|
||||||
if remote_md5 != self.act_md5:
|
if remote_md5 != self.act_md5:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@@ -30,18 +44,9 @@ class General:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
os.replace(temp_path, self.dl_file_name)
|
os.replace(temp_path, self.dl_file_name)
|
||||||
except Exception as exc:
|
except Exception:
|
||||||
if os.path.isfile(temp_path):
|
if os.path.isfile(temp_path):
|
||||||
os.remove(temp_path)
|
os.remove(temp_path)
|
||||||
if local_valid:
|
|
||||||
print_color(
|
|
||||||
"Online download failed, falling back to local {} ({})".format(
|
|
||||||
self.dl_file_name,
|
|
||||||
exc,
|
|
||||||
),
|
|
||||||
bcolors.YELLOW,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def extract(self):
|
def extract(self):
|
||||||
|
|||||||
@@ -4,14 +4,31 @@ import re
|
|||||||
import zipfile
|
import zipfile
|
||||||
import requests
|
import requests
|
||||||
from stuff.general import General
|
from stuff.general import General
|
||||||
from tools.helper import bcolors, download_file, host, print_color, run, get_download_dir
|
from tools.helper import (
|
||||||
|
bcolors,
|
||||||
|
download_file,
|
||||||
|
get_download_dir,
|
||||||
|
host,
|
||||||
|
is_offline_only,
|
||||||
|
print_color,
|
||||||
|
run,
|
||||||
|
)
|
||||||
|
|
||||||
class Magisk(General):
|
class Magisk(General):
|
||||||
stable_json = "https://raw.githubusercontent.com/os-fork/HuskyDG-magisk-files/main/stable.json"
|
_default_stable_json = (
|
||||||
|
"https://raw.githubusercontent.com/os-fork/HuskyDG-magisk-files/main/stable.json"
|
||||||
|
)
|
||||||
|
# HuskyDG/download was removed from GitHub; stable.json still points there.
|
||||||
|
_kitsune_26_4_mirror_apk = (
|
||||||
|
"https://raw.githubusercontent.com/Scratch2033Alt/KitsuneMagisk/main/26.4-kitsune.apk"
|
||||||
|
)
|
||||||
machine = host()
|
machine = host()
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.download_loc = get_download_dir()
|
self.download_loc = get_download_dir()
|
||||||
|
self.stable_json_url = (
|
||||||
|
os.environ.get("REDROID_MAGISK_STABLE_JSON", "").strip() or self._default_stable_json
|
||||||
|
)
|
||||||
self.dl_file_name = os.path.join(self.download_loc, "magisk.apk")
|
self.dl_file_name = os.path.join(self.download_loc, "magisk.apk")
|
||||||
self.release_meta_file = os.path.join(self.download_loc, "magisk.version")
|
self.release_meta_file = os.path.join(self.download_loc, "magisk.version")
|
||||||
self.extract_to = os.path.join(self.download_loc, "magisk_unpack")
|
self.extract_to = os.path.join(self.download_loc, "magisk_unpack")
|
||||||
@@ -154,27 +171,38 @@ on property:init.svc.zygote=stopped
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _magisk_download_candidates(self):
|
||||||
|
primary = self.dl_link
|
||||||
|
if "HuskyDG/download" in primary and str(self.release_version).startswith("26.4"):
|
||||||
|
return [self._kitsune_26_4_mirror_apk, primary]
|
||||||
|
return [primary]
|
||||||
|
|
||||||
def _download_remote_archive(self):
|
def _download_remote_archive(self):
|
||||||
temp_path = self.dl_file_name + ".tmp"
|
temp_path = self.dl_file_name + ".tmp"
|
||||||
try:
|
last_error = None
|
||||||
if os.path.isfile(temp_path):
|
|
||||||
os.remove(temp_path)
|
|
||||||
print_color(
|
print_color(
|
||||||
"Trying online download for Kitsune Mask {} ...".format(self.release_version),
|
"Trying online download for Kitsune Mask {} ...".format(self.release_version),
|
||||||
bcolors.GREEN,
|
bcolors.GREEN,
|
||||||
)
|
)
|
||||||
download_file(self.dl_link, temp_path)
|
for candidate_url in self._magisk_download_candidates():
|
||||||
|
try:
|
||||||
|
if os.path.isfile(temp_path):
|
||||||
|
os.remove(temp_path)
|
||||||
|
print_color(" from {} ...".format(candidate_url), bcolors.GREEN)
|
||||||
|
download_file(candidate_url, temp_path, timeout=120)
|
||||||
self._validate_archive(temp_path)
|
self._validate_archive(temp_path)
|
||||||
os.replace(temp_path, self.dl_file_name)
|
os.replace(temp_path, self.dl_file_name)
|
||||||
self._write_version()
|
self._write_version()
|
||||||
except Exception:
|
return
|
||||||
|
except Exception as exc:
|
||||||
|
last_error = exc
|
||||||
if os.path.isfile(temp_path):
|
if os.path.isfile(temp_path):
|
||||||
os.remove(temp_path)
|
os.remove(temp_path)
|
||||||
raise
|
raise last_error
|
||||||
|
|
||||||
def resolve_release(self):
|
def resolve_release(self):
|
||||||
print_color("Resolving Kitsune Mask stable release ...", bcolors.GREEN)
|
print_color("Resolving Kitsune Mask stable release ...", bcolors.GREEN)
|
||||||
response = requests.get(self.stable_json, timeout=30)
|
response = requests.get(self.stable_json_url, timeout=30)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
payload = response.json()
|
payload = response.json()
|
||||||
magisk_info = payload["magisk"]
|
magisk_info = payload["magisk"]
|
||||||
@@ -205,39 +233,32 @@ on property:init.svc.zygote=stopped
|
|||||||
local_valid = self._has_valid_local_archive()
|
local_valid = self._has_valid_local_archive()
|
||||||
local_version = self._cached_version() or "bundled"
|
local_version = self._cached_version() or "bundled"
|
||||||
|
|
||||||
try:
|
if local_valid:
|
||||||
|
self.release_version = local_version
|
||||||
|
print_color(
|
||||||
|
"Using offline Magisk/Kitsune APK {} ({}) ...".format(
|
||||||
|
self.dl_file_name,
|
||||||
|
self.release_version,
|
||||||
|
),
|
||||||
|
bcolors.GREEN,
|
||||||
|
)
|
||||||
|
self._validate_archive()
|
||||||
|
return
|
||||||
|
|
||||||
|
if is_offline_only():
|
||||||
|
raise FileNotFoundError(
|
||||||
|
"REDROID_OFFLINE is set but no valid offline magisk.apk at {}".format(
|
||||||
|
self.dl_file_name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if self.custom_url:
|
if self.custom_url:
|
||||||
self.dl_link = self.custom_url
|
self.dl_link = self.custom_url
|
||||||
self.release_version = "url:" + os.path.basename(self.custom_url)
|
self.release_version = "url:" + os.path.basename(self.custom_url)
|
||||||
else:
|
else:
|
||||||
self.resolve_release()
|
self.resolve_release()
|
||||||
except Exception as exc:
|
|
||||||
if local_valid:
|
|
||||||
self.release_version = local_version
|
|
||||||
print_color(
|
|
||||||
"Resolving Kitsune Mask online failed, falling back to local {} ({})".format(
|
|
||||||
self.release_version,
|
|
||||||
exc,
|
|
||||||
),
|
|
||||||
bcolors.YELLOW,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
raise
|
|
||||||
|
|
||||||
try:
|
|
||||||
self._download_remote_archive()
|
self._download_remote_archive()
|
||||||
except Exception as exc:
|
|
||||||
if local_valid:
|
|
||||||
self.release_version = local_version
|
|
||||||
print_color(
|
|
||||||
"Downloading Kitsune Mask online failed, falling back to local {} ({})".format(
|
|
||||||
self.release_version,
|
|
||||||
exc,
|
|
||||||
),
|
|
||||||
bcolors.YELLOW,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
raise
|
|
||||||
|
|
||||||
def extract(self):
|
def extract(self):
|
||||||
print_color("Extracting archive...", bcolors.GREEN)
|
print_color("Extracting archive...", bcolors.GREEN)
|
||||||
|
|||||||
@@ -26,6 +26,12 @@ def get_image_dir():
|
|||||||
return ensure_dir(IMAGE_ROOT)
|
return ensure_dir(IMAGE_ROOT)
|
||||||
|
|
||||||
|
|
||||||
|
def is_offline_only():
|
||||||
|
"""If true, redroid build must not use the network (env REDROID_OFFLINE or CLI --offline)."""
|
||||||
|
v = os.environ.get("REDROID_OFFLINE", "").strip().lower()
|
||||||
|
return v in ("1", "true", "yes", "on")
|
||||||
|
|
||||||
|
|
||||||
def file_md5(path, block_size=1024 * 1024):
|
def file_md5(path, block_size=1024 * 1024):
|
||||||
hasher = hashlib.md5()
|
hasher = hashlib.md5()
|
||||||
with open(path, "rb") as handle:
|
with open(path, "rb") as handle:
|
||||||
|
|||||||
Reference in New Issue
Block a user