feat(site): 并入 Gin 官网与文档服务,对齐桌面产品形态
CI / Lint & TypeCheck (push) Canceled after 0s
CI / Test (push) Canceled after 0s
CI / Tauri Foundation Acceptance (push) Canceled after 0s
CI / Build (macos-latest) (push) Canceled after 0s
CI / Build (ubuntu-latest) (push) Canceled after 0s
CI / Build (windows-latest) (push) Canceled after 0s

- apps/server:Gin embed 官网 + /docs + /health + /download(无操作 API)
- run.sh 重写 web/gui 场景;.gitignore 排除 server 二进制与挂载的安装包
- README/AGENTS/CLAUDE 同步「浏览器只看官网、操作走桌面」的产品形态说明
- 终端视口背景变量与全宽终端边缘 hairline 修正(serial/terminal/i18n 微调)
This commit is contained in:
Logan.Li committed 2026-10-10 13:35:01 +08:00
1 parent b18214e96a
commit 5eb11826db
36 files changed
+3676 -144

No files matched your search

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
<rect x="64" y="64" width="896" height="896" rx="224" fill="#101820"/>
<rect x="116" y="116" width="792" height="792" rx="180" fill="#17232d"/>
<path d="M258 326 454 512 258 698" fill="none" stroke="#f2f7f8" stroke-width="92" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M500 698h244" fill="none" stroke="#55aef2" stroke-width="92" stroke-linecap="round"/>
<circle cx="750" cy="308" r="52" fill="#42d392"/>
</svg>

After

Width:  |  Height:  |  Size: 502 B

+35
View File
@@ -0,0 +1,35 @@
// OpsXShell 产品站 — 无依赖静态脚本
document.addEventListener('DOMContentLoaded', () => {
// 移动端导航
const toggle = document.querySelector('.nav-toggle')
const links = document.querySelector('.nav-links')
toggle?.addEventListener('click', () => links?.classList.toggle('open'))
links?.querySelectorAll('a').forEach((a) =>
a.addEventListener('click', () => links.classList.remove('open'))
)
// 复制 SHA-256
document.querySelectorAll('[data-copy]').forEach((button) => {
button.addEventListener('click', async () => {
const value = button.getAttribute('data-copy') || ''
try {
await navigator.clipboard.writeText(value)
const original = button.textContent
button.textContent = '已复制 ✓'
setTimeout(() => { button.textContent = original }, 1600)
} catch {
// 剪贴板不可用(如 file:// 非安全上下文)时退回选中文本提示
button.textContent = '请手动复制'
setTimeout(() => { button.textContent = original }, 1600)
}
})
})
// FAQ 单开(details 互斥)
const faqs = document.querySelectorAll('.faq-item')
faqs.forEach((item) =>
item.addEventListener('toggle', () => {
if (item.open) faqs.forEach((other) => { if (other !== item) other.open = false })
})
)
})
+811
View File
@@ -0,0 +1,811 @@
:root {
--bg: #0b1118;
--bg-soft: #0e1720;
--panel: #14212c;
--panel-2: #17232d;
--border: rgba(120, 160, 190, 0.16);
--text: #e8f0f4;
--text-2: #aabccb;
--text-3: #7590a3;
--accent: #55aef2;
--accent-soft: rgba(85, 174, 242, 0.14);
--green: #3fd68f;
--green-soft: rgba(63, 214, 143, 0.12);
--amber: #f5c04e;
--purple: #a78bfa;
--red: #f87171;
--radius: 14px;
--mono: 'SF Mono', ui-monospace, 'Cascadia Code', Menlo, Consolas, monospace;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
scroll-behavior: smooth;
}
body {
background:
radial-gradient(1100px 500px at 75% -8%, rgba(85, 174, 242, 0.1), transparent 60%),
radial-gradient(800px 420px at 12% 4%, rgba(63, 214, 143, 0.06), transparent 55%), var(--bg);
color: var(--text);
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
line-height: 1.65;
-webkit-font-smoothing: antialiased;
}
a {
color: var(--accent);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.container {
max-width: 1080px;
margin: 0 auto;
padding: 0 24px;
}
/* ── Nav ─────────────────────────────────────────── */
.nav {
position: sticky;
top: 0;
z-index: 50;
background: rgba(11, 17, 24, 0.86);
backdrop-filter: blur(12px);
border-bottom: 1px solid var(--border);
}
.nav-inner {
display: flex;
align-items: center;
gap: 14px;
height: 60px;
}
.brand {
display: flex;
align-items: center;
gap: 10px;
font-weight: 700;
font-size: 17px;
color: var(--text);
}
.brand:hover {
text-decoration: none;
}
.brand img {
width: 28px;
height: 28px;
border-radius: 7px;
}
.brand .tm {
font-size: 10px;
color: var(--text-3);
font-weight: 500;
margin-left: 2px;
}
.nav-links {
display: flex;
gap: 4px;
margin-left: auto;
align-items: center;
}
.nav-links a {
color: var(--text-2);
font-size: 14px;
padding: 7px 12px;
border-radius: 8px;
}
.nav-links a:hover {
color: var(--text);
background: var(--panel);
text-decoration: none;
}
.nav-cta {
background: var(--accent);
color: #06121c !important;
font-weight: 600;
padding: 7px 16px !important;
border-radius: 9px;
}
.nav-cta:hover {
background: #7cc0f5;
}
.nav-toggle {
display: none;
margin-left: auto;
background: none;
border: 1px solid var(--border);
color: var(--text-2);
border-radius: 8px;
padding: 6px 11px;
font-size: 16px;
cursor: pointer;
}
/* ── Hero ────────────────────────────────────────── */
.hero {
padding: 84px 0 64px;
}
.hero-grid {
display: grid;
grid-template-columns: 1.05fr 1fr;
gap: 52px;
align-items: center;
}
.hero h1 {
font-size: 44px;
line-height: 1.22;
letter-spacing: -0.5px;
margin: 18px 0 14px;
}
.hero h1 .hl {
color: var(--accent);
}
.hero .tagline {
color: var(--text-2);
font-size: 17px;
margin-bottom: 26px;
}
.hero-badges {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 30px;
}
.badge {
font-size: 12px;
color: var(--text-2);
border: 1px solid var(--border);
background: var(--panel);
padding: 4px 11px;
border-radius: 999px;
}
.badge b {
color: var(--green);
font-weight: 600;
}
.hero-ctas {
display: flex;
flex-wrap: wrap;
gap: 12px;
align-items: center;
}
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
border-radius: 10px;
padding: 11px 22px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
border: 1px solid transparent;
transition: all 0.15s ease;
}
.btn-primary {
background: var(--accent);
color: #06121c;
}
.btn-primary:hover {
background: #7cc0f5;
text-decoration: none;
transform: translateY(-1px);
}
.btn-ghost {
background: transparent;
color: var(--text);
border-color: var(--border);
}
.btn-ghost:hover {
border-color: var(--accent);
text-decoration: none;
}
.hero-note {
font-size: 12.5px;
color: var(--text-3);
margin-top: 14px;
}
/* Terminal mock (pure CSS, no fake screenshot) */
.term {
background: #0d1620;
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
box-shadow: 0 24px 70px rgba(0, 0, 0, 0.45);
}
.term-bar {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 14px;
background: #101b26;
border-bottom: 1px solid var(--border);
}
.term-dot {
width: 11px;
height: 11px;
border-radius: 50%;
}
.term-dot.r {
background: #ff5f57;
}
.term-dot.y {
background: #febc2e;
}
.term-dot.g {
background: #28c840;
}
.term-title {
font-size: 12px;
color: var(--text-3);
margin-left: 6px;
font-family: var(--mono);
}
.term-body {
padding: 16px 18px 18px;
font-family: var(--mono);
font-size: 12.8px;
line-height: 1.8;
}
.term-body .p {
color: var(--green);
}
.term-body .c {
color: var(--text);
}
.term-body .dim {
color: #93aab9;
}
.term-body .out {
color: #c3d2dd;
}
.term-body .err {
color: var(--red);
}
.term-body .mark {
color: var(--amber);
}
.cursor {
display: inline-block;
width: 8px;
height: 15px;
background: var(--green);
vertical-align: -2px;
animation: blink 1.1s steps(1) infinite;
}
@keyframes blink {
50% {
opacity: 0;
}
}
/* ── Sections ────────────────────────────────────── */
section {
padding: 74px 0;
}
.section-alt {
background: var(--bg-soft);
border-top: 1px solid var(--border);
border-bottom: 1px solid var(--border);
}
.section-head {
max-width: 660px;
margin-bottom: 40px;
}
.kicker {
color: var(--accent);
font-size: 13px;
font-weight: 700;
letter-spacing: 2.5px;
text-transform: uppercase;
}
.section-head h2 {
font-size: 30px;
margin: 10px 0 12px;
letter-spacing: -0.3px;
}
.section-head p {
color: var(--text-2);
font-size: 15.5px;
}
.grid {
display: grid;
gap: 18px;
}
.grid-3 {
grid-template-columns: repeat(3, 1fr);
}
.grid-2 {
grid-template-columns: repeat(2, 1fr);
}
.card {
background: var(--panel);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 22px 22px 20px;
transition:
border-color 0.15s ease,
transform 0.15s ease;
}
.card:hover {
border-color: rgba(85, 174, 242, 0.45);
transform: translateY(-2px);
}
.card .icon {
width: 38px;
height: 38px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 14px;
font-size: 19px;
}
.card h3 {
font-size: 16.5px;
margin-bottom: 8px;
}
.card p {
color: var(--text-2);
font-size: 13.8px;
}
.card .tag {
display: inline-block;
font-size: 11px;
padding: 2px 8px;
border-radius: 999px;
margin-left: 8px;
vertical-align: 2px;
}
.tag-free {
background: var(--green-soft);
color: var(--green);
}
.tag-pro {
background: var(--accent-soft);
color: var(--accent);
}
/* AI workflow */
.flow {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 14px;
margin: 34px 0 26px;
}
.flow-step {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 12px;
padding: 18px;
position: relative;
}
.flow-step .no {
font-family: var(--mono);
font-size: 12px;
color: var(--accent);
background: var(--accent-soft);
border-radius: 7px;
padding: 2px 8px;
display: inline-block;
margin-bottom: 10px;
}
.flow-step h4 {
font-size: 14.5px;
margin-bottom: 6px;
}
.flow-step p {
font-size: 12.8px;
color: var(--text-2);
}
.confirm-card {
background: #0d1620;
border: 1px solid var(--border);
border-radius: 12px;
font-family: var(--mono);
font-size: 13px;
overflow: hidden;
}
.confirm-head {
padding: 10px 16px;
border-bottom: 1px solid var(--border);
display: flex;
gap: 8px;
align-items: center;
font-size: 12px;
color: var(--text-3);
}
.risk-pill {
padding: 1px 9px;
border-radius: 999px;
font-size: 11px;
font-weight: 600;
}
.risk-mid {
background: rgba(245, 192, 78, 0.14);
color: var(--amber);
}
.risk-high {
background: rgba(248, 113, 113, 0.15);
color: var(--red);
}
.risk-safe {
background: var(--green-soft);
color: var(--green);
}
.confirm-cmd {
padding: 14px 16px;
color: var(--text);
word-break: break-all;
}
.confirm-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 0 16px 16px;
}
.confirm-actions span {
font-size: 12px;
padding: 5px 12px;
border-radius: 8px;
border: 1px solid var(--border);
color: var(--text-2);
}
.confirm-actions .danger {
border-color: rgba(248, 113, 113, 0.4);
color: var(--red);
}
.confirm-actions .primary {
background: var(--accent);
color: #06121c;
border-color: var(--accent);
font-weight: 600;
}
.confirm-actions .green {
border-color: rgba(63, 214, 143, 0.4);
color: var(--green);
}
.check-list {
list-style: none;
display: grid;
gap: 10px;
margin-top: 26px;
}
.check-list li {
display: flex;
gap: 10px;
font-size: 14px;
color: var(--text-2);
}
.check-list li::before {
content: '✓';
color: var(--green);
font-weight: 700;
flex-shrink: 0;
}
.check-list li b {
color: var(--text);
font-weight: 600;
}
/* Pricing */
.pricing {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 18px;
}
.plan {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 16px;
padding: 26px;
display: flex;
flex-direction: column;
}
.plan.featured {
border-color: rgba(85, 174, 242, 0.55);
background: linear-gradient(180deg, rgba(85, 174, 242, 0.08), var(--panel) 45%);
position: relative;
}
.plan .ribbon {
position: absolute;
top: -12px;
left: 24px;
background: var(--accent);
color: #06121c;
font-size: 11.5px;
font-weight: 700;
padding: 3px 12px;
border-radius: 999px;
}
.plan h3 {
font-size: 17px;
}
.plan .price {
font-size: 34px;
font-weight: 700;
margin: 12px 0 2px;
}
.plan .price small {
font-size: 14px;
color: var(--text-3);
font-weight: 400;
}
.plan .price-note {
font-size: 12.5px;
color: var(--text-3);
margin-bottom: 18px;
}
.plan ul {
list-style: none;
display: grid;
gap: 9px;
margin: 4px 0 24px;
flex: 1;
}
.plan ul li {
font-size: 13.8px;
color: var(--text-2);
display: flex;
gap: 9px;
}
.plan ul li::before {
content: '✓';
color: var(--green);
font-weight: 700;
flex-shrink: 0;
}
.plan ul li.locked::before {
content: '+';
color: var(--accent);
}
/* Security */
.sec-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 14px;
}
.sec-item {
display: flex;
gap: 14px;
background: var(--panel);
border: 1px solid var(--border);
border-radius: 12px;
padding: 18px;
}
.sec-item .ic {
font-size: 20px;
flex-shrink: 0;
}
.sec-item h4 {
font-size: 14.5px;
margin-bottom: 4px;
}
.sec-item p {
font-size: 13px;
color: var(--text-2);
}
/* Tech + download */
.tech-list {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.tech-list span {
font-family: var(--mono);
font-size: 13px;
background: var(--panel);
border: 1px solid var(--border);
padding: 7px 14px;
border-radius: 9px;
color: var(--text-2);
}
.dl-box {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 16px;
padding: 30px;
display: grid;
grid-template-columns: 1.4fr 1fr;
gap: 30px;
align-items: center;
}
.dl-box h3 {
font-size: 19px;
margin-bottom: 6px;
}
.dl-box .meta {
color: var(--text-3);
font-size: 13px;
margin-bottom: 18px;
}
.sha {
font-family: var(--mono);
font-size: 11.5px;
color: var(--text-3);
background: #0d1620;
border: 1px solid var(--border);
border-radius: 9px;
padding: 10px 12px;
word-break: break-all;
display: flex;
gap: 10px;
align-items: flex-start;
margin-top: 14px;
}
.sha button {
background: none;
border: none;
color: var(--accent);
cursor: pointer;
font-size: 11.5px;
flex-shrink: 0;
font-family: inherit;
}
.dl-note {
font-size: 12.5px;
color: var(--text-3);
margin-top: 14px;
}
.dl-app {
display: flex;
align-items: center;
gap: 18px;
justify-content: center;
}
.dl-app img {
width: 92px;
height: 92px;
}
.dl-app .os {
text-align: left;
}
.dl-app .os b {
display: block;
font-size: 15px;
}
.dl-app .os span {
color: var(--text-3);
font-size: 12.5px;
}
/* FAQ */
.faq-item {
border: 1px solid var(--border);
border-radius: 12px;
background: var(--panel);
margin-bottom: 10px;
overflow: hidden;
}
.faq-item summary {
cursor: pointer;
padding: 16px 20px;
font-size: 15px;
font-weight: 600;
list-style: none;
display: flex;
justify-content: space-between;
align-items: center;
}
.faq-item summary::-webkit-details-marker {
display: none;
}
.faq-item summary::after {
content: '+';
color: var(--text-3);
font-size: 18px;
}
.faq-item[open] summary::after {
content: '−';
}
.faq-item .answer {
padding: 0 20px 18px;
color: var(--text-2);
font-size: 14px;
}
/* Footer */
footer {
border-top: 1px solid var(--border);
padding: 40px 0 48px;
color: var(--text-3);
font-size: 13px;
}
.footer-inner {
display: flex;
flex-wrap: wrap;
gap: 24px;
justify-content: space-between;
}
.footer-brand {
display: flex;
align-items: center;
gap: 10px;
color: var(--text);
font-weight: 600;
}
.footer-brand img {
width: 22px;
height: 22px;
border-radius: 6px;
}
footer nav {
display: flex;
flex-wrap: wrap;
gap: 16px;
}
footer a {
color: var(--text-3);
}
footer a:hover {
color: var(--accent);
}
/* ── Responsive ──────────────────────────────────── */
@media (max-width: 960px) {
.hero-grid {
grid-template-columns: 1fr;
gap: 36px;
}
.hero h1 {
font-size: 34px;
}
.grid-3 {
grid-template-columns: repeat(2, 1fr);
}
.flow {
grid-template-columns: repeat(2, 1fr);
}
.pricing {
grid-template-columns: 1fr;
}
.dl-box {
grid-template-columns: 1fr;
}
}
@media (max-width: 640px) {
.nav-links {
display: none;
position: absolute;
top: 60px;
left: 0;
right: 0;
background: var(--bg-soft);
border-bottom: 1px solid var(--border);
flex-direction: column;
align-items: stretch;
padding: 10px 18px 16px;
gap: 2px;
}
.nav-links.open {
display: flex;
}
.nav-toggle {
display: block;
}
.grid-3,
.grid-2,
.sec-grid {
grid-template-columns: 1fr;
}
.flow {
grid-template-columns: 1fr;
}
section {
padding: 56px 0;
}
}
+63
View File
@@ -0,0 +1,63 @@
// Package site 提供产品官网(嵌入 HTML/CSS/JS)。
package site
import (
"embed"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
//go:embed template.html assets
var files embed.FS
type Handler struct {
html string
}
func New() *Handler {
raw, err := files.ReadFile("template.html")
if err != nil {
panic("site: template.html 缺失: " + err.Error())
}
return &Handler{html: string(raw)}
}
func (h *Handler) Register(r *gin.Engine) {
r.GET("/", h.Page)
r.GET("/home", h.Page)
r.GET("/index.html", h.Page)
r.GET("/site/assets/*file", h.Asset)
}
func (h *Handler) Page(c *gin.Context) {
c.Header("Content-Type", "text/html; charset=utf-8")
c.Header("Cache-Control", "no-cache")
_, _ = c.Writer.WriteString(h.html)
}
func (h *Handler) Asset(c *gin.Context) {
name := strings.TrimPrefix(c.Param("file"), "/")
if strings.Contains(name, "..") {
c.Status(http.StatusBadRequest)
return
}
data, err := files.ReadFile("assets/" + name)
if err != nil {
c.Status(http.StatusNotFound)
return
}
switch {
case strings.HasSuffix(name, ".css"):
c.Header("Content-Type", "text/css; charset=utf-8")
case strings.HasSuffix(name, ".js"):
c.Header("Content-Type", "application/javascript; charset=utf-8")
case strings.HasSuffix(name, ".svg"):
c.Header("Content-Type", "image/svg+xml")
default:
c.Header("Content-Type", "application/octet-stream")
}
c.Header("Cache-Control", "public, max-age=3600")
c.Data(http.StatusOK, "", data)
}
+391
View File
@@ -0,0 +1,391 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>OpsXShell — 轻量原生运维终端</title>
<meta name="description" content="OpsXShell 是基于 Tauri 2 与 Rust 的轻量原生运维终端:SSH 多标签分屏、SFTP 双栏、端口转发、RDP/VNC 远程桌面、串口与终端 AI 助手。便捷、人性化、不做堡垒机。" />
<link rel="icon" type="image/svg+xml" href="/site/assets/icon.svg" />
<link rel="stylesheet" href="/site/assets/style.css" />
</head>
<body>
<!-- ── Nav ─────────────────────────────────────── -->
<header class="nav">
<div class="container nav-inner">
<a class="brand" href="#top">
<img src="/site/assets/icon.svg" alt="OpsXShell 图标" />
OpsXShell<span class="tm">v2.1.1</span>
</a>
<button class="nav-toggle" aria-label="打开菜单">☰</button>
<nav class="nav-links">
<a href="#features">功能</a>
<a href="#ai">终端 AI</a>
<a href="#pricing">价格</a>
<a href="#security">安全</a>
<a href="#download">下载</a>
<a href="/docs">文档</a>
<a href="#faq">FAQ</a>
<a class="nav-cta" href="#download">免费下载</a>
</nav>
</div>
</header>
<!-- ── Hero ────────────────────────────────────── -->
<div id="top"></div>
<section class="hero">
<div class="container hero-grid">
<div>
<span class="kicker">Tauri 2 · Rust · 原生桌面</span>
<h1>连上就能干活的<br /><span class="hl">轻量运维终端</span></h1>
<p class="tagline">
终端、传文件、转发、远程桌面——短路径直达。没有插件市场,没有审批流,
打开就用。
</p>
<div class="hero-badges">
<span class="badge"><b>●</b> SSH / SFTP</span>
<span class="badge"><b>●</b> 端口转发 / SOCKS5</span>
<span class="badge"><b>●</b> RDP / VNC</span>
<span class="badge"><b>●</b> 串口</span>
<span class="badge"><b>●</b> 7 语种界面</span>
</div>
<div class="hero-ctas">
<a class="btn btn-primary" href="/download/OpsXShell_2.1.1_aarch64.dmg">
⬇ 下载 macOS 版(Apple Silicon)
</a>
<a class="btn btn-ghost" href="#features">看看能做什么</a>
</div>
<p class="hero-note">v2.1.1 · 13 MB · 免费层即可使用 SSH 终端、SFTP 与本地终端</p>
</div>
<div class="term" aria-hidden="true">
<div class="term-bar">
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
<span class="term-title">opsxshell — root@web-prod ~ │ 分屏 1/2</span>
</div>
<div class="term-body">
<div><span class="p">➜</span> <span class="c">ssh dev@10.0.0.8</span></div>
<div class="out">Host key verified via ~/.ssh/known_hosts ✓</div>
<div><span class="p">➜</span> <span class="c">df -h /var && systemctl status nginx --no-pager</span></div>
<div class="out">/dev/sda1 98G 41G 53G 44% /var</div>
<div class="out">● nginx.service - active (running)</div>
<div><span class="mark">▶ 转发</span> <span class="dim">socks5://127.0.0.1:1080 经 web-prod 出口已就绪</span></div>
<div><span class="mark">▶ SFTP</span> <span class="dim">deploy.zip 上传完成 · 断点续传 · 原子替换</span></div>
<div><span class="mark">▶ AI</span> <span class="c">总结刚才的部署输出</span><span class="cursor"></span></div>
</div>
</div>
</div>
</section>
<!-- ── Features ────────────────────────────────── -->
<section id="features" class="section-alt">
<div class="container">
<div class="section-head">
<span class="kicker">核心能力</span>
<h2>一个窗口,覆盖日常远程运维</h2>
<p>SSH、SFTP、转发、远程桌面、串口和本地终端共用一个多标签工作区;常用流程保持简短,高级配置默认收起。</p>
</div>
<div class="grid grid-3">
<div class="card">
<div class="icon" style="background:rgba(85,174,242,.14)">⌨️</div>
<h3>SSH 终端与会话管理</h3>
<p>密码 / 私钥 / SSH Agent 自动尝试,主机指纹走 <code>~/.ssh/known_hosts</code> 校验。会话分组、多标签、最多 4 分屏、命令片段、<code>ssh://</code> 链接唤起快速连接。</p>
<span class="tag tag-free">免费</span>
</div>
<div class="card">
<div class="icon" style="background:rgba(167,139,250,.14)">📁</div>
<h3>SFTP 双栏文件管理</h3>
<p>本地 / 远程双栏拖拽互传、断点续传、上传完成前原子替换、重命名与权限修改;远程文本直接编辑(约 2MB 上限)。</p>
<span class="tag tag-free">免费</span>
</div>
<div class="card">
<div class="icon" style="background:rgba(63,214,143,.13)">🔗</div>
<h3>端口转发</h3>
<p>本地 / 远程 / 动态 SOCKS5 转发,规则与运行实例分离;状态栏「转发」抽屉统一管理,断线自动保留未停止的 Listener。</p>
<span class="tag tag-pro">专业版</span>
</div>
<div class="card">
<div class="icon" style="background:rgba(245,192,78,.13)">🖥️</div>
<h3>远程桌面 RDP / VNC</h3>
<p>原生 IronRDP 与 VNC 会话,帧与输入经 Tauri 事件直达 Canvas;运行态只在内存,断开即清理。</p>
<span class="tag tag-pro">专业版</span>
</div>
<div class="card">
<div class="icon" style="background:rgba(85,174,242,.14)">📤</div>
<h3>批量执行与广播输入</h3>
<p>多主机批量命令结果只在当前窗口汇总;同窗多分屏广播同步输入,敲一遍、处处生效(桌面会话除外)。</p>
<span class="tag tag-pro">专业版</span>
</div>
<div class="card">
<div class="icon" style="background:rgba(63,214,143,.13)">🔩</div>
<h3>本地终端与串口</h3>
<p>系统 zsh / bash / sh 本地终端开箱即用;串口终端适配路由器、交换机与开发板调试。HTTP CONNECT / SOCKS5 连接代理。</p>
<span class="tag tag-free">本地终端免费</span><span class="tag tag-pro">串口专业版</span>
</div>
</div>
</div>
</section>
<!-- ── AI ──────────────────────────────────────── -->
<section id="ai">
<div class="container">
<div class="section-head">
<span class="kicker">终端 AI 助手 · 专业版</span>
<h2>先看上下文,再提建议,执行权始终在你手里</h2>
<p>AI 只复用当前活跃的 SSH 连接,不建立独立凭据边界;最多 8 步执行循环,五级风险评级,危险命令硬拒绝并在 Rust 侧二次校验。</p>
</div>
<div class="flow">
<div class="flow-step">
<span class="no">01</span>
<h4>读取终端上下文</h4>
<p>以选中文本或当前视口为起点,Agent 优先提出只读命令收集证据,不凭空猜测环境。</p>
</div>
<div class="flow-step">
<span class="no">02</span>
<h4>风险分级确认</h4>
<p>严格 / 宽松两档模式;五级风险(安全 → 禁止执行),critical 永远直接拒绝。</p>
</div>
<div class="flow-step">
<span class="no">03</span>
<h4>你决定怎么执行</h4>
<p>拒绝、只插入不执行、本次执行、本次+始终允许——四种选择都由你点击。</p>
</div>
<div class="flow-step">
<span class="no">04</span>
<h4>结果与小结</h4>
<p>命令真实输出进入下一轮上下文;一键「总结输出」从长日志里提炼要点。</p>
</div>
</div>
<div class="confirm-card" aria-hidden="true">
<div class="confirm-head">
<span>Agent 第 2/8 轮</span>
<span class="risk-pill risk-high">高风险</span>
<span style="margin-left:auto">宽松模式</span>
</div>
<div class="confirm-cmd">$ systemctl restart nginx</div>
<div class="confirm-actions">
<span class="danger">✕ 拒绝</span>
<span class="green">⎘ 插入不执行</span>
<span class="primary">▶ 本次执行</span>
<span>∞ 本次+始终</span>
</div>
</div>
<ul class="check-list">
<li><b>不保存命令与对话历史</b>——AI 上下文只存在于当前窗口会话内存中。</li>
<li><b>供应商自选</b>——OpenAI / Anthropic 兼容接口、new-api、本地 Ollama 均可,API Key 经 AES-256-GCM 加密落盘。</li>
<li><b>可选「全部确认」</b>——开启后除只读白名单命令外,每一条都需你手动放行。</li>
<li><b>随时停止</b>——生成或执行中一键取消,远端命令同步中止。</li>
</ul>
</div>
</section>
<!-- ── Pricing ─────────────────────────────────── -->
<section id="pricing" class="section-alt">
<div class="container">
<div class="section-head">
<span class="kicker">价格</span>
<h2>免费够用,专业版解锁重活</h2>
<p>许可证绑定设备而非订阅账号;离线许可证(LICOFF-)支持完全离线激活。</p>
</div>
<div class="pricing">
<div class="plan">
<h3>免费</h3>
<div class="price">¥0</div>
<div class="price-note">日常 SSH 运维完全够用</div>
<ul>
<li>SSH 终端(密码 / 私钥 / Agent)</li>
<li>SFTP 双栏文件管理</li>
<li>本地终端(zsh / bash / sh)</li>
<li>会话管理与 <code>~/.ssh/config</code> 导入</li>
<li>7 语种界面</li>
</ul>
<a class="btn btn-ghost" href="/download/OpsXShell_2.1.1_aarch64.dmg">免费下载</a>
</div>
<div class="plan featured">
<span class="ribbon">推荐</span>
<h3>专业版 · 包年</h3>
<div class="price">¥99 <small>/ 年</small></div>
<div class="price-note">2 台设备</div>
<ul>
<li class="locked">端口转发(本地 / 远程 / SOCKS5)</li>
<li class="locked">终端 AI 助手</li>
<li class="locked">批量执行 / 广播同步</li>
<li class="locked">远程桌面 RDP / VNC</li>
<li class="locked">串口终端与连接代理</li>
<li class="locked">主机清单批量导入导出</li>
</ul>
<a class="btn btn-primary" href="#faq">应用内升级</a>
</div>
<div class="plan">
<h3>专业版 · 永久</h3>
<div class="price">¥199</div>
<div class="price-note">5 台设备 · 终身授权</div>
<ul>
<li>包含包年全部功能</li>
<li>一次买断,无续费</li>
<li>离线许可证支持</li>
<li>后续版本持续可用</li>
</ul>
<a class="btn btn-ghost" href="#faq">应用内升级</a>
</div>
</div>
</div>
</section>
<!-- ── Security ────────────────────────────────── -->
<section id="security">
<div class="container">
<div class="section-head">
<span class="kicker">安全边界</span>
<h2>凭据加密落盘,运行态只在内存</h2>
<p>产品聚焦「日常远程干活」:审计、录像、审批等堡垒机能力刻意不做——宁可少而顺,不做重型平台。</p>
</div>
<div class="sec-grid">
<div class="sec-item">
<span class="ic">🔐</span>
<div>
<h4>AES-256-GCM 本地加密</h4>
<p>会话密码、私钥口令、模型 API Key 全部加密后落盘;主密钥固定在 <code>~/.opsxshell/master.key</code>,目录 0700、文件 0600。</p>
</div>
</div>
<div class="sec-item">
<span class="ic">🧾</span>
<div>
<h4>known_hosts 主机指纹</h4>
<p>首次连接指纹确认,后续变更即告警;不静默接受陌生主机。</p>
</div>
</div>
<div class="sec-item">
<span class="ic">🕶️</span>
<div>
<h4>不留命令历史</h4>
<p>不保存终端命令历史;批量命令结果与 AI 对话只保留在当前窗口。</p>
</div>
</div>
<div class="sec-item">
<span class="ic">⚡</span>
<div>
<h4>运行态不落盘</h4>
<p>转发 Listener、RDP / VNC worker 的运行态只存在原生侧内存;界面的「停止」关闭的是真实原生资源。</p>
</div>
</div>
</div>
</div>
</section>
<!-- ── Tech ────────────────────────────────────── -->
<section class="section-alt">
<div class="container">
<div class="section-head">
<span class="kicker">技术栈</span>
<h2>原生性能,跨平台内核</h2>
</div>
<div class="tech-list">
<span>Tauri 2</span><span>Rust 2021</span><span>ssh2 / libssh2</span><span>IronRDP</span>
<span>vnc-rs</span><span>portable-pty</span><span>serialport</span><span>React 18</span>
<span>TypeScript 5</span><span>Zustand</span><span>XTerm.js</span><span>i18next · 7 语种</span>
</div>
</div>
</section>
<!-- ── Download ────────────────────────────────── -->
<section id="download">
<div class="container">
<div class="section-head">
<span class="kicker">下载</span>
<h2>拿走即用</h2>
</div>
<div class="dl-box">
<div>
<h3>OpsXShell v2.1.1 · macOS(Apple Silicon)</h3>
<p class="meta">.dmg 安装包 · 13 MB · macOS 13+ · 2026-09-02 构建</p>
<a class="btn btn-primary" href="/download/OpsXShell_2.1.1_aarch64.dmg">⬇ 下载 OpsXShell_2.1.1_aarch64.dmg</a>
<div class="sha">
<span>
SHA-256<br />
423828c8d19cd77d1aceda80ccbb82d0e5155d27d559df5a8ff0985dca0f033
</span>
<button data-copy="423828c8d19cd77d1aceda80ccbb82d0e5155d27d559df5a8ff0985dca0f033">复制</button>
</div>
<p class="dl-note">
当前版本未做开发者签名与公证:首次打开请右键点击 .app →「打开」,或在系统设置中允许。
Windows 与 Linux 版本将在对应平台实机验收后发布。
</p>
</div>
<div class="dl-app">
<img src="/site/assets/icon.svg" alt="OpsXShell 应用图标" />
<div class="os">
<b>macOS 13+</b>
<span>Apple Silicon(M1/M2/M3/M4)</span>
</div>
</div>
</div>
</div>
</section>
<!-- ── FAQ ─────────────────────────────────────── -->
<section id="faq" class="section-alt">
<div class="container">
<div class="section-head">
<span class="kicker">FAQ</span>
<h2>常见问题</h2>
</div>
<details class="faq-item">
<summary>OpsXShell 会保存我的 SSH 密码吗?</summary>
<div class="answer">会话密码、私钥口令经 AES-256-GCM 加密后保存在本机应用数据目录,主密钥单独存放于 <code>~/.opsxshell/master.key</code>(目录 0700 / 文件 0600)。不上传任何凭据;也支持完全不保存密码的 SSH Agent 认证。</div>
</details>
<details class="faq-item">
<summary>AI 会自动执行命令吗?</summary>
<div class="answer">默认按「严格 / 宽松」两档确认:宽松模式下仅高风险命令需要确认,严格模式中风险起确认。危险命令(如 <code>rm -rf /</code>)无论什么模式都直接拒绝,且 Rust 侧二次校验。你还可以开启「所有命令都需确认」,或在确认卡上选择「只插入不执行」。</div>
</details>
<details class="faq-item">
<summary>支持 <code>~/.ssh/config</code> 和跳板机吗?</summary>
<div class="answer">支持导入 <code>~/.ssh/config</code> 常用字段;SSH 代理 / 跳板(HTTP CONNECT、SOCKS5)属于专业版能力。</div>
</details>
<details class="faq-item">
<summary>免费版和专业版差在哪?</summary>
<div class="answer">免费版包含 SSH 终端、SFTP、本地终端与会话管理。专业版解锁端口转发、连接代理、终端 AI、批量执行 / 广播、RDP / VNC 远程桌面、串口与主机清单批量导入导出。包年 ¥99(2 台设备)或永久 ¥199(5 台设备),均在应用内升级。</div>
</details>
<details class="faq-item">
<summary>支持 Windows / Linux 吗?</summary>
<div class="answer">内核(Tauri 2 + Rust)面向 macOS / Windows / Linux 跨平台设计;安装包按「目标系统实机构建并验收」的原则发布。当前提供 macOS Apple Silicon 包,Windows 与 Linux 版在对应平台完成实机验收后上线。</div>
</details>
<details class="faq-item">
<summary>数据保存在哪里?</summary>
<div class="answer">会话与设置保存在系统应用数据目录的 <code>opsxshell-data.json</code>;终端标签布局持久化,但连接、转发、远程桌面等运行态只在内存,应用重启后按需重连。</div>
</details>
</div>
</section>
<!-- ── Footer ──────────────────────────────────── -->
<footer>
<div class="container footer-inner">
<div>
<div class="footer-brand">
<img src="/site/assets/icon.svg" alt="" />
OpsXShell
</div>
<p style="margin-top:8px">轻量运维终端 · 便捷、人性化、操作简单实用</p>
</div>
<nav>
<a href="#features">功能</a>
<a href="#ai">终端 AI</a>
<a href="#pricing">价格</a>
<a href="#security">安全</a>
<a href="#download">下载</a>
<a href="/docs">文档</a>
<a href="#faq">FAQ</a>
</nav>
</div>
<div class="container" style="margin-top:22px">
<p>© 2026 OpsXShell · 不做审计、审批与堡垒机——宁可少而顺。</p>
</div>
</footer>
<script src="/site/assets/main.js"></script>
</body>
</html>