Bitwarden SeederUtility 深度指南:用 CLI 为数据库注入组织、用户与 Stripe 测试订阅
【免费下载链接】serverBitwarden infrastructure/backend (API, database, Docker, etc).项目地址: https://gitcode.com/GitHub_Trending/ser/server
SeederUtility 是 Bitwarden 仓库中util/Seeder库的命令行封装,用于在 Bitwarden 数据库中批量生成可控的测试数据——组织(含用户、分组、集合、密码条)、独立个人用户、以及基于 JSON 预设的可复现场景。读完本文,你将掌握全部三个子命令的完整参数与取值约束、Stripe 测试模式计费的开通前提与失败边界,并能从源码层面理解参数校验、依赖注入延迟初始化等实现细节。
工具定位与运行架构
SeederUtility 基于 CommandDotNet 构建,入口为 Program.cs,其Main方法通过AppRunner<Program>注册了三个[Subcommand]子命令:organization、preset、individual,分别对应 OrganizationCommand、PresetCommand 与 IndividualCommand。
每个命令的执行路径一致:
- 调用
args.Validate()完成参数校验(非法参数抛出ArgumentException); - 通过 SeederServiceFactory.Create 构造一个独立的
ServiceCollection,把 Seeder 库所需的DatabaseContext、IMapper、IPasswordHasher<User>、IManglerService、ILicensingService、IAttachmentStorageService、ISeederLicenseSigner等解析进作用域; - 委托给 Seeder 库的 Recipe 执行落库——
organization/preset走OrganizationRecipe.SeedAsync,individual走IndividualUserRecipe.SeedAsync; - 以
ConsoleOutput.PrintRow输出结果键值对(如Organization、Owner、Password、各类计数)。
两个值得注意的源码设计:
- 计费依赖延迟构造:SeederServiceFactory 中
BillingInitializer被包成一个Func<IStripeBillingInitializer>闭包,只有真正传了--stripe-billing的命令才会触达IOrganizationBillingService -> IBraintreeGateway, IStripeAdapter, ...这条 DI 图,避免每条命令都构建计费子系统; - 统一错误退出码:三个命令都
catch (Exception ex) when (ex is ArgumentException or InvalidOperationException),打印Error: ...后Environment.Exit(1),因此脚本化调用可以依赖退出码判断成败。
快速开始
在util/SeederUtility目录下构建并运行:
dotnet build dotnet run -- <command> [options]登录凭据约定(后续所有命令通用):
- 所有被种子化的用户默认密码为
asdfasdfasdf,可用--password覆盖; - 组织预设的组织者邮箱默认为
owner@<domain>,可用--owner-email覆盖;个人预设的邮箱来自预设内的user.email字段; - 对
individual命令:提供--first-name/--last-name时邮箱为{first}.{last}@individual.example;不提供姓名时生成随机 Faker 身份,并自动启用 mangling(源码见 IndividualArgs.Validate)。
organization:自由控制组织形态
当预设目录无法满足需求时使用——例如需要一个完全没有库数据的组织(所有预设都包含 ciphers)。命令定义为 OrganizationCommand:“Seed an organization with users and optional vault data (ciphers, collections, groups)”。
常用示例(继承自官方 README)
# 带库数据的小组织 dotnet run -- organization -n SmallOrg -d small.example -u 3 -c 10 -g 5 -o Traditional --mangle # 仅用户,无库数据 dotnet run -- organization -n MyOrgNoCiphers -u 100 -d myorg-no-ciphers.example # 自定义密码与计划类型 dotnet run -- organization -n CustomOrg -d custom.example -u 10 -c 100 -g 3 --password "MyTestPassword1" --plan-type teams-annually完整参数表(源自 OrganizationArgs.cs)
| 参数 | 说明与默认值 |
|---|---|
-n, --name | 组织名称(必填) |
-u, --users | 用户数,最小 1 |
-d, --domain | 用户邮箱域,必须以.example结尾(RFC 2606 保留域) |
--claimed-domain | 已验证(claimed)域,可重复传入多个 |
-c, --ciphers | 密码条数量,默认 0(无库数据) |
-g, --groups | 分组数量,默认 0 |
--collections | 集合数量,默认 0;使用密度档案时必需 |
--density | 密度档案:balanced、highPerm、highCollection、broad、minimal、groupHeavy、sparse |
-m, --mix-user-statuses | 真实状态混合:85% confirmed、invited/accepted/revoked 各 5%;默认 true,需 ≥10 用户 |
-o, --org-structure | 集合结构:Traditional、Spotify、Modern、Government、SchoolDistrict、Healthcare、Startup |
-r, --region | 人名地理区域:NorthAmerica、Europe、AsiaPacific、LatinAmerica、MiddleEast、Africa、Global |
--mangle | 启用 mangling 做测试隔离(每次运行生成唯一的 ID/邮箱/标识符) |
--password | 所有账户密码,默认asdfasdfasdf |
--owner-email | 覆盖组织者邮箱,必须是合法邮箱且 User 表中不存在 |
--plan-type | free、teams-monthly、teams-annually、enterprise-monthly、enterprise-annually、teams-starter、families-annually;默认enterprise-annually |
--kdf-iterations | KDF 迭代次数,默认 5000,最小 5000;生产级 e2e 测试建议 600000 |
--auto-confirm-users | 邀请用户无需人工审批自动确认 |
--allow-admin-collection-access | 允许管理员/所有者访问所有集合项 |
--limit-item-deletion | 仅 Can Manage 权限成员可删除条目 |
--limit-collection-creation/--limit-collection-deletion | 集合创建/删除仅限管理员/所有者 |
--stripe-billing/--skip-trial/--trial-days | Stripe 计费选项,见下文 |
参数校验规则
OrganizationArgs.Validate 在执行前强制以下约束,违反即报Error:并以退出码 1 终止:
Users至少为 1;Domain必须以.example结尾,例如myorg.example;--structure、--region、--density均为闭集枚举,非法值给出允许列表提示;--plan-type必须可被PlanFeatures.Parse解析;--kdf-iterations小于 5000 被拒绝;--stripe-billing与--plan-type free互斥——“The Free plan has no Stripe subscription”;--owner-email必须包含@。
individual:独立个人用户
用于需要可预测邮箱的命名用户或带生成式个人库的场景——individual 预设只会创建无库数据的裸账户。命令见 IndividualCommand。
# 命名用户,可预测邮箱 (john.doe@individual.example) dotnet run -- individual --subscription free --first-name John --last-name Doe # Premium 命名用户,带个人库(约 75 条 ciphers、5 个文件夹) dotnet run -- individual --subscription premium --first-name Jane --last-name Smith --vault # 随机姓名——自动启用 mangling dotnet run -- individual --subscription premium --vault # 自建实例——签署并写入 license 文件,使 premium 状态被识别 dotnet run -- individual --subscription premium --first-name Jane --last-name Smith --self-hosted # 老化账户——CreationDate 回拨 365 天 dotnet run -- individual --subscription free --account-age-days 365参数要点(源自 IndividualArgs.cs):
--subscription仅接受free或premium(大小写不敏感);--first-name/--last-name必须成对出现,否则报错 “Provide both ... or neither”;--vault生成约 75 条个人 ciphers 与文件夹;--kdf-iterations默认 5000、最小 5000;--account-age-days不得为负(默认 0 即当天);--email可显式指定邮箱。
自建实例的 license 机制:加--self-hosted才会为 premium 状态签署并写入 license 文件;只有当licenseCertificatePath与licenseCertificatePassword指向持有 Bitwarden开发版licensing 密钥的 PFX 时才实际写 license——生产证书被刻意不信任。找不到匹配证书时,seeder 记录警告并跳过 license 生成,账户依然创建,只是 premium 状态不被识别。
账户老化的边界:--account-age-days N只回拨CreationDate,各修订时间戳(revision dates)仍停留在种子化当时——从源码结构看,这保持了时间线的一致性,避免修订日期早于创建日期的矛盾数据。
preset:基于内嵌 JSON 目录的可复现场景
预设是精选的 JSON fixture,包含特定的用户、分组、集合与 cipher 关系——每次运行都是同一份数据,适合需要已知、可复现场景而非随机生成数据的场合。
# 列出可用预设 dotnet run -- preset --list # 日常开发预设,带易记的角色登录 dotnet run -- preset --name dev.playground # QA 预设,已知用户与关系 dotnet run -- preset --name qa.enterprise-basic --mangle # 性能测试的规模化预设 dotnet run -- preset --name scale.md-balanced-sterling-cooper --mangle # 个人用户预设 dotnet run -- preset --name individual.premium --mangle命令路由逻辑(PresetCommand.ExecuteAsync):通过PresetCatalogService.IsIndividualPreset判断预设属于个人还是组织,分别走IndividualUserRecipe或OrganizationRecipe。--list支持--output text|json两种格式,JSON 输出会按organization/individual分组并附Available Fixtures清单。
组织预设接受以下覆盖参数(PresetArgs.cs):
--mangle:每次运行生成唯一 ID、邮箱与标识符,使同一预设可反复种子化;--org-name:覆盖组织显示名;--owner-email:覆盖所有者登录邮箱(默认owner@<preset-domain>);--kdf-iterations:覆盖预设中的 KDF 值;--stripe-billing/--skip-trial/--trial-days:与organization命令完全一致的 Stripe 选项。
两个覆盖项可与--mangle组合使用。个人预设不支持--stripe-billing,源码会直接抛出 “Only organization presets can be billed today”(PresetCommand.cs 第 28–33 行)。
完整预设目录见 presets.md;不知道跑什么命令时可先查 Scenarios 指南,它把常见任务映射到具体命令。
Stripe 测试计费:让 Billing 页面真正可用
默认情况下,种子化组织没有任何计费数据:网关字段保持 NULL,seeder 对 Stripe零调用。对组织执行--stripe-billing(organization或preset)后,会在 Stripe测试环境创建真实 customer 与 subscription,使座位自动扩容、订阅管理与升级流程表现得和手工创建的组织一样——否则 Billing → Subscription 页面会挂起,无从测试。
计费参数
三个选项由 StripeBillingArgs 统一校验与映射:
| 参数 | 适用范围 | 效果 |
|---|---|---|
--stripe-billing | organization、preset | 为组织创建 Stripe customer + subscription。与--plan-type free及个人预设互斥 |
--skip-trial | 配合--stripe-billing | 订阅直接active而非trialing,立即以pm_card_visa测试卡扣费 |
--trial-days N | 配合--stripe-billing | 试用期天数,1–30(默认 30),与--skip-trial互斥 |
# 30 天试用期的 Teams 组织,订阅页面真正可用 dotnet run -- organization -n "Billing Test" -u 3 -d billingtest.example --plan-type teams-monthly --stripe-billing --mangle # 预设组织,无试用期的已付费订阅 dotnet run -- preset --name qa.enterprise-basic --stripe-billing --skip-trial --mangle成功时输出会多出两行:
StripeCustomer : cus_… StripeSubscription : sub_…前提条件
- 在
bitwarden-seeder-utility的 user secrets 中配置globalSettings:stripe:apiKey,且必须是测试模式密钥(sk_test_…)——生产密钥会被直接拒绝,该工具永不触碰生产计费; globalSettings:pricingUri必须已设置,以便解析计划定价。appsettings.Development.json 已内置该值(https://billingpricing.qa.bitwarden.pw),因此必须ASPNETCORE_ENVIRONMENT=Development才能生效;globalSettings:selfHosted必须为false——自建模式下 Pricing Service 永远不会被调用,无法解析任何计划。
三项检查在创建任何实体之前完成:配置错误的 opt-in 会以报错信息和退出码 1 失败,而不是留下半个种子化组织。
环境解析逻辑见 GlobalSettingsFactory:它同时尊重ASPNETCORE_ENVIRONMENT与DOTNET_ENVIRONMENT,且回落到 Development 而非 Production,确保本地开发时appsettings.Development.json生效;配置文件从二进制所在目录(AppContext.BaseDirectory)解析,与调用者工作目录无关。
已知边界(Caveats)
- 种子化的 Teams 与 Enterprise 组织带
UseSecretsManager = 1且SmSeats为 NULL:只有SmSeats有值时 Secrets Manager 才计入订阅,因此这些组织在数据库里置了标志位,但 Stripe 侧没有对应行项目; - 计费发生在数据库提交之后。若此时 Stripe 拒绝请求(密钥过期、网络故障),组织已经提交,命令以非零退出码结束并打印 Stripe 错误及“已写穿的”网关 ID——customer 创建前失败则两者皆 NULL,订阅创建中失败则会留下真实的
GatewayCustomerId。重跑前请检查错误信息中的实际状态,并取消孤儿 Stripe customer; - 销毁本地数据库不会取消 Stripe 侧订阅;测试模式订阅约 90 天后自动取消。
延伸阅读路径
- 参数到选项的映射:
OrganizationArgs.ToOptions()生成OrganizationVaultOptions,IndividualArgs.ToOptions()生成IndividualUserOptions,均直接驱动 util/Seeder 库的 Recipe; - 预设目录与 fixture 清单:util/Seeder/Seeds/docs/presets.md;
- 任务导向的命令映射:util/Seeder/Seeds/docs/scenarios/README.md;
- API 侧对应的种子化服务:util/SeederApi 提供 HTTP 形式的同等能力,可与本 CLI 对照理解同一套 Recipe 的两种消费方式。
【免费下载链接】serverBitwarden infrastructure/backend (API, database, Docker, etc).项目地址: https://gitcode.com/GitHub_Trending/ser/server
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考