Skip to content
This repository was archived by the owner on Sep 15, 2025. It is now read-only.

Commit 36f976b

Browse files
committed
wip
1 parent 95849a1 commit 36f976b

File tree

16 files changed

+623
-189
lines changed

16 files changed

+623
-189
lines changed

Cargo.lock

Lines changed: 261 additions & 177 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ clap = { version = "4.5.22", features = ["derive", "env"] }
1111
futures = "0.3.31"
1212
lazy_static = "1.5.0"
1313
log = "0.4.22"
14-
meilisearch-sdk = "0.28.0"
14+
meilisearch-sdk = "0.29.1"
1515
nanoid = "0.4.0"
16+
once_cell = "1.21.3"
1617
openssl = "0.10.70"
1718
rand = { version = "0.8.5", features = ["serde"] }
1819
redis = { version = "0.27.6", features = ["tokio-rustls-comp", "cluster", "json", "r2d2"] }
@@ -21,6 +22,7 @@ sea-orm = { version = "1.1.6", features = ["sqlx-postgres", "runtime-tokio-rustl
2122
serde = { version = "1.0.215", features = ["derive", "serde_derive"] }
2223
serde_json = "1.0.133"
2324
serde_yml = "0.0.12"
25+
syn = "2.0.106"
2426
syntect = "5.2.0"
2527
thiserror = "2.0.11"
2628
tokio = { version = "1.43.0", features = ["full"] }

TODO.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# For 0.3.0
2+
- [ ] `.config/default.yml`の読み込み方の改善(バージョン・ソフトウェアによる差異の考慮、~~staticなload~~)
3+
- [ ] 各CLI機能の共通部分をservices以下に切り出し
4+
- [ ] cfgによるバージョンとソフトウェアによる挙動の違いを考慮
5+
- [ ] docs整備
6+
- [ ] notectl自体のconfigを設定できる機能(あるいは環境変数?)
7+
- [ ] testを書く
8+
- [ ] cfgを利用した条件付きコンパイル
9+
- [ ] entitiesのバージョン管理のマクロ
10+
- [ ] パスワード強制変更コマンド (要sudo/doas) <-docker環境下でどうする?
11+
- [ ] 2025.7.0以前でのCaptchaリセット機能(2025.8.0でも使えるようにするけどその場合非推奨を出す)

src/cli/command.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@ use crate::cli::note::NoteCommand;
77
use crate::cli::remote::RemoteCommand;
88
use crate::cli::search::SearchCommand;
99
use crate::cli::user::UserCommand;
10+
use crate::configs::server::ServerConfig;
1011

12+
// TODO: globalな引数にconfig_pathを追加する
1113
#[derive(Debug, Parser)]
1214
#[command(name = "notectl", about = "A CLI tool for managing misskey", color = ColorChoice::Always, styles = super::style::style())]
1315
pub struct Cli {
1416
#[clap(subcommand)]
1517
pub cmd: Commands,
18+
#[arg(short = 'c', long = "config", global = true)]
19+
pub config_path: String,
1620
}
1721

1822
#[derive(Debug, Subcommand)]
@@ -35,6 +39,7 @@ pub enum Commands {
3539

3640
pub async fn exec() -> Result<(), Box<dyn std::error::Error>> {
3741
let args = Cli::parse();
42+
ServerConfig::init(&args.config_path)?;
3843
match args.cmd {
3944
Commands::Webpush(cmd) => {
4045
if let Err(e) = cmd.exec() {

src/config.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
use serde::{Deserialize, Serialize};
2+
use std::sync::RwLock;
23
use std::{fs, path::Path};
34
use syntect::easy::HighlightLines;
45
use syntect::highlighting::{Style, ThemeSet};
56
use syntect::parsing::SyntaxSet;
67
use syntect::util::{as_24_bit_terminal_escaped, LinesWithEndings};
78

9+
static CONFIG: once_cell::sync::OnceCell<RwLock<MisskeyConfig>> = once_cell::sync::OnceCell::new();
10+
811
#[derive(Debug, Serialize, Deserialize)]
912
#[serde(rename_all = "camelCase")]
1013
pub struct MisskeyConfig {

src/configs/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pub mod notectl;
2+
pub mod server;
3+
4+
// TODO?: 両方のinitializeを同時に行える関数を追加する?
5+
// 要検討

src/configs/notectl.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// TODO
2+
3+
// ログ出力レベルの変更
4+
// SeaORMのmin_connections, max_connectionsの変更
5+
// SeaORMのtimeoutの設定(あれば)
6+
// DB接続のリトライ回数・間隔設定
7+
// DB接続のSSL設定
8+
// Redis周りも将来的にやる

src/configs/server.rs

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
use serde::{Deserialize, Serialize};
2+
use std::collections::HashMap;
3+
use std::sync::RwLock;
4+
use std::{fs, path::Path};
5+
use thiserror::Error;
6+
use clap::ValueEnum;
7+
8+
static CONFIG: once_cell::sync::OnceCell<RwLock<MisskeyConfig>> = once_cell::sync::OnceCell::new();
9+
10+
#[derive(Debug, Serialize, Deserialize)]
11+
#[serde(rename_all = "camelCase")]
12+
pub struct MisskeyConfig {
13+
pub publish_tarball_instead_of_provide_repository_url: Option<bool>,
14+
pub setup_password: Option<String>,
15+
pub url: String,
16+
pub port: u64,
17+
pub db: DbConfig,
18+
pub db_replications: bool,
19+
pub db_slaves: Option<Vec<DbConfig>>,
20+
pub redis: RedisConfig,
21+
pub redis_for_pubsub: Option<RedisConfig>,
22+
pub redis_for_job_queue: Option<RedisConfig>,
23+
pub redis_for_timelines: Option<RedisConfig>,
24+
pub redis_for_reactions: Option<RedisConfig>,
25+
#[serde(rename = "fulltextSearch")]
26+
pub full_text_search: Option<FullTextSearch>,
27+
pub meilisearch: Option<MeilisearchConfig>,
28+
pub id: IdMethod,
29+
pub serde_for_backend: Option<SentryForBackendConfig>,
30+
pub serde_for_frontend: Option<SentryForFrontendConfig>,
31+
pub disable_hsts: Option<bool>,
32+
pub cluster_limit: Option<u64>,
33+
pub deliver_job_concurrency: Option<u64>,
34+
pub inbox_job_concurrency: Option<u64>,
35+
pub relationship_job_concurrency: Option<u64>,
36+
pub deliver_job_per_sec: Option<u64>,
37+
pub inbox_job_per_sec: Option<u64>,
38+
pub relationship_job_per_sec: Option<u64>,
39+
pub deliver_job_max_attempts: Option<u64>,
40+
pub inbox_job_max_attempts: Option<u64>,
41+
pub outgoing_address: Option<String>,
42+
pub outgoing_address_family: Option<OutgoingAddressFamily>,
43+
pub proxy: Option<String>,
44+
pub proxy_bypass_hosts: Option<Vec<String>>,
45+
pub proxy_smtp: Option<String>,
46+
pub media_proxy: Option<String>,
47+
pub proxy_remote_files: bool,
48+
pub video_thumbnail_generator: Option<String>,
49+
pub sign_to_activity_pub_get: bool,
50+
pub allowed_private_networks: Option<Vec<String>>,
51+
pub max_file_size: Option<u64>,
52+
pub pid_file: Option<String>,
53+
pub per_channel_max_note_cache_count: Option<u64>,
54+
pub per_user_notifications_max_count: Option<u64>,
55+
pub deactivate_antenna_threshold: Option<u64>,
56+
}
57+
58+
#[derive(Debug, Serialize, Deserialize)]
59+
pub struct DbConfig {
60+
pub host: String,
61+
pub port: u64,
62+
pub db: String,
63+
pub user: String,
64+
pub pass: String,
65+
pub disable_cache: Option<bool>,
66+
pub extra: Option<DbExtraConfig>,
67+
}
68+
69+
// TODO: pgのextraオプションへの追加対応 https://github.com/misskey-dev/misskey/issues/15108
70+
#[derive(Debug, Serialize, Deserialize)]
71+
pub struct DbExtraConfig {
72+
pub ssl: bool,
73+
pub statement_timeout: Option<u64>,
74+
pub query_timeout: Option<u64>,
75+
pub lock_timeout: Option<u64>,
76+
#[serde(flatten)]
77+
#[serde(skip_serializing)]
78+
_ignored: HashMap<String, serde_yml::Value>,
79+
}
80+
81+
#[derive(Debug, Serialize, Deserialize)]
82+
pub struct RedisConfig {
83+
pub host: String,
84+
pub port: u64,
85+
pub family: Option<RedisFamily>,
86+
pub pass: Option<String>,
87+
pub prefix: Option<String>,
88+
pub db: Option<u64>,
89+
}
90+
91+
#[derive(Debug, Serialize, Deserialize)]
92+
pub enum RedisFamily {
93+
Both = 0,
94+
IPv4 = 4,
95+
IPv6 = 6,
96+
}
97+
98+
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
99+
pub struct FullTextSearch {
100+
pub provider: FullTextSearchProvider,
101+
}
102+
103+
// TODO: サメがtsvectorを使う場合の設定も追加
104+
#[derive(Debug, Serialize, Deserialize, Clone, Copy, ValueEnum)]
105+
#[serde(rename_all = "camelCase")]
106+
pub enum FullTextSearchProvider {
107+
#[serde(rename = "sqlLike")]
108+
SqlLike,
109+
#[serde(rename = "sqlPgroonga")]
110+
SqlPgroonga,
111+
#[serde(rename = "meilisearch")]
112+
Meilisearch,
113+
}
114+
115+
#[derive(Debug, Serialize, Deserialize, Clone)]
116+
#[serde(rename_all = "camelCase")]
117+
pub struct MeilisearchConfig {
118+
pub host: String,
119+
pub port: u64,
120+
pub api_key: String,
121+
pub ssl: bool,
122+
pub index: String,
123+
pub scope: Option<MeilisearchScope>,
124+
}
125+
126+
#[derive(Debug, Serialize, Deserialize, Clone)]
127+
#[serde(rename_all = "lowercase")]
128+
pub enum MeilisearchScope {
129+
Local,
130+
Global,
131+
Custom(Vec<String>),
132+
}
133+
134+
#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, ValueEnum)]
135+
#[serde(rename_all = "lowercase")]
136+
pub enum IdMethod {
137+
Aid,
138+
#[default]
139+
Aidx,
140+
Meid,
141+
Ulid,
142+
ObjectId,
143+
}
144+
145+
#[derive(Debug, Serialize, Deserialize)]
146+
#[serde(rename_all = "camelCase")]
147+
pub struct SentryForBackendConfig {
148+
pub enable_node_profiling: bool,
149+
pub options: Option<SentryOptions>,
150+
}
151+
152+
#[derive(Debug, Serialize, Deserialize)]
153+
#[serde(rename_all = "camelCase")]
154+
pub struct SentryForFrontendConfig {
155+
pub options: Option<SentryOptions>,
156+
}
157+
158+
#[derive(Debug, Serialize, Deserialize)]
159+
pub struct SentryOptions {
160+
pub dsn: String,
161+
}
162+
163+
#[derive(Debug, Serialize, Deserialize)]
164+
#[serde(rename_all = "lowercase")]
165+
pub enum OutgoingAddressFamily {
166+
IPv4,
167+
IPv6,
168+
Dual,
169+
}
170+
171+
#[derive(Debug, Error)]
172+
pub enum ConfigError {
173+
#[error("Config not initialized")]
174+
ConfigNotInitialized,
175+
176+
#[error("File not found: {path}")]
177+
ConfigFileNotFound { path: String },
178+
179+
#[error("Failed to read config file: {0}")]
180+
ConfigFileReadError(#[from] std::io::Error),
181+
182+
#[error("Failed to parse config file: {0}")]
183+
ConfigFileParseError(#[from] serde_yml::Error),
184+
185+
#[error("Validation error: {0}")]
186+
ConfigValidationError(String),
187+
188+
#[error("Initialization error")]
189+
ConfigInitializationError,
190+
}
191+
192+
pub struct ServerConfig;
193+
194+
impl ServerConfig {
195+
pub fn init(config_path: &str) -> Result<(), ConfigError> {
196+
let config = Self::load_from_file(config_path)?;
197+
198+
CONFIG
199+
.set(RwLock::new(config))
200+
.map_err(|_| ConfigError::ConfigInitializationError);
201+
202+
Ok(())
203+
}
204+
205+
fn load_from_file(config_path: &str) -> Result<MisskeyConfig, ConfigError> {
206+
let path = Path::new(config_path);
207+
208+
if !path.exists() {
209+
return Err(ConfigError::ConfigFileNotFound {
210+
path: config_path.to_string(),
211+
});
212+
}
213+
214+
let config_content = match fs::read_to_string(path) {
215+
Ok(content) => content,
216+
Err(e) => return Err(ConfigError::ConfigFileReadError(e)),
217+
};
218+
let config: MisskeyConfig = match serde_yml::from_str(&config_content) {
219+
Ok(cfg) => cfg,
220+
Err(e) => return Err(ConfigError::ConfigFileParseError(e)),
221+
};
222+
223+
Ok(config)
224+
}
225+
226+
pub fn get() -> Result<std::sync::RwLockReadGuard<'static, MisskeyConfig>, ConfigError> {
227+
let config = CONFIG.get().ok_or(ConfigError::ConfigNotInitialized)?;
228+
config.read().map_err(|_| ConfigError::ConfigNotInitialized)
229+
}
230+
231+
pub fn get_id_method() -> Result<IdMethod, ConfigError> {
232+
Self::get().map(|cfg| cfg.id)
233+
}
234+
235+
pub fn get_search_provider() -> Result<Option<FullTextSearchProvider>, ConfigError> {
236+
Self::get().map(|cfg| cfg.full_text_search.map(|provider| provider.provider))
237+
}
238+
239+
pub fn get_meilisearch_config() -> Result<Option<MeilisearchConfig>, ConfigError> {
240+
Self::get().map(|cfg| cfg.meilisearch.clone())
241+
}
242+
}

src/main.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
mod cli;
2-
mod config;
2+
mod config; // TODO: 消す
3+
mod configs; // TODO: rename
34
mod consts;
45
mod db;
56
mod entities;
7+
mod services;
68
mod util;
79

810
#[tokio::main]

src/services/id.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use crate::util::id::*;
2+
use thiserror::Error;
3+
use crate::configs::server::{ServerConfig, IdMethod};
4+
5+
#[derive(Error, Debug)]
6+
pub enum IdServiceError {
7+
#[error("Invalid ID format")]
8+
InvalidIdFormat,
9+
10+
#[error("ID generation error. {reason} ")]
11+
IdGenerationError { reason: String },
12+
13+
#[error("Unsafe ID detected")]
14+
UnsafeIdError,
15+
16+
#[error("Parsing error: {0}")]
17+
ParseError(#[from] std::num::ParseIntError),
18+
}
19+
20+
pub struct IdService {
21+
pub method: IdMethod,
22+
}
23+
24+
impl IdService {
25+
pub fn new(method: IdMethod) -> Self {
26+
Self { method }
27+
}
28+
29+
pub fn gen(&self, time: u64) -> Result<String, IdServiceError> {
30+
let result = match self.method {
31+
IdMethod::Aid => aid::gen_aid(time),
32+
IdMethod::Aidx => aidx::gen_aidx(time),
33+
IdMethod::Meid => meid::gen_meid(time),
34+
IdMethod::ObjectId => objectid::gen_object_id(time),
35+
IdMethod::Ulid => ulid::gen_ulid(time),
36+
};
37+
38+
result.map_err(|reason| IdServiceError::IdGenerationError {
39+
reason: reason.to_string(),
40+
})
41+
}
42+
43+
pub fn parse(&self, id: &str) -> Result<std::time::SystemTime, IdServiceError> {
44+
let result = match self.method {
45+
IdMethod::Aid => aid::parse(id),
46+
IdMethod::Aidx => aidx::parse(id),
47+
IdMethod::Meid => meid::parse(id),
48+
IdMethod::ObjectId => objectid::parse(id),
49+
IdMethod::Ulid => ulid::parse(id),
50+
};
51+
52+
result.map_err(|e| IdServiceError::ParseError(e))
53+
}
54+
55+
// TODO: validation
56+
}

0 commit comments

Comments
 (0)