qubes-anaconda-addon: An initial-setup module
This commit introduces an initial-setup module to replace the firstboot-based initial set-up procedures required by Qubes OS.
This commit is contained in:
parent
b47c25d7d6
commit
bb7c2caa13
339
qubes-anaconda-addon/LICENSE
Normal file
339
qubes-anaconda-addon/LICENSE
Normal file
@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
1
qubes-anaconda-addon/README
Normal file
1
qubes-anaconda-addon/README
Normal file
@ -0,0 +1 @@
|
||||
Repository for the "Qubes OS" Anaconda addon.
|
154
qubes-anaconda-addon/firstboot-qubes-text
Executable file
154
qubes-anaconda-addon/firstboot-qubes-text
Executable file
@ -0,0 +1,154 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Failsafe minimal text-mode firstboot
|
||||
|
||||
# Welcome
|
||||
|
||||
if [ "x$1" = "x--help" ]; then
|
||||
echo "Failsafe minimal text-mode firstboot"
|
||||
echo "For unattended mode use: $0 <username> <userpass> <vm-creation-option-number>"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "########################################################"
|
||||
echo " Welcome to `cat /etc/qubes-release`"
|
||||
echo "########################################################"
|
||||
echo
|
||||
echo "This is failsafe text-mode firstboot. If you see this message, you have"
|
||||
echo "some problem with Xorg (most probably video driver)"
|
||||
echo
|
||||
echo "Anyway, some basic setup is needed to continue:"
|
||||
|
||||
# User creation
|
||||
|
||||
echo
|
||||
echo "1. Setup user account"
|
||||
exists=0
|
||||
user=$1
|
||||
while [ -z "$user" ]; do
|
||||
echo -n "Enter desired username (may already exist): "
|
||||
read user
|
||||
if echo "$user" | grep -q "[^a-z0-9]"; then
|
||||
echo "ERROR: Invalid characters in username, try again"
|
||||
user=
|
||||
elif id $user > /dev/null 2>&1; then
|
||||
if [ $(id -u ${user}) -ge 1000 ] && id -n -G ${user} | grep -q qubes; then
|
||||
echo "OK: Using an existing user: \"${user}\""
|
||||
exists=1
|
||||
break
|
||||
fi
|
||||
echo "ERROR: This user already exists or is not suitable. Please try again"
|
||||
user=
|
||||
else
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${exists} -eq 0 ]; then
|
||||
useradd -G qubes -m "$user" || exit 1
|
||||
if [ -n "$2" ]; then
|
||||
echo -e "$2\n$2" | passwd --stdin "$user"
|
||||
else
|
||||
while ! passwd "$user"; do true; done
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create default VMs
|
||||
|
||||
echo
|
||||
echo "2. Create default VMs"
|
||||
echo
|
||||
echo "Choose one option:"
|
||||
echo " 1. Create default service VMs, and pre-defined AppVMs (work, banking, personal, untrusted)"
|
||||
echo " 2. Just create default service VMs"
|
||||
echo " 3. Do not create any VMs right now, but configure template(s)"
|
||||
echo " 4. Do not do anything (not recommended, for advanced users only)"
|
||||
vms_option=$3
|
||||
while true; do
|
||||
if [ -z "$vms_option" ]; then
|
||||
echo -n "Enter your choice (1/2/3/4): "
|
||||
read vms_option
|
||||
fi
|
||||
if [ "$vms_option" == "1" ]; then
|
||||
vms_template=yes
|
||||
vms_service=yes
|
||||
vms_app=yes
|
||||
break
|
||||
elif [ "$vms_option" == "2" ]; then
|
||||
vms_template=yes
|
||||
vms_service=yes
|
||||
break
|
||||
elif [ "$vms_option" == "3" ]; then
|
||||
vms_template=yes
|
||||
break
|
||||
elif [ "$vms_option" == "4" ]; then
|
||||
break
|
||||
else
|
||||
echo "ERROR: Invalid choice, try again"
|
||||
vms_option=
|
||||
fi
|
||||
done
|
||||
|
||||
set -e
|
||||
|
||||
if [ "$vms_template" == "yes" ]; then
|
||||
for template in `ls /var/lib/qubes/vm-templates`; do
|
||||
echo "-> Configuring template $template..."
|
||||
qvm-start --no-guid $template
|
||||
su -g "qubes" -c "qvm-sync-appmenus $template" - $user
|
||||
qvm-shutdown --wait $template
|
||||
done
|
||||
|
||||
qubes-prefs --set default-template 'fedora-23'
|
||||
fi
|
||||
|
||||
if [ "$vms_service" == "yes" -o "$vms_app" == "yes" ]; then
|
||||
echo "-> Configuring Qubes OS management framework..."
|
||||
|
||||
if test -e /var/log/salt/minion; then
|
||||
mv /var/log/salt/minion /var/log/salt/minion.install || :
|
||||
fi
|
||||
|
||||
qubesctl state.sls config -l quiet --out quiet
|
||||
fi
|
||||
|
||||
states=()
|
||||
|
||||
if [ "$vms_service" == "yes" ]; then
|
||||
states=("${states[@]}" qvm.sys-net qvm.sys-firewall)
|
||||
fi
|
||||
|
||||
if [ "$vms_app" == "yes" ]; then
|
||||
states=("${states[@]}" qvm.personal qvm.work qvm.untrusted qvm.vault)
|
||||
fi
|
||||
|
||||
if [ "$vms_service" == "yes" -o "$vms_app" == "yes" ]; then
|
||||
for state in "${states[@]}"; do
|
||||
echo "-> Requesting creation of VM: ${state#qvm.}"
|
||||
qubesctl top.enable "${state}" saltenv=dom0 -l quiet --out quiet
|
||||
done
|
||||
|
||||
echo "-> Creating VMs"
|
||||
qubesctl "state.highstate"
|
||||
fi
|
||||
|
||||
if [ "$vms_service" == "yes" ]; then
|
||||
echo "--> Configuring service VMs"
|
||||
default_netvm="sys-net"
|
||||
default_firewallvm="sys-firewall"
|
||||
|
||||
su -g "qubes" -c "qvm-prefs --set ${default_firewallvm} netvm ${default_netvm}" - $user
|
||||
su -g "qubes" -c "qubes-prefs --set default-netvm ${default_firewallvm}" - $user
|
||||
su -g "qubes" -c "qubes-prefs --set updatevm ${default_firewallvm}" - $user
|
||||
su -g "qubes" -c "qubes-prefs --set clockvm ${default_netvm}" - $user
|
||||
|
||||
echo "-> Starting network..."
|
||||
service qubes-netvm start
|
||||
|
||||
# DispVM creation fails with the following error message, most likely due to missing $DISPLAY:
|
||||
# "Cannot start qubes-guid!"
|
||||
#echo "-> Creating DispVM savefile (can take long time)..."
|
||||
#su -g "qubes" -c "/usr/bin/qvm-create-default-dvm --default-template --default-script" - $user || :
|
||||
fi
|
||||
|
||||
echo "-> Done."
|
@ -0,0 +1,5 @@
|
||||
# do not import gui package here because it imports Gtk which causes a traceback
|
||||
# if X server is not yet running
|
||||
#import gui
|
||||
|
||||
import tui
|
@ -0,0 +1,7 @@
|
||||
"""
|
||||
We need org_qubes_os_initial_setup/gui to be a package so that relative imports
|
||||
work.
|
||||
|
||||
"""
|
||||
|
||||
import spokes
|
@ -0,0 +1 @@
|
||||
import qubes_os
|
@ -0,0 +1,105 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.19.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.0"/>
|
||||
<requires lib="AnacondaWidgets" version="1.0"/>
|
||||
<object class="AnacondaSpokeWindow" id="qubesOsSpokeWindow">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="window_name" translatable="yes">QUBES OS</property>
|
||||
<signal name="button-clicked" handler="on_back_clicked" swapped="no"/>
|
||||
<child internal-child="main_box">
|
||||
<object class="GtkBox" id="AnacondaSpokeWindow-main_box1">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">6</property>
|
||||
<child internal-child="nav_box">
|
||||
<object class="GtkEventBox" id="AnacondaSpokeWindow-nav_box1">
|
||||
<property name="app_paintable">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child internal-child="nav_area">
|
||||
<object class="GtkGrid" id="AnacondaSpokeWindow-nav_area1">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">6</property>
|
||||
<property name="margin_right">6</property>
|
||||
<property name="margin_top">6</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child internal-child="alignment">
|
||||
<object class="GtkAlignment" id="AnacondaSpokeWindow-alignment1">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="yalign">0</property>
|
||||
<property name="xscale">0</property>
|
||||
<property name="yscale">0.5</property>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkBox" id="AnacondaSpokeWindow-action_area1">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="mainBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
@ -0,0 +1,636 @@
|
||||
#
|
||||
# Copyright (C) 2016 M. Vefa Bicakci <m.v.b@runbox.com>
|
||||
# Copyright (C) 2016 Qubes OS Developers
|
||||
# Copyright (C) 2013 Red Hat, Inc.
|
||||
#
|
||||
# This copyrighted material is made available to anyone wishing to use,
|
||||
# modify, copy, or redistribute it subject to the terms and conditions of
|
||||
# the GNU General Public License v.2, or (at your option) any later version.
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY expressed or implied, including the implied warranties of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
# Public License for more details. You should have received a copy of the
|
||||
# GNU General Public License along with this program; if not, write to the
|
||||
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
|
||||
# source code or documentation are not subject to the GNU General Public
|
||||
# License and may only be used or replicated with the express permission of
|
||||
# Red Hat, Inc.
|
||||
#
|
||||
# Red Hat Author(s): Vratislav Podzimek <vpodzime@redhat.com>
|
||||
#
|
||||
|
||||
"""Module with the QubesOsSpoke class."""
|
||||
|
||||
# import gettext
|
||||
# _ = lambda x: gettext.ldgettext("qubes-os-anaconda-plugin", x)
|
||||
|
||||
# will never be translated
|
||||
_ = lambda x: x
|
||||
N_ = lambda x: x
|
||||
|
||||
import functools
|
||||
import grp
|
||||
import logging
|
||||
import os
|
||||
import os.path
|
||||
import pyudev
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
gi.require_version('Gdk', '3.0')
|
||||
gi.require_version('GLib', '2.0')
|
||||
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import GLib
|
||||
|
||||
from pyanaconda import iutil
|
||||
from pyanaconda.ui.categories.system import SystemCategory
|
||||
from pyanaconda.ui.gui.spokes import NormalSpoke
|
||||
from pyanaconda.ui.common import FirstbootOnlySpokeMixIn
|
||||
|
||||
# export only the spoke, no helper functions, classes or constants
|
||||
__all__ = ["QubesOsSpoke"]
|
||||
|
||||
def is_package_installed(pkgname):
|
||||
return not subprocess.call(['rpm', '-q', pkgname],
|
||||
stdout=open(os.devnull, 'w'), stderr=open(os.devnull, 'w'))
|
||||
|
||||
def usb_keyboard_present():
|
||||
context = pyudev.Context()
|
||||
keyboards = context.list_devices(subsystem='input', ID_INPUT_KEYBOARD='1')
|
||||
return any([d.get('ID_USB_INTERFACES', False) for d in keyboards])
|
||||
|
||||
def started_from_usb():
|
||||
def get_all_used_devices(dev):
|
||||
stat = os.stat(dev)
|
||||
if stat.st_rdev:
|
||||
# XXX any better idea how to handle device-mapper?
|
||||
sysfs_slaves = '/sys/dev/block/{}:{}/slaves'.format(
|
||||
os.major(stat.st_rdev), os.minor(stat.st_rdev))
|
||||
if os.path.exists(sysfs_slaves):
|
||||
for slave_dev in os.listdir(sysfs_slaves):
|
||||
for d in get_all_used_devices('/dev/{}'.format(slave_dev)):
|
||||
yield d
|
||||
else:
|
||||
yield dev
|
||||
|
||||
context = pyudev.Context()
|
||||
mounts = open('/proc/mounts').readlines()
|
||||
for mount in mounts:
|
||||
device = mount.split(' ')[0]
|
||||
if not os.path.exists(device):
|
||||
continue
|
||||
for dev in get_all_used_devices(device):
|
||||
udev_info = pyudev.Device.from_device_file(context, dev)
|
||||
if udev_info.get('ID_USB_INTERFACES', False):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
class QubesChoice(object):
|
||||
instances = []
|
||||
|
||||
def __init__(self, label, states, depend=None, extra_check=None,
|
||||
replace=None, indent=False):
|
||||
self.widget = Gtk.CheckButton(label=label)
|
||||
self.states = states
|
||||
self.depend = depend
|
||||
self.extra_check = extra_check
|
||||
self.selected = None
|
||||
self.replace = replace
|
||||
|
||||
self._can_be_sensitive = True
|
||||
|
||||
if indent:
|
||||
self.outer_widget = Gtk.Alignment()
|
||||
self.outer_widget.add(self.widget)
|
||||
self.outer_widget.set_padding(0, 0, 20, 0)
|
||||
else:
|
||||
self.outer_widget = self.widget
|
||||
|
||||
if self.depend is not None:
|
||||
self.depend.widget.connect('toggled', self.friend_on_toggled)
|
||||
self.depend.widget.connect('notify::sensitive', self.friend_on_toggled)
|
||||
self.friend_on_toggled(self.depend.widget)
|
||||
|
||||
self.instances.append(self)
|
||||
|
||||
def friend_on_toggled(self, other_widget, param_string=''):
|
||||
self.set_sensitive(other_widget.get_active())
|
||||
|
||||
def get_selected(self):
|
||||
return (self.selected
|
||||
if self.selected is not None
|
||||
else self.widget.get_sensitive() and self.widget.get_active())
|
||||
|
||||
def store_selected(self):
|
||||
self.selected = self.get_selected()
|
||||
|
||||
def set_sensitive(self, sensitive):
|
||||
if self._can_be_sensitive:
|
||||
self.widget.set_sensitive(sensitive)
|
||||
|
||||
@classmethod
|
||||
def on_check_advanced_toggled(cls, widget):
|
||||
selected = widget.get_active()
|
||||
|
||||
# this works, because you cannot instantiate the choices in wrong order
|
||||
# (cls.instances is a list and have deterministic ordering)
|
||||
for choice in cls.instances:
|
||||
choice.set_sensitive(not selected and
|
||||
(choice.depend is None or choice.depend.get_selected()))
|
||||
|
||||
@classmethod
|
||||
def get_states(cls):
|
||||
replaced = functools.reduce(
|
||||
lambda x, y: x+y if y else x,
|
||||
(choice.replace for choice in cls.instances if
|
||||
choice.get_selected()),
|
||||
()
|
||||
)
|
||||
for choice in cls.instances:
|
||||
if choice.get_selected():
|
||||
for state in choice.states:
|
||||
if state not in replaced:
|
||||
yield state
|
||||
|
||||
|
||||
class DisabledChoice(QubesChoice):
|
||||
def __init__(self, label):
|
||||
super(DisabledChoice, self).__init__(label, ())
|
||||
self.widget.set_sensitive(False)
|
||||
self._can_be_sensitive = False
|
||||
|
||||
class QubesOsSpoke(FirstbootOnlySpokeMixIn, NormalSpoke):
|
||||
"""
|
||||
Since this class inherits from the FirstbootOnlySpokeMixIn, it will
|
||||
only appear in the Initial Setup (successor of the Firstboot tool).
|
||||
|
||||
:see: pyanaconda.ui.common.UIObject
|
||||
:see: pyanaconda.ui.common.Spoke
|
||||
:see: pyanaconda.ui.gui.GUIObject
|
||||
:see: pyanaconda.ui.common.FirstbootSpokeMixIn
|
||||
:see: pyanaconda.ui.gui.spokes.NormalSpoke
|
||||
|
||||
"""
|
||||
|
||||
### class attributes defined by API ###
|
||||
|
||||
# list all top-level objects from the .glade file that should be exposed
|
||||
# to the spoke or leave empty to extract everything
|
||||
builderObjects = ["qubesOsSpokeWindow"]
|
||||
|
||||
# the name of the main window widget
|
||||
mainWidgetName = "qubesOsSpokeWindow"
|
||||
|
||||
# name of the .glade file in the same directory as this source
|
||||
uiFile = "qubes_os.glade"
|
||||
|
||||
# category this spoke belongs to
|
||||
category = SystemCategory
|
||||
|
||||
# spoke icon (will be displayed on the hub)
|
||||
# preferred are the -symbolic icons as these are used in Anaconda's spokes
|
||||
icon = "qubes-logo-icon"
|
||||
|
||||
# title of the spoke (will be displayed on the hub)
|
||||
title = N_("_QUBES OS")
|
||||
|
||||
### methods defined by API ###
|
||||
def __init__(self, data, storage, payload, instclass):
|
||||
"""
|
||||
:see: pyanaconda.ui.common.Spoke.__init__
|
||||
:param data: data object passed to every spoke to load/store data
|
||||
from/to it
|
||||
:type data: pykickstart.base.BaseHandler
|
||||
:param storage: object storing storage-related information
|
||||
(disks, partitioning, bootloader, etc.)
|
||||
:type storage: blivet.Blivet
|
||||
:param payload: object storing packaging-related information
|
||||
:type payload: pyanaconda.packaging.Payload
|
||||
:param instclass: distribution-specific information
|
||||
:type instclass: pyanaconda.installclass.BaseInstallClass
|
||||
|
||||
"""
|
||||
|
||||
NormalSpoke.__init__(self, data, storage, payload, instclass)
|
||||
|
||||
self.logger = logging.getLogger("anaconda")
|
||||
|
||||
self.main_box = self.builder.get_object("mainBox")
|
||||
self.thread_dialog = None
|
||||
|
||||
self.process_error = None
|
||||
self.qubes_user = None
|
||||
self.qubes_gid = None
|
||||
self.default_template = 'fedora-23'
|
||||
|
||||
self.set_stage("Start-up")
|
||||
self.done = False
|
||||
|
||||
self.__init_qubes_choices()
|
||||
|
||||
def __init_qubes_choices(self):
|
||||
self.choice_network = QubesChoice(
|
||||
_('Create default system qubes (sys-net, sys-firewall)'),
|
||||
('qvm.sys-net', 'qvm.sys-firewall'))
|
||||
|
||||
self.choice_default = QubesChoice(
|
||||
_('Create default application qubes '
|
||||
'(personal, work, untrusted, vault)'),
|
||||
('qvm.personal', 'qvm.work', 'qvm.untrusted', 'qvm.vault'),
|
||||
depend=self.choice_network)
|
||||
|
||||
if (is_package_installed('qubes-template-whonix-gw') and
|
||||
is_package_installed('qubes-template-whonix-ws')):
|
||||
self.choice_whonix = QubesChoice(
|
||||
_('Create Whonix Gateway and Workstation qubes '
|
||||
'(sys-whonix, anon-whonix)'),
|
||||
('qvm.sys-whonix', 'qvm.anon-whonix'),
|
||||
depend=self.choice_network)
|
||||
else:
|
||||
self.choice_whonix = DisabledChoice(_("Whonix not installed"))
|
||||
|
||||
self.choice_whonix_default = QubesChoice(
|
||||
_('Route applications traffic and updates through Tor anonymity '
|
||||
'network [experimental]'),
|
||||
(),
|
||||
depend=self.choice_whonix,
|
||||
indent=True)
|
||||
|
||||
if not usb_keyboard_present() and not started_from_usb():
|
||||
self.choice_usb = QubesChoice(
|
||||
_('Create USB qube holding all USB controllers (sys-usb) '
|
||||
'[experimental]'),
|
||||
('qvm.sys-usb',))
|
||||
else:
|
||||
self.choice_usb = DisabledChoice(
|
||||
_('USB qube configuration disabled - you are using USB '
|
||||
'keyboard or USB disk'))
|
||||
|
||||
self.choice_usb_with_net = QubesChoice(
|
||||
_("Use sys-net qube for both networking and USB devices"),
|
||||
('qvm.sys-net-with-usb',),
|
||||
depend=self.choice_usb,
|
||||
replace=('qvm.sys-usb',),
|
||||
indent=True
|
||||
)
|
||||
|
||||
self.check_advanced = Gtk.CheckButton(label=_('Do not configure anything (for advanced users)'))
|
||||
self.check_advanced.connect('toggled', QubesChoice.on_check_advanced_toggled)
|
||||
|
||||
for choice in QubesChoice.instances:
|
||||
self.main_box.pack_start(choice.outer_widget, False, True, 0)
|
||||
|
||||
self.main_box.pack_end(self.check_advanced, False, True, 0)
|
||||
|
||||
self.check_advanced.set_active(False)
|
||||
|
||||
self.choice_network.widget.set_active(True)
|
||||
self.choice_default.widget.set_active(True)
|
||||
if self.choice_whonix.widget.get_sensitive():
|
||||
self.choice_whonix.widget.set_active(True)
|
||||
|
||||
def initialize(self):
|
||||
"""
|
||||
The initialize method that is called after the instance is created.
|
||||
The difference between __init__ and this method is that this may take
|
||||
a long time and thus could be called in a separated thread.
|
||||
|
||||
:see: pyanaconda.ui.common.UIObject.initialize
|
||||
|
||||
"""
|
||||
|
||||
NormalSpoke.initialize(self)
|
||||
|
||||
def refresh(self):
|
||||
"""
|
||||
The refresh method that is called every time the spoke is displayed.
|
||||
It should update the UI elements according to the contents of
|
||||
self.data.
|
||||
|
||||
:see: pyanaconda.ui.common.UIObject.refresh
|
||||
|
||||
"""
|
||||
|
||||
# nothing to do here
|
||||
pass
|
||||
|
||||
def apply(self):
|
||||
"""
|
||||
The apply method that is called when the spoke is left. It should
|
||||
update the contents of self.data with values set in the GUI elements.
|
||||
|
||||
"""
|
||||
|
||||
# nothing to do here
|
||||
pass
|
||||
|
||||
@property
|
||||
def ready(self):
|
||||
"""
|
||||
The ready property that tells whether the spoke is ready (can be visited)
|
||||
or not. The spoke is made (in)sensitive based on the returned value.
|
||||
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def completed(self):
|
||||
"""
|
||||
The completed property that tells whether all mandatory items on the
|
||||
spoke are set, or not. The spoke will be marked on the hub as completed
|
||||
or uncompleted acording to the returned value.
|
||||
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
|
||||
return self.done
|
||||
|
||||
@property
|
||||
def mandatory(self):
|
||||
"""
|
||||
The mandatory property that tells whether the spoke is mandatory to be
|
||||
completed to continue in the installation process.
|
||||
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
return True
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
"""
|
||||
The status property that is a brief string describing the state of the
|
||||
spoke. It should describe whether all values are set and if possible
|
||||
also the values themselves. The returned value will appear on the hub
|
||||
below the spoke's title.
|
||||
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
|
||||
return ""
|
||||
|
||||
def execute(self):
|
||||
"""
|
||||
The execute method that is called when the spoke is left. It is
|
||||
supposed to do all changes to the runtime environment according to
|
||||
the values set in the GUI elements.
|
||||
|
||||
"""
|
||||
for choice in QubesChoice.instances:
|
||||
choice.store_selected()
|
||||
|
||||
self.thread_dialog = ThreadDialog("Qubes OS Setup", self.do_setup, (), transient_for=self.main_window)
|
||||
self.thread_dialog.run()
|
||||
self.thread_dialog.destroy()
|
||||
|
||||
def do_setup(self, *args):
|
||||
try:
|
||||
self.qubes_gid = grp.getgrnam('qubes').gr_gid
|
||||
|
||||
qubes_users = grp.getgrnam('qubes').gr_mem
|
||||
|
||||
if len(qubes_users) < 1:
|
||||
self.showErrorMessage(_("You must create a user account to create default VMs."))
|
||||
return
|
||||
else:
|
||||
self.qubes_user = qubes_users[0]
|
||||
|
||||
if self.check_advanced.get_active():
|
||||
return
|
||||
|
||||
errors = []
|
||||
|
||||
os.setgid(self.qubes_gid)
|
||||
os.umask(0o0007)
|
||||
|
||||
# Finish template(s) installation, because it wasn't fully possible
|
||||
# from anaconda (it isn't possible to start a VM there).
|
||||
# This is specific to firstboot, not general configuration.
|
||||
for template in os.listdir('/var/lib/qubes/vm-templates'):
|
||||
try:
|
||||
self.configure_template(template)
|
||||
except Exception as e:
|
||||
errors.append((self.stage, str(e)))
|
||||
|
||||
self.configure_default_template()
|
||||
self.configure_qubes()
|
||||
self.configure_network()
|
||||
if self.choice_usb.get_selected() and not self.choice_usb_with_net.get_selected():
|
||||
# Workaround for #1464 (so qvm.start from salt can't be used)
|
||||
self.run_command(['systemctl', 'start', 'qubes-vm@sys-usb.service'])
|
||||
|
||||
try:
|
||||
self.configure_default_dvm()
|
||||
except Exception as e:
|
||||
errors.append((self.stage, str(e)))
|
||||
|
||||
if errors:
|
||||
msg = ""
|
||||
for (stage, error) in errors:
|
||||
msg += "{} failed:\n{}\n\n".format(stage, error)
|
||||
|
||||
raise Exception(msg)
|
||||
|
||||
except Exception as e:
|
||||
self.showErrorMessage(str(e))
|
||||
|
||||
finally:
|
||||
self.thread_dialog.done()
|
||||
self.done = True
|
||||
|
||||
def set_stage(self, stage):
|
||||
self.stage = stage
|
||||
|
||||
if self.thread_dialog != None:
|
||||
self.thread_dialog.set_text(stage)
|
||||
|
||||
def run_command(self, command, stdin=None):
|
||||
try:
|
||||
sys_root = iutil.getSysroot()
|
||||
|
||||
cmd = iutil.startProgram(command, stderr=subprocess.PIPE, stdin=stdin, root=sys_root)
|
||||
|
||||
(stdout, stderr) = cmd.communicate()
|
||||
|
||||
stdout = stdout.decode("utf-8")
|
||||
stderr = stderr.decode("utf-8")
|
||||
|
||||
if cmd.returncode == 0:
|
||||
self.process_error = None
|
||||
else:
|
||||
self.process_error = "{} failed:\nstdout: \"{}\"\nstderr: \"{}\"".format(command, stdout, stderr)
|
||||
|
||||
except Exception as e:
|
||||
self.process_error = str(e)
|
||||
|
||||
if self.process_error:
|
||||
self.logger.error(self.process_error)
|
||||
raise Exception(self.process_error)
|
||||
|
||||
return (stdout, stderr)
|
||||
|
||||
def configure_qubes(self):
|
||||
self.set_stage('Executing qubes configuration')
|
||||
|
||||
try:
|
||||
# get rid of initial entries (from package installation time)
|
||||
os.rename('/var/log/salt/minion', '/var/log/salt/minion.install')
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
# Refresh minion configuration to make sure all installed formulas are included
|
||||
self.run_command(['qubesctl', 'state.sls', 'config', '-l', 'quiet', '--out', 'quiet'])
|
||||
|
||||
for state in QubesChoice.get_states():
|
||||
print("Setting up state: {}".format(state))
|
||||
self.run_command(['qubesctl', 'top.enable', state, 'saltenv=dom0', '-l', 'quiet', '--out', 'quiet'])
|
||||
|
||||
try:
|
||||
self.run_command(['qubesctl', 'state.highstate'])
|
||||
except Exception:
|
||||
raise Exception(
|
||||
("Qubes initial configuration failed. Login to the system and " +
|
||||
"check /var/log/salt/minion for details. " +
|
||||
"You can retry configuration by calling " +
|
||||
"'sudo qubesctl state.highstate' in dom0 (you will get " +
|
||||
"detailed state there)."))
|
||||
|
||||
def configure_default_template(self):
|
||||
self.set_stage('Setting default template')
|
||||
self.run_command(['/usr/bin/qubes-prefs', '--set', 'default-template', self.default_template])
|
||||
|
||||
def configure_default_dvm(self):
|
||||
self.set_stage("Creating default DisposableVM")
|
||||
|
||||
try:
|
||||
self.run_command(['su', '-c', '/usr/bin/qvm-create-default-dvm --default-template --default-script', self.qubes_user])
|
||||
except Exception:
|
||||
# Kill DispVM template if still running
|
||||
# Do not use self.run_command to not clobber process output
|
||||
subprocess.call(['qvm-kill', '{}-dvm'.format(self.default_template)])
|
||||
raise
|
||||
|
||||
def configure_network(self):
|
||||
self.set_stage('Setting up networking')
|
||||
|
||||
default_netvm = 'sys-firewall'
|
||||
if self.choice_whonix_default.get_selected():
|
||||
default_netvm = 'sys-whonix'
|
||||
|
||||
self.run_command(['/usr/bin/qvm-prefs', '--force-root', '--set', 'sys-firewall', 'netvm', 'sys-net'])
|
||||
self.run_command(['/usr/bin/qubes-prefs', '--set', 'default-netvm', default_netvm])
|
||||
self.run_command(['/usr/bin/qubes-prefs', '--set', 'updatevm', default_netvm])
|
||||
self.run_command(['/usr/bin/qubes-prefs', '--set', 'clockvm', 'sys-net'])
|
||||
self.run_command(['/usr/sbin/service', 'qubes-netvm', 'start'])
|
||||
|
||||
def configure_template(self, template):
|
||||
self.set_stage("Configuring TemplateVM {}".format(template))
|
||||
self.run_command(['qvm-start', '--no-guid', template])
|
||||
self.run_command(['su', '-c', 'qvm-sync-appmenus {}'.format(template), '-', self.qubes_user])
|
||||
self.run_command(['qvm-shutdown', '--wait', template])
|
||||
|
||||
def showErrorMessage(self, text):
|
||||
self.thread_dialog.run_in_ui_thread(self.showErrorMessageHelper, text)
|
||||
|
||||
def showErrorMessageHelper(self, text):
|
||||
dlg = Gtk.MessageDialog(title="Error", message_type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK, text=text)
|
||||
dlg.set_position(Gtk.WindowPosition.CENTER)
|
||||
dlg.set_modal(True)
|
||||
dlg.set_transient_for(self.thread_dialog)
|
||||
dlg.run()
|
||||
dlg.destroy()
|
||||
|
||||
class ThreadDialog(Gtk.Dialog):
|
||||
def __init__(self, title, fun, args, transient_for=None):
|
||||
Gtk.Dialog.__init__(self, title=title, transient_for=transient_for)
|
||||
|
||||
self.set_modal(True)
|
||||
self.set_default_size(500, 100)
|
||||
|
||||
self.connect('delete-event', self.on_delete_event)
|
||||
|
||||
self.progress = Gtk.ProgressBar()
|
||||
self.progress.set_pulse_step(100)
|
||||
self.progress.set_text("")
|
||||
self.progress.set_show_text(False)
|
||||
|
||||
self.label = Gtk.Label()
|
||||
self.label.set_line_wrap(True)
|
||||
self.label.set_text("")
|
||||
|
||||
self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
self.box.pack_start(self.progress, True, True, 0)
|
||||
self.box.pack_start(self.label, True, True, 0)
|
||||
|
||||
self.get_content_area().pack_start(self.box, True, True, 0)
|
||||
|
||||
self.fun = fun
|
||||
self.args = args
|
||||
|
||||
self.logger = logging.getLogger("anaconda")
|
||||
|
||||
self.thread = threading.Thread(target=self.fun, args=self.args)
|
||||
|
||||
def on_delete_event(self, widget=None, *args):
|
||||
# Ignore the clicks on the close button by returning True.
|
||||
self.logger.info("Caught delete-event")
|
||||
return True
|
||||
|
||||
def set_text(self, text):
|
||||
Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT, 0, self.label.set_text, text)
|
||||
|
||||
def done(self):
|
||||
Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT, 100, self.done_helper, ())
|
||||
|
||||
def done_helper(self, *args):
|
||||
self.logger.info("Joining thread.")
|
||||
self.thread.join()
|
||||
|
||||
self.logger.info("Stopping self.")
|
||||
self.response(Gtk.ResponseType.ACCEPT)
|
||||
|
||||
def run_in_ui_thread(self, fun, *args):
|
||||
Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT, 0, fun, *args)
|
||||
|
||||
def run(self):
|
||||
self.thread.start()
|
||||
self.progress.pulse()
|
||||
self.show_all()
|
||||
|
||||
ret = None
|
||||
while ret in (None, Gtk.ResponseType.DELETE_EVENT):
|
||||
ret = super(ThreadDialog, self).run()
|
||||
|
||||
return ret
|
||||
|
||||
if __name__ == "__main__":
|
||||
import time
|
||||
|
||||
def hello_fun(*args):
|
||||
global thread_dialog
|
||||
thread_dialog.set_text("Hello, world! " * 30)
|
||||
time.sleep(2)
|
||||
thread_dialog.set_text("Goodbye, world!")
|
||||
time.sleep(1)
|
||||
thread_dialog.done()
|
||||
return
|
||||
|
||||
logger = logging.getLogger("anaconda")
|
||||
handler = logging.StreamHandler()
|
||||
logger.addHandler(handler)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
thread_dialog = ThreadDialog("Hello", hello_fun, ())
|
||||
thread_dialog.run()
|
@ -0,0 +1 @@
|
||||
import spokes
|
@ -0,0 +1 @@
|
||||
import qubes_os
|
@ -0,0 +1,206 @@
|
||||
#
|
||||
# Copyright (C) 2013 Red Hat, Inc.
|
||||
#
|
||||
# This copyrighted material is made available to anyone wishing to use,
|
||||
# modify, copy, or redistribute it subject to the terms and conditions of
|
||||
# the GNU General Public License v.2, or (at your option) any later version.
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY expressed or implied, including the implied warranties of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
# Public License for more details. You should have received a copy of the
|
||||
# GNU General Public License along with this program; if not, write to the
|
||||
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
|
||||
# source code or documentation are not subject to the GNU General Public
|
||||
# License and may only be used or replicated with the express permission of
|
||||
# Red Hat, Inc.
|
||||
#
|
||||
# Red Hat Author(s): Vratislav Podzimek <vpodzime@redhat.com>
|
||||
#
|
||||
|
||||
"""Module with the class for the Qubes OS TUI spoke."""
|
||||
|
||||
# import gettext
|
||||
# _ = lambda x: gettext.ldgettext("qubes-os-anaconda-plugin", x)
|
||||
|
||||
# will never be translated
|
||||
_ = lambda x: x
|
||||
N_ = lambda x: x
|
||||
|
||||
import subprocess
|
||||
|
||||
from pyanaconda.ui.categories.system import SystemCategory
|
||||
from pyanaconda.ui.tui.spokes import NormalTUISpoke
|
||||
from pyanaconda.constants_text import INPUT_PROCESSED, INPUT_DISCARDED
|
||||
from pyanaconda.ui.common import FirstbootOnlySpokeMixIn
|
||||
|
||||
# export only the HelloWorldSpoke and HelloWorldEditSpoke classes
|
||||
__all__ = ["QubesOsSpoke"]
|
||||
|
||||
class QubesOsSpoke(FirstbootOnlySpokeMixIn, NormalTUISpoke):
|
||||
"""
|
||||
Since this class inherits from the FirstbootOnlySpokeMixIn, it will
|
||||
only appear in the Initial Setup (successor of the Firstboot tool).
|
||||
|
||||
:see: pyanaconda.ui.tui.TUISpoke
|
||||
:see: pyanaconda.ui.common.FirstbootSpokeMixIn
|
||||
:see: pyanaconda.ui.tui.tuiobject.TUIObject
|
||||
:see: pyaanconda.ui.tui.simpleline.Widget
|
||||
|
||||
"""
|
||||
|
||||
### class attributes defined by API ###
|
||||
|
||||
# title of the spoke
|
||||
title = N_("Qubes OS")
|
||||
|
||||
# category this spoke belongs to
|
||||
category = SystemCategory
|
||||
|
||||
def __init__(self, app, data, storage, payload, instclass):
|
||||
"""
|
||||
:see: pyanaconda.ui.tui.base.UIScreen
|
||||
:see: pyanaconda.ui.tui.base.App
|
||||
:param app: reference to application which is a main class for TUI
|
||||
screen handling, it is responsible for mainloop control
|
||||
and keeping track of the stack where all TUI screens are
|
||||
scheduled
|
||||
:type app: instance of pyanaconda.ui.tui.base.App
|
||||
:param data: data object passed to every spoke to load/store data
|
||||
from/to it
|
||||
:type data: pykickstart.base.BaseHandler
|
||||
:param storage: object storing storage-related information
|
||||
(disks, partitioning, bootloader, etc.)
|
||||
:type storage: blivet.Blivet
|
||||
:param payload: object storing packaging-related information
|
||||
:type payload: pyanaconda.packaging.Payload
|
||||
:param instclass: distribution-specific information
|
||||
:type instclass: pyanaconda.installclass.BaseInstallClass
|
||||
|
||||
"""
|
||||
|
||||
NormalTUISpoke.__init__(self, app, data, storage, payload, instclass)
|
||||
|
||||
self.done = False
|
||||
|
||||
def initialize(self):
|
||||
"""
|
||||
The initialize method that is called after the instance is created.
|
||||
The difference between __init__ and this method is that this may take
|
||||
a long time and thus could be called in a separated thread.
|
||||
|
||||
:see: pyanaconda.ui.common.UIObject.initialize
|
||||
|
||||
"""
|
||||
|
||||
NormalTUISpoke.initialize(self)
|
||||
|
||||
def refresh(self, args=None):
|
||||
"""
|
||||
The refresh method that is called every time the spoke is displayed.
|
||||
It should update the UI elements according to the contents of
|
||||
self.data.
|
||||
|
||||
:see: pyanaconda.ui.common.UIObject.refresh
|
||||
:see: pyanaconda.ui.tui.base.UIScreen.refresh
|
||||
:param args: optional argument that may be used when the screen is
|
||||
scheduled (passed to App.switch_screen* methods)
|
||||
:type args: anything
|
||||
:return: whether this screen requests input or not
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
|
||||
return True
|
||||
|
||||
def apply(self):
|
||||
"""
|
||||
The apply method that is called when the spoke is left. It should
|
||||
update the contents of self.data with values set in the spoke.
|
||||
|
||||
"""
|
||||
|
||||
proc = subprocess.Popen(["/usr/share/qubes/firstboot-qubes-text"])
|
||||
proc.wait()
|
||||
|
||||
self.done = True
|
||||
|
||||
def execute(self):
|
||||
"""
|
||||
The excecute method that is called when the spoke is left. It is
|
||||
supposed to do all changes to the runtime environment according to
|
||||
the values set in the spoke.
|
||||
|
||||
"""
|
||||
|
||||
# nothing to do here
|
||||
pass
|
||||
|
||||
@property
|
||||
def completed(self):
|
||||
"""
|
||||
The completed property that tells whether all mandatory items on the
|
||||
spoke are set, or not. The spoke will be marked on the hub as completed
|
||||
or uncompleted acording to the returned value.
|
||||
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
|
||||
return self.done
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
"""
|
||||
The status property that is a brief string describing the state of the
|
||||
spoke. It should describe whether all values are set and if possible
|
||||
also the values themselves. The returned value will appear on the hub
|
||||
below the spoke's title.
|
||||
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
|
||||
return ""
|
||||
|
||||
def input(self, args, key):
|
||||
"""
|
||||
The input method that is called by the main loop on user's input.
|
||||
|
||||
:param args: optional argument that may be used when the screen is
|
||||
scheduled (passed to App.switch_screen* methods)
|
||||
:type args: anything
|
||||
:param key: user's input
|
||||
:type key: unicode
|
||||
:return: if the input should not be handled here, return it, otherwise
|
||||
return INPUT_PROCESSED or INPUT_DISCARDED if the input was
|
||||
processed succesfully or not respectively
|
||||
:rtype: bool|unicode
|
||||
|
||||
"""
|
||||
|
||||
if key:
|
||||
# Do something with the user's input.
|
||||
pass
|
||||
|
||||
# no other actions scheduled, apply changes
|
||||
self.apply()
|
||||
|
||||
# close the current screen (remove it from the stack)
|
||||
self.close()
|
||||
return INPUT_PROCESSED
|
||||
|
||||
def prompt(self, args=None):
|
||||
"""
|
||||
The prompt method that is called by the main loop to get the prompt
|
||||
for this screen.
|
||||
|
||||
:param args: optional argument that can be passed to App.switch_screen*
|
||||
methods
|
||||
:type args: anything
|
||||
:return: text that should be used in the prompt for the input
|
||||
:rtype: unicode|None
|
||||
|
||||
"""
|
||||
|
||||
return _("Please press enter to start Qubes OS configuration.")
|
47
qubes-anaconda-addon/qubes-anaconda-addon.spec
Normal file
47
qubes-anaconda-addon/qubes-anaconda-addon.spec
Normal file
@ -0,0 +1,47 @@
|
||||
%if 0%{?qubes_builder}
|
||||
%define _builddir %(pwd)/qubes-anaconda-addon
|
||||
%endif
|
||||
|
||||
Name: qubes-anaconda-addon
|
||||
Version: 1.0
|
||||
Release: 1%{?dist}
|
||||
Summary: Anaconda addon useful for Qubes OS specific setup procedures
|
||||
|
||||
License: GPLv2+
|
||||
|
||||
BuildArch: noarch
|
||||
BuildRequires: python3
|
||||
Requires: python3
|
||||
#Requires: anaconda >= 19
|
||||
|
||||
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
|
||||
|
||||
%description
|
||||
This is an addon that makes available Qubes OS specific setup functionality
|
||||
at first boot time.
|
||||
|
||||
%prep
|
||||
true
|
||||
|
||||
%build
|
||||
true
|
||||
|
||||
%install
|
||||
rm -rf $RPM_BUILD_ROOT
|
||||
|
||||
install -d $RPM_BUILD_ROOT/%{_datadir}/qubes
|
||||
install --mode 0755 firstboot-qubes-text $RPM_BUILD_ROOT/%{_datadir}/qubes/firstboot-qubes-text
|
||||
|
||||
install -d $RPM_BUILD_ROOT/%{_datadir}/anaconda/addons
|
||||
cp -a org_qubes_os_initial_setup $RPM_BUILD_ROOT/%{_datadir}/anaconda/addons/
|
||||
|
||||
%files
|
||||
%defattr(-,root,root,-)
|
||||
%doc LICENSE README
|
||||
%{_datadir}/qubes/firstboot-qubes-text
|
||||
%dir %{_datadir}/anaconda/addons/org_qubes_os_initial_setup
|
||||
%{_datadir}/anaconda/addons/org_qubes_os_initial_setup/*
|
||||
|
||||
%changelog
|
||||
* Sun Mar 27 2016 M. Vefa Bicakci <m.v.b@runbox.com> - 1.0-1
|
||||
- Initial package for qubes-anaconda-addon
|
Loading…
Reference in New Issue
Block a user