quip-node-manager
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseQuip Node Manager
Quip Node Manager
Skill by ara.so — Daily 2026 Skills collection.
Quip Node Manager is a cross-platform desktop application (macOS, Linux, Windows) built with Tauri v2 + Rust that lets you run, configure, and monitor Quip Network nodes. It supports two execution modes — Docker (default on Windows/Linux) and Native binary (default on macOS) — with a GUI front-end and an optional terminal UI ( flag).
--cli由ara.so开发的Skill——属于Daily 2026 Skills合集。
Quip Node Manager是一款基于Tauri v2 + Rust构建的跨平台桌面应用(支持macOS、Linux、Windows),可用于运行、配置和监控Quip Network节点。它支持两种执行模式——Docker(Windows/Linux默认模式)和Native binary(macOS默认模式),提供GUI前端和可选的终端UI(使用参数)。
--cliInstallation
安装
Quick Install (macOS / Linux)
快速安装(macOS / Linux)
sh
curl -fsSL https://gitlab.com/quip.network/quip-node-manager/-/raw/main/scripts/install.sh | shsh
curl -fsSL https://gitlab.com/quip.network/quip-node-manager/-/raw/main/scripts/install.sh | shQuick Install (Windows PowerShell)
快速安装(Windows PowerShell)
powershell
irm https://gitlab.com/quip.network/quip-node-manager/-/raw/main/scripts/install.ps1 | iexpowershell
irm https://gitlab.com/quip.network/quip-node-manager/-/raw/main/scripts/install.ps1 | iexManual Installation
手动安装
| Platform | Format | Steps |
|---|---|---|
| macOS | | Open DMG, drag to |
| Linux | | |
| Linux | | |
| Windows | | Run installer; click More info → Run anyway if SmartScreen warns |
| 平台 | 格式 | 步骤 |
|---|---|---|
| macOS | | 打开DMG文件,将应用拖至 |
| Linux | | 执行 |
| Linux | | 执行 |
| Windows | | 运行安装程序;若SmartScreen发出警告,点击更多信息 → 仍要运行 |
Development Setup
开发环境搭建
Prerequisites
前置条件
- Rust (stable toolchain)
- Bun or Node.js
- Platform Tauri v2 deps: https://v2.tauri.app/start/prerequisites/
- Rust(稳定工具链)
- Bun 或 Node.js
- 平台相关的Tauri v2依赖:https://v2.tauri.app/start/prerequisites/
Clone and Run
克隆并运行
sh
git clone https://gitlab.com/quip.network/quip-node-manager.git
cd quip-node-manager
bun install # Install JS dependencies
bun run dev # Launch hot-reloading dev build (opens desktop window)
bun run build # Production build for current platformsh
git clone https://gitlab.com/quip.network/quip-node-manager.git
cd quip-node-manager
bun install # 安装JS依赖
bun run dev # 启动热重载开发构建(打开桌面窗口)
bun run build # 为当前平台构建生产版本Rust-only checks (no frontend needed)
仅Rust检查(无需前端)
sh
cd src-tauri
cargo check # Fast type-check
cargo clippy # Lint with suggestions
cargo test # Run unit testssh
cd src-tauri
cargo check # 快速类型检查
cargo clippy # 代码检查并给出建议
cargo test # 运行单元测试Architecture
架构
quip-node-manager/
├── src/ # Frontend: vanilla HTML/CSS/JS
│ ├── index.html
│ ├── main.js # Tauri IPC calls (window.__TAURI__)
│ └── styles.css
├── src-tauri/
│ ├── src/
│ │ ├── main.rs # App entry point
│ │ ├── commands.rs # Tauri IPC command handlers
│ │ ├── node.rs # Node process management
│ │ ├── config.rs # TOML config generation
│ │ ├── docker.rs # Docker mode logic
│ │ ├── updater.rs # Background update monitor
│ │ └── gpu.rs # CUDA/Metal device detection
│ ├── Cargo.toml
│ └── tauri.conf.json
├── scripts/
│ ├── install.sh
│ └── install.ps1
└── AGENTS.md # Detailed architecture docsData directory: — stores settings, TOML config, secrets, downloaded binaries, and trust database.
~/quip-data/quip-node-manager/
├── src/ # 前端:原生HTML/CSS/JS
│ ├── index.html
│ ├── main.js # Tauri IPC调用(window.__TAURI__)
│ └── styles.css
├── src-tauri/
│ ├── src/
│ │ ├── main.rs # 应用入口
│ │ ├── commands.rs # Tauri IPC命令处理器
│ │ ├── node.rs # 节点进程管理
│ │ ├── config.rs # TOML配置生成
│ │ ├── docker.rs # Docker模式逻辑
│ │ ├── updater.rs # 后台更新监控
│ │ └── gpu.rs # CUDA/Metal设备检测
│ ├── Cargo.toml
│ └── tauri.conf.json
├── scripts/
│ ├── install.sh
│ └── install.ps1
└── AGENTS.md # 详细架构文档数据目录: —— 存储设置、TOML配置、密钥、下载的二进制文件和信任数据库。
~/quip-data/Key Concepts
核心概念
Run Modes
运行模式
| Mode | Default On | How it works |
|---|---|---|
| Docker | Windows, Linux | Pulls/manages a container image via Docker daemon |
| Native | macOS | Downloads a standalone quip-protocol binary |
| 模式 | 默认平台 | 工作原理 |
|---|---|---|
| Docker | Windows、Linux | 通过Docker守护进程拉取/管理容器镜像 |
| Native | macOS | 下载独立的quip-protocol二进制文件 |
Pre-flight Checklist
预启动检查清单
Before starting the node, the manager verifies:
- Docker available (Docker mode) or binary downloaded (Native mode)
- Node secret exists in
~/quip-data/ - Public IP reachable
- Port forwarding configured
- Firewall rules allow node traffic
启动节点前,管理器会验证:
- Docker可用(Docker模式)或二进制文件已下载(Native模式)
- 节点密钥存在于中
~/quip-data/ - 公网IP可访问
- 端口转发已配置
- 防火墙规则允许节点流量
Tauri IPC Commands (Frontend → Backend)
Tauri IPC命令(前端 → 后端)
The frontend calls Rust commands via . Key commands:
window.__TAURI__.core.invokejs
const { invoke } = window.__TAURI__.core;
// Get current node status
const status = await invoke('get_node_status');
// Returns: { running: bool, mode: 'docker'|'native', uptime_secs: number }
// Start the node
await invoke('start_node');
// Stop the node
await invoke('stop_node');
// Get configuration
const config = await invoke('get_config');
// Save configuration
await invoke('save_config', { config: { /* see config schema below */ } });
// Run pre-flight checks
const checks = await invoke('run_preflight');
// Returns: { docker: bool, secret: bool, public_ip: string, port_open: bool, firewall: bool }
// Get GPU devices
const gpus = await invoke('get_gpu_devices');
// Returns: [{ id: string, name: string, type: 'cuda'|'metal', enabled: bool, utilization: number }]
// Check for updates
const update = await invoke('check_for_updates');
// Returns: { app: string|null, node: string|null, docker_image: string|null }前端通过调用Rust命令。核心命令:
window.__TAURI__.core.invokejs
const { invoke } = window.__TAURI__.core;
// 获取当前节点状态
const status = await invoke('get_node_status');
// 返回值:{ running: bool, mode: 'docker'|'native', uptime_secs: number }
// 启动节点
await invoke('start_node');
// 停止节点
await invoke('stop_node');
// 获取配置
const config = await invoke('get_config');
// 保存配置
await invoke('save_config', { config: { /* 见下方配置 schema */ } });
// 运行预启动检查
const checks = await invoke('run_preflight');
// 返回值:{ docker: bool, secret: bool, public_ip: string, port_open: bool, firewall: bool }
// 获取GPU设备
const gpus = await invoke('get_gpu_devices');
// 返回值:[{ id: string, name: string, type: 'cuda'|'metal', enabled: bool, utilization: number }]
// 检查更新
const update = await invoke('check_for_updates');
// 返回值:{ app: string|null, node: string|null, docker_image: string|null }Configuration Schema (TOML)
配置Schema(TOML)
The app generates a TOML config written to matching the quip-protocol format:
~/quip-data/config.tomltoml
[node]
mode = "native" # or "docker"
secret_path = "/Users/you/quip-data/secret"
data_dir = "/Users/you/quip-data"
[network]
public_ip = "1.2.3.4"
port = 9000
tls_cert = "/path/to/cert.pem" # optional
tls_key = "/path/to/key.pem" # optional
[gpu]
enabled = true
devices = ["0", "1"]
utilization = 80 # percent
yield_mode = false
[dwave]
enabled = false
token = "" # set via env: DWAVE_TOKEN
daily_budget_seconds = 60
[updates]
auto_restart_on_image_update = true
check_interval_secs = 1800应用会生成符合quip-protocol格式的TOML配置,并写入:
~/quip-data/config.tomltoml
[node]
mode = "native" # 或 "docker"
secret_path = "/Users/you/quip-data/secret"
data_dir = "/Users/you/quip-data"
[network]
public_ip = "1.2.3.4"
port = 9000
tls_cert = "/path/to/cert.pem" # 可选
tls_key = "/path/to/key.pem" # 可选
[gpu]
enabled = true
devices = ["0", "1"]
utilization = 80 # 百分比
yield_mode = false
[dwave]
enabled = false
token = "" # 通过环境变量DWAVE_TOKEN设置
daily_budget_seconds = 60
[updates]
auto_restart_on_image_update = true
check_interval_secs = 1800Rust Backend Patterns
Rust后端模式
Adding a Tauri Command
添加Tauri命令
rust
// src-tauri/src/commands.rs
use tauri::State;
use crate::node::NodeManager;
#[tauri::command]
pub async fn get_node_status(
manager: State<'_, NodeManager>,
) -> Result<NodeStatus, String> {
manager.status().await.map_err(|e| e.to_string())
}
#[tauri::command]
pub async fn start_node(
manager: State<'_, NodeManager>,
) -> Result<(), String> {
manager.start().await.map_err(|e| e.to_string())
}Register in :
main.rsrust
// src-tauri/src/main.rs
fn main() {
tauri::Builder::default()
.manage(NodeManager::new())
.invoke_handler(tauri::generate_handler![
commands::get_node_status,
commands::start_node,
commands::stop_node,
commands::get_config,
commands::save_config,
commands::run_preflight,
commands::get_gpu_devices,
commands::check_for_updates,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}rust
// src-tauri/src/commands.rs
use tauri::State;
use crate::node::NodeManager;
#[tauri::command]
pub async fn get_node_status(
manager: State<'_, NodeManager>,
) -> Result<NodeStatus, String> {
manager.status().await.map_err(|e| e.to_string())
}
#[tauri::command]
pub async fn start_node(
manager: State<'_, NodeManager>,
) -> Result<(), String> {
manager.start().await.map_err(|e| e.to_string())
}在中注册:
main.rsrust
// src-tauri/src/main.rs
fn main() {
tauri::Builder::default()
.manage(NodeManager::new())
.invoke_handler(tauri::generate_handler![
commands::get_node_status,
commands::start_node,
commands::stop_node,
commands::get_config,
commands::save_config,
commands::run_preflight,
commands::get_gpu_devices,
commands::check_for_updates,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}Emitting Live Log Events to Frontend
向前端发送实时日志事件
rust
// src-tauri/src/node.rs
use tauri::{AppHandle, Emitter};
pub async fn stream_logs(app: AppHandle, mut reader: impl AsyncBufRead + Unpin) {
let mut line = String::new();
loop {
line.clear();
match reader.read_line(&mut line).await {
Ok(0) => break, // EOF
Ok(_) => {
app.emit("node-log-line", line.trim().to_string())
.unwrap_or_default();
}
Err(e) => {
app.emit("node-log-error", e.to_string()).unwrap_or_default();
break;
}
}
}
}Listen in the frontend:
js
const { listen } = window.__TAURI__.event;
const unlisten = await listen('node-log-line', (event) => {
appendToLogDrawer(event.payload);
});
// Cleanup when drawer closes:
unlisten();rust
// src-tauri/src/node.rs
use tauri::{AppHandle, Emitter};
pub async fn stream_logs(app: AppHandle, mut reader: impl AsyncBufRead + Unpin) {
let mut line = String::new();
loop {
line.clear();
match reader.read_line(&mut line).await {
Ok(0) => break, // 文件结束
Ok(_) => {
app.emit("node-log-line", line.trim().to_string())
.unwrap_or_default();
}
Err(e) => {
app.emit("node-log-error", e.to_string()).unwrap_or_default();
break;
}
}
}
}在前端监听:
js
const { listen } = window.__TAURI__.event;
const unlisten = await listen('node-log-line', (event) => {
appendToLogDrawer(event.payload);
});
// 关闭日志抽屉时清理:
unlisten();Docker Mode: Managing Containers
Docker模式:容器管理
rust
// src-tauri/src/docker.rs
use std::process::Command;
pub fn pull_image(image: &str) -> Result<(), String> {
let status = Command::new("docker")
.args(["pull", image])
.status()
.map_err(|e| format!("docker not found: {e}"))?;
if status.success() { Ok(()) } else { Err("docker pull failed".into()) }
}
pub fn run_node_container(image: &str, data_dir: &str, port: u16) -> Result<String, String> {
let output = Command::new("docker")
.args([
"run", "-d",
"--name", "quip-node",
"-p", &format!("{port}:{port}"),
"-v", &format!("{data_dir}:/quip-data"),
image,
])
.output()
.map_err(|e| e.to_string())?;
if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
} else {
Err(String::from_utf8_lossy(&output.stderr).to_string())
}
}rust
// src-tauri/src/docker.rs
use std::process::Command;
pub fn pull_image(image: &str) -> Result<(), String> {
let status = Command::new("docker")
.args(["pull", image])
.status()
.map_err(|e| format!("docker not found: {e}"))?;
if status.success() { Ok(()) } else { Err("docker pull failed".into()) }
}
pub fn run_node_container(image: &str, data_dir: &str, port: u16) -> Result<String, String> {
let output = Command::new("docker")
.args([
"run", "-d",
"--name", "quip-node",
"-p", &format!("{port}:{port}"),
"-v", &format!("{data_dir}:/quip-data"),
image,
])
.output()
.map_err(|e| e.to_string())?;
if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
} else {
Err(String::from_utf8_lossy(&output.stderr).to_string())
}
}GPU Detection
GPU检测
rust
// src-tauri/src/gpu.rs
#[derive(serde::Serialize)]
pub struct GpuDevice {
pub id: String,
pub name: String,
pub device_type: String, // "cuda" or "metal"
pub enabled: bool,
pub utilization: u8,
}
pub fn detect_gpus() -> Vec<GpuDevice> {
let mut devices = Vec::new();
// CUDA detection via nvidia-smi
if let Ok(output) = std::process::Command::new("nvidia-smi")
.args(["--query-gpu=index,name", "--format=csv,noheader"])
.output()
{
for line in String::from_utf8_lossy(&output.stdout).lines() {
let parts: Vec<&str> = line.splitn(2, ',').collect();
if parts.len() == 2 {
devices.push(GpuDevice {
id: parts[0].trim().to_string(),
name: parts[1].trim().to_string(),
device_type: "cuda".into(),
enabled: true,
utilization: 80,
});
}
}
}
// macOS Metal: presence of Metal framework implies GPU
#[cfg(target_os = "macos")]
devices.push(GpuDevice {
id: "0".into(),
name: "Apple Metal GPU".into(),
device_type: "metal".into(),
enabled: true,
utilization: 80,
});
devices
}rust
// src-tauri/src/gpu.rs
#[derive(serde::Serialize)]
pub struct GpuDevice {
pub id: String,
pub name: String,
pub device_type: String, // "cuda" 或 "metal"
pub enabled: bool,
pub utilization: u8,
}
pub fn detect_gpus() -> Vec<GpuDevice> {
let mut devices = Vec::new();
// 通过nvidia-smi检测CUDA
if let Ok(output) = std::process::Command::new("nvidia-smi")
.args(["--query-gpu=index,name", "--format=csv,noheader"])
.output()
{
for line in String::from_utf8_lossy(&output.stdout).lines() {
let parts: Vec<&str> = line.splitn(2, ',').collect();
if parts.len() == 2 {
devices.push(GpuDevice {
id: parts[0].trim().to_string(),
name: parts[1].trim().to_string(),
device_type: "cuda".into(),
enabled: true,
utilization: 80,
});
}
}
}
// macOS Metal:存在Metal框架即表示有GPU
#[cfg(target_os = "macos")]
devices.push(GpuDevice {
id: "0".into(),
name: "Apple Metal GPU".into(),
device_type: "metal".into(),
enabled: true,
utilization: 80,
});
devices
}CLI Mode
CLI模式
sh
quip-node-manager --cliLaunches a terminal UI (TUI) instead of the desktop window — useful for headless servers.
sh
quip-node-manager --cli启动终端UI(TUI)而非桌面窗口——适用于无界面服务器。
TLS Certificate Setup
TLS证书设置
The GUI includes a built-in walkthrough. For scripted setup:
Let's Encrypt (certbot):
sh
sudo certbot certonly --standalone -d your.domain.comGUI内置了设置向导。如需脚本化设置:
Let's Encrypt(certbot):
sh
sudo certbot certonly --standalone -d your.domain.comCerts written to /etc/letsencrypt/live/your.domain.com/
证书会写入/etc/letsencrypt/live/your.domain.com/
Then set in config:
```toml
[network]
tls_cert = "/etc/letsencrypt/live/your.domain.com/fullchain.pem"
tls_key = "/etc/letsencrypt/live/your.domain.com/privkey.pem"Self-signed (openssl):
sh
openssl req -x509 -newkey rsa:4096 -keyout ~/quip-data/key.pem \
-out ~/quip-data/cert.pem -days 365 -nodes \
-subj "/CN=quip-node"
然后在配置中设置:
```toml
[network]
tls_cert = "/etc/letsencrypt/live/your.domain.com/fullchain.pem"
tls_key = "/etc/letsencrypt/live/your.domain.com/privkey.pem"自签名证书(openssl):
sh
openssl req -x509 -newkey rsa:4096 -keyout ~/quip-data/key.pem \
-out ~/quip-data/cert.pem -days 365 -nodes \
-subj "/CN=quip-node"Environment Variables
环境变量
| Variable | Purpose |
|---|---|
| D-Wave QPU API token (never hard-code) |
| Override default |
| Override Docker image tag |
| Logging level (e.g. |
| 环境变量 | 用途 |
|---|---|
| D-Wave QPU API令牌(切勿硬编码) |
| 覆盖默认的 |
| 覆盖Docker镜像标签 |
| 日志级别(例如 |
Background Update Monitor
后台更新监控
The app checks every 30 minutes for:
- New Docker images
- New native binaries
- New app releases
Configure in :
config.tomltoml
[updates]
check_interval_secs = 1800
auto_restart_on_image_update = true # restarts node on new Docker image应用每30分钟检查一次:
- 新的Docker镜像
- 新的原生二进制文件
- 新的应用版本
可在中配置:
config.tomltoml
[updates]
check_interval_secs = 1800
auto_restart_on_image_update = true # 有新Docker镜像时自动重启节点Troubleshooting
故障排查
| Problem | Fix |
|---|---|
| macOS "app is damaged" | Run |
| Windows SmartScreen block | Click More info → Run anyway |
| Docker not found | Install Docker Desktop and ensure daemon is running |
| Pre-flight: port closed | Forward UDP/TCP port 9000 on router; check |
| Node won't start (native) | Check |
| Blank GUI window (dev) | Run |
| Run |
| Logs not streaming | Ensure |
| 问题 | 解决方法 |
|---|---|
| macOS提示“应用已损坏” | 运行 |
| Windows SmartScreen拦截 | 点击更多信息 → 仍要运行 |
| 找不到Docker | 安装Docker Desktop并确保守护进程正在运行 |
| 预启动检查:端口关闭 | 在路由器上转发UDP/TCP 9000端口;检查 |
| 节点无法启动(Native模式) | 检查 |
| GUI窗口空白(开发环境) | 运行 |
| 运行 |
| 日志无法流式传输 | 确保在调用 |
Contributing
贡献指南
- Fork on GitLab:
https://gitlab.com/quip.network/quip-node-manager - Make changes; run and
cargo clippybefore submittingbun run build - License: AGPL-3.0-or-later — all contributions must be compatible
- See AGENTS.md for AI-agent-specific architecture guidance
- 在GitLab上复刻项目:
https://gitlab.com/quip.network/quip-node-manager - 进行修改;提交前需运行和
cargo clippybun run build - 许可证:AGPL-3.0-or-later —— 所有贡献必须符合该许可证要求
- 查看AGENTS.md获取AI Agent相关的架构指导