feat(kenapi): introduce new command-line interface with serve and migrate options
ZhenYi committed
Deleted app/apihost/src/main.rs app/apihost/src/main.rs +0 −99
@@ -1,99 +0,0 @@
1#![recursion_limit = "256"]
2
3use std::net::SocketAddr;
4
5use anyhow::Context;
6use axum::Router;
7use config::scoped::{ApihostConfig, ScopedConfig};
8use tokio::net::TcpListener;
9use tokio_util::sync::CancellationToken;
10use tracing::{info, warn};
11use utoipa_swagger_ui::SwaggerUi;
12use webapi::{openapi::api_doc, router};
13
14#[tokio::main]
15async fn main() -> anyhow::Result<()> {
16 export::telemetry::init_tracing();
17
18 let cfg = ApihostConfig::load().context("loading configuration")?;
19 if !cfg.apihost.enabled() {
20 info!("apihost is disabled by config; exiting");
21 return Ok(());
22 }
23
24 info!("connecting to database / redis / storage / email");
25 let state = export::AppState::with_config(cfg.clone().into_app_config())
26 .await
27 .context("building AppState")?;
28 info!("dependencies ready");
29
30 if let Err(err) = export::telemetry::init_meter_provider() {
31 warn!(error = %err, "failed to init meter provider; metrics disabled");
32 }
33 export::metrics::spawn_queue_depth_reporter(
34 state.redis.clone(),
35 &[
36 ("push.jobs", export::push::QUEUE_TOPIC.to_string()),
37 ("push.jobs.dlq", export::push::DLQ_TOPIC.to_string()),
38 ],
39 );
40
41 tokio::spawn(chat::worker::run_background_tasks(state.clone()));
42
43 if cfg.rocketmq.enabled {
44 let conn = queue::RocketMqConn::init(&cfg.rocketmq)
45 .await
46 .map_err(|err| anyhow::anyhow!("failed to start the rocketmq producer: {err}"))?;
47 tokio::spawn(queue::relay::run(
48 state.database.primary().clone(),
49 queue::Publisher::new(conn),
50 CancellationToken::new(),
51 ));
52 info!("event outbox relay started");
53 } else {
54 info!("rocketmq is disabled; chat message events are not published");
55 }
56
57 // Web Push 消费者:与其它后台任务一起挂在 apihost 上,不为它单开进程。
58 // 配置非法(密钥缺失或公私钥不成对)时直接报错退出——静默不投递比启动
59 // 失败更难被发现。
60 if cfg.push.enabled {
61 for _ in 0..cfg.push.workers.max(1) {
62 let worker = push::PushWorker::new(state.clone(), CancellationToken::new())
63 .map_err(|err| anyhow::anyhow!("failed to start push worker: {err}"))?;
64 tokio::spawn(worker.run());
65 }
66 info!(
67 workers = cfg.push.workers.max(1),
68 "web push workers started"
69 );
70 }
71
72 let api_router: Router = router(state)
73 .await
74 .map_err(|err| anyhow::anyhow!("failed to build router: {err}"))?;
75 let doc = api_doc();
76 let api_router =
77 api_router.merge(SwaggerUi::new("/api/docs").url("/api/docs/openapi.json", doc));
78
79 let bind_addr = cfg.apihost.bind_addr().to_string();
80 let port = cfg.apihost.port();
81 let addr: SocketAddr = format!("{bind_addr}:{port}")
82 .parse()
83 .with_context(|| format!("parsing bind address {bind_addr}:{port}"))?;
84
85 let listener = TcpListener::bind(addr)
86 .await
87 .with_context(|| format!("binding {addr}"))?;
88 info!(%addr, "apihost listening");
89
90 axum::serve(listener, api_router.into_make_service())
91 .with_graceful_shutdown(export::telemetry::shutdown_signal())
92 .await
93 .context("axum server error")?;
94
95 export::telemetry::shutdown_telemetry();
96
97 info!("apihost shut down");
98 Ok(())
99}
Deleted app/migrate/src/main.rs app/migrate/src/main.rs +0 −33
@@ -1,33 +0,0 @@
1use anyhow::Context;
2use config::scoped::{MigrateConfig, ScopedConfig};
3use models::migrate::sync_schema;
4use sea_orm::Database;
5use tracing::info;
6
7const DEFAULT_LOG_FILTER: &str = "info,migrate=debug";
8
9#[tokio::main]
10async fn main() -> anyhow::Result<()> {
11 tracing_subscriber::fmt()
12 .with_env_filter(
13 tracing_subscriber::EnvFilter::try_from_default_env()
14 .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(DEFAULT_LOG_FILTER)),
15 )
16 .init();
17
18 let cfg = MigrateConfig::load().context("loading configuration")?;
19
20 let opts = cfg
21 .database
22 .primary_connect_options()
23 .map_err(|e| anyhow::anyhow!(e))?;
24 let db = Database::connect(opts)
25 .await
26 .context("connecting to postgres")?;
27
28 info!("running schema migration");
29 sync_schema(&db).await.context("running schema sync")?;
30 info!("schema migration completed");
31
32 Ok(())
33}
Deleted app/migrate/Cargo.toml app/migrate/Cargo.toml +0 −13
@@ -1,13 +0,0 @@
1[package]
2name = "migrate"
3version.workspace = true
4edition.workspace = true
5
6[dependencies]
7anyhow = { workspace = true }
8config = { workspace = true }
9models = { workspace = true }
10sea-orm = { workspace = true }
11tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
12tracing = { workspace = true }
13tracing-subscriber = { workspace = true }
No newline at end of file
Deleted app/openapi/src/main.rs app/openapi/src/main.rs +0 −53
@@ -1,53 +0,0 @@
1use std::path::{Path, PathBuf};
2
3use anyhow::{Context, anyhow};
4
5fn generate<F>(generate: F) -> anyhow::Result<String>
6where
7 F: FnOnce() -> anyhow::Result<String> + Send + 'static,
8{
9 std::thread::Builder::new()
10 .stack_size(256 * 1024 * 1024)
11 .spawn(generate)
12 .context("spawn generator thread")?
13 .join()
14 .map_err(|_| anyhow!("openapi generation panicked (stack overflow?)"))?
15}
16
17fn check_file(path: &Path, json: &str) -> anyhow::Result<()> {
18 let existing =
19 std::fs::read_to_string(path).with_context(|| format!("reading {}", path.display()))?;
20 if existing.trim_end() != json.trim_end() {
21 eprintln!(
22 "{} is out of date; run `cargo run -p openapi` to regenerate",
23 path.display()
24 );
25 std::process::exit(1);
26 }
27 Ok(())
28}
29
30fn main() -> anyhow::Result<()> {
31 let web = generate(|| {
32 let doc = webapi::openapi::api_doc();
33 doc.to_pretty_json().context("serializing web openapi doc")
34 })?;
35
36 let crate_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
37 let root = crate_dir
38 .ancestors()
39 .nth(2)
40 .ok_or_else(|| anyhow!("workspace root not found above {}", crate_dir.display()))?;
41 let web_path = root.join("openapi.json");
42
43 let check = std::env::args().nth(1).as_deref() == Some("--check");
44 if check {
45 check_file(&web_path, &web)?;
46 println!("openapi.json is up to date");
47 return Ok(());
48 }
49
50 std::fs::write(&web_path, web).with_context(|| format!("writing {}", web_path.display()))?;
51 println!("wrote {}", web_path.display());
52 Ok(())
53}
Deleted app/openapi/Cargo.toml app/openapi/Cargo.toml +0 −9
@@ -1,9 +0,0 @@
1[package]
2name = "openapi"
3version.workspace = true
4edition.workspace = true
5publish = false
6
7[dependencies]
8webapi = { workspace = true }
9anyhow = { workspace = true }
Added app/src/cli.rs app/src/cli.rs +18 −0 View app/src/cli.rs
@@ -0,0 +1,18 @@
1use clap::{Parser, Subcommand};
2
3#[derive(Parser)]
4#[command(name = "kenapi", version)]
5pub struct Cli {
6 #[command(subcommand)]
7 pub command: Command,
8}
9
10#[derive(Subcommand)]
11pub enum Command {
12 Serve,
13 Migrate,
14 Openapi {
15 #[arg(long)]
16 check: bool,
17 },
18}
Added app/src/main.rs app/src/main.rs +193 −0 View app/src/main.rs
@@ -0,0 +1,193 @@
1#![recursion_limit = "256"]
2
3mod cli;
4
5use std::net::SocketAddr;
6use std::path::PathBuf;
7
8use anyhow::{Context, anyhow};
9use clap::Parser;
10
11use crate::cli::{Cli, Command};
12
13fn main() -> anyhow::Result<()> {
14 match Cli::parse().command {
15 Command::Serve => serve(),
16 Command::Migrate => migrate(),
17 Command::Openapi { check } => openapi(check),
18 }
19}
20
21#[tokio::main]
22async fn serve() -> anyhow::Result<()> {
23 use axum::Router;
24 use config::scoped::{ApihostConfig, ScopedConfig};
25 use tokio::net::TcpListener;
26 use tokio_util::sync::CancellationToken;
27 use tracing::{info, warn};
28 use utoipa_swagger_ui::SwaggerUi;
29 use webapi::{openapi::api_doc, router};
30
31 export::telemetry::init_tracing();
32
33 let cfg = ApihostConfig::load().context("loading configuration")?;
34 if !cfg.apihost.enabled() {
35 info!("apihost is disabled by config; exiting");
36 return Ok(());
37 }
38
39 info!("connecting to database / redis / storage / email");
40 let state = export::AppState::with_config(cfg.clone().into_app_config())
41 .await
42 .context("building AppState")?;
43 info!("dependencies ready");
44
45 if let Err(err) = export::telemetry::init_meter_provider() {
46 warn!(error = %err, "failed to init meter provider; metrics disabled");
47 }
48 export::metrics::spawn_queue_depth_reporter(
49 state.redis.clone(),
50 &[
51 ("push.jobs", export::push::QUEUE_TOPIC.to_string()),
52 ("push.jobs.dlq", export::push::DLQ_TOPIC.to_string()),
53 ],
54 );
55
56 tokio::spawn(chat::worker::run_background_tasks(state.clone()));
57
58 if cfg.rocketmq.enabled {
59 let conn = queue::RocketMqConn::init(&cfg.rocketmq)
60 .await
61 .map_err(|err| anyhow::anyhow!("failed to start the rocketmq producer: {err}"))?;
62 tokio::spawn(queue::relay::run(
63 state.database.primary().clone(),
64 queue::Publisher::new(conn),
65 CancellationToken::new(),
66 ));
67 info!("event outbox relay started");
68 } else {
69 info!("rocketmq is disabled; chat message events are not published");
70 }
71
72 if cfg.push.enabled {
73 for _ in 0..cfg.push.workers.max(1) {
74 let worker = push::PushWorker::new(state.clone(), CancellationToken::new())
75 .map_err(|err| anyhow::anyhow!("failed to start push worker: {err}"))?;
76 tokio::spawn(worker.run());
77 }
78 info!(
79 workers = cfg.push.workers.max(1),
80 "web push workers started"
81 );
82 }
83
84 let api_router: Router = router(state)
85 .await
86 .map_err(|err| anyhow::anyhow!("failed to build router: {err}"))?;
87 let doc = api_doc();
88 let api_router =
89 api_router.merge(SwaggerUi::new("/api/docs").url("/api/docs/openapi.json", doc));
90
91 let bind_addr = cfg.apihost.bind_addr().to_string();
92 let port = cfg.apihost.port();
93 let addr: SocketAddr = format!("{bind_addr}:{port}")
94 .parse()
95 .with_context(|| format!("parsing bind address {bind_addr}:{port}"))?;
96
97 let listener = TcpListener::bind(addr)
98 .await
99 .with_context(|| format!("binding {addr}"))?;
100 info!(%addr, "apihost listening");
101
102 axum::serve(listener, api_router.into_make_service())
103 .with_graceful_shutdown(export::telemetry::shutdown_signal())
104 .await
105 .context("axum server error")?;
106
107 export::telemetry::shutdown_telemetry();
108
109 info!("apihost shut down");
110 Ok(())
111}
112
113#[tokio::main]
114async fn migrate() -> anyhow::Result<()> {
115 use config::scoped::{MigrateConfig, ScopedConfig};
116 use models::migrate::sync_schema;
117 use sea_orm::Database;
118 use tracing::info;
119
120 const DEFAULT_LOG_FILTER: &str = "info,kenapi=debug";
121
122 tracing_subscriber::fmt()
123 .with_env_filter(
124 tracing_subscriber::EnvFilter::try_from_default_env()
125 .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(DEFAULT_LOG_FILTER)),
126 )
127 .init();
128
129 let cfg = MigrateConfig::load().context("loading configuration")?;
130
131 let opts = cfg
132 .database
133 .primary_connect_options()
134 .map_err(|e| anyhow::anyhow!(e))?;
135 let db = Database::connect(opts)
136 .await
137 .context("connecting to postgres")?;
138
139 info!("running schema migration");
140 sync_schema(&db).await.context("running schema sync")?;
141 info!("schema migration completed");
142
143 Ok(())
144}
145
146fn openapi(check: bool) -> anyhow::Result<()> {
147 fn generate<F>(generate: F) -> anyhow::Result<String>
148 where
149 F: FnOnce() -> anyhow::Result<String> + Send + 'static,
150 {
151 std::thread::Builder::new()
152 .stack_size(256 * 1024 * 1024)
153 .spawn(generate)
154 .context("spawn generator thread")?
155 .join()
156 .map_err(|_| anyhow!("openapi generation panicked (stack overflow?)"))?
157 }
158
159 fn check_file(path: &std::path::Path, json: &str) -> anyhow::Result<()> {
160 let existing =
161 std::fs::read_to_string(path).with_context(|| format!("reading {}", path.display()))?;
162 if existing.trim_end() != json.trim_end() {
163 eprintln!(
164 "{} is out of date; run `cargo run -p kenapi -- openapi` to regenerate",
165 path.display()
166 );
167 std::process::exit(1);
168 }
169 Ok(())
170 }
171
172 let web = generate(|| {
173 let doc = webapi::openapi::api_doc();
174 doc.to_pretty_json().context("serializing web openapi doc")
175 })?;
176
177 let crate_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
178 let root = crate_dir
179 .ancestors()
180 .nth(1)
181 .ok_or_else(|| anyhow!("workspace root not found above {}", crate_dir.display()))?;
182 let web_path = root.join("openapi.json");
183
184 if check {
185 check_file(&web_path, &web)?;
186 println!("openapi.json is up to date");
187 return Ok(());
188 }
189
190 std::fs::write(&web_path, web).with_context(|| format!("writing {}", web_path.display()))?;
191 println!("wrote {}", web_path.display());
192 Ok(())
193}
Renamed app/apihost/Cargo.toml → app/Cargo.toml app/Cargo.toml +11 −8 View app/Cargo.toml
@@ -1,23 +1,26 @@
11[package]
2name = "apihost"
2name = "kenapi"
33version.workspace = true
44edition.workspace = true
55publish = false
66
77[dependencies]
8webapi = { workspace = true }
8anyhow = { workspace = true }
99auth = { workspace = true }
10axum = { workspace = true }
1011chat = { workspace = true }
11push = { workspace = true }
12queue = { workspace = true }
12clap = { workspace = true }
13config = { workspace = true }
1314export = { workspace = true }
1415jwt = { workspace = true }
15config = { workspace = true }
16axum = { workspace = true }
16models = { workspace = true }
17push = { workspace = true }
18queue = { workspace = true }
19sea-orm = { workspace = true }
1720tokio = { workspace = true, features = ["signal", "net", "macros", "rt-multi-thread"] }
1821tokio-util = { workspace = true }
1922tracing = { workspace = true }
2023tracing-subscriber = { workspace = true }
21anyhow = { workspace = true }
2224utoipa = { workspace = true }
23utoipa-swagger-ui = { workspace = true }
No newline at end of file
25utoipa-swagger-ui = { workspace = true }
26webapi = { workspace = true }
Modified charts/core/templates/apihost.yaml charts/core/templates/apihost.yaml +1 −1 View charts/core/templates/apihost.yaml
@@ -62,7 +62,7 @@
6262 - name: apihost
6363 image: {{ include "gitdata.image" . }}
6464 imagePullPolicy: {{ .Values.image.pullPolicy }}
65 args: ["apihost"]
65 args: ["serve"]
6666 {{- include "gitdata.envFrom" . | nindent 10 }}
6767 env:
6868 {{- include "gitdata.env" . | nindent 12 }}
Modified charts/core/templates/migrate-job.yaml charts/core/templates/migrate-job.yaml +1 −1 View charts/core/templates/migrate-job.yaml
@@ -27,7 +27,7 @@
2727 - name: migrate
2828 image: {{ include "gitdata.image" . }}
2929 imagePullPolicy: {{ .Values.image.pullPolicy }}
30 command: ["migrate"]
30 command: ["kenapi", "migrate"]
3131 {{- include "gitdata.envFrom" . | nindent 10 }}
3232 env:
3333 {{- include "gitdata.env" . | nindent 12 }}
Modified charts/core/Chart.yaml charts/core/Chart.yaml +1 −1 View charts/core/Chart.yaml
@@ -1,6 +1,6 @@
11apiVersion: v2
22name: gitdata
3description: GitData — Rust workspace (apihost / migrate)
3description: GitData — Rust workspace (kenapi: serve / migrate)
44type: application
55version: 0.1.0
66appVersion: "1.0.0"
Modified deploy/compose.yaml deploy/compose.yaml +1 −1 View deploy/compose.yaml
@@ -14,7 +14,7 @@
1414# 1. 建库 + 迁移
1515# docker exec postgres psql -U postgres -c 'CREATE DATABASE gitdata'
1616# docker run --rm --network host -v "$PWD/config:/app/config:ro" \
17# --entrypoint /usr/local/bin/migrate kenapi:local
17# --entrypoint /usr/local/bin/kenapi kenapi:local migrate
1818# 2. S3 桶(RustFS):gitdata(与 config/local.toml 的 [s3].bucket 一致)
1919# 3. KenOAuth 里要有 client_id = gitdata-web 的公开客户端(见 KenOAuth 配置)
2020#
Modified docs/oauth.md docs/oauth.md +1 −1 View docs/oauth.md
@@ -28,7 +28,7 @@
2828| B4 | ✅ | space/member/ban/group/invite/join-rule/join-request 全部写透 KenOAuth;`apply_space_join_rules` 经 `EnsureBotMember(role_ids)` 投影;权限判定改 `CheckPermissions`(300s Redis 投影) |
2929| B5 | ✅ | `/internal` 服务密钥守卫复核;未使用依赖清理;`openapi.json` + web 客户端重新生成;`openapi --check`、web build/lint 通过;全仓代码无 `session_layer`/`lib/session`/`admin_rpc` 引用 |
3030
31> 当前 `cargo check --workspace`、KenOAuth `cargo check` 与 `cargo run -p openapi -- --check` 均通过;`bun run --cwd web build` 通过。
31> 当前 `cargo check --workspace`、KenOAuth `cargo check` 与 `cargo run -p kenapi -- openapi --check` 均通过;`bun run --cwd web build` 通过。
3232>
3333> 已知待收尾:
3434> - B4.3 的物理删表改为「保留本地表作为投影缓存」:读路径与 `dir` 覆盖仍依赖这些表,事实源已全部在 KenOAuth。
Modified scripts/dev.ps1 scripts/dev.ps1 +7 −9 View scripts/dev.ps1
@@ -26,8 +26,8 @@
2626$DevHostKey = Join-Path $DevDir 'ssh_host_ed25519'
2727
2828$Services = [ordered]@{
29 migrate = @{ Exe = 'migrate'; Port = 0; Health = $false }
30 apihost = @{ Exe = 'apihost'; Port = 4081; Health = $true }
29 migrate = @{ Exe = 'kenapi'; Args = 'migrate'; Port = 0; Health = $false }
30 apihost = @{ Exe = 'kenapi'; Args = 'serve'; Port = 4081; Health = $true }
3131}
3232$Order = @('migrate', 'apihost')
3333$Backends = @('migrate', 'apihost')
@@ -153,10 +153,10 @@
153153function Invoke-Migrate {
154154 Ensure-Dir
155155 Set-ServiceEnv
156 $exe = Join-Path $BinDir 'migrate.exe'
156 $exe = Join-Path $BinDir 'kenapi.exe'
157157 if (-not (Test-Path -LiteralPath $exe)) { throw "缺少 $exe,请先执行 just build" }
158158 Write-Info '[migrate] 同步数据库 schema ...'
159 $proc = Start-Process -FilePath $exe -WorkingDirectory $Root -PassThru -Wait `
159 $proc = Start-Process -FilePath $exe -ArgumentList 'migrate' -WorkingDirectory $Root -PassThru -Wait `
160160 -RedirectStandardOutput (Join-Path $LogDir 'migrate.log') `
161161 -RedirectStandardError (Join-Path $LogDir 'migrate.err.log') `
162162 -WindowStyle Hidden
@@ -203,7 +203,7 @@
203203
204204 $exe = Join-Path $BinDir ($svc.Exe + '.exe')
205205 if (-not (Test-Path -LiteralPath $exe)) { throw "缺少 $exe,请先执行 just build" }
206 $procId = Start-Daemon -Name $Name -Exe $exe -Arguments '' -WorkingDirectory $Root -Out $out -Err $err
206 $procId = Start-Daemon -Name $Name -Exe $exe -Arguments $svc.Args -WorkingDirectory $Root -Out $out -Err $err
207207
208208 Register-ServiceProcess $Name $procId
209209 Write-Info "[$Name] 启动 pid=$procId"
@@ -296,10 +296,8 @@
296296 }
297297 }
298298 Write-Info "== 后端二进制 ($BinDir) =="
299 foreach ($name in $Backends) {
300 $exe = Join-Path $BinDir ($Services[$name].Exe + '.exe')
301 if (Test-Path -LiteralPath $exe) { Write-Info (" {0,-12} OK" -f $name) } else { Write-Warn (" {0,-12} 未构建(just build)" -f $name); $ok = $false }
302 }
299 $exe = Join-Path $BinDir 'kenapi.exe'
300 if (Test-Path -LiteralPath $exe) { Write-Info (" {0,-12} OK" -f 'kenapi') } else { Write-Warn (" {0,-12} 未构建(just build)" -f 'kenapi'); $ok = $false }
303301 if (-not $ok) {
304302 Write-Info ''
305303 Write-Info 'just infra 启动 WSL 侧 PostgreSQL / Redis / RustFS'
Modified scripts/dev.sh scripts/dev.sh +10 −12 View scripts/dev.sh
@@ -252,13 +252,11 @@
252252 done
253253
254254 info "== 后端二进制 ($BIN_DIR) =="
255 for name in $BACKENDS; do
256 if [ -x "$BIN_DIR/$name" ]; then
257 printf ' %-12s OK\n' "$name"
258 else
259 warn " $(printf '%-12s 未构建' "$name")"; ok=0
260 fi
261 done
255 if [ -x "$BIN_DIR/kenapi" ]; then
256 printf ' %-12s OK\n' kenapi
257 else
258 warn " $(printf '%-12s 未构建' kenapi)"; ok=0
259 fi
262260
263261 if [ "$ok" = "0" ]; then
264262 info ''
@@ -376,12 +374,12 @@
376374cmd_migrate() {
377375 ensure_path
378376 service_env || return 1
379 if [ ! -x "$BIN_DIR/migrate" ]; then
380 fail " 缺少 $BIN_DIR/migrate,请先执行 just build"
377 if [ ! -x "$BIN_DIR/kenapi" ]; then
378 fail " 缺少 $BIN_DIR/kenapi,请先执行 just build"
381379 return 1
382380 fi
383381 info '[migrate] 同步数据库 schema ...'
384 if ! "$BIN_DIR/migrate" >"$LOG_DIR/migrate.log" 2>"$LOG_DIR/migrate.err.log"; then
382 if ! "$BIN_DIR/kenapi" migrate >"$LOG_DIR/migrate.log" 2>"$LOG_DIR/migrate.err.log"; then
385383 fail ' [migrate] 失败,见 just logs migrate'
386384 tail -n 15 "$LOG_DIR/migrate.err.log" 2>/dev/null
387385 return 1
@@ -430,9 +428,9 @@
430428
431429 case "$name" in
432430 *)
433 [ -x "$BIN_DIR/$name" ] || { fail " 缺少 $BIN_DIR/$name,请先执行 just build"; return 1; }
431 [ -x "$BIN_DIR/kenapi" ] || { fail " 缺少 $BIN_DIR/kenapi,请先执行 just build"; return 1; }
434432 dir="$ROOT"
435 cmd="$BIN_DIR/$name"
433 cmd="$BIN_DIR/kenapi serve"
436434 ;;
437435 esac
438436
Modified Cargo.lock Cargo.lock +139 −43 View Cargo.lock
@@ -159,40 +159,68 @@
159159]
160160
161161[[package]]
162name = "antidote"
162name = "anstream"
163163version = "1.0.0"
164164source = "registry+https://github.com/rust-lang/crates.io-index"
165checksum = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5"
165checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
166dependencies = [
167 "anstyle",
168 "anstyle-parse",
169 "anstyle-query",
170 "anstyle-wincon",
171 "colorchoice",
172 "is_terminal_polyfill",
173 "utf8parse",
174]
166175
167176[[package]]
168name = "anyhow"
169version = "1.0.104"
177name = "anstyle"
178version = "1.0.14"
170179source = "registry+https://github.com/rust-lang/crates.io-index"
171checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
180checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
172181
173182[[package]]
174name = "apihost"
183name = "anstyle-parse"
175184version = "1.0.0"
185source = "registry+https://github.com/rust-lang/crates.io-index"
186checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
176187dependencies = [
177 "anyhow",
178 "auth",
179 "axum",
180 "chat",
181 "config 1.0.0",
182 "export",
183 "jwt",
184 "push",
185 "queue",
186 "tokio",
187 "tokio-util",
188 "tracing",
189 "tracing-subscriber",
190 "utoipa",
191 "utoipa-swagger-ui",
192 "webapi",
188 "utf8parse",
193189]
194190
195191[[package]]
192name = "anstyle-query"
193version = "1.1.5"
194source = "registry+https://github.com/rust-lang/crates.io-index"
195checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
196dependencies = [
197 "windows-sys 0.61.2",
198]
199
200[[package]]
201name = "anstyle-wincon"
202version = "3.0.11"
203source = "registry+https://github.com/rust-lang/crates.io-index"
204checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
205dependencies = [
206 "anstyle",
207 "once_cell_polyfill",
208 "windows-sys 0.61.2",
209]
210
211[[package]]
212name = "antidote"
213version = "1.0.0"
214source = "registry+https://github.com/rust-lang/crates.io-index"
215checksum = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5"
216
217[[package]]
218name = "anyhow"
219version = "1.0.104"
220source = "registry+https://github.com/rust-lang/crates.io-index"
221checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
222
223[[package]]
196224name = "arbitrary"
197225version = "1.4.2"
198226source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1065,6 +1093,46 @@
10651093]
10661094
10671095[[package]]
1096name = "clap"
1097version = "4.6.7"
1098source = "registry+https://github.com/rust-lang/crates.io-index"
1099checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946"
1100dependencies = [
1101 "clap_builder",
1102 "clap_derive",
1103]
1104
1105[[package]]
1106name = "clap_builder"
1107version = "4.6.7"
1108source = "registry+https://github.com/rust-lang/crates.io-index"
1109checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d"
1110dependencies = [
1111 "anstream",
1112 "anstyle",
1113 "clap_lex",
1114 "strsim",
1115]
1116
1117[[package]]
1118name = "clap_derive"
1119version = "4.6.7"
1120source = "registry+https://github.com/rust-lang/crates.io-index"
1121checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0"
1122dependencies = [
1123 "heck 0.5.0",
1124 "proc-macro2",
1125 "quote",
1126 "syn 3.0.3",
1127]
1128
1129[[package]]
1130name = "clap_lex"
1131version = "1.1.1"
1132source = "registry+https://github.com/rust-lang/crates.io-index"
1133checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486"
1134
1135[[package]]
10681136name = "cmake"
10691137version = "0.1.58"
10701138source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1106,6 +1174,12 @@
11061174checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
11071175
11081176[[package]]
1177name = "colorchoice"
1178version = "1.0.5"
1179source = "registry+https://github.com/rust-lang/crates.io-index"
1180checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
1181
1182[[package]]
11091183name = "combine"
11101184version = "4.6.7"
11111185source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3055,6 +3129,12 @@
30553129checksum = "cf370abdafd54d13e54a620e8c3e1145f28e46cc9d704bc6d94414559df41763"
30563130
30573131[[package]]
3132name = "is_terminal_polyfill"
3133version = "1.70.2"
3134source = "registry+https://github.com/rust-lang/crates.io-index"
3135checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
3136
3137[[package]]
30583138name = "issues"
30593139version = "1.0.0"
30603140dependencies = [
@@ -3284,6 +3364,31 @@
32843364]
32853365
32863366[[package]]
3367name = "kenapi"
3368version = "1.0.0"
3369dependencies = [
3370 "anyhow",
3371 "auth",
3372 "axum",
3373 "chat",
3374 "clap",
3375 "config 1.0.0",
3376 "export",
3377 "jwt",
3378 "models",
3379 "push",
3380 "queue",
3381 "sea-orm",
3382 "tokio",
3383 "tokio-util",
3384 "tracing",
3385 "tracing-subscriber",
3386 "utoipa",
3387 "utoipa-swagger-ui",
3388 "webapi",
3389]
3390
3391[[package]]
32873392name = "kenauth"
32883393version = "1.0.0"
32893394dependencies = [
@@ -3712,19 +3817,6 @@
37123817]
37133818
37143819[[package]]
3715name = "migrate"
3716version = "1.0.0"
3717dependencies = [
3718 "anyhow",
3719 "config 1.0.0",
3720 "models",
3721 "sea-orm",
3722 "tokio",
3723 "tracing",
3724 "tracing-subscriber",
3725]
3726
3727[[package]]
37283820name = "mime"
37293821version = "0.2.6"
37303822source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4189,18 +4281,16 @@
41894281checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
41904282
41914283[[package]]
4284name = "once_cell_polyfill"
4285version = "1.70.2"
4286source = "registry+https://github.com/rust-lang/crates.io-index"
4287checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
4288
4289[[package]]
41924290name = "opaque-debug"
41934291version = "0.3.1"
41944292source = "registry+https://github.com/rust-lang/crates.io-index"
41954293checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
4196
4197[[package]]
4198name = "openapi"
4199version = "1.0.0"
4200dependencies = [
4201 "anyhow",
4202 "webapi",
4203]
42044294
42054295[[package]]
42064296name = "openssl"
@@ -7811,6 +7901,12 @@
78117901checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
78127902
78137903[[package]]
7904name = "utf8parse"
7905version = "0.2.2"
7906source = "registry+https://github.com/rust-lang/crates.io-index"
7907checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
7908
7909[[package]]
78147910name = "utoipa"
78157911version = "5.5.0"
78167912source = "registry+https://github.com/rust-lang/crates.io-index"
Modified Cargo.toml Cargo.toml +2 −1 View Cargo.toml
@@ -1,7 +1,7 @@
11[workspace]
22members = [
33 "lib/*",
4 "app/*"
4 "app"
55]
66
77resolver = "3"
@@ -38,6 +38,7 @@
3838
3939
4040tokio = "1.53.1"
41clap = { version = "4.6.7", features = ["derive"] }
4142serde = "1.0.229"
4243serde_json = "1.0.151"
4344axum = { version = "0.8.9", features = ["ws"] }
Modified Dockerfile Dockerfile +6 −6 View Dockerfile
@@ -1,7 +1,7 @@
11# syntax=docker/dockerfile:1
22
33# ---------------------------------------------------------------------------
4# KenApi — GitDataAI 主业务后端(cargo workspace,二进制 apihost / migrate / openapi)
4# KenApi — GitDataAI 主业务后端(cargo workspace,二进制 kenapi:serve / migrate / openapi)
55#
66# docker build -t kenapi:local .
77# docker run --rm -p 3000:3000 \
@@ -57,8 +57,8 @@
5757 --mount=type=cache,id=kenapi-target,target=/build/target \
5858 cargo build --release --locked --workspace --bins \
5959 && mkdir -p /out \
60 && cp target/release/apihost target/release/migrate target/release/openapi /out/ \
61 && strip /out/apihost /out/migrate /out/openapi
60 && cp target/release/kenapi /out/ \
61 && strip /out/kenapi
6262
6363# ---------------------------------------------------------------------------
6464# runtime
@@ -86,10 +86,10 @@
8686
8787# 只带默认配置;config/local.toml 与各类密钥由部署方挂载或注入。
8888COPY --chown=gitdata:gitdata config/default.toml config/default.toml
89COPY --from=builder /out/apihost /out/migrate /out/openapi /usr/local/bin/
89COPY --from=builder /out/kenapi /usr/local/bin/
9090
9191LABEL org.opencontainers.image.title="KenApi" \
92 org.opencontainers.image.description="GitDataAI business API (apihost / migrate / openapi)" \
92 org.opencontainers.image.description="GitDataAI business API (kenapi: serve / migrate / openapi)" \
9393 org.opencontainers.image.version="1.0.0"
9494
9595ENV RUST_BACKTRACE=0
@@ -103,4 +103,4 @@
103103 CMD curl -fsS "http://127.0.0.1:${KENAPI_HEALTH_PORT:-7051}/healthz" >/dev/null || exit 1
104104
105105ENTRYPOINT ["/usr/bin/tini", "--"]
106CMD ["apihost"]
106CMD ["kenapi", "serve"]
Modified justfile justfile +2 −2 View justfile
@@ -96,11 +96,11 @@
9696
9797# 重新生成 openapi.json
9898openapi:
99 @cargo run -p openapi
99 @cargo run -p kenapi -- openapi
100100
101101# 校验 openapi 是否与源码一致
102102openapi-check:
103 @cargo run -p openapi -- --check
103 @cargo run -p kenapi -- openapi --check
104104
105105# cargo fmt --all
106106fmt: