From cbcccc570e5c4469bc9c7ef5a56478a23d7d117a Mon Sep 17 00:00:00 2001
From: gdz66601
Date: Mon, 21 Sep 2026 03:40:03 +0800
Subject: [PATCH] =?UTF-8?q?Revert=20"=E7=BB=9F=E4=B8=80=20SSH=20=E5=B7=A5?=
=?UTF-8?q?=E4=BD=9C=E5=8F=B0=E4=B8=8E=E4=B8=BB=E7=95=8C=E9=9D=A2=E8=A7=86?=
=?UTF-8?q?=E8=A7=89=EF=BC=8C=E9=9A=94=E7=A6=BB=E4=B8=BB=E9=A2=98=E9=80=82?=
=?UTF-8?q?=E9=85=8D=E5=B1=82"?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This reverts commit 68e0ac1588c426a723341e3d2639c0488cf252b0.
---
.github/workflows/deploy.yml | 21 +-
.github/workflows/docs.yml | 45 -
.gitignore | 3 -
.hallmark/log.json | 9 -
DEPLOYMENT.md | 115 +-
README.md | 75 +-
docs/.vitepress/config.ts | 98 -
.../components/ScreenshotPlaceholder.vue | 29 -
docs/.vitepress/theme/index.ts | 11 -
docs/.vitepress/theme/style.css | 499 ----
docs/.vitepress/theme/tokens.css | 75 -
docs/ZERO_TRUST.md | 22 +-
docs/deploy/actions.md | 186 --
docs/deploy/api-token.md | 62 -
docs/deploy/runtime-secrets.md | 71 -
docs/deploy/troubleshooting.md | 80 -
docs/deploy/verification.md | 87 -
docs/deploy/zero-trust.md | 103 -
docs/development/contributing.md | 42 -
docs/development/docs-site.md | 59 -
docs/development/local.md | 61 -
docs/development/workspace-theme.md | 22 -
docs/guide/overview.md | 48 -
docs/guide/requirements.md | 61 -
docs/index.md | 63 -
docs/public/images/edgessh-dashboard-demo.png | Bin 206997 -> 0 bytes
docs/public/screenshots/.gitkeep | 1 -
.../public/screenshots/01-fork-repository.png | Bin 9754 -> 0 bytes
.../02-cloudflare-api-token-permissions.png | Bin 83120 -> 0 bytes
.../03-github-actions-variables.png | Bin 55232 -> 0 bytes
.../screenshots/04-run-deploy-workflow.png | Bin 10578 -> 0 bytes
.../screenshots/08-github-actions-secrets.png | Bin 55232 -> 0 bytes
docs/reference/configuration.md | 56 -
docs/reference/limits.md | 41 -
docs/reference/screenshots.md | 52 -
docs/reference/security.md | 73 -
docs/usage/files-and-processes.md | 44 -
docs/usage/hosts.md | 37 -
docs/usage/terminal.md | 46 -
docs/wrangler.toml | 3 -
frontend/index.html | 10 +-
frontend/src/main.ts | 1 -
frontend/src/workspace-theme.css | 242 --
package-lock.json | 2008 +----------------
package.json | 13 +-
scripts-tmp-local-index.html | 613 -----
scripts/prepare-deployment.mjs | 143 --
scripts/prepare-pages-project.mjs | 92 -
scripts/sync-worker-secrets.mjs | 66 -
src/accounts/crypto.ts | 29 +-
src/accounts/hosts.ts | 11 +-
src/accounts/http.ts | 15 +-
tests/deployment.test.ts | 105 -
tests/encryption.test.ts | 29 +-
tests/hosts.test.ts | 146 --
tests/pages-deployment.test.ts | 56 -
tests/workspace-theme.test.ts | 33 -
wrangler.toml | 6 +-
58 files changed, 117 insertions(+), 5801 deletions(-)
delete mode 100644 .github/workflows/docs.yml
delete mode 100644 .hallmark/log.json
delete mode 100644 docs/.vitepress/config.ts
delete mode 100644 docs/.vitepress/theme/components/ScreenshotPlaceholder.vue
delete mode 100644 docs/.vitepress/theme/index.ts
delete mode 100644 docs/.vitepress/theme/style.css
delete mode 100644 docs/.vitepress/theme/tokens.css
delete mode 100644 docs/deploy/actions.md
delete mode 100644 docs/deploy/api-token.md
delete mode 100644 docs/deploy/runtime-secrets.md
delete mode 100644 docs/deploy/troubleshooting.md
delete mode 100644 docs/deploy/verification.md
delete mode 100644 docs/deploy/zero-trust.md
delete mode 100644 docs/development/contributing.md
delete mode 100644 docs/development/docs-site.md
delete mode 100644 docs/development/local.md
delete mode 100644 docs/development/workspace-theme.md
delete mode 100644 docs/guide/overview.md
delete mode 100644 docs/guide/requirements.md
delete mode 100644 docs/index.md
delete mode 100644 docs/public/images/edgessh-dashboard-demo.png
delete mode 100644 docs/public/screenshots/.gitkeep
delete mode 100644 docs/public/screenshots/01-fork-repository.png
delete mode 100644 docs/public/screenshots/02-cloudflare-api-token-permissions.png
delete mode 100644 docs/public/screenshots/03-github-actions-variables.png
delete mode 100644 docs/public/screenshots/04-run-deploy-workflow.png
delete mode 100644 docs/public/screenshots/08-github-actions-secrets.png
delete mode 100644 docs/reference/configuration.md
delete mode 100644 docs/reference/limits.md
delete mode 100644 docs/reference/screenshots.md
delete mode 100644 docs/reference/security.md
delete mode 100644 docs/usage/files-and-processes.md
delete mode 100644 docs/usage/hosts.md
delete mode 100644 docs/usage/terminal.md
delete mode 100644 docs/wrangler.toml
delete mode 100644 frontend/src/workspace-theme.css
delete mode 100644 scripts-tmp-local-index.html
delete mode 100644 scripts/prepare-deployment.mjs
delete mode 100644 scripts/prepare-pages-project.mjs
delete mode 100644 scripts/sync-worker-secrets.mjs
delete mode 100644 tests/deployment.test.ts
delete mode 100644 tests/hosts.test.ts
delete mode 100644 tests/pages-deployment.test.ts
delete mode 100644 tests/workspace-theme.test.ts
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index eeae2b1..a86dff5 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -4,10 +4,6 @@ on:
push:
branches:
- main
- paths-ignore:
- - docs/**
- - .github/workflows/docs.yml
- - scripts/prepare-pages-project.mjs
workflow_dispatch:
concurrency:
@@ -25,12 +21,6 @@ jobs:
env:
CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
- ACCESS_TEAM_DOMAIN: ${{ secrets.ACCESS_TEAM_DOMAIN }}
- ACCESS_AUD: ${{ secrets.ACCESS_AUD }}
- ENCRYPTION_KEY: ${{ secrets.ENCRYPTION_KEY }}
- WORKER_NAME: ${{ vars.WORKER_NAME }}
- D1_DATABASE_NAME: ${{ vars.D1_DATABASE_NAME }}
- CUSTOM_DOMAIN: ${{ vars.CUSTOM_DOMAIN }}
steps:
- name: Check out repository
uses: actions/checkout@v4
@@ -47,11 +37,8 @@ jobs:
- name: Check project
run: npm run check
- - name: Prepare Cloudflare resources
- run: npm run prepare:deploy
+ - name: Apply D1 migrations
+ run: npx wrangler d1 migrations apply DB --remote
- - name: Sync Worker secrets
- run: npm run deploy:secrets
-
- - name: Migrate database and deploy Worker
- run: npm run deploy:worker
+ - name: Deploy Worker
+ run: npm run deploy
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
deleted file mode 100644
index df3e292..0000000
--- a/.github/workflows/docs.yml
+++ /dev/null
@@ -1,45 +0,0 @@
-name: Deploy docs
-
-on:
- push:
- branches:
- - main
- paths:
- - docs/**
- - package.json
- - package-lock.json
- - scripts/prepare-pages-project.mjs
- - .github/workflows/docs.yml
- workflow_dispatch:
-
-concurrency:
- group: docs-pages-deploy
- cancel-in-progress: false
-
-permissions:
- contents: read
-
-jobs:
- deploy:
- name: Deploy VitePress to Cloudflare Pages
- runs-on: ubuntu-latest
- timeout-minutes: 10
- env:
- CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
- CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
- DOCS_PROJECT_NAME: ${{ vars.DOCS_PROJECT_NAME }}
- steps:
- - name: Check out repository
- uses: actions/checkout@v4
-
- - name: Set up Node.js
- uses: actions/setup-node@v4
- with:
- node-version: 22.12.0
- cache: npm
-
- - name: Install dependencies
- run: npm ci
-
- - name: Build and deploy documentation
- run: npm run deploy:docs
diff --git a/.gitignore b/.gitignore
index 10afa8c..69b8cd0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,6 @@
node_modules/
dist/
.wrangler/
-docs/.vitepress/cache/
-docs/.vitepress/dist/
-wrangler.deploy.toml
coverage/
*.log
.dev.vars
diff --git a/.hallmark/log.json b/.hallmark/log.json
deleted file mode 100644
index eca26b8..0000000
--- a/.hallmark/log.json
+++ /dev/null
@@ -1,9 +0,0 @@
-[
- {
- "date": "2026-09-20",
- "macrostructure": "Workbench",
- "theme": "Coral",
- "enrichment": "real product capture",
- "brief": "EdgeSSH VitePress deployment documentation"
- }
-]
diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md
index 6de89c0..b86e7b3 100644
--- a/DEPLOYMENT.md
+++ b/DEPLOYMENT.md
@@ -1,7 +1,5 @@
# EdgeSSH 第一版:部署与验收
-面向部署者的最新分步教程、截图占位与故障排查统一维护在 [EdgeSSH 文档网站](https://edgessh-docs.pages.dev/deploy/actions)。本文件保留架构边界和维护者验收记录。
-
## 架构与边界
- 单管理员,无本地用户名、密码、注册、验证码或用户表。身份认证由 Cloudflare Zero Trust Access 完成。
@@ -17,49 +15,29 @@
| 名称 | 配置位置 | 含义 |
| --- | --- | --- |
-| `DB` | workflow 生成的 D1 binding | 主机资料数据库,首次自动创建、后续按名称复用 |
+| `DB` | `wrangler.toml` 的 D1 binding | 主机资料数据库,优先复用已有资源 |
| `SSH_SESSIONS` | `wrangler.toml` 的 Durable Object binding | SSH 会话隔离,保留现有类名及迁移历史 |
| `ASSETS` | `wrangler.toml` 的静态资源 binding | 前端构建产物,不需要手动创建 |
-| `ACCESS_TEAM_DOMAIN` | GitHub Actions Secret,同步为 Worker Secret | 团队域名,例如 `my-team.cloudflareaccess.com`,不含协议或路径 |
-| `ACCESS_AUD` | GitHub Actions Secret,同步为 Worker Secret | 此 Access 应用的 Application Audience (AUD) Tag |
-| `ENCRYPTION_KEY` | GitHub Actions Secret,同步为 Worker Secret | 32 字节安全随机数的标准 Base64 |
+| `ACCESS_TEAM_DOMAIN` | Cloudflare Worker Secret | 团队域名,例如 `my-team.cloudflareaccess.com`,不含协议或路径 |
+| `ACCESS_AUD` | Cloudflare Worker Secret | 此 Access 应用的 Application Audience (AUD) Tag |
+| `ENCRYPTION_KEY` | Cloudflare Worker Secret | 32 字节安全随机数的标准 Base64 |
| `CONNECT_TIMEOUT_MS` | 可选普通变量 | 建连超时;仓库默认 `10000`,通常不要额外配置 |
-`DB`、`SSH_SESSIONS` 与 `ASSETS` 是绑定,不是环境变量。三项运行时值由 GitHub Actions 在部署时同步为 Worker Secret,无需进入 Worker 控制台重复填写。
+`DB`、`SSH_SESSIONS` 与 `ASSETS` 是绑定,不是环境变量。生产环境实际需要手动填写的运行时值只有三个 Worker Secret。
-### 用户填写总览
+## Cloudflare 控制台配置
-使用自定义域名完成正式部署时,一共填写 **6 个值**:
+### 1. 创建 D1 数据库
-| 名称 | 填写位置 | 来源 |
-| --- | --- | --- |
-| `CLOUDFLARE_ACCOUNT_ID` | GitHub Actions Variable | Cloudflare 账户概览或 Worker 概览 |
-| `CUSTOM_DOMAIN` | GitHub Actions Variable | 已接入当前 Cloudflare 账户的自定义域名 |
-| `CLOUDFLARE_API_TOKEN` | GitHub Actions Secret | Cloudflare API Tokens 页面创建 |
-| `ACCESS_TEAM_DOMAIN` | GitHub Actions Secret | Zero Trust Settings 中的 Team Domain |
-| `ACCESS_AUD` | GitHub Actions Secret | Self-hosted Access 应用的 AUD Tag |
-| `ENCRYPTION_KEY` | GitHub Actions Secret | 本机或可信密码管理器生成的 32 字节随机数 Base64 |
-
-以上 6 项是正式部署的完整必填清单。`WORKER_NAME` 和 `D1_DATABASE_NAME` 是可选的 GitHub Actions Variable,默认分别为 `edgessh` 和 `-accounts`。D1 数据库、数据库 ID、数据表、Worker 及 Custom Domain route 均由 workflow 创建或更新,不要求用户编辑 `wrangler.toml`。
-
-## 准备 Cloudflare 身份与凭据
-
-Cloudflare 控制台只用于创建 API Token 与 Zero Trust Access 应用。不要在控制台手工创建 D1、部署 Worker 或维护另一套 Worker Secret。
-
-### 1. D1 自动初始化
-
-无需在控制台创建 D1,也不要修改 `wrangler.toml` 中的占位 ID。workflow 会使用 `D1_DATABASE_NAME`,或默认的 `-accounts`,在目标账户中按精确名称查找数据库:
-
-1. 已存在同名数据库时,读取其 ID 并继续复用。
-2. 不存在时,通过 Cloudflare API 创建一次,再把新 ID 写入 runner 内的临时 Wrangler 配置。
-3. 使用该临时配置执行远程 migration,只添加项目所需 schema,不清空已有数据。
-
-数据库 ID 不会写回仓库。改变 `D1_DATABASE_NAME` 会改用或创建另一个数据库,因此日常部署应保持名称稳定。
+1. 打开 Cloudflare Dashboard 的 D1 页面;新版界面通常位于 `Storage & databases > D1`,菜单名称可能随控制台更新而变化。
+2. 复用本项目已有数据库,或创建一个新的数据库;不要为每次部署重复创建。
+3. 将数据库名称和 ID 写入 `wrangler.toml` 的 `[[d1_databases]]`,binding 保持为 `DB`。
+4. 数据表不需要在控制台手动建立。GitHub Actions 会执行远程 migration,只添加项目所需 schema,不清空已有数据。
### 2. 配置 Zero Trust Access
1. 进入 `Zero Trust > Access > Applications`,添加一个 `Self-hosted` 应用。
-2. 应用域名填写最终访问 EdgeSSH 的自定义域名,并保证该域名与 GitHub Actions 的 `CUSTOM_DOMAIN` Variable 一致。
+2. 应用域名填写最终访问 EdgeSSH 的自定义域名,并保证该域名与 `wrangler.toml` 的 route 一致。
3. 添加 Allow 策略,只包含管理员的明确邮箱或身份。不要使用 `Everyone`、`Bypass` 或允许整个邮箱域的宽泛规则。
4. 在 Zero Trust 的团队设置中找到 Team Domain,保存不带 `https://` 和路径的域名,例如 `my-team.cloudflareaccess.com`。
5. 在 Access 应用详情中复制 Application Audience (AUD) Tag。不要把应用 ID、Client ID 或策略 ID 当作 AUD。
@@ -81,59 +59,47 @@ Cloudflare 账户 ID 可在 Dashboard 的账户概览或 Worker 概览中复制
| 名称 | GitHub 类型 | 值 |
| --- | --- | --- |
| `CLOUDFLARE_ACCOUNT_ID` | Variable | Cloudflare 账户 ID |
-| `CUSTOM_DOMAIN` | Variable | 自定义域名的纯主机名 |
| `CLOUDFLARE_API_TOKEN` | Secret | 上一步创建的最小权限 API Token |
-| `ACCESS_TEAM_DOMAIN` | Secret | Zero Trust Team Domain,不带协议或路径 |
-| `ACCESS_AUD` | Secret | Self-hosted Access 应用的 AUD Tag |
-| `ENCRYPTION_KEY` | Secret | 固定的 32 字节安全随机数 Base64 |
-可选项放在必填项之后,第一次部署通常不要填写:
+进入 `Actions > Deploy > Run workflow` 完成首次部署。workflow 会安装依赖、检查项目、迁移 D1 并创建或更新 Worker。首次运行时尚未配置运行时 Secret,受保护 API 暂时返回 503 属于预期现象。
-| 名称 | GitHub 类型 | 默认值 |
-| --- | --- | --- |
-| `WORKER_NAME` | 可选 Variable | `edgessh` |
-| `D1_DATABASE_NAME` | 可选 Variable | `-accounts` |
+### 5. 在 Worker 界面添加运行时 Secret
-六项必填配置全部保存后,进入 `Actions > Deploy > Run workflow` 完成首次部署。workflow 会安装依赖、检查项目、创建或复用 D1、同步运行时 Secret、执行 migration,并创建或更新 Worker 与自定义域名。
-
-### 5. 生成并保存运行时 Secret
-
-在首次部署前,将以下三项添加到 GitHub Actions 的 **Secrets**,不要添加到 Variables:
+首次部署完成后,进入 `Workers & Pages > edgessh > Settings > Variables and Secrets`,依次添加以下三项,并将类型选择为 **Secret**:
| 名称 | 填写内容 |
| --- | --- |
| `ACCESS_TEAM_DOMAIN` | Zero Trust Team Domain,不带协议和路径 |
| `ACCESS_AUD` | Access 应用详情中的 Application Audience (AUD) Tag |
-| `ENCRYPTION_KEY` | 32 字节安全随机数的标准 Base64,只在首次部署前生成 |
+| `ENCRYPTION_KEY` | 32 字节安全随机数的标准 Base64,只在首次部署时生成 |
-workflow 会校验三项 Secret 是否存在,并通过标准输入调用 Wrangler 的批量 Secret API;Secret 不会进入命令参数、临时配置或日志。不要为 `DB`、`SSH_SESSIONS` 或 `ASSETS` 创建同名变量。`CONNECT_TIMEOUT_MS` 已由 `wrangler.toml` 设置为 `10000`,只有确实需要调整 2,000 至 30,000 毫秒的建连超时时才在配置中修改。
+保存后按控制台提示部署新版本。不要在 GitHub Actions 中重复保存这三项,也不要为 `DB`、`SSH_SESSIONS` 或 `ASSETS` 创建同名变量。`CONNECT_TIMEOUT_MS` 已由 `wrangler.toml` 设置为 `10000`,只有确实需要调整 2,000 至 30,000 毫秒的建连超时时才在配置中修改。
`ENCRYPTION_KEY` 应由可信的密码管理器或本机安全随机数工具生成,解码后必须正好为 32 字节。不要使用普通密码、UUID、示例值或在线随机字符串网页。密钥丢失将无法解密现有资料;当前版本不支持直接轮换,后续部署必须复用原值。
-可在本机运行 `node -e "console.log(require('node:crypto').randomBytes(32).toString('base64'))"` 生成一次;只把输出保存到 Secret,命令本身不会把密钥写入项目文件。
-
## 部署顺序
-1. 确定自定义域名;如需覆盖默认值,再确定稳定的 Worker 名称与 D1 名称。同一 Cloudflare 账户内不要与无关项目重名。
-2. 配置 Access 与最小权限 API Token,再将部署凭据、三项运行时 Secret 和可选名称统一保存到 GitHub Actions。
-3. 手动运行一次 `Deploy` workflow,让 GitHub Actions 自动创建或复用 D1、同步 Worker Secret、执行 migration,并创建或更新 Worker。
-4. 以后只需推送到 `main`;workflow 会复用现有资源,并以 GitHub Actions 中的固定 Secret 自动发布,不需要再进入 Worker 控制台或在本地执行 Wrangler 命令。
-5. 通过实际 Access 入口验收。所有账号、主机与连接 API 均需 Access 保护;缺少 Access Secret 时部署会直接失败,不会发布一个待补配置的版本。
+1. 检查 Cloudflare 已有 Worker、D1 与 Access 应用,确认资源属于本项目,避免覆盖其他项目。
+2. 配置 D1、Access 与最小权限 API Token,再将两个部署值保存到 GitHub Actions。
+3. 手动运行一次 `Deploy` workflow,让 GitHub Actions 自动迁移 D1 并创建或更新 Worker。
+4. 在 Worker 的 `Variables and Secrets` 界面添加三个运行时 Secret,保存并部署。
+5. 以后只需推送到 `main`;workflow 会复用原有资源和 Secret 自动发布,不需要在本地执行 Wrangler 部署命令。
+6. 通过实际 Access 入口验收。所有账号、主机与连接 API 均需 Access 保护;缺少 Access Secret 时会返回 503,不能操作主机或连接 SSH。
密钥丢失将无法解密现有资料。更换密钥必须设计旧密钥解密、新密钥重加密的迁移,不可直接覆盖 Secret。当前版本不提供自动轮换。更换 Access 团队或身份导致 `sub` 改变时,也需要显式的数据迁移。
### GitHub Actions
-`.github/workflows/deploy.yml` 在推送到 `main` 或手动触发时依次执行安装、项目检查、D1 幂等初始化、远程 migration 与 Worker 部署。生产部署使用并发锁串行执行,避免初始化、迁移和 Worker 版本交错。
+`.github/workflows/deploy.yml` 在推送到 `main` 或手动触发时依次执行安装、项目检查、远程 D1 迁移与 Worker 部署。生产部署使用并发锁串行执行,避免迁移和 Worker 版本交错。
-`Deploy` workflow 使用“用户填写总览”中的 6 项必填配置。资源名称仍是可选 Variable:
+仓库只需配置两个 Actions 值:
-| 名称 | 默认值 |
-| --- | --- |
-| `WORKER_NAME` | `edgessh` |
-| `D1_DATABASE_NAME` | `-accounts` |
+| 名称 | 配置位置 | 要求 |
+| --- | --- | --- |
+| `CLOUDFLARE_ACCOUNT_ID` | Actions Variable | 目标 Cloudflare 账户 ID |
+| `CLOUDFLARE_API_TOKEN` | Actions Secret | 限定到目标账户,并具备 Workers 部署与 D1 迁移权限 |
-三个运行时 Secret 只保存在 GitHub Actions Secrets 与 Cloudflare Worker Secrets 中。workflow 每次部署都会从 Actions 环境读取并幂等同步,用户无需打开 Cloudflare Worker 的 `Variables and Secrets` 页面;不要把本地 `.dev.vars` 或手动 `wrangler secret put` 作为普通用户的生产配置流程。
+三个运行时 Worker Secret 不进入 GitHub Actions,统一在 Cloudflare Worker 的 `Variables and Secrets` 界面管理;日常自动部署只复用 Cloudflare 中已有值。不要把本地 `.dev.vars` 或 `wrangler secret put` 作为普通用户的生产配置流程。
## 验收清单(5.6 SOL 执行)
@@ -157,10 +123,19 @@ workflow 会校验三项 Secret 是否存在,并通过标准输入调用 Wrang
- 首页为中文;原 SSH 工作台保留中英文切换。
- 国旗使用本地 `flag-icons` SVG(MIT,许可证随静态资源部署),在 Windows 上也显示实际旗帜,不依赖 emoji 字体。
-## 部署后验收
+## 2026-09-19 线上部署记录
-- 仓库中的 `wrangler.toml` 只包含 D1 占位 ID,不包含维护者账户的资源 ID 或固定自定义域名。
-- workflow 按 `D1_DATABASE_NAME` 在部署者自己的账户内查找或创建 D1,把真实 ID 仅写入 runner 内已忽略的 `wrangler.deploy.toml`,再执行 migration 与 Worker 部署。
-- workflow 使用必填的 `CUSTOM_DOMAIN`,为部署者自己的域名创建或更新 Custom Domain。
-- 首次部署后确认 D1 中存在 `hosts` 与 `d1_migrations` 表,再通过 Access 登录,完成主机新增、刷新后恢复、编辑和删除验收。
-- 账号、主机、凭据与 SSH 接口始终校验 Access JWT;`workers.dev` 不能绕过应用层认证。
+- 正式入口:`https://ssh.dltwcnm.ccwu.cc`
+- D1:`edgessh-accounts`(`2c1b7a95-a5d6-4a72-a8d2-4132b74aa268`)
+- D1 schema:`hosts`、`d1_migrations`;记录数分别为 0、1
+- 活动版本:由 GitHub Actions 的 `Deploy` workflow 自动发布,当前状态以 Actions 运行记录与 Cloudflare 控制台为准
+- Secret 名称:`ENCRYPTION_KEY`、`ACCESS_TEAM_DOMAIN`、`ACCESS_AUD`;本次部署未读取或覆盖任何运行时 Secret
+- 无会话 Smoke:正式入口 `/` 与 `/api/auth/me` 均返回 Access 302,TLS 正常;未记录重定向地址、Team Domain、AUD、JWT、Cookie 或 Secret 值
+- 诊断入口:`workers.dev` 已开启,仅额外公开固定 `8.8.8.8` 的定位链路检查;账号、主机、凭据与 SSH 等接口仍必须通过 Access JWT 校验
+- 待验收:需要用户登录 Access 后验收账号 API,并使用真实授权 SSH 目标验收终端、SFTP 与进程面板
+
+## 自定义域名
+
+- 正式入口:`https://ssh.dltwcnm.ccwu.cc`
+- `workers.dev` 作为诊断入口保留;Worker 仍在应用层强制校验所有生产账号与连接 API,不允许绕过自定义域名上的 Access 策略。
+- Access 应用、策略与三个运行时 Secret 均由用户在 Cloudflare 控制台维护;GitHub Actions 只负责迁移与部署代码。
diff --git a/README.md b/README.md
index cc39003..63764ff 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@
-[文档网站](https://edgessh-docs.pages.dev) · [Actions 部署](https://edgessh-docs.pages.dev/deploy/actions) · [功能特性](#功能特性) · [界面预览](#界面预览) · [本地开发](#本地开发) · [安全与限制](#安全与限制)
+[功能特性](#功能特性) · [界面预览](#界面预览) · [快速开始](#快速开始) · [本地开发](#本地开发) · [安全与限制](#安全与限制)
@@ -32,18 +32,6 @@
-
-
-
- EdgeChat
- 基于 Cloudflare 的开源自部署团队聊天系统
- |
-
- 查看开源项目 →
- |
-
-
-
## 功能特性
@@ -124,9 +112,9 @@ EdgeSSH 没有独立的本地登录系统,正式入口必须先经过 Cloudfla
2. 添加一条 **Allow(允许)** 策略,使用 **Include > Emails** 明确填写管理员邮箱;不要使用 `Everyone(所有人)` 或 `Bypass(绕过)`。
3. 选择 **Identity Provider(标识提供程序)**。个人部署可使用 **One-time PIN(一次性 PIN)**;新建 Zero Trust 组织若没有该选项,需要先到 **Integrations(集成)> Identity providers(标识提供程序)** 手动添加。
4. 从 Access 应用中取得 **Application Audience (AUD) Tag(应用程序受众 (AUD) 标签)**,并从 Zero Trust **Settings(设置)** 取得 **Team Domain(团队域)**。
-5. 将它们分别保存为 GitHub Actions Secret `ACCESS_AUD` 与 `ACCESS_TEAM_DOMAIN`,由 `Deploy` workflow 同步为 Worker Secret。
+5. 将它们分别写入 `ACCESS_AUD` 与 `ACCESS_TEAM_DOMAIN` Worker Secret。
-完整的控制台点击步骤、OTP 配置、参数获取和故障排查见 **[Cloudflare Zero Trust 配置指南](https://edgessh-docs.pages.dev/deploy/zero-trust)**。
+完整的控制台点击步骤、OTP 配置、参数获取和故障排查见 **[Cloudflare Zero Trust 配置指南](docs/ZERO_TRUST.md)**。
### 获取项目
@@ -141,62 +129,37 @@ npm ci
> [!IMPORTANT]
> 本项目面向**单管理员**使用。Access 策略应仅允许管理员的明确身份,不要配置 Everyone 或 Bypass。缺少 Access 配置时,受保护 API 会拒绝访问,不会降级为匿名网关。
-1. 在 GitHub Actions 配置 Cloudflare 账户 ID、API Token 和三项运行时 Secret;Worker 名称、D1 名称与自定义域名通过仓库 Variable 设置,不需要修改 `wrangler.toml`。
-2. 若使用自定义域名,在 Zero Trust 控制台为同一域名创建 Self-hosted Access 应用,只允许管理员的明确邮箱或身份,并记下 Team Domain 与 Application Audience (AUD) Tag。
-3. 手动运行一次 `Deploy` workflow。它会按名称查找 D1,不存在时自动创建,通过 Wrangler 同步运行时 Secret,随后执行 migration 并创建或更新 Worker;以后推送到 `main` 会复用同一资源更新部署。
-4. 通过 Access 入口登录并完成验收;无需再进入 Worker 控制台补填环境变量。
+1. 在 Cloudflare 控制台复用或创建 D1 数据库,并调整 `wrangler.toml` 中的 Worker 名称、自定义域名与 D1 `database_id`,**不要直接使用仓库中原部署环境的资源 ID**。
+2. 在 Zero Trust 控制台创建 Self-hosted Access 应用,只允许管理员的明确邮箱或身份,并记下 Team Domain 与 Application Audience (AUD) Tag。
+3. 在 GitHub 配置一个 Actions Variable 和一个 Actions Secret,首次运行 `Deploy` workflow;它会自动检查项目、迁移 D1 并创建或更新 Worker。
+4. Worker 首次出现后,在 Cloudflare 控制台的 `Workers & Pages > 你的 Worker(默认 edgessh)> Settings > Variables and Secrets` 中添加三个运行时 Secret。保存并部署这些设置后即可通过 Access 入口验收。
| 配置 | 类型与位置 | 用途 |
| --- | --- | --- |
-| `DB` | 部署时生成的 D1 binding | 保存加密主机资料;数据库首次自动创建、后续按名称复用 |
+| `DB` | `wrangler.toml` 中的 D1 binding | 保存加密主机资料 |
| `SSH_SESSIONS` | `wrangler.toml` 中的 Durable Object binding | 隔离 SSH 会话 |
| `ASSETS` | `wrangler.toml` 中的静态资源 binding | 提供前端资源 |
-| `ACCESS_TEAM_DOMAIN` | GitHub Actions Secret,同步为 Worker Secret | Access 团队域名,如 `my-team.cloudflareaccess.com`,不含协议和路径 |
-| `ACCESS_AUD` | GitHub Actions Secret,同步为 Worker Secret | Access 应用的 Application Audience (AUD) Tag |
-| `ENCRYPTION_KEY` | GitHub Actions Secret,同步为 Worker Secret | 32 字节安全随机密钥的标准 Base64 |
+| `ACCESS_TEAM_DOMAIN` | Cloudflare Worker Secret | Access 团队域名,如 `my-team.cloudflareaccess.com`,不含协议和路径 |
+| `ACCESS_AUD` | Cloudflare Worker Secret | Access 应用的 Application Audience (AUD) Tag |
+| `ENCRYPTION_KEY` | Cloudflare Worker Secret | 32 字节安全随机密钥的标准 Base64 |
| `CONNECT_TIMEOUT_MS` | 可选普通变量 | TCP 建连超时;仓库已设为 `10000`,通常无需填写 |
-生产运行时的 `ACCESS_TEAM_DOMAIN`、`ACCESS_AUD` 与 `ENCRYPTION_KEY` 统一保存在 GitHub Actions Secrets 中,workflow 会在部署时将它们同步为 Worker Secret。`DB`、`SSH_SESSIONS` 与 `ASSETS` 是部署生成的绑定,不是环境变量;其中 D1 数据库资源由 workflow 单独确保存在。`CONNECT_TIMEOUT_MS` 已有默认配置,也无需重复添加。
+生产运行时只需在 Cloudflare 控制台填写 `ACCESS_TEAM_DOMAIN`、`ACCESS_AUD` 与 `ENCRYPTION_KEY` 三个 Worker Secret。`DB`、`SSH_SESSIONS` 与 `ASSETS` 是部署时创建的绑定,不是环境变量;`CONNECT_TIMEOUT_MS` 已有默认配置,也无需重复添加。
-**首次部署才生成 `ENCRYPTION_KEY`,后续部署始终复用同一个 GitHub Secret。** 丢失或直接替换密钥会导致已有资料无法解密。不要把密钥保存为 GitHub Variable,也不要写入源码、`wrangler.toml`、日志或提交记录。
+**首次部署才生成 `ENCRYPTION_KEY`,后续部署始终复用原值。** 丢失或直接替换密钥会导致已有资料无法解密。密钥只粘贴到 Cloudflare 的 Secret 输入框,不要写入源码、`wrangler.toml`、GitHub Actions 或提交记录。
-完整配置顺序、安全边界与验收清单见 [VitePress 部署指南](https://edgessh-docs.pages.dev/deploy/actions)。
+完整配置顺序、安全边界与验收清单见 [部署指南](DEPLOYMENT.md)。
### GitHub Actions 自动部署
-仓库内置的 `Deploy` workflow 会在推送到 `main` 后自动检查、准备 D1、执行 migration 并部署,也可在 Actions 页面手动触发。首次使用前,在仓库的 `Settings > Secrets and variables > Actions` 中配置:
+仓库内置的 `Deploy` workflow 会在推送到 `main` 后自动检查、迁移 D1 并部署,也可在 Actions 页面手动触发。首次使用前,在仓库的 `Settings > Secrets and variables > Actions` 中配置:
-#### 用户需要填写的内容
-
-使用自定义域名完成正式部署时,用户一共需要在 GitHub Actions 中填写 **6 个值**:2 个 Variable 和 4 个 Secret。
-
-| 名称 | 填写位置 | 内容 |
+| 名称 | GitHub 配置类型 | 用途 |
| --- | --- | --- |
-| `CLOUDFLARE_ACCOUNT_ID` | GitHub Actions Variable | Cloudflare 账户 ID |
-| `CUSTOM_DOMAIN` | GitHub Actions Variable | 自定义域名的纯主机名,例如 `ssh.example.com` |
-| `CLOUDFLARE_API_TOKEN` | GitHub Actions Secret | 具备 Workers、D1 Edit 和目标 Zone 权限的 API Token |
-| `ACCESS_TEAM_DOMAIN` | GitHub Actions Secret | Zero Trust Team Domain,不含协议和路径 |
-| `ACCESS_AUD` | GitHub Actions Secret | Access 应用的 Application Audience (AUD) Tag |
-| `ENCRYPTION_KEY` | GitHub Actions Secret | 32 字节安全随机数的标准 Base64 |
+| `CLOUDFLARE_ACCOUNT_ID` | Variable | Cloudflare 账户 ID |
+| `CLOUDFLARE_API_TOKEN` | Secret | 具备 Workers 部署和 D1 迁移权限的 API Token |
-#### 可选配置
-
-| 名称 | 类型 | 默认值 |
-| --- | --- | --- |
-| `WORKER_NAME` | GitHub Actions Variable | `edgessh` |
-| `D1_DATABASE_NAME` | GitHub Actions Variable | `-accounts` |
-
-这两个名称通常保持默认值即可,不计入上述 6 个必填值。用户不需要填写 D1 ID、手动创建数据库、进入 Worker 控制台保存 Secret 或修改 `wrangler.toml`。
-
-完整操作顺序:
-
-1. Fork 仓库,并确保准备使用的自定义域名已经接入 Cloudflare。
-2. 创建部署用 API Token,在 GitHub 填写 `CLOUDFLARE_ACCOUNT_ID`、`CLOUDFLARE_API_TOKEN` 与 `CUSTOM_DOMAIN`。
-3. 在 Zero Trust 创建绑定同一域名的 Self-hosted Access 应用,只允许管理员身份,并取得 Team Domain 与 AUD;生成一次加密密钥,然后将 `ACCESS_TEAM_DOMAIN`、`ACCESS_AUD` 与 `ENCRYPTION_KEY` 保存为 GitHub Actions Secret。
-4. 在 GitHub 的 `Actions > Deploy` 手动运行一次 workflow;它会自动创建或复用 D1、同步 Worker Secret、执行 migration、创建或更新 Worker,并绑定自定义域名。
-5. 访问自定义域名,通过 Access 登录并完成账号 API 与 SSH 功能验收。以后推送到 `main` 会自动复用现有资源更新部署。
-
-标准部署要求上表 6 项全部填写。创建 API Token 时,可使用 Cloudflare 的 `Edit Cloudflare Workers` 模板并补充 `D1: Edit`,再将 Account 与 Zone Resources 限制到实际使用的账户和域名。workflow 不会把解析出的数据库 ID 或 Secret 写回仓库:数据库 ID 只进入 runner 内的临时配置,Secret 只经标准输入交给 Wrangler。部署、D1 初始化与运行时 Secret 同步均由 Action 完成,不需要进入 Cloudflare Worker 控制台手工发布或维护另一套 Secret。
+Action 只需要这两项部署凭据。创建 API Token 时,可使用 Cloudflare 的 `Edit Cloudflare Workers` 模板并补充 `D1: Edit`,再将 Account 与 Zone Resources 限制到实际使用的账户和域名;该模板会覆盖 Worker 与自定义域名部署所需权限。`ACCESS_TEAM_DOMAIN`、`ACCESS_AUD` 与 `ENCRYPTION_KEY` 只保存在 Cloudflare Worker Secrets 中,普通部署不会读取或覆盖它们。API Token 属于敏感信息,必须使用 GitHub Secret,不能保存为普通 Variable。
## 本地开发
diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
deleted file mode 100644
index d54ecab..0000000
--- a/docs/.vitepress/config.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-import { defineConfig } from 'vitepress';
-
-const repository = 'https://github.com/aozorae/EdgeSSH';
-
-export default defineConfig({
- lang: 'zh-CN',
- title: 'EdgeSSH',
- description: 'EdgeSSH 部署、配置与使用文档',
- cleanUrls: true,
- lastUpdated: true,
- head: [
- ['meta', { name: 'theme-color', content: '#f4511e' }],
- ['meta', { name: 'viewport', content: 'width=device-width, initial-scale=1, viewport-fit=cover' }],
- ],
- themeConfig: {
- siteTitle: 'EdgeSSH',
- logo: false,
- nav: [
- { text: '部署', link: '/deploy/actions', activeMatch: '^/deploy/' },
- { text: '使用', link: '/usage/hosts', activeMatch: '^/usage/' },
- { text: '参考', link: '/reference/configuration', activeMatch: '^/reference/' },
- { text: '开发', link: '/development/local' },
- { text: 'GitHub', link: repository },
- ],
- sidebar: [
- {
- text: '开始',
- items: [
- { text: '认识 EdgeSSH', link: '/guide/overview' },
- { text: '部署前准备', link: '/guide/requirements' },
- ],
- },
- {
- text: 'GitHub Actions 部署',
- items: [
- { text: '完整部署流程', link: '/deploy/actions' },
- { text: 'Cloudflare API Token', link: '/deploy/api-token' },
- { text: 'Zero Trust Access', link: '/deploy/zero-trust' },
- { text: 'Worker 运行时 Secret', link: '/deploy/runtime-secrets' },
- { text: '部署后验收', link: '/deploy/verification' },
- { text: '常见部署问题', link: '/deploy/troubleshooting' },
- ],
- },
- {
- text: '日常使用',
- items: [
- { text: '管理主机', link: '/usage/hosts' },
- { text: '终端与主机指纹', link: '/usage/terminal' },
- { text: '文件与进程', link: '/usage/files-and-processes' },
- ],
- },
- {
- text: '参考',
- items: [
- { text: '配置项', link: '/reference/configuration' },
- { text: '架构与安全边界', link: '/reference/security' },
- { text: '支持范围与限制', link: '/reference/limits' },
- { text: '截图清单', link: '/reference/screenshots' },
- ],
- },
- {
- text: '开发',
- items: [
- { text: '本地开发', link: '/development/local' },
- { text: '贡献指南', link: '/development/contributing' },
- { text: '文档站部署', link: '/development/docs-site' },
- ],
- },
- ],
- socialLinks: [{ icon: 'github', link: repository }],
- search: {
- provider: 'local',
- options: {
- translations: {
- button: { buttonText: '搜索文档', buttonAriaLabel: '搜索文档' },
- modal: {
- noResultsText: '没有找到相关内容',
- resetButtonTitle: '清除查询',
- footer: { selectText: '选择', navigateText: '切换', closeText: '关闭' },
- },
- },
- },
- },
- outline: { level: [2, 3], label: '本页内容' },
- editLink: { pattern: `${repository}/edit/main/docs/:path`, text: '在 GitHub 上编辑此页' },
- lastUpdated: { text: '最后更新', formatOptions: { dateStyle: 'medium', timeStyle: 'short' } },
- docFooter: { prev: '上一页', next: '下一页' },
- returnToTopLabel: '返回顶部',
- sidebarMenuLabel: '文档目录',
- darkModeSwitchLabel: '切换主题',
- lightModeSwitchTitle: '切换到浅色模式',
- darkModeSwitchTitle: '切换到深色模式',
- footer: {
- message: '面向个人管理员的 Cloudflare 边缘 SSH 工作台',
- copyright: 'Apache License 2.0',
- },
- },
-});
diff --git a/docs/.vitepress/theme/components/ScreenshotPlaceholder.vue b/docs/.vitepress/theme/components/ScreenshotPlaceholder.vue
deleted file mode 100644
index d7d514d..0000000
--- a/docs/.vitepress/theme/components/ScreenshotPlaceholder.vue
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
-
-
-
-
⌗
-
-
{{ title }}
-
{{ description }}
-
-
-
- {{ caption || '界面参考' }}
-
-
- 待补截图:docs/public/screenshots/{{ filename }}
-
-
-
diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts
deleted file mode 100644
index 77197bf..0000000
--- a/docs/.vitepress/theme/index.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import DefaultTheme from 'vitepress/theme';
-import type { Theme } from 'vitepress';
-import ScreenshotPlaceholder from './components/ScreenshotPlaceholder.vue';
-import './style.css';
-
-export default {
- extends: DefaultTheme,
- enhanceApp({ app }) {
- app.component('ScreenshotPlaceholder', ScreenshotPlaceholder);
- },
-} satisfies Theme;
diff --git a/docs/.vitepress/theme/style.css b/docs/.vitepress/theme/style.css
deleted file mode 100644
index bb325da..0000000
--- a/docs/.vitepress/theme/style.css
+++ /dev/null
@@ -1,499 +0,0 @@
-/* Hallmark · pre-emit critique: P5 H4 E4 S5 R5 V4 · genre: modern-minimal · macrostructure: Workbench · theme: Coral · enrichment: real product capture · nav: N13 search-first · footer: Ft1 mast-headed · contrast: pass · mobile: pass */
-@import url('https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:wght@600;700&family=JetBrains+Mono:wght@400;500;600&family=Noto+Sans+SC:wght@400;500;600;700&display=swap');
-@import './tokens.css';
-
-html,
-body {
- overflow-x: clip;
-}
-
-:root {
- --vp-c-bg: var(--color-paper);
- --vp-c-bg-alt: var(--color-paper-soft);
- --vp-c-bg-elv: var(--color-paper-raised);
- --vp-c-bg-soft: var(--color-paper-soft);
- --vp-c-text-1: var(--color-ink);
- --vp-c-text-2: var(--color-ink-soft);
- --vp-c-text-3: var(--color-muted);
- --vp-c-divider: var(--color-rule);
- --vp-c-gutter: var(--color-rule);
- --vp-c-brand-1: var(--color-accent-strong);
- --vp-c-brand-2: var(--color-accent);
- --vp-c-brand-3: var(--color-accent-strong);
- --vp-c-brand-soft: var(--color-accent-soft);
- --vp-font-family-base: var(--font-body);
- --vp-font-family-mono: var(--font-mono);
- --vp-layout-max-width: 1440px;
- --vp-sidebar-width: 288px;
- --vp-border-radius: var(--radius-md);
-}
-
-body {
- background: var(--color-paper);
- color: var(--color-ink-soft);
- font-family: var(--font-body);
- font-size: var(--text-base);
- letter-spacing: 0;
-}
-
-h1,
-h2,
-h3,
-h4,
-.VPNavBarTitle,
-.VPHomeHero .name,
-.VPHomeHero .text {
- color: var(--color-ink);
- font-family: var(--font-display);
- font-style: normal;
- letter-spacing: 0;
- min-width: 0;
- overflow-wrap: anywhere;
-}
-
-a,
-button,
-.VPButton {
- white-space: nowrap;
-}
-
-a:focus-visible,
-button:focus-visible,
-.VPButton:focus-visible {
- outline: 3px solid var(--color-focus);
- outline-offset: 3px;
-}
-
-.VPNav {
- border-bottom: var(--rule-thin) solid var(--color-rule);
-}
-
-.VPNavBarTitle .title::before {
- color: var(--color-accent);
- content: '>_';
- font-family: var(--font-mono);
- font-size: var(--text-sm);
- margin-inline-end: var(--space-sm);
-}
-
-.VPNavBarTitle .title,
-.VPNavBarMenuLink,
-.VPNavBarMenuGroup .text {
- letter-spacing: 0;
-}
-
-.DocSearch-Button,
-.VPNavBarSearch .DocSearch-Button {
- border: var(--rule-thin) solid var(--color-rule-strong);
- border-radius: var(--radius-md);
- min-width: 180px;
-}
-
-.VPHome {
- padding-bottom: var(--space-3xl);
-}
-
-.VPHomeHero {
- padding: var(--space-2xl) var(--space-lg) var(--space-3xl);
-}
-
-.VPHomeHero .container {
- display: block;
- max-width: 1120px;
-}
-
-.VPHomeHero .main {
- max-width: 800px;
-}
-
-.VPHomeHero .name {
- color: var(--color-ink);
- font-size: var(--text-display);
- line-height: 1.05;
-}
-
-.VPHomeHero .name .clip {
- background: none;
- -webkit-text-fill-color: currentColor;
-}
-
-.VPHomeHero .text {
- font-size: var(--text-xl);
- line-height: 1.2;
- max-width: 44rem;
-}
-
-.VPHomeHero .tagline {
- color: var(--color-ink-soft);
- font-size: var(--text-md);
- line-height: 1.7;
- max-width: 44rem;
-}
-
-.VPButton {
- border-radius: var(--radius-md) !important;
- font-weight: 600;
- min-height: 44px;
- transition: background-color var(--dur-short) var(--ease-out), color var(--dur-short) var(--ease-out), transform var(--dur-micro) var(--ease-out);
-}
-
-.VPButton:active {
- transform: translateY(1px);
-}
-
-.VPButton.brand {
- background: var(--color-accent-strong);
- border-color: var(--color-accent-strong);
- color: var(--color-paper-raised);
-}
-
-.VPButton.alt {
- background: transparent;
- border-color: var(--color-rule-strong);
- color: var(--color-ink);
-}
-
-.home-workbench,
-.home-path,
-.home-boundary {
- margin-inline: auto;
- max-width: 1120px;
- padding-inline: var(--space-lg);
-}
-
-.home-workbench {
- display: grid;
- gap: var(--space-xl);
- grid-template-columns: minmax(0, 1.55fr) minmax(240px, 0.45fr);
-}
-
-.home-capture {
- margin: 0;
-}
-
-.home-capture img {
- border: var(--rule-thin) solid var(--color-rule-strong);
- border-radius: var(--radius-lg);
- display: block;
- height: auto;
- width: 100%;
-}
-
-.home-capture figcaption,
-.screenshot-slot figcaption {
- color: var(--color-muted);
- font-family: var(--font-mono);
- font-size: var(--text-xs);
- line-height: 1.6;
- margin-top: var(--space-sm);
-}
-
-.home-workbench__note {
- align-self: end;
- border-top: var(--rule-thin) solid var(--color-rule-strong);
- padding-top: var(--space-lg);
-}
-
-.home-workbench__note strong {
- color: var(--color-ink);
- display: block;
- font-family: var(--font-display);
- font-size: var(--text-lg);
- line-height: 1.25;
-}
-
-.home-workbench__note p {
- line-height: 1.75;
-}
-
-.home-path {
- padding-top: var(--space-3xl);
-}
-
-.home-path h2,
-.home-boundary h2 {
- border: 0;
- font-size: var(--text-xl);
- margin: 0 0 var(--space-xl);
- padding: 0;
-}
-
-.deploy-track {
- border-top: var(--rule-thin) solid var(--color-rule-strong);
- display: grid;
- grid-template-columns: repeat(4, minmax(0, 1fr));
-}
-
-.deploy-track > div {
- border-bottom: var(--rule-thin) solid var(--color-rule);
- padding: var(--space-lg) var(--space-md) var(--space-xl) 0;
-}
-
-.deploy-track > div + div {
- border-inline-start: var(--rule-thin) solid var(--color-rule);
- padding-inline-start: var(--space-md);
-}
-
-.deploy-track span {
- color: var(--color-accent-strong);
- display: block;
- font-family: var(--font-mono);
- font-size: var(--text-sm);
- margin-bottom: var(--space-lg);
-}
-
-.deploy-track strong {
- color: var(--color-ink);
- display: block;
- font-size: var(--text-md);
- margin-bottom: var(--space-xs);
-}
-
-.deploy-track p {
- color: var(--color-ink-soft);
- line-height: 1.65;
- margin: 0;
-}
-
-.home-boundary {
- display: grid;
- gap: var(--space-xl);
- grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr);
- padding-top: var(--space-3xl);
-}
-
-.home-boundary > div:last-child {
- border-top: var(--rule-thin) solid var(--color-rule-strong);
- line-height: 1.8;
- padding-top: var(--space-lg);
-}
-
-.home-link {
- color: var(--color-accent-strong);
- display: inline-block;
- font-weight: 600;
- text-decoration: underline;
- text-decoration-thickness: var(--rule-thin);
- text-underline-offset: var(--space-2xs);
-}
-
-.vp-doc h1 {
- font-size: 2.5rem;
- line-height: 1.16;
-}
-
-.vp-doc h2 {
- border-top: var(--rule-thin) solid var(--color-rule);
- font-size: var(--text-xl);
- margin-top: var(--space-2xl);
- padding-top: var(--space-xl);
-}
-
-.vp-doc h3 {
- font-size: var(--text-lg);
- margin-top: var(--space-xl);
-}
-
-.vp-doc p,
-.vp-doc li {
- line-height: 1.8;
-}
-
-.vp-doc a {
- text-underline-offset: var(--space-2xs);
-}
-
-.vp-doc .header-anchor {
- float: none;
- margin-inline-start: var(--space-xs);
- opacity: 0;
- position: static;
-}
-
-.vp-doc :is(h1, h2, h3, h4):hover .header-anchor,
-.vp-doc .header-anchor:focus-visible {
- opacity: 1;
-}
-
-.vp-doc table {
- display: block;
- overflow-x: auto;
- width: 100%;
-}
-
-.vp-doc tr {
- background: transparent;
-}
-
-.vp-doc th,
-.vp-doc td {
- border-color: var(--color-rule);
-}
-
-.vp-doc code {
- border-radius: var(--radius-sm);
-}
-
-.vp-doc div[class*='language-'] {
- background: var(--color-code);
- border: var(--rule-thin) solid var(--color-rule-strong);
- border-radius: var(--radius-md);
-}
-
-.vp-doc div[class*='language-'] code {
- color: var(--color-code-ink);
-}
-
-.custom-block {
- border: var(--rule-thin) solid var(--color-rule-strong);
- border-radius: var(--radius-md);
-}
-
-.custom-block.tip {
- background: var(--color-success-soft);
- border-color: var(--color-success);
-}
-
-.custom-block.info {
- background: var(--color-info-soft);
- border-color: var(--color-info);
-}
-
-.custom-block.warning,
-.custom-block.danger {
- border-color: var(--color-warning);
-}
-
-.screenshot-slot {
- margin: var(--space-xl) 0;
-}
-
-.screenshot-slot img,
-.screenshot-slot__empty {
- border: var(--rule-thin) dashed var(--color-rule-strong);
- border-radius: var(--radius-md);
- min-height: 260px;
- width: 100%;
-}
-
-.screenshot-slot img {
- display: block;
- height: auto;
- margin-inline: auto;
- min-height: 0;
- width: auto;
-}
-
-.screenshot-slot__empty {
- align-items: center;
- background: var(--color-paper-soft);
- display: flex;
- gap: var(--space-lg);
- justify-content: center;
- padding: var(--space-xl);
-}
-
-.screenshot-slot__mark {
- color: var(--color-accent);
- font-family: var(--font-mono);
- font-size: 2.5rem;
-}
-
-.screenshot-slot__empty strong {
- color: var(--color-ink);
- font-size: var(--text-md);
-}
-
-.screenshot-slot__empty p {
- color: var(--color-ink-soft);
- margin: var(--space-xs) 0 0;
- max-width: 54ch;
-}
-
-@media (hover: hover) and (pointer: fine) {
- .VPButton:hover {
- transform: translateY(-1px);
- }
-
- .home-link:hover {
- text-decoration-thickness: 2px;
- }
-}
-
-@media (max-width: 59.99rem) {
- .home-workbench,
- .home-boundary {
- grid-template-columns: minmax(0, 1fr);
- }
-
- .deploy-track {
- grid-template-columns: repeat(2, minmax(0, 1fr));
- }
-
- .deploy-track > div:nth-child(3) {
- border-inline-start: 0;
- padding-inline-start: 0;
- }
-}
-
-@media (max-width: 39.99rem) {
- .VPHomeHero {
- padding: var(--space-xl) var(--space-md) var(--space-2xl);
- }
-
- .VPHomeHero .name {
- font-size: 2.75rem;
- }
-
- .VPHomeHero .text,
- .home-path h2,
- .home-boundary h2 {
- font-size: 1.75rem;
- }
-
- .DocSearch-Button,
- .VPNavBarSearch .DocSearch-Button {
- border: 0;
- min-height: 44px;
- min-width: 44px;
- padding: 0;
- width: 44px;
- }
-
- .home-workbench,
- .home-path,
- .home-boundary {
- padding-inline: var(--space-md);
- }
-
- .deploy-track {
- grid-template-columns: minmax(0, 1fr);
- }
-
- .deploy-track > div,
- .deploy-track > div + div,
- .deploy-track > div:nth-child(3) {
- border-inline-start: 0;
- padding-inline-start: 0;
- }
-
- .screenshot-slot__empty {
- align-items: flex-start;
- flex-direction: column;
- min-height: 220px;
- padding: var(--space-lg);
- }
-
- .vp-doc h1 {
- font-size: 2rem;
- }
-}
-
-@media (prefers-reduced-motion: reduce) {
- *,
- *::before,
- *::after {
- scroll-behavior: auto !important;
- transition-duration: 0ms !important;
- }
-}
diff --git a/docs/.vitepress/theme/tokens.css b/docs/.vitepress/theme/tokens.css
deleted file mode 100644
index ac7a9a9..0000000
--- a/docs/.vitepress/theme/tokens.css
+++ /dev/null
@@ -1,75 +0,0 @@
-:root {
- --color-paper: oklch(98.5% 0.006 240);
- --color-paper-raised: oklch(100% 0.004 240);
- --color-paper-soft: oklch(96% 0.01 230);
- --color-ink: oklch(22% 0.025 250);
- --color-ink-soft: oklch(42% 0.025 245);
- --color-muted: oklch(55% 0.02 240);
- --color-rule: oklch(89% 0.014 235);
- --color-rule-strong: oklch(78% 0.025 235);
- --color-accent: oklch(64% 0.2 40);
- --color-accent-strong: oklch(55% 0.2 38);
- --color-accent-soft: oklch(95% 0.035 45);
- --color-success: oklch(52% 0.13 157);
- --color-success-soft: oklch(95% 0.025 157);
- --color-info: oklch(56% 0.14 245);
- --color-info-soft: oklch(95% 0.025 245);
- --color-warning: oklch(64% 0.15 78);
- --color-code: oklch(19% 0.024 250);
- --color-code-ink: oklch(92% 0.012 235);
- --color-focus: oklch(58% 0.18 40);
-
- --font-display: "Bricolage Grotesque", "Noto Sans SC", sans-serif;
- --font-body: "Noto Sans SC", "Segoe UI", sans-serif;
- --font-mono: "JetBrains Mono", "Cascadia Code", monospace;
-
- --space-3xs: 0.125rem;
- --space-2xs: 0.25rem;
- --space-xs: 0.5rem;
- --space-sm: 0.75rem;
- --space-md: 1rem;
- --space-lg: 1.5rem;
- --space-xl: 2.5rem;
- --space-2xl: 4rem;
- --space-3xl: 6rem;
-
- --text-xs: 0.75rem;
- --text-sm: 0.875rem;
- --text-base: 1rem;
- --text-md: 1.125rem;
- --text-lg: 1.5rem;
- --text-xl: 2rem;
- --text-display: 3.5rem;
-
- --radius-sm: 4px;
- --radius-md: 6px;
- --radius-lg: 8px;
- --rule-thin: 1px;
- --dur-micro: 120ms;
- --dur-short: 220ms;
- --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
- --ease-in: cubic-bezier(0.7, 0, 0.84, 0);
- --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
-}
-
-.dark {
- --color-paper: oklch(17% 0.018 250);
- --color-paper-raised: oklch(21% 0.02 250);
- --color-paper-soft: oklch(24% 0.02 245);
- --color-ink: oklch(94% 0.01 235);
- --color-ink-soft: oklch(76% 0.018 235);
- --color-muted: oklch(66% 0.018 235);
- --color-rule: oklch(31% 0.025 245);
- --color-rule-strong: oklch(43% 0.035 240);
- --color-accent: oklch(72% 0.17 48);
- --color-accent-strong: oklch(78% 0.14 50);
- --color-accent-soft: oklch(27% 0.045 42);
- --color-success: oklch(72% 0.12 157);
- --color-success-soft: oklch(25% 0.04 157);
- --color-info: oklch(72% 0.11 245);
- --color-info-soft: oklch(25% 0.04 245);
- --color-warning: oklch(78% 0.12 82);
- --color-code: oklch(13% 0.018 250);
- --color-code-ink: oklch(92% 0.012 235);
- --color-focus: oklch(74% 0.15 48);
-}
diff --git a/docs/ZERO_TRUST.md b/docs/ZERO_TRUST.md
index c247a0f..ed5d723 100644
--- a/docs/ZERO_TRUST.md
+++ b/docs/ZERO_TRUST.md
@@ -113,16 +113,20 @@ my-team.cloudflareaccess.com
Cloudflare 官方获取 AUD 的说明:
https://developers.cloudflare.com/cloudflare-one/access-controls/applications/http-apps/authorization-cookie/validating-json/
-## 在 GitHub Actions 保存 Worker Secret
+## 在 Cloudflare 控制台保存 Worker Secret
-取得 Team Domain 与 AUD 后,在 GitHub 仓库进入 `Settings > Secrets and variables > Actions`:
+先按 [部署指南](../DEPLOYMENT.md) 配置 GitHub Actions,并手动运行一次 `Deploy` workflow。首次部署完成、Worker 出现在 Cloudflare 后:
-1. 添加 `ACCESS_TEAM_DOMAIN` Secret,粘贴前面取得的 Team Domain。
-2. 添加 `ACCESS_AUD` Secret,粘贴前面取得的 Application Audience (AUD) Tag。
-3. 添加 `ENCRYPTION_KEY` Secret。按照[运行时 Secret 指南](https://edgessh-docs.pages.dev/deploy/runtime-secrets#生成-encryption_key)生成 32 字节安全随机数的标准 Base64;首次生成后必须妥善保管,后续部署继续使用同一个值。
-4. 确认三项 Secret 均已添加,再手动运行一次 `Deploy` workflow。
+1. 打开 Cloudflare Dashboard,进入 `Workers & Pages`。
+2. 选择你的 EdgeSSH Worker(默认名称为 `edgessh`)。
+3. 进入 `Settings(设置)> Variables and Secrets(变量与机密)`。
+4. 添加 `ACCESS_TEAM_DOMAIN`,类型选择 **Secret**,粘贴前面取得的 Team Domain。
+5. 添加 `ACCESS_AUD`,类型选择 **Secret**,粘贴前面取得的 Application Audience (AUD) Tag。
+6. 按控制台提示保存并部署新版本。
-workflow 会通过 Wrangler 将这三项值同步为同名 Worker Secret。不要将它们保存成 GitHub Actions Variable,也不要写进仓库、本地命令或日志;生产部署不需要手动执行 Wrangler Secret 或 Worker 部署命令。
+不要把这两个值保存为 GitHub Actions Secret。GitHub Actions 只需要 `CLOUDFLARE_ACCOUNT_ID` 和 `CLOUDFLARE_API_TOKEN` 两项部署凭据;Access 参数属于 Worker 运行时 Secret,应始终留在 Cloudflare 中。
+
+EdgeSSH 还需要一个 `ENCRYPTION_KEY` Worker Secret。它与上述两项在同一个控制台页面配置,生成要求和密钥保管注意事项见 [部署指南](../DEPLOYMENT.md#5-在-worker-界面添加运行时-secret)。生产部署不需要在本地执行 Wrangler Secret 或 Worker 部署命令。
## 验证配置
@@ -140,9 +144,9 @@ workflow 会通过 Wrangler 将这三项值同步为同名 Worker Secret。不
检查:
-- `ACCESS_TEAM_DOMAIN` 是否已经保存为 GitHub Actions Secret,并由最新一次 `Deploy` workflow 成功同步。
+- `ACCESS_TEAM_DOMAIN` 是否已经在 Worker 的 `Variables and Secrets` 页面保存为 Secret,并部署到当前版本。
- Team Domain 是否为 `xxx.cloudflareaccess.com`,且没有 `https://`。
-- `ACCESS_AUD` 是否已保存为 GitHub Actions Secret,且值来自当前 Access 应用。
+- `ACCESS_AUD` 是否已在同一页面保存为 Secret,且值来自当前 Access 应用。
### 登录后提示“Access 登录已失效”
diff --git a/docs/deploy/actions.md b/docs/deploy/actions.md
deleted file mode 100644
index ee863f5..0000000
--- a/docs/deploy/actions.md
+++ /dev/null
@@ -1,186 +0,0 @@
-# 使用 GitHub Actions 部署
-
-这是推荐的生产部署方式。首次执行会创建或复用 D1、同步运行时 Secret、应用数据库迁移并发布 Worker;以后推送到 `main` 会复用同一批资源更新代码。
-
-## 开始前必须填写的 6 项
-
-正式部署只使用 GitHub 仓库的 `Settings → Secrets and variables → Actions` 作为配置入口。先一次填完下面 6 项,再运行 `Deploy` workflow:
-
-| 名称 | GitHub Actions 类型 | 填写内容 |
-| --- | --- | --- |
-| `CLOUDFLARE_ACCOUNT_ID` | Variable | Cloudflare 账户 ID |
-| `CUSTOM_DOMAIN` | Variable | EdgeSSH 的自定义域名,只写主机名 |
-| `CLOUDFLARE_API_TOKEN` | Secret | 具备 Worker、D1 与目标 Zone 权限的 API Token |
-| `ACCESS_TEAM_DOMAIN` | Secret | Zero Trust Team Domain,不带协议或路径 |
-| `ACCESS_AUD` | Secret | EdgeSSH Access 应用的 AUD Tag |
-| `ENCRYPTION_KEY` | Secret | 固定的 32 字节安全随机数 Base64 |
-
-::: danger 六项缺一不可
-本教程的正式部署流程要求 2 个 Variable 与 4 个 Secret 全部存在。运行时 Secret 会由 workflow 自动同步到 Worker;不要再到 Cloudflare Worker 控制台保存一套配置,也不要在控制台手工部署。
-:::
-
-## 部署路线图
-
-1. Fork EdgeSSH 仓库。
-2. 创建 Cloudflare API Token。
-3. 在 Cloudflare Zero Trust 创建 Access 应用。
-4. 在 GitHub 保存全部六项正式部署配置。
-5. 手动运行一次 `Deploy` workflow。
-6. 确认 workflow 已同步三个运行时 Secret。
-7. 访问自定义域名并完成验收。
-
-::: tip 一个配置入口
-六项正式部署配置全部保存在 GitHub Actions:账户 ID 与自定义域名使用 Variable,API Token、Access 参数与加密密钥使用 Secret。workflow 会通过 Cloudflare API 将三项运行时值同步为 Worker Secret,无需再到 Worker 控制台补填。
-:::
-
-## 1. Fork 仓库
-
-打开 [EdgeSSH GitHub 仓库](https://github.com/aozorae/EdgeSSH),选择 **Fork**,保留默认分支 `main`。
-
-Fork 完成后,地址通常是:
-
-```text
-https://github.com/<你的 GitHub 用户名>/EdgeSSH
-```
-
-
-
-## 2. 创建 Cloudflare API Token
-
-在 Cloudflare 个人资料的 **API Tokens** 页面创建 Token。以官方 **Edit Cloudflare Workers** 模板为起点,并确认 Token 具备:
-
-- Account:Workers Scripts Edit。
-- Account:D1 Edit。
-- Zone:目标域名所需的 Workers Routes Edit 权限。
-- Account Resources:只选择实际部署所用账户。
-- Zone Resources:只选择 EdgeSSH 域名所在 Zone。
-
-Token 只会完整显示一次。创建后直接保存到 GitHub Secret,不要粘贴到聊天、Issue、截图或仓库文件。
-
-详细配置见[创建 API Token](/deploy/api-token)。
-
-## 3. 在 GitHub Actions 填写六项配置
-
-进入你的 Fork:
-
-```text
-Settings → Secrets and variables → Actions
-```
-
-按照页面顶部的必填表,在 **Variables** 页签保存 `CLOUDFLARE_ACCOUNT_ID` 与 `CUSTOM_DOMAIN`,在 **Secrets** 页签保存另外四项。三项运行时 Secret 的值在下一节取得。Variable 的值会显示在设置页,Secret 保存后不会再次显示;不要把四项敏感值错放到 Variable。
-
-### 可选配置
-
-以下 Variable 不影响标准部署,第一次部署通常不要填写:
-
-| 名称 | 默认值 | 用途 |
-| --- | --- | --- |
-| `WORKER_NAME` | `edgessh` | 自定义 Worker 名称 |
-| `D1_DATABASE_NAME` | `-accounts` | 自定义 D1 数据库名称 |
-
-`DOCS_PROJECT_NAME` 只属于文档站的 `Deploy docs` workflow,不是 EdgeSSH 应用部署的必填项。
-
-
-
-## 4. 配置 Zero Trust Access
-
-在运行 Action 前,先为 `CUSTOM_DOMAIN` 中的同一域名创建 Self-hosted Access 应用,并添加只允许明确管理员邮箱的 Allow 策略。
-
-你需要从这个应用取得两个值:
-
-- `ACCESS_TEAM_DOMAIN`:例如 `my-team.cloudflareaccess.com`。
-- `ACCESS_AUD`:Application Audience (AUD) Tag。
-
-再按[运行时 Secret](/deploy/runtime-secrets)中的方式生成一次 `ENCRYPTION_KEY`,回到 GitHub Actions Secrets 保存这三项值。加密密钥在后续部署中必须保持不变。
-
-完整点击路径见[配置 Zero Trust Access](/deploy/zero-trust)。
-
-## 5. 首次运行 Deploy
-
-进入 Fork 的:
-
-```text
-Actions → Deploy → Run workflow
-```
-
-选择 `main` 后运行。workflow 会依次:
-
-1. 检出仓库并安装锁定版本依赖。
-2. 执行类型检查、自动化测试、文档构建与 Wrangler dry-run。
-3. 按 `D1_DATABASE_NAME` 精确查找 D1,不存在时创建一次。
-4. 生成只存在于 runner 内的临时 Wrangler 配置,写入真实 D1 ID。
-5. 从 GitHub Actions 读取三项运行时 Secret,并经标准输入同步给 Wrangler。
-6. 对远程 D1 执行 migration。
-7. 创建或更新 Worker,并绑定必填配置中的自定义域名。
-
-
-
-### 怎样判断成功
-
-等待运行记录显示绿色成功标记,并展开 **Prepare Cloudflare resources**、**Sync Worker secrets** 与对应的部署步骤。资源准备日志中应能看到:
-
-```text
-已创建 D1 数据库:edgessh-accounts
-```
-
-或后续部署中的:
-
-```text
-已复用 D1 数据库:edgessh-accounts
-```
-
-日志还应显示 Worker 名称和自定义域名,但不应出现 API Token、Access 参数或加密密钥。
-
-
-
-::: info 缺少 Secret 会直接停止部署
-workflow 会在发布 Worker 前校验三项运行时 Secret。任一项缺失时部署会失败,不会留下一个需要去控制台补配置的版本。
-:::
-
-## 6. 确认运行时 Secret 已同步
-
-成功的运行记录中,**Sync Worker secrets** 步骤会列出已处理的 Secret 名称,但不会显示值。三项值仅经标准输入交给 Wrangler,不进入命令参数、临时配置或日志。
-
-如需核验,可在 Cloudflare Worker 的 Variables and Secrets 页面确认三个名称存在;该页面不是配置入口,也不需要再次保存或部署。生成与保管加密密钥的规则见[Worker 运行时 Secret](/deploy/runtime-secrets)。
-
-## 7. 完成首次验收
-
-访问 `https://你的自定义域名`。正确顺序是:
-
-1. 浏览器先进入 Cloudflare Access 登录页。
-2. 只有 Allow 策略中的邮箱可以完成登录。
-3. 登录后显示 EdgeSSH 主机总览,而不是配置错误。
-4. 新建一台测试主机,核对首次连接指纹,再验收终端、SFTP 与进程面板。
-
-不要只看到首页就宣布部署完成。使用[部署后验收清单](/deploy/verification)逐项检查。
-
-## 后续更新
-
-以后将更新合并或推送到 `main` 即可。Action 会复用同名 D1,并从 GitHub Actions Secrets 幂等同步 Worker Secret;不需要在本地运行 Wrangler,也不需要重复创建资源。
-
-保持 `WORKER_NAME` 和 `D1_DATABASE_NAME` 稳定。修改数据库名称会切换到另一个数据库,不会自动迁移旧数据。
diff --git a/docs/deploy/api-token.md b/docs/deploy/api-token.md
deleted file mode 100644
index d2c5783..0000000
--- a/docs/deploy/api-token.md
+++ /dev/null
@@ -1,62 +0,0 @@
-# 创建 Cloudflare API Token
-
-部署 Token 只负责让 GitHub Actions 管理 Worker、D1 与自定义域名。它不是 EdgeSSH 的登录凭据,也不应作为 Worker 运行时变量。
-
-## 创建入口
-
-1. 登录 Cloudflare Dashboard。
-2. 打开右上角个人资料。
-3. 进入 **My Profile → API Tokens**。
-4. 选择 **Create Token**。
-5. 以 **Edit Cloudflare Workers** 模板作为起点。
-
-## 权限建议
-
-确保 Token 覆盖以下能力:
-
-| 范围 | 权限 | 用途 |
-| --- | --- | --- |
-| Account | Workers Scripts: Edit | 创建或更新 EdgeSSH Worker |
-| Account | D1: Edit | 查找、创建 D1 并执行 migration |
-| Zone | Workers Routes: Edit | 绑定 `CUSTOM_DOMAIN` |
-
-Cloudflare 控制台的权限名称可能随界面调整。判断标准是:Token 能部署 Worker、管理目标账户的 D1,并为目标 Zone 配置 Worker 自定义域名。
-
-资源范围应限定到实际账户和域名。EdgeSSH 不使用 KV 或 R2,不要为了省事授予无关资源权限。
-
-
-
-## 保存到 GitHub
-
-Token 创建完成后,只会完整显示一次:
-
-1. 复制 Token。
-2. 打开 Fork 的 `Settings → Secrets and variables → Actions`。
-3. 在 **Secrets** 中创建 `CLOUDFLARE_API_TOKEN`。
-4. 粘贴并保存。
-
-不要把 Token 存为普通 Variable。也不要把 Token 写入 `.env`、`.dev.vars`、`wrangler.toml`、README 或截图。
-
-API Token 只是正式部署的 6 项必填配置之一。创建完成后回到[GitHub Actions 完整部署流程](/deploy/actions#开始前必须填写的-6-项),将 2 个 Variable 与 4 个 Secret 一次配置完整。
-
-## 验证与排错
-
-Action 报 `Authentication error` 或 `Invalid API Token` 时:
-
-- 确认复制的是 API Token,不是 Global API Key。
-- 确认 Secret 名称精确为 `CLOUDFLARE_API_TOKEN`。
-- 确认 Token 尚未被撤销或过期。
-- 确认 Account Resources 包含 `CLOUDFLARE_ACCOUNT_ID` 对应账户。
-
-Action 能部署 Worker、但无法创建 D1 时,通常是缺少 Account `D1: Edit`。能部署但自定义域名绑定失败时,通常是 Zone 范围或 Workers Routes 权限不正确。
-
-## 文档站 Pages 权限
-
-仓库的 `Deploy docs` workflow 会把 VitePress 静态产物发布到独立 Cloudflare Pages 项目。若同一个 Token 也用于文档站,请额外确认它具备 Account `Cloudflare Pages: Edit`。文档项目默认名为 `edgessh-docs`,不会访问 EdgeSSH 的 D1 或 Worker Secret。
diff --git a/docs/deploy/runtime-secrets.md b/docs/deploy/runtime-secrets.md
deleted file mode 100644
index 4f6dc59..0000000
--- a/docs/deploy/runtime-secrets.md
+++ /dev/null
@@ -1,71 +0,0 @@
-# 通过 GitHub Actions 配置 Worker 运行时 Secret
-
-三个运行时 Secret 统一保存在 GitHub Actions Secrets 中。`Deploy` workflow 会在发布前将它们同步到 Worker,不需要进入 Cloudflare Worker 控制台补填。
-
-## 打开配置页
-
-在 GitHub 仓库进入:
-
-```text
-Settings → Secrets and variables → Actions → Secrets
-```
-
-## 添加三项 Secret
-
-| 名称 | 值 | 来源 |
-| --- | --- | --- |
-| `ACCESS_TEAM_DOMAIN` | `my-team.cloudflareaccess.com` | Zero Trust Settings |
-| `ACCESS_AUD` | 一串 Audience Tag | EdgeSSH Access 应用 |
-| `ENCRYPTION_KEY` | 32 字节随机数的标准 Base64 | 本机安全随机数工具 |
-
-三项都创建为 Repository Secret,不要保存为 Repository Variable。配置完成后运行 `Deploy` workflow,**Sync Worker secrets** 步骤会通过 Wrangler 批量同步它们。
-
-
-
-## 生成 ENCRYPTION_KEY
-
-在可信的本机终端运行下列任一命令。
-
-### Node.js
-
-```bash
-node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
-```
-
-### OpenSSL
-
-```bash
-openssl rand -base64 32
-```
-
-输出应是标准 Base64,解码后正好 32 字节。把它直接保存为 GitHub Actions Secret `ENCRYPTION_KEY`,不要使用普通密码、UUID、示例值或在线随机字符串网页。
-
-::: danger 加密密钥必须长期保管
-首次生成后,将 `ENCRYPTION_KEY` 保存到可信密码管理器。丢失密钥会导致 D1 中已有主机资料无法解密。当前版本不支持直接轮换,后续部署必须复用原值。
-:::
-
-## 哪些值不是环境变量
-
-不要手动创建以下同名变量:
-
-- `DB`:D1 binding,由部署配置生成。
-- `SSH_SESSIONS`:Durable Object binding。
-- `ASSETS`:Worker 静态资源 binding。
-
-`CONNECT_TIMEOUT_MS` 是普通变量,仓库默认值为 `10000`。除非确实需要调整 2,000 至 30,000 毫秒的建连超时,否则保持默认。
-
-## 保存后的检查
-
-1. GitHub Actions Secrets 列表中能看到三个 Secret 名称。
-2. 最新一次 `Deploy` 的 **Sync Worker secrets** 步骤成功。
-3. 访问正式域名并通过 Access 登录。
-4. 主机列表接口不再返回“管理员尚未配置 Zero Trust Access”。
-
-运行时 Secret 不需要在每次 Action 中重填。workflow 会把 GitHub 中的固定值幂等同步给 Worker;尤其不要直接替换 `ENCRYPTION_KEY`,否则已有主机资料将无法解密。
diff --git a/docs/deploy/troubleshooting.md b/docs/deploy/troubleshooting.md
deleted file mode 100644
index 0a57496..0000000
--- a/docs/deploy/troubleshooting.md
+++ /dev/null
@@ -1,80 +0,0 @@
-# 常见部署问题
-
-先找到失败发生在哪一层:GitHub Action、Cloudflare 资源、Access 身份、Worker Secret,还是 SSH 目标。不要通过关闭认证或放宽网络限制来绕过问题。
-
-## Action 找不到账户或认证失败
-
-检查:
-
-- `CLOUDFLARE_ACCOUNT_ID` 是否保存为 GitHub Variable。
-- `CLOUDFLARE_API_TOKEN` 是否保存为 GitHub Secret。
-- Token 的 Account Resources 是否包含该账户。
-- Token 是否过期、撤销或复制不完整。
-
-## D1 创建或 migration 失败
-
-常见原因:
-
-- Token 缺少 Account `D1: Edit`。
-- `D1_DATABASE_NAME` 含不支持字符。
-- 同一账户中存在名称冲突但权限不足以读取。
-- 上一次并发部署仍在运行。
-
-不要手工把某个 D1 ID写进 `wrangler.toml`。部署脚本会按数据库名称解析 ID,并只写入 runner 的临时配置。
-
-## 自定义域名绑定失败
-
-确认:
-
-- `CUSTOM_DOMAIN` 只有纯主机名。
-- 域名所在 Zone 已接入同一个 Cloudflare 账户。
-- Token 的 Zone Resources 包含这个 Zone。
-- Access 应用与 `CUSTOM_DOMAIN` 使用相同主机名。
-
-## Action 成功,但打开后返回 503
-
-确认最新一次 `Deploy` 包含并成功执行 **Sync Worker secrets**,然后检查:
-
-- GitHub Actions Secrets 中存在 `ACCESS_TEAM_DOMAIN`、`ACCESS_AUD`、`ENCRYPTION_KEY`。
-- 三项名称拼写完全一致,且没有误存为 Variable。
-- Team Domain 和 AUD 来自当前自定义域名对应的 Access 应用。
-
-Cloudflare Worker 的 Variables and Secrets 页面只用于核验三个名称是否已同步,不要在那里维护另一套值。
-
-## 页面先登录,之后提示 Access 已失效
-
-通常是 `ACCESS_AUD` 来自另一个应用,或 `ACCESS_TEAM_DOMAIN` 属于另一个 Zero Trust 组织。重新从当前自定义域名对应的 Self-hosted 应用复制 AUD。
-
-## 主机能保存但连接超时
-
-检查目标层:
-
-- 主机名是否解析到公网地址。
-- SSH 服务端口是否正确。
-- 目标防火墙是否允许外部连接。
-- 云服务商安全组是否开放对应端口。
-- 目标是否只允许固定来源 IP。Cloudflare Workers 出站地址不适合传统固定 IP 白名单。
-
-不要把 `CONNECT_TIMEOUT_MS` 调得很大来掩盖网络不可达。默认 10 秒足以区分正常连接与大多数路由问题。
-
-## 私钥认证失败
-
-当前只支持未加密 OpenSSH 格式的 Ed25519、RSA、ECDSA P-256/P-384/P-521 私钥。不支持:
-
-- 带 passphrase 的加密私钥。
-- PEM 或 PKCS#8 私钥。
-- SSH Agent。
-- 需要多轮交互的 MFA 登录。
-
-先在受信任终端用同一账号和密钥验证目标,再核对私钥格式。
-
-## 怎样提交有效 Issue
-
-提供:
-
-- 失败所在步骤。
-- Action 步骤名称与脱敏错误信息。
-- 浏览器、操作系统和目标 SSH 服务类型。
-- 能稳定复现的最少步骤。
-
-不要提供密码、私钥、API Token、Access JWT、Cookie、Team Domain、AUD、未脱敏 IP 或完整 Action 日志。
diff --git a/docs/deploy/verification.md b/docs/deploy/verification.md
deleted file mode 100644
index 12565b5..0000000
--- a/docs/deploy/verification.md
+++ /dev/null
@@ -1,87 +0,0 @@
-# 部署后验收
-
-把“网页能打开”当作开始,而不是结束。下面的检查从身份边界到真实 SSH 功能逐步推进,便于在出现问题时定位到正确层级。
-
-## 1. Access 入口
-
-- 在未登录浏览器中访问正式域名,应先出现 Cloudflare Access。
-- 使用不在 Allow 策略中的邮箱,不能进入 EdgeSSH。
-- 使用管理员邮箱登录后,应进入主机总览。
-- 正式使用始终从自定义域名进入,不把 `workers.dev` 地址分享为入口。
-
-## 2. 账号 API
-
-登录后直接访问:
-
-```text
-https://ssh.example.com/api/auth/me
-```
-
-把域名替换为你的实际入口。应返回当前 Access 身份的受控信息,而不是 401、503 或 HTML 登录页。
-
-若返回 503,优先检查三个 GitHub Actions Secret 是否已由最新一次 `Deploy` 成功同步。若返回身份失效,检查 Team Domain、AUD 与当前 Access 应用是否一致。
-
-## 3. 主机资料
-
-创建一台测试主机,建议填写:
-
-- 一个清晰的显示名称。
-- 公网地址与 SSH 端口。
-- 低权限测试用户名。
-- 测试分组。
-- 密码或受支持的未加密 OpenSSH 私钥。
-
-保存后刷新页面,确认记录仍然存在。再编辑非凭据字段,确认留空不会意外清除原凭据。
-
-
-
-## 4. 首次连接与指纹
-
-首次连接时,EdgeSSH 会展示服务器 SHA-256 指纹。不要直接确认:
-
-1. 从服务器控制台或可信渠道取得真实主机指纹。
-2. 逐字比较 EdgeSSH 显示值。
-3. 一致后再确认并发送 SSH 凭据。
-
-指纹不一致可能意味着服务器重装、地址指向了另一台机器,或连接遭到拦截。先查明原因,不要通过删除指纹来绕过。
-
-## 5. 终端
-
-- Shell 可以正常交互并显示中文。
-- 调整浏览器大小后,终端列数与行数同步。
-- 全屏进入与退出正常。
-- 返回主机总览后,会话按预期断开。
-- 目标使用 GB18030 或 Big5 时,切换对应终端编码验证显示。
-
-## 6. SFTP
-
-使用专门的测试目录验收:
-
-1. 浏览目录。
-2. 新建子目录。
-3. 上传一个小文件。
-4. 下载并比较文件内容。
-5. 重命名文件。
-6. 删除文件与空目录。
-
-不要使用生产关键目录做首次测试。当前单文件上传与下载上限为 64 MiB,非空目录不能直接删除。
-
-## 7. 进程与系统信息
-
-- 能显示 CPU、负载、内存与 Swap。
-- 能加载进程列表。
-- 能识别常见 Linux 发行版、版本与架构。
-- 辅助面板失败时,主终端会话仍能明确显示错误,不泄露内部堆栈。
-
-## 8. 清理测试数据
-
-删除本次创建的临时主机记录和目标服务器上的测试文件。不要清空整个 D1,也不要删除 `d1_migrations` 表。
-
-完成所有检查后,才可以把部署标记为可正常使用。
diff --git a/docs/deploy/zero-trust.md b/docs/deploy/zero-trust.md
deleted file mode 100644
index e82c58d..0000000
--- a/docs/deploy/zero-trust.md
+++ /dev/null
@@ -1,103 +0,0 @@
-# 配置 Cloudflare Zero Trust Access
-
-EdgeSSH 不提供本地用户名、密码或注册系统。生产入口必须先经过 Cloudflare Access,Worker 还会再次验证 Access 注入的 JWT。
-
-::: warning 使用明确身份
-推荐“明确邮箱 + Allow”。不要使用 `Everyone`、`Bypass`,也不要只按登录方式放行所有能够接收验证码的人。
-:::
-
-## 1. 准备登录方式
-
-个人部署可以使用 One-time PIN 邮件验证码。若 Zero Trust 组织中没有该选项:
-
-1. 进入 **Zero Trust → Integrations → Identity providers**。
-2. 选择 **Add new identity provider**。
-3. 添加 **One-time PIN** 并保存。
-
-如果已经配置 Google、GitHub、Microsoft Entra ID 或其他身份提供程序,可以继续使用已有方式。
-
-## 2. 创建 Self-hosted 应用
-
-1. 进入 **Zero Trust → Access controls → Applications**。
-2. 选择 **Create new application**。
-3. 选择 **Self-hosted and private**。
-4. 添加 Public hostname。
-5. Application name 填写 `EdgeSSH`。
-6. Public hostname 填写与 GitHub `CUSTOM_DOMAIN` 完全相同的域名。
-
-
-
-## 3. 添加 Allow 策略
-
-在应用的 Access policies 中创建策略:
-
-| 项目 | 推荐值 |
-| --- | --- |
-| Policy name | `EdgeSSH Admin` |
-| Action | `Allow` |
-| Rule type | `Include` |
-| Selector | `Emails` |
-| Value | 你的完整管理员邮箱 |
-
-需要多名管理员时,逐个添加明确邮箱。只有在你确实希望整个企业邮箱域都能管理服务器时,才使用整域授权。
-
-
-
-## 4. 获取 Team Domain
-
-进入 Zero Trust 设置,找到 Team name 或 Team domain。保存为 GitHub Actions Secret `ACCESS_TEAM_DOMAIN` 时只保留域名:
-
-```text
-my-team.cloudflareaccess.com
-```
-
-不要带 `https://`、路径或末尾斜杠。
-
-## 5. 获取 Application Audience
-
-1. 回到 **Access controls → Applications**。
-2. 打开刚创建的 EdgeSSH 应用。
-3. 在 Additional settings 中找到 **Application Audience (AUD) Tag**。
-4. 复制完整值,后续保存为 `ACCESS_AUD`。
-
-不要把应用 ID、Client ID 或策略 ID 当成 AUD。
-
-## 6. 验证 Access 本身
-
-在运行 `Deploy` workflow 前,也可以先访问自定义域名验证外层策略:
-
-- 未登录时应先出现 Cloudflare Access 登录页。
-- 不在 Allow 策略内的邮箱不能进入。
-- 允许的邮箱能够完成认证。
-
-
-
-## 常见问题
-
-### One-time PIN 收不到邮件
-
-先确认策略中的 Emails 与登录邮箱完全一致。Cloudflare 不会向未被策略允许的邮箱发送验证码,但页面可能仍显示已发送,避免泄露访问名单。
-
-### 登录后提示 Access 登录已失效
-
-通常是以下三项没有对应同一个应用:
-
-- 正在访问的自定义域名。
-- `ACCESS_TEAM_DOMAIN` 所属 Zero Trust 组织。
-- `ACCESS_AUD` 所属 Self-hosted 应用。
-
-### 页面没有出现 Access 登录
-
-确认 Access 应用 Public hostname 与 `CUSTOM_DOMAIN` 完全一致。不要把 `workers.dev` 诊断地址当作正式入口。
diff --git a/docs/development/contributing.md b/docs/development/contributing.md
deleted file mode 100644
index 6b7a171..0000000
--- a/docs/development/contributing.md
+++ /dev/null
@@ -1,42 +0,0 @@
-# 贡献指南
-
-欢迎通过 Issue 反馈问题,或通过 Pull Request 提交改进。
-
-## 提交前
-
-运行完整检查:
-
-```bash
-npm run check
-```
-
-如果修改了文档,再启动本地预览检查桌面和移动端:
-
-```bash
-npm run docs:dev
-```
-
-## 变更范围
-
-- 一个文件尽量承担单一职责。
-- 优先沿用现有模块、类型与错误响应结构。
-- 对外接口返回统一错误,不暴露堆栈、源码或内部数据。
-- Worker 请求链路中的 I/O 必须异步等待,避免阻塞计算。
-- 数据库迁移只追加必要 schema,不在普通部署中清空生产数据。
-
-## 测试真实功能
-
-涉及 SSH、SFTP、进程面板或操作系统探测时,使用你有权访问的测试服务器。Pull Request 中明确写出已验证的范围;没有真实目标时,不要声称真实连接已通过。
-
-## 安全报告
-
-Issue 和 Pull Request 中不要上传:
-
-- 密码或私钥。
-- Cloudflare API Token 或 Worker Secret。
-- Access JWT、Cookie、AUD 或 Team Domain。
-- 未脱敏的服务器 IP、域名或完整日志。
-
-## 上游与许可证
-
-EdgeSSH 基于 [Worker Web SSH](https://github.com/cmliu/CF-Workers-WebSSH) 深度开发,采用 Apache License 2.0。修改与分发时请保留适用的版权和许可证声明。
diff --git a/docs/development/docs-site.md b/docs/development/docs-site.md
deleted file mode 100644
index 391590d..0000000
--- a/docs/development/docs-site.md
+++ /dev/null
@@ -1,59 +0,0 @@
-# 部署文档站到 Cloudflare Pages
-
-VitePress 文档通过独立的 `Deploy docs` GitHub Actions workflow 发布到 Cloudflare Pages。它不会写入 EdgeSSH Worker 的 `dist/`,也不会访问 D1 或运行时 Secret。
-
-## 首次部署
-
-文档 workflow 复用两个部署凭据:
-
-- GitHub Variable `CLOUDFLARE_ACCOUNT_ID`。
-- GitHub Secret `CLOUDFLARE_API_TOKEN`。
-
-API Token 需要 Account `Cloudflare Pages: Edit`。可选 Variable `DOCS_PROJECT_NAME` 控制 Pages 项目名,默认 `edgessh-docs`。
-
-进入:
-
-```text
-Actions → Deploy docs → Run workflow
-```
-
-首次运行会按名称检查 Pages 项目,不存在时创建,存在时直接复用。随后构建 `docs/.vitepress/dist` 并发布到生产分支 `main`。
-
-## 添加多个自定义域名
-
-首次成功后打开 Cloudflare Dashboard:
-
-```text
-Workers & Pages → edgessh-docs → Custom domains
-```
-
-选择 **Set up a custom domain**,逐个添加文档域名。Pages 项目与 EdgeSSH Worker 相互独立,因此可以为文档站绑定多个域名,而不修改 Worker 的 `CUSTOM_DOMAIN`。
-
-
-
-## 自动触发
-
-推送到 `main` 且改动以下路径时,文档 workflow 自动运行:
-
-- `docs/**`
-- `package.json` 或 `package-lock.json`
-- 文档部署 workflow 与 Pages 准备脚本
-
-应用 Worker 的 `Deploy` workflow 与文档的 `Deploy docs` workflow 使用不同并发锁和不同产物目录,不会互相覆盖。纯 `docs/**`、文档 workflow 或 Pages 准备脚本改动也被 Worker workflow 排除,不会顺带发布生产 EdgeSSH 应用。
-
-## 本地预览
-
-```bash
-npm run docs:dev
-```
-
-生产构建检查:
-
-```bash
-npm run docs:build
-npm run docs:preview
-```
diff --git a/docs/development/local.md b/docs/development/local.md
deleted file mode 100644
index 6f73229..0000000
--- a/docs/development/local.md
+++ /dev/null
@@ -1,61 +0,0 @@
-# 本地开发
-
-本地开发需要 Node.js 22.12.0 或更高版本,以及 npm。
-
-## 安装依赖
-
-```bash
-git clone https://github.com/aozorae/EdgeSSH.git
-cd EdgeSSH
-npm ci
-```
-
-## 准备本地变量
-
-```bash
-cp .env.example .dev.vars
-```
-
-在 `.dev.vars` 中填写开发用的 `ACCESS_TEAM_DOMAIN`、`ACCESS_AUD` 与 `ENCRYPTION_KEY`。本地仍会执行真实 Access 身份校验,不提供匿名绕过。
-
-初始化本地 D1:
-
-```bash
-npx wrangler d1 migrations apply DB --local
-```
-
-## 启动 Worker
-
-```bash
-npm run dev
-```
-
-Wrangler 会按 `wrangler.toml` 构建前端并启动本地 Worker。
-
-需要前端热更新时,在另一个终端运行:
-
-```bash
-npm run dev:web
-```
-
-Vite 默认位于 `http://localhost:5173`,并把 `/api` 代理到本地 Worker 的 `8787` 端口。
-
-## 启动文档站
-
-```bash
-npm run docs:dev
-```
-
-VitePress 会输出本地地址。文档站不需要 D1、Access 或 SSH 目标。
-
-## 检查命令
-
-| 命令 | 用途 |
-| --- | --- |
-| `npm run typecheck` | 检查 Worker 与前端类型 |
-| `npm test` | 运行 Node 测试 |
-| `npm run build:web` | 构建 EdgeSSH 前端 |
-| `npm run docs:build` | 构建 VitePress 文档 |
-| `npm run check` | 类型、测试、文档与 Wrangler dry-run |
-
-涉及连接、SFTP 或进程面板的改动,最终仍要使用真实且已授权的 SSH 目标验收。
diff --git a/docs/development/workspace-theme.md b/docs/development/workspace-theme.md
deleted file mode 100644
index a124940..0000000
--- a/docs/development/workspace-theme.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# SSH 工作台主题适配
-
-## 维护边界
-
-主机总览继续由 `frontend/src/dashboard.css` 和 `dashboard.ts` 管理,布局、地球和主机列表不因工作台换肤而调整。
-
-`frontend/src/workspace-theme.css` 是 EdgeSSH 自有的薄适配层。在 `main.ts` 中紧跟上游 `style.css` 导入,复用总览的 `--home-*` 色彩与字体,将它们映射到上游既有的 `--bg`、`--panel`、`--accent` 等变量。组件覆盖限定在 `#app` 内,避免影响总览。
-
-- 默认浅色;原来的 `workers-webssh.theme` 偏好和深色切换继续生效。
-- 终端输出始终保留上游深色背景和 ANSI 调色板。绿色输出不是品牌色,不应全局替换;成功状态保留绿色,危险操作保留红色。
-- 工具栏、连接表单、文件/进程面板、会话日志与弹窗使用统一外壳;不复制业务组件,不改变 DOM ID、状态类、事件绑定或协议。
-- 小屏只通过 CSS 将资源指标放到工具栏第二行;原有文件树、分栏、全屏和面板开关继续由上游管理。
-
-## 合并上游时
-
-1. 正常合并上游的 `style.css`、`main.ts` 和业务模块,不把品牌样式搬回这些文件。
-2. 保留 `main.ts` 里最后导入的 `workspace-theme.css`,以及 HTML 的浅色默认值。
-3. 若上游改名或删除 CSS 变量/组件类,在适配层更新对应映射,而不是维护第二份组件。
-4. 执行 `npm run check`。主题的静态契约测试用于提醒入口或关键选择器漂移,不代替浏览器验收。
-5. 浏览器复核浅/深色、连接表单、空终端、已连接终端、文件/进程/日志、指纹与危险操作弹窗;检查 320、375、414、768px 与桌面。
-
-本次不修改 SSH/SFTP/进程逻辑,并不意味着未来合并永远无冲突。适配层依赖的上游 CSS 变量、类名或结构变化时,仍需要逐项验证。
diff --git a/docs/guide/overview.md b/docs/guide/overview.md
deleted file mode 100644
index a2f5887..0000000
--- a/docs/guide/overview.md
+++ /dev/null
@@ -1,48 +0,0 @@
-# 认识 EdgeSSH
-
-EdgeSSH 是运行在 Cloudflare Workers 上的个人 SSH 工作台。部署完成后,你可以从浏览器管理主机、打开交互终端、使用 SFTP 管理文件,并查看远端进程与系统资源。
-
-## 它解决什么问题
-
-传统 WebSSH 通常还要维护一台中转服务器。EdgeSSH 把前端静态资源、身份校验、主机资料和 SSH 客户端放在 Cloudflare 边缘运行:
-
-```text
-浏览器
- ├─ 主机总览与地球视图
- ├─ xterm.js 交互终端
- ├─ SFTP 文件管理
- └─ 进程与系统信息
- │ HTTPS / WebSocket
- ▼
-Cloudflare Access → Worker → Durable Object → TCP Socket → SSH 服务器
- │
- └─ D1:每个身份独立的加密主机资料
-```
-
-浏览器不会直接连接 SSH 服务器。每个连接会话由 Durable Object 隔离,Worker 通过 Cloudflare TCP Sockets 连接目标的公网 SSH 端口。
-
-## 主要能力
-
-| 能力 | 说明 |
-| --- | --- |
-| 主机管理 | 保存地址、端口、用户名、分组和认证信息;支持搜索、编辑与删除 |
-| 地球视图 | 根据公网 IP 显示主机位置;位置不代表在线状态 |
-| 浏览器终端 | 交互式 Shell、窗口尺寸同步、全屏、会话日志与多种终端编码 |
-| SFTP | 浏览目录、上传、下载、新建目录、重命名与删除空目录 |
-| 进程面板 | 查看 CPU、负载、内存、Swap 与进程列表 |
-| 系统识别 | 通过 SSH 探测操作系统、版本与架构 |
-
-## 凭据怎样保存
-
-主机资料通过 AES-256-GCM 加密后写入 D1。密文绑定 Access 用户身份与记录 ID,列表接口不会返回密码或私钥;建立连接时才在 Worker 内解密,并只把必要数据放入当前会话。
-
-::: warning 这不是端到端加密
-Worker 是实际的 SSH 客户端,会在会话内处理明文凭据。只把 EdgeSSH 部署到你信任的 Cloudflare 账户,并为 SSH 使用最小权限账号或密钥。
-:::
-
-## 推荐阅读顺序
-
-1. 阅读[部署前准备](/guide/requirements),确认账户、域名和目标服务器都满足条件。
-2. 按[GitHub Actions 完整部署流程](/deploy/actions)完成首次发布。
-3. 按[部署后验收](/deploy/verification)检查 Access、主机 API 和真实 SSH 连接。
-4. 使用前阅读[支持范围与限制](/reference/limits),避免把未支持的认证方式当作配置问题。
diff --git a/docs/guide/requirements.md b/docs/guide/requirements.md
deleted file mode 100644
index 2a70b2f..0000000
--- a/docs/guide/requirements.md
+++ /dev/null
@@ -1,61 +0,0 @@
-# 部署前准备
-
-先完成本页清单,再打开多个控制台操作。这样可以避免部署到一半才发现域名、权限或 SSH 目标不满足要求。
-
-## 必备资源
-
-- 一个 GitHub 账户,用于 Fork 仓库和运行 Actions。
-- 一个可使用 Workers、Durable Objects、D1 与 Pages 的 Cloudflare 账户。
-- 一个已经接入同一 Cloudflare 账户的域名,例如 `example.com`。
-- 一个准备分配给 EdgeSSH 的子域名,例如 `ssh.example.com`。
-- 一台你有权访问、能够从公网连接的 SSH 服务器。
-- 一个可以接收 Cloudflare Access 登录邮件的管理员邮箱,或已有的身份提供程序。
-
-::: info Node.js 只影响本地开发
-只用 GitHub Actions 部署时,不需要在自己的电脑安装 Node.js。需要本地开发时,使用 Node.js 22.12.0 或更高版本。
-:::
-
-## 域名要求
-
-正式入口应使用受 Cloudflare Access 保护的自定义域名。后续教程统一使用:
-
-```text
-ssh.example.com
-```
-
-替换为你自己的域名即可。填写 GitHub Variable 时只写主机名,不要带 `https://`、端口、路径或末尾斜杠。
-
-## SSH 目标要求
-
-目标必须解析到公网 IP。EdgeSSH 会拒绝回环地址、私有地址、链路本地地址和其他不应从公网访问的目标,以降低 SSRF 与 DNS 重绑定风险。
-
-建议为验收准备一个专用的低权限 SSH 账号,并确认:
-
-- SSH 服务监听公网可达端口。
-- 防火墙允许 Cloudflare Workers 发起连接。
-- 你已准备密码或一个受支持的未加密 OpenSSH 私钥。
-- 你知道首次连接时应该核对的服务器指纹。
-
-## 正式部署必须填写的 6 个值
-
-这 6 项全部在 GitHub 仓库的 **Secrets and variables → Actions** 中填写。完成之前不要运行 `Deploy` workflow:
-
-| 名称 | 保存位置 | 是否敏感 |
-| --- | --- | --- |
-| `CLOUDFLARE_ACCOUNT_ID` | GitHub Actions Variable | 否 |
-| `CUSTOM_DOMAIN` | GitHub Actions Variable | 否 |
-| `CLOUDFLARE_API_TOKEN` | GitHub Actions Secret | 是 |
-| `ACCESS_TEAM_DOMAIN` | GitHub Actions Secret,自动同步为 Worker Secret | 是 |
-| `ACCESS_AUD` | GitHub Actions Secret,自动同步为 Worker Secret | 是 |
-| `ENCRYPTION_KEY` | GitHub Actions Secret,自动同步为 Worker Secret | 是 |
-
-`WORKER_NAME` 与 `D1_DATABASE_NAME` 是可选项,通常不要填写。默认名称分别是 `edgessh` 与 `edgessh-accounts`。
-
-## 不要提前做的事
-
-- 不要手动创建 D1。Action 会按名称创建或复用数据库。
-- 不要修改 `wrangler.toml` 中的全零 D1 ID。它是版本库中的安全占位值。
-- 不要把运行时 Secret 保存为 GitHub Variable,也不要写入源码、`wrangler.toml` 或 Issue。
-- 不要先生成多个 `ENCRYPTION_KEY`。首次确定后要长期复用同一个值。
-
-准备完成后,进入[GitHub Actions 完整部署流程](/deploy/actions)。
diff --git a/docs/index.md b/docs/index.md
deleted file mode 100644
index ca1d7b2..0000000
--- a/docs/index.md
+++ /dev/null
@@ -1,63 +0,0 @@
----
-layout: home
-
-hero:
- name: EdgeSSH
- text: 从 Fork 到上线,可逐项核对
- tagline: 使用 GitHub Actions 创建或复用 Cloudflare 资源。无需手写 D1 ID,也无需在本地执行生产部署命令。
- actions:
- - theme: brand
- text: 使用 Actions 部署
- link: /deploy/actions
- - theme: alt
- text: 先了解 EdgeSSH
- link: /guide/overview
----
-
-
-
-
- 主机资料加密保存在 D1;从总览进入终端、SFTP 和进程面板。
-
-
-
打开浏览器,连接你的服务器。
-
EdgeSSH 运行于 Cloudflare Workers。浏览器只连接 Worker,由独立 Durable Object 会话通过 TCP Socket 连接公网 SSH 服务。
-
查看安全边界
-
-
-
-
- 一条可以重复执行的部署路径
-
-
-
01
-
准备 Cloudflare
-
接入域名,创建最小权限 API Token,并配置 Access 应用。
-
-
-
02
-
填写 GitHub
-
保存账户 ID、自定义域名与四项 Secret,名称通常保持默认。
-
-
-
03
-
运行 Deploy
-
Action 自动检查项目、创建或复用 D1、执行迁移并发布 Worker。
-
-
-
04
-
验证自动同步
-
确认 Action 已同步 Access 参数与固定加密密钥,再完成验收。
-
-
-
-
-
-
-
先明确边界,再保存凭据
-
-
-
EdgeSSH 面向个人管理员,不提供本地注册或匿名入口。Cloudflare Access 负责身份认证,Worker 在应用内再次校验 Access JWT。它不是端到端加密:Worker 在建立 SSH 会话时会处理明文凭据,因此 Cloudflare 账户和 Worker Secret 管理权限都属于信任边界。
-
开始完整部署
-
-
diff --git a/docs/public/images/edgessh-dashboard-demo.png b/docs/public/images/edgessh-dashboard-demo.png
deleted file mode 100644
index ed846dae4ae81c1bf3cb4dd551529389461b4648..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 206997
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