OpenWebUI中,语音输入需要HTTPS才能使用麦克风等硬件资源,在局域网中通过NGINX转发实现HTTPS访问。
具体包含三个部分
- 容器部署open-webui和nginx
- 生成ssl证书
- 修改nginx配置文件
1、容器部署
基于docker-compose
,执行docker-compose up -d
即可。
其中主要的就是映射conf文件夹和ssl的路径,不映射手动添加也一样。open-webui的环境变量就看个人需求了。
yaml
services:
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
restart: always
networks:
- openwebui
nginx:
image: nginx
container_name: nginx
restart: always
volumes:
- /nginx/conf.d:/etc/nginx/conf.d
- /nginx/ssl:/ssl
ports:
- "38080:8080"
networks:
- openwebui
# 为空即可
networks:
openwebui:
2、生成ssl证书
这网上教程很多,基本一个命令就搞定,这里找GPT写了一个基于Flask的页面,方便访问(问就是因为懒得敲命令 )。具体代码放最后,有需要的可以复制。生成的文件放到上面配置的ssl文件夹里。
3.修改conf
在conf.d
文件夹下新建default.conf
,写入内容如下,修改server_name
以及ssl
相关文件。
由于配置了network,localtion中访问地址可以直接用docker-compose中的app名字。
重启nginx容器即可访问。
shell
server {
listen 8080 ssl;
server_name yourdomain.com;
# 配置crt
ssl_certificate "/ssl/xxx.crt";
# 配置key
ssl_certificate_key "/ssl/xxx.key";
location /{
proxy_pass http://open-webui:8080;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_redirect default;
}
}
附.生成ssl证书代码
pip install cryptography
python
import os
from flask import Flask, jsonify, send_from_directory, request, render_template_string
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
from datetime import datetime, timedelta
app = Flask(__name__)
# SSL 证书文件夹
SSL_FOLDER = "ssl"
if not os.path.exists(SSL_FOLDER):
os.makedirs(SSL_FOLDER)
# 生成 SSL 证书
def generate_ssl_cert(domain_name):
key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
subject = issuer = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"California"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"San Francisco"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"My Organization"),
x509.NameAttribute(NameOID.COMMON_NAME, domain_name),
])
cert = x509.CertificateBuilder().subject_name(
subject
).issuer_name(
issuer
).public_key(
key.public_key()
).serial_number(
x509.random_serial_number()
).not_valid_before(
datetime.utcnow()
).not_valid_after(
datetime.utcnow() + timedelta(days=365)
).add_extension(
x509.SubjectAlternativeName([x509.DNSName(domain_name)]),
critical=False,
).sign(key, hashes.SHA256(), default_backend())
cert_filename = f"{domain_name}.crt"
key_filename = f"{domain_name}.key"
cert_path = os.path.join(SSL_FOLDER, cert_filename)
key_path = os.path.join(SSL_FOLDER, key_filename)
with open(key_path, "wb") as f:
f.write(key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption()
))
with open(cert_path, "wb") as f:
f.write(cert.public_bytes(serialization.Encoding.PEM))
return cert_filename, key_filename
# 首页接口,显示 HTML 页面
@app.route("/", methods=["GET"])
def index():
return render_template_string('''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SSL Certificate Generator</title>
<script>
async function generateCert() {
const domainName = document.getElementById('domain_name').value;
const response = await fetch('/generate_cert', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ domain_name: domainName })
});
if (response.ok) {
const result = await response.json();
document.getElementById('cert_link').href = `/download_cert/${result.certificate}`;
document.getElementById('cert_link').innerText = result.certificate;
document.getElementById('key_link').href = `/download_cert/${result.key}`;
document.getElementById('key_link').innerText = result.key;
} else {
alert('Error generating certificate');
}
}
</script>
</head>
<body>
<h1>SSL Certificate Generator</h1>
<form οnsubmit="event.preventDefault(); generateCert();">
<label for="domain_name">Domain Name:</label>
<input type="text" id="domain_name" name="domain_name" required>
<button type="submit">Generate Certificate</button>
</form>
<h2>Generated Files</h2>
<p>Certificate: <a id="cert_link" href="#" target="_blank">No certificate generated</a></p>
<p>Private Key: <a id="key_link" href="#" target="_blank">No key generated</a></p>
</body>
</html>
''')
# 生成证书接口
@app.route("/generate_cert", methods=["POST"])
def generate_cert():
domain_name = request.form.get("domain_name")
if not domain_name:
return jsonify({"error": "Domain name is required"}), 400
cert_filename, key_filename = generate_ssl_cert(domain_name)
return jsonify({"certificate": cert_filename, "key": key_filename})
# 下载证书接口
@app.route("/download_cert/<filename>", methods=["GET"])
def download_cert(filename):
return send_from_directory(SSL_FOLDER, filename)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)