From d70552a73425b6ffb135fca92de29af378f307a0 Mon Sep 17 00:00:00 2001 From: catborise Date: Tue, 3 Nov 2026 12:23:36 +0300 Subject: [PATCH] fix(installer): a failed step stops the installer, and openSUSE gets a working panel Tested on Ubuntu 22.04, 24.04 and 26.04, Debian 12 and 13, AlmaLinux 10 and openSUSE Leap 15.6. On Leap the panel did not start: the secret key was written with python3 before the installer looked for Python 3.10+, and Leap has only python3.11, so the key was empty. The installer still said "Finished!" and exited 0, as it did for any failed step. Now Python is found first. The package list update, each package, the clone, the key, pip, migrate and collectstatic stop the installer with a message when they fail (pipefail makes a logged step's status count). Also: - Ubuntu 26.04 is installed; Ubuntu 18/20 and Debian 9-11 are refused up front (their Python is older than 3.10, so they failed later). - The uos and openEuler branches are gone: untested, and their Python is too old as well. - Only `apt-get update`, no upgrade of the whole system. - Debian no longer asks for smbios-utils (gone in Debian 13) and uuid, which nothing uses; Debian and Ubuntu take python3-libvirt. - RHEL reads the time zone like the others, and installs hostname for ALLOWED_HOSTS. - The unused wvcuser account is no longer created. - The end of the run says where the admin password is. --- README.md | 2 +- conf/install-lib.sh | 4 +- conf/nginx/openEuler_nginx.conf | 38 ------- conf/nginx/uos_eagle_nginx.conf | 85 --------------- conf/nginx/uos_nginx.conf | 89 ---------------- conf/test_install_lib.sh | 2 + webvirtcloud.sh | 183 ++++++-------------------------- 7 files changed, 40 insertions(+), 363 deletions(-) delete mode 100644 conf/nginx/openEuler_nginx.conf delete mode 100644 conf/nginx/uos_eagle_nginx.conf delete mode 100644 conf/nginx/uos_nginx.conf diff --git a/README.md b/README.md index 513e36f7..98bccb16 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ WebVirtCloud and `virt-manager`/`virsh` can manage the same libvirt host **concu ## Installation -The installer supports Ubuntu 22.04 / 24.04, Debian 12, RHEL / Rocky Linux / AlmaLinux / Oracle Linux 10, openSUSE Leap 15.x / Tumbleweed and SLES 15; the panel runs on a virtual machine, a physical host or a KVM host itself and needs Python 3.10 or newer. CI tests Python 3.10–3.13 on Ubuntu (sqlite, and PostgreSQL on 3.12) and the Docker image; the distro installs are not tested in CI. Pick one of the three ways below, then do the [first login](#first-login). +The installer supports Ubuntu 22.04 / 24.04 / 26.04, Debian 12 / 13, RHEL / Rocky Linux / AlmaLinux / Oracle Linux 10, openSUSE Leap 15.x / Tumbleweed and SLES 15; the panel runs on a virtual machine, a physical host or a KVM host itself and needs Python 3.10 or newer. CI tests Python 3.10–3.13 on Ubuntu (sqlite, and PostgreSQL on 3.12) and the Docker image; the distro installs are not tested in CI. Pick one of the three ways below, then do the [first login](#first-login). ### Installer diff --git a/conf/install-lib.sh b/conf/install-lib.sh index ee1c17b1..b8fbb8b8 100755 --- a/conf/install-lib.sh +++ b/conf/install-lib.sh @@ -4,7 +4,7 @@ # Create /secret_key (mode 600) only if missing or empty; keep an # existing key. A blank key would make Django fail to boot, so an empty file is -# treated as missing. +# treated as missing, and the function fails when it could not write one. wvc_ensure_secret_key() { local data_dir="$1" python="${2:-python3}" key_file key_file="$data_dir/secret_key" @@ -15,6 +15,8 @@ wvc_ensure_secret_key() { ( umask 077; "$python" -c 'import secrets; print(secrets.token_urlsafe(50))' > "$key_file" ) fi chmod 600 "$key_file" + # an empty key means the python did not run: the panel would not start + [ -s "$key_file" ] } # Copy settings.py from the template only when it does not already exist, so a diff --git a/conf/nginx/openEuler_nginx.conf b/conf/nginx/openEuler_nginx.conf deleted file mode 100644 index dfa72fcd..00000000 --- a/conf/nginx/openEuler_nginx.conf +++ /dev/null @@ -1,38 +0,0 @@ -# For more information on configuration, see: -# * Official English Documentation: http://nginx.org/en/docs/ -# * Official Russian Documentation: http://nginx.org/ru/docs/ - -user nginx; -worker_processes auto; -error_log /var/log/nginx/error.log; -pid /run/nginx.pid; - -# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic. -include /usr/share/nginx/modules/*.conf; - -events { - worker_connections 1024; -} - -http { - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - access_log /var/log/nginx/access.log main; - - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - types_hash_max_size 2048; - - include /etc/nginx/mime.types; - default_type application/octet-stream; - - # Load modular configuration files from the /etc/nginx/conf.d directory. - # See http://nginx.org/en/docs/ngx_core_module.html#include - # for more information. - include /etc/nginx/conf.d/*.conf; - -} diff --git a/conf/nginx/uos_eagle_nginx.conf b/conf/nginx/uos_eagle_nginx.conf deleted file mode 100644 index 7914c8ef..00000000 --- a/conf/nginx/uos_eagle_nginx.conf +++ /dev/null @@ -1,85 +0,0 @@ -user www-data; -worker_processes auto; -pid /run/nginx.pid; -include /etc/nginx/modules-enabled/*.conf; - -events { - worker_connections 768; - # multi_accept on; -} - -http { - - ## - # Basic Settings - ## - - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - types_hash_max_size 2048; - # server_tokens off; - - # server_names_hash_bucket_size 64; - # server_name_in_redirect off; - - include /etc/nginx/mime.types; - default_type application/octet-stream; - - ## - # SSL Settings - ## - - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE - ssl_prefer_server_ciphers on; - - ## - # Logging Settings - ## - - access_log /var/log/nginx/access.log; - error_log /var/log/nginx/error.log; - - ## - # Gzip Settings - ## - - gzip on; - - # gzip_vary on; - # gzip_proxied any; - # gzip_comp_level 6; - # gzip_buffers 16 8k; - # gzip_http_version 1.1; - # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; - - ## - # Virtual Host Configs - ## - - include /etc/nginx/conf.d/*.conf; - include /etc/nginx/sites-enabled/*; -} - - -#mail { -# # See sample authentication script at: -# # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript -# -# # auth_http localhost/auth.php; -# # pop3_capabilities "TOP" "USER"; -# # imap_capabilities "IMAP4rev1" "UIDPLUS"; -# -# server { -# listen localhost:110; -# protocol pop3; -# proxy on; -# } -# -# server { -# listen localhost:143; -# protocol imap; -# proxy on; -# } -#} \ No newline at end of file diff --git a/conf/nginx/uos_nginx.conf b/conf/nginx/uos_nginx.conf deleted file mode 100644 index 5c956d61..00000000 --- a/conf/nginx/uos_nginx.conf +++ /dev/null @@ -1,89 +0,0 @@ -# For more information on configuration, see: -# * Official English Documentation: http://nginx.org/en/docs/ -# * Official Russian Documentation: http://nginx.org/ru/docs/ - -user nginx; -worker_processes auto; -error_log /var/log/nginx/error.log; -pid /run/nginx.pid; - -# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic. -include /usr/share/nginx/modules/*.conf; - -events { - worker_connections 1024; -} - -http { - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - access_log /var/log/nginx/access.log main; - - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - types_hash_max_size 2048; - - include /etc/nginx/mime.types; - default_type application/octet-stream; - - # Load modular configuration files from the /etc/nginx/conf.d directory. - # See http://nginx.org/en/docs/ngx_core_module.html#include - # for more information. - include /etc/nginx/conf.d/*.conf; - -# server { -# listen 80 default_server; -# listen [::]:80 default_server; -# server_name _; -# root /usr/share/nginx/html; -# -# # Load configuration files for the default server block. -# include /etc/nginx/default.d/*.conf; -# -# location / { -# } -# -# error_page 404 /404.html; -# location = /40x.html { -# } -# -# error_page 500 502 503 504 /50x.html; -# location = /50x.html { -# } -# } -# -# Settings for a TLS enabled server. -# -# server { -# listen 443 ssl http2 default_server; -# listen [::]:443 ssl http2 default_server; -# server_name _; -# root /usr/share/nginx/html; -# -# ssl_certificate "/etc/pki/nginx/server.crt"; -# ssl_certificate_key "/etc/pki/nginx/private/server.key"; -# ssl_session_cache shared:SSL:1m; -# ssl_session_timeout 10m; -# ssl_ciphers PROFILE=SYSTEM; -# ssl_prefer_server_ciphers on; -# -# # Load configuration files for the default server block. -# include /etc/nginx/default.d/*.conf; -# -# location / { -# } -# -# error_page 404 /404.html; -# location = /40x.html { -# } -# -# error_page 500 502 503 504 /50x.html; -# location = /50x.html { -# } -# } - -} \ No newline at end of file diff --git a/conf/test_install_lib.sh b/conf/test_install_lib.sh index 5bf51615..89eec413 100755 --- a/conf/test_install_lib.sh +++ b/conf/test_install_lib.sh @@ -22,6 +22,8 @@ check "existing key kept" "[ \"\$(cat '$tmp/data/secret_key')\" = \"$first\" ]" wvc_ensure_secret_key "$tmp/data" check "empty key regenerated" "[ -s '$tmp/data/secret_key' ]" check "data dir is 700" "[ \"\$(stat -c %a '$tmp/data')\" = 700 ]" +rm "$tmp/data/secret_key" +check "fails when python cannot run" "! wvc_ensure_secret_key '$tmp/data' no-such-python 2>/dev/null" # wvc_ensure_settings: creates once, keeps on re-run echo "TEMPLATE" > "$tmp/tpl" diff --git a/webvirtcloud.sh b/webvirtcloud.sh index 960f5da1..dc3c9639 100755 --- a/webvirtcloud.sh +++ b/webvirtcloud.sh @@ -1,4 +1,6 @@ #!/bin/bash +# a failing step fails the pipeline that logs it +set -o pipefail #/ Usage: webvirtcloud.sh [-vh] #/ #/ Install Webvirtcloud virtualization web interface. @@ -69,9 +71,6 @@ if [ -f /etc/supervisor/conf.d/webvirtcloud.conf ] || [ -f /etc/supervisord.d/we exit 1 fi -clear - -readonly APP_USER="wvcuser" readonly APP_REPO_URL="${APP_REPO_URL:-https://github.com/retspen/webvirtcloud.git}" readonly APP_NAME="webvirtcloud" readonly APP_PATH="/srv/$APP_NAME" @@ -123,7 +122,7 @@ install_packages () { echo " * $p already installed" else echo " * Installing $p" - log "DEBIAN_FRONTEND=noninteractive apt-get install -y $p" + log "DEBIAN_FRONTEND=noninteractive apt-get install -y $p" || { echo "Installing $p failed." >&2; exit 1; } fi done; ;; @@ -133,45 +132,27 @@ install_packages () { echo " * $p already installed" else echo " * Installing $p" - log "yum -y install $p" + log "yum -y install $p" || { echo "Installing $p failed." >&2; exit 1; } fi done; ;; - fedora|openEuler) + fedora) for p in $PACKAGES; do if dnf list installed "$p" >/dev/null 2>&1; then echo " * $p already installed" else echo " * Installing $p" - log "dnf -y install $p" + log "dnf -y install $p" || { echo "Installing $p failed." >&2; exit 1; } fi done; ;; - uos) - if test "${codename}" == "eagle"; then - check_package_cmd=("dpkg" "-s") - install_package_cmd="apt-get install -y" - else - check_package_cmd=("dnf" "list" "installed") - install_package_cmd="dnf -y install" - fi - for p in $PACKAGES; do - # shellcheck disable=SC2048 - if ${check_package_cmd[*]} "$p" >/dev/null 2>&1; then - echo " * $p already installed" - else - echo " * Installing $p" - log "${install_package_cmd} $p" - fi - done - ;; suse) for p in $PACKAGES; do if rpm -q "$p" >/dev/null 2>&1; then echo " * $p already installed" else echo " * Installing $p" - log "zypper --non-interactive install -y $p" + log "zypper --non-interactive install -y $p" || { echo "Installing $p failed." >&2; exit 1; } fi done ;; @@ -219,31 +200,10 @@ configure_systemd () { systemctl enable --now webvirt-backup.timer || exit 1 } -create_user () { - echo "* Creating webvirtcloud user." - - if [ "$distro" == "ubuntu" ] || [ "$distro" == "debian" ] || - [[ "$distro" == "uos" && "$codename" == "eagle" ]]; then - adduser --quiet --disabled-password --gecos '""' "$APP_USER" - elif [ "$distro" == "suse" ]; then - useradd -m -s /bin/bash "$APP_USER" 2>/dev/null || adduser "$APP_USER" - else - useradd -m -s /bin/bash "$APP_USER" 2>/dev/null || adduser "$APP_USER" - fi - - usermod -a -G "$nginx_group" "$APP_USER" 2>/dev/null || true +add_service_groups () { + # the panel's services reach a local libvirt and KVM as the web server's user usermod -a -G libvirt "$nginx_group" 2>/dev/null || true usermod -a -G kvm "$nginx_group" 2>/dev/null || true - usermod -a -G libvirt "$APP_USER" 2>/dev/null || true - usermod -a -G kvm "$APP_USER" 2>/dev/null || true -} - -run_as_app_user () { - if ! hash sudo 2>/dev/null; then - su -c "$@" "$APP_USER" - else - sudo -i -u "$APP_USER" "$@" - fi } check_python () { @@ -288,11 +248,11 @@ activate_python_environment () { install_webvirtcloud () { - create_user - + add_service_groups + if [ ! -d "$APP_PATH/.git" ]; then echo "* Cloning $APP_NAME from github to the web directory." - log "git clone $APP_REPO_URL $APP_PATH" + log "git clone $APP_REPO_URL $APP_PATH" || { echo "Cloning $APP_REPO_URL failed." >&2; exit 1; } else echo "* $APP_NAME already present in $APP_PATH." fi @@ -300,8 +260,11 @@ install_webvirtcloud () { # shellcheck source=conf/install-lib.sh . "$APP_PATH/conf/install-lib.sh" + echo "* Checking up Python3 version." + check_python + echo "* Ensuring Django secret key (data/secret_key, kept across re-runs)." - wvc_ensure_secret_key "$APP_PATH/data" "$PYTHON" + wvc_ensure_secret_key "$APP_PATH/data" "$PYTHON" || { echo "The secret key could not be written." >&2; exit 1; } echo "* Configuring settings.py file." if wvc_ensure_settings "$APP_PATH/webvirtcloud/settings.py.template" "$APP_PATH/webvirtcloud/settings.py" >/dev/null; then @@ -334,23 +297,20 @@ install_webvirtcloud () { # after migrate creates it. wvc_harden_modes "$APP_PATH/webvirtcloud/settings.py" "" "$APP_PATH/data" - echo "* Checking up Python3 version." - check_python - echo "* Activate virtual environment." activate_python_environment echo "* Install App's Python requirements." - pip3 install -U pip - pip3 install -r conf/requirements.txt -q + pip3 install -U pip || exit 1 + pip3 install -r conf/requirements.txt -q || { echo "Installing the Python requirements failed." >&2; exit 1; } chown -R "$nginx_group":"$nginx_group" "$APP_PATH" echo "* Django Migrate." - log "$PYTHON $APP_PATH/manage.py migrate" + log "$PYTHON $APP_PATH/manage.py migrate" || { echo "manage.py migrate failed: see /var/log/webvirtcloud-install.log." >&2; exit 1; } echo "* Django Collect Static" - log "$PYTHON $APP_PATH/manage.py collectstatic --noinput" + log "$PYTHON $APP_PATH/manage.py collectstatic --noinput" || { echo "manage.py collectstatic failed: see /var/log/webvirtcloud-install.log." >&2; exit 1; } echo "* Restricting file modes on secret, settings and database." wvc_harden_modes "$APP_PATH/webvirtcloud/settings.py" "$APP_PATH/db.sqlite3" "$APP_PATH/data" @@ -460,27 +420,6 @@ case $distro in nginx_group=nginx nginxfile=/etc/nginx/conf.d/$APP_NAME.conf ;; - *Uos*|*uos*) - # codename may be fuyu, kongzi, eagle or empty string. - output_expand="" - if test -n "${codename}"; then - output_expand=" codename ${codename}" - fi - echo " The installer has detected $distro version $version${output_expand}." - distro=uos - nginxfile=/etc/nginx/conf.d/$APP_NAME.conf - if test "${codename}" == "eagle"; then - nginx_group=www-data - else - nginx_group=nginx - fi - ;; - *OpenEuler*|*openEuler*) - echo " The installer has detected $distro version $version." - distro=openEuler - nginx_group=nginx - nginxfile=/etc/nginx/conf.d/$APP_NAME.conf - ;; *) if [[ "$id_like" =~ suse ]]; then echo " The installer has detected $distro (SUSE family) version $version." @@ -536,16 +475,17 @@ echo "=========" case $distro in debian) debian_major="${version%%.*}" - if [[ -n "$debian_major" && "$debian_major" -ge 9 ]]; then - # Install for Debian 9.x / 10.x / 11.x / 12.x + if [[ -n "$debian_major" && "$debian_major" -ge 12 ]]; then + # Install for Debian 12 and later tzone=\'$(get_timezone)\' echo -n "* Updating installed packages." - log "apt-get update && apt-get -y upgrade" & pid=$! + log "apt-get update" & pid=$! progress + wait "$pid" || { echo "Updating the package lists failed." >&2; exit 1; } echo "* Installing OS requirements." - PACKAGES="git python3-venv python3-virtualenv python3-pip python3-dev python3-lxml libvirt-dev zlib1g-dev libxslt1-dev nginx smbios-utils libsasl2-modules gcc pkg-config python3-guestfs uuid" + PACKAGES="git python3-venv python3-virtualenv python3-pip python3-dev python3-lxml python3-libvirt libvirt-dev zlib1g-dev libxslt1-dev nginx libsasl2-modules gcc pkg-config python3-guestfs" install_packages @@ -566,16 +506,17 @@ case $distro in fi ;; ubuntu) - if [[ "$version" =~ ^(18|20|22|24) ]]; then - # Install for Ubuntu 18 / 20 / 22 / 24 + if [[ "$version" =~ ^(22|24|26)\. ]]; then + # Install for Ubuntu 22.04 / 24.04 / 26.04 tzone=\'$(get_timezone)\' echo -n "* Updating installed packages." - log "apt-get update && apt-get -y upgrade" & pid=$! + log "apt-get update" & pid=$! progress + wait "$pid" || { echo "Updating the package lists failed." >&2; exit 1; } echo "* Installing OS requirements." - PACKAGES="git python3-venv python3-virtualenv python3-pip python3-dev python3-lxml libvirt-dev zlib1g-dev libxslt1-dev nginx libsasl2-modules gcc pkg-config python3-guestfs" + PACKAGES="git python3-venv python3-virtualenv python3-pip python3-dev python3-lxml python3-libvirt libvirt-dev zlib1g-dev libxslt1-dev nginx libsasl2-modules gcc pkg-config python3-guestfs" install_packages set_hosts @@ -597,7 +538,7 @@ case $distro in centos) if [[ "$version" =~ ^10 ]]; then # Install for CentOS/Redhat 10 - tzone=\'$(timedatectl|grep "Time zone"| awk '{print $3}')\' + tzone=\'$(get_timezone)\' if command -v "crb" >/dev/null 2>&1; then echo "* CRB Repo is found, enabling it." @@ -611,13 +552,11 @@ case $distro in log "yum -y install wget epel-release" echo "* Installing OS requirements." - PACKAGES="git python3-virtualenv python3-devel libvirt-devel python3-libvirt glibc gcc nginx python3-lxml python3-libguestfs iproute-tc cyrus-sasl-md5 openssl-devel" + PACKAGES="git hostname python3-devel libvirt-devel python3-libvirt glibc gcc nginx python3-lxml python3-libguestfs iproute-tc cyrus-sasl-md5 openssl-devel" install_packages set_hosts - echo "* Configuring virtualenv." - log "pip3 install virtualenv" install_webvirtcloud echo "* Configuring Nginx." @@ -636,62 +575,6 @@ case $distro in exit 1 fi ;; - uos) - if [[ "$version" == "20" ]]; then - # Install for uos 20 - tzone=\'$(timedatectl|grep "Time zone"| awk '{print $3}')\' - - echo "* Installing OS requirements." - if test "${codename}" == "eagle"; then - PACKAGES="git virtualenv python3-virtualenv python3-dev python3-lxml libvirt-dev zlib1g-dev libxslt1-dev nginx libsasl2-modules gcc pkg-config python3-guestfs uuid" - else - PACKAGES="git python3-virtualenv python3-devel python3-pip libvirt-devel glibc gcc nginx python3-lxml python3-libguestfs iproute-tc cyrus-sasl-md5" - fi - install_packages - - set_hosts - - install_webvirtcloud - - echo "* Configuring Nginx." - configure_nginx - - set_firewall - - set_selinux - - echo "* Configuring systemd." - configure_systemd - - restart_nginx - fi - ;; - openEuler) - if [[ "$version" == "20.03" ]]; then - # Install for openEuler 20.03 - tzone=\'$(timedatectl|grep "Time zone"| awk '{print $3}')\' - - echo "* Installing OS requirements." - PACKAGES="git python3-virtualenv python3-devel python3-pip libvirt-devel glibc gcc nginx python3-lxml python3-libguestfs iproute-tc cyrus-sasl-md5" - install_packages - - set_hosts - - install_webvirtcloud - - echo "* Configuring Nginx." - configure_nginx - - set_firewall - - set_selinux - - echo "* Configuring systemd." - configure_systemd - - restart_nginx - fi - ;; suse) # Install for openSUSE Leap 15.x / Tumbleweed / SLES 15 tzone=\'$(get_timezone)\' @@ -699,6 +582,7 @@ case $distro in echo -n "* Updating installed packages." log "zypper --non-interactive refresh" & pid=$! progress + wait "$pid" || { echo "Updating the package lists failed." >&2; exit 1; } echo "* Installing OS requirements." # On SUSE, install Python 3.11 stack to satisfy Python >= 3.10 requirement along with the native libvirt bindings @@ -728,6 +612,7 @@ esac echo "" echo " ***Open http://$fqdn to login to webvirtcloud.***" +echo " The admin password: cat $APP_PATH/data/admin_password" echo "" echo "" echo "* Cleaning up..."