diff --git a/.gitignore b/.gitignore index 557c264..4f5add3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ obj/ riderModule.iml /_ReSharper.Caches/ .idea -rules.yaml \ No newline at end of file +rules.yaml +/VirtualDDNSRouter.Client/settings.yaml +settings.yaml \ No newline at end of file diff --git a/VirtualDDNSRouter.Client/Dockerfile b/VirtualDDNSRouter.Client/Dockerfile new file mode 100644 index 0000000..fb26a59 --- /dev/null +++ b/VirtualDDNSRouter.Client/Dockerfile @@ -0,0 +1,21 @@ +FROM mcr.microsoft.com/dotnet/runtime:10.0 AS base +USER $APP_UID +WORKDIR /app + +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["VirtualDDNSRouter.Client/VirtualDDNSRouter.Client.csproj", "VirtualDDNSRouter.Client/"] +RUN dotnet restore "VirtualDDNSRouter.Client/VirtualDDNSRouter.Client.csproj" +COPY . . +WORKDIR "/src/VirtualDDNSRouter.Client" +RUN dotnet build "./VirtualDDNSRouter.Client.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./VirtualDDNSRouter.Client.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "VirtualDDNSRouter.Client.dll"] diff --git a/VirtualDDNSRouter.Client/Helpers/YamlParser.cs b/VirtualDDNSRouter.Client/Helpers/YamlParser.cs new file mode 100644 index 0000000..e1a5043 --- /dev/null +++ b/VirtualDDNSRouter.Client/Helpers/YamlParser.cs @@ -0,0 +1,39 @@ +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; +using Settings = VirtualDDNSRouter.Client.Models.Settings; + +namespace VirtualDDNSRouter.Client.Helpers; + +public class Helpers +{ + private static readonly string YamlFilePath = "settings.yaml"; + + public static async Task GetSettings() + { + if (!File.Exists(YamlFilePath)) + throw new FileNotFoundException($"Settings file not found: {YamlFilePath}"); + + var yamlContent = await File.ReadAllTextAsync(YamlFilePath).ConfigureAwait(false); + + // Build the deserializer with explicit naming convention + var deserializer = new StaticDeserializerBuilder(new YamlStaticContext()) + .WithNamingConvention(UnderscoredNamingConvention.Instance) + .IgnoreUnmatchedProperties() + .Build(); + + // Deserialize into Settings + var settings = deserializer.Deserialize(yamlContent); + + if (settings is null || string.IsNullOrWhiteSpace(settings.Host) || string.IsNullOrWhiteSpace(settings.Path) || + string.IsNullOrWhiteSpace(settings.ApiKey)) + throw new Exception("Invalid settings file"); + + return settings; + } + + [YamlStaticContext] + [YamlSerializable(typeof(Settings))] + private partial class YamlStaticContext : StaticContext + { + } +} \ No newline at end of file diff --git a/VirtualDDNSRouter.Client/Models/Settings.cs b/VirtualDDNSRouter.Client/Models/Settings.cs new file mode 100644 index 0000000..67fb5e9 --- /dev/null +++ b/VirtualDDNSRouter.Client/Models/Settings.cs @@ -0,0 +1,10 @@ +namespace VirtualDDNSRouter.Client.Models; + +public class Settings +{ + public string Host { get; set; } = string.Empty; + public string Path { get; set; } = string.Empty; + public ushort DestinationPort { get; set; } = 80; + public string ApiKey { get; set; } = string.Empty; + public ushort RefreshIntervalMinutes { get; set; } = 5; +} \ No newline at end of file diff --git a/VirtualDDNSRouter.Client/Program.cs b/VirtualDDNSRouter.Client/Program.cs new file mode 100644 index 0000000..65e8ce7 --- /dev/null +++ b/VirtualDDNSRouter.Client/Program.cs @@ -0,0 +1,26 @@ +using VirtualDDNSRouter.Client.Helpers; + +var settings = await Helpers.GetSettings(); + +using var cts = new CancellationTokenSource(); +Console.CancelKeyPress += (s, e) => +{ + Console.WriteLine("Shutdown requested…"); + e.Cancel = true; + cts.Cancel(); +}; + +var timer = new PeriodicTimer(TimeSpan.FromMinutes(settings.RefreshIntervalMinutes)); + +var client = new HttpClient { Timeout = TimeSpan.FromSeconds(10) }; +var request = new HttpRequestMessage(HttpMethod.Get, + new Uri($"{settings.Host}/setip/{settings.Path}/{settings.DestinationPort}/{settings.ApiKey}")); +while (await timer.WaitForNextTickAsync()) +{ + if (cts.IsCancellationRequested) break; + var response = await client.SendAsync(request, cts.Token); + if (response.IsSuccessStatusCode) + Console.WriteLine("[INFO] IP address updated at " + DateTime.Now.ToString("HH:mm:ss")); + else + Console.WriteLine("[ERROR] IP address update failed at " + DateTime.Now.ToString("HH:mm:ss")); +} \ No newline at end of file diff --git a/VirtualDDNSRouter.Client/VirtualDDNSRouter.Client.csproj b/VirtualDDNSRouter.Client/VirtualDDNSRouter.Client.csproj new file mode 100644 index 0000000..e273f26 --- /dev/null +++ b/VirtualDDNSRouter.Client/VirtualDDNSRouter.Client.csproj @@ -0,0 +1,23 @@ + + + + Exe + net10.0 + enable + enable + true + true + Linux + + + + + .dockerignore + + + + + + + + diff --git a/VirtualDDNSRouter.Client/settings.example.yaml b/VirtualDDNSRouter.Client/settings.example.yaml new file mode 100644 index 0000000..af885ed --- /dev/null +++ b/VirtualDDNSRouter.Client/settings.example.yaml @@ -0,0 +1,5 @@ +host: example.com +path: odoo +destination_port: 8081 +api_key: abc123XYZ +refresh_interval_minutes: 3 \ No newline at end of file diff --git a/VirtualDDNSRouter.Server/Models/Rule.cs b/VirtualDDNSRouter.Server/Models/Rule.cs index 4e658fa..c399901 100644 --- a/VirtualDDNSRouter.Server/Models/Rule.cs +++ b/VirtualDDNSRouter.Server/Models/Rule.cs @@ -10,7 +10,7 @@ public record Rule public Rule() { - } // Needed for AOT static deserializer + } // Needed for AOT static deserializer - AI } public record Route diff --git a/VirtualDDNSRouter.sln b/VirtualDDNSRouter.sln index 18ca1eb..e8d61df 100644 --- a/VirtualDDNSRouter.sln +++ b/VirtualDDNSRouter.sln @@ -7,6 +7,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution compose.yaml = compose.yaml EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VirtualDDNSRouter.Client", "VirtualDDNSRouter.Client\VirtualDDNSRouter.Client.csproj", "{68BC4818-F3A1-4862-BAE6-6F43D3237E63}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -17,5 +19,9 @@ Global {6101BFD3-C31C-41CB-9402-A8B9F3EBEE22}.Debug|Any CPU.Build.0 = Debug|Any CPU {6101BFD3-C31C-41CB-9402-A8B9F3EBEE22}.Release|Any CPU.ActiveCfg = Release|Any CPU {6101BFD3-C31C-41CB-9402-A8B9F3EBEE22}.Release|Any CPU.Build.0 = Release|Any CPU + {68BC4818-F3A1-4862-BAE6-6F43D3237E63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {68BC4818-F3A1-4862-BAE6-6F43D3237E63}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68BC4818-F3A1-4862-BAE6-6F43D3237E63}.Release|Any CPU.ActiveCfg = Release|Any CPU + {68BC4818-F3A1-4862-BAE6-6F43D3237E63}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/compose.yaml b/compose.yaml index 56ef1ee..1cd5046 100644 --- a/compose.yaml +++ b/compose.yaml @@ -5,3 +5,9 @@ context: . dockerfile: VirtualDDNSRouter.Server/Dockerfile + virtualddnsrouter.client: + image: virtualddnsrouter.client + build: + context: . + dockerfile: VirtualDDNSRouter.Client/Dockerfile +