Add Debian package support and publish workflow

This commit is contained in:
Stillkill committed 2026-06-08 21:13:04 -05:00
1 parent bd1478bca1
commit 79cb442d35
8 files changed
+319 -3

No files matched your search

File renamed without changes.
+62
View File
@@ -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/<PKG-VER>/${{ 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 }}"
@@ -0,0 +1,90 @@
using GiteaPublish.Gitea;
using Spectre.Console;
using Spectre.Console.Cli;
namespace GiteaPublish.Commands.Package;
public class PackageDeleteDebianCommand : AsyncCommand<PackageDeleteDebianCommand.DeleteDebianSettings>
{
private readonly DebianPackageRepo _packageRepo;
private readonly IAnsiConsole _console;
public PackageDeleteDebianCommand(DebianPackageRepo packageRepo, IAnsiConsole console)
{
_packageRepo = packageRepo;
_console = console;
}
public override async Task<int> 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 <DISTRIBUTION>", isRequired: true)]
public string Distribution { get; init; } = string.Empty;
[CommandOption("-c|--component <COMPONENT>", isRequired: true)]
public string Component { get; init; } = string.Empty;
[CommandOption("-p|--package-name <PACKAGE_NAME>", isRequired: true)]
public string PackageName { get; init; } = string.Empty;
[CommandOption("-v|--package-version <PACKAGE_VERSION>", isRequired: true)]
public string PackageVersion { get; init; } = string.Empty;
[CommandOption("-a|--architecture <ARCHITECTURE>", isRequired: true)]
public string Architecture { get; init; } = string.Empty;
}
}
@@ -0,0 +1,83 @@
using GiteaPublish.Gitea;
using Spectre.Console;
using Spectre.Console.Cli;
namespace GiteaPublish.Commands.Package;
public class PackagePublishDebianCommand : AsyncCommand<PackagePublishDebianCommand.PublishDebianSettings>
{
private readonly DebianPackageRepo _packageRepo;
private readonly IAnsiConsole _console;
public PackagePublishDebianCommand(DebianPackageRepo packageRepo, IAnsiConsole console)
{
_packageRepo = packageRepo;
_console = console;
}
public override async Task<int> 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 <FILE>", isRequired: true)]
public required string File { get; init; }
[CommandOption("-d|--distribution <DISTRIBUTION>", isRequired: true)]
public string Distribution { get; init; } = string.Empty;
[CommandOption("-c|--component <COMPONENT>", isRequired: true)]
public string Component { get; init; } = string.Empty;
}
}
+62
View File
@@ -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<bool> 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<bool> 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.[/]");
}
}
+1 -1
View File
@@ -5,7 +5,7 @@
<TargetFramework>net10.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<Version>1.0.0.14</Version> <Version>1.0.1.0</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
+14 -2
View File
@@ -15,6 +15,7 @@ class Program
{ {
ServiceCollection services = new(); ServiceCollection services = new();
services.AddSingleton<IPackageRepo, ArchPackageRepo>(); services.AddSingleton<IPackageRepo, ArchPackageRepo>();
services.AddSingleton<DebianPackageRepo>();
services.AddSingleton(CreateHttpClient()); services.AddSingleton(CreateHttpClient());
services.AddSingleton<ReleaseService>(); services.AddSingleton<ReleaseService>();
TypeRegistrar registrar = new TypeRegistrar(services); TypeRegistrar registrar = new TypeRegistrar(services);
@@ -34,8 +35,19 @@ class Program
.WithExample("package arch publish --username <username> --password <password or token> " + .WithExample("package arch publish --username <username> --password <password or token> " +
"-o <repo owner> -r <repo name> -f <package file> -b <base url>"); "-o <repo owner> -r <repo name> -f <package file> -b <base url>");
// archConfig.AddCommand<PackageDeleteArchCommand>("delete") packageConfig.AddBranch("debian", debianConfig =>
// .WithDescription("Deletes an Arch package from a Gitea repository"); {
debianConfig.AddCommand<PackagePublishDebianCommand>("publish")
.WithDescription("Publish a Debian package to a Gitea repository")
.WithExample("package debian publish --username <username> --password <password or token> " +
"-o <owner> -d <distribution> -c <component> -f <package.deb> -b <base url>");
debianConfig.AddCommand<PackageDeleteDebianCommand>("delete")
.WithDescription("Delete a Debian package from a Gitea repository")
.WithExample("package debian delete --username <username> --password <password or token> " +
"-o <owner> -d <distribution> -c <component> " +
"-p <package name> -v <package version> -a <architecture> -b <base url>");
});
}); });
}); });
+7
View File
@@ -0,0 +1,7 @@
Package: gitea-publish
Version: <PKG-VER>
Section: utils
Priority: optional
Architecture: amd64
Maintainer: Stillkill <stillkill@example.com>
Description: Tool to publish packages to Gitea