Skip to main content
状态:kira-auth 服务代码已实现,fly.toml / Dockerfile / GitHub Actions 已就绪。是否已实际 deploy 以及 kira-web 端切到薄 auth client 的 cutover,是独立的、进行中的工作。本页按代码与配置文件描述部署形态。

部署形态

平台Fly.io
Fly appkira-auth
primary_regionsjc
区域分布fra / nrt / sjc(对齐 web/be/cdn 三区)
每机资源shared-cpu-4x / 4gb([[vm]] cpu_kind=shared, cpus=4)
内部端口8095(force_https = true)
弹性auto_stop_machines = "stop",auto_start_machines = true,min_machines_running = 3
公网域名https://authapi.kira.art
入口bun /app/src/bootstrap.ts(先 startTelemetry()import index)
机器数 + 区域分布是 runtime scale 状态(fly scale count --region),不在 fly.toml;但 [[vm]] 尺寸会被 deploy 重新套用,所以必须和实际一致,否则 deploy 会把机器打回小尺寸。

无状态 = 可水平扩

完全无进程内存、无 DB。会话权威是 Supabase;refresh token 封印在客户端 .kira.art cookie,每请求自带 → 任意机器可处理。封印密钥 AUTH_REFRESH_SECRET 由 Fly secret 注入、所有机器同值 → 多机安全。auto_stop 缩到 min 3 台热机,有量 auto_start 拉到上限。

环境变量

非密配置走 fly.toml [env];密钥走 fly secrets set(不入代码、不进本文件)。src/env.ts 是 fail-fast(无 zod),必填缺失直接抛。

必填

变量来源说明
PUBLIC_BASE_URL[env]authapi 自身入口;redirectTo = ${PUBLIC_BASE_URL}/callback。prod https://authapi.kira.art,dev http://localhost:8095
SUPABASE_URL[env]Supabase 项目 URL(https://base.kira.art),必须和 kira-web 的 NEXT_PUBLIC_SUPABASE_URL 同 host
SUPABASE_ANON_KEYsecretOAuth 两段 + GoTrue 转发
SUPABASE_KEYsecretservice-role key,onboarding 写库(claim_referral / user_profiles)
AUTH_REFRESH_SECRETsecret封印 ka_session 的密钥,≥32 字符。所有机器同值
COOKIE_DOMAIN[env]会话 cookie 作用域。prod .kira.art(prod+creator 子域无感共享);dev 留空 → host-only
ALLOWED_REDIRECT_ORIGINS[env]CSV origin。既是 /login?next= 回跳白名单(防开放重定向),也是 /refresh/logout 的 CORS 允许 origin。第一个 = 非法 next 的兜底默认。prod https://kira.art,https://creator.kira.art,https://poisson.art
OTEL_EXPORTER_OTLP_ENDPOINT[env]http://kira-otel-collector.internal:4318。OTel opt-in:prod + 此项都在才开,dev 留空 = no-op(只 stdout)
NODE_ENV[env]production(prod);决定 IS_PROD(cookie Secure / telemetry 开关)

可选

变量默认说明
PORT8095监听端口
OTEL_LOG_LEVELdebug 时把 OTel/exporter 诊断打到 console

fly.toml [env](非密,已签入)

[env]
  NODE_ENV = "production"
  PUBLIC_BASE_URL = "https://authapi.kira.art"
  SUPABASE_URL = "https://base.kira.art"
  COOKIE_DOMAIN = ".kira.art"
  ALLOWED_REDIRECT_ORIGINS = "https://kira.art,https://creator.kira.art,https://poisson.art"
  OTEL_EXPORTER_OTLP_ENDPOINT = "http://kira-otel-collector.internal:4318"

设置 secrets

不要把真实 secret 写进文档或仓库。SUPABASE_* 从 prod 邻居服务抄真值;AUTH_REFRESH_SECRET 新生成
# 生成新的封印密钥(≥32 字符)
openssl rand -base64 48

fly secrets set \
  AUTH_REFRESH_SECRET="<openssl rand -base64 48 的输出>" \
  SUPABASE_ANON_KEY="<from prod neighbor>" \
  SUPABASE_KEY="<service-role, from prod neighbor>" \
  -a kira-auth

Supabase Auth 回调注册

Supabase Auth → URL Configuration → Redirect URLs 必须包含 ${PUBLIC_BASE_URL}/callback:
  • prod:https://authapi.kira.art/callback
  • dev:http://localhost:8095/callback
否则 Google 回跳到 /callbackexchangeCodeForSession 会被 Supabase 拒。

Observability

OTel 接法对齐 kira-agent / kira-be,导出到 kira-otel-collector.internal:4318 → Dash0。版本钉死 stable 2.7.1 / exp 0.218.0(见 memory: OTel pin transitive deps)。dev(非 production)完全 no-op,只 stdout。
信号实现备注
入站 trace@hono/otel httpInstrumentationMiddlewareSERVER span + http.server.request.duration histogram + active_requests
出站 traceinstallFetchTracing() —— 手写 globalThis.fetch wrapperCLIENT span(http.host/method/url/status_code,≥400 标 ERROR),覆盖所有 Supabase GoTrue 调用
metrics全局 MeterProvider,PeriodicExportingMetricReader(30s)RED 信号
logsappLog(telemetry/logger.ts)4 级 + per-call report opt
errorscaptureException(telemetry/errors.ts)ERROR log 带 exception.*(Dash0 分组源)+ span.recordException
手写 fetch wrapper,不用 @opentelemetry/instrumentation-fetch:后者是浏览器 instrumentation,每个出站 fetch 都 new PerformanceObserver(),Bun 在每次 observer cycle 泄漏 native RSS → OOM(已在 kira-be / kira-agent 实证 + 修)。这个 wrapper 发同样的 CLIENT span,零 PerformanceObserver、不 clone response → RSS 平。

Trace 采样 & propagation

  • 100% 采样(AlwaysOnSampler);tail-sampling(error/慢 100% + 正常 10%)在 kira-otel-collector 做。
  • W3C 只注自家:traceparent 只注入到 *.kira.art*.internal 服务,不外泄给 Google / Supabase。

日志上报规则

appLog per-level 默认 report:debug/info=false,warn/error=true,可 per-call 用 opts.report 覆盖。stdout 永远写(dev TTY 彩色 / prod JSON);Dash0 仅 prod 且 report=true
  • appLog.info("kira-auth.listening", …, { report: true }) —— 启动确认,显式上 Dash0。
  • appLog.info("callback.login_success", …) —— report=false(默认),避免和 PostHog 登录事件重复。
  • refresh.upstream_unavailablewarn(真异常,上 Dash0);refresh.expiredrefresh.no_sessiondebug(常见会话到期,只 stdout)。

4xx 不标 ERROR span

@hono/otel 默认按 c.error 把 4xx HTTPException 标 ERROR。按 OTel HTTP 语义,4xx SERVER 不该是 ERROR —— src/index.ts@hono/otel 内侧吃掉 <500HTTPException,只 5xx + 未捕获错误才 captureException(见 API 的错误 span 语义)。

无 user/thread context propagator

authapi 自身是登录服务,请求里没有已验证用户上下文(用户身份在 /callback exchange 后才有,按需在那条 log 手动带 user.id)。

CI/CD

.github/workflows/fly-deploy.yml:push 到 mainbun install --frozen-lockfilebun run typecheckbun testflyctl deploy --remote-only → Slack 通知。Fly token 用 secret POISSON_ART_DEPLOY_FLYIO

上线前 checklist

1

Fly app + secrets

kira-auth app 已建;fly secretsAUTH_REFRESH_SECRET(新生成)、SUPABASE_ANON_KEYSUPABASE_KEY[env]COOKIE_DOMAIN=.kira.artALLOWED_REDIRECT_ORIGINSOTEL_EXPORTER_OTLP_ENDPOINTNODE_ENV=productionPUBLIC_BASE_URLSUPABASE_URL
2

Cloudflare 路由

authapi.kira.art 指向 Fly app(kira-auth.internal)。Notion Key 页加 ## kira-auth secret 分组。
3

Supabase 回调注册

Supabase Auth → URL Configuration → Redirect URLs 含 https://authapi.kira.art/callback(dev 另加 http://localhost:8095/callback)。
4

Collector 可达

kira-otel-collector 运行中,http://kira-otel-collector.internal:4318 可达。
5

Dash0 dashboard + no-data alert

建 authapi overview dashboard + no-data alert:absent_over_time(dash0.spans SERVER[5m])(rate/threshold alert 在 0 流量时全静默,服务挂反而不报警,必须有 no-data 兜底)。
6

kira-web 切薄 auth client(独立工作)

kira-web 移除 @supabase/* client、改用只跟 authapi 说话的薄 auth client(access 存 localStorage,过期/缺失 → POST /refresh)。这是 kira-web 侧的后续改动,本服务定契约在先。

本地开发

cp .env.example .env   # 填 SUPABASE_* 真值 + AUTH_REFRESH_SECRET + ALLOWED_REDIRECT_ORIGINS
bun install
bun run dev            # :8095
dev 默认 COOKIE_DOMAIN 留空 → host-only cookie(localhost:3000 ↔ :8095 同 site,可用)。dev 无 OTEL_EXPORTER_OTLP_ENDPOINT → telemetry no-op,只 stdout。
poisson.art 跨注册域:走同一个 authapi.kira.art,但 SameSite=Lax.kira.art cookie 不会在 poisson 的跨站 /refresh fetch 被带上 → 静默刷新失败、需重登(测试环境可接受)。把 poisson origin 加进 ALLOWED_REDIRECT_ORIGINS(同时管 redirect 白名单 + CORS)。要 poisson 也无感,需把会话 cookie 改 SameSite=None; Secure(见 lib/session.ts 注释,待决策)。