use crate::active::ActiveBuildMap;
use protocol::{write_json_frame, HelloAckPayload, MsgType};
use std::path::PathBuf;
use std::sync::Arc;
use tokio::io::AsyncWrite;
#[allow(unsafe_code)] pub fn get_hostname() -> String {
if let Ok(name) = std::env::var("HOSTNAME").or_else(|_| std::env::var("COMPUTERNAME")) {
if !name.is_empty() {
return name;
}
}
#[cfg(unix)]
{
let mut buf = [0u8; 256];
let res = unsafe { libc::gethostname(buf.as_mut_ptr() as *mut libc::c_char, buf.len()) };
if res == 0 {
let len = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
if let Ok(s) = std::str::from_utf8(&buf[..len]) {
if !s.is_empty() {
return s.to_string();
}
}
}
}
"fhd-agent".to_string()
}
#[derive(Clone)]
pub struct ServerContext {
pub expected_token: Option<String>,
pub workdir_root: PathBuf,
pub custom_shell: Option<String>,
pub semaphore: Arc<tokio::sync::Semaphore>,
pub lock_manager: workspace::WorkspaceLockManager,
pub tags: Vec<String>,
pub queue_depth: Arc<std::sync::atomic::AtomicUsize>,
pub max_runs: usize,
pub min_disk_bytes: u64,
pub cas_store: Option<workspace::CasStore>,
pub start_time: std::time::Instant,
pub active_builds: ActiveBuildMap,
pub connection_limiter: Arc<tokio::sync::Semaphore>,
pub max_queued_runs: usize,
}
impl ServerContext {
pub fn authorize(&self, provided: Option<&str>) -> bool {
match self.expected_token.as_deref() {
None => true,
Some("") => true,
Some(expected) => protocol::ct_eq_tokens(provided, Some(expected)),
}
}
}
pub fn validate_start_config(
token: Option<&str>,
allow_unauthenticated: bool,
) -> Result<(), String> {
match token {
Some(t) if t.trim().is_empty() => Err(
"--token was set to an empty string. Set a real token via --token or the \
FARHAND_TOKEN environment variable, or pass --allow-unauthenticated to \
intentionally disable authentication."
.to_string(),
),
Some(_) => Ok(()),
None if allow_unauthenticated => Ok(()),
None => Err(
"No authentication token configured. fhd executes commands sent by \
authenticated clients, so it refuses to start without a token.\n \
Set one: --token <secret> (or FARHAND_TOKEN env var)\n \
Dev only: --allow-unauthenticated (NEVER expose to untrusted networks)"
.to_string(),
),
}
}
pub(crate) const DEFAULT_MAX_CONNECTIONS: usize = 32;
pub(crate) const UNLIMITED_CONNECTIONS: usize = 1 << 20;
pub fn is_exposed_bind(listen: &str) -> bool {
match listen.parse::<std::net::SocketAddr>() {
Ok(addr) => !addr.ip().is_loopback(),
Err(_) => true,
}
}
pub(crate) async fn deny_control_request<W: AsyncWrite + Unpin>(
stream: &mut W,
request: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let message = format!("Unauthorized {request} request");
let ack = HelloAckPayload {
ok: false,
error: Some(message.clone()),
compression: None,
remote_workdir: None,
};
write_json_frame(stream, MsgType::HelloAck, &ack).await?;
Err(message.into())
}