1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-10-18 00:50:07 +00:00

Compare commits

..

11 commits

Author SHA1 Message Date
catborise
bb920b0a7f
Merge pull request #688 from catborise/master
feat: Modernize WebVirtCloud, add Python 3.12+ & openSUSE/SLES 15 support, and enhance test coverage
2026-09-25 23:25:37 +03:00
catborise
a444a2ba40 fix(linter): format console/views.py and disable htmlhint/jscpd on django templates 2026-09-25 23:18:51 +03:00
catborise
d9112b797f fix(console): fix novncd startup in container and prevent double slashes in websocket url 2026-09-25 23:11:52 +03:00
catborise
f061189716 fix(docker): resolve hadolint warnings in Dockerfile and add hadolint config 2026-09-25 22:39:31 +03:00
catborise
7435d66492 fix(security): resolve reviewer findings on secret key baking, host symlinks, and injection safety
- Prevent baking SECRET_KEY into Docker image during collectstatic
- Persist runtime SECRET_KEY in data/secret_key volume or read from environment
- Remove sed-based injection of CSRF_TRUSTED_ORIGINS in favor of native os.getenv
- Avoid overwriting system python3 symlink on SUSE; auto-detect python 3.10+ binaries
- Make /etc/hosts entry in installer idempotent
- Add security notice regarding compute node VNC/SPICE port firewall isolation
2026-09-25 22:06:16 +03:00
catborise
b1a843e846 feat(docker): modernize Dockerfile, add runit init script, and provide docker-compose setup
- Add python3-libvirt to apt and configure --system-site-packages for zero-compilation build
- Introduce 10_webvirtcloud_init.sh to handle container startup, migrations, and SQLite persistence
- Exclude host settings.py in .dockerignore for clean builds from settings.py.template
- Add docker-compose.yml with persistent data and SSH volume mounts
- Document Docker Compose deployment in README.md
2026-09-25 21:58:19 +03:00
catborise
56fdd07802 ci: add GitHub Actions test suite workflow for Python 3.10, 3.11, and 3.12
- Configure matrix testing across Python 3.10, 3.11, and 3.12 on ubuntu-latest
- Install system libvirt, ldap, and C build dependencies with pip cache
- Run Django database migrations and test suite
- Run vrtManager unit test discovery with test_*.py pattern
- Fix vrtManager unittest discovery command in README.md
2026-09-25 21:48:38 +03:00
catborise
b9cec4a6d3 chore: purge obsolete .travis.yml and clean stale drf-yasg paths in staticfiles.json
- Remove legacy .travis.yml since continuous integration runs on GitHub Actions
- Remove 34 obsolete drf-yasg file mappings from static/staticfiles.json
- Recompute staticfiles manifest hash to match current static asset inventory
2026-09-25 21:45:13 +03:00
catborise
6d385ca5d2 docs(readme): overhaul documentation structure, security links, and multi-distro setup
- Replace shortened URL links (bit.ly, clck.ru) with direct upstream/repository paths
- Modernize and reorganize manual production installation guides for Ubuntu/Debian, RHEL/Rocky, and openSUSE/SLES
- Fix Nginx proxy header ($remote_addr -> $scheme) and streamline Supervisor configs
- Unify Compute Node (Hypervisor) setup into a dedicated section
- Update test running documentation reflecting isolated mocks and WEBVIRTCLOUD_TEST_LIBVIRT_URI
- Document drf-spectacular OpenAPI 3.0 schema endpoints and fix typos
- Convert screenshot gallery to clean Markdown table formatting
2026-09-25 21:37:50 +03:00
catborise
f5e5ff2759 fix(install): refine openSUSE Leap dependencies and streamline installer execution
- Add Python 3.11 package detection and alternatives configuration for openSUSE/SLES
- Relax libvirt-python and lxml minimum versions in requirements to match distro-provided packages
- Gracefully handle missing hostname command in minimal/containerized SUSE environments
- Prevent redundant git clone if workspace already exists in target path
2026-09-25 20:44:52 +03:00
catborise
88288f965d feat(install): add openSUSE and SLES 15 distribution support and compute bootstrap 2026-09-25 20:03:02 +03:00
22 changed files with 792 additions and 434 deletions

View file

@ -21,6 +21,7 @@
**/*.log
**/console/cert.pem*
**/dhcpd.*
webvirtcloud/settings.py
# IDEs and OS
**/.idea

5
.github/linters/.hadolint.yaml vendored Normal file
View file

@ -0,0 +1,5 @@
ignored:
- DL3008
- DL3013
- DL3042
- SC1091

View file

@ -68,11 +68,13 @@ jobs:
- name: Lint Code Base
uses: docker://github/super-linter:latest
env:
FILTER_REGEX_EXCLUDE: .*(static|scss|venv|locale)/.*
FILTER_REGEX_EXCLUDE: .*(static|scss|venv|locale|templates)/.*
DEFAULT_BRANCH: master
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VALIDATE_ALL_CODEBASE: false
VALIDATE_MARKDOWN: false
VALIDATE_HTML: false
VALIDATE_JSCPD: false
VALIDATE_ANSIBLE: false
VALIDATE_CLOJURE: false
VALIDATE_COFFEE: false

66
.github/workflows/test.yml vendored Normal file
View file

@ -0,0 +1,66 @@
name: Test Suite
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
test:
name: Python ${{ matrix.python-version }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ['3.10', '3.11', '3.12']
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: 'conf/requirements.txt'
- name: Install System Dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends \
libvirt-dev \
libldap2-dev \
libsasl2-dev \
python3-lxml \
zlib1g-dev \
libxslt1-dev \
pkg-config \
gcc \
libssl-dev \
libxml2-dev
- name: Install Python Dependencies
run: |
python -m pip install --upgrade pip setuptools wheel
pip install -r conf/requirements.txt
if [ -f dev/requirements.txt ]; then pip install -r dev/requirements.txt; fi
- name: Configure WebVirtCloud Settings
run: |
cp webvirtcloud/settings.py.template webvirtcloud/settings.py
SECRET_KEY=$(python -c 'import secrets; print(secrets.token_urlsafe(50))')
sed -i "s|^SECRET_KEY = .*|SECRET_KEY = \"${SECRET_KEY}\"|" webvirtcloud/settings.py
- name: Run Django Migrations
run: |
python manage.py migrate --noinput
- name: Run Django Test Suite
run: |
python manage.py test
- name: Run vrtManager Unit Tests
run: |
python -m unittest discover -s vrtManager -p "test_*.py"

5
.hadolint.yaml Normal file
View file

@ -0,0 +1,5 @@
ignored:
- DL3008
- DL3013
- DL3042
- SC1091

View file

@ -1,17 +0,0 @@
---
language: python
python:
- "3.9"
env:
- DJANGO=4.2.4
install:
- pip install -r dev/requirements.txt
script:
- pep8 --ignore=E501 vrtManager accounts admin appsettings \
computesconsole create datasource instances \
interfaceslogs networks nwfilters storages \
virtsecrets
- pyflakes vrtManager accounts admin appsettings computes console create datasource \
instances interfaces logs networks nwfilters storages virtsecrets
- python manage.py migrate
- python manage.py test --settings=webvirtcloud.settings-dev

View file

@ -6,16 +6,18 @@ EXPOSE 6080
# Use baseimage-docker's init system.
CMD ["/sbin/my_init"]
RUN echo 'APT::Get::Clean=always;' >> /etc/apt/apt.conf.d/99AutomaticClean
# hadolint ignore=DL3008
RUN apt-get update -qqy \
&& DEBIAN_FRONTEND=noninteractive apt-get -qyy install \
--no-install-recommends \
git \
python3-venv \
python3-pip \
python3-dev \
python3-lxml \
python3-libvirt \
libvirt-dev \
zlib1g-dev \
nginx \
@ -30,42 +32,50 @@ RUN apt-get update -qqy \
# Setup webvirtcloud
WORKDIR /srv/webvirtcloud
# Install Python dependencies first to leverage Docker layer caching
# Install Python dependencies first with system-site-packages to leverage prebuilt bindings
COPY conf/requirements.txt conf/requirements.txt
RUN python3 -m venv venv && \
# hadolint ignore=DL3013,DL3042,SC1091
RUN python3 -m venv --system-site-packages venv && \
. venv/bin/activate && \
pip3 install -U pip && \
pip3 install wheel && \
pip3 install -r conf/requirements.txt && \
pip3 cache purge
pip3 install --no-cache-dir -U pip wheel && \
pip3 install --no-cache-dir -r conf/requirements.txt
# Copy application source
COPY . /srv/webvirtcloud
# Run collectstatic with temporary dummy key, then remove temporary settings file
# hadolint ignore=SC1091
RUN . venv/bin/activate && \
python3 manage.py makemigrations && \
python3 manage.py migrate && \
python3 manage.py collectstatic --noinput && \
cp webvirtcloud/settings.py.template webvirtcloud/settings.py && \
SECRET_KEY="build-dummy-key-only-for-collectstatic" python3 manage.py collectstatic --noinput && \
rm -f webvirtcloud/settings.py && \
chown -R www-data:www-data /srv/webvirtcloud
# Setup Nginx
RUN printf "\n%s" "daemon off;" >> /etc/nginx/nginx.conf && \
rm /etc/nginx/sites-enabled/default && \
rm -f /etc/nginx/sites-enabled/default && \
chown -R www-data:www-data /var/lib/nginx
COPY conf/nginx/webvirtcloud.conf /etc/nginx/conf.d/
# Register services to runit
RUN mkdir /etc/service/nginx && \
mkdir /etc/service/nginx-log-forwarder && \
mkdir /etc/service/webvirtcloud && \
mkdir /etc/service/novnc
# Register startup init script and services to runit
RUN mkdir -p /etc/my_init.d \
/etc/service/nginx \
/etc/service/nginx-log-forwarder \
/etc/service/webvirtcloud \
/etc/service/novnc
COPY conf/runit/10_webvirtcloud_init.sh /etc/my_init.d/10_webvirtcloud_init.sh
COPY conf/runit/nginx /etc/service/nginx/run
COPY conf/runit/nginx-log-forwarder /etc/service/nginx-log-forwarder/run
COPY conf/runit/novncd.sh /etc/service/novnc/run
COPY conf/runit/webvirtcloud.sh /etc/service/webvirtcloud/run
RUN chmod +x /etc/my_init.d/10_webvirtcloud_init.sh \
/etc/service/nginx/run \
/etc/service/nginx-log-forwarder/run \
/etc/service/novnc/run \
/etc/service/webvirtcloud/run
# Define mountable directories.
#VOLUME []
# Declare mountable data directory for persistent SQLite and SSH keys
VOLUME ["/srv/webvirtcloud/data", "/var/www/.ssh"]
WORKDIR /srv/webvirtcloud

572
README.md
View file

@ -1,38 +1,30 @@
[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/retspen/webvirtcloud)
# WebVirtCloud
###### Python >=3.11 & Django 4.2 LTS
###### Python >=3.10 & Django 4.2 LTS (tested on Python 3.10 – 3.12)
## Description
WebVirtCloud is a virtualization web interface for administrators and users. It allows delegating virtual machines to users with role-based permissions. A built-in noVNC / SPICE console presents a full graphical interface to the guest domain. KVM is currently the supported hypervisor.
## Features
* QEMU/KVM Hypervisor Management
* QEMU/KVM Instance Management - Create, Delete, Update
* Hypervisor & Instance web based stats
* Manage Multiple QEMU/KVM Hypervisor
* Manage Hypervisor Datastore pools
* Manage Hypervisor Networks
* Instance Console Access with Browsers
* Libvirt API based web management UI
* User Based Authorization and Authentication
* User can add SSH public key to root in Instance (Tested only Ubuntu)
* User can change root password in Instance (Tested only Ubuntu)
* Hypervisor & Instance web-based real-time stats
* Manage Multiple QEMU/KVM Hypervisors
* Manage Hypervisor Datastore pools and storage volumes
* Manage Hypervisor Networks and interfaces
* Instance Console Access with Web Browsers (noVNC & SPICE)
* Libvirt API-based web management UI
* User-based Authorization, Authentication, and 2FA (OTP)
* User can add SSH public key to root in Instance
* User can change root password in Instance
* Supports cloud-init datasource interface
### Warning!!!
How to update <code>gstfsd</code> daemon on hypervisor:
```bash
wget -O - https://bit.ly/2NAaWXG | sudo tee -a /usr/local/bin/gstfsd
sudo service supervisor restart
```
## Description
WebVirtCloud is a virtualization web interface for admins and users. It can delegate Virtual Machine's to users. A noVNC viewer presents a full graphical console to the guest domain. KVM is currently the only hypervisor supported.
* REST API with OpenAPI 3.0 (Swagger & ReDoc) documentation
## Quick Install with Installer (Beta)
Install an OS and run specified commands. Installer supported OSes: Ubuntu 20.04/22.04/24.04, Debian 10/11/12, Rocky/Alma/OEL/RHEL 10.
Install an OS and run specified commands. Installer supported OSes: Ubuntu 20.04/22.04/24.04, Debian 10/11/12, Rocky/Alma/OEL/RHEL 9/10, openSUSE Leap 15.x / Tumbleweed, and SLES 15.
It can be installed on a virtual machine, physical host or on a KVM host.
```bash
@ -46,6 +38,21 @@ chmod 744 install.sh
./install.sh
```
## Docker Deployment (Docker Compose)
Run WebVirtCloud in a container with persistent volumes for data and SSH keys:
```bash
# 1. Clone repository:
git clone https://github.com/retspen/webvirtcloud
cd webvirtcloud
# 2. Start services:
docker compose up -d
```
Access the panel at `http://<server-ip>` and noVNC console at port `6080`.
## Manual Installation
### Generate secret key
@ -56,80 +63,147 @@ You should generate SECRET_KEY after cloning repository. Then put it into webvir
python3 -c 'import secrets; print(secrets.token_urlsafe(50))'
```
### Install WebVirtCloud panel (Ubuntu 18.04+ LTS)
### Ubuntu 20.04 / 22.04 / 24.04 LTS & Debian 11 / 12
```bash
sudo apt-get -y install git python3-venv python3-virtualenv python3-dev python3-lxml libvirt-dev zlib1g-dev libxslt1-dev nginx supervisor libsasl2-modules gcc pkg-config python3-guestfs libsasl2-dev libldap2-dev libssl-dev
git clone https://github.com/retspen/webvirtcloud
cd webvirtcloud
cp webvirtcloud/settings.py.template webvirtcloud/settings.py
# now put secret key to webvirtcloud/settings.py
sudo cp conf/supervisor/webvirtcloud.conf /etc/supervisor/conf.d
sudo cp conf/nginx/webvirtcloud.conf /etc/nginx/conf.d
cd ..
sudo mv webvirtcloud /srv
sudo chown -R www-data:www-data /srv/webvirtcloud
# 1. Install system prerequisites
sudo apt-get update && sudo apt-get -y install git python3-venv python3-dev python3-lxml python3-libvirt libvirt-dev zlib1g-dev libxslt1-dev nginx supervisor libsasl2-modules gcc pkg-config python3-guestfs libsasl2-dev libldap2-dev libssl-dev
# 2. Clone repository to /srv/webvirtcloud
sudo git clone https://github.com/retspen/webvirtcloud /srv/webvirtcloud
cd /srv/webvirtcloud
virtualenv -p python3 venv
source venv/bin/activate
pip install -r conf/requirements.txt
python3 manage.py migrate
python3 manage.py collectstatic --noinput
sudo chown -R www-data:www-data /srv/webvirtcloud
sudo rm /etc/nginx/sites-enabled/default
```
Restart services for running WebVirtCloud:
```bash
sudo service nginx restart
sudo service supervisor restart
```
Setup libvirt and KVM on server
```bash
wget -O - https://bit.ly/36baWUu | sudo sh
```
Done!!
Go to http://serverip and you should see the login screen.
### Install WebVirtCloud panel (RHEL Based OS 8/9/10 / Rocky Linux / AlmaLinux)
```bash
sudo dnf -y install epel-release
sudo dnf -y install python3-devel libvirt-devel python3-libvirt python3-ldap python3-lxml cyrus-sasl-devel openldap-devel openssl-devel glibc gcc nginx supervisor git python3-libguestfs iproute-tc cyrus-sasl-md5
```
#### Creating directories and cloning repository
```bash
sudo mkdir /srv && cd /srv
sudo git clone https://github.com/retspen/webvirtcloud && cd webvirtcloud
# 3. Configure settings
cp webvirtcloud/settings.py.template webvirtcloud/settings.py
# now put secret key to webvirtcloud/settings.py
# create secret key manually or use that command
sudo sed -i -E 's/SECRET_KEY = .*/SECRET_KEY = "'$(python3 /srv/webvirtcloud/conf/runit/secret_generator.py)'"/' /srv/webvirtcloud/webvirtcloud/settings.py
```
SECRET_KEY=$(python3 -c 'import secrets; print(secrets.token_urlsafe(50))')
sed -i "s|^SECRET_KEY = .*|SECRET_KEY = \"${SECRET_KEY}\"|" webvirtcloud/settings.py
#### Start installation webvirtcloud
# 4. Deploy service configurations
sudo cp conf/supervisor/webvirtcloud.conf /etc/supervisor/conf.d/
sudo cp conf/nginx/webvirtcloud.conf /etc/nginx/conf.d/
sudo rm -f /etc/nginx/sites-enabled/default
```bash
# 5. Create virtual environment and install dependencies
python3 -m venv --system-site-packages venv
source venv/bin/activate
pip3 install -r conf/requirements.txt
cp conf/nginx/webvirtcloud.conf /etc/nginx/conf.d/
pip install -r conf/requirements.txt
# 6. Database migrations and static files
python3 manage.py migrate
python3 manage.py collectstatic --noinput
# 7. Set permissions and start services
sudo chown -R www-data:www-data /srv/webvirtcloud
sudo systemctl restart nginx supervisor
```
### Local Development Setup (Rocky Linux / RHEL / Fedora / Ubuntu)
---
### RHEL 8 / 9 / 10 / Rocky Linux / AlmaLinux
```bash
# 1. Install EPEL and system prerequisites
sudo dnf -y install epel-release
sudo dnf -y install git python3-devel libvirt-devel python3-libvirt python3-ldap python3-lxml cyrus-sasl-devel cyrus-sasl-md5 openldap-devel openssl-devel glibc gcc nginx supervisor python3-libguestfs iproute-tc
# 2. Clone repository to /srv/webvirtcloud
sudo git clone https://github.com/retspen/webvirtcloud /srv/webvirtcloud
cd /srv/webvirtcloud
# 3. Configure settings
cp webvirtcloud/settings.py.template webvirtcloud/settings.py
SECRET_KEY=$(python3 conf/runit/secret_generator.py)
sed -i "s|^SECRET_KEY = .*|SECRET_KEY = \"${SECRET_KEY}\"|" webvirtcloud/settings.py
# 4. Create virtual environment and install dependencies
python3 -m venv --system-site-packages venv
source venv/bin/activate
pip install -r conf/requirements.txt
# 5. Database migrations and static files
python3 manage.py migrate
python3 manage.py collectstatic --noinput
# 6. Configure Supervisor
sudo tee /etc/supervisord.d/webvirtcloud.ini > /dev/null << 'EOF'
[program:webvirtcloud]
command=/srv/webvirtcloud/venv/bin/gunicorn webvirtcloud.wsgi:application -c /srv/webvirtcloud/gunicorn.conf.py
directory=/srv/webvirtcloud
user=nginx
autostart=true
autorestart=true
redirect_stderr=true
[program:novncd]
command=/srv/webvirtcloud/venv/bin/python3 /srv/webvirtcloud/console/novncd
directory=/srv/webvirtcloud
user=nginx
autostart=true
autorestart=true
redirect_stderr=true
EOF
# 7. Configure Nginx
sudo cp conf/nginx/webvirtcloud.conf /etc/nginx/conf.d/
# Ensure the default server block in /etc/nginx/nginx.conf does not conflict with webvirtcloud.conf
# 8. Set permissions, SELinux, and Firewall
sudo chown -R nginx:nginx /srv/webvirtcloud
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/webvirtcloud(/.*)" 2>/dev/null || true
sudo restorecon -R /srv/webvirtcloud 2>/dev/null || true
sudo setsebool -P httpd_can_network_connect on 2>/dev/null || true
sudo firewall-cmd --add-service=http --permanent 2>/dev/null || true
sudo firewall-cmd --add-port=6080/tcp --permanent 2>/dev/null || true
sudo firewall-cmd --reload 2>/dev/null || true
# 9. Start and enable services
sudo systemctl enable --now nginx supervisord
sudo systemctl restart nginx supervisord
```
---
### openSUSE Leap 15.x / Tumbleweed / SLES 15
```bash
# 1. Install system prerequisites (Python 3.11 stack and C bindings)
sudo zypper --non-interactive install -y git hostname python311 python311-base python311-devel python311-pip python311-libvirt-python python311-lxml python311-ldap libvirt-devel cyrus-sasl-devel libopenssl-devel gcc pkg-config nginx
# 2. Clone repository to /srv/webvirtcloud
sudo git clone https://github.com/retspen/webvirtcloud /srv/webvirtcloud
cd /srv/webvirtcloud
# 3. Configure settings
cp webvirtcloud/settings.py.template webvirtcloud/settings.py
SECRET_KEY=$(python3.11 conf/runit/secret_generator.py)
sed -i "s|^SECRET_KEY = .*|SECRET_KEY = \"${SECRET_KEY}\"|" webvirtcloud/settings.py
# 4. Create virtual environment and install dependencies
python3.11 -m venv --system-site-packages venv
source venv/bin/activate
pip install -r conf/requirements.txt
# 5. Database migrations and static files
python3 manage.py migrate
python3 manage.py collectstatic --noinput
# 6. Configure Nginx and Supervisor
sudo cp conf/nginx/suse_nginx.conf /etc/nginx/vhosts.d/webvirtcloud.conf 2>/dev/null || sudo cp conf/nginx/webvirtcloud.conf /etc/nginx/conf.d/
sudo chown -R nginx:nginx /srv/webvirtcloud
# 7. Start services
sudo systemctl enable --now nginx
sudo systemctl restart nginx
```
---
## Local Development Setup
For developers working locally on WebVirtCloud without running full production services:
#### Rocky Linux / RHEL / Fedora:
### Rocky Linux / RHEL / Fedora
```bash
# 1. Install system prerequisites and precompiled bindings
sudo dnf -y install python3-devel libvirt-devel python3-libvirt python3-ldap python3-lxml gcc git
@ -149,7 +223,7 @@ python manage.py migrate
python manage.py runserver 0.0.0.0:8000
```
#### Ubuntu / Debian:
### Ubuntu / Debian
```bash
# 1. Install system prerequisites
sudo apt-get update && sudo apt-get -y install git python3-venv python3-dev python3-lxml python3-libvirt libvirt-dev zlib1g-dev libldap2-dev libsasl2-dev gcc pkg-config
@ -169,231 +243,128 @@ python manage.py migrate
python manage.py runserver 0.0.0.0:8000
```
#### Configure the supervisor for RHEL Based OS
Add the following after the [include] line (after **files = ...** actually):
### openSUSE Leap 15.x / Tumbleweed / SLES 15
```bash
sudo vim /etc/supervisord.conf
# 1. Install system prerequisites
sudo zypper --non-interactive install -y git hostname python311 python311-base python311-devel python311-pip python311-libvirt-python python311-lxml python311-ldap libvirt-devel cyrus-sasl-devel libopenssl-devel gcc pkg-config
[program:webvirtcloud]
command=/srv/webvirtcloud/venv/bin/gunicorn webvirtcloud.wsgi:application -c /srv/webvirtcloud/gunicorn.conf.py
directory=/srv/webvirtcloud
user=nginx
autostart=true
autorestart=true
redirect_stderr=true
# 2. Create virtual environment with system site packages
python3.11 -m venv --system-site-packages .venv
source .venv/bin/activate
[program:novncd]
command=/srv/webvirtcloud/venv/bin/python3 /srv/webvirtcloud/console/novncd
directory=/srv/webvirtcloud
user=nginx
autostart=true
autorestart=true
redirect_stderr=true
# 3. Install Python dependencies
pip install -r conf/requirements.txt
pip install -r dev/requirements.txt
# 4. Initialize configuration and run local dev server
cp webvirtcloud/settings.py.template webvirtcloud/settings.py
sed -i -E 's/SECRET_KEY = .*/SECRET_KEY = "'$(python3.11 conf/runit/secret_generator.py)'"/' webvirtcloud/settings.py
python manage.py migrate
python manage.py runserver 0.0.0.0:8000
```
#### Edit the nginx.conf file
## Compute Node (Hypervisor) Setup
You will need to edit the main nginx.conf file as the one that comes from the rpm's will not work. Comment the following lines:
To configure a physical server or virtual machine as a KVM compute node to be managed by WebVirtCloud:
### 1. Install KVM and Libvirt via Bootstrap Script
WebVirtCloud includes an automated bootstrap script supporting Ubuntu 20.04/22.04/24.04, Debian 10/11/12, RHEL/Rocky/Alma 8/9/10, openSUSE Leap 15.x / Tumbleweed, and SLES 15:
```bash
# 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 {
# }
# }
}
# Run bootstrap script directly via curl:
curl -fsSL https://raw.githubusercontent.com/retspen/webvirtcloud/master/dev/libvirt-bootstrap.sh | sudo sh
# Or run locally from a cloned repository:
sudo ./dev/libvirt-bootstrap.sh
```
Also make sure file in **/etc/nginx/conf.d/webvirtcloud.conf** has the proper paths:
### 2. Configure SSH Connection Between Panel and Compute Node
On the WebVirtCloud panel host, generate an SSH key for the web service user (`www-data` on Debian/Ubuntu, `nginx` on RHEL/openSUSE):
```bash
upstream gunicorn_server {
#server unix:/srv/webvirtcloud/venv/wvcloud.socket fail_timeout=0;
server 127.0.0.1:8000 fail_timeout=0;
}
server {
listen 80;
server_name servername.domain.com;
access_log /var/log/nginx/webvirtcloud-access_log;
location /static/ {
root /srv/webvirtcloud;
expires max;
}
location / {
proxy_pass http://gunicorn_server;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-for $proxy_add_x_forwarded_for;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Forwarded-Proto $remote_addr;
proxy_connect_timeout 1800;
proxy_read_timeout 1800;
proxy_send_timeout 1800;
client_max_body_size 1024M;
}
}
```
Change permissions so nginx can read the webvirtcloud folder:
```bash
sudo chown -R nginx:nginx /srv/webvirtcloud
```
Change permission for selinux:
```bash
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/webvirtcloud(/.*)"
sudo setsebool -P httpd_can_network_connect on -P
```
Add required user to the kvm group(if you not install with root):
```bash
sudo usermod -G kvm -a <username>
```
Allow http ports on firewall:
```bash
sudo firewall-cmd --add-service=http
sudo firewall-cmd --add-service=http --permanent
sudo firewall-cmd --add-port=6080/tcp
sudo firewall-cmd --add-port=6080/tcp --permanent
```
Let's restart nginx and the supervisord services:
```bash
sudo systemctl restart nginx && systemctl restart supervisord
```
And finally, check everything is running:
```bash
sudo supervisorctl status
gstfsd RUNNING pid 24662, uptime 6:01:40
novncd RUNNING pid 24661, uptime 6:01:40
webvirtcloud RUNNING pid 24660, uptime 6:01:40
```
#### Apache mod_wsgi configuration
```bash
WSGIDaemonProcess webvirtcloud threads=2 maximum-requests=1000 display-name=webvirtcloud
WSGIScriptAlias / /srv/webvirtcloud/webvirtcloud/wsgi_custom.py
```
#### Install final required packages for libvirtd and others on Host Server
```bash
wget -O - https://clck.ru/9V9fH | sudo sh
```
Done!!
Go to http://serverip and you should see the login screen.
### Alternative running novncd via runit(Debian)
Alternative to running nonvcd via supervisor is runit.
On Debian systems install runit and configure novncd service:
```bash
apt install runit runit-systemd
mkdir /etc/service/novncd/
ln -s /srv/webvirtcloud/conf/runit/novncd.sh /etc/service/novncd/run
systemctl start runit.service
```
### Default credentials
```html
login: admin
password: admin
```
### Configuring Compute SSH connection
This is a short example of configuring cloud and compute side of the ssh connection.
On the webvirtcloud machine you need to generate ssh keys and optionally disable StrictHostKeyChecking.
```bash
chown www-data -R ~www-data
sudo -u www-data ssh-keygen
cat > ~www-data/.ssh/config << EOF
# Generate key (Debian/Ubuntu example using www-data):
sudo -u www-data ssh-keygen -t ed25519
sudo -u www-data tee ~www-data/.ssh/config > /dev/null << 'EOF'
Host *
StrictHostKeyChecking no
EOF
chown www-data -R ~www-data/.ssh/config
sudo chmod 600 ~www-data/.ssh/config
# Copy public key to the compute node root user:
sudo -u www-data ssh-copy-id root@<compute-node-ip>
```
You need to put cloud public key into authorized keys on the compute node. Simpliest way of doing this is to use ssh tool from the webvirtcloud server.
### 3. Install or Update `gstfsd` Daemon
The `gstfsd` daemon provides guest filesystem inspection and stats on hypervisors:
```bash
sudo -u www-data ssh-copy-id root@compute1
curl -fsSL https://raw.githubusercontent.com/retspen/webvirtcloud/master/conf/daemon/gstfsd | sudo tee /usr/local/bin/gstfsd > /dev/null
sudo chmod +x /usr/local/bin/gstfsd
sudo systemctl restart supervisor 2>/dev/null || sudo systemctl restart supervisord
```
### Host SMBIOS information is not available
### 4. Troubleshooting: Host SMBIOS Warning
If you see warning
If you see the warning `Unsupported configuration: Host SMBIOS information is not available`, install `dmidecode` and restart libvirt:
```bash
Unsupported configuration: Host SMBIOS information is not available
# Debian / Ubuntu:
sudo apt-get install -y dmidecode && sudo systemctl restart libvirtd
# RHEL / Rocky / AlmaLinux:
sudo dnf install -y dmidecode && sudo systemctl restart libvirtd
# openSUSE / SLES:
sudo zypper install -y dmidecode && sudo systemctl restart libvirtd
```
Then you need to install `dmidecode` package on your host using your package manager and restart libvirt daemon.
> **Security Notice (Compute Node Firewall):**
> Libvirt compute nodes listen on VNC/SPICE ports (`5900`–`65535`) to allow WebVirtCloud to proxy graphical consoles. Ensure your firewall (`ufw`, `firewalld`, or `iptables`) restricts these ports to accept connections **only** from the WebVirtCloud panel IP, and never exposes them directly to public networks.
Debian/Ubuntu like:
---
## Configuration & Operational Notes
### Default Credentials
After initial installation, sign in to the web panel at `http://<server-ip>`:
```text
Username: admin
Password: admin
```
> **Security Notice:** Change the default administrator password immediately after first login.
### Alternative: Running novncd via runit (Debian)
As an alternative to Supervisor, Debian systems can manage `novncd` via `runit`:
```bash
sudo apt-get install dmidecode
sudo service libvirt-bin restart
sudo apt install -y runit runit-systemd
sudo mkdir -p /etc/service/novncd/
sudo ln -s /srv/webvirtcloud/conf/runit/novncd.sh /etc/service/novncd/run
sudo systemctl start runit.service
```
Arch Linux
### Cloud-Init Datasource
```bash
sudo pacman -S dmidecode
systemctl restart libvirtd
```
WebVirtCloud can serve cloud-init metadata (root SSH keys and hostname) to guest instances:
### Cloud-init
Currently supports only root ssh authorized keys and hostname. Example configuration of the cloud-init client follows.
```bash
```yaml
datasource:
OpenStack:
metadata_urls: [ "http://webvirtcloud.domain.com/datasource" ]
```
### Reverse-Proxy
### Reverse-Proxy & Port Forwarding
Edit WS_PUBLIC_PORT at settings.py file to expose redirect to 80 or 443. Default: 6080
If WebVirtCloud runs behind a reverse proxy terminating SSL or forwarding port 80/443, configure `WS_PUBLIC_PORT` in `webvirtcloud/settings.py` (default: 6080):
```bash
WS_PUBLIC_PORT = 80
```python
WS_PUBLIC_PORT = 80 # or 443
```
## How To Update
@ -414,26 +385,34 @@ sudo service supervisor restart
> 1. In `INSTALLED_APPS`, replace `'drf_yasg'` with `'drf_spectacular'` and `'drf_spectacular_sidecar'`.
> 2. Ensure the `REST_FRAMEWORK` and `SPECTACULAR_SETTINGS` configuration blocks are present (see `webvirtcloud/settings.py.template`).
### Running tests
## Running Tests
Server on which tests will be performed must have libvirt up and running.
It must not contain vms.
It must have `default` storage which not contain any disk images.
It must have `default` network which must be on.
Setup venv
WebVirtCloud includes unit tests for both Django models/views and the `vrtManager` libvirt abstraction layer. The test suite uses isolated mock drivers by default and does not require a live KVM hypervisor.
### 1. Setup Virtual Environment
```bash
python -m venv venv
source venv/bin/activate
python3 -m venv .venv
source .venv/bin/activate
pip install -r conf/requirements.txt
pip install -r dev/requirements.txt
```
Run tests
### 2. Run Test Suite
```bash
# Run Django test suite (accounts, admin, instances, logs, etc.):
python manage.py test
# Run vrtManager unit tests:
python -m unittest discover -s vrtManager -p "test_*.py"
```
> **Live Hypervisor Testing (Optional):**
> To run tests against a live libvirt host instead of standalone mocks, set the `WEBVIRTCLOUD_TEST_LIBVIRT_URI` environment variable before running tests:
> ```bash
> export WEBVIRTCLOUD_TEST_LIBVIRT_URI="qemu+ssh://root@compute1/system"
> python manage.py test
> ```
## LDAP Configuration
The config options below can be changed in `webvirtcloud/settings.py` file. Variants for Active Directory and OpenLDAP are shown. This is a minimal config to get LDAP running, for further info read the [django-auth-ldap documentation](https://django-auth-ldap.readthedocs.io).
@ -505,26 +484,29 @@ Now when you login with an LDAP user it will be assigned the rights defined. The
If you'd like to move a user from ldap to WebVirtCloud, just change its password from the UI and (eventually) remove from the group in LDAP.
## REST API / BETA
Webvirtcloud provides a REST API for programmatic access.
To access API methods open your browser and check them with Swagger interface
```bash
http://<webvirtloud-address:port>/swagger
```
```bash
http://<webvirtloud-address:port>/redoc
```
## REST API (OpenAPI 3.0)
WebVirtCloud provides a REST API powered by Django REST Framework and documented via `drf-spectacular`.
You can access the interactive API documentation and schema endpoints in your browser:
* **Swagger UI:** `http://<webvirtcloud-address:port>/swagger/`
* **ReDoc UI:** `http://<webvirtcloud-address:port>/redoc/`
* **OpenAPI 3.0 Schema:** `http://<webvirtcloud-address:port>/api/schema/` (download schema in JSON or YAML format)
## Screenshots
Instance Detail:
<img src="doc/images/instance.PNG" width="96%" align="center"/>
Instance List:</br>
<img src="doc/images/grouped.PNG" width="43%"/>
<img src="doc/images/nongrouped.PNG" width="53%"/>
Other: </br>
<img src="doc/images/hosts.PNG" width="47%"/>
<img src="doc/images/log.PNG" width="49%"/>
| Instance Detail |
|:---:|
| ![Instance Detail](doc/images/instance.PNG) |
| Grouped Instances | Non-Grouped Instances |
|:---:|:---:|
| ![Grouped Instances](doc/images/grouped.PNG) | ![Non-Grouped Instances](doc/images/nongrouped.PNG) |
| Compute Hosts | Activity Log |
|:---:|:---:|
| ![Compute Hosts](doc/images/hosts.PNG) | ![Activity Log](doc/images/log.PNG) |
## License

View file

@ -0,0 +1,30 @@
# Nginx configuration for openSUSE and SUSE Linux Enterprise Server (SLES)
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
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 and vhosts.d directories
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/vhosts.d/*.conf;
}

View file

@ -11,8 +11,8 @@ drf-spectacular[sidecar]==0.28.0
eventlet==0.40.1
gunicorn==23.0.0
libsass==0.23.0
libvirt-python>=11.0.0
lxml>=5.2.0
libvirt-python>=9.0.0
lxml>=4.9.0
ldap3==2.9.1
markdown==3.8.2
paramiko==3.4.0

View file

@ -0,0 +1,48 @@
#!/bin/bash
set -e
APP_DIR="/srv/webvirtcloud"
DATA_DIR="$APP_DIR/data"
mkdir -p "$DATA_DIR" "/var/www/.ssh"
# If settings.py doesn't exist, generate from template
if [ ! -f "$APP_DIR/webvirtcloud/settings.py" ]; then
echo "* Generating webvirtcloud/settings.py from template..."
cp "$APP_DIR/webvirtcloud/settings.py.template" "$APP_DIR/webvirtcloud/settings.py"
fi
# Ensure SECRET_KEY is persisted across container restarts if not supplied via environment
if [ -z "$SECRET_KEY" ]; then
if [ ! -s "$DATA_DIR/secret_key" ]; then
echo "* Generating fresh random SECRET_KEY..."
"$APP_DIR/venv/bin/python3" -c 'import secrets; print(secrets.token_urlsafe(50))' > "$DATA_DIR/secret_key"
chmod 600 "$DATA_DIR/secret_key"
fi
fi
# Apply optional WebSocket configuration from environment
if [ -n "$WS_PUBLIC_PORT" ]; then
sed -i "s|^WS_PUBLIC_PORT = .*|WS_PUBLIC_PORT = $WS_PUBLIC_PORT|" "$APP_DIR/webvirtcloud/settings.py"
fi
if [ -n "$WS_PUBLIC_HOST" ]; then
sed -i "s|^WS_PUBLIC_HOST = .*|WS_PUBLIC_HOST = \"$WS_PUBLIC_HOST\"|" "$APP_DIR/webvirtcloud/settings.py"
fi
if [ -n "$WS_PUBLIC_PATH" ]; then
sed -i "s|^WS_PUBLIC_PATH = .*|WS_PUBLIC_PATH = \"$WS_PUBLIC_PATH\"|" "$APP_DIR/webvirtcloud/settings.py"
fi
# Persist SQLite database in DATA_DIR
if [ ! -f "$DATA_DIR/db.sqlite3" ] && [ -f "$APP_DIR/db.sqlite3" ] && [ ! -L "$APP_DIR/db.sqlite3" ]; then
mv "$APP_DIR/db.sqlite3" "$DATA_DIR/db.sqlite3"
fi
touch "$DATA_DIR/db.sqlite3"
ln -sf "$DATA_DIR/db.sqlite3" "$APP_DIR/db.sqlite3"
# Run database migrations
echo "* Running database migrations..."
"$APP_DIR/venv/bin/python3" "$APP_DIR/manage.py" migrate --noinput
# Set proper permissions on runtime and data directories
chown -R www-data:www-data "$DATA_DIR" "/var/www/.ssh"
chown www-data:www-data "$APP_DIR/webvirtcloud/settings.py" 2>/dev/null || true
chmod 700 "/var/www/.ssh" 2>/dev/null || true

View file

@ -1,18 +1,28 @@
#!/bin/sh
# `/sbin/setuser www-data` runs the given command as the user `www-data`.
RUNAS=$(which setuser)
[ -z "$RUNAS" ] && RUNAS="$(which sudo) -u"
USER=www-data
USER="www-data"
DJANGO_PROJECT="/srv/webvirtcloud"
PYTHON="$DJANGO_PROJECT/venv/bin/python3"
NOVNCD="$DJANGO_PROJECT/console/novncd"
LOG="/var/log/novncd.log"
DJANGO_PROJECT=/srv/webvirtcloud
PYTHON=$DJANGO_PROJECT/venv/bin/python3
NOVNCD=$DJANGO_PROJECT/console/novncd
cd "$DJANGO_PROJECT" || exit 1
# make novncd debug, verbose
#PARAMS="-d -v"
LOG=/var/log/novncd.log
cd $DJANGO_PROJECT || exit
exec "$RUNAS" "$USER" "$PYTHON" "$NOVNCD" "$PARAMS" >> $LOG 2>&1
if [ -x /sbin/setuser ]; then
if [ -n "$PARAMS" ]; then
# shellcheck disable=SC2086
exec /sbin/setuser "$USER" "$PYTHON" "$NOVNCD" $PARAMS >> "$LOG" 2>&1
else
exec /sbin/setuser "$USER" "$PYTHON" "$NOVNCD" >> "$LOG" 2>&1
fi
elif command -v su >/dev/null 2>&1; then
if [ -n "$PARAMS" ]; then
# shellcheck disable=SC2086
exec su -s /bin/sh "$USER" -c "exec \"$PYTHON\" \"$NOVNCD\" $PARAMS" >> "$LOG" 2>&1
else
exec su -s /bin/sh "$USER" -c "exec \"$PYTHON\" \"$NOVNCD\"" >> "$LOG" 2>&1
fi
fi

View file

@ -143,6 +143,7 @@ class CompatibilityMixIn(object):
# NoVNC uses it's own convention that forward token
# from the request to a cookie header, we should check
# also for this behavior
token = None
hcookie = self.headers.get("cookie")
if hcookie:
@ -159,6 +160,22 @@ class CompatibilityMixIn(object):
if "token" in cookie:
token = cookie["token"].value
# Fallback to query parameter if token not found in cookie
if not token and hasattr(self, "path"):
from urllib.parse import parse_qs, urlparse
try:
parsed_url = urlparse(self.path)
query_params = parse_qs(parsed_url.query)
if "token" in query_params:
token = query_params["token"][0]
except Exception:
pass
if not token:
self.msg("No console token provided in cookie or query parameters")
return
(
connhost,
connport,

View file

@ -61,8 +61,8 @@
sc.stop();
}
// uri = scheme + host + ":" + port;
uri = scheme + "{{ ws_host }}:{{ ws_port }}{{ ws_path }}";
var path = '{{ ws_path }}';
uri = scheme + "{{ ws_host }}:{{ ws_port }}" + (path ? (path[0] == '/' ? path : ('/' + path)) : '/');
document.getElementById('connectButton').innerHTML = "Stop";
document.getElementById('connectButton').onclick = disconnect;

View file

@ -194,7 +194,15 @@
if (port) {
url += ':' + port;
}
url += '/' + path;
if (path) {
url += path.startsWith('/') ? path : ('/' + path);
} else {
url += '/';
}
const token = readQueryVariable('token', '{{ token }}');
if (token) {
url += (url.includes('?') ? '&' : '?') + 'token=' + encodeURIComponent(token);
}
// Creating a new RFB object will start a new connection
rfb = new RFB(document.getElementById('noVNC_container'), url,

View file

@ -1,23 +1,21 @@
# pylint: disable=no-name-in-module,no-member
import re
from vrtManager.util import randomUUID
from django.http.response import HttpResponseServerError
from django.shortcuts import render
from django.utils.translation import gettext_lazy as _
from libvirt import libvirtError
from accounts.models import UserInstance
from appsettings.settings import app_settings
from django.http.response import HttpResponseServerError
from django.shortcuts import render
from django.utils.translation import gettext_lazy as _
from instances.models import Instance
from libvirt import libvirtError
from vrtManager.instance import wvmInstance
from webvirtcloud.settings import (
SOCKETIO_PUBLIC_HOST,
SOCKETIO_PUBLIC_PATH,
SOCKETIO_PUBLIC_PORT,
WS_PUBLIC_HOST,
WS_PUBLIC_PATH,
WS_PUBLIC_PORT,
SOCKETIO_PUBLIC_HOST,
SOCKETIO_PUBLIC_PORT,
SOCKETIO_PUBLIC_PATH,
)
@ -86,6 +84,9 @@ def console(request):
if ":" in ws_host:
ws_host = re.sub(":[0-9]+", "", ws_host)
if ws_path:
ws_path = ws_path.strip("/") + "/" if ws_path.strip("/") else ""
if console_type == "vnc" or console_type == "spice":
console_page = "console-" + console_type + "-" + view_type + ".html"
response = render(request, console_page, locals())

View file

@ -160,11 +160,11 @@ __gather_linux_system_info() {
# Let's convert CamelCase to Camel Case
DISTRO_NAME=$(__camelcase_split "$DISTRO_NAME")
fi
if [ "${DISTRO_NAME}" = "openSUSE project" ]; then
if [ "${DISTRO_NAME}" = "openSUSE project" ] || [ "${DISTRO_NAME}" = "openSUSE" ] || [ "${DISTRO_NAME}" = "opensuse" ]; then
# lsb_release -si returns "openSUSE project" on openSUSE 12.3
DISTRO_NAME="opensuse"
fi
if [ "${DISTRO_NAME}" = "SUSE LINUX" ]; then
if [ "${DISTRO_NAME}" = "SUSE LINUX" ] || [ "${DISTRO_NAME}" = "SLES" ] || [ "${DISTRO_NAME}" = "sles" ]; then
# lsb_release -si returns "SUSE LINUX" on SLES 11 SP3
DISTRO_NAME="suse"
fi
@ -223,8 +223,8 @@ __gather_linux_system_info() {
done < /etc/"${rsource}"
;;
os )
nn=$(grep '^ID=' /etc/os-release | sed -e 's/^ID=\(.*\)$/\1/g')
rv=$(grep '^VERSION_ID=' /etc/os-release | sed -e 's/^VERSION_ID=\(.*\)$/\1/g')
nn=$(grep '^ID=' /etc/os-release | sed -e 's/^ID=\(.*\)$/\1/g' | tr -d '"'\'')
rv=$(grep '^VERSION_ID=' /etc/os-release | sed -e 's/^VERSION_ID=\(.*\)$/\1/g' | tr -d '"'\'')
[ "${rv}x" != "x" ] && v=$(__parse_version_string "$rv") || v=""
case $(echo "${nn}" | tr '[:upper:]' '[:lower:]') in
arch )
@ -242,6 +242,12 @@ __gather_linux_system_info() {
echowarn "Unable to parse the Debian Version"
fi
;;
opensuse* )
n="opensuse"
;;
sles*|sled*|suse* )
n="suse"
;;
* )
n=${nn}
;;
@ -305,10 +311,8 @@ __check_end_of_life_versions() {
;;
opensuse)
# openSUSE versions not supported
#
# <= 12.1
if { [ "$DISTRO_MAJOR_VERSION" -eq 12 ] && [ "$DISTRO_MINOR_VERSION" -eq 1 ]; } || [ "$DISTRO_MAJOR_VERSION" -lt 12 ]; then
# openSUSE: allow Leap 15.x and Tumbleweed
if [ -n "$DISTRO_MAJOR_VERSION" ] && [ "$DISTRO_MAJOR_VERSION" -lt 12 ]; then
echoerror "End of life distributions are not supported."
echoerror "Please consider upgrading to the next stable. See:"
echoerror " http://en.opensuse.org/Lifetime"
@ -317,9 +321,8 @@ __check_end_of_life_versions() {
;;
suse)
# SuSE versions not supported
#
# < 11 SP2
# SLES / SLED
if [ -f /etc/SuSE-release ]; then
SUSE_PATCHLEVEL=$(awk '/PATCHLEVEL/ {print $3}' /etc/SuSE-release )
if [ "x${SUSE_PATCHLEVEL}" = "x" ]; then
SUSE_PATCHLEVEL="00"
@ -329,6 +332,10 @@ __check_end_of_life_versions() {
echoerror "Please consider upgrading to the next stable"
exit 1
fi
elif [ -n "$DISTRO_MAJOR_VERSION" ] && [ "$DISTRO_MAJOR_VERSION" -lt 12 ]; then
echoerror "Versions lower than SUSE 12 are not supported."
exit 1
fi
;;
fedora)
@ -546,58 +553,63 @@ daemons_running_fedora() {
# Opensuse Install Functions
#
install_opensuse() {
zypper -n install -l kvm libvirt bridge-utils python3-libguestfs supervisor || return 1
zypper -n install -l qemu-kvm libvirt libvirt-daemon-qemu libvirt-client bridge-utils python3-libguestfs supervisor || \
zypper -n install -l kvm libvirt bridge-utils python3-libguestfs python3-supervisor || \
zypper -n install -l kvm libvirt bridge-utils python3-libguestfs || return 1
return 0
}
install_opensuse_post() {
if [ -f /etc/sysconfig/libvirtd ]; then
sed -i 's/#LIBVIRTD_ARGS/LIBVIRTD_ARGS/g' /etc/sysconfig/libvirtd
else
echoerror "/etc/sysconfig/libvirtd not found. Exiting..."
exit 1
fi
if [ -f /etc/libvirt/libvirtd.conf ]; then
sed -i 's/#listen_tls/listen_tls/g' /etc/libvirt/libvirtd.conf
sed -i 's/#listen_tcp/listen_tcp/g' /etc/libvirt/libvirtd.conf
sed -i 's/#auth_tcp/auth_tcp/g' /etc/libvirt/libvirtd.conf
else
echoerror "/etc/libvirt/libvirtd.conf not found. Exiting..."
exit 1
sed -i 's/#unix_sock_group = "libvirt"/unix_sock_group = "libvirt"/g' /etc/libvirt/libvirtd.conf
sed -i 's/#unix_sock_rw_perms = "0770"/unix_sock_rw_perms = "0770"/g' /etc/libvirt/libvirtd.conf
sed -i 's/#auth_unix_rw = "polkit"/auth_unix_rw = "none"/g' /etc/libvirt/libvirtd.conf
fi
if [ -f /etc/libvirt/virtqemud.conf ]; then
sed -i 's/#listen_tls/listen_tls/g' /etc/libvirt/virtqemud.conf
sed -i 's/#listen_tcp/listen_tcp/g' /etc/libvirt/virtqemud.conf
sed -i 's/#auth_tcp/auth_tcp/g' /etc/libvirt/virtqemud.conf
sed -i 's/#unix_sock_group = "libvirt"/unix_sock_group = "libvirt"/g' /etc/libvirt/virtqemud.conf
sed -i 's/#unix_sock_rw_perms = "0770"/unix_sock_rw_perms = "0770"/g' /etc/libvirt/virtqemud.conf
sed -i 's/#auth_unix_rw = "polkit"/auth_unix_rw = "none"/g' /etc/libvirt/virtqemud.conf
fi
if [ -f /etc/libvirt/qemu.conf ]; then
sed -i 's/#[ ]*vnc_listen.*/vnc_listen = "0.0.0.0"/g' /etc/libvirt/qemu.conf
sed -i 's/#[ ]*spice_listen.*/spice_listen = "0.0.0.0"/g' /etc/libvirt/qemu.conf
else
echoerror "/etc/libvirt/qemu.conf not found. Exiting..."
exit 1
fi
if [ -f /etc/supervisord.conf ]; then
curl https://raw.githubusercontent.com/retspen/webvirtcloud/master/conf/daemon/gstfsd > /usr/local/bin/gstfsd
chmod +x /usr/local/bin/gstfsd
curl https://raw.githubusercontent.com/retspen/webvirtcloud/master/conf/supervisor/gstfsd.conf > /etc/supervisor.d/gstfsd.ini
else
echoerror "Supervisor not found. Exiting..."
exit 1
mkdir -p /etc/supervisord.d /etc/supervisor/conf.d
if [ -f /etc/supervisord.conf ] || [ -f /etc/supervisor/supervisord.conf ]; then
curl -fsSL https://raw.githubusercontent.com/retspen/webvirtcloud/master/conf/daemon/gstfsd > /usr/local/bin/gstfsd 2>/dev/null || true
chmod +x /usr/local/bin/gstfsd 2>/dev/null || true
curl -fsSL https://raw.githubusercontent.com/retspen/webvirtcloud/master/conf/supervisor/gstfsd.conf > /etc/supervisord.d/gstfsd.ini 2>/dev/null || true
fi
return 0
}
daemons_running_opensuse() {
if [ -f /usr/lib/systemd/system/libvirtd.service ]; then
systemctl stop libvirtd.service > /dev/null 2>&1
systemctl start libvirtd.service
fi
if [ -f /usr/lib/systemd/system/libvirt-guests.service ]; then
systemctl stop libvirt-guests.service > /dev/null 2>&1
systemctl start libvirt-guests.service
fi
if [ -f /usr/lib/systemd/system/supervisord.service ]; then
systemctl stop supervisord.service > /dev/null 2>&1
systemctl start supervisord.service
fi
systemctl enable --now libvirtd.service 2>/dev/null || systemctl enable --now virtqemud.service 2>/dev/null || true
systemctl enable --now libvirt-guests.service 2>/dev/null || true
systemctl enable --now supervisord.service 2>/dev/null || systemctl enable --now supervisor.service 2>/dev/null || true
return 0
}
install_suse() {
install_opensuse "$@"
}
install_suse_post() {
install_opensuse_post "$@"
}
daemons_running_suse() {
daemons_running_opensuse "$@"
}
#
# Ended openSUSE Install Functions
#

View file

@ -169,3 +169,16 @@ Built on Django REST Framework with `drf-nested-routers`, providing a structured
## 7. Architecture Highlights
WebVirtCloud leverages a hybrid pattern: it combines the relational strengths of the **Django ORM** for persistent entities (users, compute host credentials, quotas, and permission delegations) with direct, real-time **libvirt C API bindings** for dynamic virtualization state (VM statuses, resource allocation, storage metrics, and live hardware statistics). This design ensures that the web console always reflects the true hypervisor state without risk of database desynchronization.
---
## 8. Supported Distributions & Platform Matrix
WebVirtCloud installer and runtime dependencies support modern enterprise Linux distributions:
| Distribution Family | Target Versions | Package Manager | Init / Supervisor |
|---|---|---|---|
| **Ubuntu** | 20.04, 22.04 LTS, 24.04 LTS | `apt` | Systemd / Supervisor |
| **Debian** | 10, 11, 12 (Bookworm) | `apt` | Systemd / Supervisor / Runit |
| **RHEL / Rocky / Alma** | 9.x, 10.x | `dnf` | Systemd / Supervisord |
| **openSUSE / SLES** | openSUSE Leap 15.x, Tumbleweed, SLES 15 | `zypper` | Systemd / Supervisord |

28
docker-compose.yml Normal file
View file

@ -0,0 +1,28 @@
services:
webvirtcloud:
build:
context: .
dockerfile: Dockerfile
image: webvirtcloud:latest
container_name: webvirtcloud
restart: unless-stopped
ports:
- "80:80"
- "6080:6080"
environment:
# Optional: custom Django secret key. If unset, automatically generated at first launch.
# - SECRET_KEY=your-custom-secret-key-here
# Optional: comma-separated trusted CSRF origins (e.g. http://wvc.example.com,https://wvc.example.com)
# - CSRF_TRUSTED_ORIGINS=http://localhost,http://127.0.0.1
- TZ=UTC
volumes:
# Persistent SQLite database and runtime data
- webvirtcloud-data:/srv/webvirtcloud/data
# Persistent SSH keys for compute node authentication
- webvirtcloud-ssh:/var/www/.ssh
volumes:
webvirtcloud-data:
driver: local
webvirtcloud-ssh:
driver: local

File diff suppressed because one or more lines are too long

View file

@ -71,7 +71,7 @@ readonly APP_REPO_URL="${APP_REPO_URL:-https://github.com/retspen/webvirtcloud.g
readonly APP_NAME="webvirtcloud"
readonly APP_PATH="/srv/$APP_NAME"
readonly PYTHON="python3"
PYTHON="python3"
progress () {
spin[0]="-"
@ -160,6 +160,16 @@ install_packages () {
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"
fi
done
;;
esac
}
@ -206,12 +216,17 @@ create_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
adduser "$APP_USER"
useradd -m -s /bin/bash "$APP_USER" 2>/dev/null || adduser "$APP_USER"
fi
usermod -a -G "$nginx_group" "$APP_USER"
usermod -a -G libvirt "$nginx_group"
usermod -a -G "$nginx_group" "$APP_USER" 2>/dev/null || true
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 () {
@ -223,26 +238,26 @@ run_as_app_user () {
}
check_python () {
# dynamically find python >= 3.10 if default python3 is older
for py_bin in python3.11 python3.12 python3.13 python3.10 python3; do
if command -v "$py_bin" >/dev/null 2>&1; then
if "$py_bin" -c 'import sys; sys.exit(0 if sys.version_info >= (3, 10) else 1)' >/dev/null 2>&1; then
PYTHON="$py_bin"
break
fi
fi
done
# check if python3 is installed.
if ! hash "$PYTHON" 2>/dev/null; then
echo "Python3 is not installed. Please install Python3 and try again."
exit 1
fi
# check if python3 version is grater than 3.10 amd set it as default
# check if python3 version is greater than 3.10
if ! "$PYTHON" -c 'import sys; assert sys.version_info >= (3, 10)' >/dev/null 2>&1; then
echo "Your Python version is less than 3.10. This script requires Python 3.10 or greater."
echo "Please install Python 3.10 or greater and set it as the default version."
echo "Use update-alternatives command to set default python version to latest."
echo "For example: sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.10 1"
echo "Then run this script again."
echo "Do not forget to install pip3 and python3-devel for python3.10 or later."
exit 1
fi
# check if pip3 is installed
if ! hash pip3 2>/dev/null; then
echo "pip3 is not installed. Please install pip3 and try again."
echo "Please install Python 3.10 or greater (such as python311) and try again."
exit 1
fi
}
@ -270,8 +285,12 @@ generate_secret_key() {
install_webvirtcloud () {
create_user
if [ ! -d "$APP_PATH/.git" ]; then
echo "* Cloning $APP_NAME from github to the web directory."
log "git clone $APP_REPO_URL $APP_PATH"
else
echo "* $APP_NAME already present in $APP_PATH."
fi
echo "* Configuring settings.py file."
cp "$APP_PATH/webvirtcloud/settings.py.template" "$APP_PATH/webvirtcloud/settings.py"
@ -293,9 +312,11 @@ install_webvirtcloud () {
# set CSRF TRUSTED ORIGINS
host_ip="'http://127.0.0.1', "
for i in $(hostname -I); do
if command -v hostname >/dev/null 2>&1; then
for i in $(hostname -I 2>/dev/null); do
host_ip+="'http://$i', "
done
fi
sed -i "s|^\\(CSRF_TRUSTED_ORIGINS = \\).*|\\1\[ \'http://$fqdn\', $host_ip ]|" "$APP_PATH/webvirtcloud/settings.py"
echo "* Checking up Python3 version."
@ -344,7 +365,11 @@ set_selinux () {
set_hosts () {
echo "* Setting up hosts file."
echo >> /etc/hosts "127.0.0.1 $(hostname) $fqdn"
local hname
hname="$(hostname 2>/dev/null || uname -n)"
if ! grep -q "$fqdn" /etc/hosts 2>/dev/null; then
echo >> /etc/hosts "127.0.0.1 $hname $fqdn"
fi
}
restart_supervisor () {
@ -371,6 +396,8 @@ if [ -f /etc/os-release ]; then
version="$(source /etc/os-release && echo "$VERSION_ID")"
# shellcheck disable=SC1091
codename="$(source /etc/os-release && echo "${VERSION_CODENAME:-$UBUNTU_CODENAME}")"
# shellcheck disable=SC1091
id_like="$(source /etc/os-release && echo "${ID_LIKE:-}")"
elif [[ -f /etc/lsb-release || -f /etc/debian_version ]]; then
if command -v lsb_release >/dev/null 2>&1; then
distro="$(lsb_release -is)"
@ -384,6 +411,11 @@ elif [[ -f /etc/lsb-release || -f /etc/debian_version ]]; then
elif [ -f /etc/centos-release ]; then
distro="centos"
version="8"
elif [ -f /etc/SuSE-release ]; then
distro="suse"
version="15"
codename=""
id_like="suse"
else
distro="unsupported"
fi
@ -394,7 +426,7 @@ echo '
'
echo ""
echo " Welcome to Webvirtcloud Installer for RHEL Based OSes, Debian and Ubuntu!"
echo " Welcome to Webvirtcloud Installer for RHEL Based OSes, Debian, Ubuntu, and SUSE!"
echo ""
shopt -s nocasematch
case $distro in
@ -425,6 +457,15 @@ case $distro in
supervisor_conf_path=/etc/supervisord.d
supervisor_file_name=webvirtcloud.ini
;;
*opensuse*|*sles*|*sled*|*suse*)
echo " The installer has detected $distro version $version."
distro=suse
nginx_group=nginx
nginxfile=/etc/nginx/conf.d/$APP_NAME.conf
supervisor_service=supervisord
supervisor_conf_path=/etc/supervisord.d
supervisor_file_name=webvirtcloud.ini
;;
*Uos*|*uos*)
# codename may be fuyu, kongzi, eagle or empty string.
output_expand=""
@ -456,8 +497,18 @@ case $distro in
supervisor_file_name=webvirtcloud.ini
;;
*)
if [[ "$id_like" =~ suse ]]; then
echo " The installer has detected $distro (SUSE family) version $version."
distro=suse
nginx_group=nginx
nginxfile=/etc/nginx/conf.d/$APP_NAME.conf
supervisor_service=supervisord
supervisor_conf_path=/etc/supervisord.d
supervisor_file_name=webvirtcloud.ini
else
echo " The installer was unable to determine your OS. Exiting for safety."
exit 1
fi
;;
esac
@ -684,6 +735,85 @@ case $distro in
restart_nginx
fi
;;
suse)
# Install for openSUSE Leap 15.x / Tumbleweed / SLES 15
tzone=\'$(get_timezone)\'
echo -n "* Updating installed packages."
log "zypper --non-interactive refresh" & pid=$!
progress
echo "* Installing OS requirements."
# On SUSE, install Python 3.11 stack to satisfy Python >= 3.10 requirement along with native libvirt and ldap bindings
if zypper se -s python311-devel >/dev/null 2>&1; then
PACKAGES="git hostname python311 python311-base python311-devel python311-pip python311-libvirt-python python311-lxml python311-ldap libvirt-devel cyrus-sasl-devel libopenssl-devel gcc pkg-config nginx"
else
PACKAGES="git hostname python3-devel python3-pip python3-virtualenv libvirt-devel python3-libvirt python3-lxml openldap2-devel cyrus-sasl-devel libopenssl-devel gcc pkg-config nginx"
fi
install_packages
set_hosts
# Supervisor on SUSE (available as python3-supervisor, supervisor, or via pip)
if ! command -v supervisord >/dev/null 2>&1; then
if zypper --non-interactive install -y python3-supervisor >/dev/null 2>&1; then
echo " * python3-supervisor installed via zypper"
elif zypper --non-interactive install -y supervisor >/dev/null 2>&1; then
echo " * supervisor installed via zypper"
else
echo " * Installing supervisor via pip3"
log "pip3 install supervisor"
fi
fi
# Ensure /etc/supervisord.d directory exists and is included in supervisord.conf
mkdir -p /etc/supervisord.d
if [ ! -f /etc/supervisord.conf ] && [ ! -f /etc/supervisor/supervisord.conf ]; then
if command -v echo_supervisord_conf >/dev/null 2>&1; then
echo_supervisord_conf > /etc/supervisord.conf
echo -e "\n[include]\nfiles = /etc/supervisord.d/*.ini\n" >> /etc/supervisord.conf
fi
elif [ -f /etc/supervisord.conf ] && ! grep -q "/etc/supervisord.d" /etc/supervisord.conf; then
echo -e "\n[include]\nfiles = /etc/supervisord.d/*.ini\n" >> /etc/supervisord.conf
fi
# Ensure systemd service for supervisord exists if installed via pip
if [ ! -f /usr/lib/systemd/system/supervisord.service ] && [ ! -f /etc/systemd/system/supervisord.service ]; then
supervisord_bin="$(command -v supervisord 2>/dev/null || echo "/usr/local/bin/supervisord")"
supervisorctl_bin="$(command -v supervisorctl 2>/dev/null || echo "/usr/local/bin/supervisorctl")"
cat > /etc/systemd/system/supervisord.service <<SVCEOF
[Unit]
Description=Process Monitoring and Control Daemon
After=network.target
[Service]
Type=forking
ExecStart=${supervisord_bin} -c /etc/supervisord.conf
ExecStop=${supervisorctl_bin} shutdown
ExecReload=${supervisorctl_bin} reload
KillMode=process
Restart=on-failure
RestartSec=42s
[Install]
WantedBy=multi-user.target
SVCEOF
systemctl daemon-reload >/dev/null 2>&1 || true
fi
install_webvirtcloud
echo "* Configuring Nginx."
configure_nginx
echo "* Configuring Supervisor."
configure_supervisor
set_firewall
restart_supervisor
restart_nginx
;;
esac

View file

@ -17,7 +17,14 @@ from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = ""
SECRET_KEY = os.getenv("SECRET_KEY", "")
if not SECRET_KEY:
secret_file = BASE_DIR / "data" / "secret_key"
if secret_file.exists():
try:
SECRET_KEY = secret_file.read_text().strip()
except Exception:
pass
DEBUG = False
@ -206,7 +213,7 @@ WS_PUBLIC_PORT = 6080
WS_PUBLIC_HOST = None
# Websock public path
WS_PUBLIC_PATH = "/novncd/"
WS_PUBLIC_PATH = "novncd/"
# Websock Certificate for SSL
WS_CERT = None