7 Commits

Author SHA1 Message Date
36331e8664 Исправлена подпись
All checks were successful
CI / build-test (push) Successful in 35s
Release / pack-and-publish (release) Successful in 29s
2025-12-07 08:47:06 +03:00
d0e57d8d7b Добавлено обновление
All checks were successful
CI / build-test (push) Successful in 31s
2025-11-27 11:19:34 +03:00
16fbddcdc8 ci
All checks were successful
CI / build-test (push) Successful in 27s
Release / pack-and-publish (release) Successful in 33s
2025-11-27 11:14:09 +03:00
ec3d8de187 сборкка Common
All checks were successful
CI / build-test (push) Successful in 26s
Release / pack-and-publish (release) Successful in 28s
2025-11-27 11:10:30 +03:00
4f17192c01 выключение генерации пакета
All checks were successful
CI / build-test (push) Successful in 29s
Release / pack-and-publish (release) Successful in 29s
2025-11-27 11:00:34 +03:00
2cb585c222 Доработан вызов updater.exe
All checks were successful
CI / build-test (push) Successful in 30s
Release / pack-and-publish (release) Successful in 30s
2025-11-27 10:55:38 +03:00
cc490c7112 Добавлено ожидание процесса
All checks were successful
CI / build-test (push) Successful in 25s
Release / pack-and-publish (release) Successful in 27s
2025-11-25 09:52:11 +03:00
15 changed files with 249 additions and 99 deletions

View File

@@ -19,20 +19,31 @@ jobs:
with: with:
dotnet-version: 8.0.x dotnet-version: 8.0.x
- name: Restore ReleaseUpdater
run: dotnet restore ReleaseUpdater
- name: Set version from tag - name: Set version from tag
id: version id: version
run: | run: |
TAG="${GITHUB_REF_NAME#v}" TAG="${GITHUB_REF_NAME#v}"
echo "PACKAGE_VERSION=$TAG" >> $GITHUB_OUTPUT echo "PACKAGE_VERSION=$TAG" >> $GITHUB_OUTPUT
- name: Build ReleaseUpdater - name: Replace ProjectReference with PackageReference
run: dotnet build ReleaseUpdater -c Release -p:Version=${{ steps.version.outputs.PACKAGE_VERSION }} run: |
sed -i "s#<ProjectReference Include=\"..\/ReleaseUpdater.Common\/ReleaseUpdater.Common.csproj\" />#<PackageReference Include=\"ReleaseUpdater.Common\" Version=\"${{ steps.version.outputs.PACKAGE_VERSION }}\" />#" ReleaseUpdater/ReleaseUpdater.csproj
- name: Pack ReleaseUpdater - name: Build and Pack projects
run: dotnet pack ReleaseUpdater -c Release --no-build -p:PackageVersion=${{ steps.version.outputs.PACKAGE_VERSION }} -o ./artifacts run: |
mkdir -p artifacts
for proj in $PROJECTS; do
echo "Restoring $proj..."
dotnet restore $proj
echo "Building $proj..."
dotnet build $proj -c Release -p:Version=${{ steps.version.outputs.PACKAGE_VERSION }}
echo "Packing $proj..."
dotnet pack $proj -c Release --no-build -p:PackageVersion=${{ steps.version.outputs.PACKAGE_VERSION }} -o ./artifacts
done
env:
PROJECTS: |
ReleaseUpdater.Common
ReleaseUpdater
- name: Upload package artifact - name: Upload package artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3

View File

@@ -0,0 +1,34 @@
using ArgumentsToolkit;
namespace ReleaseUpdater.Common;
/// <summary>Параметры CLI для Updater.exe.</summary>
public sealed class Options
{
[Option(nameof(ZipPath), "z", "Путь к скачанному архиву (.zip).", true)]
/// <summary>Путь к скачанному архиву (.zip).</summary>
public string ZipPath { get; set; }
[Option(nameof(InstallPath), "i", "Целевой каталог установки.", true)]
/// <summary>Целевой каталог установки.</summary>
public string InstallPath { get; set; }
[Option(nameof(AppExe), "a", "Имя исполняемого файла приложения для перезапуска", true)]
/// <summary>Имя исполняемого файла приложения для перезапуска (e.g., MyBot.exe).</summary>
public string AppExe { get; set; }
[Option(nameof(RestartDelayMs), "rd", "Миллисекунды ожидания перед перезапуском", false, 500)]
/// <summary>Необязательно: подождите миллисекунды перед перезапуском.</summary>
public int RestartDelayMs { get; set; } = 500;
[Option(nameof(UpdateDelayMs), "ud", "Миллисекунды ожидания перед обновления", false, 500)]
/// <summary>Необязательно: подождите миллисекунды перед запуском обновления.</summary>
public int UpdateDelayMs { get; set; } = 500;
[Option(nameof(WaitProcess), "wp", "PID процесса, завершение которого необходимо дождаться", false)]
/// <summary>Необязательно: дождаться завершения процесса.</summary>
public int? WaitProcess { get; set; } = null;
public static string Usage =>
"Usage: Updater.exe --zip <path.zip> --installPath <dir> --appExe <file.exe> [--restartDelayMs <int>] [--updateDelayMs <int>] [--waitProcess <int>]";
}

View File

@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageId>ReleaseUpdater.Common</PackageId>
<Version>1.0.0</Version>
<Authors>FrigaT</Authors>
<Company>FrigaT</Company>
<Product>ReleaseUpdater</Product>
<Description>Система обновления приложений через github/gitea системы контроля версий.</Description>
<Copyright>Copyright © 2025 FrigaT</Copyright>
<RepositoryUrl>https://git.frigat.duckdns.org/FrigaT/ReleaseUpdater</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://git.frigat.duckdns.org/FrigaT/ReleaseUpdater</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ArgumentsToolkit.Core" Version="0.0.3" />
</ItemGroup>
</Project>

View File

@@ -2,6 +2,8 @@
<Folder Name="/Элементы решения/"> <Folder Name="/Элементы решения/">
<File Path="README.MD" /> <File Path="README.MD" />
</Folder> </Folder>
<Project Path="ReleaseUpdater.Common/ReleaseUpdater.Common.csproj" Id="e8c120b2-6520-4652-bb85-00fcfa8077b7" />
<Project Path="ReleaseUpdater/ReleaseUpdater.csproj" /> <Project Path="ReleaseUpdater/ReleaseUpdater.csproj" />
<Project Path="Updater.Test/Updater.Test.csproj" />
<Project Path="Updater/Updater.csproj" /> <Project Path="Updater/Updater.csproj" />
</Solution> </Solution>

View File

@@ -1,9 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable>
<Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageId>ReleaseUpdater</PackageId>
<Version>1.0.0</Version>
<Authors>FrigaT</Authors>
<Company>FrigaT</Company>
<Product>ReleaseUpdater</Product>
<Description>Система обновления приложений через github/gitea системы контроля версий.</Description>
<Copyright>Copyright © 2025 FrigaT</Copyright>
<RepositoryUrl>https://git.frigat.duckdns.org/FrigaT/ReleaseUpdater</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://git.frigat.duckdns.org/FrigaT/ReleaseUpdater</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="ArgumentsToolkit.Core" Version="0.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ReleaseUpdater.Common\ReleaseUpdater.Common.csproj" />
</ItemGroup>
</Project> </Project>

View File

@@ -88,8 +88,28 @@ public static class ReleaseUpdaterFacade
BeforeInstall?.Invoke(); BeforeInstall?.Invoke();
if (updaterExePath == null) updaterExePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Updater.exe"); if (updaterExePath == null) updaterExePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Updater.exe");
var args = $"--zip \"{zipPath}\" --installPath \"{installPath}\" --appExe \"{appExe}\"";
if (installPath.EndsWith("\\"))
{
installPath = installPath[0..(installPath.Length - 1)];
}
var updaterOptions = new Common.Options()
{
ZipPath = zipPath,
InstallPath = installPath,
AppExe = appExe,
};
if (exitCurrentApp)
{
int pid = Process.GetCurrentProcess().Id;
updaterOptions.WaitProcess = pid;
}
var args = ArgumentsToolkit.ArgumentsParser.ToArguments(updaterOptions, true);
var process = Process.Start(new ProcessStartInfo var process = Process.Start(new ProcessStartInfo
{ {

21
Updater.Test/Program.cs Normal file
View File

@@ -0,0 +1,21 @@
using ReleaseUpdater;
namespace Updater.Test;
internal class Program
{
static async Task Main(string[] args)
{
string installPath = AppDomain.CurrentDomain.BaseDirectory;
string appExe = "RetailUpdatesBot.exe";
string updaterPath = Path.Combine(installPath, "Tools\\Updater.exe");
var url = "https://git.frigat.duckdns.org/api/v1/repos/automacon/RetailUpdatesBot/releases";
var APIKey = "0552a77699d7506711946fc71cc6635515726bd1"; //токен
await ReleaseUpdaterFacade.UpdateWithExternalAsync(url, APIKey, installPath, appExe, "latest", updaterPath, true);
Console.ReadKey();
}
}

Binary file not shown.

View File

@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ReleaseUpdater\ReleaseUpdater.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Tools\Updater.exe">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

15
Updater/Core/ExitCodes.cs Normal file
View File

@@ -0,0 +1,15 @@
namespace Updater.Core;
public static class ExitCodes
{
/// <summary>Успешное обновление.</summary>
public const int Ok = 0;
/// <summary>Неверные аргументы командной строки.</summary>
public const int InvalidArgs = 2;
/// <summary>Ошибка извлечения.</summary>
public const int ExtractFailed = 3;
/// <summary>Ошибка установки (копировать/заменить).</summary>
public const int InstallFailed = 4;
/// <summary>Ошибка перезапуска.</summary>
public const int RestartFailed = 5;
}

View File

@@ -1,70 +0,0 @@
namespace Updater.Core;
/// <summary>Параметры CLI для Updater.exe.</summary>
public sealed class Options
{
/// <summary>Путь к скачанному архиву (.zip).</summary>
public required string ZipPath { get; init; }
/// <summary>Целевой каталог установки.</summary>
public required string InstallPath { get; init; }
/// <summary>Имя исполняемого файла приложения для перезапуска (e.g., MyBot.exe).</summary>
public required string AppExe { get; init; }
/// <summary>Необязательно: подождите миллисекунды перед перезапуском.</summary>
public int RestartDelayMs { get; init; } = 500;
/// <summary>Необязательно: подождите миллисекунды перед запуском обновления.</summary>
public int UpdateDelayMs { get; init; } = 500;
public static string Usage =>
"Usage: Updater.exe --zip <path.zip> --installPath <dir> --appExe <file.exe> [--restartDelayMs <int>] [--updateDelayMs <int>]";
/// <summary>Папрсинг CLI аргументов в Options.</summary>
public static Options Parse(string[] args)
{
var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < args.Length; i++)
{
if (!args[i].StartsWith("--")) continue;
var key = args[i][2..];
var val = (i + 1 < args.Length && !args[i + 1].StartsWith("--")) ? args[i + 1] : "true";
dict[key] = val;
}
var zip = Require(dict, "zip");
var install = Require(dict, "installPath");
var exe = Require(dict, "appExe");
if (!File.Exists(zip)) throw new FileNotFoundException("Zip not found", zip);
Directory.CreateDirectory(install);
return new Options
{
ZipPath = Path.GetFullPath(zip),
InstallPath = Path.GetFullPath(install),
AppExe = exe,
RestartDelayMs = dict.TryGetValue("restartDelayMs", out var d) && int.TryParse(d, out var n) ? n : 500,
UpdateDelayMs = dict.TryGetValue("updateDelayMs", out var d2) && int.TryParse(d2, out var n2) ? n2 : 500
};
}
private static string Require(IDictionary<string, string> dict, string key)
=> dict.TryGetValue(key, out var v) && !string.IsNullOrWhiteSpace(v)
? v : throw new ArgumentException($"Missing --{key}");
}
public static class ExitCodes
{
/// <summary>Успешное обновление.</summary>
public const int Ok = 0;
/// <summary>Неверные аргументы командной строки.</summary>
public const int InvalidArgs = 2;
/// <summary>Ошибка извлечения.</summary>
public const int ExtractFailed = 3;
/// <summary>Ошибка установки (копировать/заменить).</summary>
public const int InstallFailed = 4;
/// <summary>Ошибка перезапуска.</summary>
public const int RestartFailed = 5;
}

View File

@@ -1,4 +1,6 @@
namespace Updater.Core; using ReleaseUpdater.Common;
namespace Updater.Core;
/// <summary> /// <summary>
/// Управляет потоком обновлений: извлечение, установка, перезапуск. /// Управляет потоком обновлений: извлечение, установка, перезапуск.

View File

@@ -1,4 +1,6 @@
using Updater.Core; using ReleaseUpdater.Common;
using System.Diagnostics;
using Updater.Core;
namespace Updater; namespace Updater;
@@ -7,26 +9,43 @@ internal sealed class Program
static int Main(string[] args) static int Main(string[] args)
{ {
var logger = new ConsoleLogger(); var logger = new ConsoleLogger();
Options? options;
try var parseResult = ArgumentsToolkit.ArgumentsParser.Parse<Options>(args);
if (!parseResult.Success)
{ {
options = Options.Parse(args); logger.Error($"Arguments error:");
} parseResult.Errors.ForEach(t => logger.Error(t.Message));
catch (Exception ex)
{
logger.Error($"Arguments error: {ex.Message}");
Console.WriteLine(Options.Usage);
return ExitCodes.InvalidArgs; return ExitCodes.InvalidArgs;
} }
var options = parseResult.Value!;
Thread.Sleep(options.UpdateDelayMs); Thread.Sleep(options.UpdateDelayMs);
if (options.WaitProcess != null)
try
{
using (var proc = Process.GetProcessById(options.WaitProcess.Value))
{
logger.Info($"Waiting for the process to complete {options.WaitProcess}...");
proc.WaitForExit(); // блокирует выполнение до завершения процесса
logger.Info("Process is completed.");
}
}
catch (ArgumentException)
{
logger.Info($"Process with PID {options.WaitProcess} not found.");
}
var extractor = new ZipExtractor(logger); var extractor = new ZipExtractor(logger);
var installer = new SafeFileInstaller(logger); var installer = new SafeFileInstaller(logger);
var procMgr = new ProcessManager(logger); var procMgr = new ProcessManager(logger);
var app = new UpdaterApp(logger, extractor, installer, procMgr); var app = new UpdaterApp(logger, extractor, installer, procMgr);
return app.Run(options);
var exitCode = app.Run(options);
return exitCode;
} }
} }

View File

@@ -1,10 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<Version>1.0.0</Version>
<Authors>FrigaT</Authors>
<Company>FrigaT</Company>
<Product>ReleaseUpdater</Product>
<Description>Запускатор обновления</Description>
<Copyright>Copyright © 2025 FrigaT</Copyright>
<RepositoryUrl>https://git.frigat.duckdns.org/FrigaT/ReleaseUpdater</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://git.frigat.duckdns.org/FrigaT/ReleaseUpdater</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ArgumentsToolkit" Version="0.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ReleaseUpdater.Common\ReleaseUpdater.Common.csproj" />
</ItemGroup>
</Project> </Project>

12
nuget.config Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="Gitea_FrigaT" value="https://git.frigat.duckdns.org/api/packages/FrigaT/nuget/index.json" />
</packageSources>
<packageSourceCredentials>
<GiteaFeed>
<add key="Username" value="FrigaT" />
<add key="ClearTextPassword" value="e5a0d9cc9684bbb4bf8d3f12fd2d0b43f9b0f336" />
</GiteaFeed>
</packageSourceCredentials>
</configuration>