Files
StefanandClaude Opus 5 ab4432cf84 Dateikonflikte beim Upgrade gezielt auflösen
Auf dem Gerät scheiterte das Upgrade an libmagic:

  trying to overwrite '.../include/magic.h', which is also in package file 5.47

Termux hat libmagic aus dem Paket file herausgelöst. Beide bringen dieselbe Datei
mit, und dpkg verweigert grundsätzlich, wenn zwei Pakete um eine Datei streiten.
Das ist kein Defekt, sondern der normale Zwischenzustand einer Paketaufteilung —
und er blockiert danach jede weitere Installation.

Beide Skripte erkennen den Konflikt jetzt an der Meldung und wiederholen mit
--force-overwrite. Bewusst erst dann und nicht von vornherein: pauschal
mitgegeben würde die Option jede künftige Dateikollision stillschweigend
übergehen, auch eine, die auf ein echtes Problem hinweist. Die betroffene Zeile
wird vorher angezeigt, damit nachvollziehbar bleibt, was übergangen wird.

Gegen die tatsächliche Ausgabe vom Gerät geprüft: Konflikt wird erkannt, und nach
dem Rauschfilter bleiben genau die zwei Zeilen übrig, auf die es ankommt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 21:52:20 +02:00

226 lines
9.9 KiB
Bash
Executable File

#!/data/data/com.termux/files/usr/bin/bash
# HPOS — Startpunkt auf dem Telefon.
#
# Dieses Skript ist absichtlich das EINZIGE, was per adb aufs Telefon kommt.
# Alles Weitere holt es sich per git. Der Grund: danach ist das Repo auf dem
# Telefon eine Arbeitskopie, keine Kopie. Aktualisieren heißt dann schlicht
#
# cd ~/hpos && git pull
#
# statt jedes Mal wieder den PC anzuwerfen und Dateien zu schieben.
#
# Aufruf in Termux:
# bash /sdcard/hpos-init.sh
# bash /sdcard/hpos-init.sh https://beispiel/anderes-repo.git
set -uo pipefail
REPO="${1:-${HPOS_REPO:-@@REPO_URL@@}}"
ZIEL="$HOME/hpos"
if [ -t 1 ]; then
C_OK=$'\033[32m'; C_WARN=$'\033[33m'; C_ERR=$'\033[31m'
C_HEAD=$'\033[1;36m'; C_DIM=$'\033[2m'; C_OFF=$'\033[0m'
else
C_OK=""; C_WARN=""; C_ERR=""; C_HEAD=""; C_DIM=""; C_OFF=""
fi
kopf() { printf '\n%s══ %s ══%s\n' "$C_HEAD" "$*" "$C_OFF"; }
ok() { printf '%s ✓%s %s\n' "$C_OK" "$C_OFF" "$*"; }
warn() { printf '%s !%s %s\n' "$C_WARN" "$C_OFF" "$*"; }
fehl() { printf '%s ✗%s %s\n' "$C_ERR" "$C_OFF" "$*"; }
hinweis() { printf '%s %s%s\n' "$C_DIM" "$*" "$C_OFF"; }
case "$REPO" in
@@*) fehl "Keine Repo-Adresse hinterlegt."
hinweis "Aufruf mit Adresse: bash /sdcard/hpos-init.sh <url>"
exit 1 ;;
esac
# Benutzernamen aus der Adresse entfernen. Lesen geht anonym; steht ein Name in
# der URL, fragt git trotzdem nach einem Passwort — auf einer Bildschirmtastatur
# eine unnötige Quälerei. Zum Schreiben braucht man ihn ohnehin nur auf dem PC.
REPO_ROH="$REPO"
REPO=$(printf '%s' "$REPO" | sed -E 's#^(https?://)[^/@]+@#\1#')
[ "$REPO" != "$REPO_ROH" ] && ANONYM=1 || ANONYM=0
kopf "HPOS — Ersteinrichtung auf dem Telefon"
hinweis "Repo: $REPO"
[ "$ANONYM" = 1 ] && hinweis "(Benutzername entfernt — gelesen wird anonym)"
# ── Vollupgrade ──────────────────────────────────────────────────────────────
# Muss VOR jedem anderen Paket laufen. Termux verträgt keine Teilupgrades: die
# Pakete werden gegeneinander gebaut, und ein einzeln nachinstalliertes trifft
# sonst auf veraltete Abhängigkeiten. Genau daran ist proot-distro gescheitert —
# es trug die Shebang-Zeile python3.13, während eine andere Fassung installiert war.
#
# Überspringbar mit HPOS_OHNE_UPGRADE=1, etwa bei knappem Datenvolumen.
kopf "Termux auf einen Stand bringen"
if [ "${HPOS_OHNE_UPGRADE:-0}" = 1 ]; then
warn "Übersprungen (HPOS_OHNE_UPGRADE=1)"
hinweis "Bei Problemen mit proot-distro ist das die erste Stelle zum Nachsehen."
else
hinweis "Das dauert und lädt einiges. Es erspart aber genau die Fehler,"
hinweis "die sonst erst Stunden später als kaputtes proot-distro auffallen."
export DEBIAN_FRONTEND=noninteractive
# Beim Wechsel der Python-Version wirft dpkg dutzende Zeilen
# "unable to delete old directory ... Directory not empty". Das sind
# Warnungen, keine Fehler: in site-packages liegen noch .pyc-Dateien oder
# selbst installierte Module. Sie herauszufiltern ist kein Verstecken —
# sie verdecken sonst die eine Zeile, auf die es ankommt.
rauschfilter() { grep -viE 'unable to delete old directory|^(Unpacking|Preparing|Selecting) '; }
# --force-confold: vorhandene Konfiguration behalten, statt mitten im Lauf
# eine Rückfrage zu stellen, die niemand beantwortet.
AUSGABE=$(mktemp)
pkg update -y >"$AUSGABE" 2>&1; rauschfilter <"$AUSGABE"
upgrade_lauf() {
pkg upgrade -y -o Dpkg::Options::=--force-confold "$@" >"$AUSGABE" 2>&1
local rc=$?
rauschfilter <"$AUSGABE"
return $rc
}
if upgrade_lauf; then
ok "Termux ist aktuell"
else
# Dateikonflikt gezielt behandeln. Er entsteht, wenn ein Paket aufgeteilt
# wurde und beide Teile kurzzeitig dieselbe Datei mitbringen — bei Termux
# etwa libmagic, herausgelöst aus file. dpkg verweigert dann grundsätzlich.
#
# --force-overwrite wird bewusst NUR hier gesetzt, nachdem der Konflikt in
# der Ausgabe nachgewiesen ist. Pauschal mitzugeben hieße, jede künftige
# Dateikollision stillschweigend zu übergehen.
if grep -q 'which is also in package' "$AUSGABE"; then
warn "Dateikonflikt zwischen zwei Paketen — typisch bei einer Paketaufteilung."
grep 'trying to overwrite' "$AUSGABE" | head -3 | sed 's/^/ /'
hinweis "Wiederhole gezielt mit --force-overwrite."
if upgrade_lauf -o Dpkg::Options::=--force-overwrite; then
ok "Konflikt aufgelöst, Upgrade durchgelaufen"
rm -f "$AUSGABE"
AUSGABE=""
fi
fi
if [ -n "$AUSGABE" ] && ! upgrade_lauf; then
warn "Upgrade weiterhin unvollständig — versuche dpkg zu entwirren."
# Nach einem Abbruch steckt dpkg halb fertig fest und blockiert alles.
dpkg --configure -a >>"$AUSGABE" 2>&1
apt-get -f install -y >>"$AUSGABE" 2>&1
if upgrade_lauf -o Dpkg::Options::=--force-overwrite; then
ok "Nach der Reparatur durchgelaufen"
else
warn "Bleibt unvollständig."
hinweis "Speicherplatz prüfen: df -h \$PREFIX (nicht / — das ist die Systempartition)"
hinweis "Fehler ohne Rauschen: pkg upgrade -y 2>&1 | grep -viE 'unable to delete' | tail -20"
hinweis "Weiter geht es trotzdem — aber hier ansetzen, falls später etwas klemmt."
fi
fi
fi
rm -f "$AUSGABE" 2>/dev/null || true
fi
# ── git ──────────────────────────────────────────────────────────────────────
kopf "git"
if command -v git >/dev/null 2>&1; then
ok "bereits vorhanden ($(git --version))"
else
if pkg install -y git; then ok "installiert"
else fehl "Installation von git fehlgeschlagen"; exit 1; fi
fi
# Das Telefon liest nur. Es soll nie versehentlich versuchen zurückzuschreiben —
# ein Push von hier wäre weder gewollt noch möglich.
git config --global credential.helper "" 2>/dev/null || true
# ── Rückfallebene: Archiv statt Klon ─────────────────────────────────────────
# Gitea, Forgejo und GitHub bieten den Baum zusätzlich als Tarball an. Der Weg
# dorthin geht nicht über upload-pack und funktioniert deshalb auch dann noch,
# wenn der Klon am Server scheitert. Das Ergebnis ist aber nur ein Abzug: kein
# .git, kein 'git pull'. Es ist eine Notlösung, keine Alternative.
archiv_holen() {
local basis zweig="${HPOS_ZWEIG:-main}" url tmp
basis="${REPO%.git}"
command -v curl >/dev/null 2>&1 || pkg install -y curl >/dev/null 2>&1
command -v tar >/dev/null 2>&1 || pkg install -y tar >/dev/null 2>&1
tmp=$(mktemp -d)
for url in "$basis/archive/$zweig.tar.gz" \
"$basis/archive/refs/heads/$zweig.tar.gz"; do
hinweis "Versuche Archiv: $url"
if curl -sfL -o "$tmp/a.tar.gz" "$url" && tar tzf "$tmp/a.tar.gz" >/dev/null 2>&1; then
mkdir -p "$ZIEL"
# --strip-components=1: die Archive enthalten alles unter einem
# Wurzelverzeichnis, das wir nicht mit übernehmen wollen.
if tar xzf "$tmp/a.tar.gz" -C "$ZIEL" --strip-components=1; then
rm -rf "$tmp"
ok "Archiv entpackt nach $ZIEL"
return 0
fi
fi
done
rm -rf "$tmp"
return 1
}
# ── Holen oder aktualisieren ─────────────────────────────────────────────────
kopf "Repo"
if [ -d "$ZIEL/.git" ]; then
ok "Arbeitskopie vorhanden — aktualisiere"
if git -C "$ZIEL" pull --ff-only; then
ok "aktuell"
else
warn "git pull fehlgeschlagen."
hinweis "Lokale Änderungen? Dann: cd ~/hpos && git stash && git pull"
exit 1
fi
else
# --depth reicht nicht: für 'git pull' später brauchen wir eine vollwertige
# Arbeitskopie, kein abgeschnittenes Archiv.
if git clone "$REPO" "$ZIEL"; then
ok "geklont nach $ZIEL"
else
fehl "git clone fehlgeschlagen."
hinweis "Adresse war: $REPO"
echo
# Ein scheiternder Klon bei erreichbarem Repo hat fast immer eine
# serverseitige Ursache: upload-pack stirbt beim Schnüren des Pakets,
# während die Objekte selbst heil sind. Erkennbar daran, dass sich ein
# Archiv weiterhin erzeugen lässt — und genau das ist die Rückfallebene.
if archiv_holen; then
warn "Über ein Archiv geholt, NICHT als Arbeitskopie."
hinweis "'git pull' funktioniert damit nicht. Sobald der Server wieder"
hinweis "Pakete ausliefert: rm -rf ~/hpos && bash /sdcard/hpos-init.sh"
else
fehl "Auch die Rückfallebene über das Archiv hat nicht geklappt."
hinweis "Ist das Repo öffentlich lesbar? Ohne das braucht es Zugangsdaten,"
hinweis "und die gehören nicht aufs Telefon."
exit 1
fi
fi
fi
chmod +x "$ZIEL"/spike/*.sh "$ZIEL"/spike/inner/*.sh 2>/dev/null || true
# ── Weiter ───────────────────────────────────────────────────────────────────
kopf "Fertig"
cat <<TEXT
Das Repo liegt jetzt unter ~/hpos und ist eine Arbeitskopie.
Aktualisieren später:
cd ~/hpos && git pull
Nächster Schritt:
cd ~/hpos
bash spike/00-bootstrap-termux.sh
Danach die Beweise der Reihe nach — S1 zuerst, weil er der wackligste ist:
bash spike/01-s1-plasma-mobile.sh
bash spike/02-s2-mikrofon.sh
bash spike/03-s3-pipewire-kamera.sh
bash spike/04-s4-intent-handoff.sh
TEXT