Add auto update to linux and macos
This commit is contained in:
parent
952eee6d32
commit
dc7c03661d
2
.github/workflows/build-linux.yml
vendored
2
.github/workflows/build-linux.yml
vendored
@ -66,7 +66,7 @@ jobs:
|
||||
id: zip
|
||||
working-directory: ./Source/bin/Publish/${{ matrix.os }}-${{ matrix.release_name }}
|
||||
run: |
|
||||
delfiles=("libmp3lame.x86.dll" "libmp3lame.x64.dll" "ffmpegaac.x86.dll" "ffmpegaac.x64.dll" "ZipExtractor.exe")
|
||||
delfiles=("libmp3lame.x86.dll" "libmp3lame.x64.dll" "ffmpegaac.x86.dll" "ffmpegaac.x64.dll")
|
||||
for n in "${delfiles[@]}"; do rm "$n"; done
|
||||
osbuild="$(echo '${{ matrix.os }}' | tr '[:upper:]' '[:lower:]')"
|
||||
artifact="Libation.${{ steps.get_version.outputs.version }}-${osbuild}-${{ matrix.release_name }}"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"WindowsClassic": "Libation\\.\\d+\\.\\d+\\.\\d+-win(dows)?-classic\\.zip",
|
||||
"WindowsAvalonia": "Libation\\.\\d+\\.\\d+\\.\\d+-win(dows)?-chardonnay\\.zip",
|
||||
"LinuxAvalonia": "Libation\\.\\d+\\.\\d+\\.\\d+-linux-chardonnay",
|
||||
"MacOSAvalonia": "Libation\\.\\d+\\.\\d+\\.\\d+-macos-chardonnay"
|
||||
"LinuxAvalonia": "Libation\\.\\d+\\.\\d+\\.\\d+-linux-chardonnay\\.deb",
|
||||
"MacOSAvalonia": "Libation\\.app-x64-\\d+\\.\\d+\\.\\d+\\.tgz"
|
||||
}
|
||||
|
||||
@ -9,18 +9,14 @@ This walkthrough should get you up and running with Libation on your Mac.
|
||||
|
||||
## Install Libation
|
||||
|
||||
- Download the `Libation.app.x.x.x.tar.gz` file from the latest release and extract it.
|
||||
- Download the `Libation.app-x64-x.x.x.tgz` file from the latest release and extract it.
|
||||
- Move the extracted Libation app bundle to your applications folder.
|
||||
- Open a terminal (Go > Utilities > Terminal)
|
||||
- In the terminal type the following commands
|
||||
- `sudo spctl --add --label "Libation" /Applications/Libation.app` (you'll be prompted to enter your password.)
|
||||
- `sudo spctl --master-disable`
|
||||
- Keep the terminal open and run the Libation app
|
||||
- Go back to terminal and type the following command
|
||||
- `sudo spctl --master-enable`
|
||||
- Close the terminal
|
||||
|
||||
Libation is now registered with gatekeeper and will run even when gatekeeper is turned back on.
|
||||
- Copy/paste/run the following command (you'll be prompted to enter your password)
|
||||
```Console
|
||||
sudo spctl --master-disable && sudo spctl --add --label "Libation" /Applications/Libation.app && open /Applications/Libation.app && sudo spctl --master-enable
|
||||
```
|
||||
- Close the terminal and use Libation!
|
||||
|
||||
## Running Hangover
|
||||
|
||||
@ -37,6 +33,8 @@ open /Applications/Libation.app --args cli
|
||||
```
|
||||
To use LibationCli from an unsandboxed terminal, you must disable gatekeeper again and run the program directly at `/Applications/Libation.app/Contents/MacOS/LibationCli`
|
||||
|
||||
Then use `./LibationCli` to execute a command.
|
||||
|
||||
## Get Libation running on Mac
|
||||
|
||||
[Run Libation on MacOS](https://user-images.githubusercontent.com/37587114/213933357-983d8ede-2738-4b32-9c6e-40de21ff09c2.mp4)
|
||||
|
||||
@ -106,7 +106,10 @@ ln -s /usr/lib/libation/Hangover /usr/bin/hangover
|
||||
ln -s /usr/lib/libation/LibationCli /usr/bin/libationcli
|
||||
|
||||
# Increase the maximum number of inotify instances
|
||||
echo fs.inotify.max_user_instances=524288 | tee -a /etc/sysctl.conf && sysctl -p
|
||||
|
||||
if ! grep -q 'fs.inotify.max_user_instances=524288' /etc/sysctl.conf; then
|
||||
echo fs.inotify.max_user_instances=524288 | tee -a /etc/sysctl.conf && sysctl -p
|
||||
fi
|
||||
|
||||
# workaround until this file is moved to the user's home directory
|
||||
touch /usr/lib/libation/appsettings.json
|
||||
|
||||
@ -69,7 +69,7 @@ echo "Set Libation version number..."
|
||||
sed -i -e "s/VERSION_STRING/$VERSION/" "$BUNDLE_CONTENTS/Info.plist"
|
||||
|
||||
echo "deleting unneeded files.."
|
||||
delfiles=("libmp3lame.x64.so" "ffmpegaac.x64.so" "Libation.desktop" "libation.icns" "Info.plist" "glass-with-glow_256.svg")
|
||||
delfiles=("libmp3lame.x64.so" "ffmpegaac.x64.so" "libation.icns" "Info.plist")
|
||||
for n in "${delfiles[@]}"; do rm "$BUNDLE_MACOS/$n"; done
|
||||
|
||||
echo "Creating app bundle: $BUNDLE-$VERSION.tar.gz"
|
||||
@ -77,7 +77,7 @@ tar -czvf "$BUNDLE-$VERSION.tar.gz" "$BUNDLE"
|
||||
|
||||
mkdir bundle
|
||||
echo "moving to ./bundle/$BUNDLE-$VERSION.tar.gz"
|
||||
mv "$BUNDLE-$VERSION.tar.gz" "./bundle/$BUNDLE-$VERSION.tar.gz"
|
||||
mv "$BUNDLE-$VERSION.tar.gz" "./bundle/$BUNDLE-x64-$VERSION.tgz"
|
||||
|
||||
rm -r "$BUNDLE"
|
||||
|
||||
|
||||
@ -116,26 +116,6 @@
|
||||
<ProjectReference Include="..\LibationUiBase\LibationUiBase.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="glass-with-glow_256.svg">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Info.plist">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Libation.desktop">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="libation.icns">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="ZipExtractor.exe">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<Target Name="SpicNSpan" AfterTargets="Clean">
|
||||
<!-- Remove obj folder -->
|
||||
<RemoveDir Directories="$(BaseIntermediateOutputPath)" />
|
||||
|
||||
@ -16,15 +16,20 @@ namespace LibationAvalonia
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
if (Configuration.IsMacOs && args != null && args.Length != 0 && args[0] == "hangover")
|
||||
|
||||
if (Configuration.IsMacOs && args?.Length > 0 && args[0] == "hangover")
|
||||
{
|
||||
//Launch the Hangover app within the sandbox
|
||||
//We can do this because we're already executing inside the sandbox.
|
||||
//Any process created in the sandbox executes in the same sandbox.
|
||||
//Unfortunately, all sandbox files are read/execute, so no writing!
|
||||
|
||||
Assembly asm = Assembly.GetExecutingAssembly();
|
||||
string path = Path.GetDirectoryName(asm.Location);
|
||||
Process.Start("Hangover" + (Configuration.IsWindows ? ".exe" : ""));
|
||||
return;
|
||||
}
|
||||
if (Configuration.IsMacOs && args != null && args.Length != 0 && args[0] == "cli")
|
||||
if (Configuration.IsMacOs && args?.Length > 0 && args[0] == "cli")
|
||||
{
|
||||
//Open a new Terminal in the sandbox
|
||||
Assembly asm2 = Assembly.GetExecutingAssembly();
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
using LibationFileManager;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
{
|
||||
|
||||
@ -26,7 +26,7 @@ namespace LibationAvalonia.Views
|
||||
|
||||
//Silently download the update in the background, save it to a temp file.
|
||||
|
||||
var zipFile = Path.GetTempFileName();
|
||||
var zipFile = Path.Combine(Path.GetTempPath(), Path.GetFileName(upgradeProperties.ZipUrl));
|
||||
try
|
||||
{
|
||||
System.Net.Http.HttpClient cli = new();
|
||||
@ -42,36 +42,6 @@ namespace LibationAvalonia.Views
|
||||
return zipFile;
|
||||
}
|
||||
|
||||
void runWindowsUpgrader(string zipFile)
|
||||
{
|
||||
var thisExe = Environment.ProcessPath;
|
||||
var thisDir = Path.GetDirectoryName(thisExe);
|
||||
|
||||
var zipExtractor = Path.Combine(Path.GetTempPath(), "ZipExtractor.exe");
|
||||
|
||||
File.Copy("ZipExtractor.exe", zipExtractor, overwrite: true);
|
||||
|
||||
var psi = new System.Diagnostics.ProcessStartInfo()
|
||||
{
|
||||
FileName = zipExtractor,
|
||||
UseShellExecute = true,
|
||||
Verb = "runas",
|
||||
WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal,
|
||||
CreateNoWindow = true,
|
||||
ArgumentList =
|
||||
{
|
||||
"--input",
|
||||
zipFile,
|
||||
"--output",
|
||||
thisDir,
|
||||
"--executable",
|
||||
thisExe
|
||||
}
|
||||
};
|
||||
|
||||
System.Diagnostics.Process.Start(psi);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var upgradeProperties = await Task.Run(LibationScaffolding.GetLatestRelease);
|
||||
@ -83,26 +53,22 @@ namespace LibationAvalonia.Views
|
||||
if (config.GetString(propertyName: ignoreUpdate) == upgradeProperties.LatestRelease.ToString())
|
||||
return;
|
||||
|
||||
var notificationResult = await new UpgradeNotificationDialog(upgradeProperties, Configuration.IsWindows).ShowDialog<DialogResult>(this);
|
||||
var interop = InteropFactory.Create();
|
||||
|
||||
var notificationResult = await new UpgradeNotificationDialog(upgradeProperties, interop.CanUpdate).ShowDialog<DialogResult>(this);
|
||||
|
||||
if (notificationResult == DialogResult.Ignore)
|
||||
config.SetString(upgradeProperties.LatestRelease.ToString(), ignoreUpdate);
|
||||
|
||||
if (notificationResult != DialogResult.OK || !Configuration.IsWindows) return;
|
||||
if (notificationResult != DialogResult.OK) return;
|
||||
|
||||
//Download the update file in the background,
|
||||
//then wire up installaion on window close.
|
||||
string updateBundle = await downloadUpdate(upgradeProperties);
|
||||
|
||||
string zipFile = await downloadUpdate(upgradeProperties);
|
||||
if (string.IsNullOrEmpty(updateBundle) || !File.Exists(updateBundle)) return;
|
||||
|
||||
if (string.IsNullOrEmpty(zipFile) || !File.Exists(zipFile))
|
||||
return;
|
||||
|
||||
Closed += (_, _) =>
|
||||
{
|
||||
if (File.Exists(zipFile))
|
||||
runWindowsUpgrader(zipFile);
|
||||
};
|
||||
//Install the update
|
||||
interop.InstallUpdate(updateBundle);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace LibationFileManager
|
||||
{
|
||||
@ -6,6 +7,8 @@ namespace LibationFileManager
|
||||
{
|
||||
void SetFolderIcon(string image, string directory);
|
||||
void DeleteFolderIcon(string directory);
|
||||
void CopyTextToClipboard(string text);
|
||||
Process RunAsRoot(string exe, string args);
|
||||
void InstallUpdate(string updateBundle);
|
||||
bool CanUpdate { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,14 +1,18 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace LibationFileManager
|
||||
{
|
||||
public class NullInteropFunctions : IInteropFunctions
|
||||
{
|
||||
|
||||
public NullInteropFunctions() { }
|
||||
public NullInteropFunctions(params object[] values) { }
|
||||
|
||||
public void SetFolderIcon(string image, string directory) => throw new PlatformNotSupportedException();
|
||||
public void DeleteFolderIcon(string directory) => throw new PlatformNotSupportedException();
|
||||
public void CopyTextToClipboard(string text) => throw new PlatformNotSupportedException();
|
||||
public bool CanUpdate => throw new PlatformNotSupportedException();
|
||||
public Process RunAsRoot(string exe, string args) => throw new PlatformNotSupportedException();
|
||||
public void InstallUpdate(string updateBundle) => throw new PlatformNotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,7 +123,7 @@ namespace LibationWinForms.GridView
|
||||
{
|
||||
var dgv = (DataGridView)sender;
|
||||
var text = dgv[e.ColumnIndex, e.RowIndex].FormattedValue.ToString();
|
||||
InteropFactory.Create().CopyTextToClipboard(text);
|
||||
Clipboard.SetDataObject(text, false, 5, 150);
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
|
||||
@ -34,4 +34,28 @@
|
||||
<ProjectReference Include="..\..\AppScaffolding\AppScaffolding.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="glass-with-glow_256.svg">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Libation.desktop">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -1,14 +1,73 @@
|
||||
using LibationFileManager;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace LinuxConfigApp
|
||||
{
|
||||
internal class LinuxInterop : IInteropFunctions
|
||||
{
|
||||
//Different terminal apps possibly installed on a linux system
|
||||
// [0] console executable
|
||||
// [1] argument to set the concole's title
|
||||
// [2] argument to pass a command to be executed to the terminal
|
||||
static readonly string[][] consoleCommands =
|
||||
{
|
||||
new[] {"konsole", "--title", "-e"},
|
||||
new[] {"gnome-terminal", "--title", "--"},
|
||||
new[] {"mate-terminal", "--title", "-x"},
|
||||
new[] {"xterm", "-T", "-e"},
|
||||
};
|
||||
|
||||
public LinuxInterop() { }
|
||||
public LinuxInterop(params object[] values) { }
|
||||
|
||||
public void SetFolderIcon(string image, string directory) => throw new PlatformNotSupportedException();
|
||||
public void DeleteFolderIcon(string directory) => throw new PlatformNotSupportedException();
|
||||
public void CopyTextToClipboard(string text) => throw new PlatformNotSupportedException();
|
||||
|
||||
//only run the audo updater is the current app was installed from the
|
||||
//.deb package. Try to detect this by checking if the symlink exists.
|
||||
public bool CanUpdate => Directory.Exists("/usr/bin/libation");
|
||||
public void InstallUpdate(string updateBundle)
|
||||
{
|
||||
RunAsRoot("apt", $"install '{updateBundle}'");
|
||||
}
|
||||
|
||||
public Process RunAsRoot(string exe, string args)
|
||||
{
|
||||
//cribbed this script from VirtualBox's guest additions installer.
|
||||
//It's designed to launch the system's gui superuser password
|
||||
//prompt across multiple distributions and desktop environments.
|
||||
const string runasroot = "/tmp/runasroot.sh";
|
||||
File.WriteAllBytes(runasroot, Properties.Resources.runasroot);
|
||||
|
||||
string command = $"{exe ?? ""} {args ?? ""}".Trim();
|
||||
|
||||
foreach (var console in consoleCommands)
|
||||
{
|
||||
ProcessStartInfo psi = new()
|
||||
{
|
||||
FileName = console[0],
|
||||
UseShellExecute = false,
|
||||
ArgumentList =
|
||||
{
|
||||
console[1],
|
||||
$"Running '{exe}' as root",
|
||||
console[2],
|
||||
"/bin/sh",
|
||||
runasroot,
|
||||
"Installing libation.deb",
|
||||
command,
|
||||
$"Please run '{command}' manually"
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
return Process.Start(psi);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
73
Source/LoadByOS/LinuxConfigApp/Properties/Resources.Designer.cs
generated
Normal file
73
Source/LoadByOS/LinuxConfigApp/Properties/Resources.Designer.cs
generated
Normal file
@ -0,0 +1,73 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace LinuxConfigApp.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("LinuxConfigApp.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Byte[].
|
||||
/// </summary>
|
||||
internal static byte[] runasroot {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("runasroot", resourceCulture);
|
||||
return ((byte[])(obj));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
124
Source/LoadByOS/LinuxConfigApp/Properties/Resources.resx
Normal file
124
Source/LoadByOS/LinuxConfigApp/Properties/Resources.resx
Normal file
@ -0,0 +1,124 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<data name="runasroot" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\runasroot.sh;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
</root>
|
||||
188
Source/LoadByOS/LinuxConfigApp/Resources/runasroot.sh
Normal file
188
Source/LoadByOS/LinuxConfigApp/Resources/runasroot.sh
Normal file
@ -0,0 +1,188 @@
|
||||
#!/bin/sh
|
||||
# $Id: runasroot.sh 153224 2022-08-22 17:43:14Z klaus $
|
||||
## @file
|
||||
# VirtualBox privileged execution helper script for Linux and Solaris
|
||||
#
|
||||
|
||||
#
|
||||
# Copyright (C) 2009-2022 Oracle and/or its affiliates.
|
||||
#
|
||||
# This file is part of VirtualBox base platform packages, as
|
||||
# available from https://www.virtualbox.org.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation, in version 3 of the
|
||||
# License.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <https://www.gnu.org/licenses>.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
#
|
||||
|
||||
# Deal with differing "which" semantics
|
||||
mywhich() {
|
||||
which "$1" 2>/dev/null | grep -v "no $1"
|
||||
}
|
||||
|
||||
# Get the name and execute switch for a useful terminal emulator
|
||||
#
|
||||
# Sets $gxtpath to the emulator path or empty
|
||||
# Sets $gxttitle to the "title" switch for that emulator
|
||||
# Sets $gxtexec to the "execute" switch for that emulator
|
||||
# May clobber $gtx*
|
||||
# Calls mywhich
|
||||
getxterm() {
|
||||
# gnome-terminal uses -e differently to other emulators
|
||||
for gxti in "konsole --title -e" "gnome-terminal --title -x" "xterm -T -e"; do
|
||||
set $gxti
|
||||
gxtpath="`mywhich $1`"
|
||||
case "$gxtpath" in ?*)
|
||||
gxttitle=$2
|
||||
gxtexec=$3
|
||||
return
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# Quotes its argument by inserting '\' in front of every character save
|
||||
# for 'A-Za-z0-9/'. Prints the result to stdout.
|
||||
quotify() {
|
||||
echo "$1" | sed -e 's/\([^a-zA-Z0-9/]\)/\\\1/g'
|
||||
}
|
||||
|
||||
ostype=`uname -s`
|
||||
if test "$ostype" != "Linux" && test "$ostype" != "SunOS" ; then
|
||||
echo "Linux/Solaris not detected."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
HAS_TERMINAL=""
|
||||
case "$1" in "--has-terminal")
|
||||
shift
|
||||
HAS_TERMINAL="yes"
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$#" in "2"|"3")
|
||||
;;
|
||||
*)
|
||||
echo "Usage: `basename $0` DESCRIPTION COMMAND [ADVICE]" >&2
|
||||
echo >&2
|
||||
echo "Attempt to execute COMMAND with root privileges, displaying DESCRIPTION if" >&2
|
||||
echo "possible and displaying ADVICE if possible if no su(1)-like tool is available." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
DESCRIPTION=$1
|
||||
COMMAND=$2
|
||||
ADVICE=$3
|
||||
PATH=$PATH:/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin:/usr/local/sbin:/usr/X11/bin
|
||||
|
||||
case "$ostype" in SunOS)
|
||||
PATH=$PATH:/usr/sfw/bin:/usr/gnu/bin:/usr/xpg4/bin:/usr/xpg6/bin:/usr/openwin/bin:/usr/ucb
|
||||
GKSU_SWITCHES="-au root"
|
||||
;;
|
||||
*)
|
||||
GKSU_SWITCHES=""
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$HAS_TERMINAL" in "")
|
||||
case "$DISPLAY" in ?*)
|
||||
KDESUDO="`mywhich kdesudo`"
|
||||
case "$KDESUDO" in ?*)
|
||||
eval "`quotify "$KDESUDO"` --comment `quotify "$DESCRIPTION"` -- $COMMAND"
|
||||
exit
|
||||
;;
|
||||
esac
|
||||
|
||||
KDESU="`mywhich kdesu`"
|
||||
case "$KDESU" in ?*)
|
||||
"$KDESU" -c "$COMMAND"
|
||||
exit
|
||||
;;
|
||||
esac
|
||||
|
||||
GKSU="`mywhich gksu`"
|
||||
case "$GKSU" in ?*)
|
||||
# Older gksu does not grok --description nor '--' and multiple args.
|
||||
# @todo which versions do?
|
||||
# "$GKSU" --description "$DESCRIPTION" -- "$@"
|
||||
# Note that $GKSU_SWITCHES is NOT quoted in the following
|
||||
"$GKSU" $GKSU_SWITCHES "$COMMAND"
|
||||
exit
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac # $DISPLAY
|
||||
;;
|
||||
esac # ! $HAS_TERMINAL
|
||||
|
||||
# pkexec may work for ssh console sessions as well if the right agents
|
||||
# are installed. However it is very generic and does not allow for any
|
||||
# custom messages. Thus it comes after gksu.
|
||||
## @todo should we insist on either a display or a terminal?
|
||||
# case "$DISPLAY$HAS_TERMINAL" in ?*)
|
||||
PKEXEC="`mywhich pkexec`"
|
||||
case "$PKEXEC" in ?*)
|
||||
eval "\"$PKEXEC\" $COMMAND"
|
||||
exit
|
||||
;;
|
||||
esac
|
||||
# ;;S
|
||||
#esac
|
||||
|
||||
case "$HAS_TERMINAL" in ?*)
|
||||
USE_SUDO=
|
||||
grep -q Ubuntu /etc/lsb-release 2>/dev/null && USE_SUDO=true
|
||||
# On Ubuntu we need sudo instead of su. Assume this works, and is only
|
||||
# needed for Ubuntu until proven wrong.
|
||||
case $USE_SUDO in true)
|
||||
SUDO_COMMAND="`quotify "$SUDO"` -- $COMMAND"
|
||||
eval "$SUDO_COMMAND"
|
||||
exit
|
||||
;;
|
||||
esac
|
||||
|
||||
SU="`mywhich su`"
|
||||
case "$SU" in ?*)
|
||||
"$SU" - root -c "$COMMAND"
|
||||
exit
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
# The ultimate fallback is running 'su -' within an xterm. We use the
|
||||
# title of the xterm to tell what is going on.
|
||||
case "$DISPLAY" in ?*)
|
||||
SU="`mywhich su`"
|
||||
case "$SU" in ?*)
|
||||
getxterm
|
||||
case "$gxtpath" in ?*)
|
||||
"$gxtpath" "$gxttitle" "$DESCRIPTION - su" "$gxtexec" su - root -c "$COMMAND"
|
||||
exit
|
||||
;;
|
||||
esac
|
||||
esac
|
||||
esac # $DISPLAY
|
||||
|
||||
# Failure...
|
||||
case "$DISPLAY" in ?*)
|
||||
echo "Unable to locate 'pkexec', 'gksu' or 'su+xterm'. $ADVICE" >&2
|
||||
;;
|
||||
*)
|
||||
echo "Unable to locate 'pkexec'. $ADVICE" >&2
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 1
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@ -15,6 +15,8 @@
|
||||
<string>libation.icns</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>VERSION_STRING</string>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<false/>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
@ -23,5 +25,7 @@
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-executable-page-protection</key>
|
||||
<true/>
|
||||
<key>com.apple.security.automation.apple-events</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -34,4 +34,13 @@
|
||||
<ProjectReference Include="..\..\AppScaffolding\AppScaffolding.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Info.plist">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="libation.icns">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -1,14 +1,78 @@
|
||||
using LibationFileManager;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace MacOSConfigApp
|
||||
{
|
||||
internal class MacOSInterop : IInteropFunctions
|
||||
{
|
||||
private const string AppPath = "/Applications/Libation.app";
|
||||
public MacOSInterop() { }
|
||||
public MacOSInterop(params object[] values) { }
|
||||
|
||||
public void SetFolderIcon(string image, string directory) => throw new PlatformNotSupportedException();
|
||||
public void DeleteFolderIcon(string directory) => throw new PlatformNotSupportedException();
|
||||
public void CopyTextToClipboard(string text) => throw new PlatformNotSupportedException();
|
||||
|
||||
//I haven't figured out how to find the app bundle's directory from within
|
||||
//the running process, so don't update unless it's "installed" in /Applications
|
||||
public bool CanUpdate => Directory.Exists(AppPath);
|
||||
|
||||
public void InstallUpdate(string updateBundle)
|
||||
{
|
||||
Serilog.Log.Information($"Extracting update bundle to {AppPath}");
|
||||
|
||||
//tar wil overwrite existing without elevated privileges
|
||||
Process.Start("tar", $"-xzf \"{updateBundle}\" -C \"/Applications\"").WaitForExit();
|
||||
|
||||
//For now, it seems like this step is unnecessary. We can overwrite and
|
||||
//run Libation without needing to re-add the exception. This is insurance.
|
||||
RunAsRoot(null, $"""
|
||||
sudo spctl --master-disable
|
||||
sudo spctl --add --label 'Libation' {AppPath}
|
||||
open {AppPath}
|
||||
sudo spctl --master-enable
|
||||
""");
|
||||
}
|
||||
|
||||
//Using osascript -e '[script]' works from the terminal, but I haven't figured
|
||||
//out the syntax for it to work from create_process, so write to stdin instead.
|
||||
public Process RunAsRoot(string _, string command)
|
||||
{
|
||||
const string osascript = "osascript";
|
||||
var fullCommand = $"do shell script \"{command}\" with administrator privileges";
|
||||
|
||||
var psi = new ProcessStartInfo()
|
||||
{
|
||||
FileName = osascript,
|
||||
UseShellExecute = false,
|
||||
Arguments = "-",
|
||||
RedirectStandardError= true,
|
||||
RedirectStandardOutput= true,
|
||||
RedirectStandardInput= true,
|
||||
};
|
||||
|
||||
Serilog.Log.Logger.Information($"running {osascript} as root: {{script}}", fullCommand);
|
||||
|
||||
var proc = Process.Start(psi);
|
||||
proc.ErrorDataReceived += Proc_ErrorDataReceived;
|
||||
proc.OutputDataReceived += Proc_OutputDataReceived;
|
||||
proc.BeginErrorReadLine();
|
||||
proc.BeginOutputReadLine();
|
||||
proc.StandardInput.WriteLine(fullCommand);
|
||||
proc.StandardInput.Close();
|
||||
|
||||
return proc;
|
||||
}
|
||||
|
||||
private void Proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
if (e.Data != null)
|
||||
Serilog.Log.Logger.Information("stderr: {data}", e.Data);
|
||||
}
|
||||
|
||||
private void Proc_ErrorDataReceived(object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
if (e.Data!= null)
|
||||
Serilog.Log.Logger.Information("stderr: {data}", e.Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@ -35,8 +36,31 @@ namespace WindowsConfigApp
|
||||
|
||||
public void DeleteFolderIcon(string directory)
|
||||
=> new DirectoryInfo(directory)?.DeleteIcon();
|
||||
public bool CanUpdate => true;
|
||||
public void InstallUpdate(string updateBundle)
|
||||
{
|
||||
var thisExe = Environment.ProcessPath;
|
||||
var thisDir = Path.GetDirectoryName(thisExe);
|
||||
var zipExtractor = Path.Combine(Path.GetTempPath(), "ZipExtractor.exe");
|
||||
|
||||
public void CopyTextToClipboard(string text)
|
||||
=> Clipboard.SetText(text);
|
||||
File.Copy("ZipExtractor.exe", zipExtractor, overwrite: true);
|
||||
|
||||
RunAsRoot(zipExtractor, $"--input \"{updateBundle}\" --output \"{thisDir}\" --executable \"{thisExe}\"");
|
||||
}
|
||||
|
||||
public Process RunAsRoot(string exe, string args)
|
||||
{
|
||||
var psi = new ProcessStartInfo()
|
||||
{
|
||||
FileName = exe,
|
||||
UseShellExecute = true,
|
||||
Verb = "runas",
|
||||
WindowStyle = ProcessWindowStyle.Normal,
|
||||
CreateNoWindow = true,
|
||||
Arguments = args
|
||||
};
|
||||
|
||||
return Process.Start(psi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,4 +40,10 @@
|
||||
<ProjectReference Include="..\..\AppScaffolding\AppScaffolding.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="ZipExtractor.exe">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Loading…
x
Reference in New Issue
Block a user