【LLM】局域网内为容器服务启用HTTPS

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)
相关推荐
我爱学Python!20 小时前
大模型教程:使用 Milvus、vLLM 和 Llama 3.1 搭建 RAG 应用
自然语言处理·大模型·llm·大语言模型·llama·milvus·rag
CodeDevMaster1 天前
Xorbits Inference(Xinference):一款性能强大且功能全面的大模型部署与分布式推理框架
语言模型·llm·部署·推理框架·xinference
Hoper.J2 天前
3. 进阶指南:自定义 Prompt 提升大模型解题能力
语言模型·llm·prompt·aigc
强哥之神2 天前
谈谈LLM训练中的“过拟合”与“欠拟合”
人工智能·深度学习·神经网络·机器学习·llm·过拟合·overfitting
强哥之神2 天前
白话:大型语言模型中的幻觉(Hallucinations)
人工智能·机器学习·语言模型·自然语言处理·llm·llama·hallucinations
CodeDevMaster3 天前
基于基础模型BERT与大语言模型ChatGLM3-6B实现文本分类、信息抽取、文本匹配等任务
语言模型·大模型·llm·bert·chatglm3-6b·基础模型
强哥之神4 天前
一文读懂:如何将广告融入大型语言模型(LLM)输出
人工智能·机器学习·语言模型·llm·机器翻译引擎·广告投递
人工智能MOS5 天前
Qwen1.5模型文本分类微调实战教程
人工智能·计算机视觉·数据挖掘·大模型·llm·微调·llama
AI大模型_学习君5 天前
百度大模型构建智能问答系统技术实践
人工智能·pytorch·深度学习·llm·ai大模型·大模型训练·百度大模型
loong_XL5 天前
qwen2 VL 多模态图文模型;图像、视频使用案例
大模型·llm·aigc·多模态