- 人工智能
- AI Agent
- 网络安全
- 后端
- 前端
【免费下载链接】AiSOC
Open-source AI Security Operations Center: alert fusion, LLM-agent triage, MITRE ATT&CK investigation, and a replayable decision ledger for every agent step. Self-hostable, runs with no API keys, MIT licensed. Ships an MCP server for Claude, Cursor and Continue.
一个安全产品的公开演示如果随时可能打不开,会比没有演示更伤信任。AiSOC 的 ADR-0004 记录了团队如何把tryaisoc.com/dashboard从"维护者笔记本 + Cloudflare Tunnel"迁移到"托管模式管线上的常驻 Fly.io demo 租户",并保留了录屏作为兜底。本文以该决策记录为骨架,结合仓库里真实存在的infra/fly/部署脚本、seed_demo.py数据灌入器、demo 只读中间件与前端 demo 模式模块,逐层还原这套方案的架构、参数、幂等设计与运维验证方式,同时明确指出 ADR 中哪些路径至今仍不存在,避免读者照抄一个不存在的实现。
背景:为什么"演示掉线"比"没有演示"更糟
ADR 开篇给出的现状非常直白:tryaisoc.com/dashboard是产品的在线演示,但它由一个Cloudflare Tunnel 反向代理到维护者自建机器上的实例:
"the demo at tryaisoc.com is a self-hosted instance fronted by a Cloudflare Tunnel — when it's reachable, the stack is running locally on a maintainer's box. It can therefore go offline at any time."
对安全产品而言,这种形态的失败模式是结构性的:单一笔记本、单一网络、单一维护者的作息。ADR 引用的两个季度遥测显示,隧道在营业时间的约 6%内处于不可用状态。潜客点开落地页 CTA 后看到连接超时,会直接推断"产品本身坏了",而不是"演示环境没开"。
因此决策范围被收敛为二选一:
- (a)投入一个真正常驻的部署,保留落地页的实时演示 CTA;
- (b)移除实时演示 CTA,改为指向托管录屏(Phase 4.3 产出的 90 秒视频)。
ADR 特别说明:Phase 4.3 的录屏与 Phase 4.2 的托管模式管线都已存在,所以两条路都不是从零开始。这也是后续"两边并存"方案能成立的前提。
决策:常驻 Fly.io demo 租户,并保留双通道兜底
ADR 的决策是(a):投入一个镜像托管模式管线的常驻 Fly.io 部署,保留落地页的实时演示 CTA,退役 Cloudflare Tunnel。具体拆成五条可执行约定:
| # | 决策内容 | 设计意图 |
|---|---|---|
| 1 | 用 Phase 4.2 的自动开通管线,在tryaisoc.com/dashboard上开一个名为demo的托管租户,清单文件放在infra/fly/managed/tenants/demo.yaml,由 AiSOC 核心团队而非客户成功团队持有 | demo 与真实托管租户走同一条代码路径,避免"演示环境专用的一坨脚本" |
| 2 | demo租户拥有独立 Fly 应用栈(API + realtime + web)、独立 Fly Postgres、独立 Fly Redis,形状与真实托管租户完全一致;唯一区别是每次重新部署都灌入确定性 fixtures(与录屏录制时使用同一套) | 隔离性与真实租户对齐,数据可预测 |
| 3 | 增加一个每日 GitHub Actions 任务(.github/workflows/demo-reseed.yml),对 demo 租户执行flyctl ssh console,调用services/api/app/demo/seed.py重灌数据;过程幂等——先删既有 demo 数据,再重建标准场景 | 防止"被足够多人点过之后,每个潜客看到不同的坏状态" |
| 4 | 两个展示面并存:落地 CTA 指向/dashboard(实时),同一区块内嵌 90 秒录屏作为兜底;部署不可用时录屏仍可播放 | 消除死胡同 UX |
| 5 | README 中"demo lives at"的措辞改为 demo 租户 SLO("目标是 demo dashboard 99.9% 可达;若不可达,下方录屏为兜底"),删掉 Cloudflare Tunnel 免责声明 | 不再为演示掉线道歉 |
注意第 3 条与仓库实际实现存在差异,后文"与仓库现状对齐"一节会详细说明:仓库里真正落地的重置机制是Fly 定时机器 +release_command+ 部署后 SSH 引导三条路径,而不是 GitHub Actions。
落地架构:一次访问要经过哪些组件
ADR 把 demo 定义为"与托管租户同形状"的一套栈。仓库中的 infra/fly/README.md 给出了这套栈的完整拓扑与主机名划分,可以据此还原"点开 CTA 之后发生了什么":
tryaisoc.com api.tryaisoc.com ws.tryaisoc.com │ │ │ ▼ ▼ ▼ ┌──────────────┐ ┌──────────────────┐ ┌──────────────────────┐ │ aisoc-demo- │ │ aisoc-demo-api │ │ aisoc-demo-realtime │ │ web (Next.js)│ │ (FastAPI) │ │ (WebSocket) │ └──────────────┘ └──────────────────┘ └──────────────────────┘ │ │ │ └───── 6PN internal ────┴─────────────────────┘ │ ┌───────────┴───────────┐ ▼ ▼ ┌────────────────────┐ ┌──────────────────────┐ │ aisoc-demo-agents │ │ Fly Postgres / Redis │ │ AISOC_AGENT_MODE= │ │ demo 专用实例 │ │ deterministic │ └──────────────────────┘ └────────────────────┘为什么是三个公网主机名而不是全部挂在 apex 上,README 给出的理由是实现层面的:realtime 服务使用原生 WebSocket,Next.js 的 rewrites 在生产环境无法代理 WS;而把/api/v1/*全部经 web 应用转发会让每个 API 调用都穿过 web 机器,延迟翻倍并限制独立扩容。拆分api./ws.到各自的 Fly 证书上,也让浏览器的 CORS/CSP 边界保持显式。
为了把 demo 做"薄",一组可选组件在 demo 路径上被显式关闭(对应 infra/fly/api/fly.toml 的[env]段):
| 组件 | demo 状态 | 关闭原因 |
|---|---|---|
| Kafka | 关闭 | realtime 在 demo 路径上使用 Redis pub/sub |
| ClickHouse | 关闭 | demo 流程没有分析型查询 |
| OpenSearch | 关闭 | 检测规则随合成匹配载荷一起下发 |
| Neo4j | 关闭 | 攻击图不在标准 demo 路径上 |
| Qdrant | 关闭 | 知识库查询走镜像内 Postgres +pg_trgm |
对应环境变量为AISOC_DISABLE_KAFKA、AISOC_DISABLE_CLICKHOUSE、AISOC_DISABLE_NEO4J、AISOC_DISABLE_QDRANT。自建用户需要完整栈时,入口是仓库根目录的docker-compose.yml与各服务Dockerfile。
与仓库现状对齐:ADR 中哪些路径存在,哪些不存在
这是本文最需要强调的一节。ADR 自身在 Consequences 里就加了一条:::caution,明确说明那七个"随 ADR 落地的代码/基础设施变更"是计划清单而非已交付记录,其中四项从未进入仓库。我逐项核对了当前工作树:
| ADR 列出的路径 | 当前仓库状态 | 说明 |
|---|---|---|
infra/fly/managed/tenants/demo.yaml | 不存在 | tenants/下只有_example.yaml |
.github/workflows/demo-reseed.yml | 不存在 | 工作流目录中无此文件 |
services/api/app/demo/seed.py | 不存在 | 无app/demo/目录 |
docs/runbooks/demo-tenant-down.md | 不存在 | docs/runbooks/有service-down.md等通用手册,但无此文件 |
apps/web/src/components/landing/Hero.tsx | 未按此路径落地 | 实际承载 CTA 与录屏卡片的是 onboarding 组件 |
apps/web/public/_redirects | 不存在 | — |
README.md的 "Try it live" 章节 | 不存在 | ADR 自己也注明 README 从未有过该标题 |
ADR 给出的等价实现是可执行的:确定性的灌入器真实存在于 services/api/app/scripts/seed_demo.py,并且可以通过make demo触发。这一点非常重要——ADR 的决策意图成立,但引用其路径前必须先核对工作树,否则会把读者引向 404。
更关键的现状是:infra/fly/README.md 顶部记录了 2026-09-28 的实测结论——这套 Fly 栈当前并不存在。Fly 组织存在逾期账单,所有应用因欠费被回收,三个*.fly.dev主机名均为NXDOMAIN;api.与ws.子域仍解析到 Cloudflare 并返回HTTP 525(边缘在,源站已消失)。原本从该目录部署的deploy-api.yml与deploy-web.yml两个工作流也已被移除,因为它们早已退化为仅workflow_dispatch可触发、且每次在main上的运行都失败。
因此阅读infra/fly/的正确姿势是:把它当作一套可复现的部署设计与脚本,而不是一个正在运行的环境的说明。README 也给出了恢复顺序(见下文"运维验证"一节)。
数据确定性:seed_demo.py如何保证每个访客看到同一场调查
ADR 把"确定性 fixtures"作为 demo 租户与真实租户的唯一差异,这条要求在代码层由 services/api/app/scripts/seed_demo.py 承担。该模块的文档字符串明确了两套模式:
完整 seed(默认):填充 BOTS 形状的标准目录——15 个手工编写的INC-RT-*事件(其中INC-RT-001是一个进行中的调查)、28 条随机化告警,以及配套的连接器集合。这就是pnpm aisoc:demo与托管 demo 使用的数据集。
快速 seed(--demo-quick):只填充四个确定性用例(DEMO-001钓鱼、DEMO-002云接管、DEMO-003内部人员外泄、DEMO-004勒索),并使用固定墙钟时间,使重复运行逐字节稳定;_purge_demo_quick会在重灌前删除这四个DEMO-*事件,保证是"干净重置"而非重复插入。
幂等性在两处体现:
- 模块文档明确写入"运行两次产生相同数据集且从不重复插入行";
fly.toml的release_command注释说明灌入器在标准INC-RT-*编号已存在时会短路返回,因此可以在每次flyctl deploy上安全执行。
INC-RT-001之所以是整条链路的锚点,是因为前端 deeplink 直接指向它。在 apps/web/src/lib/demoMode.ts 中:
export function demoDeeplink(): string { return process.env.NEXT_PUBLIC_DEMO_DEEPLINK?.trim() || '/cases/INC-RT-001?tab=ledger'; }注释写明了选择理由:INC-RT-001是展示用场景——检测器刚刚触发、加密正在进行、账本正在流式输出 agent 的实时决策;运维方可用NEXT_PUBLIC_DEMO_DEEPLINK覆盖为其他事件。[infra/fly/web/fly.toml](https://link.gitcode.com/i/65dd27331999a381bc6db597afa64dc7)中该值同样被显式设为/cases/INC-RT-001?tab=ledger,并注明必须与seed_demo.py保持同步。
灌入器还有一道容易被忽略的安全护栏:_refuse_in_production()。它会读取AISOC_ALLOW_SEED,当该值为1/true/yes时直接放行;否则检查ENVIRONMENT/ENV与AISOC_DEMO_MODE,仅当处于 demo 模式或dev/development/local/test/空环境时才继续,其余情况以退出码 2 终止并提示"这会写入合成安全事件,请显式设置AISOC_ALLOW_SEED=1"。仓库根 Makefile 的demo目标正是这样调用的:
make demo # 内部执行:$(COMPOSE) run --rm -e AISOC_ALLOW_SEED=1 api python -m app.scripts.seed_demo这条护栏解释了为什么本地复现 demo 数据不会误伤生产库:合成数据本身足够逼真,才更需要一个显式的确认开关。
重播与预热:demo_seed.py在 seed 之上多做了什么
ADR 第 3 条要求"删除既有 demo 数据、重建标准场景"。仓库中真正承担"重置 + 预热"职责的是 services/api/app/scripts/demo_seed.py,它是seed_demo的 kickoff 包装器,文档字符串列出了三种调用方式:
- 部署后:
infra/fly/fly-demo-deploy.sh在部署完成后通过flyctl ssh console -a aisoc-demo-api -C "python -m app.scripts.demo_seed --reset --kickoff-investigation"执行,让第一批访客立刻看到进行中的调查; - 每日 cron:api 应用上的 Fly 定时机器每 24 小时在 00:00 UTC 重跑,擦除 demo 状态并重灌;
- 本地恢复:自建者可在仓库根执行
python scripts/demo_seed.py --reset,该 shim 会转发到本模块。
它把三件事收进一个入口:
- 清理 demo 租户数据,保证每位访客看到同一个
INC-RT-001LockBit 3.0 进行中调查; - 复用
seed_demo重灌标准数据(与自建者pnpm seed:demo得到的数据一致); - 预热一次调查,使
/cases/INC-RT-001?tab=ledger在浏览器到达时已有 agent 事件在流。
模块另有两点实现细节值得注意:它会先等待 API(默认 60 秒超时)与 agents 健康,agents 不健康时降级为跳过 kickoff 而不是失败("错过 kickoff 时 demo 仍可用,访客只是看到冷事件");并且支持--dry-run,只打印将要执行的动作而不触碰状态。
Fly 配置:把冷启动挡在 TTFI 预算之外
ADR 的目标是"点开即见调查",而 infra/fly/README.md 把这个目标量化为sub-60s time-to-first-investigation(TTFI)。配置层面对应几个关键决定,全部在 infra/fly/api/fly.toml 中:
1.release_command在流量进入前完成迁移与灌数([infra/fly/api/fly.toml](https://link.gitcode.com/i/0ccf4d0f0fd06da61eefa8a43f57b884)第 55–56 行):
[deploy] release_command = "/bin/sh -c 'rm -f /tmp/aisoc-migrations-ok && python -m app.scripts.run_migrations && { [ -f /tmp/aisoc-migrations-ok ] && python -m app.scripts.seed_demo || echo \"[release_command] migrations soft-failed (DB unreachable) — seed_demo skipped, API code deploy proceeds\"; }'"这里没有用朴素的&&,原因写在注释里:在AISOC_MIGRATIONS_REQUIRED=0的 demo 策略下,迁移器即使在整个重试预算内都连不上 Postgres 也会以 0 退出(避免 Fly Postgres 抖动劫持一次 API 代码发布),但此时seed_demo会以同样的连接失败让部署崩溃。包装器用"迁移成功哨兵文件"来区分两种情况:软失败跳过灌数,SQL 级硬失败仍以非零退出中止部署。这是"可用性优先但不掩盖真实错误"的典型折中。
2. 常驻而非自动停机(同文件第 103–114 行):
[http_service] internal_port = 8000 force_https = true auto_stop_machines = "off" auto_start_machines = true min_machines_running = 1 # keep one warm for sub-60s TTFI注释解释了为什么不能省:web 应用通过 6PN(aisoc-demo-api.internal:8000)代理/api/v1/*,而6PN 连接不会触发 Fly 的自动启动。机器一旦停掉,即使公网api.tryaisoc.com边缘能唤醒它,dashboard 的内部调用仍会 500。因此单台 demo 机器必须 7×24 保持热态。
3. 健康检查与规格:GET /health,15 秒间隔、5 秒超时、20 秒宽限期;VM 为shared-cpu-1x/ 1024MB。
4. 灌数的三条执行路径(infra/fly/README.md 的表格,与部署脚本第 318–346 行一致):
| 时机 | 方式 |
|---|---|
| 每次部署 | [deploy].release_command执行迁移 +python -m app.scripts.seed_demo,对已灌库为无操作 |
| 部署后引导 | flyctl ssh console -a aisoc-demo-api -C "python -m app.scripts.seed_demo",首次部署的双保险 |
| 每日刷新(00:00 UTC) | aisoc-demo-api上的定时机器aisoc-demo-seed-cron,用同一镜像跑同一命令后退出 |
| 本地恢复 | docker-compose -f infra/compose/docker-compose.demo.yml栈内执行同一模块 |
灌入器不是第五个 Fly 应用,而是随 api 镜像分发(python -m app.scripts.seed_demo),这样避免维护第五个 Dockerfile、也避免为跑 30 秒脚本付一个应用的常驻成本。部署脚本用flyctl machine run --schedule daily --name aisoc-demo-seed-cron -- python -m app.scripts.seed_demo创建定时机器,重复执行时因名称冲突而非零退出,被|| log "(daily seed cron already configured)"安静吞掉。
只读演示面:写保护、横幅与前端一致性
ADR 要求 demo 保留"真实产品可点击"的体验,同时不能让访客改坏共享数据集。这由两层实现:
后端:DemoModeMiddleware(services/api/app/middleware/demo_mode.py)。文档字符串明确了它的定位——不是auth/RBAC 的替代品,而是叠加在 demo 租户只读 RBAC 角色之上的纵深防御。行为分三支:
- 安全方法(
GET/HEAD/OPTIONS)或允许前缀(/health、/metrics、/api/docs、/api/v1/auth/、/api/v1/waitlist/等)直接放行并盖章; - 命中 demo 写白名单则放行。白名单是三条正则(第 56–64 行):
_DEMO_WRITE_ALLOWLIST: tuple[re.Pattern[str], ...] = ( re.compile(r"^/api/v1/cases/INC(?:-RT)?-\d{3}/investigate$"), re.compile(r"^/api/v1/investigations/[^/]+/cancel$"), re.compile(r"^/api/v1/alerts/[^/]+/ack$"), )- 其余写请求返回 403,载荷包含
error: "demo_mode_read_only"、说明文案、self_host_url、blocked_path、blocked_method。
每个响应(包括被拦截的 403)都会被盖上三个头:X-AiSOC-Demo: true、X-AiSOC-Demo-Tenant、X-AiSOC-Demo-Banner。注释里还记录了一个真实的 QA 回归:/api/v1/waitlist/曾被 demo 模式挡住,导致公网转化漏斗对每位访客返回 403(2026-07-19),因为等待名单与 demo SOC 数据集是不同表,写入不会污染共享数据。
中间件的挂载点在app.main.create_application中、CORS/GZip 之后,这样头部也能落在被缓存的错误响应上。对应测试是 services/api/tests/test_demo_mode.py,它用一个 stub Starlette 应用分别验证:关闭时中间件透明且不带头部;开启时 GET 通过、白名单写通过、任意写返回 403 与demo_mode_read_only。
前端:runtime 与 build 的优先级。apps/web/src/lib/demoMode.ts 解决了一个容易被忽视的不一致:API 用运行时的AISOC_DEMO_MODE决定是否灌数,而控制台曾只读NEXT_PUBLIC_DEMO_MODE——后者被 Next 在构建时内联进 bundle,于是拉取镜像运行时,控制台的答案被冻结在构建时刻,可能与 API 的判断相反,且外部无从查证。现在的规则是:
- 测试覆盖优先;
- 服务端读取运行时
AISOC_DEMO_MODE,命中即返回source: 'runtime',优先级高于编译值; - 否则读构建期
NEXT_PUBLIC_DEMO_MODE,返回source: 'build'; - 都没有则
enabled: false, source: 'default'。
浏览器侧只能拿到编译值(AISOC_DEMO_MODE无NEXT_PUBLIC_前缀,不会被内联),因此那是首屏值,由DemoBanner用GET /api/runtime-config(apps/web/src/app/api/runtime-config/route.ts)对账——接口的值不可能过期。横幅文案与 deeplink 同样集中在demoBannerMessage()/demoDeeplink()两个函数里,避免多处硬编码漂移。
双通道兜底:实时 CTA 与录屏并列
ADR 第 4 条是"两个展示面并存",这也是它反对"只保留录屏"的核心论据。仓库中最接近该意图的实现是 onboarding 首屏组件 apps/web/src/components/onboarding/StartHero.tsx:三个主 CTA 之下渲染一个次级卡片DemoScreencast,链接到/demo/demo.mp4,缩略图用/demo/demo-poster.png(真实帧,避免手绘 SVG 与内容漂移)。
该组件的注释记录了一次值得引以为戒的修复:这张卡片曾经链接/demo.mp4——一个文本占位符,标签写着"Coming v8.0",等于"一个 404 披着承诺",并且在三个大版本里没人发现。现在它指向实际存在的录制,文案描述的是该录制的真实内容,而不是仍未录制的四用例产品导览(后者 brief 在apps/web/public/.demo-mp4-placeholder)。
录屏资产本身在 apps/web/public/demo/README.md 中有完整披露,这对"事实准确"极其重要:
demo.mp4:2 分 57 秒,1280×720,H.264,无音轨,约 1.6 MB;hero.gif:17 秒预览循环,从demo.mp4截取;demo-poster.png:1280×720 静帧。
也就是说,ADR 所说的"90 秒录屏"与仓库中的 2 分 57 秒部署走查并不是同一个东西;StartHero注释也明确写着四用例产品导览"仍未录制"。此外 README 诚实区分了录制中的真实与编排部分:栈、镜像(从ghcr.io拉取而非本地构建)、威胁情报源、告警与 token 计数都是真实的,demo 模式关闭、未灌任何数据;被推送的那条事件是按代表性构造的,其下游全都是产品自身的工作。终端等待被压缩并由屏上角标全程标注,浏览器段落为真实速度。两轮 triage 均回落到确定性路径(控制台标为kafka:auto_triage:deterministic),录制版本为 v11.0.0,早于apps/web/public/screenshots/在 v20.1.0 的重拍。
这种"按原样保留、不重剪以美化产品"的处理方式,本身就是 ADR 想传达的态度:演示的可信度来自可核对的证据,而不是流畅的观感。
复用托管模式:一份 YAML 如何变成一整套 Fly 应用
ADR 第 1、2 条要求 demo 租户走托管模式管线。管线形状记录在 docs/managed-mode.md,实现位于 infra/fly/managed/。核心是"清单即事实":新增租户的唯一产物是在infra/fly/managed/tenants/{slug}.yaml放一个文件。
清单字段(模板见 infra/fly/managed/tenants/_example.yaml):
slug: example # 必填,[a-z0-9-]{2,32},驱动 Fly 应用名 display_name: Example Inc # 必填,面向运维的名称 plan: standard # 必填,starter | standard | enterprise region: iad # 主 Fly 区域 base_domain: aisoc.example.com # 必填,该租户的公网主机名根 contact_email: ops@example.com # 必填,Fly 用于证书续期通知 # 可选旋钮(括号为默认值): api_vm_size: shared-cpu-1x # (shared-cpu-1x) agents_vm_size: shared-cpu-2x # (shared-cpu-2x) postgres_plan: development # (development);生产用 ha realtime_min_machines: 1 # (1,WS 需要热机)以下划线开头的文件会被工作流忽略,因此_example.yaml永远不会误开通真实租户。应用名遵循aisoc-{slug}-{component}:
| 组件 | Fly 应用 | 主机名(DNS 生效后) |
|---|---|---|
| API | aisoc-{slug}-api | api.{base_domain} |
| Web | aisoc-{slug}-web | {base_domain} |
| Realtime | aisoc-{slug}-realtime | ws.{base_domain} |
| Agents | aisoc-{slug}-agents | 仅内部 6PN |
| Postgres | aisoc-{slug}-postgres | 仅内部 6PN |
| Redis | aisoc-{slug}-redis | 仅内部 6PN |
infra/fly/managed/provision.sh 的头部注释把八个幂等步骤写得很清楚:渲染 manifest 为各组件fly.toml→apps create→postgres create→ 附加 Postgres 注入DATABASE_URL→ 创建 Upstash Redis 并把 URL 写入各应用的REDIS_URL→仅在首次运行时分配 Fernet 密钥AISOC_CREDENTIAL_KEY→ 依序部署 api/agents/realtime/web → 添加主机名并申请证书。脚本要求FLY_API_TOKEN、FLY_ORG(默认aisoc-managed),并依赖flyctl、yq、envsubst、python3。
第 6 步的注释值得单列:复用既有 Fernet 密钥是关键,因为轮换会让所有已存储的连接器凭据失效。docs/managed-mode.md 补充说明该密钥不会自动轮换,轮换需按客户发起请求走运维手册,且必须重新加密全部凭据。
隔离语义也在此明确:每客户一套 Fly 应用集意味着专用 Postgres(不靠 RLS 共享集群)、专用 Redis、专用网络面(agent 只经aisoc-{slug}-api.internal:8000访问 API),以及只存在于该栈flyctl secrets中的凭据。文档直言这是"刻意强于单共享集群 + RLS"的隔离契约。
另外,docs/managed-mode.md说明 CSM 可以开清单 PR,但合并需要FLY_API_TOKEN仓库密钥,因此只有工程师能合并——合并即是显式的付费提交闸门。若需回滚交易,后续 PR 删除该清单文件并将仓库变量AISOC_ALLOW_DEPROVISION设为1,即可整体拆除。
运维验证:不要用部署工具的退出码证明 demo 活着
infra/fly/README.md 里有一段话直指本次决策最痛的教训:
A
flyctl deploythat returns zero is not a demo that works — the machine can boot, fail its first request, and sit there.
这正是 README 记载"公开演示曾经在绿灯运行背后停摆数周"的原因,也是被删除的两个工作流唯一比 shell 脚本多做的事:问应用,而不是问部署工具。对应的冒烟检查是:
# API 存活 curl -sf https://aisoc-demo-api.fly.dev/health # demo 头部可见 curl -sI https://aisoc-demo-api.fly.dev/api/v1/cases | grep -i x-aisoc # 写请求被拦截 curl -si -X POST https://aisoc-demo-api.fly.dev/api/v1/cases | head -3 # 期望:HTTP/2 403 … # 公网域名(DNS 生效后) curl -sf https://api.tryaisoc.com/health # 访客路径 open https://tryaisoc.com/cases/INC-RT-001?tab=ledger常见故障的定位顺序也被整理成表:flyctl deploy卡在 builder →flyctl builders destroy;首次请求 503 → 冷启动,检查min_machines_running=1;页面显示"数据每日重置"但写入仍生效 → API 未设AISOC_DEMO_MODE;INC-RT-001缺失 → 重跑灌入器(幂等);每日 cron 未触发 →flyctl machine list -a aisoc-demo-api查aisoc-demo-seed-cron;WS 30 秒断开 → 检查 realtime 的auto_stop_machines = "off";证书 pending →flyctl certs show <hostname> --app <app>。
对于"整栈已被回收"的当前状态,README 要求四项条件按序成立:账单结清(唯一的非工程任务,且阻塞后续三项)→ 用fly-demo-deploy.sh --provision重建应用与 Postgres/Redis 挂载 → 重新指向 DNS 并重签证书(旧的*.fly.devCNAME 目标已不解析)→ 用冒烟检查确认 HTTP 200 才算部署完成。第一项无法用仓库内任何改动绕过,因为所有flyctl调用都会先返回"账号存在逾期账单"。
成本与取舍:ADR 估算与仓库成本包络
ADR 给出的运营成本是"最小 VM 规格下,一个 demo 租户的 Fly Postgres + Redis 约$25/月",并说明 reseed 任务免费(该组织 OSS 工作流的 Linux 分钟不限)、增量工程时间约1 天(写 manifest、seed 脚本、reseed 工作流),因为托管模式管线已存在。
仓库的成本包络更细,且与 ADR 的估算不完全一致:
| 资源 | demo 月成本(估算) |
|---|---|
3×shared-cpu-1x(api、web、realtime) | ~$6 |
1×shared-cpu-2xagents | ~$5 |
| 1× 定时灌数机器(约 1 分钟/天) | <$0.10 |
| Fly Postgres(dev,3GB) | ~$2 |
| Upstash Redis | $0 |
| 出站带宽(约 50GB) | ~$1 |
| 合计 | ~$14/月 |
托管租户包络为:starter/standard ≈$14/月,enterprise(含 HA Postgres)≈$42/月。差异来源可推断为 VM 规格与 Redis 计费档位的口径不同(ADR 未展开),因此引用数字时应注明出自哪份文档。demo README 另设定"<$30/月"的可持续性目标,并说明若流量超过 50GB/月,会把灌数快照缓存到对象存储。
备选方案与未决问题
ADR 记录了四个被否决的方案,理由都属于"结构性不可修",值得完整继承:
- 只保留录屏、移除实时 CTA——否决:安全产品"可点击的真实产品"是最强首触,去掉等于放弃头号差异化。
- 保留 Cloudflare Tunnel 并提升可用性——否决:失败模式是结构性的(单笔记本、单网络),隧道配置再怎么打磨也修不了"维护者在睡觉"。它是 v0 的权宜之计,已完成使命。
- 把 demo 部署到 Render / Vercel——否决:托管模式已在 Fly 上,跨两个平台部署只会让运维面翻倍而无收益。
- 在 Fly 上开 demo 租户但跳过每日重灌(让数据累积)——否决:足够多的潜客交互最终会把 dashboard 推到录屏从未展示的状态;确定性重灌是保持首触面可预测的低成本手段。
两个未决问题及 v1 默认值:
- 身份:demo 租户应路由到合成的
analyst@tryaisoc.comSSO 身份,还是接受任意 GitHub 登录(只读)?v1 默认后者,并在横幅中暴露该限制。仓库中可见的对应实现是NEXT_PUBLIC_DEMO_AUTOLOGIN_EMAIL/NEXT_PUBLIC_DEMO_AUTOLOGIN_PASSWORD(与seed_demo.py和dev_auth.py必须保持同步)以及apps/web/src/components/demo/DemoAutoLogin.tsx。 - 重灌节奏:默认每日;若观察到潜客在演示中途丢数据,可能降到每周并增加"重置我的会话"按钮,在头 30 天遥测后复盘。
ADR 还明确把"引导式 demo 导览浮层"(类似 Stripe 的产品走查)划出范围:确定性场景 + 录屏已足够覆盖首触面。
结论:这份 ADR 的正确用法
ADR-0004 的价值不在于它描述了一套正在运行的系统,而在于它把"演示可用性"从一个运维偶然性,变成了一个有 SLO、有兜底、有幂等重灌、有明确否决理由的工程决策。阅读与引用它时,建议遵循三条纪律:
- 决策照单全收,路径逐条核对:五条决策约定与四个否决理由可以放心引用;
infra/fly/managed/tenants/demo.yaml、.github/workflows/demo-reseed.yml、services/api/app/demo/seed.py、docs/runbooks/demo-tenant-down.md四项不在工作树中,替代实现是services/api/app/scripts/seed_demo.py+demo_seed.py+make demo。 - 区分"计划"与"已交付":ADR 自己的
:::caution就是最好的提醒;infra/fly/与infra/fly/managed/的 README 顶部也各自标注了"当前未部署"。 - 验证以应用为准:
/health的 200、X-AiSOC-*头部、写请求的 403、/cases/INC-RT-001?tab=ledger能打开且账本有事件——这四项才是"demo 活着"的定义,flyctl deploy返回 0 不是。
如果要在自己的环境中复现这套演示,最短路径是先跑make up && make demo建立本地可验证基线,再对照infra/fly/fly-demo-deploy.sh与infra/fly/api/fly.toml的注释理解每个 Fly 专有参数为何这样设置,最后才考虑托管模式清单化。这样即使 Fly 侧暂时不可用,录屏、本地 demo 与合成数据集仍然构成一条完整、可核实的产品体验链。
- 人工智能
- AI Agent
- 网络安全
- 后端
- 前端
【免费下载链接】AiSOC
Open-source AI Security Operations Center: alert fusion, LLM-agent triage, MITRE ATT&CK investigation, and a replayable decision ledger for every agent step. Self-hostable, runs with no API keys, MIT licensed. Ships an MCP server for Claude, Cursor and Continue.
相关推荐
用 portfolio 项目搭建 Bento Grid 作品集页面的完整教程
用 portfolio 项目搭建 Bento Grid 作品集页面的完整教程 做作品集时,你往往有太多内容想塞进一页:协作理念、技术栈、时区偏好、进行中的项目、
AiSOC 托管实例(tryaisoc.com)实战指南:入驻流程、租户供给、隔离边界与成本口径
AiSOC 托管实例(tryaisoc.com)实战指南:入驻流程、租户供给、隔离边界与成本口径 AiSOC 以开源自托管为主要部署形态,同时提供一条 trya
人工智能AI Agent网络安全后端前端EmDash 缓存演示站开发指南:解读 infra/cache-demo 的 AGENTS.md 与 Astro + Cloudflare CMS 实战
EmDash 缓存演示站开发指南:解读 infra/cache demo 的 AGENTS.md 与 Astro + Cloudflare CMS 实战 本文以
CMS后端前端插件系统认证鉴权
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考