配置参考

本页内容

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"],
  },
})

可用的脚本有 arabicarmenianbengalichinese-simplifiedchinese-traditionalchinese-hongkongdevanagariethiopicfarsigeorgiangujaratigurmukhihebrewjapanesekannadakhmerkoreanlaomalayalammyanmaroriyasinhalatamilteluguthaitibetan

每个脚本映射到 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() 的选项:

选项类型默认值描述
teamDomainstring必需您的 Cloudflare Access 团队域名
audiencestring应用程序受众(AUD)标签。在 Workers 上,优先使用 audienceEnvVar
audienceEnvVarstring"CF_ACCESS_AUDIENCE"运行时读取受众标签的环境变量
autoProvisionbooleantrue首次登录时创建 EmDash 用户
defaultRolenumber30未被 roleMapping 匹配的用户的角色级别(参见用户角色
syncRolesbooleanfalse每次登录时重新应用 roleMapping,而不是仅在配置时
roleMappingobject将 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.comhttps://preview.example.com)下访问时,密钥验证会拒绝源与 siteUrl 不完全匹配的断言 — 尽管 WebAuthn 允许密钥在相同 rpId 下的子域之间有效。

通过 astro.config.mjs 中的 allowedOriginsEMDASH_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.allowedOriginsconfig.siteUrl 都来自 astro.config.mjs 时 — 代码中的拼写错误导致构建失败。
  • 在首次密钥验证时,当任一值来自 EMDASH_ALLOWED_ORIGINSEMDASH_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”键下。

以下配置为反向代理部署一起设置 allowedDomainsvite.server.allowedHostssiteUrl

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 数据库。以下示例连接到本地文件:

选项类型描述
urlstringfile: 前缀的文件路径
sqlite({ url: "file:./data.db" });

libsql(config)

libSQL 数据库。以下示例连接到远程 libSQL 数据库:

选项类型描述
urlstring数据库 URL
authTokenstring认证令牌(本地文件可选)
libsql({
	url: process.env.LIBSQL_DATABASE_URL,
	authToken: process.env.LIBSQL_AUTH_TOKEN,
});

postgres(config)

带连接池的 PostgreSQL 数据库。

选项类型描述
connectionStringstringPostgreSQL 连接 URL
hoststring数据库主机
portnumber数据库端口
databasestring数据库名称
userstring数据库用户
passwordstring数据库密码
sslboolean启用 SSL
pool.minnumber最小池大小(默认:0)
pool.maxnumber最大池大小(默认:10)

以下示例使用连接字符串连接:

postgres({ connectionString: process.env.DATABASE_URL });

d1(config)

Cloudflare D1 数据库。从 @emdash-cms/cloudflare 导入。

选项类型默认值描述
bindingstring来自 wrangler.jsonc 的 D1 绑定名称
sessionstring"disabled"读取复制模式:"disabled""auto""primary-first"
bookmarkCookiestring"__em_d1_bookmark"会话书签的 cookie 名称

以下示例显示基本绑定和启用读取副本的绑定:

// 基本
d1({ binding: "DB" });

// 使用读取副本
d1({ binding: "DB", session: "auto" });

session"auto""primary-first" 时,EmDash 使用 D1 Sessions API 将读取查询路由到附近的副本。经过身份验证的用户获得基于书签的读后写一致性。详情请参阅数据库选项 — 读取副本

存储适配器

emdash/astro 导入 locals3r2 适配器从 @emdash-cms/cloudflare 导入:

import emdash, { local, s3 } from "emdash/astro";
import { r2 } from "@emdash-cms/cloudflare";

local(config)

本地文件系统存储。以下示例从本地目录提供上传:

选项类型描述
directorystring目录路径
baseUrlstring用于提供文件的基本 URL
local({
	directory: "./uploads",
	baseUrl: "/_emdash/api/media/file",
});

r2(config)

Cloudflare R2 绑定。以下示例使用带有公共 URL 的 R2 绑定:

选项类型描述
bindingstringR2 绑定名称
publicUrlstring可选公共 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 兼容存储

选项类型描述
endpointstringS3 端点 URL(S3_ENDPOINT
bucketstring存储桶名称(S3_BUCKET
accessKeyIdstring访问密钥(S3_ACCESS_KEY_ID
secretAccessKeystring密钥(S3_SECRET_ACCESS_KEY
regionstring区域,默认 "auto"S3_REGION
publicUrlstring可选 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.jsonemdash 键下声明可选元数据:

{
	"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