EmDash 通过两个文件进行配置:集成配置文件 astro.config.mjs 和内容集合配置文件 src/live.config.ts。
Astro 集成
在 astro.config.mjs 中将 EmDash 配置为 Astro 集成:
import { defineConfig } from "astro/config";
import emdash, { local, s3 } from "emdash/astro";
import { sqlite, libsql } from "emdash/db";
export default defineConfig({
integrations: [
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
plugins: [],
}),
],
});
集成选项
database
必需。 数据库适配器配置。选择一个适配器:
// SQLite (Node.js)
database: sqlite({ url: "file:./data.db" });
// PostgreSQL
database: postgres({ connectionString: process.env.DATABASE_URL });
// libSQL
database: libsql({
url: process.env.LIBSQL_DATABASE_URL,
authToken: process.env.LIBSQL_AUTH_TOKEN,
});
// Cloudflare D1 (从 @emdash-cms/cloudflare 导入)
database: d1({ binding: "DB" });
详情请参阅数据库选项。
storage
必需。 媒体存储适配器配置。选择一个适配器:
// 本地文件系统(开发环境)
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
});
// R2 绑定(Cloudflare Workers)
storage: r2({
binding: "MEDIA",
publicUrl: "https://pub-xxxx.r2.dev", // 可选
});
// S3 兼容(任何平台)— 所有字段来自 S3_* 环境变量
storage: s3()
// 或使用显式值
storage: s3({
endpoint: "https://s3.amazonaws.com",
bucket: "my-bucket",
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
region: "us-east-1", // 可选,默认值:"auto"
publicUrl: "https://cdn.example.com", // 可选
});
详情请参阅存储选项。
plugins
可选。 EmDash 插件数组。以下示例注册了一个插件:
import seoPlugin from "@emdash-cms/plugin-seo";
plugins: [seoPlugin()];
fonts
可选。 管理界面字体配置。
默认情况下,EmDash 通过 Astro 字体 API 加载 Noto Sans。字体在构建时从 Google 下载并自托管,因此没有运行时 CDN 请求。基础字体涵盖拉丁文、西里尔文、希腊文、梵文和越南文字。
要添加对其他书写系统的支持,请传递脚本名称。以下示例添加了阿拉伯语和日语:
emdash({
fonts: {
scripts: ["arabic", "japanese"],
},
})
可用的脚本有 arabic、armenian、bengali、chinese-simplified、chinese-traditional、chinese-hongkong、devanagari、ethiopic、farsi、georgian、gujarati、gurmukhi、hebrew、japanese、kannada、khmer、korean、lao、malayalam、myanmar、oriya、sinhala、tamil、telugu、thai 和 tibetan。
每个脚本映射到 Google Fonts 上相应的 Noto Sans 变体(例如 "arabic" 加载 Noto Sans Arabic)。所有字体使用单个 font-family 名称并使用 unicode-range,因此浏览器只下载页面上字符所需的文件。
设置为 false 可完全禁用字体注入并使用系统字体:
emdash({
fonts: false,
})
管理 CSS 使用 --font-emdash CSS 变量。这由上面的字体配置自动设置。
auth
可选。 认证适配器。EmDash 的内置登录使用密钥;设置 auth 将其替换为外部提供商。Cloudflare Access 适配器 access() 由 @emdash-cms/cloudflare 提供:
import { access } from "@emdash-cms/cloudflare";
emdash({
auth: access({
teamDomain: "myteam.cloudflareaccess.com",
audience: "your-app-audience-tag",
roleMapping: {
Admins: 50,
Editors: 40,
},
}),
});
access() 的选项:
| 选项 | 类型 | 默认值 | 描述 |
|---|---|---|---|
teamDomain | string | 必需 | 您的 Cloudflare Access 团队域名 |
audience | string | — | 应用程序受众(AUD)标签。在 Workers 上,优先使用 audienceEnvVar。 |
audienceEnvVar | string | "CF_ACCESS_AUDIENCE" | 运行时读取受众标签的环境变量 |
autoProvision | boolean | true | 首次登录时创建 EmDash 用户 |
defaultRole | number | 30 | 未被 roleMapping 匹配的用户的角色级别(参见用户角色) |
syncRoles | boolean | false | 每次登录时重新应用 roleMapping,而不是仅在配置时 |
roleMapping | object | — | 将 IdP 组名映射到 EmDash 角色级别;第一个匹配项获胜 |
authProviders
可选。 可插拔登录提供商数组(顶级,与 auth 并列)。每个条目都是调用提供商工厂的结果,如下所示:
import { github } from "emdash/auth/providers/github";
import { google } from "emdash/auth/providers/google";
import { atproto } from "@emdash-cms/auth-atproto";
emdash({
authProviders: [github(), google(), atproto()],
});
内置提供商:
github()— 读取EMDASH_OAUTH_GITHUB_CLIENT_ID/EMDASH_OAUTH_GITHUB_CLIENT_SECRET(或无前缀回退)。google()— 读取EMDASH_OAUTH_GOOGLE_CLIENT_ID/EMDASH_OAUTH_GOOGLE_CLIENT_SECRET。atproto()— Atmosphere 账户登录(Bluesky 和更广泛的 AT 协议网络)。无需环境变量。接受{ allowedDIDs, allowedHandles, defaultRole }。参见 Atmosphere 登录指南。
第三方包可以使用相同的 AuthProviderDescriptor 形式注册自己的提供商 — 参见登录提供商。
siteUrl
可选。 站点的公共浏览器面向源(方案 + 主机 + 可选端口,无路径)。
在 TLS 终止反向代理后面,Astro.url 返回内部地址(http://localhost:4321)而不是公共地址(https://cms.example.com)。这会破坏密钥、CSRF 源匹配、OAuth 重定向、登录重定向、MCP 发现、快照导出、站点地图、robots.txt 和 JSON-LD 结构化数据。设置 siteUrl 可一次性修复所有这些问题。
集成在加载时验证此值:它必须是具有 http: 或 https: 协议的有效 URL,并标准化为源(删除路径)。
以下示例设置公共源:
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
siteUrl: "https://cms.example.com",
});
当配置中未设置 siteUrl 时,EmDash 按顺序检查环境变量:EMDASH_SITE_URL,然后是 SITE_URL。这对于在运行时设置公共 URL 的容器部署很有用。
多源密钥验证
siteUrl 定义单个规范源。当同一个 EmDash 部署可以在共享可注册父域的多个主机名(例如 https://example.com 和 https://preview.example.com)下访问时,密钥验证会拒绝源与 siteUrl 不完全匹配的断言 — 尽管 WebAuthn 允许密钥在相同 rpId 下的子域之间有效。
通过 astro.config.mjs 中的 allowedOrigins 或 EMDASH_ALLOWED_ORIGINS 环境变量声明其他接受的源。规范的 siteUrl 仍然是 rpId 的来源;此处列出的条目在验证时被接受。两个来源在运行时合并,因此配置可以声明稳定源(版本控制、代码审查),而环境添加特定于环境的额外内容(例如临时 PR 预览)。
以下示例在配置中声明一个额外的源:
emdash({
siteUrl: "https://example.com",
allowedOrigins: ["https://preview.example.com"],
})
等效值也可以来自环境变量:
EMDASH_SITE_URL=https://example.com
EMDASH_ALLOWED_ORIGINS=https://preview.example.com,https://staging.example.com
验证
EmDash 验证这些以防止浏览器永远不会遵守的无效配置:
- 每个条目必须是可解析的
http:或https:URL,没有尾随点,主机名中没有空标签。 - 当
allowedOrigins不为空时,必须设置siteUrl(任一来源),并且不能是 IP 字面量或具有尾随点主机名。 - 每个源必须与
siteUrl相同的主机名或其子域。(WebAuthn 要求rpId是每个源的可注册后缀。)
验证失败时,您将看到源归因错误,如 EmDash config error in EMDASH_ALLOWED_ORIGINS: "https://other-site.com" is not a subdomain of siteUrl "https://example.com". Allowed origins must be the same hostname as siteUrl or a subdomain of it.
错误出现的位置取决于值的声明位置:
- 在 Astro 启动时,当
config.allowedOrigins和config.siteUrl都来自astro.config.mjs时 — 代码中的拼写错误导致构建失败。 - 在首次密钥验证时,当任一值来自
EMDASH_ALLOWED_ORIGINS或EMDASH_SITE_URL时 — 环境不匹配在首次验证尝试时显示为 500。
反向代理设置
Astro 仅在允许公共主机时才反映 X-Forwarded-*。为用户访问的主机名(和方案)配置 security.allowedDomains。在 astro dev 中,添加匹配的 vite.server.allowedHosts,以便 Vite 接受代理的 Host 标头。
优先修复 allowedDomains(和转发的标头);当重建的 URL 仍然与浏览器源不同时(典型情况是 TLS 在前端终止,上游请求保持 http://)使用 siteUrl。
在前端有 TLS 的情况下,将开发服务器绑定到回环(astro dev --host 127.0.0.1)通常就足够了:代理本地连接,而 siteUrl 与公共 HTTPS 源匹配。
如果您的代理写入客户端 IP 标头,请设置 trustedProxyHeaders,以便 EmDash 的速率限制可以使用真实的客户端 IP,而不是将每个请求归类到共享的”unknown”键下。
以下配置为反向代理部署一起设置 allowedDomains、vite.server.allowedHosts 和 siteUrl:
import { defineConfig } from "astro/config";
import emdash, { local } from "emdash/astro";
import { sqlite } from "emdash/db";
export default defineConfig({
security: {
allowedDomains: [
{ hostname: "cms.example.com", protocol: "https" },
{ hostname: "cms.example.com", protocol: "http" },
],
},
vite: {
server: {
allowedHosts: ["cms.example.com"],
},
},
integrations: [
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
siteUrl: "https://cms.example.com",
}),
],
});
trustedProxyHeaders
可选。 在您控制的反向代理后面运行时信任用于客户端 IP 解析的标头。由认证速率限制(魔法链接、注册、密钥、OAuth 设备流程)和公共评论端点使用。
在 Cloudflare 上,附加到请求的 cf 对象会自动使用 — 您通常不需要设置此项。在 nginx、Caddy、Traefik、Fly、Railway 或类似服务后面的自托管部署中,将其设置为代理写入的标头,以便速率限制可以按真实客户端 IP 分组,而不是将每个请求视为”unknown”。
以下示例信任由 nginx、Caddy 或 Traefik 设置的 x-real-ip 标头:
emdash({
database: sqlite({ url: "file:./data.db" }),
trustedProxyHeaders: ["x-real-ip"],
});
标头按顺序尝试。匹配 *-forwarded-for 的值被解析为逗号分隔的列表,并使用第一个条目。以下示例优先使用 Fly.io 的标头并回退到 x-forwarded-for:
emdash({
trustedProxyHeaders: ["fly-client-ip", "x-forwarded-for"],
});
当配置中未设置时,EmDash 读取 EMDASH_TRUSTED_PROXY_HEADERS 环境变量(逗号分隔)。配置中的显式空数组会覆盖环境变量。
maxUploadSize
可选。 允许的最大媒体文件上传大小(以字节为单位)。适用于直接分块上传和签名 URL 上传。默认为 52_428_800(50 MB)。以下示例将限制提高到 100 MB:
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
maxUploadSize: 100 * 1024 * 1024, // 100 MB
});
| 值 | 描述 |
|---|---|
number(字节) | 必须是正有限整数 |
| 省略 | 默认为 50 MB |
超过配置限制的上传在直接上传路径上被拒绝并返回 413 Payload Too Large 响应,或在签名 URL 路径上返回 400 Validation Error。
数据库适配器
从 emdash/db 导入适配器:
import { sqlite, libsql, postgres } from "emdash/db";
sqlite(config)
使用 better-sqlite3 的 SQLite 数据库。以下示例连接到本地文件:
| 选项 | 类型 | 描述 |
|---|---|---|
url | string | 带 file: 前缀的文件路径 |
sqlite({ url: "file:./data.db" });
libsql(config)
libSQL 数据库。以下示例连接到远程 libSQL 数据库:
| 选项 | 类型 | 描述 |
|---|---|---|
url | string | 数据库 URL |
authToken | string | 认证令牌(本地文件可选) |
libsql({
url: process.env.LIBSQL_DATABASE_URL,
authToken: process.env.LIBSQL_AUTH_TOKEN,
});
postgres(config)
带连接池的 PostgreSQL 数据库。
| 选项 | 类型 | 描述 |
|---|---|---|
connectionString | string | PostgreSQL 连接 URL |
host | string | 数据库主机 |
port | number | 数据库端口 |
database | string | 数据库名称 |
user | string | 数据库用户 |
password | string | 数据库密码 |
ssl | boolean | 启用 SSL |
pool.min | number | 最小池大小(默认:0) |
pool.max | number | 最大池大小(默认:10) |
以下示例使用连接字符串连接:
postgres({ connectionString: process.env.DATABASE_URL });
d1(config)
Cloudflare D1 数据库。从 @emdash-cms/cloudflare 导入。
| 选项 | 类型 | 默认值 | 描述 |
|---|---|---|---|
binding | string | — | 来自 wrangler.jsonc 的 D1 绑定名称 |
session | string | "disabled" | 读取复制模式:"disabled"、"auto" 或 "primary-first" |
bookmarkCookie | string | "__em_d1_bookmark" | 会话书签的 cookie 名称 |
以下示例显示基本绑定和启用读取副本的绑定:
// 基本
d1({ binding: "DB" });
// 使用读取副本
d1({ binding: "DB", session: "auto" });
当 session 为 "auto" 或 "primary-first" 时,EmDash 使用 D1 Sessions API 将读取查询路由到附近的副本。经过身份验证的用户获得基于书签的读后写一致性。详情请参阅数据库选项 — 读取副本。
存储适配器
从 emdash/astro 导入 local 和 s3。r2 适配器从 @emdash-cms/cloudflare 导入:
import emdash, { local, s3 } from "emdash/astro";
import { r2 } from "@emdash-cms/cloudflare";
local(config)
本地文件系统存储。以下示例从本地目录提供上传:
| 选项 | 类型 | 描述 |
|---|---|---|
directory | string | 目录路径 |
baseUrl | string | 用于提供文件的基本 URL |
local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
});
r2(config)
Cloudflare R2 绑定。以下示例使用带有公共 URL 的 R2 绑定:
| 选项 | 类型 | 描述 |
|---|---|---|
binding | string | R2 绑定名称 |
publicUrl | string | 可选公共 URL |
r2({
binding: "MEDIA",
publicUrl: "https://pub-xxxx.r2.dev",
});
s3(config?)
S3 兼容存储。所有配置字段都是可选的:从 s3({...}) 中省略的任何字段都会在 Node 进程启动时从匹配的 S3_* 环境变量中解析。显式值始终优先。
先决条件: 在项目中安装 @aws-sdk/client-s3 和 @aws-sdk/s3-request-presigner。EmDash 核心不捆绑 AWS SDK。详情请参阅存储选项:S3 兼容存储。
| 选项 | 类型 | 描述 |
|---|---|---|
endpoint | string | S3 端点 URL(S3_ENDPOINT) |
bucket | string | 存储桶名称(S3_BUCKET) |
accessKeyId | string | 访问密钥(S3_ACCESS_KEY_ID) |
secretAccessKey | string | 密钥(S3_SECRET_ACCESS_KEY) |
region | string | 区域,默认 "auto"(S3_REGION) |
publicUrl | string | 可选 CDN URL(S3_PUBLIC_URL) |
以下示例从环境解析所有字段,混合配置和环境,或显式传递每个字段:
// 所有字段来自 S3_* 环境变量(Node 容器部署)
s3()
// 混合:CDN 来自配置,其余来自环境
s3({ publicUrl: "https://cdn.example.com" })
// 全部显式
s3({
endpoint: "https://xxx.r2.cloudflarestorage.com",
bucket: "media",
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
publicUrl: "https://cdn.example.com",
})
运行时环境变量解析是 Node 专用功能。在 Cloudflare Workers 上,密钥和变量通过 fetch 处理程序的 env 参数公开,而不是通过 process.env,因此不会获取 S3_* 环境变量。Workers 部署应使用 r2(config) 适配器或将显式值传递给 s3({...})。详情请参阅存储选项。
实时集合
在 src/live.config.ts 中配置 EmDash 加载器:
import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";
export const collections = {
_emdash: defineLiveCollection({
loader: emdashLoader(),
}),
};
加载器选项
emdashLoader() 函数不接受参数:
emdashLoader();
环境变量
EmDash 遵守这些环境变量:
| 变量 | 描述 |
|---|---|
EMDASH_SITE_URL | 公共浏览器面向源(回退到 SITE_URL) |
EMDASH_ALLOWED_ORIGINS | 密钥验证接受的其他源的逗号分隔列表(多子域部署)。 |
EMDASH_DATABASE_URL | 覆盖数据库 URL |
EMDASH_ENCRYPTION_KEY | 用于加密静态插件密钥的密钥。由操作员提供 — 永远不会存储在数据库中。 |
EMDASH_PREVIEW_SECRET | 预览 HMAC 密钥的可选覆盖。未设置时,生成一个稳定的每站点值并存储在数据库中。 |
EMDASH_IP_SALT | 评论者 IP 哈希盐的可选覆盖。未设置时,生成一个稳定的每站点值并存储在数据库中。 |
EMDASH_AUTH_SECRET | 遗留。如果设置,用作 IP 盐源;现有安装应保留此项以在升级时保留稳定的评论者 IP 哈希。 |
EMDASH_URL | 用于架构同步的远程 EmDash URL |
使用以下命令生成加密密钥:
npx emdash secrets generate
package.json 配置
模板和站点可以在 package.json 的 emdash 键下声明可选元数据:
{
"emdash": {
"label": "My Blog Template",
"seed": ".emdash/seed.json",
"url": "https://my-site.pages.dev"
}
}
| 选项 | 描述 |
|---|---|
label | 用于显示的模板名称 |
seed | 种子 JSON 文件的路径 |
url | 用于架构同步的远程 URL |
TypeScript 配置
EmDash 在 .emdash/types.ts 中生成类型。将路径别名添加到您的 tsconfig.json:
{
"compilerOptions": {
"paths": {
"@emdash-cms/types": ["./.emdash/types.ts"]
}
}
}
使用以下命令生成类型:
npx emdash types