feat: load application and repository configuration from env and TOML

This commit is contained in:
mahdium committed 2026-11-03 15:02:50 +03:30
commit ae8680d275
17 files changed
+301

No files matched your search

+25
View File
@@ -0,0 +1,25 @@
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.idea
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
+8
View File
@@ -0,0 +1,8 @@
bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/
.env
config.toml
+15
View File
@@ -0,0 +1,15 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Rider ignored files
/modules.xml
/contentModel.xml
/projectSettingsUpdater.xml
/.idea.ForgejoReleaseSync.iml
# Ignored default folder with query files
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="TimeTrackingManager">
<option name="totallyTimeSpent" value="248000" />
</component>
</project>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+21
View File
@@ -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 ["ForgejoReleaseSync.csproj", "./"]
RUN dotnet restore "ForgejoReleaseSync.csproj"
COPY . .
WORKDIR "/src/"
RUN dotnet build "./ForgejoReleaseSync.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./ForgejoReleaseSync.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ForgejoReleaseSync.dll"]
+19
View File
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DotNetEnv" Version="3.2.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.14" />
<PackageReference Include="RStein.TOML" Version="1.0.0" />
</ItemGroup>
</Project>
+21
View File
@@ -0,0 +1,21 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ForgejoReleaseSync", "ForgejoReleaseSync.csproj", "{268E3A9E-52D0-4B81-A677-277555C6D1F8}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0E6F637D-3E7A-4DD3-8B01-1C7BFB36586A}"
ProjectSection(SolutionItems) = preProject
compose.yaml = compose.yaml
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{268E3A9E-52D0-4B81-A677-277555C6D1F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{268E3A9E-52D0-4B81-A677-277555C6D1F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{268E3A9E-52D0-4B81-A677-277555C6D1F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{268E3A9E-52D0-4B81-A677-277555C6D1F8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
+15
View File
@@ -0,0 +1,15 @@
using ForgejoReleaseSync.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder(args);
var config = AppConfigLoader.LoadConfigFromEnv();
builder.Services.AddSingleton(config);
var repositoryConfig = await RepoConfigLoader.LoadConfigAsync(
config.RepositoryConfigFilePath);
builder.Services.AddSingleton(repositoryConfig);
var app = builder.Build();
await app.RunAsync();
+52
View File
@@ -0,0 +1,52 @@
using DotNetEnv;
namespace ForgejoReleaseSync.Services;
public class AppConfig
{
public required string ForgejoInstacne { get; set; }
public required string ForgejoToken { get; set; }
public required ushort SyncConcurrency { get; set; }
public required string RepositoryConfigFilePath { get; set; }
}
public static class AppConfigLoader
{
public static AppConfig LoadConfigFromEnv()
{
Env.Load();
var forgejoInstance = Environment.GetEnvironmentVariable("FORGEJO_INSTANCE");
var forgejoToken = Environment.GetEnvironmentVariable("FORGEJO_TOKEN");
var syncConcurrency = Environment.GetEnvironmentVariable("SYNC_CONCURRENCY");
var repoConfigFilePath = Environment.GetEnvironmentVariable("REPO_CONFIG_FILE");
if (string.IsNullOrEmpty(forgejoInstance))
throw new Exception("FORGEJO_INSTANCE environment variable is required");
if (!string.IsNullOrEmpty(forgejoToken) && forgejoInstance.EndsWith('/'))
forgejoInstance = forgejoInstance[..^1];
if (string.IsNullOrEmpty(forgejoToken))
throw new Exception("FORGEJO_TOKEN environment variable is required");
ushort syncConcurrencyParsed = 3;
if (!string.IsNullOrEmpty(syncConcurrency))
ushort.TryParse(syncConcurrency, out syncConcurrencyParsed);
if (syncConcurrencyParsed > 10)
throw new ArgumentOutOfRangeException(nameof(syncConcurrencyParsed),
"Sync concurrency cannot be greater than 10");
if (string.IsNullOrEmpty(repoConfigFilePath))
throw new Exception("REPO_CONFIG_FILE environment variable is required");
return new AppConfig
{
ForgejoInstacne = forgejoInstance,
ForgejoToken = forgejoToken,
SyncConcurrency = syncConcurrencyParsed,
RepositoryConfigFilePath = repoConfigFilePath
};
}
}
+6
View File
@@ -0,0 +1,6 @@
namespace ForgejoReleaseSync.Services;
public class ForgejoService
{
}
+6
View File
@@ -0,0 +1,6 @@
namespace ForgejoReleaseSync.Services;
public class GithubService
{
}
+71
View File
@@ -0,0 +1,71 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using RStein.TOML;
namespace ForgejoReleaseSync.Services;
public class RepositoryConfig
{
public List<RepoSyncConfig> Repositories { get; set; } = [];
}
public class RepoSyncConfig
{
public required string Name { get; set; }
public required string GithubUrl { get; set; }
public required string ForgejoUrl { get; set; }
public required string IncludeRegex { get; set; }
public required string ExcludeRegex { get; set; }
}
public abstract class RepoConfigLoader()
{
public static async Task<RepositoryConfig> LoadConfigAsync(string configPath,CancellationToken token = default)
{
try
{
var toml = await File.ReadAllTextAsync(configPath, token);
var tables = await TomlSerializer.DeserializeAsync(toml, token);
var result = new RepositoryConfig();
foreach (var table in tables)
{
if (table.Value is not TomlArray tableValues)
continue;
foreach (var content in tableValues)
{
if (content is not TomlTable fields)
continue;
result.Repositories.Add(new RepoSyncConfig
{
Name = table.Key.ToString(),
GithubUrl = GetRequiredString(fields, "GithubUrl"),
ForgejoUrl = GetRequiredString(fields, "ForgejoUrl"),
IncludeRegex = GetRequiredString(fields, "IncludeRegex"),
ExcludeRegex = GetRequiredString(fields, "ExcludeRegex")
});
}
}
return result;
}
catch (Exception e)
{
throw new InvalidDataException("Failed to load configuration file", e);
}
}
private static string GetRequiredString(
TomlTable table,
string key)
{
return (string?)table[key]
?? throw new InvalidDataException(
$"Missing required configuration property: {key}");
}
}
+7
View File
@@ -0,0 +1,7 @@
services:
forgejoreleasesync:
image: forgejoreleasesync
build:
context: .
dockerfile: Dockerfile
+11
View File
@@ -0,0 +1,11 @@
[[repo1]]
GithubUrl = "https://github.com/owner/repo1"
ForgejoUrl = "https://forgejo.example.com/owner/repo1"
IncludeRegex = ".*"
ExcludeRegex = "^$"
[[repo2]]
GithubUrl = "https://github.com/owner/repo2"
ForgejoUrl = "https://forgejo.example.com/owner/repo2"
IncludeRegex = "^v.*"
ExcludeRegex = ".*-beta.*"