cloudflare-ddns-service/src/main.rs
Jan Christian Grünhage ec8b893f6a feat: migrate to api tokens
Cloudflare's v4 API provides three ways of authenticating,
with the API tokens being the new, standard conform, recommended
way. This patch removes the use of the old auth_key + email combo
and replaces it with API tokens.

BREAKING CHANGE: auth keys can't be used anymore, switch to api tokens
2020-04-20 23:04:31 +02:00

116 lines
3.4 KiB
Rust

mod file;
mod network;
use file::{read_file, write_file};
use human_panic::setup_panic;
use network::{get_current_ip, get_dns_record_id, get_zone_identifier, update_ddns};
use quicli::prelude::*;
use std::path::PathBuf;
use structopt::StructOpt;
use anyhow::{Context, Result};
#[derive(Deserialize)]
struct Config {
api_token: String,
zone: String,
domain: String,
}
#[derive(Debug, StructOpt)]
/// Inform Cloudflare's DDNS service of the current IP address for your domain
struct Cli {
/// Your TOML config file containing all the required options (api_token, zone, domain) which you can use instead of passing the arguments to the command line
#[structopt(long = "config", short = "f")]
config: Option<PathBuf>,
/// The api token you need to generate in your Cloudflare profile
#[structopt(long = "token", short = "t", required_unless = "config")]
api_token: Option<String>,
/// The zone in which your domain is (usually that is your base domain name)
#[structopt(long = "zone", short = "z", required_unless = "config")]
zone: Option<String>,
/// The domain for which you want to report the current IP address
#[structopt(long = "domain", short = "d", required_unless = "config")]
domain: Option<String>,
/// Cache file for previously reported IP address (if skipped the IP will be reported on every execution)
#[structopt(long = "cache", short = "c")]
cache: Option<PathBuf>,
}
fn main() -> Result<()> {
setup_panic!();
let args = Cli::from_args();
let should_use_cache = args.cache.is_some();
let cached_ip: Option<String> = match args.cache.clone() {
Some(v) => {
if v.exists() {
Some(read_file(&v.clone()).context("Could not read cache file")?)
} else {
Some("0.0.0.0".to_owned())
}
}
None => None,
};
let current_ip = get_current_ip()?;
if cached_ip.is_some() && current_ip == cached_ip.unwrap() {
println!("IP is unchanged. Exiting...");
return Ok(());
}
if should_use_cache {
println!(
"Saving current IP {} to cache file {:?}...",
&current_ip,
&args.cache.clone().unwrap()
);
write_file(&args.cache.unwrap(), &current_ip)?;
}
let (api_token, zone, domain) = match args.config {
Some(c) => {
let config_str = read_file(&c)?;
let config: Config = toml::from_str(&config_str)?;
(config.api_token, config.zone, config.domain)
}
None => (
args.api_token.expect("API token is not set"),
args.zone.expect("Zone is not set"),
args.domain.expect("Domain is not set"),
),
};
update(&current_ip, &api_token, &zone, &domain)?;
println!(
"Successfully updated the A record for {} to {}",
&domain, &current_ip
);
Ok(())
}
fn update(
current_ip: &str,
api_token: &str,
zone: &str,
domain: &str,
) -> Result<()> {
let zone_id = get_zone_identifier(&zone, &api_token).context("Error getting the zone identifier")?;
let record_id = get_dns_record_id(&zone_id, &domain, &api_token).context("Error getting the DNS record ID")?;
update_ddns(
&current_ip,
&domain,
&zone_id,
&record_id,
&api_token,
).context("Error updating the DNS record")?;
Ok(())
}