OSDN Git Service

Initial commit
authorRafael Costa Rega <rcostarega@gmail.com>
Sun, 3 May 2020 19:16:21 +0000 (16:16 -0300)
committerRafael Costa Rega <rcostarega@gmail.com>
Sun, 3 May 2020 19:16:21 +0000 (16:16 -0300)
284 files changed:
CHANGELOG [new file with mode: 0644]
COPYING [new file with mode: 0644]
Cnchi/1.png [new file with mode: 0644]
Cnchi/1.xcf [new file with mode: 0644]
Cnchi/10_antergos [new file with mode: 0755]
Cnchi/2.png [new file with mode: 0644]
Cnchi/2.xcf [new file with mode: 0644]
Cnchi/20-intel.conf [new file with mode: 0644]
Cnchi/3.png [new file with mode: 0644]
Cnchi/4-RebornMaintenance.png [new file with mode: 0644]
Cnchi/4.png [new file with mode: 0644]
Cnchi/4.xcf [new file with mode: 0644]
Cnchi/Border.png [new file with mode: 0644]
Cnchi/adwaita-day.jpg [new file with mode: 0644]
Cnchi/antergos-icon.png [new file with mode: 0755]
Cnchi/antergos-install.desktop [new file with mode: 0644]
Cnchi/antergos-logo-mini2.png [new file with mode: 0644]
Cnchi/antergos-logo-mini2.xcf [new file with mode: 0644]
Cnchi/archlinux.svg [new file with mode: 0644]
Cnchi/ask.py [new file with mode: 0644]
Cnchi/bashrc [new file with mode: 0644]
Cnchi/check.py [new file with mode: 0644]
Cnchi/cinnamon.sh [new file with mode: 0644]
Cnchi/cnchi-start.sh [new file with mode: 0755]
Cnchi/cnchi.png [new file with mode: 0755]
Cnchi/cnchi.py [new file with mode: 0755]
Cnchi/desktop.py [new file with mode: 0644]
Cnchi/desktop_info.py [new file with mode: 0644]
Cnchi/encfs.py [new file with mode: 0644]
Cnchi/features.py [new file with mode: 0644]
Cnchi/features_info.py [new file with mode: 0755]
Cnchi/geoip.py [new file with mode: 0644]
Cnchi/grub2.py [new file with mode: 0644]
Cnchi/gufw.desktop [new file with mode: 0644]
Cnchi/gufw.png [new file with mode: 0644]
Cnchi/gufw.svg [new file with mode: 0644]
Cnchi/hexagon erased.xcf [new file with mode: 0644]
Cnchi/info.py [new file with mode: 0755]
Cnchi/install.py [new file with mode: 0755]
Cnchi/lightdm-webkit2-greeter.conf [new file with mode: 0644]
Cnchi/logging_utils.py [new file with mode: 0755]
Cnchi/main_window.py [new file with mode: 0644]
Cnchi/metalink.py [new file with mode: 0755]
Cnchi/mirrors.py [new file with mode: 0755]
Cnchi/pac.py [new file with mode: 0755]
Cnchi/packages.xml [new file with mode: 0755]
Cnchi/pacman.conf [new file with mode: 0755]
Cnchi/pacman.tmpl [new file with mode: 0755]
Cnchi/pacman2.conf [new file with mode: 0644]
Cnchi/pacman2.tmpl [new file with mode: 0644]
Cnchi/post_install.py [new file with mode: 0755]
Cnchi/postinstall.sh [new file with mode: 0755]
Cnchi/rank_mirrors.py [new file with mode: 0755]
Cnchi/reborn-icon-new.png [new file with mode: 0755]
Cnchi/reborn-mirrorlist [new file with mode: 0644]
Cnchi/reborn-mirrorlist2 [new file with mode: 0644]
Cnchi/refresh-keys.desktop [new file with mode: 0644]
Cnchi/refresh-keys.sh [new file with mode: 0755]
Cnchi/sddm.conf [new file with mode: 0644]
Cnchi/select_packages.py [new file with mode: 0755]
Cnchi/show_message.py [new file with mode: 0644]
Cnchi/slides.py [new file with mode: 0755]
Cnchi/systemd_boot.py [new file with mode: 0644]
Cnchi/timezone.py [new file with mode: 0644]
Cnchi/update_db.py [new file with mode: 0755]
Cnchi/updating.sh [new file with mode: 0755]
Cnchi/welcome.py [new file with mode: 0644]
HELP_ME.sh [new file with mode: 0755]
README.md [new file with mode: 0644]
TEST_FILE.sh [new file with mode: 0644]
airootfs/README.md [new file with mode: 0644]
airootfs/etc/arch-release [new file with mode: 0644]
airootfs/etc/fstab [new file with mode: 0644]
airootfs/etc/hostname [new file with mode: 0644]
airootfs/etc/lightdm/lightdm.conf [new file with mode: 0644]
airootfs/etc/locale.conf [new file with mode: 0644]
airootfs/etc/locale.gen [new file with mode: 0644]
airootfs/etc/lsb-release [new file with mode: 0644]
airootfs/etc/machine-id [new file with mode: 0644]
airootfs/etc/modprobe.d/blacklist.conf [new file with mode: 0644]
airootfs/etc/os-release [new file with mode: 0644]
airootfs/etc/pacman.conf [new file with mode: 0644]
airootfs/etc/pam.d/su [new file with mode: 0644]
airootfs/etc/reborn-mirrorlist [new file with mode: 0644]
airootfs/etc/sddm.conf [new file with mode: 0644]
airootfs/etc/shells [new file with mode: 0644]
airootfs/etc/skel/.bash_profile2 [new file with mode: 0644]
airootfs/etc/skel/.bashrc [new file with mode: 0644]
airootfs/etc/skel/.config/autostart/refresh.desktop [new file with mode: 0644]
airootfs/etc/skel/.dmrc [new file with mode: 0644]
airootfs/etc/skel/.inputrc [new file with mode: 0644]
airootfs/etc/skel/.xinitrc2 [new file with mode: 0644]
airootfs/etc/skel/.xsession2 [new file with mode: 0644]
airootfs/etc/sudoers.d/g_wheel [new file with mode: 0644]
airootfs/etc/systemd/scripts/choose-mirror [new file with mode: 0755]
airootfs/etc/systemd/system/choose-mirror.service [new file with mode: 0644]
airootfs/etc/systemd/system/etc-pacman.d-gnupg.mount [new file with mode: 0644]
airootfs/etc/systemd/system/getty@tty1.service.d/autologin.conf [new file with mode: 0644]
airootfs/etc/systemd/system/internet.service [new file with mode: 0644]
airootfs/etc/systemd/system/lightdm.service [new file with mode: 0644]
airootfs/etc/systemd/system/pacman-init.service [new file with mode: 0644]
airootfs/etc/udev/rules.d/81-dhcpcd.rules [new file with mode: 0644]
airootfs/etc/xdg/autostart/cnchi.desktop [new file with mode: 0755]
airootfs/etc/xdg/autostart/internet.desktop [new file with mode: 0644]
airootfs/root/.automated_script.sh [new file with mode: 0755]
airootfs/root/.zlogin [new file with mode: 0644]
airootfs/root/customize_airootfs.sh [new file with mode: 0755]
airootfs/root/install.txt [new file with mode: 0644]
airootfs/usr/bin/auto-partition.sh [new file with mode: 0644]
airootfs/usr/bin/cnchi [new file with mode: 0755]
airootfs/usr/bin/cnchi-git [new file with mode: 0644]
airootfs/usr/bin/cnchi-rank.sh [new file with mode: 0755]
airootfs/usr/bin/cnchi-start.sh [new file with mode: 0755]
airootfs/usr/bin/install-antergos.sh [new file with mode: 0644]
airootfs/usr/bin/internet.sh [new file with mode: 0755]
airootfs/usr/bin/pacman-boot.sh [new file with mode: 0644]
airootfs/usr/share/applications/antergos-install.desktop [new file with mode: 0644]
airootfs/usr/share/applications/cnchi.desktop [new file with mode: 0755]
airootfs/usr/share/applications/cnchi.png [new file with mode: 0644]
airootfs/usr/share/applications/deepin-root-filemanager.desktop [new file with mode: 0644]
airootfs/usr/share/applications/flatpak.desktop [new file with mode: 0644]
airootfs/usr/share/backgrounds/abstract1-reborn2.png [new file with mode: 0644]
airootfs/usr/share/backgrounds/japanese-lake.jpg [new file with mode: 0644]
airootfs/usr/share/locale/af-ZA/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/af/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/ar-SA/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/ar/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/ast/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/az-AZ/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/az/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/az@latin/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/az_IR/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/be/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/bg/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/bn-BD/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/bs-BA/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/bs/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/ca/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/ca@valencia/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/cs-CZ/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/cs/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/da/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/de-AT/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/de/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/el/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/en/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/en_GB/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/eo/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/es-VE/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/es/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/es_419/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/es_AR/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/es_CL/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/es_MX/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/et/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/eu/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/fa/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/fa_IR/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/fi/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/fr-BE/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/fr-CA/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/fr-FR/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/fr/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/gl/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/gu/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/he/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/hi/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/hr/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/hu/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/id/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/it-IT/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/it/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/it_CH/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/ja/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/ka/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/ko/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/ky/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/lt/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/mk/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/mr/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/ms/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/ms_MY/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/nb/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/nl/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/pa/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/pl-PL/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/pl/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/pt/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/pt_BR/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/pt_PT/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/ro-RO/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/ro/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/ru/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/sk/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/sl-SI/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/sl/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/sr-RS/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/sr/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/sr@latin/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/sv/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/szl/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/ta/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/ta_IN/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/tg/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/tl_PH/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/tr-TR/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/tr/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/uk/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/vi-VN/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/vi/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/wa/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/xh/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/zh/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/zh_CN.GB2312/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/zh_CN/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/zh_TW/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/locale/zu/LC_MESSAGES/cnchi.mo [new file with mode: 0644]
airootfs/usr/share/pacman/keyrings/rebornos-revoked [new file with mode: 0644]
airootfs/usr/share/pacman/keyrings/rebornos-trusted [new file with mode: 0644]
airootfs/usr/share/pacman/keyrings/rebornos.gpg [new file with mode: 0644]
airootfs/usr/share/pixmaps/cnchi.png [new file with mode: 0644]
build.sh [new file with mode: 0755]
clean.sh [new file with mode: 0755]
config [new file with mode: 0644]
efiboot/loader/entries/archiso-x86_64-cd.conf [new file with mode: 0644]
efiboot/loader/entries/archiso-x86_64-usb.conf [new file with mode: 0644]
efiboot/loader/entries/nvidia-fallback.conf [new file with mode: 0644]
efiboot/loader/entries/uefi-shell-v1-x86_64.conf [new file with mode: 0644]
efiboot/loader/entries/uefi-shell-v2-x86_64.conf [new file with mode: 0644]
efiboot/loader/loader.conf [new file with mode: 0644]
git-v2.sh [new file with mode: 0755]
gsettings/101_reborn_budgie.gschema.override [new file with mode: 0755]
gsettings/95_reborn_deepin.gschema.override [new file with mode: 0644]
images/apricity.png [new file with mode: 0644]
images/cinnamon.png [new file with mode: 0644]
images/deepin.png [new file with mode: 0644]
images/desktop-environment-apricity.svg [new file with mode: 0644]
images/desktop-environment-budgie.svg [new file with mode: 0644]
images/desktop-environment-i3.svg [new file with mode: 0644]
images/desktop-environment-pantheon.svg [new file with mode: 0644]
images/desktop-environment-windows.svg [new file with mode: 0644]
images/enlightenment.png [new file with mode: 0644]
images/i3.png [new file with mode: 0644]
images/kde.png [new file with mode: 0644]
images/lxqt.png [new file with mode: 0644]
images/openbox.png [new file with mode: 0644]
images/pantheon.png [new file with mode: 0644]
images/windows.png [new file with mode: 0644]
images/xfce.png [new file with mode: 0644]
isolinux/isolinux.cfg [new file with mode: 0644]
mkinitcpio.conf [new file with mode: 0644]
packages.both [new file with mode: 0755]
packages2.both [new file with mode: 0755]
pacman-init.service [new file with mode: 0644]
pacman.conf [new file with mode: 0644]
rebornos.gpg [new file with mode: 0644]
run.sh [new file with mode: 0755]
scripts/antergos-mirrorlist [new file with mode: 0644]
scripts/conky-start.desktop [new file with mode: 0644]
scripts/deepin-fix.service [new file with mode: 0755]
scripts/deepin-fix.sh [new file with mode: 0755]
scripts/flatpak.desktop [new file with mode: 0644]
scripts/flatpak.sh [new file with mode: 0755]
scripts/mate-panel.desktop [new file with mode: 0644]
scripts/obmenu-gen.desktop [new file with mode: 0644]
scripts/openbox-config.sh [new file with mode: 0755]
scripts/pkcon.sh [new file with mode: 0755]
scripts/pkcon2.sh [new file with mode: 0755]
scripts/plymouth-reborn.desktop [new file with mode: 0644]
scripts/plymouth.sh [new file with mode: 0755]
scripts/reflector-antergos [new file with mode: 0755]
scripts/tint2-start.desktop [new file with mode: 0644]
scripts/update.desktop [new file with mode: 0644]
set_password [new file with mode: 0644]
syslinux/archiso.cfg [new file with mode: 0644]
syslinux/archiso_head.cfg [new file with mode: 0644]
syslinux/archiso_pxe.cfg [new file with mode: 0644]
syslinux/archiso_sys.cfg [new file with mode: 0644]
syslinux/archiso_tail.cfg [new file with mode: 0644]
syslinux/splash(copy).png [new file with mode: 0644]
syslinux/splash.png [new file with mode: 0644]
syslinux/syslinux.cfg [new file with mode: 0644]
test-cnchi.sh [new file with mode: 0755]
translations.sh [new file with mode: 0755]

diff --git a/CHANGELOG b/CHANGELOG
new file mode 100644 (file)
index 0000000..91c33f1
--- /dev/null
+++ b/CHANGELOG
@@ -0,0 +1,52 @@
+python-parted and python-pyparted compiled and required by the installer.
+
+
+Important module for cnchi and not existing in the Arch Linux repository:
+
+python-pyparted
+
+For prevent module not found when "import parted", is compiled and present in RebornOS repo.
+
+
+
+Gnome extensions used (are installed from the RebornOS repository):
+
+gnome-shell-extension-appindicator  (KStatusNotifierItem/AppIndicator Support)
+gnome-shell-extension-dash-to-panel
+
+
+Visual theme used:
+
+yaru
+papirus icon theme
+
+The RebornOS live installer can be used as the original Arch Linux installation ISO
+for system recovery, as it comes with arch-install-scripts installed.
+
+===========================================================================
+
+2020.04.27
+
+Previously, cnchi was downloaded from the Antergos (read-only) Gitlab repository.
+Now, cnchi is downloaded from our repository.
+Location: https://repo.rebornos.org/RebornOS/sources/cnchi/
+
+
+===========================================================================
+
+2020.04.29
+
+Change load.conf (live efi boot) to:
+
+timeout 5
+default archiso-x86_64.conf
+
+Live image now starts correctly in EFI mode
+
+Change the Wallpaper
+
+Change to a new location/subgroup:
+
+https://gitlab.com/reborn-os-team/rebornos-cnchi/cnchi-gnome-based
+
+
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..94a9ed0
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/Cnchi/1.png b/Cnchi/1.png
new file mode 100644 (file)
index 0000000..01e71b4
Binary files /dev/null and b/Cnchi/1.png differ
diff --git a/Cnchi/1.xcf b/Cnchi/1.xcf
new file mode 100644 (file)
index 0000000..5fbc173
Binary files /dev/null and b/Cnchi/1.xcf differ
diff --git a/Cnchi/10_antergos b/Cnchi/10_antergos
new file mode 100755 (executable)
index 0000000..cd3626b
--- /dev/null
@@ -0,0 +1,324 @@
+#! /bin/sh
+set -e
+
+# grub-mkconfig helper script.
+# Copyright (C) 2006,2007,2008,2009,2010  Free Software Foundation, Inc.
+#
+# GRUB is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# GRUB 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 GRUB.  If not, see <http://www.gnu.org/licenses/>.
+
+# Disable 10_linux
+
+if [ -f /etc/grub.d/10_linux ]; then
+    chmod -x /etc/grub.d/10_linux
+fi
+
+# Disable 10_archlinux
+if [ -f /etc/grub.d/10_archlinux ]; then
+    chmod -x /etc/grub.d/10_archlinux
+fi
+
+prefix="/usr"
+exec_prefix="/usr"
+datarootdir="/usr/share"
+
+. "${datarootdir}/grub/grub-mkconfig_lib"
+
+export TEXTDOMAIN=grub
+export TEXTDOMAINDIR="${datarootdir}/locale"
+
+CLASS="--class gnu-linux --class gnu --class os"
+
+if [ "${GRUB_DISTRIBUTOR}" = "Antergos" ]; then
+  CLASS="--class arch ${CLASS}"
+fi
+
+if [ "x${GRUB_DISTRIBUTOR}" = "x" ] ; then
+  OS=Linux
+else
+  OS="RebornOS"
+  CLASS="--class $(echo ${GRUB_DISTRIBUTOR} | tr 'A-Z' 'a-z' | cut -d' ' -f1|LC_ALL=C sed 's,[^[:alnum:]_],_,g') ${CLASS}"
+fi
+
+# loop-AES arranges things so that /dev/loop/X can be our root device, but
+# the initrds that Linux uses don't like that.
+case ${GRUB_DEVICE} in
+  /dev/loop/*|/dev/loop[0-9])
+    GRUB_DEVICE=`losetup ${GRUB_DEVICE} | sed -e "s/^[^(]*(\([^)]\+\)).*/\1/"`
+  ;;
+esac
+
+if [ "x${GRUB_DEVICE_UUID}" = "x" ] || [ "x${GRUB_DISABLE_LINUX_UUID}" = "xtrue" ] \
+    || ! test -e "/dev/disk/by-uuid/${GRUB_DEVICE_UUID}" \
+    || uses_abstraction "${GRUB_DEVICE}" lvm; then
+  LINUX_ROOT_DEVICE=${GRUB_DEVICE}
+else
+  LINUX_ROOT_DEVICE=UUID=${GRUB_DEVICE_UUID}
+fi
+
+case x"$GRUB_FS" in
+    xbtrfs)
+       rootsubvol="`make_system_path_relative_to_its_root /`"
+       rootsubvol="${rootsubvol#/}"
+       if [ "x${rootsubvol}" != x ]; then
+           GRUB_CMDLINE_LINUX="rootflags=subvol=${rootsubvol} ${GRUB_CMDLINE_LINUX}"
+       fi;;
+    xzfs)
+       rpool=`${grub_probe} --device ${GRUB_DEVICE} --target=fs_label 2>/dev/null || true`
+       bootfs="`make_system_path_relative_to_its_root / | sed -e "s,@$,,"`"
+       LINUX_ROOT_DEVICE="ZFS=${rpool}${bootfs}"
+       ;;
+esac
+
+intel_ucode=
+if test -e "/boot/intel-ucode.img" ; then
+    gettext_printf "Found Intel Microcode image\n" >&2
+    intel_ucode="$(make_system_path_relative_to_its_root /boot/intel-ucode.img)"
+fi
+
+title_correction_code=
+
+linux_entry ()
+{
+  os="$1"
+  version="$2"
+  type="$3"
+  args="$4"
+
+  if [ "$os" == "Antergos Linux" ] || [ "$os" == "Arch Linux" ] || [ "$os" == "Manjaro Linux" ] || [ "$os" == "RebornOS" ]; then
+      is_arch=True
+  fi
+
+  if [ -z "$boot_device_id" ]; then
+      boot_device_id="$(grub_get_device_id "${GRUB_DEVICE}")"
+  fi
+  if [ x$type != xsimple ] && [ "$is_arch" != True ]; then
+      case $type in
+         recovery)
+             title="$(gettext_printf "%s, with Linux %s (recovery mode)" "${os}" "${version}")" ;;
+         fallback)
+          title="$(gettext_printf "RebornOS, with Linux %s (fallback initramfs)" "${os}" "${version}")" ;;
+         *)
+             title="$(gettext_printf "RebornOS, with %s Kernel" "${os}" "${version}")" ;;
+      esac
+      if [ x"$title" = x"$GRUB_ACTUAL_DEFAULT" ] || [ x"Previous Linux versions>$title" = x"$GRUB_ACTUAL_DEFAULT" ]; then
+         replacement_title="$(echo "Advanced options for RebornOS" | sed 's,>,>>,g')>$(echo "$title" | sed 's,>,>>,g')"
+         quoted="$(echo "$GRUB_ACTUAL_DEFAULT" | grub_quote)"
+         title_correction_code="${title_correction_code}if [ \"x\$default\" = '$quoted' ]; then default='$(echo "$replacement_title" | grub_quote)'; fi;"
+         grub_warn "$(gettext_printf "Please don't use old title \`%s' for GRUB_DEFAULT, use \`%s' (for versions before 2.00) or \`%s' (for 2.00 or later)" "$GRUB_ACTUAL_DEFAULT" "$replacement_title" "gnulinux-advanced-$boot_device_id>gnulinux-$version-$type-$boot_device_id")"
+      fi
+      echo "menuentry '$(echo "$title" | grub_quote)' ${CLASS} \$menuentry_id_option 'gnulinux-$version-$type-$boot_device_id' {" | sed "s/^/$submenu_indentation/"
+  else
+       if [ "$is_arch" ]; then
+               if [ x$type != xfallback ]; then
+               if [ "$version" == "linux" ]; then
+                title="$(gettext_printf "%s" "${os}")"
+               elif [ "$version" == "linux-lts" ]; then
+                title="$(gettext_printf "RebornOS LTS Kernel" "${os}")"
+               else
+                title="$(gettext_printf "RebornOS, with %s Kernel" "${os}" "${version}")"
+               fi
+        else
+                       if [ "$version" == "linux" ]; then
+                title="$(gettext_printf "RebornOS - Fallback" "${os}")"
+               elif [ "$version" == "linux-lts" ]; then
+                title="$(gettext_printf "RebornOS LTS Kernel - Fallback" "${os}")"
+               else
+                title="$(gettext_printf "RebornOS, with %s Kernel - Fallback" "${os}" "${version}")"
+               fi
+        fi
+
+               os="$title"
+       fi
+       echo "menuentry '$(echo "$os" | grub_quote)' ${CLASS} \$menuentry_id_option 'gnulinux-simple-$boot_device_id' {" | sed "s/^/$submenu_indentation/"
+  fi
+  if [ x$type != xrecovery ] && [ x$type != xfallback ] ; then
+      save_default_entry | grub_add_tab
+  fi
+
+  # Use ELILO's generic "efifb" when it's known to be available.
+  # FIXME: We need an interface to select vesafb in case efifb can't be used.
+  if [ "x$GRUB_GFXPAYLOAD_LINUX" = x ]; then
+      echo "   load_video" | sed "s/^/$submenu_indentation/"
+      if grep -qx "CONFIG_FB_EFI=y" "${config}" 2> /dev/null \
+         && grep -qx "CONFIG_VT_HW_CONSOLE_BINDING=y" "${config}" 2> /dev/null; then
+         echo "        set gfxpayload=keep" | sed "s/^/$submenu_indentation/"
+      fi
+  else
+      if [ "x$GRUB_GFXPAYLOAD_LINUX" != xtext ]; then
+         echo "        load_video" | sed "s/^/$submenu_indentation/"
+      fi
+      echo "   set gfxpayload=$GRUB_GFXPAYLOAD_LINUX" | sed "s/^/$submenu_indentation/"
+  fi
+
+  echo "       insmod gzio" | sed "s/^/$submenu_indentation/"
+
+  if [ x$dirname = x/ ]; then
+    if [ -z "${prepare_root_cache}" ]; then
+      prepare_root_cache="$(prepare_grub_to_access_device ${GRUB_DEVICE} | grub_add_tab)"
+    fi
+    printf '%s\n' "${prepare_root_cache}" | sed "s/^/$submenu_indentation/"
+  else
+    if [ -z "${prepare_boot_cache}" ]; then
+      prepare_boot_cache="$(prepare_grub_to_access_device ${GRUB_DEVICE_BOOT} | grub_add_tab)"
+    fi
+    printf '%s\n' "${prepare_boot_cache}" | sed "s/^/$submenu_indentation/"
+  fi
+  message="$(gettext_printf "Loading  %s kernel ..." ${version})"
+  sed "s/^/$submenu_indentation/" << EOF
+       echo    '$(echo "$message" | grub_quote)'
+       linux   ${rel_dirname}/${basename} root=${linux_root_device_thisversion} rw ${args}
+EOF
+       if test -n "${initrd}" ; then
+               if [ "$is_arch" ] && [ x$type = xfallback ] ; then
+                       initrd="initramfs-${version}-fallback.img"
+               fi
+    # TRANSLATORS: ramdisk isn't identifier. Should be translated.
+       message="$(gettext_printf "Loading initial ramdisk ...")"
+       sed "s/^/$submenu_indentation/" << EOF
+       echo    '$(echo "$message" | grub_quote)'
+       initrd  ${intel_ucode} ${rel_dirname}/${initrd}
+EOF
+       fi
+       sed "s/^/$submenu_indentation/" << EOF
+}
+EOF
+}
+
+machine=`uname -m`
+case "x$machine" in
+    xi?86 | xx86_64)
+       list=`for i in /boot/vmlinuz-* /vmlinuz-* /boot/kernel-* ; do
+                  if grub_file_is_not_garbage "$i" ; then echo -n "$i " ; fi
+              done` ;;
+    *)
+       list=`for i in /boot/vmlinuz-* /boot/vmlinux-* /vmlinuz-* /vmlinux-* /boot/kernel-* ; do
+                  if grub_file_is_not_garbage "$i" ; then echo -n "$i " ; fi
+            done` ;;
+esac
+
+case "$machine" in
+    i?86) GENKERNEL_ARCH="x86" ;;
+    mips|mips64) GENKERNEL_ARCH="mips" ;;
+    mipsel|mips64el) GENKERNEL_ARCH="mipsel" ;;
+    arm*) GENKERNEL_ARCH="arm" ;;
+    *) GENKERNEL_ARCH="$machine" ;;
+esac
+
+prepare_boot_cache=
+prepare_root_cache=
+boot_device_id=
+title_correction_code=
+
+# Extra indentation to add to menu entries in a submenu. We're not in a submenu
+# yet, so it's empty. In a submenu it will be equal to '\t' (one tab).
+submenu_indentation=""
+
+is_top_level=true
+while [ "x$list" != "x" ] ; do
+  if [ $(echo $list | grep -q '[0-9]') ]; then
+    linux=`version_find_latest $list`
+  else
+    artmp=($list)
+    linux=${artmp[0]}
+  fi
+
+  gettext_printf "Found linux image: %s\n" "$linux" >&2
+  basename=`basename $linux`
+  dirname=`dirname $linux`
+  rel_dirname=`make_system_path_relative_to_its_root $dirname`
+  version=`echo $basename | sed -e "s,vmlinuz-,,g"`
+  alt_version=`echo $version | sed -e "s,\.old$,,g"`
+  linux_root_device_thisversion="${LINUX_ROOT_DEVICE}"
+
+  initrd=
+  for i in "initrd.img-${version}" "initrd-${version}.img" "initrd-${version}.gz" \
+          "initrd-${version}" "initramfs-${version}.img" \
+          "initrd.img-${alt_version}" "initrd-${alt_version}.img" \
+          "initrd-${alt_version}" "initramfs-${alt_version}.img" \
+           "initrd-linux-${version}" "initramfs-linux-${version}.img" \
+           "initrd-linux-${alt_version}" "initramfs-linux-${alt_version}.img" \
+          "initramfs-genkernel-${version}" \
+          "initramfs-genkernel-${alt_version}" \
+          "initramfs-genkernel-${GENKERNEL_ARCH}-${version}" \
+          "initramfs-genkernel-${GENKERNEL_ARCH}-${alt_version}"; do
+    if test -e "${dirname}/${i}" ; then
+      initrd="$i"
+      break
+    fi
+  done
+
+  config=
+  for i in "${dirname}/config-${version}" "${dirname}/config-${alt_version}" "/etc/kernels/kernel-config-${version}" ; do
+    if test -e "${i}" ; then
+      config="${i}"
+      break
+    fi
+  done
+
+  initramfs=
+  if test -n "${config}" ; then
+      initramfs=`grep CONFIG_INITRAMFS_SOURCE= "${config}" | cut -f2 -d= | tr -d \"`
+  fi
+
+  if test -n "${initrd}" ; then
+    gettext_printf "Found initrd image: %s\n" "${dirname}/${initrd}" >&2
+  elif test -z "${initramfs}" ; then
+    # "UUID=" and "ZFS=" magic is parsed by initrd or initramfs.  Since there's
+    # no initrd or builtin initramfs, it can't work here.
+    linux_root_device_thisversion=${GRUB_DEVICE}
+  fi
+
+  # GRUB_DISABLE_SUBMENU=y is the documented value to disable the submenu, testing for true is for backwards compatibility with Antergos
+  # see https://wiki.archlinux.org/index.php/GRUB/Tips_and_tricks#Disable_submenu
+  if [ "x$is_top_level" = xtrue ] && [ "x${GRUB_DISABLE_SUBMENU}" != xy ] && [ "x${GRUB_DISABLE_SUBMENU}" != xtrue ]; then
+    linux_entry "${OS}" "${version}" simple \
+    "${GRUB_CMDLINE_LINUX} ${GRUB_CMDLINE_LINUX_DEFAULT}"
+
+    submenu_indentation="$grub_tab"
+
+    if [ -z "$boot_device_id" ]; then
+       boot_device_id="$(grub_get_device_id "${GRUB_DEVICE}")"
+    fi
+    # TRANSLATORS: %s is replaced with an OS name
+    echo "submenu '$(gettext_printf "Advanced options for %s" "RebornOS" | grub_quote)' \$menuentry_id_option 'gnulinux-advanced-$boot_device_id' {"
+    is_top_level=false
+  fi
+
+  linux_entry "${OS}" "${version}" advanced \
+              "${GRUB_CMDLINE_LINUX} ${GRUB_CMDLINE_LINUX_DEFAULT}"
+  if test -e "${dirname}/initramfs-${version}-fallback.img" ; then
+      initrd="initramfs-${version}-fallback.img"
+
+      if test -n "${initrd}" ; then
+        gettext_printf "Found fallback initramfs image: %s\n" "${dirname}/${initrd}" >&2
+      fi
+
+      linux_entry "${OS}" "${version}" fallback \
+      "${GRUB_CMDLINE_LINUX} ${GRUB_CMDLINE_LINUX_DEFAULT}"
+  fi
+  if [ "x${GRUB_DISABLE_RECOVERY}" != "xtrue" ]; then
+    linux_entry "${OS}" "${version}" recovery \
+                "single ${GRUB_CMDLINE_LINUX}"
+  fi
+
+  list=`echo $list | tr ' ' '\n' | grep -vx $linux | tr '\n' ' '`
+done
+
+# If at least one kernel was found, then we need to
+# add a closing '}' for the submenu command.
+if [ x"$is_top_level" != xtrue ]; then
+  echo '}'
+fi
+
+echo "$title_correction_code"
diff --git a/Cnchi/2.png b/Cnchi/2.png
new file mode 100644 (file)
index 0000000..7ce1094
Binary files /dev/null and b/Cnchi/2.png differ
diff --git a/Cnchi/2.xcf b/Cnchi/2.xcf
new file mode 100644 (file)
index 0000000..64a22c8
Binary files /dev/null and b/Cnchi/2.xcf differ
diff --git a/Cnchi/20-intel.conf b/Cnchi/20-intel.conf
new file mode 100644 (file)
index 0000000..27c23c7
--- /dev/null
@@ -0,0 +1,5 @@
+Section "Device"
+   Identifier  "Intel Graphics"
+   Driver      "intel"
+   Option      "AccelMethod" "uxa"
+EndSection
diff --git a/Cnchi/3.png b/Cnchi/3.png
new file mode 100644 (file)
index 0000000..c0e510a
Binary files /dev/null and b/Cnchi/3.png differ
diff --git a/Cnchi/4-RebornMaintenance.png b/Cnchi/4-RebornMaintenance.png
new file mode 100644 (file)
index 0000000..f33ce7f
Binary files /dev/null and b/Cnchi/4-RebornMaintenance.png differ
diff --git a/Cnchi/4.png b/Cnchi/4.png
new file mode 100644 (file)
index 0000000..5ae810a
Binary files /dev/null and b/Cnchi/4.png differ
diff --git a/Cnchi/4.xcf b/Cnchi/4.xcf
new file mode 100644 (file)
index 0000000..302f500
Binary files /dev/null and b/Cnchi/4.xcf differ
diff --git a/Cnchi/Border.png b/Cnchi/Border.png
new file mode 100644 (file)
index 0000000..8d6e86f
Binary files /dev/null and b/Cnchi/Border.png differ
diff --git a/Cnchi/adwaita-day.jpg b/Cnchi/adwaita-day.jpg
new file mode 100644 (file)
index 0000000..6d17793
Binary files /dev/null and b/Cnchi/adwaita-day.jpg differ
diff --git a/Cnchi/antergos-icon.png b/Cnchi/antergos-icon.png
new file mode 100755 (executable)
index 0000000..181725b
Binary files /dev/null and b/Cnchi/antergos-icon.png differ
diff --git a/Cnchi/antergos-install.desktop b/Cnchi/antergos-install.desktop
new file mode 100644 (file)
index 0000000..344da88
--- /dev/null
@@ -0,0 +1,15 @@
+[Desktop Entry]
+Comment=Install the operating system to disk
+Comment[pl]=Zainstaluj system RebornOS
+Comment[sk]=Nainštaluje operačný systém na disk
+Exec=sudo /usr/bin/cnchi-start.sh
+GenericName=RebornOS Installer
+Icon=/usr/share/pixmaps/reborn-icon-new.png
+Name=Reborn Installer
+Name[en_US]=RebornOS Installer
+Name[pl]=Zainstaluj RebornOS
+Name[sk]=Inštalovať RebornOS
+Terminal=True
+Type=Application
+Categories=System;
+
diff --git a/Cnchi/antergos-logo-mini2.png b/Cnchi/antergos-logo-mini2.png
new file mode 100644 (file)
index 0000000..f2bd332
Binary files /dev/null and b/Cnchi/antergos-logo-mini2.png differ
diff --git a/Cnchi/antergos-logo-mini2.xcf b/Cnchi/antergos-logo-mini2.xcf
new file mode 100644 (file)
index 0000000..0958148
Binary files /dev/null and b/Cnchi/antergos-logo-mini2.xcf differ
diff --git a/Cnchi/archlinux.svg b/Cnchi/archlinux.svg
new file mode 100644 (file)
index 0000000..31daadb
Binary files /dev/null and b/Cnchi/archlinux.svg differ
diff --git a/Cnchi/ask.py b/Cnchi/ask.py
new file mode 100644 (file)
index 0000000..e7a21e3
--- /dev/null
@@ -0,0 +1,520 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# ask.py
+#
+# Copyright © 2013-2019 RebornOS
+#
+# This file is part of Cnchi.
+#
+# Cnchi is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Cnchi 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.
+#
+# The following additional terms are in effect as per Section 7 of the license:
+#
+# The preservation of all legal notices and author attributions in
+# the material or in the Appropriate Legal Notices displayed
+# by works containing it is required.
+#
+# You should have received a copy of the GNU General Public License
+# along with Cnchi; If not, see <http://www.gnu.org/licenses/>.
+
+
+""" Asks which type of installation the user wants to perform """
+
+import os
+import logging
+import subprocess
+
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+
+import bootinfo
+from pages.gtkbasebox import GtkBaseBox
+import misc.extra as misc
+from browser_window import BrowserWindow
+
+# When testing, no _() is available
+try:
+    _("")
+except NameError as err:
+    def _(message):
+        return message
+
+
+def check_alongside_disk_layout():
+    """ Alongside can only work if user has followed the recommended
+        BIOS-Based Disk-Partition Configurations shown in
+        http://technet.microsoft.com/en-us/library/dd744364(v=ws.10).aspx """
+
+    # TODO: Add more scenarios where alongside could work
+
+    partitions = misc.get_partitions()
+    # logging.debug(partitions)
+    extended = False
+    for partition in partitions:
+        if misc.is_partition_extended(partition):
+            extended = True
+
+    if extended:
+        return False
+
+    # We just seek for sda partitions
+    partitions_sda = []
+    for partition in partitions:
+        if "sda" in partition:
+            partitions_sda.append(partition)
+
+    # There's no extended partition, so all partitions must be primary
+    if len(partitions_sda) < 4:
+        return True
+
+    return False
+
+
+def load_zfs():
+    """ Load ZFS kernel module """
+    cmd = ["modprobe", "zfs"]
+    try:
+        with misc.raised_privileges():
+            subprocess.check_output(cmd, stderr=subprocess.STDOUT)
+        logging.debug("ZFS kernel module loaded successfully.")
+    except subprocess.CalledProcessError as err:
+        error_msg = err.output.decode().rstrip()
+        logging.warning("%s", error_msg)
+        return False
+    return True
+
+
+class InstallationAsk(GtkBaseBox):
+    """ Asks user which type of installation wants to perform """
+    def __init__(self, params, prev_page="mirrors", next_page=None):
+        super().__init__(self, params, "ask", prev_page, next_page)
+
+        data_dir = self.settings.get("data")
+
+        partitioner_dir = os.path.join(
+            data_dir,
+            "images",
+            "partitioner",
+            "small")
+
+        image = self.gui.get_object("automatic_image")
+        path = os.path.join(partitioner_dir, "automatic.png")
+        image.set_from_file(path)
+
+        # image = self.gui.get_object("alongside_image")
+        # path = os.path.join(partitioner_dir, "alongside.png")
+        # image.set_from_file(path)
+
+        image = self.gui.get_object("advanced_image")
+        path = os.path.join(partitioner_dir, "advanced.png")
+        image.set_from_file(path)
+
+        self.other_oses = []
+
+        # DISABLE ALONGSIDE INSTALLATION. IT'S NOT READY YET
+        # enable_alongside = self.check_alongside()
+        enable_alongside = False
+        self.settings.set('enable_alongside', enable_alongside)
+
+        #if enable_alongside:
+        #    msg = "Cnchi will enable the 'alongside' installation mode."
+        #else:
+        #    msg = "Cnchi will NOT enable the 'alongside' installation mode."
+        #logging.debug(msg)
+
+        # By default, select automatic installation
+        self.next_page = "installation_automatic"
+        self.settings.set("partition_mode", "automatic")
+
+        self.is_zfs_available = load_zfs()
+
+        self.enable_automatic_options(True)
+
+        btn_label = _(
+            "I need help with an RebornOS / Windows(tm) dual boot setup!")
+        btn = Gtk.Button.new_with_label(btn_label)
+        btn.connect(
+            'clicked', self.alongside_wiki_button_clicked)
+        ask_box = self.gui.get_object("ask")
+        ask_box.pack_start(btn, True, False, 0)
+
+        self.browser = None
+
+    def alongside_wiki_button_clicked(self, _widget, _data=None):
+        """ Shows dual installation wiki page in a browser window  """
+        try:
+            self.browser = BrowserWindow("RebornOS Wiki - Dual Boot")
+            url = ("https://sourceforge.net/p/rebornos/wiki/Dual%20Boot%20RebornOS%20%26%20Windows%20UEFI%20%28Expanded%29%20%E2%80%93%20by%20linuxhelmet/")
+            self.browser.load_url(url)
+        except Exception as err:
+            logging.warning("Could not show RebornOS wiki: %s", err)
+
+    def check_alongside(self):
+        """ Check if alongside installation type must be enabled.
+            Alongside only works when Windows is installed on sda """
+
+        enable_alongside = False
+
+        # FIXME: Alongside does not work in UEFI systems
+        if os.path.exists("/sys/firmware/efi"):
+            msg = "The 'alongside' installation mode does not work in UEFI systems"
+            logging.debug(msg)
+            enable_alongside = False
+        else:
+            oses = bootinfo.get_os_dict()
+            self.other_oses = []
+            for key in oses:
+                # We only check the first hard disk
+                non_valid = ["unknown", "Swap",
+                             "Data or Swap", self.other_oses]
+                if "sda" in key and oses[key] not in non_valid:
+                    self.other_oses.append(oses[key])
+
+            if self.other_oses:
+                for detected_os in self.other_oses:
+                    if "windows" in detected_os.lower():
+                        logging.debug("Windows(tm) OS detected.")
+                        enable_alongside = True
+                if not enable_alongside:
+                    logging.debug("Windows(tm) OS not detected.")
+                    enable_alongside = False
+            else:
+                logging.debug("Can't detect any OS in device sda.")
+                enable_alongside = False
+
+            if not check_alongside_disk_layout():
+                msg = "Unsuported disk layout for the 'alongside' installation mode"
+                logging.debug(msg)
+                enable_alongside = False
+
+        return enable_alongside
+
+    def enable_automatic_options(self, status):
+        """ Enables or disables automatic installation options """
+        names = [
+            "encrypt_checkbutton", "encrypt_label",
+            "lvm_checkbutton", "lvm_label",
+            "home_checkbutton", "home_label"]
+
+        for name in names:
+            obj = self.gui.get_object(name)
+            obj.set_sensitive(status)
+
+        names = ["zfs_checkbutton", "zfs_label"]
+        for name in names:
+            obj = self.gui.get_object(name)
+            obj.set_sensitive(status and self.is_zfs_available)
+
+    def prepare(self, direction):
+        """ Prepares screen """
+        # Read options and set widgets accordingly
+        widgets_settings = {
+            ('use_luks', 'encrypt_checkbutton'), ('use_lvm', 'lvm_checkbutton'),
+            ('use_zfs', 'zfs_checkbutton'), ('use_home', 'home_checkbutton')}
+
+        for (setting_name, widget_id) in widgets_settings:
+            widget = self.gui.get_object(widget_id)
+            setting_value = self.settings.get(setting_name)
+            widget.set_active(setting_value)
+
+        self.translate_ui()
+        self.show_all()
+
+        if not self.settings.get('enable_alongside'):
+            self.hide_option("alongside")
+
+        self.forward_button.set_sensitive(True)
+
+    def hide_option(self, option):
+        """ Hides widgets """
+        widgets = []
+        if option == "alongside":
+            widgets = [
+                "alongside_radiobutton",
+                "alongside_description",
+                "alongside_image"]
+
+        for name in widgets:
+            widget = self.gui.get_object(name)
+            if widget is not None:
+                widget.hide()
+
+    def get_os_list_str(self):
+        """ Get string with the detected os names """
+        os_str = ""
+        len_other_oses = len(self.other_oses)
+        if len_other_oses > 0:
+            if len_other_oses > 1:
+                if len_other_oses == 2:
+                    os_str = _(" and ").join(self.other_oses)
+                else:
+                    os_str = ", ".join(self.other_oses)
+            else:
+                os_str = self.other_oses[0]
+
+        # Truncate string if it's too large
+        if len(os_str) > 40:
+            os_str = os_str[:40] + "..."
+
+        return os_str
+
+    def translate_ui(self):
+        """ Translates screen before showing it """
+        self.header.set_subtitle(_("Installation Type"))
+
+        self.forward_button.set_always_show_image(True)
+        self.forward_button.set_sensitive(True)
+
+        # description_style = '<span style="italic">{0}</span>'
+        # bold_style = '<span weight="bold">{0}</span>'
+
+        oses_str = self.get_os_list_str()
+
+        max_width_chars = 80
+
+        # Automatic Install
+        radio = self.gui.get_object("automatic_radiobutton")
+        if oses_str:
+            txt = _("Replace {0} with RebornOS").format(oses_str)
+        else:
+            txt = _("Erase disk and install RebornOS")
+        radio.set_label(txt)
+        radio.set_name('auto_radio_btn')
+
+        label = self.gui.get_object("automatic_description")
+        txt = _("Warning: This will erase ALL data on your disk.")
+        # txt = description_style.format(txt)
+        label.set_text(txt)
+        label.set_name("automatic_desc")
+        label.set_hexpand(False)
+        label.set_line_wrap(True)
+        label.set_max_width_chars(max_width_chars)
+
+        button = self.gui.get_object("encrypt_checkbutton")
+        txt = _("Encrypt this installation for increased security.")
+        button.set_label(txt)
+        button.set_name("enc_btn")
+        button.set_hexpand(False)
+        # button.set_line_wrap(True)
+        # button.set_max_width_chars(max_width_chars)
+
+        label = self.gui.get_object("encrypt_label")
+        txt = _("You will be asked to create an encryption password in the "
+                "next step.")
+        # txt = description_style.format(txt)
+        label.set_text(txt)
+        label.set_name("enc_label")
+        label.set_hexpand(False)
+        label.set_line_wrap(True)
+        label.set_max_width_chars(max_width_chars)
+
+        button = self.gui.get_object("lvm_checkbutton")
+        txt = _("Use LVM with this installation.")
+        button.set_label(txt)
+        button.set_name("lvm_btn")
+        button.set_hexpand(False)
+        # button.set_line_wrap(True)
+        # button.set_max_width_chars(max_width_chars)
+
+        label = self.gui.get_object("lvm_label")
+        txt = _("This will setup LVM and allow you to easily manage "
+                "partitions and create snapshots.")
+        # txt = description_style.format(txt)
+        label.set_text(txt)
+        label.set_name("lvm_label")
+        label.set_hexpand(False)
+        label.set_line_wrap(True)
+        label.set_max_width_chars(max_width_chars)
+
+        button = self.gui.get_object("zfs_checkbutton")
+        txt = _("Use ZFS with this installation.")
+        button.set_label(txt)
+        button.set_name("zfs_btn")
+        button.set_hexpand(False)
+        # button.set_line_wrap(True)
+        # button.set_max_width_chars(max_width_chars)
+
+        label = self.gui.get_object("zfs_label")
+        txt = _("This will setup ZFS on your drive(s).")
+        # txt = description_style.format(txt)
+        label.set_text(txt)
+        label.set_name("zfs_label")
+        label.set_hexpand(False)
+        label.set_line_wrap(True)
+        label.set_max_width_chars(max_width_chars)
+
+        button = self.gui.get_object("home_checkbutton")
+        txt = _("Set your Home in a different partition/volume")
+        button.set_label(txt)
+        button.set_name("home_btn")
+        button.set_hexpand(False)
+        # button.set_line_wrap(True)
+        # button.set_max_width_chars(max_width_chars)
+
+        label = self.gui.get_object("home_label")
+        txt = _("This will setup your /home directory in a different "
+                "partition or volume.")
+        # txt = description_style.format(txt)
+        label.set_text(txt)
+        label.set_name("home_label")
+        label.set_hexpand(False)
+        label.set_line_wrap(True)
+        label.set_max_width_chars(max_width_chars)
+
+        # Alongside Install (For now, only works with Windows)
+        # if len(oses_str) > 0:
+        #     txt = _("Install Antergos alongside {0}").format(oses_str)
+        #     radio = self.gui.get_object("alongside_radiobutton")
+        #     radio.set_label(txt)
+        #
+        #     label = self.gui.get_object("alongside_description")
+        #     txt = _("Installs Antergos without removing {0}").format(oses_str)
+        #     txt = description_style.format(txt)
+        #     label.set_markup(txt)
+        #     label.set_line_wrap(True)
+        #
+        #     intro_txt = _("This computer has {0} installed.").format(oses_str)
+        #     intro_txt = intro_txt + "\n" + _("What do you want to do?")
+        # else:
+        intro_txt = _("How would you like to proceed?")
+
+        intro_label = self.gui.get_object("introduction")
+        # intro_txt = bold_style.format(intro_txt)
+        intro_label.set_text(intro_txt)
+        intro_label.set_name("intro_label")
+        intro_label.set_hexpand(False)
+        intro_label.set_line_wrap(True)
+        intro_label.set_max_width_chars(max_width_chars)
+
+        # Advanced Install
+        radio = self.gui.get_object("advanced_radiobutton")
+        radio.set_label(
+            _("Choose exactly where RebornOS should be installed."))
+        radio.set_name("advanced_radio_btn")
+
+        label = self.gui.get_object("advanced_description")
+        txt = _("Edit partition table and choose mount points.")
+        # txt = description_style.format(txt)
+        label.set_text(txt)
+        label.set_name("adv_desc_label")
+        label.set_hexpand(False)
+        label.set_line_wrap(True)
+        label.set_max_width_chars(max_width_chars)
+
+    def store_values(self):
+        """ Store selected values """
+        check = self.gui.get_object("encrypt_checkbutton")
+        use_luks = check.get_active()
+
+        check = self.gui.get_object("lvm_checkbutton")
+        use_lvm = check.get_active()
+
+        check = self.gui.get_object("zfs_checkbutton")
+        use_zfs = check.get_active()
+
+        check = self.gui.get_object("home_checkbutton")
+        use_home = check.get_active()
+
+        self.settings.set('use_lvm', use_lvm)
+        self.settings.set('use_luks', use_luks)
+        self.settings.set('use_luks_in_root', True)
+        self.settings.set('luks_root_volume', 'cryptAntergos')
+        self.settings.set('use_zfs', use_zfs)
+        self.settings.set('use_home', use_home)
+
+        if not self.settings.get('use_zfs'):
+            if self.settings.get('use_luks'):
+                logging.info(
+                    "RebornOS installation will be encrypted using LUKS")
+            if self.settings.get('use_lvm'):
+                logging.info("RebornOS will be installed using LVM volumes")
+                if self.settings.get('use_home'):
+                    logging.info(
+                        "RebornOS will be installed using a separate /home volume.")
+            elif self.settings.get('use_home'):
+                logging.info(
+                    "RebornOS will be installed using a separate /home partition.")
+        else:
+            logging.info("RebornOS will be installed using ZFS")
+            if self.settings.get('use_luks'):
+                logging.info("RebornOS ZFS installation will be encrypted")
+            if self.settings.get('use_home'):
+                logging.info(
+                    "RebornOS will be installed using a separate /home volume.")
+
+        if self.next_page == "installation_alongside":
+            self.settings.set('partition_mode', 'alongside')
+        elif self.next_page == "installation_advanced":
+            self.settings.set('partition_mode', 'advanced')
+        elif self.next_page == "installation_automatic":
+            self.settings.set('partition_mode', 'automatic')
+        elif self.next_page == "installation_zfs":
+            self.settings.set('partition_mode', 'zfs')
+
+        # Get sure other modules will know if zfs is activated or not
+        self.settings.set("zfs", use_zfs)
+
+        return True
+
+    def get_next_page(self):
+        """ Returns which page should be the next one """
+        return self.next_page
+
+    def automatic_radiobutton_toggled(self, widget):
+        """ Automatic selected, enable all options """
+        if widget.get_active():
+            check = self.gui.get_object("zfs_checkbutton")
+            if check.get_active():
+                self.next_page = "installation_zfs"
+            else:
+                self.next_page = "installation_automatic"
+            # Enable all options
+            self.enable_automatic_options(True)
+
+    def automatic_lvm_toggled(self, widget):
+        """ Enables / disables LVM installation """
+        if widget.get_active():
+            self.next_page = "installation_automatic"
+            # Disable ZFS if using LVM
+            check = self.gui.get_object("zfs_checkbutton")
+            if check.get_active():
+                check.set_active(False)
+
+    def automatic_zfs_toggled(self, widget):
+        """ Enables / disables ZFS installation """
+        if widget.get_active():
+            self.next_page = "installation_zfs"
+            # Disable LVM if using ZFS
+            check = self.gui.get_object("lvm_checkbutton")
+            if check.get_active():
+                check.set_active(False)
+        else:
+            self.next_page = "installation_automatic"
+
+    def alongside_radiobutton_toggled(self, widget):
+        """ Alongside selected, disable all automatic options """
+        if widget.get_active():
+            self.next_page = "installation_alongside"
+            self.enable_automatic_options(False)
+
+    def advanced_radiobutton_toggled(self, widget):
+        """ Advanced selected, disable all automatic options """
+        if widget.get_active():
+            self.next_page = "installation_advanced"
+            self.enable_automatic_options(False)
+
+
+if __name__ == '__main__':
+    from test_screen import _, run
+
+    run('InstallationAsk')
diff --git a/Cnchi/bashrc b/Cnchi/bashrc
new file mode 100644 (file)
index 0000000..490e3e3
--- /dev/null
@@ -0,0 +1,30 @@
+#
+# ~/.bashrc
+#
+
+# If not running interactively, don't do anything
+[[ $- != *i* ]] && return
+
+alias ls='ls --color=auto'
+PS1='[\u@\H \W]\$ '
+
+alias pacrepo='sudo reflector -l 20 -f 10 --save /etc/pacman.d/mirrorlist'
+alias pacman='sudo pacman'
+alias journalctl='sudo journalctl'
+alias pacu='sudo pacman -Syu --noconfirm'
+alias auru='yaourt -Syua --noconfirm'
+alias systemctl='sudo systemctl'
+alias se='ls /usr/bin | grep'
+alias update-grub=' grub-mkconfig -o /boot/grub/grub.cfg'
+
+export EDITOR=nano
+export QT_STYLE_OVERRIDE=gtk
+export QT_SELECT=qt5
+
+if [[ $LANG = '' ]]; then
+       export LANG=en_US.UTF-8
+fi
+
+neofetch
+
+path() ( IFS=: ; printf '%s\n' $PATH ; )
diff --git a/Cnchi/check.py b/Cnchi/check.py
new file mode 100644 (file)
index 0000000..4b9f2b0
--- /dev/null
@@ -0,0 +1,306 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+#  check.py
+#
+#  Copyright © 2013-2019 RebornOS
+#
+#  This file is part of Cnchi.
+#
+#  Cnchi is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Cnchi 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.
+#
+#  The following additional terms are in effect as per Section 7 of the license:
+#
+#  The preservation of all legal notices and author attributions in
+#  the material or in the Appropriate Legal Notices displayed
+#  by works containing it is required.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with Cnchi; If not, see <http://www.gnu.org/licenses/>.
+
+
+""" Check screen (detects if Reborn prerequisites are meet) """
+
+import dbus
+import logging
+import os
+import subprocess
+
+from gi.repository import GLib
+
+import info
+
+import misc.extra as misc
+from misc.run_cmd import call
+from pages.gtkbasebox import GtkBaseBox
+
+import show_message as show
+
+# Constants
+NM = 'org.freedesktop.NetworkManager'
+NM_STATE_CONNECTED_GLOBAL = 70
+UPOWER = 'org.freedesktop.UPower'
+UPOWER_PATH = '/org/freedesktop/UPower'
+MIN_ROOT_SIZE = 8000000000
+
+class Check(GtkBaseBox):
+    """ Check class """
+
+    def __init__(self, params, prev_page="language", next_page="location"):
+        """ Init class ui """
+        super().__init__(self, params, "check", prev_page, next_page)
+
+        self.remove_timer = False
+
+        self.prepare_power_source = None
+        self.prepare_network_connection = None
+        self.prepare_enough_space = None
+        self.timeout_id = None
+        self.prepare_best_results = None
+        self.updated = None
+        self.packaging_issues = None
+        self.remote_version = None
+
+        self.label_space = self.gui.get_object("label_space")
+
+        if 'checks_are_optional' in params:
+            self.checks_are_optional = params['checks_are_optional']
+        else:
+            self.checks_are_optional = False
+
+    def translate_ui(self):
+        """ Translates all ui elements """
+        txt = _("System Check")
+        self.header.set_subtitle(txt)
+
+        self.updated = self.gui.get_object("updated")
+        txt = _("Cnchi is up to date")
+        self.updated.set_property("label", txt)
+
+        self.prepare_enough_space = self.gui.get_object("prepare_enough_space")
+        txt = _("has at least {0}GB available storage space. (*)")
+        txt = txt.format(MIN_ROOT_SIZE / 1000000000)
+        self.prepare_enough_space.set_property("label", txt)
+
+        txt = _("This highly depends on which desktop environment you choose, "
+                "so you might need more space.")
+        txt = "(*) <i>{0}</i>".format(txt)
+        self.label_space.set_markup(txt)
+        self.label_space.set_hexpand(False)
+        self.label_space.set_line_wrap(True)
+        self.label_space.set_max_width_chars(80)
+
+        self.prepare_power_source = self.gui.get_object("prepare_power_source")
+        txt = _("is plugged in to a power source")
+        self.prepare_power_source.set_property("label", txt)
+
+        self.prepare_network_connection = self.gui.get_object(
+            "prepare_network_connection")
+        txt = _("is connected to the Internet")
+        self.prepare_network_connection.set_property("label", txt)
+
+        self.packaging_issues = self.gui.get_object("packaging_issues")
+        txt = _(
+            "There are no temporary packaging issues that would interfere with installation.")
+        self.packaging_issues.set_property("label", txt)
+
+        self.prepare_best_results = self.gui.get_object("prepare_best_results")
+        txt = _("For best results, please ensure that this computer:")
+        txt = '<span weight="bold" size="large">{0}</span>'.format(txt)
+        self.prepare_best_results.set_markup(txt)
+        self.prepare_best_results.set_hexpand(False)
+        self.prepare_best_results.set_line_wrap(True)
+        self.prepare_best_results.set_max_width_chars(80)
+
+    def check_all(self):
+        """ Check that all requirements are meet """
+        if os.path.exists("/tmp/.cnchi_partitioning_completed"):
+            msg = "You must reboot before retrying again."
+            logging.error(msg)
+            msg = _("You must reboot before retrying again.")
+            show.fatal_error(self.main_window, msg)
+            return False
+
+        has_internet = misc.has_connection()
+        self.prepare_network_connection.set_state(has_internet)
+
+        on_power = not self.on_battery()
+        self.prepare_power_source.set_state(on_power)
+
+        space = self.has_enough_space()
+        self.prepare_enough_space.set_state(space)
+
+        packaging_issues = os.path.exists('/tmp/.packaging_issue')
+        self.packaging_issues.set_state(not packaging_issues)
+
+        if has_internet:
+            updated = self.is_updated()
+        else:
+            updated = False
+
+        self.updated.set_state(updated)
+
+        if self.checks_are_optional:
+            return True
+
+        if has_internet and space and not packaging_issues:
+            return True
+
+        return False
+
+    def on_battery(self):
+        """ Checks if we are on battery power """
+        if self.has_battery():
+            bus = dbus.SystemBus()
+            upower = bus.get_object(UPOWER, UPOWER_PATH)
+            result = misc.get_prop(upower, UPOWER_PATH, 'OnBattery')
+            if result is None:
+                # Cannot read property, something is wrong.
+                logging.warning("Cannot read %s/%s dbus property",
+                                UPOWER_PATH, 'OnBattery')
+                # We will assume we are connected to a power supply
+                result = False
+            return result
+
+        return False
+
+    def has_battery(self):
+        """ Checks if latptop is connected to a power supply """
+        # UPower doesn't seem to have an interface for this.
+        path = '/sys/class/power_supply'
+        if os.path.exists(path):
+            for folder in os.listdir(path):
+                type_path = os.path.join(path, folder, 'type')
+                if os.path.exists(type_path):
+                    with open(type_path) as power_file:
+                        if power_file.read().startswith('Battery'):
+                            self.settings.set('laptop', 'True')
+                            return True
+        return False
+
+    @staticmethod
+    def has_enough_space():
+        """ Check that we have a disk or partition with enough space """
+
+        output = call(cmd=["lsblk", "-lnb"], debug=False).split("\n")
+
+        max_size = 0
+
+        for item in output:
+            col = item.split()
+            if len(col) >= 5:
+                if col[5] == "disk" or col[5] == "part":
+                    size = int(col[3])
+                    if size > max_size:
+                        max_size = size
+
+        return max_size >= MIN_ROOT_SIZE
+
+    def is_updated(self):
+        """ Checks that we're running the latest stable cnchi version """
+        remote_version = info.CNCHI_VERSION
+        local_version = info.CNCHI_VERSION
+
+        if remote_version:
+            return self.compare_versions(remote_version, local_version)
+        else:
+            return False
+
+    def compare_versions(self, remote, local):
+        """ Compares Cnchi versions (local vs remote) and returns true
+            if local is at least as new as remote """
+        
+        remote = remote.split('.')
+        local = local.split('.')
+
+        for i, remote_val in enumerate(remote):
+            remote[i] = info.CNCHI_VERSION
+
+        for i, local_val in enumerate(local):
+            local[i] = info.CNCHI_VERSION
+
+        if remote[0] < local[0]:
+            return True
+        
+        if remote[0] > local[0]:
+            return False
+
+        if remote[1] < local[1]:
+            return True        
+        
+        if remote[1] > local[1]:
+            return False
+        
+        if remote[2] >  local[2]:
+            return False
+        
+        return True
+
+    def get_cnchi_version_in_repo(self):
+        """ Checks cnchi version in the RebornOS repository """
+        if not self.remote_version:
+            try:
+                cmd = ["pacman", "-Ss", "cnchi"]
+                line = subprocess.check_output(cmd).decode().split()
+                version = line[1]
+                if '-' in version:
+                    version = version.split('-')[0]
+                logging.debug(
+                    'Cnchi version in the repository is: CNCHI_VERSION', version)
+            except subprocess.CalledProcessError as err:
+                logging.debug(err)
+                version = None
+            return version
+        else:
+            return self.remote_version
+
+    def on_timer(self):
+        """ If all requirements are meet, enable forward button """
+        if not self.remove_timer:
+            self.forward_button.set_sensitive(self.check_all())
+        return not self.remove_timer
+
+    def store_values(self):
+        """ Continue """
+        # Remove timer
+        self.remove_timer = True
+
+        logging.info("We have Internet connection.")
+        logging.info("We're connected to a power source.")
+        logging.info("We have enough disk space.")
+
+        # Enable forward button
+        self.forward_button.set_sensitive(True)
+        return True
+
+    def prepare(self, direction):
+        """ Load screen """
+        self.translate_ui()
+        self.show_all()
+
+        self.forward_button.set_sensitive(self.check_all())
+
+        # Set timer
+        self.timeout_id = GLib.timeout_add(5000, self.on_timer)
+
+
+# When testing, no _() is available
+try:
+    _("")
+except NameError as err:
+    def _(message):
+        return message
+
+if __name__ == '__main__':
+    from test_screen import _, run
+
+    run('Check')
+
diff --git a/Cnchi/cinnamon.sh b/Cnchi/cinnamon.sh
new file mode 100644 (file)
index 0000000..4199aa6
--- /dev/null
@@ -0,0 +1,75 @@
+sudo wget https://cinnamon-spices.linuxmint.com/files/applets/slingshot@jfarthing84.zip --directory-prefix=/usr/share/cinnamon/applets/
+sudo unzip -d /usr/share/cinnamon/applets/ /usr/share/cinnamon/applets/slingshot@jfarthing84.zip ##unzip##
+
+sudo wget https://cinnamon-spices.linuxmint.com/files/extensions/transparent-panels@germanfr.zip --directory-prefix=/$HOME/.local/share/cinnamon/extensions/
+sudo unzip -d /$HOME/.local/share/cinnamon/extensions/ /$HOME/.local/share/cinnamon/extensions/transparent-panels@germanfr.zip
+
+git clone https://github.com/fsvh/plank-themes.git
+cd plank-themes
+yes | ./install.sh
+cd ..
+rm -rf plank-themes
+
+git clone https://github.com/RebornOS/elementary-theme.git
+cd elementary-theme
+sudo mv elementary.zip /usr/share/themes/
+sudo unzip -d /usr/share/themes/ /usr/share/themes/elementary.zip
+sudo rm /usr/share/themes/elementary.zip
+sudo rm /$HOME/.local/share/cinnamon/extensions/transparent-panels@germanfr/settings-schema.json
+sudo mv settings-schema.json /$HOME/.local/share/cinnamon/extensions/transparent-panels@germanfr/
+cd ..
+rm -rf elementary-theme
+
+dconf write /org/cinnamon/desktop/wm/preferences/theme "'MacOS-Sierra'"
+dconf write /org/cinnamon/theme/name "'macOS-Sierra'"
+dconf write /org/cinnamon/desktop/interface/gtk-theme "'elementary'"
+dconf write /org/cinnamon/desktop/interface/icon-theme "'elementary'" ##elementary-icon-theme##
+dconf write /org/cinnamon/desktop/interface/gtk-decoration-layout "'close:menu,maximize'"
+
+dconf write /org/cinnamon/alttab-switcher-style "'coverflow'"
+
+donf write /org/cinnamon/hotcorner-layout "['expo:true:50', 'expo:false:50', 'scale:false:0', 'desktop:false:0']"
+
+dconf write /org/cinnamon/enabled-extensions "['transparent-panels@germanfr']"
+
+dconf write /org/cinnamon/enabled-applets "['panel1:right:0:systray@cinnamon.org:0', 'panel1:center:0:calendar@cinnamon.org:12', 'panel1:left:1:slingshot@jfarthing84:14', 'panel1:right:3:user@cinnamon.org:15', 'panel1:right:1:sound@cinnamon.org:21', 'panel1:right:2:power@cinnamon.org:22']"
+
+dconf write /org/cinnamon/panel-launchers "['DEPRECATED']"  ##plank##
+
+dconf write /org/cinnamon/panels-autohide "['1:intel']"
+
+dconf write /org/cinnamon/panels-enabled "['1:0:top']"
+
+dconf write /org/cinnamon/panels-height "['1:25']"
+
+dconf write /org/cinnamon/show-media-keys-osd "'medium'"
+
+dconf write /org/cinnamon/workspace-osd-duration "400"
+
+dconf write /org/cinnamon/workspace-osd-x "50"
+
+dconf write /org/cinnamon/workspace-osd-y "50"
+
+dconf write /org/cinnamon/desktop-effects-maximize-effect "'scale'"
+
+dconf write /org/cinnamon/desktop-effects-maximize-time "200"
+
+dconf write /org/cinnamon/desktop-effects-unmaximize-effect "'scale'"
+
+dconf write /org/cinnamon/desktop-effects-unmaximize-time "200"
+
+dconf write /net/launchpad/plank/enabled-docks "['dock1']"
+
+dconf write /net/launchpad/plank/docks/dock1/dock-items "['gnome-terminal.dockitem', 'nautilus.dockitem', 'pamac-manager.dockitem', 'pragha.dockitem', 'reborn-updates.dockitem']"
+
+dconf write /net/launchpad/plank/docks/dock1/hide-mode "'intelligent'"
+
+dconf write /net/launchpad/plank/docks/dock1/current-workspace-only true
+
+dconf write /net/launchpad/plank/docks/dock1/icon-size "40"
+
+dconf write /net/launchpad/plank/docks/dock1/theme "'Gtk+'"
+
+dconf write /net/launchpad/plank/docks/dock1/zoom-enabled true
+
+dconf write /net/launchpad/plank/docks/dock1/zoom-percent "110"
diff --git a/Cnchi/cnchi-start.sh b/Cnchi/cnchi-start.sh
new file mode 100755 (executable)
index 0000000..3a51aa0
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/bash
+sudo chmod 644 /etc/pacman.conf
+sudo -E /usr/bin/python -Wall /usr/share/cnchi/src/cnchi.py -dvz --no-check --packagelist /usr/share/cnchi/data/packages.xml
diff --git a/Cnchi/cnchi.png b/Cnchi/cnchi.png
new file mode 100755 (executable)
index 0000000..181725b
Binary files /dev/null and b/Cnchi/cnchi.png differ
diff --git a/Cnchi/cnchi.py b/Cnchi/cnchi.py
new file mode 100755 (executable)
index 0000000..392245d
--- /dev/null
@@ -0,0 +1,597 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+#  cnchi.py
+#
+#  Copyright © 2013-2019 RebornOS
+#
+#  This file is part of Cnchi.
+#
+#  Cnchi is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Cnchi 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.
+#
+#  The following additional terms are in effect as per Section 7 of the license:
+#
+#  The preservation of all legal notices and author attributions in
+#  the material or in the Appropriate Legal Notices displayed
+#  by works containing it is required.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with Cnchi; If not, see <http://www.gnu.org/licenses/>.
+
+""" Main Cnchi (Antergos Installer) module """
+
+import os
+import sys
+import shutil
+import logging
+import logging.handlers
+import gettext
+import locale
+import gi
+import pwd
+
+CNCHI_PATH = "/usr/share/cnchi"
+sys.path.append(CNCHI_PATH)
+sys.path.append(os.path.join(CNCHI_PATH, "src"))
+sys.path.append(os.path.join(CNCHI_PATH, "src/download"))
+sys.path.append(os.path.join(CNCHI_PATH, "src/hardware"))
+sys.path.append(os.path.join(CNCHI_PATH, "src/installation"))
+sys.path.append(os.path.join(CNCHI_PATH, "src/misc"))
+sys.path.append(os.path.join(CNCHI_PATH, "src/pacman"))
+sys.path.append(os.path.join(CNCHI_PATH, "src/pages"))
+sys.path.append(os.path.join(CNCHI_PATH, "src/pages/dialogs"))
+sys.path.append(os.path.join(CNCHI_PATH, "src/parted3"))
+
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gio, Gtk, GObject
+
+import misc.extra as misc
+from misc.run_cmd import call
+import show_message as show
+import info
+
+from logging_utils import ContextFilter
+import logging_color
+
+#try:
+#    from bugsnag.handlers import BugsnagHandler
+#    import bugsnag
+#    BUGSNAG_ERROR = None
+#except ImportError as err:
+#    BUGSNAG_ERROR = str(err)
+
+BUGSNAG_ERROR = "Bugsnag disabled. Makes requests raise several exceptions. Need to check what is wrong"
+
+# When testing, no _() is available
+try:
+    _("")
+except NameError as err:
+    def _(message):
+        return message
+
+
+class CnchiApp(Gtk.Application):
+    """ Main Cnchi App class """
+
+    TEMP_FOLDER = '/var/tmp/cnchi'
+
+    def __init__(self, cmd_line):
+        """ Constructor. Call base class """
+        Gtk.Application.__init__(self,
+                                 application_id="com.antergos.cnchi",
+                                 flags=Gio.ApplicationFlags.FLAGS_NONE)
+        self.tmp_running = os.path.join(CnchiApp.TEMP_FOLDER, ".setup-running")
+        self.cmd_line = cmd_line
+
+    def do_activate(self):
+        """ Override the 'activate' signal of GLib.Application.
+            Shows the default first window of the application (like a new document).
+            This corresponds to the application being launched by the desktop environment. """
+        try:
+            import main_window
+        except ImportError as err:
+            msg = "Cannot create Cnchi main window: {0}".format(err)
+            logging.error(msg)
+            sys.exit(1)
+
+        # Check if we have administrative privileges
+        if os.getuid() != 0:
+            msg = _('This installer must be run with administrative privileges, '
+                    'and cannot continue without them.')
+            show.error(None, msg)
+            return
+
+        # Check if we're already running
+        if self.already_running():
+            msg = _("You cannot run two instances of this installer.\n\n"
+                    "If you are sure that the installer is not already running\n"
+                    "you can run this installer using the --force option\n"
+                    "or you can manually delete the offending file.\n\n"
+                    "Offending file: '{0}'").format(self.tmp_running)
+            show.error(None, msg)
+            return
+
+        window = main_window.MainWindow(self, self.cmd_line)
+        self.add_window(window)
+        window.show()
+
+        try:
+            with misc.raised_privileges():
+                with open(self.tmp_running, 'w') as tmp_file:
+                    txt = "Cnchi {0}\n{1}\n".format(info.CNCHI_VERSION, os.getpid())
+                    tmp_file.write(txt)
+        except PermissionError as err:
+            logging.error(err)
+            show.error(None, err)
+
+        # This is unnecessary as show_all is called in MainWindow
+        # window.show_all()
+
+        # def do_startup(self):
+        # """ Override the 'startup' signal of GLib.Application. """
+        # Gtk.Application.do_startup(self)
+
+        # Application main menu (we don't need one atm)
+        # Leaving this here for future reference
+        # menu = Gio.Menu()
+        # menu.append("About", "win.about")
+        # menu.append("Quit", "app.quit")
+        # self.set_app_menu(menu)
+
+    def already_running(self):
+        """ Check if we're already running """
+        if os.path.exists(self.tmp_running):
+            logging.debug("File %s already exists.", self.tmp_running)
+            with open(self.tmp_running) as setup:
+                lines = setup.readlines()
+            if len(lines) >= 2:
+                try:
+                    pid = int(lines[1].strip('\n'))
+                except ValueError as err:
+                    logging.debug(err)
+                    logging.debug("Cannot read PID value.")
+                    return True
+            else:
+                logging.debug("Cannot read PID value.")
+                return True
+
+            if misc.check_pid(pid):
+                logging.info("Cnchi with pid '%d' already running.", pid)
+                return True
+
+            # Cnchi with pid 'pid' is no longer running, we can safely
+            # remove the offending file and continue.
+            os.remove(self.tmp_running)
+        return False
+
+class CnchiInit():
+    """ Initializes Cnchi """
+
+    # Useful vars for gettext (translations)
+    APP_NAME = "cnchi"
+    LOCALE_DIR = "/usr/share/locale"
+
+    # At least this GTK version is needed
+    GTK_VERSION_NEEDED = "3.18.0"
+
+    LOG_FOLDER = '/var/log/cnchi'
+    TEMP_FOLDER = '/var/tmp/cnchi'
+
+    def __init__(self):
+        """ This function initialises Cnchi """
+
+        # Sets SIGTERM handler, so Cnchi can clean up before exiting
+        # signal.signal(signal.SIGTERM, sigterm_handler)
+
+        # Create Cnchi's temporary folder
+        with misc.raised_privileges():
+            os.makedirs(CnchiInit.TEMP_FOLDER, mode=0o755, exist_ok=True)
+
+        # Configures gettext to be able to translate messages, using _()
+        self.setup_gettext()
+
+        # Command line options
+        self.cmd_line = self.parse_options()
+
+        if self.cmd_line.version:
+            print(_("Cnchi (RebornOS Installer) version {0}").format(
+                info.CNCHI_VERSION))
+            sys.exit(0)
+
+        if self.cmd_line.force:
+            misc.remove_temp_files(CnchiInit.TEMP_FOLDER)
+
+        # Drop root privileges
+        misc.drop_privileges()
+
+        # Setup our logging framework
+        self.setup_logging()
+
+        # Enables needed repositories only if it's not enabled
+        self.enable_repositories()
+
+        # Check that all repositories are present in pacman.conf file
+        if not self.check_pacman_conf("/etc/pacman.conf"):
+            sys.exit(1)
+
+        # Check Cnchi is correctly installed
+        if not self.check_for_files():
+            sys.exit(1)
+
+        # Check installed GTK version
+        if not self.check_gtk_version():
+            sys.exit(1)
+
+        # Check installed pyalpm and libalpm versions
+        if not self.check_pyalpm_version():
+            sys.exit(1)
+
+        # Check ISO version where Cnchi is running from
+        if not self.check_iso_version():
+            sys.exit(1)
+
+        # Disable suspend to RAM
+        self.disable_suspend()
+
+        # Init PyObject Threads
+        self.threads_init()
+
+    @staticmethod
+    def check_pacman_conf(path):
+        """ Check that pacman.conf has the correct options """
+        """ Remove antergos repo, and add Reborn-OS repo (Rafael) """
+
+        with open(path, 'rt') as pacman:
+            lines = pacman.readlines()
+
+        repos = [
+            "[Reborn-OS]", "[core]", "[extra]", "[community]", "[multilib]"]
+
+        for line in lines:
+            line = line.strip('\n')
+            if line in repos:
+                repos.remove(line)
+        if repos:
+            for repo in repos:
+                logging.error("Repository %s not in pacman.conf file", repo)
+            return False
+
+        logging.debug("All repositories are present in pacman.conf file")
+        return True
+
+    def setup_logging(self):
+        """ Configure our logger """
+
+        with misc.raised_privileges():
+            os.makedirs(CnchiInit.LOG_FOLDER, mode=0o755, exist_ok=True)
+
+        logger = logging.getLogger()
+
+        logger.handlers = []
+
+        if self.cmd_line.debug:
+            log_level = logging.DEBUG
+        else:
+            log_level = logging.INFO
+
+        logger.setLevel(log_level)
+
+        context_filter = ContextFilter()
+        logger.addFilter(context_filter.filter)
+
+        datefmt = "%Y-%m-%d %H:%M:%S"
+
+        fmt = "%(asctime)s [%(levelname)-7s] %(message)s  (%(filename)s:%(lineno)d)"
+        formatter = logging.Formatter(fmt, datefmt)
+
+        color_fmt = (
+            "%(asctime)s [%(levelname)-18s] %(message)s  "
+            "($BOLD%(filename)s$RESET:%(lineno)d)")
+        color_formatter = logging_color.ColoredFormatter(color_fmt, datefmt)
+
+        # File logger
+        log_path = os.path.join(CnchiInit.LOG_FOLDER, 'cnchi.log')
+        try:
+            with misc.raised_privileges():
+                file_handler = logging.FileHandler(log_path, mode='w')
+            file_handler.setLevel(log_level)
+            file_handler.setFormatter(formatter)
+            logger.addHandler(file_handler)
+        except PermissionError as permission_error:
+            print("Can't open ", log_path, " : ", permission_error)
+
+        # Stdout logger
+        if self.cmd_line.verbose:
+            # Show log messages to stdout in color (color_formatter)
+            stream_handler = logging.StreamHandler()
+            stream_handler.setLevel(log_level)
+            stream_handler.setFormatter(color_formatter)
+            logger.addHandler(stream_handler)
+
+        if not BUGSNAG_ERROR:
+            # Bugsnag logger
+            bugsnag_api = context_filter.api_key
+            if bugsnag_api is not None:
+                bugsnag.configure(
+                    api_key=bugsnag_api,
+                    app_version=info.CNCHI_VERSION,
+                    project_root='/usr/share/cnchi/cnchi',
+                    release_stage=info.CNCHI_RELEASE_STAGE)
+                bugsnag_handler = BugsnagHandler(api_key=bugsnag_api)
+                bugsnag_handler.setLevel(logging.WARNING)
+                bugsnag_handler.setFormatter(formatter)
+                bugsnag_handler.addFilter(context_filter.filter)
+                bugsnag.before_notify(
+                    context_filter.bugsnag_before_notify_callback)
+                logger.addHandler(bugsnag_handler)
+                logging.info(
+                    "Sending Cnchi log messages to bugsnag server (using python-bugsnag).")
+            else:
+                logging.warning(
+                    "Cannot read the bugsnag api key, logging to bugsnag is not possible.")
+        else:
+            logging.warning(BUGSNAG_ERROR)
+
+    @staticmethod
+    def check_gtk_version():
+        """ Check GTK version """
+        # Check desired GTK Version
+        major_needed = int(CnchiInit.GTK_VERSION_NEEDED.split(".")[0])
+        minor_needed = int(CnchiInit.GTK_VERSION_NEEDED.split(".")[1])
+        micro_needed = int(CnchiInit.GTK_VERSION_NEEDED.split(".")[2])
+
+        # Check system GTK Version
+        major = Gtk.get_major_version()
+        minor = Gtk.get_minor_version()
+        micro = Gtk.get_micro_version()
+
+        # Cnchi will be called from our liveCD that already
+        # has the latest GTK version. This is here just to
+        # help testing Cnchi in our environment.
+        wrong_gtk_version = False
+        if major_needed > major:
+            wrong_gtk_version = True
+        if major_needed == major and minor_needed > minor:
+            wrong_gtk_version = True
+        if major_needed == major and minor_needed == minor and micro_needed > micro:
+            wrong_gtk_version = True
+
+        if wrong_gtk_version:
+            text = "Detected GTK version {0}.{1}.{2} but version >= {3} is needed."
+            text = text.format(major, minor, micro,
+                               CnchiInit.GTK_VERSION_NEEDED)
+            try:
+                show.error(None, text)
+            except ImportError as import_error:
+                logging.error(import_error)
+            return False
+        logging.info("Using GTK v%d.%d.%d", major, minor, micro)
+
+        return True
+
+    @staticmethod
+    def check_pyalpm_version():
+        """ Checks python alpm binding and alpm library versions """
+        try:
+            import pyalpm
+
+            txt = "Using pyalpm v{0} as interface to libalpm v{1}"
+            txt = txt.format(pyalpm.version(), pyalpm.alpmversion())
+            logging.info(txt)
+        except (NameError, ImportError) as err:
+            try:
+                show.error(None, err)
+                logging.error(err)
+            except ImportError as import_error:
+                logging.error(import_error)
+            return False
+
+        return True
+
+    def check_iso_version(self):
+        """ Hostname contains the ISO version """
+        from socket import gethostname
+        hostname = gethostname()
+        # antergos-year.month-iso
+        prefix = "ant-"
+        suffix = "-min"
+        if hostname.startswith(prefix) or hostname.endswith(suffix):
+            # We're running form the ISO, register which version.
+            if suffix in hostname:
+                version = hostname[len(prefix):-len(suffix)]
+            else:
+                version = hostname[len(prefix):]
+            logging.debug("Running from ISO version %s", version)
+            # Delete user's chromium cache (just in case)
+            cache_dir = "/home/antergos/.cache/chromium"
+            if os.path.exists(cache_dir):
+                shutil.rmtree(path=cache_dir, ignore_errors=True)
+                logging.debug("User's chromium cache deleted")
+            # If we're running from sonar iso force a11y parameter to true
+            if hostname.endswith("sonar"):
+                self.cmd_line.a11y = True
+        else:
+            logging.warning("Not running from ISO")
+        return True
+
+    @staticmethod
+    def parse_options():
+        """ argparse http://docs.python.org/3/howto/argparse.html """
+
+        import argparse
+
+        desc = _("Cnchi v{0} - RebornOS Installer").format(info.CNCHI_VERSION)
+        parser = argparse.ArgumentParser(description=desc)
+
+        parser.add_argument(
+            "-a", "--a11y", help=_("Set accessibility feature on by default"),
+            action="store_true")
+        parser.add_argument(
+            "-c", "--cache", help=_("Use pre-downloaded xz packages when possible"),
+            nargs='?')
+        parser.add_argument(
+            "-d", "--debug", help=_("Sets Cnchi log level to 'debug'"),
+            action="store_true")
+        parser.add_argument(
+            "-e", "--environment", help=_("Sets the Desktop Environment that will be installed"),
+            nargs='?')
+        parser.add_argument(
+            "-f", "--force", help=_("Runs cnchi even when another instance is running"),
+            action="store_true")
+        parser.add_argument(
+            "-n", "--no-check", help=_("Makes checks optional in check screen"),
+            action="store_true")
+        parser.add_argument(
+            "-p", "--packagelist", help=_("Install packages referenced by a local XML file"),
+            nargs='?')
+        parser.add_argument(
+            "-s", "--logserver", help=_("Log server (deprecated, always uses bugsnag)"),
+            nargs='?')
+        parser.add_argument(
+            "-t", "--no-tryit", help=_("Disables first screen's 'try it' option"),
+            action="store_true")
+        parser.add_argument(
+            "-v", "--verbose", help=_("Show logging messages to stdout"),
+            action="store_true")
+        parser.add_argument(
+            "-V", "--version", help=_("Show Cnchi version and quit"),
+            action="store_true")
+        parser.add_argument(
+            "-z", "--hidden", help=_("Show options in development (use at your own risk!)"),
+            action="store_true")
+
+        return parser.parse_args()
+
+    @staticmethod
+    def threads_init():
+        """
+        For applications that wish to use Python threads to interact with the GNOME platform,
+        GObject.threads_init() must be called prior to running or creating threads and starting
+        main loops (see notes below for PyGObject 3.10 and greater). Generally, this should be done
+        in the first stages of an applications main entry point or right after importing GObject.
+        For multi-threaded GUI applications Gdk.threads_init() must also be called prior to running
+        Gtk.main() or Gio/Gtk.Application.run().
+        """
+        minor = Gtk.get_minor_version()
+        micro = Gtk.get_micro_version()
+
+        if minor == 10 and micro < 2:
+            # Unfortunately these versions of PyGObject suffer a bug
+            # which require a workaround to get threading working properly.
+            # Workaround: Force GIL creation
+            import threading
+            threading.Thread(target=lambda: None).start()
+
+        # Since version 3.10.2, calling threads_init is no longer needed.
+        # See: https://wiki.gnome.org/PyGObject/Threading
+        if minor < 10 or (minor == 10 and micro < 2):
+            GObject.threads_init()
+            # Gdk.threads_init()
+
+    @staticmethod
+    def setup_gettext():
+        """ This allows to translate all py texts (not the glade ones) """
+
+        gettext.textdomain(CnchiInit.APP_NAME)
+        gettext.bindtextdomain(CnchiInit.APP_NAME, CnchiInit.LOCALE_DIR)
+
+        locale_code, _encoding = locale.getdefaultlocale()
+        lang = gettext.translation(
+            CnchiInit.APP_NAME, CnchiInit.LOCALE_DIR, [locale_code], None, True)
+        lang.install()
+
+
+    @staticmethod
+    def check_for_files():
+        """ Check for some necessary files. Cnchi can't run without them """
+        paths = [
+            "/usr/share/cnchi",
+            "/usr/share/cnchi/ui",
+            "/usr/share/cnchi/data",
+            "/usr/share/cnchi/data/locale"]
+
+        for path in paths:
+            if not os.path.exists(path):
+                print(_("Cnchi files not found. Please, install Cnchi using pacman"))
+                return False
+
+        return True
+
+    @staticmethod
+    def enable_repositories():
+        """ Enable needed repositories in /etc/pacman.conf (just in case) """
+        """ Remove antergos repo, and add Reborn-OS repo (Rafael) """
+
+        repositories = ['Reborn-OS', 'core', 'extra', 'community', 'multilib']
+
+        # Read pacman.conf file
+        with open("/etc/pacman.conf", 'rt') as pconf:
+            lines = pconf.readlines()
+
+        # For each repository, check if it is enabled or not
+        enabled = {}
+        for repo in repositories:
+            enabled[repo] = False
+            for line in lines:
+                if line.startswith('[' + repo + ']'):
+                    enabled[repo] = True
+                    break
+
+        # For each repository, add it's definition if it is not enabled
+        # Remove antergos repo definition, and add Reborn-OS repo definition (Rafael)
+        for repo in repositories:
+            if not enabled[repo]:
+                logging.debug("Adding %s repository to /etc/pacman.conf", repo)
+                with misc.raised_privileges():
+                    with open("/etc/pacman.conf", 'at') as pconf:
+                        pconf.write("[{}]\n".format(repo))
+                        if repo == 'Reborn-OS':
+                            pconf.write("SigLevel = Optional TrustAll\n")
+                            pconf.write("Include = /etc/pacman.d/reborn-mirrorlist\n\n")
+                        else:
+                            pconf.write("Include = /etc/pacman.d/mirrorlist\n\n")
+
+    def disable_suspend(self):
+        """ Disable gnome settings suspend to ram """
+        """ Change logging warning. Original: 'User "antergos" does not exist'  """
+        try:
+            pwd.getpwnam('antergos')
+            schema = 'org.gnome.settings-daemon.plugins.power'
+            keys = ['sleep-inactive-ac-type', 'sleep-inactive-battery-type']
+            value = 'nothing'
+            for key in keys:
+                self.gsettings_set('antergos', schema, key, value)
+        except KeyError:
+            logging.warning('User Rebornos exist')
+
+    @staticmethod
+    def gsettings_set(user, schema, key, value):
+        """ Set a gnome setting """
+        cmd = [
+            'runuser',
+            '-l', user,
+            '-c', "dbus-launch gsettings set " + schema + " " + key + " " + value]
+
+        logging.debug("Running set on gsettings: %s", ''.join(str(e) + ' ' for e in cmd))
+        with misc.raised_privileges():
+            return call(cmd)
+
+def main():
+    """ Main function. Initializes Cnchi and creates it as a GTK App """
+    # Init cnchi
+    cnchi_init = CnchiInit()
+    # Create Gtk Application
+    my_app = CnchiApp(cnchi_init.cmd_line)
+    status = my_app.run(None)
+    sys.exit(status)
+
+if __name__ == '__main__':
+    main()
diff --git a/Cnchi/desktop.py b/Cnchi/desktop.py
new file mode 100644 (file)
index 0000000..b6eabe2
--- /dev/null
@@ -0,0 +1,251 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+#  desktop.py
+#
+#  Copyright © 2013-2019 RebornOS
+#
+#  This file is part of Cnchi.
+#
+#  Cnchi is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Cnchi 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.
+#
+#  The following additional terms are in effect as per Section 7 of the license:
+#
+#  The preservation of all legal notices and author attributions in
+#  the material or in the Appropriate Legal Notices displayed
+#  by works containing it is required.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with Cnchi; If not, see <http://www.gnu.org/licenses/>.
+
+
+""" Desktop screen """
+
+import os
+import logging
+
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk, GdkPixbuf
+
+import desktop_info
+from pages.gtkbasebox import GtkBaseBox
+import misc.extra as misc
+
+CLASS_NAME = "DesktopAsk"
+
+class DesktopAsk(GtkBaseBox):
+    """ Class to show the Desktop screen """
+
+    def __init__(self, params, prev_page="keymap", next_page="features"):
+        super().__init__(self, params, "desktop", prev_page, next_page)
+
+        data_dir = self.settings.get('data')
+        self.desktops_dir = os.path.join(data_dir, "images", "desktops")
+
+        self.desktop_info = self.gui.get_object("desktop_info")
+
+        self.desktop_image = None
+        self.icon_desktop_image = None
+
+        # Set up list box
+        self.listbox = self.gui.get_object("listbox_desktop")
+        self.listbox.connect("row-selected", self.on_listbox_row_selected)
+        self.listbox.set_selection_mode(Gtk.SelectionMode.BROWSE)
+        self.listbox.set_sort_func(self.listbox_sort_by_name, None)
+
+        self.desktop_choice = 'deepin'
+
+        self.enabled_desktops = self.settings.get("desktops")
+
+        self.set_desktop_list()
+
+    def translate_ui(self, desktop, set_header=True):
+        """ Translates all ui elements """
+        label = self.gui.get_object("desktop_info")
+        txt = "<span weight='bold'>{0}</span>\n".format(
+            desktop_info.NAMES[desktop])
+        description = desktop_info.DESCRIPTIONS[desktop]
+        txt = txt + _(description)
+        label.set_markup(txt)
+
+        # This sets the desktop's image
+        path = os.path.join(self.desktops_dir, desktop + ".png")
+        if self.desktop_image is None:
+            self.desktop_image = Gtk.Image.new_from_file(path)
+            overlay = self.gui.get_object("image_overlay")
+            overlay.add(self.desktop_image)
+        else:
+            self.desktop_image.set_from_file(path)
+
+        # and this sets the icon
+        filename = "desktop-environment-" + desktop.lower() + ".svg"
+        icon_path = os.path.join(
+            desktop_info.DESKTOP_ICONS_PATH, "scalable", filename)
+        icon_exists = os.path.exists(icon_path)
+
+        if self.icon_desktop_image is None:
+            if icon_exists:
+                pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(
+                    icon_path, 48, 48)
+                self.icon_desktop_image = Gtk.Image.new_from_pixbuf(pixbuf)
+            else:
+                filename = desktop.lower() + ".png"
+                icon_path = os.path.join(
+                    desktop_info.DESKTOP_ICONS_PATH, "48x48", filename)
+                icon_exists = os.path.exists(icon_path)
+                if icon_exists:
+                    self.icon_desktop_image = Gtk.Image.new_from_file(
+                        icon_path)
+                else:
+                    self.icon_desktop_image = Gtk.Image.new_from_icon_name(
+                        "image-missing",
+                        Gtk.IconSize.DIALOG)
+
+            overlay = self.gui.get_object("image_overlay")
+            overlay.add_overlay(self.icon_desktop_image)
+        else:
+            if icon_exists:
+                pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(
+                    icon_path, 48, 48)
+                self.icon_desktop_image.set_from_pixbuf(pixbuf)
+            else:
+                filename = desktop.lower() + ".png"
+                icon_path = os.path.join(
+                    desktop_info.DESKTOP_ICONS_PATH, "48x48", filename)
+                icon_exists = os.path.exists(icon_path)
+                if icon_exists:
+                    self.icon_desktop_image.set_from_file(icon_path)
+                else:
+                    self.icon_desktop_image.set_from_icon_name(
+                        "image-missing", Gtk.IconSize.DIALOG)
+
+        if set_header:
+            # set header text
+            txt = _("Choose Your Desktop")
+            self.header.set_subtitle(txt)
+
+    def prepare(self, direction):
+        """ Prepare screen """
+        self.translate_ui(self.desktop_choice)
+        self.show_all()
+
+    def set_desktop_list(self):
+        """ Set desktop list in the ListBox """
+        for desktop in sorted(desktop_info.NAMES):
+            if desktop in self.enabled_desktops:
+                box = Gtk.HBox()
+
+                filename = "desktop-environment-" + desktop.lower() + ".svg"
+                icon_path = os.path.join(
+                    desktop_info.DESKTOP_ICONS_PATH, "scalable", filename)
+                if os.path.exists(icon_path):
+                    pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(
+                        icon_path, 24, 24)
+                    image = Gtk.Image.new_from_pixbuf(pixbuf)
+                else:
+                    filename = desktop.lower() + ".png"
+                    icon_path = os.path.join(
+                        desktop_info.DESKTOP_ICONS_PATH, "24x24", filename)
+                    if os.path.exists(icon_path):
+                        image = Gtk.Image.new_from_file(icon_path)
+                    else:
+                        image = Gtk.Image.new_from_icon_name(
+                            "image-missing",
+                            Gtk.IconSize.LARGE_TOOLBAR)
+                box.pack_start(image, False, False, 2)
+
+                label = Gtk.Label()
+                label.set_markup(desktop_info.NAMES[desktop])
+                box.pack_start(label, False, False, 2)
+
+                self.listbox.add(box)
+
+        # Set Gnome as default
+        self.select_default_row(desktop_info.NAMES["gnome"])
+
+    @staticmethod
+    def listbox_sort_by_name(row1, row2, _user_data):
+        """ Sort function for listbox
+            Returns : < 0 if row1 should be before row2, 0 if they are equal and > 0 otherwise
+            WARNING: IF LAYOUT IS CHANGED IN fill_listbox THEN THIS SHOULD BE
+            CHANGED ACCORDINGLY. """
+        box1 = row1.get_child()
+        label1 = box1.get_children()[1]
+
+        box2 = row2.get_child()
+        label2 = box2.get_children()[1]
+
+        text = [label1.get_text(), label2.get_text()]
+        # sorted_text = misc.sort_list(text, self.settings.get("locale"))
+        sorted_text = misc.sort_list(text)
+
+        # If strings are already well sorted return < 0
+        if text[0] == sorted_text[0]:
+            return -1
+
+        # Strings must be swaped, return > 0
+        return 1
+
+    def select_default_row(self, desktop_name):
+        """ Selects default row
+            WARNING: IF LAYOUT IS CHANGED IN desktop.ui THEN THIS SHOULD BE
+            CHANGED ACCORDINGLY. """
+        for listbox_row in self.listbox.get_children():
+            for vbox in listbox_row.get_children():
+                label = vbox.get_children()[1]
+                if desktop_name == label.get_text():
+                    self.listbox.select_row(listbox_row)
+                    return
+
+    def set_desktop(self, desktop):
+        """ Show desktop info """
+        for key in desktop_info.NAMES:
+            if desktop_info.NAMES[key] == desktop:
+                self.desktop_choice = key
+                self.translate_ui(self.desktop_choice, set_header=False)
+                return
+
+    def on_listbox_row_selected(self, _listbox, listbox_row):
+        """ Someone selected a different row of the listbox
+            WARNING: IF LAYOUT IS CHANGED IN desktop.ui THEN THIS SHOULD BE
+            CHANGED ACCORDINGLY. """
+        if listbox_row is not None:
+            for vbox in listbox_row:
+                label = vbox.get_children()[1]
+                desktop = label.get_text()
+                self.set_desktop(desktop)
+
+    def store_values(self):
+        """ Store desktop """
+        self.settings.set('desktop', self.desktop_choice.lower())
+        logging.info(
+            "Cnchi will install RebornOS with the '%s' desktop",
+            self.desktop_choice.lower())
+        return True
+
+    @staticmethod
+    def scroll_to_cell(treeview, path):
+        """ Scrolls treeview to show the desired cell """
+        treeview.scroll_to_cell(path)
+        return False
+
+
+# When testing, no _() is available
+try:
+    _("")
+except NameError as err:
+    def _(message):
+        return message
+
+if __name__ == '__main__':
+    from test_screen import _, run
+    run('DesktopAsk')
diff --git a/Cnchi/desktop_info.py b/Cnchi/desktop_info.py
new file mode 100644 (file)
index 0000000..54f7055
--- /dev/null
@@ -0,0 +1,195 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+#  desktop_info.py
+#
+#  Copyright © 2013-2019 RebornOS
+#
+#  This file is part of Cnchi.
+#
+#  Cnchi is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Cnchi 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.
+#
+#  The following additional terms are in effect as per Section 7 of the license:
+#
+#  The preservation of all legal notices and author attributions in
+#  the material or in the Appropriate Legal Notices displayed
+#  by works containing it is required.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with Cnchi; If not, see <http://www.gnu.org/licenses/>.
+
+
+""" Desktop Environments information """
+
+# Enabled desktops
+
+DESKTOPS = ["base", "cinnamon", "deepin",
+            "gnome", "kde", "mate", "openbox", "xfce"]
+
+DESKTOPS_DEV = DESKTOPS + ["budgie", "enlightenment", "i3", "lxqt", "pantheon", "windows", "apricity"]
+
+DESKTOPS_A11Y = ["gnome", "mate", "apricity"]
+
+DESKTOP_ICONS_PATH = "/usr/share/cnchi/data/icons"
+
+'''
+MENU - Size appropriate for menus (16px).
+SMALL_TOOLBAR - Size appropriate for small toolbars (16px).
+LARGE_TOOLBAR - Size appropriate for large toolbars (24px)
+BUTTON - Size appropriate for buttons (16px)
+DND - Size appropriate for drag and drop (32px)
+DIALOG - Size appropriate for dialogs (48px)
+'''
+
+# Descriptive names
+NAMES = {
+    'apricity': "Original Apricity Experience",
+    'base': "Base",
+    'cinnamon': "Cinnamon",
+    'deepin': "Deepin",
+    'pantheon': "Pantheon",
+   'windows':"Windows Interface",
+    'gnome': "GNOME",
+    'kde': "KDE",
+    'mate': "MATE",
+    'openbox': "Openbox",
+    'xfce': "Xfce",
+    'budgie': "Budgie",
+    'enlightenment': "Enlightenment",
+    'i3': "i3",
+    'lxqt': "LXQt",
+}
+
+LIBS = {
+    'gtk': ["apricity", "cinnamon", "deepin", "pantheon", "gnome", "mate", "openbox", "xfce", "budgie", "enlightenment", "i3", "windows"],
+    'qt': ["kde", "lxqt"]
+}
+
+ALL_FEATURES = ["a11y", "aur", "bluetooth", "broadcom", "maintenance", "cups", "chromium", "email", "dropbox", "firefox", "firefox-developer-edition", "google-chrome", "firewire", "opera", "hardinfo", "hunspell", "vivaldi", "games", "graphics", "gtk-play", "hardinfo", "qt-play", "movie", "mycroft", "graphic_drivers", "lamp", "lts", "freeoffice", "wps-office", "libreoffice", "redshift", "power", "sshd", "spotify", "visual", "vlc", "nautilus", "nemo", "nixnote", "wallpapers", "wine"]
+
+# Not all desktops have all features
+EXCLUDED_FEATURES = {
+    'base': ["bluetooth", "chromium", "maintenance", "dropbox", "email", "firefox", "firefox-developer-edition", "google-chrome", "firewire", "opera", "vivaldi", "games", "graphic_drivers", "graphics", "hardinfo", "hunspell", "freeoffice", "wps-office", "libreoffice", "visual", "vlc", "nautilus", "nemo", "nixnote", "qt-play", "movie", "mycroft", "gtk-play", "qt-play", "power", "redshift", "spotify", "wallpapers", "wps-office", "libreoffice", "freeoffice"],
+    'apricity': ["lamp", "visual", "nautilus", "qt-play"],
+    'cinnamon': ["lamp", "visual", "nemo", "qt-play"],
+    'deepin': ["lamp", "visual", "qt-play"],
+    'pantheon': ["lamp", "visual", "qt-play", "nemo"],
+   'windows': ["lamp", "visual", "qt-play", "nemo"],
+    'gnome': ["lamp", "visual", "nautilus", "qt-play"],
+    'kde': ["lamp", "visual", "gtk-play"],
+    'mate': ["lamp", "visual", "qt-play"],
+    'openbox': ["lamp", "qt-play"],
+    'xfce': ["lamp", "visual", "qt-play"],
+    'budgie': ["lamp", "visual", "qt-play"],
+    'enlightenment': ["lamp", "visual", "qt-play"],
+    'i3': ["lamp", "qt-play"],
+    'lxqt': ["lamp", "visual", "gtk-play"]
+}
+
+# Session names for lightDM setup (/usr/share/xsessions)
+SESSIONS = {
+    'apricity' : 'gnome',
+    'cinnamon': 'cinnamon',
+    'deepin': 'deepin',
+    'pantheon': 'pantheon',
+    'gnome': 'gnome',
+    'kde': 'plasma',
+    'mate': 'mate',
+    'openbox': 'openbox',
+    'xfce': 'xfce',
+    'budgie': 'budgie-desktop',
+    'enlightenment': 'enlightenment',
+    'i3': 'i3',
+    'lxqt': 'lxsession',
+   'windows': 'windows'
+}
+
+
+# See http://docs.python.org/2/library/gettext.html "22.1.3.4. Deferred translations"
+def _(message):
+    return message
+
+
+DESCRIPTIONS = {
+    'base': _("This option will install RebornOS as command-line only system, "
+              "without any type of graphical interface. After the installation you can "
+              "customize RebornOS by installing packages with the command-line package manager."),
+   'apricity': _("Apricity OS is a now discontinued Linux distro in the Arch Linux family that simply  "
+                        "offered a highly customized GNOME dekstop experience that combined beauty with "
+                        "funcionality. With this option, the original Apricity look and feel is finally revivied! Experience "
+                         "it now on RebornOS."),
+    'cinnamon': _("Cinnamon is a Linux desktop which provides advanced, "
+                  "innovative features and a traditional desktop user experience. "
+                  "Cinnamon aims to make users feel at home by providing them with "
+                  "an easy-to-use and comfortable desktop experience."),
+    'deepin': _("Deepin desktop is a lightweight, elegant desktop environment that "
+                "has been commented as a mix between Windows and Macs by many of its' "
+                "users. It was originally created for the Linux Deepin distribution. "
+                "Now, DDE will support most Linux operating systems such as Arch "
+                "Linux, Ubuntu, Fedora, openSUSE etc."),
+    'pantheon': _("Pantheon is the desktop environment that Elementary OS runs on. "
+                  "While true Pantheon is too unstable for RebornOS to offer, we "
+                  "have tried to offer the next best thing. By selecting Pantheon, "
+                  "you will get Elementary OS's good looks on a highly customized desktop "
+                  "running Cinnamon underneath. Through this, you will be able to run "
+                  "the majority of Elementary OS's applications and experience their "
+                  "stunning style and theming - all in a stable system."), 
+    'gnome': _("GNOME 3 is an easy and elegant way to use your "
+               "computer. It features the Activities Overview which "
+               "is an easy way to access all your basic tasks."),
+
+    'kde': _("If you are looking for a familiar working environment, KDE's "
+             "Plasma Desktop offers all the tools required for a modern desktop "
+             "computing experience so you can be productive right from the start."),
+
+    'mate': _("MATE is an intuitive, attractive, and lightweight desktop "
+              "environment which provides a more traditional desktop "
+              "experience. Accelerated compositing is supported, but not "
+              "required to run MATE making it suitable for lower-end hardware."),
+
+    'openbox': _("Not actually a desktop environment, Openbox is a highly "
+                 "configurable window manager. It is known for its "
+                 "minimalistic appearance and its flexibility. It is the most "
+                 "lightweight graphical option offered by RebornOS. Please "
+                 "Note: Openbox is not recommended for users who are new to Linux."),
+
+    'xfce': _("Xfce is a lightweight desktop environment. It aims to "
+              "be fast and low on system resources, while remaining visually "
+              "appealing and user friendly. It suitable for use on older "
+              "computers and those with lower-end hardware specifications. "),
+
+    'budgie': _("Budgie is the flagship desktop of Solus and is a Solus project. "
+                "It focuses on simplicity and elegance. Written from scratch with "
+                "integration in mind, the Budgie desktop tightly integrates with "
+                "the GNOME stack, but offers an alternative desktop experience."),
+
+    'enlightenment': _("Enlightenment is not just a window manager for Linux/X11 "
+                       "and others, but also a whole suite of libraries to help "
+                       "you create beautiful user interfaces with much less work"),
+
+    'i3': _("i3 is a tiling window manager primarily targeted at advanced "
+            "users and developers."),
+
+    'lxqt': _("LXQt is the next-generation of LXDE, the Lightweight Desktop "
+              "Environment. It is lightweight, modular, blazing-fast, and "
+              "user-friendly."),
+   
+   'windows': _("While I am sure you have all heard of Windows, this option "
+                           "does NOT truly offer you straight up Windows. This is Linux after all, " 
+                           "not Microsoft. However, what this option DOES allow you to experience, "
+                           "is a Windows-like desktop running Cinnamon underneath, made to look "
+                           "and act like the Windows 10 you are already familiar with. Made with Linux "
+                          "newbies specifically in mind, this option should hopefully ensure you have "
+                          "an easy, hassle free transition to Linux.")
+}
+
+# Delete previous _() dummy declaration
+del _
diff --git a/Cnchi/encfs.py b/Cnchi/encfs.py
new file mode 100644 (file)
index 0000000..ce836f5
--- /dev/null
@@ -0,0 +1,161 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+#  encfs.py
+#
+#  Copyright © 2013-2019 RebornOS
+#
+#  This file is part of Cnchi.
+#
+#  Cnchi is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Cnchi 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.
+#
+#  The following additional terms are in effect as per Section 7 of the license:
+#
+#  The preservation of all legal notices and author attributions in
+#  the material or in the Appropriate Legal Notices displayed
+#  by works containing it is required.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with Cnchi; If not, see <http://www.gnu.org/licenses/>.
+
+
+""" Configures Antergos to encrypt user's home with encFS """
+
+import os
+import shutil
+import subprocess
+import logging
+
+import misc.extra as misc
+
+# TODO: This is unfinished and untested
+
+
+@misc.raise_privileges
+def backup_conf_files(dest_dir):
+    """ Copy encfs setup files """
+    conf_files = [
+        "etc/security/pam_encfs.conf",
+        "etc/security/pam_env.conf",
+        "etc/fuse.conf",
+        "etc/pam.d/system-login",
+        "etc/pam.d/system-auth"]
+    for conf_file in conf_files:
+        path = os.path.join(dest_dir, conf_file)
+        if os.path.exists(path):
+            shutil.copy(path, path + ".cnchi")
+    os.system("sync")
+
+
+@misc.raise_privileges
+def setup_conf_files(dest_dir):
+    """ Setup encfs """
+    path = os.path.join(dest_dir, "etc/security/pam_encfs.conf")
+    with open(path, 'w') as pam_encfs:
+        pam_encfs.write("# File created by Cnchi (RebornOS Installer)\n\n")
+        pam_encfs.write(
+            "# If this is specified program will attempt to drop permissions "
+            "before running encfs.\n")
+        pam_encfs.write("drop_permissions\n\n")
+        pam_encfs.write(
+            "# This specifies which options to pass to encfs for every user.\n")
+        pam_encfs.write(
+            "# You can find encfs options by running encfs without any arguments\n")
+        pam_encfs.write("encfs_default --idle=1\n\n")
+        pam_encfs.write("# Same for fuse\n")
+        pam_encfs.write("# you can find fuse options with encfs -H\n")
+        pam_encfs.write("fuse_default allow_root,nonempty\n\n")
+        pam_encfs.write("# Added by Cnchi - RebornOS Installer\n")
+        # USERNAME SOURCE TARGET_PATH ENCFS_Options FUSE_Options
+        pam_encfs.write("-\t/home/.encfs\t-\t-v\t-\n")
+
+    path = os.path.join(dest_dir, "etc/security/pam_env.conf")
+    with open(path, 'a') as pam_env:
+        pam_env.write("\n# Added by Cnchi - RebornOS Installer\n")
+        pam_env.write(
+            "# Set the ICEAUTHORITY file location to allow GNOME to start on encfs $HOME\n")
+        pam_env.write("ICEAUTHORITY DEFAULT=/tmp/.ICEauthority_@{PAM_USER}\n")
+
+    path = os.path.join(dest_dir, "etc/fuse.conf")
+    with open(path, 'a') as fuse_conf:
+        fuse_conf.write("\n# Added by Cnchi - RebornOS Installer\n")
+        fuse_conf.write("user_allow_other\n")
+
+    path = os.path.join(dest_dir, "etc/pam.d/system-login")
+    with open(path, 'a') as system_login:
+        system_login.write("\n# Added by Cnchi - RebornOS Installer\n")
+        system_login.write("session required\tpam_encfs.so\n")
+        system_login.write("session optional\tpam_mount.so\n")
+
+    path = os.path.join(dest_dir, "etc/pam.d/system-auth")
+    with open(path, "a") as system_auth:
+        system_auth.write("\n# Added by Cnchi - RebornOS Installer\n")
+        system_auth.write("auth sufficient\tpam_encfs.so\n")
+        system_auth.write("auth optional\tpam_mount.so\n")
+
+
+@misc.raise_privileges
+def setup(username, dest_dir, password):
+    """ Encrypt user's home folder """
+    # encfs and pam_mount packages are needed
+    # and pam_encfs from AUR, too.
+    # Reference: https://wiki.debian.org/TransparentEncryptionForHomeFolder
+
+    try:
+        backup_conf_files(dest_dir)
+        setup_conf_files(dest_dir)
+    except Exception as ex:
+        logging.error("Can't create and modify encfs configuration files.")
+        template = "An exception of type {0} occured. Arguments:\n{1!r}"
+        message = template.format(type(ex).__name__, ex.args)
+        logging.error(message)
+        logging.error("Home directory won't be encrypted.")
+        return False
+
+    # Move user home dir out of the way
+    mount_dir = os.path.join(dest_dir, "home/", username)
+    backup_dir = os.path.join(dest_dir, "var/tmp/", username)
+    shutil.move(mount_dir, backup_dir)
+
+    # Create necessary dirs, encrypted and mounted(unencrypted)
+    encrypted_dir = os.path.join(dest_dir, "home/.encfs/", username)
+    os.makedirs(encrypted_dir, mode=0o755)
+    os.makedirs(mount_dir, mode=0o755)
+
+    # Set owner
+    shutil.chown(encrypted_dir, username, "users")
+    shutil.chown(mount_dir, username, "users")
+
+    # Create encrypted directory
+    try:
+        cmd = ["/bin/echo", "-e", "p\n{0}\n".format(password)]
+        passw = subprocess.Popen(cmd, stdout=subprocess.PIPE)
+        cmd = ['encfs', '-S', encrypted_dir, mount_dir, "--public"]
+        encfs = subprocess.Popen(
+            cmd, stdin=passw.stdout, stdout=subprocess.PIPE)
+        encfs.communicate()
+        if encfs.poll() != 0:
+            logging.error("Can't run encfs. Bad password?")
+    except subprocess.CalledProcessError as err:
+        logging.error("Error running %s: %s", err.cmd, err.output)
+
+    # Restore user home files
+    for name in os.listdir(backup_dir):
+        shutil.move(os.path.join(backup_dir, name),
+                    os.path.join(mount_dir, name))
+
+    # Delete home backup
+    os.rmdir(backup_dir)
+
+
+if __name__ == '__main__':
+    setup("test", "/", "1234")
+
diff --git a/Cnchi/features.py b/Cnchi/features.py
new file mode 100644 (file)
index 0000000..f9c1059
--- /dev/null
@@ -0,0 +1,512 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+#  features.py
+#
+#  Copyright © 2013-2019 RebornOS
+#
+#  This file is part of Cnchi.
+#
+#  Cnchi is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Cnchi 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.
+#
+#  The following additional terms are in effect as per Section 7 of the license:
+#
+#  The preservation of all legal notices and author attributions in
+#  the material or in the Appropriate Legal Notices displayed
+#  by works containing it is required.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with Cnchi; If not, see <http://www.gnu.org/licenses/>.
+
+
+""" Features screen """
+import subprocess
+import logging
+
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+
+import desktop_info
+import features_info
+
+import misc.extra as misc
+
+from pages.gtkbasebox import GtkBaseBox
+
+from lembrame.dialog import LembrameDialog
+
+from hardware.modules.nvidia import Nvidia
+from hardware.modules.nvidia_390xx import Nvidia390xx
+from hardware.modules.nvidia_340xx import Nvidia340xx
+from hardware.modules.nvidia_304xx import Nvidia304xx
+from hardware.modules.catalyst import Catalyst
+from hardware.modules.amdgpu import AMDGpu
+from hardware.modules.amdgpu_exp import AMDGpuExp
+from hardware.modules.i915 import Intel915
+
+class Graphics():
+    """ Gets graphic device info using the hardware module """
+    """ Removed from nvidia detection: 340xx and 304xx. See below: """
+    """ Nvidia340xx().detect() or Nvidia304xx().detect() """
+
+    @staticmethod
+    def nvidia():
+        """ Returns true if an nVidia card is detected """
+        if (Nvidia().detect() or Nvidia390xx().detect()):
+            return True
+        return False
+
+    @staticmethod
+    def amd():
+        """ Returns true if an AMD card is detected """
+        if (Catalyst().detect() or AMDGpu().detect() or
+            AMDGpuExp().detect()):
+            return True
+        return False
+
+    @staticmethod
+    def i915():
+        """ Returns if an Intel card is detected """
+        return Intel915().detect()
+
+    def bumblebee(self):
+        """ Returns true if an nVidia and an Intel card are detected """
+        return self.nvidia() and self.i915()
+
+
+class Features(GtkBaseBox):
+    """ Features screen class """
+
+    COL_ICON = 0
+    COL_TITLE = 1
+    COL_DESCRIPTION = 2
+    COL_SWITCH = 3
+
+    def __init__(self, params, prev_page="desktop", next_page="cache"):
+        """ Initializes features ui """
+        # This is initialized each time this screen is shown in prepare()
+        self.features = None
+
+        super().__init__(self, params, "features", prev_page, next_page)
+
+        self.graphics = Graphics()
+
+        self.listbox_rows = {}
+
+        self.a11y = params['a11y']
+
+        self.show_advanced = False
+        self.advanced_checkbutton = self.gui.get_object("advanced_checkbutton")
+        self.advanced_checkbutton.set_active(False)
+
+        # Set up list box
+        self.listbox = self.gui.get_object("listbox")
+        self.listbox.set_selection_mode(Gtk.SelectionMode.NONE)
+        self.listbox.set_sort_func(self.listbox_sort_by_name, None)
+
+        # Only show ufw rules and aur disclaimer info once
+        self.info_already_shown = {"ufw": False, "aur": False}
+
+        # Only load defaults for each DE the first time this screen is shown
+        self.defaults_loaded = False
+
+        # Store which features where active when lembrame was selected
+        # (when lembrame is selected, all other features are deactivated)
+        self.features_lembrame = []
+
+    def show_advanced_toggled(self, _widget):
+        """ Display or hide advanced features """
+        self.show_advanced = self.advanced_checkbutton.get_active()
+        self.update_advanced_features()
+
+    @staticmethod
+    def on_listbox_row_selected(_listbox, listbox_row):
+        """ Someone selected a different row of the listbox
+            WARNING: IF LIST LAYOUT IS CHANGED THEN THIS SHOULD BE CHANGED ACCORDINGLY. """
+        if listbox_row is not None:
+            for vbox in listbox_row:
+                switch = vbox.get_children()[2]
+                if switch:
+                    switch.set_active(not switch.get_active())
+
+    def add_feature_icon(self, feature, box):
+        """ Adds feature icon to listbox row box """
+        if feature in features_info.ICON_NAMES:
+            icon_name = features_info.ICON_NAMES[feature]
+        else:
+            logging.debug("No icon found for feature %s", feature)
+            icon_name = "missing"
+
+        image = Gtk.Image.new_from_icon_name(icon_name, Gtk.IconSize.DND)
+        object_name = "image_" + feature
+        image.set_name(object_name)
+        image.set_property('margin_start', 10)
+        self.listbox_rows[feature].append(image)
+        box.pack_start(image, False, False, 0)
+
+    def add_feature_label(self, feature, box):
+        """ Adds feature title and label to listbox row box """
+        text_box = Gtk.VBox()
+
+        object_name = "label_title_" + feature
+        label_title = Gtk.Label.new()
+        label_title.set_halign(Gtk.Align.START)
+        label_title.set_justify(Gtk.Justification.LEFT)
+        label_title.set_name(object_name)
+        self.listbox_rows[feature].append(label_title)
+        text_box.pack_start(label_title, False, True, 0)
+
+        object_name = "label_" + feature
+        label = Gtk.Label.new()
+        label.set_halign(Gtk.Align.START)
+        label.set_justify(Gtk.Justification.LEFT)
+        label.set_name(object_name)
+        self.listbox_rows[feature].append(label)
+        text_box.pack_start(label, False, False, 0)
+        box.pack_start(text_box, False, False, 0)
+
+    def on_switch_activated(self, switch, _gparam):
+        """ Feature has been activated or deactivated """
+        for feature in self.features:
+            row = self.listbox_rows[feature]
+            if row[Features.COL_SWITCH] == switch:
+                is_active = switch.get_active()
+                self.settings.set("feature_" + feature, is_active)
+                # Extra actions on this switch trigger
+                self.extra_switch_actions(feature, is_active)
+
+    def extra_switch_actions(self, feature, is_active):
+        """ Lembrame feature disables all others, any other disables lembrame """
+        if is_active:
+            if feature == 'lembrame':
+                # Disable all switches if Lembrame is selected
+                logging.debug("Activating Lembrame. Deactivating the rest of the switches")
+                self.features_lembrame = []
+                for row_feature in self.listbox_rows:
+                    if row_feature != 'lembrame':
+                        switch = self.listbox_rows[row_feature][Features.COL_SWITCH]
+                        if switch.get_active():
+                            self.features_lembrame.append(row_feature)
+                            switch.set_active(False)
+            else:
+                # Disable lembrame if any other option is activated
+                self.features_lembrame = []
+                try:
+                    switch = self.listbox_rows['lembrame'][Features.COL_SWITCH]
+                    if switch and switch.get_active():
+                        msg = "Activating something else besides Lembrame. Deactivating Lembrame."
+                        logging.debug(msg)
+                        switch.set_active(False)
+                except KeyError as err:
+                    pass
+        elif feature == 'lembrame':
+            # Activate previous deactivated features
+            for row_feature in self.features_lembrame:
+                switch = self.listbox_rows[row_feature][Features.COL_SWITCH]
+                switch.set_active(True)
+            self.features_lembrame = []
+
+    def add_feature_switch(self, feature, box):
+        """ Add a switch so the user can activate/deactivate the feature """
+        object_name = "switch_" + feature
+        switch = Gtk.Switch.new()
+        switch.set_name(object_name)
+        switch.set_property('margin_top', 10)
+        switch.set_property('margin_bottom', 10)
+        switch.set_property('margin_end', 10)
+        switch.connect("notify::active", self.on_switch_activated)
+        self.listbox_rows[feature].append(switch)
+        box.pack_end(switch, False, False, 0)
+
+    def fill_listbox(self):
+        """ Fills listbox with all the features and switches """
+        for listbox_row in self.listbox.get_children():
+            listbox_row.destroy()
+
+        self.listbox_rows = {}
+
+        # Only add graphic-driver feature if an AMD or Nvidia is detected
+        if "graphic_drivers" in self.features:
+            allow = False
+            if self.graphics.amd():
+                allow = True
+            if self.graphics.nvidia() and not self.graphics.bumblebee():
+                allow = True
+            if not allow:
+                logging.debug("Neither AMD nor Nvidia cards have been detected. "
+                              "Removing proprietary graphic drivers feature.")
+                self.features.remove("graphic_drivers")
+
+        for feature in self.features:
+            box = Gtk.Box(spacing=20)
+            box.set_name(feature + "-row")
+
+            self.listbox_rows[feature] = []
+
+            self.add_feature_icon(feature, box)
+            self.add_feature_label(feature, box)
+            self.add_feature_switch(feature, box)
+            # Add row to our gtklist
+            self.listbox.add(box)
+
+        self.listbox.show_all()
+
+    def update_advanced_features(self):
+        """ Shows or hides advanced features """
+        try:
+            if self.features:
+                for feature in self.features:
+                    row = self.listbox_rows[feature]
+                    box = row[Features.COL_ICON].get_parent()
+                    listboxrow = box.get_parent()
+                    if feature in features_info.ADVANCED and not self.show_advanced:
+                        listboxrow.hide()
+                    else:
+                        listboxrow.show()
+        except AttributeError as msg:
+            logging.debug(msg)
+
+    @staticmethod
+    def listbox_sort_by_name(row1, row2, _user_data):
+        """ Sort function for listbox
+            Returns : < 0 if row1 should be before row2, 0 if they are equal and > 0 otherwise
+            WARNING: IF LAYOUT IS CHANGED IN fill_listbox THEN THIS SHOULD BE
+            CHANGED ACCORDINGLY. """
+        box1 = row1.get_child()
+        txt_box1 = box1.get_children()[1]
+        label1 = txt_box1.get_children()[0]
+
+        box2 = row2.get_child()
+        txt_box2 = box2.get_children()[1]
+        label2 = txt_box2.get_children()[0]
+
+        text = [label1.get_text(), label2.get_text()]
+        # sorted_text = misc.sort_list(text, self.settings.get("locale"))
+        sorted_text = misc.sort_list(text)
+
+        # If strings are already well sorted return < 0
+        if text[0] == sorted_text[0]:
+            return -1
+
+        # Strings must be swaped, return > 0
+        return 1
+
+    def set_row_text(self, feature, title, desc, tooltip):
+        """ Set translated text to our listbox feature row """
+        if feature in self.listbox_rows:
+            title = "<span weight='bold' size='large'>{0}</span>".format(title)
+            desc = "<span size='small'>{0}</span>".format(desc)
+            row = self.listbox_rows[feature]
+            row[Features.COL_TITLE].set_markup(title)
+            row[Features.COL_DESCRIPTION].set_markup(desc)
+            for widget in row:
+                widget.set_tooltip_markup(tooltip)
+
+    def translate_ui(self):
+        """ Translates all ui elements """
+
+        self.advanced_checkbutton.set_label(_("Show advanced features"))
+
+        desktop = self.settings.get('desktop')
+        txt = desktop_info.NAMES[desktop] + " - " + _("Feature Selection")
+        self.header.set_subtitle(txt)
+
+        for feature in self.features:
+            title = _(features_info.TITLES[feature])
+            desc = _(features_info.DESCRIPTIONS[feature])
+            tooltip = _(features_info.TOOLTIPS[feature])
+            self.set_row_text(feature, title, desc, tooltip)
+
+        # Sort listbox items
+        self.listbox.invalidate_sort()
+
+    def switch_defaults_on(self):
+        """ Enable some features by default """
+
+        if 'bluetooth' in self.features:
+            try:
+                process1 = subprocess.Popen(["/usr/bin/lsusb"], stdout=subprocess.PIPE)
+                process2 = subprocess.Popen(
+                    ["grep", "-i", "bluetooth"],
+                    stdin=process1.stdout,
+                    stdout=subprocess.PIPE)
+                process1.stdout.close()
+                out, _process_error = process2.communicate()
+                if out.decode():
+                    row = self.listbox_rows['bluetooth']
+                    row[Features.COL_SWITCH].set_active(True)
+            except subprocess.CalledProcessError as err:
+                logging.warning(
+                    "Error checking bluetooth presence. Command %s failed: %s",
+                    err.cmd,
+                    err.output)
+
+        if 'cups' in self.features:
+            row = self.listbox_rows['cups']
+            row[Features.COL_SWITCH].set_active(True)
+
+        if 'visual' in self.features:
+            row = self.listbox_rows['visual']
+            row[Features.COL_SWITCH].set_active(True)
+
+        if 'chromium' in self.features:
+            row = self.listbox_rows['firefox']
+            row[Features.COL_SWITCH].set_active(True)
+
+        if 'aur' in self.features:
+            row = self.listbox_rows['aur']
+            row[Features.COL_SWITCH].set_active(True)
+
+        if 'lts' in self.features:
+            row = self.listbox_rows['lts']
+            row[Features.COL_SWITCH].set_active(True)
+
+        if 'a11y' in self.features and self.a11y:
+            row = self.listbox_rows['a11y']
+            row[Features.COL_SWITCH].set_active(True)
+
+    def show_disclaimer_messages(self):
+        """ Show ufw and AUR warning messages if necessary """
+        # Show ufw info message if ufw is selected (show it only once)
+        if self.settings.get("feature_firewall") and not self.info_already_shown["ufw"]:
+            self.show_info_dialog("ufw")
+            self.info_already_shown["ufw"] = True
+
+        # Show AUR disclaimer if AUR is selected (show it only once)
+        if self.settings.get("feature_aur") and not self.info_already_shown["aur"]:
+            self.show_info_dialog("aur")
+            self.info_already_shown["aur"] = True
+
+    def show_info_dialog(self, feature):
+        """ Some features show an information dialog when this screen is accepted """
+        if feature == "aur":
+            # Aur disclaimer
+            txt1 = _("Arch User Repository - Disclaimer")
+            txt2 = _("The Arch User Repository is a collection of user-submitted PKGBUILDs\n"
+                     "that supplement software available from the official repositories.\n\n"
+                     "The AUR is community driven and NOT supported by Arch or RebornOS.\n")
+        elif feature == "ufw":
+            # Ufw rules info
+            txt1 = _("Uncomplicated Firewall will be installed with these rules:")
+            toallow = misc.get_network()
+            txt2 = _("ufw default deny\nufw allow from {0}\nufw allow Transmission\n"
+                     "ufw allow SSH").format(toallow)
+        else:
+            # No message
+            return
+
+        txt1 = "<big>{0}</big>".format(txt1)
+        txt2 = "<i>{0}</i>".format(txt2)
+
+        info = Gtk.MessageDialog(
+            transient_for=self.get_main_window(),
+            modal=True,
+            destroy_with_parent=True,
+            message_type=Gtk.MessageType.INFO,
+            buttons=Gtk.ButtonsType.CLOSE)
+        info.set_markup(txt1)
+        info.format_secondary_markup(txt2)
+        info.run()
+        info.destroy()
+
+    def ask_nginx(self):
+        """ LAMP: Ask user if he wants Apache or Nginx """
+        if self.settings.get("feature_lamp"):
+            info = Gtk.MessageDialog(
+                transient_for=self.get_main_window(),
+                modal=True,
+                destroy_with_parent=True,
+                message_type=Gtk.MessageType.INFO,
+                buttons=Gtk.ButtonsType.YES_NO)
+            info.set_markup("LAMP / LEMP")
+            msg = _(
+                "Do you want to install the Nginx server instead of the Apache server?")
+            info.format_secondary_markup(msg)
+            response = info.run()
+            info.destroy()
+            if response == Gtk.ResponseType.YES:
+                self.settings.set("feature_lemp", True)
+            else:
+                self.settings.set("feature_lemp", False)
+
+    def ask_lembrame(self):
+        """ Asks user for lembrame credentials """
+        if self.settings.get("feature_lembrame"):
+            dlg = LembrameDialog(
+                self.get_main_window(),
+                self.gui_dir)
+
+            response = dlg.run()
+
+            if response == Gtk.ResponseType.APPLY:
+                logging.debug("Saving Lembrame credentials")
+                self.settings.set(
+                    'lembrame_credentials',
+                    dlg.get_credentials())
+
+            dlg.destroy()
+
+    def store_switches(self):
+        """ Store current feature selections """
+        for feature in self.features:
+            row = self.listbox_rows[feature]
+            is_active = row[Features.COL_SWITCH].get_active()
+            self.settings.set("feature_" + feature, is_active)
+            if is_active:
+                logging.debug("Feature '%s' has been selected", feature)
+
+    def store_values(self):
+        """ Go to next screen, but first save changes """
+
+        self.store_switches()
+        self.show_disclaimer_messages()
+        self.ask_nginx()
+        self.ask_lembrame()
+
+        self.listbox_rows = {}
+
+        return True
+
+    def prepare(self, direction):
+        """ Prepare features screen to get ready to show itself """
+        # Each desktop has its own features
+        desktop = self.settings.get('desktop')
+        self.features = list(
+            set(desktop_info.ALL_FEATURES) -
+            set(desktop_info.EXCLUDED_FEATURES[desktop]))
+        self.fill_listbox()
+        self.translate_ui()
+        self.show_all()
+        if not self.defaults_loaded:
+            self.switch_defaults_on()
+            # Only load defaults once
+            self.defaults_loaded = True
+        else:
+            # Load values user has chosen when this screen is shown again
+            self.load_values()
+        self.update_advanced_features()
+
+    def load_values(self):
+        """ Get previous selected switches values """
+        for feature in self.features:
+            row = self.listbox_rows[feature]
+            is_active = self.settings.get("feature_" + feature)
+            if row[Features.COL_SWITCH] is not None and is_active is not None:
+                row[Features.COL_SWITCH].set_active(is_active)
+
+
+# When testing, no _() is available
+try:
+    _("")
+except NameError as err:
+    def _(message):
+        return message
diff --git a/Cnchi/features_info.py b/Cnchi/features_info.py
new file mode 100755 (executable)
index 0000000..13e904d
--- /dev/null
@@ -0,0 +1,359 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+#  features_info.py
+#
+#  Copyright © 2013-2019 RebornOS
+#
+#  This file is part of Cnchi.
+#
+#  Cnchi is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Cnchi 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.
+#
+#  The following additional terms are in effect as per Section 7 of the license:
+#
+#  The preservation of all legal notices and author attributions in
+#  the material or in the Appropriate Legal Notices displayed
+#  by works containing it is required.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with Cnchi; If not, see <http://www.gnu.org/licenses/>.
+
+
+""" Features information """
+
+# Note: As each desktop has its own features, these are listed
+# in desktop_info file instead of here.
+
+
+ICON_NAMES = {
+    'a11y': 'preferences-desktop-accessibility',
+    'aur': 'system-software-install',
+    'bluetooth': 'bluetooth',
+    'broadcom':'cs-drivers',
+    'cups': 'printer',
+    'chromium': 'chromium',
+    'dropbox': 'dropbox',
+    'email': 'thunderbird',
+    'firefox': 'firefox',
+    'firefox-developer-edition': 'firefox-developer-edition',
+    'google-chrome': 'google-chrome',
+#    'firewall': 'network-server',
+    'fonts': 'preferences-desktop-font',
+    'firewire': 'drive-harddisk-ieee1394',
+    'games': 'applications-games',
+#    'graphics': 'apps.com.pixlr',
+    'graphics': 'accessories-painting',
+    'gtk-play': 'applications-games',
+    'hardinfo': 'hardinfo',
+    'qt-play': 'applications-games',
+    'maintenance': 'baobab',
+    'movie': 'artemanufrij.screencast',
+    'mycroft': 'deepin-voice-recorder',
+    'graphic_drivers': 'gnome-system',
+    'lamp': 'applications-internet',
+    'lts': 'applications-accessories',
+    'nemo': 'system-file-manager',
+    'nautilus': 'system-file-manager',
+    'nixnote': 'evernote',
+    'wps-office': 'wps-office-wpt',
+    'libreoffice': 'libreoffice-writer',
+    'freeoffice': 'libreoffice-writer',
+    'power': 'battery-full-charged',
+#    'plymouth': 'debian-plymouth-manager',
+    'redshift': 'redshift',
+    'sshd': 'gnome-mime-x-directory-smb-share',
+    'spotify': 'spotify-client',
+#    'skype': 'skype',
+    'visual': 'video-display',
+    'vivaldi': 'vivaldi',
+    'vlc': 'vlc',
+    'wallpapers': 'background',
+    'wine': 'wine',
+    'opera': 'opera'}
+
+# These features are considered 'advanced' so it won't be shown by default
+ADVANCED = [ 'lamp', 'sshd', 'visual', 'firewire', 'broadcom', 'nautilus', 'nemo', 'email', 'wallpapers', 'hunspell' ]
+
+# These features are considered 'basic', and will be shown by default
+BASIC = ['opera', 'wine', 'wallpapers', 'vlc', 'vivaldi', 'visual', 'spotify', 'redshift', 'power', 'libreoffice', 'wps-office', 'freeoffice', 'nixnote', 'lts', 'graphic_drivers', 'mycroft', 'movie', 'maintenance', 'qt-play', 'hardinfo', 'gtk-play', 'graphics', 'games', 'fonts', 'firefox', 'firefox-developer-edition', 'google-chrome', 'email', 'dropbox', 'chromium', 'cups', 'bluetooth', 'aur', 'a11y']
+
+# See http://docs.python.org/2/library/gettext.html "22.1.3.4. Deferred translations"
+def _(message):
+    return message
+
+TITLES = {
+    'a11y': _("Adds accessibility packages"),
+    'aur': _("Arch User Repository (AUR) Support"),
+    'bluetooth': _("Bluetooth Support"),
+    'broadcom': _("Broadcom Driver Support"),
+    'cups': _("Printing Support"),
+    'chromium': _("Chromium Web Browser"),
+    'dropbox': _("Dropbox"),
+    'email': _("Desktop Email Client"),
+    'firefox': _("Firefox Web Browser"),
+    'firefox-developer-edition': _("Firefox Developer Edition"),
+    'google-chrome': _("Google Chrome"),
+    'opera': _("Opera Web Browser"),
+    'vivaldi': _("Vivaldi Web Browser"),
+#    'firewall': _("Uncomplicated Firewall"),
+    'fonts': _("Extra Truetype Fonts"),
+    'firewire': _("Support For Firewire Devices"),
+    'games': _("Steam + PlayonLinux"),
+    'graphic_drivers': _("Graphic drivers (Proprietary)"),
+    'gtk-play': _("Popular Games for Linux"),
+    'qt-play': _("Popular Games for Linux"),
+    'hardinfo': _("Hardware Analysis"),
+    'maintenance': _("Applications to Perform System Maintenance"),
+    'movie': _("Common Video Editing Programs for Linux"),
+    'mycroft': _("Mycroft"),
+    'graphics': _("Common Photo editing Programs for Linux"),
+    'hunspell': _("Spell Check"),
+    'lamp': _("Apache (or Nginx) + Mariadb + PHP"),
+    'lts': _("Kernel (LTS version)"),
+    'libreoffice': _("LibreOffice"),
+    'wps-office': _("WPS Office"),
+    'freeoffice': _("FreeOffice"),
+    'power': _("Power Saving"),
+#    'plymouth': _("Boot Screen"),
+    'redshift': _("Redshift"),
+    'sshd': _("Windows sharing SMB"),
+#    'skype': _("Skype"),
+    'spotify': _("Spotify"),
+    'visual': _("Visual Effects"),
+    'vlc': _("VLC"),
+    'wallpapers': _("Wallpapers Cycler"),
+    'wine': _("Run Windows Programs on Linux"),
+    'nemo': _("Nemo File Manager"),
+    'nixnote': _("Evernote For Linux"),
+    'nautilus': _("Nautilus File Manager")}
+
+DESCRIPTIONS = {
+    'a11y': _("Useful packages for individuals who are blind or visually impaired."),
+    'aur': _("The AUR is a community-driven repository for Arch users."),
+    'bluetooth': _("Enables your system to make wireless connections via Bluetooth."),
+    'broadcom': _("Enables your system to effectively use a Broadcom driver"),
+    'chromium': _("Open-source web browser from Google."),
+    'email': _("Installs Thunderbird as your Desktop Email Client"),
+    'dropbox': _("Free file hosting service for Linux (installed from external source to avoid copywrite issues)"),
+    'firefox': _("A popular open-source graphical web browser from Mozilla."),
+    'firefox-developer-edition': _("Firefox for Developers"),
+    'google-chrome': _("Chrome is a free Internet browser officially released by Google"),
+    'opera':_("Opera is an innovative, minimalistic web browser from Opera.Inc"),
+    'vivaldi': _("Vivaldi is a free, fast web browser designed for power-users."),
+    'fonts': _("TrueType fonts from the Google Fonts project."),
+    'firewire': _("Linux Support For Firewire Devices"),
+    'games': _("Installs Steam and Playonlinux for gaming enthusiasts."),
+    'graphic_drivers': _("Installs AMD or Nvidia proprietary graphic driver."),
+    'gtk-play': _("Popular games for Linux, all created for use on your Desktop Environment"),
+    'hunspell': _("Slightly Broken (atm) Spell Check Packages for RebornOS"),
+    'hardinfo': _("Easy application for extensive hardware analysis"),
+    'qt-play': _("Popular games for Linux, all created for use on your Desktop Environment"),
+    'maintenance': _("Common Applications to Perform System Maintenance On Linux"),
+    'movie': _("Common video editing programs for Linux"),
+    'mycroft': _("An Open Source alternative to AIs such as Amazon's Alexa and Apple's Siri"),
+    'graphics': _("Common Photo editing Programs for Linux"),
+    'lamp': _("Apache (or Nginx) + Mariadb + PHP installation and setup."),
+    'cups': _("Installation of printer drivers and management tools."),
+    'wps-office': _("Office Suit for Linux, made for those used to MS Office"),
+    'libreoffice': _("Open source office suite. Supports editing MS Office files."),
+    'freeoffice': _("FreeOffice is a full-featured Office suite."),
+    'visual': _("Enable transparency, shadows, and other desktop effects."),
+    'vlc': _("Ultimate Media Player For Linux"),
+#    'firewall': _("Control the incoming and outgoing network traffic."),
+    'lts': _("Long term support (LTS) Linux kernel and modules."),
+    'power': _("Power Saving Tools Geared Specifically for Laptops"),
+#    'plymouth': _("Uses Plymouth To Offer You a Polished Boot Screen"),
+    'redshift': _("Color Temperature Adjuster Based on Local Time"),
+    'sshd': _("Provides client access to shared files and printers."),
+#   'skype': _("A User Friendly Video Chat Tool Made By Microsoft"),
+    'spotify': _("A widely popular music, podcast, and video streaming service"),
+    'nemo': _("Default file manager for the Cinnamon desktop."),
+    'wallpapers': _("Wallpapers Cycler That Changes Wallpapers Every Day"),
+    'wine': _("Run Common Windows Programs on Linux Easily"),
+    'nixnote': _("An Opensource implementation of Evernote for Linux"),
+    'nautilus': _("Default file manager for the Gnome desktop.")}
+
+TOOLTIPS = {
+    'a11y': _("Useful packages for individuals who are blind or visually impaired."),
+    'aur': _("Use yaourt to install AUR packages.\n"
+             "The AUR was created to organize and share new packages\n"
+             "from the community and to help expedite popular packages'\n"
+             "inclusion into the [community] repository."),
+    'bluetooth': _("Bluetooth is a standard for the short-range wireless\n"
+                   "interconnection of cellular phones, computers, and\n"
+                   "other electronic devices. In Linux, the canonical\n"
+                   "implementation of the Bluetooth protocol stack is BlueZ."),
+    'broadcom': _("NOTE: IF YOU ARE UNSURE EXACTLY OF WHAT NEMO IS, IT IS ADVISED TO\n"
+                               "NOT ENABLE THIS FEATURE.\n"
+                              "However, if you are sure that you are using a braodcom driver on your system,\n"
+                              "then it is advised to enable this option as it installs several broadcom dependencies."),
+    'cups': _("CUPS is the standards-based, open source printing\n"
+              "system developed by Apple Inc. for OS® X and other\n"
+              "UNIX®-like operating systems."),
+    'chromium': _("Chromium is an open-source browser project that aims to build a\n"
+                  "safer, faster, and more stable way for all users to experience the web.\n"
+                  "(this is the default)"),
+    'email': _("Thunderbird is one of the most common and stable desktop email clients\n"
+               "for Linux around. It is is a free, open source, cross-platform email, news,\n"
+               "RSS, and chat client developed by the Mozilla Foundation for you."),
+    'dropbox': _("Dropbox is a free file hosting and synchronization service for Linux\n"
+                 "that integrates fully into your file manager - all for free (installed from \n"
+                 "external source to avoid copywrite issues)"),
+    'firefox': _("Mozilla Firefox (known simply as Firefox) is a free and\n"
+                 "open-source web browser developed for Windows, OS X, and Linux,\n"
+                 "with a mobile version for Android, by the Mozilla Foundation and\n"
+                 "its subsidiary, the Mozilla Corporation. Firefox uses the Gecko\n"
+                 "layout engine to render web pages, which implements current and\n"
+                 "anticipated web standards.  Enable this option to install Firefox\n"
+                 "instead of Chromium"),
+    'firefox-developer-edition': _("The Firefox Developer Edition is a modified version of Firefox\n"
+                                   "that is specifically designed for web developers. It also uses\n"
+                                   "a separate profile from the regular version so that running\n"
+                                   "them side-by-side is an option."),
+    'google-chrome': _("Chrome is a free Internet browser officially released by Google\n"
+                       "on December 11, 2008. Its features include synchronization with\n"
+                       "Google services and accounts, tabbed browsing, and automatic\n"
+                       "translation and spell check of web pages. It also features an\n"
+                       "integrated address bar/search bar, called the omnibox."),
+    'opera': _("Opera is a freeware, cross-platform web browser developed by\n"
+              "Opera.Inc. The browser is aimed at conventional internet users\n"
+              "and those who enjoy simplicity"),
+    'vivaldi': _("Vivaldi is a freeware, cross-platform web browser developed by\n"
+                 "Vivaldi Technologies. It was officially launched on April 12, 2016.\n"
+                 "The browser is aimed at staunch technologists, heavy Internet users,\n"
+                 "and previous Opera web browser users disgruntled by Opera's transition\n"
+                 "from the Presto layout engine to the Blink layout engine, which\n"
+                 "removed many popular features."),
+#    'firewall': _("Ufw stands for Uncomplicated Firewall, and is a program for\n"
+#                  "managing a netfilter firewall. It provides a command line\n"
+#                  "interface and aims to be uncomplicated and easy to use."),
+    'fonts': _("Fonts: adobe-source-code-pro, adobe-source-sans-pro, jsmath, lohit\n"
+               "oldstand, openarch, otf-bitter, otf-goudy, andika, anonymous-pro\n"
+               "cantarell, cardo, chromeos-fonts, comfortaa, droid, google-fonts\n"
+               "google-webfonts, inconsolata, kimberly_geswein_print, lekton\n"
+               "medievalsharp, nova, oldstandard, opensans, oxygen, pt-mono\n"
+               "pt-sans, roboto, sil-fonts, sortsmillgoudy, source-code-pro\n"
+               "source-sans-pro, ubuntu-font-family, vollkorn, fira-mono\n"
+               "fira-sans and lato."),
+    'firewire': _("NOTE: IF YOU ARE UNSURE EXACTLY OF WHAT FIREWIRE IS, IT IS ADVISED TO\n"
+                  "NOT ENABLE THIS FEATURE.\n"
+                  "That said, firewire is an alternative introduced by Apple to USB devices\n"
+                  "that offers a much faster data exchange rate. It is often used in cameras\n"
+                  "and other small devices."),
+    'games': _("Steam is one of the most popular gaming clients that supports\n"
+               "linux in technology and gaming, while PlayOnLinux\n"
+               "is a very easy manager to setting up games to play\n"
+               "through wine, instead of doing it manually."),
+    'graphic_drivers': _("Installs AMD or Nvidia proprietary graphics driver instead\n"
+                         "of the open-source variant. Do NOT install this if you have a\n"
+                         "Nvidia Optimus laptop"),
+    'gtk-play': _("Popular games for Linux, ranging from complex games like 0 A.D,\n"
+                  "Battle for Wesnoth, and Super Tux to basics like Solitaire,\n"
+                  "Mines, and Soduku - all tailored for a gtk environemt"),
+    'hardinfo': _("Simple application for hardware analysis and system benchmarking.\n"
+                  "Through this, you can easily view all of your system specs without\n"
+                  "having to revert to the commandline."),
+    'hunspell': _("Blah blah blah... honestly not too much to say for this one. Did I mention it's a spell checker?"),
+    'qt-play': _("Popular games for Linux, ranging from complex games like 0 A.D,\n"
+                 "Battle for Wesnoth, and Super Tux to basics like Solitaire,\n"
+                 "Mines, and Soduku - all tailored for a qt environemt"),
+    'maintenance': _("This option install some common applications used for\n"
+                     "system maintenance in Linux.\n"
+                     "Specificaly, this option installs Bleachbit, Stacer, Timeshift, and RebornOS Recovery.\n"
+                     "BleachBit is a free and open-source disk space cleaner, privacy manager,\n"
+                     "and computer system optimizer. Whereas Bleachbit and Stacer both primarily clean your system,\n"
+                     "Timeshift and RebornOS Recovery are preventative solutions to sudden losses\n"
+                     "of data. Timeshift enables you to easily backup your data locally\n"
+                     "while RebornOS Recovery allows you to save a list of all your installed\n"
+                     "programs in a file so that you can reinstall them later."),
+    'movie': _("Common video editing programs for Linux, such as Open Shot, KdenLive,\n"
+               "Pitivi, and Avidemux"),
+    'mycroft': _("Open Source alternative to AIs such as Amazon's Alexa and Apple's Siri. Just like other AIs,\n"
+                        "Mycroft is voice activated and can perform numerous tasks. However, unlike other IAs,\n"
+                        "Mycroft does NOT collect any information on its users - keeping your privacy intact"),
+    'graphics': _("Common Photo editing Programs for Linux, such as Gimp, GtThumb,\n"
+                  "Rapid Photo Downloader, Rawtherapee, and DarkTable"),
+    'lamp': _("This option installs a web server (you can choose\n"
+              "Apache or Nginx) plus a database server (Mariadb)\n"
+              "and PHP."),
+    'lts': _("The linux-lts package is an alternative Arch kernel package.\n"
+             "This particular kernel version enjoys long-term support from upstream,\n"
+             "including security fixes and some feature backports. Additionally, this\n"
+             "package includes ext4 support. For RebornOS users seeking a long-term\n"
+             "support kernel, or who want a fallback kernel in case the latest kernel\n"
+             "version causes problems, this option is the answer."),
+    'freeoffice': _("FreeOffice is a full-featured Office suite with word processing,\n"
+                    "spreadsheet and presentation software. It is seamlessly compatible\n"
+                    "with Microsoft Office and available for Windows, Mac and Linux.\n"
+                    "Best of all, it's completely free for both personal and commercial use."),
+    'wps-office': _("WPS Office is the free power-packed Office Suite made to make\n"
+                      "even those most used to Microsoft Office feel at home. Looking nearly\n"
+                      "identical to Microsoft Office, this productivity suite is great for everyone"),
+    'libreoffice': _("LibreOffice is the free power-packed Open Source\n"
+                "personal productivity suite for Windows, Macintosh\n"
+                "and Linux, that gives you six feature-rich applications\n"
+                "for all your document production and data processing\n"
+                "needs: Writer, Calc, Impress, Draw, Math and Base."),
+    'power': _("Two programs are installed through this, namely TLP and Thermald.\n"
+               "TLP will automatically adjust your laptop to optimize your battery\n"
+               "performance in the background without interfering with your daily use at all,\n"
+               "and Thermald will conveniently ensure that your fans and CPU both remain\n"
+               "at acceptable levels"),
+#    'plymouth': _("Dislike commands and status reports flowing across your screen while booting up?\n"
+#                             "Want your computer to have an extra bit of eye-candy? Just enable this option,\n"
+#                             "which will configure Plymouth - the standard boot screen program for Linux - for you"),
+    'redshift': _("Redshift is an application that adjusts the computer display's color temperature\n"
+                          "based upon the time of day - with absolutely no manual intervention needed after\n"
+                          "the initial setup."),
+    'sshd': _("Most usage of SMB involves computers running Microsoft Windows.\n"
+             "Use this option to be able to browse SMB shares from your computer."),
+#    'skype': _("Skype is a user-friendly video chat tool made by Microsoft for all ages.\n"
+#               "While it's Linux support often drags behind the latest version,\n"
+#               "it is still widely popular and well-known, allowing you to converse\n"
+#               "with the ones you love"),
+    'spotify': _("Spotify is a widely popular music, podcast, and video streaming service.\n"
+                 "It offers millions of songs and sound tracks, all available for free.\n"
+                 "However, a paid subscription is required to download the songs and listen to them\n"
+                 "offline. (Installed from external source to avoid copywrite issues)"),
+    'visual': _("Compton is a lightweight, standalone composite manager,\n"
+                "suitable for use with window managers that do not natively\n"
+                "provide compositing functionality. Compton itself is a fork\n"
+                "of xcompmgr-dana, which in turn is a fork of xcompmgr.\n"
+                "See the compton github page for further information."),
+    'vlc': _("VLC is often considered the ultimate media player, no matter\n"
+             "what system you use. It is highly versitile, and can play almost any\n"
+             "media format imaginable, even damaged ones. If you ever have a problem\n"
+             "with videos or music, VLC can likely solve it."),
+    'nemo': _("NOTE: IF YOU ARE UNSURE EXACTLY OF WHAT NEMO IS, IT IS ADVISED TO\n"
+              "NOT ENABLE THIS FEATURE.\n"
+              "That said, Nemo is the default file manager for the Cinnamon desktop.\n"
+              "It is praised for it's features, but does not compare to Konquerer,\n"
+              "the KDE file manager."),
+    'nixnote': _("Nixnote, (formerly Nevernote) is an opensource client for Evernote,\n"
+                 "allowing you to track your digital life in a single, convenient place.\n"
+                 "NOTE: an Evernote account is required for cloud synchronization."),
+    'wallpapers': _("Installs a program known as Variety, an easy way to automatically\n"
+                    "change your wallpaper from thousands of high-quality, free images each day."),
+    'wine': _("Easily run several Windows programs on Linux - safely. Wine offers a simple,\n"
+              "non-technical method of installing Windows software. Simply download the desired\n"
+              ".exe file like you normally would for Windows, and then right click to run using\n"
+              "Wine. It's as easy as that.\n"
+              "In addition, this comes with PlayOnLinux and Lutris both installed for you to make even\n"
+              "the more troublesome games possible on Linux.\n"
+              "NOTE: NOT ALL PROGRAMS WILL WORK WITH WINE, ALTHOUGH THE MAJORITY DO"),
+    'nautilus': _("NOTE: IF YOU ARE UNSURE EXACTLY OF WHAT NAUTILUS IS, IT IS ADVISED TO\n"
+              "NOT ENABLE THIS FEATURE.\n"
+              "That said, Nautilus is the default file manager for the Gnome desktop.\n"
+              "It is praised for it's ease of use, and currently has a few more\n"
+              "features than Deepin's file manager.")}
+
+# Delete previous _() dummy declaration
+del _
diff --git a/Cnchi/geoip.py b/Cnchi/geoip.py
new file mode 100644 (file)
index 0000000..8fc2959
--- /dev/null
@@ -0,0 +1,148 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+#  geoip.py
+#
+# Copyright © 2013-2019 RebornOS
+#
+# This file is part of Cnchi.
+#
+# Cnchi is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Cnchi 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.
+#
+# The following additional terms are in effect as per Section 7 of the license:
+#
+# The preservation of all legal notices and author attributions in
+# the material or in the Appropriate Legal Notices displayed
+# by works containing it is required.
+#
+# You should have received a copy of the GNU General Public License
+# along with Cnchi; If not, see <http://www.gnu.org/licenses/>.
+
+""" GeoIP Location module
+    Needs python-geoip2 python-maxminddb geoip2-database """
+
+import json
+import logging
+import os
+import time
+import requests
+
+import maxminddb
+import geoip2.database
+import misc.extra as misc
+
+class GeoIP():
+    """ Store GeoIP information """
+
+    REPO_CITY_DATABASE = '/usr/share/GeoIP/GeoLite2-City.mmdb'
+    LOCAL_CITY_DATABASE = '/usr/share/cnchi/data/GeoLite2-City.mmdb'
+
+    SERVERS = ["ipapi.com", "ipgeolocation.io"]
+
+    def __init__(self):
+        self.record = None
+        self._maybe_wait_for_network()
+        self._load_data_and_ip()
+
+    @staticmethod
+    def _maybe_wait_for_network():
+        # Wait until there is an Internet connection available
+        if not misc.has_connection():
+            logging.warning(
+                "Can't get network status. Cnchi will try again in a moment")
+            while not misc.has_connection():
+                time.sleep(4)  # Wait 4 seconds and try again
+
+        logging.debug("A working network connection has been detected.")
+
+    def _load_data_and_ip(self):
+        """ Gets public IP and loads GeoIP2 database """
+        db_path = GeoIP.REPO_CITY_DATABASE
+        if not os.path.exists(db_path):
+            db_path = GeoIP.LOCAL_CITY_DATABASE
+
+        if os.path.exists(db_path):
+            myip = self._get_external_ip()
+            logging.debug("Your external IP address is: %s", myip)
+            if myip:
+                self._load_database(db_path, myip)
+                if self.record:
+                    logging.debug("GeoIP database loaded (%s)", db_path)
+            else:
+                logging.error("Cannot get your external IP address!")
+        else:
+            logging.error("Cannot find Cities GeoIP database")
+
+
+    @staticmethod
+    def _get_external_ip():
+        """ Get external IP """
+        for srv in GeoIP.SERVERS:
+            srv = "http://" + srv[::-1]
+            try:
+                json_text = requests.get(srv).text
+                if not "503 Over Quota" in json_text:
+                    data = json.loads(json_text)
+                    return data['ip']
+            except (requests.ConnectionError, json.decoder.JSONDecodeError) as err:
+                logging.warning(
+                    "Error getting external IP from %s: %s", srv, err)
+        return None
+
+    def _load_database(self, db_path, myip):
+        """ Loads cities database """
+        try:
+            reader = geoip2.database.Reader(db_path)
+            self.record = reader.city(myip)
+        except maxminddb.errors.InvalidDatabaseError as err:
+            logging.error(err)
+
+    def get_city(self):
+        """ Returns city information
+            'city': {'geoname_id', 'names'} """
+        if self.record:
+            return self.record.city
+        return None
+
+    def get_country(self):
+        """ Returns country information
+            'country': {'geoname_id', 'is_in_european_union', 'iso_code', 'names'} """
+        if self.record:
+            return self.record.country
+        return None
+
+    def get_continent(self):
+        """ Returns continent information
+            'continent': {'code', 'geoname_id', 'names'} """
+        if self.record:
+            return self.record.continent
+        return None
+
+    def get_location(self):
+        """ Returns location information
+            'location': {'accuracy_radius', 'latitude', 'longitude', 'time_zone'} """
+        if self.record:
+            return self.record.location
+        return None
+
+
+
+def test_module():
+    """ Test module """
+    geo = GeoIP()
+    print("City:", geo.get_city())
+    print("Country:", geo.get_country())
+    print("Continent:", geo.get_continent())
+    print("Location:", geo.get_location())
+
+
+if __name__ == "__main__":
+    test_module()
diff --git a/Cnchi/grub2.py b/Cnchi/grub2.py
new file mode 100644 (file)
index 0000000..8018705
--- /dev/null
@@ -0,0 +1,472 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# grub2.py
+#
+# Copyright © 2013-2019 RebornOS
+#
+# This file is part of Cnchi.
+#
+# Cnchi is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Cnchi 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.
+#
+# The following additional terms are in effect as per Section 7 of the license:
+#
+# The preservation of all legal notices and author attributions in
+# the material or in the Appropriate Legal Notices displayed
+# by works containing it is required.
+#
+# You should have received a copy of the GNU General Public License
+# along with Cnchi; If not, see <http://www.gnu.org/licenses/>.
+
+
+""" GRUB2 bootloader installation """
+
+import logging
+import os
+import shutil
+import subprocess
+import re
+import threading
+import time
+
+try:
+    import parted3.fs_module as fs
+    from installation import special_dirs
+    from misc.run_cmd import call, chroot_call
+    from misc.extra import random_generator
+except ImportError:
+    pass
+
+
+# When testing, no _() is available
+try:
+    _("")
+except NameError as err:
+    def _(message):
+        return message
+
+
+class Grub2(object):
+    """ Class to perform boot loader installation """
+
+    def __init__(self, dest_dir, settings, uuids):
+        self.dest_dir = dest_dir
+        self.settings = settings
+        self.uuids = uuids
+
+    def install(self):
+        """ Install Grub2 bootloader """
+        self.modify_grub_default()
+        self.prepare_grub_d()
+
+        if os.path.exists('/sys/firmware/efi'):
+            logging.debug("Cnchi will install the Grub2 (efi) loader")
+            self.install_efi()
+        else:
+            logging.debug("Cnchi will install the Grub2 (bios) loader")
+            self.install_bios()
+
+        self.check_root_uuid_in_grub()
+
+    def check_root_uuid_in_grub(self):
+        """ Checks grub.cfg for correct root UUID """
+        if self.settings.get("zfs"):
+            # No root uuid checking if using zfs
+            return
+
+        if "/" not in self.uuids:
+            logging.warning(
+                "Root uuid variable is not set. I can't check root UUID"
+                "in grub.cfg, let's hope it's ok")
+            return
+
+        ruuid_str = 'root=UUID={0}'.format(self.uuids["/"])
+
+        cmdline_linux = self.settings.get('GRUB_CMDLINE_LINUX')
+        if cmdline_linux is None:
+            cmdline_linux = ""
+
+        cmdline_linux_default = self.settings.get('GRUB_CMDLINE_LINUX_DEFAULT')
+        if cmdline_linux_default is None:
+            cmdline_linux_default = ""
+
+        boot_command = 'linux /vmlinuz-linux {0} {1} {2}\n'.format(
+            ruuid_str,
+            cmdline_linux,
+            cmdline_linux_default)
+
+        pattern = re.compile(
+            "menuentry 'RebornOS'[\s\S]*initramfs-linux.img\n}")
+
+        cfg = os.path.join(self.dest_dir, "boot/grub/grub.cfg")
+        with open(cfg) as grub_file:
+            parse = grub_file.read()
+
+        if not self.settings.get('use_luks') and ruuid_str not in parse:
+            entry = pattern.search(parse)
+            if entry:
+                logging.debug(
+                    "Wrong uuid in grub.cfg, Cnchi will try to fix it.")
+                new_entry = re.sub(
+                    "linux\t/vmlinuz.*quiet\n",
+                    boot_command,
+                    entry.group())
+                parse = parse.replace(entry.group(), new_entry)
+
+                with open(cfg, 'w') as grub_file:
+                    grub_file.write(parse)
+
+    def modify_grub_default(self):
+        """ If using LUKS as root, we need to modify GRUB_CMDLINE_LINUX
+            GRUB_CMDLINE_LINUX : Command-line arguments to add to menu entries
+            for the Linux kernel.
+            GRUB_CMDLINE_LINUX_DEFAULT : Unless ‘GRUB_DISABLE_RECOVERY’ is set
+            to ‘true’, two menu entries will be generated for each Linux kernel:
+            one default entry and one entry for recovery mode. This option lists
+            command-line arguments to add only to the default menu entry, after
+            those listed in ‘GRUB_CMDLINE_LINUX’. """
+
+        plymouth_bin = os.path.join(self.dest_dir, "usr/bin/plymouth")
+        cmd_linux_default = "quiet"
+        cmd_linux = ""
+
+        # https://www.kernel.org/doc/Documentation/kernel-parameters.txt
+        # cmd_linux_default : quiet splash resume=UUID=ABC zfs=ABC
+
+        if os.path.exists(plymouth_bin):
+            cmd_linux_default += " splash"
+
+        # resume does not work in zfs (or so it seems)
+        if "swap" in self.uuids and not self.settings.get("zfs"):
+            cmd_linux_default += " resume=UUID={0}".format(self.uuids["swap"])
+
+        if self.settings.get("zfs"):
+            zfs_pool_name = self.settings.get("zfs_pool_name")
+            cmd_linux += " zfs={0}".format(zfs_pool_name)
+
+        if self.settings.get('use_luks'):
+            # When using separate boot partition,
+            # add GRUB_ENABLE_CRYPTODISK to grub.cfg
+            if self.uuids["/"] != self.uuids["/boot"]:
+                self.set_grub_option("GRUB_ENABLE_CRYPTODISK", "y")
+
+            # Let GRUB automatically add the kernel parameters for
+            # root encryption
+            luks_root_volume = self.settings.get('luks_root_volume')
+            logging.debug("Luks Root Volume: %s", luks_root_volume)
+
+            if (self.settings.get("partition_mode") == "advanced" and
+                    self.settings.get('use_luks_in_root')):
+                # In advanced, if using luks in root device,
+                # we store root device it in luks_root_device var
+                root_device = self.settings.get('luks_root_device')
+                self.uuids["/"] = fs.get_uuid(root_device)
+
+            cmd_linux += " cryptdevice=/dev/disk/by-uuid/{0}:{1}".format(
+                self.uuids["/"],
+                luks_root_volume)
+
+            if self.settings.get("luks_root_password") == "":
+                # No luks password, so user wants to use a keyfile
+                cmd_linux += " cryptkey=/dev/disk/by-uuid/{0}:ext2:/.keyfile-root".format(
+                    self.uuids["/boot"])
+
+        # Remove leading/ending spaces
+        cmd_linux_default = cmd_linux_default.strip()
+        cmd_linux = cmd_linux.strip()
+
+        # Modify /etc/default/grub
+        self.set_grub_option(
+            "GRUB_THEME", "/boot/grub/themes/Vimix/theme.txt")
+        self.set_grub_option("GRUB_DISTRIBUTOR", "RebornOS")
+        self.set_grub_option("GRUB_CMDLINE_LINUX_DEFAULT", cmd_linux_default)
+        self.set_grub_option("GRUB_CMDLINE_LINUX", cmd_linux)
+
+        # Also store grub line in settings, we'll use it later in check_root_uuid_in_grub()
+        try:
+            self.settings.set('GRUB_CMDLINE_LINUX', cmd_linux)
+        except AttributeError:
+            pass
+
+        logging.debug("Grub configuration completed successfully.")
+
+    def set_grub_option(self, option, cmd):
+        """ Changes a grub setup option in /etc/default/grub """
+        try:
+            default_grub_path = os.path.join(
+                self.dest_dir, "etc/default", "grub")
+            default_grub_lines = []
+
+            with open(default_grub_path, 'r', newline='\n') as grub_file:
+                default_grub_lines = [x for x in grub_file.readlines()]
+
+            with open(default_grub_path, 'w', newline='\n') as grub_file:
+                param_in_file = False
+                param_to_look_for = option + '='
+                for line in default_grub_lines:
+                    if param_to_look_for in line:
+                        # Option was already in file, update it
+                        line = '{0}="{1}"\n'.format(option, cmd)
+                        param_in_file = True
+                    grub_file.write(line)
+
+                if not param_in_file:
+                    # Option was not found. Thus, append new option
+                    grub_file.write('\n{0}="{1}"\n'.format(option, cmd))
+
+            logging.debug('Set %s="%s" in /etc/default/grub', option, cmd)
+        except FileNotFoundError as ex:
+            logging.error(ex)
+        except Exception as ex:
+            tpl1 = "Can't modify {0}".format(default_grub_path)
+            tpl2 = "An exception of type {0} occured. Arguments:\n{1!r}"
+            template = '{0} {1}'.format(tpl1, tpl2)
+            message = template.format(type(ex).__name__, ex.args)
+            logging.error(message)
+
+    def prepare_grub_d(self):
+        """ Copies 10_antergos script into /etc/grub.d/ """
+        grub_d_dir = os.path.join(self.dest_dir, "etc/grub.d")
+        script_dir = os.path.join(self.settings.get("cnchi"), "scripts")
+        script = "10_antergos"
+
+        os.makedirs(grub_d_dir, mode=0o755, exist_ok=True)
+
+        script_path = os.path.join(script_dir, script)
+        if os.path.exists(script_path):
+            try:
+                shutil.copy2(script_path, grub_d_dir)
+                os.chmod(os.path.join(grub_d_dir, script), 0o755)
+            except FileNotFoundError:
+                logging.debug("Could not copy %s to grub.d", script)
+            except FileExistsError:
+                pass
+        else:
+            logging.warning("Can't find script %s", script_path)
+
+    def grub_ripper(self):
+        while True:
+            time.sleep(10)
+            try:
+                ret = subprocess.check_output(
+                    ['pidof', 'grub-mount']).decode().strip()
+                if ret:
+                    subprocess.check_output(['kill', '-9', ret.split()[0]])
+                else:
+                    break
+            except subprocess.CalledProcessError as err:
+                logging.warning("Error running %s: %s", err.cmd, err.output)
+                break
+
+    def run_mkconfig(self):
+        """ Create grub.cfg file using grub-mkconfig """
+        logging.debug("Generating grub.cfg...")
+
+        # Make sure that /dev and others are mounted (binded).
+        special_dirs.mount(self.dest_dir)
+
+        # Hack to kill grub-mount hanging
+        threading.Thread(target=self.grub_ripper).start()
+
+        # Add -l option to os-prober's umount call so that it does not hang
+        self.apply_osprober_patch()
+        logging.debug("Running grub-mkconfig...")
+        locale = self.settings.get("locale")
+        cmd = 'LANG={0} grub-mkconfig -o /boot/grub/grub.cfg'.format(locale)
+        cmd_sh = ['sh', '-c', cmd]
+        if not chroot_call(cmd_sh, self.dest_dir, timeout=300):
+            msg = ("grub-mkconfig does not respond. Killing grub-mount and"
+                   "os-prober so we can continue.")
+            logging.error(msg)
+            call(['killall', 'grub-mount'])
+            call(['killall', 'os-prober'])
+
+    def install_bios(self):
+        """ Install Grub2 bootloader in a BIOS system """
+        grub_location = self.settings.get('bootloader_device')
+        txt = _("Installing GRUB(2) BIOS boot loader in {0}").format(
+            grub_location)
+        logging.info(txt)
+
+        # /dev and others need to be mounted (binded).
+        # We call mount_special_dirs here just to be sure
+        special_dirs.mount(self.dest_dir)
+
+        grub_install = ['grub-install',
+                        '--directory=/usr/lib/grub/i386-pc',
+                        '--target=i386-pc',
+                        '--boot-directory=/boot',
+                        '--recheck']
+
+        # Use --force when installing in /dev/sdXY or in /dev/mmcblk
+        if len(grub_location) > len("/dev/sdX"):
+            grub_install.append("--force")
+
+        grub_install.append(grub_location)
+
+        chroot_call(grub_install, self.dest_dir)
+
+        self.install_locales()
+
+        self.run_mkconfig()
+
+        grub_cfg_path = os.path.join(self.dest_dir, "boot/grub/grub.cfg")
+        with open(grub_cfg_path) as grub_cfg:
+            if "Antergos" in grub_cfg.read():
+                txt = _("GRUB(2) BIOS has been successfully installed.")
+                logging.info(txt)
+                self.settings.set('bootloader_installation_successful', True)
+            else:
+                txt = _("ERROR installing GRUB(2) BIOS.")
+                logging.warning(txt)
+                self.settings.set('bootloader_installation_successful', False)
+
+    def install_efi(self):
+        """ Install Grub2 bootloader in a UEFI system """
+        uefi_arch = "x86_64"
+        spec_uefi_arch = "x64"
+        spec_uefi_arch_caps = "X64"
+        fpath = '/install/boot/efi/EFI/RebornOS'
+        bootloader_id = 'RebornOS' if not os.path.exists(fpath) else \
+            'RebornOS_{0}'.format(random_generator())
+
+        # grub2 in efi needs efibootmgr
+        if not os.path.exists("/usr/bin/efibootmgr"):
+            txt = _(
+                "Please install efibootmgr package to install Grub2 for x86_64-efi platform.")
+            logging.warning(txt)
+            txt = _("GRUB(2) will NOT be installed")
+            logging.warning(txt)
+            self.settings.set('bootloader_installation_successful', False)
+            return
+
+        txt = _("Installing GRUB(2) UEFI {0} boot loader").format(uefi_arch)
+        logging.info(txt)
+
+        grub_install = [
+            'grub-install',
+            '--target={0}-efi'.format(uefi_arch),
+            '--efi-directory=/install/boot/efi',
+            '--bootloader-id={0}'.format(bootloader_id),
+            '--boot-directory=/install/boot',
+            '--recheck']
+        load_module = ['modprobe', '-a', 'efivarfs']
+
+        call(load_module, timeout=15)
+        call(grub_install, timeout=120)
+
+        self.install_locales()
+
+        # Copy grub into dirs known to be used as default by some OEMs
+        # if they do not exist yet.
+        grub_defaults = [
+            os.path.join(
+                self.dest_dir,
+                "boot/efi/EFI/BOOT",
+                "BOOT{0}.efi".format(spec_uefi_arch_caps)),
+            os.path.join(
+                self.dest_dir,
+                "boot/efi/EFI/Microsoft/Boot",
+                "bootmgfw.efi")]
+
+        grub_path = os.path.join(
+            self.dest_dir,
+            "boot/efi/EFI/antergos_grub",
+            "grub{0}.efi".format(spec_uefi_arch))
+
+        for grub_default in grub_defaults:
+            path = grub_default.split()[0]
+            if not os.path.exists(path):
+                msg = _("No OEM loader found in %s. Copying Grub(2) into dir.")
+                logging.info(msg, path)
+                os.makedirs(path, mode=0o755)
+                msg_failed = _("Copying Grub(2) into OEM dir failed: %s")
+                try:
+                    shutil.copy(grub_path, grub_default)
+                except FileNotFoundError:
+                    logging.warning(msg_failed, _("File not found."))
+                except FileExistsError:
+                    logging.warning(msg_failed, _("File already exists."))
+                except Exception as ex:
+                    template = "An exception of type {0} occured. Arguments:\n{1!r}"
+                    message = template.format(type(ex).__name__, ex.args)
+                    logging.error(message)
+
+        self.run_mkconfig()
+
+        paths = [
+            os.path.join(self.dest_dir, "boot/grub/x86_64-efi/core.efi"),
+            os.path.join(
+                self.dest_dir,
+                "boot/efi/EFI/{0}".format(bootloader_id),
+                "grub{0}.efi".format(spec_uefi_arch))]
+
+        exists = True
+        for path in paths:
+            if not os.path.exists(path):
+                exists = False
+                logging.debug("Path '%s' doesn't exist, when it should", path)
+
+        if exists:
+            logging.info("GRUB(2) UEFI install completed successfully")
+            self.settings.set('bootloader_installation_successful', True)
+        else:
+            logging.warning(
+                "GRUB(2) UEFI install may not have completed successfully.")
+            self.settings.set('bootloader_installation_successful', False)
+
+    def apply_osprober_patch(self):
+        """ Adds -l option to os-prober's umount call so that it does not hang """
+        osp_path = os.path.join(
+            self.dest_dir,
+            "usr/lib/os-probes/50mounted-tests")
+        if os.path.exists(osp_path):
+            with open(osp_path) as osp:
+                text = osp.read().replace("umount", "umount -l")
+            with open(osp_path, 'w') as osp:
+                osp.write(text)
+            logging.debug("50mounted-tests file patched successfully")
+        else:
+            logging.warning("Failed to patch 50mounted-tests, file not found.")
+
+    def install_locales(self):
+        """ Install Grub2 locales """
+        logging.debug("Installing Grub2 locales.")
+        dest_locale_dir = os.path.join(self.dest_dir, "boot/grub/locale")
+
+        os.makedirs(dest_locale_dir, mode=0o755, exist_ok=True)
+
+        grub_mo = os.path.join(
+            self.dest_dir,
+            "usr/share/locale/en@quot/LC_MESSAGES/grub.mo")
+
+        try:
+            shutil.copy2(grub_mo, os.path.join(dest_locale_dir, "en.mo"))
+        except FileNotFoundError:
+            logging.warning("Can't install GRUB(2) locale.")
+        except FileExistsError:
+            # Ignore if already exists
+            pass
+
+
+if __name__ == '__main__':
+    os.makedirs("/install/etc/default", mode=0o755, exist_ok=True)
+    shutil.copy2("/etc/default/grub", "/install/etc/default/grub")
+    dest_dir = "/install"
+    settings = {}
+    settings["zfs"] = True
+    settings["zfs_pool_name"] = "Reborn_d3sq"
+    settings["use_luks"] = True
+    uuids = {}
+    uuids["/"] = "ABCD"
+    uuids["/boot"] = "ZXCV"
+    grub2 = Grub2(dest_dir, settings, uuids)
+    grub2.modify_grub_default()
diff --git a/Cnchi/gufw.desktop b/Cnchi/gufw.desktop
new file mode 100644 (file)
index 0000000..cb3743e
--- /dev/null
@@ -0,0 +1,12 @@
+[Desktop Entry]
+Version=1.0
+Type=Application
+Name=Uncomplicated Firewall
+Comment=Is a program for managing a netfilter firewall.
+Icon=/usr/share/icons/default/gufw.svg
+Exec=gufw
+NoDisplay=false
+Categories=Utility;
+Keywords=firewall
+StartupNotify=true
+Terminal=false
diff --git a/Cnchi/gufw.png b/Cnchi/gufw.png
new file mode 100644 (file)
index 0000000..989b160
Binary files /dev/null and b/Cnchi/gufw.png differ
diff --git a/Cnchi/gufw.svg b/Cnchi/gufw.svg
new file mode 100644 (file)
index 0000000..9f381be
--- /dev/null
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" version="1">
+ <g fill="#7e97de" transform="matrix(.11222 0 0 .11343 4.2138 4)">
+  <path d="m324.48 51.943-146.49-51.658c-1.076-0.38-2.25-0.38-3.326 0l-146.49 51.658c-1.999 0.705-3.337 2.595-3.337 4.715 0 52.278 13.834 112.71 37.956 165.8 19.566 43.069 54.751 100.52 111.25 129.62 0.719 0.37 1.504 0.555 2.29 0.555s1.571-0.185 2.29-0.555c56.496-29.106 91.68-86.556 111.25-129.62 24.121-53.094 37.955-113.53 37.955-165.8 0-2.12-1.338-4.01-3.337-4.715z"/>
+ </g>
+ <g opacity=".2" transform="matrix(.059409 0 0 .062386 13.525 14.001)">
+  <path d="m324.48 51.943-146.49-51.658c-1.076-0.38-2.25-0.38-3.326 0l-146.49 51.658c-1.999 0.705-3.337 2.595-3.337 4.715 0 52.278 13.834 112.71 37.956 165.8 19.566 43.069 54.751 100.52 111.25 129.62 0.719 0.37 1.504 0.555 2.29 0.555s1.571-0.185 2.29-0.555c56.496-29.106 91.68-86.556 111.25-129.62 24.121-53.094 37.955-113.53 37.955-165.8 0-2.12-1.338-4.01-3.337-4.715z"/>
+ </g>
+ <g fill="#fff" transform="matrix(.059409 0 0 .062386 13.525 13)">
+  <path d="m324.48 51.943-146.49-51.658c-1.076-0.38-2.25-0.38-3.326 0l-146.49 51.658c-1.999 0.705-3.337 2.595-3.337 4.715 0 52.278 13.834 112.71 37.956 165.8 19.566 43.069 54.751 100.52 111.25 129.62 0.719 0.37 1.504 0.555 2.29 0.555s1.571-0.185 2.29-0.555c56.496-29.106 91.68-86.556 111.25-129.62 24.121-53.094 37.955-113.53 37.955-165.8 0-2.12-1.338-4.01-3.337-4.715z"/>
+ </g>
+ <g fill="#5c7bd5" transform="matrix(.11222 0 0 .11343 4.2138 4)">
+  <path fill="#fff" opacity=".2" transform="matrix(8.9114 0 0 8.8161 -37.55 -35.264)" d="m24 4c-0.063 0-0.125 0.0117-0.186 0.0332l-16.439 5.8594c-0.2243 0.0799-0.375 0.2924-0.375 0.5334 0 0.27 0.0209 0.552 0.0273 0.826 0.0528-0.166 0.1795-0.299 0.3477-0.359l16.439-5.8598c0.121-0.0431 0.253-0.0431 0.374 0l16.437 5.8598c0.168 0.06 0.295 0.193 0.348 0.359 0.006-0.274 0.027-0.556 0.027-0.826 0-0.241-0.151-0.4535-0.375-0.5334l-16.437-5.8594c-0.061-0.0215-0.125-0.0332-0.188-0.0332z"/>
+ </g>
+ <g opacity=".2" transform="matrix(.11222 0 0 .11343 4.2138 4.9995)">
+  <path transform="matrix(8.9114,0,0,8.8161,-37.55,-35.264)" d="m7.0273 10.258c-0.0164 0.053-0.0273 0.11-0.0273 0.168 0 5.93 1.5529 12.786 4.26 18.808 2.195 4.886 6.142 11.402 12.482 14.704 0.081 0.041 0.17 0.062 0.258 0.062s0.177-0.021 0.258-0.062c6.34-3.302 10.286-9.818 12.482-14.704 2.707-6.022 4.26-12.878 4.26-18.808 0-0.058-0.011-0.115-0.027-0.168-0.136 5.735-1.65 12.231-4.233 17.976-2.196 4.886-6.142 11.402-12.482 14.704-0.081 0.041-0.17 0.062-0.258 0.062s-0.177-0.021-0.258-0.062c-6.34-3.302-10.287-9.818-12.482-14.704-2.5827-5.745-4.0974-12.241-4.2327-17.976z"/>
+ </g>
+</svg>
diff --git a/Cnchi/hexagon erased.xcf b/Cnchi/hexagon erased.xcf
new file mode 100644 (file)
index 0000000..3640c26
Binary files /dev/null and b/Cnchi/hexagon erased.xcf differ
diff --git a/Cnchi/info.py b/Cnchi/info.py
new file mode 100755 (executable)
index 0000000..31919d8
--- /dev/null
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+#  info.py
+#
+#  Copyright © 2013-2019 RebornOS
+#
+#  This file is part of Cnchi.
+#
+#  Cnchi is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Cnchi 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.
+#
+#  The following additional terms are in effect as per Section 7 of the license:
+#
+#  The preservation of all legal notices and author attributions in
+#  the material or in the Appropriate Legal Notices displayed
+#  by works containing it is required.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with Cnchi; If not, see <http://www.gnu.org/licenses/>.
+
+
+""" Set some Cnchi global constants """
+
+CNCHI_VERSION = "RebornOS Installer Gnome based 2020.05.02"
+CNCHI_WEBSITE = "https://rebornos.org"
+CNCHI_RELEASE_STAGE = "production"
+
+if __name__ == '__main__':
+    print(CNCHI_VERSION)
diff --git a/Cnchi/install.py b/Cnchi/install.py
new file mode 100755 (executable)
index 0000000..fa1544a
--- /dev/null
@@ -0,0 +1,524 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# install.py
+#
+# Copyright © 2013-2018 Antergos
+#
+# This file is part of Cnchi.
+#
+# Cnchi 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.
+#
+# Cnchi 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 Cnchi; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+""" Installation process module. """
+
+import glob
+import logging
+import os
+import shutil
+import sys
+
+from mako.template import Template
+
+from download import download
+
+from installation import special_dirs
+from installation import post_install
+from installation import mount
+
+import misc.extra as misc
+from misc.extra import InstallError
+from misc.run_cmd import call
+from misc.events import Events
+import pacman.pac as pac
+
+import hardware.hardware as hardware
+
+DEST_DIR = "/install"
+
+# When testing, no _() is available
+try:
+    _("")
+except NameError as err:
+    def _(message):
+        return message
+
+class Installation():
+    """ Installation process thread class """
+
+    TMP_PACMAN_CONF = "/tmp/pacman.conf"
+
+    def __init__(self, settings, callback_queue, packages, metalinks,
+                 mount_devices, fs_devices, ssd=None, blvm=False):
+        """ Initialize installation class """
+
+        self.settings = settings
+        self.events = Events(callback_queue)
+        self.packages = packages
+        self.metalinks = metalinks
+
+        self.method = self.settings.get('partition_mode')
+
+        self.desktop = self.settings.get('desktop').lower()
+
+        # This flag tells us if there is a lvm partition (from advanced install)
+        # If it's true we'll have to add the 'lvm2' hook to mkinitcpio
+        self.blvm = blvm
+
+        if ssd is not None:
+            self.ssd = ssd
+        else:
+            self.ssd = {}
+
+        self.mount_devices = mount_devices
+
+        self.fs_devices = fs_devices
+
+        self.running = True
+        self.error = False
+
+        self.auto_device = ""
+        self.packages = packages
+        self.pacman = None
+
+        self.pacman_cache_dir = ''
+
+        # Cnchi will store here info (packages needed, post install actions, ...)
+        # for the detected hardware
+        self.hardware_install = None
+
+    def queue_fatal_event(self, txt):
+        """ Queues the fatal event and exits process """
+        self.error = True
+        self.running = False
+        self.events.add('error', txt)
+        # self.callback_queue.join()
+        sys.exit(0)
+
+    def mount_partitions(self):
+        """ Do not call this in automatic mode as AutoPartition class mounts
+        the root and boot devices itself. (We call it if using ZFS, though) """
+
+        if os.path.exists(DEST_DIR) and not self.method == "zfs":
+            # If we're recovering from a failed/stoped install, there'll be
+            # some mounted directories. Try to unmount them first.
+            # We use unmount_all_in_directory from auto_partition to do this.
+            # ZFS already mounts everything automagically (except /boot that
+            # is not in zfs)
+            mount.unmount_all_in_directory(DEST_DIR)
+
+        # NOTE: Advanced method formats root by default in advanced.py
+        if "/" in self.mount_devices:
+            root_partition = self.mount_devices["/"]
+        else:
+            root_partition = ""
+
+        # Boot partition
+        if "/boot" in self.mount_devices:
+            boot_partition = self.mount_devices["/boot"]
+        else:
+            boot_partition = ""
+
+        # EFI partition
+        if "/boot/efi" in self.mount_devices:
+            efi_partition = self.mount_devices["/boot/efi"]
+        else:
+            efi_partition = ""
+
+        # Swap partition
+        if "swap" in self.mount_devices:
+            swap_partition = self.mount_devices["swap"]
+        else:
+            swap_partition = ""
+
+        # Mount root partition
+        if self.method == "zfs":
+            # Mount /
+            logging.debug("ZFS: Mounting root")
+            cmd = ["zfs", "mount", "-a"]
+            call(cmd)
+        elif root_partition:
+            txt = "Mounting root partition {0} into {1} directory".format(
+                root_partition, DEST_DIR)
+            logging.debug(txt)
+            cmd = ['mount', root_partition, DEST_DIR]
+            call(cmd, fatal=True)
+
+        # We also mount the boot partition if it's needed
+        boot_path = os.path.join(DEST_DIR, "boot")
+        os.makedirs(boot_path, mode=0o755, exist_ok=True)
+        if boot_partition:
+            txt = _("Mounting boot partition {0} into {1} directory").format(
+                boot_partition, boot_path)
+            logging.debug(txt)
+            cmd = ['mount', boot_partition, boot_path]
+            call(cmd, fatal=True)
+
+        if self.method == "zfs" and efi_partition:
+            # In automatic zfs mode, it could be that we have a specific EFI
+            # partition (different from /boot partition). This happens if using
+            # EFI and grub2 bootloader
+            efi_path = os.path.join(DEST_DIR, "boot", "efi")
+            os.makedirs(efi_path, mode=0o755, exist_ok=True)
+            txt = _("Mounting EFI partition {0} into {1} directory").format(
+                efi_partition, efi_path)
+            logging.debug(txt)
+            cmd = ['mount', efi_partition, efi_path]
+            call(cmd, fatal=True)
+
+        # In advanced mode, mount all partitions (root and boot are already mounted)
+        if self.method == 'advanced':
+            for path in self.mount_devices:
+                if path == "":
+                    # Ignore devices without a mount path
+                    continue
+
+                mount_part = self.mount_devices[path]
+
+                if mount_part not in [root_partition, boot_partition, swap_partition]:
+                    if path[0] == '/':
+                        path = path[1:]
+                    mount_dir = os.path.join(DEST_DIR, path)
+                    try:
+                        os.makedirs(mount_dir, mode=0o755, exist_ok=True)
+                        txt = _("Mounting partition {0} into {1} directory")
+                        txt = txt.format(mount_part, mount_dir)
+                        logging.debug(txt)
+                        cmd = ['mount', mount_part, mount_dir]
+                        call(cmd)
+                    except OSError:
+                        logging.warning(
+                            "Could not create %s directory", mount_dir)
+                elif mount_part == swap_partition:
+                    logging.debug("Activating swap in %s", mount_part)
+                    cmd = ['swapon', swap_partition]
+                    call(cmd)
+
+    @misc.raise_privileges
+    def run(self):
+        """ Run installation """
+
+        # From this point, on a warning situation, Cnchi should try to continue,
+        # so we need to catch the exception here. If we don't catch the exception
+        # here, it will be catched in run() and managed as a fatal error.
+        # On the other hand, if we want to clarify the exception message we can
+        # catch it here and then raise an InstallError exception.
+
+        if not os.path.exists(DEST_DIR):
+            os.makedirs(DEST_DIR, mode=0o755, exist_ok=True)
+
+        # Make sure the antergos-repo-priority package's alpm hook doesn't run.
+        if not os.environ.get('CNCHI_RUNNING', False):
+            os.environ['CNCHI_RUNNING'] = 'True'
+
+        msg = _("Installing using the '{0}' method").format(self.method)
+        self.events.add('info', msg)
+
+        # Mount needed partitions (in automatic it's already done)
+        if self.method in ['alongside', 'advanced', 'zfs']:
+            self.mount_partitions()
+
+        # Nasty workaround:
+        # If pacman was stoped and /var is in another partition than root
+        # (so as to be able to resume install), database lock file will still
+        # be in place. We must delete it or this new installation will fail
+        db_lock = os.path.join(DEST_DIR, "var/lib/pacman/db.lck")
+        if os.path.exists(db_lock):
+            os.remove(db_lock)
+            logging.debug("%s deleted", db_lock)
+
+        # Create some needed folders
+        folders = [
+            os.path.join(DEST_DIR, 'var/lib/pacman'),
+            os.path.join(DEST_DIR, 'etc/pacman.d/gnupg'),
+            os.path.join(DEST_DIR, 'var/log')]
+
+        for folder in folders:
+            os.makedirs(folder, mode=0o755, exist_ok=True)
+
+        # If kernel images exists in /boot they are most likely from a failed
+        # install attempt and need to be removed otherwise pyalpm will raise a
+        # fatal exception later on.
+        kernel_imgs = (
+            "/install/boot/vmlinuz-linux",
+            "/install/boot/vmlinuz-linux-lts",
+            "/install/boot/initramfs-linux.img",
+            "/install/boot/initramfs-linux-fallback.img",
+            "/install/boot/initramfs-linux-lts.img",
+            "/install/boot/initramfs-linux-lts-fallback.img")
+
+        for img in kernel_imgs:
+            if os.path.exists(img):
+                os.remove(img)
+
+        # If intel-ucode or grub2-theme-antergos files exist in /boot they are
+        # most likely either from another linux installation or from a failed
+        # install attempt and need to be removed otherwise pyalpm will refuse
+        # to install those packages (like above)
+        if os.path.exists('/install/boot/intel-ucode.img'):
+            logging.debug("Removing previous intel-ucode.img file found in /boot")
+            os.remove('/install/boot/intel-ucode.img')
+        if os.path.exists('/install/boot/grub/themes/Antergos-Default'):
+            logging.debug("Removing previous Antergos-Default grub2 theme found in /boot")
+            shutil.rmtree('/install/boot/grub/themes/Antergos-Default')
+
+        logging.debug("Preparing pacman...")
+        self.prepare_pacman()
+        logging.debug("Pacman ready")
+
+        # Run driver's pre-install scripts
+        try:
+            logging.debug("Running hardware drivers pre-install jobs...")
+            proprietary = self.settings.get('feature_graphic_drivers')
+            self.hardware_install = hardware.HardwareInstall(
+                self.settings.get("cnchi"),
+                use_proprietary_graphic_drivers=proprietary)
+            self.hardware_install.pre_install(DEST_DIR)
+        except Exception as ex:
+            template = "Error in hardware module. " \
+                       "An exception of type {0} occured. Arguments:\n{1!r}"
+            message = template.format(type(ex).__name__, ex.args)
+            logging.error(message)
+
+        logging.debug("Downloading packages...")
+        self.download_packages()
+
+        # This mounts (binds) /dev and others to /DEST_DIR/dev and others
+        special_dirs.mount(DEST_DIR)
+
+        logging.debug("Installing packages...")
+        self.install_packages()
+
+        logging.debug("Configuring system...")
+        post = post_install.PostInstallation(
+            self.settings,
+            self.events.queue,
+            self.mount_devices,
+            self.fs_devices,
+            self.ssd,
+            self.blvm)
+        post.configure_system(self.hardware_install)
+
+        # This unmounts (unbinds) /dev and others to /DEST_DIR/dev and others
+        special_dirs.umount(DEST_DIR)
+
+        # Run postinstall script (we need special dirs unmounted but dest_dir mounted!)
+        logging.debug("Running postinstall.sh script...")
+        post.set_desktop_settings()
+
+        # Copy installer log to the new installation
+        logging.debug("Copying install log to /var/log/cnchi")
+        post.copy_logs()
+
+        self.events.add('pulse', 'stop')
+        self.events.add('progress_bar', 'hide')
+
+        # Finally, try to unmount DEST_DIR
+        mount.unmount_all_in_directory(DEST_DIR)
+
+        self.running = False
+
+        # Installation finished successfully
+        self.events.add('finished', _("Installation finished"))
+        self.error = False
+        return True
+
+    def download_packages(self):
+        """ Downloads necessary packages """
+
+        self.pacman_cache_dir = os.path.join(DEST_DIR, 'var/cache/pacman/pkg')
+
+        pacman_conf = {}
+        pacman_conf['file'] = Installation.TMP_PACMAN_CONF
+        pacman_conf['cache'] = self.pacman_cache_dir
+
+        download_packages = download.DownloadPackages(
+            package_names=self.packages,
+            pacman_conf=pacman_conf,
+            settings=self.settings,
+            callback_queue=self.events.queue)
+
+        # Metalinks have already been calculated before,
+        # When downloadpackages class has been called in process.py to test
+        # that Cnchi was able to create it before partitioning/formatting
+        download_packages.start_download(self.metalinks)
+
+    def create_pacman_conf_file(self):
+        """ Creates a temporary pacman.conf """
+        myarch = os.uname()[-1]
+        msg = _("Creating a temporary pacman.conf for {0} architecture").format(
+            myarch)
+        logging.debug(msg)
+
+        # Template functionality. Needs Mako (see http://www.makotemplates.org/)
+        template_file_name = os.path.join(
+            self.settings.get('data'), 'pacman.tmpl')
+        file_template = Template(filename=template_file_name)
+        file_rendered = file_template.render(
+            destDir=DEST_DIR,
+            arch=myarch,
+            desktop=self.desktop)
+        filename = Installation.TMP_PACMAN_CONF
+        dirname = os.path.dirname(filename)
+        os.makedirs(dirname, mode=0o755, exist_ok=True)
+        with open(filename, "w") as my_file:
+            my_file.write(file_rendered)
+
+    def prepare_pacman(self):
+        """ Configures pacman and syncs db on destination system """
+
+        self.create_pacman_conf_file()
+
+        msg = _("Updating package manager security. Please wait...")
+        self.events.add('info', msg)
+        self.prepare_pacman_keyring()
+
+        # Init pyalpm
+        try:
+            self.pacman = pac.Pac(
+                Installation.TMP_PACMAN_CONF, self.events.queue)
+        except Exception as ex:
+            self.pacman = None
+            template = ("Can't initialize pyalpm. "
+                        "An exception of type {0} occured. Arguments:\n{1!r}")
+            message = template.format(type(ex).__name__, ex.args)
+            logging.error(message)
+            raise InstallError(message)
+
+        # Refresh pacman databases
+        if not self.pacman.refresh():
+            logging.error("Can't refresh pacman databases.")
+            raise InstallError(_("Can't refresh pacman databases."))
+
+    @staticmethod
+    def prepare_pacman_keyring():
+        """ Add gnupg pacman files to installed system """
+
+        dirs = ["var/cache/pacman/pkg", "var/lib/pacman"]
+        for pacman_dir in dirs:
+            mydir = os.path.join(DEST_DIR, pacman_dir)
+            os.makedirs(mydir, mode=0o755, exist_ok=True)
+
+        # Be sure that haveged is running (liveCD)
+        # haveged is a daemon that generates system entropy; this speeds up
+        # critical operations in cryptographic programs such as gnupg
+        # (including the generation of new keyrings)
+        cmd = ["systemctl", "start", "haveged"]
+        call(cmd)
+
+        # Delete old gnupg files
+        dest_path = os.path.join(DEST_DIR, "etc/pacman.d/gnupg")
+        cmd = ["rm", "-rf", dest_path]
+        call(cmd)
+        os.mkdir(dest_path)
+
+        # Tell pacman-key to regenerate gnupg files
+        # Initialize the pacman keyring
+        cmd = ["pacman-key", "--init", "--gpgdir", dest_path]
+        call(cmd)
+
+        # Load the signature keys
+        # Delete antergos dest_pat, add rebornos dest-path (Rafael)
+        cmd = ["pacman-key", "--populate", "--gpgdir",
+               dest_path, "archlinux", "rebornos"]
+        call(cmd)
+
+        # path = os.path.join(DEST_DIR, "root/.gnupg/dirmngr_ldapservers.conf")
+        # Run dirmngr
+        # https://bbs.archlinux.org/viewtopic.php?id=190380
+        with open(os.devnull, 'r') as dev_null:
+            cmd = ["dirmngr"]
+            call(cmd, stdin=dev_null)
+
+        # Refresh and update the signature keys
+        # cmd = ["pacman-key", "--refresh-keys", "--gpgdir", dest_path]
+        # call(cmd)
+
+    def delete_stale_pkgs(self, stale_pkgs):
+        """ Failure might be due to stale cached packages. Delete them. """
+        for stale_pkg in stale_pkgs:
+            filepath = os.path.join(self.pacman_cache_dir, stale_pkg)
+            to_delete = glob.glob(filepath + '***') if filepath else []
+            if to_delete and len(to_delete) <= 20:
+                for fpath in to_delete:
+                    try:
+                        os.remove(fpath)
+                    except OSError as err:
+                        logging.error(err)
+
+    @staticmethod
+    def use_build_server_repo():
+        """ Setup pacman.conf to use build server repository """
+        with open('/etc/pacman.conf', 'r') as pacman_conf:
+            contents = pacman_conf.readlines()
+        with open('/etc/pacman.conf', 'w') as new_pacman_conf:
+            for line in contents:
+                if 'reborn-mirrorlist' in line:
+                    line = 'Server = https://repo.rebornos.org/RebornOS/'
+                new_pacman_conf.write(line)
+
+    def install_packages(self):
+        """ Start pacman installation of packages """
+        result = False
+        # This shouldn't be necessary if download.py really downloaded all
+        # needed packages, but it does not do it (why?)
+        for cache_dir in self.settings.get('xz_cache'):
+            self.pacman.handle.add_cachedir(cache_dir)
+
+        logging.debug("Installing packages...")
+
+        try:
+            result = self.pacman.install(pkgs=self.packages)
+        except pac.pyalpm.error:
+            pass
+
+        stale_pkgs = self.settings.get('cache_pkgs_md5_check_failed')
+
+        if not result and stale_pkgs and os.path.exists(self.pacman_cache_dir):
+            # Failure might be due to stale cached packages. Delete them and try again.
+            logging.warning(
+                "Can't install necessary packages. Let's try again deleting stale packages first.")
+            self.delete_stale_pkgs(stale_pkgs)
+            self.pacman.refresh()
+            try:
+                result = self.pacman.install(pkgs=self.packages)
+            except pac.pyalpm.error:
+                pass
+
+        if not result:
+            # Failure might be due to antergos mirror issues. Try using build server repo.
+            logging.warning(
+                "Can't install necessary packages. Let's try again using a tier 1 mirror.")
+            self.use_build_server_repo()
+            self.pacman.refresh()
+            try:
+                result = self.pacman.install(pkgs=self.packages)
+            except pac.pyalpm.error:
+                pass
+
+        if not result:
+            txt = _("Can't install necessary packages. Cnchi can't continue.")
+            raise InstallError(txt)
+
+        # All downloading and installing has been done, so we hide progress bar
+        self.events.add('progress_bar', 'hide')
+
+    def is_running(self):
+        """ Checks if thread is running """
+        return self.running
+
+    def is_ok(self):
+        """ Checks if an error has been issued """
+        return not self.error
diff --git a/Cnchi/lightdm-webkit2-greeter.conf b/Cnchi/lightdm-webkit2-greeter.conf
new file mode 100644 (file)
index 0000000..a1af3e7
--- /dev/null
@@ -0,0 +1,35 @@
+#
+# [greeter]
+# debug_mode          = Greeter theme debug mode.
+# detect_theme_errors = Provide an option to load a fallback theme when theme errors are detected.
+# screensaver_timeout = Blank the screen after this many seconds of inactivity.
+# secure_mode         = Don't allow themes to make remote http requests.
+# time_format         = A moment.js format string so the greeter can generate localized time for display.
+# time_language       = Language to use when displaying the time or "auto" to use the system's language.
+# webkit_theme        = Webkit theme to use.
+#
+# NOTE: See moment.js documentation for format string options: http://momentjs.com/docs/#/displaying/format/
+#
+
+[greeter]
+debug_mode          = false
+detect_theme_errors = true
+screensaver_timeout = 300
+secure_mode         = true
+time_format         = LT
+time_language       = auto
+webkit_theme = lightdm-webkit-theme-aether 
+
+#
+# [branding]
+# background_images = Path to directory that contains background images for use by themes.
+# logo              = Path to logo image for use by greeter themes.
+# user_image        = Default user image/avatar. This is used by themes for users that have no .face image.
+#
+# NOTE: Paths must be accessible to the lightdm system user account (so they cannot be anywhere in /home)
+#
+
+[branding]
+background_images = /usr/share/backgrounds/
+logo              = /usr/share/lightdm-webkit/themes/lightdm-webkit-theme-aether/src/img/arch-logo.png
+user_image        = /usr/share/lightdm-webkit/themes/lightdm-webkit-theme-aether/src/img/arch-logo.png
diff --git a/Cnchi/logging_utils.py b/Cnchi/logging_utils.py
new file mode 100755 (executable)
index 0000000..a5f8738
--- /dev/null
@@ -0,0 +1,230 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# logging_utils.py
+#
+# Copyright © 2015-2018 Antergos
+#
+# This file is part of Cnchi.
+#
+# Cnchi is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Cnchi 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.
+#
+# The following additional terms are in effect as per Section 7 of the license:
+#
+# The preservation of all legal notices and author attributions in
+# the material or in the Appropriate Legal Notices displayed
+# by works containing it is required.
+#
+# You should have received a copy of the GNU General Public License
+# along with Cnchi; If not, see <http://www.gnu.org/licenses/>.
+
+""" Logging utils to ease log calls """
+
+import logging
+import uuid
+import json
+import os
+import requests
+
+from info import CNCHI_VERSION, CNCHI_RELEASE_STAGE
+
+
+class Singleton(type):
+    """ Single instance """
+    _instance = None
+
+    def __call__(cls, *args, **kwargs):
+        if not cls._instance:
+            cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
+        return cls._instance
+
+    def __new__(cls, *args, **kwargs):
+        obj = super().__new__(cls, *args, **kwargs)
+        obj.ip_addr = None
+        obj.install_id = None
+        obj.api_key = None
+        obj.have_install_id = False
+        obj.after_location_screen = False
+        obj.name = 'Cnchi'
+        obj.description = 'Installer'
+        obj.key = 'X-{}-{}'.format(obj.name, obj.description)
+
+        return obj
+
+
+class ContextFilter(logging.Filter, metaclass=Singleton):
+    """ Context filter for logging methods to send logs to bugsnag """
+    LOG_FOLDER = '/var/log/cnchi'
+
+    def __init__(self):
+        super().__init__()
+        self.api_key = self.get_bugsnag_api()
+        self.have_install_id = False
+        self.after_location_screen = False
+        self.install_id = ""
+        self.ip_addr = '1.2.3.4'
+
+    def filter(self, record):
+        uid = str(uuid.uuid1()).split("-")
+        record.uuid = uid[3] + "-" + uid[1] + "-" + uid[2] + "-" + uid[4]
+        record.ip_addr = self.ip_addr
+        record.install_id = self.install_id
+        return True
+
+    def get_and_save_install_id(self, is_location_screen=False):
+        """ Obtain an install identification """
+        if self.have_install_id:
+            return self.install_id
+
+        if is_location_screen:
+            self.after_location_screen = True
+
+        if CNCHI_RELEASE_STAGE == 'development':
+            self.install_id = 'development'
+            self.ip_addr = '1.2.3.4'
+            self.have_install_id = True
+            return 'development'
+
+        info = {'ip': '1.2.3.4', 'id': '0'}
+        url = self.get_url_for_id_request()
+        headers = {self.api_key: CNCHI_VERSION}
+
+        try:
+            req = requests.get(url, headers=headers)
+            req.raise_for_status()
+            info = json.loads(req.json())
+        except Exception as err:
+            logger = logging.getLogger()
+            msg = "Unable to get an Id for this installation. Error: {0}".format(err.args)
+            logger.debug(msg)
+
+        try:
+            self.ip_addr = info['ip']
+            self.install_id = info['id']
+            self.have_install_id = True
+        except (TypeError, KeyError):
+            self.have_install_id = False
+
+        return self.install_id
+
+    @staticmethod
+    def get_bugsnag_api():
+        """ Gets bugsnag API key """
+        config_path = '/etc/cnchi.conf'
+        alt_config_path = '/usr/share/cnchi/data/cnchi.conf'
+        bugsnag_api = None
+
+        if (not os.path.exists(config_path) and
+                os.path.exists(alt_config_path)):
+            config_path = alt_config_path
+
+        if os.path.exists(config_path):
+            with open(config_path) as bugsnag_conf:
+                bugsnag_api = bugsnag_conf.readline().strip()
+
+        return bugsnag_api
+
+    def get_url_for_id_request(self):
+        """ Constructs bugsnag url """
+        build_server = None
+
+        if self.api_key and CNCHI_RELEASE_STAGE != 'development':
+            parts = {
+                1: 'com', 2: 'http', 3: 'hook', 4: 'build',
+                5: 'antergos', 6: 'cnchi', 7: '://'
+            }
+            build_server = '{}{}{}.{}.{}/{}?{}={}'.format(
+                parts[2], parts[7], parts[4], parts[5],
+                parts[1], parts[3], parts[6], self.api_key
+            )
+        return build_server
+
+    @staticmethod
+    def filter_log_lines(log):
+        """ Filter log lines """
+        keep_lines = []
+        look_for = ['[WARNING]', '[ERROR]']
+        log_lines = log.readlines()
+
+        for i, log_line in enumerate(log_lines):
+            for pattern in look_for:
+                if pattern in log_line:
+                    try:
+                        if 10 < i < (len(log_lines) - 10):
+                            keep_lines.extend([log_lines[l]
+                                               for l in range(i - 10, i + 10)])
+                        elif i < 10:
+                            keep_lines.extend([log_lines[l]
+                                               for l in range(0, i)])
+                        elif i > (len(log_lines) - 10):
+                            keep_lines.extend([log_lines[l]
+                                               for l in range(i, len(log_lines))])
+                    except (IndexError, KeyError) as err:
+                        print(err)
+
+        return keep_lines
+
+    def bugsnag_before_notify_callback(self, notification=None):
+        """ Filter unwanted notifications here """
+        if notification is not None:
+            excluded = ["No such interface '/org/freedesktop/UPower'"]
+
+            if any(True for pattern in excluded if pattern in str(notification.exception)):
+                return False
+
+            if self.after_location_screen and not self.have_install_id:
+                self.get_and_save_install_id()
+
+            notification.user = {"id": self.ip_addr,
+                                 "name": self.install_id,
+                                 "install_id": self.install_id}
+
+            logs = [
+                os.path.join(ContextFilter.LOG_FOLDER, '{0}.log'.format(n))
+                for n in ['cnchi', 'cnchi-alpm', 'pacman', 'postinstall']]
+            missing = [f for f in logs if not os.path.exists(f)]
+            if missing:
+                for log in missing:
+                    open(log, 'a').close()
+
+            with open(logs[0], 'r') as cnchi:
+                with open(logs[1], 'r') as pacman:
+                    with open(logs[2], 'r') as postinstall:
+                        log_dict = {'pacman': pacman,
+                                    'postinstall': postinstall}
+                        parse = {
+                            log: [line.strip() for line in log_dict[log]]
+                            for log in log_dict
+                        }
+                        parse['cnchi'] = self.filter_log_lines(cnchi)
+                        notification.add_tab('logs', parse)
+
+            return notification
+        return False
+
+    def send_install_result(self, result):
+        """ Sends install result to bugsnag server (result: str) """
+        try:
+            if self.after_location_screen and not self.have_install_id:
+                self.get_and_save_install_id()
+            build_server = self.get_url_for_id_request()
+            if build_server and self.install_id:
+                url = "{0}&install_id={1}&result={2}"
+                url = url.format(build_server, self.install_id, result)
+                headers = {self.api_key: CNCHI_VERSION}
+                req = requests.get(url, headers=headers)
+                json.loads(req.json())
+        except Exception as ex:
+            logger = logging.getLogger()
+            template = "Can't send install result. An exception of type {0} occured. "
+            template += "Arguments:\n{1!r}"
+            message = template.format(type(ex).__name__, ex.args)
+            logger.error(message)
diff --git a/Cnchi/main_window.py b/Cnchi/main_window.py
new file mode 100644 (file)
index 0000000..d0699d1
--- /dev/null
@@ -0,0 +1,515 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# main_window.py
+#
+# Copyright © 2013-2018 Antergos
+#
+# This file is part of Cnchi.
+#
+# Cnchi is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Cnchi 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.
+#
+# The following additional terms are in effect as per Section 7 of the license:
+#
+# The preservation of all legal notices and author attributions in
+# the material or in the Appropriate Legal Notices displayed
+# by works containing it is required.
+#
+# You should have received a copy of the GNU General Public License
+# along with Cnchi; If not, see <http://www.gnu.org/licenses/>.
+
+
+""" Main Cnchi Window """
+
+import os
+import multiprocessing
+import logging
+
+import config
+import desktop_info
+import info
+import misc.extra as misc
+
+import pages.welcome
+import pages.language
+import pages.location
+import pages.cache
+import pages.check
+import pages.desktop
+import pages.features
+import pages.keymap
+import pages.timezone
+import pages.user_info
+import pages.slides
+import pages.summary
+import pages.mirrors
+import pages.ask
+import pages.automatic
+import pages.alongside
+import pages.advanced
+import pages.zfs
+
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk, Gdk
+
+
+# When testing, no _() is available
+try:
+    _("")
+except NameError as err:
+    def _(message):
+        return message
+
+def atk_set_image_description(widget, description):
+    """ Sets the textual description for a widget that displays image/pixmap
+        information onscreen. """
+    atk_widget = widget.get_accessible()
+    if atk_widget is not None:
+        atk_widget.set_object_description(description)
+
+
+def atk_set_object_description(widget, description):
+    """ Sets the textual description for a widget """
+    atk_widget = widget.get_accessible()
+    if atk_widget is not None:
+        atk_widget.set_image_description(description)
+        # atk_object_set_name
+
+
+class MainWindow(Gtk.ApplicationWindow):
+    """ Cnchi main window """
+
+    def __init__(self, app, cmd_line):
+        Gtk.ApplicationWindow.__init__(self, title="Cnchi", application=app)
+
+        self._main_window_width = 875
+        self._main_window_height = 550
+
+        logging.info("Cnchi installer version %s", info.CNCHI_VERSION)
+
+        self.settings = config.Settings()
+        self.gui_dir = self.settings.get('ui')
+
+        if not os.path.exists(self.gui_dir):
+            cnchi_dir = os.path.join(os.path.dirname(__file__), './')
+            self.settings.set('cnchi', cnchi_dir)
+
+            gui_dir = os.path.join(os.path.dirname(__file__), 'ui/')
+            self.settings.set('ui', gui_dir)
+
+            data_dir = os.path.join(os.path.dirname(__file__), 'data/')
+            self.settings.set('data', data_dir)
+
+            self.gui_dir = self.settings.get('ui')
+
+        # By default, always try to use local /var/cache/pacman/pkg
+        xz_cache = ["/var/cache/pacman/pkg"]
+
+        # Check command line
+        if cmd_line.cache and cmd_line.cache not in xz_cache:
+            xz_cache.append(cmd_line.cache)
+
+        # Log cache dirs
+        for xz_path in xz_cache:
+            logging.debug(
+                "Cnchi will use '%s' as a source for cached xz packages",
+                xz_path)
+
+        # Store cache dirs in config
+        self.settings.set('xz_cache', xz_cache)
+
+        data_dir = self.settings.get('data')
+
+        # For things we are not ready for users to test
+        self.settings.set('hidden', cmd_line.hidden)
+
+        # a11y
+        self.settings.set('a11y', cmd_line.a11y)
+
+        # Set enabled desktops
+        if self.settings.get('hidden'):
+            self.settings.set('desktops', desktop_info.DESKTOPS_DEV)
+        elif self.settings.get('a11y'):
+            self.settings.set('desktops', desktop_info.DESKTOPS_A11Y)
+        else:
+            self.settings.set('desktops', desktop_info.DESKTOPS)
+
+        if cmd_line.environment:
+            my_desktop = cmd_line.environment.lower()
+            if my_desktop in desktop_info.DESKTOPS:
+                self.settings.set('desktop', my_desktop)
+                self.settings.set('desktop_ask', False)
+                logging.debug(
+                    "Cnchi will install the %s desktop environment",
+                    my_desktop)
+
+        self.cnchi_ui = Gtk.Builder()
+        path = os.path.join(self.gui_dir, "cnchi.ui")
+        self.cnchi_ui.add_from_file(path)
+
+        main = self.cnchi_ui.get_object("main")
+        # main.set_property("halign", Gtk.Align.CENTER)
+        self.add(main)
+
+        self.header_ui = Gtk.Builder()
+        path = os.path.join(self.gui_dir, "header.ui")
+        self.header_ui.add_from_file(path)
+        self.header = self.header_ui.get_object("header")
+
+        self.logo = self.header_ui.get_object("logo")
+        path = os.path.join(
+            data_dir, "images", "antergos", "antergos-logo-mini2.png")
+        self.logo.set_from_file(path)
+
+        # To honor our css
+        self.header.set_name("header")
+        self.logo.set_name("logo")
+
+        self.main_box = self.cnchi_ui.get_object("main_box")
+        # self.main_box.set_property("halign", Gtk.Align.CENTER)
+
+        ##self.main_box.set_property('width_request', 800)
+
+        self.progressbar = self.cnchi_ui.get_object("main_progressbar")
+        self.progressbar.set_name('process_progressbar')
+        # a11y
+        self.progressbar.set_can_focus(False)
+
+        self.forward_button = self.header_ui.get_object("forward_button")
+        self.backwards_button = self.header_ui.get_object("backwards_button")
+
+        # atk_set_image_description(self.forward_button, _("Next step"))
+        # atk_set_image_description(self.backwards_button, _("Previous step"))
+        # atk_set_object_description(self.forward_button, _("Next step"))
+        # atk_set_object_description(self.backwards_button, _("Previous step"))
+
+        self.forward_button.set_name('fwd_btn')
+        self.forward_button.set_always_show_image(True)
+
+        self.backwards_button.set_name('bk_btn')
+        self.backwards_button.set_always_show_image(True)
+
+        # a11y
+        if cmd_line.a11y:
+            self.forward_button.set_label(_("Next"))
+            self.backwards_button.set_label(_("Back"))
+
+        # Create a queue. Will be used to report pacman messages
+        # (pacman/pac.py) to the main thread (installation/process.py)
+        self.callback_queue = multiprocessing.JoinableQueue()
+
+        if cmd_line.packagelist:
+            self.settings.set('alternate_package_list', cmd_line.packagelist)
+            logging.info(
+                "Using '%s' file as package list",
+                self.settings.get('alternate_package_list'))
+
+        self.set_titlebar(self.header)
+
+        # Prepare params dict to pass common parameters to all screens
+        self.params = dict()
+        self.params['main_window'] = self
+        self.params['header'] = self.header
+        self.params['gui_dir'] = self.gui_dir
+        self.params['forward_button'] = self.forward_button
+        self.params['backwards_button'] = self.backwards_button
+        self.params['callback_queue'] = self.callback_queue
+        self.params['settings'] = self.settings
+        self.params['main_progressbar'] = self.progressbar
+
+        self.params['checks_are_optional'] = cmd_line.no_check
+        self.params['no_tryit'] = cmd_line.no_tryit
+        self.params['a11y'] = cmd_line.a11y
+
+        # Just load the first two screens (the other ones will be loaded later)
+        # We do this so the user has not to wait for all the screens to be
+        # loaded
+        self.pages = dict()
+        self.pages["welcome"] = pages.welcome.Welcome(self.params)
+
+        if os.path.exists('/home/reborn/.config/openbox'):
+            # In minimal iso, load language screen now
+            self.pages["language"] = pages.language.Language(self.params)
+
+            # Fix bugy Gtk window size when using Openbox
+            self._main_window_width = 750
+            self._main_window_height = 450
+
+        self.connect('delete-event', self.on_exit_button_clicked)
+        self.connect('key-release-event', self.on_key_release)
+
+        self.cnchi_ui.connect_signals(self)
+        self.header_ui.connect_signals(self)
+
+        nil, major, minor = info.CNCHI_VERSION.split('.')
+        name = 'Cnchi '
+        title_string = "{0} {1}.{2}.{3}".format(name, nil, major, minor)
+        self.set_title(title_string)
+        self.header.set_title(title_string)
+        self.header.set_subtitle(_("RebornOS Installer"))
+        self.header.set_show_close_button(True)
+        self.tooltip_string = "{0} {1}.{2}.{3}".format(name, nil, major, minor)
+        self.header.forall(self.header_for_all_callback, self.tooltip_string)
+
+        self.set_geometry()
+
+        # Set window icon
+        icon_path = os.path.join(
+            data_dir,
+            "images",
+            "antergos",
+            "antergos-icon.png")
+        self.set_icon_from_file(icon_path)
+
+        # Set the first page to show
+
+        # If minimal iso is detected, skip the welcome page.
+        if os.path.exists('/home/reborn/.config/openbox'):
+            self.current_page = self.pages["language"]
+            self.settings.set('timezone_start', True)
+        else:
+            self.current_page = self.pages["welcome"]
+
+        self.main_box.add(self.current_page)
+
+        # Use our css file
+        style_provider = Gtk.CssProvider()
+
+        style_css = os.path.join(data_dir, "css", "gtk-style.css")
+
+        with open(style_css, 'rb') as css:
+            css_data = css.read()
+
+        style_provider.load_from_data(css_data)
+
+        Gtk.StyleContext.add_provider_for_screen(
+            Gdk.Screen.get_default(), style_provider,
+            Gtk.STYLE_PROVIDER_PRIORITY_USER
+        )
+
+        # Show main window
+        self.show_all()
+
+        self.current_page.prepare('forwards')
+
+        # Hide backwards button
+        self.backwards_button.hide()
+
+        self.progressbar.set_fraction(0)
+        self.progressbar_step = 0
+
+        # Do not hide progress bar for minimal iso as it would break
+        # the widget alignment on language page.
+        if not os.path.exists('/home/reborn/.config/openbox'):
+            # Hide progress bar
+            self.progressbar.hide()
+
+        self.set_focus(None)
+
+        misc.gtk_refresh()
+
+    def header_for_all_callback(self, widget, _data):
+        """ Show tooltip in header """
+        if isinstance(widget, Gtk.Box):
+            widget.forall(self.header_for_all_callback, self.tooltip_string)
+        elif widget.get_style_context().has_class('title'):
+            widget.set_tooltip_text(self.tooltip_string)
+
+    def load_pages(self):
+        """ Preload all installer pages """
+        if not os.path.exists('/home/reborn/.config/openbox'):
+            self.pages["language"] = pages.language.Language(self.params)
+
+        self.pages["check"] = pages.check.Check(self.params)
+        self.pages["location"] = pages.location.Location(self.params)
+
+        self.pages["timezone"] = pages.timezone.Timezone(self.params)
+
+        if self.settings.get('desktop_ask'):
+            self.pages["keymap"] = pages.keymap.Keymap(self.params)
+            self.pages["desktop"] = pages.desktop.DesktopAsk(self.params)
+            self.pages["features"] = pages.features.Features(self.params)
+        else:
+            self.pages["keymap"] = pages.keymap.Keymap(
+                self.params,
+                next_page='features')
+            self.pages["features"] = pages.features.Features(
+                self.params,
+                prev_page='keymap')
+
+        self.pages["cache"] = pages.cache.Cache(self.params)
+        self.pages["mirrors"] = pages.mirrors.Mirrors(self.params)
+
+        self.pages["installation_ask"] = pages.ask.InstallationAsk(
+            self.params)
+        self.pages["installation_automatic"] = pages.automatic.InstallationAutomatic(
+            self.params)
+
+        if self.settings.get("enable_alongside"):
+            self.pages["installation_alongside"] = pages.alongside.InstallationAlongside(
+                self.params)
+        else:
+            self.pages["installation_alongside"] = None
+
+        self.pages["installation_advanced"] = pages.advanced.InstallationAdvanced(
+            self.params)
+        self.pages["installation_zfs"] = pages.zfs.InstallationZFS(
+            self.params)
+        self.pages["user_info"] = pages.user_info.UserInfo(self.params)
+        self.pages["summary"] = pages.summary.Summary(self.params)
+        self.pages["slides"] = pages.slides.Slides(self.params)
+
+        diff = 2
+        if os.path.exists('/home/reborn/.config/openbox'):
+            # In minimal (openbox) we don't have a welcome screen
+            diff = 3
+
+        num_pages = len(self.pages) - diff
+
+        if num_pages > 0:
+            self.progressbar_step = 1.0 / num_pages
+
+    def set_geometry(self):
+        """ Sets Cnchi window geometry """
+        self.set_position(Gtk.WindowPosition.CENTER)
+        self.set_resizable(False)
+
+        (min_width, natural_width) = self.get_preferred_width()
+        (min_height, natural_height) = self.get_preferred_width()
+        logging.debug("Main window minimal size: %dx%d", min_width, min_height)
+        logging.debug("Main window natural size: %dx%d", natural_width, natural_height)
+        logging.debug("Setting main window size to %dx%d",
+                      self._main_window_width, self._main_window_height)
+
+        self.set_size_request(self._main_window_width,
+                              self._main_window_height)
+        self.set_default_size(self._main_window_width,
+                              self._main_window_height)
+
+        geom = Gdk.Geometry()
+        geom.min_width = self._main_window_width
+        geom.min_height = self._main_window_height
+        geom.max_width = self._main_window_width
+        geom.max_height = self._main_window_height
+        geom.base_width = self._main_window_width
+        geom.base_height = self._main_window_height
+        geom.width_inc = 0
+        geom.height_inc = 0
+
+        hints = (Gdk.WindowHints.MIN_SIZE |
+                 Gdk.WindowHints.MAX_SIZE |
+                 Gdk.WindowHints.BASE_SIZE |
+                 Gdk.WindowHints.RESIZE_INC)
+
+        self.set_geometry_hints(None, geom, hints)
+
+    def on_key_release(self, _widget, event, _data=None):
+        """ Callback called when a key is released """
+        if event.keyval == Gdk.keyval_from_name('Escape'):
+            response = self.confirm_quitting()
+            if response == Gtk.ResponseType.YES:
+                self.on_exit_button_clicked(self)
+                self.destroy()
+
+    def confirm_quitting(self):
+        """ Shows confirmation message before quitting """
+        message = Gtk.MessageDialog(
+            transient_for=self,
+            modal=True,
+            destroy_with_parent=True,
+            message_type=Gtk.MessageType.QUESTION,
+            buttons=Gtk.ButtonsType.YES_NO,
+            text=_("Do you really want to quit the installer?"))
+        response = message.run()
+        message.destroy()
+        return response
+
+    def on_exit_button_clicked(self, _widget, _data=None):
+        """ Quit Cnchi """
+        try:
+            misc.remove_temp_files(self.settings.get('temp'))
+            logging.info("Quiting installer...")
+            for proc in multiprocessing.active_children():
+                proc.terminate()
+            logging.shutdown()
+        except KeyboardInterrupt:
+            pass
+
+    def set_progressbar_step(self, add_value):
+        """ Update progress bar """
+        new_value = self.progressbar.get_fraction() + add_value
+        if new_value > 1:
+            new_value = 1
+        if new_value < 0:
+            new_value = 0
+        self.progressbar.set_fraction(new_value)
+        if new_value > 0:
+            self.progressbar.show()
+        else:
+            self.progressbar.hide()
+
+    def on_forward_button_clicked(self, _widget, _data=None):
+        """ Show next screen """
+        next_page = self.current_page.get_next_page()
+
+        if next_page is not None:
+            # self.logo.hide()
+            if next_page not in self.pages.keys():
+                # Load all pages
+                self.load_pages()
+                self.progressbar_step = 1.0 / (len(self.pages) - 2)
+
+            stored = self.current_page.store_values()
+
+            if stored:
+                self.set_progressbar_step(self.progressbar_step)
+                self.main_box.remove(self.current_page)
+
+                self.current_page = self.pages[next_page]
+
+                if self.current_page is not None:
+                    self.current_page.prepare('forwards')
+                    self.main_box.add(self.current_page)
+                    if self.current_page.get_prev_page() is not None:
+                        # There is a previous page, show back button
+                        self.backwards_button.show()
+                        self.backwards_button.set_sensitive(True)
+                    else:
+                        # We can't go back, hide back button
+                        self.backwards_button.hide()
+                        if self.current_page == "slides":
+                            # Show logo in slides screen
+                            self.logo.show_all()
+
+    def on_backwards_button_clicked(self, _widget, _data=None):
+        """ Show previous screen """
+        prev_page = self.current_page.get_prev_page()
+
+        if prev_page is not None:
+            self.set_progressbar_step(-self.progressbar_step)
+
+            # If we go backwards, don't store user changes
+            # self.current_page.store_values()
+
+            self.main_box.remove(self.current_page)
+
+            self.current_page = self.pages[prev_page]
+
+            if self.current_page is not None:
+                self.current_page.prepare('backwards')
+                self.main_box.add(self.current_page)
+
+                if self.current_page.get_prev_page() is None:
+                    # We're at the first page
+                    self.backwards_button.hide()
+                    self.progressbar.hide()
+                    self.logo.show_all()
diff --git a/Cnchi/metalink.py b/Cnchi/metalink.py
new file mode 100755 (executable)
index 0000000..08e8898
--- /dev/null
@@ -0,0 +1,557 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+#  metalink.py
+#
+#  Parts of code from pm2ml Copyright (C) 2012-2013 Xyne
+#  Copyright © 2013-2018 Antergos
+#
+#  This file is part of Cnchi.
+#
+#  Cnchi is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Cnchi 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.
+#
+#  The following additional terms are in effect as per Section 7 of the license:
+#
+#  The preservation of all legal notices and author attributions in
+#  the material or in the Appropriate Legal Notices displayed
+#  by works containing it is required.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with Cnchi; If not, see <http://www.gnu.org/licenses/>.
+
+
+""" Operations with metalinks """
+
+import logging
+import tempfile
+import os
+
+import hashlib
+import re
+import argparse
+
+from collections import deque
+
+from xml.dom.minidom import getDOMImplementation
+import xml.etree.cElementTree as elementTree
+
+import pyalpm
+
+MAX_URLS = 15
+
+
+def get_info(metalink):
+    """ Reads metalink xml info and returns it """
+
+    # tag = "{urn:ietf:params:xml:ns:metalink}"
+
+    temp_file = tempfile.NamedTemporaryFile(delete=False)
+    temp_file.write(str(metalink).encode('UTF-8'))
+    temp_file.close()
+
+    metalink_info = {}
+    element = {}
+
+    for event, elem in elementTree.iterparse(temp_file.name, events=('start', 'end')):
+        if event == 'start':
+            tag = elem.tag.split('}')[1]
+            if tag == 'file':
+                element['filename'] = elem.attrib['name']
+            elif tag == 'hash':
+                hash_type = elem.attrib['type']
+                try:
+                    element['hash'][hash_type] = elem.text
+                except KeyError:
+                    element['hash'] = {hash_type: elem.text}
+            elif tag == 'url':
+                try:
+                    element['urls'].append(elem.text)
+                except KeyError:
+                    element['urls'] = [elem.text]
+            else:
+                element[tag] = elem.text
+        if event == 'end' and elem.tag.endswith('file'):
+            # Limit to MAX_URLS for each file
+            if len(element['urls']) > MAX_URLS:
+                element['urls'] = element['urls'][:MAX_URLS]
+            key = element['identity']
+            metalink_info[key] = element.copy()
+            element.clear()
+            elem.clear()
+
+    if os.path.exists(temp_file.name):
+        os.remove(temp_file.name)
+
+    return metalink_info
+
+
+def create(alpm, package_name, pacman_conf_file):
+    """ Creates a metalink to download package_name and its dependencies """
+
+    options = ["--conf", pacman_conf_file, "--noconfirm", "--all-deps"]
+
+    if package_name == "databases":
+        options.append("--refresh")
+    else:
+        options.append(package_name)
+
+    download_queue, not_found, missing_deps = build_download_queue(
+        alpm, args=options)
+
+    if not_found:
+        not_found = sorted(not_found)
+        msg = "Can't find these packages: " + ' '.join(not_found)
+        logging.error(msg)
+        return None
+
+    if missing_deps:
+        missing_deps = sorted(missing_deps)
+        msg = "Can't resolve these dependencies: " + ' '.join(missing_deps)
+        logging.error(msg)
+        return None
+
+    if download_queue:
+        metalink = download_queue_to_metalink(download_queue)
+        return metalink
+
+    logging.error("Unable to create download queue for package %s", package_name)
+    return None
+
+# From here comes modified code from pm2ml
+# pm2ml is Copyright (C) 2012-2013 Xyne
+# More info: http://xyne.archlinux.ca/projects/pm2ml
+
+def download_queue_to_metalink(download_queue):
+    """ Converts a download_queue object to a metalink """
+    metalink = Metalink()
+
+    for database, sigs in download_queue.dbs:
+        metalink.add_db(database, sigs)
+
+    for pkg, urls, sigs in download_queue.sync_pkgs:
+        metalink.add_sync_pkg(pkg, urls, sigs)
+
+    return metalink
+
+
+class Metalink():
+    """ Metalink class """
+
+    def __init__(self):
+        self.doc = getDOMImplementation().createDocument(
+            None, "metalink", None)
+        self.doc.documentElement.setAttribute(
+            'xmlns', "urn:ietf:params:xml:ns:metalink")
+        self.files = self.doc.documentElement
+
+    # def __del__(self):
+    #    self.doc.unlink()
+
+    def __str__(self):
+        """ Get a string representation of a metalink """
+        return re.sub(
+            r'(?<=>)\n\s*([^\s<].*?)\s*\n\s*',
+            r'\1',
+            self.doc.toprettyxml(indent=' ')
+        )
+
+    def add_urls(self, element, urls):
+        """Add URL elements to the given element."""
+        for url in urls:
+            url_tag = self.doc.createElement('url')
+            element.appendChild(url_tag)
+            url_val = self.doc.createTextNode(url)
+            url_tag.appendChild(url_val)
+
+    def add_sync_pkg(self, pkg, urls, sigs=False):
+        """Add a sync db package."""
+        file_ = self.doc.createElement("file")
+        file_.setAttribute("name", pkg.filename)
+        self.files.appendChild(file_)
+        for tag, db_attr, attrs in (
+                ('identity', 'name', ()),
+                ('size', 'size', ()),
+                ('version', 'version', ()),
+                ('description', 'desc', ()),
+                ('hash', 'sha256sum', (('type', 'sha256'),)),
+                ('hash', 'md5sum', (('type', 'md5'),))):
+            tag = self.doc.createElement(tag)
+            file_.appendChild(tag)
+            val = self.doc.createTextNode(str(getattr(pkg, db_attr)))
+            tag.appendChild(val)
+            for key, val in attrs:
+                tag.setAttribute(key, val)
+        urls = list(urls)
+        self.add_urls(file_, urls)
+        if sigs:
+            self.add_file(pkg.filename + '.sig', (u + '.sig' for u in urls))
+
+    def add_file(self, name, urls):
+        """Add a signature file."""
+        file_ = self.doc.createElement("file")
+        file_.setAttribute("name", name)
+        self.files.appendChild(file_)
+        self.add_urls(file_, urls)
+
+    def add_db(self, database, sigs=False):
+        """Add a sync db."""
+        file_ = self.doc.createElement("file")
+        name = database.name + '.db'
+        file_.setAttribute("name", name)
+        self.files.appendChild(file_)
+        urls = list(os.path.join(url, database.name + '.db')
+                    for url in database.servers)
+        self.add_urls(file_, urls)
+        if sigs:
+            self.add_file(name + '.sig', (u + '.sig' for u in urls))
+
+
+class PkgSet():
+    """ Represents a set of packages """
+
+    def __init__(self, pkgs=None):
+        """ Init our internal self.pkgs dict with all given packages in pkgs """
+
+        self.pkgs = dict()
+        if pkgs:
+            for pkg in pkgs:
+                self.pkgs[pkg.name] = pkg
+
+    def __repr__(self):
+        return 'PkgSet({0})'.format(repr(self.pkgs))
+
+    def add(self, pkg):
+        """ Adds package info to the set """
+        self.pkgs[pkg.name] = pkg
+
+    def __and__(self, other):
+        new = PkgSet(set(self.pkgs.values()) & set(other.pkgs.values()))
+        return new
+
+    def __iand__(self, other):
+        self.pkgs = self.__and__(other).pkgs
+        return self
+
+    def __or__(self, other):
+        copy = PkgSet(list(self.pkgs.values()))
+        return copy.__ior__(other)
+
+    def __ior__(self, other):
+        self.pkgs.update(other.pkgs)
+        return self
+
+    def __contains__(self, pkg):
+        return pkg.name in self.pkgs
+
+    def __iter__(self):
+        for value in self.pkgs.values():
+            yield value
+
+    def __len__(self):
+        return len(self.pkgs)
+
+
+class DownloadQueue():
+    """ Represents a download queue """
+
+    def __init__(self):
+        self.dbs = list()
+        self.sync_pkgs = list()
+
+    def __bool__(self):
+        return bool(self.dbs or self.sync_pkgs)
+
+    def __nonzero__(self):
+        return self.dbs or self.sync_pkgs
+
+    def add_db(self, database, sigs=False):
+        """ Adds db info and signatures to the queue """
+        self.dbs.append((database, sigs))
+
+    def add_sync_pkg(self, pkg, urls, sigs=False):
+        """ Adds package and its urls to the queue """
+        self.sync_pkgs.append((pkg, urls, sigs))
+
+
+def parse_args(args):
+    """ Parse arguments to build_download_queue function
+        These arguments mimic pacman ones """
+    parser = argparse.ArgumentParser()
+
+    parser.add_argument('pkgs', nargs='*', default=[], metavar='<pkgname>',
+                        help='Packages or groups to download.')
+    parser.add_argument('--all-deps', action='store_true', dest='alldeps',
+                        help='Include all dependencies even if they are already installed.')
+    parser.add_argument('-c', '--conf', metavar='<path>', default='/etc/pacman.conf', dest='conf',
+                        help='Use a different pacman.conf file.')
+    parser.add_argument('--noconfirm', action='store_true', dest='noconfirm',
+                        help='Suppress user prompts.')
+    parser.add_argument('-d', '--nodeps', action='store_true', dest='nodeps',
+                        help='Skip dependencies.')
+    parser.add_argument('--needed', action='store_true', dest='needed',
+                        help='Skip packages if they already exist in the cache.')
+    help_msg = '''Include signature files for repos with optional and required SigLevels.
+        Pass this flag twice to attempt to download signature for all databases and packages.'''
+    parser.add_argument('-s', '--sigs', action='count', default=0, dest='sigs',
+                        help=help_msg)
+    parser.add_argument('-y', '--databases', '--refresh', action='store_true', dest='db',
+                        help='Download databases.')
+
+    return parser.parse_args(args)
+
+
+def get_antergos_repo_pkgs(alpm_handle):
+    """ Returns pkgs from Antergos groups (mate, mate-extra) and
+        the antergos db info """
+
+    antdb = None
+    for database in alpm_handle.get_syncdbs():
+        if database.name == 'Reborn-OS':
+            antdb = database
+            break
+
+    if not antdb:
+        logging.error("Cannot sync Antergos repository database!")
+        return {}, None
+
+    group_names = ['mate', 'mate-extra']
+    groups = []
+    for group_name in group_names:
+        group = antdb.read_grp(group_name)
+        if not group:
+            # Group does not exist
+            group = ['None', []]
+        groups.append(group)
+
+    repo_pkgs = {
+        pkg for group in groups
+        for pkg in group[1] if group}
+
+    return repo_pkgs, antdb
+
+
+def resolve_deps(alpm_handle, other, alldeps):
+    """ Resolve dependencies """
+    missing_deps = []
+    queue = deque(other)
+    local_cache = alpm_handle.get_localdb().pkgcache
+    syncdbs = alpm_handle.get_syncdbs()
+    seen = set(queue)
+    while queue:
+        pkg = queue.popleft()
+        for dep in pkg.depends:
+            if pyalpm.find_satisfier(local_cache, dep) is None or alldeps:
+                for database in syncdbs:
+                    prov = pyalpm.find_satisfier(database.pkgcache, dep)
+                    if prov:
+                        other.add(prov)
+                        if prov.name not in seen:
+                            seen.add(prov.name)
+                            queue.append(prov)
+                        break
+                else:
+                    missing_deps.append(dep)
+    return other, missing_deps
+
+
+def create_package_set(requested, ant_repo_pkgs, antdb, alpm_handle):
+    """ Create package set from requested set """
+
+    found = set()
+    other = PkgSet()
+
+    for pkg in requested:
+        for database in alpm_handle.get_syncdbs():
+            # if pkg is in antergos repo, fetch it from it (instead of another repo)
+            # pkg should be sourced from the antergos repo only.
+            if antdb and pkg in ant_repo_pkgs and database.name != 'Reborn-OS':
+                database = antdb
+
+            syncpkg = database.get_pkg(pkg)
+
+            if syncpkg:
+                other.add(syncpkg)
+                break
+            else:
+                syncgrp = database.read_grp(pkg)
+                if syncgrp:
+                    found.add(pkg)
+                    #other_grp |= PkgSet(syncgrp[1])
+                    other |= PkgSet(syncgrp[1])
+                    break
+
+    return found, other
+
+def build_download_queue(alpm, args=None):
+    """ Function to build a download queue.
+        Needs a pkgname in args """
+
+    pargs = parse_args(args)
+
+    requested = set(pargs.pkgs)
+
+    handle = alpm.get_handle()
+    conf = alpm.get_config()
+
+    missing_deps = list()
+
+    ant_repo_pkgs, antdb = get_antergos_repo_pkgs(handle)
+
+    if not antdb:
+        logging.error("Cannot load antergos repository database")
+        return None, None, None
+
+    found, other = create_package_set(requested, ant_repo_pkgs, antdb, handle)
+
+    # foreign_names = requested - set(x.name for x in other)
+
+    # Resolve dependencies.
+    if other and not pargs.nodeps:
+        other, missing_deps = resolve_deps(handle, other, pargs.alldeps)
+
+    found |= set(other.pkgs)
+    not_found = requested - found
+    if pargs.needed:
+        other = PkgSet(list(check_cache(conf, other)))
+
+    # Create download queue
+    download_queue = DownloadQueue()
+
+    # Add databases (and their signature)
+    if pargs.db:
+        for database in handle.get_syncdbs():
+            try:
+                siglevel = conf[database.name]['SigLevel'].split()[0]
+            except KeyError:
+                siglevel = None
+            download_sig = needs_sig(siglevel, pargs.sigs, 'Database')
+            download_queue.add_db(database, download_sig)
+
+    # Add packages (pkg, url, signature)
+    for pkg in other:
+        try:
+            siglevel = conf[pkg.db.name]['SigLevel'].split()[0]
+        except KeyError:
+            siglevel = None
+        download_sig = needs_sig(siglevel, pargs.sigs, 'Package')
+
+        urls = []
+        server_urls = list(pkg.db.servers)
+        for server_url in server_urls:
+            url = os.path.join(server_url, pkg.filename)
+            urls.append(url)
+
+        # Limit to MAX_URLS url
+        while len(urls) > MAX_URLS:
+            urls.pop()
+
+        download_queue.add_sync_pkg(pkg, urls, download_sig)
+
+    return download_queue, not_found, missing_deps
+
+
+def get_checksum(path, typ):
+    """ Returns checksum of a file """
+    new_hash = hashlib.new(typ)
+    block_size = new_hash.block_size
+    try:
+        with open(path, 'rb') as myfile:
+            buf = myfile.read(block_size)
+            while buf:
+                new_hash.update(buf)
+                buf = myfile.read(block_size)
+        return new_hash.hexdigest()
+    except FileNotFoundError:
+        return -1
+    except IOError as io_error:
+        logging.error(io_error)
+
+
+def check_cache(conf, pkgs):
+    """ Checks package checksum in cache """
+    for pkg in pkgs:
+        for cache in conf.options['CacheDir']:
+            fpath = os.path.join(cache, pkg.filename)
+            for checksum in ('sha256', 'md5'):
+                real_checksum = get_checksum(fpath, checksum)
+                correct_checksum = getattr(pkg, checksum + 'sum')
+                if real_checksum is None or real_checksum != correct_checksum:
+                    yield pkg
+                    break
+            else:
+                continue
+            break
+
+
+def needs_sig(siglevel, insistence, prefix):
+    """ Determines if a signature should be downloaded.
+        The siglevel is the pacman.conf SigLevel for the given repo.
+        The insistence is an integer. Anything below 1 will return false,
+        anything above 1 will return true, and 1 will check if the
+        siglevel is required or optional.
+        The prefix is either "Database" or "Package". """
+
+    if insistence > 1:
+        return True
+    elif insistence == 1 and siglevel:
+        for sl_type in ('Required', 'Optional'):
+            if siglevel == sl_type or siglevel == prefix + sl_type:
+                return True
+    return False
+
+
+def test_module():
+    """ Module test function """
+    import gettext
+
+    _ = gettext.gettext
+
+    formatter = logging.Formatter(
+        '[%(asctime)s] [%(module)s] %(levelname)s: %(message)s',
+        "%Y-%m-%d %H:%M:%S.%f")
+    logger = logging.getLogger()
+    logger.setLevel(logging.DEBUG)
+    stream_handler = logging.StreamHandler()
+    stream_handler.setLevel(logging.DEBUG)
+    stream_handler.setFormatter(formatter)
+    logger.addHandler(stream_handler)
+
+    #import gc
+    #import pprint
+    import sys
+    cnchi_path = "/usr/share/cnchi"
+    sys.path.append(cnchi_path)
+    sys.path.append(os.path.join(cnchi_path, "src"))
+    import pacman.pac as pac
+
+    pacman = pac.Pac(
+        conf_path="/etc/pacman.conf",
+        callback_queue=None)
+
+    print("Creating metalink...")
+    meta4 = create(
+        alpm=pacman,
+        #package_name="ipw2200-fw",
+        package_name="base-devel",
+        pacman_conf_file="/etc/pacman.conf")
+    #print(meta4)
+    #print('=' * 20)
+    if meta4:
+        print(get_info(meta4))
+    # print(get_info(meta4)['ipw2200-fw']['urls'])
+
+    pacman.release()
+    del pacman
+
+
+# Test case
+if __name__ == '__main__':
+    test_module()
diff --git a/Cnchi/mirrors.py b/Cnchi/mirrors.py
new file mode 100755 (executable)
index 0000000..5932b77
--- /dev/null
@@ -0,0 +1,474 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# mirrors.py
+#
+# Copyright © 2013-2018 Antergos
+#
+# This file is part of Cnchi.
+#
+# Cnchi is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Cnchi 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.
+#
+# The following additional terms are in effect as per Section 7 of the license:
+#
+# The preservation of all legal notices and author attributions in
+# the material or in the Appropriate Legal Notices displayed
+# by works containing it is required.
+#
+# You should have received a copy of the GNU General Public License
+# along with Cnchi; If not, see <http://www.gnu.org/licenses/>.
+
+
+""" Let advanced users manage mirrorlist files """
+
+import logging
+import multiprocessing
+import shutil
+
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk, Gdk, GObject
+
+try:
+    gi.require_foreign("cairo")
+except ImportError:
+    print("No pycairo integration")
+
+import cairo
+from pages.gtkbasebox import GtkBaseBox
+from rank_mirrors import RankMirrors
+
+if __name__ == '__main__':
+    import sys
+    sys.path.append('/usr/share/cnchi/src')
+
+# When testing, no _() is available
+try:
+    _("")
+except NameError as err:
+    def _(message):
+        return message
+
+
+class MirrorListBoxRow(Gtk.ListBoxRow):
+    """ Represents a mirror """
+    def __init__(self, url, active, switch_cb, drag_cbs):
+        super(Gtk.ListBoxRow, self).__init__()
+        #self.data = data
+        # self.add(Gtk.Label(data))
+
+        self.data = url
+
+        box = Gtk.Box(spacing=20)
+
+        self.handle = Gtk.EventBox.new()
+        self.handle.add(Gtk.Image.new_from_icon_name("open-menu-symbolic", 1))
+        box.pack_start(self.handle, False, False, 0)
+
+        # Add mirror url label
+        self.label = Gtk.Label.new()
+        self.label.set_halign(Gtk.Align.START)
+        self.label.set_justify(Gtk.Justification.LEFT)
+        self.label.set_name(url)
+        # Only show site address
+        url_parts = url.split('/')
+        text_url = url_parts[0] + "//" + url_parts[2]
+        self.label.set_text(text_url)
+        box.pack_start(self.label, False, True, 0)
+
+        # Add mirror switch
+        self.switch = Gtk.Switch.new()
+        self.switch.set_name("switch_" + url)
+        self.switch.set_property('margin_top', 2)
+        self.switch.set_property('margin_bottom', 2)
+        self.switch.set_property('margin_end', 10)
+        self.switch.connect("notify::active", switch_cb)
+        self.switch.set_active(active)
+        box.pack_end(self.switch, False, False, 0)
+
+        self.add(box)
+
+        self.set_selectable(True)
+
+        # Drag and drop
+        # Source
+        self.handle.drag_source_set(
+            Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.MOVE)
+        self.handle.drag_source_add_text_targets()
+        self.handle.connect("drag-begin", drag_cbs['drag-begin'])
+        self.handle.connect("drag-data-get", drag_cbs['drag-data-get'])
+        #self.handle.connect("drag-data-delete", self.drag_data_delete)
+        #self.handle.connect("drag-end", self.drag_end)
+
+        # Destination
+        self.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.MOVE)
+        self.drag_dest_add_text_targets()
+        self.connect("drag-data-received", drag_cbs['drag-data-received'])
+        #self.connect("drag-motion", self.drag_motion);
+        #self.connect("drag-crop", self.drag_crop);
+
+    def is_active(self):