diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 8d7aee0..5ec16f3 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -60,8 +60,8 @@ jobs: name: cloudflare-ddns v${{ steps.get_version.outputs.VERSION }} files: | LICENSE - cloudflare-ddns-macOS-${{ steps.get_version.outputs.VERSION }} - cloudflare-ddns-ubuntu-${{ steps.get_version.outputs.VERSION }} + cloudflare-ddns-macOS-${{ steps.get_version.outputs.VERSION }}.tar.xz + cloudflare-ddns-ubuntu-${{ steps.get_version.outputs.VERSION }}.tar.xz env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} publish_crate: diff --git a/Cargo.lock b/Cargo.lock index 0ae0a25..e98e33e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -153,7 +153,7 @@ dependencies = [ [[package]] name = "cloudflare-ddns" -version = "0.1.8" +version = "0.2.0" dependencies = [ "exitcode 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -163,6 +163,7 @@ dependencies = [ "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1551,6 +1552,14 @@ dependencies = [ "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "toml" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "try-lock" version = "0.2.2" @@ -1926,6 +1935,7 @@ dependencies = [ "checksum tokio-threadpool 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "90ca01319dea1e376a001e8dc192d42ebde6dd532532a5bad988ac37db365b19" "checksum tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "f2106812d500ed25a4f38235b9cae8f78a09edf43203e16e59c3b769a342a60e" "checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" +"checksum toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7aabe75941d914b72bf3e5d3932ed92ce0664d49d8432305a8b547c37227724" "checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" "checksum try_from 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "283d3b89e1368717881a9d51dad843cc435380d8109c9e47d38780a324698d8b" "checksum unicase 2.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2e2e6bd1e59e56598518beb94fd6db628ded570326f0a98c679a304bd9f00150" diff --git a/Cargo.toml b/Cargo.toml index 4dadce8..030b08d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cloudflare-ddns" -version = "0.1.8" +version = "0.2.0" authors = ["Rostislav Raykov "] edition = "2018" description = "A simple CLI tool to use Cloudflare's free DDNS service" @@ -12,8 +12,6 @@ license = "MIT" documentation = "https://github.com/zbrox/cloudflare-ddns" readme = "README.md" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] quicli = "0.4" structopt = "0.3.1" @@ -22,4 +20,5 @@ serde = "1.0.101" serde_json = "1.0.40" exitcode = "1.1.2" human-panic = "1.0.1" -failure = "0.1.5" \ No newline at end of file +failure = "0.1.5" +toml = "0.5.3" diff --git a/README.md b/README.md index 83e9f05..1f62359 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,26 @@ This is a simple CLI you can use to continuously update an A DNS record for a do ``` -k, --key The auth key you need to generate in your Cloudflare profile - -c, --cache Cache file for previously reported IP address (if skipped the IP will be reported on every execution) + -c, --cache Cache file for previously reported IP address (if skipped the IP will be reported on every + execution) + -f, --config Your TOML config file containing all the required options (email, auth_key, zone, domain) + which you can use instead of passing the arguments to the command line -d, --domain The domain for which you want to report the current IP address -e, --email Your Cloudflare login email -z, --zone The zone in which your domain is (usually that is your base domain name) ``` +### Config + +You Can pass a path to a configuration file (`-f` or `--config`) instead of each option as a command line argument. The configuration should be a [TOML](https://github.com/toml-lang/toml) file and hold the same options. Here's a sample: + +```TOML +email = "example@example.com" +auth_key = "secretkey" +domain = "example.example.com" +zone = "example.com" +``` + ## Cloudflare Setup You need to do some preparatory work in Cloudflare. Firstly this assumes you're using Cloudflare already to manage the DNS records for your domain. diff --git a/src/file.rs b/src/file.rs index 9fb14fc..2782813 100644 --- a/src/file.rs +++ b/src/file.rs @@ -1,19 +1,12 @@ use failure::Error; -use std::fs::File; -use std::io::prelude::*; +use quicli::fs::{write_to_file, read_file}; use std::path::PathBuf; pub fn read_cache_file(path: &PathBuf) -> Result { - let mut file = File::open(&path)?; - let mut s = String::new(); - file.read_to_string(&mut s)?; - - Ok(s.clone()) + Ok(read_file(path)?) } pub fn write_cache_file(path: &PathBuf, ip: &str) -> Result<(), Error> { - let mut file = File::create(&path)?; - file.write_all(ip.as_bytes())?; - + write_to_file(path, ip)?; Ok(()) } diff --git a/src/main.rs b/src/main.rs index 86272b8..37db21f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,28 +4,41 @@ mod network; use file::{read_cache_file, write_cache_file}; use human_panic::setup_panic; use network::{get_current_ip, get_dns_record_id, get_zone_identifier, update_ddns}; +use quicli::fs::read_file; use quicli::prelude::*; use std::path::PathBuf; use structopt::StructOpt; +#[derive(Deserialize)] +struct Config { + email: String, + auth_key: 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 (email, auth_key, zone, domain) which you can use instead of passing the arguments to the command line + #[structopt(long = "config", short = "f")] + config: Option, + /// Your Cloudflare login email - #[structopt(long = "email", short = "e")] - email: String, + #[structopt(long = "email", short = "e", required_unless = "config")] + email: Option, /// The auth key you need to generate in your Cloudflare profile - #[structopt(long = "key", short = "k")] - auth_key: String, + #[structopt(long = "key", short = "k", required_unless = "config")] + auth_key: Option, /// The zone in which your domain is (usually that is your base domain name) - #[structopt(long = "zone", short = "z")] - zone: String, + #[structopt(long = "zone", short = "z", required_unless = "config")] + zone: Option, /// The domain for which you want to report the current IP address - #[structopt(long = "domain", short = "d")] - domain: String, + #[structopt(long = "domain", short = "d", required_unless = "config")] + domain: Option, /// Cache file for previously reported IP address (if skipped the IP will be reported on every execution) #[structopt(long = "cache", short = "c")] @@ -34,10 +47,9 @@ struct Cli { fn main() -> CliResult { setup_panic!(); - let args = Cli::from_args(); - let should_use_cache = args.cache.is_some(); + let should_use_cache = args.cache.is_some(); let cached_ip: Option = match args.cache.clone() { Some(v) => { if v.exists() { @@ -64,22 +76,48 @@ fn main() -> CliResult { write_cache_file(&args.cache.unwrap(), ¤t_ip)?; } - let zone_id = get_zone_identifier(&args.zone, &args.email, &args.auth_key)?; - let record_id = get_dns_record_id(&zone_id, &args.domain, &args.email, &args.auth_key)?; + let (email, auth_key, zone, domain) = match args.config { + Some(c) => { + let config_str = read_file(c)?; + let config: Config = toml::from_str(&config_str)?; + (config.email, config.auth_key, config.zone, config.domain) + } + None => ( + args.email.expect("Email is not set"), + args.auth_key.expect("Auth key is not set"), + args.zone.expect("Zone is not set"), + args.domain.expect("Domain is not set"), + ), + }; - update_ddns( - ¤t_ip, - &args.domain, - &zone_id, - &record_id, - &args.email, - &args.auth_key, - )?; + update(¤t_ip, &email, &auth_key, &zone, &domain)?; println!( "Successfully updated the A record for {} to {}", - &args.domain, ¤t_ip + &domain, ¤t_ip ); Ok(()) } + +fn update( + current_ip: &str, + email: &str, + auth_key: &str, + zone: &str, + domain: &str, +) -> Result<(), Error> { + let zone_id = get_zone_identifier(&zone, &email, &auth_key)?; + let record_id = get_dns_record_id(&zone_id, &domain, &email, &auth_key)?; + + update_ddns( + ¤t_ip, + &domain, + &zone_id, + &record_id, + &email, + &auth_key, + )?; + + Ok(()) +} diff --git a/src/network.rs b/src/network.rs index 40a5c17..3fd8957 100644 --- a/src/network.rs +++ b/src/network.rs @@ -77,7 +77,16 @@ pub fn get_dns_record_id( return Err(format_err!("API Error: {}", err)); } - Ok(response.result[0].id.clone()) + let id = match response.result.first() { + Some(v) => v.id.clone(), + None => { + return Err(format_err!( + "Unexpected API result for DNS record. Check if you passed the right options." + )) + } + }; + + Ok(id) } pub fn get_current_ip() -> Result {