2ae9196
ZhenYi
feat(lib/git): add HTTP handler tree with routes and errors across analysis, archives, blobs, branches, commits, conflicts, content, diffs, hooks, lfs, logs, maintenance, packs, patches, pipelines, refs, remotes, repos, signatures, submodules, tags, trees
2ae9196
use std::net::SocketAddr;
use std::sync::Arc;
use russh::keys::PrivateKey;
use russh::server::Server as _;
use russh::{MethodKind, MethodSet, SshId, server};
use tokio::sync::Semaphore;
use config::git::GitSshConfig;
use crate::ssh::error::{SshError, SshResult};
use crate::ssh::handler::GitSshHandler;
use crate::ssh::service::GitSshService;
pub struct GitSshServer {
service: Arc<GitSshService>,
session_slots: Arc<Semaphore>,
}
impl GitSshServer {
pub fn new(service: Arc<GitSshService>) -> Self {
let max = service.config.max_sessions.max(1);
Self {
service,
session_slots: Arc::new(Semaphore::new(max)),
}
}
}
impl server::Server for GitSshServer {
type Handler = GitSshHandler;
fn new_client(&mut self, peer_addr: Option<SocketAddr>) -> Self::Handler {
let permit = self.session_slots.clone().try_acquire_owned().ok();
GitSshHandler::new(self.service.clone(), peer_addr, permit)
}
fn handle_session_error(&mut self, error: russh::Error) {
tracing::debug!("ssh session error: {error}");
}
}
pub fn load_host_key(path: &str) -> SshResult<PrivateKey> {
let data = std::fs::read_to_string(path)
.map_err(|e| SshError::KeyLoadFailed(format!("cannot read {path}: {e}")))?;
PrivateKey::from_openssh(&data)
.map_err(|e| SshError::KeyLoadFailed(format!("openssh decode failed for {path}: {e}")))
}
pub fn build_server_config(config: &GitSshConfig) -> SshResult<server::Config> {
let host_key = load_host_key(&config.host_key_path)?;
let mut methods = MethodSet::empty();
methods.push(MethodKind::PublicKey);
Ok(server::Config {
server_id: SshId::Standard(format!("SSH-2.0-gitdata-{}", env!("CARGO_PKG_VERSION")).into()),
methods,
keys: vec![host_key],
max_auth_attempts: 10,
auth_rejection_time: config.auth_rejection_time,
inactivity_timeout: config.inactivity_timeout,
keepalive_interval: Some(config.keepalive_interval),
keepalive_max: config.keepalive_max as usize,
..Default::default()
})
}
pub async fn run_ssh(service: GitSshService) -> SshResult<()> {
let bind_addr = service.config.bind_addr.clone();
let port = service.config.port;
let config = Arc::new(build_server_config(&service.config)?);
let mut server = GitSshServer::new(Arc::new(service));
server
.run_on_address(config, format!("{bind_addr}:{port}"))
.await
.map_err(SshError::Io)?;
Ok(())
}
pub async fn serve_on_listener(
service: GitSshService,
listener: tokio::net::TcpListener,
) -> SshResult<()> {
let config = Arc::new(build_server_config(&service.config)?);
let mut server = GitSshServer::new(Arc::new(service));
let running = server.run_on_socket(config, &listener);
running.await.map_err(SshError::Io)?;
Ok(())
}