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..."