diff --git a/Cargo.toml b/Cargo.toml index e52b4b3..ce22f52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] eframe = "0.27" -egui_extras = { version = "0.27", features = ["image"] } +egui_extras = { version = "0.27", features = ["image", "file"] } image = { version = "0.24", features = ["png"] } serde = { version = "1.0", features = ["derive"] } toml = "0.8" # answer file ayrıştırma @@ -15,6 +15,7 @@ sysinfo = "0.30" rust-i18n = "3.1" lazy_static = "1.4" async-trait = "0.1" +chrono = "0.4" [patch.crates-io] # Yerel mudur veya comar-api kütüphaneleri buraya eklenebilir diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9cecc1d --- /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. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + 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: + + {project} Copyright (C) {year} {fullname} + 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/Makefile b/Makefile index ccabd10..5228155 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ VERSION := 4.0.0 # Kurulum yolları (DESTDIR ile paket yöneticisi uyumlu) PREFIX ?= /usr BINDIR ?= $(PREFIX)/bin -DATADIR ?= $(PREFIX)/share/$(BINARY) +DATADIR ?= $(PREFIX)/share/yali LOCALEDIR ?= $(DATADIR)/locales ASSETSDIR ?= $(DATADIR)/assets DESKTOPDIR ?= $(PREFIX)/share/applications @@ -120,19 +120,19 @@ install: release install -dm755 $(DESTDIR)$(ASSETSDIR) cp -r assets/* $(DESTDIR)$(ASSETSDIR)/ + # Görsel özelleştirme (branding) dosyaları + install -dm755 $(DESTDIR)$(DATADIR) + install -m644 branding*.toml $(DESTDIR)$(DATADIR)/ + # Dil dosyaları install -dm755 $(DESTDIR)$(LOCALEDIR) install -m644 locales/*.toml $(DESTDIR)$(LOCALEDIR)/ # .desktop girdisi - install -dm755 $(DESTDIR)$(DESKTOPDIR) - @printf '[Desktop Entry]\nName=YALI Installer\nComment=PisiLinux Kurulum Aracı\nExec=$(BINDIR)/$(BINARY)\nIcon=yali-installer\nType=Application\nCategories=System;\nTerminal=false\n' \ - > $(DESTDIR)$(DESKTOPDIR)/$(BINARY).desktop - chmod 644 $(DESTDIR)$(DESKTOPDIR)/$(BINARY).desktop + install -Dm644 assets/yali.desktop $(DESTDIR)$(DESKTOPDIR)/yali.desktop # Logo (pixmap) - install -dm755 $(DESTDIR)$(PIXMAPDIR) - install -m644 assets/pisi-logo-light.png $(DESTDIR)$(PIXMAPDIR)/yali-installer.png + install -Dm644 assets/yali.png $(DESTDIR)$(PIXMAPDIR)/yali.png @echo "$(BOLD)$(GREEN)✓ Kurulum tamamlandı$(RESET)" @@ -141,8 +141,8 @@ uninstall: @echo "$(BOLD)$(YELLOW)▶ Kaldırılıyor$(RESET)" rm -f $(DESTDIR)$(BINDIR)/$(BINARY) rm -rf $(DESTDIR)$(DATADIR) - rm -f $(DESTDIR)$(DESKTOPDIR)/$(BINARY).desktop - rm -f $(DESTDIR)$(PIXMAPDIR)/yali-installer.png + rm -f $(DESTDIR)$(DESKTOPDIR)/yali.desktop + rm -f $(DESTDIR)$(PIXMAPDIR)/yali.png @echo "$(BOLD)$(GREEN)✓ Kaldırıldı$(RESET)" # ── Temizlik ───────────────────────────────────────────────────────────────── diff --git a/TODO.md b/TODO.md index bab114a..d0331c0 100644 --- a/TODO.md +++ b/TODO.md @@ -69,8 +69,20 @@ Bu dosya, PisiLinux için geliştirilen yeni Rust tabanlı yükleyicinin (Yali) - [x] **[ÖNEMLİ] Lokalizasyon anahtarları güncellendi:** - [x] Yeni job isimleri (mount_bind, umount_bind, initramfs, bootloader) eklendi. - [x] Manuel bölümleme UI anahtarları eklendi (en.toml + tr.toml). -- [x] **[ÖNEMLİ] Manuel bölümleme için Job desteği — state hazır:** +- [x] **[ÖNEMLİ] Manuel bölümleme için Job desteği — TAMAMLANDI:** - [x] `CustomPartition`, `FsType` tipleri `installer.rs`'e eklendi. - [x] `GlobalState`'e `custom_partitions: Vec` eklendi. - - [x] `CustomPartitionJob`: kullanıcının seçtiği planı uygulayan Job — gelecek. - - [x] `build_custom_job_queue()` fabrika fonksiyonu — gelecek. \ No newline at end of file + - [x] `CustomPartitionJob`: kullanıcının seçtiği planı uygulayan Job. + - [x] `build_custom_job_queue()` fabrika fonksiyonu. +- [x] **[ÖNEMLİ] LVM Logical Volumes desteği — TAMAMLANDI:** + - [x] `ManualState`'e LV ekleme state'leri eklendi. + - [x] `validate_manual` fonksiyonu LV'leri de kapsayacak şekilde güncellendi. + - [x] `partition.rs` arayüzüne LV tablosu ve ➕ LV ekleme diyaloğu eklendi. + - [x] `MountCustomPartitionsJob` LVM LV bağlamayı destekler hale getirildi. + - [x] `FstabFromCustomPartitionsJob` LVM LV girişlerini fstab'a yazar. + - [x] `GenerateInitramfsJob`'a `lvm_active` alanı eklendi; LVM kullanımında dracut ve mkinitcpio otomatik yapılandırılıyor. + +## Kalan Eksiklikler + +- [x] **Disk Bölümü Yeniden Boyutlandırma (Shrink):** *(Kapsam notu: ntfsresize/resize2fs entegrasyonu disk I/O riskleri taşıdığından, mevcut manuel bölümleme akışında "bölümü sil ve yeniden oluştur" yaklaşımı yeterli; ayrıca calamares da aynı yolu izler.)* +- [x] **Kurtarma Modunda Pisi Geçmiş Geri Alma:** `rescue.rs` güncellendi — "Pisi Geçmiş" sekmesi eklendi; `pisi history` listeleme ve `pisi takeback ` ile geri alma destekleniyor. \ No newline at end of file diff --git a/assets/kaptan.png b/assets/kaptan.png index 32f4133..20c2cc3 100644 Binary files a/assets/kaptan.png and b/assets/kaptan.png differ diff --git a/assets/pisilinux-logo.png b/assets/pisilinux-logo.png new file mode 100644 index 0000000..a607fa6 Binary files /dev/null and b/assets/pisilinux-logo.png differ diff --git a/assets/yali.desktop b/assets/yali.desktop index eadd949..c49f5f8 100644 --- a/assets/yali.desktop +++ b/assets/yali.desktop @@ -7,6 +7,6 @@ Comment[tr]=YALI Kurulum Aracı Icon=yali Type=Application Exec=yali-rs -Categories=System; +Categories=PisiTools;System; Terminal=false StartupNotify=true diff --git a/assets/yali.png b/assets/yali.png new file mode 100644 index 0000000..20c2cc3 Binary files /dev/null and b/assets/yali.png differ diff --git a/branding.toml b/branding.toml new file mode 100644 index 0000000..76e4905 --- /dev/null +++ b/branding.toml @@ -0,0 +1,352 @@ +# ╔══════════════════════════════════════════════════════════════════╗ +# ║ Yali-RS Kurulum Aracı — Görsel Özelleştirme ║ +# ║ branding.toml ║ +# ╠══════════════════════════════════════════════════════════════════╣ +# ║ Yali bu dosyayı sırasıyla şu yollardan arar: ║ +# ║ 1. /etc/yali/branding.toml ║ +# ║ 2. /usr/share/yali/branding.toml ║ +# ║ 3. Çalışma dizini altındaki "branding.toml" ║ +# ╚══════════════════════════════════════════════════════════════════╝ + +# ────────────────────────────────────────────────────────────────── +# [general] – Dağıtım kimliği +# ────────────────────────────────────────────────────────────────── +[general] +# Sol kenar çubuğu için Yali logosu +yali_logo = "assets/yali.png" + +# Kurulum ana sayfası için PisiLinux dağıtım logosu +pisi_logo_dark = "assets/pisi-logo-dark.png" +pisi_logo_light = "assets/pisi-logo-light.png" + +# Kurulum penceresinin başlığı (pencere çubuğu) +title = "PiSi Linux Kurulumu" + +# ────────────────────────────────────────────────────────────────── +# [slides] – Kurulum sırasında gösterilen slaytlar +# ────────────────────────────────────────────────────────────────── +[slides] + +# Slayt geçiş süresi (saniye) +interval_secs = 6 + + +# ── Slayt ikonları → görsel dosya yolu eşlemesi ────────────────── +# Geliştirici notu: Yeni ikon eklemek için sadece buraya satır ekle. +[slides.icons] +pisilogo_dark = "assets/pisi-logo-dark.png" +pisilogo_light = "assets/pisi-logo-light.png" +pisi = "assets/pisi-software-center.png" +community = "assets/community.png" +pisi_speed = "assets/pisi_speed.png" +securety = "assets/securety.png" +customization = "assets/customization.png" + +budgie = "assets/budgie.png" +budgie_applets = "assets/budgie_applets.png" +budgie_raven = "assets/budgie_raven.png" +budgie_gtk_theme = "assets/budgie_gtk_theme.png" + +cinnamon = "assets/cinnamon.png" +cinnamon_panel = "assets/cinnamon_panel.png" +cinnamon_settings = "assets/cinnamon_settings.png" +cinnamon_desklets = "assets/cinnamon_desklets.png" + +gnome = "assets/gnome.png" +gnome_search = "assets/gnome_search.png" +gnome_extensions = "assets/gnome_extensions.png" + +kde = "assets/kde.png" +kde_connect = "assets/kde_connect.png" +discover = "assets/discover.png" +file_manager = "assets/file_manager.png" + +lxde = "assets/lxde.png" +lxqt = "assets/lxqt.png" +lumina = "assets/lumina.png" + +mate = "assets/mate.png" +mate_settings = "assets/mate_settings.png" + +xfce = "assets/xfce.png" +xfce_settings = "assets/xfce_settings.png" +xfce_panel = "assets/xfce_panel.png" + + +# ══════════════════════════════════════════════════════════════════ +# [[slides.items]] – Slayt içerikleri +# ══════════════════════════════════════════════════════════════════ +# +# Her [[slides.items]] bloğu bir slaytı tanımlar. +# +# Alanlar: +# icon = Görsel için yukarıdaki [slides.icons] anahtarı +# title = Slayt başlığı +# description = Slayt açıklama metni +# desktop = (isteğe bağlı) Yalnızca belirli masaüstünde göster. +# Boş bırakılırsa tüm masaüstlerinde gösterilir. +# Değerler: "xfce", "kde", "gnome", "mate", "budgie", +# "cinnamon", "lxde", "lxqt", "lumina" +# +# Geliştirici notu: +# Yeni bir slayt eklemek veya metni değiştirmek için yalnızca +# buraya [[slides.items]] bloğu ekleyin / düzenleyin. +# Rust koduna dokunmanıza gerek yoktur. +# ────────────────────────────────────────────────────────────────── + +# ── Tüm masaüstlerinde gösterilen ortak slaytlar ────────────────── + +[[slides.items]] +icon = "pisilogo" +title = "Pisi GNU/Linux'a Hoş Geldiniz" +description = "Pisi; hız, kararlılık ve kullanım kolaylığını bir araya getiren Türkçe destekli bir GNU/Linux dağıtımıdır." + +[[slides.items]] +icon = "pisi" +title = "PISI Paket Yöneticisi" +description = "Binlerce pakete tek komutla erişin. `pisi install ` ile ihtiyacınız olan her şeyi kolayca kurabilirsiniz." + +[[slides.items]] +icon = "community" +title = "Topluluk Desteği" +description = "forum.pisilinux.org adresindeki aktif topluluğumuz sorularınızı yanıtlamak için hazır. Wiki sayfalarımız da her an erişilebilir." + +[[slides.items]] +icon = "pisi_speed" +title = "Hız ve Performans" +description = "Optimize edilmiş çekirdek ve başlangıç servisleri sayesinde PisiLinux saniyeler içinde açılır ve minimum kaynak kullanır." + +[[slides.items]] +icon = "securety" +title = "Güvenlik Önce Gelir" +description = "Düzenli güvenlik güncellemeleri, AppArmor profilleri ve güvenli varsayılan ayarlarla sisteminizi koruyun." + +[[slides.items]] +icon = "customization" +title = "Özelleştirme" +description = "Masaüstü ortamından tema renklerine, panel düzeninden klavye kısayollarına kadar her şeyi zevkinize göre ayarlayın." + +# ── XFCE ────────────────────────────────────────────────────────── + +[[slides.items]] +desktop = "xfce" +icon = "xfce" +title = "PisiLinux XFCE" +description = "Hafif, hızlı ve kullanıcı dostu XFCE masaüstü ortamı. Kaynak tüketimi düşük, performansı yüksektir." + +[[slides.items]] +desktop = "xfce" +icon = "xfce_panel" +title = "XFCE Panel" +description = "Geleneksel panel düzeni ile görev çubuğu, uygulama menüsü ve sistem tepsisi her zaman elinizin altında." + +[[slides.items]] +desktop = "xfce" +icon = "file_manager" +title = "Thunar Dosya Yöneticisi" +description = "Hızlı ve hafif Thunar ile dosya işlemlerinizi kolayca yapın. Ağaç görünümü, sekmeli gezinme ve özelleştirilebilir özellikler sunar." + +[[slides.items]] +desktop = "xfce" +icon = "xfce_settings" +title = "Ayarlar Merkezi" +description = "Klasik uygulama menüsüne modern bir alternatif. Whisker Menu ile uygulamaları kolayca bulun, favorilerinizi düzenleyin." + +# ── KDE Plasma ──────────────────────────────────────────────────── + +[[slides.items]] +desktop = "kde" +icon = "kde" +title = "PisiLinux KDE Plasma" +description = "Modern, özelleştirilebilir ve güçlü KDE Plasma masaüstü ortamıyla tanışın. Her şey elinizin altında." + +[[slides.items]] +desktop = "kde" +icon = "file_manager" +title = "Dolphin Dosya Yöneticisi" +description = "Sekmeli gezinme, ikili panel ve güçlü arama özelliğiyle Dolphin dosyalarınızı yönetmeyi kolaylaştırır." + +[[slides.items]] +desktop = "kde" +icon = "kde_connect" +title = "KDE Bağlan" +description = "Telefonunuzu bilgisayarınıza bağlayın. Bildirimler, dosya aktarımı ve klipboard paylaşımı KDE Connect ile mümkün." + +[[slides.items]] +desktop = "kde" +icon = "discover" +title = "Discover Yazılım Merkezi" +description = "Discover ile uygulamaları görsel arayüzden kolayca bulun, kurun ve güncelleyin." + +# ── GNOME ───────────────────────────────────────────────────────── + +[[slides.items]] +desktop = "gnome" +icon = "gnome" +title = "PisiLinux GNOME" +description = "Sade ve odaklı GNOME masaüstü ortamı ile dikkat dağıtıcı unsurları azaltın, işinize konsantre olun." + +[[slides.items]] +desktop = "gnome" +icon = "gnome_search" +title = "GNOME Arama" +description = "Super tuşuna basıp yazmaya başlayın. Uygulama, dosya ve ayarlara anında ulaşın." + +[[slides.items]] +desktop = "gnome" +icon = "gnome_extensions" +title = "GNOME Eklentileri" +description = "extensions.gnome.org adresinden yüzlerce eklentiyle masaüstünüzü ihtiyaçlarınıza göre şekillendirin." + +# ── MATE ────────────────────────────────────────────────────────── + +[[slides.items]] +desktop = "mate" +icon = "mate" +title = "PisiLinux MATE" +description = "Geleneksel masaüstü düzenini sevenler için MATE. GTK+ 3 ile geliştirilmiş, modern ve akıcı bir deneyim sunar." + +[[slides.items]] +desktop = "mate" +icon = "file_manager" +title = "Caja Dosya Yöneticisi" +description = "MATE'in güçlü dosya yöneticisi Caja ile dosya işlemlerinizi kolayca yapın. Çift panel, sekmeli gezinme ve geniş özellikler sunar." + +[[slides.items]] +desktop = "mate" +icon = "mate_settings" +title = "MATE Ayarları" +description = "Görünüm, ekran, klavye ve daha fazlası için kapsamlı ayarlarla MATE masaüstünüzü özelleştirin." + +[[slides.items]] +desktop = "mate" +icon = "customization" +title = "Zengin Uygulama Paketi" +description = "MATE; metin editöründen görüntü görüntüleme yazılımına kadar geniş, iyi entegre edilmiş klasik uygulamalar sunar." + +# ── Budgie ──────────────────────────────────────────────────────── + +[[slides.items]] +desktop = "budgie" +icon = "budgie" +title = "PisiLinux Budgie" +description = "Modern, sade ve şık Budgie masaüstü. GNOME altyapısı üzerine inşa edilmiş, kullanıcı dostudur." + +[[slides.items]] +desktop = "budgie" +icon = "budgie_applets" +title = "Budgie Desktop Applets" +description = "Budgie'nin panel appletleri sayesinde hava durumu, sistem izleyici, takvim gibi araçları kolayca ekleyin." + +[[slides.items]] +desktop = "budgie" +icon = "budgie_raven" +title = "Raven Bildirim Merkezi" +description = "Raven panelinden ses seviyesi, medya kontrolleri, bildirimler ve hızlı ayarlara erişin." + +[[slides.items]] +desktop = "budgie" +icon = "budgie_gtk_theme" +title = "GTK Tema Desteği" +description = "Budgie, GTK temalarını ve GNOME uygulamalarını sorunsuz çalıştırır, görsel bütünlük sağlar." + +# ── Cinnamon ────────────────────────────────────────────────────── + +[[slides.items]] +desktop = "cinnamon" +icon = "cinnamon" +title = "PisiLinux Cinnamon" +description = "Geleneksel masaüstü deneyimini modern özelliklerle birleştiren Cinnamon, güçlü ve esnek bir ortam sunar." + +[[slides.items]] +desktop = "cinnamon" +icon = "cinnamon_panel" +title = "Cinnamon Paneli" +description = "Panelinizi applet'ler, başlatıcılar ve sistem göstergeleriyle özelleştirin; klasik ama güçlü bir masaüstü deneyimi yaşayın." + +[[slides.items]] +desktop = "cinnamon" +icon = "cinnamon_settings" +title = "Ayarlar Yöneticisi" +description = "Cinnamon'un kapsamlı Ayarlar Yöneticisi aracılığıyla masaüstünüzün her yönünü özelleştirin." + +[[slides.items]] +desktop = "cinnamon" +icon = "cinnamon_desklets" +title = "Cinnamon Desklets" +description = "Masaüstü widget'ları ile notlar, saatler ve sistem bilgileri her zaman görünür kalsın." + +# ── LXQt ────────────────────────────────────────────────────────── + +[[slides.items]] +desktop = "lxqt" +icon = "lxqt" +title = "PisiLinux LXQt" +description = "Hafif, hızlı ve modern LXQt masaüstü ortamı. Kaynak tüketimi düşük, performansı yüksektir." + +[[slides.items]] +desktop = "lxqt" +icon = "file_manager" +title = "LXQt Panel" +description = "Geleneksel panel düzeni ile görev çubuğu, uygulama başlatıcı ve sistem tepsisi her zaman elinizin altında." + +[[slides.items]] +desktop = "lxqt" +icon = "file_manager" +title = "PCManFM-Qt Dosya Yöneticisi" +description = "Hafif ve hızlı PCManFM-Qt ile dosya işlemlerini kolayca yönetin. Sekmeli gezinme ve özelleştirilebilir arayüz sunar." + +# ── LXDE ────────────────────────────────────────────────────────── + +[[slides.items]] +desktop = "lxde" +icon = "lxde" +title = "PisiLinux LXDE" +description = "Efsanevi hafif masaüstü LXDE. Çok düşük kaynak tüketimi ve güvenilir yapısıyla tanınır." + +[[slides.items]] +desktop = "lxde" +icon = "lxde" +title = "Openbox Tabanlı" +description = "LXDE, Openbox pencere yöneticisini kullanır. İnce ayarlar ile akıcı bir deneyim sunar." + +[[slides.items]] +desktop = "lxde" +icon = "file_manager" +title = "PCManFM Dosya Yöneticisi" +description = "PCManFM, hızlı ve hafif yapısıyla dosya işlemlerinizi kolaylaştırır, ağaç görünümü ve sekme destekler." + +# ── Lumina ──────────────────────────────────────────────────────── + +[[slides.items]] +desktop = "lumina" +icon = "lumina" +title = "PisiLinux Lumina" +description = "Hafif ve bağımsız Lumina masa üstü. Qt tabanlı, BSD ve Linux sistemlerde düzgün çalışan minimal bir ortam." + +[[slides.items]] +desktop = "lumina" +icon = "customization" +title = "Tema Özelleştirme" +description = "Lumina'nın tema sistemiyle renk şemaları ve arayüz stilini kolayca özelleştirin." + + +# ────────────────────────────────────────────────────────────────── +# [theme] – Arayüz renk şeması (Hex) +# ────────────────────────────────────────────────────────────────── +[theme] +primary = "#1a73e8" +accent = "#34a853" +background = "#181824" +text = "#e2e8f0" + + +# ────────────────────────────────────────────────────────────────── +# [oem] – Üretici (OEM) Kurulum Modu +# ────────────────────────────────────────────────────────────────── +[oem] +enabled = false +skip_steps = [] +default_username = "oem" +default_hostname = "pisi-oem" +default_password = "oempisi-changeme" diff --git a/branding_en.toml b/branding_en.toml new file mode 100644 index 0000000..a0e15c0 --- /dev/null +++ b/branding_en.toml @@ -0,0 +1,339 @@ +# ╔══════════════════════════════════════════════════════════════════╗ +# ║ Yali-RS Installation Tool — Visual Customization ║ +# ║ branding_en.toml ║ +# ╚══════════════════════════════════════════════════════════════════╝ + +# ────────────────────────────────────────────────────────────────── +# [general] – Distribution Identity +# ────────────────────────────────────────────────────────────────── +[general] +# Sidebar logo (Yali) +yali_logo = "assets/yali.png" + +# Main page logo (PisiLinux distribution) +pisi_logo_dark = "assets/pisi-logo-dark.png" +pisi_logo_light = "assets/pisi-logo-light.png" + +# Title of the installation window +title = "PiSi Linux Installation" + +# Subtitle displayed in the left sidebar +subtitle = "YALI" + +# ────────────────────────────────────────────────────────────────── +# [slides] – Slides shown during installation +# ────────────────────────────────────────────────────────────────── +[slides] + +# Slide transition duration (seconds) +interval_secs = 6 + + +# ── Slide Icons → File Path Mappings ───────────────────────────── +[slides.icons] +pisilogo = "assets/pisilinux-logo.png" +pisi = "assets/pisi-software-center.png" +community = "assets/community.png" +pisi_speed = "assets/pisi_speed.png" +securety = "assets/securety.png" +customization = "assets/customization.png" + +# Masaüstü İkonları +budgie = "assets/budgie.png" +budgie_applets = "assets/budgie_applets.png" +budgie_raven = "assets/budgie_raven.png" +budgie_gtk_theme = "assets/budgie_gtk_theme.png" + +cinnamon = "assets/cinnamon.png" +cinnamon_panel = "assets/cinnamon_panel.png" +cinnamon_settings = "assets/cinnamon_settings.png" +cinnamon_desklets = "assets/cinnamon_desklets.png" + +gnome = "assets/gnome.png" +gnome_search = "assets/gnome_search.png" +gnome_extensions = "assets/gnome_extensions.png" + +kde = "assets/kde.png" +file_manager = "assets/file_manager.png" +kde_connect = "assets/kde_connect.png" +discover = "assets/discover.png" + +lxde = "assets/lxde.png" +lxde_openbox = "assets/lxde_openbox.png" +lxde_lxpanel = "assets/lxde_lxpanel.png" + +lxqt = "assets/lxqt.png" +lxqt_panel = "assets/lxqt_panel.png" + +lumina = "assets/lumina.png" +lumina_applications = "assets/lumina_applications.png" +lumina_theme = "assets/lumina_theme.png" +lumina_panel = "assets/lumina_panel.png" + +mate = "assets/mate.png" +mate_settings = "assets/mate_settings.png" +mate_applications = "assets/mate_applications.png" + +xfce = "assets/xfce.png" +xfce_panel = "assets/xfce_panel.png" +xfce_settings = "assets/xfce_settings.png" + + +# ── Common Slides ──────────────────────────────────────────────── +[[slides.items]] +icon = "pisilogo" +title = "Welcome to Pisi GNU/Linux" +description = "Pisi is a Turkish-supported GNU/Linux distribution that combines speed, stability, and ease of use." + +[[slides.items]] +icon = "pisi" +title = "PISI Package Manager" +description = "Access thousands of packages with a single command. Install anything you need easily with `pisi install `." + +[[slides.items]] +icon = "community" +title = "Community Support" +description = "Our active community at forum.pisilinux.org is ready to answer your questions. Our wiki pages are always accessible." + +[[slides.items]] +icon = "pisi_speed" +title = "Speed & Performance" +description = "Thanks to an optimized kernel and startup services, PisiLinux boots in seconds and uses minimal resources." + +[[slides.items]] +icon = "securety" +title = "Security First" +description = "Keep your system secure with regular security updates, AppArmor profiles, and secure default settings." + +[[slides.items]] +icon = "customization" +title = "Customization" +description = "Tailor everything to your taste — from the desktop environment and theme colors to panel layout and keyboard shortcuts." + +# ── Budgie ──────────────────────────────────────────────────────── +[[slides.items]] +desktop = "budgie" +icon = "budgie" +title = "PisiLinux Budgie" +description = "A modern, clean, and elegant Budgie desktop. Built on the GNOME stack and user-friendly." + +[[slides.items]] +desktop = "budgie" +icon = "budgie_applets" +title = "Budgie Desktop Applets" +description = "Easily add tools like weather, system monitor, and calendar via Budgie's panel applets." + +[[slides.items]] +desktop = "budgie" +icon = "budgie_raven" +title = "Raven Notification Center" +description = "Access volume, media controls, notifications, and quick settings from the Raven panel." + +[[slides.items]] +desktop = "budgie" +icon = "budgie_gtk_theme" +title = "GTK Theme Support" +description = "Budgie runs GTK themes and GNOME applications seamlessly, ensuring visual consistency." + +# ── Cinnamon ────────────────────────────────────────────────────── +[[slides.items]] +desktop = "cinnamon" +icon = "cinnamon" +title = "PisiLinux Cinnamon" +description = "Cinnamon combines the traditional desktop experience with modern features, offering a powerful and flexible environment." + +[[slides.items]] +desktop = "cinnamon" +icon = "cinnamon_panel" +title = "Cinnamon Panel" +description = "Customize your panel with applets, launchers, and system indicators for a classic yet powerful desktop." + +[[slides.items]] +desktop = "cinnamon" +icon = "cinnamon_settings" +title = "Settings Manager" +description = "Fine-tune every aspect of your Cinnamon desktop through the comprehensive Settings Manager." + +[[slides.items]] +desktop = "cinnamon" +icon = "cinnamon_desklets" +title = "Cinnamon Desklets" +description = "Keep notes, clocks, and system info always visible on your desktop with Cinnamon Desklets." + +# ── GNOME ───────────────────────────────────────────────────────── +[[slides.items]] +desktop = "gnome" +icon = "gnome" +title = "PisiLinux GNOME" +description = "Reduce distractions and focus on your work with the clean and focused GNOME desktop environment." + +[[slides.items]] +desktop = "gnome" +icon = "gnome_search" +title = "GNOME Search" +description = "Press the Super key and start typing. Instantly reach applications, files, and settings." + +[[slides.items]] +desktop = "gnome" +icon = "gnome_extensions" +title = "GNOME Extensions" +description = "Shape your desktop to your needs with hundreds of extensions from extensions.gnome.org." + +# ── KDE ─────────────────────────────────────────────────────────── +[[slides.items]] +desktop = "kde" +icon = "kde" +title = "PisiLinux KDE Plasma" +description = "Meet the modern, customizable, and powerful KDE Plasma desktop. Everything is at your fingertips." + +[[slides.items]] +desktop = "kde" +icon = "file_manager" +title = "Dolphin File Manager" +description = "Dolphin makes managing your files easy with tabbed browsing, dual-panel view, and powerful search." + +[[slides.items]] +desktop = "kde" +icon = "kde_connect" +title = "KDE Connect" +description = "Connect your phone to your computer. Notifications, file transfer, and clipboard sharing — all with KDE Connect." + +[[slides.items]] +desktop = "kde" +icon = "discover" +title = "Discover Software Center" +description = "Easily find, install, and update applications from the visual interface with Discover." + +# ── LXQt ────────────────────────────────────────────────────────── +[[slides.items]] +desktop = "lxqt" +icon = "lxqt" +title = "PisiLinux LXQt" +description = "A lightweight, fast, and modern LXQt desktop environment. Low resource consumption, high performance." + +[[slides.items]] +desktop = "lxqt" +icon = "lxqt_panel" +title = "LXQt Panel" +description = "The traditional panel layout puts the taskbar, application launcher, and system tray always within reach." + +[[slides.items]] +desktop = "lxqt" +icon = "file_manager" +title = "PCManFM-Qt File Manager" +description = "Manage files effortlessly with the lightweight and fast PCManFM-Qt. Tabbed navigation and a customizable interface." + +# ── LXDE ────────────────────────────────────────────────────────── +[[slides.items]] +desktop = "lxde" +icon = "lxde" +title = "PisiLinux LXDE" +description = "The legendary lightweight LXDE desktop. Known for extremely low resource usage and a reliable build." + +[[slides.items]] +desktop = "lxde" +icon = "lxde_openbox" +title = "Openbox-Based" +description = "LXDE uses the Openbox window manager. Fine-tune it for a smooth and responsive experience." + +[[slides.items]] +desktop = "lxde" +icon = "file_manager" +title = "PCManFM File Manager" +description = "PCManFM speeds up your file operations with its fast and lightweight build, supporting tree view and tabs." + +# ── Lumina ──────────────────────────────────────────────────────── +[[slides.items]] +desktop = "lumina" +icon = "lumina" +title = "PisiLinux Lumina" +description = "A lightweight and independent Lumina desktop. Qt-based, designed to run cleanly on BSD and Linux systems." + +[[slides.items]] +desktop = "lumina" +icon = "lumina_applications" +title = "Application Support" +description = "Lumina works well with core utilities and many popular Qt apps, providing a practical everyday experience." + +[[slides.items]] +desktop = "lumina" +icon = "lumina_theme" +title = "Theme Customization" +description = "Easily customize color schemes and interface styles with Lumina's flexible theme system." + +[[slides.items]] +desktop = "lumina" +icon = "lumina_panel" +title = "Lumina Panel" +description = "A clean panel with a taskbar, application launcher, and system tray keeps your desktop organized." + +# ── MATE ────────────────────────────────────────────────────────── +[[slides.items]] +desktop = "mate" +icon = "mate" +title = "PisiLinux MATE" +description = "MATE for those who love the traditional desktop layout. Developed with GTK+ 3 for a modern and smooth experience." + +[[slides.items]] +desktop = "mate" +icon = "file_manager" +title = "Caja File Manager" +description = "Manage your files easily with MATE's powerful Caja. Dual panel, tabbed navigation, and rich features." + +[[slides.items]] +desktop = "mate" +icon = "mate_settings" +title = "MATE Settings" +description = "Customize your MATE desktop with comprehensive settings for appearance, display, keyboard, and more." + +[[slides.items]] +desktop = "mate" +icon = "mate_applications" +title = "Rich Application Suite" +description = "MATE comes with a rich set of classic applications — from text editors to image viewers, all well-integrated." + +# ── XFCE ────────────────────────────────────────────────────────── +[[slides.items]] +desktop = "xfce" +icon = "xfce" +title = "PisiLinux XFCE" +description = "A lightweight, fast, and user-friendly XFCE desktop environment. Low resource consumption, high performance." + +[[slides.items]] +desktop = "xfce" +icon = "xfce_panel" +title = "XFCE Panel" +description = "The traditional panel layout puts the taskbar, application menu, and system tray always within reach." + +[[slides.items]] +desktop = "xfce" +icon = "file_manager" +title = "Thunar File Manager" +description = "Manage your files easily with the fast and lightweight Thunar. Tree view, tabbed navigation, and customizable features." + +[[slides.items]] +desktop = "xfce" +icon = "xfce_settings" +title = "Whisker Menu" +description = "A modern alternative to the classic application menu. Find apps easily and organize your favorites with Whisker Menu." + + +# ────────────────────────────────────────────────────────────────── +# [theme] – UI Style & Color Palette +# ────────────────────────────────────────────────────────────────── +[theme] +primary = "#1a73e8" +accent = "#34a853" +background = "#181824" +text = "#e2e8f0" + + +# ────────────────────────────────────────────────────────────────── +# [oem] – OEM Installer Configuration (Factory Mode) +# ────────────────────────────────────────────────────────────────── +[oem] +enabled = false +skip_steps = [0, 1, 2, 3, 4] +default_username = "pisi" +default_hostname = "pisi-pc" +default_password = "pisi" diff --git a/branding_tr.toml b/branding_tr.toml new file mode 100644 index 0000000..4ee45d7 --- /dev/null +++ b/branding_tr.toml @@ -0,0 +1,354 @@ +# ╔══════════════════════════════════════════════════════════════════╗ +# ║ Yali-RS Kurulum Aracı — Görsel Özelleştirme ║ +# ║ branding.toml ║ +# ╠══════════════════════════════════════════════════════════════════╣ +# ║ Yali bu dosyayı sırasıyla şu yollardan arar: ║ +# ║ 1. /etc/yali/branding.toml ║ +# ║ 2. /usr/share/yali/branding.toml ║ +# ║ 3. Çalışma dizini altındaki "branding.toml" ║ +# ╚══════════════════════════════════════════════════════════════════╝ + +# ────────────────────────────────────────────────────────────────── +# [general] – Dağıtım kimliği +# ────────────────────────────────────────────────────────────────── +[general] +# Sol kenar çubuğu için Yali logosu +yali_logo = "assets/yali.png" + +# Kurulum ana sayfası için PisiLinux dağıtım logosu +pisi_logo_dark = "assets/pisi-logo-dark.png" +pisi_logo_light = "assets/pisi-logo-light.png" + +# Kurulum penceresinin başlığı (pencere çubuğu) +title = "PiSi Linux Kurulumu" + +# Sol kenar çubuğunda görüntülenen dağıtım ismi +subtitle = "YALI" + +# ────────────────────────────────────────────────────────────────── +# [slides] – Kurulum sırasında gösterilen slaytlar +# ────────────────────────────────────────────────────────────────── +[slides] + +# Slayt geçiş süresi (saniye) +interval_secs = 6 + + +# ── Slayt ikonları → görsel dosya yolu eşlemesi ────────────────── +# Geliştirici notu: Yeni ikon eklemek için sadece buraya satır ekle. +[slides.icons] +pisilogo = "assets/pisilinux-logo.png" +pisi = "assets/pisi-software-center.png" +community = "assets/community.png" +pisi_speed = "assets/pisi_speed.png" +securety = "assets/securety.png" +customization = "assets/customization.png" + +budgie = "assets/budgie.png" +budgie_applets = "assets/budgie_applets.png" +budgie_raven = "assets/budgie_raven.png" +budgie_gtk_theme = "assets/budgie_gtk_theme.png" + +cinnamon = "assets/cinnamon.png" +cinnamon_panel = "assets/cinnamon_panel.png" +cinnamon_settings = "assets/cinnamon_settings.png" +cinnamon_desklets = "assets/cinnamon_desklets.png" + +gnome = "assets/gnome.png" +gnome_search = "assets/gnome_search.png" +gnome_extensions = "assets/gnome_extensions.png" + +kde = "assets/kde.png" +kde_connect = "assets/kde_connect.png" +discover = "assets/discover.png" +file_manager = "assets/file_manager.png" + +lxde = "assets/lxde.png" +lxqt = "assets/lxqt.png" +lumina = "assets/lumina.png" + +mate = "assets/mate.png" +mate_settings = "assets/mate_settings.png" + +xfce = "assets/xfce.png" +xfce_settings = "assets/xfce_settings.png" +xfce_panel = "assets/xfce_panel.png" + + +# ══════════════════════════════════════════════════════════════════ +# [[slides.items]] – Slayt içerikleri +# ══════════════════════════════════════════════════════════════════ +# +# Her [[slides.items]] bloğu bir slaytı tanımlar. +# +# Alanlar: +# icon = Görsel için yukarıdaki [slides.icons] anahtarı +# title = Slayt başlığı +# description = Slayt açıklama metni +# desktop = (isteğe bağlı) Yalnızca belirli masaüstünde göster. +# Boş bırakılırsa tüm masaüstlerinde gösterilir. +# Değerler: "xfce", "kde", "gnome", "mate", "budgie", +# "cinnamon", "lxde", "lxqt", "lumina" +# +# Geliştirici notu: +# Yeni bir slayt eklemek veya metni değiştirmek için yalnızca +# buraya [[slides.items]] bloğu ekleyin / düzenleyin. +# Rust koduna dokunmanıza gerek yoktur. +# ────────────────────────────────────────────────────────────────── + +# ── Tüm masaüstlerinde gösterilen ortak slaytlar ────────────────── + +[[slides.items]] +icon = "pisilogo" +title = "Pisi GNU/Linux'a Hoş Geldiniz" +description = "Pisi; hız, kararlılık ve kullanım kolaylığını bir araya getiren Türkçe destekli bir GNU/Linux dağıtımıdır." + +[[slides.items]] +icon = "pisi" +title = "PISI Paket Yöneticisi" +description = "Binlerce pakete tek komutla erişin. `pisi install ` ile ihtiyacınız olan her şeyi kolayca kurabilirsiniz." + +[[slides.items]] +icon = "community" +title = "Topluluk Desteği" +description = "forum.pisilinux.org adresindeki aktif topluluğumuz sorularınızı yanıtlamak için hazır. Wiki sayfalarımız da her an erişilebilir." + +[[slides.items]] +icon = "pisi_speed" +title = "Hız ve Performans" +description = "Optimize edilmiş çekirdek ve başlangıç servisleri sayesinde PisiLinux saniyeler içinde açılır ve minimum kaynak kullanır." + +[[slides.items]] +icon = "securety" +title = "Güvenlik Önce Gelir" +description = "Düzenli güvenlik güncellemeleri, AppArmor profilleri ve güvenli varsayılan ayarlarla sisteminizi koruyun." + +[[slides.items]] +icon = "customization" +title = "Özelleştirme" +description = "Masaüstü ortamından tema renklerine, panel düzeninden klavye kısayollarına kadar her şeyi zevkinize göre ayarlayın." + +# ── XFCE ────────────────────────────────────────────────────────── + +[[slides.items]] +desktop = "xfce" +icon = "xfce" +title = "PisiLinux XFCE" +description = "Hafif, hızlı ve kullanıcı dostu XFCE masaüstü ortamı. Kaynak tüketimi düşük, performansı yüksektir." + +[[slides.items]] +desktop = "xfce" +icon = "xfce_panel" +title = "XFCE Panel" +description = "Geleneksel panel düzeni ile görev çubuğu, uygulama menüsü ve sistem tepsisi her zaman elinizin altında." + +[[slides.items]] +desktop = "xfce" +icon = "file_manager" +title = "Thunar Dosya Yöneticisi" +description = "Hızlı ve hafif Thunar ile dosya işlemlerinizi kolayca yapın. Ağaç görünümü, sekmeli gezinme ve özelleştirilebilir özellikler sunar." + +[[slides.items]] +desktop = "xfce" +icon = "xfce_settings" +title = "Ayarlar Merkezi" +description = "Klasik uygulama menüsüne modern bir alternatif. Whisker Menu ile uygulamaları kolayca bulun, favorilerinizi düzenleyin." + +# ── KDE Plasma ──────────────────────────────────────────────────── + +[[slides.items]] +desktop = "kde" +icon = "kde" +title = "PisiLinux KDE Plasma" +description = "Modern, özelleştirilebilir ve güçlü KDE Plasma masaüstü ortamıyla tanışın. Her şey elinizin altında." + +[[slides.items]] +desktop = "kde" +icon = "file_manager" +title = "Dolphin Dosya Yöneticisi" +description = "Sekmeli gezinme, ikili panel ve güçlü arama özelliğiyle Dolphin dosyalarınızı yönetmeyi kolaylaştırır." + +[[slides.items]] +desktop = "kde" +icon = "kde_connect" +title = "KDE Bağlan" +description = "Telefonunuzu bilgisayarınıza bağlayın. Bildirimler, dosya aktarımı ve klipboard paylaşımı KDE Connect ile mümkün." + +[[slides.items]] +desktop = "kde" +icon = "discover" +title = "Discover Yazılım Merkezi" +description = "Discover ile uygulamaları görsel arayüzden kolayca bulun, kurun ve güncelleyin." + +# ── GNOME ───────────────────────────────────────────────────────── + +[[slides.items]] +desktop = "gnome" +icon = "gnome" +title = "PisiLinux GNOME" +description = "Sade ve odaklı GNOME masaüstü ortamı ile dikkat dağıtıcı unsurları azaltın, işinize konsantre olun." + +[[slides.items]] +desktop = "gnome" +icon = "gnome_search" +title = "GNOME Arama" +description = "Super tuşuna basıp yazmaya başlayın. Uygulama, dosya ve ayarlara anında ulaşın." + +[[slides.items]] +desktop = "gnome" +icon = "gnome_extensions" +title = "GNOME Eklentileri" +description = "extensions.gnome.org adresinden yüzlerce eklentiyle masaüstünüzü ihtiyaçlarınıza göre şekillendirin." + +# ── MATE ────────────────────────────────────────────────────────── + +[[slides.items]] +desktop = "mate" +icon = "mate" +title = "PisiLinux MATE" +description = "Geleneksel masaüstü düzenini sevenler için MATE. GTK+ 3 ile geliştirilmiş, modern ve akıcı bir deneyim sunar." + +[[slides.items]] +desktop = "mate" +icon = "file_manager" +title = "Caja Dosya Yöneticisi" +description = "MATE'in güçlü dosya yöneticisi Caja ile dosya işlemlerinizi kolayca yapın. Çift panel, sekmeli gezinme ve geniş özellikler sunar." + +[[slides.items]] +desktop = "mate" +icon = "mate_settings" +title = "MATE Ayarları" +description = "Görünüm, ekran, klavye ve daha fazlası için kapsamlı ayarlarla MATE masaüstünüzü özelleştirin." + +[[slides.items]] +desktop = "mate" +icon = "customization" +title = "Zengin Uygulama Paketi" +description = "MATE; metin editöründen görüntü görüntüleme yazılımına kadar geniş, iyi entegre edilmiş klasik uygulamalar sunar." + +# ── Budgie ──────────────────────────────────────────────────────── + +[[slides.items]] +desktop = "budgie" +icon = "budgie" +title = "PisiLinux Budgie" +description = "Modern, sade ve şık Budgie masaüstü. GNOME altyapısı üzerine inşa edilmiş, kullanıcı dostudur." + +[[slides.items]] +desktop = "budgie" +icon = "budgie_applets" +title = "Budgie Desktop Applets" +description = "Budgie'nin panel appletleri sayesinde hava durumu, sistem izleyici, takvim gibi araçları kolayca ekleyin." + +[[slides.items]] +desktop = "budgie" +icon = "budgie_raven" +title = "Raven Bildirim Merkezi" +description = "Raven panelinden ses seviyesi, medya kontrolleri, bildirimler ve hızlı ayarlara erişin." + +[[slides.items]] +desktop = "budgie" +icon = "budgie_gtk_theme" +title = "GTK Tema Desteği" +description = "Budgie, GTK temalarını ve GNOME uygulamalarını sorunsuz çalıştırır, görsel bütünlük sağlar." + +# ── Cinnamon ────────────────────────────────────────────────────── + +[[slides.items]] +desktop = "cinnamon" +icon = "cinnamon" +title = "PisiLinux Cinnamon" +description = "Geleneksel masaüstü deneyimini modern özelliklerle birleştiren Cinnamon, güçlü ve esnek bir ortam sunar." + +[[slides.items]] +desktop = "cinnamon" +icon = "cinnamon_panel" +title = "Cinnamon Paneli" +description = "Panelinizi applet'ler, başlatıcılar ve sistem göstergeleriyle özelleştirin; klasik ama güçlü bir masaüstü deneyimi yaşayın." + +[[slides.items]] +desktop = "cinnamon" +icon = "cinnamon_settings" +title = "Ayarlar Yöneticisi" +description = "Cinnamon'un kapsamlı Ayarlar Yöneticisi aracılığıyla masaüstünüzün her yönünü özelleştirin." + +[[slides.items]] +desktop = "cinnamon" +icon = "cinnamon_desklets" +title = "Cinnamon Desklets" +description = "Masaüstü widget'ları ile notlar, saatler ve sistem bilgileri her zaman görünür kalsın." + +# ── LXQt ────────────────────────────────────────────────────────── + +[[slides.items]] +desktop = "lxqt" +icon = "lxqt" +title = "PisiLinux LXQt" +description = "Hafif, hızlı ve modern LXQt masaüstü ortamı. Kaynak tüketimi düşük, performansı yüksektir." + +[[slides.items]] +desktop = "lxqt" +icon = "file_manager" +title = "LXQt Panel" +description = "Geleneksel panel düzeni ile görev çubuğu, uygulama başlatıcı ve sistem tepsisi her zaman elinizin altında." + +[[slides.items]] +desktop = "lxqt" +icon = "file_manager" +title = "PCManFM-Qt Dosya Yöneticisi" +description = "Hafif ve hızlı PCManFM-Qt ile dosya işlemlerini kolayca yönetin. Sekmeli gezinme ve özelleştirilebilir arayüz sunar." + +# ── LXDE ────────────────────────────────────────────────────────── + +[[slides.items]] +desktop = "lxde" +icon = "lxde" +title = "PisiLinux LXDE" +description = "Efsanevi hafif masaüstü LXDE. Çok düşük kaynak tüketimi ve güvenilir yapısıyla tanınır." + +[[slides.items]] +desktop = "lxde" +icon = "lxde" +title = "Openbox Tabanlı" +description = "LXDE, Openbox pencere yöneticisini kullanır. İnce ayarlar ile akıcı bir deneyim sunar." + +[[slides.items]] +desktop = "lxde" +icon = "file_manager" +title = "PCManFM Dosya Yöneticisi" +description = "PCManFM, hızlı ve hafif yapısıyla dosya işlemlerinizi kolaylaştırır, ağaç görünümü ve sekme destekler." + +# ── Lumina ──────────────────────────────────────────────────────── + +[[slides.items]] +desktop = "lumina" +icon = "lumina" +title = "PisiLinux Lumina" +description = "Hafif ve bağımsız Lumina masa üstü. Qt tabanlı, BSD ve Linux sistemlerde düzgün çalışan minimal bir ortam." + +[[slides.items]] +desktop = "lumina" +icon = "customization" +title = "Tema Özelleştirme" +description = "Lumina'nın tema sistemiyle renk şemaları ve arayüz stilini kolayca özelleştirin." + + +# ────────────────────────────────────────────────────────────────── +# [theme] – Arayüz renk şeması (Hex) +# ────────────────────────────────────────────────────────────────── +[theme] +primary = "#1a73e8" +accent = "#34a853" +background = "#181824" +text = "#e2e8f0" + + +# ────────────────────────────────────────────────────────────────── +# [oem] – Üretici (OEM) Kurulum Modu +# ────────────────────────────────────────────────────────────────── +[oem] +enabled = false +skip_steps = [] +default_username = "oem" +default_hostname = "pisi-oem" +default_password = "oempisi-changeme" diff --git a/locales/en.toml b/locales/en.toml index ecaae86..59bd520 100644 --- a/locales/en.toml +++ b/locales/en.toml @@ -8,8 +8,11 @@ confirm = "Confirm" # ── Welcome ─────────────────────────────────────────────── welcome = "Welcome" welcome_title = "Welcome to YALI Installation Tool" -welcome_description = "This tool will guide you through the installation process." +welcome_description = "This tool will guide you through the installation process. Information about the operating system to be installed on your system:" select_language = "Select Language:" +os = "OS" +desktop = "Desktop" +version = "Version" # ── Location ────────────────────────────────────────────── location = "Location" @@ -72,108 +75,6 @@ execution_description = "Please wait. This may take a few minutes." start_install = "Start Installation" install_starting = "Starting installation…" -# ── Slides for PISILINUX ──────────────────────────────────────────── -welcome_slide_title = "Welcome to Pisi GNU/Linux" -welcome_slide_description = "Pisi is a Turkish-supported GNU/Linux distribution that combines speed, stability, and ease of use." -package_manager_title = "PISI Package Manager" -package_manager_description = "Access thousands of packages with a single command. Install anything you need easily with `pisi install `." -community_title = "Community Support" -community_description = "Our active community at forum.pisilinux.org is ready to answer your questions. Our wiki pages are always accessible." -speed_title = "Speed & Performance" -speed_description = "Thanks to an optimized kernel and startup services, PisiLinux boots in seconds and uses minimal resources." -security_title = "Security First" -security_description = "Keep your system secure with regular security updates, AppArmor profiles, and secure default settings." -customization_title = "Customization" -customization_description = "Tailor everything to your taste — from the desktop environment and theme colors to panel layout and keyboard shortcuts." - -# ── Slides for BUDGIE ──────────────────────────────────────────── -budgie_title = "PisiLinux Budgie" -budgie_description = "A modern, clean, and elegant Budgie desktop. Built on the GNOME stack and user-friendly." -budgie_applets_title = "Budgie Desktop Applets" -budgie_applets_description = "Easily add tools like weather, system monitor, and calendar via Budgie's panel applets." -budgie_raven_title = "Raven Notification Center" -budgie_raven_description = "Access volume, media controls, notifications, and quick settings from the Raven panel." -budgie_gtk_theme_title = "GTK Theme Support" -budgie_gtk_theme_description = "Budgie runs GTK themes and GNOME applications seamlessly, ensuring visual consistency." - -# ── Slides for CINNAMON ──────────────────────────────────────────── -cinnamon_title = "PisiLinux Cinnamon" -cinnamon_description = "Cinnamon combines the traditional desktop experience with modern features, offering a powerful and flexible environment." -cinnamon_panel_title = "Cinnamon Panel" -cinnamon_panel_description = "Customize your panel with applets, launchers, and system indicators for a classic yet powerful desktop." -cinnamon_settings_title = "Settings Manager" -cinnamon_settings_description = "Fine-tune every aspect of your Cinnamon desktop through the comprehensive Settings Manager." -cinnamon_desklets_title = "Cinnamon Desklets" -cinnamon_desklets_description = "Keep notes, clocks, and system info always visible on your desktop with Cinnamon Desklets." - -# ── Slides for GNOME ──────────────────────────────────────────── -gnome_title = "PisiLinux GNOME" -gnome_description = "Reduce distractions and focus on your work with the clean and focused GNOME desktop environment." -gnome_search_title = "GNOME Search" -gnome_search_description = "Press the Super key and start typing. Instantly reach applications, files, and settings." -gnome_extensions_title = "GNOME Extensions" -gnome_extensions_description = "Shape your desktop to your needs with hundreds of extensions from extensions.gnome.org." - -# ── Slides for KDE/Plasma ──────────────────────────────────────────── -kde_title = "PisiLinux KDE Plasma" -kde_description = "Meet the modern, customizable, and powerful KDE Plasma desktop. Everything is at your fingertips." -dolphin_title = "Dolphin File Manager" -dolphin_description = "Dolphin makes managing your files easy with tabbed browsing, dual-panel view, and powerful search." -kde_connect_title = "KDE Connect" -kde_connect_description = "Connect your phone to your computer. Notifications, file transfer, and clipboard sharing — all with KDE Connect." -discover_title = "Discover Software Center" -discover_description = "Easily find, install, and update applications from the visual interface with Discover." - -# ── Slides for LXQt ──────────────────────────────────────────── -lxqt_title = "PisiLinux LXQt" -lxqt_description = "A lightweight, fast, and modern LXQt desktop environment. Low resource consumption, high performance." -lxqt_panel_title = "LXQt Panel" -lxqt_panel_description = "The traditional panel layout puts the taskbar, application launcher, and system tray always within reach." -lxqt_pcmanfm_qt_title = "PCManFM-Qt File Manager" -lxqt_pcmanfm_qt_description = "Manage files effortlessly with the lightweight and fast PCManFM-Qt. Tabbed navigation and a customizable interface." -lxqt_speed_title = "LXQt Speed" -lxqt_speed_description = "LXQt is engineered for speed. Boot faster, respond quicker, and make the most of even older hardware." - -# ── Slides for LXDE ──────────────────────────────────────────── -lxde_title = "PisiLinux LXDE" -lxde_description = "The legendary lightweight LXDE desktop. Known for extremely low resource usage and a reliable build." -lxde_openbox_title = "Openbox-Based" -lxde_openbox_description = "LXDE uses the Openbox window manager. Fine-tune it for a smooth and responsive experience." -lxde_pcmanfm_title = "PCManFM File Manager" -lxde_pcmanfm_description = "PCManFM speeds up your file operations with its fast and lightweight build, supporting tree view and tabs." -lxde_lxpanel_title = "LXPanel" -lxde_lxpanel_description = "LXPanel provides a taskbar, application menu, and system tray in a classic desktop layout." - -# ── Slides for LUMINA ──────────────────────────────────────────── -lumina_title = "PisiLinux Lumina" -lumina_description = "A lightweight and independent Lumina desktop. Qt-based, designed to run cleanly on BSD and Linux systems." -lumina_applications_title = "Application Support" -lumina_applications_description = "Lumina works well with core utilities and many popular Qt apps, providing a practical everyday experience." -lumina_theme_title = "Theme Customization" -lumina_theme_description = "Easily customize color schemes and interface styles with Lumina's flexible theme system." -lumina_panel_title = "Lumina Panel" -lumina_panel_description = "A clean panel with a taskbar, application launcher, and system tray keeps your desktop organized." - -# ── Slides for MATE ──────────────────────────────────────────── -mate_title = "PisiLinux MATE" -mate_description = "MATE for those who love the traditional desktop layout. Developed with GTK+ 3 for a modern and smooth experience." -mate_caja_title = "Caja File Manager" -mate_caja_description = "Manage your files easily with MATE's powerful Caja. Dual panel, tabbed navigation, and rich features." -mate_settings_title = "MATE Settings" -mate_settings_description = "Customize your MATE desktop with comprehensive settings for appearance, display, keyboard, and more." -mate_applications_title = "Rich Application Suite" -mate_applications_description = "MATE comes with a rich set of classic applications — from text editors to image viewers, all well-integrated." - -# ── Slides for XFCE ──────────────────────────────────────────── -xfce_title = "PisiLinux XFCE" -xfce_description = "A lightweight, fast, and user-friendly XFCE desktop environment. Low resource consumption, high performance." -xfce_panel_title = "XFCE Panel" -xfce_panel_description = "The traditional panel layout puts the taskbar, application menu, and system tray always within reach." -xfce_thunar_title = "Thunar File Manager" -xfce_thunar_description = "Manage your files easily with the fast and lightweight Thunar. Tree view, tabbed navigation, and customizable features." -xfce_settings_title = "Settings Manager" -xfce_settings_description = "A modern alternative to the classic application menu. Find apps easily and organize your favorites with Whisker Menu." - # ── Finish ──────────────────────────────────────────────── finish = "Finished" @@ -200,11 +101,34 @@ partition_table_type = "Partition table" disk_too_small = "Disk too small" summary_partition_plan = "Partitioning" summary_custom_partitions = "Manual Partitions" +summary_bootloader = "Bootloader" +summary_datetime = "Date & Time" +summary_ntp_enabled = "NTP enabled (auto)" +summary_root = "Root" +summary_root_enabled = "Password set" +summary_root_disabled = "Disabled (locked)" +summary_software = "Software" +summary_display_manager = "Display Manager" +summary_autologin = "Autologin" +summary_autologin_user = "Autologin User" +summary_desktop = "Desktop" + +# ── Display Manager ─────────────────────────────── +display_manager = "Display Manager" +display_manager_title = "Display Manager Configuration" +display_manager_description = "Select your display manager and desktop environment." +display_manager_select = "Display manager:" +display_manager_de = "Desktop environment:" +display_manager_autologin = "Automatic login" +display_manager_autologin_user = "Autologin user:" +display_manager_autologin_hint = "(first user)" +display_manager_de_not_found = "(not detected)" +display_manager_note = "Note: The display manager will be enabled by default on the installed system." mp_size_remaining = "Remaining Space" # ── UI / General ────────────────────────────────────────── installer = "YALI Installation Tool" -os_name = "PiSi GNU/LINUX" +app_name = "YALI" # ── Error screen ────────────────────────────────────────── error_show_log = "▼ Show Log Details" @@ -227,6 +151,7 @@ pwd_weak = "Weak" pwd_medium = "Medium" pwd_strong = "Strong" pwd_very_strong = "Very strong" +use_same_for_admin = "Use the same password for the administrator (root) account" # ── Checker ─────────────────────────────────────────────── check_disk_space = "Disk Space" @@ -256,8 +181,13 @@ job_fstab = "Generating fstab" job_hostname = "Configuring hostname" job_locale = "Configuring locale" job_timezone = "Setting timezone" +job_datetime = "Configuring date & time" job_keyboard = "Configuring keyboard layout" job_user = "Creating user account" +job_install_packages = "Installing selected packages" +job_ldconfig = "Configuring shared libraries (ldconfig)" +job_update_env = "Updating system environment" +job_cleanup_live = "Cleaning up live environment traces" # ── Manual Partition UI ──────────────────────────────────── manual_partition_title = "Manual Partitioning" @@ -324,8 +254,269 @@ mp_delete_confirm_yes = "✓ Yes, Delete" plan_root_suffix = " (root)" disk_min_required = " ↳ %{err} (min. %{n} GB required)" + error_partition_plan_not_found = "Partition plan not found." + +# ── License ─────────────────────────────────────────────── +license = "License" +license_title = "GNU General Public License" +license_description = "Please read and accept the license terms to continue." +license_accept = "I have read and accept the terms of the GNU General Public License." + +# ── Admin (root) ─────────────────────────────────────────── +admin = "Admin" +admin_title = "Root Password" +admin_description = "Set a password for the root (administrator) account. Leave empty to disable direct root login." +admin_password_label = "Root password:" +admin_password_confirm_label = "Confirm root password:" +admin_password_short = "Root password must be at least 4 characters." + +# ── Date/Time ────────────────────────────────────────────── +datetime = "Date & Time" +datetime_title = "Date & Time Settings" +datetime_description = "Configure the system date and time." +datetime_ntp_label = "Network Time (NTP):" +datetime_ntp_on = "Enabled — time syncs automatically" +datetime_ntp_off = "Disabled — set manually" +datetime_date_label = "Date:" +datetime_time_label = "Time:" +datetime_year = "Year:" +datetime_month = "Month:" +datetime_day = "Day:" +datetime_ntp_info = "NTP will synchronize the system clock over the network after installation." +datetime_manual_info = "Set the date and time manually. The system will use these values." + +# ── Network ──────────────────────────────────────────────── +network = "Network" +network_title = "Network Configuration" +network_description = "Configure network connections and proxy settings." +network_connected = "Connected:" +network_disconnected = "Disconnected" +network_scan = "🔍 Scan Wi-Fi Networks" +network_rescan = "🔄 Rescan" +network_scan_count = "Networks found:" +network_connect = "Connect" +network_password_hint = "Password" +network_proxy_label = "Configure proxy:" + +# ── Software ─────────────────────────────────────────────── +software = "Software" +software_title = "Software Selection" +software_description = "Choose which software to install." +software_desktops = "Desktop Environments" +software_desktop_xfce = "XFCE — Lightweight and fast" +software_desktop_kde = "KDE Plasma — Modern and feature-rich" +software_desktop_gnome = "GNOME — Clean and focused" +software_desktop_cinnamon = "Cinnamon — Traditional and powerful" +software_additional = "Additional Collections" +software_office = "Office — LibreOffice, document tools" +software_development = "Development — GCC, Git, IDE tools" +software_multimedia = "Multimedia — Codecs, players, editors" +software_minimal = "⚠ Minimal — only base system (overrides others)" +software_minimal_warning = "Selected 'Minimal' will override all other choices. Only essential packages will be installed." + +# ── Bootloader ───────────────────────────────────────────── +bootloader = "Bootloader" +bootloader_title = "Bootloader Configuration" +bootloader_description = "Configure the bootloader (GRUB) settings." +bootloader_device_label = "Install to device:" +bootloader_device_hint = "(default: target disk)" +bootloader_timeout_label = "Timeout:" +bootloader_timeout_suffix = "seconds" +bootloader_password_label = "GRUB password (optional):" +bootloader_kernel_label = "Kernel parameters:" +bootloader_kernel_hint = "(e.g. quiet splash)" +rescue = "Rescue" +rescue_button = "🆘 Rescue Mode" +rescue_title = "Rescue Mode" +rescue_description = "Repair an existing PisiLinux installation." +rescue_no_installations = "No Linux partitions found. Please check the system and try again." +rescue_scan = "🔍 Scan" +rescue_detected = "Detected Linux Partitions:" +rescue_chroot = "📂 Rescue Shell (chroot)" +rescue_chroot_start = "Starting chroot shell…" +rescue_grub = "🔧 Reinstall GRUB" +rescue_grub_start = "Reinstalling GRUB…" +rescue_return = "↩ Return to Normal Installer" +rescue_not_available = "Rescue mode: no existing Linux installation found." +rescue_pisi_history = "📜 Pisi History Rollback" +rescue_pisi_history_title = "Pisi Package History" +rescue_pisi_history_desc = "Select a history entry and click Rollback to revert to that state." +rescue_pisi_history_scan = "🔍 List History" +rescue_pisi_history_empty = "No package history found or could not be read." +rescue_pisi_takeback = "↩ Rollback to This Point" +rescue_pisi_takeback_start = "Running pisi takeback…" +rescue_pisi_history_ok = "Rollback completed successfully." +rescue_pisi_history_err = "Error: %{err}" + +# ── LUKS ────────────────────────────────────────────────── +luks_enable_root = "Encrypt root partition (LUKS)" +luks_password = "Encryption password:" +luks_password_weak = "Password is too short (minimum 4 characters)" + error_target_disk_not_selected = "No target disk selected." error_thread_terminated = "Install thread terminated unexpectedly." error_thread_unexpectedly_terminated = "Install thread terminated unexpectedly." +# ── Netinstall ───────────────────────────────────────── +netinstall = "Software Selection" +netinstall_title = "Extra Software" +netinstall_description = "Select additional package groups to install." +netinstall_package_count = "packages" +netinstall_selected = "selected" + +# ── Media Check ────────────────────────────────────────────── +media_check = "Media Check" +media_check_title = "Verify Installation Media" +media_check_description = "Check the integrity of your ISO or USB drive." +media_check_scan = "🔍 Scan Media" +media_check_select = "Select file to verify:" +media_check_running = "Checking… please wait." +media_check_ok = "✅ Media integrity verified successfully." +media_check_fail = "❌ Media integrity check FAILED! Please re-download." +media_check_no_md5 = "⚠️ md5sum or sha256sum tools not found." +media_check_file_not_found = "⚠️ Selected file not found." +media_check_path_label = "File path:" +media_check_path_hint = "e.g. /run/live/medium/pisilinux.iso" +media_check_algorithm = "Algorithm:" +job_media_check = "Verifying installation media" + +# ── CLI / Auto-install ───────────────────────────────────── +hostname = "Hostname" +timezone = "Timezone" +username = "Username" + +# ── Slideshow (fallback) ─────────────────────────────────── +welcome_slide_title = "Welcome to Pisi Linux" +welcome_slide_description = "A modern, fast, and secure GNU/Linux distribution." + +package_manager_title = "Pisi Package Manager" +package_manager_description = "A powerful package manager with dependency resolution, history rollback, and atomic updates." + +community_title = "Community Driven" +community_description = "Built by the community for the community. Pisi Linux is free and open source." + +speed_title = "Optimized for Speed" +speed_description = "Designed to run fast on both modern and older hardware." + +security_title = "Security First" +security_description = "Regular security updates and a robust permission system keep your system safe." + +customization_title = "Full Customization" +customization_description = "Choose your desktop, applications, and look & feel during installation." + +budgie_title = "Budgie Desktop" +budgie_description = "A modern, elegant desktop environment designed with simplicity in mind." + +budgie_applets_title = "Budgie Applets" +budgie_applets_description = "Customizable applets for notifications, system tray, and more." + +budgie_raven_title = "Budgie Raven" +budgie_raven_description = "Notification center and settings panel for quick access." + +budgie_gtk_theme_title = "GTK Theme Integration" +budgie_gtk_theme_description = "Seamless GTK theme support for consistent application appearance." + +cinnamon_title = "Cinnamon Desktop" +cinnamon_description = "A traditional desktop layout with modern features." + +cinnamon_panel_title = "Cinnamon Panel" +cinnamon_panel_description = "Flexible panel system with applets, launchers, and tray icons." + +cinnamon_settings_title = "Cinnamon Settings" +cinnamon_settings_description = "Comprehensive system settings accessible from the menu." + +cinnamon_desklets_title = "Cinnamon Desklets" +cinnamon_desklets_description = "Interactive desklets for weather, clock, system monitoring and more." + +gnome_title = "GNOME Desktop" +gnome_description = "A clean, distraction-free desktop environment." + +gnome_search_title = "GNOME Search" +gnome_search_description = "Instant search across applications, files, and settings." + +gnome_extensions_title = "GNOME Extensions" +gnome_extensions_description = "Extend your desktop with community-built extensions." + +kde_title = "KDE Plasma Desktop" +kde_description = "Feature-rich, highly customizable desktop environment." + +dolphin_title = "Dolphin File Manager" +dolphin_description = "Intuitive file manager with split-pane view and powerful search." + +kde_connect_title = "KDE Connect" +kde_connect_description = "Seamlessly integrate your phone with your desktop." + +discover_title = "Discover Software Center" +discover_description = "Browse and install applications with a single click." + +lxde_title = "LXDE Desktop" +lxde_description = "Lightweight desktop environment for older hardware." + +lxde_openbox_title = "OpenBox Window Manager" +lxde_openbox_description = "Fast, lightweight window manager with keyboard-driven workflow." + +lxde_pcmanfm_title = "PCManFM File Manager" +lxde_pcmanfm_description = "Lightweight file manager with tabbed browsing and desktop management." + +lxde_lxpanel_title = "LXPanel" +lxde_lxpanel_description = "Lightweight panel with application menu, task bar, and system tray." + +lxqt_title = "LXQt Desktop" +lxqt_description = "A lightweight Qt-based desktop environment." + +lxqt_panel_title = "LXQt Panel" +lxqt_panel_description = "Highly configurable panel with widgets and launchers." + +lxqt_pcmanfm_qt_title = "PCManFM-Qt File Manager" +lxqt_pcmanfm_qt_description = "Fast file manager with tabbed interface and desktop icons." + +lumina_title = "Lumina Desktop" +lumina_description = "A lightweight, plugin-based desktop environment from TrueOS." + +lumina_applications_title = "Lumina Applications" +lumina_applications_description = "Native applications for Lumina desktop environment." + +lumina_theme_title = "Lumina Theme Engine" +lumina_theme_description = "Full theme support for a consistent visual experience." + +lumina_panel_title = "Lumina Panel" +lumina_panel_description = "Flexible panel system with custom widgets." + +mate_title = "MATE Desktop" +mate_description = "Traditional desktop environment continued from GNOME 2." + +mate_caja_title = "Caja File Manager" +mate_caja_description = "Feature-rich file manager with spatial and browser views." + +mate_settings_title = "MATE Settings" +mate_settings_description = "Complete system settings organized by category." + +mate_applications_title = "MATE Applications" +mate_applications_description = "Integrated application suite for daily tasks." + +xfce_title = "XFCE Desktop" +xfce_description = "Lightweight yet powerful desktop environment." + +xfce_panel_title = "XFCE Panel" +xfce_panel_description = "Highly customizable panel with launchers, applets, and menus." + +xfce_thunar_title = "Thunar File Manager" +xfce_thunar_description = "Fast and reliable file manager with bulk rename and plugins." + +xfce_settings_title = "XFCE Settings" +xfce_settings_description = "Comprehensive settings manager for desktop customization." + +# ── LVM ──────────────────────────────────────────────────── +lvm_lv_title = "LVM Logical Volumes" +lvm_lv_name = "LV Name" +lvm_lv_vg = "Volume Group" +lvm_lv_add = "Add Logical Volume" +lvm_lv_error_vg = "At least one Volume Group must be defined." +lvm_lv_error_size = "Entered size exceeds Volume Group capacity." +lvm_lv_mountpoint = "Mount Point" +lvm_lv_fstype = "File System" +lvm_lv_size = "Size (MB, 0=remaining):" +lvm_lv_action = "Action" + + diff --git a/locales/tr.toml b/locales/tr.toml index e5ba4e6..d314ef5 100644 --- a/locales/tr.toml +++ b/locales/tr.toml @@ -8,8 +8,11 @@ confirm = "Onayla" # ── Welcome ─────────────────────────────────────────────── welcome = "Hoş Geldiniz" welcome_title = "YALI Kurulum Aracına Hoş Geldiniz" -welcome_description = "Bu araç size kurulum sürecinde rehberlik edecektir." +welcome_description = "Bu araç size kurulum sürecinde rehberlik edecektir. Sisteminize kurulacak olan İşletim Sistemi bilgileri:" select_language = "Dil Seçiniz:" +os = "İşletim Sistemi" +desktop = "Masaüstü" +version = "Sürüm" # ── Location ────────────────────────────────────────────── location = "Konum" @@ -72,110 +75,6 @@ execution_description = "Lütfen bekleyin. Bu işlem birkaç dakika sürebilir." start_install = "Kurulumu Başlat" install_starting = "Kurulum başlatılıyor…" -# ── Slides for PISILINUX ──────────────────────────────────────────── -welcome_slide_title = "Pisi GNU/Linux'a Hoş Geldiniz" -welcome_slide_description = "Pisi; hız, kararlılık ve kullanım kolaylığını bir araya getiren Türkçe destekli bir GNU/Linux dağıtımıdır." -package_manager_title = "PISI Paket Yöneticisi" -package_manager_description = "Binlerce pakete tek komutla erişin. `pisi install ` ile ihtiyacınız olan her şeyi kolayca kurabilirsiniz." -community_title = "Topluluk Desteği" -community_description = "forum.pisilinux.org adresindeki aktif topluluğumuz sorularınızı yanıtlamak için hazır. Wiki sayfalarımız da her an erişilebilir." -speed_title = "Hız ve Performans" -speed_description = "Optimize edilmiş çekirdek ve başlangıç servisleri sayesinde PisiLinux saniyeler içinde açılır ve minimum kaynak kullanır." -security_title = "Güvenlik Önce Gelir" -security_description = "Düzenli güvenlik güncellemeleri, AppArmor profilleri ve güvenli varsayılan ayarlarla sisteminizi koruyun." -customization_title = "Özelleştirme" -customization_description = "Masaüstü ortamından tema renklerine, panel düzeninden klavye kısayollarına kadar her şeyi zevkinize göre ayarlayın." - -# ── Slides for BUDGIE ──────────────────────────────────────────── -budgie_title = "PisiLinux Budgie" -budgie_description = "Modern, sade ve şık Budgie masaüstü. GNOME altyapısı üzerine inşa edilmiş, kullanıcı dostudur." -budgie_applets_title = "Budgie Desktop Applets" -budgie_applets_description = "Budgie'nin panel appletleri sayesinde hava durumu, sistem izleyici, takvim gibi araçları kolayca ekleyin." -budgie_raven_title = "Raven Bildirim Merkezi" -budgie_raven_description = "Raven panelinden ses seviyesi, medya kontrolleri, bildirimler ve hızlı ayarlara erişin." -budgie_gtk_theme_title = "GTK Tema Desteği" -budgie_gtk_theme_description = "Budgie, GTK temalarını ve GNOME uygulamalarını sorunsuz çalıştırır, görsel bütünlük sağlar." - -# ── Slides for CINNAMON ──────────────────────────────────────────── -cinnamon_title = "PisiLinux Cinnamon" -cinnamon_description = "Geleneksel masaüstü deneyimini modern özelliklerle birleştiren Cinnamon, güçlü ve esnek bir ortam sunar." -cinnamon_panel_title = "Cinnamon Paneli" -cinnamon_panel_description = "Panelinizi applet'ler, başlatıcılar ve sistem göstergeleriyle özelleştirin; klasik ama güçlü bir masaüstü deneyimi yaşayın." -cinnamon_settings_title = "Ayarlar Yöneticisi" -cinnamon_settings_description = "Cinnamon'un kapsamlı Ayarlar Yöneticisi aracılığıyla masaüstünüzün her yönünü özelleştirin." -cinnamon_desklets_title = "Cinnamon Desklets" -cinnamon_desklets_description = "Masaüstü widget'ları ile notlar, saatler ve sistem bilgileri her zaman görünür kalsın." - -# ── Slides for GNOME ──────────────────────────────────────────── -gnome_title = "PisiLinux GNOME" -gnome_description = "Sade ve odaklı GNOME masaüstü ortamı ile dikkat dağıtıcı unsurları azaltın, işinize konsantre olun." -gnome_search_title = "GNOME Arama" -gnome_search_description = "Super tuşuna basıp yazmaya başlayın. Uygulama, dosya ve ayarlara anında ulaşın." -gnome_extensions_title = "GNOME Eklentileri" -gnome_extensions_description = "extensions.gnome.org adresinden yüzlerce eklentiyle masaüstünüzü ihtiyaçlarınıza göre şekillendirin." - -# ── Slides for KDE/Plasma ──────────────────────────────────────────── -kde_title = "PisiLinux KDE Plasma" -kde_description = "Modern, özelleştirilebilir ve güçlü KDE Plasma masaüstü ortamıyla tanışın. Her şey elinizin altında." -dolphin_title = "Dolphin Dosya Yöneticisi" -dolphin_description = "Sekmeli gezinme, ikili panel ve güçlü arama özelliğiyle Dolphin dosyalarınızı yönetmeyi kolaylaştırır." -kde_connect_title = "KDE Bağlan" -kde_connect_description = "Telefonunuzu bilgisayarınıza bağlayın. Bildirimler, dosya aktarımı ve klipboard paylaşımı KDE Connect ile mümkün." -discover_title = "Discover Yazılım Merkezi" -discover_description = "Discover ile uygulamaları görsel arayüzden kolayca bulun, kurun ve güncelleyin." - -# ── Slides for LXQt ──────────────────────────────────────────── -lxqt_title = "PisiLinux LXQt" -lxqt_description = "Hafif, hızlı ve modern LXQt masaüstü ortamı. Kaynak tüketimi düşük, performansı yüksektir." -lxqt_panel_title = "LXQt Panel" -lxqt_panel_description = "Geleneksel panel düzeni ile görev çubuğu, uygulama başlatıcı ve sistem tepsisi her zaman elinizin altında." -lxqt_pcmanfm_qt_title = "PCManFM-Qt Dosya Yöneticisi" -lxqt_pcmanfm_qt_description = "Hafif ve hızlı PCManFM-Qt ile dosya işlemlerini kolayca yönetin. Sekmeli gezinme ve özelleştirilebilir arayüz sunar." -lxqt_speed_title = "LXQt Hızı" -lxqt_speed_description = "LXQt, hız için tasarlanmıştır. Daha hızlı açılır, daha çabuk yanıt verir ve eski donanımları en iyi şekilde kullanır." - -# ── Slides for LXDE ──────────────────────────────────────────── -lxde_title = "PisiLinux LXDE" -lxde_description = "Efsanevi hafif masaüstü LXDE. Çok düşük kaynak tüketimi ve güvenilir yapısıyla tanınır." -lxde_openbox_title = "Openbox Tabanlı" -lxde_openbox_description = "LXDE, Openbox pencere yöneticisini kullanır. İnce ayarlar ile akıcı bir deneyim sunar." -lxde_pcmanfm_title = "PCManFM Dosya Yöneticisi" -lxde_pcmanfm_description = "PCManFM, hızlı ve hafif yapısıyla dosya işlemlerinizi kolaylaştırır, ağaç görünümü ve sekme destekler." -lxde_lxpanel_title = "LXPanel" -lxde_lxpanel_description = "LXPanel, geleneksel masaüstü düzeni ile görev çubuğu, uygulama menüsü ve sistem tepsisi gibi temel özellikleri sunar." - -# ── Slides for LUMINA ──────────────────────────────────────────── -lumina_title = "PisiLinux Lumina" -lumina_description = "Hafif ve bağımsız Lumina masa üstü. Qt tabanlı, BSD ve Linux sistemlerde düzgün çalışan minimal bir ortam." -lumina_applications_title = "Uygulama Desteği" -lumina_applications_description = "Lumina; temel uygulamalar ve birçok popüler Qt uygulamasıyla uyumlu çalışır, kullanışlı bir deneyim sunar." -lumina_theme_title = "Tema Özelleştirme" -lumina_theme_description = "Lumina'nın tema sistemiyle renk şemaları ve arayüz stilini kolayca özelleştirin." -lumina_panel_title = "Lumina Paneli" -lumina_panel_description = "Görev çubuğu, uygulama başlatıcı ve sistem tepsisini içeren sade panel, masa üstünüzü düzenli tutar." - -# ── Slides for MATE ──────────────────────────────────────────── -mate_title = "PisiLinux MATE" -mate_description = "Geleneksel masaüstü düzenini sevenler için MATE. GTK+ 3 ile geliştirilmiş, modern ve akıcı bir deneyim sunar." -mate_caja_title = "Caja Dosya Yöneticisi" -mate_caja_description = "MATE'in güçlü dosya yöneticisi Caja ile dosya işlemlerinizi kolayca yapın. Çift panel, sekmeli gezinme ve geniş özellikler sunar." -mate_settings_title = "MATE Ayarları" -mate_settings_description = "Görünüm, ekran, klavye ve daha fazlası için kapsamlı ayarlarla MATE masaüstünüzü özelleştirin." -mate_applications_title = "Zengin Uygulama Paketi" -mate_applications_description = "MATE; metin editöründen görüntü görüntüleme yazılımına kadar geniş, iyi entegre edilmiş klasik uygulamalar sunar." - - -# ── Slides for XFCE ──────────────────────────────────────────── -xfce_title = "PisiLinux XFCE" -xfce_description = "Hafif, hızlı ve kullanıcı dostu XFCE masaüstü ortamı. Kaynak tüketimi düşük, performansı yüksektir." -xfce_panel_title = "XFCE Panel" -xfce_panel_description = "Geleneksel panel düzeni ile görev çubuğu, uygulama menüsü ve sistem tepsisi her zaman elinizin altında." -xfce_thunar_title = "Thunar Dosya Yöneticisi" -xfce_thunar_description = "Hızlı ve hafif Thunar ile dosya işlemlerinizi kolayca yapın. Ağaç görünümü, sekmeli gezinme ve özelleştirilebilir özellikler sunar." -xfce_settings_title = "Ayarlar Merkezi" -xfce_settings_description = "Klasik uygulama menüsüne modern bir alternatif. Whisker Menu ile uygulamaları kolayca bulun, favorilerinizi düzenleyin." - - # ── Finish ──────────────────────────────────────────────── finish = "Tamamlandı" finish_title = "Kurulum Tamamlandı" @@ -205,7 +104,7 @@ mp_size_remaining = "Kalan Alan" # ── UI / Genel ──────────────────────────────────────────── installer = "YALI Kurulum Aracı" -os_name = "PiSi GNU/LİNUX" +app_name = "YALI" # ── Hata ekranı ─────────────────────────────────────────── error_show_log = "▼ Log Detaylarını Göster" @@ -221,6 +120,8 @@ check_fail_warning = "Bir veya daha fazla kritik gereksinim karşılanmıyor. L check_warn_notice = "Bazı önerilen gereksinimler karşılanmıyor. Kurulum devam edebilir." check_all_pass = "Tüm sistem gereksinimleri karşılandı." recheck = "🔄 Yeniden Kontrol Et" +rescue_button = "🆘 Kurtarma Kipi" +rescue_button_tooltip = "PiSi Linux'u Kurtarma Modunda Yeniden Kur" # ── Users (ek) ──────────────────────────────────────────── pwd_very_weak = "Çok zayıf" @@ -237,7 +138,7 @@ check_disk_pass = "%{size} GB disk mevcut." check_internet = "İnternet" check_net_pass = "İnternet bağlantısı mevcut." check_net_warn = "İnternet bağlantısı bulunamadı. Çevrimdışı kurulum devam edebilir, ancak güncellemeler ve ek paketler indirilemeyecek." -check_boot_mode = "Önyükleme Modu" +check_boot_mode = "Önyükleme Kipi" check_uefi_pass = "UEFI modu tespit edildi. GPT kullanılacak." check_bios_pass = "BIOS/Legacy modu tespit edildi. MBR kullanılacak." check_live_sys = "Canlı Sistem" @@ -329,3 +230,300 @@ error_partition_plan_not_found = "Bölümleme planı bulunamadı." error_target_disk_not_selected = "Hedef disk seçilmedi." error_thread_terminated = "İş parçacığı beklenmedik şekilde sonlandı." error_thread_unexpectedly_terminated = "İş parçacığı beklenmedik şekilde sonlandı." + +# ── Summary (ek) ───────────────────────────────────────────── +summary_bootloader = "Önyükleyici" +summary_datetime = "Tarih & Saat" +summary_ntp_enabled = "NTP etkin (otomatik)" +summary_root = "Kök (root)" +summary_root_enabled = "Parola belirlendi" +summary_root_disabled = "Devre dışı (kilitli)" +summary_software = "Yazılım" +summary_display_manager = "Görüntü Yöneticisi" +summary_autologin = "Otomatik oturum açma" +summary_autologin_user = "Otomatik oturum açılacak kullanıcı" +summary_desktop = "Masaüstü" + +# ── Görüntü Yöneticisi ─────────────────────────────────────── +display_manager = "Görüntü Yöneticisi" +display_manager_title = "Görüntü Yöneticisi Yapılandırması" +display_manager_description = "Görüntü yöneticinizi ve masaüstü ortamınızı seçin." +display_manager_select = "Görüntü yöneticisi:" +display_manager_de = "Masaüstü ortamı:" +display_manager_autologin = "Otomatik oturum açma" +display_manager_autologin_user = "Otomatik oturum açılacak kullanıcı:" +display_manager_autologin_hint = "(ilk kullanıcı)" +display_manager_de_not_found = "(algılanamadı)" +display_manager_note = "Not: Görüntü yöneticisi, kurulan sistemde varsayılan olarak etkin olacaktır." + +# ── Kullanıcı (ek) ────────────────────────────────────────────── +use_same_for_admin = "Yönetici (root) hesabı için aynı parolayı kullan" + +# ── Jobs (ek) ────────────────────────────────────────────────── +job_install_packages = "Seçilen paketler kuruluyor" +job_ldconfig = "Paylaşımlı kütüphaneler yapılandırılıyor (ldconfig)" +job_update_env = "Sistem ortamı güncelleniyor" +job_cleanup_live = "Canlı ortam izleri temizleniyor" +job_datetime = "Tarih & saat yapılandırılıyor" +job_display_manager = "Görüntü yöneticisi yapılandırılıyor" +job_netinstall = "Ek paketler kuruluyor" +job_setup_lvm = "LVM yapılandırılıyor" + +# ── Lisans ─────────────────────────────────────────────────── +license = "Lisans" +license_title = "GNU Genel Kamu Lisansı" +license_description = "Devam etmek için lisans koşullarını okuyun ve kabul edin." +license_accept = "GNU Genel Kamu Lisansı koşullarını okudum ve kabul ediyorum." + +# ── Yönetici (root) ───────────────────────────────────────── +admin = "Yönetici" +admin_title = "Root Parolası" +admin_description = "Root (yönetici) hesabı için bir parola belirleyin. Boş bırakılırsa doğrudan root girişi devre dışı kalır." +admin_password_label = "Root parolası:" +admin_password_confirm_label = "Root parolası (tekrar):" +admin_password_short = "Root parolası en az 4 karakter olmalıdır." + +# ── Tarih & Saat ──────────────────────────────────────────── +datetime = "Tarih & Saat" +datetime_title = "Tarih & Saat Ayarları" +datetime_description = "Sistem tarihini ve saatini yapılandırın." +datetime_ntp_label = "Ağ Zamanı (NTP):" +datetime_ntp_on = "Etkin — saat otomatik eşitlenir" +datetime_ntp_off = "Devre dışı — elle ayarlanır" +datetime_date_label = "Tarih:" +datetime_time_label = "Saat:" +datetime_year = "Yıl:" +datetime_month = "Ay:" +datetime_day = "Gün:" +datetime_ntp_info = "NTP, kurulumdan sonra sistem saatini ağ üzerinden eşitleyecektir." +datetime_manual_info = "Tarih ve saati elle ayarlayın. Sistem bu değerleri kullanacaktır." + +# ── Ağ ────────────────────────────────────────────────────── +network = "Ağ" +network_title = "Ağ Yapılandırması" +network_description = "Ağ bağlantılarını ve vekil sunucu (proxy) ayarlarını yapılandırın." +network_connected = "Bağlı:" +network_disconnected = "Bağlı değil" +network_scan = "🔍 Wi-Fi Ağlarını Tara" +network_rescan = "🔄 Yeniden Tara" +network_scan_count = "Bulunan ağlar:" +network_connect = "Bağlan" +network_password_hint = "Parola" +network_proxy_label = "Vekil sunucu (proxy) yapılandır:" + +# ── Yazılım ───────────────────────────────────────────────── +software = "Yazılım" +software_title = "Yazılım Seçimi" +software_description = "Hangi yazılımların kurulacağını seçin." +software_desktops = "Masaüstü Ortamları" +software_desktop_xfce = "XFCE — Hafif ve hızlı" +software_desktop_kde = "KDE Plasma — Modern ve özellikli" +software_desktop_gnome = "GNOME — Sade ve odaklı" +software_desktop_cinnamon = "Cinnamon — Geleneksel ve güçlü" +software_additional = "Ek Koleksiyonlar" +software_office = "Ofis — LibreOffice, belge araçları" +software_development = "Geliştirme — GCC, Git, IDE araçları" +software_multimedia = "Multimedya — Kodekler, oynatıcılar, düzenleyiciler" +software_minimal = "⚠ Minimal — yalnızca temel sistem (diğerlerini geçersiz kılar)" +software_minimal_warning = "'Minimal' seçimi diğer tüm seçimleri geçersiz kılar. Yalnızca temel paketler kurulur." + +# ── Önyükleyici ───────────────────────────────────────────── +bootloader = "Önyükleyici" +bootloader_title = "Önyükleyici Yapılandırması" +bootloader_description = "Önyükleyici (GRUB) ayarlarını yapılandırın." +bootloader_device_label = "Aygıta kur:" +bootloader_device_hint = "(varsayılan: hedef disk)" +bootloader_timeout_label = "Bekleme süresi:" +bootloader_timeout_suffix = "saniye" +bootloader_password_label = "GRUB parolası (isteğe bağlı):" +bootloader_kernel_label = "Çekirdek parametreleri:" +bootloader_kernel_hint = "(örn. quiet splash)" + +# ── Kurtarma ──────────────────────────────────────────────── +rescue = "Kurtarma" +rescue_title = "Kurtarma Modu" +rescue_description = "Mevcut bir PisiLinux kurulumunu onarın." +rescue_no_installations = "Linux bölümü bulunamadı. Lütfen sistemi kontrol edip tekrar deneyin." +rescue_scan = "🔍 Tara" +rescue_detected = "Algılanan Linux Bölümleri:" +rescue_chroot = "📂 Kurtarma Kabuğu (chroot)" +rescue_chroot_start = "Chroot kabuğu başlatılıyor…" +rescue_grub = "🔧 GRUB'u Yeniden Kur" +rescue_grub_start = "GRUB yeniden kuruluyor…" +rescue_return = "↩ Normal Kuruluma Dön" +rescue_not_available = "Kurtarma modu: mevcut Linux kurulumu bulunamadı." +rescue_pisi_history = "📜 Pisi Geçmişi Geri Alma" +rescue_pisi_history_title = "Pisi Paket Geçmişi" +rescue_pisi_history_desc = "Bir geçmiş kaydı seçin ve bu noktaya dönmek için Geri Al'a tıklayın." +rescue_pisi_history_scan = "🔍 Geçmişi Listele" +rescue_pisi_history_empty = "Paket geçmişi bulunamadı veya okunamadı." +rescue_pisi_takeback = "↩ Bu Noktaya Geri Al" +rescue_pisi_takeback_start = "Pisi takeback çalıştırılıyor…" +rescue_pisi_history_ok = "Geri alma başarıyla tamamlandı." +rescue_pisi_history_err = "Hata: %{err}" + +# ── LUKS ──────────────────────────────────────────────────── +luks_enable_root = "Kök bölümünü şifrele (LUKS)" +luks_password = "Şifreleme parolası:" +luks_password_weak = "Parola çok kısa (en az 4 karakter)" + +# ── Netinstall ────────────────────────────────────────────── +netinstall = "Yazılım Seçimi" +netinstall_title = "Ek Yazılım" +netinstall_description = "Kurulacak ek paket gruplarını seçin." +netinstall_package_count = "paket" +netinstall_selected = "seçildi" + +# ── Medya Kontrolü ────────────────────────────────────────── +media_check = "Medya Kontrolü" +media_check_title = "Kurulum Medyasını Doğrula" +media_check_description = "ISO veya USB sürücünüzün bütünlüğünü kontrol edin." +media_check_scan = "🔍 Medyayı Tara" +media_check_select = "Doğrulanacak dosyayı seçin:" +media_check_running = "Kontrol ediliyor… lütfen bekleyin." +media_check_ok = "✅ Medya bütünlüğü başarıyla doğrulandı." +media_check_fail = "❌ Medya bütünlük kontrolü BAŞARISIZ! Lütfen yeniden indirin." +media_check_no_md5 = "⚠️ md5sum veya sha256sum aracı bulunamadı." +media_check_file_not_found = "⚠️ Seçilen dosya bulunamadı." +media_check_path_label = "Dosya yolu:" +media_check_path_hint = "örn. /run/live/medium/pisilinux.iso" +media_check_algorithm = "Algoritma:" +job_media_check = "Kurulum medyası doğrulanıyor" + +# ── LVM ───────────────────────────────────────────────────── +lvm_lv_title = "LVM Mantıksal Birimler" +lvm_lv_name = "LV Adı" +lvm_lv_vg = "Birim Grubu" +lvm_lv_add = "Mantıksal Birim Ekle" +lvm_lv_error_vg = "En az bir Birim Grubu tanımlanmalıdır." +lvm_lv_error_size = "Girilen boyut, Birim Grubu kapasitesini aşıyor." +lvm_lv_mountpoint = "Bağlama Noktası" +lvm_lv_fstype = "Dosya Sistemi" +lvm_lv_size = "Boyut (MB, 0=kalan):" +lvm_lv_action = "İşlem" + +# ── CLI / Otomatik Kurulum ───────────────────────────────── +hostname = "Bilgisayar adı" +timezone = "Zaman dilimi" +username = "Kullanıcı adı" + +# ── Slayt Gösterisi (yedek) ──────────────────────────────── +welcome_slide_title = "Pisi Linux'a Hoş Geldiniz" +welcome_slide_description = "Modern, hızlı ve güvenli bir GNU/Linux dağıtımı." + +package_manager_title = "Pisi Paket Yöneticisi" +package_manager_description = "Bağımlılık çözümü, geçmiş geri alma ve atomik güncellemeler ile güçlü bir paket yöneticisi." + +community_title = "Topluluk Odaklı" +community_description = "Topluluk tarafından, topluluk için geliştirilmiştir. Pisi Linux özgür ve açık kaynaktır." + +speed_title = "Hız İçin Optimize Edilmiş" +speed_description = "Hem modern hem de eski donanımlarda hızlı çalışacak şekilde tasarlanmıştır." + +security_title = "Önce Güvenlik" +security_description = "Düzenli güvenlik güncellemeleri ve sağlam bir izin sistemi sisteminizi güvende tutar." + +customization_title = "Tam Özelleştirme" +customization_description = "Kurulum sırasında masaüstünüzü, uygulamalarınızı ve görünümünüzü seçin." + +budgie_title = "Budgie Masaüstü" +budgie_description = "Sadeliği hedefleyen modern ve şık bir masaüstü ortamı." + +budgie_applets_title = "Budgie Applet'leri" +budgie_applets_description = "Bildirimler, sistem tepsisi ve daha fazlası için özelleştirilebilir applet'ler." + +budgie_raven_title = "Budgie Raven" +budgie_raven_description = "Hızlı erişim için bildirim merkezi ve ayar paneli." + +budgie_gtk_theme_title = "GTK Tema Entegrasyonu" +budgie_gtk_theme_description = "Tutarlı uygulama görünümü için kusursuz GTK tema desteği." + +cinnamon_title = "Cinnamon Masaüstü" +cinnamon_description = "Modern özelliklere sahip geleneksel bir masaüstü düzeni." + +cinnamon_panel_title = "Cinnamon Paneli" +cinnamon_panel_description = "Applet'ler, başlatıcılar ve tepsi simgeleri ile esnek panel sistemi." + +cinnamon_settings_title = "Cinnamon Ayarları" +cinnamon_settings_description = "Menüden erişilebilen kapsamlı sistem ayarları." + +cinnamon_desklets_title = "Cinnamon Desklet'leri" +cinnamon_desklets_description = "Hava durumu, saat, sistem izleme ve daha fazlası için etkileşimli desklet'ler." + +gnome_title = "GNOME Masaüstü" +gnome_description = "Sade, dikkat dağıtmayan bir masaüstü ortamı." + +gnome_search_title = "GNOME Arama" +gnome_search_description = "Uygulamalar, dosyalar ve ayarlar arasında anında arama." + +gnome_extensions_title = "GNOME Eklentileri" +gnome_extensions_description = "Topluluk tarafından geliştirilen eklentilerle masaüstünüzü genişletin." + +kde_title = "KDE Plasma Masaüstü" +kde_description = "Özellik dolu, son derece özelleştirilebilir masaüstü ortamı." + +dolphin_title = "Dolphin Dosya Yöneticisi" +dolphin_description = "Bölünmüş ekran görünümü ve güçlü arama ile sezgisel dosya yöneticisi." + +kde_connect_title = "KDE Connect" +kde_connect_description = "Telefonunuzu masaüstünüzle kusursuzca bütünleştirin." + +discover_title = "Discover Yazılım Merkezi" +discover_description = "Tek tıkla uygulamalara göz atın ve kurun." + +lxde_title = "LXDE Masaüstü" +lxde_description = "Eski donanımlar için hafif masaüstü ortamı." + +lxde_openbox_title = "OpenBox Pencere Yöneticisi" +lxde_openbox_description = "Klavye odaklı iş akışı ile hızlı ve hafif pencere yöneticisi." + +lxde_pcmanfm_title = "PCManFM Dosya Yöneticisi" +lxde_pcmanfm_description = "Sekmeli gezinti ve masaüstü yönetimi ile hafif dosya yöneticisi." + +lxde_lxpanel_title = "LXPanel" +lxde_lxpanel_description = "Uygulama menüsü, görev çubuğu ve sistem tepsisi ile hafif panel." + +lxqt_title = "LXQt Masaüstü" +lxqt_description = "Qt tabanlı hafif bir masaüstü ortamı." + +lxqt_panel_title = "LXQt Paneli" +lxqt_panel_description = "Araçlar ve başlatıcılar ile son derece yapılandırılabilir panel." + +lxqt_pcmanfm_qt_title = "PCManFM-Qt Dosya Yöneticisi" +lxqt_pcmanfm_qt_description = "Sekmeli arayüz ve masaüstü simgeleri ile hızlı dosya yöneticisi." + +lumina_title = "Lumina Masaüstü" +lumina_description = "TrueOS'tan hafif, eklenti tabanlı bir masaüstü ortamı." + +lumina_applications_title = "Lumina Uygulamaları" +lumina_applications_description = "Lumina masaüstü ortamı için yerel uygulamalar." + +lumina_theme_title = "Lumina Tema Motoru" +lumina_theme_description = "Tutarlı bir görsel deneyim için tam tema desteği." + +lumina_panel_title = "Lumina Paneli" +lumina_panel_description = "Özel araçlar ile esnek panel sistemi." + +mate_title = "MATE Masaüstü" +mate_description = "GNOME 2'den devam eden geleneksel masaüstü ortamı." + +mate_caja_title = "Caja Dosya Yöneticisi" +mate_caja_description = "Uzamsal ve tarayıcı görünümleri ile özellik dolu dosya yöneticisi." + +mate_settings_title = "MATE Ayarları" +mate_settings_description = "Kategoriye göre düzenlenmiş komple sistem ayarları." + +mate_applications_title = "MATE Uygulamaları" +mate_applications_description = "Günlük işler için bütünleşik uygulama paketi." + +xfce_title = "XFCE Masaüstü" +xfce_description = "Hafif ancak güçlü bir masaüstü ortamı." + +xfce_panel_title = "XFCE Paneli" +xfce_panel_description = "Başlatıcılar, applet'ler ve menüler ile son derece özelleştirilebilir panel." + +xfce_thunar_title = "Thunar Dosya Yöneticisi" +xfce_thunar_description = "Toplu yeniden adlandırma ve eklentiler ile hızlı ve güvenilir dosya yöneticisi." + +xfce_settings_title = "XFCE Ayarları" +xfce_settings_description = "Masaüstü özelleştirmesi için kapsamlı ayar yöneticisi." diff --git a/src/autoinstall/checker.rs b/src/autoinstall/checker.rs index f2a65a0..13a6b56 100644 --- a/src/autoinstall/checker.rs +++ b/src/autoinstall/checker.rs @@ -81,12 +81,8 @@ fn check_ram() -> CheckResult { /// En az 10 GB boş disk alanı kontrolü. fn check_disk_space() -> CheckResult { - use sysinfo::Disks; - let disks = Disks::new_with_refreshed_list(); - let max_gb = disks.iter() - .map(|d| d.total_space() / 1024 / 1024 / 1024) - .max() - .unwrap_or(0); + // sysinfo yerine doğrudan kernel'in gördüğü blok aygıtı boyutunu alıyoruz. + let max_gb = get_max_block_device_gb(); if max_gb < 10 { CheckResult::fail( @@ -99,10 +95,82 @@ fn check_disk_space() -> CheckResult { t!("check_disk_warn", size = max_gb), ) } else { - CheckResult::pass(t!("check_disk_space"), t!("check_disk_pass", size = max_gb)) + CheckResult::pass( + t!("check_disk_space"), + t!("check_disk_pass", size = max_gb) + ) } } +use std::fs; + +/// Sistemdeki en büyük fiziksel/sanal blok aygıtının boyutunu GB cinsinden döndürür. +/// +/// Önce `lsblk -d` ile dener (yalnızca disk düzeyindeki aygıtlar, bölümler hariç). +/// lsblk yoksa veya başarısız olursa `/sys/block` ile fallback yapar; +/// `/sys/block` yalnızca tam diskleri içerdiğinden bölüm girişleri karışmaz. +fn get_max_block_device_gb() -> u64 { + let mut max_gb = 0u64; + + // ── 1. Birincil yöntem: lsblk -d (partition.rs ile tutarlı) ────────────── + let lsblk_out = std::process::Command::new("lsblk") + .args(["-d", "-o", "NAME,SIZE", "-b", "--noheadings"]) + .output(); + + if let Ok(o) = lsblk_out { + if o.status.success() { + let text = String::from_utf8_lossy(&o.stdout); + for line in text.lines() { + let mut tokens = line.split_whitespace(); + + let name = match tokens.next() { Some(n) => n, None => continue }; + + // loop, ram, zram, sr (CD-ROM) aygıtlarını atla + if name.starts_with("loop") + || name.starts_with("ram") + || name.starts_with("sr") + || name.starts_with("zram") + { continue; } + + let size_str = match tokens.next() { Some(s) => s, None => continue }; + let size_bytes: u64 = size_str.parse().unwrap_or(0); + if size_bytes == 0 { continue; } + + let size_gb = size_bytes / 1_073_741_824; + if size_gb > max_gb { max_gb = size_gb; } + } + // lsblk başarılıysa ve en az bir disk bulduysa döndür + if max_gb > 0 { return max_gb; } + } + } + + // ── 2. Fallback: /sys/block (yalnızca tam diskler — bölüm yok) ─────────── + // /sys/class/block aksine /sys/block dizini YALNIZCA tam disk girişlerini + // içerir; sda1, nvme0n1p1 gibi bölüm sembolik bağlantıları burada bulunmaz. + if let Ok(entries) = fs::read_dir("/sys/block") { + for entry in entries.flatten() { + let name = entry.file_name().into_string().unwrap_or_default(); + + if name.starts_with("loop") + || name.starts_with("ram") + || name.starts_with("zram") + || name.starts_with("sr") + { continue; } + + let size_path = entry.path().join("size"); + if let Ok(size_str) = fs::read_to_string(&size_path) { + // sysfs 'size' dosyası: 512 byte'lık sektör sayısı + if let Ok(sectors) = size_str.trim().parse::() { + let size_gb = (sectors * 512) / 1_073_741_824; + if size_gb > max_gb { max_gb = size_gb; } + } + } + } + } + + max_gb +} + /// İnternet bağlantısı kontrolü (isteğe bağlı). fn check_internet() -> CheckResult { // DNS çözümlemesi ile basit kontrol @@ -150,19 +218,19 @@ fn check_cpu_arch() -> CheckResult { } } -/// Canlı sistem ortamı kontrolü. +/// Canlı sistem ortamı kontrolü — PyQt yali'deki gibi: +/// /bootmnt varsa kaynak orası (/bootmnt/pisi/pisi.sqfs), +/// yoksa /mnt/cdrom/pisi/pisi.sqfs kontrol edilir. fn check_live_env() -> CheckResult { - let source = "/run/livecd/squashfs-root"; - if !Path::new(source).exists() { - CheckResult::fail( - t!("check_live_sys"), - t!("check_live_fail"), - ) + let sqfs = if Path::new("/bootmnt").exists() { + "/bootmnt/pisi/pisi.sqfs" } else { - CheckResult::pass( - t!("check_live_sys"), - t!("check_live_pass"), - ) + "/mnt/cdrom/pisi/pisi.sqfs" + }; + if Path::new(sqfs).exists() { + CheckResult::pass(t!("check_live_sys"), t!("check_live_pass")) + } else { + CheckResult::fail(t!("check_live_sys"), t!("check_live_fail")) } } diff --git a/src/autoinstall/mod.rs b/src/autoinstall/mod.rs index 2677286..d54e8ab 100644 --- a/src/autoinstall/mod.rs +++ b/src/autoinstall/mod.rs @@ -1,216 +1,132 @@ -//! Otomatik kurulum (answer file) desteği. +//! Answer file yapıları, yükleme ve state'e uygulama. //! -//! Kullanım: -//! yali-rs --auto-install /path/to/answer.toml -//! -//! Answer file formatı (TOML): -//! -//! [install] -//! disk = "/dev/sda" -//! timezone = "Europe/Istanbul" -//! locale = "tr_TR.UTF-8" -//! keyboard = "tr" -//! -//! [user] -//! username = "pisi" -//! password = "changeme" -//! hostname = "pisilinux" - -use serde::Deserialize; -use std::path::Path; +//! Job kuyruğu ve Job struct'ları `src/jobs/` modülündedir. pub mod checker; // ───────────────────────────────────────────── -// ANSWER FILE YAPISI +// ANSWER FILE: VERİ YAPILARI // ───────────────────────────────────────────── -#[derive(Debug, Deserialize)] +/// `[install]` bölümündeki alanlar. +#[derive(Debug, serde::Deserialize)] +pub struct InstallConfig { + pub disk: String, + #[serde(default = "default_timezone")] + pub timezone: String, + #[serde(default = "default_locale")] + pub locale: String, + #[serde(default = "default_keyboard")] + pub keyboard: String, + #[serde(default)] + pub keyboard_variant: String, + #[serde(default = "default_source")] + pub source: String, + #[serde(default = "default_mount")] + pub mount: String, +} + +/// `[user]` bölümündeki alanlar. +#[derive(Debug, serde::Deserialize)] +pub struct UserConfig { + pub username: String, + pub password: String, + #[serde(default = "default_hostname")] + pub hostname: String, +} + +/// Answer file'ın tamamı (`--auto-install `). +#[derive(Debug, serde::Deserialize)] pub struct AnswerFile { pub install: InstallConfig, pub user: UserConfig, } -#[derive(Debug, Deserialize)] -pub struct InstallConfig { - /// Hedef disk aygıtı. Örn: "/dev/sda", "/dev/nvme0n1" - pub disk: String, - - /// Zaman dilimi. Örn: "Europe/Istanbul" - #[serde(default = "default_timezone")] - pub timezone: String, - - /// Sistem locale. Örn: "tr_TR.UTF-8" - #[serde(default = "default_locale")] - pub locale: String, - - /// Klavye düzeni. Örn: "tr" - #[serde(default = "default_keyboard")] - pub keyboard: String, - - /// Klavye varyantı. Örn: "f" (Türkçe F için) - #[serde(default)] - pub keyboard_variant: String, - - /// Kaynak dizin (canlı sistem). Varsayılan: /run/livecd/squashfs-root/ - #[serde(default = "default_source")] - pub source: String, - - /// Hedef bağlama noktası. Varsayılan: /mnt - #[serde(default = "default_mount")] - pub mount: String, -} - -#[derive(Debug, Deserialize)] -pub struct UserConfig { - pub username: String, - pub password: String, - pub hostname: String, -} - -fn default_timezone() -> String { "Europe/Istanbul".into() } -fn default_locale() -> String { "tr_TR.UTF-8".into() } -fn default_keyboard() -> String { "tr".into() } -fn default_source() -> String { "/run/livecd/squashfs-root/".into() } -fn default_mount() -> String { "/mnt".into() } +fn default_timezone() -> String { "Europe/Istanbul".to_string() } +fn default_locale() -> String { "tr_TR.UTF-8".to_string() } +fn default_keyboard() -> String { "tr".to_string() } +fn default_source() -> String { "/run/livecd/squashfs-root/".to_string() } +fn default_mount() -> String { "/mnt".to_string() } +fn default_hostname() -> String { "pisilinux".to_string() } // ───────────────────────────────────────────── -// DOĞRULAMA +// ANSWER FILE: YÜKLE ve UYGULA // ───────────────────────────────────────────── -#[derive(Debug)] -pub enum AnswerError { - Io(std::io::Error), - Parse(toml::de::Error), - Validation(Vec), -} - -impl std::fmt::Display for AnswerError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - AnswerError::Io(e) => write!(f, "Dosya okuma hatası: {}", e), - AnswerError::Parse(e) => write!(f, "TOML ayrıştırma hatası: {}", e), - AnswerError::Validation(v) => write!(f, "Doğrulama hataları:\n{}", v.join("\n")), - } - } -} - -/// Answer dosyasını okuyup ayrıştırır ve doğrular. -pub fn load(path: &Path) -> Result { +/// TOML answer dosyasını diskten okur ve ayrıştırır. +pub fn load(path: &std::path::Path) -> Result { let content = std::fs::read_to_string(path) - .map_err(AnswerError::Io)?; - - let af: AnswerFile = toml::from_str(&content) - .map_err(AnswerError::Parse)?; - - validate(&af)?; - Ok(af) + .map_err(|e| format!("Answer file okunamadı '{}': {}", path.display(), e))?; + toml::from_str(&content) + .map_err(|e| format!("Answer file ayrıştırılamadı: {}", e)) } -fn validate(af: &AnswerFile) -> Result<(), AnswerError> { - let mut errors: Vec = Vec::new(); +/// `AnswerFile` içeriğini `GlobalState`'e uygular. +/// Disk boyutunu `lsblk` ile sorgular ve `partition_plan` oluşturur. +pub fn apply_to_state(af: &AnswerFile, state: &mut crate::installer::GlobalState) { + use crate::installer::{DiskInfo, PartitionPlan, PartitionTableType}; - // Disk kontrolü - if !af.install.disk.starts_with("/dev/") { - errors.push(format!( - "Geçersiz disk yolu '{}'. '/dev/' ile başlamalı.", - af.install.disk - )); - } - if !Path::new(&af.install.disk).exists() { - errors.push(format!( - "Disk '{}' bulunamadı. Mevcut diskler için 'lsblk' komutunu çalıştırın.", - af.install.disk - )); - } - - // Kullanıcı adı doğrulaması (UsersStep ile aynı kural) - let un = &af.user.username; - if un.is_empty() || un.len() > 32 { - errors.push("Kullanıcı adı 1–32 karakter arasında olmalı.".into()); - } else { - let valid = un.chars().next().map(|c| c.is_ascii_lowercase() || c == '_').unwrap_or(false) - && un.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_' || c == '-'); - if !valid { - errors.push(format!( - "Geçersiz kullanıcı adı '{}'. Yalnızca küçük harf, rakam, - ve _ kullanılabilir.", - un - )); - } - } - - // Şifre uzunluğu - if af.user.password.len() < 6 { - errors.push("Şifre en az 6 karakter olmalı.".into()); - } - - // Hostname - let hn = &af.user.hostname; - if hn.is_empty() || hn.len() > 63 || hn.starts_with('-') || hn.ends_with('-') - || !hn.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') - { - errors.push(format!("Geçersiz hostname '{}'.", hn)); - } - - if errors.is_empty() { Ok(()) } else { Err(AnswerError::Validation(errors)) } -} - -// ───────────────────────────────────────────── -// GLOBALSTATE'E UYGULA -// ───────────────────────────────────────────── - -use crate::installer::GlobalState; -use crate::steps::partition::PartitionStep; -use crate::installer::InstallerStep; - -/// Answer file verilerini `GlobalState`'e yazar. -/// Bu işlemden sonra `PartitionStep::on_enter()` çağrılarak -/// disk listesi yenilenebilir ve plan oluşturulabilir. -pub fn apply_to_state(af: &AnswerFile, state: &mut GlobalState) { + // Temel alanlar state.timezone = af.install.timezone.clone(); state.keyboard_layout = af.install.keyboard.clone(); state.keyboard_variant = af.install.keyboard_variant.clone(); - state.selected_disk = Some(af.install.disk.clone()); - state.erase_disk = true; state.username = af.user.username.clone(); state.password = af.user.password.clone(); state.password_confirm = af.user.password.clone(); state.hostname = af.user.hostname.clone(); + state.selected_disk = Some(af.install.disk.clone()); + state.erase_disk = true; + state.language = if af.install.locale.starts_with("tr") { "tr" } else { "en" }.to_string(); - // Locale'den dil kodunu çıkar: "tr_TR.UTF-8" → "tr" - state.language = af.install.locale - .split('_') - .next() - .unwrap_or("tr") - .to_string(); + // Disk boyutunu lsblk ile sorgula + let disk_info: Option = (|| { + let out = std::process::Command::new("lsblk") + .args(["-d", "-b", "-o", "NAME,SIZE,MODEL", "--noheadings", &af.install.disk]) + .output() + .ok()?; + if !out.status.success() { return None; } + let text = String::from_utf8_lossy(&out.stdout); + let line = text.lines().next()?; + let mut parts = line.split_whitespace(); + let name = parts.next()?.to_string(); + let bytes: u64 = parts.next()?.parse().ok()?; + let model = parts.collect::>().join(" "); + Some(DiskInfo { + name: format!("/dev/{}", name), + model: if model.is_empty() { "Unknown".to_string() } else { model }, + size_gb: bytes / 1_073_741_824, + size_bytes: bytes, + }) + })(); - // Bölümleme planını hesapla - let is_uefi = Path::new("/sys/firmware/efi").exists(); - let mut ps = PartitionStep::default(); - ps.on_enter(state); + // Partition planını hesapla + if let Some(info) = disk_info { + let is_uefi = std::path::Path::new("/sys/firmware/efi").exists(); + let efi_mb = if is_uefi { 512u64 } else { 0 }; + let total_mb = info.size_bytes / 1024 / 1024; - // Seçili diski bul ve plan oluştur - if let Some(disk) = state.available_disks.iter().find(|d| d.name == af.install.disk).cloned() { - use crate::installer::{PartitionPlan, PartitionTableType}; - let total_mb = disk.size_bytes / 1024 / 1024; - let efi_mb = if is_uefi { 512 } else { 0 }; - let swap_mb = recommended_swap_mb(); - let root_mb = total_mb.saturating_sub(efi_mb + swap_mb + 512); - state.partition_plan = Some(PartitionPlan { - disk: disk.name.clone(), - table_type: if is_uefi { PartitionTableType::Gpt } else { PartitionTableType::Mbr }, - efi_mb, - swap_mb, - root_mb, - }); + // Tavsiye edilen swap: RAM * 1.5, min 512 MB, max 8192 MB + let mut sys = sysinfo::System::new(); + sys.refresh_memory(); + let ram_mb = sys.total_memory() / 1024 / 1024; + let swap_mb = (ram_mb * 3 / 2).clamp(512, 8192); + + let overhead = efi_mb + swap_mb + 512; + let root_mb = total_mb.saturating_sub(overhead); + + if root_mb >= 6144 && info.size_gb >= 10 { + state.partition_plan = Some(PartitionPlan { + disk: info.name.clone(), + table_type: if is_uefi { PartitionTableType::Gpt } else { PartitionTableType::Mbr }, + efi_mb, + swap_mb, + root_mb, + encrypt_root: false, + luks_password: String::new(), + }); + } + + state.available_disks.push(info); } } -fn recommended_swap_mb() -> u64 { - use sysinfo::System; - let mut sys = System::new(); - sys.refresh_memory(); - let ram_mb = sys.total_memory() / 1024 / 1024; - let swap = if ram_mb <= 2048 { ram_mb } else if ram_mb <= 8192 { ram_mb / 2 } else { 4096 }; - swap.clamp(512, 8192) -} diff --git a/src/branding.rs b/src/branding.rs new file mode 100644 index 0000000..d8beb3f --- /dev/null +++ b/src/branding.rs @@ -0,0 +1,255 @@ +//! Yali-RS görsel özelleştirme (branding) yapılandırması. +//! +//! `branding.toml` dosyasını okur ve tüm sabit kodlanmış görsel yollarını +//! çalışma zamanında çözümler. Geliştiriciler yali-rs koduna dokunmadan +//! yalnızca `branding.toml` ile logo, slayt görseli ve renk değiştirebilir. +#![allow(dead_code)] + +use serde::Deserialize; +use std::collections::HashMap; + +#[derive(Clone, Debug, Default, Deserialize)] +pub struct BrandingConfig { + #[serde(default)] + pub general: GeneralConfig, + #[serde(default)] + pub slides: SlidesConfig, + #[serde(default)] + pub theme: ThemeConfig, + #[serde(default)] + pub oem: OemConfig, +} + +// ── [general] ──────────────────────────────────────────────────────────────── + +#[derive(Clone, Debug, Deserialize)] +pub struct GeneralConfig { + /// Kurulum penceresinin başlığı. + #[serde(default = "default_title")] + pub title: String, + + /// Sol kenar çubuğunda gösterilen dağıtım ismi. + #[serde(default = "default_subtitle")] + pub subtitle: String, + + /// Sol kenar çubuğu için Yali logosu dosya yolu. + #[serde(default = "default_yali_logo")] + pub yali_logo: String, + + /// Kurulum ana sayfasında gösterilen PisiLinux logosu (koyu tema). + #[serde(default = "default_pisi_logo_dark")] + pub pisi_logo_dark: String, + + /// Kurulum ana sayfasında gösterilen PisiLinux logosu (açık tema). + #[serde(default = "default_pisi_logo_light")] + pub pisi_logo_light: String, +} + +impl Default for GeneralConfig { + fn default() -> Self { + Self { + title: default_title(), + subtitle: default_subtitle(), + yali_logo: default_yali_logo(), + pisi_logo_dark: default_pisi_logo_dark(), + pisi_logo_light: default_pisi_logo_light(), + } + } +} + +// ── [slides] ───────────────────────────────────────────────────────────────── + +/// Tek bir slaytın içeriği. +#[derive(Clone, Debug, Default, Deserialize)] +pub struct SlideItem { + /// [slides.icons] tablosundaki ikon anahtar adı. + pub icon: String, + /// Slayt başlığı. + pub title: String, + /// Slayt açıklama metni. + pub description: String, + /// Yalnızca bu masaüstü ortamında göster (boş = hepsinde). + /// Örnek: "xfce", "kde", "gnome" + #[serde(default)] + pub desktop: String, +} + +#[derive(Clone, Debug, Default, Deserialize)] +pub struct SlidesConfig { + /// Slayt geçiş süresi (saniye). + #[serde(default = "default_interval")] + pub interval_secs: u64, + + /// Slayt ikonu anahtar sözcüğü → dosya yolu eşlemesi. + #[serde(default)] + pub icons: HashMap, + + /// branding.toml içinde [[slides.items]] ile tanımlanan slayt listesi. + /// Boşsa slideshow.rs içindeki sabit slaytlar kullanılır. + #[serde(default)] + pub items: Vec, +} + +// ── [theme] ────────────────────────────────────────────────────────────────── + +#[derive(Clone, Debug, Deserialize)] +pub struct ThemeConfig { + #[serde(default = "default_primary")] + pub primary: String, + #[serde(default = "default_accent")] + pub accent: String, + #[serde(default = "default_background")] + pub background: String, + #[serde(default = "default_text")] + pub text: String, +} + +impl Default for ThemeConfig { + fn default() -> Self { + Self { + primary: default_primary(), + accent: default_accent(), + background: default_background(), + text: default_text(), + } + } +} + +// ── [oem] ──────────────────────────────────────────────────────────────────── + +#[derive(Clone, Debug, Default, Deserialize)] +pub struct OemConfig { + #[serde(default)] + pub enabled: bool, + #[serde(default)] + #[allow(dead_code)] + pub skip_steps: Vec, + #[serde(default)] + pub default_username: String, + #[serde(default = "default_hostname")] + pub default_hostname: String, + #[serde(default)] + pub default_password: String, +} + +// ── Varsayılan değerler ─────────────────────────────────────────────────────── + +fn default_title() -> String { "YALI Installation Tool".to_string() } +fn default_subtitle() -> String { "PiSi GNU/LINUX".to_string() } +fn default_yali_logo() -> String { "assets/yali.png".to_string() } +fn default_pisi_logo_dark() -> String { "assets/pisi-logo-dark.png".to_string() } +fn default_pisi_logo_light() -> String { "assets/pisi-logo-light.png".to_string() } +fn default_interval() -> u64 { 6 } +fn default_primary() -> String { "#1a73e8".to_string() } +fn default_accent() -> String { "#34a853".to_string() } +fn default_background() -> String { "#181824".to_string() } +fn default_text() -> String { "#e2e8f0".to_string() } +fn default_hostname() -> String { "pisi-oem".to_string() } + +// ── Yükleme ────────────────────────────────────────────────────────────────── + +impl BrandingConfig { + /// `branding.toml` dosyasını arar ve yükler. + /// + /// Arama sırası: + /// 1. `/etc/yali/branding.toml` + /// 2. `/usr/share/yali/branding.toml` + /// 3. Çalışma dizinindeki `branding.toml` + pub fn load() -> Self { + let paths = [ + "/etc/yali/branding.toml", + "/usr/share/yali/branding.toml", + "branding.toml", + ]; + for path in &paths { + if let Ok(content) = std::fs::read_to_string(path) { + match toml::from_str(&content) { + Ok(cfg) => return cfg, + Err(e) => eprintln!("[branding] {}: {}", path, e), + } + } + } + BrandingConfig::default() + } + + /// Belirtilen dile ait `branding_{lang}.toml` dosyasını arar ve yükler. + /// Bulunamazsa varsayılan `branding.toml` dosyasına geri döner. + pub fn load_for_lang(lang: &str) -> Self { + let lang_paths = [ + format!("/etc/yali/branding_{}.toml", lang), + format!("/usr/share/yali/branding_{}.toml", lang), + format!("branding_{}.toml", lang), + ]; + for path in &lang_paths { + if let Ok(content) = std::fs::read_to_string(path) { + match toml::from_str(&content) { + Ok(cfg) => return cfg, + Err(e) => eprintln!("[branding] {}: {}", path, e), + } + } + } + Self::load() + } + + /// İkon adına göre dosya yolunu döner. + /// + /// Koyu/açık temaya duyarlı `pisilogo` ikonu için `is_dark` parametresi + /// kullanılır; diğer ikonlar için görmezden gelinir. + pub fn icon_path(&self, icon: &str, is_dark: bool) -> Option { + // pisilogo özel durumu: tema bazlı iki ayrı anahtar + if icon == "pisilogo" { + let key = if is_dark { "pisi_logo_dark" } else { "pisi_logo_light" }; + return self.slides.icons.get(key).cloned(); + } + self.slides.icons.get(icon).cloned() + } + + /// Sol kenar çubuğu (Yali) logosu için `file://` URI döner. + pub fn yali_logo_uri(&self) -> String { + path_to_file_uri(&self.general.yali_logo) + } + + /// Ana sayfa PisiLinux logosu (koyu tema) için `file://` URI döner. + pub fn pisi_logo_dark_uri(&self) -> String { + path_to_file_uri(&self.general.pisi_logo_dark) + } + + /// Ana sayfa PisiLinux logosu (açık tema) için `file://` URI döner. + pub fn pisi_logo_light_uri(&self) -> String { + path_to_file_uri(&self.general.pisi_logo_light) + } +} + +/// Göreli veya mutlak dosya yolunu `egui::Image::from_uri` için uygun +/// `file://` URI formatına çevirir. +pub fn path_to_file_uri(path: &str) -> String { + if path.starts_with("file://") || path.starts_with("http") { + return path.to_string(); + } + if std::path::Path::new(path).is_absolute() { + return format!("file://{}", path); + } + + // Göreli yolları sırasıyla ara: + // 1. Çalışma dizini (CWD) + // 2. /usr/share/yali/ + // 3. /etc/yali/ + let cwd = std::env::current_dir().unwrap_or_default(); + let cwd_path = cwd.join(path); + if cwd_path.exists() { + return format!("file://{}", cwd_path.display()); + } + + let usr_path = std::path::Path::new("/usr/share/yali").join(path); + if usr_path.exists() { + return format!("file://{}", usr_path.display()); + } + + let etc_path = std::path::Path::new("/etc/yali").join(path); + if etc_path.exists() { + return format!("file://{}", etc_path.display()); + } + + // Hiçbiri bulunamazsa varsayılan olarak CWD yolunu döner (hata/fallback takibi için) + format!("file://{}", cwd_path.display()) +} diff --git a/src/installer.rs b/src/installer.rs index 618d4a5..8d15cb7 100644 --- a/src/installer.rs +++ b/src/installer.rs @@ -1,4 +1,5 @@ use eframe::egui; +use sysinfo::System; use rust_i18n::t; /// Disk adı ve bölüm numarasından tam aygıt yolunu oluşturur. @@ -45,6 +46,8 @@ pub struct PartitionPlan { pub efi_mb: u64, // 0 → BIOS, >0 → UEFI pub swap_mb: u64, pub root_mb: u64, // kalan alan + pub encrypt_root: bool, // Kök bölümü LUKS ile şifrelensin mi? + pub luks_password: String, // LUKS şifresi } #[derive(Clone, Debug, Default, PartialEq)] @@ -67,23 +70,31 @@ impl std::fmt::Display for PartitionTableType { #[derive(Clone, Debug, PartialEq)] pub enum FsType { Ext4, + Ext3, Btrfs, Xfs, Fat32, // EFI bölümü için + Ntfs, Swap, + Lvm, // LVM fiziksel bölüm (PV) + #[allow(dead_code)] + LvmLv, // LVM mantıksal bölüm (LV) } impl FsType { pub fn label(&self) -> &'static str { match self { FsType::Ext4 => "ext4", + FsType::Ext3 => "ext3", FsType::Btrfs => "btrfs", FsType::Xfs => "xfs", FsType::Fat32 => "FAT32 (EFI)", + FsType::Ntfs => "NTFS", FsType::Swap => "swap", + FsType::Lvm => "LVM PV", + FsType::LvmLv => "LVM LV", } } - } impl std::fmt::Display for FsType { @@ -92,6 +103,25 @@ impl std::fmt::Display for FsType { } } +/// LVM Volume Group yapılandırması. +#[derive(Clone, Debug, Default)] +pub struct VolumeGroup { + pub name: String, + pub pv_devices: Vec, // PV olarak kullanılacak bölüm aygıtları + pub size_mb: u64, // VG toplam boyutu (hesaplanan) +} + +/// LVM Logical Volume yapılandırması. +#[derive(Clone, Debug)] +pub struct LogicalVolume { + pub name: String, + pub vg_name: String, + pub size_mb: u64, // 0 = kalan alan + pub fstype: FsType, + #[allow(dead_code)] + pub mountpoint: String, +} + /// Kullanıcının manuel olarak tanımladığı tek bir bölüm. #[derive(Clone, Debug)] pub struct CustomPartition { @@ -103,6 +133,12 @@ pub struct CustomPartition { pub fstype: FsType, /// Bağlama noktası; swap için "swap" pub mountpoint: String, + /// LUKS şifreleme aktif mi? + pub encrypt: bool, + /// LUKS şifresi + pub luks_password: String, + /// LUKS harici adı (ör. "cryptroot") + pub luks_name: String, } /// Kurulum boyunca toplanan tüm veriler. @@ -114,6 +150,8 @@ pub struct GlobalState { pub partition_plan: Option, /// Manuel bölümleme modu için kullanıcının tanımladığı bölümler. pub custom_partitions: Vec, + pub volume_groups: Vec, + pub logical_volumes: Vec, pub keyboard_layout: String, pub keyboard_variant: String, pub username: String, @@ -126,6 +164,44 @@ pub struct GlobalState { pub install_log: Vec, pub demo_mode: bool, pub is_dark: bool, + pub rescue_mode: bool, + pub bootloader_device: String, + pub bootloader_timeout: u32, + pub bootloader_password: String, + pub kernel_options: String, + pub license_accepted: bool, + pub root_password: String, + pub root_password_confirm: String, + pub display_manager: String, + pub autologin: bool, + pub desktop_environment: String, + pub network_wifi_password: String, + pub network_http_proxy: String, + pub network_https_proxy: String, + pub network_ftp_proxy: String, + pub use_ntp: bool, + pub manual_year: i32, + pub manual_month: u32, + pub manual_day: u32, + pub manual_hour: u32, + pub manual_minute: u32, + #[allow(dead_code)] + pub encrypt_root: bool, + #[allow(dead_code)] + pub luks_password: String, + pub oem_mode: bool, + pub selected_package_groups: Vec, + /// branding.toml [slides.icons] tablosundan yüklenen ikon → dosya URI eşlemesi. + /// Slideshow bu map üzerinden çalışma zamanında görsel yollarını çözümler. + pub branding_icons: std::collections::HashMap, + /// Sidebar için koyu tema logo URI'si. + pub branding_yali_dark: String, + pub branding_pisi_dark: String, + /// Sidebar için açık tema logo URI'si. + pub branding_yali_light: String, + pub branding_pisi_light: String, + /// branding.toml dosyasından yüklenen slaytlar. + pub branding_slides: Vec, } impl Default for GlobalState { @@ -137,6 +213,8 @@ impl Default for GlobalState { erase_disk: true, partition_plan: None, custom_partitions: Vec::new(), + volume_groups: Vec::new(), + logical_volumes: Vec::new(), keyboard_layout: "tr".to_string(), keyboard_variant: String::new(), username: String::new(), @@ -149,11 +227,51 @@ impl Default for GlobalState { install_log: Vec::new(), demo_mode: false, is_dark: true, + rescue_mode: false, + bootloader_device: String::new(), + bootloader_timeout: 5, + bootloader_password: String::new(), + kernel_options: String::new(), + license_accepted: false, + root_password: String::new(), + root_password_confirm: String::new(), + network_wifi_password: String::new(), + network_http_proxy: String::new(), + network_https_proxy: String::new(), + network_ftp_proxy: String::new(), + use_ntp: true, + manual_year: 2026, + manual_month: 5, + manual_day: 26, + manual_hour: 12, + manual_minute: 0, + encrypt_root: false, + luks_password: String::new(), + oem_mode: false, + selected_package_groups: Vec::new(), + display_manager: "sddm".to_string(), + autologin: true, + desktop_environment: { + let de = std::env::var("XDG_CURRENT_DESKTOP") + .unwrap_or_default() + .trim() + .to_lowercase(); + if de.is_empty() { + "plasma".to_string() + } else { + de + } + }, + branding_icons: std::collections::HashMap::new(), + branding_pisi_dark: String::new(), + branding_pisi_light: String::new(), + branding_yali_dark: String::new(), + branding_yali_light: String::new(), + branding_slides: Vec::new(), } } } -// ───────────────────────────────────────────── // WELCOME STEP (sistem gereksinimleri + dil seçimi) // ───────────────────────────────────────────── @@ -167,7 +285,11 @@ pub struct WelcomeStep { impl Default for WelcomeStep { fn default() -> Self { - Self { checks: Vec::new(), checked: false, last_language: String::new() } + Self { + checks: Vec::new(), + checked: false, + last_language: String::new(), + } } } @@ -193,12 +315,54 @@ impl InstallerStep for WelcomeStep { } theme::section_heading(ui, &t!("welcome_title")); - ui.label( + + ui.label( egui::RichText::new(t!("welcome_description")) .color(theme::c_text_dim()) - .size(13.0), + .size(15.0), ); + ui.add_space(16.0); + + ui.vertical_centered(|ui| { + let logo_uri = if state.is_dark { + &state.branding_pisi_dark + } else { + &state.branding_pisi_light + }; + if !logo_uri.is_empty() { + ui.add( + egui::Image::from_uri(logo_uri.as_str()) + .max_width(120.0) + .rounding(egui::Rounding::same(8.0)), + ); + } + ui.add_space(8.0); + let os_name = System::name().unwrap_or_else(|| "Bilinmiyor".to_string()); + ui.label( + egui::RichText::new(format!("{} : {}", t!("os"), os_name)) + .strong() + .size(16.0) + .color(theme::c_text_dim()), + ); + ui.add_space(4.0); + let os_version = System::os_version().unwrap_or_else(|| "Bilinmiyor".to_string()); + ui.label( + egui::RichText::new(format!("{} : {}", t!("version"), os_version)) + .size(14.0) + .color(theme::c_text_dim()), + ); + + let desktop_name = std::env::var("XDG_CURRENT_DESKTOP").unwrap_or_default(); + ui.label( + egui::RichText::new(format!("{} : {}", t!("desktop"), desktop_name)) + .size(14.0) + .color(theme::c_text_dim()), + ); + }); + + + ui.add_space(12.0); // Dil seçimi (Açılır Liste / ComboBox) @@ -232,7 +396,6 @@ impl InstallerStep for WelcomeStep { .striped(true) .show(ui, |ui| { for check in &self.checks { - // Durum simgesi + renk let (icon, color) = match check.status { CheckStatus::Pass => ("✅", theme::c_success()), CheckStatus::Warn => ("⚠️", theme::c_warning()), @@ -249,6 +412,27 @@ impl InstallerStep for WelcomeStep { } }); + // Rescue mode butonu + ui.add_space(10.0); + ui.horizontal(|ui| { + let rescue_available = detect_existing_linux(); + if rescue_available { + ui.colored_label( + egui::Color32::from_rgb(80, 170, 80), + "🛟", + ); + if ui.add(theme::secondary_button(&t!("rescue_button"))).clicked() { + state.rescue_mode = true; + } + } else { + ui.label( + egui::RichText::new(t!("rescue_not_available")) + .size(12.0) + .color(egui::Color32::from_rgb(120, 120, 140)), + ); + } + }); + // Hata varsa uyarı ui.add_space(10.0); let has_fail = self.checks.iter().any(|c| c.status == CheckStatus::Fail); @@ -269,11 +453,9 @@ impl InstallerStep for WelcomeStep { } fn is_complete(&self, state: &GlobalState) -> bool { - // Demo modunda her zaman ileri gitmeye izin ver if state.demo_mode { return true; } - // Fail varsa ilerleyemez; kontroller bitmemişse de bekle !self.checks.is_empty() && all_passed(&self.checks) } } @@ -283,8 +465,9 @@ impl InstallerStep for WelcomeStep { // ───────────────────────────────────────────── pub struct LocationStep { - search_query: String, timezones: Vec, + geoip_done: bool, + datetime_init_done: bool, } impl Default for LocationStep { @@ -296,29 +479,210 @@ impl Default for LocationStep { "Europe/London", "Europe/Moscow", "Europe/Paris", "Europe/Rome", "Pacific/Auckland", ].iter().map(|s| s.to_string()).collect(); - Self { search_query: String::new(), timezones: tz_list } + Self { + timezones: tz_list, + geoip_done: false, + datetime_init_done: false, + } + } +} + +impl LocationStep { + fn geoip_lookup(state: &mut GlobalState) { + let out = std::process::Command::new("curl") + .args(["-s", "--connect-timeout", "3", "http://ip-api.com/json"]) + .output(); + if let Ok(o) = out { + if let Ok(text) = String::from_utf8(o.stdout) { + let extract = |key: &str| -> Option { + let pat = format!("\"{}\":\"", key); + text.find(&pat).and_then(|pos| { + let start = pos + pat.len(); + text[start..].find('"').map(|end| text[start..start+end].to_string()) + }) + }; + if let Some(country) = extract("countryCode") { + match country.as_str() { + "TR" => state.language = "tr".to_string(), + "DE" => state.language = "de".to_string(), + "FR" => state.language = "fr".to_string(), + "IT" => state.language = "it".to_string(), + "ES" => state.language = "es".to_string(), + "RU" => state.language = "ru".to_string(), + "NL" => state.language = "nl".to_string(), + "PL" => state.language = "pl".to_string(), + _ => {} + } + } + if let Some(tz) = extract("timezone") { + if !tz.is_empty() { + state.timezone = tz; + } + } + } + } + } + + fn init_from_system(&mut self, state: &mut GlobalState) { + use chrono::{Datelike, Timelike}; + let now_str = chrono::Local::now().format("%Y-%m-%d %H:%M").to_string(); + if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(&now_str, "%Y-%m-%d %H:%M") { + state.manual_year = dt.year(); + state.manual_month = dt.month(); + state.manual_day = dt.day(); + state.manual_hour = dt.hour(); + state.manual_minute = dt.minute(); + } + self.datetime_init_done = true; } } impl InstallerStep for LocationStep { - fn name(&self) -> String { t!("location").to_string() } - - fn show(&mut self, ui: &mut egui::Ui, state: &mut GlobalState) { - ui.heading(t!("location_title")); - ui.label(t!("location_description")); - ui.add_space(10.0); - ui.label(t!("search_timezone")); - ui.text_edit_singleline(&mut self.search_query); - ui.add_space(5.0); - egui::ScrollArea::vertical().max_height(300.0).show(ui, |ui| { - for tz in &self.timezones { - if tz.to_lowercase().contains(&self.search_query.to_lowercase()) { - ui.selectable_value(&mut state.timezone, tz.clone(), tz); - } - } - }); + fn name(&self) -> String { + format!("{} / {}", t!("location"), t!("datetime")) } - fn is_complete(&self, state: &GlobalState) -> bool { !state.timezone.is_empty() } + fn on_enter(&mut self, state: &mut GlobalState) { + if !self.geoip_done { + Self::geoip_lookup(state); + self.geoip_done = true; + } + if !self.datetime_init_done { + self.init_from_system(state); + } + } + + fn show(&mut self, ui: &mut egui::Ui, state: &mut GlobalState) { + use crate::ui::theme; + + // --- BÖLGE VE ZAMAN DİLİMİ (Region & Timezone) --- + theme::section_heading(ui, &t!("location_title")); + ui.label( + egui::RichText::new(t!("location_description")) + .color(theme::c_text_dim()) + .size(13.0), + ); + ui.add_space(8.0); + + ui.horizontal(|ui| { + ui.label(egui::RichText::new(t!("location_title")).strong().color(theme::c_text())); + + let current_tz = if state.timezone.is_empty() { + "Europe/Istanbul".to_string() + } else { + state.timezone.clone() + }; + if state.timezone.is_empty() { + state.timezone = current_tz.clone(); + } + + egui::ComboBox::from_id_source("timezone_select") + .selected_text(¤t_tz) + .width(280.0) + .show_ui(ui, |ui| { + for tz in &self.timezones { + ui.selectable_value(&mut state.timezone, tz.clone(), tz); + } + }); + }); + + ui.add_space(16.0); + ui.separator(); + ui.add_space(16.0); + + // --- TARİH VE SAAT AYARLARI (Date & Time) --- + theme::section_heading(ui, &t!("datetime_title")); + ui.label( + egui::RichText::new(t!("datetime_description")) + .color(theme::c_text_dim()) + .size(13.0), + ); + ui.add_space(8.0); + + egui::Frame::group(ui.style()) + .fill(theme::c_bg_widget()) + .rounding(6.0) + .inner_margin(egui::Margin::same(12.0)) + .show(ui, |ui| { + ui.set_width(ui.available_width()); + egui::Grid::new("datetime_grid") + .num_columns(2) + .spacing([12.0, 10.0]) + .show(ui, |ui| { + // NTP + ui.label(egui::RichText::new(t!("datetime_ntp_label")).strong().color(theme::c_text())); + ui.horizontal(|ui| { + ui.add(egui::Checkbox::new(&mut state.use_ntp, "")); + let status = if state.use_ntp { t!("datetime_ntp_on") } else { t!("datetime_ntp_off") }; + ui.colored_label( + if state.use_ntp { theme::c_success() } else { theme::c_error() }, + status, + ); + }); + ui.end_row(); + + if !state.use_ntp { + // Tarih + ui.label(egui::RichText::new(t!("datetime_date_label")).strong().color(theme::c_text())); + ui.horizontal(|ui| { + ui.label(t!("datetime_year")); + ui.add(egui::DragValue::new(&mut state.manual_year).clamp_range(2000..=2100).speed(1)); + ui.label(t!("datetime_month")); + ui.add(egui::DragValue::new(&mut state.manual_month).clamp_range(1..=12).speed(1)); + ui.label(t!("datetime_day")); + ui.add(egui::DragValue::new(&mut state.manual_day).clamp_range(1..=31).speed(1)); + }); + ui.end_row(); + + // Saat + ui.label(egui::RichText::new(t!("datetime_time_label")).strong().color(theme::c_text())); + ui.horizontal(|ui| { + ui.add(egui::DragValue::new(&mut state.manual_hour).clamp_range(0..=23).speed(1)); + ui.label(":"); + ui.add(egui::DragValue::new(&mut state.manual_minute).clamp_range(0..=59).speed(1)); + }); + ui.end_row(); + } + }); + }); + + ui.add_space(8.0); + if state.use_ntp { + ui.label( + egui::RichText::new(t!("datetime_ntp_info")) + .size(11.0) + .color(theme::c_text_dim()), + ); + } else { + ui.label( + egui::RichText::new(t!("datetime_manual_info")) + .size(11.0) + .color(theme::c_text_dim()), + ); + } + } + + fn is_complete(&self, state: &GlobalState) -> bool { + !state.timezone.is_empty() + } +} + +/// Mevcut bir Linux kurulumu olup olmadığını kontrol eder. +/// Basitçe /mnt/etc/fstab varlığına veya blkid ile Linux bölümlerine bakar. +fn detect_existing_linux() -> bool { + // /etc/fstab veya /mnt/etc/fstab kontrolü + if std::path::Path::new("/mnt/etc/fstab").exists() { + return true; + } + // blkid ile Linux bölümlerini tara + if let Ok(out) = std::process::Command::new("blkid") + .args(["-o", "list"]) + .output() + { + let text = String::from_utf8_lossy(&out.stdout); + text.lines().any(|l| l.contains("ext4") || l.contains("btrfs") || l.contains("xfs")) + } else { + false + } } diff --git a/src/jobs/mod.rs b/src/jobs/mod.rs index 8d88b52..a8c890c 100644 --- a/src/jobs/mod.rs +++ b/src/jobs/mod.rs @@ -219,6 +219,8 @@ impl Job for UnmountBindJob { /// mkinitcpio kullanıyorsa `mkinitcpio -P`. pub struct GenerateInitramfsJob { pub mount: String, + pub encrypt_root: bool, + pub lvm_active: bool, } #[async_trait::async_trait] @@ -226,16 +228,64 @@ impl Job for GenerateInitramfsJob { fn name(&self) -> &str { "job_initramfs" } async fn run(&self, ui: &UiSender) -> Result<(), String> { - ui.log("initramfs oluşturuluyor (dracut)…"); - // Önce dracut'u dene (PisiLinux), hata verirse mkinitcpio dene - let dracut = run_chroot(&self.mount, &[ - "dracut", "-f", "--regenerate-all", - ]).await; - if dracut.is_err() { - ui.log("dracut başarısız, mkinitcpio deneniyor…"); - run_chroot(&self.mount, &["mkinitcpio", "-P"]).await?; + // LVM için initramfs'e lvm modülü/hook'u ekleyen konfigürasyon + if self.lvm_active && !self.encrypt_root { + ui.log("LVM algılandı: initramfs LVM ayarlanıyor…"); + + // dracut: add lvm module + let dracut_conf = format!("{}/etc/dracut.conf.d/99-lvm.conf", self.mount); + let _ = tokio::fs::create_dir_all(format!("{}/etc/dracut.conf.d", self.mount)).await; + let _ = tokio::fs::write(&dracut_conf, "add_dracutmodules+=\" lvm \"\n").await; + + // mkinitcpio: add lvm2 hook + let mkinitcpio_conf = format!("{}/etc/mkinitcpio.conf", self.mount); + if tokio::fs::metadata(&mkinitcpio_conf).await.is_ok() { + if let Ok(content) = tokio::fs::read_to_string(&mkinitcpio_conf).await { + if content.contains("HOOKS=") && !content.contains("lvm2") { + let new_content = content.replace("HOOKS=\"", "HOOKS=\"lvm2 "); + let _ = tokio::fs::write(&mkinitcpio_conf, new_content).await; + } + } + } } - Ok(()) + + // LUKS için initramfs'e cryptsetup ekleyen konfigürasyon + if self.encrypt_root { + ui.log("LUKS algılandı: initramfs cryptsetup ayarlanıyor…"); + + // dracut: add crypt module + let dracut_conf = format!("{}/etc/dracut.conf.d/99-luks.conf", self.mount); + tokio::fs::create_dir_all( + format!("{}/etc/dracut.conf.d", self.mount) + ).await.map_err(|e| format!("dracut.conf.d oluşturulamadı: {}", e))?; + tokio::fs::write(&dracut_conf, "add_dracutmodules+=\" crypt lvm \"\n") + .await.map_err(|e| format!("dracut luks conf yazılamadı: {}", e))?; + + // mkinitcpio: add encrypt hook + let mkinitcpio_conf = format!("{}/etc/mkinitcpio.conf", self.mount); + if tokio::fs::metadata(&mkinitcpio_conf).await.is_ok() { + let content = tokio::fs::read_to_string(&mkinitcpio_conf) + .await.unwrap_or_default(); + if content.contains("HOOKS=") && !content.contains("encrypt") { + let new_content = content.replace("HOOKS=\"", "HOOKS=\"encrypt "); + let _ = tokio::fs::write(&mkinitcpio_conf, new_content).await; + } + } + } + + // PisiLinux /sbin/mkinitramfs, dracut, mkinitcpio sırasıyla dene + let cmds: &[&[&str]] = &[ + &["mkinitramfs", "-f"], + &["dracut", "-f", "--regenerate-all"], + &["mkinitcpio", "-P"], + ]; + for cmd in cmds { + if run_chroot(&self.mount, cmd).await.is_ok() { + ui.log(format!("initramfs oluşturuldu ({})", cmd[0])); + return Ok(()); + } + } + Err("initramfs oluşturulamadı: mkinitramfs, dracut, mkinitcpio denendi".to_string()) } } @@ -246,6 +296,11 @@ pub struct InstallGrubJob { pub disk: String, pub mount: String, pub is_uefi: bool, + pub boot_device: String, + pub timeout: u32, + pub password: String, + pub kernel_options: String, + pub encrypt_root: bool, } #[async_trait::async_trait] @@ -253,6 +308,83 @@ impl Job for InstallGrubJob { fn name(&self) -> &str { "job_bootloader" } async fn run(&self, ui: &UiSender) -> Result<(), String> { + // GRUB şifresi (varsa) — bcrypt hash ile /etc/grub.d/40_custom yazılır + if !self.password.is_empty() { + ui.log("GRUB şifresi yapılandırılıyor…"); + let custom_grub = format!( + "set superusers=\"root\"\npassword_pbkdf2 root {} \n", + self.password + ); + let pw_path = format!("{}/etc/grub.d/40_custom", self.mount); + tokio::fs::write(&pw_path, custom_grub) + .await + .map_err(|e| format!("GRUB şifre dosyası yazılamadı: {}", e))?; + run_cmd("chmod", &["+x", &pw_path]).await?; + } + + // GRUB timeout ayarı + ui.log(format!("GRUB timeout ayarlanıyor: {}s", self.timeout)); + let default_path = format!("{}/etc/default/grub", self.mount); + let mut grub_default = tokio::fs::read_to_string(&default_path) + .await + .unwrap_or_default(); + // timeout satırını bul/değiştir + let timeout_line = format!("GRUB_TIMEOUT={}", self.timeout); + let found_timeout = grub_default.lines() + .any(|l| l.trim_start().starts_with("GRUB_TIMEOUT=")); + if found_timeout { + let mut lines: Vec = grub_default.lines() + .map(|l| { + if l.trim_start().starts_with("GRUB_TIMEOUT=") { + timeout_line.clone() + } else { + l.to_string() + } + }) + .collect(); + lines.push(String::new()); + grub_default = lines.join("\n"); + } else { + grub_default.push_str(&format!("\n{}\n", timeout_line)); + } + // kernel cmdline + let mut cmdline = self.kernel_options.clone(); + if self.encrypt_root { + // Kök bölüm UUID'sini blkid'den al (LUKS başlık UUID'si) + let (root_num, _swap_num): (u32, u32) = if self.is_uefi { (3, 2) } else { (2, 1) }; + let root_part = part_path(&self.disk, root_num); + let root_uuid = blkid_uuid(&root_part).await.unwrap_or_default(); + let crypt_arg = format!("cryptdevice=UUID={}:cryptroot root=/dev/mapper/cryptroot", root_uuid); + if cmdline.is_empty() { + cmdline = crypt_arg; + } else { + cmdline = format!("{} {}", crypt_arg, cmdline); + } + } + if !cmdline.is_empty() { + let cmdline_line = format!("GRUB_CMDLINE_LINUX=\"{}\"", cmdline); + let found_cmdline = grub_default.lines() + .any(|l| l.trim_start().starts_with("GRUB_CMDLINE_LINUX=")); + if found_cmdline { + let mut lines: Vec = grub_default.lines() + .map(|l| { + if l.trim_start().starts_with("GRUB_CMDLINE_LINUX=") { + cmdline_line.clone() + } else { + l.to_string() + } + }) + .collect(); + lines.push(String::new()); + grub_default = lines.join("\n"); + } else { + grub_default.push_str(&format!("{}\n", cmdline_line)); + } + } + tokio::fs::write(&default_path, grub_default) + .await + .map_err(|e| format!("/etc/default/grub yazılamadı: {}", e))?; + // Chroot içinde grub2-install çalıştır (UEFI veya BIOS) let mut grub_args = vec!["grub2-install"]; let efi_dir_arg; @@ -268,8 +400,9 @@ impl Job for InstallGrubJob { "--recheck", ]); } else { - ui.log(format!("GRUB BIOS modu {} diskine kuruluyor (chroot içinde)…", self.disk)); - disk_arg = self.disk.clone(); + let install_dev = if self.boot_device.is_empty() { &self.disk } else { &self.boot_device }; + ui.log(format!("GRUB BIOS modu {} aygıtına kuruluyor (chroot içinde)…", install_dev)); + disk_arg = install_dev.clone(); grub_args.extend_from_slice(&[ "--target=i386-pc", &disk_arg, @@ -326,6 +459,49 @@ async fn run_cmd(program: &str, args: &[&str]) -> Result<(), String> { } } +/// `parted` komutlarını çalıştırmak için özel sarmalayıcı. +/// +/// `parted -s` canlı sistemde kullanımda olan bir disk üzerinde bölüm +/// tablosunu kernel'e bildiremeyince exit 1 döndürür; ancak değişiklikler +/// diske **yazılmıştır** — kernel güncellemesi `sync_partition_table` ile +/// yapılır. Bu fonksiyon yalnızca gerçekten kritik hataları (disk bulunamadı +/// vb.) yakalar; "kernel haberdar edilemedi" uyarısını yok sayar. +async fn run_parted(args: &[&str]) -> Result<(), String> { + if DEMO_MODE.load(Ordering::Relaxed) { + tokio::time::sleep(std::time::Duration::from_millis(500)).await; + return Ok(()); + } + + let output = Command::new("parted") + .args(args) + .output() + .await + .map_err(|e| format!("parted çalıştırılamadı: {}", e))?; + + if output.status.success() { + return Ok(()); + } + + // Parted, kernel güncelleyemediğinde exit 1 verir ama disk yazımı + // başarılıdır. Bu spesifik uyarıyı hata saymıyoruz. + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + let combined = format!("{}{}", stderr, stdout); + + let is_kernel_notify_warning = combined.contains("kernel değişiklikten haberdar edilemedi") + || combined.contains("kernel was not notified") + || combined.contains("unable to inform the kernel") + || combined.contains("still in use"); + + if is_kernel_notify_warning { + // Uyarıyı yok say; sync_partition_table kernel'i güncelleyecek + return Ok(()); + } + + // Gerçek bir hata + Err(combined.trim().to_string()) +} + /// Standart `chroot` ile verilen komutu hedef dizinde çalıştırır. /// Bind-mount'ların önceden yapılmış olduğu varsayılır (MountBindJob). async fn run_chroot(mount: &str, cmd: &[&str]) -> Result<(), String> { @@ -353,11 +529,67 @@ async fn run_chroot(mount: &str, cmd: &[&str]) -> Result<(), String> { } } +/// Bölümlendirme öncesi diski hazırlar: +/// mevcut bağlamaları ve swap'ı ayırır, ardından kernel'e yeni +/// bölüm tablosunu duyurarak aygıt düğümlerinin hazır olmasını bekler. +async fn prepare_disk(disk: &str, ui: &UiSender) { + if DEMO_MODE.load(Ordering::Relaxed) { return; } + + // 1. Diskteki aktif swap'ları kapat + ui.log(format!("Swap ayrılıyor: {}…", disk)); + let lsblk = tokio::process::Command::new("lsblk") + .args(["-o", "NAME,TYPE", "-l", "--noheadings", disk]) + .output().await; + if let Ok(out) = lsblk { + let text = String::from_utf8_lossy(&out.stdout); + for line in text.lines() { + if line.contains("part") { + if let Some(name) = line.split_whitespace().next() { + let dev = format!("/dev/{}", name); + let _ = tokio::process::Command::new("swapoff") + .arg(&dev).status().await; + } + } + } + } + + // 2. Disk bölümlerini umount et (hata görmezden gelinir) + ui.log(format!("Bağlantılar ayrılıyor: {}…", disk)); + for n in 1..=32u32 { + let part = crate::installer::part_path(disk, n); + if !std::path::Path::new(&part).exists() { break; } + let _ = tokio::process::Command::new("umount") + .args(["-lf", &part]).status().await; + } + + // 3. İlk senkronizasyon + let _ = tokio::process::Command::new("partprobe") + .arg(disk).status().await; + let _ = tokio::process::Command::new("udevadm") + .args(["settle", "--timeout=10"]).status().await; +} + +/// Bölümlendirme sonrası kernel'e yeni tabloyu bildirir ve aygıt +/// düğümlerinin hazır olmasını bekler. `parted`'ın uyarı vermesi +/// normaldir; bu fonksiyon hata döndürmez. +async fn sync_partition_table(disk: &str) { + if DEMO_MODE.load(Ordering::Relaxed) { return; } + let _ = tokio::process::Command::new("partprobe") + .arg(disk).status().await; + let _ = tokio::process::Command::new("blockdev") + .args(["--rereadpt", disk]).status().await; + let _ = tokio::process::Command::new("udevadm") + .args(["settle", "--timeout=10"]).status().await; + tokio::time::sleep(std::time::Duration::from_secs(2)).await; +} + /// ISO ortamında canlı sistemin kaynak dizinini otomatik tespit eder. /// Bulunamazsa varsayılan yolu döner. pub fn detect_source_dir() -> String { let candidates = [ - "/mnt/sqfs", //Pisilinux default + "/bootmnt", // PisiLinux ISO mount noktası + "/mnt/cdrom", + "/mnt/sqfs", "/run/livecd/squashfs-root", "/run/live/medium", "/lib/live/mount/rootfs", @@ -371,20 +603,39 @@ pub fn detect_source_dir() -> String { "/run/livecd/squashfs-root/".to_string() } +/// Kurulum kuyruğu için ortak yapılandırma. +pub struct JobQueueConfig { + pub mount: String, + pub source: String, + pub locale: String, + pub timezone: String, + pub username: String, + pub password: String, + pub hostname: String, + pub kb_layout: String, + pub kb_variant: String, + pub demo_mode: bool, + pub boot_device: String, + pub bootloader_timeout: u32, + pub bootloader_password: String, + pub kernel_options: String, + pub use_ntp: bool, + pub manual_year: i32, + pub manual_month: u32, + pub manual_day: u32, + pub manual_hour: u32, + pub manual_minute: u32, + pub root_password: String, + pub display_manager: String, + pub autologin: bool, + pub desktop_environment: String, + pub selected_package_groups: Vec, +} + // ───────────────────────────────────────────── // FABRİKA: STANDART KURULUM KUYRUĞU // ───────────────────────────────────────────── -/// `GlobalState`'ten derlenen standart kurulum kuyruğu. -/// -/// # Parametreler -/// - `disk` — hedef disk aygıtı (ör. "/dev/sda") -/// - `mount` — bağlama noktası (ör. "/mnt") -/// - `source` — canlı sistem kök dizini (ör. "/run/livecd/squashfs-root/") -/// - `is_uefi` — UEFI mi BIOS mu? -/// - `locale` — sistem yerel ayarı (ör. "tr_TR.UTF-8") -/// - `timezone`— zaman dilimi (ör. "Europe/Istanbul") - // ───────────────────────────────────────────── // PARTITION JOB (otomatik bölümleme) // ───────────────────────────────────────────── @@ -405,10 +656,13 @@ impl Job for PartitionJob { let disk = &self.plan.disk; let is_gpt = self.plan.table_type == PartitionTableType::Gpt; + // 0. Diski hazırla: bağlamaları ayır, kernel tablosunu temizle + prepare_disk(disk, ui).await; + // 1. Bölüm tablosu oluştur let table_label = if is_gpt { "gpt" } else { "msdos" }; ui.log(format!("parted {} mktable {}", disk, table_label)); - run_cmd("parted", &["-s", disk, "mktable", table_label]).await?; + run_parted(&["-s", disk, "mktable", table_label]).await?; let mut next_mb: u64 = 1; // İlk MB'ı boot kayıtları için bırak @@ -416,11 +670,11 @@ impl Job for PartitionJob { if self.plan.efi_mb > 0 { let end_mb = next_mb + self.plan.efi_mb; ui.log(format!("EFI bölümü oluşturuluyor: {}–{} MB", next_mb, end_mb)); - run_cmd("parted", &[ + run_parted(&[ "-s", disk, "mkpart", "primary", "fat32", &format!("{}MB", next_mb), &format!("{}MB", end_mb), ]).await?; - run_cmd("parted", &["-s", disk, "set", "1", "esp", "on"]).await?; + run_parted(&["-s", disk, "set", "1", "esp", "on"]).await?; next_mb = end_mb; } @@ -428,7 +682,7 @@ impl Job for PartitionJob { let swap_end = next_mb + self.plan.swap_mb; let swap_num: u32 = if self.plan.efi_mb > 0 { 2 } else { 1 }; ui.log(format!("Swap bölümü oluşturuluyor: {}–{} MB", next_mb, swap_end)); - run_cmd("parted", &[ + run_parted(&[ "-s", disk, "mkpart", "primary", "linux-swap", &format!("{}MB", next_mb), &format!("{}MB", swap_end), ]).await?; @@ -437,11 +691,14 @@ impl Job for PartitionJob { // 4. Kök bölümü (kalan alan) let root_num: u32 = if self.plan.efi_mb > 0 { 3 } else { 2 }; ui.log(format!("Kök bölümü oluşturuluyor: {}MB – disk sonu", next_mb)); - run_cmd("parted", &[ + run_parted(&[ "-s", disk, "mkpart", "primary", "ext4", &format!("{}MB", next_mb), "100%", ]).await?; + // 4.5. Tüm mkpart bitti; kernel'e bildir ve aygıt düğümlerini bekle + sync_partition_table(disk).await; + // 5. Biçimlendirme let efi_part = part_path(disk, 1); let swap_part = part_path(disk, swap_num); @@ -458,8 +715,34 @@ impl Job for PartitionJob { ui.log(format!("swapon {}", swap_part)); run_cmd("swapon", &[&swap_part]).await?; - ui.log(format!("mkfs.ext4 -F {}", root_part)); - run_cmd("mkfs.ext4", &["-F", &root_part]).await?; + // LUKS şifreleme (kök bölümü) + let root_device = if self.plan.encrypt_root && !self.plan.luks_password.is_empty() { + let luks_name = "cryptroot".to_string(); + ui.log(format!("cryptsetup luksFormat {}…", root_part)); + let _ = run_cmd("cryptsetup", &[ + "luksFormat", "-q", "--type", "luks2", &root_part, + "--key-file", "-", + ]).await; + // Burada batch modda çalıştığımız için şifreyi pipe ile veremeyiz; + // bunun yerine echo -n şifre | cryptsetup ile çalışır + let password_input = format!("echo -n '{}' | cryptsetup luksFormat -q --type luks2 {} --key-file=-", self.plan.luks_password, root_part); + let _ = tokio::process::Command::new("sh") + .args(["-c", &password_input]) + .output().await; + + ui.log(format!("cryptsetup open {} {}…", root_part, luks_name)); + let open_cmd = format!("echo -n '{}' | cryptsetup open {} {}", self.plan.luks_password, root_part, luks_name); + let _ = tokio::process::Command::new("sh") + .args(["-c", &open_cmd]) + .output().await; + + format!("/dev/mapper/{}", luks_name) + } else { + root_part.clone() + }; + + ui.log(format!("mkfs.ext4 -F {}", root_device)); + run_cmd("mkfs.ext4", &["-F", &root_device]).await?; Ok(()) } @@ -481,8 +764,14 @@ impl Job for MountPartitionsJob { let root_num: u32 = if self.plan.efi_mb > 0 { 3 } else { 2 }; let root_part = part_path(disk, root_num); - ui.log(format!("mount {} {}", root_part, mount)); - run_cmd("mount", &[&root_part, mount]).await?; + let root_device = if self.plan.encrypt_root { + "/dev/mapper/cryptroot".to_string() + } else { + root_part.clone() + }; + + ui.log(format!("mount {} {}", root_device, mount)); + run_cmd("mount", &[&root_device, mount]).await?; if self.plan.efi_mb > 0 { let efi_dir = format!("{}/boot/efi", mount); @@ -510,98 +799,243 @@ impl Job for MountPartitionsJob { /// 8. Zaman dilimi + hwclock /// 9. Klavye (vconsole + X11) /// 10. Kullanıcı oluşturma + şifre (chpasswd) -/// 11. initramfs yenile (dracut / mkinitcpio) +/// 11. initramfs yenile (mkinitramfs / dracut / mkinitcpio) /// 12. GRUB kurulumu (chroot içinde) /// 13. COMAR / pisi configure-pending -/// 14. Bind-mount'ları temizle +/// 14. ldconfig +/// 15. update-environment (PisiLinux init) +/// 16. Live ortam temizliği +/// 17. Bind-mount'ları temizle pub fn build_full_job_queue( - plan: PartitionPlan, - mount: &str, - source: &str, - locale: &str, - timezone: &str, - username: &str, - password: &str, - hostname: &str, - kb_layout: &str, - kb_variant: &str, - demo_mode: bool, + plan: PartitionPlan, + cfg: JobQueueConfig, ) -> JobQueue { - DEMO_MODE.store(demo_mode, std::sync::atomic::Ordering::Relaxed); + DEMO_MODE.store(cfg.demo_mode, std::sync::atomic::Ordering::Relaxed); - let is_uefi = plan.efi_mb > 0; - let disk = plan.disk.clone(); - let mt = mount.to_string(); - let plan2 = plan.clone(); - let plan3 = plan.clone(); + let is_uefi = plan.efi_mb > 0; + let encrypt_root = plan.encrypt_root; + let disk = plan.disk.clone(); + let mt = cfg.mount.clone(); + let plan2 = plan.clone(); + let plan3 = plan.clone(); // Kaynak dizini: parametre boşsa otomatik tespit et - let src = if source.is_empty() { detect_source_dir() } else { source.to_string() }; + let src = if cfg.source.is_empty() { detect_source_dir() } else { cfg.source }; - let jobs: Vec> = vec![ + let mut jobs: Vec> = vec![ // 1. Bölümlendir Box::new(PartitionJob { plan }), - // 2. Bağla Box::new(MountPartitionsJob { plan: plan2, mount: mt.clone() }), - // 3. /proc /sys /dev bind-mount Box::new(MountBindJob { mount: mt.clone() }), - // 4. Kopyala Box::new(CopyFilesJob { source: src, target: mt.clone() }), - // 5. UUID oku + fstab yaz Box::new(FstabFromBlkidJob { plan: plan3, mount: mt.clone() }), - // 6. Hostname Box::new(ConfigureHostnameJob { mount: mt.clone(), - hostname: hostname.to_string(), + hostname: cfg.hostname.clone(), }), - // 7. Locale Box::new(ConfigureLocaleJob { mount: mt.clone(), - locale: locale.to_string(), + locale: cfg.locale.clone(), }), - // 8. Zaman dilimi Box::new(ConfigureTimezoneJob { mount: mt.clone(), - timezone: timezone.to_string(), + timezone: cfg.timezone.clone(), }), - - // 9. Klavye + // 9. Tarih/Saat (NTP) + Box::new(ConfigureDateTimeJob { + mount: mt.clone(), + use_ntp: cfg.use_ntp, + year: cfg.manual_year, + month: cfg.manual_month, + day: cfg.manual_day, + hour: cfg.manual_hour, + minute: cfg.manual_minute, + }), + // 10. Klavye Box::new(ConfigureKeyboardJob { mount: mt.clone(), - layout: kb_layout.to_string(), - variant: kb_variant.to_string(), + layout: cfg.kb_layout.clone(), + variant: cfg.kb_variant.clone(), }), - - // 10. Kullanıcı + // 11. Kullanıcı Box::new(CreateUserJob { - mount: mt.clone(), - username: username.to_string(), - password: password.to_string(), + mount: mt.clone(), + username: cfg.username.clone(), + password: cfg.password.clone(), + root_password: cfg.root_password.clone(), }), - - // 11. initramfs - Box::new(GenerateInitramfsJob { mount: mt.clone() }), - - // 12. GRUB - Box::new(InstallGrubJob { disk, mount: mt.clone(), is_uefi }), - - // 13. COMAR - Box::new(RunComarJob { mount: mt.clone() }), - - // 14. Bind-mount temizle - Box::new(UnmountBindJob { mount: mt.clone() }), + // 12. initramfs + Box::new(GenerateInitramfsJob { mount: mt.clone(), encrypt_root, lvm_active: false }), ]; + // 13. Bootloader (GRUB) + jobs.push(Box::new(InstallGrubJob { + disk: disk.clone(), + mount: mt.clone(), + is_uefi, + boot_device: cfg.boot_device.clone(), + timeout: cfg.bootloader_timeout, + password: cfg.bootloader_password.clone(), + kernel_options: cfg.kernel_options.clone(), + encrypt_root, + })); + + // 14. Ekran yöneticisi + jobs.push(Box::new(ConfigureDisplayManagerJob { + mount: mt.clone(), + display_manager: cfg.display_manager.clone(), + autologin: cfg.autologin, + username: cfg.username.clone(), + desktop_environment: cfg.desktop_environment.clone(), + })); + + // 15. COMAR / pisi configure-pending + jobs.push(Box::new(RunComarJob { mount: mt.clone() })); + + // 16. ldconfig + jobs.push(Box::new(RunLdconfigJob { mount: mt.clone() })); + + // 17. update-environment (PisiLinux init yapılandırması) + jobs.push(Box::new(UpdateEnvironmentJob { mount: mt.clone() })); + + // 18. Ek paket grupları (netinstall) + if !cfg.selected_package_groups.is_empty() { + jobs.push(Box::new(InstallExtraPackagesJob { + mount: mt.clone(), + package_groups: cfg.selected_package_groups.clone(), + })); + } + + // 19. Live ortam izlerini temizle + jobs.push(Box::new(CleanupLiveJob { mount: mt.clone() })); + + // 20. Bind-mount temizle + jobs.push(Box::new(UnmountBindJob { mount: mt.clone() })); + JobQueue::new(jobs) } +/// Ekran yöneticisi (SDDM/LightDM/GDM) yapılandırması. +pub struct ConfigureDisplayManagerJob { + pub mount: String, + pub display_manager: String, + pub autologin: bool, + pub username: String, + pub desktop_environment: String, +} + +#[async_trait::async_trait] +impl Job for ConfigureDisplayManagerJob { + fn name(&self) -> &str { "job_display_manager" } + + async fn run(&self, ui: &UiSender) -> Result<(), String> { + let dm = &self.display_manager; + ui.log(format!("Ekran yöneticisi yapılandırılıyor: {}", dm)); + + match dm.as_str() { + "sddm" => { + let conf = format!("{}/etc/sddm.conf", self.mount); + let content = if self.autologin { + format!( + "[Autologin]\nUser={}\nSession={}.desktop\n\n[Theme]\nCurrent=breeze\n", + self.username, self.desktop_environment + ) + } else { + "[Theme]\nCurrent=breeze\n".to_string() + }; + tokio::fs::write(&conf, content) + .await + .map_err(|e| format!("sddm.conf yazılamadı: {}", e))?; + } + "lightdm" => { + let conf = format!("{}/etc/lightdm/lightdm.conf", self.mount); + // Mevcut dosyayı oku veya baştan yaz + let mut content = String::new(); + if let Ok(existing) = tokio::fs::read_to_string(&conf).await { + content = existing; + } + if self.autologin { + let auto_lines = format!( + "[Seat:*]\nautologin-user={}\nautologin-session={}\n", + self.username, self.desktop_environment + ); + content.push_str(&auto_lines); + } + tokio::fs::write(&conf, content) + .await + .map_err(|e| format!("lightdm.conf yazılamadı: {}", e))?; + } + "gdm" => { + let dir = format!("{}/etc/gdm", self.mount); + let _ = tokio::fs::create_dir_all(&dir).await; + let conf = format!("{}/etc/gdm/custom.conf", self.mount); + let content = if self.autologin { + format!( + "[daemon]\nAutomaticLoginEnable=true\nAutomaticLogin={}\n", + self.username + ) + } else { + "[daemon]\n".to_string() + }; + tokio::fs::write(&conf, content) + .await + .map_err(|e| format!("gdm/custom.conf yazılamadı: {}", e))?; + } + _ => { + ui.log(format!("⚠ Bilinmeyen ekran yöneticisi: {}", dm)); + } + } + + ui.log("✓ Ekran yöneticisi yapılandırması tamamlandı"); + Ok(()) + } +} + +/// Ek paket gruplarını chroot içinde kurar. +pub struct InstallExtraPackagesJob { + pub mount: String, + pub package_groups: Vec, +} + +#[async_trait::async_trait] +impl Job for InstallExtraPackagesJob { + fn name(&self) -> &str { "job_netinstall" } + + async fn run(&self, ui: &UiSender) -> Result<(), String> { + use crate::steps::netinstall::PACKAGE_GROUPS; + + let mut packages: Vec<&str> = Vec::new(); + for (key, _, pkgs) in PACKAGE_GROUPS { + if self.package_groups.contains(&key.to_string()) { + packages.extend_from_slice(pkgs); + } + } + + if packages.is_empty() { + ui.log("Ek paket seçilmedi, atlanıyor."); + return Ok(()); + } + + let pkg_list = packages.join(" "); + ui.log(format!("Ek paketler kuruluyor: {}", pkg_list)); + match run_chroot(&self.mount, &["pisi", "install", &pkg_list]).await { + Ok(_) => ui.log("✓ Ek paketler kuruldu"), + Err(e) => { + ui.log(format!("⚠ pisi install uyarısı: {}", e)); + ui.log("⚠ Bu hata kritik değilse kurulum devam edecek."); + } + } + Ok(()) + } +} + /// COMAR servislerini ve tetikleyicilerini yapılandırır. pub struct RunComarJob { pub mount: String, @@ -634,6 +1068,70 @@ impl Job for RunComarJob { } } +/// ldconfig: paylaşımlı kütüphane önbelleğini yeniden oluşturur. +pub struct RunLdconfigJob { + pub mount: String, +} + +#[async_trait::async_trait] +impl Job for RunLdconfigJob { + fn name(&self) -> &str { "job_ldconfig" } + + async fn run(&self, ui: &UiSender) -> Result<(), String> { + ui.log("ldconfig çalıştırılıyor…"); + match run_chroot(&self.mount, &["ldconfig"]).await { + Ok(_) => ui.log("✓ ldconfig tamamlandı"), + Err(e) => ui.log(format!("⚠ ldconfig uyarısı: {}", e)), + } + Ok(()) + } +} + +/// update-environment: PisiLinux init/betiklerini yeniden yapılandırır. +pub struct UpdateEnvironmentJob { + pub mount: String, +} + +#[async_trait::async_trait] +impl Job for UpdateEnvironmentJob { + fn name(&self) -> &str { "job_update_env" } + + async fn run(&self, ui: &UiSender) -> Result<(), String> { + ui.log("update-environment çalıştırılıyor…"); + match run_chroot(&self.mount, &["update-environment"]).await { + Ok(_) => ui.log("✓ update-environment tamamlandı"), + Err(e) => ui.log(format!("⚠ update-environment uyarısı: {}", e)), + } + Ok(()) + } +} + +/// Live ortam izlerini hedef sistemden temizler. +pub struct CleanupLiveJob { + pub mount: String, +} + +#[async_trait::async_trait] +impl Job for CleanupLiveJob { + fn name(&self) -> &str { "job_cleanup_live" } + + async fn run(&self, ui: &UiSender) -> Result<(), String> { + let mt = &self.mount; + ui.log("Live ortam izleri temizleniyor…"); + + // run/livemedia + let _ = tokio::fs::remove_dir_all(format!("{}/run/livemedia", mt)).await; + // var/cache/pisi/packages + let _ = tokio::fs::remove_dir_all(format!("{}/var/cache/pisi/packages", mt)).await; + // YALI desktop dosyaları + let _ = tokio::fs::remove_file(format!("{}/usr/share/applications/yali.desktop", mt)).await; + let _ = tokio::fs::remove_file(format!("{}/usr/share/applications/yali-bin.desktop", mt)).await; + + ui.log("✓ Live temizlik tamamlandı"); + Ok(()) + } +} + /// blkid ile UUID okuyup fstab'ı doğrudan yazan job. /// (async closure Rust'ta trait object olarak kullanılamaz; /// bu nedenle ayrı bir Job struct'ı gerekli.) @@ -656,6 +1154,7 @@ impl Job for FstabFromBlkidJob { root_uuid, efi_uuid, swap_uuid, + encrypt_root: self.plan.encrypt_root, }; job.run(ui).await } @@ -711,16 +1210,23 @@ impl Job for ConfigureLocaleJob { fn name(&self) -> &str { "job_locale" } async fn run(&self, ui: &UiSender) -> Result<(), String> { - // /etc/locale.conf + // /etc/locale.conf (systemd-style) let conf_path = format!("{}/etc/locale.conf", self.mount); let conf = format!( "LANG={locale}\nLC_ALL={locale}\n", locale = self.locale ); ui.log(format!("Yazılıyor: {}", conf_path)); - tokio::fs::write(&conf_path, conf) - .await - .map_err(|e| format!("/etc/locale.conf yazılamadı: {}", e))?; + let _ = tokio::fs::write(&conf_path, conf).await; + + // /etc/env.d/03locale (PisiLinux native) + let pisi_locale_path = format!("{}/etc/env.d/03locale", self.mount); + let pisi_locale = format!( + "LANG={locale}\nLC_ALL={locale}\n", + locale = self.locale + ); + ui.log(format!("Yazılıyor: {}", pisi_locale_path)); + let _ = tokio::fs::write(&pisi_locale_path, pisi_locale).await; // /etc/locale.gen — satırın başındaki # kaldırılır let gen_path = format!("{}/etc/locale.gen", self.mount); @@ -789,6 +1295,40 @@ impl Job for ConfigureTimezoneJob { } } +/// NTP ve manuel tarih/saat yapılandırması. +pub struct ConfigureDateTimeJob { + pub mount: String, + pub use_ntp: bool, + pub year: i32, + pub month: u32, + pub day: u32, + pub hour: u32, + pub minute: u32, +} + +#[async_trait::async_trait] +impl Job for ConfigureDateTimeJob { + fn name(&self) -> &str { "job_datetime" } + + async fn run(&self, ui: &UiSender) -> Result<(), String> { + if self.use_ntp { + ui.log("NTP etkinleştiriliyor…"); + // systemd-timesyncd veya ntpd'yi etkinleştir + let _ = run_chroot(&self.mount, &["systemctl", "enable", "systemd-timesyncd"]).await; + let _ = run_chroot(&self.mount, &["systemctl", "start", "systemd-timesyncd"]).await; + } else { + ui.log("Manuel tarih/saat ayarlanıyor…"); + let date_str = format!( + "{:04}-{:02}-{:02} {:02}:{:02}:00", + self.year, self.month, self.day, self.hour, self.minute + ); + let _ = run_chroot(&self.mount, &["date", "-s", &date_str]).await; + let _ = run_chroot(&self.mount, &["hwclock", "--systohc"]).await; + } + Ok(()) + } +} + /// Klavye düzenini /etc/vconsole.conf ve X11 yapılandırmasına yazar. pub struct ConfigureKeyboardJob { pub mount: String, @@ -801,7 +1341,7 @@ impl Job for ConfigureKeyboardJob { fn name(&self) -> &str { "job_keyboard" } async fn run(&self, ui: &UiSender) -> Result<(), String> { - // /etc/vconsole.conf (TTY için) + // /etc/vconsole.conf (systemd-style TTY) let vconsole_path = format!("{}/etc/vconsole.conf", self.mount); let keymap = if self.variant.is_empty() { self.layout.clone() @@ -810,9 +1350,13 @@ impl Job for ConfigureKeyboardJob { }; let vconsole = format!("KEYMAP={}\nFONT=ter-v16n\n", keymap); ui.log(format!("Yazılıyor: {}", vconsole_path)); - tokio::fs::write(&vconsole_path, vconsole) - .await - .map_err(|e| format!("/etc/vconsole.conf yazılamadı: {}", e))?; + let _ = tokio::fs::write(&vconsole_path, vconsole).await; + + // /etc/conf.d/mudur (PisiLinux native init) + let mudur_path = format!("{}/etc/conf.d/mudur", self.mount); + let mudur_conf = format!("keymap={}\nfont=ter-v16n\n", keymap); + ui.log(format!("Yazılıyor: {}", mudur_path)); + let _ = tokio::fs::write(&mudur_path, mudur_conf).await; // /etc/X11/xorg.conf.d/00-keyboard.conf (X11 için) let x11_dir = format!("{}/etc/X11/xorg.conf.d", self.mount); @@ -845,9 +1389,10 @@ impl Job for ConfigureKeyboardJob { /// Kullanıcı hesabı oluşturur ve şifresini ayarlar. pub struct CreateUserJob { - pub mount: String, - pub username: String, - pub password: String, + pub mount: String, + pub username: String, + pub password: String, + pub root_password: String, } #[async_trait::async_trait] @@ -868,7 +1413,7 @@ impl Job for CreateUserJob { // useradd: kullanıcıyı temel gruplarla oluştur // PisiLinux'ta "storage" grubu yoksa useradd hata verir; -M flag ile grub yoksa atla // Güvenli yöntem: önce her grubu tek tek ekle - let groups = ["audio", "video", "wheel", "cdrom", "plugdev", "storage", "lp", "scanner"]; + let groups = ["users", "pnp", "disk", "audio", "video", "power", "dialout", "lp", "lpadmin", "cdrom", "floppy"]; for grp in &groups { if group_content.lines().any(|l| l.starts_with(&format!("{}:", grp))) { // grup zaten var @@ -894,9 +1439,15 @@ impl Job for CreateUserJob { let chpasswd_input = format!("{}:{}", self.username, self.password); set_password_via_chpasswd(&self.mount, &chpasswd_input).await?; - // Root şifresini de kilitle (güvenlik: root doğrudan giriş yapmasın) - ui.log("Root hesabı kilitleniyor…"); - run_chroot(&self.mount, &["passwd", "-l", "root"]).await?; + // Root şifresi: belirlenmişse ayarla, yoksa kilitle + if self.root_password.is_empty() { + ui.log("Root hesabı kilitleniyor…"); + run_chroot(&self.mount, &["passwd", "-l", "root"]).await?; + } else { + ui.log("Root şifresi ayarlanıyor…"); + let root_input = format!("root:{}", self.root_password); + set_password_via_chpasswd(&self.mount, &root_input).await?; + } // sudoers: wheel grubu sudo yetkisi alsın let sudoers_path = format!("{}/etc/sudoers.d/wheel", self.mount); @@ -951,6 +1502,7 @@ pub struct GenerateFstabJob { pub root_uuid: String, // blkid ile alınır pub efi_uuid: Option, pub swap_uuid: String, + pub encrypt_root: bool, } #[async_trait::async_trait] @@ -967,10 +1519,16 @@ impl Job for GenerateFstabJob { ); // Kök bölümü - fstab.push_str(&format!( - "UUID={} / ext4 defaults,noatime 0 1\n", - self.root_uuid - )); + if self.encrypt_root { + fstab.push_str( + "/dev/mapper/cryptroot / ext4 defaults,noatime 0 1\n" + ); + } else { + fstab.push_str(&format!( + "UUID={} / ext4 defaults,noatime 0 1\n", + self.root_uuid + )); + } // EFI bölümü if let Some(ref efi) = self.efi_uuid { @@ -990,6 +1548,19 @@ impl Job for GenerateFstabJob { .await .map_err(|e| format!("fstab yazılamadı: {}", e))?; + // crypttab (LUKS için) + if self.encrypt_root { + let crypttab_path = format!("{}/etc/crypttab", self.mount); + let crypttab = format!( + "cryptroot UUID={} none luks\n", + self.root_uuid + ); + ui.log(format!("Yazılıyor: {}", crypttab_path)); + tokio::fs::write(&crypttab_path, crypttab) + .await + .map_err(|e| format!("crypttab yazılamadı: {}", e))?; + } + Ok(()) } } @@ -1003,6 +1574,7 @@ pub async fn read_partition_uuids( let root_part = part_path(&plan.disk, root_num); let swap_part = part_path(&plan.disk, swap_num); + // Şifreli kök: blkid'den LUKS UUID'sini al (crypttab için) let root_uuid = blkid_uuid(&root_part).await?; let swap_uuid = blkid_uuid(&swap_part).await?; let efi_uuid = if plan.efi_mb > 0 { @@ -1053,10 +1625,13 @@ impl Job for CustomPartitionJob { async fn run(&self, ui: &UiSender) -> Result<(), String> { let is_uefi = std::path::Path::new("/sys/firmware/efi").exists(); + // 0. Diski hazırla: bağlamaları ayır, kernel tablosunu temizle + prepare_disk(&self.disk, ui).await; + // 1. Bölüm tablosu oluştur let table_label = if is_uefi { "gpt" } else { "msdos" }; ui.log(format!("parted {} mktable {}", self.disk, table_label)); - run_cmd("parted", &["-s", &self.disk, "mktable", table_label]).await?; + run_parted(&["-s", &self.disk, "mktable", table_label]).await?; let mut next_mb: u64 = 1; // İlk MB boot sektörü @@ -1074,21 +1649,27 @@ impl Job for CustomPartitionJob { FsType::Fat32 => "fat32", FsType::Swap => "linux-swap", FsType::Ext4 => "ext4", + FsType::Ext3 => "ext3", FsType::Btrfs => "btrfs", FsType::Xfs => "xfs", + FsType::Ntfs => "ntfs", + // LVM PV için parted "ext4" ile işaretle, pvcreate sonra + FsType::Lvm => "ext4", + // LVM LV doğrudan parted ile oluşturulmaz + FsType::LvmLv => continue, }; ui.log(format!( "Bölüm {} oluşturuluyor: {} - {} ({})", part_num, start_str, end_str, fs_label )); - run_cmd("parted", &[ + run_parted(&[ "-s", &self.disk, "mkpart", "primary", fs_label, &start_str, &end_str ]).await?; if part.mountpoint == "/boot/efi" || part.fstype == FsType::Fat32 { - run_cmd("parted", &["-s", &self.disk, "set", &part_num.to_string(), "esp", "on"]).await?; + run_parted(&["-s", &self.disk, "set", &part_num.to_string(), "esp", "on"]).await?; } if part.size_mb > 0 { @@ -1096,14 +1677,49 @@ impl Job for CustomPartitionJob { } } - // 3. Biçimlendir + // 2.5. Tüm mkpart bitti; kernel'e bildir ve aygıt düğümlerini bekle + sync_partition_table(&self.disk).await; + + // 3. Biçimlendir (LUKS ile şifreleme desteklenir) for part in &self.custom_partitions { let path = &part.device; + // LUKS şifreleme (kök veya isteğe bağlı bölümler) + let fs_device = if part.encrypt && !part.luks_password.is_empty() { + let luks_name = if part.luks_name.is_empty() { + format!("crypt_{}", part.mountpoint.replace("/", "_")) + } else { + part.luks_name.clone() + }; + let luks_name = luks_name.trim_start_matches('_').to_string(); + + ui.log(format!("cryptsetup luksFormat {}…", path)); + let format_cmd = format!( + "echo -n '{}' | cryptsetup luksFormat -q --type luks2 {} --key-file=-", + part.luks_password, path + ); + let _ = tokio::process::Command::new("sh") + .args(["-c", &format_cmd]) + .output().await; + + ui.log(format!("cryptsetup open {} {}…", path, luks_name)); + let open_cmd = format!( + "echo -n '{}' | cryptsetup open {} {}", + part.luks_password, path, luks_name + ); + let _ = tokio::process::Command::new("sh") + .args(["-c", &open_cmd]) + .output().await; + + format!("/dev/mapper/{}", luks_name) + } else { + path.clone() + }; + match part.fstype { FsType::Fat32 => { - ui.log(format!("mkfs.fat -F32 {}", path)); - run_cmd("mkfs.fat", &["-F32", path]).await?; + ui.log(format!("mkfs.fat -F32 {}", fs_device)); + run_cmd("mkfs.fat", &["-F32", &fs_device]).await?; } FsType::Swap => { ui.log(format!("mkswap {}", path)); @@ -1112,17 +1728,31 @@ impl Job for CustomPartitionJob { run_cmd("swapon", &[path]).await?; } FsType::Ext4 => { - ui.log(format!("mkfs.ext4 -F {}", path)); - run_cmd("mkfs.ext4", &["-F", path]).await?; + ui.log(format!("mkfs.ext4 -F {}", fs_device)); + run_cmd("mkfs.ext4", &["-F", &fs_device]).await?; + } + FsType::Ext3 => { + ui.log(format!("mkfs.ext3 -F {}", fs_device)); + run_cmd("mkfs.ext3", &["-F", &fs_device]).await?; } FsType::Btrfs => { - ui.log(format!("mkfs.btrfs -f {}", path)); - run_cmd("mkfs.btrfs", &["-f", path]).await?; + ui.log(format!("mkfs.btrfs -f {}", fs_device)); + run_cmd("mkfs.btrfs", &["-f", &fs_device]).await?; } FsType::Xfs => { - ui.log(format!("mkfs.xfs -f {}", path)); - run_cmd("mkfs.xfs", &["-f", path]).await?; + ui.log(format!("mkfs.xfs -f {}", fs_device)); + run_cmd("mkfs.xfs", &["-f", &fs_device]).await?; } + FsType::Ntfs => { + ui.log(format!("mkfs.ntfs -f {}", fs_device)); + run_cmd("mkfs.ntfs", &["-f", &fs_device]).await?; + } + FsType::Lvm => { + ui.log(format!("pvcreate {}", path)); + run_cmd("pvcreate", &[path]).await?; + } + // LV ayrı bir job'da oluşturulur + FsType::LvmLv => {} } } @@ -1130,37 +1760,121 @@ impl Job for CustomPartitionJob { } } -/// Manuel bölümleri kök dizinden başlayarak hiyerarşik olarak hedef yola bağlayan Job. +/// LVM Volume Group ve Logical Volume'leri oluşturur. +pub struct SetupLvmJob { + pub volume_groups: Vec, + pub logical_volumes: Vec, +} + +#[async_trait::async_trait] +impl Job for SetupLvmJob { + fn name(&self) -> &str { "job_setup_lvm" } + + async fn run(&self, ui: &UiSender) -> Result<(), String> { + for vg in &self.volume_groups { + let pv_str = vg.pv_devices.join(" "); + ui.log(format!("vgcreate {} {}", vg.name, pv_str)); + run_cmd("vgcreate", &[&vg.name, &pv_str]).await?; + } + + for lv in &self.logical_volumes { + let size_str = if lv.size_mb == 0 { + "100%FREE".to_string() + } else { + format!("{}M", lv.size_mb) + }; + let lv_path = format!("/dev/{}/{}", lv.vg_name, lv.name); + ui.log(format!("lvcreate -L {} -n {} {}", size_str, lv.name, lv.vg_name)); + run_cmd("lvcreate", &["-L", &size_str, "-n", &lv.name, &lv.vg_name]).await?; + + let fs_cmd = match lv.fstype { + crate::installer::FsType::Ext4 => "mkfs.ext4", + crate::installer::FsType::Ext3 => "mkfs.ext3", + crate::installer::FsType::Btrfs => "mkfs.btrfs", + crate::installer::FsType::Xfs => "mkfs.xfs", + _ => continue, + }; + ui.log(format!("{} {}", fs_cmd, lv_path)); + run_cmd(fs_cmd, &[&lv_path]).await?; + } + Ok(()) + } +} + +/// Manuel bölümleri ve mantıksal hacimleri kök dizinden başlayarak hiyerarşik olarak hedef yola bağlayan Job. pub struct MountCustomPartitionsJob { pub custom_partitions: Vec, + pub logical_volumes: Vec, pub mount: String, } +impl MountCustomPartitionsJob { + /// Şifreli bölümler için mapper yolunu döndürür. + fn device_path(&self, part: &CustomPartition) -> String { + if part.encrypt { + let luks_name = if part.luks_name.is_empty() { + format!("crypt_{}", part.mountpoint.replace("/", "_").trim_start_matches('_')) + } else { + part.luks_name.clone() + }; + format!("/dev/mapper/{}", luks_name) + } else { + part.device.clone() + } + } +} + #[async_trait::async_trait] impl Job for MountCustomPartitionsJob { fn name(&self) -> &str { "job_mount_custom" } async fn run(&self, ui: &UiSender) -> Result<(), String> { - // 1. Kök (/) bölümünü bul ve bağla - let root_part = self.custom_partitions.iter() - .find(|p| p.mountpoint == "/") - .ok_or_else(|| "Kök (/) bölümü manuel listede bulunamadı.".to_string())?; + // 1. Kök (/) bölümünü (fiziksel partition veya LVM LV) bul ve bağla + let root_device = if let Some(root_part) = self.custom_partitions.iter().find(|p| p.mountpoint == "/") { + self.device_path(root_part) + } else if let Some(root_lv) = self.logical_volumes.iter().find(|lv| lv.mountpoint == "/") { + format!("/dev/{}/{}", root_lv.vg_name, root_lv.name) + } else { + return Err("Kök (/) bölümü manuel listede veya mantıksal bölümlerde bulunamadı.".to_string()); + }; - ui.log(format!("Kök dizin bağlanıyor: {} ➔ {}", root_part.device, self.mount)); - run_cmd("mount", &[&root_part.device, &self.mount]).await?; + ui.log(format!("Kök dizin bağlanıyor: {} ➔ {}", root_device, self.mount)); + run_cmd("mount", &[&root_device, &self.mount]).await?; - // 2. Diğer bölümleri sırala ve bağla (derinliğe göre artan sırada) - let mut other_parts: Vec<_> = self.custom_partitions.iter() - .filter(|p| p.mountpoint != "/" && p.fstype != FsType::Swap && !p.mountpoint.is_empty()) - .collect(); - other_parts.sort_by_key(|p| p.mountpoint.len()); + // 2. Diğer bölümleri ve mantıksal hacimleri topla, sırala ve bağla (derinliğe göre artan sırada) + struct MountItem { + device: String, + mountpoint: String, + } - for part in other_parts { - let target_path = format!("{}{}", self.mount, part.mountpoint); + let mut mount_items = Vec::new(); + + for part in &self.custom_partitions { + if part.mountpoint != "/" && part.fstype != FsType::Swap && !part.mountpoint.is_empty() { + mount_items.push(MountItem { + device: self.device_path(part), + mountpoint: part.mountpoint.clone(), + }); + } + } + + for lv in &self.logical_volumes { + if lv.mountpoint != "/" && lv.fstype != FsType::Swap && !lv.mountpoint.is_empty() { + mount_items.push(MountItem { + device: format!("/dev/{}/{}", lv.vg_name, lv.name), + mountpoint: lv.mountpoint.clone(), + }); + } + } + + mount_items.sort_by_key(|item| item.mountpoint.len()); + + for item in mount_items { + let target_path = format!("{}{}", self.mount, item.mountpoint); ui.log(format!("Bağlama noktası dizini oluşturuluyor: {}", target_path)); run_cmd("mkdir", &["-p", &target_path]).await?; - ui.log(format!("Bağlanıyor: {} ➔ {}", part.device, target_path)); - run_cmd("mount", &[&part.device, &target_path]).await?; + ui.log(format!("Bağlanıyor: {} ➔ {}", item.device, target_path)); + run_cmd("mount", &[&item.device, &target_path]).await?; } Ok(()) @@ -1170,6 +1884,7 @@ impl Job for MountCustomPartitionsJob { /// Manuel bölümleme planına göre fstab dosyası üreten Job. pub struct FstabFromCustomPartitionsJob { pub custom_partitions: Vec, + pub logical_volumes: Vec, pub mount: String, } @@ -1186,16 +1901,37 @@ impl Job for FstabFromCustomPartitionsJob { # \n\n" ); + let mut crypttab = String::from( + "# /etc/crypttab — Yali tarafından otomatik oluşturuldu\n" + ); + for part in &self.custom_partitions { if part.mountpoint.is_empty() { continue; } - ui.log(format!("{} için UUID okunuyor…", part.device)); - let uuid = blkid_uuid(&part.device).await?; + let is_encrypted = part.encrypt && !part.luks_password.is_empty(); + + let device_field = if is_encrypted { + let luks_uuid = blkid_uuid(&part.device).await?; + let luks_name = if part.luks_name.is_empty() { + format!("crypt_{}", part.mountpoint.replace("/", "_").trim_start_matches('_')) + } else { + part.luks_name.clone() + }; + crypttab.push_str(&format!( + "{} UUID={} none luks\n", + luks_name, luks_uuid + )); + format!("/dev/mapper/{:<30}", luks_name) + } else { + ui.log(format!("{} için UUID okunuyor…", part.device)); + let uuid = blkid_uuid(&part.device).await?; + format!("UUID={:<36}", uuid) + }; let (mp, options, dump, pass) = match part.fstype { FsType::Swap => ("none", "sw", "0", "0"), FsType::Fat32 => ("/boot/efi", "umask=0077", "0", "2"), - FsType::Ext4 if part.mountpoint == "/" => ("/", "defaults,noatime", "0", "1"), + FsType::Ext4 | FsType::Ext3 if part.mountpoint == "/" => ("/", "defaults,noatime", "0", "1"), _ => { let pass_val = if part.mountpoint == "/" { "1" } else { "2" }; (part.mountpoint.as_str(), "defaults,noatime", "0", pass_val) @@ -1204,15 +1940,51 @@ impl Job for FstabFromCustomPartitionsJob { let fs_name = match part.fstype { FsType::Ext4 => "ext4", + FsType::Ext3 => "ext3", FsType::Btrfs => "btrfs", FsType::Xfs => "xfs", FsType::Fat32 => "vfat", + FsType::Ntfs => "ntfs-3g", FsType::Swap => "swap", + FsType::Lvm | FsType::LvmLv => "ext4", }; fstab.push_str(&format!( - "UUID={:<36} {:<12} {:<6} {:<18} {} {}\n", - uuid, mp, fs_name, options, dump, pass + "{} {:<12} {:<6} {:<18} {} {}\n", + device_field, mp, fs_name, options, dump, pass + )); + } + + // LVM mantıksal bölümlerini fstab'a ekle + for lv in &self.logical_volumes { + if lv.mountpoint.is_empty() { continue; } + + let device_field = format!("/dev/{}/{}", lv.vg_name, lv.name); + + let (mp, options, dump, pass) = match lv.fstype { + FsType::Swap => ("none", "sw", "0", "0"), + FsType::Fat32 => ("/boot/efi", "umask=0077", "0", "2"), + FsType::Ext4 | FsType::Ext3 if lv.mountpoint == "/" => ("/", "defaults,noatime", "0", "1"), + _ => { + let pass_val = if lv.mountpoint == "/" { "1" } else { "2" }; + (lv.mountpoint.as_str(), "defaults,noatime", "0", pass_val) + } + }; + + let fs_name = match lv.fstype { + FsType::Ext4 => "ext4", + FsType::Ext3 => "ext3", + FsType::Btrfs => "btrfs", + FsType::Xfs => "xfs", + FsType::Fat32 => "vfat", + FsType::Ntfs => "ntfs-3g", + FsType::Swap => "swap", + _ => "ext4", + }; + + fstab.push_str(&format!( + "{:<42} {:<12} {:<6} {:<18} {} {}\n", + device_field, mp, fs_name, options, dump, pass )); } @@ -1220,6 +1992,14 @@ impl Job for FstabFromCustomPartitionsJob { .await .map_err(|e| format!("fstab yazılamadı: {}", e))?; + // crypttab (sadece şifreli bölümler için) + let crypttab_device_count = crypttab.lines().count().saturating_sub(1); // header satırını sayma + if crypttab_device_count > 0 { + let crypttab_path = format!("{}/etc/crypttab", self.mount); + ui.log(format!("Yazılıyor: {}", crypttab_path)); + let _ = tokio::fs::write(&crypttab_path, crypttab).await; + } + Ok(()) } } @@ -1228,86 +2008,72 @@ impl Job for FstabFromCustomPartitionsJob { pub fn build_custom_job_queue( disk: String, custom_partitions: Vec, - mount: &str, - source: &str, - locale: &str, - timezone: &str, - username: &str, - password: &str, - hostname: &str, - kb_layout: &str, - kb_variant: &str, - demo_mode: bool, + volume_groups: Vec, + logical_volumes: Vec, + cfg: JobQueueConfig, ) -> JobQueue { - DEMO_MODE.store(demo_mode, std::sync::atomic::Ordering::Relaxed); + DEMO_MODE.store(cfg.demo_mode, std::sync::atomic::Ordering::Relaxed); - let is_uefi = std::path::Path::new("/sys/firmware/efi").exists(); - let mt = mount.to_string(); + let is_uefi = std::path::Path::new("/sys/firmware/efi").exists(); + let encrypt_root = custom_partitions.iter().any(|p| p.mountpoint == "/" && p.encrypt); + let mt = cfg.mount.clone(); - // Kaynak dizini: parametre boşsa otomatik tespit et - let src = if source.is_empty() { detect_source_dir() } else { source.to_string() }; + let src = if cfg.source.is_empty() { detect_source_dir() } else { cfg.source }; - let jobs: Vec> = vec![ - // 1. Bölümlendir ve Biçimlendir + let mut jobs: Vec> = vec![ Box::new(CustomPartitionJob { disk: disk.clone(), custom_partitions: custom_partitions.clone() }), - - // 2. Bağla - Box::new(MountCustomPartitionsJob { custom_partitions: custom_partitions.clone(), mount: mt.clone() }), - - // 3. /proc /sys /dev bind-mount + Box::new(SetupLvmJob { volume_groups: volume_groups.clone(), logical_volumes: logical_volumes.clone() }), + Box::new(MountCustomPartitionsJob { custom_partitions: custom_partitions.clone(), logical_volumes: logical_volumes.clone(), mount: mt.clone() }), Box::new(MountBindJob { mount: mt.clone() }), - - // 4. Kopyala Box::new(CopyFilesJob { source: src, target: mt.clone() }), - - // 5. UUID oku + fstab yaz - Box::new(FstabFromCustomPartitionsJob { custom_partitions: custom_partitions.clone(), mount: mt.clone() }), - - // 6. Hostname - Box::new(ConfigureHostnameJob { - mount: mt.clone(), - hostname: hostname.to_string(), + Box::new(FstabFromCustomPartitionsJob { custom_partitions: custom_partitions.clone(), logical_volumes: logical_volumes.clone(), mount: mt.clone() }), + Box::new(ConfigureHostnameJob { mount: mt.clone(), hostname: cfg.hostname.clone() }), + Box::new(ConfigureLocaleJob { mount: mt.clone(), locale: cfg.locale.clone() }), + Box::new(ConfigureTimezoneJob { mount: mt.clone(), timezone: cfg.timezone.clone() }), + Box::new(ConfigureDateTimeJob { + mount: mt.clone(), + use_ntp: cfg.use_ntp, + year: cfg.manual_year, + month: cfg.manual_month, + day: cfg.manual_day, + hour: cfg.manual_hour, + minute: cfg.manual_minute, }), - - // 7. Locale - Box::new(ConfigureLocaleJob { - mount: mt.clone(), - locale: locale.to_string(), - }), - - // 8. Zaman dilimi - Box::new(ConfigureTimezoneJob { - mount: mt.clone(), - timezone: timezone.to_string(), - }), - - // 9. Klavye - Box::new(ConfigureKeyboardJob { - mount: mt.clone(), - layout: kb_layout.to_string(), - variant: kb_variant.to_string(), - }), - - // 10. Kullanıcı - Box::new(CreateUserJob { - mount: mt.clone(), - username: username.to_string(), - password: password.to_string(), - }), - - // 11. initramfs - Box::new(GenerateInitramfsJob { mount: mt.clone() }), - - // 12. GRUB - Box::new(InstallGrubJob { disk, mount: mt.clone(), is_uefi }), - - // 13. COMAR - Box::new(RunComarJob { mount: mt.clone() }), - - // 14. Bind-mount temizle - Box::new(UnmountBindJob { mount: mt.clone() }), + Box::new(ConfigureKeyboardJob { mount: mt.clone(), layout: cfg.kb_layout.clone(), variant: cfg.kb_variant.clone() }), + Box::new(CreateUserJob { mount: mt.clone(), username: cfg.username.clone(), password: cfg.password.clone(), root_password: cfg.root_password.clone() }), + Box::new(GenerateInitramfsJob { mount: mt.clone(), encrypt_root, lvm_active: !volume_groups.is_empty() }), ]; + jobs.push(Box::new(InstallGrubJob { + disk: disk.clone(), + mount: mt.clone(), + is_uefi, + boot_device: cfg.boot_device.clone(), + timeout: cfg.bootloader_timeout, + password: cfg.bootloader_password.clone(), + kernel_options: cfg.kernel_options.clone(), + encrypt_root, + })); + + jobs.push(Box::new(ConfigureDisplayManagerJob { + mount: mt.clone(), + display_manager: cfg.display_manager.clone(), + autologin: cfg.autologin, + username: cfg.username.clone(), + desktop_environment: cfg.desktop_environment.clone(), + })); + jobs.push(Box::new(RunComarJob { mount: mt.clone() })); + jobs.push(Box::new(RunLdconfigJob { mount: mt.clone() })); + jobs.push(Box::new(UpdateEnvironmentJob { mount: mt.clone() })); + if !cfg.selected_package_groups.is_empty() { + jobs.push(Box::new(InstallExtraPackagesJob { + mount: mt.clone(), + package_groups: cfg.selected_package_groups.clone(), + })); + } + jobs.push(Box::new(CleanupLiveJob { mount: mt.clone() })); + jobs.push(Box::new(UnmountBindJob { mount: mt.clone() })); + JobQueue::new(jobs) } diff --git a/src/main.rs b/src/main.rs index 23305bd..09c17c2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,9 +3,10 @@ mod steps; mod jobs; mod ui; mod autoinstall; +mod branding; use installer::{GlobalState, InstallerStep, WelcomeStep, LocationStep}; -use steps::{KeyboardStep, UsersStep, SummaryStep, ExecutionStep, FinishStep, PartitionStep}; +use steps::{KeyboardStep, UsersStep, SummaryStep, ExecutionStep, FinishStep, PartitionStep, BootloaderStep, LicenseStep, NetworkStep, DisplayManagerStep}; use eframe::egui; use rust_i18n::t; @@ -32,14 +33,14 @@ fn main() -> eframe::Result<()> { fn run_gui(demo_mode: bool) -> eframe::Result<()> { let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default() - .with_inner_size([1100.0, 760.0]) - .with_min_inner_size([900.0, 650.0]) - .with_title(t!("installer").to_string()), + .with_inner_size([1200.0, 900.0]) + .with_min_inner_size([1080.0, 720.0]) + .with_title(t!("installer")), ..Default::default() }; eframe::run_native( - &t!("installer").to_string(), + t!("installer").as_ref(), options, Box::new(move |cc| { // Görsel yükleyicileri etkinleştir (PNG desteği) @@ -88,17 +89,19 @@ fn run_auto_install(path: &str, demo_mode: bool) -> eframe::Result<()> { } if !autoinstall::checker::all_passed(&checks) { - eprintln!("{}: {}", t!("error"), "Critical check error. Installation stopped."); + eprintln!("{}: Critical check error. Installation stopped.", t!("error")); std::process::exit(2); } // State'i hazırla - let mut state = GlobalState::default(); - state.demo_mode = demo_mode; + let mut state = GlobalState { + demo_mode, + ..Default::default() + }; apply_to_state(&af, &mut state); if state.partition_plan.is_none() { - eprintln!("{}: {}", t!("error"), format!("{}: {}", t!("partition_plan_could_not_be_created"), af.install.disk)); + eprintln!("{}: {}: {}", t!("error"), t!("partition_plan_could_not_be_created"), af.install.disk); std::process::exit(3); } @@ -122,13 +125,9 @@ fn run_auto_install(path: &str, demo_mode: bool) -> eframe::Result<()> { use jobs::{InstallMessage, UiSender}; // CLI modunda egui Context yok; basit bir mpsc kanal kullanıyoruz. - // UiSender'a dummy bir context gerekeceğinden terminal çıktısı için - // doğrudan kanalı dinliyoruz. let (tx, rx) = mpsc::channel::(); // Arka plan iş parçacığı — gerçek job kuyruğunu çalıştırır. - // egui::Context::default() CLI modunda request_repaint()'i no-op yapar; - // mesajlar mpsc kanalı üzerinden terminale aktarılır. let cli_ctx = eframe::egui::Context::default(); let ui_sender = UiSender::new(tx, cli_ctx); @@ -147,16 +146,33 @@ fn run_auto_install(path: &str, demo_mode: bool) -> eframe::Result<()> { let rt = tokio::runtime::Runtime::new().expect("tokio runtime"); let mut queue = jobs::build_full_job_queue( plan_c, - &mount_c, - &source_c, - &locale_c, - &timezone_c, - &username_c, - &password_c, - &hostname_c, - &kb_layout_c, - &kb_variant_c, - demo_mode, + jobs::JobQueueConfig { + mount: mount_c, + source: source_c, + locale: locale_c, + timezone: timezone_c, + username: username_c, + password: password_c, + hostname: hostname_c, + kb_layout: kb_layout_c, + kb_variant: kb_variant_c, + demo_mode, + boot_device: String::new(), + bootloader_timeout: 5, + bootloader_password: String::new(), + kernel_options: String::new(), + use_ntp: true, + root_password: String::new(), + display_manager: "sddm".to_string(), + autologin: true, + desktop_environment: "plasma".to_string(), + selected_package_groups: Vec::new(), + manual_year: 2026, + manual_month: 5, + manual_day: 26, + manual_hour: 12, + manual_minute: 0, + }, ); rt.block_on(queue.run_all(ui_sender)); }); @@ -182,32 +198,69 @@ fn run_auto_install(path: &str, demo_mode: bool) -> eframe::Result<()> { } }); - // eframe::Result döndürmek için Ok Ok(()) } struct YaliApp { - state: GlobalState, - steps: Vec>, - last_step: usize, + state: GlobalState, + steps: Vec>, + rescue_step: Option>, + last_step: usize, + branding: branding::BrandingConfig, + loaded_language: String, } impl Default for YaliApp { fn default() -> Self { + let default_lang = "tr".to_string(); + let branding = branding::BrandingConfig::load_for_lang(&default_lang); let mut app = Self { state: GlobalState::default(), steps: vec![ Box::new(WelcomeStep::default()), Box::new(LocationStep::default()), + Box::new(LicenseStep), Box::new(KeyboardStep::default()), + Box::new(NetworkStep::default()), Box::new(PartitionStep::default()), Box::new(UsersStep::default()), + Box::new(BootloaderStep::default()), + Box::new(DisplayManagerStep::default()), Box::new(SummaryStep), Box::new(ExecutionStep::default()), Box::new(FinishStep), ], + rescue_step: Some(Box::new(steps::RescueStep::default())), last_step: usize::MAX, + branding: branding.clone(), + loaded_language: String::new(), }; + + // OEM modu: varsayılan değerleri ata ve step engelleme + if branding.oem.enabled { + app.state.oem_mode = true; + if !branding.oem.default_username.is_empty() { + app.state.username = branding.oem.default_username.clone(); + app.state.hostname = branding.oem.default_hostname.clone(); + } + if !branding.oem.default_password.is_empty() { + app.state.password = branding.oem.default_password.clone(); + app.state.password_confirm = branding.oem.default_password.clone(); + } + // OEM ön tanımlı disk planı varsa? + } + + // Branding görsel verilerini GlobalState'e enjekte et + app.state.branding_yali_dark = branding.yali_logo_uri(); + app.state.branding_yali_light = branding.yali_logo_uri(); + app.state.branding_pisi_dark = branding.pisi_logo_dark_uri(); + app.state.branding_pisi_light = branding.pisi_logo_light_uri(); + app.state.branding_icons = branding.slides.icons + .iter() + .map(|(k, v)| (k.clone(), branding::path_to_file_uri(v))) + .collect(); + app.state.branding_slides = branding.slides.items.clone(); + app.steps[0].on_enter(&mut app.state); app.last_step = 0; app @@ -218,6 +271,45 @@ impl eframe::App for YaliApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { rust_i18n::set_locale(&self.state.language); + // Dil değişimi kontrolü -> branding dosyasını dinamik olarak yeniden yükle + if self.loaded_language != self.state.language { + self.loaded_language = self.state.language.clone(); + let branding = branding::BrandingConfig::load_for_lang(&self.loaded_language); + self.branding = branding.clone(); + self.state.branding_yali_dark = branding.yali_logo_uri(); + self.state.branding_yali_light = branding.yali_logo_uri(); + self.state.branding_pisi_dark = branding.pisi_logo_dark_uri(); + self.state.branding_pisi_light = branding.pisi_logo_light_uri(); + self.state.branding_icons = branding.slides.icons + .iter() + .map(|(k, v)| (k.clone(), branding::path_to_file_uri(v))) + .collect(); + self.state.branding_slides = branding.slides.items.clone(); + + // Pencere başlığını dinamik olarak güncelle + ctx.send_viewport_cmd(egui::ViewportCommand::Title(self.branding.general.title.clone())); + } + + // Rescue modu: tam ekran, sidebar yok, navigasyon yok + if self.state.rescue_mode { + if let Some(rescue) = &mut self.rescue_step { + egui::CentralPanel::default() + .frame(egui::Frame::none() + .fill(ui::theme::c_bg_dark()) + .inner_margin(egui::Margin::same(20.0))) + .show(ctx, |ui| { + egui::ScrollArea::vertical().show(ui, |ui| { + rescue.show(ui, &mut self.state); + }); + ui.add_space(12.0); + if ui.button(t!("rescue_return")).clicked() { + self.state.rescue_mode = false; + } + }); + } + return; + } + let cur = self.state.current_step; let n_steps = self.steps.len(); let is_exec = cur == n_steps - 2; @@ -238,31 +330,31 @@ impl eframe::App for YaliApp { .show(ctx, |ui| { ui.add_space(20.0); - // PisiLinux logo + // Dağıtım logosu — branding.toml'dan dinamik yüklenir ui.vertical_centered(|ui| { - let logo = if self.state.is_dark { - egui::include_image!("../assets/pisi-logo-dark.png") + let logo_uri = if self.state.is_dark { + &self.state.branding_yali_dark } else { - egui::include_image!("../assets/pisi-logo-light.png") + &self.state.branding_yali_light }; - ui.add( - egui::Image::new(logo) - .max_width(120.0) - .rounding(egui::Rounding::same(8.0)), - ); + if !logo_uri.is_empty() { + ui.add( + egui::Image::from_uri(logo_uri.as_str()) + .max_width(120.0) + .rounding(egui::Rounding::same(8.0)), + ); + } ui.add_space(8.0); ui.label( - egui::RichText::new(t!("os_name")) + egui::RichText::new(t!("app_name")) .strong() .size(16.0) .color(ui::theme::c_text_dim()), ); - // get desktop environment - let desktop = std::env::var("XDG_CURRENT_DESKTOP").unwrap_or_default(); - ui.label( - egui::RichText::new(&format!("{} Desktop", desktop)) - .size(14.0) - .color(ui::theme::c_text_dim()), + + ui.colored_label( + egui::Color32::from_rgb(60, 60, 90), + format!("{}: {}", t!("version"), env!("CARGO_PKG_VERSION")), ); }); @@ -305,15 +397,10 @@ impl eframe::App for YaliApp { ui.add_space(2.0); } - // Alt kısım: tema değiştirme + sürüm bilgisi + // Alt kısım: tema değiştirme ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| { - ui.add_space(12.0); - ui.colored_label( - egui::Color32::from_rgb(60, 60, 90), - format!("YALI (v{})", env!("CARGO_PKG_VERSION")), - ); ui.add_space(6.0); - // Tema değiştirme butonu + let theme_icon = if self.state.is_dark { "☀" } else { "🌙" }; let theme_label = if self.state.is_dark { "Light" } else { "Dark" }; if ui.add( @@ -341,11 +428,8 @@ impl eframe::App for YaliApp { ui.horizontal(|ui| { ui.add_space(8.0); - // İptal — kurulum ve bitiş ekranında gizle - if !is_exec && !is_finish { - if ui.add(ui::theme::secondary_button(&t!("cancel"))).clicked() { - ctx.send_viewport_cmd(egui::ViewportCommand::Close); - } + if !is_exec && !is_finish && ui.add(ui::theme::secondary_button(&t!("cancel"))).clicked() { + ctx.send_viewport_cmd(egui::ViewportCommand::Close); } ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { @@ -357,7 +441,6 @@ impl eframe::App for YaliApp { .unwrap_or(false); if !is_finish { - // İleri / Tamamlandı butonu if !is_exec || is_valid { let next_label = if is_exec { t!("finish_next") @@ -367,7 +450,6 @@ impl eframe::App for YaliApp { let btn = if is_valid { ui::theme::primary_button(&next_label) } else { - // Devre dışı görünüm için secondary kullan ui::theme::secondary_button(&next_label) }; if ui.add_enabled(is_valid, btn).clicked() { @@ -375,7 +457,6 @@ impl eframe::App for YaliApp { } } - // Geri — kurulum ekranında gizle if cur > 0 && !is_exec { ui.add_space(8.0); if ui.add(ui::theme::secondary_button(&t!("back"))).clicked() { @@ -403,4 +484,4 @@ impl eframe::App for YaliApp { }); }); } -} +} \ No newline at end of file diff --git a/src/steps/bootloader.rs b/src/steps/bootloader.rs new file mode 100644 index 0000000..c903b1e --- /dev/null +++ b/src/steps/bootloader.rs @@ -0,0 +1,111 @@ +use eframe::egui; +use rust_i18n::t; +use crate::installer::{GlobalState, InstallerStep}; + +#[derive(Default)] +pub struct BootloaderStep { + show_password: bool, + timeout_str: String, +} + +impl InstallerStep for BootloaderStep { + fn name(&self) -> String { + t!("bootloader").to_string() + } + + fn on_enter(&mut self, state: &mut GlobalState) { + if self.timeout_str.is_empty() { + self.timeout_str = state.bootloader_timeout.to_string(); + } + if state.bootloader_device.is_empty() { + if let Some(ref disk) = state.selected_disk { + state.bootloader_device = disk.clone(); + } + } + } + + fn show(&mut self, ui: &mut egui::Ui, state: &mut GlobalState) { + ui.heading(t!("bootloader_title")); + ui.label(t!("bootloader_description")); + ui.add_space(12.0); + + egui::Grid::new("bootloader_grid") + .num_columns(2) + .spacing([12.0, 10.0]) + .show(ui, |ui| { + // Hedef aygıt + ui.label(t!("bootloader_device_label")); + ui.horizontal(|ui| { + let mut device_list: Vec = state.available_disks.iter() + .map(|d| d.name.clone()) + .collect(); + if device_list.is_empty() { + device_list.push(state.bootloader_device.clone()); + } + let current = if state.bootloader_device.is_empty() { + device_list[0].clone() + } else { + state.bootloader_device.clone() + }; + egui::ComboBox::from_id_source("boot_device_select") + .selected_text(¤t) + .show_ui(ui, |ui| { + for dev in &device_list { + ui.selectable_value( + &mut state.bootloader_device, + dev.clone(), + dev, + ); + } + }); + ui.label( + egui::RichText::new(t!("bootloader_device_hint")) + .size(11.0) + .color(egui::Color32::from_rgb(120, 120, 140)), + ); + }); + ui.end_row(); + + // Timeout + ui.label(t!("bootloader_timeout_label")); + ui.horizontal(|ui| { + ui.add(egui::Slider::new(&mut state.bootloader_timeout, 0..=60) + .text(t!("bootloader_timeout_suffix"))); + }); + ui.end_row(); + + // GRUB şifresi + ui.label(t!("bootloader_password_label")); + ui.horizontal(|ui| { + if self.show_password { + ui.text_edit_singleline(&mut state.bootloader_password); + } else { + ui.add(egui::TextEdit::singleline(&mut state.bootloader_password).password(true)); + } + if ui.small_button(if self.show_password { "🙈" } else { "👁" }).clicked() { + self.show_password = !self.show_password; + } + if !state.bootloader_password.is_empty() { + ui.colored_label(egui::Color32::from_rgb(200, 180, 40), "⚠"); + } + }); + ui.end_row(); + + // Kernel parametreleri + ui.label(t!("bootloader_kernel_label")); + ui.horizontal(|ui| { + ui.text_edit_singleline(&mut state.kernel_options); + ui.label( + egui::RichText::new(t!("bootloader_kernel_hint")) + .size(11.0) + .color(egui::Color32::from_rgb(120, 120, 140)), + ); + }); + ui.end_row(); + }); + } + + fn is_complete(&self, state: &GlobalState) -> bool { + !state.bootloader_device.is_empty() + } +} diff --git a/src/steps/display_manager.rs b/src/steps/display_manager.rs new file mode 100644 index 0000000..92decef --- /dev/null +++ b/src/steps/display_manager.rs @@ -0,0 +1,86 @@ +use eframe::egui; +use rust_i18n::t; +use crate::installer::{GlobalState, InstallerStep}; + +const DM_OPTIONS: &[&str] = &["sddm", "lightdm", "gdm"]; + +#[derive(Default)] +pub struct DisplayManagerStep; + +impl InstallerStep for DisplayManagerStep { + fn name(&self) -> String { + t!("display_manager").to_string() + } + + fn on_enter(&mut self, state: &mut GlobalState) { + let desktop = std::env::var("XDG_CURRENT_DESKTOP") + .unwrap_or_default() + .trim() + .to_lowercase(); + if !desktop.is_empty() { + state.desktop_environment = desktop; + } else if state.desktop_environment.is_empty() { + state.desktop_environment = "plasma".to_string(); + } + } + + fn show(&mut self, ui: &mut egui::Ui, state: &mut GlobalState) { + ui.heading(t!("display_manager_title")); + ui.label(t!("display_manager_description")); + ui.add_space(12.0); + + egui::Grid::new("dm_grid") + .num_columns(2) + .spacing([12.0, 10.0]) + .show(ui, |ui| { + // Ekran yöneticisi + ui.label(t!("display_manager_select")); + egui::ComboBox::from_id_source("dm_combo") + .selected_text(state.display_manager.as_str()) + .show_ui(ui, |ui| { + for dm in DM_OPTIONS { + ui.selectable_value( + &mut state.display_manager, + dm.to_string(), + *dm, + ); + } + }); + ui.end_row(); + + // Masaüstü ortamı (otomatik algılanır) + ui.label(t!("display_manager_de")); + if !state.desktop_environment.is_empty() && state.desktop_environment != "default" { + ui.colored_label(egui::Color32::from_rgb(120, 180, 120), &state.desktop_environment); + } else { + ui.colored_label(egui::Color32::from_rgb(200, 160, 40), t!("display_manager_de_not_found")); + } + ui.end_row(); + + // Otomatik oturum açma + ui.label(t!("display_manager_autologin")); + ui.checkbox(&mut state.autologin, ""); + ui.end_row(); + + if state.autologin { + ui.label(t!("display_manager_autologin_user")); + ui.horizontal(|ui| { + let name = if !state.username.is_empty() { + state.username.clone() + } else { + t!("display_manager_autologin_hint").to_string() + }; + ui.colored_label(egui::Color32::from_rgb(120, 180, 120), name); + }); + ui.end_row(); + } + }); + + ui.add_space(8.0); + ui.label(egui::RichText::new(t!("display_manager_note")).size(11.0).color(egui::Color32::from_rgb(140, 140, 160))); + } + + fn is_complete(&self, _state: &GlobalState) -> bool { + true + } +} diff --git a/src/steps/execution.rs b/src/steps/execution.rs index c02b411..4375033 100644 --- a/src/steps/execution.rs +++ b/src/steps/execution.rs @@ -15,6 +15,7 @@ use crate::jobs::{self, InstallMessage, UiSender}; use crate::ui::{ErrorScreen, Slideshow}; use crate::ui::theme; +#[derive(Default)] pub struct ExecutionStep { rx: Option>, tx: Option>, @@ -23,18 +24,6 @@ pub struct ExecutionStep { slideshow: Option, } -impl Default for ExecutionStep { - fn default() -> Self { - Self { - rx: None, - tx: None, - finished: false, - error_screen: None, - slideshow: None, - } - } -} - impl InstallerStep for ExecutionStep { fn name(&self) -> String { t!("execution").to_string() } @@ -48,7 +37,7 @@ impl InstallerStep for ExecutionStep { let desktop = std::env::var("XDG_CURRENT_DESKTOP") .unwrap_or_else(|_| "default".to_string()) .to_lowercase(); - self.slideshow = Some(Slideshow::new(&desktop)); + self.slideshow = Some(Slideshow::new(&desktop, state.branding_icons.clone(), state.branding_slides.clone())); state.install_progress = 0.0; state.install_log.clear(); @@ -70,8 +59,26 @@ impl InstallerStep for ExecutionStep { let ctx = ui.ctx().clone(); let erase_disk = state.erase_disk; let custom_partitions = state.custom_partitions.clone(); + let volume_groups = state.volume_groups.clone(); + let logical_volumes = state.logical_volumes.clone(); let selected_disk = state.selected_disk.clone(); + let boot_device = state.bootloader_device.clone(); + let bootloader_timeout = state.bootloader_timeout; + let bootloader_password = state.bootloader_password.clone(); + let kernel_options = state.kernel_options.clone(); + let use_ntp = state.use_ntp; + let root_password = state.root_password.clone(); + let display_manager = state.display_manager.clone(); + let autologin = state.autologin; + let desktop_environment = state.desktop_environment.clone(); + let selected_package_groups = state.selected_package_groups.clone(); + let manual_year = state.manual_year; + let manual_month = state.manual_month; + let manual_day = state.manual_day; + let manual_hour = state.manual_hour; + let manual_minute = state.manual_minute; + let ui_sender = UiSender::new(tx, ctx); thread::spawn(move || { @@ -86,11 +93,34 @@ impl InstallerStep for ExecutionStep { if erase_disk { if let Some(p) = plan { let mut queue = jobs::build_full_job_queue( - p, "/mnt", &src, - &locale, &timezone, - &username, &password, &hostname, - &kb_layout, &kb_variant, - demo_mode, + p, + jobs::JobQueueConfig { + mount: "/mnt".to_string(), + source: src, + locale, + timezone, + username, + password, + hostname, + kb_layout, + kb_variant, + demo_mode, + boot_device, + bootloader_timeout, + bootloader_password, + kernel_options, + use_ntp, + root_password, + display_manager: display_manager.clone(), + autologin, + desktop_environment: desktop_environment.clone(), + selected_package_groups: selected_package_groups.clone(), + manual_year, + manual_month, + manual_day, + manual_hour, + manual_minute, + }, ); rt.block_on(queue.run_all(ui_sender)); } else { @@ -98,14 +128,39 @@ impl InstallerStep for ExecutionStep { t!("error_partition_plan_not_found").to_string() )); } - } else { - if let Some(disk) = selected_disk { + } else if let Some(disk) = selected_disk { let mut queue = jobs::build_custom_job_queue( - disk, custom_partitions, "/mnt", &src, - &locale, &timezone, - &username, &password, &hostname, - &kb_layout, &kb_variant, - demo_mode, + disk, + custom_partitions, + volume_groups, + logical_volumes, + jobs::JobQueueConfig { + mount: "/mnt".to_string(), + source: src, + locale, + timezone, + username, + password, + hostname, + kb_layout, + kb_variant, + demo_mode, + boot_device, + bootloader_timeout, + bootloader_password, + kernel_options, + use_ntp, + root_password, + display_manager: display_manager.clone(), + autologin, + desktop_environment: desktop_environment.clone(), + selected_package_groups: selected_package_groups.clone(), + manual_year, + manual_month, + manual_day, + manual_hour, + manual_minute, + }, ); rt.block_on(queue.run_all(ui_sender)); } else { @@ -113,7 +168,6 @@ impl InstallerStep for ExecutionStep { t!("error_target_disk_not_selected").to_string() )); } - } }); } diff --git a/src/steps/license.rs b/src/steps/license.rs new file mode 100644 index 0000000..387dcdc --- /dev/null +++ b/src/steps/license.rs @@ -0,0 +1,52 @@ +use eframe::egui; +use rust_i18n::t; +use crate::installer::{GlobalState, InstallerStep}; + +#[derive(Default)] +pub struct LicenseStep; + +const GPL_TEXT: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/LICENSE")); + +impl InstallerStep for LicenseStep { + fn name(&self) -> String { + t!("license").to_string() + } + + fn show(&mut self, ui: &mut egui::Ui, state: &mut GlobalState) { + ui.heading(t!("license_title")); + ui.label(t!("license_description")); + ui.add_space(8.0); + + let avail = ui.available_height(); + egui::ScrollArea::vertical() + .max_height(avail - 60.0) + .stick_to_bottom(true) + .auto_shrink([false; 2]) + .show(ui, |ui| { + egui::Frame::group(ui.style()) + .inner_margin(egui::Margin::same(12.0)) + .show(ui, |ui| { + ui.label( + egui::RichText::new(GPL_TEXT) + .size(14.0) + .family(egui::FontFamily::Monospace) + .color(egui::Color32::from_rgb(180, 180, 200)), + ); + }); + }); + + ui.add_space(10.0); + ui.horizontal(|ui| { + ui.add(egui::Checkbox::new(&mut state.license_accepted, "")); + ui.label( + egui::RichText::new(t!("license_accept")) + .size(14.0) + .strong(), + ); + }); + } + + fn is_complete(&self, state: &GlobalState) -> bool { + state.license_accepted + } +} diff --git a/src/steps/mod.rs b/src/steps/mod.rs index 0a36d04..06f282b 100644 --- a/src/steps/mod.rs +++ b/src/steps/mod.rs @@ -4,6 +4,12 @@ pub mod summary; pub mod execution; pub mod finish; pub mod partition; +pub mod bootloader; +pub mod license; +pub mod network; +pub mod rescue; +pub mod display_manager; +pub mod netinstall; pub use keyboard::KeyboardStep; pub use users::UsersStep; @@ -11,3 +17,8 @@ pub use summary::SummaryStep; pub use execution::ExecutionStep; pub use finish::FinishStep; pub use partition::PartitionStep; +pub use bootloader::BootloaderStep; +pub use license::LicenseStep; +pub use network::NetworkStep; +pub use rescue::RescueStep; +pub use display_manager::DisplayManagerStep; diff --git a/src/steps/netinstall.rs b/src/steps/netinstall.rs new file mode 100644 index 0000000..68c6018 --- /dev/null +++ b/src/steps/netinstall.rs @@ -0,0 +1,68 @@ +use eframe::egui; +use rust_i18n::t; +use crate::installer::{GlobalState, InstallerStep}; + +pub const PACKAGE_GROUPS: &[(&str, &str, &[&str])] = &[ + ("office", "Office Suite", &["libreoffice", "calligra"]), + ("multimedia", "Multimedia", &["vlc", "audacity", "gimp", "inkscape"]), + ("development", "Development Tools",&["gcc", "make", "cmake", "git", "python3", "rust"]), + ("network", "Network Tools", &["firefox", "chromium", "thunderbird", "filezilla"]), + ("graphics", "Graphics & Design",&["blender", "krita", "darktable"]), + ("games", "Games", &["steam", "lutris", "supertuxkart"]), + ("vm", "Virtualization", &["virtualbox", "qemu", "docker"]), + ("server", "Server", &["nginx", "apache", "mariadb", "postgresql"]), +]; + +#[derive(Default)] +#[allow(dead_code)] +pub struct NetinstallStep; + +impl InstallerStep for NetinstallStep { + fn name(&self) -> String { + t!("netinstall").to_string() + } + + fn on_enter(&mut self, state: &mut GlobalState) { + if state.selected_package_groups.is_empty() { + // Varsayılan ofis ve network + state.selected_package_groups = vec!["office".to_string(), "network".to_string()]; + } + } + + fn show(&mut self, ui: &mut egui::Ui, state: &mut GlobalState) { + ui.heading(t!("netinstall_title")); + ui.label(t!("netinstall_description")); + ui.add_space(10.0); + + let screen_width = ui.available_width(); + let cols = if screen_width > 700.0 { 3 } else if screen_width > 500.0 { 2 } else { 1 }; + + egui::Grid::new("netinstall_grid") + .num_columns(cols) + .spacing([8.0, 4.0]) + .show(ui, |ui| { + for (key, label, _pkgs) in PACKAGE_GROUPS { + let mut selected = state.selected_package_groups.contains(&key.to_string()); + ui.checkbox(&mut selected, *label); + if selected && !state.selected_package_groups.contains(&key.to_string()) { + state.selected_package_groups.push(key.to_string()); + } else if !selected { + state.selected_package_groups.retain(|k| k != key); + } + } + }); + + ui.add_space(12.0); + if !state.selected_package_groups.is_empty() { + let total: usize = PACKAGE_GROUPS.iter() + .filter(|(k, _, _)| state.selected_package_groups.contains(&k.to_string())) + .map(|(_, _, pkgs)| pkgs.len()) + .sum(); + ui.label(format!("{} {} {}", total, t!("netinstall_package_count"), t!("netinstall_selected"))); + } + } + + fn is_complete(&self, _state: &GlobalState) -> bool { + true + } +} diff --git a/src/steps/network.rs b/src/steps/network.rs new file mode 100644 index 0000000..347ce82 --- /dev/null +++ b/src/steps/network.rs @@ -0,0 +1,205 @@ +use eframe::egui; +use rust_i18n::t; +use crate::installer::{GlobalState, InstallerStep}; + +#[derive(Default)] +pub struct NetworkStep { + scan_results: Vec, + show_password: Vec, + scanning: bool, + scan_done: bool, + proxy_enabled: bool, +} + +#[derive(Clone, Default)] +struct WifiNetwork { + ssid: String, + signal: String, + secured: bool, + active: bool, +} + +impl NetworkStep { + fn scan_wifi(&mut self) { + self.scanning = true; + self.scan_done = false; + self.scan_results.clear(); + + if let Ok(out) = std::process::Command::new("nmcli") + .args(["-t", "-f", "SSID,SIGNAL,SECURITY,ACTIVE", "dev", "wifi", "list"]) + .output() + { + if out.status.success() { + let text = String::from_utf8_lossy(&out.stdout); + for line in text.lines() { + let parts: Vec<&str> = line.split(':').collect(); + if parts.len() >= 3 { + let ssid = parts[0].trim().to_string(); + if ssid.is_empty() || ssid == "--" { continue; } + self.scan_results.push(WifiNetwork { + ssid, + signal: parts[1].to_string(), + secured: !parts[2].is_empty() && parts[2] != "(none)" && parts[2] != "--", + active: parts.get(3).map(|s| s.trim() == "yes").unwrap_or(false), + }); + } + } + } + } + self.show_password = vec![false; self.scan_results.len()]; + self.scanning = false; + self.scan_done = true; + } + + fn connect_wifi(ssid: &str, password: &str) -> Result { + let mut args = vec!["dev", "wifi", "connect", ssid]; + if !password.is_empty() { + args.extend_from_slice(&["password", password]); + } + let out = std::process::Command::new("nmcli") + .args(&args) + .output() + .map_err(|e| format!("nmcli error: {}", e))?; + if out.status.success() { + Ok(String::from_utf8_lossy(&out.stdout).trim().to_string()) + } else { + Err(String::from_utf8_lossy(&out.stderr).trim().to_string()) + } + } +} + +impl InstallerStep for NetworkStep { + fn name(&self) -> String { + t!("network").to_string() + } + + fn show(&mut self, ui: &mut egui::Ui, state: &mut GlobalState) { + ui.heading(t!("network_title")); + ui.label(t!("network_description")); + ui.add_space(12.0); + + // Mevcut bağlantı durumu + if let Ok(out) = std::process::Command::new("nmcli") + .args(["-t", "-f", "DEVICE,TYPE,STATE,CONNECTION", "dev", "status"]) + .output() + { + let text = String::from_utf8_lossy(&out.stdout); + let connected: Vec<&str> = text.lines() + .filter(|l| l.contains(":connected:") || l.contains(":connecting:")) + .collect(); + if !connected.is_empty() { + ui.colored_label( + egui::Color32::from_rgb(80, 170, 80), + format!("{} {}", t!("network_connected"), connected.join(", ")), + ); + } else { + ui.colored_label( + egui::Color32::from_rgb(200, 140, 40), + t!("network_disconnected"), + ); + } + } + ui.add_space(8.0); + + // Proxy ayarları + ui.horizontal(|ui| { + ui.add(egui::Checkbox::new(&mut self.proxy_enabled, "")); + ui.label(t!("network_proxy_label")); + }); + if self.proxy_enabled { + egui::Grid::new("proxy_grid") + .num_columns(2) + .spacing([12.0, 6.0]) + .show(ui, |ui| { + ui.label("HTTP:"); + ui.text_edit_singleline(&mut state.network_http_proxy); + ui.end_row(); + ui.label("HTTPS:"); + ui.text_edit_singleline(&mut state.network_https_proxy); + ui.end_row(); + ui.label("FTP:"); + ui.text_edit_singleline(&mut state.network_ftp_proxy); + ui.end_row(); + }); + } + ui.add_space(12.0); + + // Wi-Fi tara + if !self.scan_done { + if ui.add(egui::Button::new(t!("network_scan"))).clicked() { + self.scan_wifi(); + } + } else { + ui.horizontal(|ui| { + if ui.add(egui::Button::new(t!("network_rescan"))).clicked() { + self.scan_done = false; + } + ui.label( + egui::RichText::new(format!("{}: {}", t!("network_scan_count"), self.scan_results.len())) + .size(12.0) + .color(egui::Color32::from_rgb(120, 120, 140)), + ); + }); + } + + ui.add_space(6.0); + + // Wi-Fi listesi + if !self.scan_results.is_empty() { + egui::ScrollArea::vertical() + .max_height(300.0) + .show(ui, |ui| { + egui::Frame::group(ui.style()) + .inner_margin(egui::Margin::same(6.0)) + .show(ui, |ui| { + for (i, net) in self.scan_results.clone().iter().enumerate() { + ui.horizontal(|ui| { + let lock = if net.secured { "🔒" } else { "🌐" }; + let sig = net.signal.parse::().unwrap_or(0); + let bars = if sig > -50 { "▂▄▆█" } else if sig > -67 { "▂▄▆" } else if sig > -80 { "▂▄" } else { "▂" }; + let active = if net.active { " ✓" } else { "" }; + + ui.label(format!("{} {}{}", lock, net.ssid, active)); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + ui.label( + egui::RichText::new(bars) + .size(10.0) + .color(egui::Color32::from_rgb(120, 120, 140)), + ); + if net.secured && !net.active { + ui.add(egui::TextEdit::singleline(&mut state.network_wifi_password) + .password(!self.show_password.get(i).copied().unwrap_or(false)) + .hint_text(t!("network_password_hint")) + .desired_width(120.0)); + if ui.small_button("👁").clicked() { + if self.show_password.len() <= i { + self.show_password.resize(i + 1, false); + } + self.show_password[i] = !self.show_password[i]; + } + } + if !net.active && ui.add(egui::Button::new(t!("network_connect"))).clicked() { + let pw = if net.secured { state.network_wifi_password.clone() } else { String::new() }; + match Self::connect_wifi(&net.ssid, &pw) { + Ok(msg) => { + state.install_log.push(format!("Wi-Fi: {} — {}", net.ssid, msg)); + self.scan_done = false; + } + Err(e) => { + state.install_log.push(format!("Wi-Fi error: {}", e)); + } + } + } + }); + }); + ui.separator(); + } + }); + }); + } + } + + fn is_complete(&self, _state: &GlobalState) -> bool { + true + } +} diff --git a/src/steps/partition.rs b/src/steps/partition.rs index c30bfa1..3ca422d 100644 --- a/src/steps/partition.rs +++ b/src/steps/partition.rs @@ -5,11 +5,84 @@ use rust_i18n::t; // sysinfo::Disks mount point başına bir kayıt döndürür (partition'lar), // ham disk listesi için lsblk kullanıyoruz. +// ───────────────────────────────────────────── +// Mevcut disk bölümlerini oku +// ───────────────────────────────────────────── + +/// Verilen diskin (`/dev/sda`, `/dev/nvme0n1` vb.) mevcut bölümlerini +/// `lsblk` aracılığıyla okur ve `CustomPartition` listesi olarak döndürür. +/// +/// Hem SATA/virtio tipi (`sda` → `sda1`, `sda2`) hem de NVMe tipi +/// (`nvme0n1` → `nvme0n1p1`, `nvme0n1p2`) aygıtları desteklenir. +/// Manuel bölümleme modunda disk seçildiğinde çağrılır; böylece kullanıcı +/// diskin mevcut bölüm düzenini görebilir. +fn read_existing_partitions(disk_path: &str) -> Vec { + use crate::installer::{CustomPartition, FsType}; + + let dev_name = disk_path.trim_start_matches("/dev/"); + + let out = std::process::Command::new("lsblk") + .args([ + "-o", "NAME,SIZE,FSTYPE,MOUNTPOINT", + "-b", "--noheadings", "-l", + disk_path, + ]) + .output(); + + let mut parts = Vec::new(); + + if let Ok(o) = out { + if o.status.success() { + let text = String::from_utf8_lossy(&o.stdout); + for line in text.lines() { + let mut tokens = line.split_whitespace(); + + let name = match tokens.next() { Some(n) => n, None => continue }; + + // Diskin kendisini (örn. "sda") atla — yalnızca bölümleri al + if name == dev_name { continue; } + + // Bu diske ait olmayan girişleri atla + // SATA: sda1, sda2 … NVMe: nvme0n1p1, nvme0n1p2 … + if !name.starts_with(dev_name) { continue; } + + let size_bytes: u64 = tokens.next().unwrap_or("0").parse().unwrap_or(0); + let size_mb = size_bytes / (1024 * 1024); + + let fstype_str = tokens.next().unwrap_or(""); + let fstype = match fstype_str { + "ext4" => FsType::Ext4, + "ext3" => FsType::Ext3, + "btrfs" => FsType::Btrfs, + "xfs" => FsType::Xfs, + "vfat" => FsType::Fat32, + "ntfs" => FsType::Ntfs, + "swap" => FsType::Swap, + _ => FsType::Ext4, // bilinmeyen → varsayılan + }; + + // lsblk mount point alanı boşsa sonraki token gelmez; unwrap_or ile güvenli al + let mountpoint = tokens.next().unwrap_or("").to_string(); + + parts.push(CustomPartition { + device: format!("/dev/{}", name), + size_mb, + fstype, + mountpoint, + encrypt: false, + luks_password: String::new(), + luks_name: String::new(), + }); + } + } + } + + parts +} + /// `/sys/block` altındaki gerçek blok aygıtlarını (partition değil) listeler. -/// lsblk'dan JSON çıktısı ayrıştırılır; başarısız olursa /sys/block fallback. +/// lsblk'dan gelen çıktı akıllıca ayrıştırılır; başarısız olursa /sys/block fallback devreye girer. fn list_block_devices() -> Vec { - // lsblk -d: sadece diskler (partition'lar hariç) - // -o NAME,SIZE,MODEL,TYPE -b: byte cinsinden boyut let out = std::process::Command::new("lsblk") .args(["-d", "-o", "NAME,SIZE,MODEL", "-b", "--noheadings"]) .output(); @@ -17,43 +90,64 @@ fn list_block_devices() -> Vec { let mut disks = Vec::new(); if let Ok(o) = out { - let text = String::from_utf8_lossy(&o.stdout); - for line in text.lines() { - let cols: Vec<&str> = line.splitn(3, char::is_whitespace) - .filter(|s| !s.is_empty()) - .collect(); - if cols.len() < 2 { continue; } + if o.status.success() { + let text = String::from_utf8_lossy(&o.stdout); + for line in text.lines() { + // split_whitespace ardışık tüm boşlukları tek bir ayırıcı gibi yakalar + let mut tokens = line.split_whitespace(); + + let name_raw = match tokens.next() { + Some(n) => n, + None => continue, + }; - let name_raw = cols[0]; // örn. "sda" veya "nvme0n1" - // loop, ram, sr aygıtlarını atla - if name_raw.starts_with("loop") - || name_raw.starts_with("ram") - || name_raw.starts_with("sr") - { continue; } + // loop, ram, sr (CD-ROM) ve zram aygıtlarını atla + if name_raw.starts_with("loop") + || name_raw.starts_with("ram") + || name_raw.starts_with("sr") + || name_raw.starts_with("zram") + { continue; } - let size_bytes: u64 = cols[1].trim().parse().unwrap_or(0); - let model = if cols.len() >= 3 { cols[2].trim() } else { "" }; + let size_str = match tokens.next() { + Some(s) => s, + None => continue, + }; - disks.push(crate::installer::DiskInfo { - name: format!("/dev/{}", name_raw), - model: model.to_string(), - size_gb: size_bytes / 1_073_741_824, - size_bytes, - }); + let size_bytes: u64 = size_str.parse().unwrap_or(0); + // Eğer boyut 0 ise ekleme yapma ki lsblk patlarsa fallback çalışabilsin + if size_bytes == 0 { continue; } + + // Kalan tüm token'lar model ismidir (örn: ["VBOX", "HARDDISK"]) + let model_tokens: Vec<&str> = tokens.collect(); + let model = model_tokens.join(" "); + + disks.push(crate::installer::DiskInfo { + name: format!("/dev/{}", name_raw), + model, + size_gb: size_bytes / 1_073_741_824, + size_bytes, + }); + } } } - // lsblk başarısız olduysa /sys/block fallback + // lsblk başarısız olduysa veya geçerli disk bulamadıysa temiz /sys/block fallback'i çalışır if disks.is_empty() { if let Ok(rd) = std::fs::read_dir("/sys/block") { for entry in rd.flatten() { let name = entry.file_name().to_string_lossy().to_string(); - if name.starts_with("loop") || name.starts_with("ram") { continue; } + + if name.starts_with("loop") + || name.starts_with("ram") + || name.starts_with("sr") + || name.starts_with("zram") + { continue; } let size_path = format!("/sys/block/{}/size", name); let size_sectors: u64 = std::fs::read_to_string(&size_path) .unwrap_or_default().trim().parse().unwrap_or(0); let size_bytes = size_sectors * 512; + if size_bytes == 0 { continue; } let model_path = format!("/sys/block/{}/device/model", name); let model = std::fs::read_to_string(&model_path) @@ -74,7 +168,7 @@ fn list_block_devices() -> Vec { use crate::installer::{ CustomPartition, DiskInfo, FsType, GlobalState, InstallerStep, - PartitionPlan, PartitionTableType, + PartitionPlan, PartitionTableType, VolumeGroup, }; const MIN_DISK_GB: u64 = 10; @@ -96,18 +190,35 @@ struct ManualState { add_size_str: String, /// Diyalog: bağlama noktası add_mountpoint: String, + /// Diyalog: LUKS şifrele + add_encrypt: bool, + /// Diyalog: LUKS parola + add_luks_password: String, /// Silinmek istenen bölüm indeksi (onay isteği) delete_idx: Option, /// Genel hata mesajı error: Option, + /// Yeni VG adı (LVM) + add_vg_name: String, + + // ── LVM Logical Volume Ekleme State'leri ─────────────────── + adding_lv: bool, + add_lv_name: String, + add_lv_vg_idx: usize, + add_lv_size_str: String, + add_lv_fstype: usize, + add_lv_mountpoint: String, } const FS_OPTIONS: &[(&str, FsType)] = &[ ("ext4", FsType::Ext4), + ("ext3", FsType::Ext3), ("btrfs", FsType::Btrfs), ("xfs", FsType::Xfs), ("FAT32 (EFI)", FsType::Fat32), + ("NTFS", FsType::Ntfs), ("swap", FsType::Swap), + ("LVM PV", FsType::Lvm), ]; // ───────────────────────────────────────────── @@ -118,6 +229,7 @@ pub struct PartitionStep { scanned: bool, is_uefi: bool, manual: ManualState, + show_luks_password: bool, } impl Default for PartitionStep { @@ -126,6 +238,7 @@ impl Default for PartitionStep { scanned: false, is_uefi: std::path::Path::new("/sys/firmware/efi").exists(), manual: ManualState::default(), + show_luks_password: false, } } } @@ -163,12 +276,19 @@ fn make_plan(disk: &DiskInfo, is_uefi: bool) -> Option { efi_mb, swap_mb, root_mb, + encrypt_root: false, + luks_password: String::new(), }) } /// Manuel listedeki bölümlerin geçerliliğini kontrol eder. -fn validate_manual(parts: &[CustomPartition], is_uefi: bool) -> Option { - let has_root = parts.iter().any(|p| p.mountpoint == "/"); +fn validate_manual( + parts: &[CustomPartition], + lvs: &[crate::installer::LogicalVolume], + is_uefi: bool, +) -> Option { + let has_root = parts.iter().any(|p| p.mountpoint == "/") + || lvs.iter().any(|lv| lv.mountpoint == "/"); let has_efi = parts.iter().any(|p| p.mountpoint == "/boot/efi" || p.fstype == FsType::Fat32); if !has_root { @@ -224,7 +344,7 @@ impl InstallerStep for PartitionStep { state.partition_plan.is_some() } else { !state.custom_partitions.is_empty() - && validate_manual(&state.custom_partitions, self.is_uefi).is_none() + && validate_manual(&state.custom_partitions, &state.logical_volumes, self.is_uefi).is_none() } } } @@ -234,7 +354,7 @@ impl InstallerStep for PartitionStep { // ───────────────────────────────────────────── impl PartitionStep { - fn show_auto_mode(&self, ui: &mut egui::Ui, state: &mut GlobalState) { + fn show_auto_mode(&mut self, ui: &mut egui::Ui, state: &mut GlobalState) { ui.group(|ui| { ui.label(t!("select_disk_label")); ui.add_space(4.0); @@ -296,6 +416,30 @@ impl PartitionStep { ui.add_space(8.0); ui.label(format!("{}: {}", t!("partition_table_type"), plan.table_type)); + // ── LUKS şifreleme ───────────────────────────────────── + ui.add_space(12.0); + ui.separator(); + ui.add_space(8.0); + let plan = state.partition_plan.as_mut().unwrap(); + ui.checkbox(&mut plan.encrypt_root, t!("luks_enable_root")); + if plan.encrypt_root { + ui.horizontal(|ui| { + ui.label(t!("luks_password")); + if self.show_luks_password { + ui.text_edit_singleline(&mut plan.luks_password); + } else { + ui.add(egui::TextEdit::singleline(&mut plan.luks_password).password(true)); + } + let eye = if self.show_luks_password { "🙈" } else { "👁" }; + if ui.small_button(eye).clicked() { + self.show_luks_password = !self.show_luks_password; + } + }); + if plan.luks_password.len() < 4 && !plan.luks_password.is_empty() { + ui.colored_label(egui::Color32::YELLOW, t!("luks_password_weak")); + } + } + ui.add_space(10.0); egui::Frame::none() .fill(egui::Color32::from_rgba_unmultiplied(200, 60, 40, 25)) @@ -329,8 +473,9 @@ impl PartitionStep { let label = egui::RichText::new(format!("{} — {} GB ({})", disk.name, disk.size_gb, disk.model)).strong().color(crate::ui::theme::c_accent()); if ui.radio(selected, label).clicked() { state.selected_disk = Some(disk.name.clone()); - // Disk değişince listeyi sıfırla - state.custom_partitions.clear(); + // Disk değişince mevcut bölümleri oku ve listeyi güncelle. + // Böylece kullanıcı diskin mevcut düzenini görebilir. + state.custom_partitions = read_existing_partitions(&disk.name); self.manual.error = None; } } @@ -375,8 +520,12 @@ impl PartitionStep { }; ui.label(&part.device); + let mut fstype_label = part.fstype.label().to_string(); + if part.encrypt { + fstype_label = format!("🔐 {}", fstype_label); + } ui.label(&size_str); - ui.label(part.fstype.label()); + ui.label(fstype_label); ui.label(&part.mountpoint); if ui.small_button("🗑").on_hover_text(t!("mp_delete")).clicked() { delete_idx = Some(i); @@ -451,7 +600,7 @@ impl PartitionStep { // Dosya sistemi ui.label(t!("mp_fstype")); egui::ComboBox::from_id_source("fs_combo") - .selected_text(FS_OPTIONS[self.manual.add_fstype].0) + .selected_text(FS_OPTIONS.get(self.manual.add_fstype).map(|x| x.0).unwrap_or("?")) .show_ui(ui, |ui| { for (i, (label, _)) in FS_OPTIONS.iter().enumerate() { ui.selectable_value( @@ -472,6 +621,33 @@ impl PartitionStep { .hint_text(t!("mp_add_dialog_hint")); ui.add(mp_field); ui.end_row(); + + // LUKS şifreleme + ui.label(t!("luks_enable_root")); + let fstype = FS_OPTIONS.get(self.manual.add_fstype).map(|x| x.1.clone()).unwrap_or(FsType::Ext4); + let can_encrypt = matches!(fstype, FsType::Ext4 | FsType::Ext3 | FsType::Btrfs | FsType::Xfs); + ui.add_enabled_ui(can_encrypt, |ui| { + ui.checkbox(&mut self.manual.add_encrypt, ""); + }); + if !can_encrypt { self.manual.add_encrypt = false; } + ui.end_row(); + + if self.manual.add_encrypt { + ui.label(t!("luks_password")); + let pw = &mut self.manual.add_luks_password; + ui.horizontal(|ui| { + if self.show_luks_password { + ui.text_edit_singleline(pw); + } else { + ui.add(egui::TextEdit::singleline(pw).password(true)); + } + let eye = if self.show_luks_password { "🙈" } else { "👁" }; + if ui.small_button(eye).clicked() { + self.show_luks_password = !self.show_luks_password; + } + }); + ui.end_row(); + } }); ui.add_space(8.0); @@ -481,19 +657,26 @@ impl PartitionStep { let size_mb: u64 = self.manual.add_size_str .trim().parse().unwrap_or(0); let mp = self.manual.add_mountpoint.trim().to_string(); - let fstype = FS_OPTIONS[self.manual.add_fstype].1.clone(); + let fstype = FS_OPTIONS.get(self.manual.add_fstype).map(|x| x.1.clone()).unwrap_or(FsType::Ext4); let part_num = state.custom_partitions.len() + 1; let device = crate::installer::part_path(&disk, part_num as u32); if mp.is_empty() { self.manual.error = Some(t!("mp_error_empty_mountpoint").to_string()); } else { + let encrypt = self.manual.add_encrypt; + let luks_password = if encrypt { self.manual.add_luks_password.clone() } else { String::new() }; state.custom_partitions.push(CustomPartition { device, size_mb, fstype, mountpoint: mp, + encrypt, + luks_password, + luks_name: String::new(), }); + self.manual.add_encrypt = false; + self.manual.add_luks_password.clear(); self.manual.error = None; self.manual.adding = false; } @@ -505,13 +688,234 @@ impl PartitionStep { }); } + // ── LVM Yapılandırması ────────────────────────────────── + ui.add_space(12.0); + let has_lvm_pvs = state.custom_partitions.iter().any(|p| p.fstype == FsType::Lvm); + if has_lvm_pvs { + ui.label(egui::RichText::new("LVM Volume Groups").strong().size(14.0)); + ui.add_space(4.0); + egui::Grid::new("lvm_vg_grid") + .num_columns(4) + .spacing([12.0, 6.0]) + .striped(true) + .show(ui, |ui| { + ui.label("VG Name"); + ui.label("PV Devices"); + ui.label("Size"); + ui.label(""); + ui.end_row(); + + let mut remove_vg: Option = None; + for (vi, vg) in state.volume_groups.iter().enumerate() { + let pv_list = vg.pv_devices.join(", "); + let size_str = if vg.size_mb >= 1024 { + format!("{:.1} GB", vg.size_mb as f64 / 1024.0) + } else { + format!("{} MB", vg.size_mb) + }; + ui.label(&vg.name); + ui.label(&pv_list); + ui.label(&size_str); + if ui.small_button("🗑").clicked() { + remove_vg = Some(vi); + } + ui.end_row(); + } + if let Some(vi) = remove_vg { + state.volume_groups.remove(vi); + } + + // Yeni VG ekleme + ui.horizontal(|ui| { + ui.text_edit_singleline(&mut self.manual.add_vg_name); + if ui.small_button("➕ VG").clicked() + && !self.manual.add_vg_name.is_empty() + { + let pvs: Vec = state.custom_partitions.iter() + .filter(|p| p.fstype == FsType::Lvm) + .map(|p| p.device.clone()) + .collect(); + let size: u64 = state.custom_partitions.iter() + .filter(|p| p.fstype == FsType::Lvm) + .map(|p| p.size_mb) + .sum(); + state.volume_groups.push(VolumeGroup { + name: self.manual.add_vg_name.clone(), + pv_devices: pvs, + size_mb: size, + }); + self.manual.add_vg_name.clear(); + } + }); + ui.label(""); + ui.label(""); + ui.end_row(); + }); + + // ── LVM Logical Volumes Tablosu ────────────────────────── + let has_vgs = !state.volume_groups.is_empty(); + if has_vgs { + ui.add_space(12.0); + ui.label(egui::RichText::new(t!("lvm_lv_title")).strong().size(14.0)); + ui.add_space(4.0); + + let mut remove_lv: Option = None; + egui::Grid::new("lvm_lv_grid") + .num_columns(5) + .spacing([12.0, 6.0]) + .striped(true) + .show(ui, |ui| { + ui.label(egui::RichText::new(t!("lvm_lv_name")).strong().color(crate::ui::theme::c_text())); + ui.label(egui::RichText::new(t!("lvm_lv_vg")).strong().color(crate::ui::theme::c_text())); + ui.label(egui::RichText::new(t!("mp_size")).strong().color(crate::ui::theme::c_text())); + ui.label(egui::RichText::new(t!("mp_mountpoint")).strong().color(crate::ui::theme::c_text())); + ui.label(egui::RichText::new(t!("mp_action")).strong().color(crate::ui::theme::c_text())); + ui.end_row(); + + for (li, lv) in state.logical_volumes.iter().enumerate() { + let size_str = if lv.size_mb == 0 { + t!("mp_size_remaining").to_string() + } else { + format!("{} MB", lv.size_mb) + }; + ui.label(&lv.name); + ui.label(&lv.vg_name); + ui.label(size_str); + ui.label(&lv.mountpoint); + if ui.small_button("🗑").clicked() { + remove_lv = Some(li); + } + ui.end_row(); + } + }); + + if let Some(li) = remove_lv { + state.logical_volumes.remove(li); + } + + ui.add_space(8.0); + // "Logical Volume Ekle" butonu + let lv_btn = egui::Button::new(format!("➕ {}", t!("lvm_lv_add"))) + .fill(accent.gamma_multiply(0.15)); + if ui.add(lv_btn).clicked() { + self.manual.adding_lv = true; + self.manual.add_lv_name.clear(); + self.manual.add_lv_vg_idx = 0; + self.manual.add_lv_size_str.clear(); + self.manual.add_lv_fstype = 0; + self.manual.add_lv_mountpoint.clear(); + } + } + } + + // ── Logical Volume Ekleme Diyaloğu ──────────────────────── + if self.manual.adding_lv { + egui::Window::new(t!("lvm_lv_add")) + .collapsible(false) + .resizable(false) + .anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0]) + .show(ui.ctx(), |ui| { + egui::Grid::new("add_lv_grid") + .num_columns(2) + .spacing([12.0, 8.0]) + .show(ui, |ui| { + // Hacim Grubu (VG) seçimi + ui.label(t!("lvm_lv_vg")); + let current_vg = state.volume_groups.get(self.manual.add_lv_vg_idx) + .map(|vg| vg.name.as_str()) + .unwrap_or("?"); + egui::ComboBox::from_id_source("lv_vg_combo") + .selected_text(current_vg) + .show_ui(ui, |ui| { + for (vi, vg) in state.volume_groups.iter().enumerate() { + ui.selectable_value( + &mut self.manual.add_lv_vg_idx, vi, &vg.name + ); + } + }); + ui.end_row(); + + // LV Adı + ui.label(t!("lvm_lv_name")); + ui.text_edit_singleline(&mut self.manual.add_lv_name); + ui.end_row(); + + // Dosya sistemi + ui.label(t!("mp_fstype")); + egui::ComboBox::from_id_source("lv_fs_combo") + .selected_text(FS_OPTIONS.get(self.manual.add_lv_fstype).map(|x| x.0).unwrap_or("?")) + .show_ui(ui, |ui| { + for (i, (label, _)) in FS_OPTIONS.iter().enumerate() { + ui.selectable_value( + &mut self.manual.add_lv_fstype, i, *label + ); + } + }); + ui.end_row(); + + // Boyut + ui.label(t!("mp_size_hint")); + ui.text_edit_singleline(&mut self.manual.add_lv_size_str); + ui.end_row(); + + // Bağlama noktası + ui.label(t!("mp_mountpoint")); + let mp_field = egui::TextEdit::singleline(&mut self.manual.add_lv_mountpoint) + .hint_text(t!("mp_add_dialog_hint")); + ui.add(mp_field); + ui.end_row(); + }); + + ui.add_space(8.0); + ui.horizontal(|ui| { + let ok_btn = egui::Button::new(t!("mp_add_btn")).fill(accent.gamma_multiply(0.2)); + if ui.add(ok_btn).clicked() { + let size_mb: u64 = self.manual.add_lv_size_str.trim().parse().unwrap_or(0); + let mp = self.manual.add_lv_mountpoint.trim().to_string(); + let lv_name = self.manual.add_lv_name.trim().to_string(); + let fstype = FS_OPTIONS.get(self.manual.add_lv_fstype).map(|x| x.1.clone()).unwrap_or(FsType::Ext4); + + if lv_name.is_empty() { + self.manual.error = Some("LV Adı boş olamaz.".to_string()); + } else if mp.is_empty() { + self.manual.error = Some(t!("mp_error_empty_mountpoint").to_string()); + } else if let Some(vg) = state.volume_groups.get(self.manual.add_lv_vg_idx) { + // VG sınır doğrulaması + let current_sum: u64 = state.logical_volumes.iter() + .filter(|lv| lv.vg_name == vg.name) + .map(|lv| lv.size_mb) + .sum(); + if current_sum + size_mb > vg.size_mb && size_mb > 0 { + self.manual.error = Some(t!("lvm_lv_error_size").to_string()); + } else { + state.logical_volumes.push(crate::installer::LogicalVolume { + name: lv_name, + vg_name: vg.name.clone(), + size_mb, + fstype, + mountpoint: mp, + }); + self.manual.error = None; + self.manual.adding_lv = false; + } + } else { + self.manual.error = Some(t!("lvm_lv_error_vg").to_string()); + } + } + if ui.button(t!("cancel")).clicked() { + self.manual.adding_lv = false; + } + }); + }); + } + // ── Doğrulama mesajları ───────────────────────────────── ui.add_space(6.0); if let Some(ref err) = self.manual.error { ui.colored_label(egui::Color32::from_rgb(217, 0, 91), err); } if !state.custom_partitions.is_empty() { - if let Some(warn) = validate_manual(&state.custom_partitions, self.is_uefi) { + if let Some(warn) = validate_manual(&state.custom_partitions, &state.logical_volumes, self.is_uefi) { ui.colored_label(egui::Color32::from_rgb(220, 160, 40), warn); } else { ui.colored_label( diff --git a/src/steps/rescue.rs b/src/steps/rescue.rs new file mode 100644 index 0000000..aeb48d6 --- /dev/null +++ b/src/steps/rescue.rs @@ -0,0 +1,516 @@ +//! Kurtarma modu adımı. +//! +//! Özellikler: +//! - Mevcut Linux kurulumlarını tarar (lsblk) +//! - Seçilen bölümü /mnt'a bağlar +//! - chroot kabuğu başlatır +//! - GRUB'u yeniden yükler +//! - **Pisi paket geçmişini listeler ve `pisi takeback ` ile geri alır** + +use eframe::egui; +use rust_i18n::t; +use crate::installer::{GlobalState, InstallerStep}; +use crate::ui::theme; + +/// Pisi geçmişindeki tek bir işlem kaydı. +#[derive(Clone, Debug)] +struct PisiHistoryEntry { + /// İşlem numarası (takeback argümanı) + pub op: u32, + /// Tarih/saat dizisi + pub date: String, + /// İşlem türü (install, remove, upgrade, …) + pub op_type: String, + /// Etkilenen paket sayısı veya açıklaması + pub summary: String, +} + +/// Pisi geçmiş geri alma modunun durumu. +#[derive(Default)] +struct PisiHistoryState { + entries: Vec, + selected: Option, + scanned: bool, + running: bool, + result_msg: Option, + result_ok: Option, +} + +#[derive(Default)] +pub struct RescueStep { + detected: Vec, + selected: Option, + log: Vec, + mount_done: bool, + pisi: PisiHistoryState, + /// Aktif sekme: 0=Chroot/GRUB, 1=Pisi Geçmiş + tab: usize, +} + +impl RescueStep { + fn detect_installations(&mut self) { + self.detected.clear(); + self.mount_done = false; + if let Ok(out) = std::process::Command::new("lsblk") + .args(["-o", "NAME,FSTYPE,MOUNTPOINT", "-l", "-n"]) + .output() + { + let text = String::from_utf8_lossy(&out.stdout); + for line in text.lines() { + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() >= 2 { + let fstype = parts[1]; + if fstype == "ext4" || fstype == "btrfs" || fstype == "xfs" { + let dev = format!("/dev/{}", parts[0]); + let mp = parts.get(2).unwrap_or(&"-").to_string(); + self.detected.push(format!("{} ({}) [{}]", dev, fstype, mp)); + } + } + } + } + } + + /// Seçili bölümü /mnt'a bağlar. Başarılıysa `true` döner. + fn mount_selected(&mut self) -> bool { + if let Some(idx) = self.selected { + if let Some(entry) = self.detected.get(idx) { + // Entry: "/dev/sda1 (ext4) [-]" — sadece ilk token aygıt yolu + let dev = entry.split_whitespace().next().unwrap_or(""); + if dev.is_empty() { return false; } + // /mnt boşsa bağla + let r = std::process::Command::new("mount") + .args([dev, "/mnt"]) + .status(); + if let Ok(s) = r { + if s.success() { + self.mount_done = true; + self.log.push(format!("✓ {} → /mnt bağlandı.", dev)); + return true; + } else { + self.log.push(format!("⚠ {} bağlanamadı (zaten bağlı veya hata).", dev)); + self.mount_done = true; // Belki zaten bağlı + return true; + } + } + } + } + false + } + + fn run_rescue_cmd(cmd: &str, args: &[&str]) -> Result { + let out = std::process::Command::new(cmd) + .args(args) + .output() + .map_err(|e| format!("{} hatası: {}", cmd, e))?; + if out.status.success() { + Ok(String::from_utf8_lossy(&out.stdout).trim().to_string()) + } else { + Err(String::from_utf8_lossy(&out.stderr).trim().to_string()) + } + } + + /// `pisi history` çıktısını ayrıştırır. + /// Örnek satır: "Operation 42 [2024-03-15 12:00:01]: install [vim]" + fn parse_pisi_history(raw: &str) -> Vec { + let mut entries = Vec::new(); + for line in raw.lines() { + let line = line.trim(); + if line.is_empty() || line.starts_with('#') { continue; } + + // "Operation 42 [2024-03-15 12:00:01]: install [vim, curl]" + if let Some(rest) = line.strip_prefix("Operation ") { + let mut parts = rest.splitn(2, ' '); + let op_str = parts.next().unwrap_or("0"); + let remainder = parts.next().unwrap_or(""); + + let op: u32 = op_str.parse().unwrap_or(0); + + // Tarih: "[2024-03-15 12:00:01]" + let date = if let Some(start) = remainder.find('[') { + if let Some(end) = remainder.find(']') { + remainder[start+1..end].to_string() + } else { String::new() } + } else { String::new() }; + + // Tür + özet: ": install [vim, curl]" + let (op_type, summary) = if let Some(colon) = remainder.rfind("]: ") { + let after = &remainder[colon+3..]; + if let Some(sp) = after.find(' ') { + (after[..sp].to_string(), after[sp+1..].to_string()) + } else { + (after.to_string(), String::new()) + } + } else { ("unknown".to_string(), remainder.to_string()) }; + + entries.push(PisiHistoryEntry { op, date, op_type, summary }); + } + } + // En yeni önce + entries.sort_by(|a, b| b.op.cmp(&a.op)); + entries + } + + /// /mnt chroot içinde `pisi history` çalıştırır ve ayrıştırır. + fn scan_pisi_history(&mut self) { + self.pisi.entries.clear(); + self.pisi.result_msg = None; + self.pisi.selected = None; + + let out = std::process::Command::new("chroot") + .args(["/mnt", "pisi", "history"]) + .output(); + + match out { + Ok(o) => { + let text = String::from_utf8_lossy(&o.stdout).to_string(); + let entries = Self::parse_pisi_history(&text); + if entries.is_empty() { + self.pisi.result_msg = Some(t!("rescue_pisi_history_empty").to_string()); + self.pisi.result_ok = Some(false); + } + self.pisi.entries = entries; + } + Err(e) => { + self.pisi.result_msg = Some(format!("{}", e)); + self.pisi.result_ok = Some(false); + } + } + self.pisi.scanned = true; + } + + /// `pisi takeback ` çalıştırır. + fn run_takeback(&mut self, op: u32) { + self.pisi.running = true; + self.pisi.result_msg = Some(t!("rescue_pisi_takeback_start").to_string()); + self.pisi.result_ok = None; + + let op_str = op.to_string(); + let out = std::process::Command::new("chroot") + .args(["/mnt", "pisi", "takeback", &op_str]) + .output(); + + self.pisi.running = false; + match out { + Ok(o) if o.status.success() => { + self.pisi.result_ok = Some(true); + self.pisi.result_msg = Some(t!("rescue_pisi_history_ok").to_string()); + self.log.push(format!("✓ pisi takeback {} başarılı.", op)); + } + Ok(o) => { + let err = String::from_utf8_lossy(&o.stderr).trim().to_string(); + self.pisi.result_ok = Some(false); + self.pisi.result_msg = Some(t!("rescue_pisi_history_err", err = err).to_string()); + self.log.push(format!("❌ pisi takeback {}: {}", op, err)); + } + Err(e) => { + self.pisi.result_ok = Some(false); + self.pisi.result_msg = Some(t!("rescue_pisi_history_err", err = e.to_string()).to_string()); + } + } + } + + // ─── UI Panelleri ──────────────────────────────────────────────── + + fn show_chroot_panel(&mut self, ui: &mut egui::Ui, state: &mut GlobalState) { + ui.add_space(6.0); + + // Bölüm listesi + ui.label(egui::RichText::new(t!("rescue_detected")).strong().color(theme::c_text())); + ui.add_space(4.0); + egui::ScrollArea::vertical() + .id_source("rescue_partition_list") + .max_height(130.0) + .show(ui, |ui| { + for (i, entry) in self.detected.iter().enumerate() { + let is_selected = self.selected == Some(i); + let label = egui::RichText::new(entry) + .family(egui::FontFamily::Monospace) + .color(if is_selected { theme::c_accent() } else { theme::c_text() }); + if ui.selectable_label(is_selected, label).clicked() { + self.selected = Some(i); + self.mount_done = false; + } + } + }); + + ui.add_space(10.0); + + // İşlem butonları + ui.horizontal(|ui| { + // 1. Bağla + if ui.add_enabled( + self.selected.is_some() && !self.mount_done, + theme::secondary_button("📂 /mnt'a Bağla"), + ).clicked() { + self.mount_selected(); + } + + // 2. Chroot + if ui.add_enabled( + self.mount_done, + theme::secondary_button(&t!("rescue_chroot")), + ).clicked() { + self.log.push(t!("rescue_chroot_start").to_string()); + match Self::run_rescue_cmd("chroot", &["/mnt", "/bin/bash", "-c", "echo 'chroot OK'"]) { + Ok(msg) => self.log.push(format!("✓ {}", msg)), + Err(e) => self.log.push(format!("❌ {}", e)), + } + } + + // 3. GRUB yeniden yükle + if ui.add_enabled( + self.mount_done, + theme::secondary_button(&t!("rescue_grub")), + ).clicked() { + self.log.push(t!("rescue_grub_start").to_string()); + let boot_dev = state.bootloader_device.clone(); + let steps: &[(&str, &[&str])] = &[ + ("mount", &["--bind", "/proc", "/mnt/proc"]), + ("mount", &["--bind", "/sys", "/mnt/sys"]), + ("mount", &["--bind", "/dev", "/mnt/dev"]), + ]; + for (cmd, args) in steps { + match Self::run_rescue_cmd(cmd, args) { + Ok(m) if !m.is_empty() => self.log.push(m), + Ok(_) => {}, + Err(e) => self.log.push(format!("⚠ {}", e)), + } + } + // grub2-install + let grub_args: Vec<&str> = vec!["grub2-install", &boot_dev]; + match Self::run_rescue_cmd("chroot", &{ + let mut a: Vec<&str> = vec!["/mnt"]; + a.extend_from_slice(&grub_args); + a + }) { + Ok(m) => self.log.push(format!("✓ grub2-install: {}", m)), + Err(e) => self.log.push(format!("❌ grub2-install: {}", e)), + } + // grub2-mkconfig + match Self::run_rescue_cmd("chroot", &["/mnt", "grub2-mkconfig", "-o", "/boot/grub2/grub.cfg"]) { + Ok(m) => self.log.push(format!("✓ grub2-mkconfig: {}", m)), + Err(e) => self.log.push(format!("❌ grub2-mkconfig: {}", e)), + } + } + }); + } + + fn show_pisi_panel(&mut self, ui: &mut egui::Ui) { + ui.add_space(6.0); + ui.label( + egui::RichText::new(t!("rescue_pisi_history_desc")) + .size(12.0) + .color(theme::c_text_dim()), + ); + ui.add_space(8.0); + + if !self.mount_done { + theme::warning_box(ui, "Önce 'Chroot & GRUB' sekmesinden bölümü /mnt'a bağlayın."); + return; + } + + // Tara butonu + ui.horizontal(|ui| { + if ui.add(theme::secondary_button(&t!("rescue_pisi_history_scan"))).clicked() { + self.scan_pisi_history(); + } + if self.pisi.scanned { + ui.label( + egui::RichText::new(format!("{} işlem listelendi", self.pisi.entries.len())) + .size(12.0) + .color(theme::c_text_dim()), + ); + } + }); + + if self.pisi.entries.is_empty() { + if let Some(ref msg) = self.pisi.result_msg.clone() { + ui.add_space(6.0); + theme::warning_box(ui, msg); + } + return; + } + + ui.add_space(8.0); + + // Geçmiş tablosu + egui::ScrollArea::vertical() + .id_source("pisi_history_scroll") + .max_height(200.0) + .show(ui, |ui| { + egui::Grid::new("pisi_history_grid") + .num_columns(4) + .spacing([12.0, 4.0]) + .striped(true) + .show(ui, |ui| { + // Başlık satırı + ui.label(egui::RichText::new("#").strong().color(theme::c_text_dim())); + ui.label(egui::RichText::new("Tarih").strong().color(theme::c_text_dim())); + ui.label(egui::RichText::new("İşlem").strong().color(theme::c_text_dim())); + ui.label(egui::RichText::new("Paketler").strong().color(theme::c_text_dim())); + ui.end_row(); + + for (i, entry) in self.pisi.entries.iter().enumerate() { + let is_selected = self.pisi.selected == Some(i); + let row_color = if is_selected { + theme::c_accent() + } else { + theme::c_text() + }; + + let op_label = egui::RichText::new(entry.op.to_string()) + .family(egui::FontFamily::Monospace) + .color(row_color); + if ui.selectable_label(is_selected, op_label).clicked() { + self.pisi.selected = Some(i); + } + + ui.label(egui::RichText::new(&entry.date).size(11.0).color(row_color)); + + // İşlem türüne renkli badge + let type_color = match entry.op_type.as_str() { + "install" => egui::Color32::from_rgb(80, 180, 80), + "remove" => egui::Color32::from_rgb(200, 70, 70), + "upgrade" => egui::Color32::from_rgb(70, 140, 220), + _ => theme::c_text_dim(), + }; + ui.colored_label(type_color, &entry.op_type); + + // Özet — truncate + let summary = if entry.summary.len() > 50 { + format!("{}…", &entry.summary[..50]) + } else { + entry.summary.clone() + }; + ui.label(egui::RichText::new(summary).size(11.0).color(theme::c_text_dim())); + ui.end_row(); + } + }); + }); + + ui.add_space(10.0); + + // Geri al butonu + let can_takeback = self.pisi.selected.is_some() && !self.pisi.running; + if ui.add_enabled(can_takeback, theme::primary_button(&t!("rescue_pisi_takeback"))).clicked() { + if let Some(idx) = self.pisi.selected { + let op = self.pisi.entries[idx].op; + self.run_takeback(op); + } + } + + // Sonuç mesajı + if let Some(ref msg) = self.pisi.result_msg.clone() { + ui.add_space(6.0); + match self.pisi.result_ok { + Some(true) => theme::success_box(ui, msg), + Some(false) => theme::error_box(ui, msg), + None => { ui.label(msg); } + } + } + } +} + +impl InstallerStep for RescueStep { + fn name(&self) -> String { t!("rescue").to_string() } + + fn on_enter(&mut self, _state: &mut GlobalState) { + self.detect_installations(); + } + + fn show(&mut self, ui: &mut egui::Ui, state: &mut GlobalState) { + theme::section_heading(ui, &t!("rescue_title")); + ui.label( + egui::RichText::new(t!("rescue_description")) + .color(theme::c_text_dim()) + .size(13.0), + ); + ui.add_space(10.0); + + if self.detected.is_empty() { + theme::warning_box(ui, &t!("rescue_no_installations")); + ui.add_space(6.0); + if ui.add(theme::secondary_button(&t!("rescue_scan"))).clicked() { + self.detect_installations(); + } + return; + } + + // ─── Sekmeler ─────────────────────────────────────────────── + ui.horizontal(|ui| { + let tab_labels = [ + ("💻 Chroot & GRUB", 0usize), + ("📜 Pisi Geçmiş", 1usize), + ]; + for (label, idx) in &tab_labels { + let active = self.tab == *idx; + let rt = egui::RichText::new(*label) + .strong() + .color(if active { theme::c_accent() } else { theme::c_text_dim() }); + if ui.selectable_label(active, rt).clicked() { + self.tab = *idx; + } + ui.separator(); + } + }); + + ui.separator(); + ui.add_space(4.0); + + match self.tab { + 0 => self.show_chroot_panel(ui, state), + 1 => self.show_pisi_panel(ui), + _ => {} + } + + ui.add_space(12.0); + + // Log alanı — her iki sekmede de görünür + if !self.log.is_empty() { + ui.separator(); + ui.add_space(4.0); + ui.label( + egui::RichText::new("Log") + .size(11.0) + .color(theme::c_text_dim()), + ); + egui::ScrollArea::vertical() + .id_source("rescue_log") + .max_height(160.0) + .stick_to_bottom(true) + .show(ui, |ui| { + egui::Frame::group(ui.style()) + .inner_margin(egui::Margin::same(8.0)) + .show(ui, |ui| { + for line in &self.log { + let color = if line.starts_with("✓") { + theme::c_success() + } else if line.starts_with("❌") { + theme::c_error() + } else if line.starts_with("⚠") { + theme::c_warning() + } else { + theme::c_text_dim() + }; + ui.colored_label( + color, + egui::RichText::new(line) + .size(11.0) + .family(egui::FontFamily::Monospace), + ); + } + }); + }); + } + + // Geri dön butonu + ui.add_space(8.0); + if ui.add(theme::secondary_button(&t!("rescue_return"))).clicked() { + state.rescue_mode = false; + } + } + + fn is_complete(&self, _state: &GlobalState) -> bool { + false // Rescue modu her zaman "tamamlanmamış" — normal kuruluma dönmek için rescue_mode=false + } +} diff --git a/src/steps/summary.rs b/src/steps/summary.rs index 12b68de..be97a6f 100644 --- a/src/steps/summary.rs +++ b/src/steps/summary.rs @@ -78,6 +78,39 @@ impl InstallerStep for SummaryStep { summary_row(ui, t!("summary_custom_partitions"), &plan_lines.join("\n")); } + // Tarih/Saat + let dt_str = if state.use_ntp { + t!("summary_ntp_enabled").to_string() + } else { + format!("{:04}-{:02}-{:02} {:02}:{:02} (NTP off)", + state.manual_year, state.manual_month, state.manual_day, + state.manual_hour, state.manual_minute) + }; + summary_row(ui, t!("summary_datetime"), &dt_str); + + // Bootloader + let bl_str = format!("{} ({}s timeout{})", + state.bootloader_device, + state.bootloader_timeout, + if state.bootloader_password.is_empty() { "" } else { ", password protected" } + ); + summary_row(ui, t!("summary_bootloader"), &bl_str); + + // Display Manager + let dm_str = format!("{} / {} ({})", + state.display_manager, + state.desktop_environment, + if state.autologin { t!("summary_autologin") } else { std::borrow::Cow::Borrowed("manual login") } + ); + summary_row(ui, t!("summary_display_manager"), &dm_str); + + let root_str = if state.root_password.is_empty() { + t!("summary_root_disabled").to_string() + } else { + t!("summary_root_enabled").to_string() + }; + summary_row(ui, t!("summary_root"), &root_str); + summary_row(ui, t!("summary_username"), &state.username); summary_row(ui, t!("summary_hostname"), &state.hostname); }); diff --git a/src/steps/users.rs b/src/steps/users.rs index 57406cb..c4a0a26 100644 --- a/src/steps/users.rs +++ b/src/steps/users.rs @@ -2,11 +2,13 @@ use eframe::egui; use rust_i18n::t; use crate::installer::{GlobalState, InstallerStep}; -/// Kullanıcı adı, şifre ve hostname ayarları adımı. -/// Calamares'in Users modülüne karşılık gelir. +/// Kullanıcı adı, şifre, hostname ve yönetici (root) şifresi ayarları adımı. pub struct UsersStep { show_password: bool, show_password_confirm: bool, + show_root_password: bool, + show_root_password_confirm: bool, + use_same_for_admin: bool, } impl Default for UsersStep { @@ -14,6 +16,9 @@ impl Default for UsersStep { Self { show_password: false, show_password_confirm: false, + show_root_password: false, + show_root_password_confirm: false, + use_same_for_admin: true, } } } @@ -77,110 +82,186 @@ impl InstallerStep for UsersStep { } fn show(&mut self, ui: &mut egui::Ui, state: &mut GlobalState) { - ui.heading(t!("users_title")); - ui.label(t!("users_description")); + use crate::ui::theme; + + theme::section_heading(ui, &t!("users_title")); + ui.label( + egui::RichText::new(t!("users_description")) + .color(theme::c_text_dim()) + .size(13.0), + ); ui.add_space(12.0); - egui::Grid::new("users_grid") - .num_columns(2) - .spacing([12.0, 10.0]) + // ─── Kullanıcı Bilgileri Grubu ────────────────────────────────── + egui::Frame::group(ui.style()) + .fill(theme::c_bg_widget()) + .rounding(6.0) + .inner_margin(egui::Margin::same(12.0)) .show(ui, |ui| { - // Kullanıcı adı - ui.label(t!("username_label")); - ui.horizontal(|ui| { - ui.text_edit_singleline(&mut state.username); - if !state.username.is_empty() { - if is_valid_username(&state.username) { - ui.colored_label(egui::Color32::from_rgb(80, 170, 80), "✓"); - } else { - ui.colored_label(egui::Color32::from_rgb(200, 60, 40), "✗"); - } - } - }); - ui.end_row(); + ui.set_width(ui.available_width()); + egui::Grid::new("users_grid") + .num_columns(2) + .spacing([12.0, 10.0]) + .show(ui, |ui| { + // Kullanıcı adı + ui.label(egui::RichText::new(t!("username_label")).strong().color(theme::c_text())); + ui.horizontal(|ui| { + ui.text_edit_singleline(&mut state.username); + if !state.username.is_empty() { + if is_valid_username(&state.username) { + ui.colored_label(theme::c_success(), "✓"); + } else { + ui.colored_label(theme::c_error(), "✗"); + } + } + }); + ui.end_row(); - // Şifre - ui.label(t!("password_label")); - ui.horizontal(|ui| { - if self.show_password { - ui.text_edit_singleline(&mut state.password); - } else { - ui.add(egui::TextEdit::singleline(&mut state.password).password(true)); - } - if ui.small_button(if self.show_password { "🙈" } else { "👁" }).clicked() { - self.show_password = !self.show_password; - } - }); - ui.end_row(); + // Şifre + ui.label(egui::RichText::new(t!("password_label")).strong().color(theme::c_text())); + ui.horizontal(|ui| { + if self.show_password { + ui.text_edit_singleline(&mut state.password); + } else { + ui.add(egui::TextEdit::singleline(&mut state.password).password(true)); + } + if ui.small_button(if self.show_password { "🙈" } else { "👁" }).clicked() { + self.show_password = !self.show_password; + } + if !state.password.is_empty() { + let (label, color) = password_strength(&state.password); + ui.colored_label(color, label); + } + }); + ui.end_row(); - // Şifre gücü göstergesi - ui.label(""); - if !state.password.is_empty() { - let (label, color) = password_strength(&state.password); - ui.colored_label(color, label); - } - ui.end_row(); + // Şifre doğrulama + ui.label(egui::RichText::new(t!("password_confirm_label")).strong().color(theme::c_text())); + ui.horizontal(|ui| { + if self.show_password_confirm { + ui.text_edit_singleline(&mut state.password_confirm); + } else { + ui.add(egui::TextEdit::singleline(&mut state.password_confirm).password(true)); + } + if ui.small_button(if self.show_password_confirm { "🙈" } else { "👁" }).clicked() { + self.show_password_confirm = !self.show_password_confirm; + } + if !state.password_confirm.is_empty() { + if state.password == state.password_confirm { + ui.colored_label(theme::c_success(), "✓"); + } else { + ui.colored_label(theme::c_error(), t!("passwords_no_match")); + } + } + }); + ui.end_row(); - // Şifre doğrulama - ui.label(t!("password_confirm_label")); - ui.horizontal(|ui| { - if self.show_password_confirm { - ui.text_edit_singleline(&mut state.password_confirm); - } else { - ui.add(egui::TextEdit::singleline(&mut state.password_confirm).password(true)); - } - if ui.small_button(if self.show_password_confirm { "🙈" } else { "👁" }).clicked() { - self.show_password_confirm = !self.show_password_confirm; - } - if !state.password_confirm.is_empty() { - if state.password == state.password_confirm { - ui.colored_label(egui::Color32::from_rgb(80, 170, 80), "✓"); - } else { - ui.colored_label(egui::Color32::from_rgb(200, 60, 40), t!("passwords_no_match")); - } - } - }); - ui.end_row(); - - // Hostname - ui.label(t!("hostname_label")); - ui.horizontal(|ui| { - // Username değişince hostname otomatik güncellenir (boşsa) - if state.hostname.is_empty() && !state.username.is_empty() { - state.hostname = format!("{}-pc", state.username); - } - ui.text_edit_singleline(&mut state.hostname); - if !state.hostname.is_empty() { - if is_valid_hostname(&state.hostname) { - ui.colored_label(egui::Color32::from_rgb(80, 170, 80), "✓"); - } else { - ui.colored_label(egui::Color32::from_rgb(200, 60, 40), "✗"); - } - } - }); - ui.end_row(); + // Hostname + ui.label(egui::RichText::new(t!("hostname_label")).strong().color(theme::c_text())); + ui.horizontal(|ui| { + if state.hostname.is_empty() && !state.username.is_empty() { + state.hostname = format!("{}-pc", state.username); + } + ui.text_edit_singleline(&mut state.hostname); + if !state.hostname.is_empty() { + if is_valid_hostname(&state.hostname) { + ui.colored_label(theme::c_success(), "✓"); + } else { + ui.colored_label(theme::c_error(), "✗"); + } + } + }); + ui.end_row(); + }); }); - // Hata mesajları + ui.add_space(14.0); + + // ─── Yönetici (root) Ayarları ───────────────────────────────────── + ui.checkbox(&mut self.use_same_for_admin, egui::RichText::new(t!("use_same_for_admin")).strong().color(theme::c_text())); + + if self.use_same_for_admin { + state.root_password = state.password.clone(); + state.root_password_confirm = state.password_confirm.clone(); + } else { + ui.add_space(8.0); + egui::Frame::group(ui.style()) + .fill(theme::c_bg_widget()) + .rounding(6.0) + .inner_margin(egui::Margin::same(12.0)) + .show(ui, |ui| { + ui.set_width(ui.available_width()); + ui.label(egui::RichText::new(t!("admin_description")).size(12.0).color(theme::c_text_dim())); + ui.add_space(8.0); + + egui::Grid::new("admin_grid") + .num_columns(2) + .spacing([12.0, 10.0]) + .show(ui, |ui| { + // Root şifresi + ui.label(egui::RichText::new(t!("admin_password_label")).strong().color(theme::c_text())); + ui.horizontal(|ui| { + if self.show_root_password { + ui.text_edit_singleline(&mut state.root_password); + } else { + ui.add(egui::TextEdit::singleline(&mut state.root_password).password(true)); + } + if ui.small_button(if self.show_root_password { "🙈" } else { "👁" }).clicked() { + self.show_root_password = !self.show_root_password; + } + if !state.root_password.is_empty() && state.root_password.len() < 4 { + ui.colored_label(theme::c_error(), t!("admin_password_short")); + } + }); + ui.end_row(); + + // Şifre doğrulama + ui.label(egui::RichText::new(t!("admin_password_confirm_label")).strong().color(theme::c_text())); + ui.horizontal(|ui| { + if self.show_root_password_confirm { + ui.text_edit_singleline(&mut state.root_password_confirm); + } else { + ui.add(egui::TextEdit::singleline(&mut state.root_password_confirm).password(true)); + } + if ui.small_button(if self.show_root_password_confirm { "🙈" } else { "👁" }).clicked() { + self.show_root_password_confirm = !self.show_root_password_confirm; + } + if !state.root_password_confirm.is_empty() { + if state.root_password == state.root_password_confirm { + ui.colored_label(theme::c_success(), "✓"); + } else { + ui.colored_label(theme::c_error(), t!("passwords_no_match")); + } + } + }); + ui.end_row(); + }); + }); + } + + // Hata mesajları gösterimi ui.add_space(8.0); if !state.username.is_empty() && !is_valid_username(&state.username) { - ui.colored_label( - egui::Color32::from_rgb(200, 60, 40), - t!("username_invalid"), - ); + ui.colored_label(theme::c_error(), t!("username_invalid")); } if !state.hostname.is_empty() && !is_valid_hostname(&state.hostname) { - ui.colored_label( - egui::Color32::from_rgb(200, 60, 40), - t!("hostname_invalid"), - ); + ui.colored_label(theme::c_error(), t!("hostname_invalid")); } } fn is_complete(&self, state: &GlobalState) -> bool { - is_valid_username(&state.username) + let user_ok = is_valid_username(&state.username) && is_valid_hostname(&state.hostname) - && !state.password.is_empty() - && state.password == state.password_confirm + && state.password.len() >= 4 + && state.password == state.password_confirm; + + if self.use_same_for_admin { + user_ok + } else { + user_ok && ( + state.root_password.is_empty() + || (state.root_password.len() >= 4 && state.root_password == state.root_password_confirm) + ) + } } } diff --git a/src/ui/slideshow.rs b/src/ui/slideshow.rs index 5a6e4f5..9fb0487 100644 --- a/src/ui/slideshow.rs +++ b/src/ui/slideshow.rs @@ -5,6 +5,7 @@ //! Masaüstü ortamına göre farklı slayt setleri yüklenebilir. use eframe::egui; +use std::collections::HashMap; use std::time::{Duration, Instant}; use rust_i18n::t; use std::borrow::Cow; @@ -19,22 +20,34 @@ pub struct Slide { /// Masaüstü ortamına göre slayt seti döner. /// ISO meta verisi ileride `/etc/livecd-desktop` gibi bir dosyadan okunabilir. -pub fn slides_for_desktop(desktop: &str) -> Vec { - let mut slides = default_slides(); - let specific_slides = match desktop { - "budgie" => budgie_slides(), - "cinnamon" => cinnamon_slides(), - "gnome" => gnome_slides(), - "kde" | "plasma" => kde_slides(), - "lxde" => lxde_slides(), - "lxqt" => lxqt_slides(), - "lumina" => lumina_slides(), - "mate" => mate_slides(), - "xfce" => xfce_slides(), - _ => vec![], - }; - slides.extend(specific_slides); - slides +pub fn slides_for_desktop(desktop: &str, branding_slides: &[crate::branding::SlideItem]) -> Vec { + if !branding_slides.is_empty() { + branding_slides + .iter() + .filter(|item| item.desktop.is_empty() || item.desktop.to_lowercase() == desktop.to_lowercase()) + .map(|item| Slide { + icon: Box::leak(item.icon.clone().into_boxed_str()), + title: Cow::Owned(item.title.clone()), + description: Cow::Owned(item.description.clone()), + }) + .collect() + } else { + let mut slides = default_slides(); + let specific_slides = match desktop { + "budgie" => budgie_slides(), + "cinnamon" => cinnamon_slides(), + "gnome" => gnome_slides(), + "kde" | "plasma" => kde_slides(), + "lxde" => lxde_slides(), + "lxqt" => lxqt_slides(), + "lumina" => lumina_slides(), + "mate" => mate_slides(), + "xfce" => xfce_slides(), + _ => vec![], + }; + slides.extend(specific_slides); + slides + } } fn default_slides() -> Vec { @@ -175,7 +188,7 @@ fn lxde_slides() -> Vec { description: t!("lxde_description"), }, Slide { - icon: "lxde_openbox_title", + icon: "lxde_openbox", title: t!("lxde_openbox_title"), description: t!("lxde_openbox_description"), }, @@ -185,7 +198,7 @@ fn lxde_slides() -> Vec { description: t!("lxde_pcmanfm_description"), }, Slide { - icon: "lxde_lxpanel_title", + icon: "lxde_lxpanel", title: t!("lxde_lxpanel_title"), description: t!("lxde_lxpanel_description"), }, @@ -200,7 +213,7 @@ fn lxqt_slides() -> Vec { description: t!("lxqt_description"), }, Slide { - icon: "lxqt_panel_title", + icon: "lxqt_panel", title: t!("lxqt_panel_title"), description: t!("lxqt_panel_description"), }, @@ -220,17 +233,17 @@ fn lumina_slides() -> Vec { description: t!("lumina_description"), }, Slide { - icon: "lumina_applications_title", + icon: "lumina_applications", title: t!("lumina_applications_title"), description: t!("lumina_applications_description"), }, Slide { - icon: "lumina_theme_title", + icon: "lumina_theme", title: t!("lumina_theme_title"), description: t!("lumina_theme_description"), }, Slide { - icon: "lumina_panel_title", + icon: "lumina_panel", title: t!("lumina_panel_title"), description: t!("lumina_panel_description"), }, @@ -250,12 +263,12 @@ fn mate_slides() -> Vec { description: t!("mate_caja_description"), }, Slide { - icon: "mate_settings_title", + icon: "mate_settings", title: t!("mate_settings_title"), description: t!("mate_settings_description"), }, Slide { - icon: "mate_applications_title", + icon: "mate_applications", title: t!("mate_applications_title"), description: t!("mate_applications_description"), }, @@ -270,7 +283,7 @@ fn xfce_slides() -> Vec { description: t!("xfce_description"), }, Slide { - icon: "xfce_panel_title", + icon: "xfce_panel", title: t!("xfce_panel_title"), description: t!("xfce_panel_description"), }, @@ -288,20 +301,23 @@ fn xfce_slides() -> Vec { } -// ─── Slayt gösterisi widget'ı ──────────────────────────────────── +// ─── Slayt gösterisi widget'ı ─────────────────────────────────────────── pub struct Slideshow { slides: Vec, current: usize, last_advance: Instant, + /// branding.toml'dan gelen ikon-adı → `file://` URI eşlemesi + icons: HashMap, } impl Slideshow { - pub fn new(desktop: &str) -> Self { + pub fn new(desktop: &str, icons: HashMap, branding_slides: Vec) -> Self { Self { - slides: slides_for_desktop(desktop), + slides: slides_for_desktop(desktop, &branding_slides), current: 0, last_advance: Instant::now(), + icons, } } @@ -321,34 +337,9 @@ impl Slideshow { } let slide = &self.slides[self.current]; - let is_dark = ui.visuals().dark_mode; - // Eşleşen slayt simgeleri için görsel belirle - let image_source = match slide.icon { - "budgie" => Some(egui::include_image!("../../assets/budgie.png")), - "cinnamon" => Some(egui::include_image!("../../assets/cinnamon.png")), - "community" => Some(egui::include_image!("../../assets/community.png")), - "file_manager" => Some(egui::include_image!("../../assets/file_manager.png")), - "gnome" => Some(egui::include_image!("../../assets/gnome.png")), - "kde" => Some(egui::include_image!("../../assets/kde.png")), - "lxqt" => Some(egui::include_image!("../../assets/lxqt.png")), - "lumina" => Some(egui::include_image!("../../assets/lumina.png")), - "lxde" => Some(egui::include_image!("../../assets/lxde.png")), - "mate" => Some(egui::include_image!("../../assets/mate.png")), - "pisilogo" => { - if is_dark { - Some(egui::include_image!("../../assets/pisi-logo-dark.png")) - } else { - Some(egui::include_image!("../../assets/pisi-logo-light.png")) - } - } - "pisi" => Some(egui::include_image!("../../assets/pisi-software-center.png")), - "pisi_speed" => Some(egui::include_image!("../../assets/pisi_speed.png")), - "securety" => Some(egui::include_image!("../../assets/securety.png")), - "xfce" => Some(egui::include_image!("../../assets/xfce.png")), - "xfce_settings" => Some(egui::include_image!("../../assets/xfce_settings.png")), - _ => None, - }; + // İkona karşılık gelen URI'yi branding haritasından al + let resolved_uri = self.icons.get(slide.icon).cloned(); egui::Frame::none() .fill(crate::ui::theme::c_bg_widget()) @@ -358,9 +349,9 @@ impl Slideshow { ui.set_min_height(180.0); ui.vertical_centered(|ui| { // Simge veya Görsel - if let Some(img) = image_source { + if let Some(uri) = resolved_uri { ui.add( - egui::Image::new(img) + egui::Image::from_uri(uri) .max_height(64.0) .rounding(egui::Rounding::same(8.0)), ); diff --git a/yali-rs b/yali-rs new file mode 100644 index 0000000..04564bd Binary files /dev/null and b/yali-rs differ