2 * API Reference: https://api.gandi.net/docs/livedns/
3 * Some similar implementations:
4 * - https://github.com/rmarchant/gandi-ddns/blob/master/gandi_ddns.py
5 * - https://github.com/brianhp2/gandi-automatic-dns
8 #![cfg_attr(debug_assertions, allow(unused_variables, unused_imports, dead_code))]
10 use std
::{ fmt
::format
, fs
::File
, net
::{ IpAddr
, Ipv4Addr
}, thread
, time
};
11 use ron
::{ de
::from_reader
, ser
::to_writer
};
12 use serde
::{ Deserialize
, Serialize
};
13 use serde_json
::Value
;
15 type Result
<T
> = std
::result
::Result
<T
, Box
<dyn std
::error
::Error
>>;
22 impl std
::fmt
::Display
for Error
{
23 fn fmt(&self, f
: &mut std
::fmt
::Formatter
<'_
>) -> std
::fmt
::Result
{
24 f
.write_str(&self.message
)
28 impl std
::error
::Error
for Error
{ }
30 #[derive(Debug, Deserialize, Serialize)]
32 delay_between_check
: time
::Duration
,
38 fn default() -> Self {
39 Config
{ delay_between_check
: time
::Duration
::from_secs(60), api_key
: String
::from(""), domains
: Vec
::new() }
42 fn read(file_path
: &str) -> Result
<Config
> {
43 match File
::open(file_path
) {
44 Ok(file
) => from_reader(file
).map_err(|e
| e
.into()),
45 // The file doesn't exit -> create it with default values.
47 let file
= File
::create(file_path
)?
;
48 let default_config
= Config
::default();
49 to_writer(file
, &default_config
)?
;
56 const FILE_CONF
: &str = "config.ron";
58 fn main() -> Result
<()> {
59 println!("GANDI DynDNS");
61 let config
= Config
::read(FILE_CONF
)?
;
63 println!("Configuration: {:?}", config
);
66 let time_beginning_loop
= time
::Instant
::now();
68 check_and_update_dns(&config
.api_key
);
70 let elapsed
= time
::Instant
::now() - time_beginning_loop
;
72 if elapsed
< config
.delay_between_check
{
73 let to_wait
= config
.delay_between_check
- elapsed
;
74 thread
::sleep(to_wait
);
80 fn check_and_update_dns(api_key
: &str) {
83 //dbg!(get_real_ip());
85 get_current_record_ip(api_key
);
88 fn get_real_ip() -> Result
<Ipv4Addr
> {
90 let url
= "https://api.ipify.org";
91 let client
= reqwest
::blocking
::Client
::new();
93 match client
.get(url
).send() {
95 if resp
.status().is_success() {
96 let content
= resp
.text().unwrap();
97 match content
.parse
::<IpAddr
>() {
98 Ok(IpAddr
::V4(ip_v4
)) => Ok(ip_v4
),
99 /*Err(_)*/ _
=> Err(Box
::new(Error
{ message
: String
::from("Can't parse IPv4 from ipify") }))
101 //println!("Content:\n{:?}", content);
103 //println!("Request unsuccessful:\n{:#?}", resp);
104 Err(Box
::new(Error
{ message
: format!("Request unsuccessful: {:#?}", resp
) }))
108 //println!("Error during request: {:?}", error);
109 Err(Box
::new(Error
{ message
: format!("Error during request: {:?}", error
) }))
114 fn request_livedns_gandi(api_key
: &str, url_fragment
: &str) -> Result
<Value
> {
115 let url
= format!("https://api.gandi.net/v5/livedns/{}", url_fragment
);
116 let client
= reqwest
::blocking
::Client
::new();
118 match client
.get(&url
).header("Authorization", format!("Apikey {}", api_key
)).send() {
120 if resp
.status().is_success() {
121 let content
= resp
.text().unwrap();
122 Ok(serde_json
::from_str(&content
).unwrap())
124 Err(Box
::new(Error
{ message
: format!("Request unsuccessful: {:#?}", resp
) }))
127 Err(Box
::new(Error
{ message
: format!("Error during request: {:?}", error
) }))
131 fn get_current_record_ip(api_key
: &str) -> Result
<Ipv4Addr
> {
133 request_livedns_gandi(api_key
, "domains/euphorik.ch/records/home/A")?
; // TODO...
135 //.map(|json_value| json_value["rrset_values"][0].as_str().unwrap())
137 Result
::Err(Box
::new(Error
{ message
: String
::new() }))
140 //let url = "https://api.gandi.net/v5/livedns/domains/euphorik.ch/records";
141 //let url = "https://api.gandi.net/v5/livedns/domains"; // ?sharing_id="
142 //let url = "https://api.gandi.net/v5/organization/organizations";
143 //let url = "https://api.gandi.net/v5/livedns/domains/euphorik.ch/records/home/A";
146 let client = reqwest::blocking::Client::new();
148 match client.get(url).header("Authorization", format!("Apikey {}", api_key)).send() {
150 if resp.status().is_success() {
151 let content = resp.text().unwrap();
153 let json: Value = serde_json::from_str(&content).unwrap();
154 let prout = json["rrset_values"][0].as_str().unwrap();
155 println!("IP: {}", prout);
157 println!("Content:\n{}", serde_json::to_string_pretty(&json).unwrap());
159 println!("Request unsuccessful:\n{:#?}", resp);
162 println!("Error during request: {:?}", error)
167 fn update_record_ip() {