├── .gitignore
├── LICENSE
├── LICENSE.CC-BY-SA-4.0
├── README.md
├── dist
├── com.solus_project.Installer.desktop
├── install-symbolic.svg
├── org.freedesktop.policykit.pkexec.policy
└── os-installer-wrapper
├── landing.png
├── os-installer-gtk
├── os_installer2
├── __init__.py
├── application.py
├── data
│ ├── install-complete.png
│ ├── install-complete.svg
│ ├── install-solus-192-arc-style.png
│ ├── install-solus-192-arc-style.svg
│ ├── livepreview-192-arc-style.png
│ ├── livepreview-192-arc-style.svg
│ ├── picklocation-192-arc-style.png
│ ├── picklocation-192-arc-style.svg
│ └── styling.css
├── diskman.py
├── diskops.py
├── mainwindow.py
├── pages
│ ├── __init__.py
│ ├── basepage.py
│ ├── complete.py
│ ├── disk_location.py
│ ├── geoip.py
│ ├── keyboard.py
│ ├── language.py
│ ├── location.py
│ ├── partitioning.py
│ ├── progress.py
│ ├── summary.py
│ ├── system.py
│ ├── timezone.py
│ └── users.py
├── permissions.py
├── postinstall.py
├── strategy.py
├── tz.py
└── users.py
├── setup.py
└── test.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | build/
3 | *.egg-info/*
4 |
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 | {description}
294 | Copyright (C) {year} {fullname}
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | {signature of Ty Coon}, 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
--------------------------------------------------------------------------------
/LICENSE.CC-BY-SA-4.0:
--------------------------------------------------------------------------------
1 | Attribution-ShareAlike 4.0 International
2 |
3 | =======================================================================
4 |
5 | Creative Commons Corporation ("Creative Commons") is not a law firm and
6 | does not provide legal services or legal advice. Distribution of
7 | Creative Commons public licenses does not create a lawyer-client or
8 | other relationship. Creative Commons makes its licenses and related
9 | information available on an "as-is" basis. Creative Commons gives no
10 | warranties regarding its licenses, any material licensed under their
11 | terms and conditions, or any related information. Creative Commons
12 | disclaims all liability for damages resulting from their use to the
13 | fullest extent possible.
14 |
15 | Using Creative Commons Public Licenses
16 |
17 | Creative Commons public licenses provide a standard set of terms and
18 | conditions that creators and other rights holders may use to share
19 | original works of authorship and other material subject to copyright
20 | and certain other rights specified in the public license below. The
21 | following considerations are for informational purposes only, are not
22 | exhaustive, and do not form part of our licenses.
23 |
24 | Considerations for licensors: Our public licenses are
25 | intended for use by those authorized to give the public
26 | permission to use material in ways otherwise restricted by
27 | copyright and certain other rights. Our licenses are
28 | irrevocable. Licensors should read and understand the terms
29 | and conditions of the license they choose before applying it.
30 | Licensors should also secure all rights necessary before
31 | applying our licenses so that the public can reuse the
32 | material as expected. Licensors should clearly mark any
33 | material not subject to the license. This includes other CC-
34 | licensed material, or material used under an exception or
35 | limitation to copyright. More considerations for licensors:
36 | wiki.creativecommons.org/Considerations_for_licensors
37 |
38 | Considerations for the public: By using one of our public
39 | licenses, a licensor grants the public permission to use the
40 | licensed material under specified terms and conditions. If
41 | the licensor's permission is not necessary for any reason--for
42 | example, because of any applicable exception or limitation to
43 | copyright--then that use is not regulated by the license. Our
44 | licenses grant only permissions under copyright and certain
45 | other rights that a licensor has authority to grant. Use of
46 | the licensed material may still be restricted for other
47 | reasons, including because others have copyright or other
48 | rights in the material. A licensor may make special requests,
49 | such as asking that all changes be marked or described.
50 | Although not required by our licenses, you are encouraged to
51 | respect those requests where reasonable. More_considerations
52 | for the public:
53 | wiki.creativecommons.org/Considerations_for_licensees
54 |
55 | =======================================================================
56 |
57 | Creative Commons Attribution-ShareAlike 4.0 International Public
58 | License
59 |
60 | By exercising the Licensed Rights (defined below), You accept and agree
61 | to be bound by the terms and conditions of this Creative Commons
62 | Attribution-ShareAlike 4.0 International Public License ("Public
63 | License"). To the extent this Public License may be interpreted as a
64 | contract, You are granted the Licensed Rights in consideration of Your
65 | acceptance of these terms and conditions, and the Licensor grants You
66 | such rights in consideration of benefits the Licensor receives from
67 | making the Licensed Material available under these terms and
68 | conditions.
69 |
70 |
71 | Section 1 -- Definitions.
72 |
73 | a. Adapted Material means material subject to Copyright and Similar
74 | Rights that is derived from or based upon the Licensed Material
75 | and in which the Licensed Material is translated, altered,
76 | arranged, transformed, or otherwise modified in a manner requiring
77 | permission under the Copyright and Similar Rights held by the
78 | Licensor. For purposes of this Public License, where the Licensed
79 | Material is a musical work, performance, or sound recording,
80 | Adapted Material is always produced where the Licensed Material is
81 | synched in timed relation with a moving image.
82 |
83 | b. Adapter's License means the license You apply to Your Copyright
84 | and Similar Rights in Your contributions to Adapted Material in
85 | accordance with the terms and conditions of this Public License.
86 |
87 | c. BY-SA Compatible License means a license listed at
88 | creativecommons.org/compatiblelicenses, approved by Creative
89 | Commons as essentially the equivalent of this Public License.
90 |
91 | d. Copyright and Similar Rights means copyright and/or similar rights
92 | closely related to copyright including, without limitation,
93 | performance, broadcast, sound recording, and Sui Generis Database
94 | Rights, without regard to how the rights are labeled or
95 | categorized. For purposes of this Public License, the rights
96 | specified in Section 2(b)(1)-(2) are not Copyright and Similar
97 | Rights.
98 |
99 | e. Effective Technological Measures means those measures that, in the
100 | absence of proper authority, may not be circumvented under laws
101 | fulfilling obligations under Article 11 of the WIPO Copyright
102 | Treaty adopted on December 20, 1996, and/or similar international
103 | agreements.
104 |
105 | f. Exceptions and Limitations means fair use, fair dealing, and/or
106 | any other exception or limitation to Copyright and Similar Rights
107 | that applies to Your use of the Licensed Material.
108 |
109 | g. License Elements means the license attributes listed in the name
110 | of a Creative Commons Public License. The License Elements of this
111 | Public License are Attribution and ShareAlike.
112 |
113 | h. Licensed Material means the artistic or literary work, database,
114 | or other material to which the Licensor applied this Public
115 | License.
116 |
117 | i. Licensed Rights means the rights granted to You subject to the
118 | terms and conditions of this Public License, which are limited to
119 | all Copyright and Similar Rights that apply to Your use of the
120 | Licensed Material and that the Licensor has authority to license.
121 |
122 | j. Licensor means the individual(s) or entity(ies) granting rights
123 | under this Public License.
124 |
125 | k. Share means to provide material to the public by any means or
126 | process that requires permission under the Licensed Rights, such
127 | as reproduction, public display, public performance, distribution,
128 | dissemination, communication, or importation, and to make material
129 | available to the public including in ways that members of the
130 | public may access the material from a place and at a time
131 | individually chosen by them.
132 |
133 | l. Sui Generis Database Rights means rights other than copyright
134 | resulting from Directive 96/9/EC of the European Parliament and of
135 | the Council of 11 March 1996 on the legal protection of databases,
136 | as amended and/or succeeded, as well as other essentially
137 | equivalent rights anywhere in the world.
138 |
139 | m. You means the individual or entity exercising the Licensed Rights
140 | under this Public License. Your has a corresponding meaning.
141 |
142 |
143 | Section 2 -- Scope.
144 |
145 | a. License grant.
146 |
147 | 1. Subject to the terms and conditions of this Public License,
148 | the Licensor hereby grants You a worldwide, royalty-free,
149 | non-sublicensable, non-exclusive, irrevocable license to
150 | exercise the Licensed Rights in the Licensed Material to:
151 |
152 | a. reproduce and Share the Licensed Material, in whole or
153 | in part; and
154 |
155 | b. produce, reproduce, and Share Adapted Material.
156 |
157 | 2. Exceptions and Limitations. For the avoidance of doubt, where
158 | Exceptions and Limitations apply to Your use, this Public
159 | License does not apply, and You do not need to comply with
160 | its terms and conditions.
161 |
162 | 3. Term. The term of this Public License is specified in Section
163 | 6(a).
164 |
165 | 4. Media and formats; technical modifications allowed. The
166 | Licensor authorizes You to exercise the Licensed Rights in
167 | all media and formats whether now known or hereafter created,
168 | and to make technical modifications necessary to do so. The
169 | Licensor waives and/or agrees not to assert any right or
170 | authority to forbid You from making technical modifications
171 | necessary to exercise the Licensed Rights, including
172 | technical modifications necessary to circumvent Effective
173 | Technological Measures. For purposes of this Public License,
174 | simply making modifications authorized by this Section 2(a)
175 | (4) never produces Adapted Material.
176 |
177 | 5. Downstream recipients.
178 |
179 | a. Offer from the Licensor -- Licensed Material. Every
180 | recipient of the Licensed Material automatically
181 | receives an offer from the Licensor to exercise the
182 | Licensed Rights under the terms and conditions of this
183 | Public License.
184 |
185 | b. Additional offer from the Licensor -- Adapted Material.
186 | Every recipient of Adapted Material from You
187 | automatically receives an offer from the Licensor to
188 | exercise the Licensed Rights in the Adapted Material
189 | under the conditions of the Adapter's License You apply.
190 |
191 | c. No downstream restrictions. You may not offer or impose
192 | any additional or different terms or conditions on, or
193 | apply any Effective Technological Measures to, the
194 | Licensed Material if doing so restricts exercise of the
195 | Licensed Rights by any recipient of the Licensed
196 | Material.
197 |
198 | 6. No endorsement. Nothing in this Public License constitutes or
199 | may be construed as permission to assert or imply that You
200 | are, or that Your use of the Licensed Material is, connected
201 | with, or sponsored, endorsed, or granted official status by,
202 | the Licensor or others designated to receive attribution as
203 | provided in Section 3(a)(1)(A)(i).
204 |
205 | b. Other rights.
206 |
207 | 1. Moral rights, such as the right of integrity, are not
208 | licensed under this Public License, nor are publicity,
209 | privacy, and/or other similar personality rights; however, to
210 | the extent possible, the Licensor waives and/or agrees not to
211 | assert any such rights held by the Licensor to the limited
212 | extent necessary to allow You to exercise the Licensed
213 | Rights, but not otherwise.
214 |
215 | 2. Patent and trademark rights are not licensed under this
216 | Public License.
217 |
218 | 3. To the extent possible, the Licensor waives any right to
219 | collect royalties from You for the exercise of the Licensed
220 | Rights, whether directly or through a collecting society
221 | under any voluntary or waivable statutory or compulsory
222 | licensing scheme. In all other cases the Licensor expressly
223 | reserves any right to collect such royalties.
224 |
225 |
226 | Section 3 -- License Conditions.
227 |
228 | Your exercise of the Licensed Rights is expressly made subject to the
229 | following conditions.
230 |
231 | a. Attribution.
232 |
233 | 1. If You Share the Licensed Material (including in modified
234 | form), You must:
235 |
236 | a. retain the following if it is supplied by the Licensor
237 | with the Licensed Material:
238 |
239 | i. identification of the creator(s) of the Licensed
240 | Material and any others designated to receive
241 | attribution, in any reasonable manner requested by
242 | the Licensor (including by pseudonym if
243 | designated);
244 |
245 | ii. a copyright notice;
246 |
247 | iii. a notice that refers to this Public License;
248 |
249 | iv. a notice that refers to the disclaimer of
250 | warranties;
251 |
252 | v. a URI or hyperlink to the Licensed Material to the
253 | extent reasonably practicable;
254 |
255 | b. indicate if You modified the Licensed Material and
256 | retain an indication of any previous modifications; and
257 |
258 | c. indicate the Licensed Material is licensed under this
259 | Public License, and include the text of, or the URI or
260 | hyperlink to, this Public License.
261 |
262 | 2. You may satisfy the conditions in Section 3(a)(1) in any
263 | reasonable manner based on the medium, means, and context in
264 | which You Share the Licensed Material. For example, it may be
265 | reasonable to satisfy the conditions by providing a URI or
266 | hyperlink to a resource that includes the required
267 | information.
268 |
269 | 3. If requested by the Licensor, You must remove any of the
270 | information required by Section 3(a)(1)(A) to the extent
271 | reasonably practicable.
272 |
273 | b. ShareAlike.
274 |
275 | In addition to the conditions in Section 3(a), if You Share
276 | Adapted Material You produce, the following conditions also apply.
277 |
278 | 1. The Adapter's License You apply must be a Creative Commons
279 | license with the same License Elements, this version or
280 | later, or a BY-SA Compatible License.
281 |
282 | 2. You must include the text of, or the URI or hyperlink to, the
283 | Adapter's License You apply. You may satisfy this condition
284 | in any reasonable manner based on the medium, means, and
285 | context in which You Share Adapted Material.
286 |
287 | 3. You may not offer or impose any additional or different terms
288 | or conditions on, or apply any Effective Technological
289 | Measures to, Adapted Material that restrict exercise of the
290 | rights granted under the Adapter's License You apply.
291 |
292 |
293 | Section 4 -- Sui Generis Database Rights.
294 |
295 | Where the Licensed Rights include Sui Generis Database Rights that
296 | apply to Your use of the Licensed Material:
297 |
298 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right
299 | to extract, reuse, reproduce, and Share all or a substantial
300 | portion of the contents of the database;
301 |
302 | b. if You include all or a substantial portion of the database
303 | contents in a database in which You have Sui Generis Database
304 | Rights, then the database in which You have Sui Generis Database
305 | Rights (but not its individual contents) is Adapted Material,
306 |
307 | including for purposes of Section 3(b); and
308 | c. You must comply with the conditions in Section 3(a) if You Share
309 | all or a substantial portion of the contents of the database.
310 |
311 | For the avoidance of doubt, this Section 4 supplements and does not
312 | replace Your obligations under this Public License where the Licensed
313 | Rights include other Copyright and Similar Rights.
314 |
315 |
316 | Section 5 -- Disclaimer of Warranties and Limitation of Liability.
317 |
318 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
319 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
320 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
321 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
322 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
323 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
324 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
325 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
326 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
327 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
328 |
329 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
330 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
331 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
332 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
333 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
334 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
335 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
336 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
337 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
338 |
339 | c. The disclaimer of warranties and limitation of liability provided
340 | above shall be interpreted in a manner that, to the extent
341 | possible, most closely approximates an absolute disclaimer and
342 | waiver of all liability.
343 |
344 |
345 | Section 6 -- Term and Termination.
346 |
347 | a. This Public License applies for the term of the Copyright and
348 | Similar Rights licensed here. However, if You fail to comply with
349 | this Public License, then Your rights under this Public License
350 | terminate automatically.
351 |
352 | b. Where Your right to use the Licensed Material has terminated under
353 | Section 6(a), it reinstates:
354 |
355 | 1. automatically as of the date the violation is cured, provided
356 | it is cured within 30 days of Your discovery of the
357 | violation; or
358 |
359 | 2. upon express reinstatement by the Licensor.
360 |
361 | For the avoidance of doubt, this Section 6(b) does not affect any
362 | right the Licensor may have to seek remedies for Your violations
363 | of this Public License.
364 |
365 | c. For the avoidance of doubt, the Licensor may also offer the
366 | Licensed Material under separate terms or conditions or stop
367 | distributing the Licensed Material at any time; however, doing so
368 | will not terminate this Public License.
369 |
370 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
371 | License.
372 |
373 |
374 | Section 7 -- Other Terms and Conditions.
375 |
376 | a. The Licensor shall not be bound by any additional or different
377 | terms or conditions communicated by You unless expressly agreed.
378 |
379 | b. Any arrangements, understandings, or agreements regarding the
380 | Licensed Material not stated herein are separate from and
381 | independent of the terms and conditions of this Public License.
382 |
383 |
384 | Section 8 -- Interpretation.
385 |
386 | a. For the avoidance of doubt, this Public License does not, and
387 | shall not be interpreted to, reduce, limit, restrict, or impose
388 | conditions on any use of the Licensed Material that could lawfully
389 | be made without permission under this Public License.
390 |
391 | b. To the extent possible, if any provision of this Public License is
392 | deemed unenforceable, it shall be automatically reformed to the
393 | minimum extent necessary to make it enforceable. If the provision
394 | cannot be reformed, it shall be severed from this Public License
395 | without affecting the enforceability of the remaining terms and
396 | conditions.
397 |
398 | c. No term or condition of this Public License will be waived and no
399 | failure to comply consented to unless expressly agreed to by the
400 | Licensor.
401 |
402 | d. Nothing in this Public License constitutes or may be interpreted
403 | as a limitation upon, or waiver of, any privileges and immunities
404 | that apply to the Licensor or You, including from the legal
405 | processes of any jurisdiction or authority.
406 |
407 |
408 | =======================================================================
409 |
410 | Creative Commons is not a party to its public
411 | licenses. Notwithstanding, Creative Commons may elect to apply one of
412 | its public licenses to material it publishes and in those instances
413 | will be considered the “Licensor.” The text of the Creative Commons
414 | public licenses is dedicated to the public domain under the CC0 Public
415 | Domain Dedication. Except for the limited purpose of indicating that
416 | material is shared under a Creative Commons public license or as
417 | otherwise permitted by the Creative Commons policies published at
418 | creativecommons.org/policies, Creative Commons does not authorize the
419 | use of the trademark "Creative Commons" or any other trademark or logo
420 | of Creative Commons without its prior written consent including,
421 | without limitation, in connection with any unauthorized modifications
422 | to any of its public licenses or any other arrangements,
423 | understandings, or agreements concerning use of licensed material. For
424 | the avoidance of doubt, this paragraph does not form part of the
425 | public licenses.
426 |
427 | Creative Commons may be contacted at creativecommons.org.
428 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # os-installer
2 |
3 | os-installer is the official [Solus](https://getsol.us) installer.
4 |
5 | 
6 |
7 | ## License
8 |
9 | - The icons are the result of successive iterations by [Alexandros Felekidis](https://github.com/alpha/) and [Sam Hewitt](https://github.com/snwh/icons/tree/master/solus-installer). They are available under the terms of the [Creative Commons Attribution-Share Alike](https://creativecommons.org/licenses/by-sa/4.0/) License. Please see the [included license file](COPY.CC-BY-SA-4.0) for more details.
10 | - All code is available under the terms of the GPL-2.0 License. Please see [LICENSE](LICENSE) for more details.
--------------------------------------------------------------------------------
/dist/com.solus_project.Installer.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Name=Install OS
3 | Icon=system-software-install
4 | Exec=os-installer-wrapper
5 | Terminal=false
6 | Type=Application
7 | Categories=X-GNOME-Settings-Panel;GTK;System;Settings;
8 | Keywords=Install;SystemSettings;
9 |
10 |
--------------------------------------------------------------------------------
/dist/org.freedesktop.policykit.pkexec.policy:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 | SolusProject
7 | https://solus-project.com/
8 |
9 |
10 | Install Solus
11 | Authentication is required to install Solus
12 |
13 | no
14 | no
15 | yes
16 |
17 | /usr/bin/os-installer-gtk
18 | true
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/dist/os-installer-wrapper:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | nohup pkexec /usr/bin/os-installer-gtk
3 |
4 |
--------------------------------------------------------------------------------
/landing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getsolus/os-installer/a06bc5e4491f64de48366a264fbb50a003d148c2/landing.png
--------------------------------------------------------------------------------
/os-installer-gtk:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python2.7
2 | # -*- coding: utf-8 -*-
3 | #
4 | # This file is part of os-installer
5 | #
6 | # Copyright 2013-2019 Solus
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 2 of the License, or
11 | # (at your option) any later version.
12 | #
13 |
14 | import sys
15 | import os
16 | from os_installer2.application import InstallerApplication
17 | from os_installer2.permissions import PermissionsManager
18 | from os_installer2 import SOURCE_FILESYSTEM, join_resource_path
19 | from gi.repository import Gdk, GObject, Gtk, Gio
20 |
21 |
22 | def init_css():
23 | """ Set up the CSS before we throw any windows up """
24 | try:
25 | f = Gio.File.new_for_path(join_resource_path("styling.css"))
26 | css = Gtk.CssProvider()
27 | css.load_from_file(f)
28 | screen = Gdk.Screen.get_default()
29 | prio = Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
30 | Gtk.StyleContext.add_provider_for_screen(screen,
31 | css,
32 | prio)
33 | except Exception as e:
34 | print("Error loading CSS: {}".format(e))
35 |
36 |
37 | if __name__ == "__main__":
38 | if os.geteuid() != 0:
39 | sys.stderr.write("You must be root to use OsInstaller\n")
40 | sys.stderr.flush()
41 | sys.exit(1)
42 |
43 | # No source filesystem? No cookies for you!
44 | if not os.path.exists(SOURCE_FILESYSTEM):
45 | msg = "Source file system is missing, cannot continue.\n\n{}".format(
46 | SOURCE_FILESYSTEM)
47 |
48 | d = Gtk.MessageDialog(parent=None, flags=Gtk.DialogFlags.MODAL,
49 | type=Gtk.MessageType.WARNING,
50 | buttons=Gtk.ButtonsType.CLOSE,
51 | message_format=msg)
52 |
53 | d.run()
54 | d.destroy()
55 | sys.exit(1)
56 |
57 | # Immediately drop permissions before we init GTK
58 | p = PermissionsManager()
59 | p.down_permissions()
60 |
61 | GObject.threads_init()
62 | Gdk.threads_init()
63 |
64 | init_css()
65 |
66 | app = InstallerApplication()
67 | r = app.run(sys.argv)
68 | sys.exit(r)
69 |
--------------------------------------------------------------------------------
/os_installer2/__init__.py:
--------------------------------------------------------------------------------
1 | #!/bin/true
2 | # -*- coding: utf-8 -*-
3 | #
4 | # This file is part of os-installer
5 | #
6 | # Copyright 2013-2020 Solus
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 2 of the License, or
11 | # (at your option) any later version.
12 | #
13 |
14 | import gi.repository
15 | import os
16 | import locale
17 | gi.require_version('Gtk', '3.0')
18 | gi.require_version('Gio', '2.0')
19 | gi.require_version('GnomeDesktop', '3.0')
20 | gi.require_version('TimezoneMap', '1.0')
21 |
22 |
23 | # The path of the source filesystem
24 | SOURCE_FILESYSTEM = "/run/initramfs/live/LiveOS/squashfs.img"
25 |
26 | # The guy inside that is actually our filesystem to copy
27 | INNER_FILESYSTEM = "LiveOS/rootfs.img"
28 |
29 | # Absolute minimum size
30 | MB = 1000 * 1000
31 | GB = 1000 * MB
32 | MIN_REQUIRED_SIZE = 10 * GB
33 |
34 |
35 | def get_resource_path():
36 | bsPath = os.path.dirname(__file__)
37 | return os.path.join(bsPath, "data")
38 |
39 |
40 | def join_resource_path(path):
41 | return os.path.join(get_resource_path(), path)
42 |
43 |
44 | def format_size(size):
45 | """ Get the *abyte size (not mebibyte) format """
46 | labels = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
47 |
48 | for i, label in enumerate(labels):
49 | if size < 1000 or i == len(labels) - 1:
50 | return size, label
51 | size = float(size / 1000)
52 |
53 |
54 | def format_size_local(size, double_precision=False):
55 | """ Get the locale appropriate representation of the size """
56 | numeric, code = format_size(size)
57 | fmt = "%.1f" if not double_precision else "%.2f"
58 | SZ = "{} {}".format(locale.format(fmt, numeric, grouping=True), code)
59 | return SZ
60 |
--------------------------------------------------------------------------------
/os_installer2/application.py:
--------------------------------------------------------------------------------
1 | #!/bin/true
2 | # -*- coding: utf-8 -*-
3 | #
4 | # This file is part of os-installer
5 | #
6 | # Copyright 2013-2020 Solus
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 2 of the License, or
11 | # (at your option) any later version.
12 | #
13 |
14 | from .mainwindow import MainWindow
15 | from gi.repository import Gio, Gtk
16 |
17 | APP_ID = "com.solus_project.Installer"
18 |
19 |
20 | class InstallerApplication(Gtk.Application):
21 |
22 | app_window = None
23 |
24 | def __init__(self):
25 | Gtk.Application.__init__(self,
26 | application_id=APP_ID,
27 | flags=Gio.ApplicationFlags.FLAGS_NONE)
28 | self.connect("activate", self.on_activate)
29 |
30 | def on_activate(self, app):
31 | if self.app_window is None:
32 | self.app_window = MainWindow(self)
33 | self.app_window.present()
34 |
--------------------------------------------------------------------------------
/os_installer2/data/install-complete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getsolus/os-installer/a06bc5e4491f64de48366a264fbb50a003d148c2/os_installer2/data/install-complete.png
--------------------------------------------------------------------------------
/os_installer2/data/install-complete.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
277 |
--------------------------------------------------------------------------------
/os_installer2/data/install-solus-192-arc-style.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getsolus/os-installer/a06bc5e4491f64de48366a264fbb50a003d148c2/os_installer2/data/install-solus-192-arc-style.png
--------------------------------------------------------------------------------
/os_installer2/data/install-solus-192-arc-style.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
263 |
--------------------------------------------------------------------------------
/os_installer2/data/livepreview-192-arc-style.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getsolus/os-installer/a06bc5e4491f64de48366a264fbb50a003d148c2/os_installer2/data/livepreview-192-arc-style.png
--------------------------------------------------------------------------------
/os_installer2/data/livepreview-192-arc-style.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
319 |
--------------------------------------------------------------------------------
/os_installer2/data/picklocation-192-arc-style.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getsolus/os-installer/a06bc5e4491f64de48366a264fbb50a003d148c2/os_installer2/data/picklocation-192-arc-style.png
--------------------------------------------------------------------------------
/os_installer2/data/picklocation-192-arc-style.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
256 |
--------------------------------------------------------------------------------
/os_installer2/data/styling.css:
--------------------------------------------------------------------------------
1 | list {
2 | background-image: none;
3 | background-color: transparent;
4 | }
5 |
6 | scrolledwindow {
7 | background-image: none;
8 | background-color: transparent;
9 | }
10 |
--------------------------------------------------------------------------------
/os_installer2/mainwindow.py:
--------------------------------------------------------------------------------
1 | #!/bin/true
2 | # -*- coding: utf-8 -*-
3 | #
4 | # This file is part of os-installer
5 | #
6 | # Copyright 2013-2020 Solus
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 2 of the License, or
11 | # (at your option) any later version.
12 | #
13 | from gi.repository import Gtk, GLib, Gdk
14 | from .diskman import DiskManager
15 | from .permissions import PermissionsManager
16 | from .pages.language import InstallerLanguagePage
17 | from .pages.location import InstallerLocationPage
18 | from .pages.geoip import InstallerGeoipPage
19 | from .pages.keyboard import InstallerKeyboardPage
20 | from .pages.timezone import InstallerTimezonePage
21 | from .pages.disk_location import InstallerDiskLocationPage
22 | from .pages.partitioning import InstallerPartitioningPage
23 | from .pages.system import InstallerSystemPage
24 | from .pages.users import InstallerUsersPage
25 | from .pages.summary import InstallerSummaryPage
26 | from .pages.progress import InstallerProgressPage
27 | from .pages.complete import InstallationCompletePage
28 | import sys
29 | import threading
30 | import traceback
31 | import os
32 |
33 | class FancyLabel(Gtk.Label):
34 |
35 | page_id = None
36 |
37 | def __init__(self, page):
38 | Gtk.Label.__init__(self)
39 | self.set_label(page.get_sidebar_title())
40 | self.page_id = page.get_name()
41 | self.set_halign(Gtk.Align.START)
42 | self.set_property("margin", 6)
43 | self.set_property("margin-start", 24)
44 | self.set_property("margin-end", 24)
45 | self.get_style_context().add_class("dim-label")
46 |
47 | class InstallInfo:
48 | """ For tracking purposes between pages """
49 |
50 | # Chosen locale
51 | locale = None
52 | locale_sz = None
53 |
54 | # Chosen keyboard
55 | keyboard = None
56 | keyboard_sz = None
57 |
58 | # Main Window reference
59 | owner = None
60 |
61 | # Timezone for the system
62 | timezone = None
63 | timezone_c = None
64 |
65 | # The chosen disk strategy
66 | strategy = None
67 |
68 | # Whether to enable geoip lookups
69 | enable_geoip = False
70 | cached_location = None
71 | cached_timezone = None
72 |
73 | # system hostname
74 | hostname = None
75 |
76 | # Windows was detected
77 | windows_present = False
78 |
79 | users = None
80 |
81 | # Disk prober
82 | prober = None
83 |
84 | # Bootloader target
85 | bootloader = None
86 | bootloader_sz = None
87 | bootloader_install = False
88 |
89 | invalidated = False
90 |
91 | def __init__(self):
92 | self.users = list()
93 | self.bootloader_install = True
94 |
95 |
96 | class MainWindow(Gtk.ApplicationWindow):
97 |
98 | stack = None
99 | installer_stack = None
100 | installer_page = None
101 | application = None
102 | prev_button = None
103 | next_button = None
104 |
105 | box_labels = None
106 |
107 | pages = list()
108 | page_index = 0
109 |
110 | info = None
111 |
112 | disk_manager = None
113 | perms = None
114 |
115 | # Skip direction
116 | skip_forward = False
117 |
118 | can_quit = True
119 | is_final_step = False
120 |
121 | image_step = None
122 | label_step = None
123 | plasma = False
124 |
125 | def quit_handler(self, w, udata=None):
126 | """ Ensure quit stuff is sane ... """
127 | if not self.can_quit:
128 | True
129 | return False
130 |
131 | def __init__(self, app):
132 | Gtk.ApplicationWindow.__init__(self, application=app)
133 | self.application = app
134 |
135 | settings = Gtk.Settings.get_default()
136 | settings.set_property("gtk-application-prefer-dark-theme", False)
137 |
138 | self.image_step = Gtk.Image.new_from_icon_name("system-software-install", Gtk.IconSize.DIALOG)
139 | self.image_step.set_property("margin", 8)
140 | self.label_step = Gtk.Label.new("")
141 | self.label_step.set_property("margin", 8)
142 |
143 | self.set_title("Install Solus")
144 |
145 | self.headerbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
146 | self.headerbox.get_style_context().add_class("header-box")
147 | self.headerbox.pack_start(self.image_step, False, False, 0)
148 | # self.headerbox.pack_start(self.label_step, False, False, 0)
149 | self.box_labels = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
150 | self.box_labels.set_valign(Gtk.Align.START)
151 | self.box_labels.set_property("margin-bottom", 40)
152 | self.box_labels.set_property("margin-top", 20)
153 | self.headerbox.pack_start(self.box_labels, True, True, 0)
154 |
155 | vanity_icon = "start-here-symbolic"
156 | vanity_string = ""
157 |
158 | if os.path.exists("/usr/bin/budgie-panel"):
159 | vanity_icon = "budgie-desktop-symbolic"
160 | vanity_string = "Solus Budgie"
161 | elif os.path.exists("/usr/bin/gnome-shell"):
162 | vanity_icon = "desktop-environment-gnome"
163 | vanity_string = "Solus GNOME"
164 | elif os.path.exists("/usr/bin/mate-panel"):
165 | vanity_icon = "mate"
166 | vanity_string = "Solus MATE"
167 | elif os.path.exists("/usr/bin/plasmashell"):
168 | vanity_icon = "plasma"
169 | vanity_string = "Solus Plasma"
170 | self.plasma = True
171 | else:
172 | vanity_icon = "start-here-solus"
173 | vanity_string = "Solus"
174 |
175 | img_vanity = Gtk.Image.new_from_icon_name(vanity_icon, Gtk.IconSize.LARGE_TOOLBAR)
176 | img_vanity.set_property("margin", 8)
177 | img_vanity.set_property("margin-top", 0)
178 | lab_vanity = Gtk.Label.new(vanity_string)
179 | lab_vanity.set_property("margin-start", 4)
180 | lab_vanity.set_property("margin-end", 8)
181 | lab_vanity.set_property("margin-bottom", 8)
182 | self.headerbox.pack_end(lab_vanity, False, False, 0)
183 | self.headerbox.pack_end(img_vanity, False, False, 0)
184 |
185 | self.set_icon_name("system-software-install")
186 | self.connect("delete-event", self.quit_handler)
187 |
188 | self.set_position(Gtk.WindowPosition.CENTER)
189 | self.set_default_size(768, 500)
190 |
191 | # Main "install" page
192 | self.installer_page = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
193 | self.installer_stack = Gtk.Stack()
194 | self.installer_page.pack_start(self.installer_stack, True, True, 0)
195 |
196 | self.installer_wrap = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
197 | self.installer_wrap.pack_start(self.headerbox, False, False, 0)
198 | sep = Gtk.Separator.new(Gtk.Orientation.VERTICAL)
199 | self.installer_wrap.pack_start(sep, False, False, 0)
200 | self.installer_wrap.pack_start(self.installer_page, True, True, 0)
201 |
202 | ltr = Gtk.StackTransitionType.SLIDE_LEFT_RIGHT
203 | self.installer_stack.set_transition_type(ltr)
204 | self.add(self.installer_wrap)
205 |
206 |
207 | # nav buttons
208 | bbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 4)
209 | bbox.set_halign(Gtk.Align.END)
210 | self.prev_button = Gtk.Button.new_with_label("Previous")
211 | self.next_button = Gtk.Button.new_with_label("Next")
212 | bbox.pack_start(self.prev_button, False, False, 0)
213 | bbox.pack_start(self.next_button, False, False, 0)
214 | bbox.set_margin_top(10)
215 | bbox.set_margin_bottom(10)
216 | bbox.set_margin_end(10)
217 | self.prev_button.set_property("margin-start", 4)
218 | self.next_button.set_property("margin-start", 4)
219 |
220 | # sep before nav
221 | sep = Gtk.Separator.new(Gtk.Orientation.HORIZONTAL)
222 | sep.set_margin_top(20)
223 | self.installer_page.pack_end(bbox, False, 0, 0)
224 | self.installer_page.pack_end(sep, False, 0, 0)
225 |
226 | self.info = InstallInfo()
227 | self.info.owner = self
228 |
229 | self.get_style_context().add_class("installer-window")
230 |
231 | # Hook up actions
232 | self.prev_button.connect("clicked", lambda x: self.prev_page())
233 | self.next_button.connect("clicked", lambda x: self.next_page())
234 | # Load other pages here into installer_stack
235 | try:
236 | self.add_installer_page(InstallerLanguagePage())
237 | self.add_installer_page(InstallerLocationPage())
238 | self.add_installer_page(InstallerGeoipPage())
239 | self.add_installer_page(InstallerKeyboardPage())
240 | self.add_installer_page(InstallerTimezonePage())
241 | self.add_installer_page(InstallerDiskLocationPage())
242 | self.add_installer_page(InstallerPartitioningPage())
243 | self.add_installer_page(InstallerSystemPage())
244 | self.add_installer_page(InstallerUsersPage())
245 | self.add_installer_page(InstallerSummaryPage(self.plasma))
246 | self.add_installer_page(InstallerProgressPage())
247 | self.add_installer_page(InstallationCompletePage())
248 | except Exception as e:
249 | print("Fatal error during startup: {}".format(e))
250 | traceback.print_exc(file=sys.stderr)
251 | sys.exit(1)
252 |
253 | # Shared helpers
254 | self.perms = PermissionsManager()
255 | self.disk_manager = DiskManager()
256 |
257 | self.update_current_page()
258 | self.show_all()
259 |
260 | GLib.idle_add(self.start_threads)
261 |
262 | def start_threads(self):
263 | self.set_can_next(False)
264 | start_thr = threading.Thread(target=self.perform_inits)
265 | start_thr.daemon = True
266 | start_thr.start()
267 | return False
268 |
269 | def perform_inits(self):
270 | self.info.owner.get_perms_manager().down_permissions()
271 | """ Force expensive children to init outside main thread """
272 | for page in self.pages:
273 | try:
274 | page.do_expensive_init()
275 | except Exception as e:
276 | print("Fatal exception initialising: {}".format(e))
277 |
278 |
279 | def add_installer_page(self, page):
280 | """ Work a page into the set """
281 | self.installer_stack.add_named(page, page.get_name())
282 | lab = FancyLabel(page)
283 | self.box_labels.pack_start(lab, False, False, 0)
284 | self.pages.append(page)
285 |
286 | def next_page(self):
287 | """ Move to next page """
288 | if self.is_final_step:
289 | msg = "Installation will make changes to your disks, and could " \
290 | "result in data loss.\nDo you wish to install?"
291 | d = Gtk.MessageDialog(parent=self, flags=Gtk.DialogFlags.MODAL,
292 | type=Gtk.MessageType.WARNING,
293 | buttons=Gtk.ButtonsType.OK_CANCEL,
294 | message_format=msg)
295 |
296 | r = d.run()
297 | d.destroy()
298 | if r != Gtk.ResponseType.OK:
299 | return
300 |
301 | self.skip_forward = True
302 | index = self.page_index + 1
303 | if index >= len(self.pages):
304 | return
305 | page = self.pages[index]
306 | if page.is_hidden():
307 | index += 1
308 | self.page_index = index
309 | self.update_current_page()
310 |
311 | def prev_page(self):
312 | self.skip_forward = False
313 | """ Move to previous page """
314 | index = self.page_index - 1
315 | if index < 0:
316 | return
317 | page = self.pages[index]
318 | if page.is_hidden():
319 | index -= 1
320 | self.page_index = index
321 | self.update_current_page()
322 |
323 | def update_current_page(self):
324 | page = self.pages[self.page_index]
325 | self.set_final_step(False)
326 |
327 | if self.page_index == len(self.pages) - 1:
328 | self.set_can_next(False)
329 | else:
330 | # TODO: Have pages check next-ness
331 | self.set_can_next(True)
332 | if self.page_index == 0:
333 | self.set_can_previous(False)
334 | else:
335 | self.set_can_previous(True)
336 | page.prepare(self.info)
337 |
338 | for label in self.box_labels.get_children():
339 | if label.page_id == page.get_name():
340 | label.get_style_context().remove_class("dim-label")
341 | else:
342 | label.get_style_context().add_class("dim-label")
343 |
344 | iname = page.get_icon_name(plasma=self.plasma)
345 | self.image_step.set_from_icon_name(iname,
346 | Gtk.IconSize.DIALOG)
347 | # self.image_step.set_pixel_size(32)
348 | self.installer_stack.set_visible_child_name(page.get_name())
349 |
350 | def set_can_previous(self, can_prev):
351 | self.prev_button.set_sensitive(can_prev)
352 |
353 | def set_can_next(self, can_next):
354 | self.next_button.set_sensitive(can_next)
355 |
356 | def set_final_step(self, final):
357 | """ Mark this as the final step, should also
358 | add a prompt on selection """
359 | if final:
360 | self.next_button.set_label("Install")
361 | else:
362 | self.next_button.set_label("Next")
363 | self.is_final_step = final
364 |
365 | def set_can_quit(self, can_quit):
366 | """ Override quit handling """
367 | self.can_quit = can_quit
368 | if not self.can_quit:
369 | self.prev_button.hide()
370 | self.next_button.hide()
371 | # self.set_deletable(False)
372 | else:
373 | self.prev_button.show_all()
374 | self.next_button.show_all()
375 | # self.set_deletable(True)
376 |
377 | def get_disk_manager(self):
378 | """ Return our disk manager object """
379 | return self.disk_manager
380 |
381 | def get_perms_manager(self):
382 | """ Return permission manager """
383 | return self.perms
384 |
385 | def skip_page(self):
386 | GLib.idle_add(self._skip_page)
387 |
388 | def _skip_page(self):
389 | """ Allow pages to request skipping to next page """
390 | if self.skip_forward:
391 | self.next_page()
392 | else:
393 | self.prev_page()
394 | return False
395 |
--------------------------------------------------------------------------------
/os_installer2/pages/__init__.py:
--------------------------------------------------------------------------------
1 | #!/bin/true
2 | # -*- coding: utf-8 -*-
3 | #
4 | # This file is part of os-installer
5 | #
6 | # Copyright 2013-2020 Solus
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 2 of the License, or
11 | # (at your option) any later version.
12 | #
13 |
14 | # Credit: gnome-inital-setup
15 | default_locales = [
16 | "en_US.UTF-8",
17 | "de_DE.UTF-8",
18 | "fr_FR.UTF-8",
19 | "es_ES.UTF-8",
20 | "zh_CN.UTF-8",
21 | "ja_JP.UTF-8",
22 | "ru_RU.UTF-8",
23 | "ar_EG.UTF-8"
24 | ]
25 |
--------------------------------------------------------------------------------
/os_installer2/pages/basepage.py:
--------------------------------------------------------------------------------
1 | #!/bin/true
2 | # -*- coding: utf-8 -*-
3 | #
4 | # This file is part of os-installer
5 | #
6 | # Copyright 2013-2020 Solus
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 2 of the License, or
11 | # (at your option) any later version.
12 | #
13 |
14 | from gi.repository import Gtk
15 |
16 |
17 | class BasePage(Gtk.Box):
18 | """ Base widget for all page implementations to save on duplication. """
19 |
20 | def __init__(self):
21 | Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL, spacing=0)
22 |
23 | mk = u"{}".format(self.get_title())
24 | lab = Gtk.Label.new(mk)
25 | lab.set_property("margin-top", 10)
26 | lab.set_property("margin-start", 20)
27 | lab.set_property("margin-bottom", 10)
28 | lab.set_use_markup(True)
29 | lab.set_halign(Gtk.Align.START)
30 | self.pack_start(lab, False, False, 0)
31 |
32 | sep = Gtk.Separator.new(Gtk.Orientation.HORIZONTAL)
33 | self.pack_start(sep, False, False, 0)
34 |
35 | def get_title(self):
36 | return None
37 |
38 | def get_sidebar_title(self):
39 | return "Not implemented.."
40 |
41 | def get_name(self):
42 | return None
43 |
44 | def get_icon_name(self, plasma=False):
45 | return "dialog-error"
46 |
47 | def get_primary_answer(self):
48 | return None
49 |
50 | def prepare(self, info):
51 | pass
52 |
53 | def seed(self, setup):
54 | pass
55 |
56 | def is_hidden(self):
57 | return False
58 |
59 | def do_expensive_init(self):
60 | """ Do expensive startup tasks outside of the main thread """
61 | pass
62 |
--------------------------------------------------------------------------------
/os_installer2/pages/complete.py:
--------------------------------------------------------------------------------
1 | #!/bin/true
2 | # -*- coding: utf-8 -*-
3 | #
4 | # This file is part of os-installer
5 | #
6 | # Copyright 2013-2020 Solus
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 2 of the License, or
11 | # (at your option) any later version.
12 | #
13 |
14 | from .basepage import BasePage
15 | from gi.repository import Gtk
16 | from os_installer2 import join_resource_path as jrp
17 | import dbus
18 |
19 |
20 | class InstallationCompletePage(BasePage):
21 | """ Last page seen by users """
22 |
23 | def __init__(self):
24 | BasePage.__init__(self)
25 |
26 | box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
27 | self.pack_start(box, True, True, 0)
28 | box.set_border_width(40)
29 | box.set_valign(Gtk.Align.CENTER)
30 | box.set_halign(Gtk.Align.CENTER)
31 |
32 | img = Gtk.Image.new_from_file(jrp("install-complete.png"))
33 | img.set_halign(Gtk.Align.CENTER)
34 | box.pack_start(img, False, False, 0)
35 |
36 | lab = Gtk.Label("You may now exit the installer.")
37 | lab.set_use_markup(True)
38 | lab.set_halign(Gtk.Align.CENTER)
39 | lab.set_property("xalign", 0.5)
40 | box.pack_start(lab, False, False, 0)
41 | lab.set_property("margin-top", 5)
42 | lab.set_property("margin-bottom", 5)
43 |
44 | lab = Gtk.Label("Restart and then remove any installation media to "
45 | "start using your new operating system.")
46 | lab.set_use_markup(True)
47 | lab.set_halign(Gtk.Align.CENTER)
48 | lab.set_property("xalign", 0.5)
49 | box.pack_start(lab, False, False, 0)
50 |
51 | # Reboot button, everyone loves these..
52 | reboot_button = Gtk.Button("Restart now")
53 | reboot_button.get_style_context().add_class("suggested-action")
54 | reboot_button.set_property("margin-top", 10)
55 | reboot_button.set_halign(Gtk.Align.CENTER)
56 | reboot_button.connect('clicked', self.reboot)
57 | box.pack_start(reboot_button, False, False, 0)
58 |
59 | def get_title(self):
60 | return "Installation complete!"
61 |
62 | def get_name(self):
63 | return "complete"
64 |
65 | def get_sidebar_title(self):
66 | return "Complete"
67 |
68 | def get_icon_name(self, plasma=False):
69 | return "start-here-solus"
70 |
71 | def reboot(self, btn, udata=None):
72 | try:
73 | bus = dbus.SystemBus()
74 | rskel = bus.get_object('org.freedesktop.login1',
75 | '/org/freedesktop/login1')
76 | iface = dbus.Interface(rskel, 'org.freedesktop.login1.Manager')
77 | iface.Reboot(True)
78 | except Exception as ex:
79 | print("Exception rebooting: {}".format(ex))
80 |
--------------------------------------------------------------------------------
/os_installer2/pages/disk_location.py:
--------------------------------------------------------------------------------
1 | #!/bin/true
2 | # -*- coding: utf-8 -*-
3 | #
4 | # This file is part of os-installer
5 | #
6 | # Copyright 2013-2020 Solus
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 2 of the License, or
11 | # (at your option) any later version.
12 | #
13 |
14 | from .basepage import BasePage
15 | from gi.repository import Gdk, Gtk, GLib
16 | from os_installer2.diskman import DriveProber
17 | from os_installer2.strategy import DiskStrategyManager
18 | import threading
19 |
20 |
21 | class BrokenWindowsPage(Gtk.Box):
22 | """ Indicate to the user that they booted in the wrong mode """
23 |
24 | owner = None
25 |
26 | def __init__(self, owner):
27 | Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL, spacing=0)
28 |
29 | self.owner = owner
30 |
31 | img = Gtk.Image.new_from_icon_name("face-crying-symbolic",
32 | Gtk.IconSize.DIALOG)
33 |
34 | self.pack_start(img, False, False, 10)
35 |
36 | label = Gtk.Label("{}".format(
37 | "You have booted Solus in UEFI mode, however your\n"
38 | "system is configured by Windows to use BIOS mode.\n"
39 | "If you wish to dual-boot, please reboot using the"
40 | " legacy mode.\n"
41 | "Otherwise, you may wipe disks in the next screen"))
42 | label.set_property("xalign", 0.5)
43 | label.set_use_markup(True)
44 | self.pack_start(label, False, False, 10)
45 |
46 | button = Gtk.Button.new_with_label("Continue")
47 | button.get_style_context().add_class("destructive-action")
48 | button.set_halign(Gtk.Align.CENTER)
49 | self.pack_start(button, False, False, 10)
50 |
51 | button.connect("clicked", self.on_clicked)
52 |
53 | self.set_valign(Gtk.Align.CENTER)
54 | self.set_halign(Gtk.Align.CENTER)
55 |
56 | def on_clicked(self, btn, w=None):
57 | """ Update owner page """
58 | self.owner.can_continue = True
59 | self.owner.info.owner.set_can_next(True)
60 | self.owner.stack.set_visible_child_name("chooser")
61 |
62 |
63 | class ChooserPage(Gtk.Box):
64 | """ Main chooser UI """
65 |
66 | combo = None
67 | strategy_box = None
68 | respond = False
69 | manager = None
70 | drives = None
71 |
72 | # To record the strategy.
73 | info = None
74 |
75 | def __init__(self):
76 | Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL, spacing=0)
77 |
78 | self.set_property("orientation", Gtk.Orientation.VERTICAL)
79 | self.set_border_width(40)
80 |
81 | # set up the disk selector
82 | self.combo = Gtk.ComboBoxText()
83 |
84 | self.pack_start(self.combo, False, False, 0)
85 |
86 | self.strategy_box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
87 | self.strategy_box.set_margin_top(20)
88 | self.pack_start(self.strategy_box, True, True, 0)
89 |
90 | self.respond = True
91 | self.combo.connect("changed", self.on_combo_changed)
92 |
93 | def on_combo_changed(self, combo, w=None):
94 | if not self.respond:
95 | return
96 | drive = self.drives[combo.get_active_id()]
97 | strats = self.manager.get_strategies(drive)
98 |
99 | self.reset_options()
100 | leader = None
101 | for strat in strats:
102 | # update it
103 | strat.update_operations(self.info.owner.get_disk_manager(),
104 | self.info)
105 | button = Gtk.RadioButton.new_with_label_from_widget(
106 | leader, strat.get_display_string())
107 | button.strategy = strat
108 | if not leader:
109 | leader = button
110 | button.get_child().set_use_markup(True)
111 | button.connect("toggled", self.on_radio_toggle)
112 | self.strategy_box.pack_start(button, False, False, 8)
113 | button.show_all()
114 | # Force selection
115 | if leader:
116 | self.on_radio_toggle(leader)
117 |
118 | def reset_options(self):
119 | """ Reset available strategies """
120 | for widget in self.strategy_box.get_children():
121 | widget.destroy()
122 |
123 | def on_radio_toggle(self, radio, w=None):
124 | """ Handle setting of a strategy """
125 | if not radio.get_active():
126 | return
127 | strat = radio.strategy
128 | self.info.strategy = strat
129 |
130 | def reset(self):
131 | self.respond = False
132 | self.drives = dict()
133 | self.combo.remove_all()
134 | self.reset_options()
135 | self.respond = True
136 |
137 | def set_drives(self, info, prober):
138 | """ Set the display drives """
139 | self.info = info
140 | self.info.strategy = None
141 | self.reset()
142 |
143 | self.manager = DiskStrategyManager(prober)
144 | active_id = None
145 | for drive in prober.drives:
146 | self.combo.append(drive.path, drive.get_display_string())
147 | self.drives[drive.path] = drive
148 | if not active_id:
149 | active_id = drive.path
150 | self.combo.set_active_id(active_id)
151 |
152 |
153 | class WhoopsPage(Gtk.Box):
154 | """ No disks on this system """
155 |
156 | def __init__(self):
157 | Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL, spacing=0)
158 |
159 | img = Gtk.Image.new_from_icon_name("face-crying-symbolic",
160 | Gtk.IconSize.DIALOG)
161 |
162 | self.pack_start(img, False, False, 10)
163 |
164 | label = Gtk.Label("{}".format(
165 | "Oh no! Your system has no usable disks available.\n"
166 | "There is nowhere to install Solus.\n"
167 | "Please ensure you have a minimum of 10GB available"
168 | "\nstorage to complete a full Solus installation."))
169 | label.set_property("xalign", 0.5)
170 | label.set_use_markup(True)
171 | self.pack_start(label, False, False, 10)
172 |
173 | self.set_valign(Gtk.Align.CENTER)
174 | self.set_halign(Gtk.Align.CENTER)
175 |
176 |
177 | class LoadingPage(Gtk.Box):
178 | """ Spinner/load box """
179 |
180 | def __init__(self):
181 | Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL, spacing=0)
182 |
183 | self.spinner = Gtk.Spinner()
184 | self.pack_start(self.spinner, False, False, 10)
185 |
186 | self.label = Gtk.Label("Examining local storage devices" + u"…")
187 | self.pack_start(self.label, False, False, 10)
188 |
189 | self.set_valign(Gtk.Align.CENTER)
190 | self.set_halign(Gtk.Align.CENTER)
191 |
192 | def start(self):
193 | self.spinner.start()
194 |
195 | def stop(self):
196 | self.spinner.stop()
197 |
198 |
199 | class InstallerDiskLocationPage(BasePage):
200 | """ Disk location selection. """
201 |
202 | had_init = False
203 | spinner = None
204 |
205 | stack = None
206 | whoops = None
207 | chooser = None
208 | prober = None
209 | can_continue = False
210 |
211 | def __init__(self):
212 | BasePage.__init__(self)
213 |
214 | self.stack = Gtk.Stack()
215 | self.pack_start(self.stack, True, True, 0)
216 |
217 | self.spinner = LoadingPage()
218 |
219 | self.whoops = WhoopsPage()
220 | self.stack.add_named(self.whoops, "whoops")
221 | broken = BrokenWindowsPage(self)
222 | self.stack.add_named(broken, "broken-windows")
223 | self.stack.add_named(self.spinner, "loading")
224 | self.chooser = ChooserPage()
225 | self.stack.add_named(self.chooser, "chooser")
226 |
227 | self.stack.set_visible_child_name("loading")
228 |
229 | def get_title(self):
230 | return "Where should we install?"
231 |
232 | def get_sidebar_title(self):
233 | return "Disks"
234 |
235 | def get_name(self):
236 | return "disk-location"
237 |
238 | def get_icon_name(self, plasma=False):
239 | if plasma:
240 | return "drive-harddisk"
241 | return "disk-utility"
242 |
243 | def load_disks(self):
244 | """ Load the disks within a thread """
245 | # Scan parts
246 | dm = self.info.owner.get_disk_manager()
247 | perms = self.info.owner.get_perms_manager()
248 |
249 | perms.up_permissions()
250 | self.prober = DriveProber(dm)
251 | self.info.prober = self.prober
252 | self.prober.probe()
253 | perms.down_permissions()
254 |
255 | # Currently the only GTK call here
256 | Gdk.threads_enter()
257 | self.info.owner.set_can_previous(True)
258 | can_continue = True
259 |
260 | if len(self.prober.drives) == 0:
261 | # No drives
262 | self.stack.set_visible_child_name("whoops")
263 | can_continue = False
264 | elif self.prober.is_broken_windows_uefi():
265 | self.stack.set_visible_child_name("broken-windows")
266 | self.can_continue = False
267 | else:
268 | # Let them choose
269 | self.stack.set_visible_child_name("chooser")
270 | self.can_continue = True
271 | self.spinner.stop()
272 | Gdk.threads_leave()
273 |
274 | if can_continue:
275 | GLib.idle_add(self.update_disks)
276 |
277 | def update_disks(self):
278 | """ Thread load finished, update UI from discovered info """
279 | self.chooser.set_drives(self.info, self.prober)
280 | self.info.windows_present = False
281 | for drive in self.prober.drives:
282 | for os in drive.operating_systems:
283 | os_ = drive.operating_systems[os]
284 | if os_.otype == "windows":
285 | self.info.windows_present = True
286 | break
287 |
288 | # Allow forward navigation now
289 | self.info.owner.set_can_next(self.can_continue)
290 | return False
291 |
292 | def init_view(self):
293 | """ Prepare for viewing... """
294 | if self.had_init and not self.info.invalidated:
295 | return
296 | self.info.invalidated = False
297 | self.can_continue = False
298 | self.had_init = True
299 | self.stack.set_visible_child_name("loading")
300 | self.spinner.start()
301 | self.spinner.show_all()
302 | GLib.idle_add(self.prepare_view)
303 |
304 | def prepare_view(self):
305 | """ Do the real job after GTK has done things.. """
306 | self.info.owner.set_can_previous(False)
307 | self.queue_draw()
308 |
309 | t = threading.Thread(target=self.load_disks)
310 | t.daemon = True
311 | t.start()
312 | return False
313 |
314 | def prepare(self, info):
315 | self.info = info
316 | self.init_view()
317 | self.info.owner.set_can_next(self.can_continue)
318 |
--------------------------------------------------------------------------------
/os_installer2/pages/geoip.py:
--------------------------------------------------------------------------------
1 | #!/bin/true
2 | # -*- coding: utf-8 -*-
3 | #
4 | # This file is part of os-installer
5 | #
6 | # Copyright 2013-2020 Solus
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 2 of the License, or
11 | # (at your option) any later version.
12 | #
13 |
14 | from .basepage import BasePage
15 | from gi.repository import GLib, Gtk
16 | from io import BytesIO
17 | import threading
18 | import re
19 | import pygeoip
20 | import pycurl
21 |
22 | IP_CHECK = "https://location.getsol.us"
23 | TIMEOUT = 10
24 |
25 |
26 | class InstallerGeoipPage(BasePage):
27 | """ Geoip lookup. """
28 |
29 | info = None
30 | tried_find = False
31 | spinner = None
32 | dlabel = None
33 |
34 | def __init__(self):
35 | BasePage.__init__(self)
36 |
37 | hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
38 | self.pack_start(hbox, True, True, 0)
39 | hbox.set_margin_top(20)
40 | hbox.set_border_width(40)
41 |
42 | self.spinner = Gtk.Spinner()
43 | hbox.pack_start(self.spinner, False, False, 10)
44 |
45 | self.dlabel = Gtk.Label("Finding your location" + u"…" + "")
46 | self.dlabel.set_use_markup(True)
47 | self.dlabel.set_halign(Gtk.Align.START)
48 | hbox.pack_start(self.dlabel, False, False, 10)
49 |
50 | hbox.set_halign(Gtk.Align.CENTER)
51 |
52 | def get_title(self):
53 | return "Looking for your location" + u"…"
54 |
55 | def get_name(self):
56 | return "geoip-lookup"
57 |
58 | def get_sidebar_title(self):
59 | return u"↠" + " Find location"
60 |
61 | def get_icon_name(self, plasma=False):
62 | return "mark-location-symbolic"
63 |
64 | def prepare(self, info):
65 | self.info = info
66 | if not self.info.enable_geoip:
67 | self.info.owner.skip_page()
68 | return
69 | if self.info.cached_timezone:
70 | self.info.owner.skip_page()
71 | return
72 | if self.tried_find:
73 | return
74 |
75 | # Start our geoip thread
76 | self.schedule_lookup()
77 |
78 | def schedule_lookup(self):
79 | self.tried_find = True
80 | self.info.owner.set_can_next(False)
81 | self.info.owner.set_can_previous(False)
82 | self.spinner.start()
83 | GLib.idle_add(self.begin_thread)
84 |
85 | def begin_thread(self):
86 | t = threading.Thread(target=self.perform_lookup)
87 | t.start()
88 | return False
89 |
90 | def end_thread(self):
91 | if self.info.cached_location:
92 | l = self.info.cached_location
93 | t = self.info.cached_timezone
94 | c = "{} {}".format(l, t)
95 | self.dlabel.set_markup("Found location: {}".format(c))
96 | else:
97 | self.dlabel.set_markup("Unable to find location")
98 | self.info.owner.set_can_previous(True)
99 | self.spinner.stop()
100 |
101 | GLib.timeout_add(1500, self.go_skipping)
102 | return False
103 |
104 | def go_skipping(self):
105 | self.info.owner.skip_page()
106 | return False
107 |
108 | def get_ip_address(self):
109 | """ Get our external IP address for this machine """
110 | try:
111 | buffer = BytesIO()
112 | curl = pycurl.Curl()
113 | curl.setopt(curl.IPRESOLVE, 1)
114 | curl.setopt(curl.WRITEDATA, buffer)
115 | curl.setopt(curl.URL, IP_CHECK)
116 | curl.perform()
117 | curl.close()
118 | b = buffer.getvalue()
119 | contents = b.decode('utf-8')
120 |
121 | regex = r'Address: (.+)$'
122 | reg = re.compile(regex)
123 | return reg.search(contents).group(1)
124 | except Exception as e:
125 | print(e)
126 | return None
127 |
128 | def perform_lookup(self):
129 | self.info.owner.get_perms_manager().down_permissions()
130 | """ Perform the actual lookup """
131 | ip = self.get_ip_address()
132 | if not ip:
133 | # Consider getting something useful here...
134 | GLib.idle_add(self.end_thread)
135 | return
136 |
137 | gi = pygeoip.GeoIP("/usr/share/GeoIP/City.dat")
138 | country = gi.country_code_by_addr(ip)
139 | timezone = gi.time_zone_by_addr(ip)
140 | self.info.cached_location = country
141 | self.info.cached_timezone = timezone
142 | # Return to thread main
143 | GLib.idle_add(self.end_thread)
144 |
--------------------------------------------------------------------------------
/os_installer2/pages/keyboard.py:
--------------------------------------------------------------------------------
1 | #!/bin/true
2 | # -*- coding: utf-8 -*-
3 | #
4 | # This file is part of os-installer
5 | #
6 | # Copyright 2013-2020 Solus
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 2 of the License, or
11 | # (at your option) any later version.
12 | #
13 |
14 | from .basepage import BasePage
15 | from gi.repository import Gtk, GnomeDesktop
16 | import subprocess
17 |
18 |
19 | class KbLabel(Gtk.Box):
20 | """ View label for locales, save code duping """
21 |
22 | kb = None
23 | dname = None
24 |
25 | def __init__(self, kb, info):
26 | Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
27 |
28 | self.kb = kb
29 |
30 | lab = Gtk.Label("")
31 | lab.set_halign(Gtk.Align.START)
32 |
33 | self.dname = info[1]
34 | self.sname = info[2]
35 | self.layout = info[3]
36 | self.variant = info[4]
37 |
38 | self.language = info[2]
39 | self.country = info[3]
40 |
41 | self.extras = info[4]
42 |
43 | self.set_property("margin", 10)
44 |
45 | lab.set_text(self.dname)
46 | self.pack_start(lab, True, True, 0)
47 |
48 | self.show_all()
49 |
50 |
51 | class InstallerKeyboardPage(BasePage):
52 | """ Basic location detection page. """
53 |
54 | layouts = None
55 | info = None
56 | had_init = False
57 | xkb = None
58 | shown_layouts = None
59 | moar_button = None
60 | extras = None
61 |
62 | def __init__(self):
63 | BasePage.__init__(self)
64 |
65 | # Hold everything up in a grid
66 | grid = Gtk.Grid()
67 | self.pack_start(grid, True, True, 0)
68 | grid.set_column_spacing(6)
69 | grid.set_row_spacing(6)
70 | grid.set_margin_start(32)
71 | grid.set_margin_top(40)
72 | grid.set_halign(Gtk.Align.CENTER)
73 |
74 | # Init main layouts view
75 | self.layouts = Gtk.ListBox()
76 | scroll = Gtk.ScrolledWindow(None, None)
77 | scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
78 | scroll.add(self.layouts)
79 | scroll.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
80 | scroll.set_vexpand(True)
81 | grid.attach(scroll, 0, 0, 2, 1)
82 |
83 | self.layouts.set_size_request(500, -1)
84 |
85 | # Input tester
86 | inp_entry = Gtk.Entry()
87 | t_str = "Type here to test your keyboard layout"
88 | inp_entry.set_placeholder_text(t_str)
89 |
90 | grid.attach(inp_entry, 0, 1, 2, 1)
91 |
92 | self.moar_button = Gtk.Image.new_from_icon_name("view-more-symbolic",
93 | Gtk.IconSize.MENU)
94 | self.moar_button.set_property("margin", 8)
95 |
96 | self.layouts.connect_after("row-selected", self.on_row_select)
97 |
98 | def on_row_select(self, lbox, newrb=None):
99 | """ Handle selections of locales """
100 | self.info.keyboard = None
101 | self.info.keyboard_sz = None
102 | if not newrb:
103 | self.info.keyboard = None
104 | self.info.keyboard_sz = None
105 | self.info.owner.set_can_next(False)
106 | return
107 | child = newrb.get_child()
108 | if child == self.moar_button:
109 | self.init_remaining()
110 | return
111 | self.info.keyboard = child.kb
112 | self.info.keyboard_sz = child.dname
113 | try:
114 | subprocess.check_call("setxkbmap {} {}".format(child.layout,child.variant), shell=True)
115 | except Exception as e:
116 | print("@ERR@: Couldn\'t set the keyboard layout: {}".format(e))
117 |
118 | self.info.owner.set_can_next(True)
119 |
120 | def init_view(self):
121 | """ Initialise ourself from GNOME XKB """
122 | if self.had_init:
123 | return
124 | self.had_init = True
125 | self.xkb = GnomeDesktop.XkbInfo()
126 |
127 | items = GnomeDesktop.parse_locale(self.info.locale)
128 | if items[0]:
129 | lang = items[1]
130 | country = items[2]
131 | else:
132 | # Shouldn't ever happen, but ya never know.
133 | lang = "en"
134 | country = "US"
135 |
136 | if self.info.cached_location:
137 | country = self.info.cached_location.upper()
138 |
139 | l = self.info.locale
140 | success, type_, id_ = GnomeDesktop.get_input_source_from_locale(l)
141 |
142 | kbset = set()
143 | kbset.update(self.xkb.get_layouts_for_country(country))
144 | kbset.update(self.xkb.get_layouts_for_language(lang))
145 |
146 | major_layouts = self.xkb.get_all_layouts()
147 | for item in major_layouts:
148 | xkbinf = self.xkb.get_layout_info(item)
149 | if not xkbinf[0]:
150 | continue
151 | if xkbinf[3].lower() == country.lower():
152 | kbset.add(item)
153 |
154 | layouts = list()
155 | for x in kbset:
156 | info = self.xkb.get_layout_info(x)
157 | if not info[0]:
158 | continue
159 | widget = KbLabel(x, info)
160 | layouts.append(widget)
161 |
162 | c = country.lower()
163 | native = filter(lambda x: x.country.lower() == c, layouts)
164 |
165 | primary = None
166 |
167 | if not native:
168 | native = layouts
169 | for item in native:
170 | if item.layout[:2].lower() == lang.lower() and not item.extras:
171 | primary = item
172 | else:
173 | for item in native:
174 | if not item.extras:
175 | primary = item
176 | break
177 |
178 | self.added = 0
179 | self.extras = list()
180 |
181 | def append_inner(layout, item):
182 | if layout in self.shown_layouts:
183 | return
184 | if self.added >= 5:
185 | self.extras.append(item)
186 | return
187 | self.shown_layouts.add(layout)
188 | self.layouts.add(item)
189 | self.added += 1
190 |
191 | self.shown_layouts = set()
192 | if primary:
193 | append_inner(primary.kb, primary)
194 | for item in native:
195 | append_inner(item.kb, item)
196 | for item in layouts:
197 | append_inner(item.kb, item)
198 |
199 | self.moar_button.show_all()
200 | kids = self.layouts.get_children()
201 | if kids:
202 | s = self.layouts.get_children()[0]
203 | self.layouts.select_row(s)
204 |
205 | self.layouts.add(self.moar_button)
206 |
207 | def init_remaining(self):
208 | layouts = self.xkb.get_all_layouts()
209 |
210 | self.moar_button.get_parent().hide()
211 |
212 | appends = list()
213 | # Deal with extras first
214 | self.extras = sorted(self.extras, key=lambda x: x.dname)
215 | for item in self.extras:
216 | if item.kb in self.shown_layouts:
217 | continue
218 | self.shown_layouts.add(item.kb)
219 | self.layouts.add(item)
220 |
221 | for layout in layouts:
222 | # Don't dupe
223 | if layout in self.shown_layouts:
224 | continue
225 | info = self.xkb.get_layout_info(layout)
226 | success = info[0]
227 | if not success:
228 | continue
229 |
230 | widget = KbLabel(layout, info)
231 | appends.append(widget)
232 | appends.sort(key=lambda x: x.dname.lower())
233 | for app in appends:
234 | if app.kb in self.shown_layouts:
235 | continue
236 | self.layouts.add(app)
237 |
238 | def get_title(self):
239 | return "Choose a keyboard layout"
240 |
241 | def get_sidebar_title(self):
242 | return "Keyboard"
243 |
244 | def get_name(self):
245 | return "keyboard"
246 |
247 | def get_icon_name(self, plasma=False):
248 | if plasma:
249 | return "input-keyboard"
250 | # Just looks nicer than input-keyboard outside breeze
251 | return "preferences-desktop-keyboard-shortcuts"
252 |
253 | def prepare(self, info):
254 | self.info = info
255 | self.init_view()
256 | if self.info.keyboard:
257 | self.info.owner.set_can_next(True)
258 | else:
259 | self.info.owner.set_can_next(False)
260 |
--------------------------------------------------------------------------------
/os_installer2/pages/language.py:
--------------------------------------------------------------------------------
1 | #!/bin/true
2 | # -*- coding: utf-8 -*-
3 | #
4 | # This file is part of os-installer
5 | #
6 | # Copyright 2013-2020 Solus
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 2 of the License, or
11 | # (at your option) any later version.
12 | #
13 |
14 | from .basepage import BasePage
15 | from . import default_locales
16 | from gi.repository import Gtk, GnomeDesktop, Gdk
17 |
18 |
19 | class LcLabel(Gtk.Label):
20 | """ View label for locales, save code duping """
21 |
22 | lc_code = None
23 | untransl = None
24 |
25 | def __init__(self, lc_code):
26 | Gtk.Label.__init__(self)
27 | self.set_text(lc_code)
28 | self.set_halign(Gtk.Align.START)
29 | self.lc_code = lc_code
30 |
31 | # transl = GnomeDesktop.get_language_from_locale(lc_code, lc_code)
32 | untransl = GnomeDesktop.get_language_from_locale(lc_code, None)
33 | self.set_property("margin", 8)
34 |
35 | self.dname = untransl
36 |
37 | self.set_text(untransl)
38 |
39 | self.show()
40 |
41 |
42 | class InstallerLanguagePage(BasePage):
43 | """ Basic language page. """
44 |
45 | # Scrollbox
46 | scroll = None
47 |
48 | # Main content
49 | listbox = None
50 | moar_button = None
51 |
52 | info = None
53 |
54 | def __init__(self):
55 | BasePage.__init__(self)
56 |
57 | self.scroll = Gtk.ScrolledWindow(None, None)
58 | self.scroll.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
59 | self.scroll.set_border_width(40)
60 | self.add(self.scroll)
61 |
62 | self.listbox = Gtk.ListBox()
63 | self.scroll.add(self.listbox)
64 | self.scroll.set_halign(Gtk.Align.CENTER)
65 | self.listbox.set_size_request(500, -1)
66 |
67 | # Fix up policy
68 | self.scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER)
69 |
70 | self.moar_button = Gtk.Image.new_from_icon_name("view-more-symbolic",
71 | Gtk.IconSize.MENU)
72 | self.moar_button.set_property("margin", 8)
73 | self.moar_button.show_all()
74 | self.listbox.connect_after("row-selected", self.on_row_select)
75 |
76 | def on_row_select(self, lbox, newrb=None):
77 | """ Handle selections of locales """
78 | self.info.locale = None
79 | self.info.locale_sz = None
80 | if not newrb:
81 | self.info.owner.set_can_next(False)
82 | return
83 | child = newrb.get_child()
84 | if child == self.moar_button:
85 | self.init_remaining()
86 | return
87 | self.info.locale = child.lc_code
88 | self.info.locale_sz = child.dname
89 | self.info.owner.set_can_next(True)
90 |
91 | def do_expensive_init(self):
92 | """ Do the hard work of actually setting up the view """
93 | Gdk.threads_enter()
94 | for lc in default_locales:
95 | self.listbox.add(LcLabel(lc))
96 | self.listbox.add(self.moar_button)
97 | Gdk.threads_leave()
98 |
99 | def init_remaining(self):
100 | """ Add the rest to the list """
101 | self.scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
102 | self.scroll.set_vexpand(True)
103 | self.scroll.set_valign(Gtk.Align.FILL)
104 | locales = GnomeDesktop.get_all_locales()
105 | self.moar_button.get_parent().hide()
106 | appends = list()
107 | for lc in locales:
108 | if lc in default_locales:
109 | continue
110 | item = LcLabel(lc)
111 | appends.append(item)
112 | appends.sort(key=lambda x: x.dname.lower())
113 | for item in appends:
114 | self.listbox.add(item)
115 |
116 | def prepare(self, info):
117 | # Nothing to seed with.
118 | self.info = info
119 | if self.info.locale:
120 | self.info.owner.set_can_next(True)
121 | else:
122 | self.info.owner.set_can_next(False)
123 |
124 | def get_title(self):
125 | return "Choose a language"
126 |
127 | def get_sidebar_title(self):
128 | return "Language"
129 |
130 | def get_name(self):
131 | return "language"
132 |
133 | def get_icon_name(self, plasma=False):
134 | return "preferences-desktop-locale"
135 |
--------------------------------------------------------------------------------
/os_installer2/pages/location.py:
--------------------------------------------------------------------------------
1 | #!/bin/true
2 | # -*- coding: utf-8 -*-
3 | #
4 | # This file is part of os-installer
5 | #
6 | # Copyright 2013-2020 Solus
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 2 of the License, or
11 | # (at your option) any later version.
12 | #
13 |
14 | from .basepage import BasePage
15 | from gi.repository import Gtk
16 |
17 |
18 | class InstallerLocationPage(BasePage):
19 | """ Basic location detection page. """
20 |
21 | info = None
22 | checkbox = None
23 |
24 | def __init__(self):
25 | BasePage.__init__(self)
26 |
27 | lab = Gtk.Label("The next few questions relate to your location. To \
28 | speed things up, the installer can perform a quick check to detect where you \
29 | are in the world and proceed automatically.")
30 | lab.set_property("xalign", 0.0)
31 | lab.set_margin_top(40)
32 |
33 | lab.set_margin_start(32)
34 |
35 | lab.set_line_wrap(True)
36 | self.pack_start(lab, False, False, 0)
37 |
38 | check_str = "Find my location automatically."
39 | self.checkbox = Gtk.CheckButton.new_with_label(check_str)
40 | self.pack_start(self.checkbox, False, False, 0)
41 | self.checkbox.set_margin_top(40)
42 | self.checkbox.set_margin_start(32)
43 |
44 | self.checkbox.connect("toggled", self.on_toggled)
45 |
46 | def on_toggled(self, w, d=None):
47 | if not self.info:
48 | return
49 | self.info.enable_geoip = w.get_active()
50 |
51 | def get_title(self):
52 | return "Where are you?"
53 |
54 | def get_sidebar_title(self):
55 | return "Location"
56 |
57 | def get_name(self):
58 | return "location"
59 |
60 | def get_icon_name(self, plasma=False):
61 | if plasma:
62 | return "applications-internet"
63 | return "maps"
64 |
65 | def prepare(self, info):
66 | self.info = info
67 | if self.info.cached_timezone:
68 | self.checkbox.set_sensitive(False)
69 | self.checkbox.set_tooltip_text("Location already found")
70 |
--------------------------------------------------------------------------------
/os_installer2/pages/summary.py:
--------------------------------------------------------------------------------
1 | #!/bin/true
2 | # -*- coding: utf-8 -*-
3 | #
4 | # This file is part of os-installer
5 | #
6 | # Copyright 2013-2020 Solus
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 2 of the License, or
11 | # (at your option) any later version.
12 | #
13 |
14 | from .basepage import BasePage
15 | from gi.repository import Gtk
16 |
17 |
18 | class FramedHeader(Gtk.Frame):
19 | """ Summary header widget """
20 |
21 | vbox = None
22 |
23 | def __init__(self, icon_name, title):
24 | Gtk.Frame.__init__(self)
25 |
26 | box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
27 |
28 | sz = Gtk.IconSize.DIALOG
29 | image = Gtk.Image.new_from_icon_name(icon_name, sz)
30 | box.pack_start(image, False, False, 0)
31 |
32 | image.set_property("margin", 10)
33 | image.set_valign(Gtk.Align.START)
34 |
35 | label = Gtk.Label("{}".format(title))
36 | label.set_use_markup(True)
37 | box.pack_start(label, False, False, 0)
38 | label.set_halign(Gtk.Align.START)
39 | label.set_valign(Gtk.Align.START)
40 | label.set_property("margin", 10)
41 |
42 | self.vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
43 | box.pack_end(self.vbox, False, False, 0)
44 | self.vbox.set_property("margin", 10)
45 |
46 | self.add(box)
47 |
48 | def add_label(self, label):
49 | self.vbox.pack_start(label, False, False, 2)
50 | label.show_all()
51 |
52 |
53 | class InstallerSummaryPage(BasePage):
54 | """ Installer summary page. """
55 |
56 | locale_details = None
57 | install_details = None
58 | system_details = None
59 | user_details = None
60 |
61 | def __init__(self, plasma):
62 | BasePage.__init__(self)
63 | self.plasma = plasma
64 |
65 | scroll = Gtk.ScrolledWindow(None, None)
66 | scroll.set_border_width(40)
67 | scroll.set_margin_top(20)
68 | self.pack_start(scroll, True, True, 0)
69 | scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
70 |
71 | items = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
72 | scroll.add(items)
73 | scroll.set_overlay_scrolling(False)
74 | self.locale_details = FramedHeader(
75 | "preferences-desktop-locale",
76 | "Language & Region")
77 | items.pack_start(self.locale_details, False, False, 2)
78 |
79 | disk_icon = "disk-utility"
80 | if self.plasma:
81 | disk_icon = "drive-harddisk"
82 |
83 | self.install_details = FramedHeader(disk_icon,
84 | "Installation")
85 | items.pack_start(self.install_details, False, False, 2)
86 |
87 | self.user_details = FramedHeader("system-users", "Users")
88 | items.pack_start(self.user_details, False, False, 2)
89 |
90 | self.system_details = FramedHeader("preferences-system",
91 | "System Details")
92 | items.pack_start(self.system_details, False, False, 2)
93 |
94 | def get_title(self):
95 | return "Review options before installation"
96 |
97 | def get_sidebar_title(self):
98 | return "Summary"
99 |
100 | def get_name(self):
101 | return "summary"
102 |
103 | def get_icon_name(self, plasma=False):
104 | if plasma:
105 | return "korg-todo"
106 | return "gnome-todo"
107 |
108 | def _clean_label(self, label):
109 | lab = Gtk.Label(label)
110 | lab.set_halign(Gtk.Align.END)
111 | return lab
112 |
113 | def prepare(self, info):
114 | info.owner.set_final_step(True)
115 |
116 | # First up, region stuff
117 | for kid in self.locale_details.vbox.get_children():
118 | kid.destroy()
119 |
120 | self.locale_details.add_label(self._clean_label(
121 | "Use {} as system language".format(info.locale_sz)))
122 | self.locale_details.add_label(self._clean_label(
123 | "Use {} as default keyboard layout".format(info.keyboard_sz)))
124 | self.locale_details.add_label(self._clean_label(
125 | "Set timezone to {}".format(info.timezone)))
126 |
127 | # Details
128 | for kid in self.system_details.vbox.get_children():
129 | kid.destroy()
130 | self.system_details.add_label(self._clean_label(
131 | "Set device host-name to \"{}\"".format(info.hostname)))
132 | if info.bootloader_install:
133 | if info.bootloader_sz == "c":
134 | s = info.bootloader
135 | else:
136 | s = "Install bootloader to {}".format(info.bootloader_sz)
137 | self.system_details.add_label(self._clean_label(s))
138 |
139 | # Users
140 | for kid in self.user_details.vbox.get_children():
141 | kid.destroy()
142 | for user in info.users:
143 | sz = "Create administrative user {} ({})"
144 | if not user.admin:
145 | sz = "Create regular user {} ({})"
146 | self.user_details.add_label(self._clean_label(
147 | sz.format(user.realname, user.username)))
148 |
149 | # disk
150 | for kid in self.install_details.vbox.get_children():
151 | kid.destroy()
152 | info.strategy.reset_operations()
153 | info.strategy.update_operations(info.owner.get_disk_manager(), info)
154 | for line in info.strategy.explain(info.owner.get_disk_manager(), info):
155 | self.install_details.add_label(self._clean_label(line))
156 |
--------------------------------------------------------------------------------
/os_installer2/pages/system.py:
--------------------------------------------------------------------------------
1 | #!/bin/true
2 | # -*- coding: utf-8 -*-
3 | #
4 | # This file is part of os-installer
5 | #
6 | # Copyright 2013-2020 Solus
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 2 of the License, or
11 | # (at your option) any later version.
12 | #
13 |
14 | from .basepage import BasePage
15 | from gi.repository import Gtk
16 | import re
17 |
18 | # This is all we allow. OK.
19 | ValidHostnameRegex = "^[a-z_][a-z0-9_-]*[$]?$"
20 |
21 |
22 | class InstallerSystemPage(BasePage):
23 | """ System Settings page. """
24 |
25 | info = None
26 | host_reg = None
27 | host_entry = None
28 | error_label = None
29 | check_boot = None
30 | combo_boot = None
31 | # Bootloader issues
32 | error_label2 = None
33 | respond = True
34 |
35 | def __init__(self):
36 | BasePage.__init__(self)
37 | self.host_reg = re.compile(ValidHostnameRegex)
38 |
39 | wid_group = Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL)
40 |
41 | mbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
42 | mbox.set_margin_top(40)
43 | self.pack_start(mbox, False, False, 0)
44 | mbox.set_halign(Gtk.Align.CENTER)
45 | mbox.set_hexpand(False)
46 | mbox.set_vexpand(False)
47 |
48 | # hostname section
49 | host = Gtk.Frame()
50 | host.set_shadow_type(Gtk.ShadowType.NONE)
51 | host.set_label("What name should this computer use on the network?")
52 | host.get_label_widget().set_margin_bottom(8)
53 | mbox.pack_start(host, False, False, 10)
54 |
55 | self.host_entry = Gtk.Entry()
56 | self.host_entry.connect("changed", self.host_validate)
57 | self.host_entry.set_placeholder_text("Type the hostname here")
58 | host.add(self.host_entry)
59 |
60 | wid_group.add_widget(host)
61 |
62 | boot = Gtk.Frame()
63 | self.boot_frame = boot
64 | boot.set_halign(Gtk.Align.CENTER)
65 | boot.set_shadow_type(Gtk.ShadowType.NONE)
66 | self.check_boot = Gtk.CheckButton.new_with_label(
67 | "Install a bootloader")
68 | self.check_boot.set_active(True)
69 | self.check_boot.connect("toggled", self.on_boot_toggled)
70 | self.check_boot.set_margin_bottom(5)
71 | self.check_boot.set_margin_top(5)
72 | boot.set_label_widget(self.check_boot)
73 | self.combo_boot = Gtk.ComboBoxText()
74 | self.combo_boot.connect("changed", self.on_combo_changed)
75 | boot.add(self.combo_boot)
76 | self.pack_start(boot, False, False, 0)
77 | wid_group.add_widget(self.combo_boot)
78 |
79 | self.error_label = Gtk.Label.new("")
80 | self.error_label.set_valign(Gtk.Align.START)
81 | self.pack_end(self.error_label, False, False, 0)
82 | wid_group.add_widget(self.error_label)
83 |
84 | self.error_label2 = Gtk.Label.new("")
85 | self.error_label2.set_valign(Gtk.Align.START)
86 | self.pack_end(self.error_label2, False, False, 0)
87 | wid_group.add_widget(self.error_label2)
88 |
89 | def on_boot_toggled(self, w, d=None):
90 | """ Handle bootloader install """
91 | self.combo_boot.set_sensitive(w.get_active())
92 | self.info.bootloader_install = w.get_active()
93 |
94 | def on_combo_changed(self, combo, w=None):
95 | """ Combo updated """
96 | if not self.respond:
97 | return
98 | if not self.info:
99 | return
100 | self.info.bootloader_sz = combo.get_active_id()
101 | self.info.bootloader = combo.get_active_text()
102 |
103 | self.check_forward()
104 |
105 | def check_forward(self):
106 | """ Determine if we can forward/back """
107 | bs = [self.info.hostname]
108 | if self.check_boot.get_active():
109 | bs.append(self.info.bootloader)
110 | misfires = [x for x in bs if not x]
111 | if len(misfires) == 0:
112 | self.info.owner.set_can_next(True)
113 | else:
114 | self.info.owner.set_can_next(False)
115 |
116 | def host_validate(self, entry):
117 | """ Validate the hostname """
118 | text = entry.get_text()
119 | match = self.host_reg.match(text)
120 | can_fwd = False
121 | if match is None:
122 | entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY,
123 | "action-unavailable-symbolic")
124 | self.error_label.set_markup(
125 | "Hostnames must be lowercase, and only contain "
126 | "letters,\nnumbers, hyphens and underscores."
127 | "Hostnames must\nalso start with a lowercase letter.")
128 | else:
129 | entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY,
130 | "emblem-ok-symbolic")
131 | self.error_label.set_markup("")
132 | can_fwd = True
133 | if not self.info:
134 | return
135 | if can_fwd:
136 | self.info.hostname = text
137 | else:
138 | self.info.hostname = None
139 | self.check_forward()
140 |
141 | def get_title(self):
142 | return "Configure the bootloader & hostname"
143 |
144 | def get_name(self):
145 | return "system-settings"
146 |
147 | def get_sidebar_title(self):
148 | return "System Settings"
149 |
150 | def get_icon_name(self, plasma=False):
151 | return "preferences-system"
152 |
153 | def prepare(self, info):
154 | self.info = info
155 | dm = self.info.owner.get_disk_manager()
156 | if dm.is_efi_booted():
157 | self.info.bootloader_install = True
158 | self.check_boot.set_active(True)
159 | self.check_boot.set_sensitive(False)
160 | self.check_boot.set_label(
161 | "Bootloader installation mandatory with UEFI")
162 |
163 | self.check_forward()
164 |
165 | self.respond = False
166 | self.combo_boot.remove_all()
167 | options = info.strategy.get_boot_loader_options()
168 | for loader, id in options:
169 | self.combo_boot.append(id, loader)
170 | self.respond = True
171 | if len(options) > 0:
172 | self.combo_boot.set_active(0)
173 | self.error_label2.set_label("")
174 | return
175 | info.bootloader = None
176 | self.info.owner.set_can_next(False)
177 | err = info.strategy.get_errors()
178 | # UEFI specific
179 | if err:
180 | self.error_label2.set_label(
181 | "Failed to find location for bootloader: {}".format(err))
182 | else:
183 | self.check_boot.set_active(False)
184 | self.error_label2.set_label(
185 | "Warning: Cannot find a valid bootloader location (MBR disk)")
186 |
--------------------------------------------------------------------------------
/os_installer2/pages/timezone.py:
--------------------------------------------------------------------------------
1 | #!/bin/true
2 | # -*- coding: utf-8 -*-
3 | #
4 | # This file is part of os-installer
5 | #
6 | # Copyright 2013-2020 Solus
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 2 of the License, or
11 | # (at your option) any later version.
12 | #
13 |
14 | from .basepage import BasePage
15 | from os_installer2.tz import Database
16 |
17 | from gi.repository import TimezoneMap, Gtk, Gdk
18 |
19 |
20 | class InstallerTimezonePage(BasePage):
21 | """ timezone setup page. """
22 |
23 | tmap = None
24 | locations = None
25 | db = None
26 |
27 | def __init__(self):
28 | BasePage.__init__(self)
29 | self.frame = Gtk.Frame()
30 | self.frame.set_shadow_type(Gtk.ShadowType.NONE)
31 | self.tmap = TimezoneMap.TimezoneMap()
32 | self.pack_start(self.frame, True, True, 0)
33 | self.frame.set_margin_end(0)
34 | self.frame.set_margin_start(0)
35 | self.frame.add(self.tmap)
36 |
37 | self.locations = Gtk.Entry()
38 | self.locations.set_property("margin-right", 30)
39 | self.locations.set_property("margin-start", 30)
40 | self.locations.set_property("margin-top", 10)
41 | self.pack_end(self.locations, False, False, 0)
42 |
43 | self.locations.set_placeholder_text("Search for your timezone" + u"…")
44 |
45 | completion = TimezoneMap.TimezoneCompletion()
46 | completion.set_text_column(0)
47 | completion.set_inline_completion(True)
48 | completion.set_inline_selection(True)
49 | completion.connect("match-selected", self.change_timezone)
50 | self.locations.set_completion(completion)
51 | self.tmap.connect("location-changed", self.changed)
52 |
53 | def do_expensive_init(self):
54 | # Set up timezone database
55 | self.db = Database()
56 |
57 | tz_model = Gtk.ListStore(str, str, str, str, float, float, str)
58 |
59 | for item in self.db.locations:
60 | tz_model.append([item.human_zone, item.human_country, None,
61 | item.country, item.longitude, item.latitude,
62 | item.zone])
63 |
64 | Gdk.threads_enter()
65 | self.locations.get_completion().set_model(tz_model)
66 | Gdk.threads_leave()
67 |
68 | def get_title(self):
69 | return "Choose your timezone"
70 |
71 | def get_sidebar_title(self):
72 | return "Timezone"
73 |
74 | def get_name(self):
75 | return "timezone"
76 |
77 | def get_icon_name(self, plasma=False):
78 | return "preferences-system-time"
79 |
80 | def change_timezone(self, completion, model, selection):
81 | item = model[selection]
82 | zone = item[6]
83 | self.tmap.set_timezone(zone)
84 |
85 | def changed(self, map, location):
86 | zone = location.get_property("zone")
87 | nice_loc = self.db.tz_to_loc[zone]
88 |
89 | self.timezone_human = "{} ({})".format(nice_loc.human_zone,
90 | nice_loc.human_country)
91 | self.tmap.set_watermark(self.timezone_human)
92 | self.locations.set_text(nice_loc.human_zone)
93 |
94 | # Ok to go forward
95 | self.info.owner.set_can_next(True)
96 | self.info.timezone = zone
97 | self.info.timezone_c = location.get_property("country")
98 |
99 | def prepare(self, info):
100 | self.info = info
101 | if self.info.timezone:
102 | self.info.owner.set_can_next(True)
103 | else:
104 | # Use geoip
105 | if self.info.cached_timezone:
106 | self.tmap.set_timezone(self.info.cached_timezone)
107 | self.timezone = self.info.cached_timezone
108 | self.info.owner.set_can_next(True)
109 | else:
110 | self.info.owner.set_can_next(False)
111 |
--------------------------------------------------------------------------------
/os_installer2/pages/users.py:
--------------------------------------------------------------------------------
1 | #!/bin/true
2 | # -*- coding: utf-8 -*-
3 | #
4 | # This file is part of os-installer
5 | #
6 | # Copyright 2013-2020 Solus
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 2 of the License, or
11 | # (at your option) any later version.
12 | #
13 |
14 | from .basepage import BasePage
15 | from gi.repository import Gtk
16 | from os_installer2.users import User, USERNAME_REGEX, PASSWORD_LENGTH
17 | import re
18 |
19 | LABEL_COLUMN = 0
20 | DATA_COLUMN = 1
21 |
22 |
23 | class UserPanel(Gtk.Box):
24 | """Userpanel. Represents a user. Whoda thunk it. """
25 |
26 | def __init__(self, user):
27 | Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL, spacing=0)
28 |
29 | self.user = user
30 |
31 | label = Gtk.Label("{} - {}".format(
32 | self.user.realname,
33 | self.user.username))
34 | label.set_use_markup(True)
35 | label_details = Gtk.Label("")
36 | details = ""
37 |
38 | if self.user.autologin:
39 | details += " - {}".format(
40 | "will be automatically logged into the computer")
41 | else:
42 | details += " - {}".format(
43 | "will use a password to log into the computer")
44 | if self.user.admin:
45 | details += "\n - {}".format(
46 | "will have administrative capabilities")
47 | else:
48 | details += "\n - {}".format("will be an ordinary user")
49 |
50 | label_details.set_markup(details)
51 | label.set_halign(Gtk.Align.START)
52 | label_details.set_halign(Gtk.Align.START)
53 | self.pack_start(label, True, True, 4)
54 | self.pack_start(label_details, False, False, 0)
55 |
56 |
57 | class NewUserPage(Gtk.Grid):
58 | """ Form for creating a new user """
59 |
60 | uname_field = None
61 | rname_field = None
62 | pword_field = None
63 | pword_field2 = None
64 |
65 | def is_bad_field(self, field):
66 | """ Validation for gecos """
67 | t = field.get_text()
68 | if len(t) < 2:
69 | return True
70 | bad_guys = ['"', '\'', '/', '\\', ';', '@', '!']
71 | hits = [x for x in bad_guys if x in t]
72 | if len(hits) > 0:
73 | return True
74 | return False
75 |
76 | def validator(self, entry):
77 | if entry == self.uname_field:
78 | # Perform username validation
79 | if self.username_regex.match(entry.get_text()):
80 | self.uname_field.set_icon_from_icon_name(
81 | Gtk.EntryIconPosition.SECONDARY, "emblem-ok-symbolic")
82 | self.update_score(self.uname_field, True)
83 | else:
84 | # Username = bad
85 | self.uname_field.set_icon_from_icon_name(
86 | Gtk.EntryIconPosition.SECONDARY,
87 | "action-unavailable-symbolic")
88 | self.update_score(self.uname_field, False)
89 | elif entry == self.rname_field:
90 | if not self.is_bad_field(self.rname_field):
91 | self.rname_field.set_icon_from_icon_name(
92 | Gtk.EntryIconPosition.SECONDARY, "emblem-ok-symbolic")
93 | self.update_score(self.rname_field, True)
94 | else:
95 | # Bad realname
96 | self.rname_field.set_icon_from_icon_name(
97 | Gtk.EntryIconPosition.SECONDARY,
98 | "action-unavailable-symbolic")
99 | self.update_score(self.rname_field, False)
100 | else:
101 | # Handle the two password fields together
102 | pass1 = self.pword_field.get_text()
103 | pass2 = self.pword_field2.get_text()
104 |
105 | if len(pass1) >= PASSWORD_LENGTH:
106 | self.pword_field.set_icon_from_icon_name(
107 | Gtk.EntryIconPosition.SECONDARY, "emblem-ok-symbolic")
108 | self.update_score(self.pword_field, True)
109 | else:
110 | # Bad password
111 | self.pword_field.set_icon_from_icon_name(
112 | Gtk.EntryIconPosition.SECONDARY,
113 | "action-unavailable-symbolic")
114 | self.update_score(self.pword_field, False)
115 |
116 | if len(pass1) >= PASSWORD_LENGTH and pass1 == pass2:
117 | self.pword_field2.set_icon_from_icon_name(
118 | Gtk.EntryIconPosition.SECONDARY, "emblem-ok-symbolic")
119 | self.update_score(self.pword_field2, True)
120 | else:
121 | # Bad password2
122 | self.pword_field2.set_icon_from_icon_name(
123 | Gtk.EntryIconPosition.SECONDARY,
124 | "action-unavailable-symbolic")
125 | self.update_score(self.pword_field2, False)
126 |
127 | def update_score(self, widget, score):
128 | """ Update the score for validation """
129 | if widget not in self.scores:
130 | self.scores[widget] = score
131 | else:
132 | self.scores[widget] = score
133 |
134 | total_score = len([i for i in self.scores.values() if i])
135 | self.ok.set_sensitive(total_score == self.needed_score)
136 |
137 | def __init__(self, owner):
138 | Gtk.Grid.__init__(self)
139 | self.owner = owner
140 | self.set_margin_top(40)
141 |
142 | self.set_column_spacing(10)
143 | self.set_row_spacing(10)
144 | self.set_margin_left(100)
145 | self.set_margin_right(100)
146 |
147 | self.scores = dict()
148 | self.needed_score = 4
149 |
150 | self.username_regex = re.compile(USERNAME_REGEX)
151 |
152 | row = 0
153 | uname_label = Gtk.Label("Username:")
154 | self.uname_field = Gtk.Entry()
155 | self.uname_field.set_hexpand(True)
156 | self.uname_field.connect("changed", self.validator)
157 | self.attach(uname_label, LABEL_COLUMN, row, 1, 1)
158 | self.attach(self.uname_field, DATA_COLUMN, row, 1, 1)
159 |
160 | row += 1
161 | rname_label = Gtk.Label("Real name:")
162 | self.rname_field = Gtk.Entry()
163 | self.rname_field.connect("changed", self.validator)
164 | self.attach(rname_label, LABEL_COLUMN, row, 1, 1)
165 | self.attach(self.rname_field, DATA_COLUMN, row, 1, 1)
166 |
167 | row += 1
168 | pword_label = Gtk.Label("Password:")
169 | self.pword_field = Gtk.Entry()
170 | self.pword_field.set_visibility(False)
171 | self.pword_field.connect("changed", self.validator)
172 | self.attach(pword_label, LABEL_COLUMN, row, 1, 1)
173 | self.attach(self.pword_field, DATA_COLUMN, row, 1, 1)
174 |
175 | row += 1
176 | pword_label2 = Gtk.Label("Confirm password:")
177 | self.pword_field2 = Gtk.Entry()
178 | self.pword_field2.connect("changed", self.validator)
179 | self.pword_field2.set_visibility(False)
180 | self.attach(pword_label2, LABEL_COLUMN, row, 1, 1)
181 | self.attach(self.pword_field2, DATA_COLUMN, row, 1, 1)
182 |
183 | row += 1
184 | # And now an administrative user check
185 | self.adminuser = Gtk.CheckButton(
186 | "This user should have administrative capabilities")
187 | self.attach(self.adminuser, DATA_COLUMN, row, 1, 1)
188 |
189 | row += 1
190 | btnbox = Gtk.ButtonBox()
191 | btnbox.set_spacing(5)
192 | # Lastly the action buttons
193 | self.ok = Gtk.Button("Add now")
194 | self.ok.get_style_context().add_class("suggested-action")
195 | ok_image = Gtk.Image()
196 | ok_image.set_from_icon_name("list-add-symbolic", Gtk.IconSize.BUTTON)
197 | self.ok.set_image(ok_image)
198 | self.ok.set_sensitive(False)
199 | self.ok.connect("clicked", self.add_user)
200 |
201 | self.cancel = Gtk.Button("Cancel")
202 | self.cancel.connect("clicked", lambda x: self.owner.show_main())
203 | cancel_image = Gtk.Image()
204 | cancel_image.set_from_icon_name("window-close-symbolic",
205 | Gtk.IconSize.BUTTON)
206 | self.cancel.set_image(cancel_image)
207 |
208 | btnbox.set_layout(Gtk.ButtonBoxStyle.START)
209 | btnbox.set_margin_top(10)
210 | btnbox.add(self.ok)
211 | btnbox.add(self.cancel)
212 | self.attach(btnbox, DATA_COLUMN, row, 1, 1)
213 |
214 | for label in [uname_label, rname_label, pword_label, pword_label2]:
215 | label.set_halign(Gtk.Align.START)
216 |
217 | def clear_form(self):
218 | """ Clear the form state """
219 | items = [
220 | self.uname_field,
221 | self.rname_field,
222 | self.pword_field,
223 | self.pword_field2
224 | ]
225 | for entry in items:
226 | entry.set_text("")
227 | entry.set_icon_from_icon_name(
228 | Gtk.EntryIconPosition.SECONDARY, None)
229 | for check in [self.adminuser]:
230 | check.set_active(False)
231 | self.adminuser.set_sensitive(True)
232 |
233 | def add_user(self, w=None):
234 | user = User(self.uname_field.get_text(),
235 | self.rname_field.get_text(),
236 | self.pword_field.get_text(),
237 | False,
238 | self.adminuser.get_active())
239 | self.owner.add_new_user(user)
240 | self.owner.show_main()
241 |
242 |
243 | class InstallerUsersPage(BasePage):
244 | """ User management. """
245 |
246 | info = None
247 | had_init = False
248 |
249 | def __init__(self):
250 | BasePage.__init__(self)
251 |
252 | self.listbox = Gtk.ListBox()
253 | self.listbox.connect("row-activated", self.activated)
254 | scroller = Gtk.ScrolledWindow(None, None)
255 | scroller.add(self.listbox)
256 | scroller.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
257 | scroller.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
258 | scroller.get_style_context().set_junction_sides(
259 | Gtk.JunctionSides.BOTTOM)
260 |
261 | # Placeholder stuff
262 | placeholder = Gtk.Label("{}".format(
263 | "You haven\'t added any users yet."))
264 | placeholder.set_use_markup(True)
265 | placeholder.show()
266 | self.listbox.set_placeholder(placeholder)
267 |
268 | toolbar = Gtk.Toolbar()
269 | toolbar.set_icon_size(Gtk.IconSize.SMALL_TOOLBAR)
270 | toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR)
271 | junctions = Gtk.JunctionSides.TOP
272 | toolbar.get_style_context().set_junction_sides(junctions)
273 |
274 | add = Gtk.ToolButton()
275 | add.connect("clicked", self.add_user)
276 | add.set_icon_name("list-add-symbolic")
277 | toolbar.add(add)
278 |
279 | self.remove = Gtk.ToolButton()
280 | self.remove.set_icon_name("list-remove-symbolic")
281 | self.remove.set_sensitive(False)
282 | self.remove.connect("clicked", self.delete_user)
283 | toolbar.add(self.remove)
284 |
285 | # We use a stack here too, because dialogs are horrible.
286 | self.stack = Gtk.Stack()
287 | self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_UP_DOWN)
288 | self.pack_start(self.stack, True, True, 0)
289 |
290 | main_page = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
291 | main_page.pack_start(scroller, True, True, 0)
292 | main_page.pack_start(toolbar, False, False, 0)
293 | main_page.set_border_width(40)
294 |
295 | self.stack.add_named(main_page, "main")
296 |
297 | self.add_user_page = NewUserPage(self)
298 | self.stack.add_named(self.add_user_page, "add-user")
299 |
300 | def activated(self, box, row):
301 | if row is None:
302 | self.remove.set_sensitive(False)
303 | return
304 | self.remove.set_sensitive(True)
305 |
306 | def delete_user(self, w=None):
307 | row = self.listbox.get_selected_row()
308 | if row is None:
309 | self.remove.set_sensitive(False)
310 | return
311 | user = row.get_children()[0].user
312 | self.info.users.remove(user)
313 | self.listbox.remove(row)
314 | self.remove.set_sensitive(False)
315 |
316 | if len(self.info.users) == 0:
317 | self.info.owner.set_can_next(False)
318 |
319 | def add_user(self, widget):
320 | admins = [user for user in self.info.users if user.admin]
321 | self.stack.set_visible_child_name("add-user")
322 | if len(admins) == 0:
323 | # Force this new user to be an administrator
324 | self.add_user_page.adminuser.set_sensitive(False)
325 | self.add_user_page.adminuser.set_active(True)
326 | self.info.owner.set_can_previous(False)
327 |
328 | def add_new_user(self, user):
329 | self.info.users.append(user)
330 | user_panel = UserPanel(user)
331 | self.listbox.add(user_panel)
332 | self.listbox.show_all()
333 | self.info.owner.set_can_next(True)
334 |
335 | def show_main(self):
336 | self.stack.set_visible_child_name("main")
337 | self.info.owner.set_can_previous(True)
338 | self.add_user_page.clear_form()
339 |
340 | def prepare(self, info):
341 | self.info = info
342 | if not self.info.users:
343 | self.info.users = list()
344 |
345 | self.info.owner.set_can_previous(True)
346 | self.info.owner.set_can_next(len(self.info.users) > 0)
347 | self.stack.set_visible_child_name("main")
348 | self.show_all()
349 | self.add_user_page.clear_form()
350 |
351 | # Start on the new user page
352 | if not self.had_init:
353 | self.add_user(None)
354 | self.had_init = True
355 |
356 | def get_title(self):
357 | return "Who will use this device?"
358 |
359 | def get_sidebar_title(self):
360 | return "Users"
361 |
362 | def get_name(self):
363 | return "users"
364 |
365 | def get_icon_name(self, plasma=False):
366 | return "system-users"
367 |
--------------------------------------------------------------------------------
/os_installer2/permissions.py:
--------------------------------------------------------------------------------
1 | #!/bin/true
2 | # -*- coding: utf-8 -*-
3 | #
4 | # This file is part of os-installer
5 | #
6 | # Copyright 2013-2020 Solus
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 2 of the License, or
11 | # (at your option) any later version.
12 | #
13 |
14 | import os
15 | import pwd
16 |
17 |
18 | class PermissionsManager:
19 |
20 | down_uid = None
21 | down_gid = None
22 | home_dir = None
23 |
24 | def __init__(self):
25 | if "PKEXEC_UID" in os.environ:
26 | id_ = os.environ["PKEXEC_UID"]
27 | try:
28 | uid = int(id_)
29 | self.down_uid = uid
30 | self.down_gid = uid
31 | self.set_details()
32 | except Exception as e:
33 | print("Defaulting on fallback UID: {}".format(e))
34 | return
35 | if "SUDO_UID" in os.environ:
36 | id_ = os.environ["SUDO_UID"]
37 | try:
38 | uid = int(id_)
39 | self.down_uid = uid
40 | self.down_gid = uid
41 | self.set_details()
42 | except Exception as e:
43 | print("Defaulting on fallback UID: {}".format(e))
44 |
45 | def set_details(self):
46 | pw = pwd.getpwuid(self.down_uid)
47 | if not pw:
48 | self.home_dir = "/home/live"
49 | return
50 | self.home_dir = pw.pw_dir
51 |
52 | def down_permissions(self):
53 | """ Drop our current permissions """
54 | try:
55 | os.setresgid(self.down_gid, self.down_gid, 0)
56 | os.setresuid(self.down_uid, self.down_uid, 0)
57 | os.environ['HOME'] = self.home_dir
58 | except Exception as e:
59 | print("Failed to drop permissions: {}".format(e))
60 | return False
61 | return True
62 |
63 | def up_permissions(self):
64 | """ Elevate our current permissions """
65 | try:
66 | os.setresuid(0, 0, 0)
67 | os.setresgid(0, 0, 0)
68 | os.environ['HOME'] = '/root'
69 | except Exception as e:
70 | print("Failed to raise permissions: {}".format(e))
71 | return False
72 | return True
73 |
--------------------------------------------------------------------------------
/os_installer2/postinstall.py:
--------------------------------------------------------------------------------
1 | #!/bin/true
2 | # -*- coding: utf-8 -*-
3 | #
4 | # This file is part of os-installer
5 | #
6 | # Copyright 2013-2020 Solus
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 2 of the License, or
11 | # (at your option) any later version.
12 | #
13 |
14 | import subprocess
15 | import os
16 | from collections import OrderedDict
17 | from .diskman import DiskManager
18 | from .diskops import DiskOpCreateSwap, DiskOpUseSwap, DiskOpUseHome
19 | from .diskops import DiskOpCreateBoot
20 | from .diskops import DiskOpCreateLUKSContainer
21 | from .strategy import EmptyDiskStrategy
22 | import shutil
23 |
24 |
25 | def get_part_uuid(path, part_uuid=False):
26 | """ Get the UUID of a given partition """
27 | col = "PARTUUID" if part_uuid else "UUID"
28 | cmd = "blkid -s {} -o value {}".format(col, path)
29 | try:
30 | o = subprocess.check_output(cmd, shell=True)
31 | o = o.split("\n")[0]
32 | o = o.replace("\r", "").replace("\n", "").strip()
33 | return o
34 | except Exception as e:
35 | print("UUID lookup failed: {}".format(e))
36 | return None
37 |
38 |
39 | class PostInstallStep:
40 | """ Basic post-install API """
41 |
42 | # Tracking operations
43 | info = None
44 |
45 | # Installer reference for doing the hard lifting
46 | installer = None
47 |
48 | # Errors for this obj
49 | errors = None
50 |
51 | def __init__(self, info, installer):
52 | self.info = info
53 | self.installer = installer
54 |
55 | def apply(self):
56 | """ Apply this post-install step """
57 | print("NOT IMPLEMENTED!")
58 | return False
59 |
60 | def get_display_string(self):
61 | return "I AM NOT IMPLEMENTED!"
62 |
63 | def set_errors(self, err):
64 | """ Set the errors for this step """
65 | self.errors = err
66 |
67 | def get_errors(self):
68 | """ Get the errors, if any, for this step """
69 | return self.errors
70 |
71 | def run_in_chroot(self, command):
72 | """ Helper to enable quick boolean chroot usage """
73 | full_cmd = "LC_ALL=C chroot \"{}\" /bin/sh -c \"{}\"".format(
74 | self.installer.get_installer_target_filesystem(),
75 | command)
76 | try:
77 | subprocess.check_call(full_cmd, shell=True)
78 | except Exception as e:
79 | self.set_errors(e)
80 | return False
81 | return True
82 |
83 | def is_long_step(self):
84 | """ Override when this is a long operation and the progressbar should
85 | pulse, so the user doesn't believe the UI locked up """
86 | return False
87 |
88 |
89 | class PostInstallVfs(PostInstallStep):
90 | """ Set up the virtual filesystems required for all other steps """
91 |
92 | vfs_points = None
93 |
94 | def __init__(self, info, installer):
95 | PostInstallStep.__init__(self, info, installer)
96 | self.vfs_points = OrderedDict([
97 | ("/dev", "{}/dev"),
98 | ("/dev/shm", "{}/dev/shm"),
99 | ("/dev/pts", "{}/dev/pts"),
100 | ("/sys", "{}/sys"),
101 | ("/proc", "{}/proc"),
102 | ])
103 |
104 | def get_display_string(self):
105 | return "Setting up virtual filesystems"
106 |
107 | def apply(self):
108 | target = self.installer.get_installer_target_filesystem()
109 | for source_point in self.vfs_points:
110 | target_point = self.vfs_points[source_point].format(target)
111 | cmd = "mount --bind {} \"{}\"".format(source_point, target_point)
112 | try:
113 | subprocess.check_call(cmd, shell=True)
114 | self.installer.mount_tracker[source_point] = target_point
115 | except Exception as e:
116 | self.set_errors("Failed to bind-mount vfs: {}".format(e))
117 | return False
118 | return True
119 |
120 |
121 | class PostInstallRemoveLiveConfig(PostInstallStep):
122 | """ Remove the live user from the filesystem """
123 |
124 | """ Packages that are of no use to the user, i.e. us. """
125 | live_packages = None
126 | original_source = None
127 | modified_files = None
128 |
129 | def __init__(self, info, installer):
130 | PostInstallStep.__init__(self, info, installer)
131 | self.live_packages = [
132 | "network-manager-livecd",
133 | "os-installer",
134 | ]
135 | # Find the branding package to nuke
136 | if os.path.exists("/usr/bin/mate-panel"):
137 | self.live_packages.append("mate-desktop-branding-livecd")
138 | elif os.path.exists("/usr/bin/gnome-shell"):
139 | self.live_packages.append("gnome-desktop-branding-livecd")
140 | elif os.path.exists("/usr/bin/plasmashell"):
141 | self.live_packages.append("plasma-desktop-branding-livecd")
142 | else:
143 | self.live_packages.append("budgie-desktop-branding-livecd")
144 |
145 | self.original_source = "/usr/share/os-installer"
146 | self.modified_files = [
147 | "/etc/gdm/custom.conf"
148 | ]
149 |
150 | def get_display_string(self):
151 | return "Removing live configuration"
152 |
153 | def apply(self):
154 | # Forcibly remove the user (TODO: Make all this configurable... )
155 | if not self.run_in_chroot("userdel -fr live"):
156 | return False
157 |
158 | packages = []
159 | packages.extend(self.live_packages)
160 |
161 | # Don't keep GRUB around on UEFI installs, causes confusion.
162 | if self.info.strategy.is_uefi():
163 | packages.extend(["grub2", "os-prober"])
164 |
165 | # Return live-specific packages
166 | cmd_remove = "eopkg rmf {} -y".format(
167 | " ".join(packages))
168 | if not self.run_in_chroot(cmd_remove):
169 | self.set_errors("Failed to remove live packages")
170 | return False
171 |
172 | # Replace the modified
173 | target_fs = self.installer.get_installer_target_filesystem()
174 |
175 | for replacement in self.modified_files:
176 | bname = os.path.basename(replacement)
177 | source_file = os.path.join(self.original_source, bname)
178 | target_file = os.path.join(target_fs, replacement[1:])
179 |
180 | if not os.path.exists(source_file):
181 | continue
182 | target_dir = os.path.dirname(target_file)
183 | if not os.path.exists(target_dir):
184 | try:
185 | os.makedirs(target_dir, 0o0755)
186 | except Exception as e:
187 | self.set_errors("Cannot mkdir: {}".format(e))
188 | return False
189 | try:
190 | shutil.copy2(source_file, target_file)
191 | except Exception as e:
192 | self.set_errors("Cannot update file: {}".format(e))
193 | return False
194 |
195 | # Remove sudo
196 | if not self.run_in_chroot("rm /etc/sudoers.d/os-installer"):
197 | return False
198 |
199 | # Remove history
200 | history_dir = os.path.join(target_fs, "var/lib/eopkg/history")
201 | try:
202 | shutil.rmtree(history_dir)
203 | except Exception as e:
204 | self.set_errors("Cannot remove history: {}".format(e))
205 | return False
206 |
207 | # Recreate blank directory
208 | try:
209 | os.makedirs(history_dir, 0o0755)
210 | except Exception as e:
211 | self.set_errors("Cannot mkdir: {}".format(e))
212 | return False
213 | return True
214 |
215 | def is_long_step(self):
216 | """ Have to remove packages and users, compile schemas, etc """
217 | return True
218 |
219 |
220 | class PostInstallSyncFilesystems(PostInstallStep):
221 | """ Just call sync, nothing fancy """
222 |
223 | def __init__(self, info, installer):
224 | PostInstallStep.__init__(self, info, installer)
225 |
226 | def get_display_string(self):
227 | return "Flushing buffers to disk.. please wait"
228 |
229 | def is_long_step(self):
230 | return True
231 |
232 | def apply(self):
233 | try:
234 | subprocess.check_call("sync", shell=True)
235 | except:
236 | pass
237 | return True
238 |
239 |
240 | class PostInstallMachineID(PostInstallStep):
241 | """ Initialise the machine-id """
242 |
243 | def __init__(self, info, installer):
244 | PostInstallStep.__init__(self, info, installer)
245 |
246 | def get_display_string(self):
247 | return "Creating machine-id for new installation"
248 |
249 | def apply(self):
250 | fp = os.path.join(self.installer.get_installer_target_filesystem(),
251 | "etc/machine-id")
252 | # Delete existing machine-id
253 | if os.path.exists(fp):
254 | try:
255 | os.remove(fp)
256 | except Exception as e:
257 | self.set_errors(e)
258 | return False
259 |
260 | # Now create a new machine-id
261 | if not self.run_in_chroot("systemd-machine-id-setup"):
262 | self.set_errors("Failed to construct machine-id")
263 | return False
264 | return True
265 |
266 |
267 | # We use this guy to set the global layout..
268 | KEYBOARD_CONFIG_TEMPLATE = """
269 | # Read and parsed by systemd-localed. It's probably wise not to edit this file
270 | # manually too freely.
271 | Section "InputClass"
272 | Identifier "system-keyboard"
273 | MatchIsKeyboard "on"
274 | Option "XkbModel" "%(XKB_MODEL)s"
275 | Option "XkbLayout" "%(XKB_LAYOUT)s"
276 | EndSection
277 | """
278 |
279 |
280 | class PostInstallKeyboard(PostInstallStep):
281 | """ Set the keyboard layout on the target device """
282 |
283 | def __init__(self, info, installer):
284 | PostInstallStep.__init__(self, info, installer)
285 |
286 | def get_display_string(self):
287 | return "Storing keyboard configuration"
288 |
289 | def apply(self):
290 | xkb_model = "pc104"
291 | x11dir = os.path.join(self.installer.get_installer_target_filesystem(),
292 | "etc/X11/xorg.conf.d")
293 | x11file = os.path.join(x11dir, "00-keyboard.conf")
294 |
295 | # create the x11 dir
296 | if not os.path.exists(x11dir):
297 | try:
298 | os.makedirs(x11dir, 0o0755)
299 | except Exception as ex:
300 | self.set_errors(ex)
301 | return False
302 |
303 | # set up the template
304 | tmpl = KEYBOARD_CONFIG_TEMPLATE % {
305 | 'XKB_MODEL': xkb_model, 'XKB_LAYOUT': self.info.keyboard
306 | }
307 |
308 | # write the template to disk
309 | tmpl = tmpl.strip() + "\n"
310 | try:
311 | with open(x11file, "w") as xfile:
312 | os.chmod(x11file, 0o0644)
313 | xfile.write(tmpl)
314 | except Exception as ex:
315 | self.set_errors(ex)
316 | return False
317 | return True
318 |
319 |
320 | class PostInstallLocale(PostInstallStep):
321 | """ Set the system locale """
322 |
323 | def __init__(self, info, installer):
324 | PostInstallStep.__init__(self, info, installer)
325 |
326 | def get_display_string(self):
327 | return "Storing system locale"
328 |
329 | def apply(self):
330 | lang = self.info.locale
331 |
332 | # Dump to locale.conf
333 | fpath = os.path.join(self.installer.get_installer_target_filesystem(),
334 | "etc/locale.conf")
335 | try:
336 | with open(fpath, "w") as localef:
337 | os.chmod(fpath, 0o0644)
338 | if not lang.endswith(".utf8"):
339 | lc = lang.split(".")[0]
340 | lang = "{}.utf8".format(lc)
341 | lang = lang.replace(".utf8", ".UTF-8")
342 | localef.write("LANG={}\n".format(lang))
343 | except Exception as e:
344 | self.set_errors(e)
345 | return False
346 | return True
347 |
348 |
349 | ADJTIME_LOCAL = """
350 | 0.0 0 0.0
351 | 0
352 | LOCAL
353 | """
354 |
355 |
356 | class PostInstallTimezone(PostInstallStep):
357 | """ Set up the timezone """
358 |
359 | def __init__(self, info, installer):
360 | PostInstallStep.__init__(self, info, installer)
361 |
362 | def get_display_string(self):
363 | return "Storing system timezone"
364 |
365 | def apply(self):
366 | """ Link /etc/localtime up to zoneinfo """
367 | loc = self.info.timezone
368 | self.run_in_chroot("rm -f /etc/localtime")
369 | cmd = "ln -sf \"/usr/share/zoneinfo/{}\" /etc/localtime".format(loc)
370 | if not self.run_in_chroot(cmd):
371 | self.set_errors("Failed to set timezone")
372 | return False
373 |
374 | if not self.info.windows_present:
375 | return True
376 |
377 | # Set adjtime to local for Windows users
378 | adjp = os.path.join(self.installer.get_installer_target_filesystem(),
379 | "etc/adjtime")
380 | try:
381 | with open(adjp, "w") as adjtime:
382 | os.chmod(adjp, 0o0644)
383 | adjtime.write(ADJTIME_LOCAL.strip() + "\n")
384 | except Exception as e:
385 | print("Warning: Failed to update adjtime: {}".format(e))
386 | return True
387 |
388 |
389 | class PostInstallUsers(PostInstallStep):
390 | """ Add users to the new installation """
391 |
392 | normal_groups = None
393 | admin_groups = None
394 |
395 | def __init__(self, info, installer):
396 | PostInstallStep.__init__(self, info, installer)
397 |
398 | self.normal_groups = [
399 | "audio",
400 | "video",
401 | "cdrom",
402 | "dialout",
403 | "fuse",
404 | "users",
405 | "sambashares",
406 | ]
407 | self.admin_groups = [
408 | "sudo",
409 | "lpadmin",
410 | "plugdev",
411 | "scanner",
412 | ]
413 |
414 | def get_display_string(self):
415 | return "Creating users"
416 |
417 | def apply(self):
418 | """ Add all of the system users """
419 |
420 | pwd_file = []
421 |
422 | # Add all the users.
423 | for user in self.info.users:
424 | groups = []
425 | groups.extend(self.normal_groups)
426 | if user.admin:
427 | groups.extend(self.admin_groups)
428 |
429 | cmd = "useradd -s {} -c '{}' -G {} -m {}".format(
430 | "/bin/bash", # Default shell in Solus
431 | user.realname,
432 | ",".join(groups),
433 | user.username)
434 |
435 | try:
436 | self.run_in_chroot(cmd)
437 | except Exception as e:
438 | self.set_errors("Cannot configure user: {}".format(e))
439 | return False
440 |
441 | pwd_file.append("{}:{}".format(user.username, user.password))
442 |
443 | f = os.path.join(self.installer.get_installer_target_filesystem(),
444 | "tmp/newusers.conf")
445 |
446 | # Pass off the passwords to chpasswd
447 | pwds_done = False
448 | fd = None
449 | try:
450 | fd = open(f, "w")
451 | fd.write("\n".join(pwd_file))
452 | fd.close()
453 | fd = None
454 | self.run_in_chroot("cat /tmp/newusers.conf | chpasswd")
455 | os.remove(f)
456 | pwds_done = True
457 | except Exception as e:
458 | self.set_errors("Unable to update passwords: {}".format(e))
459 |
460 | if fd:
461 | fd.close()
462 |
463 | if not pwds_done:
464 | try:
465 | os.remove(f)
466 | except:
467 | pass
468 | return False
469 |
470 | # Disable the root account
471 | if not self.run_in_chroot("passwd -d root"):
472 | self.set_errors("Failed to disable root account")
473 | return False
474 | return True
475 |
476 |
477 | class PostInstallHostname(PostInstallStep):
478 | """ Set up the hostname """
479 |
480 | def __init__(self, info, installer):
481 | PostInstallStep.__init__(self, info, installer)
482 |
483 | def get_display_string(self):
484 | return "Setting the system hostname"
485 |
486 | def apply(self):
487 | hosts = [
488 | "127.0.0.1\tlocalhost",
489 | "127.0.0.1\t{}".format(self.info.hostname),
490 | "# The following lines are desirable for IPv6 capable hosts",
491 | "::1 localhost ip6-localhost ip6-loopback",
492 | "fe00::0 ip6-localnet",
493 | "ff00::0 ip6-mcastprefix",
494 | "ff02::1 ip6-allnodes",
495 | "ff02::2 ip6-allrouters",
496 | "ff02::3 ip6-allhosts"
497 | ]
498 |
499 | bpath = self.installer.get_installer_target_filesystem()
500 | hostname_file = os.path.join(bpath, "etc/hostname")
501 | hosts_file = os.path.join(bpath, "etc/hosts")
502 | try:
503 | with open(hostname_file, "w") as hout:
504 | os.chmod(hostname_file, 0o0644)
505 | hout.write("{}\n".format(self.info.hostname))
506 | with open(hosts_file, "w") as hpout:
507 | os.chmod(hosts_file, 0o0644)
508 | hpout.write("\n".join(hosts) + "\n")
509 | except Exception as e:
510 | self.set_errors("Failed to configure hosts: {}".format(e))
511 | return False
512 | return True
513 |
514 |
515 | class PostInstallDiskOptimize(PostInstallStep):
516 | """ Optimize disk usage """
517 |
518 | def __init__(self, info, installer):
519 | PostInstallStep.__init__(self, info, installer)
520 |
521 | def get_display_string(self):
522 | return "Optimizing the disk configuration"
523 |
524 | def apply(self):
525 | dev_path = self.info.strategy.drive.path
526 |
527 | cmd = None
528 | if DiskManager.is_device_ssd(dev_path):
529 | cmd = "systemctl enable fstrim.timer"
530 | else:
531 | # TODO: Support readahead
532 | return True
533 |
534 | if not self.run_in_chroot(cmd):
535 | self.set_errors("Unable to apply disk optimizations")
536 | return False
537 | return True
538 |
539 | class PostInstallUsysconf(PostInstallStep):
540 | """ Run usysconf for the target """
541 |
542 | def __init__(self, info, installer):
543 | PostInstallStep.__init__(self, info, installer)
544 |
545 | def get_display_string(self):
546 | return "Running usysconf"
547 |
548 | def apply(self):
549 | """ Perform a full usysconf run """
550 | try:
551 | self.run_in_chroot("usysconf run -f")
552 | except Exception as e:
553 | self.set_errors("Failed to run usysconf: {}".format(e))
554 | return False
555 | return True
556 |
557 | def is_long_step(self):
558 | """ Its.. just long. Seriously """
559 | return True
560 |
561 | FSTAB_HEADER = """
562 | # /etc/fstab: static file system information.
563 | #
564 | #
565 |
566 | # /dev/ROOT / ext3 noatime 0 1
567 | # /dev/SWAP none swap sw 0 0
568 | # /dev/fd0 /mnt/floppy auto noauto 0 0
569 | none /proc proc nosuid,noexec 0 0
570 | none /dev/shm tmpfs defaults 0 0
571 | """
572 |
573 |
574 | class PostInstallFstab(PostInstallStep):
575 | """ Write the fstab to disk """
576 |
577 | def __init__(self, info, installer):
578 | PostInstallStep.__init__(self, info, installer)
579 |
580 | def get_display_string(self):
581 | return "Writing filesystem mount points"
582 |
583 | def apply(self):
584 | """ Do the dull task of writing the fstab """
585 | strat = self.info.strategy
586 | disk = strat.disk
587 |
588 | appends = []
589 |
590 | ext4_ops = "rw,relatime,errors=remount-ro"
591 |
592 | for op in strat.get_operations():
593 | # TODO: Add custom mountpoints here!
594 | # Skip swap for GPT/UEFI
595 | if isinstance(op, DiskOpUseHome):
596 | huuid = get_part_uuid(op.home_part.path)
597 | fs = op.home_part_fs
598 | desc = "# {} at time of installation".format(op.home_part.path)
599 | i = "UUID={}\t/home\t{}\t{}\t0\t2"
600 | appends.append(desc)
601 | appends.append(i.format(huuid, fs, ext4_ops))
602 | continue
603 | elif isinstance(op, DiskOpCreateBoot):
604 | buuid = get_part_uuid(op.part.path)
605 | fs = op.fstype
606 | desc = "# {} at time of installation".format(op.part.path)
607 | i = "UUID={}\t/boot\t{}\t{}\t0\t2"
608 | appends.append(desc)
609 | appends.append(i.format(buuid, fs, ext4_ops))
610 | continue
611 |
612 | # All swap handling from hereon out
613 | swap_path = None
614 | if isinstance(op, DiskOpCreateSwap):
615 | swap_path = op.part.path
616 | elif isinstance(op, DiskOpUseSwap):
617 | swap_path = op.swap_part.path
618 |
619 | if not swap_path:
620 | continue
621 |
622 | uuid = get_part_uuid(swap_path)
623 | if uuid:
624 | im = "UUID={}\tswap\tswap\tsw\t0\t0".format(uuid)
625 | appends.append(im)
626 | else:
627 | appends.append("{}\tswap\tswap\tsw\t0\t0".format(swap_path))
628 |
629 | # Add the root partition last
630 | root = strat.get_root_partition()
631 | uuid = get_part_uuid(root)
632 | appends.append("# {} at time of installation".format(root))
633 |
634 | appends.append("UUID={}\t/\text4\t{}\t0\t1".format(uuid, ext4_ops))
635 |
636 | fp = os.path.join(self.installer.get_installer_target_filesystem(),
637 | "etc/fstab")
638 |
639 | try:
640 | with open(fp, "w") as fstab:
641 | fstab.write(FSTAB_HEADER.strip() + "\n")
642 | fstab.write("\n".join(appends) + "\n")
643 | except Exception as e:
644 | self.set_errors("Failed to write fstab: {}".format(e))
645 | return False
646 | return True
647 |
648 |
649 | class PostInstallBootloader(PostInstallStep):
650 | """ Install the bootloader itself """
651 |
652 | # We record swap uuid into resume= parameter
653 | swap_uuid = None
654 |
655 | def __init__(self, info, installer):
656 | PostInstallStep.__init__(self, info, installer)
657 |
658 | def get_display_string(self):
659 | return "Configuring bootloader.. please wait"
660 |
661 | def apply(self):
662 | # Determine the swap path
663 | swap_path = None
664 | for op in self.info.strategy.get_operations():
665 | if isinstance(op, DiskOpCreateSwap):
666 | swap_path = op.part.path
667 | elif isinstance(op, DiskOpUseSwap):
668 | swap_path = op.swap_part.path
669 |
670 | if swap_path is not None:
671 | self.swap_uuid = get_part_uuid(swap_path)
672 |
673 | if self.info.strategy.is_uefi():
674 | return self.apply_boot_loader()
675 | return self.apply_bios()
676 |
677 | def is_long_step(self):
678 | """ UEFI no, GRUB yes. """
679 | return not self.info.strategy.is_uefi()
680 |
681 | def get_luks_uuid(self):
682 | """ Get the cached LUKS Container UUID """
683 | luks_uuid = None
684 | for op in self.info.strategy.get_operations():
685 | if isinstance(op, DiskOpCreateLUKSContainer):
686 | luks_uuid = op.crypto_uuid
687 | break
688 | return luks_uuid
689 |
690 | def is_encrypted_install(self):
691 | strategy = self.info.strategy
692 | if isinstance(strategy, EmptyDiskStrategy):
693 | if not strategy.use_lvm2:
694 | return False
695 | if not strategy.use_encryption:
696 | return False
697 | else:
698 | return False
699 | return True
700 |
701 | def is_lvm2_install(self):
702 | strategy = self.info.strategy
703 | if isinstance(strategy, EmptyDiskStrategy):
704 | if strategy.use_lvm2:
705 | return True
706 | return False
707 |
708 | def apply_boot_loader(self):
709 | """ Invoke clr-boot-manager itself """
710 | target = self.installer.get_installer_target_filesystem()
711 |
712 | kdir = os.path.join(target, "etc/kernel/cmdline.d")
713 | kresumefile = os.path.join(kdir, "10_resume.conf")
714 |
715 | # Attempt to mount efivarfs dir in order to create the EFI boot entry
716 | # No big deal if it fails, we'll rely on shim's fallback to create it.
717 | efivardir = "/sys/firmware/efi/efivars"
718 | target_point = "{}{}".format(target, efivardir)
719 | efivar_cmd = "mount --types efivarfs {} \"{}\"".format(
720 | efivardir,
721 | target_point)
722 | try:
723 | subprocess.check_call(efivar_cmd, shell=True)
724 | self.installer.mount_tracker[efivardir] = target_point
725 | except Exception as e:
726 | print("Error mounting efivar vfs: {}".format(e))
727 | print("Buggy UEFI firmware, relying on shim's fallback")
728 |
729 | # Write out the resume= parameter for clr-boot-manager
730 | if self.swap_uuid is not None:
731 | if not os.path.exists(kdir):
732 | try:
733 | os.makedirs(kdir, 00755)
734 | with open(kresumefile, "w") as kfile_output:
735 | swap = "resume=UUID={}".format(self.swap_uuid)
736 | kfile_output.write(swap)
737 | except Exception as ex:
738 | self.set_errors("Error with kernel config: {}".format(ex))
739 | return False
740 |
741 | cmd = "clr-boot-manager update"
742 | if not self.run_in_chroot(cmd):
743 | self.set_errors("Failed to update bootloader configuration")
744 | return False
745 | return True
746 |
747 | def apply_bios(self):
748 | """ Take the BIOS approach to bootloader configuration """
749 | if not self.info.bootloader_install:
750 | # Still need detecting from other distros
751 | return self.apply_boot_loader()
752 | cmd = "grub-install --force \"{}\"".format(self.info.bootloader_sz)
753 | if not self.run_in_chroot(cmd):
754 | self.set_errors("Failed to install GRUB bootloader")
755 | return False
756 | # Proxy back to CBM
757 | return self.apply_boot_loader()
758 |
759 | def get_ichild(self, root, child):
760 | t1 = os.path.join(root, child)
761 | if os.path.exists(t1) or not os.path.exists(root):
762 | return t1
763 | try:
764 | for i in os.listdir(root):
765 | i2 = i.lower()
766 | if i2 == child:
767 | return os.path.join(root, i)
768 | except Exception as ex:
769 | print("Error obtaining {} dir: {}".format(child, ex))
770 | return t1
771 |
772 | def get_efi_dir(self, base):
773 | return self.get_ichild(base, "EFI")
774 |
--------------------------------------------------------------------------------
/os_installer2/tz.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8; Mode: Python; indent-tabs-mode: nil; tab-width: 4 -*-
2 |
3 | # Copyright (C) 2006, 2007 Canonical Ltd.
4 | # Written by Colin Watson .
5 | #
6 | # This program is free software; you can redistribute it and/or modify
7 | # it under the terms of the GNU General Public License as published by
8 | # the Free Software Foundation; either version 2 of the License, or
9 | # (at your option) any later version.
10 | #
11 | # This program is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with this program; if not, write to the Free Software
18 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 |
20 | from __future__ import print_function
21 |
22 | import datetime
23 | import hashlib
24 | import os
25 | import sys
26 | import time
27 | import xml.dom.minidom
28 |
29 |
30 | TZ_DATA_FILE = '/usr/share/zoneinfo/zone.tab'
31 | ISO_3166_FILE = '/usr/share/xml/iso-codes/iso_3166.xml'
32 |
33 |
34 | def _seconds_since_epoch(dt):
35 | # TODO cjwatson 2006-02-23: %s escape is not portable
36 | return int(dt.replace(tzinfo=None).strftime('%s'))
37 |
38 |
39 | class SystemTzInfo(datetime.tzinfo):
40 | def __init__(self, tz=None):
41 | self.tz = tz
42 |
43 | def _select_tz(self):
44 | tzbackup = None
45 | if 'TZ' in os.environ:
46 | tzbackup = os.environ['TZ']
47 | if self.tz is not None:
48 | os.environ['TZ'] = self.tz
49 | time.tzset()
50 | return tzbackup
51 |
52 | def _restore_tz(self, tzbackup):
53 | if tzbackup is None:
54 | if 'TZ' in os.environ:
55 | del os.environ['TZ']
56 | else:
57 | os.environ['TZ'] = tzbackup
58 | time.tzset()
59 |
60 | def utcoffset(self, dt):
61 | tzbackup = self._select_tz()
62 | try:
63 | if time.daylight == 0:
64 | # no DST information
65 | dstminutes = -time.timezone / 60
66 | else:
67 | localtime = time.localtime(_seconds_since_epoch(dt))
68 | if localtime.tm_isdst != 1:
69 | # not in DST
70 | dstminutes = -time.timezone / 60
71 | else:
72 | # in DST
73 | dstminutes = -time.altzone / 60
74 | return datetime.timedelta(minutes=int(dstminutes))
75 | finally:
76 | self._restore_tz(tzbackup)
77 |
78 | def rawutcoffset(self, unused_dt):
79 | tzbackup = self._select_tz()
80 | try:
81 | dstminutes = -time.timezone / 60
82 | return datetime.timedelta(minutes=int(dstminutes))
83 | finally:
84 | self._restore_tz(tzbackup)
85 |
86 | def dst(self, dt):
87 | tzbackup = self._select_tz()
88 | try:
89 | if time.daylight == 0:
90 | # no DST information, so assume no DST; None would be more
91 | # accurate but causes awkwardness in fromutc()
92 | return datetime.timedelta(0)
93 | else:
94 | localtime = time.localtime(_seconds_since_epoch(dt))
95 | if localtime.tm_isdst != 1:
96 | # not in DST
97 | return datetime.timedelta(0)
98 | else:
99 | dstminutes = (time.timezone - time.altzone) / 60
100 | return datetime.timedelta(minutes=int(dstminutes))
101 | finally:
102 | self._restore_tz(tzbackup)
103 |
104 | def tzname(self, unused_dt):
105 | return self.tz
106 |
107 | def tzname_letters(self, dt):
108 | tzbackup = self._select_tz()
109 | try:
110 | localtime = time.localtime(_seconds_since_epoch(dt))
111 | return time.strftime('%Z', localtime)
112 | finally:
113 | self._restore_tz(tzbackup)
114 |
115 |
116 | class Iso3166(object):
117 | def __init__(self):
118 | self.names = {}
119 | document = xml.dom.minidom.parse(ISO_3166_FILE)
120 | entries = document.getElementsByTagName('iso_3166_entries')[0]
121 | self.handle_entries(entries)
122 |
123 | def handle_entries(self, entries):
124 | for entry in entries.getElementsByTagName('iso_3166_entry'):
125 | self.handle_entry(entry)
126 |
127 | def handle_entry(self, entry):
128 | if (entry.hasAttribute('alpha_2_code') and
129 | (entry.hasAttribute('common_name') or
130 | entry.hasAttribute('name'))):
131 | alpha_2_code = entry.getAttribute('alpha_2_code')
132 | if entry.hasAttribute('common_name'):
133 | name = entry.getAttribute('common_name').encode('utf-8')
134 | else:
135 | name = entry.getAttribute('name').encode('utf-8')
136 | self.names[alpha_2_code] = name
137 |
138 |
139 | # Much of the Location and Database classes are a rough translation of
140 | # gnome-system-tools/src/time/tz.c. Thanks to Hans Petter Jansson
141 | # for that.
142 |
143 | def _parse_position(position, wholedigits):
144 | if position == '' or len(position) < 4 or wholedigits > 9:
145 | return 0.0
146 | wholestr = position[:wholedigits + 1]
147 | fractionstr = position[wholedigits + 1:]
148 | whole = float(wholestr)
149 | fraction = float(fractionstr)
150 | if whole >= 0.0:
151 | return whole + fraction / pow(10.0, len(fractionstr))
152 | else:
153 | return whole - fraction / pow(10.0, len(fractionstr))
154 |
155 |
156 | class Location(object):
157 | def __init__(self, zonetab_line, iso3166):
158 | bits = zonetab_line.rstrip().split('\t', 3)
159 | latlong = bits[1]
160 | latlongsplit = latlong.find('-', 1)
161 | if latlongsplit == -1:
162 | latlongsplit = latlong.find('+', 1)
163 | if latlongsplit != -1:
164 | latitude = latlong[:latlongsplit]
165 | longitude = latlong[latlongsplit:]
166 | else:
167 | latitude = latlong
168 | longitude = '+0'
169 |
170 | self.country = bits[0]
171 | if self.country in iso3166.names:
172 | self.human_country = iso3166.names[self.country]
173 | else:
174 | self.human_country = self.country
175 | self.zone = bits[2]
176 | self.human_zone = self.zone.replace('_', ' ').split('/')[-1]
177 | if len(bits) > 3:
178 | self.comment = bits[3]
179 | else:
180 | self.comment = None
181 | self.latitude = _parse_position(latitude, 2)
182 | self.longitude = _parse_position(longitude, 3)
183 |
184 | # Grab md5sum of the timezone file for later comparison
185 | try:
186 | zone_path = os.path.join('/usr/share/zoneinfo', self.zone)
187 | with open(zone_path, 'rb') as tz_file:
188 | self.md5sum = hashlib.md5(tz_file.read()).digest()
189 | except IOError:
190 | self.md5sum = None
191 |
192 | try:
193 | today = datetime.datetime.today()
194 | except (ValueError, OverflowError):
195 | # Some versions of Python have problems with clocks set before
196 | # the epoch (http://python.org/sf/1646728). Assuming that the
197 | # time is set to the epoch will at least let us avoid crashing,
198 | # although the UTC offset and zone letters may be wrong.
199 | today = datetime.datetime.fromtimestamp(0)
200 | self.info = SystemTzInfo(self.zone)
201 | self.utc_offset = self.info.utcoffset(today)
202 | self.raw_utc_offset = self.info.rawutcoffset(today)
203 | self.zone_letters = self.info.tzname_letters(today)
204 |
205 |
206 | class _Database(object):
207 | def __init__(self):
208 | self.locations = []
209 | iso3166 = Iso3166()
210 | with open(TZ_DATA_FILE) as tzdata:
211 | for line in tzdata:
212 | if line.startswith('#'):
213 | continue
214 | self.locations.append(Location(line, iso3166))
215 |
216 | # Build mappings from timezone->location and country->locations
217 | self.cc_to_locs = {}
218 | self.tz_to_loc = {}
219 | for loc in self.locations:
220 | self.tz_to_loc[loc.zone] = loc
221 | if loc.country in self.cc_to_locs:
222 | self.cc_to_locs[loc.country] += [loc]
223 | else:
224 | self.cc_to_locs[loc.country] = [loc]
225 |
226 | def get_loc(self, tz):
227 | # Sometimes we'll encounter timezones that aren't really
228 | # city-zones, like "US/Eastern" or "Mexico/General". So first,
229 | # we check if the timezone is known. If it isn't, we search for
230 | # one with the same md5sum and make a reference to it
231 | try:
232 | return self.tz_to_loc[tz]
233 | except:
234 | try:
235 | zone_path = os.path.join('/usr/share/zoneinfo', tz)
236 | with open(zone_path, 'rb') as tz_file:
237 | md5sum = hashlib.md5(tz_file.read()).digest()
238 |
239 | for loc in self.locations:
240 | if md5sum == loc.md5sum:
241 | self.tz_to_loc[tz] = loc
242 | return loc
243 | except IOError:
244 | pass
245 |
246 | # If not found, oh well, just warn and move on.
247 | print('Could not understand timezone', tz, file=sys.stderr)
248 | self.tz_to_loc[tz] = None # save it for the future
249 | return None
250 |
251 |
252 | _database = None
253 |
254 |
255 | def Database():
256 | global _database
257 | if not _database:
258 | _database = _Database()
259 | return _database
260 |
--------------------------------------------------------------------------------
/os_installer2/users.py:
--------------------------------------------------------------------------------
1 | #!/bin/true
2 | # -*- coding: utf-8 -*-
3 | #
4 | # This file is part of os-installer
5 | #
6 | # Copyright 2013-2020 Solus
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 2 of the License, or
11 | # (at your option) any later version.
12 | #
13 |
14 |
15 | """ Allowed regex for a username """
16 | USERNAME_REGEX = "^[a-z_][a-z0-9_-]*[$]?$"
17 |
18 | """ Minimum password length """
19 | PASSWORD_LENGTH = 6
20 |
21 |
22 | class User:
23 | """ Nothing fancifull here, just a user object creation thinger """
24 |
25 | # System username
26 | username = None
27 |
28 | # Real name (i.e. GECOS)
29 | realname = None
30 |
31 | # Chosen password
32 | password = None
33 |
34 | # Administrator? i.e. sudo
35 | admin = False
36 |
37 | # Autologin? Not yet implemented..
38 | autologin = False
39 |
40 | def __init__(self, username, realname, password, autologin, admin):
41 | """ Create a new user with the given fields """
42 | self.username = username
43 | self.realname = realname
44 | self.password = password
45 | self.autologin = autologin
46 | self.admin = admin
47 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | setup(
4 | name = "os-installer",
5 | version = "16.0",
6 | author = "Solus",
7 | author_email = "copyright@getsol.us",
8 | description = ("Operating System Installer"),
9 | license = "GPL-2.0",
10 | url = "https://github.com/getsolus/os-installer",
11 | packages = ['os_installer2', 'os_installer2.pages'],
12 | scripts = ['os-installer-gtk'],
13 | classifiers = [ "License :: OSI Approved :: GPL-2.0 License"],
14 | package_data = {'os_installer2': ['data/*.png', 'data/*.svg', 'data/*.css']},
15 | )
16 |
--------------------------------------------------------------------------------
/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | pep8 os-installer-gtk os_installer2/*.py os_installer2/pages/*.py || exit 1
4 | flake8 os-installer-gtk os_installer2/*.py os_installer2/pages/*.py || exit 1
5 |
6 | # check use of %s
7 | t=`grep '%s' os-installer-gtk os_installer2/*.py os_installer2/pages/*.py | grep -v tz.py`
8 | if [[ $t == "" ]]; then
9 | exit 0
10 | fi
11 | echo "Found use of '%s' in tree"
12 | echo "$t"
13 | exit 1
14 |
--------------------------------------------------------------------------------