diff --git a/.gitea/workflows/workflows.yaml b/.gitea/workflows/publish-arch.yaml similarity index 100% rename from .gitea/workflows/workflows.yaml rename to .gitea/workflows/publish-arch.yaml diff --git a/.gitea/workflows/publish-debian.yaml b/.gitea/workflows/publish-debian.yaml new file mode 100644 index 0000000..7513cc2 --- /dev/null +++ b/.gitea/workflows/publish-debian.yaml @@ -0,0 +1,62 @@ +name: Publish Debian Package + +on: + push: + branches: [ "main" ] + paths: + - 'GiteaPublish/**' + - '.gitea/workflows/publish-debian.yaml' + workflow_dispatch: + +jobs: + build-and-publish: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 10.0.x + + - name: Build GiteaPublish Tool + run: | + dotnet publish GiteaPublish/GiteaPublish.csproj -c Release -r linux-x64 --self-contained \ + -p:PublishSingleFile=true \ + -p:PublishReadyToRun=true \ + -p:UseAppHost=true \ + -o ./publish-tool + + - name: Get application version + id: get_version + run: | + VERSION=$(dotnet run -v q --property WarningLevel=0 --project GiteaPublish -- --version) + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Application version: $VERSION" + + - name: Build Debian Package + run: | + sed -i "s//${{ steps.get_version.outputs.version }}/g" debian/control + mkdir -p package/usr/bin + cp ./publish-tool/GiteaPublish package/usr/bin/gitea-publish + mkdir -p package/DEBIAN + cp debian/control package/DEBIAN/ + chmod 755 package/usr/bin/gitea-publish + chmod 755 package/DEBIAN + chmod 644 package/DEBIAN/control + dpkg-deb --build package gitea-publish_${{ steps.get_version.outputs.version }}_amd64.deb + + - name: Publish to Gitea + env: + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + GITEA_USERNAME: ${{ github.actor }} + run: | + ./publish-tool/GiteaPublish package debian publish \ + --username "${GITEA_USERNAME}" \ + --password "${GITEA_TOKEN}" \ + --owner "${{ github.repository_owner }}" \ + --distribution "stable" \ + --component "main" \ + --file "gitea-publish_${{ steps.get_version.outputs.version }}_amd64.deb" \ + --base-url "${{ github.server_url }}" diff --git a/GiteaPublish/Commands/Package/PackageDeleteDebianCommand.cs b/GiteaPublish/Commands/Package/PackageDeleteDebianCommand.cs new file mode 100644 index 0000000..3c25008 --- /dev/null +++ b/GiteaPublish/Commands/Package/PackageDeleteDebianCommand.cs @@ -0,0 +1,90 @@ +using GiteaPublish.Gitea; +using Spectre.Console; +using Spectre.Console.Cli; + +namespace GiteaPublish.Commands.Package; + +public class PackageDeleteDebianCommand : AsyncCommand +{ + private readonly DebianPackageRepo _packageRepo; + private readonly IAnsiConsole _console; + + public PackageDeleteDebianCommand(DebianPackageRepo packageRepo, IAnsiConsole console) + { + _packageRepo = packageRepo; + _console = console; + } + + public override async Task ExecuteAsync(CommandContext context, DeleteDebianSettings settings, CancellationToken cancellationToken) + { + _packageRepo.SetAuthentication(settings.GetAuth()); + + bool success = await _packageRepo.DeletePackageAsync( + settings.Owner, + settings.Distribution, + settings.Component, + settings.PackageName, + settings.PackageVersion, + settings.Architecture, + settings.BaseUrl); + + if (success) + { + _console.MarkupLine("[green]Debian package deleted successfully![/]"); + return 0; + } + + _console.MarkupLine("[red]Failed to delete Debian package.[/]"); + return 1; + } + + public override ValidationResult Validate(CommandContext context, DeleteDebianSettings settings) + { + if (string.IsNullOrWhiteSpace(settings.Owner)) + return ValidationResult.Error("Owner is required."); + + if (string.IsNullOrWhiteSpace(settings.Distribution)) + return ValidationResult.Error("Distribution is required."); + + if (string.IsNullOrWhiteSpace(settings.Component)) + return ValidationResult.Error("Component is required."); + + if (string.IsNullOrWhiteSpace(settings.PackageName)) + return ValidationResult.Error("Package name is required."); + + if (string.IsNullOrWhiteSpace(settings.PackageVersion)) + return ValidationResult.Error("Package version is required."); + + if (string.IsNullOrWhiteSpace(settings.Architecture)) + return ValidationResult.Error("Architecture is required."); + + if (string.IsNullOrWhiteSpace(settings.AuthFile)) + { + if (string.IsNullOrWhiteSpace(settings.Username)) + return ValidationResult.Error("Username is required when --auth-file is not used."); + + if (string.IsNullOrWhiteSpace(settings.Password)) + return ValidationResult.Error("Password is required when --auth-file is not used."); + } + + return ValidationResult.Success(); + } + + public sealed class DeleteDebianSettings : PackageSettings + { + [CommandOption("-d|--distribution ", isRequired: true)] + public string Distribution { get; init; } = string.Empty; + + [CommandOption("-c|--component ", isRequired: true)] + public string Component { get; init; } = string.Empty; + + [CommandOption("-p|--package-name ", isRequired: true)] + public string PackageName { get; init; } = string.Empty; + + [CommandOption("-v|--package-version ", isRequired: true)] + public string PackageVersion { get; init; } = string.Empty; + + [CommandOption("-a|--architecture ", isRequired: true)] + public string Architecture { get; init; } = string.Empty; + } +} \ No newline at end of file diff --git a/GiteaPublish/Commands/Package/PackagePublishDebianCommand.cs b/GiteaPublish/Commands/Package/PackagePublishDebianCommand.cs new file mode 100644 index 0000000..458a02d --- /dev/null +++ b/GiteaPublish/Commands/Package/PackagePublishDebianCommand.cs @@ -0,0 +1,83 @@ +using GiteaPublish.Gitea; +using Spectre.Console; +using Spectre.Console.Cli; + +namespace GiteaPublish.Commands.Package; + + +public class PackagePublishDebianCommand : AsyncCommand +{ + private readonly DebianPackageRepo _packageRepo; + private readonly IAnsiConsole _console; + + public PackagePublishDebianCommand(DebianPackageRepo packageRepo, IAnsiConsole console) + { + _packageRepo = packageRepo; + _console = console; + } + + public override async Task ExecuteAsync( + CommandContext context, + PublishDebianSettings settings, + CancellationToken cancellationToken) + { + _packageRepo.SetAuthentication(settings.GetAuth()); + + bool success = await _packageRepo.PublishPackageAsync( + settings.Owner, + settings.Distribution, + settings.Component, + settings.File, + settings.BaseUrl); + + if (success) + { + _console.MarkupLine("[green]Debian package published successfully![/]"); + return 0; + } + + _console.MarkupLine("[red]Failed to publish Debian package.[/]"); + return 1; + } + + public override ValidationResult Validate(CommandContext context, PublishDebianSettings settings) + { + if (!File.Exists(settings.File)) + return ValidationResult.Error($"File '{settings.File}' does not exist."); + + if (!settings.File.EndsWith(".deb", StringComparison.OrdinalIgnoreCase)) + return ValidationResult.Error("File must be a Debian package ending with '.deb'."); + + if (string.IsNullOrWhiteSpace(settings.Owner)) + return ValidationResult.Error("Owner is required."); + + if (string.IsNullOrWhiteSpace(settings.Distribution)) + return ValidationResult.Error("Distribution is required."); + + if (string.IsNullOrWhiteSpace(settings.Component)) + return ValidationResult.Error("Component is required."); + + if (string.IsNullOrWhiteSpace(settings.AuthFile)) + { + if (string.IsNullOrWhiteSpace(settings.Username)) + return ValidationResult.Error("Username is required when --auth-file is not used."); + + if (string.IsNullOrWhiteSpace(settings.Password)) + return ValidationResult.Error("Password is required when --auth-file is not used."); + } + + return ValidationResult.Success(); + } + + public sealed class PublishDebianSettings : PackageSettings + { + [CommandOption("-f|--file ", isRequired: true)] + public required string File { get; init; } + + [CommandOption("-d|--distribution ", isRequired: true)] + public string Distribution { get; init; } = string.Empty; + + [CommandOption("-c|--component ", isRequired: true)] + public string Component { get; init; } = string.Empty; + } +} \ No newline at end of file diff --git a/GiteaPublish/Gitea/DebianPackageRepo.cs b/GiteaPublish/Gitea/DebianPackageRepo.cs new file mode 100644 index 0000000..3a8a77b --- /dev/null +++ b/GiteaPublish/Gitea/DebianPackageRepo.cs @@ -0,0 +1,62 @@ +using System.Net.Http.Headers; +using Spectre.Console; + +namespace GiteaPublish.Gitea; + +public class DebianPackageRepo(HttpClient httpClient, IAnsiConsole ansiConsole) +{ + private BasicAuthModel BasicAuth { get; set; } = new BasicAuthModel(); + + public async Task PublishPackageAsync(string owner, string distribution, string component, string filePath, + string baseUrl) + { + httpClient.BaseAddress = new Uri(baseUrl.TrimEnd('/')); + ansiConsole.MarkupLine( + $"[blue]Publishing Debian package {filePath.EscapeMarkup()} to {owner.EscapeMarkup()}/{distribution.EscapeMarkup()}/{component.EscapeMarkup()}...[/]"); + HttpRequestMessage request = new( + HttpMethod.Put, + $"/api/packages/{Uri.EscapeDataString(owner)}/debian/pool/{Uri.EscapeDataString(distribution)}/{Uri.EscapeDataString(component)}/upload"); + request.Content = new ByteArrayContent(await File.ReadAllBytesAsync(filePath)); + request.Headers.Authorization = new AuthenticationHeaderValue("Basic", BasicAuth.AuthHeader); + + HttpResponseMessage response = await httpClient.SendAsync(request); + + if (response.IsSuccessStatusCode) + return true; + + ansiConsole.MarkupLine($"[red]Failed to publish Debian package: {response.StatusCode}[/]"); + ansiConsole.MarkupLine($"[red]{(await response.Content.ReadAsStringAsync()).EscapeMarkup()}[/]"); + return false; + } + + public async Task DeletePackageAsync(string owner, string distribution, string component, string packageName, + string packageVersion, string architecture, string baseUrl) + { + httpClient.BaseAddress = new Uri(baseUrl.TrimEnd('/')); + + ansiConsole.MarkupLine( + $"[blue]Deleting Debian package {packageName} {packageVersion} [{architecture}] from {owner}/{distribution}/{component}...[/]"); + + HttpRequestMessage request = new( + HttpMethod.Delete, + $"/api/packages/{Uri.EscapeDataString(owner)}/debian/pool/{Uri.EscapeDataString(distribution)}/{Uri.EscapeDataString(component)}/{Uri.EscapeDataString(packageName)}/{Uri.EscapeDataString(packageVersion)}/{Uri.EscapeDataString(architecture)}"); + + request.Headers.Authorization = new AuthenticationHeaderValue("Basic", BasicAuth.AuthHeader); + + HttpResponseMessage response = await httpClient.SendAsync(request); + + if (response.IsSuccessStatusCode) + return true; + + ansiConsole.MarkupLine($"[red]Failed to delete Debian package: {response.StatusCode}[/]"); + ansiConsole.MarkupLine($"[red]{(await response.Content.ReadAsStringAsync()).EscapeMarkup()}[/]"); + + return false; + } + + public void SetAuthentication(BasicAuthModel basicAuth) + { + BasicAuth = basicAuth; + ansiConsole.MarkupLine("[green]Authentication set.[/]"); + } +} \ No newline at end of file diff --git a/GiteaPublish/GiteaPublish.csproj b/GiteaPublish/GiteaPublish.csproj index 8d77867..dfadfa8 100644 --- a/GiteaPublish/GiteaPublish.csproj +++ b/GiteaPublish/GiteaPublish.csproj @@ -5,7 +5,7 @@ net10.0 enable enable - 1.0.0.14 + 1.0.1.0 diff --git a/GiteaPublish/Program.cs b/GiteaPublish/Program.cs index af8c3fd..1e97e2d 100644 --- a/GiteaPublish/Program.cs +++ b/GiteaPublish/Program.cs @@ -15,6 +15,7 @@ class Program { ServiceCollection services = new(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(CreateHttpClient()); services.AddSingleton(); TypeRegistrar registrar = new TypeRegistrar(services); @@ -34,8 +35,19 @@ class Program .WithExample("package arch publish --username --password " + "-o -r -f -b "); - // archConfig.AddCommand("delete") - // .WithDescription("Deletes an Arch package from a Gitea repository"); + packageConfig.AddBranch("debian", debianConfig => + { + debianConfig.AddCommand("publish") + .WithDescription("Publish a Debian package to a Gitea repository") + .WithExample("package debian publish --username --password " + + "-o -d -c -f -b "); + + debianConfig.AddCommand("delete") + .WithDescription("Delete a Debian package from a Gitea repository") + .WithExample("package debian delete --username --password " + + "-o -d -c " + + "-p -v -a -b "); + }); }); }); diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..efd20f8 --- /dev/null +++ b/debian/control @@ -0,0 +1,7 @@ +Package: gitea-publish +Version: +Section: utils +Priority: optional +Architecture: amd64 +Maintainer: Stillkill +Description: Tool to publish packages to Gitea