1mod args;
4mod connection;
5mod init;
6mod lifecycle;
7mod provider;
8mod telemetry;
9
10use std::ffi::OsString;
11#[cfg(any(unix, test))]
12use std::fs::{self, File, OpenOptions, TryLockError};
13#[cfg(any(unix, test))]
14use std::io::Write;
15#[cfg(any(unix, test))]
16use std::io::{Read as _, Seek as _, SeekFrom};
17use std::net::SocketAddr;
18#[cfg(unix)]
19use std::os::unix::process::CommandExt as _;
20use std::path::{Path, PathBuf};
21#[cfg(unix)]
22use std::process::Stdio;
23#[cfg(unix)]
24use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
25
26#[cfg(unix)]
27use crate::auth::PairingStatus;
28use crate::auth::{AuthStore, PairingGrant};
29use crate::client::{Endpoint, GatewayClient, MAX_PENDING_FRAMES};
30use crate::cloudflare::CloudflareTunnel;
31use crate::config::{
32 CloudflareConfig, ConfigStore, DEFAULT_LISTEN, GatewayConfig, TlsConfig, load_secret_file,
33 state_dir,
34};
35use crate::server::GatewayServer;
36use crate::wire::{ClientKind, ClientMessage, ServerMessage};
37use crate::{Error, Result};
38#[cfg(unix)]
39use nix::sys::signal::{Signal, kill};
40#[cfg(unix)]
41use nix::unistd::Pid;
42#[cfg(any(unix, test))]
43use serde::Deserialize;
44use serde::Serialize;
45#[cfg(unix)]
46use tokio::process::{Child, Command as TokioCommand};
47#[cfg(unix)]
48use tokio::signal::unix::{Signal as TokioSignal, SignalKind, signal};
49use uuid::Uuid;
50
51#[cfg(test)]
52use self::args::parse;
53pub use self::args::{CloudflareInit, FrontendCommand, GatewayCli};
54use self::args::{Command, ConnectOptions, InitOptions, RegisterProviderOptions, parse_cli};
55use self::connection::*;
56use self::init::*;
57pub use self::init::{
58 initialize_named_cloudflare, initialize_quick_cloudflare, reset_gateway_state,
59};
60use self::lifecycle::*;
61pub use self::lifecycle::{ensure_background_gateway, startup_error};
62use self::provider::*;
63
64#[cfg(unix)]
65pub fn terminate_process_group(pid: u32) {
67 if pid <= 1 {
68 return;
69 }
70 let Ok(pid) = i32::try_from(pid) else {
71 return;
72 };
73 let Some(group) = pid.checked_neg() else {
74 return;
75 };
76 let _ = kill(Pid::from_raw(group), Signal::SIGKILL);
77}
78
79#[cfg(any(unix, test))]
80const PROCESS_FILE: &str = "gateway-process.json";
81#[cfg(unix)]
82const STARTUP_FILE: &str = "gateway-start.lock";
83#[cfg(unix)]
84const STATE_MARKER_FILE: &str = "gateway.toml";
85#[cfg(any(unix, test))]
86const MAX_PROCESS_RECORD_BYTES: usize = 4 * 1024;
87#[cfg(unix)]
88const EXIT_TIMEOUT: Duration = Duration::from_secs(5);
89#[cfg(unix)]
90const EXIT_POLL_INTERVAL: Duration = Duration::from_millis(100);
91#[cfg(unix)]
92const BACKGROUND_START_TIMEOUT: Duration = Duration::from_secs(40);
93#[cfg(unix)]
94const BACKGROUND_START_POLL_INTERVAL: Duration = Duration::from_millis(50);
95#[cfg(unix)]
96const MAX_BACKGROUND_ERROR_BYTES: u64 = 16 * 1024;
97#[cfg(unix)]
98const CONNECTION_POLL_INTERVAL: Duration = Duration::from_millis(100);
99
100pub async fn run(
105 arguments: Vec<OsString>,
106 save_local_client: fn(&Endpoint, String) -> Result<()>,
107 load_local_client: fn(&Endpoint) -> Result<Option<String>>,
108) -> Result<()> {
109 let cli = match parse_cli(arguments) {
110 Ok(cli) => cli,
111 Err(error)
112 if matches!(
113 error.kind(),
114 clap::error::ErrorKind::DisplayHelp | clap::error::ErrorKind::DisplayVersion
115 ) =>
116 {
117 error.print()?;
118 return Ok(());
119 }
120 Err(error) => return Err(Error::Config(error.to_string())),
121 };
122 run_cli(cli, save_local_client, load_local_client).await
123}
124
125pub async fn run_cli(
130 cli: GatewayCli,
131 save_local_client: fn(&Endpoint, String) -> Result<()>,
132 load_local_client: fn(&Endpoint) -> Result<Option<String>>,
133) -> Result<()> {
134 match cli.into_command()? {
135 Command::PrintDefaultConfig => {
136 println!(
137 "{}",
138 toml::to_string_pretty(&GatewayConfig::new(crate::config::DEFAULT_LISTEN, None)?)
139 .map_err(|error| Error::Config(format!(
140 "cannot encode default gateway configuration: {error}"
141 )))?
142 );
143 Ok(())
144 }
145 Command::CheckConfig { state_dir } => {
146 ConfigStore::open(state_dir)?;
147 println!("gateway configuration is valid");
148 Ok(())
149 }
150 Command::ExportComputerResources { directory } => {
151 crate::computer_runtime::export_resources(&directory)
152 }
153 Command::Telemetry { state_dir, command } => {
154 telemetry::run(state_dir, command, load_local_client).await
155 }
156 Command::SetRuntime {
157 state_dir,
158 idle_exit_seconds,
159 storage_limit_bytes,
160 ingress,
161 clear_ingress,
162 clear_storage_limit,
163 } => {
164 let (store, mut config) = ConfigStore::open(state_dir)?;
165 ensure_gateway_stopped(&store, &config)?;
166 if let Some(seconds) = idle_exit_seconds {
167 config.runtime.idle_exit_seconds = seconds;
168 }
169 if let Some(bytes) = storage_limit_bytes {
170 config.runtime.storage_limit_bytes = Some(bytes);
171 }
172 if let Some(address) = ingress {
173 config.runtime.ingress = Some(address);
174 }
175 if clear_ingress {
176 config.runtime.ingress = None;
177 }
178 if clear_storage_limit {
179 config.runtime.storage_limit_bytes = None;
180 }
181 config.validate()?;
182 store.save(&config)
183 }
184 Command::Init(options) => initialize(options),
185 Command::Bootstrap { state_dir } => initialize_bootstrap(state_dir, save_local_client),
186 Command::ResetBotDefaults { state_dir } => reset_bot_defaults(state_dir),
187 Command::SetDesktop { state_dir, enabled } => set_desktop(state_dir, enabled),
188 Command::PairingCode { state_dir } => pairing_code(state_dir, load_local_client).await,
189 Command::ClearProviderCredential {
190 state_dir,
191 instance,
192 } => provider::clear_provider_credential(state_dir, instance, load_local_client).await,
193 Command::RegisterProvider(options) => {
194 register_provider_command(options, load_local_client).await
195 }
196 Command::Connect(options) => connect(options, load_local_client).await,
197 Command::Serve {
198 state_dir,
199 background,
200 } => {
201 if background {
202 serve_in_background(state_dir, load_local_client).await
203 } else {
204 serve(state_dir, true, save_local_client, load_local_client).await
205 }
206 }
207 Command::ServeChild { state_dir } => {
208 serve(state_dir, false, save_local_client, load_local_client).await
209 }
210 Command::Exit { state_dir } => exit_gateway(state_dir),
211 }
212}
213
214#[cfg(test)]
215mod tests;