# 从零部署自己的中心

这是 Linux x86_64 + systemd 的独立部署示例，供安装者明确选择“部署自己的中心”后由 AI 按实际环境执行。域名、端口、目录和服务账号必须先确认；现有同名服务/目录存在时走更新流程，不能覆盖。Windows 电脑可以作为连接器；本页不宣称提供 Windows 服务管理器安装方案。

## 1. 准备程序、账号与空目录

在安装者授权的目标服务器解压并校验公开包。先确认 18081 端口空闲，`/opt/sshmcp-center`、`/var/lib/sshmcp-center`、`/etc/sshmcp-center` 和 `sshmcp-center.service` 没有被其他部署使用，再从包根目录执行：

```bash
sudo useradd --system --user-group --home-dir /var/lib/sshmcp-center --shell /usr/sbin/nologin sshmcp-center
sudo install -d -m 755 /opt/sshmcp-center/bin /opt/sshmcp-center/libexec
sudo install -m 755 linux-x86_64/mcp-ssh-manager /opt/sshmcp-center/bin/mcp-ssh-manager
sudo install -m 755 linux-x86_64/sshmcp-file-agent /opt/sshmcp-center/libexec/sshmcp-file-agent
sudo install -d -m 700 -o sshmcp-center -g sshmcp-center /var/lib/sshmcp-center
sudo install -d -m 700 -o sshmcp-center -g sshmcp-center /var/lib/sshmcp-center/config /var/lib/sshmcp-center/credentials /var/lib/sshmcp-center/.ssh
sudo install -m 600 -o sshmcp-center -g sshmcp-center config/ssh-config.empty.toml /var/lib/sshmcp-center/config/ssh-config.toml
sudo install -d -m 700 /etc/sshmcp-center
```

账号已经存在时先核对归属，不重复创建；发行版没有 useradd/systemd 时按本机服务管理器适配，不机械执行。本例开始时没有任何真实服务器或私钥。

## 2. 生成自己的接入 Key，不回显

把下面域名替换成你自己的 HTTPS 中心域名。Python 只用标准库；以独占创建写文件，已存在文件会停止，不轮换旧 Key。

```bash
sudo python3 - https://sshmcp.example.com <<'PY'
import json, os, secrets, sys
from pathlib import Path
os.umask(0o077)
root = Path('/etc/sshmcp-center')
names = ['service.env', 'API_KEY', 'ENDPOINT', 'connection.json']
if any((root / name).exists() for name in names):
    raise SystemExit('Existing enrollment data: preserve it and use the update workflow.')
key = secrets.token_urlsafe(32)
endpoint = sys.argv[1]
values = {
    'service.env': 'MCP_HTTP_BEARER_TOKEN=' + key + '\nMCP_CATALOG_EXPORT=true\nSSH_CONFIG_PATH=/var/lib/sshmcp-center/config/ssh-config.toml\nSSH_ENV_PATH=/var/lib/sshmcp-center/config/standalone.env\nMCP_SSH_MANAGER_FILE_AGENT_BINARY=/opt/sshmcp-center/libexec/sshmcp-file-agent\n',
    'API_KEY': key + '\n',
    'ENDPOINT': endpoint + '\n',
    'connection.json': json.dumps({'endpoint': endpoint, 'api_key': key}) + '\n'
}
for name, value in values.items():
    with (root / name).open('x') as file:
        file.write(value)
print('Private center enrollment files created; no key was printed.')
PY
```

这些文件是中心所有者的私密资料。不要把 service.env、API_KEY 或 connection.json 放到网页目录、日志或公开包。中心权威 TOML 与其引用的 SSH 私钥按设计保存在服务器磁盘，权限受限；只有连接器收到的 SSH 身份采用仅内存策略。

## 3. 安装并启动服务

将以下内容保存为新的 `/etc/systemd/system/sshmcp-center.service`；现有同名 unit 必须先审查，不能覆盖：

```ini
[Unit]
Description=Private SSHMCP center
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=sshmcp-center
Group=sshmcp-center
WorkingDirectory=/var/lib/sshmcp-center
Environment=HOME=/var/lib/sshmcp-center
EnvironmentFile=/etc/sshmcp-center/service.env
ExecStart=/opt/sshmcp-center/bin/mcp-ssh-manager --http --bind 127.0.0.1:18081
Restart=on-failure
RestartSec=3
UMask=0077
NoNewPrivileges=yes
ProtectHome=yes
ProtectSystem=strict
ReadWritePaths=/var/lib/sshmcp-center
PrivateTmp=yes

[Install]
WantedBy=multi-user.target
```

```bash
sudo systemd-analyze verify /etc/systemd/system/sshmcp-center.service
sudo systemctl daemon-reload
sudo systemctl enable --now sshmcp-center.service
curl --fail http://127.0.0.1:18081/healthz
sudo systemctl show sshmcp-center.service -p ActiveState -p NRestarts
```

Type=simple 的启动返回早于端口就绪时，在最多 10 秒内重试只读 health，再做后续验证。不要把不带 Key 的 health 成功当作鉴权成功。

## 4. 配置自己的 HTTPS 入口

把域名 DNS 指向自己的服务器，按已有网络规则开放对应 HTTPS 入口，使用已有反向代理或依其官方安装文档安装。不要覆盖其他站点。以 Caddy 为例，追加独立站点块，域名替换为自己的：

```caddyfile
sshmcp.example.com {
    @foreign_origin {
        header Origin *
        not header Origin https://sshmcp.example.com
    }
    respond @foreign_origin "Forbidden" 403

    @api path /mcp /mcp/* /v1/catalog /v1/catalog/control
    handle @api {
        header Cache-Control "no-store"
        reverse_proxy 127.0.0.1:18081 {
            header_up Host 127.0.0.1:18081
            # Origin was checked at this edge; the upstream has a loopback host.
            header_up -Origin
        }
    }
    handle {
        respond "Not found" 404
    }
}
```

模板采用 Caddy 官方的[请求匹配](https://caddyserver.com/docs/caddyfile/matchers)和[反向代理头处理](https://caddyserver.com/docs/caddyfile/directives/reverse_proxy)。对实际配置执行 `caddy validate` 成功后才 reload，保留证书申请/续期所需网络；如果前面还有 CDN，私有 API 必须绕过缓存并保留鉴权。此示例不自动配置 CDN、DNS 或其他站点。

## 5. 验证并接入电脑

先验证缺失和错误 Key 被拒绝、合法 Key 返回 `sshmcp-device-catalog-v1`、Cache-Control 为 no-store、非法 Origin 为 403。认证请求由 AI 使用受限文件读 Key 放入 HTTP Authorization，不能把它写进命令行值或输出目录正文。

通过自己的私密文件传输方式，将 `/etc/sshmcp-center/API_KEY` 与 ENDPOINT（或 connection.json）送到自己可信电脑的私有目录。然后用 [CENTER.md](CENTER.md) 的原生 install 命令，把 `--key-file` 指向这份私密 API_KEY，并仅选择安装者已选客户端。不要把服务器环境文件当安装包公开分发。

本机安装收据通过并重载后，用 ssh_server_manage 添加自己的目标。中心上被引用的 SSH 私钥应归服务账号可读，放在 `/var/lib/sshmcp-center/credentials/` 等稳定私密目录；目录不能引用将被删除的测试路径。目标地址/原跳板必须从接入电脑可用。

完成一个目标的只读登录、两连接器唯一条目同步，以及本机文件传输后，再按 [PRIVATE_BUNDLES.md](PRIVATE_BUNDLES.md) 从自己保存的 connection.json 制作免输入私人包。备份/更新/迁移/撤销流程统一见 [WORKFLOWS.md](WORKFLOWS.md)。
