mobius_gateway/
command.rs1mod args;
4mod connection;
5mod init;
6mod lifecycle;
7
8use std::ffi::OsString;
9#[cfg(any(unix, test))]
10use std::fs::{self, File, OpenOptions, TryLockError};
11#[cfg(unix)]
12use std::io::IsTerminal as _;
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::fs::PermissionsExt as _;
20#[cfg(unix)]
21use std::os::unix::process::CommandExt as _;
22use std::path::{Path, PathBuf};
23#[cfg(unix)]
24use std::process::Stdio;
25#[cfg(unix)]
26use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
27
28#[cfg(any(unix, test))]
29use qrcode::QrCode;
30#[cfg(any(unix, test))]
31use qrcode::render::unicode::Dense1x2;
32#[cfg(any(unix, test))]
33use url::Url;
34
35#[cfg(unix)]
36use crate::auth::PairingStatus;
37use crate::auth::{AuthStore, PairingGrant};
38use crate::client::{Endpoint, GatewayClient, MAX_PENDING_FRAMES};
39use crate::cloudflare::CloudflareTunnel;
40use crate::config::{
41 CloudflareConfig, ConfigStore, DEFAULT_LISTEN, GatewayConfig, TlsConfig, load_cloudflare_token,
42 state_dir,
43};
44use crate::server::GatewayServer;
45use crate::wire::{ClientKind, ClientMessage, ServerMessage};
46use crate::{Error, Result};
47#[cfg(unix)]
48use nix::sys::signal::{Signal, kill};
49#[cfg(unix)]
50use nix::unistd::Pid;
51#[cfg(any(unix, test))]
52use serde::Deserialize;
53use serde::Serialize;
54#[cfg(unix)]
55use tokio::process::{Child, Command as TokioCommand};
56#[cfg(unix)]
57use tokio::signal::unix::{Signal as TokioSignal, SignalKind, signal};
58use uuid::Uuid;
59
60use self::args::*;
61use self::connection::*;
62use self::init::*;
63pub use self::init::{
64 initialize_named_cloudflare, initialize_quick_cloudflare, reset_gateway_state,
65};
66pub use self::lifecycle::ensure_background_gateway;
67use self::lifecycle::*;
68
69pub const USAGE: &str = "usage: mobius-gateway [--state-dir PATH]\n \
70 mobius-gateway provider [--state-dir PATH]\n \
71 mobius-gateway init [--state-dir PATH] [--listen ADDR] \
72 [--tls-cert PATH --tls-key PATH] \
73 [--cloudflare-hostname HOST --cloudflare-token-file PATH]\n \
74 mobius-gateway bootstrap [--state-dir PATH]\n \
75 mobius-gateway pairing-code [--state-dir PATH] --json\n \
76 mobius-gateway connect [--state-dir PATH] [--endpoint ENDPOINT]\n \
77 mobius-gateway serve [--state-dir PATH] [--background]\n \
78 mobius-gateway exit [--state-dir PATH]";
79
80#[cfg(any(unix, test))]
81const PROCESS_FILE: &str = "gateway-process.json";
82#[cfg(unix)]
83const STARTUP_FILE: &str = "gateway-start.lock";
84#[cfg(unix)]
85const STATE_MARKER_FILE: &str = "gateway.toml";
86#[cfg(any(unix, test))]
87const MAX_PROCESS_RECORD_BYTES: usize = 4 * 1024;
88#[cfg(unix)]
89const EXIT_TIMEOUT: Duration = Duration::from_secs(5);
90#[cfg(unix)]
91const EXIT_POLL_INTERVAL: Duration = Duration::from_millis(100);
92#[cfg(unix)]
93const BACKGROUND_START_TIMEOUT: Duration = Duration::from_secs(40);
94#[cfg(unix)]
95const BACKGROUND_START_POLL_INTERVAL: Duration = Duration::from_millis(50);
96#[cfg(unix)]
97const MAX_BACKGROUND_ERROR_BYTES: u64 = 16 * 1024;
98#[cfg(unix)]
99const CONNECTION_POLL_INTERVAL: Duration = Duration::from_millis(100);
100
101pub async fn run(
103 arguments: Vec<OsString>,
104 save_local_client: fn(&Endpoint, String) -> Result<()>,
105 load_local_client: fn(&Endpoint) -> Result<Option<String>>,
106) -> Result<()> {
107 if matches!(arguments.as_slice(), [flag] if flag == "--help" || flag == "-h") {
108 println!("{USAGE}");
109 return Ok(());
110 }
111 if matches!(arguments.as_slice(), [flag] if flag == "--version" || flag == "-V") {
112 println!("mobius-gateway {}", env!("CARGO_PKG_VERSION"));
113 return Ok(());
114 }
115 match parse(arguments)? {
116 Command::Init(options) => initialize(options),
117 Command::Bootstrap { state_dir } => initialize_bootstrap(state_dir, save_local_client),
118 Command::PairingCode { state_dir } => pairing_code(state_dir, load_local_client).await,
119 Command::Connect(options) => connect(options, load_local_client).await,
120 Command::Serve {
121 state_dir,
122 background,
123 } => {
124 if background {
125 serve_in_background(state_dir).await
126 } else {
127 serve(state_dir, true, save_local_client).await
128 }
129 }
130 Command::ServeChild { state_dir } => serve(state_dir, false, save_local_client).await,
131 Command::Exit { state_dir } => exit_gateway(state_dir),
132 }
133}
134
135#[cfg(test)]
136mod tests;