feat: Add smart JSON storage and intelligent config checking v1.2.0
- Add automatic JSON file creation when none exists - Implement IP presence checking before config updates - Only update existing entries, never add new ones automatically - Enhanced Apache support with proper directive detection - Improved debug logging and error handling - Non-intrusive behavior preserves existing configurations
This commit is contained in:
@@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.2.0] - 2025-10-03
|
||||
|
||||
### Added
|
||||
- **Smart JSON Storage**: Automatic creation of IP tracking files when none exist
|
||||
- Creates JSON files with hostname, IP, and timestamps automatically
|
||||
- Prevents duplicate work by storing resolved IP addresses
|
||||
- **Intelligent Config Checking**: Pre-update verification of IP presence in config files
|
||||
- Checks if current IP already exists in nginx/apache configurations
|
||||
- Avoids unnecessary updates when IP is already correctly configured
|
||||
- **Non-Intrusive Behavior**: Enhanced update logic for better reliability
|
||||
- Only updates existing allow/deny entries, never adds new ones automatically
|
||||
- Provides clear feedback when no changes are needed
|
||||
- **Enhanced Apache Support**: Extended IP checking for Apache configurations
|
||||
- Supports both "Allow from" and "Require ip" directive formats
|
||||
- Consistent behavior between nginx and Apache handlers
|
||||
|
||||
### Changed
|
||||
- **Update Logic**: Improved decision-making process for config updates
|
||||
- Better handling of first-run scenarios without existing JSON files
|
||||
- More granular control over when configuration changes occur
|
||||
- **Debug Logging**: Enhanced debugging output for troubleshooting
|
||||
- Clearer messages about JSON file creation and IP checking
|
||||
- Better visibility into decision-making process
|
||||
|
||||
## [1.1.9] - 2025-01-03
|
||||
|
||||
### Fixed
|
||||
|
||||
Generated
+1
-1
@@ -291,7 +291,7 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "ddns_updater"
|
||||
version = "1.1.9"
|
||||
version = "1.2.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ddns_updater"
|
||||
version = "1.1.9"
|
||||
version = "1.2.0"
|
||||
edition = "2021" # Use 2021 edition for better compatibility
|
||||
default-run = "ddns_updater"
|
||||
rust-version = "1.82" # Specify minimum Rust version for Debian 12
|
||||
|
||||
@@ -10,6 +10,9 @@ A Rust-based Dynamic DNS (DDNS) updater that automatically manages nginx allow l
|
||||
## Features
|
||||
|
||||
- **Automatic IP Detection**: Monitors your public IP address changes
|
||||
- **Smart JSON Storage**: Automatically creates and manages IP tracking files
|
||||
- **Intelligent Config Checking**: Verifies IP presence before making changes
|
||||
- **Non-Intrusive Updates**: Only updates existing entries, never adds new ones
|
||||
- **Multiple Config Support**: Process single files or entire directories
|
||||
- **Smart Cleanup**: Removes old duplicate IP entries for the same host
|
||||
- **Nginx Integration**: Automatically validates and reloads nginx configurations
|
||||
|
||||
@@ -55,6 +55,12 @@ pub trait WebServerHandler: Send + Sync {
|
||||
config: &WebServerConfig,
|
||||
) -> Result<bool, Box<dyn std::error::Error + Send + Sync>>;
|
||||
|
||||
async fn check_ip_in_config(
|
||||
&self,
|
||||
config: &WebServerConfig,
|
||||
ip: IpAddr,
|
||||
) -> Result<bool, Box<dyn std::error::Error + Send + Sync>>;
|
||||
|
||||
fn server_type(&self) -> crate::domain::entities::WebServerType;
|
||||
}
|
||||
|
||||
|
||||
+33
-4
@@ -70,11 +70,40 @@ impl DdnsUpdateService {
|
||||
let stored_ip = self.ip_repository.load_ip(hostname).await?;
|
||||
eprintln!("DEBUG: Stored IP: {:?}", stored_ip);
|
||||
|
||||
// Check if IP has changed
|
||||
if let Some(old_ip) = stored_ip {
|
||||
if old_ip == current_ip {
|
||||
eprintln!("DEBUG: IP unchanged, returning NoChange");
|
||||
// If no JSON file exists, create it with current IP and check config
|
||||
if stored_ip.is_none() {
|
||||
eprintln!("DEBUG: No stored IP found, creating JSON file and checking config");
|
||||
|
||||
// Store the current IP in JSON file
|
||||
self.ip_repository.store_ip(hostname, current_ip).await?;
|
||||
eprintln!("DEBUG: Created new JSON file with IP: {}", current_ip);
|
||||
|
||||
// Check if the current IP is already in the config file
|
||||
let ip_in_config = self
|
||||
.web_server_handler
|
||||
.check_ip_in_config(config, current_ip)
|
||||
.await?;
|
||||
|
||||
if ip_in_config {
|
||||
eprintln!(
|
||||
"DEBUG: IP {} already exists in config, no update needed",
|
||||
current_ip
|
||||
);
|
||||
return Ok(UpdateResult::NoChange { ip: current_ip });
|
||||
} else {
|
||||
eprintln!(
|
||||
"DEBUG: IP {} not found in config, but not adding new entries",
|
||||
current_ip
|
||||
);
|
||||
return Ok(UpdateResult::NoChange { ip: current_ip });
|
||||
}
|
||||
} else {
|
||||
// Check if IP has changed
|
||||
if let Some(old_ip) = stored_ip {
|
||||
if old_ip == current_ip {
|
||||
eprintln!("DEBUG: IP unchanged, returning NoChange");
|
||||
return Ok(UpdateResult::NoChange { ip: current_ip });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -166,6 +166,33 @@ impl WebServerHandler for ApacheHandler {
|
||||
self.validate_config(config).await
|
||||
}
|
||||
|
||||
async fn check_ip_in_config(
|
||||
&self,
|
||||
config: &WebServerConfig,
|
||||
ip: IpAddr,
|
||||
) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let content = fs::read_to_string(&config.path).await?;
|
||||
let ip_str = ip.to_string();
|
||||
|
||||
// Check if the IP exists in any Allow directive (Apache format)
|
||||
for line in content.lines() {
|
||||
let trimmed = line.trim();
|
||||
// Apache uses "Allow from" or "Require ip" directives
|
||||
if (trimmed.starts_with("Allow from ") || trimmed.starts_with("Require ip "))
|
||||
&& trimmed.contains(&ip_str)
|
||||
{
|
||||
eprintln!(
|
||||
"DEBUG: Found IP {} in Apache config line: {}",
|
||||
ip_str, trimmed
|
||||
);
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
eprintln!("DEBUG: IP {} not found in Apache config file", ip_str);
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn server_type(&self) -> WebServerType {
|
||||
WebServerType::Apache
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use tokio::fs;
|
||||
|
||||
use crate::domain::entities::{WebServerConfig, WebServerType};
|
||||
use crate::domain::entities::WebServerConfig;
|
||||
use crate::domain::ports::WebServerHandler;
|
||||
|
||||
/// Nginx web server handler
|
||||
@@ -117,9 +117,6 @@ impl NginxHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// Important: Do NOT add new entries if none existed before
|
||||
// This ensures we only update existing entries
|
||||
|
||||
if updated {
|
||||
let new_content = lines.join("\n");
|
||||
eprintln!("DEBUG: About to write to config file: {:?}", config_path);
|
||||
@@ -242,8 +239,29 @@ impl WebServerHandler for NginxHandler {
|
||||
self.validate_config(config).await
|
||||
}
|
||||
|
||||
fn server_type(&self) -> WebServerType {
|
||||
WebServerType::Nginx
|
||||
async fn check_ip_in_config(
|
||||
&self,
|
||||
config: &WebServerConfig,
|
||||
ip: IpAddr,
|
||||
) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let content = fs::read_to_string(&config.path).await?;
|
||||
let ip_str = ip.to_string();
|
||||
|
||||
// Check if the IP exists in any allow directive
|
||||
for line in content.lines() {
|
||||
let trimmed = line.trim();
|
||||
if trimmed.starts_with("allow ") && trimmed.contains(&ip_str) {
|
||||
eprintln!("DEBUG: Found IP {} in config line: {}", ip_str, trimmed);
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
eprintln!("DEBUG: IP {} not found in config file", ip_str);
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn server_type(&self) -> crate::domain::entities::WebServerType {
|
||||
crate::domain::entities::WebServerType::Nginx
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name example.com;
|
||||
|
||||
location / {
|
||||
root /var/www/html;
|
||||
index index.html;
|
||||
allow 192.168.1.1;
|
||||
allow 142.250.102.138;
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name example.com;
|
||||
|
||||
location / {
|
||||
root /var/www/html;
|
||||
index index.html;
|
||||
allow 192.168.1.1;
|
||||
allow 142.250.102.138;
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name example.com;
|
||||
|
||||
location / {
|
||||
root /var/www/html;
|
||||
index index.html;
|
||||
allow 192.168.1.1;
|
||||
allow 142.250.102.138;
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name example.com;
|
||||
|
||||
location / {
|
||||
root /var/www/html;
|
||||
index index.html;
|
||||
allow 192.168.1.1;
|
||||
allow 142.250.102.138;
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name example.com;
|
||||
|
||||
location / {
|
||||
root /var/www/html;
|
||||
index index.html;
|
||||
allow 192.168.1.1;
|
||||
allow 142.250.102.138;
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
upstream backend {
|
||||
server 127.0.0.1:3000;
|
||||
server 127.0.0.1:3001;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name api.example.com;
|
||||
|
||||
ssl_certificate /etc/ssl/certs/example.crt;
|
||||
ssl_certificate_key /etc/ssl/private/example.key;
|
||||
|
||||
location /api {
|
||||
proxy_pass http://backend;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
allow 10.0.0.0/8;
|
||||
allow 142.250.102.101;
|
||||
deny all;
|
||||
}
|
||||
|
||||
location /health {
|
||||
access_log off;
|
||||
return 200 "OK";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
upstream backend {
|
||||
server 127.0.0.1:3000;
|
||||
server 127.0.0.1:3001;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name api.example.com;
|
||||
|
||||
ssl_certificate /etc/ssl/certs/example.crt;
|
||||
ssl_certificate_key /etc/ssl/private/example.key;
|
||||
|
||||
location /api {
|
||||
proxy_pass http://backend;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
allow 10.0.0.0/8;
|
||||
allow 142.250.102.101;
|
||||
deny all;
|
||||
}
|
||||
|
||||
location /health {
|
||||
access_log off;
|
||||
return 200 "OK";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
upstream backend {
|
||||
server 127.0.0.1:3000;
|
||||
server 127.0.0.1:3001;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name api.example.com;
|
||||
|
||||
ssl_certificate /etc/ssl/certs/example.crt;
|
||||
ssl_certificate_key /etc/ssl/private/example.key;
|
||||
|
||||
location /api {
|
||||
proxy_pass http://backend;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
allow 10.0.0.0/8;
|
||||
allow 142.250.102.101;
|
||||
deny all;
|
||||
}
|
||||
|
||||
location /health {
|
||||
access_log off;
|
||||
return 200 "OK";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
upstream backend {
|
||||
server 127.0.0.1:3000;
|
||||
server 127.0.0.1:3001;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name api.example.com;
|
||||
|
||||
ssl_certificate /etc/ssl/certs/example.crt;
|
||||
ssl_certificate_key /etc/ssl/private/example.key;
|
||||
|
||||
location /api {
|
||||
proxy_pass http://backend;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
allow 10.0.0.0/8;
|
||||
allow 142.250.102.101;
|
||||
deny all;
|
||||
}
|
||||
|
||||
location /health {
|
||||
access_log off;
|
||||
return 200 "OK";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
gzip on;
|
||||
gzip_types text/plain application/json;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name www.example.com;
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name www.example.com;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
allow 203.0.113.0/24;
|
||||
allow 142.250.102.139;
|
||||
deny all;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi_params;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
gzip on;
|
||||
gzip_types text/plain application/json;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name www.example.com;
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name www.example.com;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
allow 203.0.113.0/24;
|
||||
allow 142.250.102.139;
|
||||
deny all;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi_params;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
gzip on;
|
||||
gzip_types text/plain application/json;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name www.example.com;
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name www.example.com;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
allow 203.0.113.0/24;
|
||||
allow 142.250.102.139;
|
||||
deny all;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi_params;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
gzip on;
|
||||
gzip_types text/plain application/json;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name www.example.com;
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name www.example.com;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
allow 203.0.113.0/24;
|
||||
allow 142.250.102.139;
|
||||
deny all;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi_params;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
server {
|
||||
allow 23.192.228.80;
|
||||
allow 142.250.102.113;
|
||||
listen 80;
|
||||
server_name minimal.example.com;
|
||||
|
||||
location / {
|
||||
return 200 "Hello World";
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
server {
|
||||
allow 23.192.228.80;
|
||||
allow 142.250.102.113;
|
||||
listen 80;
|
||||
server_name minimal.example.com;
|
||||
|
||||
location / {
|
||||
return 200 "Hello World";
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
server {
|
||||
allow 23.192.228.80;
|
||||
allow 142.250.102.113;
|
||||
listen 80;
|
||||
server_name minimal.example.com;
|
||||
|
||||
location / {
|
||||
return 200 "Hello World";
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
server {
|
||||
allow 23.192.228.80;
|
||||
allow 142.250.102.113;
|
||||
listen 80;
|
||||
server_name minimal.example.com;
|
||||
|
||||
location / {
|
||||
return 200 "Hello World";
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name example.com;
|
||||
|
||||
location / {
|
||||
root /var/www/html;
|
||||
index index.html;
|
||||
allow 192.168.1.0/24;
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name example.com;
|
||||
|
||||
location / {
|
||||
root /var/www/html;
|
||||
index index.html;
|
||||
allow 192.168.1.0/24;
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name example.com;
|
||||
|
||||
location / {
|
||||
root /var/www/html;
|
||||
index index.html;
|
||||
allow 192.168.1.0/24;
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name example.com;
|
||||
|
||||
location / {
|
||||
root /var/www/html;
|
||||
index index.html;
|
||||
allow 192.168.1.0/24;
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user