跳过主要内容

现代应用的环境配置

为Capacitor和Electron应用配置环境。比较12因素、运行时配置和秘密管理模式

环境配置

您已经发布了版本,签署了本机二进制文件,并正常启动了生产 rollout。然后,支持人员报告说用户无法完成购买。崩溃日志看起来与之无关,所以您花了几个小时来跟踪请求、插件初始化和最近的 JavaScript 变化。最终,原因是由于在紧急生产构建中留下了一个 staging API URL。

这并不是一种常见的失败。 环境配置 是指将部署特定的设置,例如 API 端点、特性标志、数据库凭据和第三方密钥,分离到应用程序 code 中。在 Capacitor 和 Electron 应用程序中,分离变得更加困难,因为 Web 资产被打包在本机容器中,因此配置错误可以成为一个签名的艺术品,而不是一个可以在服务器上改变的值。

更深层次的问题是 配置漂移开发者更新了一个环境配置,导致开发、测试和生产环境的差异逐渐扩大。 .env 环境配置 是 Capacitor 应用程序中开发和生产之间的差异的关键。 环境配置

实践问题很简单:如何将相同的代码库部署到多个环境中,而不需要重复构建、重签或提交原生应用程序以应对每次配置更改?

目录

环境配置审计清单

The most expensive configuration bugs rarely look like configuration bugs. A wrong API base URL can surface as an authentication failure, an empty account screen, a payment error, or a native plugin crash. By the time the incident reaches the engineer who owns the build pipeline, the original mistake may be buried under several commits and a successful store submission.

Hybrid applications have a particular failure pattern. Capacitor compiles a web application and places its assets inside an iOS or Android project. Electron packages the renderer and main-process code into a desktop application. If an endpoint or feature flag is resolved during that build, the resulting value travels with the artifact. Changing it later usually means producing a new bundle, signing it again, and distributing it through the applicable channel.

异常开始于无害异常

配置漂移源于合理的捷径:

  • 一个本地覆盖: 开发人员硬编码一个测试端点来解锁一个功能并忘记删除它。
  • 一个独立的管道变量: CI 使用一个生产值,它与仓库文档中记录的配置不符。
  • 一个本机设置: Android、iOS 和 Electron 接收不同的插件标识符或回调设置。
  • 一个未文档的标志: 运维人员直接在部署系统中更改一个功能标志,而 staging 继续使用旧的行为。

每个选择都可以独立工作。问题出在没有人知道哪个值是权威的,哪个环境拥有它,最后一次更改是什么时候。

实践规则: 如果一个配置值可以独立于应用逻辑改变,认为它是一个部署输入,而不是源 code。

A开发环境应该尽可能地模仿生产环境,以暴露集成问题,同时仍使用隔离的端点、凭据和数据。当环境发生漂移时,开发环境就不再是一个有用的演练。一个发布可以通过一套假设通过所有测试,而在另一套假设下立即失败。

构建时配置创建了发布瓶颈

构建时配置仍然有用。公共编译时值、平台特定的标识符和由原生工具所需的设置通常需要在应用程序打包之前存在。问题在于使用构建时注入的值来实现操作可能需要在发布后改变的值。

假设一个生产API转移到一个新的端点。使用传统Capacitor工作流程,团队会改变值,构建Web资产,同步本机项目,签署应用程序并通过平台的发布过程分发它。 Electron团队面临着一个类似的周期,当改变的值属于打包应用程序内部时。

这种工作流程适用于有意的产品发布。它并不是对紧急配置修正的合适反应。二进制文件可能是健康的,JavaScript可能是未变的,而一个单独的字符串却迫使整个交付周期。

更安全的设计分离了三个层次:

  1. 应用程序逻辑应该在所有环境中保持一致。
  2. 环境配置选择端点、标志和非敏感的运行时行为。
  3. 机密材料,属于受控存储,应在必要时注入。

这种分离使得漂移变得可见。它还为团队提供了一个明确的答案:当生产环境表现出不同行为时,比较配置输入之前不要指责code。

比较四种主要配置模式

没有一种配置模式适合混合应用的所有部分。后端可以在启动时读取进程环境变量,而Capacitor渲染器可能没有传统的服务器进程。Electron在主进程和渲染器之间又增加了一个界限。正确的选择取决于值是否为公共值、敏感值、在发布后是否可变或由本机构建工具所需。

模式一:12-Factor环境变量

12-Factor模型将配置视为环境特定的输入,而不是硬编码的应用状态。这在服务器进程、容器和CI任务中自然适用。服务可以在启动时读取其值并在多个部署中使用相同的工件。

客户端应用程序使得该模型变得复杂。浏览器JavaScript引用值最终必须可用给客户端,因此不应仅因为它通过环境变量传递而被视为机密。公共API源或特性标志可以使用构建时替换,但具有有意义特权的凭据不应将其打包到可逆客户端包中。

对于Capacitor和Electron,这种模式适用于 构建编排和公共配置并非用于保护机密。它具有低的概念性负担,但在存在另一个交付层的情况下,运行时可变性有限。

模式二:按环境构建

团队通常维护开发、测试和生产构建配置文件。每个配置文件选择自己的端点、应用程序标识符、原生服务文件和功能标志。这种方法易于解释并且与平台要求兼容。

其弱点是工件分歧。三个构建可能包含不同的行为,不仅仅是不同的设置,尤其是在条件编译或平台特定脚本进入管道时。每次配置更改都会创建另一个构建并可能触发签名、认证、商店处理或手动发布协调。

回滚也与工件分发相关。您可以回滚到较旧的二进制文件,但用户可能已经安装了不同的版本,并且回滚路径取决于分发渠道。

模式三:运行时配置

运行时配置将可变值移出原生工件。应用程序在启动时检索配置文档或读取本地缓存文档以此来让团队更正端点和标志而无需更改应用程序code。

与此同时,需要一个安全的缓存、一个有界超时以及一个已知的回退策略。如果远程配置服务不可用,应用程序需要这些。回退策略绝不能指向错误的环境。验证文档的schema,根据需要验证其来源,并记录应用程序应用的配置版本。

对于混合应用程序来说,这通常是非机密值的最灵活模式。然而,它需要小心地处理离线行为,因为移动用户可能会在没有网络连接的情况下启动应用程序。

模式四:专门的机密管理

HashiCorp Vault 和 AWS Secrets Manager 等平台旨在控制敏感的后端材料。它们支持访问策略、审计跟踪、加密和轮换工作流。因此,它们适合于可以在不向用户暴露凭据的情况下,通过后端进行身份验证的服务器端服务。

它们并不能解决客户端机密问题。将机密传递给移动或桌面客户端时,通常可以由设备的所有者检查。客户端应用程序应只接收安全可披露的值,而保留特权操作在后端。

模式 构建复杂度 存储重新提交所需 回滚速度 最佳选择
12 因子变量 服务器低,混合构建中等 通常,针对捆绑客户端值 一般 后端服务和公共构建输入
按环境构建 随着环境数量的增加 是的,当捆绑值发生变化时 慢到一般 小团队,频繁发布
运行时配置 一般 不兼容的Web层变化 快速,带有版本化负载 可变端点、标志和客户端行为
机密管理平台 中等到高 不适用于后端仅秘密 适用于服务器消费者 特权后端凭证

团队正在处理一个应用程序,偶尔发布,可以从环境构建中开始加上严格验证。一个成长中的团队需要并行的生产和测试环境,需要一个运行时层来减少 artifact 分歧。一个高频率的团队应该结合不可变的应用程序捆绑包、运行时配置和后端凭证的机密管理器。这个 特性标志的实现指南适用于Capacitor 适合于该运行时层,假设标志被限定、审计并且安全地暴露给客户端。

安全性和CI/CD的影响你不能忽视

一个配置值并不是因为它来自CI而自动安全的。一个值一旦进入客户端捆绑包、原生资源、Electron存档、日志文件、崩溃报告或设备备份,假设有权访问该 artifact 的人可能会检查它。

公共标识符和客户端设置与机密不同。一个仅用于识别应用程序的移动API密钥在bundle中可能是可接受的,如果提供者期望它在那里并限制了其使用。一个数据库凭据、签名密钥、特权令牌或未受限制的服务凭据不安全地放在同一个地方。OWASP SAMM建议将职责分开或加密生产机密,防止未受保护的机密进入仓库,并管理其生命周期而不是等待事故发生。

一个图表,展示了CI/CD和部署管道中硬编码机密的三个安全风险阶段。

管道泄露配置

CI/CD系统在平凡的方式上失败。一个shell命令打印一个扩展的变量,一个失败的构建包含一个机密在异常中,或者一个调试步骤存档一个环境快照。一个 .env.production 文件也可能进入版本控制,因为一个仓库继承了一个不完整的 .gitignore.

使用一个安全存储器来存储敏感值,并且使管道的权限变得狭窄。构建任务应该只接收所需的值,并且日志应该掩盖它们。生成的JavaScript、原生资源、Electron存档和源映射应该审查是否包含意外的包含。一个校验和或签名在远程传递的配置载荷中可以帮助检测篡改,但它不能将一个客户可见的值转换为机密。

处理分析或其他敏感数据的团队可以使用一个独立的资源,如ELECTE的 安全性白皮书 在审查更广泛的数据保护责任时

安全性必须与发布速度共存

生产机密的安全模式是通过控制存储、在传输和存储时进行加密、最小特权访问和轮换来实现的。对于可变的公共端点,安全模式是不同的。端点可以通过版本化的运行时文档交付、在使用之前验证并限制,以便不良值的关闭

不要将特权凭据放入Capacitor或Electroncode中以避免后端回旋转。不要依赖混淆、压缩或隐藏渲染器变量。这些技术使轻松检查更困难,但它们不会改变客户设备的信任模型

实用的管道分离责任

  • 源代码管理 提交配置方案和安全示例,而不是存活机密
  • 构建阶段 仅注入生产 artifact 所需的值,并防止机密在日志中扩展
  • 交付阶段 通过受认证、可观察的通道应用环境特定的运行时捆绑
  • 旋转阶段: 在定义的时间表上,撤销并替换后端凭证,提供紧急失效路径。

该 CI/CD机密管理实践对于Capacitor团队来说是最有用的。 当每个变量都有明确的说明时,说明它是否为公共或敏感变量,谁拥有它,哪些环境使用它,以及是否需要原生重建时,实用性才会提高。

在Capacitor和Electron中实施环境配置

可维护的设置始于一个配置契约,而不是一堆框架特定的条件语句。将安全的环境输入保存在可预测的文件中,通过构建系统加载它们,并通过小型类型服务向应用code暴露它们。

可行的仓库布局如下:

.env
.env.staging
.env.production
.env.example
src/config/
  schema.ts
  config-service.ts
capacitor.config.ts
electron/
  main.ts
  preload.ts

只有 .env.example 应纳入源代码控制。其他文件应被忽略,CI应为每个目标提供值。这些文件可以包含公共端点和特性标志,但敏感的后端凭证应始终保存在专用机密存储中,并且不应成为客户端包的一部分。

定义并验证一个契约

使用Vite,客户端暴露的变量通常使用 VITE_ 前缀。该前缀是一个可见性信号,而不是安全边界。

// src/config/schema.ts
export type AppConfig = {
  apiBaseUrl: string
  enableNewCheckout: boolean
  environment: 'development' | 'staging' | 'production'
}

function required(name: string, value: string | undefined): string {
  if (!value) {
    throw new Error(`Missing required configuration: ${name}`)
  }
  return value
}

export function loadConfig(): AppConfig {
  const environment = required('VITE_APP_ENV', import.meta.env.VITE_APP_ENV)

  if (!['development', 'staging', 'production'].includes(environment)) {
    throw new Error(`Unsupported environment: ${environment}`)
  }

  return {
    apiBaseUrl: required('VITE_API_BASE_URL', import.meta.env.VITE_API_BASE_URL),
    enableNewCheckout: import.meta.env.VITE_ENABLE_NEW_CHECKOUT === 'true',
    environment: environment as AppConfig['environment'],
  }
}

调用 loadConfig() 在应用程序启动期间。缺少的值应该产生明确的错误,而不是选择本地端点。这个单一的决定可以防止生产路由错误的 bug 类型。

对于本地工作,Vite 可以选择适当的文件及其模式。一个阶段构建可以使用 .env.staging,而生产构建使用 .env.production。CI 任务应该明确设置模式,而不是继承开发人员在本地使用的任何内容。

Capacitor 本地配置

本机项目经常需要环境特定的标识符、服务文件或插件设置。将这些值保存在 capacitor.config.ts中,但避免将私密凭据放在那里。

// capacitor.config.ts
import type { CapacitorConfig } from '@capacitor/cli'

const isProduction = process.env.APP_ENV === 'production'

const config: CapacitorConfig = {
  appId: isProduction ? 'com.example.app' : 'com.example.app.staging',
  appName: isProduction ? 'Example' : 'Example Staging',
  webDir: 'dist',
  plugins: {
    PushNotifications: {
      presentationOptions: ['badge', 'sound', 'alert'],
    },
  },
}

export default config

Firebase 文件、推送通知证书和平台标识符应该由本机构建管道选择并存储在适当的访问控制下。生产应用程序不应重用阶段服务凭据,因为两种构建都编译了。

The Capacitor 本地环境设置指南 可以帮助团队标准化本地模式,但仓库仍然需要明确的契约和CI验证。

Electron需要一个进程边界

Electron的渲染器不是Node.js主进程。 在一个加固的应用中 process.env 可能在主进程中可用,但在渲染器中为undefined或故意不可用。 仅通过预载桥传递安全的配置。

// electron/main.ts
import { app, BrowserWindow } from 'electron'
import path from 'node:path'

function createWindow() {
  const window = new BrowserWindow({
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
      nodeIntegration: false,
    },
  })

  const safeConfig = {
    apiBaseUrl: process.env.API_BASE_URL,
    environment: process.env.APP_ENV,
  }

  window.webContents.on('did-finish-load', () => {
    window.webContents.send('app-config', safeConfig)
  })

  return window
}

app.whenReady().then(createWindow)
// electron/preload.ts
import { contextBridge, ipcRenderer } from 'electron'

contextBridge.exposeInMainWorld('appConfig', {
  get: () => new Promise((resolve) => {
    ipcRenderer.once('app-config', (_event, config) => resolve(config))
  }),
})

在渲染器中再次验证对象。 桥应该暴露公共运行时值,而不是秘密存储令牌或特权文件系统能力。

方面 Capacitor Electron
Electron 主配置边界 原生项目和捆绑的Web资产
主进程、预载和渲染器 公共端点和标志 公共端点和标志通过预加载传递
敏感凭证 将它们从应用程序包中排除 将它们从打包存档中排除
常见错误 本地同步后构建值过时 process.env 在渲染器中不可用
运行时更新路径 签名的Web包或远程配置 签名的Web包或受控应用程序更新

最常见的实现错误是假设 .env 文件自动为私有。打包器可以将其值包含在生成的JavaScript中,而打包步骤可以包含文件本身。检查最终的工件,而不是源树。

Simplifying Targeted Updates and Rollbacks with Capgo

即使是有条理的构建时间设置,也会留下一个硬性运营差距。如果发布后公共端点发生变化,原生应用可能仍包含旧值。重新构建和分发新二进制文件对于仅影响Web层的所需更改来说是过于繁琐的。

Capgo的实时更新模型通过 目标渠道 用于开发、测试和生产等部署级别。团队可以将签名的JavaScript、CSS、配置或资产包发布到匹配受影响环境的渠道。原生shell保持安装状态,而应用在下一次启动时应用兼容更新。

展示传统应用配置工作流程与使用Capgo平台的即时更新和回滚的比较图表。

考虑一下生产API端点突然发生变化的情况。团队可以更新配置源、构建Web包并将其发布到生产渠道,而不必等待原生商店发布。重要的边界仍然保持不变:这种方法不会绕过原生code的平台规则,也不会使秘密变为可运输的。它确实减少了修复兼容Web层配置所需的时间。

渠道使环境拥有者明确

A频道应映射到环境,而不是开发者个人的偏好。开发者接收开发内容,测试用户接收测试内容,生产用户只接收已批准的生产包。CI可以在对应的构建和验证步骤后发布适当的包。

回滚同样重要。如果新配置指向一个不健康的服务,发布运营者应该能够选择之前的已知良好包为该频道。版本历史、部署防护栏和设备级报告有助于团队确定问题是否广泛或仅限于某个发布段。

结果是更清晰的推广路径:

  1. 构建并验证开发包。
  2. 将相同的测试内容推广到测试环境。
  3. 批准生产频道更新。
  4. 监控采用和失败。
  5. 如果配置行为不正确,回滚频道。

该过程减少了为每个端点纠正而创建紧急本机构建的诱惑。 Capgo版本控制和回滚工作流 尤其适用于需要环境特定发布而不丢失可追溯历史的团队。

环境配置审计清单

每次重大发布前和任何管道变更后都运行此审计。

  • 检查 artifact: 确认以下文件中没有泄露任何机密:
  • 分离环境: 验证开发、测试和生产环境使用不同的端点、标识符和授权密钥。
  • 保护输入: 检查每个 .env 忽略变体,而 .env.example 文档所需的模式。
  • 审查 CI: 确认管道从受控存储中注入值,而不是依赖开发人员的本地机器。
  • 测试恢复: 验证运行时配置失败时,会快速失败,当缺少必需的值时,并且可以回滚到兼容的更新而不需要重建原生 shell。

环境配置审计清单,包括五个步骤的安全软件部署实践。

只有当有人可以识别出每个生产配置值的所有者、来源、范围和回滚程序时,审计才算完成。


Capgo 为 CapacitorJS 和 Electron 应用提供了签名的实时更新,包括针对性的通道和兼容的 JavaScript、CSS、配置和资产更改的版本回滚。如果您的团队想减少重建周期,同时保持环境更新的控制权,请访问 Capgo 并与您的现有 CI/CD 流程进行评估。

实时更新 Capacitor 应用

When a web-layer bug is live, ship the fix through Capgo instead of waiting days for app store approval. Users get the update in the background while native changes stay in the normal review path.

来自 Martin 的人工支持

Capgo 为您提供了创建真正专业的移动应用所需的最佳见解。