- add: packager

This commit is contained in:
2025-05-01 19:01:59 +03:00
parent 0ef326a970
commit 91c41302bb
18 changed files with 394 additions and 2 deletions

32
.github/workflows/publish.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: Publish
on:
workflow_dispatch:
# schedule:
# - cron: '0 10 * * *'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Install dependencies
run: sudo apt-get install -y python3-paramiko python3-lxml
- uses: actions/checkout@v3.6.0
with:
submodules: 'recursive'
- name: Setup .NET Core
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 9.0.x
- name: Install dependencies
run: dotnet restore
- name: Package launcher files
run: dotnet run Nebula.Packager
- name: FTP Deploy Release
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
with:
server: ${{ secrets.FTP_SERVER }}
username: ${{ secrets.FTP_USERNAME }}
password: ${{ secrets.FTP_PASSWORD }}
local-dir: ./release/
server-dir: ./release/

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ obj/
/packages/
riderModule.iml
/_ReSharper.Caches/
release/

View File

@@ -33,6 +33,8 @@
<entry key="Nebula.Launcher/Views/ServerList.axaml" value="Nebula.Launcher/Nebula.Launcher.csproj" />
<entry key="Nebula.Launcher/Views/Tabs/AccountInfoTab.axaml" value="Nebula.Launcher/Nebula.Launcher.csproj" />
<entry key="Nebula.Launcher/Views/Tabs/ServerListTab.axaml" value="Nebula.Launcher/Nebula.Launcher.csproj" />
<entry key="Nebula.UpdateResolver/App.axaml" value="Nebula.UpdateResolver/Nebula.UpdateResolver.csproj" />
<entry key="Nebula.UpdateResolver/MainWindow.axaml" value="Nebula.UpdateResolver/Nebula.UpdateResolver.csproj" />
</map>
</option>
</component>

View File

@@ -4,6 +4,7 @@
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,74 @@
using System.Diagnostics;
using System.Security.Cryptography;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Nebula.Packager;
public static class Program
{
public static void Main(string[] args)
{
Pack("","Release");
}
private static void Pack(string rootPath,string configuration)
{
var processInfo = new ProcessStartInfo
{
FileName = "dotnet",
ArgumentList =
{
"publish",
Path.Combine(rootPath,"Nebula.Launcher", "Nebula.Launcher.csproj"),
"-c", configuration,
}
};
var process = Process.Start(processInfo)!;
process.WaitForExit();
if(process.ExitCode != 0)
throw new Exception($"Packager has exited with code {process.ExitCode}");
var destinationDirectory = Path.Combine(rootPath,"release");
var sourceDirectory = Path.Combine("Nebula.Launcher", "bin", configuration,"publish");
if (Directory.Exists(destinationDirectory))
{
Directory.Delete(destinationDirectory, true);
}
Directory.CreateDirectory(destinationDirectory);
HashSet<LauncherManifestEntry> entries = new HashSet<LauncherManifestEntry>();
foreach (var fileName in Directory.EnumerateFiles(sourceDirectory, "*.*", SearchOption.AllDirectories))
{
using var md5 = MD5.Create();
using var stream = File.OpenRead(fileName);
var hash = md5.ComputeHash(stream);
var hashStr = BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
if(!File.Exists(Path.Combine(destinationDirectory, hashStr)))
File.Copy(fileName, Path.Combine(destinationDirectory, hashStr));
var fileNameCut = fileName.Remove(0, sourceDirectory.Length + 1);
entries.Add(new LauncherManifestEntry(hashStr, fileNameCut));
Console.WriteLine($"Added {hashStr} file name {fileNameCut}");
}
using var manifest = File.CreateText(Path.Combine(destinationDirectory, "manifest.json"));
manifest.AutoFlush = true;
manifest.Write(JsonSerializer.Serialize(new LauncherManifest(entries)));
}
}
public record struct LauncherManifest(
[property: JsonPropertyName("entries")] HashSet<LauncherManifestEntry> Entries
);
public record struct LauncherManifestEntry(
[property: JsonPropertyName("hash")] string Hash,
[property: JsonPropertyName("path")] string Path
);

View File

@@ -0,0 +1,10 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Nebula.UpdateResolver.App"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>

View File

@@ -0,0 +1,31 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Microsoft.Extensions.DependencyInjection;
using Nebula.Shared;
namespace Nebula.UpdateResolver;
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
var services = new ServiceCollection();
services.AddServices();
services.AddTransient<MainWindow>();
var serviceProvider = services.BuildServiceProvider();
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = serviceProvider.GetService<MainWindow>();
}
base.OnFrameworkInitializationCompleted();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

View File

@@ -0,0 +1,13 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Nebula.UpdateResolver;
public record struct LauncherManifest(
[property: JsonPropertyName("entries")] HashSet<LauncherManifestEntry> Entries
);
public record struct LauncherManifestEntry(
[property: JsonPropertyName("hash")] string Hash,
[property: JsonPropertyName("path")] string Path
);

View File

@@ -0,0 +1,40 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
ExtendClientAreaChromeHints="NoChrome"
ExtendClientAreaTitleBarHeightHint="-1"
ExtendClientAreaToDecorationsHint="True"
Height="225"
Width="400"
CanResize="False"
WindowStartupLocation="CenterScreen"
BorderThickness="0"
ShowInTaskbar="False"
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="225"
x:Class="Nebula.UpdateResolver.MainWindow"
Title="Nebula.UpdateResolver">
<Grid ColumnDefinitions="*" RowDefinitions="*,40">
<Image Grid.Row="0" Source="Assets/back.gif" Grid.RowSpan="2"/>
<Border
BorderThickness="0,3,0,0"
CornerRadius="0"
Grid.Column="0"
Grid.Row="1">
<Border.Background>
<LinearGradientBrush EndPoint="50%,100%" StartPoint="50%,0%">
<GradientStop Color="#222222" Offset="0.0" />
<GradientStop Color="#222222" Offset="1.0" />
</LinearGradientBrush>
</Border.Background>
<Border.BorderBrush>
<LinearGradientBrush EndPoint="100%,50%" StartPoint="0%,50%">
<GradientStop Color="#222222" Offset="0.0" />
<GradientStop Color="#442222" Offset="1.0" />
</LinearGradientBrush>
</Border.BorderBrush>
<Label HorizontalAlignment="Center" VerticalAlignment="Center">Hello</Label>
</Border>
</Grid>
</Window>

View File

@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Controls;
using Nebula.Shared.FileApis.Interfaces;
using Nebula.Shared.Models;
using Nebula.Shared.Services;
namespace Nebula.UpdateResolver;
public partial class MainWindow : Window
{
private readonly ConfigurationService _configurationService;
private readonly RestService _restService;
private readonly HttpClient _httpClient = new HttpClient();
public IReadWriteFileApi FileApi { get; set; }
public MainWindow(FileService fileService, ConfigurationService configurationService, RestService restService)
{
_configurationService = configurationService;
_restService = restService;
InitializeComponent();
FileApi = fileService.CreateFileApi("app");
}
private async Task DownloadFiles()
{
var info = await EnsureFiles();
foreach (var file in info.ToDelete)
{
FileApi.Remove(file.Path);
}
foreach (var file in info.ToDownload)
{
using var response = _httpClient.GetAsync(
_configurationService.GetConfigValue(UpdateConVars.UpdateCacheUrl)
+ "/" + file.Hash
);
response.Result.EnsureSuccessStatusCode();
await using var stream = await response.Result.Content.ReadAsStreamAsync();
FileApi.Save(file.Path, stream);
}
}
private async Task<ManifestEnsureInfo> EnsureFiles()
{
var manifest = await _restService.GetAsync<LauncherManifest>(
_configurationService.GetConfigValue(UpdateConVars.UpdateCacheUrl)!, CancellationToken.None);
var toDownload = new HashSet<LauncherManifestEntry>();
var toDelete = new HashSet<LauncherManifestEntry>();
if (_configurationService.TryGetConfigValue(UpdateConVars.CurrentLauncherManifest, out var currentManifest))
{
foreach (var file in currentManifest.Entries)
{
if(!manifest.Entries.Contains(file))
toDelete.Add(file);
}
foreach (var file in manifest.Entries)
{
if(!currentManifest.Entries.Contains(file))
toDownload.Add(file);
}
}
else
{
_configurationService.SetConfigValue(UpdateConVars.CurrentLauncherManifest, manifest);
toDownload = manifest.Entries;
}
return new ManifestEnsureInfo(toDownload, toDelete);
}
}
public record struct ManifestEnsureInfo(HashSet<LauncherManifestEntry> ToDownload, HashSet<LauncherManifestEntry> ToDelete);

View File

@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.2.1"/>
<PackageReference Include="Avalonia.Desktop" Version="11.2.1"/>
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.1"/>
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.1"/>
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Include="Avalonia.Diagnostics" Version="11.2.1">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Nebula.Shared\Nebula.Shared.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,21 @@
using Avalonia;
using System;
namespace Nebula.UpdateResolver;
class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
}

View File

@@ -0,0 +1,13 @@
using System;
using Nebula.Shared.Models;
using Nebula.Shared.Services;
namespace Nebula.UpdateResolver;
public static class UpdateConVars
{
public static readonly ConVar<Uri> UpdateCacheUrl =
ConVarBuilder.Build<Uri>("update.url",new Uri("https://cinka.ru/nebula-launcher/files/publish/release"));
public static readonly ConVar<LauncherManifest> CurrentLauncherManifest =
ConVarBuilder.Build<LauncherManifest>("update.manifest");
}

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embedded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="Nebula.UpdateResolver.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>

View File

@@ -10,6 +10,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nebula.Runner", "Nebula.Run
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nebula.SourceGenerators", "Nebula.SourceGenerators\Nebula.SourceGenerators.csproj", "{985A8F36-AFEB-4E85-8EDB-7C9DDEC698DC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nebula.UpdateResolver", "Nebula.UpdateResolver\Nebula.UpdateResolver.csproj", "{90CB754D-00A1-493D-A630-02BDA0AFF31A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nebula.Packager", "Nebula.Packager\Nebula.Packager.csproj", "{D444A5F9-4549-467F-9398-97DD6DB1E263}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -36,5 +40,13 @@ Global
{985A8F36-AFEB-4E85-8EDB-7C9DDEC698DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{985A8F36-AFEB-4E85-8EDB-7C9DDEC698DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{985A8F36-AFEB-4E85-8EDB-7C9DDEC698DC}.Release|Any CPU.Build.0 = Release|Any CPU
{90CB754D-00A1-493D-A630-02BDA0AFF31A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{90CB754D-00A1-493D-A630-02BDA0AFF31A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{90CB754D-00A1-493D-A630-02BDA0AFF31A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{90CB754D-00A1-493D-A630-02BDA0AFF31A}.Release|Any CPU.Build.0 = Release|Any CPU
{D444A5F9-4549-467F-9398-97DD6DB1E263}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D444A5F9-4549-467F-9398-97DD6DB1E263}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D444A5F9-4549-467F-9398-97DD6DB1E263}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D444A5F9-4549-467F-9398-97DD6DB1E263}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@@ -1,2 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFrozenDictionary_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F89dff9063ddb01ff8125b579122b88bf4de94526490d77bcbbef7d0ee662a_003FFrozenDictionary_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFrozenDictionary_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F89dff9063ddb01ff8125b579122b88bf4de94526490d77bcbbef7d0ee662a_003FFrozenDictionary_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceCollectionContainerBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa8ceca48b7b645dd875a40ee6d28725416d08_003F1b_003F6cd78dc8_003FServiceCollectionContainerBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>