|
1 | 1 | # dreamhost-ddns |
2 | | -DDNS client for Dreamhost written in Rust, compiled for multiple platforms |
3 | | -<br><br> |
| 2 | + |
| 3 | +[](https://www.gnu.org/licenses/gpl-3.0) |
| 4 | +[](https://github.com/PRProd/dreamhost-ddns/actions/workflows/rust.yml) |
4 | 5 |
|
5 | | -## Prerequisites |
6 | | -A [dreamhost](https://dreamhost.com) API key is required. To obtain one, follow the instructions listed in [step 2, here.](https://help.dreamhost.com/hc/en-us/articles/4407354972692-Connecting-to-the-DreamHost-API) |
7 | | -<br><br> |
| 6 | +<br> |
| 7 | +A lightweight Rust CLI tool that updates a DreamHost DNS **A record** with your current public IP address. |
8 | 8 |
|
9 | | -## Download & Setup |
10 | | -1. Choose the appropriate architecture inside the [binaries](/binaries) directory and download the contents (both files) |
11 | | -2. Optionally open and edit the config.toml file with your personal credentials and DNS A record |
12 | | -<br><br> |
| 9 | +This tool is designed for: |
13 | 10 |
|
14 | | -## Usage |
| 11 | +* Home servers with dynamic IPs |
| 12 | +* Self-hosted services |
| 13 | +* Home Assistant add-ons |
| 14 | +* Docker environments |
| 15 | +* Simple standalone DDNS setups |
15 | 16 |
|
16 | | -### Windows |
| 17 | +It detects your current WAN IP and updates the DNS record **only when necessary**, preventing unnecessary API calls and avoiding DNS downtime. |
| 18 | + |
| 19 | +--- |
| 20 | + |
| 21 | +## Features |
| 22 | + |
| 23 | +* Fast public IP detection using multiple services |
| 24 | +* Safe DNS updates that prevent outages |
| 25 | +* Multiple configuration methods (CLI, env vars, config file) |
| 26 | +* Structured logging with selectable log levels |
| 27 | +* Designed for automation environments like Home Assistant |
| 28 | +* Small, fast Rust binary with minimal dependencies |
| 29 | + |
| 30 | +--- |
| 31 | + |
| 32 | +## How It Works |
| 33 | + |
| 34 | +The updater compares your **current WAN IP** with the **DNS record IP** stored at DreamHost. |
| 35 | + |
| 36 | +``` |
| 37 | +Detect WAN IP |
| 38 | + │ |
| 39 | + ▼ |
| 40 | +Fetch DNS record from DreamHost |
| 41 | + │ |
| 42 | + ▼ |
| 43 | +Compare values |
| 44 | + │ |
| 45 | + ┌────┴────┐ |
| 46 | + │ │ |
| 47 | +Match Mismatch |
| 48 | + │ │ |
| 49 | +Exit Safely update DNS |
17 | 50 | ``` |
18 | | -Usage: dreamhost-ddns.exe [OPTIONS] |
19 | 51 |
|
20 | | -Options: |
21 | | - -v, --verbose |
22 | | - --log-level <LOG_LEVEL> [possible values: error, warn, info, debug, trace] |
23 | | - -c, --config <CONFIG> |
24 | | - --api-key <API_KEY> |
25 | | - --record <RECORD> |
26 | | - --dry-run |
27 | | - -h, --help Print help |
28 | | - -V, --version Print version |
| 52 | +--- |
| 53 | + |
| 54 | +## Safe DNS Updates |
| 55 | + |
| 56 | +To prevent DNS outages during updates, the tool performs the following sequence: |
| 57 | + |
| 58 | +1. Add the new DNS record |
| 59 | +2. Wait briefly for propagation |
| 60 | +3. Verify the new record exists |
| 61 | +4. Remove the old DNS record |
| 62 | + |
| 63 | +If verification fails, the old record **is not removed**, ensuring your hostname never loses a valid DNS entry. |
| 64 | + |
| 65 | +--- |
| 66 | + |
| 67 | +## WAN IP Detection |
| 68 | + |
| 69 | +Your public IP is detected using multiple services in parallel: |
| 70 | + |
| 71 | +* https://icanhazip.com |
| 72 | +* https://api.ipify.org |
| 73 | +* https://ifconfig.me/ip |
| 74 | +* https://checkip.amazonaws.com |
| 75 | + |
| 76 | +The first successful response is used, improving reliability and speed. |
| 77 | + |
| 78 | +--- |
| 79 | + |
| 80 | +## Quick Start |
| 81 | + |
| 82 | +Create a [DreamHost API key](https://help.dreamhost.com/hc/en-us/articles/4407354972692-Connecting-to-the-DreamHost-API) with **DNS permissions**, then run: |
| 83 | + |
| 84 | +```bash |
| 85 | +dreamhost-ddns \ |
| 86 | + --api-key YOUR_API_KEY \ |
| 87 | + --record home.example.com |
29 | 88 | ``` |
30 | | -<br><br> |
31 | 89 |
|
32 | | -### Linux / Others |
| 90 | +If your WAN IP differs from the DNS record, the program updates it automatically. |
| 91 | + |
| 92 | +--- |
| 93 | + |
| 94 | +## Configuration |
| 95 | + |
| 96 | +Configuration values can be provided in several ways. |
| 97 | + |
| 98 | +### Configuration Priority |
| 99 | + |
| 100 | +Values are resolved in this order: |
| 101 | + |
| 102 | +1. CLI arguments |
| 103 | +2. Environment variables |
| 104 | +3. Config file specified with `--config` |
| 105 | +4. `config.toml` in the current directory |
| 106 | + |
| 107 | +--- |
| 108 | + |
| 109 | +### CLI Arguments |
| 110 | + |
| 111 | +Example: |
| 112 | + |
| 113 | +```bash |
| 114 | +dreamhost-ddns \ |
| 115 | + --api-key YOUR_API_KEY \ |
| 116 | + --record home.example.com |
33 | 117 | ``` |
34 | | -Usage: dreamhost-ddns [OPTIONS] |
35 | 118 |
|
36 | | -Options: |
37 | | - -v, --verbose |
38 | | - --log-level <LOG_LEVEL> [possible values: error, warn, info, debug, trace] |
39 | | - -c, --config <CONFIG> |
40 | | - --api-key <API_KEY> |
41 | | - --record <RECORD> |
42 | | - --dry-run |
43 | | - -h, --help Print help |
44 | | - -V, --version Print version |
| 119 | +Available arguments: |
| 120 | + |
45 | 121 | ``` |
46 | | -You will likely need to make the dreamhost-ddns file executable first: |
| 122 | +--api-key <KEY> DreamHost API key |
| 123 | +--record <HOSTNAME> DNS A record to update |
| 124 | +--config <FILE> Optional config file |
| 125 | +--log-level <LEVEL> Logging level |
| 126 | +--verbose Shortcut for info-level logging |
| 127 | +--dry-run Show actions without modifying DNS |
| 128 | +``` |
| 129 | + |
| 130 | +--- |
| 131 | + |
| 132 | +### Environment Variables |
| 133 | + |
| 134 | +You can also configure the tool using environment variables: |
| 135 | + |
47 | 136 | ```bash |
48 | | -chmod +x dreamhost-ddns |
| 137 | +export DREAMHOST_API_KEY=YOUR_API_KEY |
| 138 | +export DNS_RECORD=home.example.com |
| 139 | + |
| 140 | +dreamhost-ddns |
49 | 141 | ``` |
50 | 142 |
|
51 | | -Note: When setting this up as a cronjob, it is recommended that you use the --config flag in the crontab entry, and specify the FULL path to your config.toml file |
52 | | -<br><br> |
| 143 | +--- |
53 | 144 |
|
| 145 | +### Config File |
54 | 146 |
|
55 | | -## Configuration |
56 | | -Configuration is quite flexible, suitable for any situation |
| 147 | +Example `config.toml`: |
57 | 148 |
|
58 | | -When using a .toml (or config.toml) file, it should be in the following format: |
59 | 149 | ```toml |
60 | | -dreamhost_api_key = "ENTER-YOUR-DREAMHOST-API-KEY-HERE" |
61 | | -dns_record = "ENTER-THE-TARGET-DNS-A-RECORD" |
| 150 | +dreamhost_api_key = "YOUR_API_KEY" |
| 151 | +dns_record = "home.example.com" |
62 | 152 | ``` |
63 | | -If you've placed a file named **config.toml** into the same directory as the executable, you can run the program simply: |
| 153 | + |
| 154 | +Run with: |
| 155 | + |
64 | 156 | ```bash |
65 | | -$ ./dreamhost-ddns |
| 157 | +dreamhost-ddns --config config.toml |
66 | 158 | ``` |
67 | | -If you've named the .toml file differently, or placed it in a different direcory, you can execute the program like this: |
68 | | -```bash |
69 | | -$ ./dreamhost-ddns --config /path/to/my/config/myconfig.toml |
| 159 | + |
| 160 | +If no configuration options are provided, the program will automatically look for `config.toml` in the current directory. |
| 161 | + |
| 162 | +--- |
| 163 | + |
| 164 | +## Logging |
| 165 | + |
| 166 | +Logging verbosity can be controlled using `--log-level`. |
| 167 | + |
| 168 | +Available levels: |
| 169 | + |
70 | 170 | ``` |
71 | | -<br><br> |
72 | | -To override a value in your config file, or to bypass the usage of a config file completely, you can pass the required arguments directly. |
| 171 | +error |
| 172 | +warn |
| 173 | +info |
| 174 | +debug |
| 175 | +trace |
| 176 | +``` |
| 177 | + |
| 178 | +Example: |
73 | 179 |
|
74 | | -In this example, a .toml config file is not required: |
75 | 180 | ```bash |
76 | | -$ ./dreamhost-ddns --api-key 8SIX753OH9 --record jenny.mydomain.com |
| 181 | +dreamhost-ddns --log-level debug |
77 | 182 | ``` |
78 | 183 |
|
79 | | -Values passed in the command line will override values from your configuration file. In this example, your config file is used only for the API Key, but not for the record: |
80 | | -```bash |
81 | | -$ ./dreamhost-ddns --record jenny.mydomain.com |
| 184 | +Example output: |
| 185 | + |
| 186 | +``` |
| 187 | +Detected WAN IP: 203.0.113.15 |
| 188 | +DNS record IP: 198.51.100.10 |
| 189 | +IP mismatch detected |
| 190 | +Updating DNS... |
| 191 | +New DNS record verified |
| 192 | +Old DNS record removed |
| 193 | +DNS updated successfully |
82 | 194 | ``` |
83 | | -<br><br> |
84 | 195 |
|
85 | | -## Important Notes |
86 | | -### Dreamhost API Rate limiting |
87 | | -The dreamhost API is limited to 500 calls daily. This DDNS client makes a minimum of one API call per run, and between four and eight calls (typically four) when updating the DNS record. When scheduling this to run, please keep this in mind when deciding how often to run. When reaching this limit, you will see this error message: |
88 | | -```txt |
89 | | -Error: DreamHost API error: rate error: module dns used more than 500 times in 1 day(s) |
| 196 | +--- |
| 197 | + |
| 198 | +## Dry Run Mode |
| 199 | + |
| 200 | +To see what changes would be made without modifying DNS: |
| 201 | + |
| 202 | +```bash |
| 203 | +dreamhost-ddns --dry-run |
90 | 204 | ``` |
91 | 205 |
|
92 | | -### Crontab recommendation |
93 | | -It is recommended to pass a configuration file parameter when defining the crontab entry even if you are using the default of config.toml. It is also recommended to set a sane yet aggressive scheduling interval. For example, this would run every 10 minutes: |
94 | | -```cron |
95 | | -*/10 * * * * /opt/dreamhost-ddns --config /opt/config.toml >> /var/log/dreamhost-ddns.log 2>&1 |
| 206 | +--- |
| 207 | + |
| 208 | +## Building |
| 209 | + |
| 210 | +Clone the repository and build using Cargo: |
| 211 | + |
| 212 | +```bash |
| 213 | +git clone https://github.com/PRProd/dreamhost-ddns |
| 214 | +cd dreamhost-ddns |
| 215 | +cargo build --release |
96 | 216 | ``` |
97 | 217 |
|
98 | | -<br><br> |
99 | | -## Under the Hood |
100 | | -### TL/DR: |
101 | | -At the highest level, this is the execution flow: |
102 | | -```txt |
103 | | -Detect WAN IP |
104 | | - │ |
105 | | - ▼ |
106 | | -Get current DNS record IP |
107 | | - │ |
108 | | - ▼ |
109 | | -Compare values |
110 | | - │ |
111 | | - ├─ same → exit |
112 | | - │ |
113 | | - └─ different |
114 | | - │ |
115 | | - ▼ |
116 | | - Safely update DNS |
117 | | -``` |
118 | | -<br><br> |
119 | | -### Basic execution is as follows: |
120 | | -```txt |
121 | | -main() |
122 | | -│ |
123 | | -├─ parse CLI args |
124 | | -│ |
125 | | -├─ configure logging |
126 | | -│ |
127 | | -├─ resolve configuration |
128 | | -│ ├─ CLI args |
129 | | -│ ├─ env vars |
130 | | -│ ├─ config file |
131 | | -│ └─ default config.toml |
132 | | -│ |
133 | | -├─ build HTTP client |
134 | | -│ |
135 | | -├─ create DreamhostClient |
136 | | -│ |
137 | | -├─ detect WAN IP |
138 | | -│ └─ parallel IP service queries |
139 | | -│ |
140 | | -├─ fetch DNS record IP |
141 | | -│ └─ DreamHost API |
142 | | -│ |
143 | | -├─ compare WAN vs DNS |
144 | | -│ │ |
145 | | -│ ├─ match → exit |
146 | | -│ │ |
147 | | -│ └─ mismatch |
148 | | -│ │ |
149 | | -│ ├─ dry-run → log only |
150 | | -│ │ |
151 | | -│ └─ update_dns() |
152 | | -│ |
153 | | -└─ exit |
154 | | -``` |
155 | | -<br><br> |
156 | | - |
157 | | -### Detecting WAN IP |
158 | | -Multiple services are queried simultaneously to determine your WAN IP. If your firewall is blocking all of these services, the application will fail: |
159 | | - - https://icanhazip.com |
160 | | - - https://api.ipify.org |
161 | | - - https://ifconfig.me/ip |
162 | | - - https://checkip.amazonaws.com |
163 | | - |
164 | | -This is how those are used |
165 | | -```txt |
166 | | -get_wan_ip() |
167 | | -│ |
168 | | -├─ shuffle service list |
169 | | -│ |
170 | | -├─ spawn parallel threads |
171 | | -│ |
172 | | -├─ query IP services |
173 | | -│ |
174 | | -├─ first successful response wins |
175 | | -│ |
176 | | -└─ cancel remaining workers |
177 | | -``` |
178 | | -<br><br> |
179 | | - |
180 | | -### Updating DNS |
181 | | -In the best case scenario, three API calls are made to update the DNS record. For safety reasons, a verification is done before the old DNS record is removed. If the verification fails, it is retried up to five times which involves one additional API call each time. |
182 | | -```txt |
183 | | -update_dns() |
184 | | -│ |
185 | | -├─ add new DNS record |
186 | | -│ |
187 | | -├─ wait for propagation |
188 | | -│ |
189 | | -├─ verify new record exists |
190 | | -│ │ |
191 | | -│ ├─ retry up to 5 times |
192 | | -│ │ |
193 | | -│ └─ fail → abort update |
194 | | -│ |
195 | | -└─ remove old DNS record |
| 218 | +The compiled binary will be located at: |
| 219 | + |
196 | 220 | ``` |
| 221 | +target/release/dreamhost-ddns |
| 222 | +``` |
| 223 | + |
| 224 | +--- |
| 225 | + |
| 226 | +## Home Assistant Add-on |
| 227 | + |
| 228 | +This tool also powers a Home Assistant add-on: |
| 229 | + |
| 230 | +https://github.com/PRProd/home-assistant-addon-dreamhost-ddns |
| 231 | + |
| 232 | +The add-on wraps this binary and allows configuration directly from the Home Assistant UI. |
| 233 | + |
0 commit comments