diff --git a/README.md b/README.md index 2297afef..e077b19a 100644 --- a/README.md +++ b/README.md @@ -400,6 +400,23 @@ datasource: The serial (xterm.js) console is unavailable until `console/socketiod` is rewritten with authentication. Existing installs: set `autostart=false` for `[program:socketiod]`, comment out `location /socket.io/` in nginx, and set `SOCKETIO_HOST = "127.0.0.1"` in `settings.py`. +### Configuration from the Environment + +Docker passes the `environment:` of `docker-compose.yml` and +`docker-compose.override.yml` to every process in the container. On bare metal, either edit `webvirtcloud/settings.py`, or set the +same `environment=` line in both `[program:webvirtcloud]` and `[program:novncd]` +of the supervisor config (novncd reads the database too) and export the +variables before running `manage.py` (e.g. `migrate`): + +| Variable | Effect | +| --- | --- | +| `ALLOWED_HOSTS` | extra host names/IPs (comma-separated, `*` for any) | +| `CSRF_TRUSTED_ORIGINS` | extra trusted origins, e.g. `https://wvc.example.com` | +| `WEBVIRTCLOUD_HTTPS=1` | HTTPS profile (secure cookies, redirect, HSTS) | +| `WEBVIRTCLOUD_DB_HOST` | use PostgreSQL; `WEBVIRTCLOUD_DB_PORT`/`_NAME`/`_USER`/`_PASSWORD` (defaults 5432/webvirtcloud/webvirtcloud/empty) | +| `EMAIL_HOST`, `EMAIL_PORT`, `EMAIL_HOST_USER`, `EMAIL_HOST_PASSWORD`, `EMAIL_USE_TLS` | SMTP for e-mail OTP | +| `WEBVIRTCLOUD_LOG_FILE` | log file path (default `data/webvirtcloud.log`) | + ### Reverse-Proxy & Port Forwarding If WebVirtCloud runs behind a reverse proxy terminating SSL or forwarding port 80/443, set the public noVNC port in `webvirtcloud/settings.py` (default 6080). The bundled nginx config already proxies `/novncd/`: @@ -471,6 +488,10 @@ for any other host get 400. Bare metal (your settings.py is kept): replace reached by, and copy the `if extra_hosts := ...` lines below it from the template so the env var works. +Environment-driven database and email settings: copy the `DATABASES` and +`EMAIL` blocks of the template into your `webvirtcloud/settings.py` to use them. +PostgreSQL is for new installs; moving an existing sqlite database is not covered. + ```bash # Go to Installation Directory cd /srv/webvirtcloud diff --git a/conf/requirements.txt b/conf/requirements.txt index f6cca4c7..0176a466 100644 --- a/conf/requirements.txt +++ b/conf/requirements.txt @@ -14,7 +14,7 @@ libsass==0.23.0 libvirt-python>=9.0.0 lxml>=6.1 paramiko==5.0.0 -#psycopg2-binary +psycopg[binary]==3.3.6 python-engineio==4.14.0 python-socketio==5.17.0 websockify==0.13.0 diff --git a/docker-compose.yml b/docker-compose.yml index 9e6544fb..f8db10ab 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,6 +14,13 @@ services: # - 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 + # Optional: PostgreSQL instead of the bundled sqlite (also WEBVIRTCLOUD_DB_PORT/_NAME/_USER) + # - WEBVIRTCLOUD_DB_HOST=db.example.com + # - WEBVIRTCLOUD_DB_PASSWORD=change-me + # Optional: SMTP for e-mail OTP (also EMAIL_PORT, EMAIL_USE_TLS=0/1) + # - EMAIL_HOST=smtp.example.com + # - EMAIL_HOST_USER=webvirtcloud@example.com + # - EMAIL_HOST_PASSWORD=change-me # Host names/IPs the panel is reached by (comma-separated; "*" accepts any). # Without it only localhost works. Set it in docker-compose.override.yml. - ALLOWED_HOSTS=localhost diff --git a/webvirtcloud/settings.py.template b/webvirtcloud/settings.py.template index e8632cad..ed836954 100644 --- a/webvirtcloud/settings.py.template +++ b/webvirtcloud/settings.py.template @@ -134,12 +134,26 @@ WSGI_APPLICATION = "webvirtcloud.wsgi.application" # Database # https://docs.djangoproject.com/en/3.0/ref/settings/#databases -DATABASES = { - "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": Path.joinpath(BASE_DIR, "db.sqlite3"), +# 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"), + } } -} AUTHENTICATION_BACKENDS = [ # first: refuses logins that are locked out @@ -355,15 +369,15 @@ SPECTACULAR_SETTINGS = { "REDOC_DIST": "SIDECAR", } -# EMAIL Config +# EMAIL Config (SMTP; override through the environment) EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' -EMAIL_HOST = 'smtp.gmail.com' -EMAIL_USE_TLS = True -EMAIL_PORT = 587 +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 = '' +EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER", "") ## password associated with above email-id -EMAIL_HOST_PASSWORD = '' +EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD", "") # LDAP Config # diff --git a/webvirtcloud/test_settings_env.py b/webvirtcloud/test_settings_env.py index 584dff66..571a94de 100644 --- a/webvirtcloud/test_settings_env.py +++ b/webvirtcloud/test_settings_env.py @@ -11,7 +11,12 @@ from django.test import SimpleTestCase, TestCase, override_settings # Env vars that change the settings; removed from the inherited environment so # every case starts from the defaults. -CONFIG_VARS = ["WEBVIRTCLOUD_HTTPS", "ALLOWED_HOSTS"] +CONFIG_VARS = [ + "WEBVIRTCLOUD_HTTPS", "ALLOWED_HOSTS", + "EMAIL_HOST", "EMAIL_PORT", "EMAIL_HOST_USER", "EMAIL_HOST_PASSWORD", "EMAIL_USE_TLS", + "WEBVIRTCLOUD_DB_HOST", "WEBVIRTCLOUD_DB_PORT", "WEBVIRTCLOUD_DB_NAME", + "WEBVIRTCLOUD_DB_USER", "WEBVIRTCLOUD_DB_PASSWORD", +] def _env(env): @@ -85,3 +90,42 @@ class AllowedHostsTestCase(SimpleTestCase): def test_allowed_hosts_star_escape(self): self.assertIn("*", self.hosts({"ALLOWED_HOSTS": "*"})) + + +class EmailEnvTestCase(SimpleTestCase): + def test_email_from_env(self): + names = ["EMAIL_HOST", "EMAIL_PORT", "EMAIL_HOST_USER", "EMAIL_HOST_PASSWORD", "EMAIL_USE_TLS"] + env = {"EMAIL_HOST": "smtp.example.com", "EMAIL_PORT": "25", "EMAIL_HOST_USER": "wvc", + "EMAIL_HOST_PASSWORD": "secret", "EMAIL_USE_TLS": "0"} + self.assertEqual(load_settings(env, names), { + "EMAIL_HOST": "smtp.example.com", "EMAIL_PORT": 25, "EMAIL_HOST_USER": "wvc", + "EMAIL_HOST_PASSWORD": "secret", "EMAIL_USE_TLS": False, + }) + + +class DatabaseEnvTestCase(SimpleTestCase): + def db(self, env): + return load_settings(env, ["DATABASES"])["DATABASES"]["default"] + + def test_sqlite_is_the_default(self): + db = self.db({}) + self.assertEqual(db["ENGINE"], "django.db.backends.sqlite3") + self.assertTrue(db["NAME"].endswith("db.sqlite3")) + + def test_postgres_from_env(self): + db = self.db({"WEBVIRTCLOUD_DB_HOST": "db.example.com", "WEBVIRTCLOUD_DB_PORT": "6432", + "WEBVIRTCLOUD_DB_NAME": "wvc", "WEBVIRTCLOUD_DB_USER": "wvcuser", + "WEBVIRTCLOUD_DB_PASSWORD": "secret"}) + self.assertEqual( + {k: db[k] for k in ("ENGINE", "HOST", "PORT", "NAME", "USER", "PASSWORD")}, + {"ENGINE": "django.db.backends.postgresql", "HOST": "db.example.com", "PORT": "6432", + "NAME": "wvc", "USER": "wvcuser", "PASSWORD": "secret"}, + ) + + def test_postgres_defaults(self): + db = self.db({"WEBVIRTCLOUD_DB_HOST": "db.example.com"}) + self.assertEqual( + {k: db[k] for k in ("ENGINE", "PORT", "NAME", "USER", "PASSWORD")}, + {"ENGINE": "django.db.backends.postgresql", "PORT": "5432", + "NAME": "webvirtcloud", "USER": "webvirtcloud", "PASSWORD": ""}, + )