From a603b4287e059705fb1b48d85bc3fccacad16669 Mon Sep 17 00:00:00 2001 From: jheaps Date: Mon, 4 Aug 2025 14:26:45 -0600 Subject: [PATCH] Move ip update to hosted service --- JoshHeaps.Net/Controllers/DebugController.cs | 5 +- JoshHeaps.Net/Program.cs | 200 ++++-------------- .../Implementations/AutoIpUpdateService.cs | 105 +++++++++ 3 files changed, 154 insertions(+), 156 deletions(-) create mode 100644 JoshHeaps.Net/Services/Implementations/AutoIpUpdateService.cs diff --git a/JoshHeaps.Net/Controllers/DebugController.cs b/JoshHeaps.Net/Controllers/DebugController.cs index 9ecd387..b60d0c7 100644 --- a/JoshHeaps.Net/Controllers/DebugController.cs +++ b/JoshHeaps.Net/Controllers/DebugController.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Mvc; +using JoshHeaps.Net.Services.Implementations; +using Microsoft.AspNetCore.Mvc; namespace JoshHeaps.Net.Controllers; @@ -9,6 +10,6 @@ public class DebugController : ControllerBase [HttpGet("IpCheck")] public ActionResult GetIpCheckingStatus() { - return Ok(Program.CheckingForIpUpdates.ToString()); + return Ok(AutoIpUpdateService.IsEnabled.ToString()); } } diff --git a/JoshHeaps.Net/Program.cs b/JoshHeaps.Net/Program.cs index c430c45..23a8be6 100644 --- a/JoshHeaps.Net/Program.cs +++ b/JoshHeaps.Net/Program.cs @@ -2,160 +2,52 @@ using JoshHeaps.Net.Hubs; using JoshHeaps.Net.Services.Implementations; using JoshHeaps.Net.Services.Interfaces; -namespace JoshHeaps.Net; +var builder = WebApplication.CreateBuilder(args); -// need to declare the class so I can add the public static bool -public class Program +// Add services to the container. +builder.Services.AddRazorPages(); +var configuration = builder.Configuration; + +builder.Services.AddControllers(); + +builder.Services.AddSignalR(); + +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); + +if (!builder.Environment.IsDevelopment()) + builder.Services.AddHostedService(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (!app.Environment.IsDevelopment()) { - public static bool CheckingForIpUpdates { get; private set; } = false; - - public static void Main(string[] args) - { - var builder = WebApplication.CreateBuilder(args); - - // Add services to the container. - builder.Services.AddRazorPages(); - var configuration = builder.Configuration; - Task updateIpTask; - - if (!builder.Environment.IsDevelopment()) - updateIpTask = Run(configuration); - - builder.Services.AddControllers(); - - builder.Services.AddSignalR(); - - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - - var app = builder.Build(); - - // Configure the HTTP request pipeline. - if (!app.Environment.IsDevelopment()) - { - app.UseExceptionHandler("/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); - } - - app.UseHttpsRedirection(); - - app.UseStaticFiles(new StaticFileOptions - { - OnPrepareResponse = ctx => - { - ctx.Context.Response.Headers.Append("Cache-Control", "no-cache, no-store, must-revalidate"); - ctx.Context.Response.Headers.Append("Pragma", "no-cache"); - ctx.Context.Response.Headers.Append("Expires", "0"); - } - }); - - app.UseRouting(); - - app.UseAuthorization(); - - app.MapRazorPages(); - - app.MapControllers(); - - app.MapHub("/chessHub"); - - app.Run(); - } - - static async Task Run(IConfiguration config) - { - CheckingForIpUpdates = true; - HttpClient httpClient = new(); - AAAARecord dnsRecord = await GetDnsRecordAsync(config); - string lastKnownIp = dnsRecord.content; - TimeSpan checkInterval = TimeSpan.FromMinutes(1); - - while (true) - { - try - { - string currentIp = await GetPublicIpAsync(httpClient) ?? ""; - - if (lastKnownIp != currentIp) - { - await UpdateDnsIpAsync(config, dnsRecord, currentIp); - - lastKnownIp = currentIp; - } - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - - await Task.Delay(checkInterval); - } - } - - static async Task GetPublicIpAsync(HttpClient client) - { - try - { - return await client.GetStringAsync(@"https://api.ipify.org/"); - } - catch - { - await Task.Delay(TimeSpan.FromSeconds(10)); - Console.WriteLine("Reattempting to grab public ip"); - - return await GetPublicIpAsync(client); - } - } - - static async Task GetDnsRecordAsync(IConfiguration config) - { - try - { - HttpClient cfClient = new(); - cfClient.DefaultRequestHeaders.Add("X-Auth-Email", config["cfEmail"]); - cfClient.DefaultRequestHeaders.Add("X-Auth-Key", config["cfKey"]); - var result = await cfClient.GetAsync(@$"https://api.cloudflare.com/client/v4/zones/{config["zoneId"]}/dns_records"); - Console.WriteLine(await result.Content.ReadAsStringAsync()); - var records = System.Text.Json.JsonSerializer.Deserialize(await result.Content.ReadAsStringAsync()); - return records!.result[0]; - } - catch - { - await Task.Delay(TimeSpan.FromSeconds(10)); - Console.WriteLine("Reattempting to grab current ip"); - - return await GetDnsRecordAsync(config); - } - } - - static async Task UpdateDnsIpAsync(IConfiguration config, AAAARecord record, string ip) - { - HttpClient cfClient = new(); - cfClient.DefaultRequestHeaders.Add("X-Auth-Email", config["cfEmail"]); - cfClient.DefaultRequestHeaders.Add("X-Auth-Key", config["cfKey"]); - object content = new - { - comment = "Update as needed", - content = ip, - name = "@", - proxied = true, - ttl = 3600, - type = "AAAA" - }; - - var result = await cfClient.PutAsJsonAsync(@$"https://api.cloudflare.com/client/v4/zones/{config["zoneId"]}/dns_records{record.id}", content); - - if (!result.IsSuccessStatusCode) - { - await Task.Delay(TimeSpan.FromSeconds(10)); - Console.WriteLine("Reattempting to update ip"); - - await UpdateDnsIpAsync(config, record, ip); - } - } - - record AAAARecord(string comment, string content, string name, string id); - - record RecordList(List result); + app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); } + +app.UseHttpsRedirection(); + +app.UseStaticFiles(new StaticFileOptions +{ + OnPrepareResponse = ctx => + { + ctx.Context.Response.Headers.Append("Cache-Control", "no-cache, no-store, must-revalidate"); + ctx.Context.Response.Headers.Append("Pragma", "no-cache"); + ctx.Context.Response.Headers.Append("Expires", "0"); + } +}); + +app.UseRouting(); + +app.UseAuthorization(); + +app.MapRazorPages(); + +app.MapControllers(); + +app.MapHub("/chessHub"); + +app.Run(); \ No newline at end of file diff --git a/JoshHeaps.Net/Services/Implementations/AutoIpUpdateService.cs b/JoshHeaps.Net/Services/Implementations/AutoIpUpdateService.cs new file mode 100644 index 0000000..21bec54 --- /dev/null +++ b/JoshHeaps.Net/Services/Implementations/AutoIpUpdateService.cs @@ -0,0 +1,105 @@ +namespace JoshHeaps.Net.Services.Implementations; + +public class AutoIpUpdateService( + IConfiguration config, + ILogger log) + : BackgroundService +{ + private static readonly TimeSpan CheckInterval = TimeSpan.FromMinutes(5); + private static readonly HttpClient httpClient = new(); + public static bool IsEnabled { get; private set; } = false; + + protected override async Task ExecuteAsync(CancellationToken stop) + { + IsEnabled = true; + var timer = new PeriodicTimer(CheckInterval); + AAAARecord dnsRecord = await GetDnsRecordAsync(); + string lastKnownIp = dnsRecord.Content; + + while (await timer.WaitForNextTickAsync(stop)) + { + try + { + string currentIp = await GetPublicIpAsync() ?? ""; + + if (lastKnownIp != currentIp) + { + await UpdateDnsIpAsync(config, dnsRecord, currentIp); + + lastKnownIp = currentIp; + } + } + catch (OperationCanceledException) { /* shutting down */ } + catch (Exception ex) + { + log.LogError(ex, "Error while attempting ip update"); + } + } + } + + private static async Task GetPublicIpAsync() + { + try + { + return await httpClient.GetStringAsync(@"https://api.ipify.org/"); + } + catch + { + await Task.Delay(TimeSpan.FromSeconds(10)); + Console.WriteLine("Reattempting to grab public ip"); + + return await GetPublicIpAsync(); + } + } + + private async Task GetDnsRecordAsync() + { + try + { + HttpClient cfClient = new(); + cfClient.DefaultRequestHeaders.Add("X-Auth-Email", config["cfEmail"]); + cfClient.DefaultRequestHeaders.Add("X-Auth-Key", config["cfKey"]); + var result = await cfClient.GetAsync(@$"https://api.cloudflare.com/client/v4/zones/{config["zoneId"]}/dns_records"); + Console.WriteLine(await result.Content.ReadAsStringAsync()); + var records = System.Text.Json.JsonSerializer.Deserialize(await result.Content.ReadAsStringAsync()); + return records!.Result[0]; + } + catch + { + await Task.Delay(TimeSpan.FromSeconds(10)); + Console.WriteLine("Reattempting to grab current ip"); + + return await GetDnsRecordAsync(); + } + } + + private static async Task UpdateDnsIpAsync(IConfiguration config, AAAARecord record, string ip) + { + HttpClient cfClient = new(); + cfClient.DefaultRequestHeaders.Add("X-Auth-Email", config["cfEmail"]); + cfClient.DefaultRequestHeaders.Add("X-Auth-Key", config["cfKey"]); + object content = new + { + comment = "Update as needed", + content = ip, + name = "@", + proxied = true, + ttl = 3600, + type = "AAAA" + }; + + var result = await cfClient.PutAsJsonAsync(@$"https://api.cloudflare.com/client/v4/zones/{config["zoneId"]}/dns_records{record.Id}", content); + + if (!result.IsSuccessStatusCode) + { + await Task.Delay(TimeSpan.FromSeconds(10)); + Console.WriteLine("Reattempting to update ip"); + + await UpdateDnsIpAsync(config, record, ip); + } + } + + record AAAARecord(string Comment, string Content, string Name, string Id); + + record RecordList(List Result); +}