PVE

PVE的备份和恢复

进入pve管理后台,在各虚拟机的备份选项点击备份

备份文件路径是Var/lib/vz/dump,下载到其他地方保持

需要恢复时将备份文件上传到Var/lib/vz/dump,在存储 local里点击对应的备份文件进行还原,然后启动虚拟机

!!!如果启动虚拟机时提示找不到iso镜像,找到硬件选项,编辑CD/DVD驱动器重新选择一下iso镜像文件

pve直通amd核显没法玩

直接装fedora,然后用fedora的虚拟机装pve

Pve虚拟铭凡nas os

第一步,获取序列号

铭凡nas os会检查bios的序列号对不对,不对的话是无法启动的,所以我们需要给虚拟机注入正确的序列号

进入Pve的命令终端

1
2
dmidecode -s system-serial-number
xxxxxxxxxxx

这一串就是我们的序列号了

第二步,新建一个虚拟机

不要任何的系统,选q35和efi。创建完成后把虚拟机硬件的全部硬盘分离并移除

然后继续回到命令行终端

1
2
nano /etc/pve/qemu-server/xxx.conf
# 虚拟机是xxx号,这里就是xxx.conf
1
2
3
agent: 1
args: -smbios type=1,serial=xxxxxxxxxxx
bios: ovmf

在bios: ovmf上面添加一行,xxxxxxx就是第一步获得的序列号

第三步,下载铭凡nas os和img2kvm并上传到Pve的root目录

注意,上传铭凡nas os之前,需要把它解压成img格式,再压缩成gz格式上传

因为img2kvm只支持gz,我也不想去改源码,就这么用吧

回到Pve的命令终端

1
chmod +x ./img2kvm
1
2
./img2kvm nasos-2.1.10.0002.img.gz xxx
xxx是虚拟机号,比如我的是106,那就是./img2kvm nasos-2.1.10.0002.img.gz 106

第四步,新建一个虚拟硬盘给虚拟机

没有第二块硬盘是无法创建存储池进入系统的,所以需要创建一块硬盘

在硬件—>添加—>硬盘 大小自定义,其他默认

如果显示的是未使用的磁盘,双击然后点添加

第五步,开机,用铭凡的app连接虚拟机初始化

在这一过程,虚拟机可能会自动关机,这是正常,只需要再次启动即可

其他问题

如果创建存储池一直错误,就把原来创建的硬盘分离并移除,然后重新建一块硬盘

铭凡nas os

img2kvm

逆向api通过外挂解锁特殊功能-----二

之前的文章 逆向api通过外挂解锁特殊功能 | leioukupo的博客
直接构建一个带tool_calls的completion发送没太大问题,但是不能流式
后续写流式tool调用时一直不行,直到和ai一起看cherry studio的源码关于tool调用这一部分时
才发现我一直套用的chat的流式生成函数,其中的finish_reason一直是None,所以各个客户端才不响应我的工具请求,让ai根据我之前的chat流式生成器写了一个tool流式生成器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
async def generate_tool_call_chunks(
tool_calls: list,
answer_id: str,
created: int,
model: str,
system_fingerprint: Optional[str] = None,
finish_reason: str = "tool_calls"
):
"""
异步生成器,模拟流式发送工具调用数据
Args:
tool_calls: 工具调用列表,每个元素应包含:
{
"id": "call_xxx",
"function": {
"name": "function_name",
"arguments": '{"param1": "value1"}' # JSON字符串
}
}
answer_id: 回答ID
created: 创建时间戳
model: 模型名称
system_fingerprint: 系统指纹
finish_reason: 完成原因,默认为 "tool_calls"
"""
# 第一步:流式发送每个工具调用的初始信息和参数
for idx, tool_call in enumerate(tool_calls):
tool_id = tool_call.get("id", f"call_{idx}")
function_name = tool_call["function"]["name"]
arguments_str = tool_call["function"]["arguments"]

# 如果 arguments 是 dict,转换为 JSON 字符串
if isinstance(arguments_str, dict):
arguments_str = json.dumps(arguments_str, ensure_ascii=False)

# 发送工具调用的开始 chunk(包含 id 和 function name)
chunk_start = {
"id": answer_id,
"object": "chat.completion.chunk",
"created": created,
"model": model,
"system_fingerprint": system_fingerprint,
"choices": [{
"index": 0,
"delta": {
"tool_calls": [{
"index": idx,
"id": tool_id,
"type": "function",
"function": {
"name": function_name,
"arguments": "" # 开始为空
}
}]
},
"logprobs": None,
"finish_reason": None
}]
}
yield f"data: {json.dumps(chunk_start, ensure_ascii=False)}\n\n"
await asyncio.sleep(random.uniform(0.01, 0.03))

# 第二步:逐个字符流式发送参数
for char in arguments_str:
chunk_arg = {
"id": answer_id,
"object": "chat.completion.chunk",
"created": created,
"model": model,
"system_fingerprint": system_fingerprint,
"choices": [{
"index": 0,
"delta": {
"tool_calls": [{
"index": idx,
"function": {
"arguments": char
}
}]
},
"logprobs": None,
"finish_reason": None
}]
}
yield f"data: {json.dumps(chunk_arg, ensure_ascii=False)}\n\n"
await asyncio.sleep(random.uniform(0.005, 0.015))

# 第三步:发送 finish_reason 为 tool_calls 的完成 chunk
completion_data = {
"id": answer_id,
"object": "chat.completion.chunk",
"created": created,
"model": model,
"system_fingerprint": system_fingerprint,
"choices": [{
"index": 0,
"delta": {},
"logprobs": None,
"finish_reason": finish_reason # "tool_calls"
}]
}
yield f"data: {json.dumps(completion_data, ensure_ascii=False)}\n\n"

# 第四步:发送流结束标记
yield "data: [DONE]\n\n"

使用这个新的tool流式生成器果然测试成功
使用的是miloco和自带的米家设备控制的mcp测试成功

image

重新捋一遍2api要模拟fc以及mcp,各位佬友可以把它当作一个模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# 首先需要一些必须的全局变量标识状态和一个异步的定时器(长时间得不到tool响应,就重置tools_use)
tools_use = False # 标识有没有使用过工具,true标识已经调用过工具了
tools_answer = 'no' # 这是单独对tools和问题进行判断的回答, no说明不需要调用工具,对其进行处理

question = None # 不一定需要,最好有一个,方便全局标记用户的问题

async def start_timer():
"""启动定时器"""
global tools_use
await asyncio.sleep(30)
if tools_use: # 60秒后如果还是True
tools_use = False
logger.debug("tools_use 超时未重置,已自动重置为 False")

# 首先要接收存储请求中的tools和tool_choice 根据tool_choice进行处理
tools_json = data.get("tools", None)
tool_choice = data.get("tool_choice", 'Auto')
# 工具选择 我根据openai api文档总结的
# 默认情况下,模型将决定何时以及使用多少工具。您可以使用 tool_choice 参数强制特定行为。
#
# 自动:(默认)调用零个、一个或多个函数。 tool_choice: "auto"
# 需要:调用一个或多个函数。 tool_choice: "required"
# 强制函数:精确调用一个特定函数。 tool_choice: {"type": "function", "name": "get_weather"}
# 允许的工具:将模型可以调用的工具限制为模型可用工具的子集。
# "tool_choice": {
# "type": "allowed_tools",
# "mode": "auto",
# "tools": [
# {"type": "function", "name": "get_weather"},
# {"type": "function", "name": "search_docs"}
# ]
# }
# }
# None不传递函数
tools_prompt=xxxxx #还是之前的系统提示词,这提示词偶尔给的json不正常,
# 不知道是我的模型问题还是参数或者提示词本身问题
if tools_use is False and tools_json is not None:
#只有还没调用tool或者有tool情况下才处理
finish_reason = "tool_calls" # tool的completion里的finish_reason一定要是这个
if isinstance(tool_choice, dict):
# 根据tool_choice的不同分别处理
# 设置tools_json里的工具
logger.debug('tool_choice is force or allowed_tools')
if "type" in tool_choice:
if tool_choice["type"] == "function":
# logger.debug('tool_choice is force')
force_tools_json = tool_choice
force_tools_dict = [force_tools_json]
# 强制调用,先调用存起来
question = messages[-1]["content"]
tools_question = str(force_tools_dict) + messages[-1]["content"]
tools_answer, prompt_tokens, completion_tokens, total_tokens, answer_id, system_fingerprint = await tools_chat(
tools_prompt, tools_question) # tools_chat根据自己的2api去写,就是用前面的提示词加tool和问题的组合去别的模型
if tools_answer != 'no':
logger.debug(f"tool_answer {tools_answer}")
tools_answer = re.sub(r"^\`\`\`json|^\`\`\`", "", tools_answer) # 偶尔出错,所以我加个正则增强稳定性
tool_call = json.loads(tools_answer)
tools_list.append(tool_call[0])
elif tool_choice["type"] == "allowed_tools":
logger.debug('tool_choice is allowed_tools')
tools_json = tool_choice.get("tools", [])
else:
logger.debug('tool_choice is auto or required')
else:
logger.debug(f'tool_choice is {tool_choice}')

# 这样写,会把所有的tool都拿去问,比较浪费时间,后续继续优化,做到和官网api一样按需调用模型
# 接着处理tool_choice为auto或者require的情况,两者差别很小,更细分等后续优化
# 转换为OpenAI标准格式 注意一定要是这样的标准格式,前面的提示词生成的是claude风格,不太会改,就写了个转换的
openai_tool = {
"id": f"call_{call_id}",
"type": "function",
"function": {
"name": item.get("name", ""),
"arguments": item.get("arguments", "{}")
}
}
tools_list = openai_tools_list
# 然后发送给客户端,不管是流式还是非流,一定记得finish_reason要是tool_calls
completion = {
"id": f"{answer_id}",
"object": "chat.completion",
"created": created,
"model": model,
"system_fingerprint": system_fingerprint,
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"tool_calls": tools_list
},
"logprobs": None,
"finish_reason": finish_reason
}],
"usage": {
"prompt_tokens": prompt_tokens,
"completion_tokens": completion_tokens,
"total_tokens": total_tokens
}
}
# 记得重置tools_use的状态为下一次的tool调用做准备
# 第二次请求的是tools_use是真了
if tools_use is True:
# 把tool的响应和用户的问题一起打包去问大模型,处理工具响应的时候要注意可能会是文件 图片 音频以及文字这几种
# 最后一种就是和工具没有一点关系的纯文字chat了
# 整体代码结构就是
if tools_use is False and tools_json is not None:
if tools_use is True:
else:

目前的话,还是有几个点需要去进一步优化
1.当tool的数量很多的时候全部调用一次时间会很长
需要优化,提前判断要调用哪些tool, 这需要提示词专家重新编写一个提示词让大模型去判断并返回tool_json
2.还有参数不够的情况, 怎么去处理
演示里有一种情况就是一个tool需要三个参数,第一次只给了一个参数,然后需要大模型去问用户其他的参数,这个暂时没想到解决办法去模拟
3.工具的返回值可能是图片 文件 音频或者文字
目前只考虑了文字,因为其他的我不知客户端发过来的内容是啥样的,图片可能是base64,但文件或者音频就不知道了
首要的就是tools_prompt的改进

Pascal-vllm报错

Pascal-vllm原版镜像报错aimv2被占用

通过挂载/usr/local/lib/python3.12/dist-packages/vllm/transformers_utils/configs/ovis.py

进行修改

分别是

1
2
3
4
31:    model_type: str = "aimv3"
76: AutoConfig.register("aimv3", AIMv2Config)
# 原始是aimv2 修改成其他名字即可,两处名字要一样
# 最好指定一下--swap-space 0.5 太大也运行不了

参考的docker compose.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
networks:
1panel-network:
external: true
services:
vllm:
command: --model ${MODEL}
container_name: ${CONTAINER_NAME}
deploy:
resources:
limits:
cpus: ${CPUS}
memory: ${MEMORY_LIMIT}
reservations:
devices:
- capabilities:
- gpu
count: all
driver: nvidia
environment:
HF_ENDPOINT: https://hf-mirror.com
HUGGING_FACE_HUB_TOKEN: ${HUGGING_FACE_HUB_TOKEN}
image: ghcr.io/sasha0552/vllm:v0.9.1
ipc: host
networks:
- 1panel-network
ports:
- ${HOST_IP}:${PANEL_APP_PORT_HTTP}:8000
restart: on-failure:5
runtime: nvidia
volumes:
- ./cache/huggingface:/root/.cache/huggingface
- /path/to/ovis.py://usr/local/lib/python3.12/dist-packages/vllm/transformers_utils/configs/ovis.py

关于An error occurred while downloading using hf_transfer. Consider disabling HF_HUB_ENABLE_HF_TRANSFER for better error handling.

设置环境变量HF_HUB_ENABLE_HF_TRANSFER=0即可

逆向api通过外挂解锁特殊功能

逆向api通过外挂解锁特殊功能

很多时候逆向得到的api只能进行文本对话,不能实现最新的ai特性或者协议。比如function call功能 MCP协议 谷歌最新的a2a协议
我经过实践和尝试,提炼了一套可行的思路和代码,使得逆向api一样可以支持最新的ai相关的特性或协议(目前仅仅尝试了function call和mcp,a2a还需要了解)

function call和Mcp的本质以及异同

1.function call

是llm的具体能力,专注于单个外部工具的调用生成

2.Mcp

一种更上层的宏观的系统设计,通常包含多个function call

异同

打个比方,function call是锤子,剪刀等工具,而Mcp就是说明书之类的东西,指导如何用锤子剪刀等工具制造东西
MCP负责决定在什么时候、为什么以及如何组合使用这些“动作”(包括Function Calling、内部思考、信息检索等)来达成最终目标


外挂的实现思路

fc(function call简称)或者Mcp说通俗点就是,llm根据tools参数和上下文判断要不要从上下文中提取参数执行fc或者Mcp.
不管是fc还是Mcp,最终的执行都不是说ai做的,而是ai返回一个结构化(通常是JSON格式)的指令,然后客户端或者别的什么端进行运算.而ai恰好就可以通过**prompt(提示词)**做到这种效果。
因为我们的思路时,在中转或者2api的服务端先过滤判断一次.
如果需要fc或者mcp,通过另外一轮ai对话提炼参数,把返回的参数加入之前的ai对话中,返回进行fc或者Mcp的运算结果.
接收到运算结果后得把结果重新编排并再进行一次对话,添加一个简单的提示词—–比如:根据上下文回答问题

大致思路流程如图

具体实现

需要一个question变量存储用户的问题,一个标志变量tools_use表示是用过了工具
是否需要使用工具我是通过提示词让ai不需要使用工具的时候就输出no

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# 我的判断是否需要使用工具的提示词
<system>
你是一个专业的 AI 助手,擅长使用各种工具解决问题。你将以专业、友好的态度与用户交流,提供准确和有价值的帮助。
</system>
<persona>

- 专业性:你具备丰富的技术知识和经验
- 思维方式:逻辑清晰,善于分析问题
- 沟通风格:友好、耐心,注重用户体验
- 工作态度:严谨认真,注重细节
</persona>

<instructions>
1. 分析用户需求:
- 仔细理解json格式的工具列表和用户的问题
- 确定需不需要调用工具,需要调用哪些工具以及调用该工具需要的参数

2. 工具使用规范:

- 每次可调用多个工具,如果没有依赖关系,尽量同时返回全部工具的参数,如果有依赖关系,就依次调用
- 如果用户明确要求依次调用才依次调用
- 确保工具调用格式正确,参数完整
3. 回答质量要求:

- 提供清晰、准确、结构化的答案
- 承认知识限制,不随意猜测或编造
- 如果需要使用工具,则返回结果必须严格使用json格式,返回结果不能有其他文字,只返回json格式
- 如果不需要使用工具返回结果不能有其他文字,只返回"no"
4.举例
例子一
用户输入以下内容:
[
{
"type": "function",
"function": {
"name": "fd71edc4f65924613b9fd8330e78eb243",
"description": "获取中国国内的天气预报",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "逗号分隔的经纬度信息 (e.g., 116.40,39.90)"
},
"days": {
"type": "string",
"enum": [
"now",
"24h",
"72h",
"168h",
"3d",
"7d",
"10d",
"15d",
"30d"
],
"description": "预报天数,now为实时天气,24h为24小时预报,72h为72小时预报,168h为168小时预报,3d为3天预报,以此类推"
}
}
}
}
}
]杭州的天气怎么样?
你需要返回的内容是:
[
{
"id": "call_M4eYNv6oPaLunpZLe1iWdfiK",
"function": {
"name": "fd71edc4f65924613b9fd8330e78eb243",
"arguments": "{\"days\":\"now\",\"location\":\"121,31\"}"
},
"type": "function"
}
]
}

例子二
用户输入以下内容:
[
{
"type": "function",
"function": {
"name": "fd71edc4f65924613b9fd8330e78eb243",
"description": "获取中国国内的天气预报",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "逗号分隔的经纬度信息 (e.g., 116.40,39.90)"
},
"days": {
"type": "string",
"enum": [
"now",
"24h",
"72h",
"168h",
"3d",
"7d",
"10d",
"15d",
"30d"
],
"description": "预报天数,now为实时天气,24h为24小时预报,72h为72小时预报,168h为168小时预报,3d为3天预报,以此类推"
}
}
}
}
}
]你好
你需要返回的内容是:
no

代码逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
tools_use = False
tools_answer = 'no'

tools_question = str(tools_json) + messages[-1]["content"] # 缓存用户问题

tools_json = data.get("tools", None) #提取tools参数
# 首先用tools_json和question以及提示词进行新的对话判断是否需要工具
# 需要的话返回的是结构化的调用参数,不需要就返回的no
# 这轮新的对话可以使用2api的模型也可以使用正规的官方api

# 只有tools_answer不是no且tools_use是false时才说需要mcp或者fc且之前没有进行过工具的调用
if tools_answer != 'no' and not tools_use:
# 需要function call
# 构造openai api格式的返回
# 生成created
current_timestamp = int(time.time())
created = current_timestamp
# 下面是告诉客户端需要调用mcp的返回值模板,看了L站n多关于mcp的帖子才知道
completion = {
"id": answer_id,
"object": "chat.completion",
"created": created,
"model": model,
"system_fingerprint": system_fingerprint,
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"tool_calls": json.loads(tools_answer) if tools_answer else None,
"content": None, # 如果有提示词能达到claude使用MCP的思考过程的效果,可以把模拟的思考过程赋值到此处
},
"logprobs": None,
"finish_reason": "tool_call"
}],
"usage": {
"prompt_tokens": prompt_tokens,
"completion_tokens": completion_tokens,
"total_tokens": total_tokens
}
}
tools_answer = 'no' # 重置相关参数,便于下次正确使用



# 判断上下文最后的messages的role角色,如果是tools,说明这是客户端发回来的mcp或者fc调用的结果
elif messages[-1]["role"] == 'tool':

#需要从头到位遍历messages,把tool的role转成user,这样逆向的api才好使用
# tools的结果在content里的第一个的text里面
messages[-1]["content"][0]["text"]


# 同时判断tools_use是不是true, 是说明这就是本次tools的调用结果
if tools_use:
# 这里需要保证对话的最后一条是用户起初提的问题,不同的逆向api这里的代码不一样比如
chat_messages["content"] = question
question = None# 恢复变量和标志位
tools_use = False

MCP的思考过程

我也写了一个粗略的提示词想要达成这种效果,但由于我的提示词功力不够以及逆向的模型过于垃圾,导致效果不理想

end

这样做了以后就可以让逆向api支持mcp协议了
经过openwebui几个简单的工具测试可以达成效果
总结一下,MCP并不是什么困难的东西,只不过批了一层外衣,MCP的模拟过程实质还是用提示词的使用,只要了解了MCP过程中的api返回值接受值的格式就可以轻松地给任意模型加上MCP
未来的llm发展我猜测为两个方向
融合和外挂

融合

就是MOE模型里进一步增加从对话中提取参数结构化输出的能力

外挂

就是主模型增强关于是否需要MCP的判断能力,需要的话交给一个极致轻量的次生模型提取参数并结构化输出。由于次生模型只需要提取参数结构化输出,是可以做到轻量且高效准确的,主模型就可以完全抛弃这部分能力,增强其他能力

补充

下一步了解下谷歌的a2a协议

8845-all in one

其他X86多网口设备均可以参考

方案一

PVE做底层,其他全部用虚拟机解决

直通核显屡次尝试失败,要么开始就不能用,要么用一会就不行了

要么直通成功,没问题了,但是测试虚拟机里安装rocm,无法使用780m核显,且vram占用一直为0

方案二

PVE直通核显失败

于是我用fedora做实体机,然后虚拟机里运行PVE

需要gpu的服务都放fedora实体机上

不需要的都放虚拟机里的PVE,这样即使重装也只需要恢复使用gpu的服务

我需要使用gpu的服务有comfyui, 影视(jellyfin或者emby等选一个,其实用的也不多,基本上看alist就行了)

第一步

创建网桥

必须双网口及以上才行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 我的两个网口分别是 enp2s0 enp3s0    使用enp2s0作为网桥   切换到root设置更方便
nmcli connection add type bridge-slave con-name pve-port1 ifname enp2s0 master Pve
# pve-port1和Pve可以自定义, enp2s0是你需要作为网桥的设备名称
nmcli connection modify pve ipv4.addresses 192.168.31.43/24
# 192.168.31.43/24 网桥的ip
nmcli connection modify pve ipv4.gateway 192.168.31.1
# 网桥的网关
nmcli connection modify pve ipv4.dns "8.8.8.8"
# 网桥的DNS
nmcli connection modify pve ipv4.method manual
# 切换网桥为手动配置
nmcli connection up pve
# 启动网桥
firewall-cmd --add-interface=pve --zone=trusted --permanent
# 无条件放行网桥流量

第二步

使用fedora的libvirt创建PVE虚拟机

1
dnf install @virtualization

可以使用virt-manager以gui方式创建虚拟机

网络源选桥接设备 设备名称填网桥名称

第三步

直通需要的设备

pci设备直通到PVE虚拟机后,无法再直通到PVE下的虚拟机,因为PVE无法开启iommu

但我要直通的pci设备只有m2转sata的转接卡

只需要把它直通给PVE就行了

然后通过scsi把硬盘块直通给虚拟机就行

1
2
3
scsi1: /dev/sdb,cache=directsync,discard=on,iothread=1
scsi2: /dev/sda,cache=directsync,discard=on,iothread=1
# 更多参数询问ai

至此,all in one完成,后续需要加服务,不需要gpu添加到虚拟机PVE

需要GPU的直接添加到宿主机,把宿主机服务的部署整理成脚本,重置后一键恢复服务

GPU失败是因为驱动问题

解决驱动问题

卸载vfio

Next-chat-web同步

  1. 修改docker的环境变量
1
WHITE_WEBDEV_ENDPOINTS=https://alist.leioukupo.top/机械盘
  1. 在机械盘目录下新建文件夹和文件
1
2
3
机械盘/
└── chatgpt-next-web
└── backup.json
  1. 配置next chat
1
2
3
# 必须启用代理
# 代理地址空着
webdev地址 : https://alist.leioukupo.top/dav/机械盘

3588Pve实践

Rock 5b卖掉后在咸鱼重新入手了一块3588,8+32的配置

Pve安装

需要debian系统

1
curl -L https://mirrors.apqa.cn/proxmox/debian/pveport.gpg | sudo tee /usr/share/keyrings/pveport.gpg >/dev/null
1
echo "deb [deb=arm64 signed-by=/usr/share/keyrings/pveport.gpg] https://mirrors.apqa.cn/proxmox/debian/pve bookworm port" | sudo tee  /etc/apt/sources.list.d/pveport.list
  1. 编辑/etc/hosts
1
2
3
4
5
6
7
8
127.0.0.1   localhost
127.0.1.1 yjh-jm10-3588
::1 localhost yjh-jm10-3588 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
192.168.31.43 proxmox-server yjh-jm10-3588
1
2
sudo apt install ifupdown2
sudo apt install proxmox-ve postfix open-iscsi pve-edk2-firmware-aarch64

最后要记得重启,在访问ip+8006就可以访问pve管理界面了,密码和账号是root和对应的密码

虚拟机测试

使用非lxc容器方式

注意点

  • cpu设置要勾选高级选项,在cpu绑定一栏输入 4,5,6,7
  • 无显示输出,需要按照pve官方文档操作

Pve官方文档

1
2
apt download pve-edk2-firmware-aarch64=3.20220526-rockchip
dpkg -i pve-edk2-firmware-aarch64_3.20220526-rockchip_all.deb

如果apt下载失败,复制pve-edk2到浏览器下载

虚拟机硬件里的显示需要选择ramfb方式

安装istore

使用虚拟机安装

1
2
qemu-img convert -f raw -O qcow2 istoreos-22.03.7-2024122712-h88k-squashfs-combined.img vm-101-disk-1.qcow2
qm importdisk 101 vm-101-disk-1.qcow2 local

打开刚才创建的虚拟机的硬件,会看到有个未使用的磁盘,双击打开,点确认即可

对应编辑一下引导顺序。
启动后,是uefi shell,没有进入istore界面,需要带efi的istore固件

lxc运行openwrt

1.先上传lxc模板,再执行下面的命令

1
2
3
4
5
6
7
8
9
10
11
12
13
pct create 101 \
local:vztmpl/rootfs.tar.xz \
--rootfs local:1 \
--ostype unmanaged \
--hostname openwrt \
--arch arm64 \
--cores 8 \
--memory 1024 \
--swap 0 \
-net0 bridge=vmbr0,name=eth0


# rootfs.tar.xz根据你的lxc模板名字修改
1
2
3
101是id
local:1的1是指该lxc容器磁盘大小是1g
memory 运行内存

2.修改lxc容器配置文件

1
nano /etc/pve/lxc/101.conf
1
2
3
4
5
6
7
8
9
10
11
arch: arm64
cores: 8
features: nesting=1
hostname: openwrt
memory: 1024
net0: name=eth0,bridge=vmbr0,hwaddr=BC:24:11:BE:DB:C8,type=veth
ostype: unmanaged
rootfs: local:101/vm-101-disk-0.raw,size=1G
swap: 0
lxc.mount.entry: /dev/ppp /dev/ppp none bind,create=file
lxc.mount.entry: /dev/net/tun /dev/met/tun none bind,create=file

3.在pve的web页面找到这个lxc容器,选项—》功能 —-》勾选嵌套

4.启动

1
pct start 101

4.1修改/etc/config/firewall,后面新增

1
2
lxc-attach 101 
# 进入op终端,用vi编辑文件
1
2
3
4
5
6
7
8
9
10
11
12
config rule
option name Allow-Web-WAN
option src wan
option dest_port 443
option proto tcp
option target ACCEPT
config rule
option name Allow-Web-WAN
option src wan
option dest_port 80
option proto tcp
option target ACCEPT

4.2.重启防火墙

1
/etc/init.d/firewall restart
1
ifconfig

根据ifconfig显示的ip访问就可以进入后台

参考链接

openwrt_lxc模板

pve开启ipv6

https://www.icn.ink/pve/57.html

Qt笔记

Qt笔记

信号

自定义信号

1
2
signals:
void sig_addOne(int value);

使用signals声明,返回值是void

emit—-发送信号

1
emit sig_add(socre++);

Qt的子线程无法直接修改ui,需要发送信号到ui线程进行修改

非基础类型参数注册

1
2
qRegisterMetaType<Score>("Score");
qRegisterMetaType<string>("string");

槽函数的参数和信号参数的关系

Qt槽函数的参数需要和信号的参数保持一致, 可以比信号的参数少, 但是不能顺序不同, 也不能比信号的参数多

信号重名如何处理

使用泛型解决信号重载问题

1
2
3
4
connect(ui->comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &Widget::onIndex);
connect(ui->comboBox, QOverload<const QString&>::of(&QComboBox::currentIndexChanged),
this, &Widget::onIndexStr);

信号和槽函数关系

  • 信号是由对象在某个特定事件发生时发出的消息。信号本身并不执行任何操作,它仅仅是表示某种状态的变化或事件的发生。例如,当一个按钮被点击时,它会发出一个clicked()信号
  • 槽是一个函数,可以接收并处理信号。当一个信号被发出时,与该信号连接的槽函数就会被调用。槽函数可以是任何成员函数、全局函数或lambda表达式

复选框状态

未选中 0

半选中 1

选中 2

ADC使用

stm32的ADC单通道单次使用

ADC通道与引脚对应关系

adc通道与对应的引脚
stm32f1没有内部温度传感器

单次ADC采集流程

1.开启对应时钟和ADC1时钟,设置PA1为模拟输入

1
2
3
4
5
6
7
8
9
10
// 开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // GPIOA的时钟定义错误,应就近挂在APB2上,而不能图省事直接挂在AHB上
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 设置ADC时钟,设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
// 初始化GPIO
GPIO_InitTypeDef ADC_PA1;
ADC_PA1.GPIO_Pin = GPIO_Pin_1;
ADC_PA1.GPIO_Mode = GPIO_Mode_AIN;// 模拟输入模式
ADC_PA1.GPIO_Speed = GPIO_Speed_50MHz;// 50MHz
GPIO_Init(GPIOA, &ADC_PA1);

2.复位ADC1,同时设置ADC1参数

1
2
3
4
5
6
7
8
ADC_DeInit(ADC1);
adc1_1.ADC_Mode = ADC_Mode_Independent;// adc工作模式 独立模式
adc1_1.ADC_ContinuousConvMode = DISABLE;// 连续转换是否开启
adc1_1.ADC_DataAlign = ADC_DataAlign_Right;// 数据对齐方式
adc1_1.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;// 触发转换方式 软件触发
adc1_1.ADC_ScanConvMode = DISABLE;// 扫描模式是否开启
adc1_1.ADC_NbrOfChannel = 1;// 转换通道数目
ADC_Init(ADC1, &adc1_1);

3.使能ADC

1
ADC_Cmd(ADC1,ENABLE);

4.配置规则通道参数

1
2
ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_7Cycles5); // 通道的转换顺序 如果设置Rank为1
//那么这个通道将会首先被转换

5.开启软件转换

1
ADC_SoftwareStartConvCmd(ADC1,ENABLE); // 软件触发转换

6.等待转换完成,读取ADC值

1
ADC_GetConversionValue(ADC1);