diff --git a/docs/40-deployment/local-development.md b/docs/40-deployment/local-development.md index f0ba2b6..633490e 100644 --- a/docs/40-deployment/local-development.md +++ b/docs/40-deployment/local-development.md @@ -7,7 +7,7 @@ pdm install --frozen-lockfile pnpm install --frozen-lockfile ``` -配置 `DATABASE_URL`、`PUBLIC_ORIGIN`、`S3_ENDPOINT_URL`、`S3_BUCKET` 和标准 AWS 凭据 `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`。启用插件文档托管时,另设 `DOCUMENTATION_ORIGIN_TEMPLATE`,例如本地的 `http://{snapshot}.docs.localhost`;模板必须把 32 位快照标识放在第一个 DNS label,使每份不可变快照拥有独立 origin。数据库必须是 PostgreSQL;远程连接默认验证 TLS,localhost 可以不启用 TLS。`PUBLIC_ORIGIN` 是 HTTPS origin,本地允许 localhost HTTP,不含路径。文档内容 origin 在生产必须使用 HTTPS;本地只允许 `.localhost`。文件配置是 dotenv 数据,不应当作 shell 脚本执行;应用从进程环境读取配置。 +配置 `DATABASE_URL`、`PUBLIC_ORIGIN`、`S3_ENDPOINT_URL`、`S3_BUCKET` 和标准 AWS 凭据 `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`。启用插件文档托管时,另设 `DOCUMENTATION_ORIGIN_TEMPLATE`,例如本地的 `http://{snapshot}.docs.localhost`;模板必须把 32 位快照标识放在第一个 DNS label,可带固定前后缀,使每份不可变快照拥有独立 origin。数据库必须是 PostgreSQL;远程连接默认验证 TLS,localhost 可以不启用 TLS。`PUBLIC_ORIGIN` 是 HTTPS origin,本地允许 localhost HTTP,不含路径。文档内容 origin 在生产必须使用 HTTPS;本地只允许 `.localhost`。文件配置是 dotenv 数据,不应当作 shell 脚本执行;应用从进程环境读取配置。 生产内容域可以与管理站使用同一可注册域。这样部署时,作者提供的 HTML/JS 可能接收或干扰父域 Cookie;不得在共享父域设置敏感 Cookie。需要浏览器级站点隔离时,应改用独立可注册域,例如 `registry.example.com` 与 `{snapshot}.exampleusercontent.net`。域名应采用 ASCII/Punycode 配置。 diff --git a/docs/40-deployment/production-registry.md b/docs/40-deployment/production-registry.md index ccfc39d..aedb6d4 100644 --- a/docs/40-deployment/production-registry.md +++ b/docs/40-deployment/production-registry.md @@ -4,7 +4,7 @@ `production.yml` 只接受精确 current-main SHA。`verify` 执行仓库检查和容器构建,不修改远程资源。`deploy` 在受保护 production 环境中,重新核验 main 后对已配置 app 向前迁移、配置数据库和单桶 S3 凭据、发布同一镜像,并验证 Heroku app origin。它不自动导入 D1、不改变域名、不创建示例数据,也不删除旧资源。 -production 环境需要 `HEROKU_APP_NAME`、`S3_ENDPOINT_URL`、`S3_BUCKET`、`DOCUMENTATION_ORIGIN_TEMPLATE` variables,以及 `HEROKU_API_KEY`、`MIGRATION_DATABASE_URL`、`DATABASE_URL`、`AWS_ACCESS_KEY_ID`、`AWS_SECRET_ACCESS_KEY` secrets。文档模板必须指向已经配置 wildcard DNS/TLS 的专用内容 origin。生产当前使用 `https://{snapshot}.docs.registry.inkcre.dev`;它与管理站同属 `inkcre.dev`,因此部署接受作者 HTML/JS 与管理站同站、可能接收或干扰父域 Cookie 的风险。`inkcre.dev` 下的服务不得设置可发送到该内容域的敏感父域 Cookie。首次配置保持既有 R2 桶,S3 token 仅授予该桶的对象读写。`MIGRATION_DATABASE_URL` 使用 `registry_owner`,仅交给迁移容器与可信角色配置命令;`DATABASE_URL` 使用同一数据库的普通 `registry_app` 角色和独立密码。迁移创建该角色及业务表授权,控制器设置密码后以 `web=1:eco` 启动应用。owner 连接与平台控制 token 不进入运行服务配置。数据库连接属于独立 Registry 项目,不复用 core-py 的数据库或发布生命周期。 +production 环境需要 `HEROKU_APP_NAME`、`S3_ENDPOINT_URL`、`S3_BUCKET`、`DOCUMENTATION_ORIGIN_TEMPLATE` variables,以及 `HEROKU_API_KEY`、`MIGRATION_DATABASE_URL`、`DATABASE_URL`、`AWS_ACCESS_KEY_ID`、`AWS_SECRET_ACCESS_KEY` secrets。文档模板必须指向已经配置 wildcard DNS/TLS 的专用内容 origin。生产当前使用 `https://registry-docs-{snapshot}.inkcre.dev`;每个不可变快照拥有独立 origin,并由 `*.inkcre.dev` DNS catch-all 与证书承载。服务只接受符合该模板的内容 host,其他落入 catch-all 的 host 返回 421。内容域与管理站同属 `inkcre.dev`,因此部署接受作者 HTML/JS 可能接收或干扰父域 Cookie 的风险;`inkcre.dev` 下的服务不得设置可发送到内容域的敏感父域 Cookie。首次配置保持既有 R2 桶,S3 token 仅授予该桶的对象读写。`MIGRATION_DATABASE_URL` 使用 `registry_owner`,仅交给迁移容器与可信角色配置命令;`DATABASE_URL` 使用同一数据库的普通 `registry_app` 角色和独立密码。迁移创建该角色及业务表授权,控制器设置密码后以 `web=1:eco` 启动应用。owner 连接与平台控制 token 不进入运行服务配置。数据库连接属于独立 Registry 项目,不复用 core-py 的数据库或发布生命周期。 Heroku 交付为 Uvicorn 设置 `FORWARDED_ALLOW_IPS=*`,由平台 HTTP 入口提供外部请求协议,补斜杠跳转保持 HTTPS。转发头不参与 namespace 授权或身份判断;认证仍由 publisher credential 决定。部署 smoke 同时检查 `/simple` 跳转到该 origin 的 HTTPS `/simple/`。 diff --git a/scripts/check_documentation.py b/scripts/check_documentation.py index f8a93dd..651ae3e 100644 --- a/scripts/check_documentation.py +++ b/scripts/check_documentation.py @@ -34,6 +34,8 @@ def check_content_sites() -> None: ("https://registry.example.com", "https://{snapshot}.exampleusercontent.net", True), ("https://registry.team.github.io", "https://{snapshot}.docs.team.github.io", True), ("https://registry.team.github.io", "https://{snapshot}.other.github.io", True), + ("https://registry.example.com", "https://registry-docs-{snapshot}.example.com", True), + ("https://registry.example.com", "https://docs.registry-{snapshot}.example.com", False), ("http://localhost", "http://{snapshot}.docs.localhost", True), ("https://registry.example.com", "http://{snapshot}.docs.localhost", False), ): diff --git a/src/inkcre_extension_registry/service/settings.py b/src/inkcre_extension_registry/service/settings.py index 236dacc..ed44818 100644 --- a/src/inkcre_extension_registry/service/settings.py +++ b/src/inkcre_extension_registry/service/settings.py @@ -105,7 +105,7 @@ def from_env(cls) -> Settings: ) if ( documentation_origin.count("{snapshot}") != 1 - or not (content.hostname or "").startswith("{snapshot}.") + or "{snapshot}" not in (content.hostname or "").split(".", 1)[0] or (content.scheme != "https" and not local_content) or (local_content and not local_http) or content.username is not None @@ -119,7 +119,7 @@ def from_env(cls) -> Settings: ): raise ValueError( "DOCUMENTATION_ORIGIN_TEMPLATE must be a separate wildcard HTTPS origin " - "with a leading {snapshot} label" + "with {snapshot} in the leading label" ) return cls( database_url=os.environ["DATABASE_URL"],