mirror of
https://github.com/Shu-Ying/farm_service.git
synced 2026-11-02 10:39:59 +08:00
add # 新增字体
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"D:/nonebot/farm_service/.agents/skills/comet/scripts/comet-hook-router.mjs\" --platform \"codex\" --project-root \"D:/nonebot/farm_service\""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
# Comet 使用的配置格式版本,请勿修改此值。
|
||||
schema: comet.project.v1
|
||||
# `/comet` 默认进入的工作流;该值也必须出现在 workflows 中。
|
||||
default_workflow: native
|
||||
# 此项目启用的工作流,可填写 native、classic 或同时启用两者。
|
||||
workflows:
|
||||
- native
|
||||
# 是否启用只读的环境感知恢复探针,同时作用于 Native 和 Classic;设为 false 可关闭自动工作流恢复。
|
||||
# ambient_resume: true | false
|
||||
ambient_resume: true
|
||||
# 当前项目的个人记忆自动学习与注入策略。
|
||||
memory:
|
||||
# 是否允许当前项目通过工作流事件自动沉淀新的个人记忆。
|
||||
# 可选值:true | false
|
||||
learning: true
|
||||
# 是否允许当前项目把个人记忆自动注入 Agent 上下文。
|
||||
# 可选值:true | false
|
||||
retrieval: true
|
||||
# 普通 Comet 任务使用的项目知识检索 Provider。
|
||||
knowledge:
|
||||
# 项目知识 Provider。
|
||||
# 可选值:local | remote
|
||||
provider: local
|
||||
# Native 工作流配置,不会改变 Classic 的状态或行为。
|
||||
native:
|
||||
# Native 规格和 change 的存放根目录;运行时数据始终位于 .comet。
|
||||
artifact_root: docs
|
||||
# Native 工作流文档使用的产物语言。
|
||||
# 可选值:en | zh-CN
|
||||
language: zh-CN
|
||||
# Native 提问澄清问题的方式:batch 每轮一次提出当前所有可回答的问题(默认),sequential 每轮只问一个。
|
||||
# 可选值:batch | sequential
|
||||
clarification_mode: batch
|
||||
# Native 归档检查成功后自动归档,或等待用户明确确认。
|
||||
# 可选值:automatic | required
|
||||
archive_confirmation: automatic
|
||||
# 同一个已确认验收目标最多允许的 Verify 失败次数;达到上限后停止完成循环。
|
||||
max_verify_failures: 5
|
||||
@@ -8,3 +8,5 @@ RUST_LOG=farm_service=info
|
||||
# Optional: leave unset to disable asset distribution. Sibling assets.zip required.
|
||||
# The release files live in this repository's root assets/ directory.
|
||||
# FARM_ASSET_MANIFEST=./assets/manifest.json
|
||||
# Optional: independent Xiaolai font archive, including the complete OFL license.
|
||||
# FARM_FONT_MANIFEST=./fonts/manifest.json
|
||||
|
||||
@@ -36,3 +36,8 @@ profraw
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
Desktop.ini
|
||||
# >>> Comet managed project state >>>
|
||||
!/.comet/
|
||||
/.comet/*
|
||||
!/.comet/config.yaml
|
||||
# <<< Comet managed project state <<<
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
- `GET /health`:公开就绪检查,实际检查数据库。
|
||||
- `GET /v1/assets/manifest.json`:公开素材清单,含版本、SHA256、大小及三源绝对URL。
|
||||
- `GET /v1/assets/{sha256}/assets.zip`:下载启动时校验过的素材快照,支持ETag。
|
||||
- `GET /v1/fonts/manifest.json`:独立字体资源清单。
|
||||
- `GET /v1/fonts/{sha256}/assets.zip`:原版小赖字体与完整 OFL 许可,支持 ETag。
|
||||
- [资源分发接口与三端接入约定](docs/assets.md)。
|
||||
- 素材发布文件位于根目录 `assets/manifest.json` 和 `assets/assets.zip`;
|
||||
ZIP 仅由本服务仓库发布,三个插件分发包不包含 ZIP。
|
||||
@@ -65,6 +67,7 @@ cargo run --locked
|
||||
| `FARM_LOG_DIR` | `./logs`,保存每日滚动日志 |
|
||||
| `RUST_LOG` | `farm_service=info` |
|
||||
| `FARM_ASSET_MANIFEST` | 可选,建议 `./assets/manifest.json`;同目录须有assets.zip;未设置时资源接口返回503 |
|
||||
| `FARM_FONT_MANIFEST` | 可选,设为 `./fonts/manifest.json`;同目录须有 assets.zip;未设置时字体接口返回503 |
|
||||
|
||||
相对目录以进程工作目录为基准;生产建议使用固定绝对路径。
|
||||
`.env.example` 只是模板,程序**不会自动加载 .env**。
|
||||
@@ -133,6 +136,13 @@ cargo clippy --locked --all-targets -- -D warnings
|
||||
|
||||
## 开源协议
|
||||
|
||||
字体资源独立于服务代码许可。`fonts/Xiaolai-Regular.ttf` 为未经修改的
|
||||
小赖字体 v3.126,采用 SIL OFL 1.1;完整版权与许可见
|
||||
[OFL-Xiaolai.txt](fonts/OFL-Xiaolai.txt)。`fonts/assets.zip` 同时包含字体
|
||||
和许可,不得单独出售字体文件。字体包通过 GitHub/Gitea 的本仓库
|
||||
`fonts/assets.zip` raw 地址或上述服务接口分发,插件缓存时校验摘要。
|
||||
资源迁移仅准备本地文件,须发布此仓库或部署服务后远端才能提供新版资源。
|
||||
|
||||
Copyright (c) 2026 Shu-Ying。
|
||||
本项目采用 GNU Affero General Public License v3.0,
|
||||
SPDX 标识为 `AGPL-3.0-only`,完整条款见 [LICENSE](LICENSE)。
|
||||
@@ -142,3 +152,7 @@ SPDX 标识为 `AGPL-3.0-only`,完整条款见 [LICENSE](LICENSE)。
|
||||
显著提供免费取得该修改版本对应源码的机会。
|
||||
源码地址应指向实际运行版本,不能仅指向不包含这些修改的上游仓库。
|
||||
协议针对程序及其对应源码,不要求公开 API 令牌、用户资料或数据库内容。
|
||||
|
||||
## 致谢
|
||||
|
||||
- [小赖字体](https://github.com/lxgw/kose-font) *lxgw 提供的开源中文手写字体,采用 SIL OFL 1.1,分发时附带完整字体许可*
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
Copyright 2020-2024 LXGW (https://github.com/lxgw/kose-font)
|
||||
Copyright 2014 Nozomi Seto (https://ja.osdn.net/projects/setofont/)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"version": "xiaolai-v3.126",
|
||||
"sha256": "c2d1c1a5247e05b766d12a8d8410594d78b1a8b86031624a05b76ee0515c1335",
|
||||
"size_bytes": 14741741,
|
||||
"sources": {
|
||||
"github": "https://raw.githubusercontent.com/Shu-Ying/farm_service/main/fonts/assets.zip",
|
||||
"gitea": "https://gitea.diuse.work/Diuse/farm_service/raw/branch/main/fonts/assets.zip",
|
||||
"farm_service": "https://farm.example/v1/fonts/c2d1c1a5247e05b766d12a8d8410594d78b1a8b86031624a05b76ee0515c1335/assets.zip"
|
||||
}
|
||||
}
|
||||
+23
-2
@@ -51,6 +51,10 @@ pub struct Manifest {
|
||||
|
||||
impl Manifest {
|
||||
pub fn validate(&self) -> Result<()> {
|
||||
self.validate_for("assets")
|
||||
}
|
||||
|
||||
fn validate_for(&self, namespace: &str) -> Result<()> {
|
||||
ensure!(self.schema == 1, "unsupported asset manifest schema");
|
||||
ensure!(
|
||||
!self.version.is_empty()
|
||||
@@ -84,7 +88,7 @@ impl Manifest {
|
||||
ensure!(
|
||||
service_url
|
||||
.path()
|
||||
.ends_with(&format!("/v1/assets/{}/assets.zip", self.sha256)),
|
||||
.ends_with(&format!("/v1/{namespace}/{}/assets.zip", self.sha256)),
|
||||
"farm_service URL must include the bundle SHA256"
|
||||
);
|
||||
Ok(())
|
||||
@@ -128,10 +132,19 @@ pub struct AssetBundle {
|
||||
impl AssetBundle {
|
||||
/// Load manifest.json and its sibling assets.zip, verifying both at startup.
|
||||
pub fn load(manifest_path: &Path) -> Result<Self> {
|
||||
Self::load_for(manifest_path, "assets")
|
||||
}
|
||||
|
||||
/// Load an independent font archive with the same integrity contract.
|
||||
pub fn load_font(manifest_path: &Path) -> Result<Self> {
|
||||
Self::load_for(manifest_path, "fonts")
|
||||
}
|
||||
|
||||
fn load_for(manifest_path: &Path, namespace: &str) -> Result<Self> {
|
||||
let manifest: Manifest =
|
||||
serde_json::from_slice(&read_bounded(manifest_path, MAX_MANIFEST_BYTES)?)
|
||||
.context("invalid asset manifest JSON")?;
|
||||
manifest.validate()?;
|
||||
manifest.validate_for(namespace)?;
|
||||
let content = read_bounded(
|
||||
&manifest_path.with_file_name("assets.zip"),
|
||||
MAX_BUNDLE_BYTES,
|
||||
@@ -169,6 +182,14 @@ pub fn router(bundle: Option<AssetBundle>) -> Router {
|
||||
.with_state(bundle)
|
||||
}
|
||||
|
||||
/// Font distribution is independent of the picture bundle and profile APIs.
|
||||
pub fn font_router(bundle: Option<AssetBundle>) -> Router {
|
||||
Router::new()
|
||||
.route("/v1/fonts/manifest.json", get(get_manifest))
|
||||
.route("/v1/fonts/{sha256}/assets.zip", get(download))
|
||||
.with_state(bundle)
|
||||
}
|
||||
|
||||
async fn get_manifest(State(bundle): State<Option<AssetBundle>>) -> Response {
|
||||
match bundle {
|
||||
Some(bundle) => {
|
||||
|
||||
@@ -14,6 +14,8 @@ pub struct Config {
|
||||
pub log_dir: PathBuf,
|
||||
/// Optional verified asset manifest; sibling assets.zip is loaded once.
|
||||
pub asset_manifest: Option<PathBuf>,
|
||||
/// Optional font archive manifest; includes the original OFL license.
|
||||
pub font_manifest: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@@ -33,6 +35,7 @@ impl Config {
|
||||
.context("invalid FARM_BIND address")?,
|
||||
api_token,
|
||||
asset_manifest: env::var_os("FARM_ASSET_MANIFEST").map(PathBuf::from),
|
||||
font_manifest: env::var_os("FARM_FONT_MANIFEST").map(PathBuf::from),
|
||||
data_dir: env::var_os("FARM_DATA_DIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| PathBuf::from("data")),
|
||||
|
||||
+9
-1
@@ -24,13 +24,21 @@ async fn main() -> Result<()> {
|
||||
.map(assets::AssetBundle::load)
|
||||
.transpose()
|
||||
.context("asset startup verification failed")?;
|
||||
let fonts = config
|
||||
.font_manifest
|
||||
.as_deref()
|
||||
.map(assets::AssetBundle::load_font)
|
||||
.transpose()
|
||||
.context("font startup verification failed")?;
|
||||
std::fs::create_dir_all(&config.data_dir)?;
|
||||
let pool = db::open_database(&config.data_dir.join("farm_service.sqlite3"))
|
||||
.await
|
||||
.context("database startup failed")?;
|
||||
let listener = tokio::net::TcpListener::bind(config.bind).await?;
|
||||
tracing::info!(address = %listener.local_addr()?, "service_ready");
|
||||
let app = api::router(pool.clone(), config.api_token).merge(assets::router(bundle));
|
||||
let app = api::router(pool.clone(), config.api_token)
|
||||
.merge(assets::router(bundle))
|
||||
.merge(assets::font_router(fonts));
|
||||
let result = axum::serve(listener, app)
|
||||
.with_graceful_shutdown(shutdown())
|
||||
.await;
|
||||
|
||||
@@ -39,6 +39,40 @@ fn save(directory: &tempfile::TempDir, manifest: &Manifest) {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn independent_font_archive_is_verified_and_public() {
|
||||
let (directory, mut manifest, content) = fixture();
|
||||
manifest.sources.farm_service = format!(
|
||||
"https://farm.example/v1/fonts/{}/assets.zip",
|
||||
manifest.sha256
|
||||
);
|
||||
save(&directory, &manifest);
|
||||
let path = directory.path().join("manifest.json");
|
||||
assert!(AssetBundle::load(&path).is_err());
|
||||
let bundle = AssetBundle::load_font(&path).unwrap();
|
||||
let app = assets::font_router(Some(bundle));
|
||||
let uri = format!("/v1/fonts/{}/assets.zip", manifest.sha256);
|
||||
let response = app
|
||||
.clone()
|
||||
.oneshot(Request::builder().uri(uri).body(Body::empty()).unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
assert_eq!(to_bytes(response.into_body(), 4096).await.unwrap(), content);
|
||||
let response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri("/v1/fonts/unknown/assets.zip")
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
||||
std::fs::write(directory.path().join("assets.zip"), b"bad").unwrap();
|
||||
assert!(AssetBundle::load_font(&path).is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn public_manifest_download_cache_and_snapshot() {
|
||||
let (directory, manifest, content) = fixture();
|
||||
|
||||
Reference in New Issue
Block a user