feat: add IPV4_DOMAIN config for dual-stack IP detection

This commit is contained in:
NetworkCats
2026-03-11 04:33:01 +08:00
parent 5565f4a0a5
commit c876d17e8c
6 changed files with 40 additions and 1 deletions
+9 -1
View File
@@ -13,9 +13,17 @@ DB_UPDATE_INTERVAL_HOURS=24
# Site domain name (used for display/metadata)
SITE_DOMAIN=localhost
# IPv4-only domain for dual-stack detection (e.g., noipv6.org)
# Leave empty to disable IPv4 detection for IPv6 users
IPV4_DOMAIN=
# Enable development mode (uses 1.1.1.1 as fallback when CF-Connecting-IP is absent)
DEV_MODE=false
# Path to the TLS certificate directory for HAproxy
# Path to the TLS certificate directory for HAProxy (main domain)
# The directory must contain origin.pem (combined cert + key)
CERT_PATH=./haproxy/certs
# Path to the TLS certificate directory for the IPv4-only domain
# The directory must contain origin.pem (combined cert + key)
IPV4_CERT_PATH=./haproxy/certs-ipv4
+2
View File
@@ -6,6 +6,7 @@ services:
volumes:
- ./haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
- ${CERT_PATH:-./haproxy/certs}:/etc/haproxy/certs:ro
- ${IPV4_CERT_PATH:-./haproxy/certs-ipv4}:/etc/haproxy/certs-ipv4:ro
depends_on:
app:
condition: service_healthy
@@ -21,6 +22,7 @@ services:
- DB_UPDATE_URL=${DB_UPDATE_URL:-https://github.com/NetworkCats/Merged-IP-Data/releases/latest/download/Merged-IP.mmdb}
- DB_UPDATE_INTERVAL_HOURS=${DB_UPDATE_INTERVAL_HOURS:-24}
- SITE_DOMAIN=${SITE_DOMAIN:-localhost}
- IPV4_DOMAIN=${IPV4_DOMAIN:-}
volumes:
- db_data:/data
healthcheck:
+22
View File
@@ -7,6 +7,7 @@ pub struct Config {
pub db_update_url: String,
pub db_update_interval_hours: u64,
pub site_domain: String,
pub ipv4_domain: String,
pub dev_mode: bool,
}
@@ -31,6 +32,8 @@ impl Config {
let site_domain = env::var("SITE_DOMAIN").unwrap_or_else(|_| "localhost".to_string());
let ipv4_domain = env::var("IPV4_DOMAIN").unwrap_or_default();
let dev_mode = env::var("DEV_MODE")
.unwrap_or_default()
.eq_ignore_ascii_case("true");
@@ -41,6 +44,7 @@ impl Config {
db_update_url,
db_update_interval_hours,
site_domain,
ipv4_domain,
dev_mode,
}
}
@@ -67,6 +71,7 @@ mod tests {
"DB_UPDATE_URL",
"DB_UPDATE_INTERVAL_HOURS",
"SITE_DOMAIN",
"IPV4_DOMAIN",
"DEV_MODE",
];
@@ -98,6 +103,7 @@ mod tests {
assert!(config.db_update_url.contains("Merged-IP.mmdb"));
assert_eq!(config.db_update_interval_hours, 24);
assert_eq!(config.site_domain, "localhost");
assert!(config.ipv4_domain.is_empty());
assert!(!config.dev_mode);
}
@@ -181,6 +187,22 @@ mod tests {
unsafe { remove_var("SITE_DOMAIN") };
}
#[test]
fn custom_ipv4_domain() {
let _guard = lock_env();
// SAFETY: ENV_LOCK is held.
unsafe {
clear_config_vars();
set_var("IPV4_DOMAIN", "noipv6.org");
}
let config = Config::from_env();
assert_eq!(config.ipv4_domain, "noipv6.org");
// SAFETY: ENV_LOCK is held.
unsafe { remove_var("IPV4_DOMAIN") };
}
#[test]
fn dev_mode_enabled() {
let _guard = lock_env();
+3
View File
@@ -25,6 +25,7 @@ const DEV_FALLBACK_IP: IpAddr = IpAddr::V4(std::net::Ipv4Addr::new(1, 1, 1, 1));
pub struct AppState {
pub db: SharedDb,
pub site_domain: Arc<str>,
pub ipv4_domain: Arc<str>,
pub dev_mode: bool,
pub openapi_json: Arc<str>,
}
@@ -36,6 +37,7 @@ struct IndexTemplate {
query: String,
is_query: bool,
site_domain: Arc<str>,
ipv4_domain: Arc<str>,
css_version: u64,
js_version: u64,
asn: String,
@@ -571,6 +573,7 @@ pub async fn root(
query: query.ip.unwrap_or_default(),
is_query,
site_domain: state.site_domain.clone(),
ipv4_domain: state.ipv4_domain.clone(),
css_version: static_assets::asset_version("style.css"),
js_version: static_assets::asset_version("script.js"),
asn: format_asn(&info),
+2
View File
@@ -39,11 +39,13 @@ async fn main() {
let cancel = CancellationToken::new();
let site_domain: std::sync::Arc<str> = config.site_domain.into();
let ipv4_domain: std::sync::Arc<str> = config.ipv4_domain.into();
let openapi_json = handlers::build_openapi_json(&site_domain);
let state = handlers::AppState {
db: shared_db.clone(),
site_domain,
ipv4_domain,
dev_mode: config.dev_mode,
openapi_json,
};
+2
View File
@@ -26,9 +26,11 @@ fn build_test_app_with_dev_mode(dev_mode: bool) -> axum::Router {
let shared_db = db::new_shared(reader);
let site_domain: std::sync::Arc<str> = "test.example.com".into();
let openapi_json = build_openapi_json(&site_domain);
let ipv4_domain: std::sync::Arc<str> = "noipv6.test.example.com".into();
let state = AppState {
db: shared_db,
site_domain,
ipv4_domain,
dev_mode,
openapi_json,
};