Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions iiab-android
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,30 @@ local current_pwd="$(pwd)"
fi
}

# Pre-seed /opt/iiab/iiab and apply AppDevForAll's in-flight upstream patches
# BEFORE the generic installer runs Ansible. Safe because the upstream installer
# (/usr/sbin/iiab from iiab-factory) clones iiab only if it is absent and never
# pulls/resets an existing checkout, so our patched tree survives into the run
# (e.g. the kolibri role reads its .patch during it). The applier is idempotent
# and exits non-zero on context drift, so a bad carry fails the build loudly
# instead of baking a half-patched tree. See tools/upstream-patches/README.md.
apply_upstream_patches() {
local applier="${K2GO_OPT_DIR}/tools/upstream-patches/apply-upstream-patches.sh"
if [ ! -f "$applier" ]; then
log "No upstream-patches applier found; skipping."
return 0
fi
command -v git > /dev/null 2>&1 || apt-get -y install git
command -v patch > /dev/null 2>&1 || apt-get -y install patch
mkdir -p /opt/iiab
if [ ! -d /opt/iiab/iiab ]; then
log "Pre-seeding /opt/iiab/iiab so upstream patches apply before Ansible..."
git clone https://github.com/iiab/iiab /opt/iiab/iiab || die "could not clone iiab/iiab to pre-seed upstream patches"
fi
log "Applying in-flight upstream patches to /opt/iiab/iiab..."
bash "$applier" --root /opt/iiab/iiab || die "upstream patch apply failed (see log above)"
}

#-----------------------------
# Demo Contents Installer
#-----------------------------
Expand Down Expand Up @@ -533,6 +557,11 @@ disable_role_32bits kiwix "$LOCAL_VARS_DEST"
#-----------------------------
ensure_proot_safe_env

#-----------------------------
# Apply our in-flight upstream patches to /opt/iiab/iiab before Ansible runs
#-----------------------------
apply_upstream_patches

#-----------------------------
# Fetch install.txt with fallback and run it
#-----------------------------
Expand Down
Binary file added tools/upstream-patches/README.md
Binary file not shown.
100 changes: 100 additions & 0 deletions tools/upstream-patches/apply-upstream-patches.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#!/usr/bin/env bash
#
# apply-upstream-patches.sh — apply AppDevForAll's in-flight changes to the
# IIAB project checkout (/opt/iiab/iiab) that are proposed upstream but not yet
# merged. Idempotent: a patch that is already present (e.g. upstream merged it,
# or a previous run applied it) is detected and skipped, never applied twice.
#
# Design & conventions: see README.md in this folder.
#
# Usage (run inside the proot guest, after /opt/iiab/iiab exists and BEFORE the
# Ansible roles run):
# tools/upstream-patches/apply-upstream-patches.sh [--root DIR] [--dry-run]
#
# Exit codes: 0 = all patches applied or already-present; 1 = one or more
# patches could not be applied (context drift → needs regeneration). We fail
# loudly rather than bake a half-patched tree.

set -euo pipefail

ROOT="/opt/iiab/iiab" # the IIAB project checkout to patch
DRY_RUN=0
STRIP=1 # patches are generated relative to the repo root → -p1

SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PATCH_DIR="${SELF_DIR}/patches"
OVERLAY_DIR="${SELF_DIR}/overlays" # optional: whole-file replacements

log() { printf '[upstream-patches] %s\n' "$*"; }
warn() { printf '[upstream-patches] WARN: %s\n' "$*" >&2; }

while [[ $# -gt 0 ]]; do
case "$1" in
--root) ROOT="$2"; shift 2 ;;
--dry-run) DRY_RUN=1; shift ;;
-h|--help) grep '^#' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
*) warn "unknown arg: $1"; exit 2 ;;
esac
done

[[ -d "$ROOT" ]] || { warn "target root not found: $ROOT (nothing to patch)"; exit 0; }
command -v patch >/dev/null 2>&1 || { warn "'patch' not installed in the guest"; exit 1; }

rc=0
applied=0 skipped=0 failed=0

# --- 1) Unified-diff patches (the primary mechanism) ------------------------
if [[ -d "$PATCH_DIR" ]]; then
# Deterministic order: NNNN- numeric prefix. *.patch only (templates use .txt).
shopt -s nullglob
for f in "$PATCH_DIR"/*.patch; do
name="$(basename "$f")"
# Already present? (upstream merged it, or a prior run applied it.) A clean
# REVERSE apply proves the change is already in the tree → skip.
if patch -p"$STRIP" -d "$ROOT" -R --dry-run < "$f" >/dev/null 2>&1; then
log "skip (already present): $name"
skipped=$((skipped+1))
continue
fi
# Not present → does it apply cleanly forward?
if patch -p"$STRIP" -d "$ROOT" --dry-run < "$f" >/dev/null 2>&1; then
if [[ "$DRY_RUN" -eq 1 ]]; then
log "would apply: $name"
else
patch -p"$STRIP" -d "$ROOT" < "$f" >/dev/null
log "applied: $name"
fi
applied=$((applied+1))
else
warn "could NOT apply (context drift / partially applied): $name"
warn " → regenerate against the current /opt/iiab/iiab, or drop if superseded."
failed=$((failed+1)); rc=1
fi
done
shopt -u nullglob
fi

# --- 2) Optional whole-file overlays ----------------------------------------
# For cases better expressed as a full-file replacement than a diff (e.g.
# replacing a file that is itself a .patch). Mirror the tree under overlays/
# rooted at the repo root; a file is copied only if it differs (idempotent).
if [[ -d "$OVERLAY_DIR" ]]; then
while IFS= read -r -d '' src; do
rel="${src#"$OVERLAY_DIR"/}"
dst="$ROOT/$rel"
if [[ -f "$dst" ]] && cmp -s "$src" "$dst"; then
log "skip overlay (identical): $rel"; skipped=$((skipped+1)); continue
fi
if [[ "$DRY_RUN" -eq 1 ]]; then
log "would overlay: $rel"
else
mkdir -p "$(dirname "$dst")"
cp -a "$src" "$dst"
log "overlay: $rel"
fi
applied=$((applied+1))
done < <(find "$OVERLAY_DIR" -type f ! -name '.gitkeep' -print0 2>/dev/null)
fi

log "summary: applied=${applied} skipped=${skipped} failed=${failed} (root=${ROOT})"
exit "$rc"
1 change: 1 addition & 0 deletions tools/upstream-patches/overlays/.gitkeep
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Optional whole-file overlays mirror the /opt/iiab/iiab tree here. See README.md.
21 changes: 21 additions & 0 deletions tools/upstream-patches/patches/0000-EXAMPLE-template.patch.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
EXAMPLE / TEMPLATE — not applied (the applier only picks up *.patch, not *.txt).
Copy this header, generate a real diff, and save as NNNN-<area>-<slug>.patch.

Upstream-PR: https://github.com/iiab/iiab/pull/XXXX # or "not yet submitted"
Upstream-Status: open # open | merged-pending-release | superseded
Applies-to: roles/<area>/... # path within /opt/iiab/iiab
Summary: one sober line: what changes and why

# Generate (relative to the repo root so it applies with -p1 over /opt/iiab/iiab):
# git -C /opt/iiab/iiab diff > 0001-<area>-<slug>.patch
# # or, mirroring the upstream commit:
# git -C /opt/iiab/iiab format-patch -1 --stdout > 0001-<area>-<slug>.patch

diff --git a/roles/<area>/tasks/main.yml b/roles/<area>/tasks/main.yml
index 0000000..0000000 100644
--- a/roles/<area>/tasks/main.yml
+++ b/roles/<area>/tasks/main.yml
@@ -1,3 +1,3 @@
# (example context)
-old line
+new line
100 changes: 100 additions & 0 deletions tools/upstream-patches/patches/0001-kolibri-trust-system-ca.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
Upstream-PR: https://github.com/iiab/iiab/pull/4442
Upstream-Status: open
Applies-to: roles/proot_services/files/kolibri-00-prevent_ip_error_out_due_A15_restrictions.patch
Summary: Kolibri: trust the system CA bundle so "Kolibri Studio (online)" import works under Android/proot

Mirrors upstream commit bbf6cb8 ("[kolibri] update proot patch: trust system CA
so online import works"). This edits the kolibri proot patch file that the
proot_services role applies to the installed Kolibri package. If the pinned
iiab/iiab commit's copy of that file drifts, the applier fails loudly and this
should be regenerated. Remove once #4442 merges and ships in the pinned commit.

diff --git a/roles/proot_services/files/kolibri-00-prevent_ip_error_out_due_A15_restrictions.patch b/roles/proot_services/files/kolibri-00-prevent_ip_error_out_due_A15_restrictions.patch
index 2ab48703d5..53924dafe7 100644
--- a/roles/proot_services/files/kolibri-00-prevent_ip_error_out_due_A15_restrictions.patch
+++ b/roles/proot_services/files/kolibri-00-prevent_ip_error_out_due_A15_restrictions.patch
@@ -1,9 +1,10 @@
This patch is necessary on,

+* Android 16 - yes
* Android 15 - yes
* Android 14 - untested
* Android 13 - untested
-* Android 12 - no
+* Android 12 - untested
* Android 11 - untested
* Andoird 10 - untested

@@ -41,3 +42,72 @@ index a11b0e9..4b0b8b7 100644


def get_urls(listen_port=None):
+--- a/usr/lib/python3/dist-packages/kolibri/utils/http_session.py
++++ b/usr/lib/python3/dist-packages/kolibri/utils/http_session.py
+@@ -19,6 +19,24 @@
+ class SameHostSession(requests.Session):
+ """A :class:`requests.Session` that refuses cross-host redirects."""
+
++ def __init__(self, *args, **kwargs):
++ super().__init__(*args, **kwargs)
++ # IIAB/proot: prefer the system CA bundle for outbound HTTPS. Under
++ # Android/proot the cert store requests bundles can be stale or absent
++ # while the system store is valid, which made every content-server
++ # request fail and disabled "Kolibri Studio (online)" despite internet.
++ import os
++
++ for _ca in (
++ os.environ.get("REQUESTS_CA_BUNDLE"),
++ os.environ.get("SSL_CERT_FILE"),
++ "/etc/ssl/certs/ca-certificates.crt",
++ "/etc/ssl/cert.pem",
++ ):
++ if _ca and os.path.exists(_ca):
++ self.verify = _ca
++ break
++
+ def get_redirect_target(self, resp):
+ target = super().get_redirect_target(resp)
+ if target is None:
+--- a/usr/lib/python3/dist-packages/kolibri/core/content/api.py
++++ b/usr/lib/python3/dist-packages/kolibri/core/content/api.py
+@@ -2066,6 +2066,41 @@
+ NetworkLocationConnectionFailure,
+ NetworkLocationNotFound,
+ ):
++ # IIAB/proot fallback: NetworkClient can wrongly report the content
++ # server as unreachable under Android/proot due stalled ca-certificates.
++ # When the device actually has internet, confirm reachability
++ # with a plain request that uses the system CA bundle and follows
++ # redirects, so the online import option is not disabled wrongly.
++ import os
++ import requests as _requests
++
++ _verify = True
++ for _ca in (
++ os.environ.get("REQUESTS_CA_BUNDLE"),
++ os.environ.get("SSL_CERT_FILE"),
++ "/etc/ssl/certs/ca-certificates.crt",
++ "/etc/ssl/cert.pem",
++ ):
++ if _ca and os.path.exists(_ca):
++ _verify = _ca
++ break
++ try:
++ _resp = _requests.get(
++ CENTRAL_CONTENT_BASE_URL.rstrip("/") + "/api/public/info",
++ timeout=15,
++ allow_redirects=True,
++ verify=_verify,
++ )
++ if _resp.status_code == 200:
++ try:
++ _data = _resp.json()
++ except ValueError:
++ _data = {}
++ _data["available"] = True
++ _data["status"] = "online"
++ return Response(_data)
++ except Exception:
++ pass
+ return Response({"status": "offline", "available": False})