├── .fmf └── version ├── initial_setup ├── gui │ ├── spokes │ │ ├── __init__.py │ │ ├── eula.py │ │ └── eula.glade │ ├── __init__.py │ ├── hubs │ │ ├── __init__.py │ │ ├── initial_setup_hub.py │ │ └── initial_setup.glade │ └── gui.py ├── tui │ ├── spokes │ │ ├── __init__.py │ │ └── eula.py │ ├── __init__.py │ ├── hubs │ │ ├── __init__.py │ │ └── initial_setup_hub.py │ └── tui.py ├── i18n.py ├── product.py ├── initial_setup_log.py ├── common.py └── __init__.py ├── scripts ├── run-gui-backend ├── reconfiguration-mode-enabled ├── run-gui-backend.guixorg ├── initial-setup-text ├── s390 │ ├── initial-setup.csh │ └── initial-setup.sh ├── initial-setup-graphical ├── run-gui-backend.guiweston ├── run-initial-setup ├── firstboot-windowmanager └── makeupdates ├── tests ├── pylint │ ├── pylint-false-positives │ ├── pylint-one.sh │ ├── intl.py │ ├── runpylint.sh │ └── pointless-override.py ├── testenv.sh ├── smoke.fmf └── lib │ └── testlib.sh ├── .gitignore ├── .pep8speaks.yml ├── plans └── smoke.fmf ├── MANIFEST.in ├── pam └── initial-setup ├── setup.py ├── systemd ├── initial-setup-reconfiguration.service └── initial-setup.service ├── branch-config.mk ├── .packit.yml ├── data └── 10-initial-setup.conf ├── pyproject.toml ├── po ├── Makefile └── initial-setup.pot ├── README.rst ├── Makefile ├── COPYING └── initial-setup.spec /.fmf/version: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /initial_setup/gui/spokes/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /initial_setup/tui/spokes/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/run-gui-backend: -------------------------------------------------------------------------------- 1 | run-gui-backend.guiweston -------------------------------------------------------------------------------- /initial_setup/tui/__init__.py: -------------------------------------------------------------------------------- 1 | from .tui import InitialSetupTextUserInterface 2 | -------------------------------------------------------------------------------- /initial_setup/gui/__init__.py: -------------------------------------------------------------------------------- 1 | from .gui import InitialSetupGraphicalUserInterface 2 | -------------------------------------------------------------------------------- /initial_setup/gui/hubs/__init__.py: -------------------------------------------------------------------------------- 1 | from .initial_setup_hub import InitialSetupMainHub 2 | -------------------------------------------------------------------------------- /initial_setup/tui/hubs/__init__.py: -------------------------------------------------------------------------------- 1 | from .initial_setup_hub import InitialSetupMainHub 2 | -------------------------------------------------------------------------------- /tests/pylint/pylint-false-positives: -------------------------------------------------------------------------------- 1 | ^initial_setup/__main__.py:[[:digit:]]+: \[E1101\(no-member\), \] Instance of 'AnacondaKSHandler' has no '.*' member$ 2 | -------------------------------------------------------------------------------- /scripts/reconfiguration-mode-enabled: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "The /.unconfigured file has been detected, enabling Initial Setup in reconfiguration mode." | systemd-cat -t initial-setup -p 6 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .ropeproject 2 | *.pyc 3 | *.pyo 4 | ~.~ 5 | .#* 6 | po/*.po 7 | po/*.gmo 8 | po/tmp 9 | tests/pylint/.pylint.d 10 | dist/ 11 | # created by `make test` call 12 | build/ 13 | ChangeLog 14 | -------------------------------------------------------------------------------- /.pep8speaks.yml: -------------------------------------------------------------------------------- 1 | # Repository configuration for pep8speaks GitHup App 2 | 3 | scanner: 4 | linter: pycodestyle 5 | 6 | pycodestyle: # Same as scanner.linter value. Other option is flake8 7 | max-line-length: 99 # Default is 79 in PEP 8 8 | -------------------------------------------------------------------------------- /scripts/run-gui-backend.guixorg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## Runs the GUI program from $@ in Xorg with one of the supported window managers 4 | 5 | WINDOWMANAGER_SCRIPT="/usr/libexec/initial-setup/firstboot-windowmanager" 6 | /bin/xinit ${WINDOWMANAGER_SCRIPT} $@ -- /bin/Xorg :9 -ac -nolisten tcp 7 | -------------------------------------------------------------------------------- /plans/smoke.fmf: -------------------------------------------------------------------------------- 1 | summary: Basic smoke test 2 | discover: 3 | how: fmf 4 | provision: 5 | how: container 6 | image: fedora:rawhide 7 | prepare: 8 | how: install 9 | copr: '@rhinstaller/Anaconda' 10 | package: anaconda 11 | missing: skip 12 | execute: 13 | how: tmt 14 | -------------------------------------------------------------------------------- /tests/testenv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -z "$top_srcdir" ]; then 4 | echo "*** top_srcdir must be set" 5 | exit 1 6 | fi 7 | 8 | # If no top_builddir is set, use top_srcdir 9 | : "${top_builddir:=$top_srcdir}" 10 | 11 | export PYTHONPATH 12 | export top_srcdir 13 | export top_builddir 14 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include COPYING 2 | include README.rst 3 | include Makefile 4 | include branch-config.mk 5 | recursive-include systemd * 6 | recursive-include scripts * 7 | recursive-include po *.po *.pot Makefile 8 | recursive-include initial_setup *.py *.glade 9 | recursive-exclude modules *.pyc *.pyo 10 | -------------------------------------------------------------------------------- /pam/initial-setup: -------------------------------------------------------------------------------- 1 | #%PAM-1.0 2 | auth sufficient pam_permit.so 3 | account sufficient pam_permit.so 4 | password sufficient pam_permit.so 5 | session required pam_loginuid.so 6 | -session optional pam_keyinit.so revoke 7 | -session optional pam_limits.so 8 | session required pam_systemd.so 9 | -------------------------------------------------------------------------------- /scripts/initial-setup-text: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import os 4 | 5 | from initial_setup import InitialSetup, InitialSetupError 6 | 7 | is_instance = InitialSetup(gui_mode=False) 8 | 9 | try: 10 | is_instance.run() 11 | except InitialSetupError: 12 | # Initial Setup using with text interface apparently failed 13 | exit(1) 14 | 15 | # check if we should reboot the machine instead of continuing to 16 | # the login screen 17 | if is_instance.reboot_on_quit: 18 | os.system("reboot") 19 | 20 | # Initial Setup using with text interface completed successfully 21 | exit(0) 22 | -------------------------------------------------------------------------------- /tests/smoke.fmf: -------------------------------------------------------------------------------- 1 | summary: Smoke test 2 | description: Import project main module in python. 3 | contact: Jiri Konecny 4 | duration: 10m 5 | framework: shell 6 | path: / 7 | test: python3 -c "import initial_setup" 8 | 9 | # Make this `path` adjustment only when we are in the Packit environement. 10 | # Packit will install RPM of this package and we should test on that. By 11 | # changing the `path` attribute we are changing also local PYTHONPATH, so 12 | # we won't be testing local ``initial_setup`` directory. 13 | adjust: 14 | path: /data 15 | when: trigger == code 16 | -------------------------------------------------------------------------------- /scripts/s390/initial-setup.csh: -------------------------------------------------------------------------------- 1 | # initial-setup.csh 2 | 3 | set IS_EXEC = /usr/libexec/initial-setup/initial-setup-text 4 | set IS_UNIT = initial-setup.service 5 | 6 | # check if the Initial Setup unit is enabled and the executable is available 7 | if ( { systemctl -q is-enabled $IS_UNIT } && -x $IS_EXEC ) then 8 | # check if we're not on 3270 terminal and root 9 | if (( `/sbin/consoletype` == "pty" ) && ( `/usr/bin/id -u` == 0 )) then 10 | $IS_EXEC --no-stdout-log --no-multi-tty && systemctl -q is-enabled $IS_UNIT && systemctl -q disable $IS_UNIT && systemctl -q stop $IS_UNIT 11 | endif 12 | endif 13 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Minimal setup.py for dynamic configuration that pyproject.toml cannot handle. 4 | Most configuration is now in pyproject.toml. 5 | """ 6 | 7 | import os 8 | from setuptools import setup 9 | 10 | # Handle s390 architecture-specific files dynamically 11 | data_files = [] 12 | if os.uname()[4].startswith('s390'): 13 | data_files.extend([ 14 | ('etc/profile.d', ['scripts/s390/initial-setup.sh']), 15 | ('etc/profile.d', ['scripts/s390/initial-setup.csh']), 16 | ]) 17 | 18 | # Call setup with only the dynamic parts 19 | setup( 20 | data_files=data_files, 21 | ) 22 | -------------------------------------------------------------------------------- /scripts/s390/initial-setup.sh: -------------------------------------------------------------------------------- 1 | # firstboot.sh 2 | 3 | IS_EXEC=/usr/libexec/initial-setup/initial-setup-text 4 | IS_UNIT=initial-setup.service 5 | 6 | IS_AVAILABLE=0 7 | # check if the Initial Setup unit is enabled and the executable is available 8 | systemctl -q is-enabled $IS_UNIT && [ -f $IS_EXEC ] && IS_AVAILABLE=1 9 | if [ $IS_AVAILABLE -eq 1 ]; then 10 | # check if we're not on 3270 terminal and root 11 | if [ $(/sbin/consoletype) = "pty" ] && [ $EUID -eq 0 ]; then 12 | $IS_EXEC --no-stdout-log --no-multi-tty && systemctl -q is-enabled $IS_UNIT && systemctl -q disable $IS_UNIT && systemctl -q stop $IS_UNIT 13 | fi 14 | fi 15 | -------------------------------------------------------------------------------- /scripts/initial-setup-graphical: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import os 4 | 5 | from initial_setup import InitialSetup, InitialSetupError 6 | 7 | # Doesn't work with native Wayland yet 8 | os.environ["GDK_BACKEND"] = "x11" 9 | 10 | is_instance = InitialSetup(gui_mode=True) 11 | 12 | try: 13 | is_instance.run() 14 | except InitialSetupError: 15 | # Initial Setup with graphical interface apparently failed 16 | exit(1) 17 | 18 | # check if we should reboot the machine instead of continuing to 19 | # the login screen 20 | if is_instance.reboot_on_quit: 21 | os.system("reboot") 22 | 23 | # Initial Setup with graphical interface completed successfully 24 | exit(0) 25 | -------------------------------------------------------------------------------- /scripts/run-gui-backend.guiweston: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## Runs the GUI program from $@ in Weston 4 | 5 | CONFIG_FILE=$(mktemp --suffix="-wl-weston-firstboot-ini") 6 | RUN_SCRIPT=$(mktemp --suffix="-wl-weston-firstboot-run") 7 | EXIT_CODE_SAVE=$(mktemp --suffix="-wl-weston-firstboot-exit") 8 | 9 | cat > ${CONFIG_FILE} << EOF 10 | [core] 11 | shell=kiosk 12 | xwayland=true 13 | 14 | [autolaunch] 15 | path=${RUN_SCRIPT} 16 | watch=true 17 | EOF 18 | 19 | cat > ${RUN_SCRIPT} << EOF 20 | #!/bin/sh 21 | $@ 22 | echo $? > ${EXIT_CODE_SAVE} 23 | EOF 24 | 25 | chmod +x ${RUN_SCRIPT} 26 | 27 | weston --config=${CONFIG_FILE} --socket=wl-firstboot-0 28 | exit_code=$(< ${EXIT_CODE_SAVE}) 29 | 30 | rm ${CONFIG_FILE} ${RUN_SCRIPT} ${EXIT_CODE_SAVE} 31 | exit $exit_code 32 | -------------------------------------------------------------------------------- /systemd/initial-setup-reconfiguration.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Initial Setup reconfiguration mode trigger service 3 | After=livesys.service plymouth-quit-wait.service 4 | After=systemd-vconsole-setup.service 5 | # getty-pre.target is a pasive target, we need to request it before we can use it 6 | Wants=getty-pre.target 7 | # prevent getty from running on any consoles before we are done 8 | Before=getty-pre.target 9 | Before=display-manager.service 10 | Before=initial-setup.service 11 | Conflicts=plymouth-quit-wait.service 12 | ConditionKernelCommandLine=!rd.live.image 13 | ConditionPathExists=/.unconfigured 14 | Requires=initial-setup.service 15 | 16 | [Service] 17 | Type=oneshot 18 | TimeoutSec=0 19 | RemainAfterExit=yes 20 | ExecStart=/usr/libexec/initial-setup/reconfiguration-mode-enabled 21 | TimeoutSec=0 22 | RemainAfterExit=no 23 | 24 | [Install] 25 | WantedBy=graphical.target 26 | WantedBy=multi-user.target 27 | -------------------------------------------------------------------------------- /initial_setup/tui/hubs/initial_setup_hub.py: -------------------------------------------------------------------------------- 1 | from pyanaconda.ui.tui.hubs import TUIHub 2 | from pyanaconda.ui.tui.spokes import NormalSpoke as TUI_spoke_class 3 | from initial_setup import product 4 | from initial_setup import common 5 | from initial_setup.i18n import _, N_ 6 | 7 | __all__ = ["InitialSetupMainHub"] 8 | 9 | 10 | class InitialSetupMainHub(TUIHub): 11 | categories = ["user", "localization"] 12 | 13 | prod_title = product.get_product_title() 14 | if prod_title: 15 | title = N_("Initial setup of %(product)s") % {"product": prod_title} 16 | else: 17 | title = N_("Initial setup") 18 | 19 | @staticmethod 20 | def get_screen_id(): 21 | """Return a unique id of this UI screen.""" 22 | return "initial-setup-summary" 23 | 24 | def __init__(self, *args): 25 | TUIHub.__init__(self, *args) 26 | 27 | def _collectCategoriesAndSpokes(self): 28 | return common.collectCategoriesAndSpokes(self, TUI_spoke_class) 29 | -------------------------------------------------------------------------------- /branch-config.mk: -------------------------------------------------------------------------------- 1 | # Makefile include for branch specific configuration settings 2 | # 3 | # Copyright (C) 2020 Red Hat, Inc. 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, but 11 | # WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | # General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see 17 | # . 18 | # 19 | # Store a branch specific configuration here to avoid dealing with 20 | # conflicts on multiple places. 21 | 22 | 23 | # Directory in localization repository specific for this branch. 24 | L10N_DIR ?= master 25 | -------------------------------------------------------------------------------- /.packit.yml: -------------------------------------------------------------------------------- 1 | specfile_path: initial-setup.spec 2 | upstream_package_name: initial-setup 3 | upstream_tag_template: r{version}-1 4 | srpm_build_deps: 5 | - make 6 | - python3-build 7 | actions: 8 | create-archive: 9 | - "make BUILD_ARGS=sdist archive" 10 | - 'bash -c "cp dist/*.tar.gz ."' 11 | - 'bash -c "ls -1 initial-setup-*.tar.gz"' 12 | jobs: 13 | - job: tests 14 | trigger: pull_request 15 | metadata: 16 | targets: 17 | - fedora-rawhide 18 | 19 | - job: copr_build 20 | trigger: pull_request 21 | metadata: 22 | targets: 23 | - fedora-eln 24 | 25 | - job: copr_build 26 | trigger: commit 27 | metadata: 28 | targets: 29 | - fedora-rawhide 30 | - fedora-eln 31 | branch: master 32 | owner: "@rhinstaller" 33 | project: Anaconda 34 | preserve_project: True 35 | 36 | - job: copr_build 37 | trigger: commit 38 | metadata: 39 | targets: 40 | - fedora-latest 41 | branch: master 42 | owner: "@rhinstaller" 43 | project: Anaconda-devel 44 | preserve_project: True 45 | -------------------------------------------------------------------------------- /data/10-initial-setup.conf: -------------------------------------------------------------------------------- 1 | # Initial Setup configuration file 2 | # Here are changes in the Anaconda configuration that are specific for the Initial Setup 3 | 4 | 5 | [Anaconda] 6 | # List of enabled kickstart modules. 7 | activatable_modules = 8 | org.fedoraproject.Anaconda.Modules.Timezone 9 | org.fedoraproject.Anaconda.Modules.Network 10 | org.fedoraproject.Anaconda.Modules.Localization 11 | org.fedoraproject.Anaconda.Modules.Security 12 | org.fedoraproject.Anaconda.Modules.Users 13 | org.fedoraproject.Anaconda.Modules.Services 14 | 15 | # Make sure modules that are not supported on installed 16 | # system are not run. 17 | forbidden_modules = 18 | org.fedoraproject.Anaconda.Modules.Subscription 19 | org.fedoraproject.Anaconda.Modules.Storage 20 | org.fedoraproject.Anaconda.Modules.Payloads 21 | 22 | optional_modules = 23 | org.fedoraproject.Anaconda.Addons.* 24 | 25 | [Installation System] 26 | # Type of the installation system. 27 | # FIXME: This is a temporary solution. 28 | type = BOOTED_OS 29 | 30 | 31 | [Installation Target] 32 | # A path to the physical root of the target. 33 | physical_root = / 34 | 35 | # A path to the system root of the target. 36 | system_root = / 37 | -------------------------------------------------------------------------------- /tests/pylint/pylint-one.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # $1 -- python source to run pylint on 4 | # 5 | 6 | if [ $# -lt 1 ]; then 7 | # no source, just exit 8 | exit 1 9 | fi 10 | 11 | file_suffix="$(eval echo \$$#|sed s?/?_?g)" 12 | 13 | pylint_output="$(pylint \ 14 | --msg-template='{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}' \ 15 | -r n --disable=C,R --rcfile=/dev/null \ 16 | --dummy-variables-rgx=_ \ 17 | --ignored-classes=Popen,TransactionSet \ 18 | --defining-attr-methods=__init__,_grabObjects,initialize,reset,start,setUp \ 19 | --load-plugins=intl,pointless-override \ 20 | $DISABLED_WARN_OPTIONS \ 21 | $DISABLED_ERR_OPTIONS \ 22 | $NON_STRICT_OPTIONS "$@" 2>&1 | \ 23 | egrep -v -f "$FALSE_POSITIVES" \ 24 | )" 25 | 26 | # I0011 is the informational "Locally disabling ...." message 27 | if [ -n "$(echo "$pylint_output" | fgrep -v '************* Module ' |\ 28 | grep -v '^I0011:')" ]; then 29 | # Replace the Module line with the actual filename 30 | pylint_output="$(echo "$pylint_output" | sed "s|\* Module .*|* Module $(eval echo \$$#)|")" 31 | echo "$pylint_output" > pylint-out_$file_suffix 32 | touch "pylint-$file_suffix-failed" 33 | fi 34 | -------------------------------------------------------------------------------- /initial_setup/i18n.py: -------------------------------------------------------------------------------- 1 | # Common place for Initial Setup translation functions. 2 | # 3 | # Copyright (C) 2018 Red Hat, Inc. 4 | # 5 | # This copyrighted material is made available to anyone wishing to use, 6 | # modify, copy, or redistribute it subject to the terms and conditions of 7 | # the GNU General Public License v.2, or (at your option) any later version. 8 | # This program is distributed in the hope that it will be useful, but WITHOUT 9 | # ANY WARRANTY expressed or implied, including the implied warranties of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 11 | # Public License for more details. You should have received a copy of the 12 | # GNU General Public License along with this program; if not, write to the 13 | # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 14 | # 02110-1301, USA. Any Red Hat trademarks that are incorporated in the 15 | # source code or documentation are not subject to the GNU General Public 16 | # License and may only be used or replicated with the express permission of 17 | # Red Hat, Inc. 18 | # 19 | 20 | __all__ = ["_", "N_"] 21 | 22 | import gettext 23 | 24 | N_ = lambda x: x 25 | _ = lambda x: gettext.translation("initial-setup", fallback=True).gettext(x) if x != "" else "" 26 | -------------------------------------------------------------------------------- /initial_setup/gui/gui.py: -------------------------------------------------------------------------------- 1 | from pyanaconda.ui.gui import QuitDialog, GraphicalUserInterface 2 | from initial_setup.product import get_product_title, is_final 3 | from initial_setup.common import get_quit_message 4 | from .hubs import InitialSetupMainHub 5 | import os 6 | 7 | 8 | class InitialSetupQuitDialog(QuitDialog): 9 | MESSAGE = get_quit_message() 10 | 11 | 12 | class InitialSetupGraphicalUserInterface(GraphicalUserInterface): 13 | """This is the main Gtk based firstboot interface. It inherits from 14 | anaconda to make the look & feel as similar as possible. 15 | """ 16 | 17 | screenshots_directory = "/tmp/initial-setup-screenshots" 18 | 19 | def __init__(self): 20 | GraphicalUserInterface.__init__(self, None, None, get_product_title, is_final(), 21 | quitDialog=InitialSetupQuitDialog) 22 | self.mainWindow.set_title("") 23 | 24 | def _list_hubs(self): 25 | return [InitialSetupMainHub] 26 | 27 | basemask = "initial_setup.gui" 28 | basepath = os.path.dirname(__file__) 29 | paths = GraphicalUserInterface.paths + { 30 | "spokes": [(basemask + ".spokes.%s", os.path.join(basepath, "spokes"))], 31 | "categories": [(basemask + ".categories.%s", os.path.join(basepath, "categories"))], 32 | } 33 | -------------------------------------------------------------------------------- /systemd/initial-setup.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Initial Setup configuration program 3 | After=livesys.service plymouth-quit-wait.service 4 | After=systemd-vconsole-setup.service 5 | After=systemd-user-sessions.service 6 | # getty-pre.target is a pasive target, we need to request it before we can use it 7 | Wants=getty-pre.target 8 | # prevent getty from running on any consoles before we are done 9 | Before=getty-pre.target 10 | Before=display-manager.service 11 | Conflicts=plymouth-quit-wait.service initial-setup-text.service initial-setup-graphical.service 12 | ConditionKernelCommandLine=!rd.live.image 13 | 14 | [Service] 15 | Type=oneshot 16 | TimeoutSec=0 17 | RemainAfterExit=yes 18 | # tell systemd to stop logging to the console, to prevent it's messages 19 | # with interfering with the Initial Setup TUI potentially running there 20 | ExecStartPre=/bin/kill -SIGRTMIN+21 1 21 | ExecStartPre=-/bin/plymouth quit 22 | ExecStart=/usr/libexec/initial-setup/run-initial-setup 23 | # re-enable systemd console logging once Initial Setup is done 24 | ExecStartPost=/bin/kill -SIGRTMIN+20 1 25 | TimeoutSec=0 26 | RemainAfterExit=no 27 | 28 | # setup session 29 | User=root 30 | Group=root 31 | PAMName=initial-setup 32 | TTYPath=/dev/tty7 33 | TTYReset=yes 34 | TTYVHangup=yes 35 | TTYVTDisallocate=yes 36 | 37 | [Install] 38 | WantedBy=graphical.target 39 | WantedBy=multi-user.target 40 | -------------------------------------------------------------------------------- /initial_setup/product.py: -------------------------------------------------------------------------------- 1 | """Module providing information about the installed product.""" 2 | import logging 3 | import os 4 | 5 | from pyanaconda.core.configuration.anaconda import conf 6 | from pyanaconda.core.util import get_os_release_value 7 | 8 | log = logging.getLogger("initial-setup") 9 | 10 | 11 | def get_product_title(): 12 | """Get product title. 13 | 14 | :return: a product title 15 | """ 16 | return get_os_release_value("PRETTY_NAME") or "" 17 | 18 | 19 | def is_final(): 20 | """Whether it is a final release of the product or not. 21 | 22 | :rtype: bool 23 | """ 24 | # doesn't really matter for the Initial Setup 25 | return True 26 | 27 | 28 | def get_license_file_name(): 29 | """Get filename of the license file best matching current localization settings. 30 | 31 | :return: filename of the license file or None if no license file found 32 | :rtype: str or None 33 | """ 34 | if not conf.license.eula: 35 | return None 36 | 37 | if not os.path.exists(conf.license.eula): 38 | return None 39 | 40 | return conf.license.eula 41 | 42 | 43 | def eula_available(): 44 | """Report if it looks like there is an EULA available on the system. 45 | 46 | :return: True if an EULA seems to be available, False otherwise 47 | :rtype: bool 48 | """ 49 | return bool(get_license_file_name()) 50 | -------------------------------------------------------------------------------- /initial_setup/gui/hubs/initial_setup_hub.py: -------------------------------------------------------------------------------- 1 | from pyanaconda.ui.gui.hubs import Hub 2 | from pyanaconda.ui.gui.spokes import NormalSpoke as GUI_spoke_class 3 | from initial_setup import product 4 | from initial_setup import common 5 | 6 | __all__ = ["InitialSetupMainHub"] 7 | 8 | 9 | class InitialSetupMainHub(Hub): 10 | uiFile = "initial_setup.glade" 11 | builderObjects = ["summaryWindow"] 12 | mainWidgetName = "summaryWindow" 13 | translationDomain = "initial-setup" 14 | 15 | # Should we automatically go to next hub if processing is done and there are no 16 | # spokes on the hub ? The correct value for Initial Setup is True, due to the 17 | # long standing Initial Setup policy of continuing with system startup if there 18 | # are no spokes to be shown. 19 | continue_if_empty = True 20 | 21 | @staticmethod 22 | def get_screen_id(): 23 | """Return a unique id of this UI screen.""" 24 | return "initial-setup-summary" 25 | 26 | def __init__(self, *args): 27 | Hub.__init__(self, *args) 28 | 29 | def _collectCategoriesAndSpokes(self): 30 | return common.collectCategoriesAndSpokes(self, GUI_spoke_class) 31 | 32 | def _createBox(self): 33 | Hub._createBox(self) 34 | 35 | # override spokes' distribution strings set by the pyanaconda module 36 | for spoke in self._spokes.values(): 37 | title = product.get_product_title().upper() 38 | spoke.window.set_property("distribution", title) 39 | -------------------------------------------------------------------------------- /tests/lib/testlib.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Shell functions for use by anaconda tests 3 | # 4 | # Copyright (C) 2014 Red Hat, Inc. 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published 8 | # by the Free Software Foundation; either version 2.1 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Lesser General Public License 17 | # along with this program. If not, see . 18 | # 19 | # Author: David Shea 20 | 21 | # Print a list of files to test on stdout 22 | # Takes filter arguments identical to the find utility, for example 23 | # findtestfiles -name '*.py'. Note that pruning directories will not 24 | # work since find is passed a list of filenames as the path arguments. 25 | findtestfiles() 26 | { 27 | # If the test is being run from a git work tree, use a list of all files 28 | # known to git 29 | if [ -d "${top_srcdir}/.git" ]; then 30 | findpath=$(git ls-files -c "${top_srcdir}") 31 | # Otherwise list everything under $top_srcdir 32 | else 33 | findpath="${top_srcdir} -type f" 34 | fi 35 | 36 | find $findpath "$@" 37 | } 38 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.0", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "initial-setup" 7 | version = "0.3.101" 8 | authors = [ 9 | {name = "Martin Kolman", email = "mkolman@redhat.com"} 10 | ] 11 | description = "Post-installation configuration utility" 12 | readme = "README.rst" 13 | license = {text = "GPL-2.0-or-later"} 14 | keywords = ["firstboot", "initial", "setup"] 15 | classifiers = [ 16 | "Development Status :: 3 - Alpha", 17 | "Environment :: X11 Applications :: GTK", 18 | "Environment :: Console", 19 | "Intended Audience :: System Administrators", 20 | "Topic :: System :: Systems Administration", 21 | "Programming Language :: Python :: 3", 22 | ] 23 | requires-python = ">=3.8" 24 | dynamic = ["dependencies"] 25 | 26 | [project.urls] 27 | Homepage = "https://fedoraproject.org/wiki/InitialSetup" 28 | Repository = "https://github.com/rhinstaller/initial-setup" 29 | issues = "https://github.com/rhinstaller/initial-setup/issues" 30 | 31 | [tool.setuptools.packages.find] 32 | exclude = ["po*", "scripts*", "systemd*", "tests*"] 33 | 34 | [tool.setuptools.package-data] 35 | "*" = ["*.glade"] 36 | 37 | [tool.setuptools.data-files] 38 | "lib/systemd/system" = ["systemd/*.service"] 39 | "libexec/initial-setup" = [ 40 | "scripts/run-initial-setup", 41 | "scripts/firstboot-windowmanager", 42 | "scripts/initial-setup-text", 43 | "scripts/initial-setup-graphical", 44 | "scripts/run-gui-backend.guixorg", 45 | "scripts/run-gui-backend.guiweston", 46 | "scripts/run-gui-backend", 47 | "scripts/reconfiguration-mode-enabled" 48 | ] 49 | "share/doc/initial-setup" = ["ChangeLog"] 50 | 51 | [tool.setuptools.dynamic] 52 | dependencies = {file = ["requirements.txt"]} 53 | -------------------------------------------------------------------------------- /scripts/run-initial-setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | IS_TEXT="/usr/libexec/initial-setup/initial-setup-text" 4 | IS_GRAPHICAL="/usr/libexec/initial-setup/initial-setup-graphical" 5 | IS_UNIT=initial-setup.service 6 | 7 | # systemd targets 8 | GRAPHICAL_TARGET=/usr/lib/systemd/system/graphical.target 9 | CURRENT_DEFAULT_TARGET=$(readlink -e /etc/systemd/system/default.target) 10 | 11 | START_GUI_COMMAND="/usr/libexec/initial-setup/run-gui-backend ${IS_GRAPHICAL} --no-stdout-log" 12 | 13 | # check if graphical Initial Setup is installed 14 | if [ -f ${IS_GRAPHICAL} ]; then 15 | # don't run the GUI on text-only systems (default.target != graphical.target), 16 | # users are not expecting a graphical interface do start in such case 17 | # and there might not even be any displays connected 18 | if [ "$CURRENT_DEFAULT_TARGET" == "$GRAPHICAL_TARGET" ]; then 19 | export XDG_RUNTIME_DIR=$(mktemp --directory --suffix="-initial-setup-runtime-dir") 20 | chmod 700 "$XDG_RUNTIME_DIR" 21 | 22 | echo "Starting Initial Setup GUI" | systemd-cat -t initial-setup -p 6 23 | ${START_GUI_COMMAND} 24 | res=$? 25 | 26 | rm -rf "$XDG_RUNTIME_DIR" 27 | else 28 | echo "Initial Setup GUI is installed, but default.target != graphical.target" | systemd-cat -t initial-setup -p 5 29 | echo "Starting Initial Setup TUI" | systemd-cat -t initial-setup -p 6 30 | ${IS_TEXT} --no-stdout-log 31 | res=$? 32 | fi 33 | else 34 | echo "Starting Initial Setup TUI" | systemd-cat -t initial-setup -p 6 35 | ${IS_TEXT} --no-stdout-log 36 | res=$? 37 | fi 38 | 39 | # check if the Initial Setup run was successful by looking at the return code 40 | if [ $res -eq 0 ]; then 41 | echo "Initial Setup finished successfully, disabling" | systemd-cat -t initial-setup -p 6 42 | systemctl -q is-enabled $IS_UNIT && systemctl disable $IS_UNIT 43 | echo "Initial Setup has been disabled" | systemd-cat -t initial-setup -p 6 44 | exit 0 45 | else 46 | echo "Initial Setup failed, keeping enabled" | systemd-cat -t initial-setup -p 3 47 | exit 1 48 | fi 49 | -------------------------------------------------------------------------------- /po/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # taken from python-meh sources 3 | # Makefile for the PO files (translation) catalog 4 | # 5 | # $Id$ 6 | 7 | TOP = ../.. 8 | 9 | # What is this package? 10 | NLSPACKAGE = initial-setup 11 | POTFILE = $(NLSPACKAGE).pot 12 | INSTALL = /usr/bin/install -c 13 | INSTALL_DATA = $(INSTALL) -m 644 14 | INSTALL_DIR = /usr/bin/install -d 15 | 16 | # destination directory 17 | INSTALL_NLS_DIR = $(RPM_BUILD_ROOT)/usr/share/locale 18 | 19 | # PO catalog handling 20 | MSGMERGE = msgmerge -v 21 | XGETTEXT = xgettext --default-domain=$(NLSPACKAGE) \ 22 | --add-comments 23 | MSGFMT = msgfmt --statistics --verbose 24 | 25 | # What do we need to do 26 | POFILES = $(wildcard *.po) 27 | MOFILES = $(patsubst %.po,%.mo,$(POFILES)) 28 | PYSRC = $(wildcard ../initial_setup/*.py ../initial_setup/*/*.py ../initial_setup/*/*/*.py) 29 | GLADEFILES = $(wildcard ../initial_setup/*/*/*.glade) 30 | 31 | all:: update-po $(MOFILES) 32 | 33 | potfile: $(PYSRC) glade-po 34 | $(XGETTEXT) -L Python --keyword=_ --keyword=N_ $(PYSRC) tmp/*.h 35 | @if cmp -s $(NLSPACKAGE).po $(POTFILE); then \ 36 | rm -f $(NLSPACKAGE).po; \ 37 | else \ 38 | mv -f $(NLSPACKAGE).po $(POTFILE); \ 39 | fi; \ 40 | 41 | glade-po: $(GLADEFILES) 42 | rm -rf tmp/ 43 | for f in $(GLADEFILES); do \ 44 | intltool-extract --type=gettext/glade -l $$f ;\ 45 | done 46 | 47 | update-po: Makefile potfile refresh-po 48 | 49 | refresh-po: Makefile 50 | for cat in $(POFILES); do \ 51 | lang=`basename $$cat .po`; \ 52 | if $(MSGMERGE) $$lang.po $(POTFILE) > $$lang.pot ; then \ 53 | mv -f $$lang.pot $$lang.po ; \ 54 | echo "$(MSGMERGE) of $$lang succeeded" ; \ 55 | else \ 56 | echo "$(MSGMERGE) of $$lang failed" ; \ 57 | rm -f $$lang.pot ; \ 58 | fi \ 59 | done 60 | 61 | clean: 62 | @rm -fv *mo *~ .depend 63 | @rm -rf tmp 64 | 65 | install: $(MOFILES) 66 | @for n in $(MOFILES); do \ 67 | l=`basename $$n .mo`; \ 68 | $(INSTALL_DIR) $(INSTALL_NLS_DIR)/$$l/LC_MESSAGES; \ 69 | $(INSTALL_DATA) --verbose $$n $(INSTALL_NLS_DIR)/$$l/LC_MESSAGES/$(NLSPACKAGE).mo; \ 70 | done 71 | 72 | %.mo: %.po 73 | $(MSGFMT) -o $@ $< 74 | 75 | .PHONY: missing depend 76 | 77 | 78 | -------------------------------------------------------------------------------- /scripts/firstboot-windowmanager: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This is the list of supported window manager binaries 4 | WMS=("gnome-kiosk" "metacity" "kwin_x11" "xfwm4" "openbox" "marco") 5 | 6 | run_gnome_kiosk() 7 | { 8 | # Apply the anaconda overrides before running gnome-kiosk 9 | if [ -z "$XDG_DATA_DIRS" ] ; then 10 | new_data_dirs="/usr/share/anaconda/window-manager:/usr/share" 11 | else 12 | new_data_dirs="/usr/share/anaconda/window-manager:${XDG_DATA_DIRS}" 13 | fi 14 | XDG_DATA_DIRS="$new_data_dirs" $1 --x11 & 15 | } 16 | 17 | run_metacity() 18 | { 19 | # Apply the anaconda overrides before running metacity 20 | if [ -z "$XDG_DATA_DIRS" ] ; then 21 | new_data_dirs="/usr/share/anaconda/window-manager:/usr/share" 22 | else 23 | new_data_dirs="/usr/share/anaconda/window-manager:${XDG_DATA_DIRS}" 24 | fi 25 | XDG_DATA_DIRS="$new_data_dirs" $1 & 26 | } 27 | 28 | # Get the application binary to start and remove it from 29 | # the argument list 30 | BINARY=$1 31 | shift 32 | 33 | for WM in ${WMS[@]}; do 34 | FILE=$(which $WM 2>/dev/null) 35 | FOUND=$? 36 | 37 | if [ $FOUND -eq 0 -a -x "$FILE" ]; then 38 | # start window manager 39 | if [ "$WM" == "gnome-kiosk" ]; then 40 | # if running gnome-kiosk apply the Anaconda/Initial Setup custom overrides 41 | echo "Running gnome-kiosk with custom overrides" | systemd-cat -t initial-setup -p 6 42 | run_gnome_kiosk "$FILE" 43 | elif [ "$WM" == "metacity" ]; then 44 | # if running Metacity apply the Anaconda/Initial Setup custom overrides 45 | echo "Running Metacity with custom overrides" | systemd-cat -t initial-setup -p 6 46 | run_metacity "$FILE" 47 | else 48 | echo "Running window manager ($FILE)" | systemd-cat -t initial-setup -p 6 49 | "$FILE" & 50 | fi 51 | 52 | pid=$! 53 | 54 | # start the application 55 | echo "Running ($BINARY)" | systemd-cat -t initial-setup -p 6 56 | $BINARY "$@" 57 | res=$? 58 | 59 | # stop window manager 60 | echo "Stopping the window manager ($FILE)" | systemd-cat -t initial-setup -p 6 61 | ps -p $pid >/dev/null && kill $pid 62 | 63 | # return result 64 | exit $res 65 | fi 66 | done 67 | 68 | # No known window manager found 69 | echo "No supported window manager found!" | systemd-cat -t initial-setup -p 3 70 | exit 1 71 | -------------------------------------------------------------------------------- /initial_setup/initial_setup_log.py: -------------------------------------------------------------------------------- 1 | # 2 | # initial_setup_log.py: Support for logging to syslog during the 3 | # Initial Setup run 4 | # 5 | # Copyright (C) 2014 Red Hat, Inc. All rights reserved. 6 | # 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | # Author(s): Martin Kolman 21 | 22 | import logging 23 | from logging.handlers import SysLogHandler, SYSLOG_UDP_PORT 24 | 25 | 26 | class InitialSetupSyslogHandler(SysLogHandler): 27 | """A SysLogHandler subclass that makes sure the Initial Setup 28 | messages are easy to identify in the syslog/Journal 29 | """ 30 | def __init__(self, 31 | address=('localhost', SYSLOG_UDP_PORT), 32 | facility=SysLogHandler.LOG_USER, 33 | tag=''): 34 | self.tag = tag 35 | SysLogHandler.__init__(self, address, facility) 36 | 37 | def emit(self, record): 38 | original_msg = record.msg 39 | # this is needed to properly show the "initial-setup" prefix 40 | # for log messages in syslog/Journal 41 | record.msg = '%s: %s' % (self.tag, original_msg) 42 | SysLogHandler.emit(self, record) 43 | record.msg = original_msg 44 | 45 | 46 | def init(stdout_log): 47 | """Initialize the Initial Setup logging system""" 48 | log = logging.getLogger("initial-setup") 49 | log.setLevel(logging.DEBUG) 50 | syslogHandler = InitialSetupSyslogHandler('/dev/log', SysLogHandler.LOG_LOCAL1, "initial-setup") 51 | syslogHandler.setLevel(logging.DEBUG) 52 | log.addHandler(syslogHandler) 53 | 54 | if stdout_log: 55 | # also log to stdout because someone is apparently running Initial Setup manually, 56 | # probably for debugging purposes 57 | stdoutHandler = logging.StreamHandler() 58 | stdoutHandler.setLevel(logging.DEBUG) 59 | stdoutHandler.setFormatter(logging.Formatter('%(levelname)s %(name)s: %(message)s')) 60 | log.addHandler(stdoutHandler) 61 | -------------------------------------------------------------------------------- /tests/pylint/intl.py: -------------------------------------------------------------------------------- 1 | # I18N-related pylint module 2 | # 3 | # Copyright (C) 2013 Red Hat, Inc. 4 | # 5 | # This copyrighted material is made available to anyone wishing to use, 6 | # modify, copy, or redistribute it subject to the terms and conditions of 7 | # the GNU General Public License v.2, or (at your option) any later version. 8 | # This program is distributed in the hope that it will be useful, but WITHOUT 9 | # ANY WARRANTY expressed or implied, including the implied warranties of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 11 | # Public License for more details. You should have received a copy of the 12 | # GNU General Public License along with this program; if not, write to the 13 | # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 14 | # 02110-1301, USA. Any Red Hat trademarks that are incorporated in the 15 | # source code or documentation are not subject to the GNU General Public 16 | # License and may only be used or replicated with the express permission of 17 | # Red Hat, Inc. 18 | # 19 | # Red Hat Author(s): Chris Lumens 20 | # 21 | 22 | import astroid 23 | 24 | from pylint.checkers import BaseChecker 25 | from pylint.checkers.utils import check_messages 26 | from pylint.interfaces import IAstroidChecker 27 | 28 | translationMethods = ["_", "N_", "P_", "C_", "CN_", "CP_"] 29 | 30 | class IntlChecker(BaseChecker): 31 | __implements__ = (IAstroidChecker, ) 32 | name = "internationalization" 33 | msgs = {"W9901": ("Found % in a call to a _() method", 34 | "found-percent-in-_", 35 | "% in a call to one of the _() methods results in incorrect translations"), 36 | "W9902": ("Found _ call at module/class level", 37 | "found-_-in-module-class", 38 | "Calling _ at the module or class level results in translations to the wrong language") 39 | } 40 | 41 | @check_messages("W9901") 42 | def visit_binop(self, node): 43 | if node.op != "%": 44 | return 45 | 46 | curr = node 47 | while curr.parent: 48 | if isinstance(curr.parent, astroid.CallFunc) and getattr(curr.parent.func, "name", "") in translationMethods: 49 | self.add_message("W9901", node=node) 50 | break 51 | 52 | curr = curr.parent 53 | 54 | @check_messages("W9902") 55 | def visit_callfunc(self, node): 56 | # The first test skips internal functions like getattr. 57 | if isinstance(node.func, astroid.Name) and node.func.name == "_": 58 | if isinstance(node.scope(), astroid.Module) or isinstance(node.scope(), astroid.Class): 59 | self.add_message("W9902", node=node) 60 | 61 | def register(linter): 62 | """required method to auto register this checker """ 63 | linter.register_checker(IntlChecker(linter)) 64 | -------------------------------------------------------------------------------- /po/initial-setup.pot: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2024-07-19 17:24+0200\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=CHARSET\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: ../initial_setup/common.py:103 21 | msgid "" 22 | "Are you sure you want to quit the configuration process?\n" 23 | "You might end up with an unusable system if you do. Unless the License " 24 | "agreement is accepted, the system will be rebooted." 25 | msgstr "" 26 | 27 | #: ../initial_setup/common.py:107 28 | msgid "" 29 | "Are you sure you want to quit the configuration process?\n" 30 | "You might end up with unusable system if you do." 31 | msgstr "" 32 | 33 | #: ../initial_setup/common.py:115 34 | msgid "LICENSING" 35 | msgstr "" 36 | 37 | #: ../initial_setup/gui/spokes/eula.py:25 38 | msgid "_License Information" 39 | msgstr "" 40 | 41 | #: ../initial_setup/gui/spokes/eula.py:49 42 | msgid "No license found. Please report this at http://bugzilla.redhat.com" 43 | msgstr "" 44 | 45 | #: ../initial_setup/gui/spokes/eula.py:88 46 | msgid "No license found" 47 | msgstr "" 48 | 49 | #: ../initial_setup/gui/spokes/eula.py:90 50 | #: ../initial_setup/tui/spokes/eula.py:67 51 | msgid "License accepted" 52 | msgstr "" 53 | 54 | #: ../initial_setup/gui/spokes/eula.py:90 55 | #: ../initial_setup/tui/spokes/eula.py:67 56 | msgid "License not accepted" 57 | msgstr "" 58 | 59 | #: ../initial_setup/tui/hubs/initial_setup_hub.py:15 60 | #, python-format 61 | msgid "Initial setup of %(product)s" 62 | msgstr "" 63 | 64 | #: ../initial_setup/tui/hubs/initial_setup_hub.py:17 65 | msgid "Initial setup" 66 | msgstr "" 67 | 68 | #: ../initial_setup/tui/spokes/eula.py:33 69 | msgid "License information" 70 | msgstr "" 71 | 72 | #. make the options aligned to the same column (the checkbox has the 73 | #. '[ ]' prepended) 74 | #: ../initial_setup/tui/spokes/eula.py:47 75 | msgid "Read the License Agreement" 76 | msgstr "" 77 | 78 | #: ../initial_setup/tui/spokes/eula.py:50 79 | msgid "I accept the license agreement." 80 | msgstr "" 81 | 82 | #: tmp/eula.glade.h:1 83 | msgid "License Information" 84 | msgstr "" 85 | 86 | #: tmp/eula.glade.h:2 87 | msgid "License Agreement:" 88 | msgstr "" 89 | 90 | #: tmp/eula.glade.h:3 91 | msgid "I _accept the license agreement." 92 | msgstr "" 93 | 94 | #: tmp/initial_setup.glade.h:1 95 | msgid "DISTRIBUTION SETUP" 96 | msgstr "" 97 | 98 | #: tmp/initial_setup.glade.h:2 99 | msgid "INITIAL SETUP" 100 | msgstr "" 101 | 102 | #: tmp/initial_setup.glade.h:3 103 | msgid "_QUIT" 104 | msgstr "" 105 | 106 | #: tmp/initial_setup.glade.h:4 107 | msgid "_FINISH CONFIGURATION" 108 | msgstr "" 109 | -------------------------------------------------------------------------------- /tests/pylint/runpylint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script will check for any pylint warning and errors using a set 4 | # of options minimizing false positives, in combination with filtering of any 5 | # warning regularexpressions listed in pylint-false-positives. 6 | # 7 | # If any warnings are found they will be stored in pylint-log and printed 8 | # to stdout and this script will exit with a status of 1, if no (non filtered) 9 | # warnings are found it exits with a status of 0 10 | 11 | # If $top_srcdir is set, assume this is being run from automake and we don't 12 | # need to keep a separate log 13 | export pylint_log=0 14 | if [ -z "$top_srcdir" ]; then 15 | export pylint_log=1 16 | fi 17 | 18 | # Unset TERM so that things that use readline don't output terminal garbage 19 | unset TERM 20 | 21 | # Don't try to connect to the accessibility socket 22 | export NO_AT_BRIDGE=1 23 | 24 | # If $top_srcdir has not been set by automake, import the test environment 25 | if [ -z "$top_srcdir" ]; then 26 | top_srcdir="$(dirname "$0")/../.." 27 | . ${top_srcdir}/tests/testenv.sh 28 | fi 29 | 30 | . ${top_srcdir}/tests/lib/testlib.sh 31 | 32 | srcdir="${top_srcdir}/tests/pylint" 33 | builddir="${top_builddir}/tests/pylint" 34 | 35 | # Need to add the pylint module directory to PYTHONPATH as well. 36 | export PYTHONPATH="${PYTHONPATH}:${srcdir}" 37 | 38 | # Save analysis data in the pylint directory 39 | export PYLINTHOME="${builddir}/.pylint.d" 40 | [ -d "$PYLINTHOME" ] || mkdir "$PYLINTHOME" 41 | 42 | export FALSE_POSITIVES="${srcdir}"/pylint-false-positives 43 | 44 | # W0212 - Access to a protected member %s of a client class 45 | export NON_STRICT_OPTIONS="--disable=W0212" 46 | 47 | # E1103 - %s %r has no %r member (but some types could not be inferred) 48 | export DISABLED_ERR_OPTIONS="--disable=E1103" 49 | 50 | # W0105 - String statement has no effect 51 | # W0110 - map/filter on lambda could be replaced by comprehension 52 | # W0141 - Used builtin function %r 53 | # W0142 - Used * or ** magic 54 | # W0511 - Used when a warning note as FIXME or XXX is detected. 55 | # W0603 - Using the global statement 56 | # W0614 - Unused import %s from wildcard import 57 | # I0011 - Locally disabling %s (i.e., pylint: disable) 58 | export DISABLED_WARN_OPTIONS="--disable=W0105,W0110,W0141,W0142,W0511,W0603,W0614,I0011" 59 | 60 | usage () { 61 | echo "usage: `basename $0` [--strict] [--help] [files...]" 62 | exit $1 63 | } 64 | 65 | # Separate the module parameters from the files list 66 | ARGS= 67 | FILES= 68 | while [ $# -gt 0 ]; do 69 | case $1 in 70 | --strict) 71 | export NON_STRICT_OPTIONS= 72 | ;; 73 | --help) 74 | usage 0 75 | ;; 76 | -*) 77 | ARGS="$ARGS $1" 78 | ;; 79 | *) 80 | FILES=$@ 81 | break 82 | esac 83 | shift 84 | done 85 | 86 | exit_status=0 87 | 88 | if [ -s pylint-log ]; then 89 | rm pylint-log 90 | fi 91 | 92 | # run pylint one file / module at a time, otherwise it sometimes gets 93 | # confused 94 | if [ -z "$FILES" ]; then 95 | # Test any file that either ends in .py or contains #!/usr/bin/python2 in 96 | # the first line. Scan everything except old_tests 97 | FILES=$(findtestfiles \( -name '*.py' -o \ 98 | -exec /bin/sh -c "head -1 {} | grep -q '#!/usr/bin/python2'" \; \) -print | \ 99 | egrep -v '(|/)doc/conf.py') 100 | fi 101 | 102 | num_cpus=$(getconf _NPROCESSORS_ONLN) 103 | # run pylint in paralel 104 | echo $FILES | xargs --max-procs=$num_cpus -n 1 "$srcdir"/pylint-one.sh $ARGS || exit 1 105 | 106 | for file in $(find -name 'pylint-out*'); do 107 | cat "$file" >> pylint-log 108 | rm "$file" 109 | done 110 | 111 | fails=$(find -name 'pylint*failed' -print -exec rm '{}' \;) 112 | if [ -z "$fails" ]; then 113 | exit_status=0 114 | else 115 | exit_status=1 116 | fi 117 | 118 | if [ -s pylint-log ]; then 119 | echo "pylint reports the following issues:" 120 | cat pylint-log 121 | elif [ -e pylint-log ]; then 122 | rm pylint-log 123 | fi 124 | 125 | exit "$exit_status" 126 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Initial Setup 2 | ============= 3 | 4 | .. image:: https://copr.fedorainfracloud.org/coprs/g/rhinstaller/Anaconda/package/initial-setup/status_image/last_build.png 5 | :alt: Build status 6 | :target: https://copr.fedorainfracloud.org/coprs/g/rhinstaller/Anaconda/package/initial-setup/ 7 | 8 | .. image:: https://translate.fedoraproject.org/widgets/initial-setup/-/master/svg-badge.svg 9 | :alt: Translation status 10 | :target: https://translate.fedoraproject.org/engage/initial-setup/?utm_source=widget 11 | 12 | Initial Setup is an application that can run during the first start 13 | of a newly installed computer and makes it possible to configure the 14 | computer according to the needs of the user. 15 | 16 | As most of the configuration options are already present during the 17 | OS installation in Anaconda, Initial Setup mainly hosts options that 18 | need to be presented during the first start, such as displaying the 19 | EULA on RHEL. Initial Setup is also often used to create a user account, 20 | as many systems are often automatically installed with kickstart 21 | and the user is only expected to create their own user account once 22 | the newly installed machine is started for the first time. 23 | 24 | Still, while Initial Setup normally does not have many options 25 | available, if the firstboot --reconfig kickstart command is provided 26 | in the installation kickstart, Initial Setup will show all configuration 27 | options available. This is usually used for OEM OS installations, 28 | where an OEM installs the computer, which is then shipped to the end user 29 | which uses Initial Setup for the final configuration of the operating system. 30 | 31 | Architecture 32 | ============ 33 | Initial Setup is basically just a thin wrapper for running spokes from Anaconda. 34 | Still, it has its own Hub, one spoke (the EULA spoke) and a translation domain ("initial-setup"). 35 | 36 | As with Anaconda, Initial Setup has both a GUI and TUI version and the package is split 37 | into a core and GUI & TUI sub packages. 38 | 39 | As Initial Setup needs to run during the early boot, it is started by a systemd unit 40 | configured to start before the normal login screen. 41 | 42 | On RHEL7 Initial Setup is by default followed by the legacy Firstboot utility, 43 | which at the moment does not have any plugins by default and should thus terminate 44 | immediately. If the given OS instance uses the Gnome 3 desktop environment, 45 | Firstboot is followed by the Gnome Initial Setup(GIS), which enables the user to customize 46 | their computer even more. 47 | 48 | On RHEL8 Firstboot is no longer available and thus Initial Setup is followed by Gnome Initial Setup 49 | on RHEL Workstation installs and directly with the login screen elsewhere. 50 | 51 | On Fedora Initial Setup is followed directly by GIS, provided Gnome 3 is installed. 52 | 53 | * RHEL8: IS -> [GIS] 54 | * RHEL7: IS -> Firstboot -> [GIS] 55 | * Fedora: IS -> [GIS] 56 | 57 | Addons 58 | ====== 59 | Like Anaconda, also Initial Setup can be used to host third party addons - flexible 60 | yet powerful modules that can configure the system based on data in kickstart 61 | while presenting a nice UI to the user. Addons can have a GUI, TUI or can be 62 | headless, working only with data in their kickstart section or from other sources. 63 | 64 | For comprehensive documentation about Anaconda/Intial Setup see the 65 | "Anaconda Addon Development Guide" by Vratislav Podzimek: 66 | 67 | * https://rhinstaller.github.io/anaconda-addon-development-guide/ 68 | 69 | Testing 70 | ======= 71 | To start tests please first install package ``tmt-all`` to your system and call:: 72 | 73 | make test 74 | 75 | First time you call the above it will setup all dependencies and then execute the tests. 76 | If you need to do the initialization of the tests again, please run:: 77 | 78 | make test-cleanup 79 | 80 | In case you want to use custom Anaconda code, please provide a COPR repository with the Anaconda 81 | (can be easily created by ``packit copr-build`` in the Anaconda branch) and execute:: 82 | 83 | make test TMT_COPR_ANACONDA_REPO= 84 | 85 | Please note, you need to call ``make test-cleanup`` first to re-initialize test environment with 86 | your custom Anaconda code. 87 | 88 | Contributing 89 | ============ 90 | * Initial Setup is released under GPLv2+ 91 | * upstream source code repository is on GitHub: https://github.com/rhinstaller/initial-setup 92 | -------------------------------------------------------------------------------- /initial_setup/gui/spokes/eula.py: -------------------------------------------------------------------------------- 1 | """EULA spoke for the Initial Setup""" 2 | 3 | import logging 4 | 5 | from pyanaconda.ui.common import FirstbootOnlySpokeMixIn 6 | from pyanaconda.ui.gui.spokes import NormalSpoke 7 | from pykickstart.constants import FIRSTBOOT_RECONFIG 8 | 9 | from initial_setup.product import eula_available, get_license_file_name 10 | from initial_setup.common import LicensingCategory 11 | from initial_setup.i18n import _, N_ 12 | 13 | log = logging.getLogger("initial-setup") 14 | 15 | __all__ = ["EULASpoke"] 16 | 17 | 18 | class EULASpoke(FirstbootOnlySpokeMixIn, NormalSpoke): 19 | """The EULA spoke""" 20 | 21 | builderObjects = ["eulaBuffer", "eulaWindow"] 22 | mainWidgetName = "eulaWindow" 23 | uiFile = "eula.glade" 24 | icon = "application-certificate-symbolic" 25 | title = N_("_License Information") 26 | category = LicensingCategory 27 | translationDomain = "initial-setup" 28 | 29 | @staticmethod 30 | def get_screen_id(): 31 | """Return a unique id of this UI screen.""" 32 | return "license-information" 33 | 34 | def initialize(self): 35 | log.debug("initializing the EULA spoke") 36 | NormalSpoke.initialize(self) 37 | 38 | self._have_eula = True 39 | self._eula_buffer = self.builder.get_object("eulaBuffer") 40 | self._agree_check_button = self.builder.get_object("agreeCheckButton") 41 | self._agree_label = self._agree_check_button.get_child() 42 | self._agree_text = self._agree_label.get_text() 43 | 44 | log.debug("looking for the license file") 45 | license_file = get_license_file_name() 46 | if not license_file: 47 | log.error("no license found") 48 | self._have_eula = False 49 | self._eula_buffer.set_text(_("No license found. Please report this " 50 | "at http://bugzilla.redhat.com")) 51 | return 52 | 53 | self._eula_buffer.set_text("") 54 | itr = self._eula_buffer.get_iter_at_offset(0) 55 | log.debug("opening the license file") 56 | with open(license_file, "r") as fobj: 57 | # insert the first line without prefixing with space 58 | try: 59 | first_line = next(fobj) 60 | except StopIteration: 61 | # nothing in the file 62 | return 63 | self._eula_buffer.insert(itr, first_line.strip()) 64 | 65 | # EULA file is preformatted for the console, we want to let Gtk 66 | # format it (blank lines should be preserved) 67 | for line in fobj: 68 | stripped_line = line.strip() 69 | if stripped_line: 70 | self._eula_buffer.insert(itr, " " + stripped_line) 71 | else: 72 | self._eula_buffer.insert(itr, "\n\n") 73 | 74 | def refresh(self): 75 | self._agree_check_button.set_sensitive(self._have_eula) 76 | self._agree_check_button.set_active(self.data.eula.agreed) 77 | 78 | def apply(self): 79 | self.data.eula.agreed = self._agree_check_button.get_active() 80 | 81 | @property 82 | def completed(self): 83 | return not self._have_eula or self.data.eula.agreed 84 | 85 | @property 86 | def status(self): 87 | if not self._have_eula: 88 | return _("No license found") 89 | 90 | return _("License accepted") if self.data.eula.agreed else _("License not accepted") 91 | 92 | @classmethod 93 | def should_run(cls, environment, data): 94 | # the EULA spoke should only run if both are true: 95 | # - we are running in Initial Setup 96 | # - an EULA is available 97 | if eula_available() and FirstbootOnlySpokeMixIn.should_run(environment, data): 98 | # don't run if we are in reconfig mode and the EULA has already been accepted 99 | if data and data.firstboot.firstboot == FIRSTBOOT_RECONFIG and data.eula.agreed: 100 | log.debug("not running license spoke: reconfig mode & license already accepted") 101 | return False 102 | return True 103 | return False 104 | 105 | def on_check_button_toggled(self, *args): 106 | if self._agree_check_button.get_active(): 107 | log.debug("license is now accepted") 108 | self._agree_label.set_markup("%s" % self._agree_text) 109 | else: 110 | log.debug("license no longer accepted") 111 | self._agree_label.set_markup(self._agree_text) 112 | -------------------------------------------------------------------------------- /initial_setup/common.py: -------------------------------------------------------------------------------- 1 | """Common methods for Initial Setup""" 2 | 3 | import os 4 | 5 | from pyanaconda.ui.common import collect 6 | from pyanaconda.core.constants import FIRSTBOOT_ENVIRON 7 | from initial_setup.i18n import _, N_ 8 | from pyanaconda.ui.categories import SpokeCategory 9 | 10 | from initial_setup.product import eula_available 11 | 12 | # a set of excluded console names 13 | # - console, tty, tty0 -> these appear to be just aliases to the default console, 14 | # leaving them in would result in duplicate input on and output from 15 | # the default console 16 | # - ptmx -> pseudoterminal configuration device not intended as general user-facing console that 17 | # starts to block on write after some amount of characters has been written to it 18 | # - ttyUSB0-4 -> used by some GSM modems apparently, blocks when TUI tries to use it, 19 | # see rhbz#1755580 for more details about the hardware in question 20 | 21 | TUI_EXCLUDED_CONSOLES = {"console", "tty", "tty0", "ptmx", "ttyUSB0", "ttyUSB1", "ttyUSB2", "ttyUSB3", "ttyUSB4"} 22 | 23 | 24 | def collect_spokes(mask_paths, spoke_parent_class): 25 | """Return a list of all spoke subclasses that should appear for a given 26 | category. Look for them in files imported as module_path % basename(f) 27 | 28 | :param mask_paths: list of mask, path tuples to search for classes 29 | :type mask_paths: list of (mask, path) 30 | 31 | :param spoke_parent_class: Spoke parent class used for checking spoke compatibility 32 | :type spoke_parent_class: GUI or TUI Spoke class 33 | 34 | :return: list of Spoke classes belonging to category 35 | :rtype: list of Spoke classes 36 | 37 | """ 38 | spokes = [] 39 | for mask, path in mask_paths: 40 | 41 | spokes.extend(collect(mask, path, 42 | lambda obj: issubclass(obj, spoke_parent_class) and obj.should_run("firstboot", None))) 43 | return spokes 44 | 45 | 46 | def collectCategoriesAndSpokes(hub_instance, spoke_parent_class): 47 | """Collects categories and spokes to be displayed on this Hub, 48 | this method overrides the Anacondas implementation so that 49 | spokes relevant to Initial setup are collected 50 | 51 | :param hub_instance: an Initial Setup GUI or TUI Hub class instance 52 | :type hub_instance: class instance 53 | 54 | :param spoke_parent_class: Spoke parent class used for checking spoke compatibility 55 | :type spoke_parent_class: GUI or TUI Spoke class 56 | 57 | :return: dictionary mapping category class to list of spoke classes 58 | :rtype: dictionary[category class] -> [ list of spoke classes ] 59 | """ 60 | ret = {} 61 | 62 | # Collect all the categories this hub displays, then collect all the 63 | # spokes belonging to all those categories. 64 | candidate_spokes = collect_spokes(hub_instance.paths["spokes"], spoke_parent_class) 65 | spokes = [spoke for spoke in candidate_spokes 66 | if spoke.should_run(FIRSTBOOT_ENVIRON, hub_instance.data)] 67 | 68 | for spoke in spokes: 69 | ret.setdefault(spoke.category, []) 70 | ret[spoke.category].append(spoke) 71 | 72 | return ret 73 | 74 | 75 | def console_filter(console_name): 76 | """Filter out consoles we don't want to attempt running the TUI on. 77 | 78 | This at the moment just means console aliases, but it's possible more 79 | consoles will have to be added for other reasons in the guture. 80 | 81 | :param str console_name: console name to check 82 | :returns: if the console name is considered usable for IS TUI 83 | :rtype: bool 84 | """ 85 | return console_name not in TUI_EXCLUDED_CONSOLES 86 | 87 | 88 | def list_usable_consoles_for_tui(): 89 | """List suitable consoles for running the Initial Setup TUI. 90 | 91 | We basically want to list any console a user might using, 92 | as we can't really be sure which console is in use or not. 93 | 94 | :returns: a list of console names considered usable for the IS TUI 95 | :rtype: list 96 | """ 97 | console_names = [c for c in os.listdir("/sys/class/tty/") if console_filter(c)] 98 | return sorted(console_names) 99 | 100 | 101 | def get_quit_message(): 102 | if eula_available(): 103 | return N_("Are you sure you want to quit the configuration process?\n" 104 | "You might end up with an unusable system if you do. Unless the " 105 | "License agreement is accepted, the system will be rebooted.") 106 | else: 107 | return N_("Are you sure you want to quit the configuration process?\n" 108 | "You might end up with unusable system if you do.") 109 | 110 | 111 | class LicensingCategory(SpokeCategory): 112 | 113 | @staticmethod 114 | def get_title(): 115 | return _("LICENSING") 116 | 117 | @staticmethod 118 | def get_sort_order(): 119 | return 100 120 | -------------------------------------------------------------------------------- /initial_setup/tui/spokes/eula.py: -------------------------------------------------------------------------------- 1 | """EULA TUI spoke for the Initial Setup""" 2 | 3 | import logging 4 | 5 | from pyanaconda.ui.tui.spokes import NormalTUISpoke 6 | from simpleline.render.widgets import TextWidget, CheckboxWidget 7 | from simpleline.render.containers import ListRowContainer 8 | from simpleline.render.screen import UIScreen, InputState 9 | from simpleline.render.screen_handler import ScreenHandler 10 | from pyanaconda.ui.common import FirstbootOnlySpokeMixIn 11 | from initial_setup.product import get_license_file_name, eula_available 12 | from initial_setup.common import LicensingCategory 13 | from initial_setup.i18n import _ 14 | from pykickstart.constants import FIRSTBOOT_RECONFIG 15 | 16 | log = logging.getLogger("initial-setup") 17 | 18 | __all__ = ["EULASpoke"] 19 | 20 | 21 | class EULASpoke(FirstbootOnlySpokeMixIn, NormalTUISpoke): 22 | """The EULA spoke providing ways to read the license and agree/disagree with it.""" 23 | 24 | category = LicensingCategory 25 | 26 | @staticmethod 27 | def get_screen_id(): 28 | """Return a unique id of this UI screen.""" 29 | return "license-information" 30 | 31 | def __init__(self, *args, **kwargs): 32 | NormalTUISpoke.__init__(self, *args, **kwargs) 33 | self.title = _("License information") 34 | self._container = None 35 | 36 | def initialize(self): 37 | NormalTUISpoke.initialize(self) 38 | 39 | def refresh(self, args=None): 40 | NormalTUISpoke.refresh(self, args) 41 | 42 | self._container = ListRowContainer(1) 43 | 44 | log.debug("license found") 45 | # make the options aligned to the same column (the checkbox has the 46 | # '[ ]' prepended) 47 | self._container.add(TextWidget("%s\n" % _("Read the License Agreement")), 48 | self._show_license_screen_callback) 49 | 50 | self._container.add(CheckboxWidget(title=_("I accept the license agreement."), 51 | completed=self.data.eula.agreed), 52 | self._license_accepted_callback) 53 | self.window.add_with_separator(self._container) 54 | 55 | @property 56 | def completed(self): 57 | # Either there is no EULA available, or user agrees/disagrees with it. 58 | return self.data.eula.agreed 59 | 60 | @property 61 | def mandatory(self): 62 | # This spoke is always mandatory. 63 | return True 64 | 65 | @property 66 | def status(self): 67 | return _("License accepted") if self.data.eula.agreed else _("License not accepted") 68 | 69 | @classmethod 70 | def should_run(cls, environment, data): 71 | # the EULA spoke should only run in Initial Setup 72 | if eula_available() and FirstbootOnlySpokeMixIn.should_run(environment, data): 73 | # don't run if we are in reconfig mode and the EULA has already been accepted 74 | if data and data.firstboot.firstboot == FIRSTBOOT_RECONFIG and data.eula.agreed: 75 | log.debug("not running license spoke: reconfig mode & license already accepted") 76 | return False 77 | return True 78 | return False 79 | 80 | def apply(self): 81 | # nothing needed here, the agreed field is changed in the input method 82 | pass 83 | 84 | def input(self, args, key): 85 | if not self._container.process_user_input(key): 86 | return key 87 | 88 | return InputState.PROCESSED 89 | 90 | @staticmethod 91 | def _show_license_screen_callback(data): 92 | # show license 93 | log.debug("showing the license") 94 | eula_screen = LicenseScreen() 95 | ScreenHandler.push_screen(eula_screen) 96 | 97 | def _license_accepted_callback(self, data): 98 | # toggle EULA agreed checkbox by changing ksdata 99 | log.debug("license accepted state changed to: %s", self.data.eula.agreed) 100 | self.data.eula.agreed = not self.data.eula.agreed 101 | self.redraw() 102 | 103 | 104 | class LicenseScreen(UIScreen): 105 | """Screen showing the License without any input from user requested.""" 106 | 107 | def __init__(self): 108 | super().__init__() 109 | 110 | self._license_file = get_license_file_name() 111 | 112 | def refresh(self, args=None): 113 | super().refresh(args) 114 | 115 | # read the license file and make it one long string so that it can be 116 | # processed by the TextWidget to fit in the screen in a best possible 117 | # way 118 | log.debug("reading the license file") 119 | with open(self._license_file, 'r') as f: 120 | license_text = f.read() 121 | 122 | self.window.add_with_separator(TextWidget(license_text)) 123 | 124 | def input(self, args, key): 125 | """ Handle user input. """ 126 | return InputState.PROCESSED_AND_CLOSE 127 | 128 | def prompt(self, args=None): 129 | # we don't want to prompt user, just close the screen 130 | self.close() 131 | return None 132 | -------------------------------------------------------------------------------- /initial_setup/gui/hubs/initial_setup.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | False 8 | DISTRIBUTION SETUP 9 | INITIAL SETUP 10 | quitButton 11 | continueButton 12 | 13 | 14 | False 15 | vertical 16 | 17 | 18 | False 19 | 20 | 21 | False 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | False 33 | False 34 | 0 35 | 36 | 37 | 38 | 39 | False 40 | 0 41 | 0.5 42 | 43 | 44 | False 45 | vertical 46 | 6 47 | 48 | 49 | True 50 | True 51 | never 52 | 53 | 54 | 55 | 56 | 57 | True 58 | True 59 | 1 60 | 61 | 62 | 63 | 64 | 65 | 66 | True 67 | True 68 | 1 69 | 70 | 71 | 72 | 73 | True 74 | False 75 | 6 76 | 6 77 | 6 78 | 79 | 80 | _QUIT 81 | True 82 | True 83 | True 84 | start 85 | True 86 | True 87 | 88 | 89 | False 90 | True 91 | 0 92 | 93 | 94 | 95 | 96 | _FINISH CONFIGURATION 97 | True 98 | True 99 | True 100 | end 101 | True 102 | 103 | 104 | False 105 | True 106 | 1 107 | 108 | 109 | 110 | 111 | False 112 | True 113 | 2 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /initial_setup/gui/spokes/eula.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | The license will go here 7 | 8 | 9 | False 10 | True 11 | True 12 | License Information 13 | 14 | 15 | 16 | False 17 | vertical 18 | 6 19 | 20 | 21 | False 22 | 23 | 24 | False 25 | 6 26 | 6 27 | 6 28 | 29 | 30 | 31 | 32 | False 33 | False 34 | 0 35 | 36 | 37 | 38 | 39 | False 40 | 0.80000001192092896 41 | 0.80000001192092896 42 | 43 | 44 | False 45 | vertical 46 | 47 | 48 | True 49 | False 50 | start 51 | 24 52 | 6 53 | License Agreement: 54 | 55 | 56 | False 57 | True 58 | 0 59 | 60 | 61 | 62 | 63 | True 64 | False 65 | True 66 | True 67 | vertical 68 | 69 | 70 | True 71 | True 72 | in 73 | 74 | 75 | True 76 | True 77 | 18 78 | True 79 | True 80 | natural 81 | 12 82 | False 83 | word 84 | 12 85 | 12 86 | False 87 | eulaBuffer 88 | 89 | 90 | 91 | 92 | False 93 | True 94 | 0 95 | 96 | 97 | 98 | 99 | I _accept the license agreement. 100 | True 101 | True 102 | False 103 | True 104 | 0 105 | True 106 | 107 | 108 | 109 | False 110 | True 111 | 2 112 | 113 | 114 | 115 | 116 | False 117 | True 118 | 1 119 | 120 | 121 | 122 | 123 | 124 | 125 | True 126 | True 127 | 1 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /scripts/makeupdates: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright (C) 2021 Red Hat, Inc. 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU Lesser General Public License as published 7 | # by the Free Software Foundation; either version 2.1 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | import getopt 20 | import os 21 | import shutil 22 | import six 23 | import subprocess 24 | import sys 25 | 26 | # The Python site-packages path for initial-setup. 27 | SITE_PACKAGES_PATH = "./usr/lib/python3.10/site-packages/" 28 | 29 | 30 | def getArchiveTag(spec, name="initial-setup"): 31 | """ Get the last tag from the .spec file 32 | """ 33 | f = open(spec) 34 | lines = f.readlines() 35 | f.close() 36 | 37 | version = "0.0" 38 | release = "1" 39 | for line in lines: 40 | if line.startswith('Version:'): 41 | version = line.split()[1] 42 | elif line.startswith('Release:'): 43 | release = line.split()[1].split('%')[0] 44 | else: 45 | continue 46 | 47 | return "-".join([name, version, release]) 48 | 49 | 50 | def getArchiveTagOffset(spec, offset, name="initial-setup"): 51 | tag = getArchiveTag(spec, name) 52 | 53 | if not tag.count("-") >= 2: 54 | return tag 55 | ldash = tag.rfind("-") 56 | bldash = tag[:ldash].rfind("-") 57 | ver = tag[bldash+1:ldash] 58 | 59 | if not ver.count(".") >= 1: 60 | return tag 61 | ver = ver[:ver.rfind(".")] 62 | 63 | if not len(ver) > 0: 64 | return tag 65 | globstr = "refs/tags/" + tag[:bldash+1] + ver + ".*" 66 | proc = subprocess.Popen(['git', 'for-each-ref', '--sort=taggerdate', 67 | '--format=%(tag)', globstr], 68 | stdout=subprocess.PIPE, 69 | stderr=subprocess.PIPE).communicate() 70 | lines = proc[0].strip("\n").split('\n') 71 | lines.reverse() 72 | 73 | try: 74 | return lines[offset] 75 | except IndexError: 76 | return tag 77 | 78 | 79 | def doGitDiff(tag, args=None): 80 | args = args or [] 81 | proc = subprocess.Popen(['git', 'diff', '--name-status', tag] + args, 82 | stdout=subprocess.PIPE, 83 | stderr=subprocess.PIPE) 84 | out, _err = proc.communicate() 85 | if six.PY3: 86 | out = out.decode("utf-8") 87 | 88 | return out.split('\n') 89 | 90 | 91 | def copyUpdatedFiles(tag, updates): 92 | sys.stdout.write("Using site-packages path: %s\n" % SITE_PACKAGES_PATH) 93 | 94 | def install_to_dir(fname, relpath): 95 | sys.stdout.write("Including %s\n" % fname) 96 | outdir = os.path.join(updates, relpath) 97 | if not os.path.isdir(outdir): 98 | os.makedirs(outdir) 99 | shutil.copy2(fname, outdir) 100 | 101 | lines = doGitDiff(tag) 102 | for line in lines: 103 | fields = line.split() 104 | 105 | if len(fields) < 2: 106 | continue 107 | 108 | status = fields[0] 109 | gitfile = fields[1] 110 | 111 | if status == "D": 112 | continue 113 | 114 | if gitfile.endswith('.spec') or (gitfile.find('Makefile') != -1) or \ 115 | gitfile.endswith('.c') or gitfile.endswith('.h') or \ 116 | gitfile.endswith('.sh') or gitfile == 'setup.py' or \ 117 | gitfile.endswith('makeupdates'): 118 | continue 119 | 120 | if gitfile.startswith('initial_setup/'): 121 | dirname = os.path.join(SITE_PACKAGES_PATH, os.path.dirname(gitfile)) 122 | install_to_dir(gitfile, dirname) 123 | elif gitfile.startswith('scripts'): 124 | install_to_dir(gitfile, "./usr/libexec/initial-setup/") 125 | elif gitfile.startswith('data'): 126 | install_to_dir(gitfile, "./etc/initial-setup/conf.d/") 127 | 128 | 129 | def addRpms(updates, add_rpms): 130 | for rpm in add_rpms: 131 | cmd = "cd %s && rpm2cpio %s | cpio -dium" % (updates, rpm) 132 | sys.stdout.write(cmd+"\n") 133 | os.system(cmd) 134 | 135 | 136 | def createUpdatesImage(cwd, updates): 137 | os.chdir(updates) 138 | os.system("find . | cpio -c -o | gzip -9cv > %s/updates.img" % (cwd,)) 139 | sys.stdout.write("updates.img ready\n") 140 | 141 | 142 | def usage(cmd): 143 | sys.stdout.write("Usage: %s [OPTION]...\n" % (cmd,)) 144 | sys.stdout.write("Options:\n") 145 | sys.stdout.write(" -k, --keep Do not delete updates subdirectory.\n") 146 | sys.stdout.write(" -h, --help Display this help and exit.\n") 147 | sys.stdout.write(" -t, --tag Make image from TAG to HEAD.\n") 148 | sys.stdout.write(" -o, --offset Make image from (latest_tag - OFFSET) to HEAD.\n") 149 | sys.stdout.write(" -a, --add Add contents of rpm to the update\n") 150 | 151 | 152 | def main(argv): 153 | prog = os.path.basename(argv[0]) 154 | cwd = os.getcwd() 155 | spec = os.path.realpath(cwd + '/initial-setup.spec') 156 | updates = cwd + '/updates' 157 | keep, show_help, unknown = False, False, False 158 | tag = None 159 | opts = [] 160 | offset = 0 161 | add_rpms = [] 162 | 163 | try: 164 | opts, _args = getopt.getopt(argv[1:], 'a:t:o:k?', 165 | ['add=', 'tag=', 'offset=', 166 | 'keep', 'help']) 167 | except getopt.GetoptError: 168 | show_help = True 169 | 170 | for o, a in opts: 171 | if o in ('-k', '--keep'): 172 | keep = True 173 | elif o in ('-?', '--help'): 174 | show_help = True 175 | elif o in ('-t', '--tag'): 176 | tag = a 177 | elif o in ('-o', '--offset'): 178 | offset = int(a) 179 | elif o in ('-a', '--add'): 180 | add_rpms.append(os.path.abspath(a)) 181 | else: 182 | unknown = True 183 | 184 | if show_help: 185 | usage(prog) 186 | sys.exit(0) 187 | elif unknown: 188 | sys.stderr.write("%s: extra operand `%s'" % (prog, argv[1],)) 189 | sys.stderr.write("Try `%s --help' for more information." % (prog,)) 190 | sys.exit(1) 191 | 192 | if not os.path.isfile(spec): 193 | sys.stderr.write("You must be at the top level of the source tree.\n") 194 | sys.exit(1) 195 | 196 | if not tag: 197 | if offset < 1: 198 | tag = getArchiveTag(spec) 199 | else: 200 | tag = getArchiveTagOffset(spec, offset) 201 | sys.stdout.write("Using tag: %s\n" % tag) 202 | 203 | if not os.path.isdir(updates): 204 | os.makedirs(updates) 205 | 206 | copyUpdatedFiles(tag, updates) 207 | 208 | if add_rpms: 209 | addRpms(updates, add_rpms) 210 | 211 | createUpdatesImage(cwd, updates) 212 | 213 | if not keep: 214 | shutil.rmtree(updates) 215 | 216 | 217 | if __name__ == "__main__": 218 | main(sys.argv) 219 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Taken from the anaconda and python-meh sources 2 | # 3 | # Copyright (C) 2009-2020 Red Hat, Inc. 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU Lesser General Public License as published 7 | # by the Free Software Foundation; either version 2.1 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | # 18 | # Author: Martin Sivak 19 | # Author: Martin Kolman 20 | 21 | include ./branch-config.mk 22 | 23 | PKGNAME=initial-setup 24 | VERSION=$(shell awk '/Version:/ { print $$2 }' $(PKGNAME).spec) 25 | RELEASE=$(shell awk '/Release:/ { print $$2 }' $(PKGNAME).spec | sed -e 's|%.*$$||g') 26 | TAG=r$(VERSION)-$(RELEASE) 27 | 28 | PYTHON=python3 29 | # Use modern python -m build instead of setup.py 30 | BUILD_CMD ?= $(PYTHON) -m build 31 | 32 | # LOCALIZATION SETTINGS 33 | L10N_REPOSITORY ?= https://github.com/rhinstaller/initial-setup-l10n.git 34 | L10N_REPOSITORY_RW ?= git@github.com:rhinstaller/initial-setup-l10n.git 35 | # Branch used in localization repository. This should be master all the time. 36 | GIT_L10N_BRANCH ?= master 37 | 38 | # Name of our local TMT run 39 | TMT_ID ?= initial-setup-tests 40 | TMT_COPR_ANACONDA_REPO ?= 41 | 42 | default: all 43 | 44 | all: po-files 45 | 46 | .PHONY: install 47 | install: 48 | $(PYTHON) -m pip install . --root=$(DESTDIR) --verbose --no-deps --no-build-isolation 49 | $(MAKE) -C po install 50 | 51 | .PHONY: clean 52 | clean: 53 | -rm *.tar.gz ChangeLog initial-setup-*.src.rpm 54 | -rm -rf $(TEST_BUILD_DIR) dist/ build/ *.egg-info 55 | -find . -name "*.pyc" -exec rm -rf {} \; 56 | 57 | # local run of TMT tests 58 | # local run will be executed on source instead of RPM. It's much faster and easier, however, 59 | # Packit and Gating will execute the tests on an installed RPM; see test fmf specification. 60 | .PHONY: test 61 | test: 62 | # Command will execute all steps first time (see TMT plans to find out more). On repeated run only 63 | # discover and execute steps will be executed. This will save a lot of time during test development. 64 | # To run the skipped prepare steps again please call `make test-cleanup`. 65 | if [ -z "$(TMT_COPR_ANACONDA_REPO)" ]; then \ 66 | tmt run -vvv --id $(TMT_ID) --until report discover -f execute -f; \ 67 | else \ 68 | tmt run -vvv --id $(TMT_ID) --until report prepare -h install --copr "$(TMT_COPR_ANACONDA_REPO)" --package anaconda discover -f execute -f; \ 69 | fi 70 | 71 | # clean the container and test data 72 | .PHONY: test-cleanup 73 | test-cleanup: 74 | tmt run -vvv --rm --id $(TMT_ID) --after report finish -f 75 | 76 | .PHONY: ChangeLog 77 | ChangeLog: 78 | (GIT_DIR=.git git log > .changelog.tmp && mv .changelog.tmp ChangeLog; rm -f .changelog.tmp) || (touch ChangeLog; echo 'git directory not found: installing possibly empty changelog.' >&2) 79 | 80 | .PHONY: tag 81 | tag: 82 | git tag -a -m "Tag as $(TAG)" -f $(TAG) 83 | @echo "Tagged as $(TAG)" 84 | 85 | .PHONY: release 86 | release: 87 | $(MAKE) bumpver 88 | $(MAKE) commit 89 | $(MAKE) tag 90 | $(MAKE) archive 91 | 92 | .PHONY: archive 93 | archive: po-pull ChangeLog 94 | $(BUILD_CMD) 95 | # Fix naming: setuptools creates initial_setup-* but we want initial-setup-* 96 | if [ -f "dist/initial_setup-$(VERSION).tar.gz" ]; then \ 97 | cd dist && \ 98 | tar -xzf initial_setup-$(VERSION).tar.gz && \ 99 | mv initial_setup-$(VERSION) $(PKGNAME)-$(VERSION) && \ 100 | tar -czf $(PKGNAME)-$(VERSION).tar.gz $(PKGNAME)-$(VERSION) && \ 101 | rm -rf initial_setup-$(VERSION).tar.gz $(PKGNAME)-$(VERSION); \ 102 | fi 103 | @echo "The archive is in $(PKGNAME)-$(VERSION).tar.gz" 104 | 105 | .PHONY: local 106 | local: po-pull ChangeLog 107 | @rm -rf $(PKGNAME)-$(VERSION).tar.gz 108 | @rm -rf /tmp/$(PKGNAME)-$(VERSION) /tmp/$(PKGNAME) 109 | @dir=$$PWD; cp -a $$dir /tmp/$(PKGNAME)-$(VERSION) 110 | @cd /tmp/$(PKGNAME)-$(VERSION) ; $(PYTHON) -m build --sdist --no-isolation 111 | @cp /tmp/$(PKGNAME)-$(VERSION)/dist/$(PKGNAME)-$(VERSION).tar.gz . 112 | @rm -rf /tmp/$(PKGNAME)-$(VERSION) 113 | @echo "The archive is in $(PKGNAME)-$(VERSION).tar.gz" 114 | 115 | .PHONY: rpmlog 116 | rpmlog: 117 | @git log --no-merges --pretty="format:- %s (%ae)" $(TAG).. |sed -e 's/@.*)/)/' 118 | @echo 119 | 120 | .PHONY: po-files 121 | po-files: 122 | $(MAKE) -C po 123 | 124 | .PHONY: potfile 125 | potfile: 126 | $(MAKE) -C po potfile 127 | 128 | .PHONY: po-pull 129 | po-pull: 130 | TEMP_DIR=$$(mktemp --tmpdir -d $(PKGNAME)-localization-XXXXXXXXXX) && \ 131 | git clone --depth 1 -b $(GIT_L10N_BRANCH) -- $(L10N_REPOSITORY) $$TEMP_DIR && \ 132 | cp $$TEMP_DIR/$(L10N_DIR)/*.po ./po/ && \ 133 | rm -rf $$TEMP_DIR 134 | 135 | .PHONY: potfile 136 | po-push: potfile 137 | # This algorithm will make these steps: 138 | # - clone localization repository 139 | # - copy pot file to this repository 140 | # - check if pot file is changed (ignore the POT-Creation-Date otherwise it's always changed) 141 | # - if not changed: 142 | # - remove cloned repository 143 | # - if changed: 144 | # - add pot file 145 | # - commit pot file 146 | # - tell user to verify this file and push to the remote from the temp dir 147 | TEMP_DIR=$$(mktemp --tmpdir -d $(PKGNAME)-localization-XXXXXXXXXX) || exit 1 ; \ 148 | git clone --depth 1 -b $(GIT_L10N_BRANCH) -- $(L10N_REPOSITORY_RW) $$TEMP_DIR || exit 2 ; \ 149 | cp ./po/$(PKGNAME).pot $$TEMP_DIR/$(L10N_DIR)/ || exit 3 ; \ 150 | pushd $$TEMP_DIR/$(L10N_DIR) ; \ 151 | git difftool --trust-exit-code -y -x "diff -u -I '^\"POT-Creation-Date: .*$$'" HEAD ./$(PKGNAME).pot &>/dev/null ; \ 152 | if [ $$? -eq 0 ] ; then \ 153 | popd ; \ 154 | echo "Pot file is up to date" ; \ 155 | rm -rf $$TEMP_DIR ; \ 156 | else \ 157 | git add ./$(PKGNAME).pot && \ 158 | git commit -m "Update $(PKGNAME).pot" && \ 159 | popd && \ 160 | echo "Pot file updated for the localization repository $(L10N_REPOSITORY)" && \ 161 | echo "Please confirm changes and push:" && \ 162 | echo "$$TEMP_DIR" ; \ 163 | fi ; 164 | 165 | .PHONY: po-push 166 | bumpver: po-push 167 | read -p "Please see the above message. Verify and push localization commit. Press anything to continue." -n 1 -r 168 | 169 | @NEWSUBVER=$$((`echo $(VERSION) |cut -d . -f 3` + 1)) ; \ 170 | NEWVERSION=`echo $(VERSION).$$NEWSUBVER |cut -d . -f 1,2,4` ; \ 171 | DATELINE="* `LANG=c date "+%a %b %d %Y"` `git config user.name` <`git config user.email`> - $$NEWVERSION-1" ; \ 172 | cl=`grep -n %changelog initial-setup.spec |cut -d : -f 1` ; \ 173 | tail --lines=+$$(($$cl + 1)) initial-setup.spec > speclog ; \ 174 | (head -n $$cl initial-setup.spec ; echo "$$DATELINE" ; make --quiet --no-print-directory rpmlog 2>/dev/null ; echo ""; cat speclog) > initial-setup.spec.new ; \ 175 | mv initial-setup.spec.new initial-setup.spec ; rm -f speclog ; \ 176 | sed -i "s/Version: $(VERSION)/Version: $$NEWVERSION/" initial-setup.spec ; \ 177 | sed -i "s/version = \"$(VERSION)\"/version = \"$$NEWVERSION\"/" pyproject.toml ; \ 178 | sed -i "s/__version__ = \"$(VERSION)\"/__version__ = \"$$NEWVERSION\"/" initial_setup/__init__.py ; \ 179 | 180 | .PHONY: commit 181 | commit: 182 | git add initial-setup.spec initial_setup/__init__.py po/initial-setup.pot pyproject.toml ; \ 183 | git commit -m "New version $(VERSION)" ; \ 184 | -------------------------------------------------------------------------------- /tests/pylint/pointless-override.py: -------------------------------------------------------------------------------- 1 | # Pylint checker for pointless class attributes overrides. 2 | # 3 | # Copyright (C) 2014 Red Hat, Inc. 4 | # 5 | # This copyrighted material is made available to anyone wishing to use, 6 | # modify, copy, or redistribute it subject to the terms and conditions of 7 | # the GNU General Public License v.2, or (at your option) any later version. 8 | # This program is distributed in the hope that it will be useful, but WITHOUT 9 | # ANY WARRANTY expressed or implied, including the implied warranties of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 11 | # Public License for more details. You should have received a copy of the 12 | # GNU General Public License along with this program; if not, write to the 13 | # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 14 | # 02110-1301, USA. Any Red Hat trademarks that are incorporated in the 15 | # source code or documentation are not subject to the GNU General Public 16 | # License and may only be used or replicated with the express permission of 17 | # Red Hat, Inc. 18 | # 19 | # Red Hat Author(s): Anne Mulhern 20 | # 21 | 22 | import abc 23 | 24 | from six import add_metaclass 25 | 26 | import astroid 27 | 28 | from pylint.checkers import BaseChecker 29 | from pylint.checkers.utils import check_messages 30 | from pylint.interfaces import IAstroidChecker 31 | 32 | @add_metaclass(abc.ABCMeta) 33 | class PointlessData(object): 34 | 35 | _DEF_CLASS = abc.abstractproperty(doc="Class of interesting definitions.") 36 | message_id = abc.abstractproperty(doc="Pylint message identifier.") 37 | 38 | @classmethod 39 | @abc.abstractmethod 40 | def _retain_node(cls, node, restrict=True): 41 | """ Determines whether to retain a node for the analysis. 42 | 43 | :param node: an AST node 44 | :type node: astroid.Class 45 | :param restrict bool: True if results returned should be restricted 46 | :returns: True if the node should be kept, otherwise False 47 | :rtype: bool 48 | 49 | Restricted nodes are candidates for being marked as overridden. 50 | Only restricted nodes are put into the initial pool of candidates. 51 | """ 52 | raise NotImplementedError() 53 | 54 | @staticmethod 55 | @abc.abstractmethod 56 | def _extract_value(node): 57 | """ Return the node that contains the assignment's value. 58 | 59 | :param node: an AST node 60 | :type node: astroid.Class 61 | :returns: the node corresponding to the value 62 | :rtype: bool 63 | """ 64 | raise NotImplementedError() 65 | 66 | @staticmethod 67 | @abc.abstractmethod 68 | def _extract_targets(node): 69 | """ Generates the names being assigned to. 70 | 71 | :param node: an AST node 72 | :type node: astroid.Class 73 | :returns: a list of assignment target names 74 | :rtype: generator of str 75 | """ 76 | raise NotImplementedError() 77 | 78 | @classmethod 79 | def get_data(cls, node, restrict=True): 80 | """ Find relevant nodes for this analysis. 81 | 82 | :param node: an AST node 83 | :type node: astroid.Class 84 | :param restrict bool: True if results returned should be restricted 85 | 86 | :rtype: generator of astroid.Class 87 | :returns: a generator of interesting nodes. 88 | 89 | Note that all nodes returned are guaranteed to be instances of 90 | some class in self._DEF_CLASS. 91 | """ 92 | nodes = (n for n in node.body if isinstance(n, cls._DEF_CLASS)) 93 | for n in nodes: 94 | if cls._retain_node(n, restrict): 95 | for name in cls._extract_targets(n): 96 | yield (name, cls._extract_value(n)) 97 | 98 | @classmethod 99 | @abc.abstractmethod 100 | def check_equal(cls, node, other): 101 | """ Check whether the two nodes are considered equal. 102 | 103 | :param node: some ast node 104 | :param other: some ast node 105 | 106 | :rtype: bool 107 | :returns: True if the nodes are considered equal, otherwise False 108 | 109 | If the method returns True, the nodes are actually equal, but it 110 | may return False when the nodes are equal. 111 | """ 112 | raise NotImplementedError() 113 | 114 | class PointlessFunctionDefinition(PointlessData): 115 | """ Looking for pointless function definitions. """ 116 | 117 | _DEF_CLASS = astroid.Function 118 | message_id = "W9952" 119 | 120 | @classmethod 121 | def _retain_node(cls, node, restrict=True): 122 | return not restrict or \ 123 | (len(node.body) == 1 and isinstance(node.body[0], astroid.Pass)) 124 | 125 | @classmethod 126 | def check_equal(cls, node, other): 127 | return len(node.body) == 1 and isinstance(node.body[0], astroid.Pass) and \ 128 | len(other.body) == 1 and isinstance(other.body[0], astroid.Pass) 129 | 130 | @staticmethod 131 | def _extract_value(node): 132 | return node 133 | 134 | @staticmethod 135 | def _extract_targets(node): 136 | yield node.name 137 | 138 | class PointlessAssignment(PointlessData): 139 | 140 | _DEF_CLASS = astroid.Assign 141 | message_id = "W9951" 142 | 143 | _VALUE_CLASSES = ( 144 | astroid.Const, 145 | astroid.Dict, 146 | astroid.List, 147 | astroid.Tuple 148 | ) 149 | 150 | @classmethod 151 | def _retain_node(cls, node, restrict=True): 152 | return not restrict or isinstance(node.value, cls._VALUE_CLASSES) 153 | 154 | @classmethod 155 | def check_equal(cls, node, other): 156 | if type(node) != type(other): 157 | return False 158 | if isinstance(node, astroid.Const): 159 | return node.value == other.value 160 | if isinstance(node, (astroid.List, astroid.Tuple)): 161 | return len(node.elts) == len(other.elts) and \ 162 | all(cls.check_equal(n, o) for (n, o) in zip(node.elts, other.elts)) 163 | if isinstance(node, astroid.Dict): 164 | return len(node.items) == len(other.items) 165 | return False 166 | 167 | @staticmethod 168 | def _extract_value(node): 169 | return node.value 170 | 171 | @staticmethod 172 | def _extract_targets(node): 173 | for target in node.targets: 174 | yield target.name 175 | 176 | class PointlessClassAttributeOverrideChecker(BaseChecker): 177 | """ If the nearest definition of the class attribute in the MRO assigns 178 | it the same value, then the overriding definition is said to be 179 | pointless. 180 | 181 | The algorithm for detecting a pointless attribute override is the following. 182 | 183 | * For each class, C: 184 | - For each attribute assignment, 185 | name_1 = name_2 ... name_n = l (where l is a literal): 186 | * For each n in (n_1, n_2): 187 | - Traverse the linearization of the MRO until the first 188 | matching assignment n = l' is identified. If l is equal to l', 189 | then consider that the assignment to l in C is a 190 | pointless override. 191 | 192 | The algorithm for detecting a pointless method override has the same 193 | general structure, and the same defects discussed below. 194 | 195 | Note that this analysis is neither sound nor complete. It is unsound 196 | under multiple inheritance. Consider the following class hierarchy:: 197 | 198 | class A(object): 199 | _attrib = False 200 | 201 | class B(A): 202 | _attrib = False 203 | 204 | class C(A): 205 | _attrib = True 206 | 207 | class D(B,C): 208 | pass 209 | 210 | In this case, starting from B, B._attrib = False would be considered 211 | pointless. However, for D the MRO is B, C, A, and removing the assignment 212 | B._attrib = False would change the inherited value of D._attrib from 213 | False to True. 214 | 215 | The analysis is incomplete because it will find some values unequal when 216 | actually they are equal. 217 | 218 | The analysis is both incomplete and unsound because it expects that 219 | assignments will always be made by means of the same syntax. 220 | """ 221 | 222 | __implements__ = (IAstroidChecker,) 223 | 224 | name = "pointless class attribute override checker" 225 | msgs = { 226 | "W9951": 227 | ( 228 | "Assignment to class attribute %s overrides identical assignment in ancestor.", 229 | "pointless-class-attribute-override", 230 | "Assignment to class attribute that overrides assignment in ancestor that assigns identical value has no effect." 231 | ), 232 | "W9952": 233 | ( 234 | "definition of %s method overrides identical method definition in ancestor", 235 | "pointless-method-definition-override", 236 | "Overriding empty method definition with another empty method definition has no effect." 237 | ) 238 | } 239 | 240 | @check_messages("W9951", "W9952") 241 | def visit_class(self, node): 242 | for checker in (PointlessAssignment, PointlessFunctionDefinition): 243 | for (name, value) in checker.get_data(node): 244 | for a in node.ancestors(): 245 | match = next((v for (n, v) in checker.get_data(a, False) if n == name), None) 246 | if match is not None: 247 | if checker.check_equal(value, match): 248 | self.add_message(checker.message_id, node=value, args=(name,)) 249 | break 250 | 251 | def register(linter): 252 | linter.register_checker(PointlessClassAttributeOverrideChecker(linter)) 253 | -------------------------------------------------------------------------------- /initial_setup/tui/tui.py: -------------------------------------------------------------------------------- 1 | from pyanaconda.ui.tui import TextUserInterface 2 | from pyanaconda.core.threads import thread_manager 3 | 4 | from initial_setup.product import get_product_title, is_final 5 | from initial_setup.common import list_usable_consoles_for_tui, get_quit_message 6 | from .hubs import InitialSetupMainHub 7 | 8 | from simpleline import App 9 | from simpleline.errors import NothingScheduledError 10 | 11 | import os 12 | import sys 13 | import select 14 | import contextlib 15 | import termios 16 | import logging 17 | log = logging.getLogger("initial-setup") 18 | 19 | QUIT_MESSAGE = get_quit_message() 20 | 21 | 22 | class MultipleTTYHandler(object): 23 | """Run the Initial Setup TUI on all usable consoles. 24 | 25 | This is done by redirecting the Initial Setup stdout to all 26 | usable consoles and then redirecting any input back to 27 | the Initial Setup stdin. 28 | """ 29 | 30 | def __init__(self, tui_stdout_fd, tui_stdin_fd): 31 | # create file objects for the TUI stdout and stdin fds 32 | self._tui_stdout_fd = tui_stdout_fd 33 | self._tui_stdout = os.fdopen(tui_stdout_fd, "r") 34 | self._tui_stdin_fd = tui_stdin_fd 35 | self._tui_stdin = os.fdopen(tui_stdin_fd, "w") 36 | 37 | self._tui_active_out_fd, active_out_fd = os.pipe() 38 | self._tui_active_out = os.fdopen(self._tui_active_out_fd, "r") 39 | self._active_out = os.fdopen(active_out_fd, "w") 40 | 41 | self._shutdown = False 42 | 43 | self._active_console_in = None 44 | self._active_console_out = None 45 | 46 | self._console_read_fos = {} 47 | self._console_write_fos = [] 48 | self._open_all_consoles() 49 | 50 | def shutdown(self): 51 | """Tell the multi TTY handler to shutdown.""" 52 | self._shutdown = True 53 | 54 | def _open_all_consoles(self): 55 | """Open all consoles suitable for running the Initial Setup TUI.""" 56 | console_write_fos = {} 57 | console_read_fos = {} 58 | console_paths = (os.path.join("/dev", c) for c in list_usable_consoles_for_tui()) 59 | usable_console_paths = [] 60 | unusable_console_paths = [] 61 | for console_path in console_paths: 62 | try: 63 | write_fo = open(console_path, "w") 64 | read_fo = open(console_path, "r") 65 | fd = read_fo.fileno() 66 | console_write_fos[fd] = write_fo 67 | # the console stdin file descriptors need to be non-blocking 68 | os.set_blocking(fd, False) 69 | console_read_fos[fd] = read_fo 70 | # If we survived till now the console might be usable 71 | # (could be read and written into). 72 | usable_console_paths.append(console_path) 73 | except Exception: 74 | log.exception("can't open console for Initial Setup TUI: %s", console_path) 75 | unusable_console_paths.append(console_path) 76 | 77 | log.debug("The Initial Setup TUI will attempt to run on the following consoles:") 78 | log.debug("\n".join(usable_console_paths)) 79 | log.debug("The following consoles could not be opened and will not be used:") 80 | log.debug("\n".join(unusable_console_paths)) 81 | self._console_read_fos = console_read_fos 82 | self._console_write_fos = console_write_fos 83 | 84 | def run(self): 85 | """Run IS TUI on multiple consoles.""" 86 | # we wait for data from the consoles 87 | fds = list(self._console_read_fos.keys()) 88 | # as well as from the anaconda stdout 89 | fds.append(self._tui_stdout_fd) 90 | fds.append(self._tui_active_out_fd) 91 | log.info("multi TTY handler starting") 92 | while True: 93 | # Watch the consoles and IS TUI stdout for data and 94 | # react accordingly. 95 | # The select also triggers every second (the 1.0 parameter), 96 | # so that the infinite loop can be promptly interrupted once 97 | # the multi TTY handler is told to shutdown. 98 | rlist, _wlist, _xlist = select.select(fds, [], [], 1.0) 99 | if self._shutdown: 100 | log.info("multi TTY handler shutting down") 101 | break 102 | if self._tui_stdout_fd in rlist: 103 | # We need to set the TUI stdout fd to non-blocking, 104 | # as otherwise reading from it would (predictably) result in 105 | # the readline() function blocking once it runs out of data. 106 | os.set_blocking(self._tui_stdout_fd, False) 107 | 108 | # The IS TUI wants to write something, 109 | # read all the lines. 110 | lines = self._tui_stdout.readlines() 111 | 112 | # After we finish reading all the data we need to set 113 | # the TUI stdout fd to blocking again. 114 | # Otherwise the fd will not be usable when we try to read from 115 | # it again for unclear reasons. 116 | os.set_blocking(self._tui_stdout_fd, True) 117 | 118 | lines.append("\n") # seems to get lost somewhere on the way 119 | 120 | # Write all the lines IS wrote to stdout to all consoles 121 | # that we consider usable for the IS TUI. 122 | for console_fo in self._console_write_fos.values(): 123 | for one_line in lines: 124 | try: 125 | console_fo.write(one_line) 126 | except OSError: 127 | log.exception("failed to write %s to console %s", one_line, console_fo) 128 | 129 | # Don't go processing the events on other file descriptors until 130 | # we're done with everything that's supposed to be on stdout 131 | continue 132 | elif self._tui_active_out_fd in rlist: 133 | # Essentially the same as above but for the acrive console only 134 | os.set_blocking(self._tui_active_out_fd, False) 135 | lines = self._tui_active_out.readlines() 136 | os.set_blocking(self._tui_active_out_fd, True) 137 | write_fo = self._active_console_out 138 | try: 139 | for one_line in lines: 140 | write_fo.write(one_line) 141 | write_fo.flush() 142 | except OSError: 143 | log.exception("failed to write %s to active console", lines) 144 | else: 145 | for fd in rlist: 146 | # Someone typed some input to a console and hit enter, 147 | # forward the input to the IS TUI stdin. 148 | read_fo = self._console_read_fos[fd] 149 | write_fo = self._console_write_fos[fd] 150 | # as the console is getting input we consider it to be 151 | # the currently active console 152 | self._active_console_in = read_fo 153 | self._active_console_out = write_fo 154 | try: 155 | data = read_fo.readline() 156 | except TypeError: 157 | log.exception("input reading failed for console %s", read_fo) 158 | continue 159 | self._tui_stdin.write(data) 160 | self._tui_stdin.flush() 161 | 162 | def custom_getpass(self, prompt='Password: '): 163 | """Prompt for a password, with echo turned off that can run on an arbitrary console. 164 | 165 | This implementation is based on the Python 3.6 getpass() source code, with added 166 | support for running getpass() on an arbitrary console, as the original implementation 167 | is hardcoded to expect input from /dev/tty, without an option to change that. 168 | 169 | Raises: 170 | EOFError: If our input tty or stdin was closed. 171 | 172 | Always restores terminal settings before returning. 173 | """ 174 | 175 | input_fo = self._active_console_in 176 | output_fo = self._active_out 177 | 178 | passwd = None 179 | with contextlib.ExitStack() as stack: 180 | input_fd = input_fo.fileno() 181 | if input_fd is not None: 182 | try: 183 | old = termios.tcgetattr(input_fd) # a copy to save 184 | new = old[:] 185 | new[3] &= ~termios.ECHO # 3 == 'lflags' 186 | tcsetattr_flags = termios.TCSAFLUSH 187 | if hasattr(termios, 'TCSASOFT'): 188 | tcsetattr_flags |= termios.TCSASOFT 189 | try: 190 | termios.tcsetattr(input_fd, tcsetattr_flags, new) 191 | passwd = self._raw_input(prompt, output_fo, input_fo=input_fo) 192 | finally: 193 | termios.tcsetattr(input_fd, tcsetattr_flags, old) 194 | output_fo.flush() # Python issue7208 195 | except termios.error: 196 | if passwd is not None: 197 | # _raw_input succeeded. The final tcsetattr failed. Reraise 198 | # instead of leaving the terminal in an unknown state. 199 | raise 200 | # We can't control the tty or stdin. Give up and use normal IO. 201 | # _fallback_getpass() raises an appropriate warning. 202 | if output_fo is not input_fo: 203 | # clean up unused file objects before blocking 204 | stack.close() 205 | passwd = self._fallback_getpass(prompt, output_fo, input_fo) 206 | 207 | output_fo.write('\n') 208 | output_fo.flush() 209 | return passwd 210 | 211 | def _fallback_getpass(self, prompt='Password: ', output_fo=None, input_fo=None): 212 | log.warning("Can not control echo on the terminal: %s", input_fo) 213 | if not output_fo: 214 | output_fo = sys.stderr 215 | print("Warning: Password input may be echoed.", file=output_fo) 216 | return self._raw_input(prompt, output_fo, input_fo) 217 | 218 | def _raw_input(self, prompt="", output_fo=None, input_fo=None): 219 | # This doesn't save the string in the GNU readline history. 220 | 221 | # The input fd has to be set as non-blocking for the general multi-tty machinery 222 | # to work, but for password input to work correctly it needs to be set as blocking 223 | # when user input is expected. 224 | # We also have to switch it back to non-blocking once user input is received. 225 | os.set_blocking(input_fo.fileno(), True) 226 | prompt = str(prompt) 227 | if prompt: 228 | try: 229 | output_fo.write(prompt) 230 | except UnicodeEncodeError: 231 | # Use replace error handler to get as much as possible printed. 232 | prompt = prompt.encode(output_fo.encoding, 'replace') 233 | prompt = prompt.decode(output_fo.encoding) 234 | output_fo.write(prompt) 235 | output_fo.flush() 236 | # NOTE: The Python C API calls flockfile() (and unlock) during readline. 237 | line = input_fo.readline() 238 | if not line: 239 | raise EOFError 240 | if line[-1] == '\n': 241 | line = line[:-1] 242 | # We got input from the user, switch the input fd back to non-blocking 243 | # so that the multi-tty machinery works correctly. 244 | os.set_blocking(input_fo.fileno(), False) 245 | return line 246 | 247 | 248 | class InitialSetupTextUserInterface(TextUserInterface): 249 | """This is the main text based firstboot interface. It inherits from 250 | anaconda to make the look & feel as similar as possible. 251 | """ 252 | 253 | ENVIRONMENT = "firstboot" 254 | 255 | def __init__(self, cli_args): 256 | """Initialize the Initial Setup text UI. 257 | 258 | :param cli_args: command line arguments parsed by Argparse 259 | """ 260 | TextUserInterface.__init__(self, None, None, get_product_title, is_final(), 261 | quitMessage=QUIT_MESSAGE) 262 | 263 | self.multi_tty_handler = None 264 | self._use_multi_tty_handler = not cli_args.no_multi_tty 265 | 266 | # In some case, such as when running Initial Setup directly 267 | # in console or from an SSH session script, we should not 268 | # start the multi TTY handler and just run in the single 269 | # local console. 270 | if self._use_multi_tty_handler: 271 | # redirect stdin and stdout to custom pipes 272 | 273 | # stdin 274 | stdin_fd, tui_stdin_fd = os.pipe() 275 | sys.stdin = os.fdopen(stdin_fd, "r") 276 | 277 | # stdout 278 | tui_stdout_fd, stdout_fd = os.pipe() 279 | sys.stdout = os.fdopen(stdout_fd, "w") 280 | sys.stdout.reconfigure(line_buffering=True) 281 | 282 | # instantiate and start the multi TTY handler 283 | self.multi_tty_handler = MultipleTTYHandler(tui_stdin_fd=tui_stdin_fd, 284 | tui_stdout_fd=tui_stdout_fd) 285 | # start the multi-tty handler 286 | thread_manager.add_thread( 287 | name="initial_setup_multi_tty_thread", 288 | target=self.multi_tty_handler.run 289 | ) 290 | 291 | def setup(self, data): 292 | TextUserInterface.setup(self, data) 293 | if self._use_multi_tty_handler: 294 | # Make sure custom getpass() from multi-tty handler is used instead 295 | # of regular getpass. This needs to be done as the default getpass() 296 | # implementation cant work with arbitrary consoles and always defaults 297 | # to /dev/tty for input. 298 | configuration = App.get_configuration() 299 | configuration.password_function = self.multi_tty_handler.custom_getpass 300 | 301 | def run(self): 302 | try: 303 | super().run() 304 | except NothingScheduledError: 305 | log.info("not starting the text UI as no user interaction is required") 306 | 307 | def _list_hubs(self): 308 | return [InitialSetupMainHub] 309 | 310 | basemask = "firstboot.tui" 311 | basepath = os.path.dirname(__file__) 312 | paths = TextUserInterface.paths + { 313 | "spokes": [(basemask + ".spokes.%s", os.path.join(basepath, "spokes"))], 314 | "categories": [(basemask + ".categories.%s", os.path.join(basepath, "categories"))], 315 | } 316 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /initial_setup/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.3.101" 2 | 3 | import os 4 | import sys 5 | import signal 6 | import pykickstart 7 | import logging 8 | import argparse 9 | import traceback 10 | import atexit 11 | 12 | from initial_setup.product import eula_available 13 | from initial_setup import initial_setup_log 14 | 15 | from pyanaconda.core.dbus import DBus 16 | from pyanaconda.core.util import get_os_release_value 17 | from pyanaconda.localization import setup_locale_environment, setup_locale 18 | from pyanaconda.core.constants import FIRSTBOOT_ENVIRON, SETUP_ON_BOOT_RECONFIG, \ 19 | SETUP_ON_BOOT_DEFAULT 20 | from pyanaconda.flags import flags 21 | from pyanaconda.core.startup.dbus_launcher import AnacondaDBusLauncher 22 | from pyanaconda.modules.common.task import sync_run_task 23 | from pyanaconda.modules.common.constants.services import BOSS, LOCALIZATION, TIMEZONE, USERS, \ 24 | SERVICES, NETWORK 25 | from pyanaconda.modules.common.structures.kickstart import KickstartReport 26 | 27 | 28 | class InitialSetupError(Exception): 29 | pass 30 | 31 | 32 | INPUT_KICKSTART_PATH = "/root/anaconda-ks.cfg" 33 | OUTPUT_KICKSTART_PATH = "/root/initial-setup-ks.cfg" 34 | RECONFIG_FILES = ["/etc/reconfigSys", "/.unconfigured"] 35 | 36 | SUPPORTED_KICKSTART_COMMANDS = ["user", 37 | "group", 38 | "keyboard", 39 | "lang", 40 | "rootpw", 41 | "timezone", 42 | "selinux", 43 | "firewall"] 44 | 45 | # set the environment so that spokes can behave accordingly 46 | flags.environs = [FIRSTBOOT_ENVIRON] 47 | 48 | signal.signal(signal.SIGINT, signal.SIG_IGN) 49 | 50 | # setup logging 51 | log = logging.getLogger("initial-setup") 52 | 53 | logging_initialized = False 54 | 55 | 56 | def log_to_journal(message, priority=3): 57 | """A quick-and-dirty direct Journal logger. 58 | 59 | A quick-and-dirty direct Journal logger used to log errors that occur 60 | before the normal Python logging system is setup and connected to Journal. 61 | 62 | :param str message: message to send to Journal 63 | :param int priority: message priority (2 - critical, 3 - error, 4 - warning, 5 - notice, 6 - info) 64 | """ 65 | os.system('echo "%s" | systemd-cat -t initial-setup -p %s' % (message, priority)) 66 | 67 | 68 | def log_exception(*exception_info): 69 | exception_text = "".join(traceback.format_exception(*exception_info)) 70 | error_message = "Initial Setup crashed due to unhandled exception:\n%s" % exception_text 71 | if logging_initialized: 72 | log.error(error_message) 73 | else: 74 | log_to_journal(error_message) 75 | 76 | 77 | sys.excepthook = log_exception 78 | 79 | 80 | class InitialSetup(object): 81 | def __init__(self, gui_mode): 82 | """Initialize the Initial Setup internals""" 83 | log.debug("initializing Initial Setup") 84 | # True if running in graphical mode, False otherwise (text mode) 85 | self.gui_mode = gui_mode 86 | # kickstart data 87 | self.data = None 88 | # reboot on quit flag 89 | self._reboot_on_quit = False 90 | 91 | # parse any command line arguments 92 | self._args = self._parse_arguments() 93 | 94 | # initialize logging 95 | initial_setup_log.init(stdout_log=not self._args.no_stdout_log) 96 | global logging_initialized 97 | logging_initialized = True 98 | 99 | log.info("Initial Setup %s" % __version__) 100 | 101 | # check if we are running as root 102 | if os.geteuid() != 0: 103 | log.critical("Initial Setup needs to be run as root") 104 | raise InitialSetupError 105 | 106 | # load configuration files 107 | from pyanaconda.core.configuration.base import ConfigurationError 108 | from pyanaconda.core.configuration.anaconda import conf 109 | try: 110 | conf.set_from_detected_profile( 111 | get_os_release_value("ID") 112 | ) 113 | except ConfigurationError as e: 114 | log.warning(str(e)) 115 | 116 | conf.set_from_files(["/etc/initial-setup/conf.d/"]) 117 | 118 | if self.gui_mode: 119 | log.debug("running in GUI mode") 120 | else: 121 | log.debug("running in TUI mode") 122 | 123 | self._external_reconfig = False 124 | 125 | # check if the reconfig mode should be enabled 126 | # by checking if at least one of the reconfig 127 | # files exist 128 | for reconfig_file in RECONFIG_FILES: 129 | if os.path.exists(reconfig_file): 130 | self.external_reconfig = True 131 | log.debug("reconfig trigger file found: %s", reconfig_file) 132 | 133 | if self.external_reconfig: 134 | log.debug("running in externally triggered reconfig mode") 135 | 136 | if self.gui_mode: 137 | # We need this so we can tell GI to look for overrides objects 138 | # also in anaconda source directories 139 | import gi.overrides 140 | for p in os.environ.get("ANACONDA_WIDGETS_OVERRIDES", "").split(":"): 141 | gi.overrides.__path__.insert(0, p) 142 | log.debug("GI overrides imported") 143 | 144 | from pyanaconda.ui.lib.addons import collect_addon_ui_paths 145 | addon_paths = ["/usr/share/initial-setup/modules", "/usr/share/anaconda/addons"] 146 | 147 | # append ADDON_PATHS dirs at the end 148 | sys.path.extend(addon_paths) 149 | 150 | self._addon_module_paths = collect_addon_ui_paths(addon_paths, self.gui_mode_id) 151 | log.info("found %d addon modules:", len(self._addon_module_paths)) 152 | for addon_path in self._addon_module_paths: 153 | log.debug(addon_path) 154 | 155 | # Too bad anaconda does not have modularized logging 156 | log.debug("initializing the Anaconda log") 157 | from pyanaconda import anaconda_logging 158 | anaconda_logging.init(write_to_journal=True) 159 | 160 | # create class for launching our dbus session 161 | self._dbus_launcher = AnacondaDBusLauncher() 162 | 163 | # group, user, root password set-before tracking 164 | self._groups_already_configured = False 165 | self._users_already_configured = False 166 | self._root_password_already_configured = False 167 | 168 | @property 169 | def external_reconfig(self): 170 | """External reconfig status. 171 | 172 | Reports if external (eq. not triggered by kickstart) has been enabled. 173 | 174 | :returns: True if external reconfig mode has been enabled, else False. 175 | :rtype: bool 176 | """ 177 | return self._external_reconfig 178 | 179 | @external_reconfig.setter 180 | def external_reconfig(self, value): 181 | self._external_reconfig = value 182 | 183 | @property 184 | def gui_mode_id(self): 185 | """String id of the current GUI mode 186 | 187 | :returns: "gui" if gui_mode is True, "tui" otherwise 188 | :rtype: str 189 | """ 190 | if self.gui_mode: 191 | return "gui" 192 | else: 193 | return "tui" 194 | 195 | @property 196 | def reboot_on_quit(self): 197 | # should the machine be rebooted once Initial Setup quits 198 | return self._reboot_on_quit 199 | 200 | def _parse_arguments(self): 201 | """Parse command line arguments""" 202 | # create an argparse instance 203 | parser = argparse.ArgumentParser(prog="Initial Setup", 204 | description="Initial Setup is can run during the first start of a newly installed" 205 | "system to configure it according to the needs of the user.") 206 | parser.add_argument("--no-stdout-log", action="store_true", default=False, help="don't log to stdout") 207 | parser.add_argument("--no-multi-tty", action="store_true", default=False, 208 | help="Don't run on multiple consoles.") 209 | parser.add_argument('--version', action='version', version=__version__) 210 | 211 | # parse arguments and return the result 212 | return parser.parse_args() 213 | 214 | def _load_kickstart(self): 215 | """Load the kickstart""" 216 | from pyanaconda import kickstart 217 | 218 | # Construct a commandMap with only the supported Anaconda's commands 219 | commandMap = dict((k, kickstart.commandMap[k]) for k in SUPPORTED_KICKSTART_COMMANDS) 220 | 221 | # Prepare new data object 222 | self.data = kickstart.AnacondaKSHandler(commandUpdates=commandMap) 223 | 224 | # lets assume there might be no kickstart file 225 | kickstart_path = None 226 | # only then try to find one 227 | if os.path.exists(INPUT_KICKSTART_PATH): 228 | log.info("using kickstart from Anaconda run for input") 229 | kickstart_path = INPUT_KICKSTART_PATH 230 | elif os.path.exists(OUTPUT_KICKSTART_PATH): 231 | log.info("using kickstart from previous run for input") 232 | kickstart_path = OUTPUT_KICKSTART_PATH 233 | else: 234 | log.info("using no kickstart file for input") 235 | 236 | # only parse kickstart (and tell Boss to parse kickstart) 237 | # after we actually found a kickstart file 238 | if kickstart_path: 239 | log.info("parsing input kickstart %s", kickstart_path) 240 | try: 241 | # Read the installed kickstart 242 | parser = kickstart.AnacondaKSParser(self.data) 243 | parser.readKickstart(kickstart_path) 244 | log.info("kickstart parsing done") 245 | except pykickstart.errors.KickstartError as kserr: 246 | log.critical("kickstart parsing failed: %s", kserr) 247 | log.critical("Initial Setup startup failed due to invalid kickstart file") 248 | raise InitialSetupError 249 | 250 | # if we got this far the kickstart should be valid, so send it to Boss as well 251 | boss = BOSS.get_proxy() 252 | report = KickstartReport.from_structure( 253 | boss.ReadKickstartFile(kickstart_path) 254 | ) 255 | 256 | if not report.is_valid(): 257 | message = "\n\n".join(map(str, report.error_messages)) 258 | raise InitialSetupError(message) 259 | 260 | if self.external_reconfig: 261 | # set the reconfig flag in kickstart so that 262 | # relevant spokes show up 263 | services_proxy = SERVICES.get_proxy() 264 | services_proxy.SetupOnBoot = SETUP_ON_BOOT_RECONFIG 265 | 266 | # Record if groups, users or root password has been set before Initial Setup 267 | # has been started, so that we don't trample over existing configuration. 268 | users_proxy = USERS.get_proxy() 269 | self._groups_already_configured = bool(users_proxy.Groups) 270 | self._users_already_configured = bool(users_proxy.Users) 271 | self._root_password_already_configured = users_proxy.IsRootPasswordSet 272 | 273 | def _setup_locale(self): 274 | log.debug("setting up locale") 275 | 276 | localization_proxy = LOCALIZATION.get_proxy() 277 | 278 | # Normalize the locale environment variables 279 | if localization_proxy.Kickstarted: 280 | locale_arg = localization_proxy.Language 281 | else: 282 | locale_arg = None 283 | setup_locale_environment(locale_arg, prefer_environment=True) 284 | setup_locale(os.environ['LANG'], text_mode=not self.gui_mode) 285 | 286 | def _initialize_network(self): 287 | log.debug("initializing network") 288 | network_proxy = NETWORK.get_proxy() 289 | network_proxy.CreateDeviceConfigurations() 290 | 291 | def _apply(self): 292 | # Do not execute sections that were part of the original 293 | # anaconda kickstart file (== have .seen flag set) 294 | 295 | log.info("applying changes") 296 | 297 | services_proxy = SERVICES.get_proxy() 298 | reconfig_mode = services_proxy.SetupOnBoot == SETUP_ON_BOOT_RECONFIG 299 | 300 | # data.selinux 301 | # data.firewall 302 | 303 | # Configure the timezone. 304 | timezone_proxy = TIMEZONE.get_proxy() 305 | for task_path in timezone_proxy.InstallWithTasks(): 306 | task_proxy = TIMEZONE.get_proxy(task_path) 307 | sync_run_task(task_proxy) 308 | 309 | # Configure the localization. 310 | localization_proxy = LOCALIZATION.get_proxy() 311 | for task_path in localization_proxy.InstallWithTasks(): 312 | task_proxy = LOCALIZATION.get_proxy(task_path) 313 | sync_run_task(task_proxy) 314 | 315 | # Configure persistent hostname 316 | network_proxy = NETWORK.get_proxy() 317 | network_task = network_proxy.ConfigureHostnameWithTask(True) 318 | task_proxy = NETWORK.get_proxy(network_task) 319 | sync_run_task(task_proxy) 320 | # Set current hostname 321 | network_proxy.SetCurrentHostname(network_proxy.Hostname) 322 | 323 | # Configure groups, users & root account 324 | # 325 | # NOTE: We only configure groups, users & root account if the respective 326 | # kickstart commands are *not* seen in the input kickstart. 327 | # This basically means that we will configure only what was 328 | # set in the Initial Setup UI and will not attempt to configure 329 | # anything that looks like it was configured previously in 330 | # the Anaconda UI or installation kickstart. 331 | users_proxy = USERS.get_proxy() 332 | 333 | if self._groups_already_configured and not reconfig_mode: 334 | log.debug("skipping user group configuration - already configured") 335 | elif users_proxy.Groups: # only run of there are some groups to create 336 | groups_task = users_proxy.ConfigureGroupsWithTask() 337 | task_proxy = USERS.get_proxy(groups_task) 338 | log.debug("configuring user groups via %s task", task_proxy.Name) 339 | sync_run_task(task_proxy) 340 | 341 | if self._users_already_configured and not reconfig_mode: 342 | log.debug("skipping user configuration - already configured") 343 | elif users_proxy.Users: # only run if there are some users to create 344 | users_task = users_proxy.ConfigureUsersWithTask() 345 | task_proxy = USERS.get_proxy(users_task) 346 | log.debug("configuring users via %s task", task_proxy.Name) 347 | sync_run_task(task_proxy) 348 | 349 | if self._root_password_already_configured and not reconfig_mode: 350 | log.debug("skipping root password configuration - already configured") 351 | else: 352 | root_task = users_proxy.SetRootPasswordWithTask() 353 | task_proxy = USERS.get_proxy(root_task) 354 | log.debug("configuring root password via %s task", task_proxy.Name) 355 | sync_run_task(task_proxy) 356 | 357 | # Configure all addons 358 | log.info("executing addons") 359 | boss_proxy = BOSS.get_proxy() 360 | for service_name, object_path in boss_proxy.CollectInstallSystemTasks(): 361 | task_proxy = DBus.get_proxy(service_name, object_path) 362 | sync_run_task(task_proxy) 363 | 364 | if self.external_reconfig: 365 | # prevent the reconfig flag from being written out 366 | # to kickstart if neither /etc/reconfigSys or /.unconfigured 367 | # are present 368 | services_proxy = SERVICES.get_proxy() 369 | services_proxy.SetupOnBoot = SETUP_ON_BOOT_DEFAULT 370 | 371 | # Write the kickstart data to file 372 | log.info("writing the Initial Setup kickstart file %s", OUTPUT_KICKSTART_PATH) 373 | with open(OUTPUT_KICKSTART_PATH, "w") as f: 374 | f.write(str(self.data)) 375 | log.info("finished writing the Initial Setup kickstart file") 376 | 377 | # Remove the reconfig files, if any - otherwise the reconfig mode 378 | # would start again next time the Initial Setup service is enabled. 379 | if self.external_reconfig: 380 | for reconfig_file in RECONFIG_FILES: 381 | if os.path.exists(reconfig_file): 382 | log.debug("removing reconfig trigger file: %s" % reconfig_file) 383 | os.remove(reconfig_file) 384 | 385 | # and we are done with applying changes 386 | log.info("all changes have been applied") 387 | 388 | def run(self): 389 | """Run Initial setup 390 | 391 | :param bool gui_mode: if GUI should be used (TUI is the default) 392 | 393 | :returns: True if the IS run was successful, False if it failed 394 | :rtype: bool 395 | """ 396 | # also register boss shutdown & DBUS session cleanup via exit handler 397 | atexit.register(self._dbus_launcher.stop) 398 | 399 | # start dbus session (if not already running) and run boss in it 400 | try: 401 | self._dbus_launcher.start() 402 | except TimeoutError as e: 403 | log.error(str(e)) 404 | return True 405 | 406 | self._load_kickstart() 407 | self._setup_locale() 408 | self._initialize_network() 409 | 410 | if self.gui_mode: 411 | try: 412 | # Try to import IS gui specifics 413 | log.debug("trying to import GUI") 414 | import initial_setup.gui 415 | except ImportError: 416 | log.exception("GUI import failed, falling back to TUI") 417 | self.gui_mode = False 418 | 419 | if self.gui_mode: 420 | # gui already imported (see above) 421 | 422 | # Add addons to search paths 423 | initial_setup.gui.InitialSetupGraphicalUserInterface.update_paths(self._addon_module_paths) 424 | 425 | # Initialize the UI 426 | log.debug("initializing GUI") 427 | ui = initial_setup.gui.InitialSetupGraphicalUserInterface() 428 | else: 429 | # Import IS gui specifics 430 | import initial_setup.tui 431 | 432 | # Add addons to search paths 433 | initial_setup.tui.InitialSetupTextUserInterface.update_paths(self._addon_module_paths) 434 | 435 | # Initialize the UI 436 | log.debug("initializing TUI") 437 | ui = initial_setup.tui.InitialSetupTextUserInterface(self._args) 438 | 439 | # Pass the data object to user interface 440 | log.debug("setting up the UI") 441 | ui.setup(self.data) 442 | 443 | # Start the application 444 | log.info("starting the UI") 445 | ret = ui.run() 446 | 447 | # we need to reboot the machine if there is an EULA, that was not agreed 448 | if eula_available() and not self.data.eula.agreed: 449 | log.warning("EULA has not been agreed - the system will be rebooted.") 450 | self._reboot_on_quit = True 451 | 452 | # TUI returns False if the app was ended prematurely 453 | # all other cases return True or None 454 | if ret is False: 455 | log.warning("ended prematurely in TUI") 456 | return True 457 | 458 | # apply changes 459 | self._apply() 460 | 461 | # in the TUI mode shutdown the multi TTY handler (if any) 462 | if not self.gui_mode and ui.multi_tty_handler: 463 | # TODO: wait for this to finish or make it blockng ? 464 | ui.multi_tty_handler.shutdown() 465 | 466 | # and we are done 467 | return True 468 | -------------------------------------------------------------------------------- /initial-setup.spec: -------------------------------------------------------------------------------- 1 | # Enable X11 for RHEL 9 and older only 2 | %bcond x11 %[0%{?rhel} && 0%{?rhel} < 10] 3 | 4 | Name: initial-setup 5 | Summary: Initial system configuration utility 6 | URL: https://fedoraproject.org/wiki/InitialSetup 7 | License: GPL-2.0-or-later 8 | Version: 0.3.101 9 | Release: 1%{?dist} 10 | 11 | # This is a Red Hat maintained package which is specific to 12 | # our distribution. 13 | # 14 | # The source is thus available only from within this SRPM 15 | # or via direct git checkout: 16 | # git clone https://github.com/rhinstaller/initial-setup 17 | Source0: %{name}-%{version}.tar.gz 18 | 19 | %define debug_package %{nil} 20 | %define anacondaver 37.8-1 21 | 22 | BuildRequires: gettext 23 | BuildRequires: python3-devel 24 | BuildRequires: systemd-units 25 | BuildRequires: gtk3-devel 26 | BuildRequires: glade-devel 27 | BuildRequires: intltool 28 | BuildRequires: make 29 | 30 | Requires: %{__python3} 31 | Requires: anaconda-tui >= %{anacondaver} 32 | Requires: libxkbcommon 33 | Requires: python3-simpleline >= 1.4 34 | Requires: systemd >= 235 35 | Requires(post): systemd 36 | Requires(preun): systemd 37 | Requires(postun): systemd 38 | Requires: util-linux 39 | Conflicts: firstboot < 19.2 40 | 41 | %description 42 | The initial-setup utility runs after installation. It guides the user through 43 | a series of steps that allows for easier configuration of the machine. 44 | 45 | %post 46 | %systemd_post initial-setup.service 47 | 48 | %preun 49 | %systemd_preun initial-setup.service 50 | 51 | %postun 52 | %systemd_postun initial-setup.service 53 | 54 | %files -f %{name}.lang 55 | %doc README.rst ChangeLog 56 | %license COPYING 57 | %{python3_sitelib}/initial_setup* 58 | %exclude %{python3_sitelib}/initial_setup/gui 59 | %{_libexecdir}/%{name}/run-initial-setup 60 | %{_libexecdir}/%{name}/initial-setup-text 61 | %{_libexecdir}/%{name}/reconfiguration-mode-enabled 62 | %{_unitdir}/initial-setup.service 63 | %{_unitdir}/initial-setup-reconfiguration.service 64 | %dir %{_sysconfdir}/%{name} 65 | %dir %{_sysconfdir}/%{name}/conf.d 66 | %config %{_sysconfdir}/%{name}/conf.d/* 67 | %{_sysconfdir}/pam.d/initial-setup 68 | 69 | %ifarch s390 s390x 70 | %{_sysconfdir}/profile.d/initial-setup.sh 71 | %{_sysconfdir}/profile.d/initial-setup.csh 72 | %endif 73 | 74 | # -------------------------------------------------------------------------- 75 | 76 | %package gui 77 | Summary: Graphical user interface for the initial-setup utility 78 | Requires: gtk3 79 | Requires: anaconda-gui >= %{anacondaver} 80 | Requires: firstboot(gui-backend) 81 | Requires: %{name} = %{version}-%{release} 82 | Suggests: %{name}-gui-wayland-generic 83 | 84 | %description gui 85 | The initial-setup-gui package contains a graphical user interface for the 86 | initial-setup utility. 87 | 88 | %files gui 89 | %{_libexecdir}/%{name}/initial-setup-graphical 90 | %{python3_sitelib}/initial_setup/gui/ 91 | 92 | # -------------------------------------------------------------------------- 93 | 94 | %package gui-wayland-generic 95 | Summary: Run the initial-setup GUI in Wayland 96 | Requires: %{name}-gui = %{version}-%{release} 97 | Requires: weston 98 | Requires: xorg-x11-server-Xwayland 99 | 100 | Provides: firstboot(gui-backend) 101 | Conflicts: firstboot(gui-backend) 102 | RemovePathPostfixes: .guiweston 103 | 104 | %description gui-wayland-generic 105 | %{summary}. 106 | 107 | %files gui-wayland-generic 108 | %{_libexecdir}/%{name}/run-gui-backend.guiweston 109 | 110 | # -------------------------------------------------------------------------- 111 | 112 | %if %{with x11} 113 | %package gui-xorg 114 | Summary: Run the initial-setup GUI in Xorg 115 | Requires: %{name}-gui = %{version}-%{release} 116 | Requires: xorg-x11-xinit 117 | Requires: xorg-x11-server-Xorg 118 | Requires: firstboot(windowmanager) 119 | 120 | Provides: firstboot(gui-backend) 121 | Conflicts: firstboot(gui-backend) 122 | RemovePathPostfixes: .guixorg 123 | 124 | %description gui-xorg 125 | %{summary}. 126 | 127 | %files gui-xorg 128 | %{_libexecdir}/%{name}/run-gui-backend.guixorg 129 | %{_libexecdir}/%{name}/firstboot-windowmanager 130 | %endif 131 | 132 | # -------------------------------------------------------------------------- 133 | 134 | %prep 135 | %autosetup -p 1 136 | 137 | # remove upstream egg-info 138 | rm -rf *.egg-info 139 | 140 | %generate_buildrequires 141 | %pyproject_buildrequires 142 | 143 | %build 144 | %make_build 145 | 146 | %install 147 | %make_install 148 | 149 | # Manually install configuration files to /etc (avoid /usr/etc issue) 150 | install -d %{buildroot}%{_sysconfdir}/%{name}/conf.d 151 | install -m 644 data/10-initial-setup.conf %{buildroot}%{_sysconfdir}/%{name}/conf.d/ 152 | install -d %{buildroot}%{_sysconfdir}/pam.d 153 | install -m 644 pam/initial-setup %{buildroot}%{_sysconfdir}/pam.d/ 154 | 155 | # Remove the default link, provide subpackages for alternatives 156 | rm %{buildroot}%{_libexecdir}/%{name}/run-gui-backend 157 | 158 | %if ! %{with x11} 159 | # We do not want to ship X11 support anymore 160 | rm -v %{buildroot}%{_libexecdir}/%{name}/run-gui-backend.guixorg 161 | rm -v %{buildroot}%{_libexecdir}/%{name}/firstboot-windowmanager 162 | %endif 163 | 164 | %find_lang %{name} 165 | 166 | %changelog 167 | * Fri Jul 19 2024 Martin Kolman - 0.3.101-1 168 | - Use threads.py submodule in favor of compatibility file threading.py (kkoukiou) 169 | 170 | * Wed Jan 03 2024 Martin Kolman - 0.3.100-1 171 | - spec: Disable shipping the X11 backend for all but RHEL < 10 (neal) 172 | - spec: Restructure and modernize (neal) 173 | 174 | * Wed Jan 03 2024 Martin Kolman - 0.3.99-1 175 | - Default initial-setup-gui GDK to X11 for all display servers (ales.astone) 176 | - Configure a seat session for running wayland compositors (ales.astone) 177 | - Add support for generic Wayland support through Weston (neal) 178 | - Allow running the graphical setup in graphic servers other than Xorg (ales.astone) 179 | - windowmanager: Remove kwin (ales.astone) 180 | 181 | * Mon Oct 09 2023 Martin Kolman - 0.3.98-1 182 | - Fix Anaconda module startup (#2241274) (mkolman) 183 | 184 | * Mon Feb 13 2023 Martin Kolman - 0.3.97-1 185 | - Make it possible to run with no kickstart (mkolman) 186 | - Fixup packit SRPM build deps (mkolman) 187 | 188 | * Thu Jan 19 2023 Martin Kolman - 0.3.96-1 189 | - SPDX compatible License: line (mkolman) 190 | - Fixup version in setup.py (mkolman) 191 | 192 | * Wed Jul 20 2022 Martin Kolman - 0.3.95-1 193 | - Use Anaconda DBus read-write properties (vponcova) 194 | 195 | * Tue Oct 12 2021 Martin Kolman - 0.3.94-1 196 | - Remove the default_help_pages configuration option (vponcova) 197 | - Add the configuration files to the updates image (vponcova) 198 | - Remove the helpFile attribute (vponcova) 199 | - Specify unique screen ids (vponcova) 200 | - Apply suggestions from code review (martin.kolman) 201 | - Fix ownership of the gui folder (#1812463) (mkolman) 202 | - Don't show the EULA spoke if the license file doesn't exist (vponcova) 203 | 204 | * Mon Jul 12 2021 Martin Kolman - 0.3.93-1 205 | - Use profiles instead of product configuration files (vponcova) 206 | - Change the Python version to 3.10 in the makeupdates script (vponcova) 207 | 208 | * Thu Jun 24 2021 Martin Kolman - 0.3.92-1 209 | - Fixup version in setup.py (mkolman) 210 | - Add gnome-kiosk to window managers usable by initial-setup (rvykydal) 211 | - Add copr builds also for branched Fedora (jkonecny) 212 | 213 | * Tue Apr 27 2021 Martin Kolman - 0.3.91-1 214 | - Disable multi TTY handler when running in SSH session (mkolman) 215 | - Add CLI option to disable multi TTY handler (mkolman) 216 | 217 | * Tue Mar 30 2021 Martin Kolman - 0.3.90-1 218 | - Remove old failing pre scriptlet (mkolman) 219 | 220 | * Tue Mar 23 2021 Martin Kolman - 0.3.89-1 221 | - Include scripts in the updates image (vponcova) 222 | - Drop build time dependency on the anaconda package (mkolman) 223 | 224 | * Mon Feb 22 2021 Martin Kolman - 0.3.88-1 225 | - Load the product configuration files (vponcova) 226 | - Clean up the code (vponcova) 227 | 228 | * Mon Feb 15 2021 Martin Kolman - 0.3.87-1 229 | - Add BuildRequires: make (tstellar) 230 | - Fixup version number in setup.py (mkolman) 231 | 232 | * Fri Feb 12 2021 Martin Kolman - 0.3.86-1 233 | - Drop python-nose from the dependencies (#1916799) (vponcova) 234 | - Add the makeupdates script (vponcova) 235 | - Remove deprecated support for add-ons (vponcova) 236 | - Don't run installation tasks of add-ons in a meta task (vponcova) 237 | - Migrate COPR builds from Jenkins to Packit (jkonecny) 238 | - Add documentation for the new test solution (jkonecny) 239 | - Add support for custom Anaconda COPR repository (jkonecny) 240 | - Use `make test` to run TMT tests locally (jkonecny) 241 | - Add TMT configuration to get Anaconda COPR build (jkonecny) 242 | - Add Packit support for initial-setup (jkonecny) 243 | - Update and use setup.py for archive creation (jkonecny) 244 | - Fix ChangeLog generation in Makefile (jkonecny) 245 | 246 | * Mon Dec 07 2020 Martin Kolman - 0.3.85-1 247 | - Adapt to category title translation fix in Anaconda (mkolman) 248 | 249 | * Tue Oct 27 2020 Martin Kolman - 0.3.84-1 250 | - Adjust to logging command changes in Anaconda (#1891621) (mkolman) 251 | - Make sure the output from custom_getpass() is serialized after stdout (lkundrak) 252 | 253 | * Wed Aug 19 2020 Martin Kolman - 0.3.83-1 254 | - Add PEP8 Speaks configuration (mkolman) 255 | - Remove hard coded name of the translation repo in tmp (jkonecny) 256 | - Update README.rst (mkolman) 257 | - Add missing branch config to manifest file (jkonecny) 258 | - Add COPR daily builds badge (jkonecny) 259 | 260 | * Mon Jun 08 2020 Martin Kolman - 0.3.82-1 261 | - Add translation badge to the README file (jkonecny) 262 | - Remove unused PREFIX variable from Makefile (jkonecny) 263 | - Use new po-push instead of Zanata (jkonecny) 264 | - Add po-push using localization repository (jkonecny) 265 | - Use translation repository to pull the translations (jkonecny) 266 | 267 | * Fri May 22 2020 Martin Kolman - 0.3.81-1 268 | - Use macro for Python 3 requirement in spec file (mkolman) 269 | - Remove outdated dependency on python3-libreport (vslavik) 270 | - Fix a typo in Zanata CLI invocation (mkolman) 271 | 272 | * Tue Dec 10 2019 Martin Kolman - 0.3.80-1 273 | - Do not call a task which has been moved into install keyboard task (rvykydal) 274 | - Adapt to changes in localization module (rvykydal) 275 | - Fix Zanata client detection in Makefile (mkolman) 276 | 277 | * Mon Nov 18 2019 Martin Kolman - 0.3.79-1 278 | - Fix import of the DBus launcher (vponcova) 279 | 280 | * Tue Nov 12 2019 Martin Kolman - 0.3.78-1 281 | - Revert "Fix import of the DBus launcher" (martin.kolman) 282 | - Run the installation tasks of the DBus addons (vponcova) 283 | - Run the installation tasks of the Timezone module (vponcova) 284 | - Fix import of the DBus launcher (vponcova) 285 | 286 | * Thu Oct 24 2019 Martin Kolman - 0.3.77-1 287 | - Run the installation tasks of the Localization module (vponcova) 288 | - Use new DBus support for reading a kickstart file (vponcova) 289 | - Use autosetup instead of setup (mkolman) 290 | - Bump Anaconda version due to networking changes (mkolman) 291 | 292 | * Fri Oct 04 2019 Martin Kolman - 0.3.76-1 293 | - Fix configuration of network hostname (#1757960) (rvykydal) 294 | 295 | * Thu Oct 03 2019 Martin Kolman - 0.3.75-1 296 | - Blacklist some USB consoles from multi-TTY handler (#1755580) (mkolman) 297 | 298 | * Wed Sep 18 2019 Martin Kolman - 0.3.74-1 299 | - Fix typo in reconfig mode detection (#1752554) (mkolman) 300 | 301 | * Wed Jul 31 2019 Martin Kolman - 0.3.73-1 302 | - Remove system root from DBus calls (vponcova) 303 | - Correct the name for libreport Python3 require (mkutlak) 304 | 305 | * Thu Jun 20 2019 Martin Kolman - 0.3.72-1 306 | - Set physical and system roots in the configuration file (vponcova) 307 | - Write Anaconda logs to journal (vponcova) 308 | - Read configuration files from /etc/initial-setup/conf.d (#1713506) (vponcova) 309 | 310 | * Thu Jun 13 2019 Martin Kolman - 0.3.71-1 311 | - Don't initialize the screen access manager (vponcova) 312 | 313 | * Wed May 15 2019 Martin Kolman - 0.3.70-1 314 | - Adjust to changes in the Users DBus module (mkolman) 315 | 316 | * Thu Apr 04 2019 Martin Kolman - 0.3.69-1 317 | - Adapt to removal of ifcfg.log (#1695967) (rvykydal) 318 | 319 | * Tue Mar 12 2019 Martin Kolman - 0.3.68-1 320 | - Remove obsolete Group definition from the spec file (mkolman) 321 | - Initialize network module (device configurations) (#1685992) (rvykydal) 322 | - Specify the type of the installation system (#1685992) (vponcova) 323 | 324 | * Tue Mar 12 2019 Martin Kolman - 0.3.67-1 325 | - Update arguments of the execute methods (#1666849) (vponcova) 326 | 327 | * Mon Jan 21 2019 Martin Kolman - 0.3.66-1 328 | - Don't call initThreading (#1666849) (vponcova) 329 | 330 | * Thu Jan 03 2019 Martin Kolman - 0.3.65-1 331 | - Remove install classes from the initial setup (vponcova) 332 | 333 | * Mon Nov 19 2018 Martin Kolman - 0.3.64-1 334 | - Handle simpleline having an empty stack (mkolman) 335 | 336 | * Mon Nov 05 2018 Martin Kolman - 0.3.63-1 337 | - Disable modules in the configuration file (vponcova) 338 | - Let the DBus launcher to set up the modules (vponcova) 339 | - README.rst: update link to anaconda addon dev guide (kenyon) 340 | 341 | * Fri Jul 27 2018 Martin Kolman - 0.3.62-1 342 | - Make EULA spoke name compatible with three column hub (mkolman) 343 | - Blacklist the ptmx console from multi-tty use (mkolman) 344 | - Add support for showing an EULA spoke (mkolman) 345 | - Add explicit dependency on X server for the GUI sub-package (mkolman) 346 | 347 | * Wed May 09 2018 Martin Kolman - 0.3.61-1 348 | - Fix the users module import (#1575650) (mkolman) 349 | 350 | * Fri May 04 2018 Martin Kolman - 0.3.60-1 351 | - Fix name of the Zanata Python client package (mkolman) 352 | - Use the Anaconda default for DBUS module timeout (mkolman) 353 | 354 | * Mon Apr 23 2018 Martin Kolman - 0.3.59-1 355 | - Fix version number in setup.py (mkolman) 356 | 357 | * Thu Apr 19 2018 Martin Kolman - 0.3.58-1 358 | - Run only the supported kickstart modules (#1566621) (vponcova) 359 | 360 | * Tue Apr 10 2018 Martin Kolman - 0.3.57-1 361 | - Adapt to a new Simpleline input handling (jkonecny) 362 | 363 | * Mon Mar 19 2018 Martin Kolman - 0.3.56-1 364 | - Adjust to Hub behavior change (mkolman) 365 | - Apply the Anaconda modularization changes (vponcova) 366 | 367 | * Mon Mar 05 2018 Martin Kolman - 0.3.55-1 368 | - Handle kickstart commands provided by DBUS modules (mkolman) 369 | - Adapt to changes in starting Boss (mkolman) 370 | 371 | * Wed Feb 28 2018 Martin Kolman - 0.3.54-1 372 | - Start and stop Boss (mkolman) 373 | - New version 0.3.53 (mkolman) 374 | - Add common function for finding bugreport URL (riehecky) 375 | 376 | * Fri Jan 19 2018 Martin Kolman - 0.3.53-1 377 | - Fix imports after Anaconda refactoring (jkonecny) 378 | - Return correct code at startup script success/failure (mkolman) 379 | 380 | * Wed Nov 29 2017 Martin Kolman - 0.3.52-1 381 | - Use getty-pre.target to prevent getty from running (mkolman) 382 | 383 | * Thu Oct 05 2017 Martin Kolman - 0.3.51-1 384 | - Don't print directory changes when outputting the changelog (mkolman) 385 | - Automate release creation (mkolman) 386 | - Don't include merges in the spec file changelog (mkolman) 387 | - Update initial-setup-reconfiguration.service too, add another (mvebu) serial console (pbrobinson) 388 | - Add some more serial console options for ARM (pbrobinson) 389 | 390 | * Mon Sep 11 2017 Martin Kolman - 0.3.50-1 391 | - Use constant+offset when turning systemd console logging on/off (mkolman) 392 | - Add some more serial console options (pbrobinson) 393 | 394 | * Wed Aug 30 2017 Martin Kolman - 0.3.49-1 395 | - Use new Simpleline package (jkonecny) 396 | - Remove unused import (jkonecny) 397 | - add yet another ARM serial console (sjenning) 398 | 399 | * Wed Jul 12 2017 Martin Kolman - 0.3.48-1 400 | - Fix Anaconda threading import name (#1469776) (mkolman) 401 | 402 | * Fri Jun 02 2017 Martin Kolman - 0.3.47-1 403 | - Adapt to anaconda_log module name change (mkolman) 404 | 405 | * Wed May 24 2017 Martin Kolman - 0.3.46-2 406 | - Drop Anaconda version bump for now (mkolman) 407 | 408 | * Wed May 24 2017 Martin Kolman - 0.3.46-1 409 | - Add support for password entry from arbitrary consoles (#1438046) (mkolman) 410 | 411 | * Wed May 17 2017 Martin Kolman - 0.3.45-1 412 | - Remove stdin & stdout definition from unit files (#1438046) (mkolman) 413 | 414 | * Mon May 15 2017 Martin Kolman - 0.3.44-1 415 | - Run the Initial Setup TUI on all usable consoles (#1438046) (mkolman) 416 | 417 | * Wed Sep 21 2016 Martin Kolman - 0.3.43-1 418 | - Initialize SAM on startup (#1375721) (mkolman) 419 | - Log unhandled exceptions to Journal (mkolman) 420 | - Suppress logging to stdout when TUI is started by s390 startup scripts (mkolman) 421 | - Fix path to TUI executable in the s390 startup scripts (#1366776) (mkolman) 422 | - Canonicalize symlinks returned by readlink (mkolman) 423 | 424 | * Fri Aug 05 2016 Martin Kolman - 0.3.42-1 425 | - Fix a typo (mkolman) 426 | - Don't run the GUI on text-only systems (#1360343) (mkolman) 427 | 428 | * Wed Jun 08 2016 Martin Kolman - 0.3.41-1 429 | - Fix reconfiguration service name (mkolman) 430 | - Fix installation path for the reconfiguration-mode-enabled script (mkolman) 431 | - Use the environs flag when setting the environment (mkolman) 432 | - Some typo fixes and logging improvements (mkolman) 433 | - Add a systemd service that enables Initial Setup if /.unconfigured exists (#1257624) (mkolman) 434 | - Adapt to addon execute() signature change (mkolman) 435 | - Replace hardcoded python3 call by a variable (mkolman) 436 | - Nicer systemctl calls (mkolman) 437 | - Use systemd-cat also for the run-initial-setup script (mkolman) 438 | - Remove a redundant Requires: line (mkolman) 439 | - Fix a typo (mkolman) 440 | - Run correct systemd scriptlets (mkolman) 441 | - Use systemd-cat for logging to the journal (mkolman) 442 | 443 | * Thu Mar 24 2016 Martin Kolman - 0.3.40-1 444 | - Use blank title for the Initial Setup window (mkolman) 445 | - Start the window manager correctly (#1160891) (mkolman) 446 | - Fix some rpmlint warnings (mkolman) 447 | 448 | * Tue Feb 16 2016 Martin Kolman - 0.3.39-1 449 | - Disable the correct service on successful completion (#1298725) (mkolman) 450 | 451 | * Tue Dec 01 2015 Martin Kolman - 0.3.38-1 452 | - Make Initial Setup startup more robust (mkolman) 453 | - Move the s390 profile scripts to a subfolder (mkolman) 454 | - Improve log messages for kickstart parsing error (mkolman) 455 | 456 | * Wed Sep 30 2015 Martin Kolman - 0.3.37-1 457 | - Stop any Initial Setup services before upgrading package (#1244394) (mkolman) 458 | - Replace systemd_postun_with_restart with systemd_postun (#1244394) (mkolman) 459 | - Fix 'bumpver' make target (vtrefny) 460 | - Add archive target to Makefile (vtrefny) 461 | 462 | * Mon Aug 31 2015 Martin Kolman - 0.3.36-1 463 | - Setup the locale before starting the UI (dshea) 464 | - Run the TUI service before hvc0.service (#1209731) (mkolman) 465 | - Don't create /etc/sysconfig/initial-setup on s390 (#1181209) (mkolman) 466 | - Use systemd service status for run detection on S390 console (#1181209) (mkolman) 467 | - Read the kickstart from previous IS run, if available (#1110439) (mkolman) 468 | - Add support for externally triggered reconfig mode (#1110439) (mkolman) 469 | - Log the reason if GUI import fails (#1229747) (mkolman) 470 | 471 | * Thu Jul 30 2015 Martin Kolman - 0.3.35-1 472 | - Fix a typo in Makefile (#1244558) (mkolman) 473 | 474 | * Thu Jul 30 2015 Martin Kolman - 0.3.34-1 475 | - Switch Initial Setup to Python 3 (#1244558) (mkolman) 476 | 477 | * Thu Apr 23 2015 Martin Kolman - 0.3.33-1 478 | - Improve the Makefile (mkolman) 479 | - Remove old GUI testing code from the Makefile (mkolman) 480 | - Update upstream URL (#1213101) (mkolman) 481 | - Update upstream Git repository URL (mkolman) 482 | 483 | * Tue Mar 31 2015 Martin Kolman - 0.3.32-1 484 | - Point out the err in case that ks parsing failed (#1145130) (fabiand) 485 | - Switch to Zanata for translations (mkolman) 486 | 487 | * Wed Mar 04 2015 Martin Kolman - 0.3.31-1 488 | - Use kwin_x11 for kde/plasma spin (#1197135) (rdieter) 489 | 490 | * Fri Feb 13 2015 Martin Kolman - 0.3.29-1 491 | - Split scriptlets for the gui subpackage (mkolman) 492 | - Use /usr/bin/python2 in scripts (mkolman) 493 | 494 | * Thu Feb 05 2015 Martin Kolman - 0.3.28-1 495 | - Fix breakage caused by README file rename (mkolman) 496 | 497 | * Thu Feb 05 2015 Martin Kolman - 0.3.27-1 498 | - Remove unneeded dependencies (mkolman) 499 | - Add the rst suffix to the README file (mkolman) 500 | - Update the link to the upstream source code repository (mkolman) 501 | - Add AnacondaKSHandler no-member error to pylint-false-positives. (mulhern) 502 | - Mark strings for translation when module is loaded. (mulhern) 503 | - Fix easy pylint errors. (mulhern) 504 | - Add pylint testing infrastructure. (mulhern) 505 | 506 | * Mon Nov 3 2014 Martin Kolman - 0.3.26-1 507 | - Explicitly require the main package in the GUI sub package (#1078917) (mkolman) 508 | 509 | * Thu Oct 23 2014 Martin Kolman - 0.3.25-1 510 | - Add syslog logging support (#1145122) (mkolman) 511 | 512 | * Fri Oct 10 2014 Martin Kolman - 0.3.24-1 513 | - Fix Initial Setup to correctly support the Anaconda built-in Help (#1072033) (mkolman) 514 | - Populate README (#1110178) (master-log) (mkolman) 515 | - Remove the --disable-overwrite parameter for the Transifex client (mkolman) 516 | 517 | * Fri Aug 08 2014 Martin Kolman - 0.3.23-1 518 | - Adapt to class changes in Anaconda (vpodzime) 519 | 520 | * Fri Jul 04 2014 Martin Kolman - 0.3.22-1 521 | - Update the initial-setup hub for the new HubWindow API (dshea) 522 | 523 | * Sat May 31 2014 Peter Robinson 0.3.21-2 524 | - Only the GUI needs a window manager 525 | 526 | * Wed May 28 2014 Martin Kolman - 0.3.21-1 527 | - Adapt to python-nose API change (mkolman) 528 | 529 | * Thu May 22 2014 Martin Kolman - 0.3.20-1 530 | - Adapt Initial Setup to the new way Anaconda handles root path (#1099581) (vpodzime) 531 | 532 | * Tue May 06 2014 Martin Kolman - 0.3.19-1 533 | - Bump required Anaconda version due to TUI category handling change (mkolman) 534 | - Override Hub collect methods also in TUI hub (mkolman) 535 | - Translation update 536 | 537 | * Mon Apr 28 2014 Martin Kolman - 0.3.18-1 538 | - Remove debugging code that was left in the tarball by mistake (#1091470) (mkolman) 539 | - Translation update 540 | 541 | * Fri Apr 11 2014 Martin Kolman - 0.3.17-1 542 | - Set initial-setup translation domain for the hub (#1040240) (mkolman) 543 | 544 | * Thu Apr 03 2014 Martin Kolman - 0.3.16-1 545 | - initial-setup-gui requires the initial-setup package (vpodzime) 546 | 547 | * Wed Mar 19 2014 Martin Kolman - 0.3.15-1 548 | - Import the product module (#1077390) (vpodzime) 549 | 550 | * Tue Feb 11 2014 Vratislav Podzimek - 0.3.14-1 551 | - Try to quit plymouth before running our X server instance (#1058329) 552 | - Get rid of the empty debuginfo package (#1062738) 553 | 554 | * Wed Feb 05 2014 Vratislav Podzimek - 0.3.13-1 555 | - Make Initial Setup an arch specific package (#1057590) (vpodzime) 556 | 557 | * Thu Nov 28 2013 Vratislav Podzimek - 0.3.12-1 558 | - Adapt to changes in anaconda tui spoke categories (#1035462) (vpodzime) 559 | - Ignore the SIGINT (#1035590) (vpodzime) 560 | 561 | * Wed Nov 20 2013 Vratislav Podzimek - 0.3.11-1 562 | - Fix how spokes are collected for the I-S main hub (vpodzime) 563 | - Override distribution text in spokes (#1028370) (vpodzime) 564 | - Get rid of the useless modules directory (vpodzime) 565 | - Split GUI code into a separate package (#999464) (vpodzime) 566 | - Fallback to text UI if GUI is not available (vpodzime) 567 | 568 | * Tue Nov 05 2013 Vratislav Podzimek - 0.3.10-1 569 | - Do not try to kill unexisting process (vpodzime) 570 | - Add some logging to our shell scripts (vpodzime) 571 | 572 | * Thu Sep 26 2013 Vratislav Podzimek - 0.3.9-1 573 | - Yet another serial console in ARMs (#1007163) (vpodzime) 574 | - Fix the base mask of initial_setup gui submodules (vpodzime) 575 | - Specify and use environment of the main hub (vpodzime) 576 | 577 | * Tue Sep 10 2013 Vratislav Podzimek - 0.3.8-1 578 | - Read /etc/os-release to get product title (#1000426) (vpodzime) 579 | - Don't let product_title() return None (vpodzime) 580 | - Apply the timezone and NTP configuration (#985566) (hdegoede) 581 | - Make handling translations easier (vpodzime) 582 | - Make translations work (vpodzime) 583 | - Sync changelog with downstream (vpodzime) 584 | 585 | * Tue Aug 27 2013 Vratislav Podzimek - 0.3.7-1 586 | - Prevent getty on various services killing us (#979174) (vpodzime) 587 | - Initialize network logging for the network spoke (vpodzime) 588 | 589 | * Sat Aug 03 2013 Fedora Release Engineering - 0.3.6-3 590 | - Rebuilt for https://fedoraproject.org/wiki/Fedora_20_Mass_Rebuild 591 | 592 | * Tue Jun 18 2013 Vratislav Podzimek - 0.3.6-2 593 | - Rebuild with dependencies available. 594 | 595 | * Tue Jun 18 2013 Vratislav Podzimek - 0.3.6-1 596 | - Make serial-getty wait for us as well (#970719) (vpodzime) 597 | - Disable the service only on successful exit (#967617) (vpodzime) 598 | 599 | * Wed May 22 2013 Vratislav Podzimek - 0.3.5-1 600 | - Reference the new repository in the .spec file (vpodzime) 601 | - Prevent systemd services from running on live images (#962196) (awilliam) 602 | - Don't traceback if the expected kickstart file doesn't exist (#950796) (vpodzime) 603 | 604 | * Mon Apr 8 2013 Vratislav Podzimek - 0.3.4-3 605 | - Rebuild with fixed spec that partly reverts the previous change 606 | 607 | * Fri Apr 5 2013 Vratislav Podzimek - 0.3.4-2 608 | - Rebuild with fixed spec that enables services after installation 609 | 610 | * Thu Mar 28 2013 Martin Sivak - 0.3.4-1 611 | - Search for proper UI variant of addons 612 | - Add addon directories to sys.path 613 | 614 | * Tue Mar 26 2013 Martin Sivak - 0.3.3-1 615 | - Systemd unit files improved 616 | 617 | * Tue Mar 26 2013 Martin Sivak - 0.3.2-1 618 | - Modify the ROOT_PATH properly 619 | - Do not execute old ksdata (from anaconda's ks file) 620 | - Save the resulting configuration to /root/initial-setup-ks.cfg 621 | 622 | * Tue Mar 26 2013 Martin Sivak - 0.3.1-2 623 | - Require python-di package 624 | 625 | * Thu Mar 21 2013 Martin Sivak - 0.3.1-1 626 | - Use updated Anaconda API 627 | - Request firstboot environment spokes 628 | - Initialize anaconda threading properly 629 | 630 | * Wed Mar 13 2013 Martin Sivak - 0.3-1 631 | - Use updated Anaconda API 632 | - Fix systemd units 633 | - Add localization spokes to TUI 634 | - Write changes to disk 635 | - Conflict with old firstboot 636 | 637 | * Wed Feb 13 2013 Martin Sivak 0.2-1 638 | - Updates for package review 639 | - Firstboot-windowmanager script 640 | 641 | * Wed Feb 13 2013 Martin Sivak 0.1-3 642 | - Updates for package review 643 | 644 | * Tue Jan 22 2013 Martin Sivak 0.1-2 645 | - Updates for package review 646 | 647 | * Tue Nov 06 2012 Martin Sivak 0.1-1 648 | - Initial release 649 | --------------------------------------------------------------------------------