11use anyhow:: { Context , Result } ;
2- use clap:: Parser ;
2+ use clap:: { Parser , Subcommand } ;
33use log:: { debug, error, info} ;
4- use reqwest:: { blocking:: Client , header} ;
54use serde:: { Deserialize , Serialize } ;
6- use std:: { env, fs:: File , io:: Read , path:: PathBuf } ;
5+ use std:: { env, fs:: File , io:: { Read , Write } , path:: PathBuf } ;
76
87/// Mini-Gateway Router CLI Tool
98#[ derive( Parser ) ]
109#[ command( name = "gwrs" ) ]
1110#[ command( about = "CLI tool for Mini-Gateway Router API" , long_about = None ) ]
1211struct Cli {
1312 /// Path to the configuration file
14- #[ arg( long, required = true ) ]
15- config : PathBuf ,
13+ #[ arg( long) ]
14+ config : Option < PathBuf > ,
1615
1716 /// Use credentials from OS environment variables (GWRS_USER, GWRS_PASS)
1817 #[ arg( long, global = true ) ]
@@ -29,6 +28,24 @@ struct Cli {
2928 /// API base URL (default: http://localhost:24042)
3029 #[ arg( long, global = true , default_value = "http://localhost:24042" ) ]
3130 url : String ,
31+
32+ #[ command( subcommand) ]
33+ command : Option < Commands > ,
34+ }
35+
36+ #[ derive( Subcommand ) ]
37+ enum Commands {
38+ /// Initialize a new configuration file
39+ Init {
40+ /// Location to create the configuration file (default: current directory)
41+ #[ arg( value_name = "LOCATION" ) ]
42+ location : Option < PathBuf > ,
43+ } ,
44+ /// Upload configuration to the router
45+ Config {
46+ /// Path to the configuration file
47+ config : PathBuf ,
48+ } ,
3249}
3350
3451#[ derive( Serialize , Deserialize , Debug ) ]
@@ -66,26 +83,64 @@ fn main() -> Result<()> {
6683 env_logger:: init ( ) ;
6784 let cli = Cli :: parse ( ) ;
6885
69- // Get credentials
70- let ( username, password) = get_credentials ( & cli) ?;
71-
72- debug ! ( "Using API URL: {}" , cli. url) ;
73- debug ! ( "Using username: {}" , username) ;
74-
75- // Create HTTP client
76- let client = Client :: new ( ) ;
77-
78- // Authenticate and get token
79- let token = authenticate ( & client, & cli. url , & username, & password) ?;
80- debug ! ( "Authentication successful, token received" ) ;
81-
82- // Upload config
83- upload_config ( & client, & cli. url , & token, & cli. config ) ?;
86+ match cli. command {
87+ Some ( Commands :: Init { location } ) => {
88+ init_config ( & location. unwrap_or_else ( || PathBuf :: from ( "." ) ) ) ?;
89+ }
90+ Some ( Commands :: Config { config } ) => {
91+ // Get credentials
92+ let ( username, password) = get_credentials ( & Credentials {
93+ osenv : cli. osenv ,
94+ user : cli. user ,
95+ pass : cli. pass
96+ } ) ?;
97+
98+ debug ! ( "Using API URL: {}" , cli. url) ;
99+ debug ! ( "Using username: {}" , username) ;
100+
101+ // Authenticate and get token
102+ let token = authenticate ( & cli. url , & username, & password) ?;
103+ debug ! ( "Authentication successful, token received" ) ;
104+
105+ // Upload config
106+ upload_config ( & cli. url , & token, & config) ?;
107+ }
108+ None => {
109+ if let Some ( config) = cli. config {
110+ // Get credentials
111+ let ( username, password) = get_credentials ( & Credentials {
112+ osenv : cli. osenv ,
113+ user : cli. user ,
114+ pass : cli. pass
115+ } ) ?;
116+
117+ debug ! ( "Using API URL: {}" , cli. url) ;
118+ debug ! ( "Using username: {}" , username) ;
119+
120+ // Authenticate and get token
121+ let token = authenticate ( & cli. url , & username, & password) ?;
122+ debug ! ( "Authentication successful, token received" ) ;
123+
124+ // Upload config
125+ upload_config ( & cli. url , & token, & config) ?;
126+ } else {
127+ error ! ( "No configuration file specified. Use --config or the config subcommand" ) ;
128+ anyhow:: bail!( "No configuration file specified. Use --config or the config subcommand" ) ;
129+ }
130+ }
131+ }
84132
85133 Ok ( ( ) )
86134}
87135
88- fn get_credentials ( cli : & Cli ) -> Result < ( String , String ) > {
136+ #[ derive( Debug ) ]
137+ struct Credentials {
138+ osenv : bool ,
139+ user : Option < String > ,
140+ pass : Option < String > ,
141+ }
142+
143+ fn get_credentials ( cli : & Credentials ) -> Result < ( String , String ) > {
89144 if cli. osenv {
90145 debug ! ( "Getting credentials from environment variables" ) ;
91146 let username = env:: var ( "GWRS_USER" ) . context ( "GWRS_USER environment variable not set" ) ?;
@@ -100,7 +155,77 @@ fn get_credentials(cli: &Cli) -> Result<(String, String)> {
100155 }
101156}
102157
103- fn authenticate ( client : & Client , base_url : & str , username : & str , password : & str ) -> Result < String > {
158+ fn init_config ( location : & PathBuf ) -> Result < ( ) > {
159+ info ! ( "Initializing configuration file in: {}" , location. display( ) ) ;
160+
161+ let config_path = location. join ( "router-config.yaml" ) ;
162+ let config_content = r#"# Mini-Gateway Router Configuration
163+ # This file contains the configuration for your router setup
164+
165+ proxy:
166+ - name: "proxy1"
167+ listen: "127.0.0.1:8080"
168+ domains:
169+ - domain: "example.com"
170+ tls: false
171+ tls_cert: |
172+ -----BEGIN CERTIFICATE-----
173+ cert
174+ -----END CERTIFICATE-----
175+ tls_key: |
176+ -----BEGIN PRIVATE KEY-----
177+ key
178+ -----END PRIVATE KEY-----
179+ highspeed:
180+ enabled: true
181+ target: "gateway1"
182+ gateway:
183+ - name: "gateway1"
184+ domain: "example.com"
185+ target: "127.0.0.1:8080"
186+ path:
187+ - priority: 1
188+ pattern: "^(.*)$"
189+ target: "/$1"
190+ "# ;
191+
192+ let mut file = File :: create ( & config_path)
193+ . context ( "Failed to create configuration file" ) ?;
194+
195+ file. write_all ( config_content. as_bytes ( ) )
196+ . context ( "Failed to write configuration file" ) ?;
197+
198+ info ! ( "Configuration file created at: {}" , config_path. display( ) ) ;
199+ println ! ( "Configuration file created at: {}" , config_path. display( ) ) ;
200+ println ! ( "\n Configuration file structure explanation:" ) ;
201+ println ! ( "1. proxy: Define your proxy servers with their settings" ) ;
202+ println ! ( " - name: Unique identifier for the proxy" ) ;
203+ println ! ( " - listen: Address and port to listen on" ) ;
204+ println ! ( " - domains: List of domains this proxy handles" ) ;
205+ println ! ( " - domain: Domain name" ) ;
206+ println ! ( " - tls: Enable/disable TLS" ) ;
207+ println ! ( " - tls_cert: TLS certificate (if tls is true)" ) ;
208+ println ! ( " - tls_key: TLS private key (if tls is true)" ) ;
209+ println ! ( " - highspeed: High-speed routing settings" ) ;
210+ println ! ( " - enabled: Enable/disable high-speed routing" ) ;
211+ println ! ( " - target: Target gateway for high-speed routing" ) ;
212+ println ! ( " - gateway: List of gateways for this proxy" ) ;
213+ println ! ( " - name: Gateway name" ) ;
214+ println ! ( " - domain: Domain for this gateway" ) ;
215+ println ! ( " - target: Target address and port" ) ;
216+ println ! ( " - path: URL path routing rules" ) ;
217+ println ! ( " - priority: Rule priority (lower numbers = higher priority)" ) ;
218+ println ! ( " - pattern: Regex pattern to match" ) ;
219+ println ! ( " - target: Target path pattern" ) ;
220+ println ! ( "\n To use this configuration:" ) ;
221+ println ! ( "1. Edit the file to match your setup" ) ;
222+ println ! ( "2. Use 'gwrs config router-config.yaml' to upload it" ) ;
223+ println ! ( "3. Add authentication with --user/--pass or --osenv" ) ;
224+
225+ Ok ( ( ) )
226+ }
227+
228+ fn authenticate ( base_url : & str , username : & str , password : & str ) -> Result < String > {
104229 info ! ( "Authenticating with username: {}" , username) ;
105230
106231 let login_url = format ! ( "{}/api/v1/users/login" , base_url) ;
@@ -109,14 +234,12 @@ fn authenticate(client: &Client, base_url: &str, username: &str, password: &str)
109234 password : password. to_string ( ) ,
110235 } ;
111236
112- let response = client
113- . post ( & login_url)
114- . json ( & login_request)
115- . send ( )
237+ let response = ureq:: post ( & login_url)
238+ . send_json ( ureq:: json!( login_request) )
116239 . context ( "Failed to send login request" ) ?;
117240
118241 let login_response = response
119- . json :: < LoginResponse > ( )
242+ . into_json :: < LoginResponse > ( )
120243 . context ( "Failed to parse login response" ) ?;
121244
122245 if !login_response. success {
@@ -134,7 +257,6 @@ fn authenticate(client: &Client, base_url: &str, username: &str, password: &str)
134257}
135258
136259fn upload_config (
137- client : & Client ,
138260 base_url : & str ,
139261 token : & str ,
140262 config_path : & PathBuf ,
@@ -155,36 +277,26 @@ fn upload_config(
155277
156278 // Prepare request
157279 let upload_url = format ! ( "{}/api/v1/settings/auto-config" , base_url) ;
158- let mut headers = header:: HeaderMap :: new ( ) ;
159- headers. insert (
160- header:: AUTHORIZATION ,
161- header:: HeaderValue :: from_str ( & format ! ( "Bearer {}" , token) ) ?,
162- ) ;
163- headers. insert (
164- header:: CONTENT_TYPE ,
165- header:: HeaderValue :: from_static ( "application/yaml" ) ,
166- ) ;
167280
168281 // Send request
169- let response = client
170- . post ( & upload_url)
171- . headers ( headers)
172- . body ( contents)
173- . send ( )
282+ let response = ureq:: post ( & upload_url)
283+ . set ( "Authorization" , & format ! ( "Bearer {}" , token) )
284+ . set ( "Content-Type" , "application/yaml" )
285+ . send_string ( & contents)
174286 . context ( "Failed to send configuration upload request" ) ?;
175287
176288 // Check status
177289 let status = response. status ( ) ;
178- if ! status. is_success ( ) {
290+ if status >= 400 {
179291 let error_text = response
180- . text ( )
292+ . into_string ( )
181293 . unwrap_or_else ( |_| "Unknown error" . to_string ( ) ) ;
182294 error ! ( "Upload failed with status {}: {}" , status, error_text) ;
183295 anyhow:: bail!( "Upload failed with status {}: {}" , status, error_text) ;
184296 }
185297
186298 let upload_response = response
187- . json :: < ConfigUploadResponse > ( )
299+ . into_json :: < ConfigUploadResponse > ( )
188300 . context ( "Failed to parse upload response" ) ?;
189301
190302 if let Some ( error) = upload_response. error {
0 commit comments