commit ad2c98443ba512d5fdf4551d129cde2e53206619 Author: Stefan Date: Sun Aug 9 17:25:36 2026 +0200 Phase 0: Repo-Gerüst, Dokumentation und Machbarkeits-Spike Vier Beweise, die das Projekt tragen, als eigenständig lauffähige Skripte: S1 Plasma Mobile in proot über genestetes kwin_wayland im X11-Backend. Fällt selbsttätig auf startplasmamobile und dann auf XFCE zurück, damit ein Fehlschlag eindeutig Plasma zuzuordnen ist und nicht der Umgebung. S2 Mikrofon über PulseAudio mit OpenSL-Quelle. Misst den Pegel statt nur die Dateigröße — der typische Fehlschlag ist eine formal korrekte WAV-Datei voller Stille. S3 Virtuelle PipeWire-Kamera per GStreamer-pipewiresink, ohne Kernel-Modul. Setzt die Firefox-Einstellung vorab, damit der Test nicht an einem vergessenen Häkchen scheitert. S4 Intent-Handoff mit lauffähiger Mini-Bridge: xdg-open-Handler für geo:, tel: und sms:, Token-Authentifizierung, feste Liste erlaubter Schemata. provision/provision.sh setzt die Hintergrundbeschränkungen per ADB und benennt, was ADB nicht kann und von Hand erledigt werden muss. Dokumentiert sind auch die sieben verworfenen Wege — Waydroid, AVF, QEMU, SIP, APK-Daten-Redirect, Fenster-Durchreichung und der Android-Server — mit Belegen, damit sie nicht erneut durchdacht werden müssen. Co-Authored-By: Claude Opus 5 (1M context) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..08a7ae1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# Spike-Ergebnisse: Rohdaten bleiben lokal, Auswertung kommt nach docs/ +spike/out/ +spike/*.log +spike/*.wav + +# Baukram +daemon/hpos-daemon +daemon/dist/ +android/build/ +android/.gradle/ +android/local.properties +*.apk +*.aab + +# Niemals ins Repo: Signaturschlüssel, Zugangsdaten, Backup-Ziele +*.keystore +*.jks +keystore.properties +secrets.env +config.local.* + +# Editor / OS +.idea/ +.vscode/ +*.swp +.DS_Store diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..8d613c7 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,42 @@ +# Mitarbeiten + +Das Projekt ist noch in **Phase 0** (Machbarkeits-Spike). Bis die vier Beweise S1–S4 +stehen, kann sich die Architektur noch grundlegend ändern — Beiträge sind trotzdem +willkommen, vor allem Geräte-Meldungen. + +## Am hilfreichsten gerade jetzt + +**Geräte-Meldungen.** Lauf die Skripte aus [`spike/`](spike/) auf deinem Gerät und +melde das Ergebnis, egal ob es funktioniert hat. Ein sauber dokumentierter Fehlschlag +ist genauso wertvoll wie ein Erfolg — beides geht in die Kompatibilitätsliste. + +## Ehernes Gesetz + +Zwei Regeln, die keine Ausnahme kennen. Ein Beitrag, der eine davon bricht, wird +abgelehnt, egal wie nützlich er sonst ist: + +1. **Der Bridge-Layer auf der Android-Seite ist zustandslos.** Er speichert keine + Nutzdaten. Alles wird ins Linux durchgereicht und nur dort persistiert. +2. **Keine Serverabhängigkeit.** Das Telefon funktioniert vollständig ohne fremde + Infrastruktur. Ein externes Backup-Ziel ist optional und jederzeit abwählbar. + +Dazu zwei Regeln, die den Alltag betreffen: + +3. **Kein Root, kein entsperrter Bootloader.** Wenn etwas nur mit Root geht, geht es + in diesem Projekt nicht. +4. **Kein Privilegienkanal im Dauerbetrieb.** ADB ist ausschließlich für die einmalige + Ersteinrichtung vorgesehen. Alles Laufende kommt mit gewöhnlichen App-Berechtigungen aus. + +## Bevor du etwas vorschlägst + +Lies [`docs/verworfene-entscheidungen.md`](docs/verworfene-entscheidungen.md). Sieben +naheliegende Wege wurden bereits geprüft und begründet verworfen — Waydroid, AVF, SIP, +APK-Daten-Redirect und andere. Wenn du einen davon wiederbeleben willst: gern, aber mit +einem Argument, das dort noch nicht widerlegt ist. + +## Stil + +- Skripte: `bash`, `set -euo pipefail`, deutschsprachige Ausgaben, jeder Schritt protokolliert +- Kommentare erklären das *Warum*, nicht das *Was* +- Architekturentscheidungen kommen als ADR nach `docs/`, nicht in die Commit-Nachricht +- Keine Zugangsdaten, Tokens oder Zieladressen im Repo — auch nicht in Beispieldateien diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ae02cf4 --- /dev/null +++ b/README.md @@ -0,0 +1,95 @@ +# HPOS — Linux-Smartphone auf Stock-Android, ohne Root + +> Arbeitstitel. Der Projektname steht noch nicht fest. + +Ein beliebiges gebrauchtes Android-Smartphone wird zum Linux-Telefon: Debian 13 mit +Plasma Mobile in Termux/proot, mit einem Backup- und Provisioning-System drumherum, +das die eigentliche Substanz des Projekts ausmacht. + +**Kein Root. Kein entsperrter Bootloader. Kein Server nötig.** + +## Was es ist + +"Linux auf dem Handy" gibt es schon. Was es nicht gibt, ist der Rest: + +- **Backup mit echten Snapshots** — Einzeldatei-Restore, Web-Manager im Browser, + ZIP-Export aufs Telefon, Ziele über SFTP/SMB/FTP/WebDAV +- **Provisioning in einem Rutsch** — Telefon auspacken, eine APK, ~20 Minuten, fertiges System +- **Klonen aufs neue Telefon per WLAN** — Gerätewechsel als Normalfall, nicht als Notfall +- **Bridge zum Android darunter** — SMS, Telefonie, Kamera, Sensoren erscheinen im Linux + über Standard-D-Bus-Schnittstellen, sodass unveränderte Plasma-Anwendungen sie nutzen + +Die Android-Installation darunter ist nur noch Treiber-Layer und Prozess-Host. +Alle Nutzdaten leben im Linux und damit im Backup. + +## Status + +**Phase 0 — Machbarkeits-Spike.** Vier Annahmen tragen das Projekt und werden zuerst +bewiesen, bevor Infrastruktur entsteht: + +| | Annahme | Wenn es scheitert | +|---|---|---| +| **S1** | Plasma Mobile läuft in proot (genestetes KWin) | XFCE-Profil wird Hauptweg | +| **S2** | Mikrofon-Eingang erreicht die Linux-Seite | Keine Videocalls, keine Sprachaufnahme | +| **S3** | Virtuelle PipeWire-Kamera ohne Kernel-Modul | Kamera nur in einer eigenen App | +| **S4** | Intent-Handoff Linux → Android-App | Keine Navigation aus dem Linux-Adressbuch | + +Noch nichts davon ist bewiesen. Die Skripte in [`spike/`](spike/) führen die Beweise; +Ergebnisse landen in [`docs/spike-protokoll.md`](docs/spike-protokoll.md). + +## Loslegen + +Getestet wird auf einem **OnePlus 7 Pro (Snapdragon 855, Adreno 640) mit Stock OxygenOS 12**. + +```bash +# 1. Auf dem PC, Telefon per USB, USB-Debugging an: +./provision/provision.sh + +# 2. Auf dem Telefon in Termux: +bash spike/00-bootstrap-termux.sh + +# 3. Beweise der Reihe nach: +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 +``` + +Details und Voraussetzungen: [`spike/README.md`](spike/README.md). + +## Dokumentation + +- [Architektur](docs/architektur.md) — wie die Teile zusammenhängen +- [Machbarkeit](docs/machbarkeit.md) — was ohne Root geht und was nicht, mit Belegen +- [Verworfene Entscheidungen](docs/verworfene-entscheidungen.md) — sieben Wege, die + geprüft und begründet verworfen wurden. **Vor jedem "warum eigentlich nicht …" lesen.** +- [Spike-Protokoll](docs/spike-protokoll.md) — Messergebnisse von Phase 0 + +## Geräte + +| Gerät | SoC / GPU | Android | Status | +|---|---|---|---| +| OnePlus 7 Pro (`guacamole`) | Snapdragon 855 / Adreno 640 | OxygenOS 12 | 🔬 im Test | + +Läuft es bei dir? [Geräte-Meldung eröffnen](.github/ISSUE_TEMPLATE/geraet-melden.md). + +## Lizenz + +GPLv3 — siehe [LICENSE](LICENSE). Das Projekt verteilt Termux (GPLv3) mit; +die einheitliche Lizenz erfüllt die Quellen-Angebotspflicht. + +--- + +# HPOS — a Linux phone on stock Android, no root + +Turns any second-hand Android phone into a Linux phone: Debian 13 with Plasma Mobile +inside Termux/proot, wrapped in the backup and provisioning system that is the actual +point of the project. + +**No root. No unlocked bootloader. No server required.** + +Currently in **Phase 0** — proving four load-bearing assumptions (S1–S4 above) before +any infrastructure gets built. See [`spike/`](spike/) and +[`docs/spike-protokoll.md`](docs/spike-protokoll.md). + +Documentation is currently German-first; English will follow once Phase 0 concludes. diff --git a/docs/architektur.md b/docs/architektur.md new file mode 100644 index 0000000..fe4718b --- /dev/null +++ b/docs/architektur.md @@ -0,0 +1,109 @@ +# Architektur + +## Überblick + +``` +┌─ Telefon (Stock-Android, unrooted) ────────────────────────────────┐ +│ │ +│ ┌────────────────────────┐ ┌────────────────────────────────┐ │ +│ │ HPOS Bridge (APK) │ │ Termux (+:API, :X11, :Boot) │ │ +│ │ ZUSTANDSLOS │ │ proot → Debian 13 Trixie │ │ +│ │ · Default-SMS-App │◄──┤ Plasma Mobile (KWin nested) │ │ +│ │ · Default-Dialer │ws │ PipeWire ← virt. Kamera │ │ +│ │ · Kamera-Server │ │ plasma-dialer + spacebar │ │ +│ │ · NFC-Reader, Sensoren│ │ hpos-daemon (Backup/Web-UI) │ │ +│ │ · Installer + Updater │ │ appmanager (Intent-Handoff) │ │ +│ └────────────────────────┘ └────────────────────────────────┘ │ +│ localhost:8474 │ +│ /sdcard/hpos-backup/ ← restic-Repo, überlebt Termux-Deinstall │ +└──────────────────────────────┬─────────────────────────────────────┘ + │ optional + ┌──────────┴───────────────────────┐ + │ Backup-Ziel außer Haus │ + │ SFTP oder rest-server │ + │ reines restic-Backend, kein Code │ + └──────────────────────────────────┘ +``` + +Das Telefon ist vollständig eigenständig. Das externe Backup-Ziel ist die **einzige** +optionale Außenanbindung — kein Dienst, von dem der Betrieb abhängt. + +## Die zwei tragenden Regeln + +### 1. Der Bridge-Layer ist zustandslos + +Die Android-Seite speichert keine Nutzdaten. SMS, Anrufliste, Kontakte, Fotos — alles +wird sofort ins Linux durchgereicht und *nur dort* persistiert. + +Der Grund ist nicht Eleganz, sondern Notwendigkeit: App-private Daten auf Android sind +ohne Root **nicht sicherbar**. Gäbe es zwei Orte, an denen Daten liegen, wäre das Backup +strukturell unvollständig. Es gibt deshalb genau einen Ort. + +Praktische Folge: Die Android-Seite darf jederzeit plattgemacht werden, ohne dass ein +Byte Nutzdaten verloren geht. Das ist zugleich der Abschlusstest des ganzen Projekts. + +### 2. Keine Serverabhängigkeit + +Kein Produktbestandteil darf fremde Infrastruktur voraussetzen. Ein Projekt, das ein +Rechenzentrum braucht, benutzt außer dem Autor niemand. + +## Warum D-Bus-Shims statt eigener Anwendungen + +Der Linux-Teil der Bridge schreibt **keine** eigenen Anwendungen für Telefonie, SMS, +Akku, Standort oder Sensoren. Stattdessen implementiert er die Standard-D-Bus-Schnittstellen, +die der Linux-Desktop ohnehin erwartet: + +| Shim | Ersetzt | Damit läuft unverändert | +|---|---|---| +| ModemManager | echtes Modem | `plasma-dialer`, `spacebar` | +| UPower | Akku-Subsystem | Akkuanzeige, Energieverwaltung | +| GeoClue | GPS | Karten, Zeitzone, Wetter | +| iio-sensor-proxy | Sensoren | Rotation, Helligkeitsautomatik | + +Das spart nicht nur Arbeit — es bedeutet auch, dass jede Verbesserung upstream geschenkt +ankommt und dass ein Gerätewechsel nur die Bridge berührt, nie die Anwendungsschicht. + +## Datenfluss: eingehende SMS + +``` +Mobilfunk → Android-Framework + → Bridge-APK (Default-SMS-Rolle, persistiert NICHTS) + → WebSocket 127.0.0.1:8474 + → ModemManager-Shim + → spacebar → Nachricht landet in der Linux-Datenbank + → restic-Backup +``` + +## Datenfluss: Navigation aus dem Adressbuch + +``` +KAddressBook → xdg-open "geo:0,0?q=" + → hpos-open (Handler) + → Bridge → startet Begleit-App (Overlay) + → am start VIEW google.navigation:q= + → Google Maps im Vollbild auf dem Host +``` + +Bewusst **kein Streaming**: volles GPU, volles GPS, kein Akkuverbrauch fürs Encodieren, +und Overlay-Apps funktionieren wie vorgesehen. Zurück ins Linux über den Task-Manager. + +## Speicherorte + +| Was | Wo | Warum | +|---|---|---| +| Debian-Rootfs, `$HOME`, alle Nutzdaten | `/data/data/com.termux/files/…` | Echtes ext4 mit Rechten und Symlinks. **Kein Image** — ein normaler Verzeichnisbaum | +| restic-Repo | `/sdcard/hpos-backup/` | Überlebt eine Deinstallation von Termux, per USB/MTP erreichbar | +| Zweitziel (optional) | SFTP / rest-server | Schützt gegen Verlust oder Diebstahl des Geräts | + +⚠️ Das Rootfs liegt in Termux' App-privatem Verzeichnis. **Termux zu deinstallieren +löscht alles.** Deshalb liegt das Backup zwingend außerhalb. + +## Bekannte Grenzen + +Vollständig mit Belegen in [machbarkeit.md](machbarkeit.md), in Kürze: + +- **Anruf-Audio** bleibt auf dem Android-Pfad. Bedienung und Daten liegen im Linux, + der Ton kommt aus derselben Hörmuschel wie immer. +- **Daten von Host-Apps** sind nicht sicherbar. Deshalb gehören dort nur konto-gebundene + Apps hin, bei denen "APK zurück plus anmelden" ein vollständiger Restore ist. +- **Kontaktloses Bezahlen** bleibt Sache des Host-Android. diff --git a/docs/machbarkeit.md b/docs/machbarkeit.md new file mode 100644 index 0000000..68e9a94 --- /dev/null +++ b/docs/machbarkeit.md @@ -0,0 +1,112 @@ +# Machbarkeit ohne Root + +Was auf ungerootetem Stock-Android geht und was nicht — mit Belegen, damit niemand +gegen eine Wand plant. + +Legende: ✅ bewiesen möglich · 🔬 im Spike zu beweisen · ❌ nachweislich unmöglich · +➖ bewusst außerhalb des Projekts + +## Basis + +| Funktion | | Weg | +|---|---|---| +| `$HOME` im echten Telefonspeicher, kein Image | ✅ | proot-distro legt einen normalen Verzeichnisbaum an | +| Debian + Desktop mit GPU-Beschleunigung | 🔬 | Termux-X11 + Turnip/Zink (Adreno) bzw. virgl/ANGLE (Mali) | +| Plasma Mobile in proot | 🔬 | **S1** — `kwin_wayland --x11-display` genestet; braucht dort weder DRM noch logind | +| Backup, Snapshots, Einzeldatei-Restore, Web-UI | ✅ | restic + rclone + eigener Daemon | +| Klonen auf neues Telefon per WLAN | ✅ | mDNS + restic-REST | + +## Telefonie und Nachrichten + +| Funktion | | Weg | +|---|---|---| +| SMS empfangen und senden | ✅ | Bridge als Default-SMS-App. Sideloaded → Play-Policy greift nicht | +| Anrufe steuern: wählen, annehmen, auflegen, DTMF, Anrufliste | ✅ | Bridge als Default-Dialer (`InCallService`) | +| **Anruf-Audio in die Linux-Umgebung leiten** | ❌ | `VOICE_CALL` erfordert `CAPTURE_AUDIO_OUTPUT` — System-App-Permission, seit Android 6 gesperrt; der Accessibility-Umweg wurde 2022 geschlossen | + +**Beleg:** [issuetracker 236553834](https://issuetracker.google.com/issues/236553834) + +Konsequenz: Audio bleibt auf dem Android-Pfad, dieselbe Hörmuschel wie immer. Bedienung +und alle Daten liegen im Linux. Ein SIP-Aufbau als Umgehung wurde geprüft und verworfen +(siehe [verworfene-entscheidungen.md](verworfene-entscheidungen.md)). + +## Medien + +| Funktion | | Weg | +|---|---|---| +| Mikrofon im Linux | 🔬 | **S2** — PulseAudio mit OpenSL-Source in Termux, `PULSE_SERVER` in den proot | +| Kamera in Browser und Element (Videocall) | 🔬 | **S3** — virtuelle PipeWire-Kamera, reiner Userspace | +| Kamera als `/dev/videoX` | ❌ | `v4l2loopback` ist ein Kernel-Modul → Root. Kein Userspace-Ersatz | + +**Beleg für den Kamera-Weg:** [PipeWire is the new v4l2loopback](https://www.ideasonboard.com/news/pipewire-is-the-new-v4l2loopback/) · +[PipeWire-Kamera in Firefox](https://jgrulich.cz/2024/01/30/how-to-use-pipewire-camera-in-firefox/) + +Ein GStreamer-`pipewiresink` mit `media.class=Video/Source` erzeugt einen Kamera-Node +ohne Kernel-Modul. Firefox ab 122 nimmt ihn über `media.webrtc.camera.allow-pipewire` +und `xdg-desktop-portal-kde` an. + +**Mikrofon-Konflikt:** Android vergibt das Mikro exklusiv an die Vordergrund-App. +Solange Termux-X11 vorn ist, hat der Linux-Stack es. Bei einem Mobilfunk-Anruf hat die +Telefonie Vorrang. Da es keine nativen Nutzer-Apps im Vordergrund gibt, tritt der +Konflikt praktisch kaum auf. + +## Android-Apps auf dem Host + +| Funktion | | Weg | +|---|---|---| +| Host-App aus dem Linux mit Daten aufrufen | 🔬 | **S4** — `xdg-open`-Handler für `geo:`/`tel:`/`sms:` → Bridge → Intent | +| Installierte Apps sichern und wiederherstellen | ✅ | `QUERY_ALL_PACKAGES` + `ApplicationInfo.sourceDir`; APKs unter `/data/app/…/base.apk` sind weltlesbar | +| Banking- und TAN-Apps | ✅ | Nativ auf dem Host, Play Integrity ist dort intakt | +| **App-Daten sichern** | ❌ | Siehe unten — zwei unabhängige K.-o.-Gründe | +| Host-App als Fenster im Linux-Desktop | ➖ | Technisch möglich, bewusst verworfen | + +### Warum App-Daten nicht sicherbar sind + +Zwei Gründe, jeder für sich schon ausreichend: + +1. **Es gibt kein gemeinsames Verzeichnis.** Seit Android 11 ist `/sdcard/Android/data` + auch für Inhaber von `MANAGE_EXTERNAL_STORAGE` gesperrt; `/data/data/` gehört + einer fremden UID mit Modus 0700. App und Termux haben keinen Ort, den beide + beschreiben können. + **Beleg:** [Storage updates in Android 11](https://developer.android.com/about/versions/11/privacy/storage) +2. **APK-Patchen skaliert nicht.** Selbst mit einem solchen Verzeichnis müsste man in + beliebigen, obfuszierten APKs jeden I/O-Pfad umschreiben — `getFilesDir`, + `SharedPreferences`, `SQLiteOpenHelper`, dazu native Bibliotheken. Bei jedem + App-Update von vorn. + +**Konsequenz als Nutzungsregel:** Auf dem Host laufen nur konto-gebundene Apps, bei +denen "APK zurück plus einmal anmelden" ein vollständiger Restore ist. Alles mit rein +lokalen Daten bekommt eine Linux-Anwendung — TOTP etwa über KeePassXC statt einer +Authenticator-App. + +## Hardware-Zugriff + +| Funktion | | Weg | +|---|---|---| +| Sensoren, Taschenlampe, Akku, GPS, Vibration | ✅ | Termux:API + Bridge | +| NFC-Tags lesen und schreiben | ✅ | Bridge-APK | +| Kontaktloses Bezahlen (HCE) | ➖ | Framework-only und Play-Integrity-gebunden. Bleibt Sache des Host-Android via Google Pay | + +## Hintergrundbetrieb + +Der Punkt, an dem das Konzept in der Praxis scheitern könnte. Zwei Ebenen: + +**Android-Standardebene** — vollständig per ADB abschaltbar und neustartfest: + +```bash +adb shell dumpsys deviceidle whitelist +com.termux +adb shell cmd appops set com.termux RUN_IN_BACKGROUND allow +adb shell cmd appops set com.termux RUN_ANY_IN_BACKGROUND allow +adb shell am set-standby-bucket com.termux never +adb shell settings put global adaptive_battery_management_enabled 0 +``` + +Entwickleroptionen helfen hier **nicht** — sie betreffen nur gecachte Prozesse. + +**Herstellerebene** — nicht per ADB erreichbar, muss manuell erledigt werden. OxygenOS 12 +basiert auf ColorOS, dessen App-Einfrierung liegt unterhalb der Android-Mechanismen: +Akku-Optimierung für Termux auf "Nicht optimieren", "Sleep Standby Optimization" aus, +adaptive Akkunutzung aus, App im Task-Manager anpinnen. + +**Nachweis statt Annahme:** Der Stack muss eine Nacht überleben. Scheitert das, ist es +ein früher Warnschuss für das Gesamtkonzept, kein Detail. diff --git a/docs/redroid-anhang.md b/docs/redroid-anhang.md new file mode 100644 index 0000000..3e9204e --- /dev/null +++ b/docs/redroid-anhang.md @@ -0,0 +1,51 @@ +# Anhang: Android-Server mit redroid + +> **Nicht Teil des Produkts.** Verworfen als [V7](verworfene-entscheidungen.md#v7--android-server-im-rechenzentrum-redroid), +> weil sein Zweck sich im Lauf der Planung aufgelöst hat. Hier dokumentiert für den Fall, +> dass sich in der Praxis doch eine echte Lücke zeigt. + +## Wann das relevant wird + +Nur für Apps, die **beide** Bedingungen erfüllen: + +1. Ihre Daten liegen rein lokal, nicht bei einem Konto — ein Restore per "APK zurück plus + anmelden" würde also Daten verlieren. +2. Es gibt keine brauchbare Linux-Anwendung als Ersatz. + +Vor dem Aufbau eines Servers erst prüfen, ob Bedingung 2 wirklich zutrifft. Der +häufigste Kandidat, Authenticator-Apps, fällt weg: **KeePassXC** kann TOTP, liegt im +Linux und ist damit vollständig im Backup. + +## Warum redroid und nicht Waydroid + +redroid ist explizit als *"Android in Cloud"* entwickelt: läuft in Docker, GPU-beschleunigt, +remote-fähig, mehrere Instanzen parallel. Waydroid ist für den lokalen Desktop gedacht und +kann kein Remote. + +Zitat aus der Projektdokumentation: *"It is more suitable than Waydroid as a cloud phone."* + +**Quelle:** [remote-android/redroid-doc](https://github.com/remote-android/redroid-doc) + +## Aufbau in Kürze + +- **Host:** x86_64 mit KVM, ~4 GB RAM je Instanz. Auf ARM-Hardware laufen Android-Apps + nativ ohne Übersetzung und damit schneller — aber dort fehlt die Werkzeugreife. +- **ARM-Apps auf x86:** `libndk_translation` ist in den offiziellen redroid-Images + enthalten. Kostet Leistung, einzelne Apps vertragen es nicht. +- **Play Services:** nachrüstbar, nötig für Push-Nachrichten. +- **Streaming aufs Telefon:** scrcpy über `adb connect`, oder Moonlight/Sunshine für + geringere Latenz. +- **Kamera und Mikrofon durchreichen:** Stream vom Telefon über einen Tunnel zum Server, + dort in `v4l2loopback` bzw. ein virtuelles Audio-Device — auf dem Server ist Root + verfügbar, dort ist das unproblematisch. +- **Backup:** Container-Daten liegen im Server-Dateisystem, restic sichert sie wie jedes + andere Verzeichnis. + +## Was es kostet + +Ein Server, ein Tunnel, ein Streaming-Frontend, ein zweiter Zweig im Backup-System — +und Mobilfunkvolumen plus Akku im laufenden Betrieb. Dazu die Abhängigkeit von fremder +Infrastruktur, die dem Projekt sonst überall erspart bleibt. + +**Play Integrity** schlägt in Containern in der Regel fehl. Banking-Apps gehören deshalb +auf den Host, nicht hierher. diff --git a/docs/spike-protokoll.md b/docs/spike-protokoll.md new file mode 100644 index 0000000..fd53f88 --- /dev/null +++ b/docs/spike-protokoll.md @@ -0,0 +1,146 @@ +# Spike-Protokoll — Phase 0 + +Ergebnisse der vier Machbarkeitsbeweise. **Dies ist das wichtigste Dokument des +Projekts**, solange Phase 0 läuft: alle folgenden Entscheidungen bauen darauf auf. + +Ehrlich ausfüllen, auch und gerade bei Fehlschlägen. Ein sauber dokumentierter +Fehlschlag ist wertvoller als ein geschöntes Ergebnis. + +## Gerät + +| | | +|---|---| +| Modell | OnePlus 7 Pro (`guacamole`) | +| SoC / GPU | Snapdragon 855 / Adreno 640 | +| RAM / Speicher | _(ausfüllen)_ | +| ROM | Stock OxygenOS 12 (Android 12) | +| Display | 3120 × 1440, virtuelle Auflösung im Test: _(ausfüllen)_ | +| Akku | 4000 mAh, Baujahr 2019 — getauscht? _(ausfüllen)_ | +| Datum des Tests | _(ausfüllen)_ | + +## Zusammenfassung + +| Beweis | Ergebnis | Konsequenz | +|---|---|---| +| **S1** Plasma Mobile in proot | ⬜ offen | | +| **S2** Mikrofon im Linux | ⬜ offen | | +| **S3** Virtuelle PipeWire-Kamera | ⬜ offen | | +| **S4** Intent-Handoff | ⬜ offen | | +| **Übernacht-Test** Hintergrundbetrieb | ⬜ offen | | + +--- + +## S1 — Plasma Mobile in proot + +**Beweisziel:** `kwin_wayland --x11-display` genestet in Termux-X11, darauf +`plasma-mobile`. Startet, reagiert auf Touch, Maliit-Tastatur erscheint. + +**Wenn es scheitert:** XFCE-Profil wird Hauptweg. Dann braucht die Kamera +`xdg-desktop-portal-gtk` statt `-kde`, die Tastatur wird `onboard`, und Telefonie +braucht eine eigene Oberfläche statt `plasma-dialer`. + +| | | +|---|---| +| Ergebnis | _(offen)_ | +| Startzeit bis Bedienoberfläche | | +| Touch funktioniert | | +| Maliit-Tastatur erscheint | | +| Rotation | | +| Fehlermeldungen | | + +--- + +## S2 — Mikrofon im Linux + +**Beweisziel:** PulseAudio in Termux mit OpenSL-Source, `PULSE_SERVER` in den proot, +`parecord` nimmt hörbar auf. + +**Wenn es scheitert:** keine Videocalls in Element und im Browser, keine Sprachaufnahme, +keine Spracheingabe. Videotelefonie nur noch nativ auf dem Host per Intent-Handoff. +Deutliche Abwertung des Produkts. + +| | | +|---|---| +| Ergebnis | _(offen)_ | +| Verwendetes PulseAudio-Modul | | +| Aufnahme hörbar | | +| Latenz / Aussetzer | | +| Verhalten bei gesperrtem Bildschirm | | + +--- + +## S3 — Virtuelle PipeWire-Kamera + +**Beweisziel:** GStreamer-`pipewiresink` mit `media.class=Video/Source` erzeugt einen +Kamera-Node; Firefox mit `media.webrtc.camera.allow-pipewire` sieht ihn auf +`webcamtests.com` und zeigt Bild. + +**Wenn es scheitert:** keine Videocalls im Linux, Kamera nur über eine eigenständige App. + +| | | +|---|---| +| Ergebnis | _(offen)_ | +| Node erscheint in `wpctl status` | | +| Firefox sieht die Kamera | | +| Bild kommt an | | +| Bildrate / Auflösung | | + +⚠️ **Pop-up-Kamera:** Die Frontkamera des OnePlus 7 Pro fährt motorisiert aus. Prüfen, +dass der Node nicht spekulativ die Frontkamera öffnet — das ist Mechanik und Verschleiß. + +--- + +## S4 — Intent-Handoff + +**Beweisziel:** Eine Adresse aus dem Linux startet per `geo:`-Intent die Navigation auf +dem Host, mit vorher gestarteter Overlay-Begleit-App. + +**Wenn es scheitert:** keine Navigation aus dem Linux-Adressbuch; Host-Apps müssen +manuell über den Task-Manager gestartet werden. + +| | | +|---|---| +| Ergebnis | _(offen)_ | +| `geo:`-URI startet Maps | | +| `google.navigation:` startet Turn-by-Turn direkt | | +| Overlay der Begleit-App liegt über Maps | | +| `SYSTEM_ALERT_WINDOW` erteilt | | +| Rückweg ins Linux über Task-Manager | | + +--- + +## Übernacht-Test — Hintergrundbetrieb + +**Beweisziel:** Der komplette Stack überlebt eine Nacht mit gesperrtem Bildschirm. + +**Wenn es scheitert:** früher Warnschuss für das Gesamtkonzept, kein Detail. OxygenOS 12 +basiert auf ColorOS mit aggressiver App-Einfrierung unterhalb der Android-Mechanismen. + +| | | +|---|---| +| ADB-Maßnahmen gesetzt (`provision.sh`) | | +| Herstellerseitige Handgriffe erledigt | | +| Prozesse nach 8 h noch da | | +| Akkuverbrauch über Nacht | | +| Was wurde beendet | | + +--- + +## Leistungsmessungen + +| Messung | Wert | +|---|---| +| `vkcube` läuft | | +| `glmark2` Score | | +| GPU-Treiberpfad (Turnip / Zink / virgl / Software) | | +| Virtuelle Auflösung | | +| Akku: Leerlauf mit laufendem Stack | | +| Akku: Desktop aktiv | | +| Thermik / Throttling | | + +--- + +## Fazit und Entscheidungen + +_(Nach Abschluss ausfüllen: welches Desktop-Profil wird gebaut, welche Fallbacks greifen, +was ändert sich am Plan.)_ diff --git a/docs/verworfene-entscheidungen.md b/docs/verworfene-entscheidungen.md new file mode 100644 index 0000000..46d2fa0 --- /dev/null +++ b/docs/verworfene-entscheidungen.md @@ -0,0 +1,148 @@ +# Verworfene Entscheidungen + +Sieben naheliegende Wege wurden geprüft und begründet verworfen. Sie stehen hier, damit +niemand — auch nicht der Autor in einem halben Jahr — von vorn anfängt zu überlegen. + +Wer einen davon wiederbeleben will: gern, aber mit einem Argument, das hier noch nicht +widerlegt ist. + +--- + +## V1 — Waydroid auf dem Gerät + +**Idee:** Android-Apps in einem Container innerhalb der Linux-Umgebung, damit ihre Daten +im Linux-Dateisystem liegen und ins Backup wandern. + +**Warum es nicht geht:** Waydroid braucht `binder` und `ashmem` als Kernel-Module sowie +Root für die LXC-Container-Steuerung. proot ist reine Userspace-Emulation von `chroot` +und hat prinzipiell keinen Kernel-Zugriff. + +**Beleg:** [waydroid#1301](https://github.com/waydroid/waydroid/discussions/1301) · +[ArchWiki: Waydroid](https://wiki.archlinux.org/title/Waydroid) + +--- + +## V2 — Waydroid in einer VM über AVF + +**Idee:** Wenn proot keinen Kernel hat, dann eben eine echte VM. Die Android +Virtualization Framework gibt ungerooteten Apps Zugang zu KVM; in einer eigenen VM +liefert man den Gast-Kernel selbst, also mit `binderfs` — und bekäme Waydroid, +`v4l2loopback`, systemd und Root *in der VM*, bei intaktem Host. + +Der Ansatz funktioniert tatsächlich: `android.permission.MANAGE_VIRTUAL_MACHINE` ist +seit Android 15 per `adb shell pm grant` erteilbar, was gut zum Provisioning-Schritt passt. + +**Warum es trotzdem verworfen wurde** — der Preis: + +| | | +|---|---| +| Qualcomm fällt raus | Snapdragon 8 Elite lässt nur signierte VMs zu. Custom-Linux-VMs gibt es nur auf Tensor, Dimensity 9400, Exynos 2500 | +| GPU nur auf Pixel 10 | Gfxstream ist per Overlay-Flag Pixel-10-exklusiv, 47 von 142 Vulkan-Extensions, noch instabil. Auf Pixel 8/9 nur Software-Rendering | +| Disk-Image | AVF erzwingt ein Image — im Widerspruch zum Ziel "echter Speicher, kein Image" | + +Das Projekt wäre damit auf ein einziges, teures Gerät festgenagelt worden. Nachdem die +Android-Apps ohnehin auf den Host gewandert sind (siehe V7), entfiel auch der Zweck. + +**Beleg:** [AOSP: AVF-Anwendungsfälle](https://source.android.com/docs/core/virtualization/usecases) · +[Run-Linux-on-Android-Guide: AVF](https://github.com/lfdevs/run-linux-on-android-guide/blob/main/docs/en-US/avf-linux.md) + +--- + +## V3 — QEMU ohne Beschleunigung + +**Idee:** Android in QEMU emulieren, wenn schon keine Virtualisierung verfügbar ist. + +**Warum es nicht geht:** TCG-Emulation läuft bei etwa 5–15 % der nativen Geschwindigkeit. +Ein Android-Gast bräuchte Minuten zum Booten und liefe mit wenigen Bildern pro Sekunde. +`/dev/kvm` steht gewöhnlichen Apps nicht zur Verfügung — der einzige unrooted Zugang zu +KVM ist AVF, also V2. + +--- + +## V4 — SIP-Telefonie im Linux + +**Idee:** Da Anruf-Audio nicht abgreifbar ist, die Rufnummer vom Telefon lösen — +Portierung zu einem VoIP-Provider, Asterisk als Registrar, Linphone im Linux. Dann +telefoniert man tatsächlich im Linux statt nur die Oberfläche dort zu haben. + +**Warum es verworfen wurde:** Der einzige echte Zugewinn wäre das Audio im Linux gewesen. +Der Preis stand in keinem Verhältnis. + +| Ohne SIP gewonnen | Ohne SIP verloren | +|---|---| +| Anrufe ohne Datenverbindung, ohne Tunnel, ohne Server | Gesprächsaufzeichnung im Linux | +| Keine dauerhafte Registrierung → deutlich weniger Akkuverbrauch | Eigenes Audio-Routing | +| Keine Portierung, kein Wochen-Vorlauf, keine laufenden Kosten | "Desktop klingelt mit" | +| Notrufe trivial korrekt statt Sonderfall | | +| Gesprächsqualität über Mobilfunk statt VoIP über mobile Daten | | +| Eine Anruf-UI statt zwei Ebenen | | + +Alles Verlorene ist Komfort, nichts davon strukturell. + +**Nachrüstbar:** ja, rein additiv und serverseitig, keine Architekturkopplung. + +--- + +## V5 — APK patchen, damit Daten ins Linux-`$HOME` wandern + +**Idee:** APKs beim Installieren umschreiben, sodass ihr Datenverzeichnis im Linux liegt +und mitgesichert wird. + +**Warum es nicht geht** — zwei unabhängige K.-o.-Gründe, siehe +[machbarkeit.md](machbarkeit.md#warum-app-daten-nicht-sicherbar-sind): + +1. Es gibt kein Verzeichnis, das App und Termux beide beschreiben können. + `/sdcard/Android/data` ist seit Android 11 auch mit `MANAGE_EXTERNAL_STORAGE` gesperrt, + `/data/data/` gehört einer fremden UID mit Modus 0700. +2. Man müsste in beliebigen, obfuszierten APKs jeden I/O-Pfad umschreiben — inklusive + nativer Bibliotheken, und bei jedem App-Update erneut. + +**Was von der Idee überlebt hat:** der Installationsstand. Paketliste und APK-Archiv +lassen sich sichern und beim Restore nachinstallieren. Für konto-gebundene Apps ist das +ein vollständiger Restore. + +--- + +## V6 — Host-Apps als Fenster im Linux-Desktop + +**Idee:** `scrcpy --new-display= --start-app=` legt ein virtuelles +Display an, startet die App dorthin und streamt nur diese. Jede App bekäme ihr eigenes +Fenster im Plasma-Desktop, ohne Vollbild-Umschalten. + +**Es funktioniert.** Ohne Root, ab Android 11. +**Beleg:** [scrcpy: virtual_display](https://github.com/Genymobile/scrcpy/blob/master/doc/virtual_display.md) + +**Warum es trotzdem verworfen wurde:** Es braucht dauerhaft Shell-Rechte über einen +Shizuku-artigen Kanal — und der überlebt ohne Root **keinen Neustart**. Damit wäre ein +permanent bröckelnder Privilegienkanal das Fundament eines Alltagsgeräts geworden. + +Der Verzicht hat mehr eingebracht als gekostet: **das gesamte Produkt kommt jetzt ohne +Privilegienkanal im Dauerbetrieb aus.** ADB wird nur noch für die einmalige +Ersteinrichtung gebraucht. Für Navigation, Teilen und Scannen ist der Intent-Handoff +ohnehin die bessere Lösung — volles GPU, volles GPS, und Overlay-Apps funktionieren, was +über einem gestreamten Fenster gar nicht ginge. + +--- + +## V7 — Android-Server im Rechenzentrum (redroid) + +**Idee:** Android-Apps in einem redroid-Container auf einem eigenen Server, aufs Telefon +gestreamt. Container-Daten liegen serverseitig und sind damit vollständig sicherbar. + +**Warum es verworfen wurde:** Der Zweck hat sich im Lauf der Planung selbst aufgelöst. + +| Ursprünglicher Zweck | Wo es tatsächlich gelandet ist | +|---|---| +| Banking- und TAN-Apps | Host — dort ist Play Integrity intakt, in einem Container scheitern sie ohnehin | +| WhatsApp, Teams, Signal | Host bzw. Matrix/Element nativ im Linux | +| Navigation | Host per Intent-Handoff | +| Apps mit rein lokalen Daten | Letzter verbleibender Fall — und auch der schrumpft: TOTP deckt KeePassXC im Linux besser ab. Übrig bleiben Offline-Notizen und Spielstände | + +Dafür lohnen ein Server, ein Tunnel, ein Streaming-Frontend und ein eigener Zweig im +Backup-System nicht. + +**Bleibt dokumentiert:** [redroid-anhang.md](redroid-anhang.md) — falls sich in der +Praxis doch eine echte Lücke zeigt, ist es ein Nachtrag, kein Umbau. + +**Daraus wurde eine Regel:** Kein Produktbestandteil darf eine Serverabhängigkeit +einführen. Einzige Ausnahme ist das optionale Backup-Ziel außer Haus. diff --git a/provision/provision.sh b/provision/provision.sh new file mode 100755 index 0000000..715b281 --- /dev/null +++ b/provision/provision.sh @@ -0,0 +1,155 @@ +#!/usr/bin/env bash +# HPOS — Ersteinrichtung vom PC aus, per ADB. +# +# ADB wird ausschließlich hier gebraucht, einmalig. Im Dauerbetrieb kommt das +# System mit gewöhnlichen App-Berechtigungen aus — das ist eine harte Regel des +# Projekts, siehe CONTRIBUTING.md. +# +# Voraussetzung: Telefon per USB, Entwickleroptionen und USB-Debugging aktiv, +# der Dialog "Diesem Computer vertrauen" bestätigt. +# +# Aufruf: ./provision/provision.sh [--dry-run] +set -uo pipefail + +TERMUX=com.termux +TROCKEN=0 +[ "${1:-}" = "--dry-run" ] && TROCKEN=1 + +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"; } + +# Führt ein adb-Kommando aus und meldet das Ergebnis, ohne bei Fehlschlag +# abzubrechen: Hersteller-ROMs kennen nicht jedes appops-Ziel, und ein fehlendes +# ist kein Grund, die restliche Einrichtung liegen zu lassen. +adbs() { + local beschreibung="$1"; shift + if [ "$TROCKEN" = 1 ]; then + printf '%s ? %s%s (adb shell %s)\n' "$C_DIM" "$beschreibung" "$C_OFF" "$*" + return 0 + fi + local aus + if aus=$(adb shell "$@" 2>&1); then + if printf '%s' "$aus" | grep -qiE 'error|exception|denied|unknown'; then + warn "$beschreibung — ROM hat abgelehnt" + hinweis "$(printf '%s' "$aus" | head -1)" + return 1 + fi + ok "$beschreibung" + return 0 + fi + warn "$beschreibung — fehlgeschlagen" + hinweis "$(printf '%s' "$aus" | head -1)" + return 1 +} + +# ── Gerät ──────────────────────────────────────────────────────────────────── +kopf "Gerät" +command -v adb >/dev/null 2>&1 || { fehl "adb nicht gefunden. Bitte android-tools-adb installieren."; exit 1; } + +ANZAHL=$(adb devices | grep -cw 'device$' || true) +if [ "$ANZAHL" -eq 0 ]; then + fehl "Kein Gerät verbunden oder nicht autorisiert." + hinweis "adb devices prüfen. Steht dort 'unauthorized', den Dialog auf dem Telefon bestätigen." + exit 1 +elif [ "$ANZAHL" -gt 1 ]; then + fehl "Mehrere Geräte verbunden — bitte nur eines anschließen." + exit 1 +fi + +for p in ro.product.manufacturer ro.product.model ro.product.device \ + ro.build.version.release ro.board.platform; do + printf ' %-28s %s\n' "$p" "$(adb shell getprop "$p" 2>/dev/null | tr -d '\r')" +done +[ "$TROCKEN" = 1 ] && warn "Trockenlauf — es wird nichts verändert." + +# ── Termux vorhanden? ──────────────────────────────────────────────────────── +kopf "Termux" +if adb shell pm list packages 2>/dev/null | tr -d '\r' | grep -q "^package:$TERMUX$"; then + ok "$TERMUX ist installiert" +else + fehl "$TERMUX ist nicht installiert." + hinweis "Termux von F-Droid oder GitHub installieren, dann dieses Skript erneut laufen lassen." + hinweis "Nicht aus dem Play Store — die dortige Fassung ist veraltet und unbrauchbar." + exit 1 +fi + +# ── Hintergrundbetrieb, Android-Standardebene ──────────────────────────────── +# Der Punkt, an dem das Konzept in der Praxis scheitern kann. Diese Maßnahmen +# überleben Neustarts. Die Entwickleroptionen helfen hier NICHT — sie betreffen +# nur gecachte Prozesse, nicht Vordergrunddienste. +kopf "Hintergrundbetrieb (Android-Standardebene)" +adbs "Aus der Doze-Beschränkung ausgenommen" dumpsys deviceidle whitelist "+$TERMUX" +adbs "RUN_IN_BACKGROUND erlaubt" cmd appops set "$TERMUX" RUN_IN_BACKGROUND allow +adbs "RUN_ANY_IN_BACKGROUND erlaubt" cmd appops set "$TERMUX" RUN_ANY_IN_BACKGROUND allow +adbs "Standby-Bucket auf 'never' gesetzt" am set-standby-bucket "$TERMUX" never +adbs "Adaptive Akkunutzung abgeschaltet" settings put global adaptive_battery_management_enabled 0 +adbs "Akku-Optimierung ignoriert" cmd appops set "$TERMUX" REQUEST_IGNORE_BATTERY_OPTIMIZATIONS allow + +# Die Zusatz-APKs, sofern schon installiert. Fehlen sie, ist das hier kein Problem. +for extra in com.termux.api com.termux.x11 com.termux.boot; do + if adb shell pm list packages 2>/dev/null | tr -d '\r' | grep -q "^package:$extra$"; then + adbs " $extra aus der Doze-Beschränkung" dumpsys deviceidle whitelist "+$extra" + adbs " $extra darf im Hintergrund laufen" cmd appops set "$extra" RUN_ANY_IN_BACKGROUND allow + fi +done + +# ── Berechtigungen ─────────────────────────────────────────────────────────── +kopf "Berechtigungen" +for perm in \ + android.permission.RECORD_AUDIO \ + android.permission.CAMERA \ + android.permission.ACCESS_FINE_LOCATION \ + android.permission.READ_EXTERNAL_STORAGE \ + android.permission.WRITE_EXTERNAL_STORAGE +do + adbs "$(basename "${perm//./\/}") für Termux:API" pm grant com.termux.api "$perm" 2>/dev/null || true +done +hinweis "Fehlschläge sind normal, solange Termux:API noch nicht installiert ist." + +# ── Was ADB nicht kann ─────────────────────────────────────────────────────── +kopf "Von Hand zu erledigen" +HERSTELLER=$(adb shell getprop ro.product.manufacturer 2>/dev/null | tr -d '\r') +cat < hpos && cd hpos + bash spike/00-bootstrap-termux.sh + + Danach die Beweise der Reihe nach — S1 zuerst, weil er der wackligste ist. + Ergebnisse gehören nach docs/spike-protokoll.md. +TEXT diff --git a/spike/00-bootstrap-termux.sh b/spike/00-bootstrap-termux.sh new file mode 100755 index 0000000..9a116b6 --- /dev/null +++ b/spike/00-bootstrap-termux.sh @@ -0,0 +1,159 @@ +#!/usr/bin/env bash +# Phase 0 — Grundinstallation in Termux. +# +# Richtet Termux-X11, den GPU-Pfad und ein Debian-13-Rootfs ein, damit die Beweise +# S1 bis S4 danach laufen können. Idempotent: mehrfaches Ausführen ist unschädlich. +# +# Ausführen IN TERMUX auf dem Telefon: bash spike/00-bootstrap-termux.sh +set -uo pipefail +source "$(dirname "$0")/lib.sh" +require_termux + +DISTRO=debian +BIND_ROOT="$REPO_ROOT" + +log "HPOS Phase 0 — Bootstrap" +log "Protokoll: $LOG" +geraete_steckbrief + +# ── Repositories ───────────────────────────────────────────────────────────── +# x11-repo bringt Termux-X11 und virglrenderer, tur-repo die Mesa-/Turnip-Pakete. +section "Paketquellen" +try pkg update -y +try pkg install -y x11-repo tur-repo +try pkg update -y + +# ── Basispakete ────────────────────────────────────────────────────────────── +# Einzeln installiert und einzeln protokolliert: Paketnamen wandern in Termux +# gelegentlich, und wir wollen wissen, WELCHES Paket fehlt, statt nur dass etwas fehlt. +section "Basispakete" +BASIS=(termux-x11-nightly pulseaudio proot-distro termux-api git python openssl termux-tools) +for p in "${BASIS[@]}"; do + if try pkg install -y "$p"; then ok "$p"; else fail "$p — fehlt, betroffene Beweise siehe unten"; fi +done + +# ── GPU-Pfad ───────────────────────────────────────────────────────────────── +# Adreno 6xx/7xx → Turnip greift direkt auf KGSL zu, das ist der schnellste Weg. +# Mali und alles andere → virgl/ANGLE. Wir installieren beides und entscheiden +# zur Laufzeit; das Ergebnis landet im GPU-Profil. +section "GPU-Pakete" +GPU_PKGS=(virglrenderer-android vulkan-loader-android vulkan-tools angle-android + mesa-vulkan-icd-wrapper mesa-zink virglrenderer-mesa-zink) +for p in "${GPU_PKGS[@]}"; do + if try pkg install -y "$p"; then ok "$p"; else warn "$p nicht verfügbar — kein Beinbruch"; fi +done + +section "GPU erkennen" +PLATFORM="$(getprop ro.board.platform 2>/dev/null)" +HARDWARE="$(getprop ro.hardware 2>/dev/null)" +GPU_PROFIL="virgl" # konservative Vorgabe +if have vulkaninfo; then + vulkaninfo --summary >>"$LOG" 2>&1 || true + GPU_NAME="$(vulkaninfo --summary 2>/dev/null | grep -m1 -i 'deviceName' | cut -d= -f2- | xargs)" +else + GPU_NAME="" +fi +log " Plattform = ${PLATFORM:-?}" +log " Hardware = ${HARDWARE:-?}" +log " GPU = ${GPU_NAME:-unbekannt}" + +case "${GPU_NAME}${PLATFORM}" in + *[Aa]dreno*|*qcom*|*sm[0-9]*|*msm*) + GPU_PROFIL="turnip" + ok "Adreno erkannt → Turnip-Profil (direkter KGSL-Zugriff, schnellster Weg)" + ;; + *[Mm]ali*) + GPU_PROFIL="virgl" + warn "Mali erkannt → virgl/ANGLE-Profil. Spürbar langsamer als Turnip." + ;; + *) + warn "GPU nicht eindeutig erkannt → virgl als Vorgabe." + hint "Bitte im Spike-Protokoll vermerken, welcher Pfad tatsächlich getragen hat." + ;; +esac + +# Das Profil ist eine Datei, kein Sonderfall im Code — so bleibt es später +# erweiterbar, ohne dass jemand Skripte anfassen muss. +mkdir -p "$REPO_ROOT/rootfs/profiles" +PROFIL_DATEI="$OUT_DIR/gpu-profil.env" +{ + echo "# Automatisch erkannt von 00-bootstrap-termux.sh" + echo "HPOS_GPU_PROFIL=$GPU_PROFIL" + echo "HPOS_GPU_NAME=\"${GPU_NAME:-unbekannt}\"" + echo "HPOS_PLATFORM=\"${PLATFORM:-unbekannt}\"" +} >"$PROFIL_DATEI" +ok "GPU-Profil geschrieben: $PROFIL_DATEI ($GPU_PROFIL)" + +# ── Termux-Eigenschaften ───────────────────────────────────────────────────── +# allow-external-apps ist die Voraussetzung dafür, dass die Bridge-APK später +# Kommandos in Termux anstoßen darf. Hier gesetzt, damit Phase 2 es vorfindet. +section "Termux-Eigenschaften" +TP="$HOME/.termux/termux.properties" +mkdir -p "$(dirname "$TP")" +touch "$TP" +if grep -q '^allow-external-apps' "$TP" 2>/dev/null; then + ok "allow-external-apps bereits gesetzt" +else + echo 'allow-external-apps = true' >>"$TP" + ok "allow-external-apps = true gesetzt" + hint "Wird erst nach 'termux-reload-settings' oder Neustart von Termux wirksam." +fi +try termux-reload-settings + +section "Speicherzugriff" +if [ -d "$HOME/storage" ]; then + ok "Speicherzugriff bereits eingerichtet" +else + warn "termux-setup-storage wird gleich einen Berechtigungsdialog zeigen — bitte erlauben." + try termux-setup-storage + sleep 2 + [ -d "$HOME/storage" ] && ok "Speicherzugriff eingerichtet" || fail "Speicherzugriff fehlt — Backup nach /sdcard geht später nicht" +fi + +# ── Wachhalten ─────────────────────────────────────────────────────────────── +# Ohne Wake-Lock schläft der Stack weg, sobald der Bildschirm aus geht. +# Die herstellerseitigen Maßnahmen kommen zusätzlich, siehe provision/. +section "Wake-Lock" +try termux-wake-lock && ok "Wake-Lock aktiv" || warn "Wake-Lock nicht gesetzt" + +# ── Debian-Rootfs ──────────────────────────────────────────────────────────── +section "Debian-Rootfs" +if proot-distro list --installed 2>/dev/null | grep -q "^$DISTRO\b" \ + || [ -d "$PREFIX/var/lib/proot-distro/installed-rootfs/$DISTRO" ]; then + ok "$DISTRO bereits installiert" +else + log " Installiere $DISTRO — das dauert einige Minuten." + need proot-distro install "$DISTRO" + ok "$DISTRO installiert" +fi + +section "Debian-Grundausstattung" +# Der Bind macht das Repo im Rootfs unter /hpos sichtbar, damit die inneren +# Skripte dieselben Dateien benutzen und Ergebnisse am selben Ort landen. +proot-distro login "$DISTRO" --bind "$BIND_ROOT:/hpos" -- bash -lc ' + set -uo pipefail + export DEBIAN_FRONTEND=noninteractive + apt-get update -qq + apt-get install -y --no-install-recommends \ + ca-certificates locales dbus-x11 sudo nano less procps \ + mesa-utils vulkan-tools pulseaudio-utils + # Deutsche und englische Locale, sonst meckert die halbe Desktop-Software. + sed -i "s/^# *\(de_DE.UTF-8\|en_US.UTF-8\)/\1/" /etc/locale.gen + locale-gen >/dev/null 2>&1 + echo "--- Debian-Version ---"; cat /etc/debian_version +' 2>&1 | tee -a "$LOG" + +# ── Abschluss ──────────────────────────────────────────────────────────────── +section "Bereit" +log "GPU-Profil : $GPU_PROFIL" +log "Rootfs : $DISTRO" +log "Repo im Rootfs sichtbar unter /hpos" +log "" +log "Nächste Schritte, in dieser Reihenfolge:" +log " bash spike/01-s1-plasma-mobile.sh # der wackligste Beweis, deshalb zuerst" +log " bash spike/02-s2-mikrofon.sh" +log " bash spike/03-s3-pipewire-kamera.sh" +log " bash spike/04-s4-intent-handoff.sh" +log "" +log "Vor dem Übernacht-Test bitte provision/provision.sh vom PC laufen lassen" +log "und die herstellerseitigen Handgriffe aus docs/machbarkeit.md erledigen." diff --git a/spike/01-s1-plasma-mobile.sh b/spike/01-s1-plasma-mobile.sh new file mode 100755 index 0000000..3e0dd84 --- /dev/null +++ b/spike/01-s1-plasma-mobile.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +# Phase 0 · Beweis S1 — Läuft Plasma Mobile in proot? +# +# Der wackligste Beweis des ganzen Projekts, deshalb zuerst. Plasma Mobile will +# Wayland, und KWin will normalerweise DRM/KMS und logind — beides gibt es ohne Root +# nicht. Die Wette: KWins X11-Backend braucht weder DRM noch Seat, wenn es genestet +# in Termux-X11 läuft. +# +# Scheitert der Beweis, wird XFCE der Hauptweg. Das ist kein Weltuntergang, ändert aber +# Kamera-Portal, Bildschirmtastatur und Telefonie-Oberfläche — siehe docs/spike-protokoll.md. +# +# Ausführen IN TERMUX: bash spike/01-s1-plasma-mobile.sh +set -uo pipefail +source "$(dirname "$0")/lib.sh" +require_termux + +DISTRO=debian +# Volle Panel-Auflösung wäre 3120x1440. Für einen Telefon-Desktop ist das zu viel +# Fläche — allein das Zeichnen frisst die Leistung. Überschreibbar per Umgebung. +BREITE="${HPOS_BREITE:-1080}" +HOEHE="${HPOS_HOEHE:-2340}" + +[ -f "$OUT_DIR/gpu-profil.env" ] && source "$OUT_DIR/gpu-profil.env" +GPU_PROFIL="${HPOS_GPU_PROFIL:-virgl}" + +log "HPOS Phase 0 · S1 — Plasma Mobile in proot" +log "Auflösung: ${BREITE}x${HOEHE} GPU-Profil: $GPU_PROFIL" +log "Protokoll: $LOG" + +# ── Alte Reste einsammeln ──────────────────────────────────────────────────── +section "Aufräumen" +pkill -f termux-x11 2>/dev/null && ok "alten X-Server beendet" || hint "kein alter X-Server" +pkill -f virgl_test_server 2>/dev/null && ok "alten virgl-Server beendet" || true +sleep 1 + +# ── X-Server ───────────────────────────────────────────────────────────────── +section "Termux-X11 starten" +if ! have termux-x11; then + fail "termux-x11 fehlt. Bitte 00-bootstrap-termux.sh laufen lassen." + exit 1 +fi +termux-x11 :0 >>"$LOG" 2>&1 & +X11_PID=$! +sleep 3 +if kill -0 "$X11_PID" 2>/dev/null; then + ok "X-Server läuft (PID $X11_PID)" +else + fail "X-Server sofort beendet — siehe Protokoll" + exit 1 +fi + +# Die App muss im Vordergrund sein, sonst sieht man nichts und Android drosselt. +log " Hole die Termux:X11-App in den Vordergrund …" +try am start --user 0 -n com.termux.x11/com.termux.x11.MainActivity +sleep 2 + +# ── GPU-Server ─────────────────────────────────────────────────────────────── +section "GPU-Pfad vorbereiten ($GPU_PROFIL)" +if [ "$GPU_PROFIL" = "virgl" ] && have virgl_test_server_android; then + virgl_test_server_android >>"$LOG" 2>&1 & + sleep 1 + ok "virgl-Server gestartet" +elif [ "$GPU_PROFIL" = "turnip" ]; then + ok "Turnip greift direkt auf KGSL zu — kein Proxy nötig" +else + warn "Kein GPU-Server aktiv, Software-Rendering wahrscheinlich" +fi + +# ── Audio, damit der Desktop nicht schon daran scheitert ───────────────────── +section "PulseAudio" +pulseaudio --kill 2>/dev/null || true +try pulseaudio --start --exit-idle-time=-1 \ + --load="module-native-protocol-tcp auth-ip-acl=127.0.0.1 auth-anonymous=1" \ + && ok "PulseAudio läuft" || warn "PulseAudio nicht gestartet — für S1 nicht kritisch" + +# ── Übergabe an Debian ─────────────────────────────────────────────────────── +section "Plasma Mobile starten" +log " Ab hier läuft alles im Debian-Rootfs. Erster Lauf lädt gut 1 GB Pakete." +log " Auf dem Telefon in die Termux:X11-App wechseln, um das Ergebnis zu sehen." +log "" + +HPOS_GPU_PROFIL="$GPU_PROFIL" HPOS_BREITE="$BREITE" HPOS_HOEHE="$HOEHE" \ +proot-distro login "$DISTRO" --shared-tmp --bind "$REPO_ROOT:/hpos" -- \ + bash /hpos/spike/inner/s1-plasma.sh 2>&1 | tee -a "$LOG" + +verdict "S1" "Zeigt die Termux:X11-App eine bedienbare Plasma-Mobile-Oberfläche? + Prüfe: reagiert Touch, erscheint die Maliit-Tastatur beim Tippen in ein Feld?" + +log "Aufräumen: pkill -f termux-x11 ; pkill -f virgl_test_server" diff --git a/spike/02-s2-mikrofon.sh b/spike/02-s2-mikrofon.sh new file mode 100755 index 0000000..cc965a8 --- /dev/null +++ b/spike/02-s2-mikrofon.sh @@ -0,0 +1,154 @@ +#!/usr/bin/env bash +# Phase 0 · Beweis S2 — Erreicht das Mikrofon die Linux-Seite? +# +# Der stillste K.-o.-Punkt des Projekts. Ohne Mikrofon im Linux gibt es keine +# Videocalls in Element oder im Browser, keine Sprachaufnahme, keine Spracheingabe. +# Die Wette: Termux' PulseAudio kann über OpenSL ES eine Aufnahmequelle anbieten, +# die per TCP in den proot durchgereicht wird. +# +# Ausführen IN TERMUX: bash spike/02-s2-mikrofon.sh +set -uo pipefail +source "$(dirname "$0")/lib.sh" +require_termux + +DISTRO=debian +DAUER="${HPOS_DAUER:-6}" +WAV="$OUT_DIR/s2-aufnahme.wav" + +log "HPOS Phase 0 · S2 — Mikrofon im Linux" +log "Protokoll: $LOG" + +# ── Berechtigung ───────────────────────────────────────────────────────────── +# Ohne erteilte Mikrofonberechtigung liefert die OpenSL-Quelle Stille statt eines +# Fehlers — der verwirrendste denkbare Fehlschlag. Deshalb vorher erzwingen. +section "Mikrofonberechtigung" +if have termux-microphone-record; then + log " Kurzer Test über Termux:API, um den Berechtigungsdialog auszulösen …" + try termux-microphone-record -d -l 2 -f "$OUT_DIR/s2-vortest.m4a" + sleep 3 + try termux-microphone-record -q + if [ -s "$OUT_DIR/s2-vortest.m4a" ]; then + ok "Termux:API kann aufnehmen — Berechtigung liegt vor" + else + fail "Termux:API konnte nicht aufnehmen" + hint "Einstellungen → Apps → Termux:API → Berechtigungen → Mikrofon erlauben." + hint "Ohne das ist S2 nicht aussagekräftig." + fi +else + warn "termux-microphone-record fehlt (Paket termux-api). Weiter ohne Vortest." +fi + +# ── PulseAudio mit Aufnahmequelle ──────────────────────────────────────────── +section "PulseAudio mit OpenSL-Quelle" +pulseaudio --kill 2>/dev/null || true +sleep 1 + +# module-sles-source ist der Teil, der wackelt: er existiert nicht in jedem +# Termux-Build. Deshalb erst mit, dann ohne — und der Unterschied wird protokolliert. +MIT_QUELLE=0 +if pulseaudio --start --exit-idle-time=-1 \ + --load="module-sles-source" \ + --load="module-native-protocol-tcp auth-ip-acl=127.0.0.1 auth-anonymous=1" \ + >>"$LOG" 2>&1; then + MIT_QUELLE=1 + ok "PulseAudio mit module-sles-source gestartet" +else + warn "module-sles-source ließ sich nicht laden — starte ohne" + try pulseaudio --start --exit-idle-time=-1 \ + --load="module-native-protocol-tcp auth-ip-acl=127.0.0.1 auth-anonymous=1" +fi +sleep 2 + +section "Verfügbare Quellen" +if have pactl; then + pactl list sources short 2>&1 | tee -a "$LOG" | sed 's/^/ /' + QUELLEN=$(pactl list sources short 2>/dev/null | wc -l) + if [ "$QUELLEN" -gt 0 ]; then + ok "$QUELLEN Aufnahmequelle(n) gemeldet" + else + fail "Keine einzige Aufnahmequelle — S2 wird so nicht gelingen" + hint "Falls module-sles-source fehlt, ist dieser Termux-Build ohne OpenSL-Aufnahme gebaut." + fi +else + warn "pactl fehlt, kann Quellen nicht auflisten" +fi + +# ── Aufnahme im Debian ─────────────────────────────────────────────────────── +section "Aufnahme aus dem Debian heraus" +log " ${DAUER} Sekunden werden aufgenommen — bitte jetzt laut und deutlich sprechen." +log "" +sleep 1 + +proot-distro login "$DISTRO" --shared-tmp --bind "$REPO_ROOT:/hpos" -- bash -lc " + set -uo pipefail + export PULSE_SERVER=127.0.0.1 + echo ' Quellen aus Sicht des Debian:' + pactl list sources short 2>&1 | sed 's/^/ /' || echo ' (pactl lieferte nichts)' + echo ' Nehme auf …' + timeout $((DAUER + 4)) parecord --channels=1 --rate=16000 --file-format=wav \ + /hpos/spike/out/$(basename "$WAV") & + REC=\$! + sleep $DAUER + kill \$REC 2>/dev/null + wait \$REC 2>/dev/null + echo ' Aufnahme beendet.' +" 2>&1 | tee -a "$LOG" + +# ── Auswertung ─────────────────────────────────────────────────────────────── +# Eine Datei zu erzeugen beweist nichts: die häufigste Fehlerform ist eine +# formal korrekte WAV-Datei voller Stille. Deshalb Größe UND Inhalt prüfen. +section "Auswertung" +if [ ! -s "$WAV" ]; then + fail "Keine Aufnahmedatei entstanden → S2 gescheitert" +else + GROESSE=$(stat -c%s "$WAV" 2>/dev/null || echo 0) + ok "Datei vorhanden: $WAV ($GROESSE Bytes)" + ERWARTET=$((DAUER * 16000 * 2 * 8 / 10)) + if [ "$GROESSE" -lt "$ERWARTET" ]; then + warn "Deutlich kleiner als erwartet (~$ERWARTET Bytes) — vermutlich abgebrochen" + fi + # Stilletest. Der typische Fehlschlag ist eine formal korrekte WAV-Datei voller + # Nullen — sie sieht in jeder Dateiliste richtig aus und enthält doch nichts. + # Deshalb wird der Pegel gemessen, nicht nur die Größe. + if have python; then + python - "$WAV" <<'PY' 2>&1 | tee -a "$LOG" +import array, math, sys, wave + +# Bewusst ohne audioop: das Modul ist in Python 3.13 entfernt worden, und Termux +# liegt regelmäßig auf der aktuellen Version. +try: + with wave.open(sys.argv[1]) as w: + breite = w.getsampwidth() + roh = w.readframes(w.getnframes()) + if breite == 1: + werte = array.array("b", bytes(b - 128 for b in roh)) + elif breite == 2: + werte = array.array("h", roh[: len(roh) // 2 * 2]) + else: + raise ValueError(f"{breite} Byte je Abtastwert werden hier nicht ausgewertet") + if not werte: + raise ValueError("keine Abtastwerte enthalten") + pegel = int(math.sqrt(sum(v * v for v in werte) / len(werte))) + spitze = max(abs(v) for v in werte) + print(f" Effektivpegel: {pegel} Spitze: {spitze}") + if spitze < 40: + print(" ✗ Praktisch Stille — die Quelle liefert keine echten Daten.") + print(" Das ist der typische Fehlschlag: Datei ja, Ton nein.") + print(" Prüfen: Mikrofonberechtigung für Termux:API, und ob") + print(" module-sles-source in diesem Termux-Build überhaupt existiert.") + else: + print(" ✓ Echtes Signal aufgenommen.") +except Exception as e: + print(f" ! Datei nicht auswertbar: {e}") +PY + fi + log "" + log " Zum Gegenhören: pactl upload-sample nicht nötig — einfach abspielen:" + log " proot-distro login $DISTRO -- env PULSE_SERVER=127.0.0.1 paplay /hpos/spike/out/$(basename "$WAV")" +fi + +log "" +log " module-sles-source geladen: $([ "$MIT_QUELLE" = 1 ] && echo ja || echo NEIN)" + +verdict "S2" "Ist auf der Aufnahme deine Stimme hörbar? + Abspielen mit dem Befehl oben. Eine Datei ohne Ton zählt als gescheitert." diff --git a/spike/03-s3-pipewire-kamera.sh b/spike/03-s3-pipewire-kamera.sh new file mode 100755 index 0000000..bb3b378 --- /dev/null +++ b/spike/03-s3-pipewire-kamera.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Phase 0 · Beweis S3 — Virtuelle Kamera ohne Kernel-Modul. +# +# v4l2loopback wäre der klassische Weg, ist aber ein Kernel-Modul und damit ohne Root +# ausgeschlossen. Die Wette: ein GStreamer-pipewiresink mit media.class=Video/Source +# erzeugt einen Kamera-Node komplett im Userspace, den Firefox über das Portal annimmt. +# +# Belegt der Beweis, funktionieren Videocalls im Browser und in Element. +# Siehe: https://www.ideasonboard.com/news/pipewire-is-the-new-v4l2loopback/ +# +# Ausführen IN TERMUX: bash spike/03-s3-pipewire-kamera.sh +set -uo pipefail +source "$(dirname "$0")/lib.sh" +require_termux + +DISTRO=debian + +log "HPOS Phase 0 · S3 — Virtuelle PipeWire-Kamera" +log "Protokoll: $LOG" +log "" +log "Voraussetzung: ein laufender X-Server aus S1." +log "Falls nicht, in einer zweiten Sitzung: termux-x11 :0 &" +log "" + +section "Übergabe an Debian" +proot-distro login "$DISTRO" --shared-tmp --bind "$REPO_ROOT:/hpos" -- \ + bash /hpos/spike/inner/s3-kamera.sh 2>&1 | tee -a "$LOG" + +verdict "S3" "Zeigt Firefox auf webcamtests.com ein Bild aus der Quelle 'HPOS-Testkamera'? + Ein reines Auftauchen im Auswahlmenü genügt nicht — es muss Bild kommen." diff --git a/spike/04-s4-intent-handoff.sh b/spike/04-s4-intent-handoff.sh new file mode 100755 index 0000000..37b3ff0 --- /dev/null +++ b/spike/04-s4-intent-handoff.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# Phase 0 · Beweis S4 — Intent-Handoff vom Linux zur Android-App. +# +# Die Wette: statt Android-Apps ins Linux zu streamen, übergibt das Linux eine +# Absicht und die App öffnet nativ im Vollbild. Für Navigation ist das nicht der +# Kompromiss, sondern die bessere Lösung — volles GPU, volles GPS, und Overlay-Apps +# funktionieren, was über einem gestreamten Fenster gar nicht ginge. +# +# Gebaut wird das nicht pro App, sondern einmal als xdg-open-Handler. Damit löst +# jede Linux-Anwendung — Adressbuch, Browser, Kalender — die richtige Android-App aus, +# ohne selbst etwas von der Bridge zu wissen. +# +# Ausführen IN TERMUX: bash spike/04-s4-intent-handoff.sh +set -uo pipefail +source "$(dirname "$0")/lib.sh" +require_termux + +DISTRO=debian +ZIEL="${HPOS_ZIEL:-Brandenburger Tor, Berlin}" +# Paketname einer Begleit-App mit Overlay, die vor der Navigation starten soll. +# Beispiel: HPOS_BEGLEIT_APP=de.blitzer bash spike/04-s4-intent-handoff.sh +BEGLEIT_APP="${HPOS_BEGLEIT_APP:-}" + +log "HPOS Phase 0 · S4 — Intent-Handoff" +log "Navigationsziel: $ZIEL" +log "Begleit-App: ${BEGLEIT_APP:-(keine)}" +log "Protokoll: $LOG" + +# ── Vorbedingungen ─────────────────────────────────────────────────────────── +section "Vorbedingungen" +if have am; then + ok "am vorhanden" +else + fail "am fehlt (Paket termux-tools) — ohne das geht nichts" + exit 1 +fi +have python && ok "python vorhanden" || { fail "python fehlt"; exit 1; } + +if [ -n "$BEGLEIT_APP" ]; then + if pm list packages 2>/dev/null | grep -q "package:$BEGLEIT_APP"; then + ok "Begleit-App $BEGLEIT_APP ist installiert" + hint "Overlay-Berechtigung nötig: Einstellungen → Apps → $BEGLEIT_APP → über anderen Apps anzeigen" + else + warn "Begleit-App $BEGLEIT_APP nicht gefunden — Overlay-Teil des Beweises entfällt" + fi +fi + +# ── Mini-Bridge starten ────────────────────────────────────────────────────── +section "Mini-Bridge" +pkill -f 'bridge-mini.py' 2>/dev/null && { hint "alte Instanz beendet"; sleep 1; } || true + +HPOS_BEGLEIT_APP="$BEGLEIT_APP" python "$REPO_ROOT/spike/bridge-mini.py" \ + >>"$OUT_DIR/bridge-mini.log" 2>&1 & +BRIDGE_PID=$! +sleep 2 + +if kill -0 "$BRIDGE_PID" 2>/dev/null; then + ok "Bridge läuft auf 127.0.0.1:8474 (PID $BRIDGE_PID)" +else + fail "Bridge startet nicht:" + tail -n 20 "$OUT_DIR/bridge-mini.log" | sed 's/^/ /' | tee -a "$LOG" + exit 1 +fi + +# ── Direkttest, bevor Linux ins Spiel kommt ────────────────────────────────── +# Erst beweisen, dass der Intent überhaupt zieht. Scheitert es hier, liegt es an +# Android und nicht an der Brücke ins Linux — ein wichtiger Unterschied. +section "Direkttest ohne Linux" +TOKEN="$(cat "$OUT_DIR/bridge-token")" +ANTWORT="$(printf '%s\t%s\n' "$TOKEN" "geo:0,0?q=$(python -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1]))" "$ZIEL")" \ + | timeout 25 python -c ' +import socket,sys +s=socket.create_connection(("127.0.0.1",8474),10) +s.sendall(sys.stdin.buffer.read()) +s.shutdown(socket.SHUT_WR) +print(s.recv(4096).decode().strip()) +' 2>&1)" +log " Antwort: $ANTWORT" +case "$ANTWORT" in + OK*) ok "Android hat den Intent angenommen — schau aufs Telefon" ;; + *) fail "Intent nicht angenommen" + hint "Ist eine Navigations-App installiert? Ohne Google Maps o. ä. gibt es keinen Empfänger." ;; +esac +sleep 6 + +# ── Der eigentliche Beweis: aus dem Linux heraus ───────────────────────────── +section "Aus dem Linux heraus" +HPOS_ZIEL="$ZIEL" proot-distro login "$DISTRO" --shared-tmp --bind "$REPO_ROOT:/hpos" -- \ + bash /hpos/spike/inner/s4-handoff.sh 2>&1 | tee -a "$LOG" + +log "" +log " Bridge-Protokoll: $OUT_DIR/bridge-mini.log" +log " Bridge beenden: kill $BRIDGE_PID" + +verdict "S4" "Hat 'xdg-open geo:…' aus dem Debian heraus die Navigation auf dem Telefon gestartet? + Und lag — falls konfiguriert — das Overlay der Begleit-App darüber?" diff --git a/spike/README.md b/spike/README.md new file mode 100644 index 0000000..e6a87f6 --- /dev/null +++ b/spike/README.md @@ -0,0 +1,87 @@ +# Phase 0 — Machbarkeits-Spike + +Vier Annahmen tragen das gesamte Projekt. Scheitert eine, sieht alles Weitere anders +aus — und das will man **vor** dem Bau der Infrastruktur wissen, nicht danach. + +Der Code hier ist bewusst Wegwerf-Code. Was zählt, sind die Protokolle: sie werden +später zur Grundlage der Setup-Skripte in `rootfs/`. + +| | Beweis | Wenn er scheitert | +|---|---|---| +| **S1** | Plasma Mobile läuft in proot (genestetes KWin) | XFCE-Profil wird Hauptweg; Kamera-Portal, Bildschirmtastatur und Telefonie-Oberfläche ändern sich mit | +| **S2** | Mikrofon-Eingang erreicht die Linux-Seite | Keine Videocalls, keine Sprachaufnahme, keine Spracheingabe | +| **S3** | Virtuelle PipeWire-Kamera ohne Kernel-Modul | Kamera nur in einer eigenständigen App, keine Browser-Videocalls | +| **S4** | Intent-Handoff Linux → Android-App | Keine Navigation aus dem Linux-Adressbuch | + +Dazu der **Übernacht-Test**: überlebt der Stack eine Nacht mit gesperrtem Bildschirm? +Scheitert er, ist das ein früher Warnschuss fürs Gesamtkonzept, kein Detail. + +## Voraussetzungen + +- Android-Telefon, Adreno 6xx/7xx bevorzugt (Turnip-Pfad) +- **Termux von F-Droid oder GitHub** — nicht aus dem Play Store, die Fassung dort ist + veraltet und unbrauchbar +- Einmalig `provision/provision.sh` vom PC, plus die dort genannten Handgriffe von Hand +- Etwa 6 GB freier Speicher (Debian-Rootfs, Plasma Mobile, Firefox) +- Geduld beim ersten Lauf: rund 1 GB Pakete + +## Ablauf + +```bash +# Einmalig, vom PC mit angeschlossenem Telefon +./provision/provision.sh + +# Auf dem Telefon in Termux +bash spike/00-bootstrap-termux.sh + +# Beweise in dieser Reihenfolge — 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 +``` + +Jedes Skript fragt am Ende nach deiner Beurteilung und schreibt sie nach +`spike/out/ergebnisse.tsv`. Rohprotokolle liegen daneben in `spike/out/*.log`. + +**Übertrage die Ergebnisse anschließend nach [`docs/spike-protokoll.md`](../docs/spike-protokoll.md).** +`spike/out/` ist absichtlich nicht im Repo — die Auswertung gehört dorthin, nicht die Rohdaten. + +## Stellschrauben + +| Variable | Vorgabe | Wofür | +|---|---|---| +| `HPOS_BREITE` / `HPOS_HOEHE` | `1080` / `2340` | Virtuelle Auflösung. Volle Panel-Auflösung ist für einen Telefon-Desktop zu viel Fläche | +| `HPOS_DAUER` | `6` | Sekunden Aufnahme in S2 | +| `HPOS_ZIEL` | Brandenburger Tor, Berlin | Navigationsziel in S4 | +| `HPOS_BEGLEIT_APP` | leer | Paketname einer Overlay-App, die in S4 vor der Navigation startet | + +Beispiel: + +```bash +HPOS_BEGLEIT_APP=de.blitzer HPOS_ZIEL="Kölner Dom" bash spike/04-s4-intent-handoff.sh +``` + +## Wenn etwas schiefgeht + +**S1 startet nicht.** Das Skript probiert von selbst zwei Wege und fällt am Ende auf +XFCE zurück. Läuft XFCE, dann tragen X-Server und GPU-Pfad — der Fehlschlag liegt dann +eindeutig bei Plasma. Läuft auch XFCE nicht, liegt es an X-Server oder GPU. + +**S2 erzeugt eine Datei ohne Ton.** Der häufigste und tückischste Fehlschlag. Das Skript +misst deshalb den Pegel. Ursache ist meist eine fehlende Mikrofonberechtigung für +Termux:API oder ein Termux-Build ohne `module-sles-source`. + +**S3: Firefox sieht die Kamera nicht.** Prüfen, ob `xdg-desktop-portal-kde` läuft — die +GTK- und wlr-Portale können das Kamera-Portal nicht bedienen. In `about:config` muss +`media.webrtc.camera.allow-pipewire` auf `true` stehen; das Skript setzt es vorab. + +**S4: kein Empfänger für den Intent.** Ohne installierte Navigations-App gibt es nichts, +was den `geo:`-Intent annehmen könnte. Das ist kein Fehler der Brücke. + +## Aufräumen + +```bash +pkill -f 'termux-x11|virgl_test_server|kwin_wayland|pipewire|bridge-mini.py' +pulseaudio --kill +``` diff --git a/spike/bridge-mini.py b/spike/bridge-mini.py new file mode 100755 index 0000000..2397ecf --- /dev/null +++ b/spike/bridge-mini.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 +"""HPOS Mini-Bridge — Prototyp des Intent-Handoffs für Beweis S4. + +Läuft in Termux, lauscht auf 127.0.0.1 und übersetzt URIs vom Linux in +Android-Intents. Das ist die kleinste ehrliche Fassung dessen, was später die +Bridge-APK macht: sie speichert nichts, sie reicht durch. + +Bewusst eng gehalten: + * nur 127.0.0.1, niemals extern erreichbar + * gemeinsames Token, damit nicht jede App auf dem Telefon Intents auslösen kann + * feste Liste erlaubter Schemata statt beliebiger Intents + +Start: python spike/bridge-mini.py +""" + +from __future__ import annotations + +import os +import shlex +import socketserver +import subprocess +import sys +import time +import urllib.parse + +HOST = "127.0.0.1" +PORT = int(os.environ.get("HPOS_BRIDGE_PORT", "8474")) + +BASIS = os.path.dirname(os.path.abspath(__file__)) +TOKEN_DATEI = os.path.join(BASIS, "out", "bridge-token") + +# Paketname einer Begleit-App, die vor der Hauptanwendung starten soll — typisch +# eine App mit Overlay, das über der Navigation liegen muss. Leer = keine. +BEGLEIT_APP = os.environ.get("HPOS_BEGLEIT_APP", "").strip() +BEGLEIT_VORLAUF = float(os.environ.get("HPOS_BEGLEIT_VORLAUF", "2.5")) + +# Was aus dem Linux kommen darf. Alles andere wird abgelehnt — eine offene +# Intent-Schnittstelle wäre ein Einfallstor für jede App auf dem Gerät. +ERLAUBTE_SCHEMATA = {"geo", "tel", "sms", "smsto", "mailto", "http", "https"} + + +def token_lesen() -> str: + """Token holen, beim ersten Start erzeugen.""" + os.makedirs(os.path.dirname(TOKEN_DATEI), exist_ok=True) + if not os.path.exists(TOKEN_DATEI): + with open(os.open(TOKEN_DATEI, os.O_CREAT | os.O_WRONLY, 0o600), "w") as f: + f.write(os.urandom(16).hex()) + with open(TOKEN_DATEI) as f: + return f.read().strip() + + +TOKEN = token_lesen() + + +def am(*args: str) -> tuple[int, str]: + """`am` aufrufen und Ergebnis zurückgeben.""" + befehl = ["am", *args] + print(f" → {' '.join(shlex.quote(a) for a in befehl)}", flush=True) + try: + p = subprocess.run(befehl, capture_output=True, text=True, timeout=20) + return p.returncode, (p.stdout + p.stderr).strip() + except FileNotFoundError: + return 127, "am nicht gefunden — Paket termux-tools fehlt" + except subprocess.TimeoutExpired: + return 124, "am hat nicht geantwortet" + + +def app_starten(paket: str) -> None: + am("start", "--user", "0", "-a", "android.intent.action.MAIN", + "-c", "android.intent.category.LAUNCHER", "-p", paket) + + +def navigation(ziel: str) -> tuple[int, str]: + """Turn-by-turn direkt starten statt nur die Karte zu zeigen. + + Vorher optional die Begleit-App, damit deren Overlay schon steht, wenn die + Navigation in den Vordergrund kommt — andersherum legt sich die Navigation + darüber und das Overlay ist verdeckt. + """ + if BEGLEIT_APP: + print(f" Begleit-App vorab: {BEGLEIT_APP}", flush=True) + app_starten(BEGLEIT_APP) + time.sleep(BEGLEIT_VORLAUF) + uri = "google.navigation:q=" + urllib.parse.quote(ziel) + "&mode=d" + return am("start", "--user", "0", "-a", "android.intent.action.VIEW", "-d", uri) + + +def behandeln(zeile: str) -> str: + """Eine Anfrage verarbeiten. Format: \\t""" + teile = zeile.split("\t", 1) + if len(teile) != 2: + return "FEHLER ungueltiges-format" + token, uri = teile[0].strip(), teile[1].strip() + + if token != TOKEN: + print(" ! Anfrage mit falschem Token abgelehnt", flush=True) + return "FEHLER token" + if not uri: + return "FEHLER leere-uri" + + schema = uri.split(":", 1)[0].lower() + + # geo:-URIs mit Suchbegriff bedeuten in der Praxis fast immer "dahin navigieren", + # nicht "Karte anschauen". Deshalb wird daraus Turn-by-turn. + if schema == "geo": + query = urllib.parse.parse_qs(urllib.parse.urlparse(uri).query).get("q", [""])[0] + if query: + rc, aus = navigation(query) + return f"OK navigation {rc} {aus}" if rc == 0 else f"FEHLER am {rc} {aus}" + + if schema == "tel": + # DIAL statt CALL: die Nummer erscheint im Wähler, gewählt wird bewusst. + rc, aus = am("start", "--user", "0", "-a", "android.intent.action.DIAL", "-d", uri) + return f"OK dial {rc} {aus}" if rc == 0 else f"FEHLER am {rc} {aus}" + + if schema in ERLAUBTE_SCHEMATA: + rc, aus = am("start", "--user", "0", "-a", "android.intent.action.VIEW", "-d", uri) + return f"OK view {rc} {aus}" if rc == 0 else f"FEHLER am {rc} {aus}" + + print(f" ! Schema abgelehnt: {schema}", flush=True) + return f"FEHLER schema-nicht-erlaubt {schema}" + + +class Handler(socketserver.StreamRequestHandler): + timeout = 10 + + def handle(self) -> None: + try: + zeile = self.rfile.readline(4096).decode("utf-8", "replace") + except Exception as e: + print(f" ! Lesefehler: {e}", flush=True) + return + if not zeile: + return + antwort = behandeln(zeile) + print(f" ← {antwort}", flush=True) + try: + self.wfile.write((antwort + "\n").encode()) + except Exception: + pass + + +class Server(socketserver.ThreadingTCPServer): + allow_reuse_address = True + daemon_threads = True + + +def main() -> int: + print("HPOS Mini-Bridge (Prototyp für Beweis S4)") + print(f" Lauscht auf {HOST}:{PORT}") + print(f" Token-Datei {TOKEN_DATEI}") + print(f" Begleit-App {BEGLEIT_APP or '(keine)'}") + print(" Beenden mit Strg+C") + print() + try: + with Server((HOST, PORT), Handler) as srv: + srv.serve_forever() + except KeyboardInterrupt: + print("\nBeendet.") + except OSError as e: + print(f"Start fehlgeschlagen: {e}", file=sys.stderr) + print(f"Läuft schon etwas auf Port {PORT}?", file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/spike/inner/s1-plasma.sh b/spike/inner/s1-plasma.sh new file mode 100755 index 0000000..45d9880 --- /dev/null +++ b/spike/inner/s1-plasma.sh @@ -0,0 +1,139 @@ +#!/bin/bash +# Läuft INNERHALB des Debian-Rootfs, aufgerufen von 01-s1-plasma-mobile.sh. +# Direkt aufrufen ergibt keinen Sinn — die Umgebung kommt vom äußeren Skript. +set -uo pipefail + +GPU_PROFIL="${HPOS_GPU_PROFIL:-virgl}" +BREITE="${HPOS_BREITE:-1080}" +HOEHE="${HPOS_HOEHE:-2340}" + +echo "── Debian-Seite: Plasma Mobile ──" + +export DEBIAN_FRONTEND=noninteractive +export DISPLAY=:0 +export XDG_RUNTIME_DIR=/tmp/hpos-runtime +export PULSE_SERVER=127.0.0.1 +export LANG=de_DE.UTF-8 +mkdir -p "$XDG_RUNTIME_DIR" && chmod 700 "$XDG_RUNTIME_DIR" + +# Plasma Mobile ist Touch-first: ohne diese Variablen kommt eine Desktop-Skalierung +# heraus, die auf einem Telefon unbedienbar ist. +export QT_QPA_PLATFORM=wayland +export QT_QUICK_CONTROLS_MOBILE=1 +export PLASMA_PLATFORM=phone +export KDE_FULL_SESSION=true + +case "$GPU_PROFIL" in + turnip) + # Zink übersetzt OpenGL auf Vulkan, Turnip liefert Vulkan direkt aus dem + # Adreno-Treiber — zusammen der schnellste Weg ohne Root. + export MESA_LOADER_DRIVER_OVERRIDE=zink + export GALLIUM_DRIVER=zink + export TU_DEBUG=noconform + ;; + virgl) + export GALLIUM_DRIVER=virpipe + export MESA_GL_VERSION_OVERRIDE=4.3 + ;; +esac +echo " GPU-Profil: $GPU_PROFIL" + +# ── Pakete ─────────────────────────────────────────────────────────────────── +# Bewusst ohne empfohlene Pakete: sonst zieht Plasma Mobile eine komplette +# Desktop-Umgebung samt Drucksystem mit, was auf einem Telefon nur Platz kostet. +if ! command -v kwin_wayland >/dev/null 2>&1; then + echo "── Installiere Plasma Mobile (einmalig, gut 1 GB) ──" + apt-get update -qq + apt-get install -y --no-install-recommends \ + plasma-mobile kwin-wayland plasma-workspace maliit-keyboard \ + xdg-desktop-portal-kde qtwayland5 libqt5waylandclient5 \ + || echo " ! Paketinstallation unvollständig — Details oben" +fi + +for b in kwin_wayland plasmashell; do + if command -v "$b" >/dev/null 2>&1; then echo " ✓ $b vorhanden" + else echo " ✗ $b fehlt"; fi +done + +# ── GPU-Nachweis, bevor der Desktop startet ────────────────────────────────── +# Wenn hier schon nichts kommt, ist ein Fehlschlag von Plasma keine Aussage über +# Plasma, sondern über den GPU-Pfad. Deshalb vorher messen. +echo "── GPU-Nachweis ──" +timeout 20 glxinfo -B 2>&1 | grep -Ei 'OpenGL (renderer|version)|Device' || echo " ! glxinfo lieferte nichts" +timeout 20 vulkaninfo --summary 2>&1 | grep -Ei 'driverName|deviceName' || echo " ! vulkaninfo lieferte nichts" + +# ── D-Bus ──────────────────────────────────────────────────────────────────── +if [ ! -e /run/dbus/system_bus_socket ]; then + mkdir -p /run/dbus + dbus-daemon --system --fork 2>/dev/null && echo " ✓ System-D-Bus gestartet" \ + || echo " ! System-D-Bus nicht gestartet (ohne logind oft verschmerzbar)" +fi + +# ── Versuch A: KWin genestet im X11-Backend ────────────────────────────────── +# Das ist die eigentliche Wette. In diesem Modus braucht KWin weder DRM noch Seat. +echo "" +echo "── Versuch A: kwin_wayland --x11-display ──" +LOGA=/tmp/hpos-s1-versuchA.log +dbus-run-session -- kwin_wayland \ + --x11-display "$DISPLAY" \ + --width "$BREITE" --height "$HOEHE" \ + --xwayland \ + -- plasmashell -p org.kde.plasma.phoneshell \ + >"$LOGA" 2>&1 & +PID_A=$! +echo " gestartet (PID $PID_A), warte 25 s auf die Oberfläche …" +sleep 25 + +if kill -0 "$PID_A" 2>/dev/null; then + echo " ✓ Versuch A läuft noch — jetzt in die Termux:X11-App wechseln und schauen." + echo "" + echo " Beenden mit: pkill -f kwin_wayland" + echo " Protokoll: $LOGA" + tail -n 25 "$LOGA" 2>/dev/null | sed 's/^/ /' + exit 0 +fi + +echo " ✗ Versuch A beendet. Letzte Zeilen:" +tail -n 40 "$LOGA" 2>/dev/null | sed 's/^/ /' + +# ── Versuch B: die von Debian mitgelieferte Sitzung ────────────────────────── +# Startet KWin selbst und wählt sein Backend eigenständig. Weniger Kontrolle, +# aber manchmal weiß das Distributionsskript etwas, das wir nicht wissen. +echo "" +echo "── Versuch B: startplasmamobile ──" +if command -v startplasmamobile >/dev/null 2>&1; then + LOGB=/tmp/hpos-s1-versuchB.log + KWIN_COMPOSE=O2 dbus-run-session -- startplasmamobile >"$LOGB" 2>&1 & + PID_B=$! + sleep 25 + if kill -0 "$PID_B" 2>/dev/null; then + echo " ✓ Versuch B läuft — in die Termux:X11-App wechseln." + tail -n 25 "$LOGB" 2>/dev/null | sed 's/^/ /' + exit 0 + fi + echo " ✗ Versuch B beendet. Letzte Zeilen:" + tail -n 40 "$LOGB" 2>/dev/null | sed 's/^/ /' +else + echo " ! startplasmamobile nicht vorhanden" +fi + +# ── Rückfallebene: XFCE, damit der Abend nicht umsonst war ─────────────────── +# Beweist zumindest, dass X-Server und GPU-Pfad tragen. Damit ist ein Fehlschlag +# von S1 eindeutig Plasma zuzuordnen und nicht der Umgebung. +echo "" +echo "── Rückfallebene: XFCE zur Gegenprobe ──" +if ! command -v startxfce4 >/dev/null 2>&1; then + apt-get install -y --no-install-recommends xfce4 xfce4-terminal >/dev/null 2>&1 \ + || echo " ! XFCE-Installation fehlgeschlagen" +fi +if command -v startxfce4 >/dev/null 2>&1; then + dbus-run-session -- startxfce4 >/tmp/hpos-s1-xfce.log 2>&1 & + sleep 12 + if pgrep -f xfwm4 >/dev/null 2>&1; then + echo " ✓ XFCE läuft. S1 ist gescheitert, aber X-Server und GPU-Pfad tragen." + echo " → Im Protokoll als 'S1 gescheitert, XFCE-Profil wird Hauptweg' vermerken." + else + echo " ✗ Auch XFCE startet nicht — dann liegt es an X-Server oder GPU, nicht an Plasma." + fi +fi +exit 1 diff --git a/spike/inner/s3-kamera.sh b/spike/inner/s3-kamera.sh new file mode 100755 index 0000000..fd9cd0a --- /dev/null +++ b/spike/inner/s3-kamera.sh @@ -0,0 +1,127 @@ +#!/bin/bash +# Läuft INNERHALB des Debian-Rootfs, aufgerufen von 03-s3-pipewire-kamera.sh. +set -uo pipefail + +export DEBIAN_FRONTEND=noninteractive +export DISPLAY=:0 +export XDG_RUNTIME_DIR=/tmp/hpos-runtime +export XDG_CURRENT_DESKTOP=KDE +export LANG=de_DE.UTF-8 +mkdir -p "$XDG_RUNTIME_DIR" && chmod 700 "$XDG_RUNTIME_DIR" + +echo "── Debian-Seite: virtuelle PipeWire-Kamera ──" + +# ── Pakete ─────────────────────────────────────────────────────────────────── +# xdg-desktop-portal-kde ist der Grund, warum Plasma Mobile hier im Vorteil ist: +# die GTK- und wlr-Portale können das Kamera-Portal nicht bedienen. +if ! command -v pw-cli >/dev/null 2>&1; then + echo "── Installiere PipeWire und GStreamer (einmalig) ──" + apt-get update -qq + apt-get install -y --no-install-recommends \ + pipewire pipewire-pulse wireplumber pipewire-bin \ + gstreamer1.0-pipewire gstreamer1.0-plugins-base gstreamer1.0-plugins-good \ + gstreamer1.0-tools xdg-desktop-portal xdg-desktop-portal-kde \ + || echo " ! Paketinstallation unvollständig" +fi + +# ── PipeWire starten ───────────────────────────────────────────────────────── +# Kein systemd im proot, also die Dienste von Hand — und in dieser Reihenfolge, +# sonst findet WirePlumber den Daemon nicht. +echo "── Dienste starten ──" +pkill -f 'pipewire|wireplumber|xdg-desktop-portal' 2>/dev/null; sleep 1 + +if [ ! -e /run/dbus/system_bus_socket ]; then + mkdir -p /run/dbus && dbus-daemon --system --fork 2>/dev/null || true +fi +eval "$(dbus-launch --sh-syntax)" 2>/dev/null || true + +pipewire >/tmp/hpos-pipewire.log 2>&1 & sleep 2 +pipewire-pulse >/tmp/hpos-pw-pulse.log 2>&1 & sleep 1 +wireplumber >/tmp/hpos-wireplumber.log 2>&1 & sleep 2 + +if pgrep -x pipewire >/dev/null; then echo " ✓ pipewire läuft"; else + echo " ✗ pipewire startet nicht:"; tail -n 20 /tmp/hpos-pipewire.log | sed 's/^/ /'; exit 1 +fi +pgrep -x wireplumber >/dev/null && echo " ✓ wireplumber läuft" \ + || echo " ! wireplumber läuft nicht — Nodes werden evtl. nicht verwaltet" + +/usr/libexec/xdg-desktop-portal-kde >/tmp/hpos-portal-kde.log 2>&1 & sleep 1 +/usr/libexec/xdg-desktop-portal >/tmp/hpos-portal.log 2>&1 & sleep 2 +pgrep -f xdg-desktop-portal >/dev/null && echo " ✓ Portal läuft" \ + || echo " ! Portal läuft nicht — Firefox wird die Kamera nicht sehen" + +# ── Die virtuelle Kamera ───────────────────────────────────────────────────── +# Im Spike steht ein Testbild statt der echten Kamera. Das ist Absicht: bewiesen +# werden soll der PIPEWIRE-Weg, nicht die Bridge. Später ersetzt der Bridge-Stream +# videotestsrc — sonst nichts. +echo "" +echo "── Virtuelle Kamera starten ──" +gst-launch-1.0 -q videotestsrc pattern=ball is-live=true \ + ! video/x-raw,format=YUY2,width=640,height=480,framerate=30/1 \ + ! pipewiresink mode=provide \ + stream-properties="props,media.class=Video/Source,node.description=HPOS-Testkamera,node.name=hpos_kamera" \ + >/tmp/hpos-kamera.log 2>&1 & +GST_PID=$! +sleep 4 + +if ! kill -0 "$GST_PID" 2>/dev/null; then + echo " ✗ GStreamer sofort beendet:" + tail -n 25 /tmp/hpos-kamera.log | sed 's/^/ /' + echo " Häufigste Ursache: gstreamer1.0-pipewire fehlt." + exit 1 +fi +echo " ✓ GStreamer läuft (PID $GST_PID)" + +# ── Nachweis, dass der Node existiert ──────────────────────────────────────── +echo "" +echo "── Node-Nachweis ──" +if command -v wpctl >/dev/null 2>&1; then + wpctl status 2>&1 | sed 's/^/ /' +fi +if pw-cli ls Node 2>/dev/null | grep -qi 'hpos_kamera\|HPOS-Testkamera'; then + echo " ✓ Kamera-Node ist in PipeWire registriert" +else + echo " ! Node nicht eindeutig gefunden — vollständige Liste:" + pw-cli ls Node 2>/dev/null | grep -Ei 'node.name|media.class' | sed 's/^/ /' +fi + +# ── Firefox ────────────────────────────────────────────────────────────────── +# Ohne media.webrtc.camera.allow-pipewire fragt Firefox weiterhin nur V4L2 ab und +# findet folgerichtig nichts. Die Einstellung wird hier vorab ins Profil geschrieben, +# damit der Test nicht an einem vergessenen Häkchen scheitert. +echo "" +echo "── Firefox vorbereiten ──" +if ! command -v firefox >/dev/null 2>&1 && ! command -v firefox-esr >/dev/null 2>&1; then + apt-get install -y --no-install-recommends firefox-esr >/dev/null 2>&1 \ + || echo " ! Firefox-Installation fehlgeschlagen" +fi +FF=$(command -v firefox || command -v firefox-esr || true) + +if [ -n "$FF" ]; then + PROFIL=$(find "$HOME/.mozilla/firefox" -maxdepth 1 -name '*.default*' -type d 2>/dev/null | head -1) + if [ -z "$PROFIL" ]; then + timeout 25 "$FF" --headless --screenshot /tmp/ff-init.png about:blank >/dev/null 2>&1 || true + PROFIL=$(find "$HOME/.mozilla/firefox" -maxdepth 1 -name '*.default*' -type d 2>/dev/null | head -1) + fi + if [ -n "$PROFIL" ]; then + { + echo 'user_pref("media.webrtc.camera.allow-pipewire", true);' + echo 'user_pref("widget.use-xdg-desktop-portal.camera", 1);' + } >>"$PROFIL/user.js" + echo " ✓ Einstellungen ins Profil geschrieben: $PROFIL" + else + echo " ! Kein Firefox-Profil gefunden — bitte manuell in about:config setzen:" + echo " media.webrtc.camera.allow-pipewire = true" + fi + echo "" + echo " Starte Firefox mit dem Kameratest …" + "$FF" https://webcamtests.com/ >/tmp/hpos-firefox.log 2>&1 & + sleep 3 + echo " → In der Termux:X11-App die Quelle 'HPOS-Testkamera' auswählen." + echo " Erwartet: ein springender Ball. Erscheint er, ist S3 bestanden." +else + echo " ! Ohne Firefox lässt sich der entscheidende Teil nicht prüfen." +fi + +echo "" +echo " Beenden mit: kill $GST_PID ; pkill -f 'pipewire|wireplumber'" diff --git a/spike/inner/s4-handoff.sh b/spike/inner/s4-handoff.sh new file mode 100755 index 0000000..fe65b84 --- /dev/null +++ b/spike/inner/s4-handoff.sh @@ -0,0 +1,106 @@ +#!/bin/bash +# Läuft INNERHALB des Debian-Rootfs, aufgerufen von 04-s4-intent-handoff.sh. +# +# Richtet hpos-open als xdg-open-Handler für geo:, tel:, sms: und mailto: ein. +# Der Witz daran: nichts wird pro Anwendung gebaut. Adressbuch, Browser, Kalender +# und Dateimanager rufen alle xdg-open auf und landen dadurch automatisch hier. +set -uo pipefail + +ZIEL="${HPOS_ZIEL:-Brandenburger Tor, Berlin}" +TOKEN_DATEI=/hpos/spike/out/bridge-token + +echo "── Debian-Seite: Intent-Handoff ──" + +export DEBIAN_FRONTEND=noninteractive +command -v xdg-open >/dev/null 2>&1 || apt-get install -y --no-install-recommends xdg-utils >/dev/null 2>&1 + +if [ ! -r "$TOKEN_DATEI" ]; then + echo " ✗ Token-Datei nicht lesbar: $TOKEN_DATEI" + echo " Läuft die Mini-Bridge? Sie erzeugt das Token beim ersten Start." + exit 1 +fi + +# ── hpos-open ──────────────────────────────────────────────────────────────── +# Der Handler selbst: nimmt eine URI, schickt sie an die Bridge, fertig. Später +# ersetzt die Bridge-APK den Gegenpart, dieses Skript bleibt wie es ist. +install -d /usr/local/bin +cat >/usr/local/bin/hpos-open <<'HANDLER' +#!/bin/sh +# Reicht eine URI an die HPOS-Bridge auf der Android-Seite weiter. +# Aufruf: hpos-open "geo:0,0?q=Adresse" +set -eu +URI="${1:-}" +[ -n "$URI" ] || { echo "Aufruf: hpos-open " >&2; exit 2; } + +TOKEN_DATEI="${HPOS_TOKEN_DATEI:-/hpos/spike/out/bridge-token}" +[ -r "$TOKEN_DATEI" ] || { echo "hpos-open: kein Token unter $TOKEN_DATEI" >&2; exit 1; } +TOKEN="$(cat "$TOKEN_DATEI")" + +printf '%s\t%s\n' "$TOKEN" "$URI" | timeout 20 python3 -c ' +import socket, sys +try: + s = socket.create_connection(("127.0.0.1", 8474), 10) +except OSError as e: + sys.exit(f"hpos-open: Bridge nicht erreichbar ({e})") +s.sendall(sys.stdin.buffer.read()) +s.shutdown(socket.SHUT_WR) +antwort = s.recv(4096).decode().strip() +print(antwort) +sys.exit(0 if antwort.startswith("OK") else 1) +' +HANDLER +chmod 755 /usr/local/bin/hpos-open +echo " ✓ /usr/local/bin/hpos-open eingerichtet" + +# ── Als Standardanwendung registrieren ─────────────────────────────────────── +cat >/usr/share/applications/hpos-open.desktop <<'DESKTOP' +[Desktop Entry] +Type=Application +Name=Auf dem Telefon öffnen +Comment=Reicht Adressen, Rufnummern und Nachrichten an die Android-Seite weiter +Exec=hpos-open %u +Terminal=false +NoDisplay=true +MimeType=x-scheme-handler/geo;x-scheme-handler/tel;x-scheme-handler/sms;x-scheme-handler/smsto;x-scheme-handler/mailto; +DESKTOP + +update-desktop-database /usr/share/applications >/dev/null 2>&1 || true +for schema in geo tel sms smsto; do + xdg-mime default hpos-open.desktop "x-scheme-handler/$schema" 2>/dev/null \ + && echo " ✓ $schema: → hpos-open" \ + || echo " ! $schema: Registrierung fehlgeschlagen" +done + +echo " Zugeordnet laut System:" +for schema in geo tel sms; do + printf ' %-6s %s\n' "$schema:" "$(xdg-mime query default "x-scheme-handler/$schema" 2>/dev/null || echo '?')" +done + +# ── Der Beweis ─────────────────────────────────────────────────────────────── +echo "" +echo "── Test 1: hpos-open direkt ──" +KODIERT=$(python3 -c 'import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1]))' "$ZIEL") +if hpos-open "geo:0,0?q=$KODIERT"; then + echo " ✓ Bridge hat angenommen" +else + echo " ✗ Bridge hat abgelehnt oder war nicht erreichbar" +fi +sleep 5 + +echo "" +echo "── Test 2: über xdg-open ──" +echo " Das ist der eigentliche Beweis: der Weg, den jede Linux-Anwendung nimmt." +if xdg-open "geo:0,0?q=$KODIERT" 2>&1; then + echo " ✓ xdg-open hat den Handler aufgerufen" +else + echo " ✗ xdg-open ist gescheitert" +fi +sleep 5 + +echo "" +echo "── Test 3: Rufnummer ──" +hpos-open "tel:+4930901820" || echo " ! tel: fehlgeschlagen" + +echo "" +echo " Jetzt aufs Telefon schauen: läuft die Navigation, liegt das Overlay darüber?" +echo " Zurück ins Linux über den Android-Task-Manager." diff --git a/spike/lib.sh b/spike/lib.sh new file mode 100755 index 0000000..3528732 --- /dev/null +++ b/spike/lib.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash +# Gemeinsame Hilfsfunktionen für die Phase-0-Spike-Skripte. +# Wird von den 0x-Skripten eingebunden, nicht direkt ausgeführt. + +# Farben nur wenn wir auf einem Terminal landen — sonst verschmutzen sie das Protokoll. +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 + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +OUT_DIR="$REPO_ROOT/spike/out" +mkdir -p "$OUT_DIR" + +# Jeder Lauf bekommt sein eigenes Protokoll. Die Sammlung ist später die halbe +# Auswertung — deshalb wird alles mitgeschrieben, auch was funktioniert hat. +LOG="${LOG:-$OUT_DIR/$(basename "${0%.sh}").log}" + +log() { printf '%s\n' "$*" | tee -a "$LOG"; } +section() { log ""; log "${C_HEAD}══ $* ══${C_OFF}"; } +ok() { log "${C_OK} ✓${C_OFF} $*"; } +warn() { log "${C_WARN} !${C_OFF} $*"; } +fail() { log "${C_ERR} ✗${C_OFF} $*"; } +hint() { log "${C_DIM} $*${C_OFF}"; } + +# Befehl ausführen, Ausgabe ins Protokoll, Fehler nicht tödlich. +# Für alles, was scheitern DARF, ohne den Spike abzubrechen. +try() { + log "${C_DIM} \$ $*${C_OFF}" + if "$@" >>"$LOG" 2>&1; then + return 0 + else + local rc=$? + warn "fehlgeschlagen (Code $rc): $*" + return $rc + fi +} + +# Wie try(), aber ein Fehlschlag beendet den Spike. Für echte Voraussetzungen. +need() { + log "${C_DIM} \$ $*${C_OFF}" + if ! "$@" >>"$LOG" 2>&1; then + fail "Abbruch — notwendiger Schritt fehlgeschlagen: $*" + log "" + log "Vollständiges Protokoll: $LOG" + exit 1 + fi +} + +have() { command -v "$1" >/dev/null 2>&1; } + +in_termux() { + case "${PREFIX:-}" in + *com.termux*) [ -d "$PREFIX/bin" ] ;; + *) return 1 ;; + esac +} + +require_termux() { + if ! in_termux; then + fail "Dieses Skript läuft in Termux auf dem Telefon, nicht auf dem PC." + exit 1 + fi +} + +# Fragt am Ende jedes Beweises nach dem, was nur ein Mensch beurteilen kann: +# ob es tatsächlich funktioniert hat. Das Ergebnis geht ins Protokoll. +verdict() { + local beweis="$1" frage="$2" + log "" + log "${C_HEAD}── Beurteilung $beweis ──${C_OFF}" + log "$frage" + printf ' [j] ja [n] nein [t] teilweise > ' + read -r antwort ' + read -r notiz >"$OUT_DIR/ergebnisse.tsv" + log "" + log "Ins Protokoll übertragen: docs/spike-protokoll.md" + log "Rohdaten: $LOG" +} + +# Gerätesteckbrief — landet in jedem Protokoll, damit Meldungen vergleichbar sind. +geraete_steckbrief() { + section "Gerät" + local p + for p in ro.product.manufacturer ro.product.model ro.product.device \ + ro.build.version.release ro.build.version.sdk ro.board.platform \ + ro.hardware ro.soc.model; do + log " $p = $(getprop "$p" 2>/dev/null || echo '?')" + done + log " Arch = $(uname -m)" + log " Kernel = $(uname -r)" + if have free; then log " RAM = $(free -h 2>/dev/null | awk '/Mem:/{print $2}')"; fi +}