Feat: main application logic

This commit is contained in:
2026-06-01 20:38:27 +02:00
parent f61baf53c8
commit fcb9d0e3b0
+143
View File
@@ -0,0 +1,143 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <curl/curl.h>
#include "config.h"
#include "gandi.h"
#include "ipcheck.h"
#include "log.h"
#include "selfcheck.h"
static volatile sig_atomic_t g_running = 1;
static void handle_signal(int sig) {
(void)sig;
g_running = 0;
}
int main(void) {
struct sigaction sa = {0};
sa.sa_handler = handle_signal;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
Config cfg = {0};
if (config_load(&cfg) != 0) return EXIT_FAILURE;
log_init();
curl_global_init(CURL_GLOBAL_DEFAULT);
int ret = EXIT_SUCCESS;
if (gandi_init(cfg.api_token) != 0) {
log_error("gandi_init failed");
ret = EXIT_FAILURE;
goto cleanup;
}
char cached_ip[GANDI_MAX_IP_LEN] = {0};
if (gandi_get_self_ip(cfg.dns_keeper_domain, cfg.dns_keeper_record, cached_ip) != 0) {
log_error("Failed to fetch initial IP from self record %s.%s",
cfg.dns_keeper_record, cfg.dns_keeper_domain);
ret = EXIT_FAILURE;
goto cleanup;
}
log_info("Self record points to %s", cached_ip);
char self_fqdn[256];
snprintf(self_fqdn, sizeof(self_fqdn), "%s.%s", cfg.dns_keeper_record, cfg.dns_keeper_domain);
if (selfcheck_start_listener(cfg.dns_keeper_port) != 0) {
log_error("Failed to start self-check listener on port %d", cfg.dns_keeper_port);
ret = EXIT_FAILURE;
goto cleanup;
}
log_info("Listening on UDP port %d", cfg.dns_keeper_port);
int fail_count = 0; /* consecutive self-check failures */
int drift_count = 0; /* when external IP matches cache but self-check fails */
while (g_running) {
if (selfcheck_domain(self_fqdn, cfg.dns_keeper_port) == 0) {
fail_count = 0;
drift_count = 0;
sleep(cfg.poll_interval);
continue;
}
fail_count++;
if (fail_count < SELFCHECK_FAIL_THRESHOLD) {
sleep(cfg.poll_interval);
continue;
}
fail_count = 0;
char public_ip[IPCHECK_MAX_IP_LEN] = {0};
if (ipcheck_get_public_ip(public_ip) != 0) {
log_warn("ipcheck: failed to determine public IP, skipping cycle");
sleep(cfg.poll_interval);
continue;
}
if (strcmp(public_ip, cached_ip) == 0) {
drift_count++;
if (drift_count >= SELFCHECK_DRIFT_THRESHOLD) {
char gandi_ip[GANDI_MAX_IP_LEN] = {0};
if (gandi_get_self_ip(cfg.dns_keeper_domain, cfg.dns_keeper_record, gandi_ip) == 0
&& strcmp(gandi_ip, cached_ip) != 0) {
log_info("Cache drift: cached %s but Gandi has %s, resyncing",
cached_ip, gandi_ip);
strncpy(cached_ip, gandi_ip, GANDI_MAX_IP_LEN - 1);
cached_ip[GANDI_MAX_IP_LEN - 1] = '\0';
}
drift_count = 0;
}
sleep(SELFCHECK_COOLDOWN_S);
continue;
}
drift_count = 0;
log_info("IP change detected: %s -> %s", cached_ip, public_ip);
if (selfcheck_ip_str(public_ip, cfg.dns_keeper_port) != 0) {
log_warn("Direct check of %s failed, deferring update", public_ip);
sleep(SELFCHECK_COOLDOWN_S);
continue;
}
int failed_updates = 0;
for (int i = 0; i < cfg.domain_count; i++) {
if (strcmp(cfg.domains[i], cfg.dns_keeper_domain) == 0) continue;
int gret = gandi_update_domains(cfg.domains[i], cached_ip, public_ip);
if (gret == -3) {
log_info("gandi: [%s] no matching A records for %s, skipping",
cfg.domains[i], cached_ip);
} else if (gret != 0) {
failed_updates = 1;
}
}
int sret = gandi_update_record(cfg.dns_keeper_domain, cfg.dns_keeper_record,
cached_ip, public_ip);
if (sret == 0) {
strncpy(cached_ip, public_ip, GANDI_MAX_IP_LEN - 1);
cached_ip[GANDI_MAX_IP_LEN - 1] = '\0';
} else {
log_error("Failed to update self record, cached IP not advanced");
failed_updates = 1;
}
if (failed_updates) log_error("One or more updates failed this cycle");
sleep(SELFCHECK_COOLDOWN_S);
}
log_info("Shutting down");
cleanup:
curl_global_cleanup();
config_free(&cfg);
return ret;
}