1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 11:16:47 +00:00
webvirtcloud/webvirtcloud/settings.py.template
catborise 0977aef587 feat(nodedevs): the host's PCI and USB devices on a Devices page
- A Devices section of each host lists its PCI devices by IOMMU group and
  its USB devices: what each is (vendor, product, IDs, class, driver), what
  the host itself has on it (its network interfaces, disks and graphics
  card, anywhere below it) and the VMs whose definitions name it.
- A VM names a PCI device by its address, a USB device by its address or
  by its model only; a VM that names a model only is shown on every device
  of that model, which one it takes is known only when it starts.
- A host without IOMMU groups says that no PCI device can be given to a VM
  and how to turn the IOMMU on.
- One listing of the host's node devices, one XML read each (a device gone
  meanwhile is left out), and the VMs' definitions read once; nothing is
  shown when they cannot be read, as a device would look free.
- Read only: giving devices to VMs comes in the next layers.
2026-10-29 13:01:17 +03:00

420 lines
13 KiB
Text

"""
Django settings for webvirtcloud project.
"""
import os
import sys
try:
import ldap
from django_auth_ldap.config import LDAPSearch, NestedActiveDirectoryGroupType
HAS_LDAP = True
except ImportError:
HAS_LDAP = False
from django.utils.translation import gettext_lazy as _
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
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
MAC_OUI = '52:54:10'
# Host names the panel answers to. The installer writes the server's; add more
# (comma-separated, "*" for any) through the environment.
ALLOWED_HOSTS = ["localhost", "127.0.0.1", "[::1]"]
if extra_hosts := os.getenv("ALLOWED_HOSTS"):
ALLOWED_HOSTS.extend([host.strip() for host in extra_hosts.split(",") if host.strip()])
CSRF_TRUSTED_ORIGINS = ["http://localhost", "http://127.0.0.1", "http://localhost:8000", "http://127.0.0.1:8000"]
if extra_origins := os.getenv("CSRF_TRUSTED_ORIGINS"):
CSRF_TRUSTED_ORIGINS.extend([origin.strip() for origin in extra_origins.split(",") if origin.strip()])
# Honor X-Forwarded-Proto for request.is_secure(). gunicorn listens on
# 127.0.0.1 only, so the bundled nginx is the one client that sets it.
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
# Production profile for a panel served over HTTPS: secure cookies, HTTP to
# HTTPS redirect and HSTS. Off by default (plain HTTP).
if os.getenv("WEBVIRTCLOUD_HTTPS") == "1":
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_SSL_REDIRECT = True
# VMs fetch cloud-init metadata over plain HTTP
SECURE_REDIRECT_EXEMPT = [r"^datasource/"]
SECURE_HSTS_SECONDS = 31536000
# Application definition
INSTALLED_APPS = [
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"django_bootstrap5",
"django_bootstrap_icons",
"django_otp",
"django_otp.plugins.otp_totp",
"drf_spectacular",
"drf_spectacular_sidecar",
"accounts",
"admin",
"appsettings",
"computes",
"console",
"datasource",
"networks",
"instances",
"interfaces",
"nwfilters",
"nodedevs",
"storages",
"virtsecrets",
"logs",
"operations",
"rest_framework",
"knox",
"axes",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.locale.LocaleMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django_otp.middleware.OTPMiddleware",
"django.contrib.auth.middleware.LoginRequiredMiddleware",
"accounts.middleware.ForcePasswordChangeMiddleware",
"django.contrib.auth.middleware.RemoteUserMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"webvirtcloud.middleware.ContentSecurityPolicyMiddleware",
"appsettings.middleware.AppSettingsMiddleware",
"webvirtcloud.middleware.ExceptionMiddleware",
# last, as django-axes requires
"axes.middleware.AxesMiddleware",
]
ROOT_URLCONF = "webvirtcloud.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [
Path.joinpath(BASE_DIR, "templates"),
],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
"appsettings.context_processors.app_settings",
],
"libraries": {
"common_tags": "webvirtcloud.common_tags",
},
},
},
]
WSGI_APPLICATION = "webvirtcloud.wsgi.application"
# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
# sqlite by default; PostgreSQL when WEBVIRTCLOUD_DB_HOST is set (a host name,
# an IP, or a unix socket directory).
if db_host := os.getenv("WEBVIRTCLOUD_DB_HOST"):
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"HOST": db_host,
"PORT": os.getenv("WEBVIRTCLOUD_DB_PORT", "5432"),
"NAME": os.getenv("WEBVIRTCLOUD_DB_NAME", "webvirtcloud"),
"USER": os.getenv("WEBVIRTCLOUD_DB_USER", "webvirtcloud"),
"PASSWORD": os.getenv("WEBVIRTCLOUD_DB_PASSWORD", ""),
}
}
else:
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": Path.joinpath(BASE_DIR, "db.sqlite3"),
# IMMEDIATE takes the write lock when a transaction starts, so a
# check-then-write cannot interleave with another process; timeout
# is how long a writer waits for that lock (seconds). WAL is set
# per connection by the operations app.
"OPTIONS": {"transaction_mode": "IMMEDIATE", "timeout": 20},
# A file, not shared-cache memory: test threads and worker processes
# open the same database, and shared-cache locks ignore the timeout.
"TEST": {"NAME": os.getenv("WEBVIRTCLOUD_TEST_DB") or Path.joinpath(BASE_DIR, "test_db.sqlite3")},
}
}
AUTHENTICATION_BACKENDS = [
# first: refuses logins that are locked out
"axes.backends.AxesStandaloneBackend",
"django.contrib.auth.backends.ModelBackend",
#"django_auth_ldap.backend.LDAPBackend",
]
LOGIN_URL = "/accounts/login/"
# Tests create users by the thousand: a fast hash for them (manage.py test only)
if sys.argv[1:2] == ["test"]:
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
# manage.py test --parallel on SQLite (WAL)
TEST_RUNNER = "webvirtcloud.test_runner.TestRunner"
# Failed logins (django-axes): a user is locked for the address the logins
# come from. The limit and the lock time are on the Settings page
# (LOGIN_FAILURE_LIMIT, LOGIN_LOCKOUT_MINUTES); unlock with
# "manage.py axes_reset_username <username>".
AXES_FAILURE_LIMIT = "accounts.lockout.failure_limit"
AXES_COOLOFF_TIME = "accounts.lockout.cooloff_time"
AXES_LOCKOUT_PARAMETERS = [["username", "ip_address"]]
AXES_RESET_ON_SUCCESS = True
AXES_CLIENT_IP_CALLABLE = "accounts.lockout.client_ip"
# Reverse proxies other than a local one whose X-Real-IP header gives the
# client address, e.g. ["10.0.0.5", "172.18.0.0/16"]
LOGIN_TRUSTED_PROXIES = []
LOGOUT_REDIRECT_URL = "/accounts/login/"
LOGIN_REDIRECT_URL="/instances/"
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_TZ = True
STATIC_URL = "/static/"
# Source assets live in static/; collectstatic gathers them with the apps'
# assets into staticfiles/ (gitignored), which WhiteNoise and nginx serve.
STATICFILES_DIRS = [BASE_DIR / "static"]
STATIC_ROOT = BASE_DIR / "staticfiles"
BS_ICONS_CACHE = Path.joinpath(BASE_DIR, 'static/icon_cache')
# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
LOCALE_PATHS = [
"locale/",
]
# Resolve the application log path and make sure its directory exists, so a
# clean checkout, CI, or a Docker build (none of which create data/) does not
# fail when logging is configured.
WEBVIRTCLOUD_LOG_FILE = os.getenv("WEBVIRTCLOUD_LOG_FILE", str(BASE_DIR / "data" / "webvirtcloud.log"))
os.makedirs(os.path.dirname(WEBVIRTCLOUD_LOG_FILE), exist_ok=True)
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': '[%(asctime)s] %(levelname)s: %(message)s'
},
},
'handlers': {
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler'
},
'default': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': WEBVIRTCLOUD_LOG_FILE,
'maxBytes': 10 * 1024 * 1024,
'backupCount': 5,
'formatter': 'standard',
},
},
'loggers': {
'': {
'handlers': ['default'],
'level': 'INFO',
'propagate': True
},
'django.request': {
"handlers": ["mail_admins"],
"level": "ERROR",
"propagate": True,
}
},
}
#
# WebVirtCloud settings
#
# The VNC console proxy (novncd). Browsers reach it through nginx's /novncd/
# on the panel's own address, so it listens on the loopback only.
WS_PORT = 6080
WS_HOST = "127.0.0.1"
# Serial console (xterm.js over socketiod) for VMs without graphics. socketiod
# accepts only the console page's signed token (as noVNC does); while this is
# False it refuses every connection. Restart socketiod after changing it.
SERIAL_CONSOLE_ENABLED = os.getenv("WEBVIRTCLOUD_SERIAL_CONSOLE") == "1"
SOCKETIO_PORT = 6081
# Listen on localhost only; the console page reaches socketiod through the
# nginx /socket.io/ location.
SOCKETIO_HOST = "127.0.0.1"
# List of console listen addresses
QEMU_CONSOLE_LISTENER_ADDRESSES = (
("127.0.0.1", _("Localhost")),
("0.0.0.0", _("All interfaces")),
)
# List taken from http://qemu.weilnetz.de/qemu-doc.html#sec_005finvocation
QEMU_KEYMAPS = [
"ar",
"da",
"de",
"de-ch",
"en-gb",
"en-us",
"es",
"et",
"fi",
"fo",
"fr",
"fr-be",
"fr-ca",
"fr-ch",
"hr",
"hu",
"is",
"it",
"ja",
"lt",
"lv",
"mk",
"nl",
"nl-be",
"no",
"pl",
"pt",
"pt-br",
"ru",
"sl",
"sv",
"th",
"tr",
]
# Keepalive interval and count for libvirt connections
LIBVIRT_KEEPALIVE_INTERVAL = 5
LIBVIRT_KEEPALIVE_COUNT = 5
ALLOW_EMPTY_PASSWORD = False
NEW_USER_DEFAULT_INSTANCES = []
# Days the owners of a VM that vanished from its compute are kept, in case it comes back
INSTANCE_OWNERSHIP_RETENTION_DAYS = 30
# OTP Config
OTP_ENABLED = False
# REST Framework & OpenAPI (drf-spectacular)
REST_FRAMEWORK = {
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
# An API token, or a session: a password alone (HTTP Basic) must not reach
# the API, as it would skip the OTP second factor the login form enforces;
# tokens are made in a session. The token class comes first, so a refused
# request gets 401 with its challenge, not 403.
"DEFAULT_AUTHENTICATION_CLASSES": [
"accounts.auth.TokenAuthentication",
"rest_framework.authentication.SessionAuthentication",
],
# A view that forgets its own permission_classes is not public.
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticated",
],
}
# API tokens (django-rest-knox): kept as SHA-512 digests, sent as
# "Authorization: Token wvc_..."; made and deleted on the profile page
KNOX_TOKEN_MODEL = "accounts.ApiToken"
REST_KNOX = {"TOKEN_PREFIX": "wvc_"}
# knox stays installed for its token code, but its migrations would create a
# knox_authtoken table that accounts.ApiToken replaces
MIGRATION_MODULES = {"knox": None}
SPECTACULAR_SETTINGS = {
"TITLE": "Webvirtcloud REST-API",
"DESCRIPTION": "Webvirtcloud REST API",
"VERSION": "v1",
"SERVE_INCLUDE_SCHEMA": False,
"SERVE_PERMISSIONS": ["rest_framework.permissions.IsAuthenticated"],
"SWAGGER_UI_DIST": "SIDECAR",
"SWAGGER_UI_FAVICON_HREF": "SIDECAR",
"REDOC_DIST": "SIDECAR",
}
# EMAIL Config (SMTP; override through the environment)
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = os.getenv("EMAIL_HOST", "smtp.gmail.com")
EMAIL_USE_TLS = os.getenv("EMAIL_USE_TLS", "1") == "1"
EMAIL_PORT = int(os.getenv("EMAIL_PORT", "587"))
## sender's email-id
EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER", "")
## password associated with above email-id
EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD", "")
# LDAP Config
#
if HAS_LDAP:
AUTH_LDAP_SERVER_URI = "ldap://example.com"
AUTH_LDAP_BIND_DN = "username@example.com"
AUTH_LDAP_BIND_PASSWORD = "password"
AUTH_LDAP_USER_SEARCH = LDAPSearch(
"CN=Users,DC=example,DC=com", ldap.SCOPE_SUBTREE, "(sAMAccountName=%(user)s)"
)
AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
"CN=Users,DC=example,DC=com", ldap.SCOPE_SUBTREE, "(objectClass=group)"
)
AUTH_LDAP_GROUP_TYPE = NestedActiveDirectoryGroupType()
AUTH_LDAP_REQUIRE_GROUP = "CN=WebVirtCloud Access,CN=Users,DC=example,DC=com"
AUTH_LDAP_USER_FLAGS_BY_GROUP = {
"is_staff": "CN=WebVirtCloud Staff,CN=Users,DC=example,DC=com",
"is_superuser": "CN=WebVirtCloud Admins,CN=Users,DC=example,DC=com",
}
AUTH_LDAP_USER_ATTR_MAP = {
"first_name": "givenName",
"last_name": "sn",
"email": "mail",
}