From acef57c63b2f02c9145556be4e287d5a9c3fcca3 Mon Sep 17 00:00:00 2001 From: Priec Date: Wed, 2 Sep 2026 14:51:00 +0200 Subject: [PATCH] search health --- client-gui2 | 2 +- common/proto/backup.proto | 2 +- common/proto/search.proto | 25 +++ common/src/proto/descriptor.bin | Bin 230348 -> 231876 bytes common/src/proto/komp_ac.backup.rs | 2 - common/src/proto/komp_ac.search.rs | 333 +++++++++++++++++++++++++++++ packaging/flatpak/README.md | 11 +- packaging/flatpak/build.sh | 2 +- server | 2 +- 9 files changed, 371 insertions(+), 8 deletions(-) diff --git a/client-gui2 b/client-gui2 index bee662d1..f4710c25 160000 --- a/client-gui2 +++ b/client-gui2 @@ -1 +1 @@ -Subproject commit bee662d1498985ab6ddaab4d5f51c4b8bda56818 +Subproject commit f4710c25be8925ce642ae4967abe63e4d67199b2 diff --git a/common/proto/backup.proto b/common/proto/backup.proto index c27f2f3e..d5408441 100644 --- a/common/proto/backup.proto +++ b/common/proto/backup.proto @@ -104,12 +104,12 @@ message BackupOperationResponse { } message BackupInfoResponse { + reserved 6, 22, 23, 24; bool success = 1; string output = 2; string server_version = 3; string database_name = 4; bool database_healthy = 5; - bool search_healthy = 6; bool ecb_healthy = 7; string ecb_status = 8; string dump_directory = 9; diff --git a/common/proto/search.proto b/common/proto/search.proto index 8e4f5448..bc11e840 100644 --- a/common/proto/search.proto +++ b/common/proto/search.proto @@ -2,11 +2,36 @@ syntax = "proto3"; package komp_ac.search; +import "common.proto"; + service Searcher { rpc Search(SearchRequest) returns (SearchResponse); rpc Count(SearchRequest) returns (SearchCountResponse); } +service SearchHealth { + rpc GetHealth(komp_ac.common.Empty) returns (SearchHealthResponse); +} + +message SearchIndexError { + int64 job_id = 1; + string profile_name = 2; + string table_name = 3; + int64 row_id = 4; + string operation = 5; + int32 attempts = 6; + string last_error = 7; +} + +message SearchHealthResponse { + bool enabled = 1; + bool ready = 2; + bool healthy = 3; + uint64 pending_jobs = 4; + uint64 retrying_jobs = 5; + repeated SearchIndexError errors = 6; +} + message SearchCountResponse { uint64 count = 1; } diff --git a/common/src/proto/descriptor.bin b/common/src/proto/descriptor.bin index 124ca415120836142f44ccc0c042ec8bf28a5dba..40435b67d20932c396dc9ad9cb657a1afe6903f6 100644 GIT binary patch delta 4988 zcmY*dNo*U*6=hc!yV*^OB1;rSk(5M9*5XYbSYD(64!mT>mhEwD+Kwf_jx5{sc#%k% zq!)SXnOuAbl9`DQJm)&dVFn1GV=e)52?AtJ33AIV$tl-41bM&ecS*h&e*ORZd(|)M z&+oE-e4qXN_l|d4d~;?yb=z6;vVZeKb$adYHznR*eX*s>VC=6X02FiUThQo9y@&5!sIumBX zBmw6GBn&c&B+BMk(fJUOB4kF9fGV(}AVrHJ!9t_yLRKuXrc0VTh!laeqCk+3#eh*1 zi6kK}MxZ_RvoV6n$9csxh}H#1A{@}7b&+CC%3wX{j}dt5YJhq0y&Xqjx|86wY<3d7mLI#5ptY-wXy z47Bd}B-29WKDD$Ew5OI9Ix2FXT3!g=Q_D*$_nFwsCez?Ov)QHL?pZJy%RqZJJ0(Vv#bxi7naut-ix3X%fNdvImNWBa<^;` zvY>5QS{Agepvbf=Xj{YaVD>BbrJzV69CFbQ-b>5t2k)irT|am)%T;!lQ|>D<%O-Q6 zy|Ts3f%eLe`L^}w>G%}(B1~|6b-mY=%{+@P#wF zSkBySZEUofbNc(lKb$akwXxQ|`(UYgukqsJcDvP9gQ54Rb!YYAy^>RNX5D%cXzBf{ z%TvA0{cbjL@iC|e-L*UGjZ14A4bpG7o|C>b`anzfR5c7*n~nBb_hGA9N*a>=pvqxt zt=nyEY<4>(55i>KVl@(m>ua6vYJA70c)wc0q_6vzskyZnAvPJ?>%gOwu(GEItN)#^SAT( zq$b0@cB9+gvO1KFtY_knT4^Elbi9;~JDWaS>z#V%6xB{$>nz`X_=QvUMh-8BncYdK zb763{(KQ+M{<`jzoITn^>BA(?|Gg4=N;-+#yYdFwI z?K!R;I!dM!ea0bensln5nGQM3h>tY|0HTbimyibphnbw7RzfhBIdq21NyOiu7}Dk> zar#qcGgADKo8#38gz3)-pR7xhPA+9uBM|K6cs1$-@Md)k46=(DK>?ZCkSG$F#PCqX-~X!D>QShVhF z2NrD_v;&JsgSvpG4kv1+3sj`xl({H%Q_I{00GKQD%>@L-GQUr#sSzM&?NG%kBCkUP z0IyDeR%#aT0(KmdEK zBGL8=C?bE7kr0|RR<4o<5+9iysSOACaJv-KL4LmiAjgB8BM=l1^79o4IUeNat4prt zxHJHu%~_h`VE*Q~HV}e0$MfM*Oo#X|0iZ*NL|V5G2sRI8%wYlo?GO$VMK6^+ocOVc zo~Ge&YEbWGNzu>q6$5}U<|z#7KOi*cdE9_tZ=T0ZlB**E3rH(En42RsHwMAVk+eB5 zP$W%kRF=WWk+B*(M=nSCfJ=Q*9pxHCfZ!eF6AlF4QNE}ofn#EdO}cP?%sO{bkz+iw zP=t|VRr8PmBHrX4b`IKcex^yP5Kx?fjF7@{n*vqrxH6A;sKUx|emP4zKMQ6eQ7}3` z3)TwVbPLu99iIiPc~b{EJ_}}A!9&c8wwNA_EG}9r9tqRt$p}qYSsaS1N%!6&s`;&h z`%(%#{!jG2|E2Rgl+Oq41dk;UXeS2E*#!daggLv2N}f#o#GEy1)ybs(DUsCIIIwB*V;w%JA?p47dd|*=s!$V455kGhJd=&v9 zyb|_>6QM^!s$9MI)vujuq!CJ2mqC~feItc-9aM`^hB`J`AG0C8DhpOj*VWZsbV}@M z*uNtO{VIwd(~-b6SvBGa!O*F>CPS-F`Cdy8?O^b);fTe2!0WPX#k3T!@1oNwzn&i4 zk&`}dsw*5l4%7{~zkIrOrq#UP`{qmMv@Qaj`5SU4XoYuZ|vP? zqk{UfJYdCi_?LH?qvOB4%N$Mea$!d6YyWfyEbrTI?O`le~UO&|Ps87ggK}{H5TQELt)Bso<7O?O@Pv1^FEe`mKUlHXaS(D&O}2b4YNN k?>RuEznV7h0U+2}?Kgkp0D_g(cRzgXZ2$1J`2N5D2l!*L{Qv*} delta 3602 zcmYk8NpD<55Xa~B>-T0}k7seuVtZ!n8QbHXI4I6S?8vra<0#zV3nV5=u;OLBCw7(> z!6AqXLM$V$h$9D(kSuN-H~}ZV1P4y!z?pw_*K7MW|Ec~}S5Sr&! zFMhp!mbvfU4fB7emA<|HVDo7A?#`o5ckd@}wEOWR@AKxE${##j-+!=iZ{y+m*5SrU zf7tRSGauyazh}`$p7PIT9((^t@ooF8zvFdtaaKvGL`wUQMM?FjfJ8tlOq2(e&L_%; z>eb3(Fl~x5n>=8fB9kbxMTJBG+tek-g!Z>Y!HLjZHA7wg22Dr-3~eVazKvxaa94I}Pr+%dVvT7h;wdmcYGm z+m(R5NGG9`fW4Tx#M_m%|58jdRtEOcC07RaGNM~_l^RYN+{=ln55wL4&S7Ux{W7e2 zY|olC|LFW?XM6YF`h!{SWZ!?{)k5{&e`OVF>80*;M?Uke$4ZGXrKL=zJ*AW%rm&05 z6e*lAMJ+B6*C-qkFa=>xqR^0noO$VFLo~tI9Aj7*ikrO9BNb*71uX0tNK_0iY~R4v zLL!VPl{H~V+AGS~GdE!m0^=@k zZ%`0h4|&@|$cFgxb#rN;fE8?8B4LziM5M5=VEYghMA0oItSFL&#)gHIqNimk)n`*A zccnPfZ%*E$QkQuwKp@95=Lm(BWgZJqC|Q}uf=63bxHJ&JDh{I!DqI@afK_-1c@$Gs zpcTg$UQlSiRbh^Z6u4?nk&cKIxN50v!WUW&q{eLcv}zzzG0_W(zQ%1I0%6oR3@9wD zMYioj!Cj5pK6Z+=Ul+K!*a`|mr!G{Y;H94JO%%M;wQa9h2`}|#i#JCy4ITo*OvZ-W zoT6;-5P%I>gNJ|-gRvOn4SZ}q=r;FJk-@ATn^*}igAM!8q2Oh3w9T6X8{#%5gfuG2 zfX3#K%K(S(4{1B&uo6y&YW0}CU}ck=8Zv;FrVE9JXw!M2A==b-;$bDcH0^Cg{Sb4@ z6*GjBmU9x4Eo(cN2jbwSl?UlXj|*fN4^gX-5hNH#uy^2oy|HcG{636^kYv z4oR$>8ZmPb3S!!sh?}-ok4$7695T1n*YZ;8ZF^hk1TSUmx0Fw}b(WKYKz(QV!a!l+ zEWfoF5;@NDWAkaN%Y0!VP@2oamK2I?efEk#0lSPVLVXC2=1S_iEe(0N5}1#kz&x5c zJEuv&G{^ZvA&rS#D?-x2TnFhk}e)qL^+Z2J` z{GvQhnqK{4uH@WMeHY8)=NfJor>0$0SiU6Ra%Ot`OBdYH^Iy8)jvByHdBUvUphKV@ zurxj6{NY+I%VFn_`oQu9fAn#^e8C^}g5@$-74E1REaN`C@;Gg@gO%`~d+m4#T3OZ6!b9`y9%BnNdxAv;coNM5#QSZ41zFM|ZMbd3supj$S@Ur&$P1ien(-nXH`#;(H`F{Wa diff --git a/common/src/proto/komp_ac.backup.rs b/common/src/proto/komp_ac.backup.rs index 0c3df534..77f2b1e9 100644 --- a/common/src/proto/komp_ac.backup.rs +++ b/common/src/proto/komp_ac.backup.rs @@ -111,8 +111,6 @@ pub struct BackupInfoResponse { pub database_name: ::prost::alloc::string::String, #[prost(bool, tag = "5")] pub database_healthy: bool, - #[prost(bool, tag = "6")] - pub search_healthy: bool, #[prost(bool, tag = "7")] pub ecb_healthy: bool, #[prost(string, tag = "8")] diff --git a/common/src/proto/komp_ac.search.rs b/common/src/proto/komp_ac.search.rs index f853e849..3ef3324d 100644 --- a/common/src/proto/komp_ac.search.rs +++ b/common/src/proto/komp_ac.search.rs @@ -1,5 +1,39 @@ // This file is @generated by prost-build. #[derive(serde::Serialize, serde::Deserialize)] +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct SearchIndexError { + #[prost(int64, tag = "1")] + pub job_id: i64, + #[prost(string, tag = "2")] + pub profile_name: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub table_name: ::prost::alloc::string::String, + #[prost(int64, tag = "4")] + pub row_id: i64, + #[prost(string, tag = "5")] + pub operation: ::prost::alloc::string::String, + #[prost(int32, tag = "6")] + pub attempts: i32, + #[prost(string, tag = "7")] + pub last_error: ::prost::alloc::string::String, +} +#[derive(serde::Serialize, serde::Deserialize)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SearchHealthResponse { + #[prost(bool, tag = "1")] + pub enabled: bool, + #[prost(bool, tag = "2")] + pub ready: bool, + #[prost(bool, tag = "3")] + pub healthy: bool, + #[prost(uint64, tag = "4")] + pub pending_jobs: u64, + #[prost(uint64, tag = "5")] + pub retrying_jobs: u64, + #[prost(message, repeated, tag = "6")] + pub errors: ::prost::alloc::vec::Vec, +} +#[derive(serde::Serialize, serde::Deserialize)] #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] pub struct SearchCountResponse { #[prost(uint64, tag = "1")] @@ -538,3 +572,302 @@ pub mod searcher_server { const NAME: &'static str = SERVICE_NAME; } } +/// Generated client implementations. +pub mod search_health_client { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + use tonic::codegen::http::Uri; + #[derive(Debug, Clone)] + pub struct SearchHealthClient { + inner: tonic::client::Grpc, + } + impl SearchHealthClient { + /// Attempt to create a new client by connecting to a given endpoint. + pub async fn connect(dst: D) -> Result + where + D: TryInto, + D::Error: Into, + { + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; + Ok(Self::new(conn)) + } + } + impl SearchHealthClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Body + std::marker::Send + 'static, + ::Error: Into + std::marker::Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> SearchHealthClient> + where + F: tonic::service::Interceptor, + T::ResponseBody: Default, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + std::marker::Send + std::marker::Sync, + { + SearchHealthClient::new(InterceptedService::new(inner, interceptor)) + } + /// Compress requests with the given encoding. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); + self + } + /// Enable decompressing responses. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_decoding_message_size(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_encoding_message_size(limit); + self + } + pub async fn get_health( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/komp_ac.search.SearchHealth/GetHealth", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("komp_ac.search.SearchHealth", "GetHealth")); + self.inner.unary(req, path, codec).await + } + } +} +/// Generated server implementations. +pub mod search_health_server { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + /// Generated trait containing gRPC methods that should be implemented for use with SearchHealthServer. + #[async_trait] + pub trait SearchHealth: std::marker::Send + std::marker::Sync + 'static { + async fn get_health( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + } + #[derive(Debug)] + pub struct SearchHealthServer { + inner: Arc, + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, + max_decoding_message_size: Option, + max_encoding_message_size: Option, + } + impl SearchHealthServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + max_decoding_message_size: None, + max_encoding_message_size: None, + } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.max_decoding_message_size = Some(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.max_encoding_message_size = Some(limit); + self + } + } + impl tonic::codegen::Service> for SearchHealthServer + where + T: SearchHealth, + B: Body + std::marker::Send + 'static, + B::Error: Into + std::marker::Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + match req.uri().path() { + "/komp_ac.search.SearchHealth/GetHealth" => { + #[allow(non_camel_case_types)] + struct GetHealthSvc(pub Arc); + impl< + T: SearchHealth, + > tonic::server::UnaryService + for GetHealthSvc { + type Response = super::SearchHealthResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_health(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetHealthSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + _ => { + Box::pin(async move { + let mut response = http::Response::new( + tonic::body::Body::default(), + ); + let headers = response.headers_mut(); + headers + .insert( + tonic::Status::GRPC_STATUS, + (tonic::Code::Unimplemented as i32).into(), + ); + headers + .insert( + http::header::CONTENT_TYPE, + tonic::metadata::GRPC_CONTENT_TYPE, + ); + Ok(response) + }) + } + } + } + } + impl Clone for SearchHealthServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + max_decoding_message_size: self.max_decoding_message_size, + max_encoding_message_size: self.max_encoding_message_size, + } + } + } + /// Generated gRPC service name + pub const SERVICE_NAME: &str = "komp_ac.search.SearchHealth"; + impl tonic::server::NamedService for SearchHealthServer { + const NAME: &'static str = SERVICE_NAME; + } +} diff --git a/packaging/flatpak/README.md b/packaging/flatpak/README.md index 2cc87270..6d2315e8 100644 --- a/packaging/flatpak/README.md +++ b/packaging/flatpak/README.md @@ -12,13 +12,20 @@ binary are downloaded by `flatpak-builder` as checksum-pinned sources. ## Build -Install `flatpak` and `flatpak-builder` through the host package manager, then -run from the repository root: +Install `flatpak`, `flatpak-builder`, and `appstreamcli` through the host package +manager, then run from the repository root: ```sh packaging/flatpak/build.sh ``` +On NixOS, all three tools can be provided without changing the system +configuration: + +```sh +nix shell nixpkgs#flatpak nixpkgs#flatpak-builder nixpkgs#appstream +``` + The script configures the per-user Flathub remote when necessary, installs the GNOME SDK and SDK extensions through `flatpak-builder`, retains its reusable cache below `${XDG_CACHE_HOME:-$HOME/.cache}/komp-ac-flatpak-builder`, and diff --git a/packaging/flatpak/build.sh b/packaging/flatpak/build.sh index 982e997b..75f54981 100755 --- a/packaging/flatpak/build.sh +++ b/packaging/flatpak/build.sh @@ -20,7 +20,7 @@ if [[ "$(uname -m)" != x86_64 ]]; then echo "The Flatpak production build currently supports x86_64 only" >&2 exit 1 fi -for command in flatpak flatpak-builder; do +for command in flatpak flatpak-builder appstreamcli; do if ! command -v "$command" >/dev/null 2>&1; then echo "$command is required to build the Flatpak" >&2 exit 1 diff --git a/server b/server index 652c3f68..c0d3d853 160000 --- a/server +++ b/server @@ -1 +1 @@ -Subproject commit 652c3f685b6855867f21efbe33ee871ef39373a2 +Subproject commit c0d3d85384d03d9b7f41d9b96b1dfd6a47ced095