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