From dc7c03661d157e7cbdbe9dd86aa8031d4b50960d Mon Sep 17 00:00:00 2001 From: Mbucari <37587114+Mbucari@users.noreply.github.com> Date: Mon, 13 Feb 2023 22:25:44 -0700 Subject: [PATCH] Add auto update to linux and macos --- .github/workflows/build-linux.yml | 2 +- .releaseindex.json | 4 +- Documentation/InstallOnMac.md | 18 +- Scripts/targz2linuxbundle.sh | 5 +- Scripts/targz2macosbundle.sh | 4 +- .../LibationAvalonia/LibationAvalonia.csproj | 20 -- Source/LibationAvalonia/Program.cs | 9 +- .../Views/MainWindow.Settings.cs | 2 - .../Views/MainWindow.Update.cs | 54 +---- .../LibationFileManager/IInteropFunctions.cs | 5 +- .../NullInteropFunctions.cs | 10 +- .../LibationWinForms/GridView/ProductsGrid.cs | 2 +- .../LinuxConfigApp}/Libation.desktop | 0 .../LinuxConfigApp/LinuxConfigApp.csproj | 24 +++ .../LoadByOS/LinuxConfigApp/LinuxInterop.cs | 65 +++++- .../Properties/Resources.Designer.cs | 73 +++++++ .../LinuxConfigApp/Properties/Resources.resx | 124 ++++++++++++ .../LinuxConfigApp/Resources/runasroot.sh | 188 ++++++++++++++++++ .../LinuxConfigApp}/glass-with-glow_256.svg | 0 .../MacOSConfigApp}/Info.plist | 4 + .../MacOSConfigApp/MacOSConfigApp.csproj | 9 + .../LoadByOS/MacOSConfigApp/MacOSInterop.cs | 74 ++++++- .../MacOSConfigApp}/libation.icns | Bin .../LoadByOS/WindowsConfigApp/WinInterop.cs | 28 ++- .../WindowsConfigApp/WindowsConfigApp.csproj | 6 + .../WindowsConfigApp}/ZipExtractor.exe | Bin 26 files changed, 631 insertions(+), 99 deletions(-) rename Source/{LibationAvalonia => LoadByOS/LinuxConfigApp}/Libation.desktop (100%) create mode 100644 Source/LoadByOS/LinuxConfigApp/Properties/Resources.Designer.cs create mode 100644 Source/LoadByOS/LinuxConfigApp/Properties/Resources.resx create mode 100644 Source/LoadByOS/LinuxConfigApp/Resources/runasroot.sh rename Source/{LibationAvalonia => LoadByOS/LinuxConfigApp}/glass-with-glow_256.svg (100%) rename Source/{LibationAvalonia => LoadByOS/MacOSConfigApp}/Info.plist (87%) rename Source/{LibationAvalonia => LoadByOS/MacOSConfigApp}/libation.icns (100%) rename Source/{LibationAvalonia => LoadByOS/WindowsConfigApp}/ZipExtractor.exe (100%) diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index 93f235f8..ec8b315d 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -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 }}" diff --git a/.releaseindex.json b/.releaseindex.json index 6acac9e8..65af875b 100644 --- a/.releaseindex.json +++ b/.releaseindex.json @@ -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" } diff --git a/Documentation/InstallOnMac.md b/Documentation/InstallOnMac.md index 7c40b106..f565999e 100644 --- a/Documentation/InstallOnMac.md +++ b/Documentation/InstallOnMac.md @@ -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) diff --git a/Scripts/targz2linuxbundle.sh b/Scripts/targz2linuxbundle.sh index 145e2a3a..b4806ad8 100644 --- a/Scripts/targz2linuxbundle.sh +++ b/Scripts/targz2linuxbundle.sh @@ -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 diff --git a/Scripts/targz2macosbundle.sh b/Scripts/targz2macosbundle.sh index 1defb754..dc121216 100644 --- a/Scripts/targz2macosbundle.sh +++ b/Scripts/targz2macosbundle.sh @@ -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" diff --git a/Source/LibationAvalonia/LibationAvalonia.csproj b/Source/LibationAvalonia/LibationAvalonia.csproj index 4ac37e7a..40399412 100644 --- a/Source/LibationAvalonia/LibationAvalonia.csproj +++ b/Source/LibationAvalonia/LibationAvalonia.csproj @@ -116,26 +116,6 @@ - - - - Always - - - Always - - - Always - - - Always - - - Always - - - - diff --git a/Source/LibationAvalonia/Program.cs b/Source/LibationAvalonia/Program.cs index 79efb369..4c5f861b 100644 --- a/Source/LibationAvalonia/Program.cs +++ b/Source/LibationAvalonia/Program.cs @@ -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(); diff --git a/Source/LibationAvalonia/Views/MainWindow.Settings.cs b/Source/LibationAvalonia/Views/MainWindow.Settings.cs index add4ecfa..9cb02447 100644 --- a/Source/LibationAvalonia/Views/MainWindow.Settings.cs +++ b/Source/LibationAvalonia/Views/MainWindow.Settings.cs @@ -1,7 +1,5 @@ using LibationFileManager; using System; -using System.Linq; -using System.Reflection; namespace LibationAvalonia.Views { diff --git a/Source/LibationAvalonia/Views/MainWindow.Update.cs b/Source/LibationAvalonia/Views/MainWindow.Update.cs index 99d2bfa8..a6858d94 100644 --- a/Source/LibationAvalonia/Views/MainWindow.Update.cs +++ b/Source/LibationAvalonia/Views/MainWindow.Update.cs @@ -14,7 +14,7 @@ namespace LibationAvalonia.Views Opened += async (_, _) => await checkForUpdates(); } - private async Task checkForUpdates() + private async Task checkForUpdates() { async Task downloadUpdate(UpgradeProperties upgradeProperties) { @@ -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(this); + var interop = InteropFactory.Create(); + + var notificationResult = await new UpgradeNotificationDialog(upgradeProperties, interop.CanUpdate).ShowDialog(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) { diff --git a/Source/LibationFileManager/IInteropFunctions.cs b/Source/LibationFileManager/IInteropFunctions.cs index 423d438d..fd1cca37 100644 --- a/Source/LibationFileManager/IInteropFunctions.cs +++ b/Source/LibationFileManager/IInteropFunctions.cs @@ -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; } } } diff --git a/Source/LibationFileManager/NullInteropFunctions.cs b/Source/LibationFileManager/NullInteropFunctions.cs index 7f0e6908..bf995d11 100644 --- a/Source/LibationFileManager/NullInteropFunctions.cs +++ b/Source/LibationFileManager/NullInteropFunctions.cs @@ -1,14 +1,18 @@ using System; +using System.Diagnostics; namespace LibationFileManager { public class NullInteropFunctions : IInteropFunctions { - public NullInteropFunctions() { } + + 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(); + } } diff --git a/Source/LibationWinForms/GridView/ProductsGrid.cs b/Source/LibationWinForms/GridView/ProductsGrid.cs index e19fb4bc..7766a73c 100644 --- a/Source/LibationWinForms/GridView/ProductsGrid.cs +++ b/Source/LibationWinForms/GridView/ProductsGrid.cs @@ -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 { } }); diff --git a/Source/LibationAvalonia/Libation.desktop b/Source/LoadByOS/LinuxConfigApp/Libation.desktop similarity index 100% rename from Source/LibationAvalonia/Libation.desktop rename to Source/LoadByOS/LinuxConfigApp/Libation.desktop diff --git a/Source/LoadByOS/LinuxConfigApp/LinuxConfigApp.csproj b/Source/LoadByOS/LinuxConfigApp/LinuxConfigApp.csproj index 9a7e5ba2..9d9a1b3e 100644 --- a/Source/LoadByOS/LinuxConfigApp/LinuxConfigApp.csproj +++ b/Source/LoadByOS/LinuxConfigApp/LinuxConfigApp.csproj @@ -34,4 +34,28 @@ + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + Always + + + Always + + + \ No newline at end of file diff --git a/Source/LoadByOS/LinuxConfigApp/LinuxInterop.cs b/Source/LoadByOS/LinuxConfigApp/LinuxInterop.cs index 08324141..bf488806 100644 --- a/Source/LoadByOS/LinuxConfigApp/LinuxInterop.cs +++ b/Source/LoadByOS/LinuxConfigApp/LinuxInterop.cs @@ -1,14 +1,73 @@ using LibationFileManager; +using System.Diagnostics; namespace LinuxConfigApp { internal class LinuxInterop : IInteropFunctions { - public LinuxInterop() { } + //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; + } + } } diff --git a/Source/LoadByOS/LinuxConfigApp/Properties/Resources.Designer.cs b/Source/LoadByOS/LinuxConfigApp/Properties/Resources.Designer.cs new file mode 100644 index 00000000..b48060ab --- /dev/null +++ b/Source/LoadByOS/LinuxConfigApp/Properties/Resources.Designer.cs @@ -0,0 +1,73 @@ +//------------------------------------------------------------------------------ +// +// 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. +// +//------------------------------------------------------------------------------ + +namespace LinuxConfigApp.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // 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() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [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; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] runasroot { + get { + object obj = ResourceManager.GetObject("runasroot", resourceCulture); + return ((byte[])(obj)); + } + } + } +} diff --git a/Source/LoadByOS/LinuxConfigApp/Properties/Resources.resx b/Source/LoadByOS/LinuxConfigApp/Properties/Resources.resx new file mode 100644 index 00000000..ef269a0f --- /dev/null +++ b/Source/LoadByOS/LinuxConfigApp/Properties/Resources.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\runasroot.sh;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Source/LoadByOS/LinuxConfigApp/Resources/runasroot.sh b/Source/LoadByOS/LinuxConfigApp/Resources/runasroot.sh new file mode 100644 index 00000000..5c64d517 --- /dev/null +++ b/Source/LoadByOS/LinuxConfigApp/Resources/runasroot.sh @@ -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 . +# +# 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 diff --git a/Source/LibationAvalonia/glass-with-glow_256.svg b/Source/LoadByOS/LinuxConfigApp/glass-with-glow_256.svg similarity index 100% rename from Source/LibationAvalonia/glass-with-glow_256.svg rename to Source/LoadByOS/LinuxConfigApp/glass-with-glow_256.svg diff --git a/Source/LibationAvalonia/Info.plist b/Source/LoadByOS/MacOSConfigApp/Info.plist similarity index 87% rename from Source/LibationAvalonia/Info.plist rename to Source/LoadByOS/MacOSConfigApp/Info.plist index c88f1c80..90cbcaf1 100644 --- a/Source/LibationAvalonia/Info.plist +++ b/Source/LoadByOS/MacOSConfigApp/Info.plist @@ -15,6 +15,8 @@ libation.icns CFBundleVersion VERSION_STRING + com.apple.security.app-sandbox + com.apple.security.cs.allow-jit com.apple.security.cs.allow-unsigned-executable-memory @@ -23,5 +25,7 @@ com.apple.security.cs.disable-executable-page-protection + com.apple.security.automation.apple-events + \ No newline at end of file diff --git a/Source/LoadByOS/MacOSConfigApp/MacOSConfigApp.csproj b/Source/LoadByOS/MacOSConfigApp/MacOSConfigApp.csproj index 55bb69bf..865067f1 100644 --- a/Source/LoadByOS/MacOSConfigApp/MacOSConfigApp.csproj +++ b/Source/LoadByOS/MacOSConfigApp/MacOSConfigApp.csproj @@ -34,4 +34,13 @@ + + + Always + + + Always + + + \ No newline at end of file diff --git a/Source/LoadByOS/MacOSConfigApp/MacOSInterop.cs b/Source/LoadByOS/MacOSConfigApp/MacOSInterop.cs index 378e7323..8bf6f9a3 100644 --- a/Source/LoadByOS/MacOSConfigApp/MacOSInterop.cs +++ b/Source/LoadByOS/MacOSConfigApp/MacOSInterop.cs @@ -1,14 +1,78 @@ using LibationFileManager; +using System.Diagnostics; namespace MacOSConfigApp { internal class MacOSInterop : IInteropFunctions - { - public MacOSInterop() { } + { + 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 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); + } + } } diff --git a/Source/LibationAvalonia/libation.icns b/Source/LoadByOS/MacOSConfigApp/libation.icns similarity index 100% rename from Source/LibationAvalonia/libation.icns rename to Source/LoadByOS/MacOSConfigApp/libation.icns diff --git a/Source/LoadByOS/WindowsConfigApp/WinInterop.cs b/Source/LoadByOS/WindowsConfigApp/WinInterop.cs index ff4ef910..42cded34 100644 --- a/Source/LoadByOS/WindowsConfigApp/WinInterop.cs +++ b/Source/LoadByOS/WindowsConfigApp/WinInterop.cs @@ -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); + } } } diff --git a/Source/LoadByOS/WindowsConfigApp/WindowsConfigApp.csproj b/Source/LoadByOS/WindowsConfigApp/WindowsConfigApp.csproj index 5e37bedb..0dba0cbf 100644 --- a/Source/LoadByOS/WindowsConfigApp/WindowsConfigApp.csproj +++ b/Source/LoadByOS/WindowsConfigApp/WindowsConfigApp.csproj @@ -40,4 +40,10 @@ + + + Always + + + \ No newline at end of file diff --git a/Source/LibationAvalonia/ZipExtractor.exe b/Source/LoadByOS/WindowsConfigApp/ZipExtractor.exe similarity index 100% rename from Source/LibationAvalonia/ZipExtractor.exe rename to Source/LoadByOS/WindowsConfigApp/ZipExtractor.exe